# Runtime Reference ## Cell Semantics Each top-level `run_code` executes a JavaScript/TypeScript cell with top-level `await`, ordinary control flow and a session root that continues across cells. Static import/export syntax is part of the language. TypeScript runtime forms such as enums and namespaces are transformed before execution. Returned and printed values form the result; binding state is independent of its presentation. The direct tool schema identifies a PTC TypeScript cell; the selected language guidance owns its conventions and restrictions. It does not describe internal async lowering as a source grammar. Stable guidance places direct Node module computation at the execution entry, while retaining external processes for tasks that require their environment or an independent engine. With the default `bindingUpdates: 'stateful'`, declarations and assignments update one logical identity in the same scope and activation, including local const bindings. An existing closure observes later updates, while a saved function value keeps its own identity. Separate block, function and iteration scopes stay separate. A bare `let x`, `var x` or `const x` preserves an existing value and creates undefined when new. Each declarator publishes its complete candidate only after its initializer and binding pattern succeed. Earlier declarators and ordinary assignment effects remain real if a later operation fails. Unreached lexical declarations do not reserve a name for future cells. An import alias follows its module until an actual successful assignment or declaration supplies a local value. A static import in a later cell switches that same identity back to the module, so existing new-generation closures observe the change. Within one cell, all static imports install their sources before the body; an ordinary declaration or assignment in the body can override them regardless of where the import appears in the text. Named default declarations associate `__default` with the local identity until independently overwritten. Static dependencies link before the body and keep their effects even if a later dependency fails. `bindingUpdates: 'protected'` retains native local restrictions, rejects cross-cell redeclarations, and protects newly created const/import bindings. Switching policy neither resets the worker nor freezes earlier writable identities. Historical cells replay under their recorded language generation; earlier closures retain that generation's behavior. See [ADR 0025](adr/0025-use-versioned-logical-binding-identities.md) for the ownership and compatibility contract. New protected modules retain these native protections and the current callable source reflection contract. Their compilation generation is distinct from historical modules, including when a policy change imports the same source again. Each session kernel owns one native Node realm. Cells, PTC-managed modules, the provided `require`, and activated Global User Binding modules execute in that realm, so Node-produced errors use the same intrinsic constructors as cell code and module evaluation observes cell writes to `globalThis`. Node's `useGlobal` REPL bootstrap is internal: its module shortcuts such as `fs`, `child_process`, and `worker_threads` are removed before code runs, and modules remain available only through the managed import and `require` paths. A binding workbench has the same rule inside its own worker; it does not share state or a realm with an Agent session. A user-created `node:vm` context or independent runtime keeps its own realm and intrinsic identities. Every program namespace is leased to one cell. Namespace objects or member functions captured in the REPL expire when that cell ends. A reusable REPL helper must look up the current namespace inside its body. Global User Binding modules use a dynamic bridge: even a module-captured member resolves the current implementation when invoked by a new cell; a removed member fails until it is available again. Continuations retain their originating cell identity and cannot borrow a later cell's lease. Official PTC mode declares only top-level `run_code`. PTC Plus registers `edit_run_code` in the program SDK before the first model request. Call `tools.edit_run_code` inside a cell. Actual derived `run_code` dispatches have their own recorded start/settle events and retain the live root transport as their parent. The private root evidence distinguishes the editor cause (`startSeq`) from derived execution identity (`executionStartSeq`); the latter identifies the published revised cell. See [ADR 0005](adr/0005-temporary-rejected-cell-edit-transport.md) for its settlement and recovery contract. The editor captures the most recent eligible cell when its official nested dispatch starts, including a successful cell that needs a small adjustment. The target remains fixed while the call is in flight. It accepts exactly one atomic `edits` or `regex_edits` array and an optional `expected_target_call_seq` guard. Invalid, unavailable or mismatched-target edits return `{ accepted: false, scheduled: false, reason }` without executing source. Every rejection names the captured cell (`run_code call ` plus a bounded first line), so a drifted target stays identifiable, and a guard mismatch also states that only the most recent settled cell remains editable and that corrected source should be re-submitted with `run_code` when the intended cell is gone. The settled log line reports the same target identity. Regex replacement uses JavaScript `$1`, `$` and `$$` templates; backslash forms such as `\\1` remain literal. Every regex item requires `pattern`, `flags`, `replacement` and `expected_matches`, including an explicit `""` flags string when no flags are needed. An accepted nested editor call returns `{ accepted: true, scheduled: true, target_call_seq }`. This is a scheduling receipt. The outer cell continues with its current state and original return value or error; after it finishes, PTC Plus dispatches the complete revised source through DSH's real `run_code` tool pipeline before settling the root result. The root result retains the outer canonical value or error and forwards the derived result's Host-rendered content, images, additional contexts and, for a successful root result, turn-conclusion flag; the next cell sees revised bindings. Official nested dispatch events retain editor identity and arguments. Private root-result metadata retains the derived source and journal for cold recovery without rewriting assistant history or emitting edit-specific runtime context. Rejecting the nested receipt prevents dispatch; losing root metadata after derived execution instead invalidates recovery evidence and does not undo effects. When the public Host skips the nested metadata projector, PTC Plus may retain private derived evidence only if its exact pre-policy frozen canonical value survives final policy with unchanged base metadata and execution identity. Block/error, value replacement, missing capture or removal of projected metadata rejects that evidence. The Host result remains unchanged; see [Derived journal settlement](durability-design.md#两阶段确认). One provider failure has a bounded transport recovery. When the provider reports the exact invalid-tool-JSON `MALFORMED_RESPONSE` as a terminal chunk or stream exception, PTC Plus changes that terminal reason to `tool-calls`; this error cannot terminate the whole turn. DSH already preserves an unparseable argument string and routes it through ordinary validation, tool execution/result recording, and the next model step. A response is trusted for call preservation only when its buffered body consists entirely of tool-call-specific framing plus at most one `usage` event. If such a response contains exactly one internally consistent declared `run_code` block, PTC Plus also tests deletion of one final `}`. It applies that correction only when the resulting value is an object accepted by the live direct-tool schema. The call keeps its index, identity, and name and proceeds through ordinary DSH validation, recording, scheduling, authority, and execution. Without a unique correction, trusted internally consistent complete blocks retain their original identity and argument bytes for the ordinary rejected-tool-result path; PTC Plus re-emits canonical start/delta/end framing so every tolerated provider variant passes DSH's stream invariant. If a tool block is incomplete or inconsistent, none was emitted, multiple usage events appear, or any other event occurs in the buffered body, PTC Plus closes any already visible text or reasoning block from its accumulated content, removes buffered unsafe block material, and emits one deterministic `run_code` call whose argument string cannot parse as a JSON object. DSH rejects it during ordinary schema validation, so no partial model call executes and the model still receives another step. Valid streams and other provider failures retain their original result. This is not a general JSON repair mechanism. When a live parse error occurs exactly at the cell EOF, PTC Plus checks all 12 suffixes of one or two closing tokens drawn from `}`, `)`, and `]` with the same preparation context. If exactly one correction succeeds, the existing editor can express it from a bounded unique suffix, and the rejected call has a persistent event identity, `PTC-C001` includes `await tools.edit_run_code({ edits: [...], expected_target_call_seq })` for use inside a new `run_code` cell. The guard must match the target captured when the edit call is persisted, so a later editable cell suppresses execution even when the same literal replacement would match it. The plugin does not apply or execute the correction automatically; ambiguous, broader, and unbound repairs keep the ordinary length-adaptive guidance. A parse-rejected derived edit does not publish its attempted source as an editable target, so its diagnostic keeps that general guidance rather than attaching a guard for the different outer root source. Published derived cells remain editable through their own recorded source identity. Use `return` for an explicit result and the native Node `console` surface for logs. Its formatting methods, including `dir` options, tables, counters, groups, timers, assertions, and traces, retain Node semantics. `process.stdout/stderr.write`, direct fd 1/2 writes and inherited child stdio are also captured. Console entries retain call order and each descriptor pipe retains byte order; the merged log does not claim a total chronology across stdout, stderr and the private control channel. All forms share the cell's aggregate output budget. Native expression completion can also supply a result value: ```ts const records = [{ id: 1 }, { id: 2 }] return records.map(record => record.id) ``` Parse, filter, and aggregate large results before returning them. Aggregate log/return overflow terminates the worker and discards the cell; its bindings are not available for a later reduction. An encoding-budget rejection (`PTC-O001`, `invalid-output`) instead keeps the worker live, so an already initialized binding can be reduced in a later cell. Neither path undoes external effects. Each physical cell has acknowledged start and end fences on stdout and stderr. Descriptor bytes arriving inside that window belong to the current physical run. The first output observed outside a round, or structured output whose async-local scheduling cell has already settled, produces an output-attribution diagnostic, closes capture and resets the worker rather than disappearing or being silently attached to another cell. If no cell is active, the Host retains that failure and rejects the next execution once instead of continuing to drain an unbounded background producer. Bare descriptor writes do not carry callback provenance; when they arrive inside a later acknowledged window, only that physical-arrival attribution is knowable. Prefer executing work directly in the current cell; reserve `code.run` for source already held as data. When nested source must be written inline, escape quotes, backslashes, and newlines exactly — its parse errors point only at the generated text, not at a position in the outer cell. For stateful/protected cells, the public prompt assembly projects the Host SDK introduction to describe runtime TypeScript `enum` and namespaces. Only the known cell-grammar phrase is replaced; native tool declarations and their guidance remain unchanged. Legacy cells keep the Host erasable-syntax restriction. Explicit `eval` and `Function` retain their native JavaScript syntax targets. ## Capability Discovery The default SDK declares `capabilities.tree/find/inspect/search` directly. `search({ query, budgetBytes?, limit?, cursor? })` retrieves complete indexed descriptions with parameters, return schemas and available metadata in one response. It matches non-contiguous English/Chinese terms, prioritizes exact symbols and names, and supplies catalog/query-bound continuation. The default is 16384 UTF-8 serialized JSON bytes and 10 considered matches per page; the minimum budget is 512 bytes. `usedBytes` includes the entire JSON envelope and oversized references, excluding outer `run_code` rendering. A contract that cannot fit is explicitly listed in `oversized` with an `inspect` reference; it is never truncated to look complete. This is lexical retrieval, not semantic inference or access to undisclosed services. See [Capability Surface](capability-projection.md) for the response boundary. The compatible `find` uses case-insensitive lexical matching, prioritizing exact `namespace.member` symbols, namespace names, and member names before complete identifier or description tokens. CamelCase and punctuation separate tokens; multiple query tokens must be contiguous. A miss means no lexical match, not proof that a capability is unavailable. `tree` and item-budgeted `inspect` keep their existing unbounded-byte interfaces. With capability management enabled, the SDK section is re-rendered from the frozen session plan: Host-declared members keep their Host schemas, complete and on-demand contracts arrive through session messages, and the always-callable `edit_run_code` transport keeps its Host signature in every render. Settings changes affect new sessions; `/capabilities refresh` synchronizes an existing session. See [public integration evidence](context-public-interface-coverage.md). The declaration states the delivered capability set and nothing else. That set is the step's admission: the definitions the Host renders its own `tools:sdk` block from and builds a program's `tools` members from. A registered definition this step does not admit is therefore declared nowhere, and one session record that closes a capability removes it from the declaration, the program members and the explorable metadata at once instead of waiting for the next composition. A record is durable when it is made, so the surfaces that decide what can be called read it directly; the delivered plan catches up at the next accepted step and states the rest. The panel is a different read — it lists the rows a session can decide about, closed ones included. A switch that is off delegates the Host surface, so the record is retained but not applied. A paused capability context and a disabled management switch both compose nothing and filter nothing: the declaration is the Host's own text, the program members are the Host's, and a closure the session recorded earlier applies again once management is on. Reading the record there would enforce a closure the same switch suspended, and enforce it only halfway — the switch also lets the Host execute that member. A session the Host still holds keeps the composition it froze, so its own snapshot decides whether this environment still applies; an agent with no composition yet answers from the configuration it would compose under. A closure is one fact whichever layer of the record states it, so a projection never asks which document recorded the decision. A saved policy rule closes a capability, and so does the legacy overrides map of a plan composed before the policy generation that the session has not refreshed; one predicate reads both. A closure also reaches the rows whose contract names the closed member as their only model path: closing the `skill` loader keeps a Skill the user can still invoke with that remaining path stated and removes one that has no path left, and a resource server row states only the operations this session still has. A row may therefore never instruct a call the same session closed. A closure written for an MCP server states the server, not any one member of it, so it resolves through that one name to everything the server owns: the `mcp____*` tool rows, the derived `mcp.` row, the `mcp:` instructions that row depends on, and the `mcp-resource-servers` entry that names the server as the argument of the three resource operations. A registry view that still carries those members does not keep them on a surface, and the plan composed after the closure states the decision — its row is `closed` — rather than restating a calling contract. Stating that path is a projection rule, not a composition rule. A composition states the path it was built under, and a decision recorded afterwards — `/capabilities` writes a record, never a plan — is already the session's newest statement, so an explorable row states the intersection of three statements: what the composition delivered, what the decision left, and what this step hands the program. The third is the same admission the declaration and the program members are made of: a composition is built once and reused while a step may deliver less, and a session that decided nothing does not recompose it, so a derived row's own contract is read against the members the cell really has. Absent that observation the composition's answer stands, because a caller that published no admission is not asking to state less than the session was composed from. A Skill row only ever loses the model path there, never gains one a composition did not state; a resource server row stays explorable only while an operation remains for its contract to name, and the guidance section and `mcp-resource-servers` directory entry it owns follow that same answer. One function states the resource sentence for both the composition and the read, so an unchanged session reads the same bytes. The assembly observes that admission instead of inferring it. The Host resolves one admission per step and publishes it with the prompt context, and the registry read takes it as its limit, so the declaration, the plan and the program describe the step that is being assembled rather than every definition registered at that scope. The admission narrows this plugin's own contract view rather than replacing it: the definitions the Host publishes are model-facing and carry no output schema, so a capability recorded from them would promise an `unknown` result and a revision no live inventory agrees with. The same set bounds the repair of a top-level call into a program call: a model that names a member this step delivers is rewritten into `tools.(...)`, and a name the registry merely carries is left as a top-level call for the Host to reject as unknown. A host that publishes no admission leaves the registry view as the only evidence, and that step's declaration is then as broad as its registry. Guidance sections follow the same admission. A host that registers instructions for a tool states them as a `tool:` section, and that text is a model-visible statement about a capability: a step that does not admit the definition delivers no member for it to instruct, so the section is withheld together with the declaration, the member and the metadata. The composition is not the admission — it is composed once and reused until the session decides something else — so a composition broader than the step (built from the registry or from an earlier step's admission) may not keep the section; a later step that delivers the definition states it again, which is why this is a statement about the step rather than a section deleted once. A projection that observed no admission keeps the registry view, as for the declaration. The two call transports are exempt: `run_code` and `edit_run_code` are the channel a program is called through, not capabilities. The `mcp-resource-servers` directory follows the same rule, and it is the one Host section whose text *is* a list of capability names: its entries are the servers a program may pass as `server`, and the three resource operations are the only members that take one. A step that delivers none of them lists no server — the section leaves instead of naming a server no member of the program can name — and it returns on the step that delivers an operation again. The rows it is rewritten from are the resource server rows this step still states, so a server's own `mcp:` instructions leave with the last row that named it and stay while a tool of that server is still delivered, because that text describes the server rather than one call's signature. A session with no composition states the directory from the admission alone, and a caller that observed no admission keeps the Host's own list. `tree()` returns namespace entries whose `members` are strings, not a flat symbol list. Form symbols with `tree.flatMap(entry => entry.members.map(member => `${entry.namespace}.${member}`))`. Reuse an observed tree within the current capability view and inspect only relevant symbols; do not infer completeness or effect guarantees from discovery. Exact symbols in examples are conditional on the live view. The advanced `repl.state` and `code.run` schemas remain behind inspection: ```ts const matches = await capabilities.find('session') return capabilities.inspect({ symbols: matches.slice(0, 8).map(item => item.symbol), budget: 8, }) ``` Discovery covers current `tools` plus the advanced `repl` and `code` namespaces. It is read-only and does not grant authority. Calls still use the typed member declared by the capability owner. Use ordinary Node.js and current-cell `tools.*` directly; reserve isolated `code.run` for source already held as data. For commands, prefer project-declared scripts and use an available typed tool. When direct host process access is more appropriate, inspect the executable that is actually installed and invoke it through Node.js `child_process`. Child processes inherit the recorded cell execution cwd when `options.cwd` is omitted, while an explicit `cwd` remains authoritative; the worker preserves the host execution environment needed to resolve package runners and shells. Add a shell or package runner only when the command requires its syntax or resolution; do not assume a particular shell exists. ## Host Surfaces The reserved `run_code` definition is resolved from whichever surface publishes it: the tool registry answers `tools.get('run_code', scope)`, and a host that keeps the definition inside its tool executor publishes it through one of three contracted readers, `requirePtcTransport()`, `requireTransport()` or `transport()`. Those readers and the executor's own declaration reader `describe([])` are the only members invoked: an executor's remaining methods are host code this plugin does not own, and a probe that called them could dispose, stop or start host state during prompt assembly. An executor that declares the name in `describe([])` and publishes no definition through those readers is *declared but unreadable*: the plugin reports one warning and keeps session execution unavailable, which is a different fact from a host without an executor. See [ADR 0050](adr/0050-read-host-surfaces-through-their-published-contract.md). The editable prompt slots (`runCode.description`, the `code` and `description` parameter texts) are written onto that definition as owned properties, and the host descriptors they replaced are restored when the attachment is disposed. A host that publishes the slots as getter-only accessors therefore receives the delivered text, and a definition whose slots cannot be replaced keeps its own text instead of half of the plugin's. ## Configuration Capability management is experimental and defaults to off. These inputs are frozen per session; saved changes apply to new sessions or `/capabilities refresh`: | Field | Default | Meaning | | --- | --- | --- | | `capabilityContextEnabled` | `true` | Pause rules, collection, recovery and plugin previews together without changing saved child settings; applies on new session or refresh | | `capabilityManagementEnabled` | `false` | Enable capability planning and projection | | `capabilityBaseline` | `'[]'` | JSON array of common capability identities | | `capabilityPolicy` | host disclosure, enabled availability, baseline/budget allocation selected, statistics allocation off | Independent default/category/MCP-source/item rules and allocation inputs; [ADR 0043](adr/0043-compose-capability-and-context-policies.md) | | `capabilityOverrides` | `'{}'` | Legacy JSON choices migrated when no capabilityPolicy is supplied | | `capabilityStatistics` | `'{"enabled":false,"windowSize":100,"minimumSamples":10,"fullThreshold":0.5}'` | Independent collection and family coverage settings; policy allocation.statistics separately consumes evidence | | `capabilityProfiles` | `{"schema":"ptc-capability-profiles/v1","profiles":[]}` | Up to 100 named reusable capability configurations; structured UI save/import/export/switch, separate from frozen session plans; [ADR 0040](adr/0040-separate-capability-configurations-from-session-observations.md) | | `capabilityBudgetBytes` | `16384` | Full-contract budget in UTF-8 bytes, minimum 1 | | `capabilityDirectoryBytes` | `2048` | Navigation budget in UTF-8 bytes, minimum 512 | | `capabilityRecoveryEnabled` | `false` | Select optional description recovery independently | | `capabilityRecoveryBytes` | `8192` | Separate description-recovery budget in UTF-8 bytes, minimum 1 | | `modelPreviewEnabled` | `false` | Select plugin result/log previews; otherwise delegate to the Host policy | | `retentionPayloadRuns` | `8` | Newest cells whose argument text stays in the session-log checkpoint, 8–512; older cells must be refolded from the log to be edited or republished | | `retentionPayloadBytes` | `8388608` | Cumulative budget for that retained text, 1 MiB–16 MiB; the oldest payload is dropped past it and a single larger cell is dropped as well, so the checkpoint never grows with what the session wrote. A checkpoint is roughly twice this budget, so the maximum bounds the worst case near 32 MB | | `retentionClusters` | `8` | Cell clusters that keep identity and observations before the oldest fold into an interval summary, 0–50000; it defaults to `retentionPayloadRuns`, which is what keeps a long session from growing linearly. `0` means unlimited (no count-based folding, the payload budget is the only bound) | A window below 8 cells or 1 MiB is not supported: a checkpoint with fewer cells in flight than that cannot keep its stored publication and its replay equal, so a smaller request falls back to the default instead of being honoured. | `modelPreviewTokens` | `4096` | Estimated model-presentation token cap, minimum 1 | The configuration uses `ptc-plus` as the DSH plugin ID and `dsh-ptc-plus` as the package name. The distinction is intentional: the former is the runtime/settings identity, while the latter is the repository and npm package identity. `enhancedToolView` defaults to `true` and controls only the browser presentation of `run_code` and `edit_run_code`. Turning it off unregisters PTC Plus's keyed tool views so DSH's native generic row renders the calls; turning it on restores the compatibility renderer without changing execution, prompt, or session behavior. `autoDescribeRunCode` defaults to `true` and appears as “Allow run_code execution without a summary.” When enabled, a call that omits the outer `run_code.description` uses derived arguments for local DSH validation, and the fallback summary enters presentation metadata only. When disabled, DSH validates the original arguments. Both states expose byte-identical model requests with the required `description` declaration; original call arguments, existing summaries, cell source, and nested native-tool JSON remain unchanged. Missing-description diagnostics append a complete DSH user message to `additionalContexts`, with an identity, text content and a `plugin:ptc-plus` notice source. The diagnostic distinguishes outer transport arguments from nested native-tool arguments and preserves existing additional messages. These notices are ordinary inbox messages, separate from recovery-tip escalation and state snapshots. This prevents new malformed diagnostic records; it does not repair session files already written by older plugin versions. ```yaml - id: ptc-plus name: dsh-ptc-plus config: enabled: true enhancedToolView: true canonicalizeToolCalls: true autoDescribeRunCode: true bindingUpdates: 'stateful' durableReplay: true unmeasuredCellLimit: 2 officialReplPriority: false tipsEnabled: true cordisToolsEnabled: false userBindingsEnabled: true computeMs: 60000 maxWallMs: 600000 maxOldGenerationSizeMb: 512 maxNestedRunCodeDepth: 8 maxOutputBytes: 67108864 sampleBoundMs: 1000 workerReclaimMs: 5000 startupProbeMs: 5000 executionSeamTimeoutMs: 30000 maxValueNodes: 100000 maxValueEdges: 1000000 maxValueArrayLength: 1000000 maxValueBigIntDigits: 100000 tipCooldownMessages: 3 tipEscalationFailures: 2 ``` The five legacy binding and module switches remain migration inputs. Complete enabled/protected choices map to the corresponding new policy; mixed old choices retain their effective controls until the user explicitly selects the new switch. The Host schema preserves an omitted `bindingUpdates` value until migration, and the settings card resolves the same policy. Selecting the switch also clears the compatibility marker. `enabled: false` is the settings-based kill switch: the Host keeps the settings namespace, client card, and passive cleanup that can withdraw old PTC declarations at the next permitted accepted step. It removes execution hooks, prompt sections, and tool surfaces without reading binding storage. Settings are applied live when their owner can reconcile them, except the capability inputs frozen per session above and the activation-only seam wait `executionSeamTimeoutMs`, which is read when the plugin activates or reloads. A submitted cell retains one configuration snapshot for its budgets and policies through preflight, execution, binding calls, completion validation, and diagnostics; an update accepted while it runs applies to cells submitted afterward. Worker lifecycle bounds are not cell budgets: `maxOldGenerationSizeMb` is fixed at creation, and `workerReclaimMs` and `startupProbeMs` are read from the current configuration when a worker is created or closed. Node fixes a worker's V8 old-generation limit at creation, so changing `maxOldGenerationSizeMb` while a session worker is active or a submitted cell is preparing to execute is rejected and rolled back rather than reported as applied. Other non-`enabled` updates reconfigure existing owners without replacing session-bound bindings. The settings card renders in whichever settings seat the installed DSH declares — the sidebar **Plugins → Installed** page per-bundle configuration (`plugins.row.config`), or the preceding Host's **Settings → Plugin configuration** card (`settings.plugin.item`) ([ADR 0019](adr/0019-plugin-settings-and-kill-switch.md)) — places the main switch beside the PTC Plus title and disables every other control while the plugin is off. The card does not register in General settings; the sparkle menu opens the same complete form in an application dialog. If a live enable or reconfiguration cannot install a complete runtime, PTC Plus unwinds or restores every owner and persists the last applied settings; a failed compensating settings write is surfaced as an activation diagnostic. The TypeScript language check is deferred until runtime activation, so a non-TypeScript host can load the plugin in disabled mode but cannot enable it. `startupProbeMs` bounds the worker startup conformance probe, which must settle before the kernel reports ready. The worker reads the value from its startup data and falls back to 5000 ms when it is absent; a healthy probe settles inside the worker's own microtasks, so the bound can only fire when startup itself yields or blocks, and the failure is reported as a startup diagnostic without executing any cell. That worker-side read is verified by inspection rather than by a timer test, because a healthy probe never reaches the bound. `officialReplPriority` defaults to `false` and is evaluated against the live provider capability rather than a release number: a provider that declares `retainsState: true` keeps a program environment per state owner, and a provider that predates that declaration answers through the one retained source form (`programFormat: 'repl'`). With the setting enabled and such a provider live, this host attachment installs no session runtime, so `run_code` and its descriptors — `programFormat`, `retainsState`, `executionInstructions`, `sandboxMode` and `timeout` — stay with the official provider, and the plugin's REPL guidance, session-state semantics, recovery, editor tool, capability SDK, REPL observation and binding workbench are suspended until ownership changes back. The settings card, enablement and stored global bindings remain available so the switch can be reversed. Changing the setting or the host mode re-evaluates ownership as an installation change: the retired attachment disposes its live kernels before the other shape installs, so no committed cell mixes owners. Calls executed under the official mode produce no plugin journal records and are never recovery evidence. The plugin's provider surface forwards `programFormat` to the live original, answers `retainsState` for itself (an installed session kernel retains a per-session environment; otherwise the original declaration decides) and retains weak owner-to-original dispatch provenance for `release(owner)` — captured Agent cleanup reaches the original instances that served that owner even after adapter removal or source replacement, and a failed cleanup remains available for explicit retry. Owner-keyed retained environments and the executed program format belong to the provider that actually runs the program, and non-session consumers such as the workflow adapter keep executing through it — while `executionInstructions`, `sandboxMode` and `timeout` stay withheld while PTC Plus owns session execution. The plugin's own session kernel state is keyed by session and released through the plugin lifecycle, not by `release(owner)`. `cordisToolsEnabled: true` atomically mounts the official `@deepseek-ai/dsh-tool-cordis` plugin and exactly the shipped `cordis-plugin-development` companion Skill in agent scopes that expose the `run_code` and `skill` tools plus the public preset and Skill services. PTC Plus resolves the official `cordis` preset through DSH's public preset service, lets the maintained filesystem provider read its Skill root as an immutable bundled Host resource, and filters that provider at the public registration boundary so sibling Skills are never published to the PTC scope. It does not copy the Skill or switch the agent's preset. The first request waits until the Cordis tool fiber, owner guidance, exact Skill provider, and scoped Skill load are all ready. An agent scope that omits one of those surfaces is left for later: the owner publishes nothing there, reports nothing, and retries when the tool surface changes or the next prompt is assembled. A scope whose exposed preset, Skill/tool service, declared Cordis service, inspect manifest, or activation contradicts the host contract fails the setting change at installation and removes every provisional contribution; reached through the host's own `agent/created` or assemble call it withdraws from that scope with one warning instead, so this plugin's unavailability never fails an agent another plugin created ([ADR 0029](adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md)). Disable and agent/runtime disposal remove both fibers. Native agents inherit neither the resulting `tools.*` members nor the Skill, and the code-only direct-tool projection remains `[run_code]`. Cordis can evaluate model-written plugins against the live runtime, so enabling it grants shell-equivalent trust. The Skill root uses the preset's absolute composition path when that generation publishes one. A pathless generation is resolved through the active Host's public `pluginPackages.packageOf` service, which returns the official singular or plural preset package from that installation rather than an ambient package reachable from the plugin profile. An older generation with no such service may resolve only its historical plural package relative to the Host's `ctx.baseUrl`; the fallback is disabled whenever `pluginPackages` exists. The exact `cordis-plugin-development/SKILL.md` must exist before the mount begins. An invalid published path remains authoritative and fails instead of falling through to another generation's package layout. Keep large Cordis host or client source in a top-level binding before calling a Cordis tool. If that call rejects while the worker stays live, an initialized binding can be reused in a short continuation. Decide whether to retry from the operation owner's retry/idempotence contract and available execution facts; a thrown call or a smaller cell does not prove that no effect occurred. Cordis Plugins and Runs are process-local. On a resumed agent or after Cordis is re-enabled, normalized journal calls can restore their recorded return values but do not prove that prior IDs, approvals, Runs, or Inspect observations are live. The fixed `tools:ptc-plus-cordis-recovery` context remains present until a new successful `cordis_inspect*` call is settled. Use that live read-only inspection before a stateful Cordis decision; do not rerun a recorded mutating call merely to rebuild state. ## Global User Bindings `userBindingsEnabled` defaults to `true`; an explicit `false` is preserved. Omitted settings use the current default, including after an upgrade. Individual entry states and user-controlled draft saving are unchanged. While disabled, PTC Plus does not read binding storage, add new declarations, inject values, register the management RPC or `/binding` command, or render Global User Binding controls. The next permitted accepted step can withdraw previously delivered declarations. Enabling it loads the single document at `$DSH_HOME/ptc-plus/bindings.json`; writes use a file lock, atomic replacement, owner-only file permissions, and an expected process-local revision. A stale revision or an external file change rejects the mutation and requires reload rather than overwriting newer content. A damaged document produces an explicit catalog error and remains unwritable until the user repairs it and reloads. Each entry has a stable `id`, display `name`, `namespace` or `top-level` scope, selected symbols, one-line purpose, enabled state, and TypeScript source. A namespace name must be a JavaScript identifier; a top-level entry uses its exported symbols as call identifiers and keeps `name` for display. Source must provide named value exports; default exports and re-exports are rejected. An omitted symbol list derives every named value export, while an explicit list limits the exposed API; clearing the Settings symbol field requests fresh derivation from the edited source. The parser derives declaration kinds, parameter and return annotations, bounded inferred types, and model-visible declarations from the selected source exports. A `namespace` entry contributes one object binding under its name, while a `top-level` entry contributes each selected export. Reserved REPL names and duplicate active identifiers fail validation. The document accepts at most 64 entries, 65,536 UTF-16 code units per source, 262,144 across all sources, and 16,384 across active declarations. Mutations validate the complete enabled declaration set before atomic replacement; an externally written document that exceeds the limit is reported as damaged storage and contributes no active entries. The source-derived interface retains unshadowed environment types, such as `AbortSignal`, `Headers`, and `URL`, and includes the referenced closure of local interfaces, type aliases, enums, and classes. Local type names are isolated per entry so two bindings may expose the same name without colliding. Public class instance fields, constructor parameter properties, methods, and accessors appear in the class shape; private, protected, static, and computed members remain hidden. Getter and setter signatures retain their separate read and write types. An imported type that cannot be represented as a self-contained declaration fails validation instead of being replaced with `unknown`; qualified `import('module').Type` references remain explicit. The current enabled snapshot is the desired input for the next cell. The independent binding API catalog represents `tools:ptc-plus-user-binding-defaults` and supplies configured prompts and selected source-derived interfaces from the first permitted request. One header identifies use inside `run_code` and replacement of the previous global binding API reference, followed only by entry labels and selected documentation. It does not prove successful activation. Successful activation, session-local shadowing and worker reconstruction do not themselves append a new binding context. Unchanged documentation is deduplicated against retained model-visible history; changed or compacted-away configuration is supplied again. Historical configuration and `tools:ptc-plus-user-bindings` snapshot sections remain readable and are withdrawn once by the next permitted accepted recovery snapshot; the current API catalog is delivered separately. The stable `tools:sdk` prefix and direct-tool schemas do not change with binding contents. A request-owned program namespace or error class takes precedence over a same-name user entry; that entry produces an activation diagnostic and is omitted from the settled snapshot without blocking independent code. If a failed initializer issued a program call, the cell becomes volatile because the successful-entry snapshot is not complete replay source for that call transcript. Namespace members and top-level exports preserve ECMAScript module live reads until assignment or a permitted redeclaration creates a session-local shadow. A shadow is not written to the global document and suppresses only the shadowed name from subsequent global activation while the entry's other names keep activating; journals migrated from versions 1 through 7 keep their recorded whole-entry suppression. Updates, disablement, and removal affect unshadowed names from the next submitted cell. Entry imports resolve from the directory containing `bindings.json` during both candidate evaluation and session activation, independently of the session cwd. Current binding modules resolve program namespaces through their compiled logical root, with a namespace view bound to the originating cell lease. Historical `stateful-module-v1` and legacy modules retain their recorded worker-global bridge. Both paths expose newly available namespaces on later requests, stop resolving removed namespaces or members, and prevent expired continuations from borrowing a later cell's authority. When a historical bridge would replace an existing worker global, installation fails before changing that global. Once an exported value may have escaped into an ordinary session binding, its synthetic-module resolution base remains available for that worker lifetime; historical modules also retain their installed bridges. When no historical binding has ever been exposed, disabled or empty activation installs no bridge; if every attempted historical activation fails, any bridge installed for that attempt is removed before the cell executes. The REPL tab and Settings management modal share a workbench for source editing, validation, declaration preview, revision-checked persistence, import of a local `.ts` file as a disabled entry, enablement, removal, and code execution. The code console evaluates the current unsaved source draft in a separate bounded Node REPL worker hosted by the plugin-owned helper process. Named exports can be called directly from TypeScript commands, with top-level `await` and variables retained across executions. It does not access the Agent session kernel or enter its journal. The compatible `run` RPC separately provides single-use candidate evaluation with an optional exported-function name and JSON argument array. Both execution paths retain DSH process authority: source and imports may create irreversible Node/OS effects before cancellation, timeout, or failure. Console controls and resource limits are documented in [Client UI](client-ui.md#全局用户-binding-工作台). Session value previews use a separate bounded observation path. A visible inventory without observations can request one read from the existing settled worker through the authenticated Host observation Remote. The read shares the cell queue, verifies the catalog and surface generation, and waits at most 250 ms without creating a worker, replaying history or changing the journal. Only worker-confirmed observation may defer the next cell's execution budgets; sending a request does not exempt background blocking from timeout recovery. The Client discards responses for replaced inventories, including an assignment that changes a binding's recorded source without changing its name. Committed ordinary values in the logical root can be read directly without invoking source getters. The legacy native-storage path additionally requires an actual evaluator probe for await lexical previews; function-local await does not suppress outer declarations. Primitive values and five fixed array slots can be displayed without getters, Proxy traps or complete object enumeration. Arrays always retain an incomplete marker because additional own properties remain uninspected. BigInt conversion is capped at 128 digits. Missing observations remain distinct from unreadable values; details belong to [ADR 0024](adr/0024-repl-console-observation.md). In an enabled PTC session, `/binding new ` and `/binding edit ` append a complete authoring task and start a normal Agent turn. The task gives the current `requestId`, field definitions, namespace invocation examples, and the dependency resolution base. Edit also supplies the exact stored entry and preserves its stable ID. The Agent submits from `run_code` through `code.submitBindingDraft({requestId, entry})`, a stable internal SDK member under enabled configuration. The first valid submission becomes one memory draft with `enabled: false`. The Host checks the captured request, exact Agent, generation, and cell lease; duplicate, concurrent, late, or mismatched submissions fail. Acceptance, turn stop, error, cancellation, replacement, disablement, or disposal revokes eligibility. Starting and ending a request does not register or unregister tools or Skills. The Agent cannot persist or execute candidates through this API. The authoring task permits small REPL cells with in-memory fixtures and assertions, including temporary variables and correction from observed results. It starts with a focused core check, uses `node:assert/strict`, and summarizes bulk checks with totals and representative failures. Tests must not modify external files or services. File and network integrations use in-memory substitutes to verify forwarding and error propagation; unknown-effect imports and initializers are reviewed without execution. Before submission, the final source, exports, types and usage prompt are checked against the requirement and tested behavior. The answer distinguishes helper failures from test-harness failures and reports untested integration. A usage prompt can be empty when the source-derived interface suffices. These instructions neither enforce an effect sandbox nor expose management RPC to the model. See the [user guide](user-bindings.en.md) for the interactive workflow. The accepted outer result carries an opaque draft locator plus complete accepted source and command identity in private metadata. Authenticated Client RPC uses that locator, never a payload session ID. Saving atomically claims the draft before revision-checked persistence; concurrent discard reports busy, and new drafts use create-only storage. Save and enable writes `enabled: true` in the same mutation. Catalog conflicts refresh authoritative state and keep still-valid drafts available for manual retry. Saving or discarding consumes writable access, while `draft-review` and the log projection retain the exact historical source. A public, structured action notice references the accepting result and exposes only the completed persistence fact to later model requests; notice failure does not repeat or undo storage. Agent/session disposal, feature disablement, and owner disposal revoke live locators and reviews, while historical source never restores save eligibility. Cold replay returns recorded submission values without another handoff. Stored, enabled, and successfully activated are distinct facts. Global UI state reports the catalog's enabled flag. Configured model documentation and a save notice prove no activation; results and diagnostics report actual execution. `repl.state` is a checkpoint function and its `list` names are checkpoint names, not bindings. Availability questions use execution results or a side-effect-free observation of a known name, without write/delete smoke tests. Effectful verification must be authorized by the task and use exclusively created resources whose ownership is established before cleanup. Every completed cell records the exact source-derived active snapshot in private result metadata, and its normalized journal records the shadow granularity plus the per-name `provider`/`local`/`absent`/`unknown` evidence of that settlement. Cold recovery validates that snapshot again, checks the name evidence against it, and replays the cell with its recorded granularity; it never substitutes the current `bindings.json` or the current per-name rule for a historical cell. Malformed or incomplete binding metadata invalidates the affected historical node and contracts recovery under the ordinary fail-closed frontier rules. An entry that fails activation is excluded from that cell's active snapshot and produces a bounded diagnostic without preventing independent `run_code` work. `durableReplay: false` starts new kernels without recorded REPL state while preserving live bindings in the current process. It does not delete session logs. `promptText` holds a `ptc-prompt-text/v1` document that replaces individual prompt slots, keyed by slot id (`guidance.opening`, `runCode.description`, `plan.navigationSentence`, `tip.body.platform`, ...). An absent id keeps the built-in sentence; an empty value omits that slot when it is omittable. Limits are measured in effective UTF-8 bytes — 4096 per slot, 16384 per anchor and 65536 for the whole document — and the same ceilings are enforced twice: the save path rejects with the slot id, and rendering re-enforces them on effective bytes, dropping an offending override back to its default and reporting `slot-exceeds-limit`, `anchor-exceeds-limit` or `document-exceeds-limit`. A projected slot must keep exactly one of each registered hole at render time as well, or it is reported as `missing-hole` and falls back; rendering never throws. Slots carry an owner and, when read-only, one of six reason codes: `fact-derived` (generated from configuration or runtime facts), `aggregate-frozen` (an aggregate container whose item set is frozen because adding or removing an item moves its neighbours' bytes), `protocol-exact-text` (compared byte for byte by a recognizer), `protocol-structural` (a structural contract nothing matches as text), `shadows-host` (replaces or appends into host-owned text) and `other-owner` (text you wrote elsewhere, shown read-only). An override of a read-only slot is refused instead of being silently ignored. Delivery follows the channel: prompt text and the `run_code` schema are rebuilt on the next request, the `edit_run_code` schema resolves when an agent installs the tool surface and therefore applies to a new session, the capability plan applies to a new session or an explicit refresh (a customized plan records `ptc-capability-environment/v5`), and the runtime and result channels apply to the next accepted step or the next trigger. Recognition does not depend on the sentence it is recognizing: a catalog message records whether it delivered an empty catalog in its own source, and a recorded message without that flag is compared against frozen historical constants, so rewriting today's empty-catalog sentence cannot reinterpret records written by another version and cannot cause a catalog to be redelivered. `internal/prompt-text.js` owns the registry and the accounting; the settings pane derives its counters from `accountPromptText`, so an unconnected channel is reported as unmeasured instead of zero bytes, and an empty document reproduces the built-in bytes exactly. Submission admission belongs to the exact DSH tool execution. Reentry within that execution shares its admission; a later root execution gets a fresh admission and session observation even when a background callback inherits the same session's asynchronous context or reuses a call ID. Only a proved derived edit may inherit its composite source and settlement evidence. Disposing a child agent/session leaves the parent's live bindings intact. Disposing the parent cancels its pending preparation; the next admitted call can recover from the verified journal without reusing that cancelled admission. With `durableReplay: true`, recovery claims only bindings backed by both verified journal ancestry and exact provenance available to the model that produced the current call. DSH's append-only event log proves reconstructability; its ordered model-visible surface proves awareness. Before every subsequent cell in a structured host session, the runtime revalidates each existing live call against that exact surface even when `replaceGeneration` is unchanged. Raw events shadowed from the surface and UI-only `dshPtcPlusBindings` metadata do not make a binding model-knowable. A result-only replacement may retain a cell when the assistant call containing its source remains visible. If compaction shadows that call, the live worker and cold recovery contract the affected state unless an explicitly selected bounded structured projection exposes the exact bindings in model context. Such a projection is not injected by default merely to retain hidden state. Natural-language summaries are not parsed as state evidence. An invalid historical association, prune replacement, or recovery boundary is also rejected as evidence for its bindings; it is not by itself a reason to reject every later valid `run_code`. Recovery takes the intersection of the model-knowable and structurally verified frontiers, then discards cells and bindings that may depend on an unknown boundary. Because the current journal does not contain a complete per-binding dependency graph, the conservative unit is an affected cell and suffix unless stronger owned evidence exists. If no non-empty frontier is provable, the worker starts from an empty REPL and executes the current cell as a new root. The first continued cell reports `PTC-R002` so the model can redeclare any missing bindings. Consecutive contractions share one full notice while every result still persists its actual contraction for later cold starts. A cell that proceeds without recovery ends that notice episode. On failure, recovery guidance accompanies the primary error; captured output contains the cell's console output. Delegated sessions read their own public DSH observation and ordered surface, independently of the parent execution context. Recovery never redispatches, reverses, or certifies effects from discarded history. This availability fallback applies only to historical PTC Plus recovery data; validation, policy, authority, approval, cancellation, and other failures of the current request retain their normal DSH behavior. PTC dynamic messages carry `source: { kind: 'plugin:ptc-plus', form: 'snapshot' | 'catalog' | 'notice' }`. Current-state snapshots replace only earlier PTC state, including historical PTC aggregate sections; they do not replace the binding API catalog, tasks, Skill instructions, tool results, or other producers. Only a later binding catalog replaces an earlier catalog, and an explicit empty catalog withdraws its API documentation. Host runtime-context clearance does not invalidate the catalog; suppression gates new delivery. Empty snapshots withdraw earlier state claims. Delivery uses the public accepted pre-step decision and honors `includeRuntimeContext`, scoped suppression, and cancellation through an empty assembly witness. Committed messages and the public ordered surface drive deduplication and reconciliation after compaction or resume. Proposals do not count as delivery; raw history does not establish model knowledge. Recovery tips are disabled with `tipsEnabled: false`. When enabled, a bounded independent PTC notice identified by `tools:ptc-plus-tip//` may appear after a repeated binding failure or a diagnostic that identifies an executable, shell, or path problem in the current execution world. The trigger and per-trigger ordinal are reconstructed from committed PTC notices and canonical historical DSH snapshot sections; each identity counts once across both sources, and visible wording does not advance history. A tip is subject to `tipCooldownMessages` and becomes detailed only after `tipEscalationFailures` unresolved matches; it never changes the code-only direct-tool list or schema. `edit_run_code` does not emit a runtime context because its real call and result already carry the relevant fact. Each new cell uses DSH's resolved current directory for Node resolution. Relative filesystem, path and default child-process operations use the cell's current directory at the time they run: a confirmed in-cell `tools.working_directory` result rebases the rest of that cell, so the cell and the Host tools resolve against the same directory, and the recorded call carries the transition while the journal keeps the cell's starting directory. Captured import/require functions, synthetic `import.meta` and loaded modules retain their source bases. Cold replay uses each cell's recorded directory and settles a recorded in-cell change from its journal entry; predecessor journals retain the original header/worker fallback ([ADR 0041](adr/0041-record-cell-execution-directories.md)). Relative files, bare packages, conditional exports, URLs and attributes retain their Node meaning. A missing requested export fails at static linking before that target module evaluates. Namespace property writes still follow the namespace object's own contract; updating an alias does not mutate the namespace. A managed namespace's members are readonly: a member write is refused and leaves the member's live value unchanged, while reflection continues to report that live value. `Reflect.set` returns `false`, and `Object.defineProperty` with a different value throws `TypeError`; a direct assignment from a non-strict cell body is silently discarded, exactly as for any other readonly property. Current `stateful-v1` and `protected-v1` cells expose one mutable, null-prototype `import.meta` object per cell. Its `url` is the file URL of the synthetic `/repl` parent, `filename` is that synthetic path, and `dirname` is the recorded cell execution cwd; when no cwd was recorded, all three use the worker cwd. Relative `new URL(..., import.meta.url)`, static import, dynamic import and provided `require` therefore start from the same parent. Frozen `legacy-v1` cells retain their historical `import.meta` parse rejection. Cell source otherwise has a deliberate CommonJS-like subset: `require`, `require.resolve` and `require.cache` exist; `require.main` is `undefined`; ambient `__dirname`, `__filename` and `module` do not exist. Use `import.meta.dirname`, `import.meta.filename` and `import.meta.url` for cell path metadata. Cell export modifiers retain local declarations; remote re-exports retain dependency loading, type-only exports are erased, and default values use the ordinary `__default` identity. Real modules expose their actual export names through a stable PTC-managed namespace. Member reads follow live sources until actual local override and return original function/object values. A namespace retained by an external function continues to provide live reads. Its readonly properties do not make it a native module namespace object. Opaque code independently using native import on the raw compiled URL, or its own native createRequire, can see the transport representation and does not receive PTC's writable-export interface. PTC-provided imports and require, binding modules, workbench commands and `code.run` children share the managed interface with independent state and lifecycle. The complete contract and acceptance conditions belong to [ADR 0025](adr/0025-use-versioned-logical-binding-identities.md#managed-module-interface). User-selected dynamic compilers retain their native syntax goals and results. Their environments remain part of the source contract: direct eval resolves the caller's logical scope, while indirect eval and Function constructors use their owning realm's root. Environment adaptation must preserve those relationships, ordinary shadowed functions and actual effects. A valid call failing because it sees generated storage names is an implementation defect. This also applies when native or opaque code invokes a supplied eval or Function constructor: after `const value=41`, `["typeof value"].map(eval)` returns `["number"]`. Function.prototype.toString, including a bound callback or module observer, returns compiler-proved original JavaScript source for compiled user functions. Recompilation preserves the source's own dependencies and function values keep their canonical identity. User overrides and separately created realms retain their own behavior. Intrinsic interfaces belong to their source generation. Source selects the owning realm's interface when reading intrinsic values, including property reads and results from native or asynchronous operations. Actual global and prototype properties stay native, including Function.prototype.toString. Legacy root source receives a stable interface after its frozen declaration lowering; its dynamic code uses the native REPL environment, while module results use the same managed namespace interface as other PTC entries. Existing closures retain their saved identities and environments across mode changes and asynchronous or module continuations. Uncompiled VM source continues to observe native engine source when it independently fetches native toString; an opaque observer can receive the PTC source interface as a callable argument. Rewrites are recorded as `meta.dshPtcPlusRewrites` on the tool result (parallel to the journal, a closed schema). Settled rewrites do not emit redundant runtime context: failures already carry execution and retry guidance in their result. Only when the rewritten cell has no valid journal does `tools:ptc-plus-rewrite-info` describe unknown completion in the next model request. `require(...)` is classified exactly like a dynamic import: allowlisted builtins (`node:assert`, `node:buffer`, `node:querystring`, `node:string_decoder`, `node:stream`, `node:util`, `node:url`, `node:zlib`) stay durable, other modules are volatile, `node:worker_threads`/`node:cluster` are rejected during preflight (`PTC-C002`), and non-literal arguments are dynamic module resolution. This rejection occurs before any cell statement executes, so a surrounding `try`/`catch` cannot catch it. ## Diagnostics | Code | Meaning | State effect | | --- | --- | --- | | `PTC-C001` | The cell cannot be parsed | Not executed; REPL unchanged; a uniquely validated and target-bound one- or two-token EOF closure includes an `await tools.edit_run_code(...)` repair for a new `run_code` cell; otherwise retry a short corrected cell with `run_code`, use edit for a localized long-cell correction, and resend with `run_code` for broad, ambiguous, or unbound repairs | | `PTC-C002` | Preflight rejected a kernel-control import | Not executed; REPL unchanged | | `PTC-N001` | Top-level binding conflict | Not executed; REPL unchanged | | `PTC-O001` | Unsupported or over-budget output | Cell executed; earlier mutations may exist | | `PTC-X001` | Uncaught runtime exception, located at the cell source line | Mutations before the throw may exist | | `PTC-R002` | Cold recovery discarded volatile, unconfirmed, damaged, or replay-abandoned history | Continued from the greatest verified frontier, possibly an empty REPL; missing bindings may need redeclaration | | `PTC-W001` | The same cell failed 3 consecutive times with an identical binding error | Supplemental warning carries the primary failure's conservative state effect, or `unknown` when unavailable | | `PTC-W002` | The same cell failed 3 consecutive times with another identical error | Supplemental warning carries the primary failure's conservative state effect, or `unknown` when unavailable | Generic stateful/protected exception help does not infer an initializer failure from the language generation. Completed declarations and actual assignments remain live; continue with the same names according to execution facts and the selected update policy, inspecting relevant values when their state is uncertain. Durability describes recovery after a kernel restart; it does not certify that a failed cell is safe to execute again. A failing cell can mutate bindings or produce an external effect before the exception. Continuation follows the operation owner's retry/idempotence contract and available execution facts; there is no universal status-query prerequisite. Host tool calls restore the DSH initiator boundary, so tools that require the exact live calling agent work through `tools.*`. Repeated lexical errors direct the model to visible source, scope, and initialization. Worker-owned lease failures direct continuation to the current namespace; proved missing members recommend capability discovery. Error text cannot impersonate that provenance. The warning threshold is three consecutive identical failures, independently of tip cooldown and escalation. Platform tips consume bounded multiline message/stderr facts when no structured owner cause exists; a generic command exit alone does not establish a platform error. Cold replay reuses validated recorded `PTC-X001` and `PTC-O001` diagnostic wording for the same actual failure before comparing the complete completion and transcript. Historical help text therefore cannot discard otherwise verified bindings merely because current guidance changed. A real failure-message, completion, or transcript mismatch still contracts recovery; no historical call is redispatched. Known top-level native calls outside the declared direct surface may be normalized into `run_code` when the live schema proves the intended tool. The canonicalizer parses arguments once: ordinary JSON is embedded as JavaScript, while values with own `__proto__` keys use safe literals, and JSON beyond the TypeScript parser safety depth uses a shallow `JSON.parse` expression, preserving execution semantics. The normalization adds no provenance or correction text visible to the model. Unknown, malformed, or internally inconsistent calls remain on the DSH host diagnostic path. The declared direct `run_code` keeps its identity. The nested editor is called through `tools.edit_run_code` and never obtains admission through this recovery path. Inside a cell, omitting the JavaScript argument of a native `tools` member is accepted only when DSH's live object schema validates `{}`. The worker canonicalizes that omission to `{}` before encoding, so dispatch, the durable call transcript, and cold replay observe the same arguments. This supports natural calls such as `tools.cordis_inspect_list()` without a tool-name exception. Passing `undefined` explicitly, omitting required input, or calling another program namespace retains its existing behavior. ## Named State `capabilities.inspect({ symbols: ['repl.state'] })` exposes the optional state API and its timing contract. A save/restore/delete return value acknowledges an accepted operation. Save checkpoints the final settled cell frontier, not the statement where `save` was called. Restore takes effect after settlement for the next cell; reads and writes after awaiting restore still use the current cell's state. Tentative saves can be discarded when the cell becomes volatile or discarded. A volatile save rejection reports the recorded reason and leaves live bindings usable. Restore discards state beyond the selected durable frontier; an unnamed restore selects the prior durable head, or an empty REPL when none exists. It does not change enabled global binding configuration, and subsequent cells can remain volatile because of those inputs or disabled durable replay. Restore therefore does not guarantee that saving will become possible. These operations neither reverse external effects nor certify retry safety. ## Limits `computeMs` measures worker event-loop active time, including synchronous blocking operations such as `execSync`; it is not a measurement proving CPU consumption. Asynchronous waits are instead bounded by `maxWallMs`. Either timeout terminates the worker and resumes later work from verified recovery state once the cell's run frame was posted. A cell that ends before that frame because its compute baseline could not be observed within `sampleBoundMs` (default 1000 ms), or because one of those bounds expired first, settles as an unmeasured no-op instead: no source ran and earlier settlements are kept. With the default limit of two the first such cell keeps the live worker; reaching `unmeasuredCellLimit` consecutive cells drops it while the proved frontier stays replayable, and the outcome is `budget-unavailable`. `workerReclaimMs` (default 5000 ms, at least 1000 ms) bounds each close stage of a stopped helper: its exit confirmation, measured from the close start with the fixed release grace capped so the kill keeps at least a 1000 ms window, and then the output pipes ending, measured from the exit. A cell-driven session-kernel exit miss keeps that helper and blocks later cells of the session; a startup, idle or output-close miss, and the binding console and candidate helpers, only retain the instance; an output that never closes retains only the instance and is reported when the session is released, without blocking later cells. A sampling failure that reports the worker itself as gone (exit, disconnect, unsendable request) keeps the worker-exit reset route. None of these outcomes establishes child-process cancellation or external-effect rollback; use an asynchronous process API when waiting on external work. - The runtime is identified as `typescript`. Stateful and protected cells erase type-only syntax and lower enums, namespaces, parameter properties, and decorators through maintained TypeScript and decorator transforms. JSX is outside the cell syntax. Historical cells retain their recorded language generation. - `tools.read` is a bounded inspection window in the current DSH tool contract. Whole-file computation in `danger-full-access` should use `node:fs/promises.readFile` or streams and becomes `volatile`. - Relative Node filesystem entry points, including `node:fs/promises` helpers and globbing, resolve from the cell's current directory: the recorded cell execution cwd at entry, updated for the remainder of the cell after a confirmed `tools.working_directory` result. Absolute paths and explicit filesystem options remain unchanged; sessions without a recorded cwd use the worker's native cwd and are classified as volatile when the access is ambient. - The callable `process.cwd` reports the cell's current directory, including a confirmed in-cell directory change; it and the `process.exit`/`abort`/`kill`/`chdir` lifecycle guards are immutable, non-configurable realm properties. Lifecycle calls throw `Error` with code `ERR_PTC_PROCESS_CONTROL`; assignment cannot replace these surfaces. Plugin-private `AsyncLocalStorage` receivers have immutable captured `getStore`, `enterWith`, and `run` own methods so user prototype changes cannot alter control state. Separately, during bootstrap only, PTC Plus synchronously intercepts the private Node REPL `AsyncLocalStorage.run` call, protects that one platform receiver, and restores the prototype in `finally` before user source or microtasks can execute. `internal/worker-realm-surfaces.js` is the tracked exception registry and write boundary for worker globals, process properties and native prototypes. Every write is classified as a stable native divergence, temporary compiler name, per-cell program binding, user declaration or restoration. A stable write must cite a registry row with the same classification; it cannot borrow a temporary or language-only row. The cwd-projected `node:fs`, `node:fs/promises`, `node:path`, and `node:child_process` callable family is a separate stable surface from `process.cwd`; its verifier compares dynamically enumerated callable descriptors and retained callable properties with a plain Node worker. Private `AsyncLocalStorage` receiver methods are likewise a separate stable surface from the temporary prototype bootstrap. Stable rows name their semantic owner, native or transport reference and executable verifier; a structured source check rejects supported direct `defineProperty`, assignment and deletion forms that bypass the boundary. Module-language behavior remains owned by the compiler/executor contract even when its `require` installation uses the same boundary. - The durable import allowlist is `node:assert`, `node:buffer`, `node:querystring`, `node:string_decoder`, `node:stream`, `node:url`, `node:util`, and `node:zlib`. Other Node imports remain usable but make the cell volatile. - Direct `node:worker_threads` and `node:cluster` references through static import, dynamic import, or unshadowed `require` are rejected during preflight, before any cell statement executes. Calls to `process.exit`, `process.abort`, `process.kill`, and `process.chdir` are rejected through direct `process`, `require`, and dynamic or static `node:process` imports. - Cold recovery replays journals from the session log; there are no compressed checkpoints or worker-LRU eviction. - DSH services or plugin APIs not exposed as a native tool or owner-provided program binding are not made callable through name-based reflection. ## 注入上下文的重复成本 一次会话里模型看到的内容由几个来源拼成,只有其中一小部分由本插件产生: | 来源 | `source.kind` | 归属 | 实测字节(2026-10-25 会话) | | --- | --- | --- | --- | | 能力环境说明 | `plugin:ptc-plus` | 本插件 | 498 B,每会话一次 | | 全局绑定 API 参考 | `plugin:ptc-plus` | 本插件 | 1 690 B,每会话一次 | | 工作区指令(`AGENTS.md`) | `agent-instructions` | 宿主 `@deepseek-ai/dsh-agent-instructions` | 23 122 B,每会话一次 | | 技能目录 | `skill-catalog` | 宿主 | 8 974 B,每会话一次 | | 运行时上下文 / 时间 / 工作目录 | `runtime-context`、`time-context`、`working-directory` | 宿主 | 各数百字节,按事件发生 | 本插件对每个会话只追加两条消息,且各自只出现一次;`test/runtime-messages.test.js` 与 `test/capability-environment.test.js` 钉住该行为。任何“同一段注入出现多次”的现象都不由本插件产生:`agent-instructions` 的生成点是宿主包 `dsh-agent-instructions`,本仓库没有写入该 `source.kind` 的代码。 历史观测:2026-10-12 的会话 `session-066846e1-…` 里 `agent-instructions` 出现三次(23 122 B 一次,随后两次逐字节相同的 23 221 B);2026-10-25 的会话只有一次。因此重复注入属于宿主的按轮/按步重发行为,本仓库既不能复现也无法修复——调整注入策略需要修改宿主,或由宿主按摘要去重。测量这类成本时,应区分“插件的两条固定消息”和“宿主的会话级注入”,不要把后者计入插件噪音。 ## 值传输失败 [PTC Value V1](value-wire.md) 区分 live 值、canonical IPC 与模型展示。输出失败 `PTC-O001` 报告不支持的类型/shape 或预算超限及值路径:类型失败需显式选取支持的数据,预算失败需缩小 graph。普通输出编码失败保留 live binding,不重复执行 cell。程序参数编码失败不派发调用;已完成调用的结果编码失败明确报告可能已有外部效果,不自动重试或重置。诊断不为识别类型执行 getter、toJSON 或用户方法。