fix(agent): align consumers with inbox lifecycle

This commit is contained in:
_Kerman
2026-08-03 13:14:24 +08:00
parent e011d3b238
commit f4a2e0d10a
13 changed files with 167 additions and 85 deletions

View File

@@ -32,6 +32,7 @@ const REMOVE = 'Queue item to remove'
const EDIT = 'Queue item to edit' const EDIT = 'Queue item to edit'
const EDITED = 'Edited queue item' const EDITED = 'Edited queue item'
const TAIL = 'Queue item preserved after stop' const TAIL = 'Queue item preserved after stop'
const WAKE = 'Wake the preserved queue'
/** Durable turn-end classifications observed by the scenario. */ /** Durable turn-end classifications observed by the scenario. */
function turnEndReasons(events: readonly SessionEvent[]): string[] { function turnEndReasons(events: readonly SessionEvent[]): string[] {
@@ -63,13 +64,13 @@ describe('web e2e: queue row actions', () => {
it.skipIf(MODE === 'record')('edits and removes exact occurrences and preserves Queue across stop', async () => { it.skipIf(MODE === 'record')('edits and removes exact occurrences and preserves Queue across stop', async () => {
overrideDir = await mkdtemp(join(tmpdir(), 'dsh-web-queue-actions-')) overrideDir = await mkdtemp(join(tmpdir(), 'dsh-web-queue-actions-'))
const readyFile = join(overrideDir, '.hang-ready') const readyFile = join(overrideDir, '.hang-ready')
const nextReadyFile = join(overrideDir, '.next-hang-ready')
const overridePath = join(overrideDir, 'replay.override.json') const overridePath = join(overrideDir, 'replay.override.json')
const recorded = deriveReplayScript(parseSessionLog(await readFile(FIXTURE, 'utf8'))) const recorded = deriveReplayScript(parseSessionLog(await readFile(FIXTURE, 'utf8')))
expect(recorded).toHaveLength(1) expect(recorded).toHaveLength(1)
const replay: ReplayEntry[] = [ const replay: ReplayEntry[] = [
{ kind: 'hang', readyFile }, { kind: 'hang', readyFile },
{ kind: 'hang', readyFile: nextReadyFile }, recorded[0]!,
recorded[0]!,
recorded[0]!, recorded[0]!,
] ]
await writeFile(overridePath, JSON.stringify(replay)) await writeFile(overridePath, JSON.stringify(replay))
@@ -86,7 +87,7 @@ describe('web e2e: queue row actions', () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-queue-actions')) onTestFailed(() => saveFailureShot(page, 'web-e2e-queue-actions'))
const input = page.locator('textarea').first() const input = page.locator('textarea').first()
const settled = scaffold.whenTurnSettled() const firstSettled = scaffold.whenTurnSettled()
await input.fill(ACTIVE_PROMPT) await input.fill(ACTIVE_PROMPT)
await input.press('Enter') await input.press('Enter')
await expect.poll(() => existsSync(readyFile), { timeout: 15_000 }).toBe(true) await expect.poll(() => existsSync(readyFile), { timeout: 15_000 }).toBe(true)
@@ -157,19 +158,22 @@ describe('web e2e: queue row actions', () => {
).toBe(2) ).toBe(2)
await page.getByRole('button', { name: 'Stop generating' }).click() await page.getByRole('button', { name: 'Stop generating' }).click()
await expect.poll(() => existsSync(nextReadyFile), { timeout: 15_000 }).toBe(true) await firstSettled
await page.getByText(TAIL, { exact: true }).waitFor()
await expect.poll(() => page.getByRole('button', { name: 'Remove queued message' }).count()) await expect.poll(() => page.getByRole('button', { name: 'Remove queued message' }).count())
.toBe(1) .toBe(2)
const preservedSnapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd) const preservedSnapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(PRESERVED_EXPECTED, preservedSnapshot, MODE) await compareOrRefreshGolden(PRESERVED_EXPECTED, preservedSnapshot, MODE)
await page.getByRole('button', { name: 'Stop generating' }).click() const settled = scaffold.whenTurnSettled()
await input.fill(WAKE)
await input.press('Enter')
await settled await settled
expect(turnEndReasons(sessionEvents)).toEqual(['aborted', 'aborted', 'completed']) await expect.poll(() => turnEndReasons(sessionEvents), { timeout: 15_000 })
expect(sessionEvents.filter(event => event.type === 'user/message' && event.data.source.kind === 'user')) .toEqual(['aborted', 'completed', 'completed', 'completed'])
.toHaveLength(3) expect(sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user'
? event.data.content.flatMap(block => block.type === 'text' ? [block.text] : [])
: [])).toEqual([ACTIVE_PROMPT, EDITED, TAIL, WAKE])
await expect.poll(() => page.locator('[data-queue-dock]').count()).toBe(0) await expect.poll(() => page.locator('[data-queue-dock]').count()).toBe(0)
}, 120_000) }, 120_000)

View File

@@ -216,7 +216,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
const agent = scaffold.ctx.agents.get(SessionId(SEED_ID)) const agent = scaffold.ctx.agents.get(SessionId(SEED_ID))
if (agent === undefined) throw new Error('seeded session did not attach an agent') if (agent === undefined) throw new Error('seeded session did not attach an agent')
agent.inject(createUserMessage({ agent.session.append('user/message', createUserMessage({
content: [{ content: [{
type: 'text', type: 'text',
text: '<system-reminder>\n' text: '<system-reminder>\n'
@@ -235,7 +235,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
digest: 'context-injection-browser-snapshot', digest: 'context-injection-browser-snapshot',
}], }],
}, },
})) }), { surfaceOp: 'append' })
await page.getByRole('button', { name: 'Context injection' }).waitFor({ timeout: 10_000 }) await page.getByRole('button', { name: 'Context injection' }).waitFor({ timeout: 10_000 })
}, 60_000) }, 60_000)
@@ -357,13 +357,13 @@ describe('web e2e: seeded history renders through cold resume', () => {
await compareOrRefreshGolden(COMMAND_ROW_EXPECTED, snapshot, MODE) await compareOrRefreshGolden(COMMAND_ROW_EXPECTED, snapshot, MODE)
}, 60_000) }, 60_000)
it.skipIf(MODE === 'record')('fits short injected context without a scrollport', async () => { it.skipIf(MODE === 'record')('fits short logged context without a scrollport', async () => {
const agent = scaffold.ctx.agents.get(SessionId(SEED_ID)) const agent = scaffold.ctx.agents.get(SessionId(SEED_ID))
if (agent === undefined) throw new Error('seeded session did not attach an agent') if (agent === undefined) throw new Error('seeded session did not attach an agent')
agent.inject(createUserMessage({ agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'Short injected context.' }], content: [{ type: 'text', text: 'Short injected context.' }],
source: { kind: 'plugin', plugin: 'fixture' }, source: { kind: 'plugin', plugin: 'fixture' },
})) }), { surfaceOp: 'append' })
const disclosures = page.getByRole('button', { name: 'Context injection' }) const disclosures = page.getByRole('button', { name: 'Context injection' })
await expect.poll(() => disclosures.count(), { timeout: 10_000 }).toBe(2) await expect.poll(() => disclosures.count(), { timeout: 10_000 }).toBe(2)

