feat: integrate pixel teacher presence and resilient classroom preview
This commit is contained in:
@@ -33,6 +33,17 @@ function baseSnapshot(): ConversationSnapshot {
|
||||
const zeroCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
|
||||
|
||||
describe('Pi session projector', () => {
|
||||
it('restores the observed expired Works Square session as a safe login recovery notice', async () => {
|
||||
const snapshot = await projectPiSessionSnapshot({
|
||||
snapshot: baseSnapshot(), workerGeneration: 1,
|
||||
state: { sessionId: 's1', isStreaming: false, isCompacting: false },
|
||||
entries: { leafId: 'failed', entries: [{ type: 'message', id: 'failed', parentId: null, message: {
|
||||
role: 'assistant', stopReason: 'error', content: [], errorMessage: '401 "Works Square login session is missing or expired"',
|
||||
} }] },
|
||||
});
|
||||
expect(snapshot.nodes).toContainEqual(expect.objectContaining({ kind: 'notice', code: 'CODING_PROVIDER_AUTH_REQUIRED', message: '登录已失效,请重新登录后继续。' }));
|
||||
expect(JSON.stringify(snapshot)).not.toContain('Works Square login');
|
||||
});
|
||||
it('hydrates stable subagent nodes and bounds unknown detail versions', async () => {
|
||||
const snapshot = await projectPiSessionSnapshot({
|
||||
snapshot: baseSnapshot(),
|
||||
@@ -242,6 +253,63 @@ describe('Pi session projector', () => {
|
||||
expect(snapshot.cursor).toEqual({ workerGeneration: 1, seq: 7 });
|
||||
});
|
||||
|
||||
it.each(['completed', 'aborted', 'failed'] as const)('preserves a confirmed %s run during same-generation hydration', async (terminalReason) => {
|
||||
const live = baseSnapshot();
|
||||
live.run = { status: terminalReason === 'failed' ? 'error' : 'idle', runId: 'run-a', terminalReason, startedAt: 1, settledAt: 2 };
|
||||
const hydrated = await projectPiSessionSnapshot({
|
||||
snapshot: live, workerGeneration: 1,
|
||||
state: { isStreaming: false, isCompacting: false }, entries: { entries: [], leafId: null },
|
||||
});
|
||||
expect(hydrated.run).toEqual(live.run);
|
||||
const nextGeneration = await projectPiSessionSnapshot({
|
||||
snapshot: live, workerGeneration: 2,
|
||||
state: { isStreaming: false, isCompacting: false }, entries: { entries: [], leafId: null },
|
||||
});
|
||||
expect(nextGeneration.run).toEqual({ status: 'idle' });
|
||||
});
|
||||
|
||||
it('does not infer a completed run from a preparing run during hydration', async () => {
|
||||
const live = baseSnapshot();
|
||||
live.run = { status: 'preparing', runId: 'run-a' };
|
||||
const hydrated = await projectPiSessionSnapshot({
|
||||
snapshot: live, workerGeneration: 1,
|
||||
state: { isStreaming: false, isCompacting: false }, entries: { entries: [], leafId: null },
|
||||
});
|
||||
expect(hydrated.run).toEqual({ status: 'idle' });
|
||||
});
|
||||
|
||||
it('matches repeated text by exact durable identity before claiming an unbound live message', async () => {
|
||||
const live = baseSnapshot();
|
||||
live.nodes = [
|
||||
{ kind: 'message', id: 'new-live', clientRequestId: 'new-request', role: 'user', status: 'complete', blocks: [{ id: 'new-text', kind: 'text', status: 'complete', text: 'hello' }] },
|
||||
{ kind: 'message', id: 'old-live', sourceEntryId: 'old-entry', clientRequestId: 'old-request', role: 'user', status: 'complete', blocks: [{ id: 'old-text', kind: 'text', status: 'complete', text: 'hello' }] },
|
||||
];
|
||||
const hydrated = await projectPiSessionSnapshot({
|
||||
snapshot: live, workerGeneration: 1, state: { isStreaming: false },
|
||||
entries: { leafId: 'new-entry', entries: [
|
||||
{ type: 'message', id: 'old-entry', parentId: null, message: { role: 'user', content: 'hello' } },
|
||||
{ type: 'message', id: 'new-entry', parentId: 'old-entry', message: { role: 'user', content: 'hello' } },
|
||||
] },
|
||||
});
|
||||
expect(hydrated.nodes).toEqual([
|
||||
expect.objectContaining({ sourceEntryId: 'old-entry', id: 'old-live', clientRequestId: 'old-request' }),
|
||||
expect.objectContaining({ sourceEntryId: 'new-entry', id: 'new-live', clientRequestId: 'new-request' }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not reuse a different durable message just because its text matches', async () => {
|
||||
const live = baseSnapshot();
|
||||
live.nodes = [{ kind: 'message', id: 'old-live', sourceEntryId: 'old-entry', clientRequestId: 'old-request', role: 'user', status: 'complete', blocks: [{ id: 'old-text', kind: 'text', status: 'complete', text: 'hello' }] }];
|
||||
const hydrated = await projectPiSessionSnapshot({
|
||||
snapshot: live, workerGeneration: 1, state: { isStreaming: false },
|
||||
entries: { leafId: 'new-entry', entries: [
|
||||
{ type: 'message', id: 'new-entry', parentId: null, message: { role: 'user', content: 'hello' } },
|
||||
] },
|
||||
});
|
||||
expect(hydrated.nodes[0]).toMatchObject({ id: 'entry:new-entry', sourceEntryId: 'new-entry' });
|
||||
expect(hydrated.nodes[0]).not.toHaveProperty('clientRequestId');
|
||||
});
|
||||
|
||||
it('projects the full active history around compaction and reconciles durable entries without replacing live IDs', async () => {
|
||||
const live: ConversationSnapshot = {
|
||||
...baseSnapshot(),
|
||||
|
||||
Reference in New Issue
Block a user