"defineTool compiles an object root, so the annotation is a bare
TypedDict class name that opens nothing" is a false universal:
parameterSchemaSpecToJsonSchema compiles an OPEN object root, so an
empty parameter table and one with unrepresentable field names both
degrade to dict[str, Any], which opens one bracket. The conclusion the
sentence carries is unaffected -- 1 or 2 against a 182 cap -- so say
"a bare TypedDict class name or dict[str, Any], neither of which
carries a chain", in the JSDoc and the test comment that copied it.
pyScalar's docstring said the subscript tool-name comment quotes
"through the same call". It quotes through its own JSON.stringify call
site in renderToolsSdkPy and never reaches pyScalar, which only takes
const/enum scalars. Same function, different call site.
The mode-'both' test attributed assembly.tools to the public schemas().
That projection is wireSchemas, wired at ctx.systemPrompt.tools.
"the two code points CPython refuses" counted classes: NUL is one code
point, unpaired surrogates are the whole 2,048-wide D800-DFFF block. Say
kinds, in both the docstring and the test comment that mirrors it, and
restore the "odd" qualifier the test comment dropped -- an even trailing
backslash run does not eat the closing quote.
The soft-keyword test title still said "only special in statement
position", which the previous commit's own three-way split contradicts
for `case`: `case_block` is a clause head inside a `match` statement, not
a statement.
Add the case the subscript tool-name path lacked. A lone surrogate is
reachable in a name through JSON.parse of MCP wire JSON, and that path
has no UNPRINTABLE / LONE_SURROGATE fallback -- only the same ES2019
well-formed stringification the Literal path leans on.
The 182 the cap is chosen against had no direct case: the existing tests
cover the root chain and the TypedDict field, both of which start one
bracket lower. An array-rooted parameters schema reaches it from a plain
ToolSdkSchema literal, no raw register() needed. Exactly 180 arrays over
a const scalar is the worst case itself -- the root frame starts at
listDepth 0, so every list[ still emits and the innermost Literal[ is
reached rather than degraded; one deeper is where the item degrades.
Name the subscript tool-name comment in pyScalar's docstring: it quotes
through the same JSON.stringify call and inherits the same escapes and
the same pass-throughs.
pyScalar's docstring named only the two code points CPython refuses
anywhere in source. A bare quote, a trailing odd backslash, and a bare
LF/CR break the Literal line just as fatally, and JSON.stringify is what
covers those too. The argument also leaned on an unstated coincidence:
every escape JSON.stringify can emit is a Python escape for the same
character, which is why the emitted text both parses and decodes back to
the declared value. Say both, and assert the second class.
"statement head" does not describe `case`, whose clause block is not a
statement. Split the positions three ways.
Add the mode 'both' by python assembly, pinning the mode-by-language
matrix rather than leaving it to the shared code path.
trim and escape commute for every input, so the new whitespace test does
not pin their order: UNPRINTABLE and LONE_SURROGATE are disjoint from the
set trim() strips, and both escapes emit plain non-whitespace ASCII,
leaving the leading and trailing whitespace runs byte-identical. State that instead of the false causal clause.
pyScalar's Literal path escapes nothing itself -- JSON.stringify is what
keeps it parseable, covering NUL and, under ES2019 well-formed
stringification, unpaired surrogates. Record the dependency and turn it
into a checked invariant. Pin the docstring emission site for a lone
surrogate too, mirroring the NUL case.
Two docstring corrections: describe's caller enumeration omitted the
synthetic { description } wrapper docLines builds, and "special in
statement position" does not describe `_`, which is special in a match
pattern. Both keep the conclusion they support.
Note which of the two table guards fires depends on the entry point.
U+00AD is 0xAD, so "Cf cannot be addressed by \xNN" was false for the
first example in its own list. The real boundary is the category: one
\xNN form covers Cc exactly, and escaping the single addressable Cf
member would leave a rule that is neither category- nor
addressability-shaped.
A lone surrogate is the NUL case rather than the invisible-character
case -- Python source must be UTF-8-encodable, and compile() raises
UnicodeEncodeError for one in a string literal or a # comment alike
(measured on 3.9). JSON.parse on a wire "\ud800" escape produces them,
so escape them as \uNNNN; the regex's u flag keeps well-formed astral
pairs intact.
Pin the whitespace-plus-surviving-control boundary, which also pins
trim-after-escape.
Unicode Cc is U+0000-U+001F plus U+007F-U+009F, and no C1 code point is
ECMAScript whitespace, so U+0080-U+009F all survived the collapse and
reached the docstring raw and invisible -- the gap the previous commit
closed for NEL alone. \xNN addresses the whole block, which is the same
reason the set stops at Cc, so widen the class to U+009F and pin
U+009B/U+009C/U+009F.
Windows-1252 bytes 0x80-0x9F decoded as Latin-1 produce exactly these.
Also: required TypedDict fields share the optional fields' listDepth
start, and a description of whitespace plus a surviving control
character is not absent.
The static-stub sentence over-generalized: `tools` and `ToolCallError`
ARE bound at run time, and a model reading "everything below is a stub"
could stop catching `ToolCallError`. State the boundary and pin both
halves in the fixed-instruction assertions.
UNPRINTABLE missed U+0085: it is Cc but not ECMAScript whitespace, so
it survived the collapse and reached the docstring raw and invisible.
Add it and scope the docstring to Cc, since the `\xNN` escape cannot
address the Cf formatting characters that pass through by design.
Record the backend PR's two runtime contracts -- inject only `tools`
and `ToolCallError`, and bind the assembly-time language to the
request -- in the Agent Note and at requireCodeRuntime.
A TypedDict reads as a constructible class, so a model that writes
FooArgs(field=1) fails with NameError before dispatch: the run request
injects only the tools namespace and ToolCallError. Say so in
SDK_INSTRUCTIONS and require plain dict/list JSON arguments. The TS
flavor needs no counterpart -- interface is visibly a type and its
"runs type-stripped" clause already covers erasure.
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.