dsh-ptc-plus 0.4.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.en.md +61 -123
  2. package/README.md +61 -123
  3. package/assets/dsh-ptc-plus-banner-en.webp +0 -0
  4. package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
  5. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  6. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  7. package/assets/ptc-plus-settings-en.png +0 -0
  8. package/assets/ptc-plus-settings-zh.png +0 -0
  9. package/client.cjs +42336 -0
  10. package/cordis.patch.yml +13 -2
  11. package/docs/README.md +7 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -179
  13. package/docs/adr/0008-define-agent-operating-contract.md +5 -1
  14. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
  15. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  16. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
  17. package/docs/adr/0019-plugin-settings-and-kill-switch.md +14 -3
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +3 -1
  21. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
  22. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +101 -35
  23. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
  24. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  25. package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
  26. package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
  27. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  28. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  29. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  30. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  31. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  32. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  33. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  34. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  35. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  36. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  37. package/docs/adr/README.md +6 -0
  38. package/docs/architecture.md +25 -27
  39. package/docs/binding-review-placement-design.md +29 -21
  40. package/docs/capability-planning-components.md +93 -0
  41. package/docs/capability-projection.md +7 -0
  42. package/docs/client-ui.md +88 -29
  43. package/docs/client-visual-contract.md +65 -0
  44. package/docs/context-management.md +22 -0
  45. package/docs/context-optimization-plan.md +353 -0
  46. package/docs/context-public-interface-coverage.md +25 -0
  47. package/docs/durability-design.md +31 -14
  48. package/docs/evaluation.md +41 -6
  49. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  50. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  51. package/docs/installation.md +50 -8
  52. package/docs/program-data-plane.md +5 -3
  53. package/docs/publishing.md +5 -5
  54. package/docs/release-coverage.md +73 -0
  55. package/docs/release-notes.md +1 -1
  56. package/docs/review-procedure.md +56 -0
  57. package/docs/runtime-reference.md +48 -16
  58. package/docs/semantic-validation.md +14 -2
  59. package/docs/stateful-binding-updates-proposal.md +2 -2
  60. package/docs/user-bindings.en.md +2 -2
  61. package/docs/user-bindings.md +3 -3
  62. package/docs/value-wire.md +11 -1
  63. package/docs/verification-optimization-work.md +52 -15
  64. package/docs/verification.md +30 -86
  65. package/index.js +147 -23
  66. package/internal/binding-toggle-receipt.js +24 -0
  67. package/internal/capability-command-receipt.js +29 -0
  68. package/internal/capability-environment.js +1247 -0
  69. package/internal/capability-plan.js +285 -0
  70. package/internal/capability-policy.js +96 -0
  71. package/internal/capability-statistics-store.js +180 -0
  72. package/internal/capability-statistics.js +210 -0
  73. package/internal/compiler-platform.js +5 -3
  74. package/internal/compiler-service.js +2 -9
  75. package/internal/config-spec.js +79 -9
  76. package/internal/cordis-tools-owner.js +22 -19
  77. package/internal/direct-surface-owner.js +29 -29
  78. package/internal/dynamic-environment-runtime.js +9 -9
  79. package/internal/edit-transport-owner.js +183 -139
  80. package/internal/execution-provider-owner.js +160 -0
  81. package/internal/execution-seam-compat.js +76 -120
  82. package/internal/isolated-worker.js +12 -8
  83. package/internal/kernel-worker.js +104 -32
  84. package/internal/native-root-dynamic.js +12 -13
  85. package/internal/program-bindings.js +190 -0
  86. package/internal/recovery-tips.js +53 -23
  87. package/internal/rejected-cell-editor.js +8 -8
  88. package/internal/repl-observation-interest.js +11 -3
  89. package/internal/rpc-contract.js +1 -0
  90. package/internal/runtime-bridge-owner.js +351 -82
  91. package/internal/runtime-config.js +58 -0
  92. package/internal/runtime-contexts.js +85 -16
  93. package/internal/runtime-intrinsics.js +1 -0
  94. package/internal/runtime-messages.js +41 -0
  95. package/internal/sdk-projection.js +10 -82
  96. package/internal/session-cell-executor.js +42 -19
  97. package/internal/session-events.js +74 -4
  98. package/internal/session-journal-recovery.js +904 -184
  99. package/internal/session-journal-schema.js +6 -3
  100. package/internal/session-journal.js +31 -2
  101. package/internal/session-log-view.js +189 -43
  102. package/internal/session-runtime.js +175 -23
  103. package/internal/settings-compat.js +23 -6
  104. package/internal/stateful-module-compiler.js +3 -2
  105. package/internal/stateful-module-runtime.js +7 -3
  106. package/internal/stateful-root-runtime.js +9 -4
  107. package/internal/user-binding-authoring.js +2 -7
  108. package/internal/user-bindings-owner.js +47 -7
  109. package/internal/validated-parse-repair.js +1 -1
  110. package/internal/value-wire.js +68 -21
  111. package/internal/worker-cwd-virtualization.js +20 -18
  112. package/internal/worker-realm-surfaces.js +3 -3
  113. package/locale/en.json +6 -0
  114. package/locale/zh.json +6 -0
  115. package/package.json +55 -38
  116. package/src/client-authoring-view.js +286 -168
  117. package/src/client-binding-review.js +4 -1
  118. package/src/client-binding-toggle.js +18 -0
  119. package/src/client-capabilities-view.js +1341 -0
  120. package/src/client-capability-sort.js +29 -0
  121. package/src/client-code-editor.js +6 -0
  122. package/src/client-copy.js +881 -8
  123. package/src/client-feature-gates.js +15 -5
  124. package/src/client-host-compat.js +23 -2
  125. package/src/client-indicator-view.js +4 -6
  126. package/src/client-primitives.js +280 -0
  127. package/src/client-repl-view.js +23 -16
  128. package/src/client-settings-view.js +412 -69
  129. package/src/client-styles.js +193 -41
  130. package/src/client-tool-hover.js +185 -0
  131. package/src/client-tool-view.js +45 -11
  132. package/src/client-transport.js +44 -9
  133. package/src/client-workbench.js +9 -10
  134. package/src/client.js +148 -68
  135. package/assets/ptc-plus-bindings-en.png +0 -0
  136. package/assets/ptc-plus-bindings-zh.png +0 -0
  137. package/assets/ptc-plus-repair-en.png +0 -0
  138. package/assets/ptc-plus-repair-zh.png +0 -0
  139. package/client.js +0 -31857
@@ -1,112 +1,56 @@
1
1
  # Deterministic verification
2
2
 
3
- `npm run check` runs `npm run verify`, checks that the source tree and review ledger did not change, and records the verified tree for the commit hook. Run `check` alone when the ledger is absent or resolved; running `verify` immediately before it repeats the full suite. While the ledger has unresolved findings, use `npm run verify` to collect verification evidence.
3
+ Development and independent review run focused tests for the changed semantic owner and its consumers. An executable edit does not by itself require the complete suite. Complete behavioral validation runs on a stable executable candidate for delivery proof, an explicit full-validation/timing request, or a cross-suite risk that focused evidence cannot settle. [ADR 0036](adr/0036-separate-focused-development-from-delivery-verification.md) owns this scheduling decision.
4
4
 