View File

@@ -196,16 +196,19 @@ describe('dsh web keyless CLI smoke', () => {
messages?: { role?: string; content?: string }[] messages?: { role?: string; content?: string }[]
tools?: { function?: { name?: string } }[] tools?: { function?: { name?: string } }[]
} }
let resolveProviderRequest!: (request: NativeProviderRequest) => void let resolveProviderRequests!: (requests: NativeProviderRequest[]) => void
const providerRequest = new Promise<NativeProviderRequest>((resolve) => { const requests: NativeProviderRequest[] = []
resolveProviderRequest = resolve const providerRequests = new Promise<NativeProviderRequest[]>((resolve) => {
resolveProviderRequests = resolve
}) })
const provider = createServer((request, response) => { const provider = createServer((request, response) => {
let body = '' let body = ''
request.setEncoding('utf8') request.setEncoding('utf8')
request.on('data', (chunk: string) => { body += chunk }) request.on('data', (chunk: string) => { body += chunk })
request.on('end', () => { request.on('end', () => {
resolveProviderRequest(JSON.parse(body) as NativeProviderRequest) const parsed = JSON.parse(body) as NativeProviderRequest
if ((parsed.tools?.length ?? 0) > 0) requests.push(parsed)
if (requests.length === 2) resolveProviderRequests(requests)
response.writeHead(200, { 'content-type': 'text/event-stream' }) response.writeHead(200, { 'content-type': 'text/event-stream' })
response.end([ response.end([
'data: {"choices":[{"delta":{"role":"assistant","content":null,"reasoning_content":""}}]}', 'data: {"choices":[{"delta":{"role":"assistant","content":null,"reasoning_content":""}}]}',
@@ -244,14 +247,21 @@ describe('dsh web keyless CLI smoke', () => {
mode: 'queue', mode: 'queue',
content: [{ type: 'text', text: 'go' }], content: [{ type: 'text', text: 'go' }],
}) })
const captured = await Promise.race([ const capturedRequests = await Promise.race([
providerRequest, providerRequests,
new Promise<never>((_resolve, reject) => { new Promise<never>((_resolve, reject) => {
setTimeout(() => { reject(new Error('provider request not received in 10s')) }, 10_000).unref() setTimeout(() => { reject(new Error('provider request not received in 10s')) }, 10_000).unref()
}), }),
]) ])
expect(captured.messages?.some(message => const initial = capturedRequests[0]
const captured = capturedRequests[1]
if (initial === undefined || captured === undefined) {
throw new Error('provider did not receive both workspace projection requests')
}
expect(initial.messages?.some(message =>
message.role === 'user' && message.content?.includes('<available_skills>'))).toBe(false) message.role === 'user' && message.content?.includes('<available_skills>'))).toBe(false)
expect(initial.messages?.some(message =>
message.role === 'user' && message.content?.includes('web-workspace-context-probe'))).toBe(false)
const workspaceMessage = captured.messages?.find(message => const workspaceMessage = captured.messages?.find(message =>
message.role === 'user' && message.content?.includes('web-workspace-context-probe')) message.role === 'user' && message.content?.includes('web-workspace-context-probe'))
const systemMessage = captured.messages?.find(message => message.role === 'system') const systemMessage = captured.messages?.find(message => message.role === 'system')

View File

@@ -16,10 +16,6 @@
- img - img
- img - img
- text: Context injection - text: Context injection
- button "Context injection":
- img
- img
- text: Context injection
- paragraph: partial - paragraph: partial
- status: Deep diving... - status: Deep diving...
- region "To-dos": - region "To-dos":

View File

@@ -19,21 +19,24 @@
- img - img
- button "Branch into a new conversation": - button "Branch into a new conversation":
- img - img
- text: {{clock}} Edited queue item {{clock}} - text: {{clock}}
- button "Copy": - button "2 queued messages" [expanded]
- img
- button "Branch into a new conversation":
- img
- paragraph: partial
- status: Deep diving...
- list: - list:
- listitem:
- text: Edited queue item
- button "Edit queued message":
- img
- button "Remove queued message":
- img
- button "Steer queued message" [disabled]:
- img
- listitem: - listitem:
- text: Queue item preserved after stop - text: Queue item preserved after stop
- button "Edit queued message": - button "Edit queued message":
- img - img
- button "Remove queued message": - button "Remove queued message":
- img - img
- button "Steer queued message": - button "Steer queued message" [disabled]:
- img - img
- textbox "Message the agent" - textbox "Message the agent"
- button "Commands": - button "Commands":
@@ -42,5 +45,5 @@
- button "Select model, current DeepSeek-V4-Flash": - button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash - text: DeepSeek-V4-Flash
- img - img
- button "Stop generating" - button "Send message" [disabled]
- text: 1 turns · 1 steps Input 0 tok · Output 0 tok - text: 1 turns · 1 steps Input 0 tok · Output 0 tok

View File

@@ -21,7 +21,7 @@ const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
// Two goldens pin the transient Host projection and its durable handoff: the // Two goldens pin the transient Host projection and its durable handoff: the
// mid-turn state renders accepted steering from session/queue while the // mid-turn state renders accepted steering from session/queue while the
// question blocks admission, then the settled state renders the same message // question blocks admission, then the settled state renders the same message
// from steering/message beside the reply that obeys it. // from user/message beside the reply that obeys it.
const MID_EXPECTED = join(SNAPSHOT_DIR, 'mid-steer.expected.md') const MID_EXPECTED = join(SNAPSHOT_DIR, 'mid-steer.expected.md')
const SETTLED_EXPECTED = join(SNAPSHOT_DIR, 'settled.expected.md') const SETTLED_EXPECTED = join(SNAPSHOT_DIR, 'settled.expected.md')
const MODE = webSnapshotMode() const MODE = webSnapshotMode()
@@ -45,6 +45,12 @@ function assistantText(events: SessionEvent[]): string {
.join('') .join('')
} }
/** Claimed user messages whose payload contains the exact scenario text. */
function claimedMessages(events: readonly SessionEvent[], text: string): SessionEvent<'user/message'>[] {
return events.filter((event): event is SessionEvent<'user/message'> =>
event.type === 'user/message' && JSON.stringify(event.data.content).includes(text))
}
describe('web e2e: mid-turn steering lands durably and visibly', () => { describe('web e2e: mid-turn steering lands durably and visibly', () => {
let scaffold: WebScaffold let scaffold: WebScaffold
let browser: Browser let browser: Browser
@@ -74,7 +80,7 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
it('strictly steers one queued row; the interjection is logged, rendered, and obeyed', async () => { it('strictly steers one queued row; the interjection is logged, rendered, and obeyed', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-steering')) onTestFailed(() => saveFailureShot(page, 'web-e2e-steering'))
if (MODE !== 'record') { if (MODE !== 'record') {
// The steer must NOT be a user/message — it lands as steering/message. // The recorded prompt inventory excludes the later same-turn steer.
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT]) expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT])
} }
const input = page.locator('textarea').first() const input = page.locator('textarea').first()
@@ -112,7 +118,7 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
} }
// Answer the composer; the tool result closes the step, the loop drains // Answer the composer; the tool result closes the step, the loop drains
// the steer as steering/message, and the steered continuation runs the // the steer as user/message, and the steered continuation runs the
// final model call. // final model call.
await composer.getByRole('radio', { name: 'Yes' }).click() await composer.getByRole('radio', { name: 'Yes' }).click()
await composer.getByRole('radio', { name: 'Yes' }).press('Enter') await composer.getByRole('radio', { name: 'Yes' }).press('Enter')
@@ -124,15 +130,14 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
// Fixture honesty: a recording where the live model ignored the steer // Fixture honesty: a recording where the live model ignored the steer
// would replay as a vacuous scenario — reject it and re-record instead. // would replay as a vacuous scenario — reject it and re-record instead.
const recorded = parseSessionLog(await readFile(FIXTURE, 'utf8')) const recorded = parseSessionLog(await readFile(FIXTURE, 'utf8'))
expect(recorded.filter(e => e.type === 'steering/message')).toHaveLength(1) expect(claimedMessages(recorded, STEER)).toHaveLength(1)
expect(assistantText(recorded)).toContain('BANANA') expect(assistantText(recorded)).toContain('BANANA')
return return
} }
// Durable: exactly one steering/message, inside turn 1, carrying the text. // Durable: exactly one claimed user/message carrying the steering text.
const steerEvents = sessionEvents.filter(e => e.type === 'steering/message') const steerEvents = claimedMessages(sessionEvents, STEER)
expect(steerEvents).toHaveLength(1) expect(steerEvents).toHaveLength(1)
expect((steerEvents[0] as SessionEvent & { data: { turn: number } }).data.turn).toBe(1)
expect(JSON.stringify(steerEvents[0])).toContain('BANANA') expect(JSON.stringify(steerEvents[0])).toContain('BANANA')
const turnEnds = sessionEvents.filter(e => e.type === 'turn/end') const turnEnds = sessionEvents.filter(e => e.type === 'turn/end')
expect(turnEnds).toHaveLength(1) expect(turnEnds).toHaveLength(1)
@@ -203,9 +208,8 @@ describe('web e2e: composer shortcut steers directly', () => {
await composer.getByRole('radio', { name: 'Yes' }).press('Enter') await composer.getByRole('radio', { name: 'Yes' }).press('Enter')
await settled await settled
const steerEvents = sessionEvents.filter(event => event.type === 'steering/message') const steerEvents = claimedMessages(sessionEvents, STEER)
expect(steerEvents).toHaveLength(1) expect(steerEvents).toHaveLength(1)
expect((steerEvents[0] as SessionEvent & { data: { turn: number } }).data.turn).toBe(1)
await expect.poll(() => page.getByText(STEER, { exact: true }).count(), { timeout: 15_000 }).toBe(1) await expect.poll(() => page.getByText(STEER, { exact: true }).count(), { timeout: 15_000 }).toBe(1)
expect(await pendingSteering.count()).toBe(0) expect(await pendingSteering.count()).toBe(0)
await expect.poll(() => page.getByText('BANANA', { exact: false }).count(), { timeout: 10_000 }) await expect.poll(() => page.getByText('BANANA', { exact: false }).count(), { timeout: 10_000 })
@@ -259,7 +263,7 @@ describe('web e2e: composer shortcut follows the swapped busy behavior', () => {
const queuedRow = page.locator('[data-queue-dock]').getByRole('listitem').filter({ hasText: queuedText }) const queuedRow = page.locator('[data-queue-dock]').getByRole('listitem').filter({ hasText: queuedText })
await queuedRow.getByText(queuedText, { exact: true }).waitFor({ timeout: 10_000 }) await queuedRow.getByText(queuedText, { exact: true }).waitFor({ timeout: 10_000 })
expect(await page.locator('[data-pending-steering]').filter({ hasText: queuedText }).count()).toBe(0) expect(await page.locator('[data-pending-steering]').filter({ hasText: queuedText }).count()).toBe(0)
expect(sessionEvents.filter(event => event.type === 'steering/message')).toHaveLength(0) expect(claimedMessages(sessionEvents, queuedText)).toHaveLength(0)
// Remove the asserted Queue row, then finish the recorded question turn // Remove the asserted Queue row, then finish the recorded question turn
// so replay teardown still proves that every fixture call was consumed. // so replay teardown still proves that every fixture call was consumed.

View File

@@ -320,12 +320,13 @@ const SCENARIOS: Scenario[] = [
// tool/code-dispatch events. Each overlay composes and pins its own header class. // tool/code-dispatch events. Each overlay composes and pins its own header class.
{ name: 'code-mode-turn', hasModelTurn: true, recorded: true, pinsHeader: true, headerClass: 'code', configPath: CODE_MODE_CONFIG }, { name: 'code-mode-turn', hasModelTurn: true, recorded: true, pinsHeader: true, headerClass: 'code', configPath: CODE_MODE_CONFIG },
// A nested fs dispatch inside run_code discovers workspace instructions. The // A nested fs dispatch inside run_code discovers workspace instructions. The
// injected user/message must follow the outer result while retaining workspace // projection enters the inbox after the outer result and becomes model-visible
// provenance, which proves Code Mode carries deferred tool context end to end. // on the following step, retaining workspace provenance end to end.
{ {
name: 'code-mode-workspace-context', name: 'code-mode-workspace-context',
hasModelTurn: true, hasModelTurn: true,
recorded: true, recorded: false,
overridden: true,
pinsHeader: true, pinsHeader: true,
headerClass: 'code-workspace-context', headerClass: 'code-workspace-context',
systemPromptSource: 'code-mode-turn', systemPromptSource: 'code-mode-turn',

View File

@@ -0,0 +1,29 @@
[
{
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
{ "type": "tool-call-delta", "index": 0, "id": "call_workspace_read", "name": "run_code", "argumentsDelta": "{\"code\":\"return await tools.read({ file_path: 'nested/task.txt' })\",\"description\":\"Read nested/task.txt\"}" },
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_workspace_read", "name": "run_code", "arguments": "{\"code\":\"return await tools.read({ file_path: 'nested/task.txt' })\",\"description\":\"Read nested/task.txt\"}" } },
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
{ "type": "finish", "reason": { "kind": "tool-calls" } }
]
},
{
"kind": "chunks",
"chunks": [
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 1 } },
{ "type": "finish", "reason": { "kind": "stop" } }
]
},
{
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "text" },
{ "type": "text-delta", "index": 0, "text": "**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`" },
{ "type": "block-end", "index": 0, "block": { "type": "text", "text": "**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`" } },
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
{ "type": "finish", "reason": { "kind": "stop" } }
]
}
]

View File

@@ -1,39 +1,41 @@
{"type":"session","version":0,"id":"b1e35a14-a592-44e6-bf23-b2496ad2bf7b","createdAt":1785014475001,"cwd":"{{cwd}}","delegationDepth":0} {"type":"session","version":0,"id":"b1e35a14-a592-44e6-bf23-b2496ad2bf7b","createdAt":1785014475001,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"agent/inbox/spliced","seq":0,"time":1785498825884,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Using ONE run_code program, call tools.read on nested/task.txt. After the program finishes, answer the workspace handshake question using the newly discovered instructions: What is the Code Mode workspace handshake?"}],"source":{"kind":"user"},"role":"user","id":"4652dbcb-f681-4ade-9b1f-02bb753dd717"}]}} {"type":"agent/inbox/spliced","seq":0,"time":1785498825884,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Using ONE run_code program, call tools.read on nested/task.txt. After the program finishes, answer the workspace handshake question using the newly discovered instructions: What is the Code Mode workspace handshake?"}],"source":{"kind":"user"},"role":"user","id":"e431aa26-6f5a-48e6-8924-3ca14f69436e"}]}}
{"type":"agent/inbox/spliced","seq":1,"time":1785498825884,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"agent/inbox/spliced","seq":1,"time":1785498825884,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"agent/inbox/spliced","seq":2,"time":1785498825916,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nWorkspace snapshot root instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"}]},"role":"user","id":"d8a936e9-51ad-420d-ae4c-2bd8fe74bed0"}]}} {"type":"agent/inbox/spliced","seq":2,"time":1785498825916,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nWorkspace snapshot root instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"}]},"role":"user","id":"2de6b267-3a7c-4af8-8e34-cd607915c10e"}]}}
{"type":"turn/start","seq":3,"time":1785498825916,"data":{"turn":1}} {"type":"turn/start","seq":3,"time":1785498825916,"data":{"turn":1}}
{"type":"step/start","seq":4,"time":1785122256264,"data":{"turn":1,"step":1}} {"type":"step/start","seq":4,"time":1785122256264,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":5,"time":1785498825917,"data":{"content":[{"type":"text","text":"Using ONE run_code program, call tools.read on nested/task.txt. After the program finishes, answer the workspace handshake question using the newly discovered instructions: What is the Code Mode workspace handshake?"}],"source":{"kind":"user"},"role":"user","id":"4652dbcb-f681-4ade-9b1f-02bb753dd717"},"surfaceOp":"append"} {"type":"user/message","seq":5,"time":1785498825917,"data":{"content":[{"type":"text","text":"Using ONE run_code program, call tools.read on nested/task.txt. After the program finishes, answer the workspace handshake question using the newly discovered instructions: What is the Code Mode workspace handshake?"}],"source":{"kind":"user"},"role":"user","id":"e431aa26-6f5a-48e6-8924-3ca14f69436e"},"surfaceOp":"append"}
{"type":"user/message","seq":6,"time":1785730478198,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"role":"user","id":"348b3f0c-54f3-440e-a3a7-c26ea6f95808"},"surfaceOp":"append"} {"type":"user/message","seq":6,"time":1785730478198,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"role":"user","id":"d43577de-e7c8-4d8b-9b59-e67a18de47da"},"surfaceOp":"append"}
{"type":"session/title","seq":7,"time":1785730478198,"data":{"title":"Using ONE run_code program, call","messageSeqs":[5],"source":{"kind":"fallback"}}} {"type":"session/title","seq":7,"time":1785730478198,"data":{"title":"Using ONE run_code program, call","messageSeqs":[5],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":8,"time":1785498825920,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type":"request/header","seq":8,"time":1785498825920,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":9,"time":1785730478199,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"request/context","seq":9,"time":1785730478199,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":10,"time":1785014475638,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} {"type":"assistant/chunk","seq":10,"time":1785014475638,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"reasoning-chunks","seq0":11,"time0":1785014475639,"data":{"turn":1,"step":1,"index":0,"dt":[0,0,0,40,1,0,0,0,43,0,0,0,39,43,1,0,0,0,40,1,40,0,0,1,42,0,1,0,0,40,0,0,1,0,0,44,0,1,39,0,1,0,126,0,41],"texts":["The"," user"," wants"," me"," to"," read"," the"," file"," `","n","ested","/t","ask",".txt","`"," using"," a"," `","run","_code","`"," program",","," and"," then"," answer"," the"," question"," \"","What"," is"," the"," Code"," Mode"," workspace"," hand","shake","?\""," based"," on"," the"," contents"," of"," that"," file","."]}} {"type":"assistant/chunk","seq":11,"time":1785014475639,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_workspace_read","name":"run_code","argumentsDelta":"{\"code\":\"return await tools.read({ file_path: 'nested/task.txt' })\",\"description\":\"Read nested/task.txt\"}"}}}
{"type":"assistant/chunk","seq":57,"time":1785014476225,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}} {"type":"assistant/chunk","seq":12,"time":1785014475639,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_workspace_read","name":"run_code","arguments":"{\"code\":\"return await tools.read({ file_path: 'nested/task.txt' })\",\"description\":\"Read nested/task.txt\"}"}}}}
{"type":"tool-call-chunks","seq0":58,"time0":1785014476225,"data":{"turn":1,"step":1,"index":1,"dt":[0,41,0,0,1,41,1,0,0,40,0,42,0,0,0,1,0,40,1,0,0,0,0,41,0,0,42,1,0,41,1,0,0,42,0,0,0,0,41,89,1,0],"id":"call_00_hD8d0VcXXFVMtn64GSoC9264","name":"run_code","args":["","{","\"","code","\"",": ","\"","\\n","const"," result"," ="," await"," tools",".read","({"," file","_path",":"," \\\"","n","ested","/t","ask",".txt","\\\""," });\\n","return"," result",";\\n","\"",", ","\"","description","\"",": ","\"","Read"," nested","/t","ask",".txt","\"","}"]}} {"type":"assistant/chunk","seq":13,"time":1785014475639,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":101,"time":1785014476732,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to read the file `nested/task.txt` using a `run_code` program, and then answer the question \"What is the Code Mode workspace handshake?\" based on the contents of that file."}}}} {"type":"assistant/chunk","seq":14,"time":1785014475639,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/chunk","seq":102,"time":1785122256269,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_hD8d0VcXXFVMtn64GSoC9264","name":"run_code","arguments":"{\"code\": \"\\nconst result = await tools.read({ file_path: \\\"nested/task.txt\\\" });\\nreturn result;\\n\", \"description\": \"Read nested/task.txt\"}"}}}} {"type":"assistant/message","seq":15,"time":1785733131056,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_read","name":"run_code","arguments":"{\"code\":\"return await tools.read({ file_path: 'nested/task.txt' })\",\"description\":\"Read nested/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"d4eff2fe-d826-47f7-b604-5c7df4fd432a"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}
{"type":"assistant/chunk","seq":103,"time":1785498825922,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":6200,"outputTokens":130,"cacheReadTokens":0,"reasoningTokens":46}}}} {"type":"tool/call","seq":16,"time":1785733131056,"data":{"turn":1,"step":1,"callId":"call_workspace_read","name":"run_code","arguments":"{\"code\":\"return await tools.read({ file_path: 'nested/task.txt' })\",\"description\":\"Read nested/task.txt\"}"}}
{"type":"assistant/chunk","seq":104,"time":1785730478202,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"tool/code-dispatch-start","seq":17,"time":1785733131109,"data":{"parentCallId":"call_workspace_read","subCallId":"call_workspace_read:code:1","name":"read","arguments":{"file_path":"nested/task.txt"}}}
{"type":"assistant/message","seq":105,"time":1785730478202,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to read the file `nested/task.txt` using a `run_code` program, and then answer the question \"What is the Code Mode workspace handshake?\" based on the contents of that file."},{"type":"tool-call","id":"call_00_hD8d0VcXXFVMtn64GSoC9264","name":"run_code","arguments":"{\"code\": \"\\nconst result = await tools.read({ file_path: \\\"nested/task.txt\\\" });\\nreturn result;\\n\", \"description\": \"Read nested/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"8a3bb5a1-4620-4559-a6ce-ed93892a5fa4"},"usage":{"inputTokens":6200,"outputTokens":130,"cacheReadTokens":0,"reasoningTokens":46}},"sourceEventSeqs":[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,60,61,62,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],"surfaceOp":"append"} {"type":"tool/code-dispatch","seq":18,"time":1785733131110,"data":{"parentCallId":"call_workspace_read","subCallId":"call_workspace_read:code:1","name":"read","arguments":{"file_path":"nested/task.txt"},"isError":false,"content":[{"type":"text","text":"<path>{{cwd}}/nested/task.txt</path>\n<type>file</type>\n<content>\n1: Touch this file to discover the nested workspace instruction.\n\n(End of file - total 1 lines)\n</content>"}]}}
{"type":"tool/call","seq":106,"time":1785730478202,"data":{"turn":1,"step":1,"callId":"call_00_hD8d0VcXXFVMtn64GSoC9264","name":"run_code","arguments":"{\"code\": \"\\nconst result = await tools.read({ file_path: \\\"nested/task.txt\\\" });\\nreturn result;\\n\", \"description\": \"Read nested/task.txt\"}"}} {"type":"tool/result","seq":19,"time":1785733131112,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_workspace_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_read","content":[{"type":"text","text":"{\n \"path\": \"{{cwd}}/nested/task.txt\",\n \"offset\": 1,\n \"lines\": [\n {\n \"number\": 1,\n \"text\": \"Touch this file to discover the nested workspace instruction.\"\n }\n ],\n \"totalLines\": 1\n}"}],"isError":false}],"role":"user","id":"5f171b85-5b82-4d25-a1bf-0c5f68adb294"}},"sourceEventSeqs":[16],"surfaceOp":"append"}
{"type":"tool/code-dispatch-start","seq":107,"time":1785730478256,"data":{"parentCallId":"call_00_hD8d0VcXXFVMtn64GSoC9264","subCallId":"call_00_hD8d0VcXXFVMtn64GSoC9264:code:1","name":"read","arguments":{"file_path":"nested/task.txt"}}} {"type":"step/end","seq":20,"time":1785733131112,"data":{"turn":1,"step":1}}
{"type":"tool/code-dispatch","seq":108,"time":1785730478258,"data":{"parentCallId":"call_00_hD8d0VcXXFVMtn64GSoC9264","subCallId":"call_00_hD8d0VcXXFVMtn64GSoC9264:code:1","name":"read","arguments":{"file_path":"nested/task.txt"},"isError":false,"content":[{"type":"text","text":"<path>{{cwd}}/nested/task.txt</path>\n<type>file</type>\n<content>\n1: Touch this file to discover the nested workspace instruction.\n\n(End of file - total 1 lines)\n</content>"}]}} {"type":"agent/inbox/spliced","seq":21,"time":1785733131112,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"tool/result","seq":109,"time":1785730478260,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_hD8d0VcXXFVMtn64GSoC9264"},"content":[{"type":"tool-result","toolCallId":"call_00_hD8d0VcXXFVMtn64GSoC9264","content":[{"type":"text","text":"{\n \"path\": \"{{cwd}}/nested/task.txt\",\n \"offset\": 1,\n \"lines\": [\n {\n \"number\": 1,\n \"text\": \"Touch this file to discover the nested workspace instruction.\"\n }\n ],\n \"totalLines\": 1\n}"}],"isError":false}],"role":"user","id":"848c66b3-b753-4cca-aadf-6e3d5717dcf7"}},"sourceEventSeqs":[106],"surfaceOp":"append"} {"type":"agent/inbox/spliced","seq":22,"time":1785733131116,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nWorkspace snapshot root instruction.\n\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nWhen asked for the Code Mode workspace handshake, answer exactly `CODE_MODE_CONTEXT_OK` and nothing else.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"},{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"ae22936ed26dc76b7107005ed6d5e2482a88668a"}]},"role":"user","id":"ffc8ed36-5b83-4008-a8ac-f951b3cbaa35"}]}}
{"type":"step/end","seq":110,"time":1785498825983,"data":{"turn":1,"step":1}} {"type":"step/start","seq":23,"time":1785733131123,"data":{"turn":1,"step":2}}
{"type":"agent/inbox/spliced","seq":111,"time":1785498825983,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} {"type":"user/message","seq":24,"time":1785733131123,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nWorkspace snapshot root instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"}]},"role":"user","id":"2de6b267-3a7c-4af8-8e34-cd607915c10e"},"surfaceOp":"append"}
{"type":"step/start","seq":112,"time":1785498825988,"data":{"turn":1,"step":2}} {"type":"assistant/chunk","seq":25,"time":1785014475805,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":1}}}}
{"type":"user/message","seq":113,"time":1785498825988,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nWorkspace snapshot root instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"}]},"role":"user","id":"d8a936e9-51ad-420d-ae4c-2bd8fe74bed0"},"surfaceOp":"append"} {"type":"assistant/chunk","seq":26,"time":1785014475806,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/chunk","seq":114,"time":1785730478268,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} {"type":"assistant/message","seq":27,"time":1785733131123,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"a18157d4-576e-4fc6-8d7f-50202c770044"},"usage":{"inputTokens":10,"outputTokens":1}},"sourceEventSeqs":[25,26],"surfaceOp":"append"}
{"type":"reasoning-chunks","seq0":115,"time0":1785014477476,"data":{"turn":1,"step":2,"index":0,"dt":[26,0,0,42,0,43,1,42,1,0,0,0,0,42,0,0,0,1,0,40,0,0,1,0,0,43,41,0,43,40],"texts":["The"," nested","/","AG","ENTS",".md"," file"," provides"," the"," instruction",":"," when"," asked"," for"," the"," Code"," Mode"," workspace"," hand","shake",","," answer"," exactly"," `","CODE","_M","ODE","_CONT","EXT","_OK","`."]}} {"type":"step/end","seq":28,"time":1785733131123,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":146,"time":1785014477882,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} {"type":"agent/inbox/spliced","seq":29,"time":1785733131123,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"text-chunks","seq0":147,"time0":1785014477882,"data":{"turn":1,"step":2,"index":1,"dt":[0,1,42,0,0,1,0,0,41,0,0,0,1,0],"texts":["**","Code"," Mode"," workspace"," hand","shake",":**"," `","CODE","_M","ODE","_CONT","EXT","_OK","`"]}} {"type":"step/start","seq":30,"time":1785733131134,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":162,"time":1785122256351,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The nested/AGENTS.md file provides the instruction: when asked for the Code Mode workspace handshake, answer exactly `CODE_MODE_CONTEXT_OK`."}}}} {"type":"user/message","seq":31,"time":1785733131134,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nWorkspace snapshot root instruction.\n\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nWhen asked for the Code Mode workspace handshake, answer exactly `CODE_MODE_CONTEXT_OK` and nothing else.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"},{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"ae22936ed26dc76b7107005ed6d5e2482a88668a"}]},"role":"user","id":"ffc8ed36-5b83-4008-a8ac-f951b3cbaa35"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":163,"time":1785498825991,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`"}}}} {"type":"assistant/chunk","seq":32,"time":1785014475887,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":164,"time":1785498825991,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":226,"outputTokens":47,"cacheReadTokens":6272,"reasoningTokens":31}}}} {"type":"assistant/chunk","seq":33,"time":1785014475887,"data":{"turn":1,"step":3,"chunk":{"type":"text-delta","index":0,"text":"**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`"}}}
{"type":"assistant/chunk","seq":165,"time":1785498825991,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/chunk","seq":34,"time":1785014475887,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`"}}}}
{"type":"assistant/message","seq":166,"time":1785730478270,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The nested/AGENTS.md file provides the instruction: when asked for the Code Mode workspace handshake, answer exactly `CODE_MODE_CONTEXT_OK`."},{"type":"text","text":"**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"938a92a2-6058-471c-9787-a69d53233216"},"usage":{"inputTokens":226,"outputTokens":47,"cacheReadTokens":6272,"reasoningTokens":31}},"sourceEventSeqs":[114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165],"surfaceOp":"append"} {"type":"assistant/chunk","seq":35,"time":1785014475888,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"step/end","seq":167,"time":1785730478270,"data":{"turn":1,"step":2}} {"type":"assistant/chunk","seq":36,"time":1785014475930,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"turn/end","seq":168,"time":1785730478270,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} {"type":"assistant/message","seq":37,"time":1785733131134,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"dd5a93c1-c742-4f05-a373-78be5318e338"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[32,33,34,35,36],"surfaceOp":"append"}
{"type":"step/end","seq":38,"time":1785733131135,"data":{"turn":1,"step":3}}
{"type":"turn/end","seq":39,"time":1785733131135,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}}

