Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.md
T
Tianyi Cui 3ca9c7d489 rename code-mode to ptc (PTC mode), except session-persistent vocabulary
Rename the tool-presentation transport from code-mode to ptc everywhere
that is not written into session logs: the mode config value becomes 'ptc',
the preset directory/id becomes ptc, the demo becomes demo:ptc, the
dispatch waterfall becomes tools/ptc-dispatch-log (types PtcDispatch*), the
prompt rule becomes tools:ptc-only, source/test files become ptc.ts etc.,
and prose says PTC mode / PTC 模式. The session-persistent vocabulary
(durable events tool/code-dispatch*, logged plugin name tools-code-mode,
sub-call id segment :code:) intentionally stays and moves in the stacked
persistence PR, which is blocked until the SESSION_FORMAT_VERSION v0→v1
migration lands with it. run_code, its code parameter, CodeSdkLanguage,
CodeRunFailedError, the dsh-code-runtime family, third-party codex names,
and frozen archived notes keep their names.
2026-08-27 23:14:31 +08:00

4.4 KiB

Agent Note: PTC mode chat rendering — sub-calls as native rows under the parent

Status: implemented

English | 中文

Scope: how the web chat view renders a run_code turn — the client-side half of the PTC mode UI stack, built on the host foundation (full-content tool/ptc-dispatch, the required description parameter). The toolview dissolution owns the slot model this rides on.

Problem

With PTC mode enabled, the chat view showed one opaque run_code row: raw program text as the summary, sub-calls invisible everywhere. The settled product requirement is the opposite: each sub-call must render identically to a native tool call — same row components, same custom registrations, same details panel — while the transcript stays honest about the fact that the model made ONE call.

Decision

Sub-calls are standard Tool call blocks attached recursively to their parent outside the surface flow, rendered through the same keyed slot as native rows, and always visible under their parent.

  • Data layer: Runtime's ToolCallTree folds in-window tool/ptc-dispatch-start and tool/ptc-dispatch events into a private per-parent index, then projects running and settled children onto recursive ToolCallBlock.subCalls. Live Session projection and projectConversationHistory share that fold; copy-on-write parent arrays and path-copy projection keep unrelated roots and siblings reference-stable. Sub-calls never join nodes — the surface flow remains exactly the model-visible turn structure. The events are narrowed structurally at the wire-consumer boundary, which also rejects cyclic parent relationships (dsh-tools' host types cannot enter the client program because the host/client Context merges collide).
  • Render layer: ChatView passes each parent with its recursive children through the whole-Tool 'conversation.chat.tool' seat. ui-tool's ToolCallTree renders the parent followed by [data-subcalls] nests, and every atomic call dispatches through the same 'tool.call.toolview' keyed slot with entryKey = Tool name and the same GenericToolCard fallback. A keyed registration therefore takes over descendant and top-level calls without registration changes. Running parents (runningCalls) receive accumulated dispatches in the same recursive block, so child rows stream in during the run.
  • run_code presentation: a new code row variant (classifier run_code → code, Code title, IconCodeOutline16) summarizes with the model-authored description and expands to the program itself (monospace on the markdown code-block fill) rather than the args JSON envelope.
  • Details panel: materialFor recursively searches nodes and runningCalls, so a selected descendant callId resolves to full args and complete output through the identical rendering path as a native settled call.

Alternatives considered

Sub-calls flat in the surface flow (fold them into nodes). Rejected: misrepresents the transcript — the model made one call; nesting under the parent preserves the code↔calls association and keeps the fold's model-visible-order invariant untouched.

Hidden until the parent row expands. Rejected by product decision: the sub-calls ARE the story of a PTC mode turn; hiding them re-creates the opacity this feature removes. The parent's expand toggle reveals only the program.

A dedicated sub-call row component. Rejected: the whole point is identity with native rows; a parallel component would drift. The nest wrapper (indent + left edge) is the only sub-call-specific chrome.

Consequences

Custom toolview registrations apply to sub-calls for free — and deliberately: there is no per-registration opt-out short of the component reading its own context, which no current consumer needs. Selection highlighting reaches nested rows through the same selectedCallId channel (group membership searches the whole tree). Trajectory/waterfall now draw sub-call spans from the dispatch timing pair (live parallel dispatch); without that timing a waterfall span would be a lie. Fixture turn 64 (?fixture) plus the ptc-round browser e2e (recorded real round, keyless replay) pin the full surface; the jsdom and Runtime suites pin slot dispatch, error states, recursive details resolution, history projection, and reference-stable path copying.