5
- ## Incremental independent review
6
-
7
- Independent review is partitioned before reviewers start. Create the ignored working plan, replace every placeholder, then record it against the commit that defines the change scope:
8
-
9
- ```bash
10
- npm run review:plan:new
11
- npm run review:plan -- --base <base-commit>
12
- ```
13
-
14
- The plan uses `dsh-review-plan/v1`, and the ignored root `REVIEW_PLAN.json` is its only active source. All five standard obligations from the template must remain present. Each standard or change-specific obligation is assigned to a lane or excluded with a concrete reason. A lane declares semantic scope, exact file paths or directory prefixes, upstream lanes, owners, consumers, and counterexamples. `.` selects the complete checkout. Paths only determine content inputs and changed-path coverage; they do not replace the semantic declarations. A lane fingerprint covers that declaration, its assigned obligations, selected raw Git entry identities, persistent invalidation generation, and upstream fingerprints. Dependencies must form a DAG. The plan command refuses missing standard obligations, placeholders, unknown obligations, dependency cycles, a non-ancestor base, and changed paths outside every lane.
15
-
16
- The plan command prints one `review-lane <id> <head> <fingerprint>` line per lane. Lanes may be reviewed concurrently. Record each clean external report with the identity captured before that review:
17
-
18
- ```bash
19
- npm run review:lane -- \
20
- --lane <id> \
21
- --evidence <report-path> \
22
- --expect-head <head> \
23
- --expect-fingerprint <lane-fingerprint>
24
- ```
25
-
26
- The report's last non-empty line must be `VERDICT: NO FINDINGS`. A readable findings or incomplete report retires the named lane and every declared dependent from the recorded plan before checking whether the current candidate drifted; restoring an earlier tree therefore cannot revive evidence that the report rejected. An unreadable or missing report path is a command-input error and does not retire valid evidence. Unrelated lanes remain effective. A clean report retains the captured `HEAD` as provenance; an unrelated concurrent edit or `HEAD` advance does not prevent recording. Direct input, upstream fingerprint, scope, obligation, owner, consumer, counterexample, or dependency changes do. Put findings in `REVIEW_FINDINGS.md`, fix them, and rerun only the incomplete lanes shown by `npm run review:status`.
27
-
28
- Review-state mutations and authorization decisions use one atomic lock under checkout-local Git metadata. Plan recording, clean or negative lane recording, finalization, clearing, and pre-commit therefore have one observable order. A negative report queued behind an older clean recorder advances the reported lane's persistent invalidation `{epoch, revision}`, including while that lane is temporarily absent from the current plan, before retiring its current dependent closure. Advancement increments the revision normally and rotates to a new random epoch with revision zero at `Number.MAX_SAFE_INTEGER`, so a valid tuple never becomes unretirable. A temporarily absent lane must already have a retained tuple; unknown or mistyped lane IDs are rejected rather than creating state. Every active lane must have an explicit tuple. The tuple participates in the lane fingerprint and propagates through dependency fingerprints, so an older clean report cannot be submitted after re-addition. It survives normal `review:clear` and temporary lane removal; only a review captured after retirement can authorize the repaired fingerprint. Missing, partial, or malformed generation state blocks status and authorization. Explicit clear preserves valid generations but removes damaged generation state; plan recording then creates a new random epoch, so old reports still cannot match. The lock records a PID and unique release token and bounds acquisition, but never reclaims another owner automatically. If a command crashed, confirm that no review-state command is active, obtain the exact checkout-local path with `git rev-parse --git-path review-findings/state.lock`, remove only that directory, and retry. `review:status` remains a diagnostic snapshot and does not grant authorization. Finalization and pre-commit also re-read the active plan, candidate, proof, lane records, and evidence before they persist or return a decision, so edits made outside the review commands fail closed.
5
+ | Scope | Entry and required selection |
6
+ | --- | --- |
7
+ | Backend | `npm run test:backend:focus -- --file test/<owner>.test.js --test-name-pattern="<case>"` |
8
+ | Client | `npm run test:client:focus -- "<test-name pattern>"` |
9
+ | Layout | `npm run test:client:layout:focus -- --case <id>` |
10
+ | Fallback/native fixture | `npm run test:client:fallback:focus -- --case <id>` |
29
11
 
30
- Do not mutate a recorded boundary implicitly. Every status, clean lane verdict, finalization, and commit decision compares the normalized active file with the recorded plan. Missing, malformed, or semantically changed active content blocks clean authorization; it does not block a findings or incomplete report from retiring the recorded lane closure. Whitespace and object-key formatting alone do not change plan identity. When evidence reveals a missing path or semantic dependency, edit `REVIEW_PLAN.json` and run `npm run review:plan` without another `--base`; the command reuses the recorded base. The new plan hash invalidates its aggregate verdict, but clean lane records survive when their base, complete declaration, selected Git entries, upstream fingerprints, and evidence hashes are unchanged.
12
+ Backend and browser entries accept `--list`, and selections can repeat. Omitting selection from a focused execution fails before starting the suite. `test:semantics` selects six files and is not a quick diagnostic; choose its relevant file/case through `test:backend:focus`. An independent layout invocation exports fresh HTML through `test:client:fixtures`, which selects only the two tests that own all 26 states. Its browser assertions remain unchanged. The complete visual gate still runs the full Client suite once and reuses its exports. Fixtures supplied through `--fixtures-dir` must come from the current candidate.
31
13
 
32
- After `review:status` reports `clean`, run the deterministic gate once and bind the composite verdict to it:
14
+ `npm run check` classifies the complete source candidate against HEAD, runs the appropriate checks below, checks source/ledger stability, and records the verified tree and verification scope for the commit hook. Run `check` alone when the ledger is absent or resolved; while findings remain unresolved, use `verify`. Do not run complete component suites around the same final command: that duplicates its work. Focused evidence is not a source-bound commit proof. Inspect a failed gate's stage/case, reproduce it with a focused entry, repair it, and rerun the final command only after the candidate is stable.
33
15
 
34
- ```bash
35
- npm run check
36
- npm run review:finalize
37
- git diff --check HEAD --
38
- ```
16
+ Stage runners print inventory, concurrency and elapsed times. Start a permitted long gate with a short tool yield and retain its process/session ID. Tell the user the command, start time and expected range before waiting. Keep verbose diagnostics in ignored logs and wait on the same process in intervals no longer than 60 seconds. Report completed stages, failures or changed timing expectations. Inspect process activity and stage output before treating a slow run as stalled; elapsed time alone is insufficient. Stop only the owned process on cancellation, and never report an interrupted run as verified. Do not start another copy while waiting.
39
17
 
40
- The final verdict uses `dsh-review-verdict/v2`. Pre-commit verifies its plan hash, lane fingerprints, evidence hashes, whole source fingerprint, `HEAD`, and prospective index. A later `HEAD` change retires the proof and aggregate verdict. Source or index drift blocks the gate while it differs; restoring the exact finalized candidate preserves those records, while keeping a changed candidate requires another `check` and `review:finalize`. Only lanes whose effective inputs changed need another independent review. `npm run review:clear` abandons the recorded plan, lane records, aggregate verdict, and verification proof while leaving the working `REVIEW_PLAN.json` available for revision; persistent invalidation generations remain so pre-retirement reports cannot revive in a rebuilt plan. Clearing is not a normal retry step. The prior single-report `verdict` command and `dsh-review-verdict/v1` records are not accepted.
18
+ ## Change classification
41
19
 