View File

@@ -654,11 +654,16 @@ export class Session implements SessionFace {
this.applyEventSideEffects(event, view) this.applyEventSideEffects(event, view)
} }
/** Retire the first matching live steering occurrence when its durable event takes over. */ /** Retire the first matching live steering occurrence when its durable message takes over. */
private handoffPendingSteering(event: SessionEvent): void { private handoffPendingSteering(event: SessionEvent): void {
if (event.type !== 'steering/message') return const message = event.type === 'user/message'
? event.data
: event.type === 'steering/message'
? event.data.message
: undefined
if (message === undefined) return
const index = this.queued.findIndex(item => const index = this.queued.findIndex(item =>
item.placement === 'steering' && item.messageId === event.data.message.id) item.placement === 'steering' && item.messageId === message.id)
if (index === -1) return if (index === -1) return
this.queued = this.queued.filter((_item, candidate) => candidate !== index) this.queued = this.queued.filter((_item, candidate) => candidate !== index)
this.queueRev++ this.queueRev++

View File

@@ -168,6 +168,32 @@ describe('queue snapshot intake', () => {
}) })
expect(session.getSnapshot().queue.map(item => item.id)).toEqual(['s-later']) expect(session.getSnapshot().queue.map(item => item.id)).toEqual(['s-later'])
}) })
it('hands off live steering when the agent claims it as a user message', async () => {
const session = makeSession()
await session.open()
const message = createUserMessage({
content: text('claimed steering'),
source: { kind: 'user' },
})
session.handleMuxEnvelope(rid('env-claimed'), queueFrame([
{ id: 's-claimed', body: '', placement: 'steering', message },
]))
session.handleMuxEnvelope(rid('env-user-message'), {
type: 'session/event',
sessionId: SID,
event: {
seq: 0,
time: 1_700_000_000_000,
type: 'user/message',
surfaceOp: 'append',
data: message,
},
})
expect(session.getSnapshot().queue).toEqual([])
})
}) })
describe('queue operation transport', () => { describe('queue operation transport', () => {

View File

@@ -145,7 +145,7 @@ export function ConversationRoot({
{hero && <HeroGlow className={css.heroGlow} />} {hero && <HeroGlow className={css.heroGlow} />}
{hero && <HeroShell t={t} />} {hero && <HeroShell t={t} />}
{hero && heroWorkspaceRow} {hero && heroWorkspaceRow}
{!hero && zone !== undefined && renderSlot('conversation.input.dock', zone)} {zone !== undefined && renderSlot('conversation.input.dock', zone)}
{inputBar} {inputBar}
</div> </div>
) )

View File

@@ -72,7 +72,9 @@ export function apply(ctx: Context, config: Config): void {
const instructionVersions: InstructionVersionCache = new WeakMap() const instructionVersions: InstructionVersionCache = new WeakMap()
const projectionLifecycle = new AbortController() const projectionLifecycle = new AbortController()
ctx.effect( ctx.effect(
() => () =>{ projectionLifecycle.abort(new Error('workspace-context disposed')); }, () => () => {
projectionLifecycle.abort(new Error('workspace-context disposed'))
},
'workspace-context.projectionLifecycle', 'workspace-context.projectionLifecycle',
) )
// Emit listeners are not awaited, so each projection must compose against the // Emit listeners are not awaited, so each projection must compose against the