test(session): cover legacy migration paths

This commit is contained in:
_Kerman
2026-08-04 12:05:53 +08:00
parent 2a9ebfcac2
commit 384c87899c
3 changed files with 199 additions and 32 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +1,33 @@
{"type":"session","version":0,"id":"4428b809-66d5-4ea2-9a03-89de742fcda1","createdAt":1785591986068,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":1785591986072,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1785591986073,"data":{"content":[{"type":"text","text":"Call glob exactly once with pattern * and path tree. Then reply with exactly GLOB_SAMPLED and nothing else."}],"source":{"kind":"user"},"role":"user","id":"3d05fb76-4185-460b-9c6a-8c1b2495bc9f"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1785591986074,"data":{"title":"Call glob exactly once with","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"agent/inbox/spliced","seq":0,"time":1785815911816,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Call glob exactly once with pattern * and path tree. Then reply with exactly GLOB_SAMPLED and nothing else."}],"source":{"kind":"user"},"role":"user","id":"b7f14c10-5c33-42b5-994e-60eed15a990e"}]}}
{"type":"agent/inbox/spliced","seq":1,"time":1785815911817,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"turn/start","seq":2,"time":1785815911838,"data":{"turn":1}}
{"type":"step/start","seq":3,"time":1785591986092,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1785591986093,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-pro"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":5,"time":1785591986094,"data":{"provider":"deepseek-official","model":"deepseek-v4-pro"}}
{"type":"assistant/chunk","seq":6,"time":1785591987500,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":7,"time0":1785591987500,"data":{"turn":1,"step":1,"index":0,"dt":[29,58,1,0,0,0,51,0,0,46,0,191,1,0,0,0,0,0,0,0,1,0,0,0,0,0,99],"texts":["The"," user"," wants"," me"," to"," call"," glob"," exactly"," once"," with"," pattern"," *"," and"," path"," tree",","," then"," reply"," with"," exactly"," \"","G","LOB","_S","AM","PL","ED","\"."]}}
{"type":"assistant/chunk","seq":35,"time":1785591988034,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":36,"time0":1785591988035,"data":{"turn":1,"step":1,"index":1,"dt":[55,0,0,1,45,0,0,57,14,0,0,0,0,77,0,0,54],"id":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","args":["","{","\"","pattern","\"",": ","\"","*","\"",", ","\"","path","\"",": ","\"","tree","\"","}"]}}
{"type":"assistant/chunk","seq":54,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to call glob exactly once with pattern * and path tree, then reply with exactly \"GLOB_SAMPLED\"."}}}}
{"type":"assistant/chunk","seq":55,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","arguments":"{\"pattern\": \"*\", \"path\": \"tree\"}"}}}}
{"type":"assistant/chunk","seq":56,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":1286,"outputTokens":87,"cacheReadTokens":0,"reasoningTokens":28}}}}
{"type":"assistant/chunk","seq":57,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":58,"time":1785591988430,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to call glob exactly once with pattern * and path tree, then reply with exactly \"GLOB_SAMPLED\"."},{"type":"tool-call","id":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","arguments":"{\"pattern\": \"*\", \"path\": \"tree\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"b74cbab2-c017-4e44-8c09-a7745d8b274a"},"usage":{"inputTokens":1286,"outputTokens":87,"cacheReadTokens":0,"reasoningTokens":28}},"sourceEventSeqs":[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57],"surfaceOp":"append"}
{"type":"tool/call","seq":59,"time":1785591988431,"data":{"turn":1,"step":1,"callId":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","arguments":"{\"pattern\": \"*\", \"path\": \"tree\"}"}}
{"type":"tool/result","seq":60,"time":1785591988476,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_1cLZjkCW0vxVw0e3xVfh3430"},"content":[{"type":"tool-result","toolCallId":"call_00_1cLZjkCW0vxVw0e3xVfh3430","content":[{"type":"text","text":"tree/archive/a.ts\ntree/docs/guide.md\ntree/src/index.ts\ntree/test/spec.ts\n\n(Showing 4 of 8 paths, sampled across 4 of the 6 top-level entries this pattern matched instead of taken in modification-time order. Narrow path to inspect a specific subtree. The complete result could not be saved; narrow pattern or path to see more.)"}],"isError":false}],"role":"user","id":"10284f88-4890-49ed-9a17-56edbd6bfaa7"},"meta":{"shape":"paths","paths":["tree/archive/a.ts","tree/docs/guide.md","tree/src/index.ts","tree/test/spec.ts"],"truncated":true,"total":8}},"sourceEventSeqs":[59],"surfaceOp":"append"}
{"type":"step/end","seq":61,"time":1785591988476,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":62,"time":1785591988482,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":63,"time":1785591989939,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":64,"time0":1785591989939,"data":{"turn":1,"step":2,"index":0,"dt":[0,0,0,49,36,103,1,0,0,326,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,14],"texts":["The"," glob"," result"," shows"," it"," was"," sampled"," -"," ","4"," of"," ","8"," paths"," across"," ","4"," of"," ","6"," top","-level"," entries","."," I"," need"," to"," reply"," with"," exactly"," \"","G","LOB","_S","AM","PL","ED","\""," as"," instructed","."]}}
{"type":"assistant/chunk","seq":105,"time":1785591990470,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"text-chunks","seq0":106,"time0":1785591990470,"data":{"turn":1,"step":2,"index":1,"dt":[0,0,0,48,0],"texts":["G","LOB","_S","AM","PL","ED"]}}
{"type":"assistant/chunk","seq":112,"time":1785591990526,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The glob result shows it was sampled - 4 of 8 paths across 4 of 6 top-level entries. I need to reply with exactly \"GLOB_SAMPLED\" as instructed."}}}}
{"type":"assistant/chunk","seq":113,"time":1785591990527,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"GLOB_SAMPLED"}}}}
{"type":"assistant/chunk","seq":114,"time":1785591990527,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":188,"outputTokens":48,"cacheReadTokens":1280,"reasoningTokens":41}}}}
{"type":"assistant/chunk","seq":115,"time":1785591990527,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":116,"time":1785591990527,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The glob result shows it was sampled - 4 of 8 paths across 4 of 6 top-level entries. I need to reply with exactly \"GLOB_SAMPLED\" as instructed."},{"type":"text","text":"GLOB_SAMPLED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"dd3a9c28-43b2-4fdc-8089-1547309a71c0"},"usage":{"inputTokens":188,"outputTokens":48,"cacheReadTokens":1280,"reasoningTokens":41}},"sourceEventSeqs":[63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115],"surfaceOp":"append"}
{"type":"step/end","seq":117,"time":1785591990527,"data":{"turn":1,"step":2}}
{"type":"turn/end","seq":118,"time":1785591990528,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"user/message","seq":4,"time":1785815911838,"data":{"content":[{"type":"text","text":"Call glob exactly once with pattern * and path tree. Then reply with exactly GLOB_SAMPLED and nothing else."}],"source":{"kind":"user"},"role":"user","id":"b7f14c10-5c33-42b5-994e-60eed15a990e"},"surfaceOp":"append"}
{"type":"session/title","seq":5,"time":1785815911838,"data":{"title":"Call glob exactly once with","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":6,"time":1785815911840,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-pro"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":7,"time":1785815911840,"data":{"provider":"deepseek-official","model":"deepseek-v4-pro"}}
{"type":"assistant/chunk","seq":8,"time":1785591987500,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":9,"time0":1785591987529,"data":{"turn":1,"step":1,"index":0,"dt":[58,1,0,0,0,51,0,0,46,0,191,1,0,0,0,0,0,0,0,1,0,0,0,0,0,99,57],"texts":["The"," user"," wants"," me"," to"," call"," glob"," exactly"," once"," with"," pattern"," *"," and"," path"," tree",","," then"," reply"," with"," exactly"," \"","G","LOB","_S","AM","PL","ED","\"."]}}
{"type":"assistant/chunk","seq":37,"time":1785591988035,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":38,"time0":1785591988090,"data":{"turn":1,"step":1,"index":1,"dt":[0,0,1,45,0,0,57,14,0,0,0,0,77,0,0,54,89],"id":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","args":["","{","\"","pattern","\"",": ","\"","*","\"",", ","\"","path","\"",": ","\"","tree","\"","}"]}}
{"type":"assistant/chunk","seq":56,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to call glob exactly once with pattern * and path tree, then reply with exactly \"GLOB_SAMPLED\"."}}}}
{"type":"assistant/chunk","seq":57,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","arguments":"{\"pattern\": \"*\", \"path\": \"tree\"}"}}}}
{"type":"assistant/chunk","seq":58,"time":1785591988427,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":1286,"outputTokens":87,"cacheReadTokens":0,"reasoningTokens":28}}}}
{"type":"assistant/chunk","seq":59,"time":1785815911849,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":60,"time":1785815911849,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to call glob exactly once with pattern * and path tree, then reply with exactly \"GLOB_SAMPLED\"."},{"type":"tool-call","id":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","arguments":"{\"pattern\": \"*\", \"path\": \"tree\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"6aa63a52-8daf-41e1-84ff-6a991c1c65c2"},"usage":{"inputTokens":1286,"outputTokens":87,"cacheReadTokens":0,"reasoningTokens":28}},"sourceEventSeqs":[8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59],"surfaceOp":"append"}
{"type":"tool/call","seq":61,"time":1785815911849,"data":{"turn":1,"step":1,"callId":"call_00_1cLZjkCW0vxVw0e3xVfh3430","name":"glob","arguments":"{\"pattern\": \"*\", \"path\": \"tree\"}"}}
{"type":"tool/result","seq":62,"time":1785815911873,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_1cLZjkCW0vxVw0e3xVfh3430"},"content":[{"type":"tool-result","toolCallId":"call_00_1cLZjkCW0vxVw0e3xVfh3430","content":[{"type":"text","text":"tree/archive/a.ts\ntree/docs/guide.md\ntree/src/index.ts\ntree/test/spec.ts\n\n(Showing 4 of 8 paths, sampled across 4 of the 6 top-level entries this pattern matched instead of taken in modification-time order. Narrow path to inspect a specific subtree. The complete result could not be saved; narrow pattern or path to see more.)"}],"isError":false}],"role":"user","id":"1aa9a147-cf62-448c-a3a3-607afc166c8b"},"meta":{"shape":"paths","paths":["tree/archive/a.ts","tree/docs/guide.md","tree/src/index.ts","tree/test/spec.ts"],"truncated":true,"total":8}},"sourceEventSeqs":[61],"surfaceOp":"append"}
{"type":"step/end","seq":63,"time":1785815911874,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":64,"time":1785815911886,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":65,"time":1785591989939,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":66,"time0":1785591989939,"data":{"turn":1,"step":2,"index":0,"dt":[0,0,49,36,103,1,0,0,326,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,14,0],"texts":["The"," glob"," result"," shows"," it"," was"," sampled"," -"," ","4"," of"," ","8"," paths"," across"," ","4"," of"," ","6"," top","-level"," entries","."," I"," need"," to"," reply"," with"," exactly"," \"","G","LOB","_S","AM","PL","ED","\""," as"," instructed","."]}}
{"type":"assistant/chunk","seq":107,"time":1785591990470,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"text-chunks","seq0":108,"time0":1785591990470,"data":{"turn":1,"step":2,"index":1,"dt":[0,0,48,0,8],"texts":["G","LOB","_S","AM","PL","ED"]}}
{"type":"assistant/chunk","seq":114,"time":1785591990527,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The glob result shows it was sampled - 4 of 8 paths across 4 of 6 top-level entries. I need to reply with exactly \"GLOB_SAMPLED\" as instructed."}}}}
{"type":"assistant/chunk","seq":115,"time":1785591990527,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"GLOB_SAMPLED"}}}}
{"type":"assistant/chunk","seq":116,"time":1785591990527,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":188,"outputTokens":48,"cacheReadTokens":1280,"reasoningTokens":41}}}}
{"type":"assistant/chunk","seq":117,"time":1785815911893,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":118,"time":1785815911894,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The glob result shows it was sampled - 4 of 8 paths across 4 of 6 top-level entries. I need to reply with exactly \"GLOB_SAMPLED\" as instructed."},{"type":"text","text":"GLOB_SAMPLED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"2ceaed84-ac69-436e-82a2-f1bdacb8ca04"},"usage":{"inputTokens":188,"outputTokens":48,"cacheReadTokens":1280,"reasoningTokens":41}},"sourceEventSeqs":[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117],"surfaceOp":"append"}
{"type":"step/end","seq":119,"time":1785815911894,"data":{"turn":1,"step":2}}
{"type":"turn/end","seq":120,"time":1785815911894,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}}

View File

@@ -166,6 +166,35 @@ function preReactLoopLog(): SessionEvent[] {
type: 'turn/end', seq: 17, time: 18,
data: { turn: 5, reason: { kind: 'error', step: 1, message: 'old thrown value' } },
},
{
type: 'turn/start', seq: 18, time: 19,
data: { turn: 6, trigger: { kind: 'message', source: { kind: 'user' } } },
},
{
type: 'turn/end', seq: 19, time: 20,
data: {
turn: 6,
reason: {
kind: 'error',
step: 0,
failure: {
message: 'old detailed provider failure',
code: 'RATE_LIMIT',
status: 429,
providerRetryAfterMs: 1000,
requestId: 'request-1',
},
},
},
},
{
type: 'turn/start', seq: 20, time: 21,
data: { turn: 7, trigger: { kind: 'message', source: { kind: 'user' } } },
},
{
type: 'turn/end', seq: 21, time: 22,
data: { turn: 7, reason: { kind: 'error', step: 0, message: 'old coded error', code: 'CODED' } },
},
] as unknown as SessionEvent[]
}
@@ -459,7 +488,9 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
for (const snapshot of snapshots) {
expect(snapshot.events.some(event => (event.type as string) === 'steering/message')).toBe(false)
expect(snapshot.events.filter(event => event.type === 'turn/start').map(event => event.data))
.toEqual([{ turn: 1 }, { turn: 2 }, { turn: 3 }, { turn: 4 }, { turn: 5 }])
.toEqual([
{ turn: 1 }, { turn: 2 }, { turn: 3 }, { turn: 4 }, { turn: 5 }, { turn: 6 }, { turn: 7 },
])
expect(snapshot.events.filter(event => event.type === 'turn/end').map(event => event.data)).toEqual([
{ turn: 1, step: 1, reason: { kind: 'completed' } },
{
@@ -474,6 +505,25 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
step: 1,
reason: { kind: 'error', error: { message: 'old thrown value', code: 'UNKNOWN' } },
},
{
turn: 6,
step: 0,
reason: {
kind: 'error',
error: {
message: 'old detailed provider failure',
code: 'RATE_LIMIT',
status: 429,
providerRetryAfterMs: 1000,
requestId: 'request-1',
},
},
},
{
turn: 7,
step: 0,
reason: { kind: 'error', error: { message: 'old coded error', code: 'CODED' } },
},
])
const resumed = new Session(id, snapshot.events, snapshot.meta)
@@ -490,7 +540,29 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
data: { id: legacySteering.data.message.id },
})
expect(suffix.events.filter(event => event.type === 'turn/end').map(event => event.data.step))
.toEqual([1, 1, 0, 0, 1])
.toEqual([1, 1, 0, 0, 1, 0, 0])
const flatId = SessionId('pre-react-loop-flat-steering')
await ctx.sessionPersistence.create(meta(flatId, WORK))
await ctx.sessionPersistence.append(flatId, [{
type: 'steering/message',
seq: 0,
time: 1,
data: {
turn: 1,
content: [{ type: 'text', text: 'flat steering' }],
source: { kind: 'user' },
},
surfaceOp: 'append',
} as unknown as SessionEvent])
expect((await ctx.sessionPersistence.inspect(flatId)).events[0]).toMatchObject({
type: 'user/message',
data: {
id: `legacy-message:${flatId}:0`,
role: 'user',
content: [{ type: 'text', text: 'flat steering' }],
},
})
} finally {
await fiber.dispose()
await fix.cleanup()
@@ -538,6 +610,13 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
} as unknown as SessionEvent,
message: 'malformed pre-react-loop steering/message',
},
{
id: 'invalid-old-steering-data',
event: {
type: 'steering/message', seq: 0, time: 1, surfaceOp: 'append', data: null,
} as unknown as SessionEvent,
message: 'malformed pre-react-loop steering/message',
},
{
id: 'invalid-old-turn-end',
event: {
@@ -546,6 +625,54 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
{
id: 'invalid-old-turn-end-reason',
event: {
type: 'turn/end', seq: 0, time: 1,
data: { turn: 1, reason: null },
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
{
id: 'invalid-old-turn-end-aborted',
event: {
type: 'turn/end', seq: 0, time: 1,
data: { turn: 1, reason: { kind: 'aborted', extra: true } },
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
{
id: 'invalid-old-turn-end-disposed',
event: {
type: 'turn/end', seq: 0, time: 1,
data: { turn: 1, reason: { kind: 'disposed', extra: true } },
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
{
id: 'invalid-old-turn-end-error-step',
event: {
type: 'turn/end', seq: 0, time: 1,
data: { turn: 1, reason: { kind: 'error', step: -1, message: 'bad step' } },
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
{
id: 'invalid-old-turn-end-error-code',
event: {
type: 'turn/end', seq: 0, time: 1,
data: { turn: 1, reason: { kind: 'error', step: 0, message: 'bad code', code: 1 } },
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
{
id: 'invalid-old-turn-end-kind',
event: {
type: 'turn/end', seq: 0, time: 1,
data: { turn: 1, reason: { kind: 'unknown' } },
} as unknown as SessionEvent,
message: 'malformed pre-react-loop turn/end',
},
]
for (const malformed of malformedLegacy) {
const malformedId = SessionId(malformed.id)
@@ -579,6 +706,22 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
} as unknown as SessionEvent])
await expect(ctx.sessionPersistence.inspect(pluginId))
.resolves.toMatchObject({ events: [{ type: 'plugin/test', data: null }] })
await expect(ctx.sessionPersistence.readFrom(pluginId, 0))
.resolves.toMatchObject({ events: [{ type: 'plugin/test', data: null }] })
for (const type of ['user/message', 'assistant/message'] as const) {
const missingContentId = SessionId(`invalid-${type}-without-content`)
await ctx.sessionPersistence.create(meta(missingContentId, WORK))
await ctx.sessionPersistence.append(missingContentId, [{
type,
seq: 0,
time: 1,
surfaceOp: 'append',
data: {},
} as unknown as SessionEvent])
await expect(ctx.sessionPersistence.readFrom(missingContentId, 0))
.rejects.toThrow('lacks an identified message')
}
} finally {
await fiber.dispose()
await fix.cleanup()
@@ -975,6 +1118,28 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise<
}
})
it('a live session whose complete seed matches loaded ownerless state claims it without appending', async () => {
const fix = await makeFixture()
const { ctx, fiber } = await freshCtx(fix)
try {
const id = SessionId('claim-exact')
const completeSeed = [
...oneTurnLog(),
{ type: 'session/end-seed', seq: 6, time: 7, data: {} },
] as SessionEvent[]
await ctx.sessionPersistence.create(meta(id, WORK))
await ctx.sessionPersistence.append(id, completeSeed)
const { events } = await ctx.sessionPersistence.load(id)
const live = ctx.sessions.create(id, { seed: events, meta: { cwd: WORK } })
await expect(ctx.sessions.flush(live)).resolves.toBe(true)
expect((await ctx.sessionPersistence.load(id)).events).toEqual(events)
} finally {
await fiber.dispose()
await fix.cleanup()
}
})
it('a live session whose seed matches the loaded prefix claims ownerless state and persists the suffix', async () => {
const fix = await makeFixture()
const { ctx, fiber } = await freshCtx(fix)