42
- `npm run verify` and `npm run check` validate the ledger, compare generated bundles, check syntax, run Client tests, and run all backend tests with coverage. Backend test files run in separate Node processes. The default process count is half the available CPUs, rounded down and bounded between one and four; each process can also own runtime Workers. Tests within each file retain their existing ordering. Coverage still requires 100% lines and functions and at least 95% branches, including compiler source remapping. The graded merge additionally asserts that the plugin-source closure it claims — `index.js` and every `internal/*.js` owner — appears in the merged report, so a module no instrumented process loads fails the gate instead of silently leaving it. Generated artifacts stay outside that closure: the root `compiler-*.cjs` bundles and the `internal/*.cjs` shims are remapped onto their original `internal/` sources or verified by the bundle checks, and `scripts/migrate-session-log.mjs` is verified by its own migration suite.
20
+ `npm run verify:docs` checks a documentation-only candidate without starting builds, runtime tests, browsers or models. It rejects a mixed candidate instead of expanding into the full gate. `npm run check` chooses this same path automatically when every change from HEAD is a regular non-executable Markdown file in `docs/`, `AGENTS.md`, `CONTEXT.md`, `README.md` or `README.en.md`. Additions and deletions count, as do unstaged and untracked source files. Markdown fixtures, templates, symlinks, executable modes, configuration, tests and implementation changes select full verification. An explicit complete-validation request uses `npm run check -- --full` for a source proof or `npm run verify` for behavioral validation alone, regardless of changed paths. An unchanged tree also uses the full path when a new check is explicitly requested; reuse an existing matching proof instead.
43
21
 
44
- Coverage, the exhaustive backend suite, and the review-bound proof belong to the local pre-commit `npm run check`. CI starts from a clean lockfile install and runs `verify:ci` on every supported Node and operating-system entry: frozen-baseline validation, semantic ownership, generated builds, Client tests, and focused runtime contracts covering compiler startup, helper-process lifecycle, plugin takeover, native-call transport, and durable argument diagnostics. Ubuntu Node 24 additionally exercises the packed plugin against both frozen official `latest` and `next` DSH graphs. CI therefore proves portable installation and runtime integration without repeating coverage or the exhaustive semantic suite on hosted runners.
22
+ The documentation path checks diff whitespace, local link target existence and literal `npm run` commands in changed documents and documents linking to them. It does not execute code blocks, visit remote links, check heading anchors or establish semantic accuracy. Independent review checks anchors, contracts, examples and instruction behavior against their owners. A deleted document is checked through its remaining referrers. Source, `HEAD` and findings stay unchanged through verification; after that stability check, a resolved local findings ledger may be archived. The proof records `documentation` or `full`. `check` does not enforce the review plan, lane evidence or prospective-index identity: `review:finalize` enforces the first two after the proof, and pre-commit enforces the last against the staged tree. [ADR 0039](adr/0039-verify-documentation-without-runtime-suites.md) owns this distinction.
45
23
 
46
- `host:baseline:verify`, which is part of deterministic verification, is offline. It requires the installed root DSH `next` cohort to match `package-lock.json` and validates the tracked `compat/latest` declaration and lock without installing it. CI separately runs `test:host-contract`: the root graph exercises `next`, while a temporary consumer created from `compat/latest/package-lock.json` exercises `latest` against the packed plugin. Registry tag lookup and lock regeneration belong only to `host:baseline:update` and the scheduled baseline-refresh workflow. That workflow publishes a changed frozen candidate as a pull request and explicitly dispatches this CI workflow once because its `GITHUB_TOKEN`-authored events cannot start another run; ordinary gates never install a floating channel.
24
+ For test-description or comment changes, inspect the diff and verify that test registration, selectors, executable fixtures and assertions retain their meaning. Select changed cases when execution can distinguish a possible behavior change; cosmetic wording does not justify unrelated runtime or browser matrices. Runtime or verification-owner changes use their focused regressions. These development observations do not replace the candidate classification used for a later commit.
47
25
 
48
- Use focused coverage while developing or after a full gate identifies a narrow coverage failure. Select every source whose threshold matters and the smallest test set that can distinguish the correction:
26
+ ## Reading test output
49
27
 
50
- ```bash
51
- npm run coverage:focus -- \
52
- --source internal/source-position-map.js \
53
- --test test/source-position-map.test.js \
54
- --test test/compiler-memory-facts.test.js \
55
- --test test/stateful-source-line-mapping.test.js
56
- ```
28
+ Start with the selected inventory and a case pattern. Keep lengthy complete-run output in an ignored log, report the command, exit status, elapsed time and failures, then read the failing case's local context. Do not replay successful-case inventories or entire prior logs into progress messages and handoffs. Preserve exact failure text and its log location; summaries are not substitutes for evidence when diagnosing a failure. Test descriptions follow [the semantic naming guidance](semantic-validation.md#test-descriptions).
57
29
 
58
- Repeat `--source` and `--test` as needed. Paths must exist inside the checkout; sources must belong to the full coverage gate, and tests must be `test/*.test.js` files. Arguments after a standalone `--` are forwarded as Node test options. The focused runner uses the same compiler bytecode preparation, mock-dependent grouping, real Worker coverage instrumentation, source-map filtering, and 100% line/function plus 95% branch thresholds as the full runner. It includes selected but unexecuted sources as uncovered, so an empty report cannot pass. Its evidence and retained TAP diagnostics use the ignored `coverage/` directory.
30
+ ## Findings ledger
59
31
 
