From a7869112937d4d42b74739f710e513ec63d1eb1d Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 26 Jul 2026 10:36:35 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20non-v?= =?UTF-8?q?acuous=20error=20assertion,=20package=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ds-review-bot findings: the browser e2e's error-state check now requires at least one error sub-row (was >= 0); the runtime README documents the codeDispatches snapshot contract and the ui-conversation README the code variant + nested sub-row semantics. --- apps/web/tests/code-mode-round.e2e.ts | 5 +++-- packages/client/runtime/README.md | 4 ++++ packages/client/ui-conversation/README.md | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/tests/code-mode-round.e2e.ts b/apps/web/tests/code-mode-round.e2e.ts index 7147a1fc06..8b25bad5ca 100644 --- a/apps/web/tests/code-mode-round.e2e.ts +++ b/apps/web/tests/code-mode-round.e2e.ts @@ -111,8 +111,9 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { const nest = page.locator('[data-subcalls]').first() await nest.waitFor({ timeout: 10_000 }) expect(await nest.locator('[data-sample="bash-global"]').count()).toBeGreaterThanOrEqual(1) - // The failing read sub-call wears the same error state a native failed row wears. - expect(await nest.locator('[data-state="error"], [data-sample][data-error]').count()).toBeGreaterThanOrEqual(0) + // The failing read sub-call wears the same error state a native failed + // row wears (the recorded program tolerates a read of missing.txt). + expect(await nest.locator('[data-state="error"]').count()).toBeGreaterThanOrEqual(1) }, 60_000) it.skipIf(MODE === 'record')('a sub-row click opens the details panel on the sub-call material', async () => { diff --git a/packages/client/runtime/README.md b/packages/client/runtime/README.md index 6b697cbed9..12bfaa459b 100644 --- a/packages/client/runtime/README.md +++ b/packages/client/runtime/README.md @@ -12,6 +12,10 @@ SlotsService gives the renderer separate bare observables for `useSessions` and `SessionsService.create` accepts an optional caller-preallocated SessionId. It throws `SessionCreateError` on failure: `requestedSessionId` remains available after transport uncertainty, while `publishedSessionId` is set when `workspace-attach-failed` proves the Host published a real Session before attachment failed. For the New Session flow, the frontend Session object owns its retained prompt and advances it through attachment and send; a partially published Session keeps the same object and prompt while it appears as Ungrouped. +## 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. + ## Session title projection `SessionManager` retains the latest validated `session/title` control snapshot independently of list and session-instance arrival. Newer event seqs replace older snapshots, title timestamps contribute to list recency, and a subscription baseline discards any retained title beyond its `lastSeq` before the optional folded title arrives. Explicit session removal also clears the retained title. The client-facing `SessionSummary.title` is therefore only the actual durable title; `displayTitle` is always present and falls back through the cwd basename and session id. A cold persisted session keeps that fallback until opening or resuming it causes the host to fold and project its log-backed title. diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 0711adcccb..ac0f33eba5 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -6,7 +6,7 @@ The no-session hero renders the frontend Session Intent from the Session list pr The view ring IS a slot: the conversation registration declares the `'conversation.view'` list slot (session scope) in its `children` table, ConversationRoot renders the active entry through its renderSlot share (`only: `), and view tabs project from the ring ledger's registration options (`id`/`order`/`label`). The chat view is this package's own ring entry; other plugins (ui-trajectory) contribute tabs through plain `ctx.slots.register` — the former package-local view registry (`registerView`/`ViewEntry`/`ConversationViewMap` and the chrome attachment table) is retired, with per-view chrome dissolved into the view components themselves. -Generic tool rows classify the built-in bash, read, search, write, and edit names into dedicated visual variants. The filesystem variants render the edit icon and `Write · ` or `Edit · ` summary while retaining the shared row-to-details interaction. +Generic tool rows classify the built-in bash, read, search, write, edit, and run_code names into dedicated visual variants. The filesystem variants render the edit icon and `Write · ` or `Edit · ` summary while retaining the shared row-to-details interaction. The code variant summarizes with the model-authored `description` and expands to the program itself; its logged sub-dispatches render as always-visible nested rows through the SAME keyed toolview hole (custom registrations and the GenericToolCard fallback apply to sub-rows unchanged), and the details panel resolves a selected sub-call id to its full logged args and complete output. Tool rows are slots too — the standalone tool ring (`ToolViewRegistry`/`ctx.toolviews`/outlet) is retired. The chat entry declares the keyed `'conversation.chat.toolview'` hole (session scope; the key space is runtime-open); its render site dispatches per row via `entryKey: toolName` with `GenericToolCard` as the call-site `fallback`. The owner payload is the uniform `ToolRowOwnerProps` (`callId`/`toolName`/`block`/`openDetails`) and `ToolRowProps` pre-composes it with the session standard kit. A registrant is a plain plugin: `ctx.slots.register({ name: 'conversation.chat.toolview', key: '', inject? }, Row)` with `inject: ['slots', 'conversation']` as the load-order seam (apply mounts ConversationService after the chat registration, so the service being present guarantees the slot is declared); session differentiation happens inside the component (`useSessions` reading `parentId` — the bash sample is the third-party-posture exemplar). Trajectory/waterfall toolview slots share this shape and land with their own render sites (RendersCheck rejects a declaration nobody renders). From 9785f0e2a0043342cda7f1e7f7de156f9fea1901 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 26 Jul 2026 12:29:16 +0800 Subject: [PATCH 2/2] fix: actually compact the cordis-dynamic fixture's request/header line The previous hygiene fix re-serialized with json.dumps defaults (spaced separators), leaving the line byte-identical; explicit compact separators make the header-scrub guard pass. --- .../tests/snapshots/cordis-dynamic-toolchain/session.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tui-agent/tests/snapshots/cordis-dynamic-toolchain/session.jsonl b/examples/tui-agent/tests/snapshots/cordis-dynamic-toolchain/session.jsonl index 227355f029..4bc679a078 100644 --- a/examples/tui-agent/tests/snapshots/cordis-dynamic-toolchain/session.jsonl +++ b/examples/tui-agent/tests/snapshots/cordis-dynamic-toolchain/session.jsonl @@ -2,7 +2,7 @@ {"type": "turn/start", "seq": 0, "time": 1783957884479, "data": {"turn": 1, "trigger": {"kind": "message", "source": {"kind": "user"}}}} {"type": "user/message", "seq": 1, "time": 1783957884479, "data": {"content": [{"type": "text", "text": "Run this advanced flow exactly once: mount a no-op Cordis plugin named snapshot-marker; use run_code to inspect the live dynamic mounts through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; unmount dyn-1; then reply with exactly ADVANCED_ACP_OK."}], "source": {"kind": "user"}}, "surfaceOp": "append"} {"type": "step/start", "seq": 2, "time": 1783957884486, "data": {"turn": 1, "step": 1}} -{"type": "request/header", "seq": 3, "time": 1783957884486, "data": {"header": {"config": {"provider": "deepseek", "model": "deepseek-v4-flash"}, "system": "{{system}}", "tools": "{{tools}}"}, "reason": "initial"}} +{"type":"request/header","seq":3,"time":1783957884486,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} {"type": "assistant/chunk", "seq": 4, "time": 1783950000005, "data": {"turn": 1, "step": 1, "chunk": {"type": "block-start", "index": 0, "blockType": "tool-call"}}} {"type": "assistant/chunk", "seq": 5, "time": 1783950000006, "data": {"turn": 1, "step": 1, "chunk": {"type": "tool-call-delta", "index": 0, "id": "advanced-mount", "name": "cordis_mount", "argumentsDelta": "{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}} {"type": "assistant/chunk", "seq": 6, "time": 1783950000007, "data": {"turn": 1, "step": 1, "chunk": {"type": "block-end", "index": 0, "block": {"type": "tool-call", "id": "advanced-mount", "name": "cordis_mount", "arguments": "{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}