The list-nesting cap guards against a tokenizer SyntaxError, which makes the
text not Python. A long `A | B | …` union is valid at any length and only
defeats CPython's compile-time C recursion (measured: 1,000 branches compile,
5,000 raise RecursionError); nothing compiles this block, and capping would
retire the deep-chain tests pinning the walk's linear time. Records that
boundary at the `oneOf` arm and in the Agent Note (both languages).
Also documents that the context-free degrade marker reads the call's
className rather than the frame's — frames propagate a derived name, so a
per-frame read would declare classes the caller cannot receive — and pins
that path with oneOf-of-objects and array-of-oneOf assertions.
A schema nesting arrays past ~200 levels rendered a `list[list[...]]` chain
CPython's tokenizer rejects outright (`too many nested parentheses`), so the
SDK block was not valid Python at all — the failure docstring escaping in the
same file already guards against. The chain now degrades to `Any` at 180
levels; nesting restarts per TypedDict field, since a field annotation is its
own logical line. Unions and nested objects are unaffected: neither
accumulates open brackets.
Also aligns the unreachable SDK_RENDERERS guard message with the two reachable
ones, and corrects a test comment that still said class docstring.
A description was emitted above the `async def`, where Python treats the
first string as the `Tools` class docstring and every later one as a dead
expression — leaving each method undocumented in the model's only source of
tool semantics. Emit it as the first statement of the method body instead.
Also names the known languages in the run_code flavor guard (the reachable
rejection, symmetric with the SDK_RENDERERS guard) and corrects three doc
claims: the code-runtime group README no longer calls the generated SDK
TypeScript, the base Code Mode note states its serial dispatch in past
tense, and the tools README points at the rationale the language-dispatch
note actually carries.
The Python renderer partitioned identifier methods ahead of subscript
comments, so a tool set like {a-tool, z} emitted z first — contradicting
the documented lexicographic contract and the TypeScript flavor, which
quotes exotic keys in place. Interleave both kinds in one ordered stream
and track emitted statements for the pass fallback.
Also correct four stale serialization claims in the base Code Mode note
that the live-parallel scheduler superseded.
- fromRequest placeholder: the last two braces of a consecutive } run now
terminate the placeholder, so patterns may end with a brace quantifier
(bot warning; the truncated pattern could even silently mis-match since
an unclosed { is literal in JS regexes)
- document that derived JSONL entries pass through the same resolution
- widen ToolRunContext/deferContext seam docs beyond composite-only usage
(source JSDoc, README pair, core-data-structures type-equiv blocks)
- pin direct-human blocked as uninstructed, completing the
goal-round/direct-human x complete/blocked test quadrant
The 60k oneOf-object test had only one object node (the innermost), so the
pre-fix code called allocateClassName once — linear, never tripping the
timeout, so it did not cover the class-name Θ(depth²) it named. Give every
level an object branch (both oneOf arms are objects) so each level propagates
a one-segment-longer class name; the pre-fix rope slice is then Θ(depth²)
(~9.5s, past the 5s default) while the capped path stays linear. Also extract
the shared cap expression into capClassNameBase (used by allocateClassName and
childClassName). py-types.ts stays at 100% per-file coverage.
The oneOf perf fix left a second Θ(depth²): a deep oneOf chain whose branches
are named objects propagated an ever-growing ConsString as the class-name
base, which allocateClassName then re-materialized (.length/.slice) at every
level. A childClassName helper now caps the base AT PROPAGATION, so each level
is O(1) and the walk is linear; the collision counter still makes truncated
bases unique. Also reword the oneOf comment (it said `+` but the code uses a
template literal — both are ConsString) and strengthen the tests: the deep
oneOf test now runs 100k levels (a quadratic regression trips the 5s timeout),
plus a 60k oneOf-object chain and a >120-char tool-name cap case. py-types.ts
stays at 100% per-file coverage.
A deep oneOf chain joined the accumulated union string at every level
(Array.join forces materialization), making it Theta(depth^2) — a
50,000-level chain took ~7.6s. Concatenate with `+` instead: V8 builds a
lazy ConsString that materializes once at the root, matching the array
arm's template-literal laziness and ts-types' composable-document approach.
The whole walk is now linear in depth. Adds a 20,000-level oneOf test
alongside the existing deep-array one; py-types.ts stays at 100% coverage.
The SDK_RENDERERS JSDoc kept the circular "a renderer here … plus the
renderer itself" phrasing the note already fixed, and its
{@link RUN_CODE_FLAVORS} pointed at a non-exported const in another module
(unresolvable). Reword to "an entry here and a RUN_CODE_FLAVORS entry in
code-mode.ts … plus the renderer function this table points at".
Follow-ups from the bot's review of the trusted-after-validation revert:
- renderType's Frame now carries JsonSchemaNode (the root schema is asserted
before any frame is built), dropping the `as Record<string, unknown>` casts,
the `node.oneOf as unknown[]` cast, and the runtime `required` filter — the
same typed-frame shape as the sibling ts-types renderer, so the "symmetric
with ts-types" claim holds structurally, not just behaviorally.
- The language-dispatch note broadens the trusted-input argument to cover all
real sources (first-party defineTool/raw registration and wire-derived plain
JSON), and the zh side uses full-width punctuation per translation-rules.md.
py-types.ts stays at 100% per-file coverage.
Rounds 6-9 of the bot review kept finding adjacent hostile-getter variants
(post-validation cycles, TOCTOU on const/enum/oneOf, self-referential
functions) because the renderer had grown per-shape runtime defenses the
sibling ts-types renderer does not have. Those inputs are unreachable: the
schema is a first-party defineTool object literal that already passed
assertSupportedJsonSchema, and per AGENTS.md "Trust TypeScript at typed
same-process seams" a typed same-process seam does not add hostile-input
handling for values the static interface forbids.
renderType now validates the whole tree once and trusts it, wrapping the walk
in one try/catch that degrades to Any — byte-for-byte the stance of the
ts-types sibling. This removes the cycle-tracking (activeSchemas/hasIdentity),
the const/enum/oneOf read snapshots, the isPyScalar re-check, the typing
rollback, and the pyScalar null->None re-read handling; the corresponding
hostile-getter tests are removed. Behavior fixes that hold for legitimate
input are kept: RESERVED soft-keyword exclusion, closed-empty-object TypedDict,
class-name cap + per-base collision counter, BigInt digits for beyond-safe
integers. py-types.ts stays at 100% per-file coverage.
The language-dispatch Agent Note documents the stance and its symmetry with
ts-types so the boundary is not re-litigated.
Address ds-review-bot v5/v6 review round 8. The prior guards re-read a
stateful getter's value between the check and the spelling, so a getter
returning different values across reads could still emit invalid Python:
- renderConstrainedScalar reads node.const ONCE into a local, then checks and
spells that snapshot; a third-read switch can no longer produce
Literal[[object Object]].
- The enum path snapshots via [...raw] (reading each element exactly once,
covering accessor-property elements) and requires the snapshot be a non-empty
all-scalar array; an emptied re-read no longer spells Literal[], and a
non-array re-read degrades.
- The oneOf branch build guards a non-array or empty re-read to Any instead of
joining to '' (a missing type).
- pyScalar spells null as None; its JSDoc no longer claims null cannot reach it.
Tests cover each re-read shape; py-types.ts stays at 100% coverage.
Address ds-review-bot v5/v6 review round 7:
- The render-walk cycle guard tracked only plain objects; a function has
typeof 'function' yet carries own properties and can reference itself, so a
post-validation getter returning a self-referential function bypassed the
guard and looped forever. A hasIdentity() helper now covers objects AND
functions, applied symmetrically at the three sites (root add, finish remove,
child check).
- renderConstrainedScalar re-reads const/enum at render time; a stateful getter
that validated as a scalar could return an object, spelling the invalid
Literal[[object Object]]. It now degrades to the broad type when the re-read
value is not a scalar (or the enum not an all-scalar array).
- The activeSchemas comment notes the out-of-scope boundary: a getter
fabricating a fresh node per read never repeats an ancestor and is
indistinguishable from a legitimately unbounded-depth schema.
Tests cover the function cycle and non-scalar const/enum re-reads; py-types.ts
stays at 100% per-file coverage.
Address ds-review-bot v5/v6 review round 6:
- renderType tracks the active ancestor schemas by object identity (the frame
stack is the DFS path). A stateful getter can mutate the graph after
validation so a child returns an ancestor at render time; without this the
walk pushed frames forever instead of degrading. A repeated ancestor now
degrades to Any, honoring the never-throw contract; distinct nodes in a
legitimately deep chain are different objects, so it stays O(1) per push and
O(depth) memory.
- The multiline allocateClassName JSDoc was still attached to the
MAX_CLASS_NAME_BASE constant (a self-referential @link, and the function had
no doc). Move the doc onto the function and give the constant its own
one-liner.
- Tests cover the post-validation cycle and a non-object render-time child;
py-types.ts stays at 100% per-file coverage.
Address ds-review-bot v5/v6 review round 5:
- allocateClassName: keep a per-base collision counter (state.nextClassCounter)
so a deep single-field chain sharing one capped base allocates in amortized
O(1) instead of rescanning from 2 each time (Theta(depth^2) time); remove the
stale one-line JSDoc left above the multiline one and attach the doc to the
function, not the constant.
- renderType's catch rolls back the typing symbols the discarded subtree added
(not just the classes) so the import line still lists exactly the symbols the
surviving output uses; the comment now names that the same path also degrades
this module's internal-invariant throws to Any, the trade for never throwing.
- README (both languages) no longer describes an installable
dsh-code-runtime-python package: the Python renderer is built in and drives
any runtime reporting language: 'python'; the first-party backend ships
separately.
- Tests: assert the render-phase degrade on the first call, assert the import
line after rollback, and cover the collision-skip loop; py-types.ts stays at
100% per-file coverage.
Address ds-review-bot v5/v6 review round 4:
- renderType now holds the no-throw contract across the whole walk, not
just root validation: a stateful getter that passes validation and then
throws in the render phase degrades the node to Any, rolling back any
classes the call had begun emitting, instead of escaping.
- allocateClassName caps the accumulated base name. Child class names
derive from their parent's, so an unbounded single-field object chain
grew the sum of names to Theta(depth^2) (a 5000-deep schema produced a
~25MB SDK); the cap keeps total emitted text linear, the collision
counter still makes truncated bases unique.
- The language-dispatch note's Consequences first sentence and the zh
guard paragraph are corrected: two table entries (not one), and
full-width Chinese punctuation per translation-rules.md.
Address ds-review-bot v5/v6 review round 3:
- Config.mode JSDoc and the regenerated config-catalog no longer claim Code
Mode requires a TypeScript runtime; both now say a language with a
registered SDK renderer.
- The active 2026-06-15-code-mode base note (both languages) follows shipped
reality: the SDK renders the loaded runtime's language, dsh-tools accepts
any language with a renderer and run_code flavor, and it cross-links the
language-dispatch note.
- The language-dispatch note distinguishes the two Object.hasOwn guards'
reachability and documents the peekRuntime no-runtime degrade vs the
rejected silent fallback.
- SDK_RENDERERS comment: adding a language is two table entries, not one.
- py-types: document the deliberate PEP 586 deviation for float Literals;
add oneOf-object-branch tests (named union classes and context-free
degrade), keeping py-types.ts at 100% per-file coverage.
Implements the durable-subagent-catalog RFC: SubagentControlService.listChildren()
enumerates a parent's direct continuable children from one sessionQuery trace,
validates each child's sole subagent/descriptor event (now carrying the durable
creation label), and returns one ordered SubagentListEntry[] with per-child
corrupt/unsupported/unavailable diagnostics. The list_agents tool ships as a
separately loadable plugin of dsh-tool-subagent-control requiring sessionQuery
at load; send_message stays usable without it.
Implement the continuable background subagents RFC: a durable child
session with a series of Task-backed activations, each disposing its
run before the Task settles.
- dsh-subagent: rename SubagentRun.sendMessage to strict steer, drop
run-level resume, add SubagentProvider.resume dispatch via
SubagentService.resume, the continuation start field, and the
versioned model-hidden subagent/descriptor session event.
- dsh-subagent-inprocess/-spawn/-fork: publish the control-allocated
child id, append the descriptor inside the initial turn, implement
cold resume from the child's own transcript under the live parent
scope, and strict running-only steer.
- dsh-subagent-control (new): SubagentControlService owning stable
child ids, descriptor snapshot/fold/authorization, Task-backed
activation with settle-then-dispose ordering, the process-local
active-run association, and steer-or-resume sendMessage routing.
- dsh-tool-subagent: background route branches on the provider's
resume capability (continuable via the control service; one-shot
task for ACP), returning both child and task ids.
- dsh-tool-subagent-control (new): the globally named send_message
tool rendering steered/started routes.
Keyless coverage spans Task ownership and disposal ordering, running
delivery, cold follow-up, descriptor rejection and rollback, known-id
reconstruction, kill during lookup, admission races, and a new
subagent-continuable ACP snapshot scenario.
- jscpd: the executor/tool mirror dsh-bash-local/dsh-tool-bash by design
(Agent Note), so the mirrored regions carry explicit ignore markers with
reasons instead of being flagged as duplication.
- pwsh-local: a self-terminated process reports SIGTERM or SIGKILL on
POSIX (PowerShell's Stop-Process choice), not only SIGTERM.
- gen-tool-catalog.spec: the shipped-tool completeness list gains 'pwsh'.
Address ds-review-bot v5/v6 review on the Python SDK renderer:
- resolveFlavor now takes a peekRuntime() reader: undefined (no runtime,
the doc-catalog harvest) degrades to the TS flavor, but a mounted
runtime whose language is absent from RUN_CODE_FLAVORS fails loud. This
removes the try/catch that silently swallowed the invalid-language path
and drops the /* v8 ignore */ that hid the flavor guard from coverage;
wireSchemas validates the runtime before projecting schemas so the
renderer-table rejection stays the canonical assembly error.
- py-types RESERVED drops the soft keywords match/case: they are legal as
TypedDict fields and methods, so keeping them needlessly degraded
common search/regex arg objects to dict[str, Any].
- py-types treats an object with omitted properties as {} like the unified
validator and TS renderer do, so a closed empty object declares an empty
TypedDict instead of a permissive dict[str, Any].
- README: symmetric jsonSchemaToPy->Any note; a stale zh SDK bullet and
limitation corrected; link the service-wide-language limitation to its
Agent Note.
Address ds-review-bot suggestions on the Python SDK renderer PR:
- resolveFlavor: widen the JSDoc and catch comment to name the
invalid-language path the doc-catalog harvest also degrades through.
- wireSchemas: note the requireCodeRuntime() call is an intentional
single gate, redundant with the per-getter resolveFlavor path.
- README: link the service-wide-language limitation to its Agent Note,
and correct the Chinese bullet that still claimed TypeScript-only.
- Delink dsh-code-runtime-python README references (that package ships in
a later PR of the split; keep the package name unlinked meanwhile).
- Add the mandatory `## Alternatives considered` and `## Consequences`
sections to the language-dispatch Agent Note.
- Regenerate config/cordis catalogs and the event graph for the shifted
index.ts source lines; re-record the README and note i18n pairings.
Code Mode generated only a TypeScript SDK and rejected any runtime whose
language was not "typescript". Add py-types.ts (jsonSchemaToPy /
renderToolsSdkPy) and select the SDK-section renderer and the run_code
schema flavor by ctx.codeRuntime.language through two parallel tables
(SDK_RENDERERS, RUN_CODE_FLAVORS), read with Object.hasOwn and failing
loud on a language with no renderer. The tool layer depends only on the
code-runtime seam's language field, so it lands independently of the
Python protocol and backend.
An empty read window (byte cap below the first selected line: `lines: []`
with `totalLines > 0`) dropped `offset` from the persisted presentation
meta, so a replayed read card could not report where the window starts or
where a continuation resumes. Carry `offset` on `FsReadMeta`,
`ReadResultView`, and the `presentationMeta` projection, and validate it in
`readMetaFromMeta` (1-based integer; the first line number may not fall
below it). Re-record the ACP fixtures and the cordis api catalog.
Also correct the Note's `parallel-file-reads` golden path
(examples/tui-agent -> apps/cli) and record the pre-card replay-degradation
tradeoff in the Decision section.
Address the review of the search render card:
- The search result view carries no `content`: it was a no-op for every
consumer and serialized the whole search text twice. A UI without a search
card falls back to the raw tool/result content; the TUI stays byte-identical
to the pre-search-card generic fallback.
- Bound the serialized presentationMeta with a configurable searchMetaMaxBytes
(default 64 KiB): the inline item cap does not bound bytes, and spill-policy
only shrinks content, never meta. capMetaBytes drops trailing groups/paths.
- Share one retention pass (retainGrepMatches/retainGlobPaths in search-core)
between the model-facing render and the meta projection; remove the second
cap/preview implementation and the presentation<->grep module cycle by
moving GrepMatch/previewLine to search-core.
- Rename the result-view discriminant kind -> shape so it no longer collides
with GenericCallView.kind (ToolCallKind, whose values include 'search').
- Narrow the entry export surface to consumed symbols.
- Sync the three bilingual ToolResultView doc pairs and the Agent Note pair;
document the deliberate empty-card acceptance vs diffsFromMeta.
- Regenerate config/tool/cordis catalogs for the new config field.
Address the code-review bot findings on the web result card:
- web_fetch's card truncated now derives from the shared renderFetchOutput
helper, matching the effective truncation the model-facing text reflects
(provider cap, source cut, or output cap), instead of the provider-only flag.
- Drop the redundant content copy from both web result views; a UI without the
web capability falls back to the raw tool/result content. Narrow the TUI
transcript view.content access accordingly.
- Set the result-state title from the call args (query/url) so a window-
truncated replay keeps a title.
- Project meta from the seam result types rather than hand-rolled value types.
- Sync the card vocabulary across core tools README, docs/core-data-structures,
the adding-a-tool cookbook, and the tool-web package README (both languages,
re-recorded pairings); regenerate the cordis api-catalog and cordis-inspect
snapshot; revise the Agent Note.
readMetaFromMeta narrows the opaque persisted meta boundary, so beyond
shape it now rejects replayed JSON that is well-typed but semantically
invalid: line numbers must be 1-based integers, totalLines a non-negative
integer, and line numbers must strictly increase without exceeding
totalLines. Any violation declines to the generic fallback.
Sync the public ToolResultView contract across the core/tools and
tool-fs READMEs and docs/core-data-structures/tools for the fourth
result-view member and the ReadFileLine vocabulary, and expand the
Agent Note Testing section with the new rejection paths and the snapshot
evidence this PR carries.