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.
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_codeturn — the client-side half of the PTC mode UI stack, built on the host foundation (full-contenttool/ptc-dispatch, the requireddescriptionparameter). 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
ToolCallTreefolds in-windowtool/ptc-dispatch-startandtool/ptc-dispatchevents into a private per-parent index, then projects running and settled children onto recursiveToolCallBlock.subCalls. Live Session projection andprojectConversationHistoryshare that fold; copy-on-write parent arrays and path-copy projection keep unrelated roots and siblings reference-stable. Sub-calls never joinnodes— 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/clientContextmerges collide). - Render layer:
ChatViewpasses each parent with its recursive children through the whole-Tool'conversation.chat.tool'seat. ui-tool'sToolCallTreerenders the parent followed by[data-subcalls]nests, and every atomic call dispatches through the same'tool.call.toolview'keyed slot withentryKey = Tool nameand the sameGenericToolCardfallback. 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_codepresentation: a newcoderow variant (classifierrun_code → code,Codetitle,IconCodeOutline16) summarizes with the model-authoreddescriptionand expands to the program itself (monospace on the markdown code-block fill) rather than the args JSON envelope.- Details panel:
materialForrecursively searchesnodesandrunningCalls, 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.