diff --git a/packages/client/runtime/README.md b/packages/client/runtime/README.md index 12bfaa459b..af164de824 100644 --- a/packages/client/runtime/README.md +++ b/packages/client/runtime/README.md @@ -14,7 +14,7 @@ SlotsService gives the renderer separate bare observables for `useSessions` and ## Code Mode sub-dispatch index -`ConversationSnapshot.codeDispatches` groups a `run_code` call's sub-dispatches under their parent callId, in start order, using the native call-block shapes: a started-but-unsettled sub-call is a `RunningToolCall` (rows derive the running ring from the shape) and its `tool/code-dispatch` settlement replaces it in place with the `ToolResultNode` form, `callTime` carrying the paired start's time. Live mux frames and history replay build the identical index; sub-calls never join the surface `nodes` flow; per-parent array and map references are memo-stable across unrelated snapshot swaps. +`ConversationSnapshot.codeDispatches` groups a `run_code` call's sub-dispatches under their parent callId, in dispatch order, as settled `ToolResultNode` entries (the native result shape): each `tool/code-dispatch` event appends one. The event carries only the settle timestamp, so `callTime` is `null` (start unknown) — no duration claim is possible from this index yet. Live mux frames and history replay build the identical index; sub-calls never join the surface `nodes` flow; per-parent array and map references are memo-stable across unrelated snapshot swaps. ## Session title projection diff --git a/packages/client/runtime/src/client/sessions/session.ts b/packages/client/runtime/src/client/sessions/session.ts index 322a2049fd..1c2eac7ea3 100644 --- a/packages/client/runtime/src/client/sessions/session.ts +++ b/packages/client/runtime/src/client/sessions/session.ts @@ -638,7 +638,10 @@ export class Session implements ObservableSnapshot { kind: 'tool-result', seq: event.seq, time: event.time, callId: data.subCallId, call: { name: data.name, argsRaw: JSON.stringify(data.arguments) }, - callTime: event.time, + // The settle event is the only timestamp this event carries; the + // start time is unknown (null per the ToolResultNode contract), so + // duration-aware consumers never see a fabricated zero-duration call. + callTime: null, content: data.content, isError: data.isError, callView: null, resultView: null, } diff --git a/packages/client/runtime/tests/session.spec.ts b/packages/client/runtime/tests/session.spec.ts index beceefce30..9cf153f105 100644 --- a/packages/client/runtime/tests/session.spec.ts +++ b/packages/client/runtime/tests/session.spec.ts @@ -659,6 +659,9 @@ describe('run_code sub-dispatch indexing', () => { expect(subs?.[0]).toMatchObject({ kind: 'tool-result', callId: 'p1:code:1', call: { name: 'bash', argsRaw: '{"command":"ls","description":"列目录"}' }, + // The settle event carries no start time: callTime stays null (never a + // fabricated zero-duration). + callTime: null, isError: false, content: [{ type: 'text', text: 'demo.txt' }], }) expect(subs?.[1]).toMatchObject({ callId: 'p1:code:2', isError: true })