60
- Focused coverage establishes only the selected source-and-test relation. It does not establish full-suite compatibility and never replaces the final `verify` or `check`. A failed full gate must be diagnosed with the smallest check that reproduces its failure before another full run. Because `check` already invokes `verify`, running both consecutively against an unchanged tree repeats the full suite without adding evidence.
32
+ Before creating or updating findings, read [Findings ledger](review-procedure.md#findings-ledger).
61
33
 
62
- Source fingerprints use Git with temporary administrative files, an index and an object directory. Configuration includes and repository attributes preserve canonical content, and newly computed objects stay in the temporary directory. One tagged file inventory identifies tracked, missing, untracked and sparse entries without repeatedly starting Git for each category. The temporary files are written directly, avoiding repeated Git initialization and configuration processes; explicit Git command arguments preserve isolation across Windows/WSL command forwarders.
63
-
64
- Set `DSH_PTC_TEST_CONCURRENCY` to a positive integer to override the backend process count. Use `1` for serial diagnosis or when CPU/memory contention affects timing-sensitive tests. This changes scheduling, not runtime compute budgets, test selection, or coverage thresholds. For example:
65
-
66
- ```bash
67
- DSH_PTC_TEST_CONCURRENCY=1 npm run verify
68
- ```
69
-
70
- ```powershell
71
- $env:DSH_PTC_TEST_CONCURRENCY = '1'
72
- npm run verify
73
- Remove-Item Env:DSH_PTC_TEST_CONCURRENCY
74
- ```
75
-
76
- Coverage runs additionally set `DSH_PTC_TEST_WORKER_COVERAGE=1` and load `scripts/instrument-worker-coverage.mjs` through Node's `--import`; that test-only seam adds the coverage variable to the helper's inner-Worker launch environment so real kernel, console, and candidate worker branches are measured. Production never sets the marker and continues to project `NODE_V8_COVERAGE` out of the user Worker environment. Because some transport tests install module mocks before importing the real transport, the runner executes two backend groups into the same coverage directory: the mock-dependent files run without the preload, and the remaining files run with it. Each group writes its own `run-*-mock.tap` or `run-*-instrumented.tap` diagnostic, both managed by the same retention and cleanup lifecycle. Test arguments such as `--test-name-pattern` are placed before the selected file list so Node applies them to test selection rather than treating them as file inputs.
77
-
78
- `npm run coverage` and `npm run coverage:focus` print their current stage and total elapsed time. Each invocation collects worker coverage in its own temporary directory under `coverage/`, removed after c8 finishes reporting, so simultaneous runs cannot erase or combine each other's evidence. The default report is printed to the terminal. A separate `coverage/run-*.tap` log retains Node's test diagnostics, including process exit codes and signals that the concise terminal reporter can omit. An explicit Node test reporter option replaces these default reporters. Source changes during verification still invalidate a `check` proof; separate coverage directories do not make concurrent source edits safe.
79
-
80
- Windows Node 24 and 26 reproductions and stacks identify a cppgc finalizer (`node::contextify::ContextifyScript`, or `ContextifyContext` when coverage was enabled) running after the environment that owned its private realm was gone, which wrote through a dangling `node::Realm` during isolate teardown. The reproduction ended the whole DSH process when an in-host session worker was terminated; macOS runners also observed whole-file aborts under coverage, but the available evidence does not establish that those aborts came from the same Windows stack or that the same cause applies across platforms. The session kernel now runs in a plugin-owned helper process (`internal/kernel-child.js`); `IsolatedWorker` sends a cooperative shutdown and, if the helper does not leave within its bound, kills only that helper. DSH is no longer the worker-teardown target on any supported host, so a wedged kernel is contained as a helper failure rather than a host-native crash. The former file-level retry wrapper is gone, and any failing CI smoke or local exhaustive test fails its run. This boundary removes the plugin's in-host kill exposure; it neither repairs the upstream Node path nor proves the macOS abort shared the Windows cause.
81
-
82
- Native oracle workers and external Windows command fixtures explicitly disable inherited `NODE_V8_COVERAGE`; they execute no code included by the plugin coverage gate. Subprocesses testing plugin implementations still collect coverage. PowerShell semantic-version observations share one invocation, and Windows registry fixtures read the three effective registry values with one real npm configuration query. Each retains its original inputs and assertions.
34
+ ## Incremental independent review
83
35
 
84
- The reporting adapter filters script records before c8 merges them. It reads source-map metadata once per input file, retains maps that lead to selected original sources, and leaves unknown or malformed maps to c8. Dependency-only compiler artifacts can be excluded before remapping; mixed compiler bundles retain their original coverage records. c8 still owns merging, remapping, counters, reporting and threshold checks. This uses an isolated adapter for c8's normalization method; dependency upgrades must pass the comparison with ordinary c8, including uncovered counters and mapped sources. The raw V8 reports are not rewritten.
36
+ Before independent review or a review-state command, read [Incremental independent review](review-procedure.md#incremental-independent-review). Keep reviewer reports outside the checkout or in an ignored path so they cannot become candidate source after the plan is frozen. Both documentation and executable changes retain source-bound proof, independent review and prospective-index checks.
85
37
 
86
- Runtime Workers defer parsing the compiler bundle until their first compiler operation. Ordinary cells already prepared by the host avoid that startup work. Once an uninstrumented host has initialized its compiler, it snapshots V8 compiler bytecode for subsequent session kernel Workers. Coverage runs prepare that snapshot once in a separate uninstrumented process and pass its temporary file through `DSH_PTC_COMPILER_BYTECODE`; instrumented compiler instances neither produce nor consume bytecode. The runner owns this variable and removes the file after the tests. Workers receive private bytes through their initialization data and do not inherit the temporary file setting.
38
+ ## Complete behavioral validation
87
39
 
88
- A Worker uses a snapshot only when its complete compiler source matches; V8 checks bytecode compatibility and recompiles when it rejects the snapshot. Workers still initialize independent compiler realms and session state. The host retains one snapshot in memory, and each Worker discards its private copy after consumption; bytecode contains no saved program values. The bundle source, VM constructor and options are captured or isolated from source-owned mutations before execution.
40
+ The full local and CI behavioral gates run `test:client:visual`: one Client suite exports fresh renderer fixtures, followed by deterministic browser layout and visual assertions. Install its locked browser first with `npx playwright install --with-deps chromium`; missing browsers fail verification. The [Client visual contract](client-visual-contract.md) owns the bounded control/state inventory, reference palettes, fault counterexamples and official-Host evidence limits. Use the selection-required browser entries for focused diagnostics; `test:client:visual` is the complete gate and is not a development feedback loop.
89
41
 
90
- Repeated Global User Binding validation reuses export descriptions and durability analysis inside the private compiler realm. Each cache keys the complete source and transform generation, retains at most 64 entries and 512 Ki characters of serialized keys, and returns fresh data to its caller. Snapshot fields, fingerprints and publication evidence still undergo validation on each request. This cache contains source-derived metadata, never executed values or module state, and is rebuilt for every compiler realm.
42
+ `npm run verify` and the full branch of `npm run check` validate the ledger, compare generated bundles, check syntax, run Client tests, and run all ordinary backend tests through `npm run test:backend` without coverage instrumentation. The ordinary runner selects the sorted root `test/*.test.js` inventory. Coverage-only runner/report tests and collection probes live under `test/coverage/` and are excluded from daily execution. The release coverage runner includes both inventories and sets `DSH_PTC_TEST_COVERAGE_SUITE=1` for mixed instrumented cases; the ordinary runner clears that marker. Both runners share the concurrency calculation. Backend test files run in separate Node processes. Compiler boundary protocols are divided into disjoint storage, Promise and reflection files so the bounded file pool can execute them alongside the parameter/namespace/error cases without sharing their native or PTC realms. The default process count is half the available CPUs, rounded down and bounded between one and eight; each process can also own runtime Workers. Tests within each file retain their existing ordering. The ordinary runner enables module mocks without preloading coverage hooks and removes inherited coverage and temporary bytecode settings from its test-process environment.
91
43
 
92
- Cell preparation carries its successful native JavaScript parse proof into scope normalization, as module preparation already does. Decorator and resource transforms consume their feature scan's existing syntax tree through Babel's AST API. Recovered invalid decorator input retains the original parser diagnostic path. Intrinsic binding skips its source scan when the compiler recorded no bindings to replace. These changes remove repeated preparation work without changing execution budgets or coverage collection.
44
+ After the shared ledger, baseline, semantic, build and syntax prerequisites pass, `scripts/deterministic-test-groups.mjs` runs the complete Client visual and backend groups concurrently on machines with at least 12 available CPUs. Smaller machines retain serial phases; CI keeps its focused runtime group. Both concurrent groups must settle, and any nonzero exit, signal or startup failure rejects verification. Each browser pool retains its own CPU bound and process isolation.
93
45
 
94
- Nested callable reflection reuses completed ordinary function recipes inside enclosing recipes. A source-position index selects the outermost reusable descendants, and syntax inspection skips their complete AST subtrees. Each reused function already contains its generated helper dependencies; class and method definition recipes keep their separate lowering paths. If all non-native syntax is inside those substituted functions, the surrounding recipe needs no additional type-erasure pass. Enclosing type syntax and resource declarations still undergo their own lowering. Callable marker emission uses cumulative edit offsets and binary searches to retain exact source boundaries without rescanning all edits for each mapping point. Declaration analysis groups at most 64 declarations into each shared ancestry witness, so Babel assigns ownership without repeatedly walking the same ancestors or retaining a full expression graph.
46
+ The full branch of the local pre-commit `npm run check` runs the complete ordinary backend suite. Both verification scopes produce a review-bound proof. Coverage runs only before a new version is released, as specified by [ADR 0035](adr/0035-run-coverage-only-before-a-new-release.md). CI starts from a clean lockfile install and runs `verify:ci` on every supported Node and operating-system entry: frozen-baseline validation, semantic ownership, generated builds, Client tests, and focused runtime contracts covering compiler startup, helper-process lifecycle, plugin takeover, native-call transport, and durable argument diagnostics. Ubuntu Node 24 additionally exercises the packed plugin against both frozen official `latest` and `next` DSH graphs. Normal CI therefore proves portable installation and runtime integration without running coverage-specific suites, collecting coverage, or running the exhaustive semantic suite on hosted runners.
95
47
 
96
- Dynamic eval and Function preparation reuse source-derived compilation results within their private compiler realm. The key includes the complete source and syntax options. Every hit rechecks the original-source facts consulted by that compilation; a changed fact requires compilation again. The cache holds at most 64 entries and 2 Mi characters of serialized keys, results and source facts. Callers receive fresh metadata, and each execution still declares its variables and creates a new activation against the current environment. Cell operation plans remain with their existing compiler owner.
48
+ `host:baseline:verify`, which is part of deterministic verification, is offline. It requires the installed root DSH `next` cohort to match `package-lock.json` and validates the tracked `compat/latest` declaration and lock without installing it. CI separately runs `test:host-contract`: the root graph exercises `next`, while a temporary consumer created from `compat/latest/package-lock.json` exercises `latest` against the packed plugin. The tracked `compat/current` graph additionally captures the newest published official release, even when its distribution is on another npm channel; its isolated packed consumer is part of `test:host-contract`. Registry and official release lookup and lock regeneration belong only to `host:baseline:update` and the scheduled baseline-refresh workflow. That workflow publishes a changed frozen candidate as a pull request and explicitly dispatches this CI workflow once because its `GITHUB_TOKEN`-authored events cannot start another run; ordinary gates never install a floating channel.
97
49
 
98
- Optional-chain transforms allocate temporary names through the complete compilation unit's allocator. A scoped Babel adapter retains Babel's UID reservations and restores its original method when the transform completes or throws. This avoids a subsequent whole-scope rename for every generated temporary while preserving uniqueness across module regions.
50
+ ## Release coverage
99
51
 
100
- Callable catalogs compress windows containing referenced source ranges. Ranges separated by at most one source block share a window, preserving compression across nearby functions; large unreferenced gaps and unused buffers are omitted. Empty catalogs retain no source buffers. Entries keep their registration order and exact UTF-16 text in the existing catalog format.
52
+ Coverage runs only when preparing a new version. Read [Release coverage](release-coverage.md) before its commands; ordinary development, review and commit preparation do not run it.
101
53
 
102
- Cell preparation traverses parsed trees and edit mappings without allocating a
103
- temporary object per unit of work. The namespace walk reads a node's own keys
104
- once and indexes node arrays directly instead of materializing an entry pair
105
- per property; the source-map builder validates and writes its four offsets
106
- positionally, and replacement runs reach that builder through a direct emitter
107
- instead of a generator and a per-run segment object. Rehomed compiler messages
108
- write their owned data descriptor directly rather than copying a generic
109
- descriptor first. Every one of these paths produced identical output before and
110
- after the change; only the immediately discarded allocations are gone.
54
+ Browser scripts accept `--list` without launching a browser or generating fixtures, repeated exact `--case <id>` selections, and `--concurrency <positive integer>`. Default invocations execute every case. Focused layout measurements use `measurements-focused.json`, separate from complete layout evidence. Ordinary backend diagnostics accept repeated `--file <test/path.test.js>` and `--list`; unknown files and coverage-only paths are rejected before execution, while supported Node name/skip patterns, reporters and destinations, timeout, concurrency, only and force-exit options remain available. Additional positional paths, unknown options and coverage flags are rejected by the same parser for both CLI and library execution. For example, `npm run test:backend -- --file test/semantic-boundary-promises.test.js --test-name-pattern="promise catch"` runs only that responsibility through the ordinary environment owner. Focused runs cannot establish the complete verification proof.
111
55
 
112
- For focused diagnostics, use `npm run test:client`, `npm run test:semantics`, or Node's test runner with selected test files. These do not replace the final complete command. Model-backed evaluation remains separate and opt-in; see [Evaluation](evaluation.md).
56
+ For development diagnostics, use the selection-required entries above. `test:semantics`, `test:client:visual` and the default backend entry are complete inventories for their respective scopes, so they are not short feedback loops. Focused execution does not replace a requested delivery gate. Model-backed evaluation remains separate and opt-in; see [Evaluation](evaluation.md).
package/index.js CHANGED
@@ -9,7 +9,12 @@
9
9
  import Schema from '@deepseek-ai/schemastery'
10
10
  import { randomUUID } from 'node:crypto'
11
11
  import { createDirectSurfaceOwner } from './internal/direct-surface-owner.js'
12
- import { createRuntimeMessageOwner } from './internal/runtime-contexts.js'
12
+ import { CapabilityStatisticsStore } from './internal/capability-statistics-store.js'
13
+ import { createCapabilityEnvironment } from './internal/capability-environment.js'
14
+ import { capabilityCommandReceipt } from './internal/capability-command-receipt.js'
15
+ import { toolSdkSchemas } from './internal/program-bindings.js'
16
+ import { createRuntimeMessageOwner, handBackRuntimeSessions } from './internal/runtime-contexts.js'
17
+ import { createSessionLogOwner } from './internal/session-log-view.js'
13
18
  import { PTC_DELIVERY_CONTEXT } from './internal/runtime-messages.js'
14
19
  import { createCordisToolsOwner } from './internal/cordis-tools-owner.js'
15
20
  import { createEditTransportOwner, EDIT_RUN_CODE } from './internal/edit-transport-owner.js'
@@ -26,6 +31,7 @@ import { createReplMemoryProjection } from './internal/repl-memory-projection.js
26
31
  import { createUserBindingDraftProjection } from './internal/user-binding-draft-projection.js'
27
32
  import { createUserBindingsOwner } from './internal/user-bindings-owner.js'
28
33
  import { createHostRpc } from './internal/host-rpc.js'
34
+ import { RPC_CONTRACTS } from './internal/rpc-contract.js'
29
35
  import { createReplObservationInterest } from './internal/repl-observation-interest.js'
30
36
  import { guidancePolicies } from './internal/binding-update-policy.js'
31
37
  import * as dshSettings from '@deepseek-ai/dsh-settings'
@@ -50,7 +56,7 @@ export const Config = Schema.object(Object.fromEntries(
50
56
  * it under the service name of one generation, so `installExecutionSeam`
51
57
  * selects the live one.
52
58
  */
53
- export const inject = ['tools', 'systemPrompt', 'agents', 'llm']
59
+ export const inject = ['tools', 'systemPrompt', 'agents']
54
60
 
55
61
  function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durableReplay, cordisToolsEnabled }) {
56
62
  const looseTopLevelRedeclarations = bindingPolicy.variableRedeclarations
@@ -76,8 +82,8 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
76
82
  : 'Mixed new/existing top-level destructuring remains unsupported; separate the declaration from the assignment.'
77
83
  const cellConventions = languageSemantics === 'legacy-v1'
78
84
  ? `Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${variableRedeclaration} ${functionClassRedeclaration} ${splitSyntax}`
79
- : `Cells support TypeScript, top-level await, return, static import and top-level export. ${languageSemantics === 'stateful-v1'
80
- ? 'Declarations and assignments update the same logical binding within its scope, including const, functions, classes and imports. A bare declaration preserves an existing value. A declaration publishes its bindings after its whole pattern initializes; explicit assignments preserve actual partial writes. Imports follow their module until locally overwritten; importing again changes that same binding source. Existing closures observe later updates, while saved values retain their identity.'
85
+ : `Cells support TypeScript, top-level await, return, static import and top-level export. ${languageSemantics === 'stateful-v1'
86
+ ? 'Declarations and assignments update the same logical binding within its scope, including const, functions, classes and imports. A bare declaration preserves an existing value. A declaration publishes its bindings after its whole pattern initializes; assignments preserve partial writes. Imports follow their module until locally overwritten; importing again changes that same binding source. Existing closures observe later updates, while saved values retain their identity.'
81
87
  : 'Redeclaration protection is enabled: existing session names cannot be redeclared, and newly created const and import bindings reject assignment. Other declarations follow native scope rules. Previously writable bindings stay writable.'}`
82
88
  const recovery = durableReplay
83
89
  ? 'Direct Node/OS access remains live but is not replayed after a kernel restart.'
@@ -85,22 +91,22 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
85
91
  const cordisRecovery = cordisToolsEnabled
86
92
  ? 'Keep large Cordis plugin source in a binding before the tool call so it can be reused after an error. A Cordis parse or validation failure can follow external effects; repeat the call only when its retry rules and current results establish that it is safe.'
87
93
  : ''
88
- return `\`run_code\` continues one persistent PTC REPL. Ordinary top-level bindings remain available to later cells, so reuse them instead of resending setup code. Choose the smallest cell that answers the request and return only the value the next step needs.
94
+ return `\`run_code\` continues one persistent PTC REPL. Use Node.js modules in the cell; use external processes when their environment is needed. Ordinary top-level bindings remain available to later cells, so reuse them instead of resending setup code. Choose the smallest cell that answers the request and return only the value the next step needs.
89
95
 
90
- After an execution error, earlier statements may have taken effect. Inspect relevant values in a short cell and continue from them; repeat external operations only when their retry rules and current results establish that it is safe. \`edit_run_code\` edits and reruns a complete cell.
96
+ After an execution error, earlier statements may have taken effect. Inspect relevant values in a short cell, then continue from them; repeat external operations only when their retry rules and current results establish safety. \`edit_run_code\` edits and reruns a complete cell.
91
97
 
92
98
  ## Cell conventions
93
- Use \`return\` for an explicit result and \`console\` for logs. Keep large inspection results in bindings or reduce them to targeted excerpts: \`tools.read\` is bounded inspection, not a lossless whole-file reader. ${cellConventions}
99
+ Use \`return\` for an explicit result and \`console\` for logs. Return supported values directly, including undefined, NaN, infinities, -0 and BigInt; JSON.stringify loses distinctions or rejects BigInt. Do not mix BigInt with Number arithmetic; use a BigInt literal or convert explicitly. Keep functions, Date, Map and other unsupported values in bindings; return needed data or type observations. Native \`tools.*\` arguments must satisfy their own JSON schemas; PTC value transport does not widen those contracts. Keep large inspection results in bindings or reduce them to targeted excerpts: \`tools.read\` is bounded inspection, not a lossless whole-file reader. Relative filesystem, path and default child-process operations use the current working directory at the time they run; a successful in-cell \`tools.working_directory({ cd })\` applies to the rest of that cell, while cell imports and \`import.meta\` keep the directory the cell started in, and \`process.chdir\` is unavailable. ${cellConventions}
94
100
 
95
101
  ## Available capabilities
96
- Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` for unfamiliar program APIs. Prefer direct current-cell work; reserve \`code.run\` for isolated execution of source held as data.
97
- Program API references expire when their cell ends. Reusable helpers must read the current namespace when called, for example \`async function readNow(args) { return tools.read(args) }\`, rather than retaining an earlier \`tools\` object or method. Background callbacks from completed cells cannot call program APIs.
102
+ Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` for unfamiliar program APIs; Reserve \`code.run\` for isolated execution of source held as data.
103
+ Program API references expire when their cell ends. Reusable helpers must read the current namespace when called, for example \`async function readNow(args) { return tools.read(args) }\`, instead of retaining an earlier \`tools\` object or method. Completed-cell callbacks cannot call program APIs.
98
104
 
99
- Native tool availability, executable names, shells, and path syntax depend on the current DSH profile and execution world; inspect them instead of assuming Windows, WSL, POSIX, or a particular shell. ${recovery}${cordisToolsEnabled ? ` ${cordisRecovery}` : ''}`
105
+ Native tool availability depends on the current DSH profile and execution world; inspect executables, shells, and paths instead of assuming Windows, WSL, POSIX, or a particular shell. ${recovery}${cordisToolsEnabled ? ` ${cordisRecovery}` : ''}`
100
106
  }
101
107
 
102
108
  /** Register the session-bound REPL runtime. */
103
- function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessionId) {
109
+ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, settingsForCommand) {
104
110
  const presentationGeneration = randomUUID()
105
111
  const replMemoryProjection = createReplMemoryProjection(presentationGeneration)
106
112
  const userBindingDraftProjection = createUserBindingDraftProjection(presentationGeneration)
@@ -111,6 +117,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
111
117
  let runtimeBridge
112
118
  let editTransport
113
119
  let directSurface
120
+ let capabilityEnvironment
114
121
  let userBindings
115
122
  let observationInterest
116
123
  let ready
@@ -331,6 +338,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
331
338
  ready = awaitCordisToolsOwner(initialCordisTools, new Promise(() => {}))
332
339
  }
333
340
  userBindings = createUserBindingsOwner(ctx, {
341
+ readSessionLog: sessionLog.read,
334
342
  enabled: activeConfig.userBindingsEnabled,
335
343
  draftProjectionAvailable,
336
344
  maxWallMs: activeConfig.maxWallMs,
@@ -338,32 +346,43 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
338
346
  maxOldGenerationSizeMb: activeConfig.maxOldGenerationSizeMb,
339
347
  valueLimits: valueLimitsFromConfig(activeConfig),
340
348
  })
349
+ capabilityEnvironment = createCapabilityEnvironment({ ctx, config: activeConfig,
350
+ toolSchemasForAgent: toolSdkSchemasForAgent, modelSchemasForAgent: toolSchemasForAgent,
351
+ store: new CapabilityStatisticsStore({ ctx }) })
341
352
  runtimeBridge = createRuntimeBridgeOwner({
342
353
  ctx,
343
354
  seam,
344
355
  observeSession: id => observationInterest.has(id),
356
+ readSessionLog: sessionLog.read,
345
357
  sessionConfig: activeConfig,
346
358
  userBindingsCwd: userBindings.cwd,
347
359
  maxNestedRunCodeDepth: activeConfig.maxNestedRunCodeDepth,
348
360
  presentationGeneration,
349
361
  sessionId,
350
- toolSchemasForAgent,
362
+ toolSchemasForAgent: toolSdkSchemasForAgent,
363
+ capabilityEnvironment,
351
364
  userBindingDraftForAgent: agent => userBindings.draftCapabilityForAgent(agent),
352
365
  bindingSubmissionForAgent: (agent, ensureLease, onAccepted) => userBindings.submissionForAgent(agent, ensureLease, onAccepted),
353
366
  })
354
367
  editTransport = createEditTransportOwner(ctx, {
368
+ readSessionLog: runtimeBridge.readNestedDispatchLog,
369
+ sessionLogView: sessionLog.project,
355
370
  durableReplay: activeConfig.durableReplay,
356
371
  executeTentative: runtimeBridge.executeTentative,
372
+ canScheduleEdit: runtimeBridge.canScheduleEdit,
373
+ scheduleEdit: runtimeBridge.scheduleEdit,
357
374
  presentationGeneration,
358
375
  sessionId,
359
376
  toolSchemasForAgent,
360
377
  })
361
378
  directSurface = createDirectSurfaceOwner({
379
+ sessionLogView: sessionLog.project,
362
380
  editTransport,
363
381
  runtimeConfig: activeConfig,
364
382
  canonicalizeToolCalls: activeConfig.canonicalizeToolCalls,
365
383
  sessionId,
366
384
  toolSchemasForAgent,
385
+ capabilityEnvironment,
367
386
  userBindingsForAgent: () => userBindings.snapshot(),
368
387
  setAgentPresentation: (agent, presentation) => (
369
388
  userBindings.setAgentPresentation(agent, presentation)
@@ -377,9 +396,89 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
377
396
  return replGuidance(guidancePolicies(activeConfig))
378
397
  },
379
398
  }))
380
- disposers.push(ctx.on('system-prompt/assemble', (assembly, context, next) => (
381
- directSurface.assemble(assembly, context, next)
382
- )))
399
+ disposers.push(ctx.on('system-prompt/assemble', async (assembly, context, next) => {
400
+ const projected = await directSurface.assemble(assembly, context, next)
401
+ if (ctx.tools.get(RUN_CODE, context?.agent) !== undefined) runtimeBridge.prepareAgent(context?.agent)
402
+ return projected
403
+ }))
404
+ disposers.push(() => capabilityEnvironment.dispose())
405
+ disposers.push(ctx.on('agent/pre-step', (payload, next) => capabilityEnvironment.preStep(payload, next), { prepend: true }))
406
+ disposers.push(ctx.on('agent/created', ({ agent }) => {
407
+ if (ctx.tools.get(RUN_CODE, agent) !== undefined) {
408
+ runtimeBridge.prepareAgent(agent)
409
+ editTransport.ensureInstalled(agent)
410
+ }
411
+ }))
412
+ for (const agent of ctx.agents?.list?.() ?? []) {
413
+ if (ctx.tools.get(RUN_CODE, agent) !== undefined) {
414
+ runtimeBridge.prepareAgent(agent)
415
+ editTransport.ensureInstalled(agent)
416
+ }
417
+ }
418
+ disposers.push(ctx.on('agent/pre-step', (payload, next) => userBindings.preStep(payload, next)))
419
+ disposers.push(ctx.on('session/event', (session, event) => userBindings.committedEvent(session, event)))
420
+ disposers.push(ctx.on('tools/post-execute', (exec, result, next) => capabilityEnvironment.postExecute(exec, result, next), { prepend: true }))
421
+ disposers.push(ctx.on('tools/ptc-dispatch-log', (dispatch, next) => capabilityEnvironment.dispatchLog(dispatch, next), { prepend: true }))
422
+ disposers.push(ctx.on('agent/status', payload => { if (payload?.agent) capabilityEnvironment.status(payload.agent, payload.status) }))
423
+ disposers.push(ctx.on('agent/created', ({ agent }) => capabilityEnvironment.status(agent, agent.status)))
424
+ if (typeof ctx.inject === 'function') {
425
+ const capabilitiesRpc = ctx.inject(['ptcPlusRpc'], scope => {
426
+ scope.effect(() => scope.ptcPlusRpc.register(RPC_CONTRACTS.capabilities, async (operation, payload, signal) => {
427
+ signal.throwIfAborted()
428
+ if (operation === 'catalog') return { ok: true, value: await capabilityEnvironment.describeCatalog(signal) }
429
+ if (operation === 'targets') return { ok: true, value: capabilityEnvironment.describeTargets() }
430
+ if (operation === 'session-catalog') return { ok: true, value: await capabilityEnvironment.describeSessionCatalog(payload, signal) }
431
+ if (operation === 'allocation') return { ok: true, value: await capabilityEnvironment.observeAllocation(payload, signal) }
432
+ if (operation !== 'inspect') return { ok: false, error: { code: 'UNKNOWN_OPERATION', message: 'Unknown capability plan operation', details: null } }
433
+ return { ok: true, value: capabilityEnvironment.describeSessions() }
434
+ }), 'ptc-plus: capability plan observation')
435
+ })
436
+ if (typeof capabilitiesRpc === 'function') disposers.push(capabilitiesRpc)
437
+ else if (typeof capabilitiesRpc?.dispose === 'function') disposers.push(() => capabilitiesRpc.dispose())
438
+ const commands = ctx.inject(['commands'], scope => {
439
+ scope.effect(() => scope.commands.register({ name: 'capabilities',
440
+ description: 'Inspect, search or change session capabilities; global choices also save settings.',
441
+ input: { hint: 'refresh | list [all|skills|tools|mcp|mcp_tools] [query] | set session|global <id> [availability|disclosure] <choice>' },
442
+ handler: async ({ commandId, agent, rawInput, signal }) => {
443
+ const input = rawInput.trim()
444
+ const receipt = (state, error) => capabilityCommandReceipt(state, String(agent.session.id), error,
445
+ state === 'pending' && capabilityEnvironment.pendingChoiceMessage(agent)
446
+ ? { commandId, rawInput, message: capabilityEnvironment.pendingChoiceMessage(agent) } : undefined)
447
+ const selection = /^set (session|global) (\S+) (?:(availability|disclosure) )?(full|on-demand|closed|automatic|inherit|enabled|host|adaptive)$/.exec(input)
448
+ if (selection) {
449
+ if (!capabilityEnvironment.canRefresh(agent)) return { kind: 'error', text: receipt('unavailable') }
450
+ const changed = await capabilityEnvironment.change(agent, {
451
+ scope: selection[1], id: decodeURIComponent(selection[2]), dimension: selection[3], selection: selection[4], commandId,
452
+ }, signal, settingsForCommand?.())
453
+ return { kind: changed.sessionApplied ? 'success' : 'error',
454
+ text: receipt(changed.sessionApplied ? changed.publication === 'pending' ? 'pending' : 'recorded' : 'saved-only', changed.error) }
455
+ }
456
+ const listing = /^list(?: (all|skills|tools|mcp|mcp_tools))?(?: (.*))?$/.exec(input)
457
+ if (listing) {
458
+ const entries = await capabilityEnvironment.catalogFor(agent, signal)
459
+ const query = (listing[2] ?? '').toLocaleLowerCase()
460
+ const kind = listing[1] ?? 'all'
461
+ return { kind: 'success', text: JSON.stringify({ schema: 'ptc-capability-catalog/v1', entries: entries.filter(entry => (
462
+ kind === 'all' || kind === 'skills' && entry.category === 'skill'
463
+ || kind === 'tools' && entry.category === 'tool' && entry.source?.kind !== 'mcp'
464
+ || kind === 'mcp' && ['server', 'resource'].includes(entry.category)
465
+ || kind === 'mcp_tools' && entry.category === 'tool' && entry.source?.kind === 'mcp'
466
+ ) && (!query || `${entry.id} ${entry.description}`.toLocaleLowerCase().includes(query))) }) }
467
+ }
468
+ if (input !== '' && input !== 'refresh') return { kind: 'error', text: receipt('usage') }
469
+ if (input === 'refresh') {
470
+ if (!capabilityEnvironment.canRefresh(agent)) {
471
+ return { kind: 'error', text: receipt('unavailable') }
472
+ }
473
+ await capabilityEnvironment.refresh(agent, undefined, signal, commandId)
474
+ return { kind: 'success', text: receipt(capabilityEnvironment.describe(agent).publication === 'pending' ? 'pending' : 'recorded') }
475
+ }
476
+ return { kind: 'success', text: JSON.stringify(capabilityEnvironment.describe(agent), null, 2) }
477
+ } }), 'ptc-plus capability command')
478
+ })
479
+ if (typeof commands === 'function') disposers.push(commands)
480
+ else if (typeof commands?.dispose === 'function') disposers.push(() => commands.dispose())
481
+ }
383
482
  disposers.push(ctx.on('llm/stream', (options, next) => directSurface.stream(options, next), { global: true }))
384
483
  disposers.push(ctx.on('tools/execute', (exec, next) => {
385
484
  const rejected = directSurface.executionRejection(exec)
@@ -395,9 +494,13 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
395
494
  ))
396
495
  .then(result => directSurface.argumentDiagnostic(exec, result))
397
496
  }
497
+ if (exec.name === EDIT_RUN_CODE) return runtimeBridge.withSubmission(exec, next)
498
+ capabilityEnvironment.validateSupply(exec.agent, exec.name, exec.arguments)
499
+ capabilityEnvironment.admitted(exec)
398
500
  return next()
399
501
  }))
400
502
  disposers.push(ctx.on('tools/result', (exec, result) => {
503
+ capabilityEnvironment.dispatched(exec, result)
401
504
  directSurface.handleResult(exec)
402
505
  if (exec.name === EDIT_RUN_CODE) return editTransport.handleResult(exec, result)
403
506
  if (exec.name === RUN_CODE) return runtimeBridge.handleResult(exec, result)
@@ -416,6 +519,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
416
519
  () => editTransport.disposeAgent(agent),
417
520
  () => runtimeBridge.disposeAgent(agent),
418
521
  () => userBindings.clearAgentPresentation(agent),
522
+ () => capabilityEnvironment.disposeAgent(agent),
419
523
  ], 'ptc-plus: agent disposal failed')))
420
524
  disposers.push(ctx.on('session/disposed', session => disposeLifecycleOwners([
421
525
  () => directSurface.disposeSession(session),
@@ -457,8 +561,6 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
457
561
  try {
458
562
  runtimeBridge.reconfigure(nextConfig)
459
563
  rollbacks.push(() => runtimeBridge.reconfigure(previousConfig))
460
- editTransport.reconfigure(nextConfig)
461
- rollbacks.push(() => editTransport.reconfigure(previousConfig))
462
564
  directSurface.reconfigure(nextConfig)
463
565
  rollbacks.push(() => directSurface.reconfigure(previousConfig))
464
566
  await userBindings.reconfigure(nextConfig)
@@ -488,6 +590,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
488
590
  }
489
591
  }
490
592
  activeConfig = nextConfig
593
+ capabilityEnvironment.reconfigure(nextConfig)
491
594
  observationInterest.reconfigure(nextConfig.replViewEnabled)
492
595
  } catch (error) {
493
596
  const rollbackFailures = []
@@ -527,9 +630,13 @@ export const apply = (ctx, config = {}) => {
527
630
 
528
631
  /** Install the plugin into the scope of the seam the host registered. */
529
632
  function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatileConfig) {
633
+ let settingsWriter
634
+ const sessionLog = createSessionLogOwner(ctx)
530
635
  const toolSchemasForAgent = agent => typeof ctx.tools.schemas === 'function'
531
636
  ? ctx.tools.schemas(agent)
532
637
  : []
638
+ // SDK contracts include output schemas; model-facing schemas remain unchanged.
639
+ const toolSdkSchemasForAgent = agent => toolSdkSchemas(ctx.tools, agent)
533
640
  const sessionId = agent => {
534
641
  const id = agent?.session?.id ?? agent?.id
535
642
  return id === undefined ? undefined : String(id)
@@ -613,7 +720,7 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
613
720
  }
614
721
  let candidate
615
722
  try {
616
- candidate = installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, sessionId)
723
+ candidate = installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter)
617
724
  record.runtime = candidate
618
725
  } catch (error) {
619
726
  const failedRuntime = error?.[INSTALL_CLEANUP]
@@ -755,9 +862,10 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
755
862
  }
756
863
  ctx.effect(() => async () => controller.dispose(), 'ptc-plus runtime lifecycle')
757
864
 
865
+ const handbackOwners = Object.fromEntries(['sessions', 'sessionPersistence', 'sessionQuery'].map(name => [name, ctx.get(name)]))
758
866
  const messageOwner = createRuntimeMessageOwner(context => (
759
867
  committed?.retiring ? [] : committed?.runtime.contextsForRequest(context) ?? []
760
- ))
868
+ ), sessionLog.project, () => committed !== undefined && committed.retiring !== true, handbackOwners.sessionQuery)
761
869
  ctx.effect(() => ctx.systemPrompt.context({
762
870
  name: PTC_DELIVERY_CONTEXT, order: 98, text: '',
763
871
  }), 'ptc-plus dynamic message delivery witness')
@@ -769,10 +877,13 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
769
877
  )), 'ptc-plus accepted messages')
770
878
  ctx.effect(() => ctx.on('agent/disposed', ({ agent }) => messageOwner.disposeAgent(agent)), 'ptc-plus message agent disposal')
771
879
  ctx.effect(() => () => messageOwner.dispose(), 'ptc-plus dynamic message lifecycle')
880
+ ctx.effect(() => async () => {
881
+ await controller.dispose()
882
+ await handBackRuntimeSessions(ctx, handbackOwners)
883
+ }, 'ptc-plus provider handback')
772
884
 
773
885
  let configSource = () => resolveConfig(volatileConfig ?? resolvedConfig)
774
886
  let configurationGeneration = 0
775
- let settingsWriter
776
887
  let configurationRollback = false
777
888
  const reportActivationFailure = (error) => {
778
889
  ctx.logger?.warn?.('ptc-plus: runtime activation failed', error)
@@ -788,7 +899,7 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
788
899
  ? { enabled: false }
789
900
  : Object.fromEntries(CONFIG_FIELDS.map(field => [field.key, previousConfig[field.key]]))
790
901
  try {
791
- await settingsWriter.update(SETTINGS_NAMESPACE, patch)
902
+ await settingsWriter.update(patch)
792
903
  } catch (rollbackError) {
793
904
  reportActivationFailure(new Error(
794
905
  `ptc-plus: failed to roll back runtime configuration: ${rollbackError.message}`,
@@ -840,11 +951,24 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
840
951
  },
841
952
  onChange: reconcile,
842
953
  },
843
- onProvider(provider) {
844
- settingsWriter = provider
954
+ onProvider(_provider, writer) {
955
+ settingsWriter = writer
956
+ return () => { if (settingsWriter === writer) settingsWriter = undefined }
845
957
  },
846
958
  })
847
959
  }
960
+ ctx.effect(() => seam.onSourceChange(() => {
961
+ const current = resolveConfig(configSource())
962
+ if (!current.enabled) return undefined
963
+ if (seam.language !== 'typescript') {
964
+ const language = seam.language
965
+ const cleanup = controller.uninstall()
966
+ reportActivationFailure(new Error('ptc-plus: unsupported code runtime language '
967
+ + JSON.stringify(language) + '; only "typescript" is supported'))
968
+ return cleanup
969
+ }
970
+ return reconcile(true)
971
+ }), 'ptc-plus original language lifecycle')
848
972
  watchVolatileConfig(volatileHost, ctx, volatileConfig, () => reconcile())
849
973
  if (configurationGeneration === 0) return reconcile(settingsWriter === undefined)
850
974
  }
@@ -0,0 +1,24 @@
1
+ import { isPtcMessageSource, PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
2
+
3
+ const PREFIX = 'Global binding configuration saved. This does not prove model catalog delivery or worker initialization:\n'
4
+
5
+ export function bindingToggleMessage(entry, revision) {
6
+ const receipt = { schema: 'ptc-binding-toggle/v1', id: entry.id, name: entry.name, enabled: entry.enabled, revision }
7
+ if (!valid(receipt)) throw new TypeError('invalid binding toggle receipt')
8
+ return { source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'binding-toggle', receipt },
9
+ content: [{ type: 'text', text: PREFIX + JSON.stringify(receipt) }] }
10
+ }
11
+
12
+ function valid(receipt) {
13
+ return receipt?.schema === 'ptc-binding-toggle/v1' && typeof receipt.id === 'string' && receipt.id.length > 0
14
+ && receipt.id.length <= 64 && typeof receipt.name === 'string' && receipt.name.length <= 256
15
+ && typeof receipt.enabled === 'boolean' && Number.isSafeInteger(receipt.revision) && receipt.revision >= 0
16
+ && Object.keys(receipt).length === 5
17
+ }
18
+
19
+ export function readBindingToggle(message) {
20
+ const receipt = message?.source?.receipt
21
+ return isPtcMessageSource(message?.source) && message.source.form === 'binding-toggle' && valid(receipt)
22
+ && message.content?.length === 1 && message.content[0]?.type === 'text'
23
+ && message.content[0].text === PREFIX + JSON.stringify(receipt) ? receipt : undefined
24
+ }