dsh-ptc-plus 0.4.10 → 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 (122) hide show
  1. package/README.en.md +60 -130
  2. package/README.md +60 -130
  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 +1 -0
  11. package/docs/README.md +6 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
  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 +1 -1
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +2 -0
  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 +25 -23
  23. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  24. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  25. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  26. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  27. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  28. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  29. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  30. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  31. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  32. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  33. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  34. package/docs/adr/README.md +6 -0
  35. package/docs/architecture.md +23 -25
  36. package/docs/capability-planning-components.md +93 -0
  37. package/docs/capability-projection.md +7 -0
  38. package/docs/client-ui.md +70 -23
  39. package/docs/client-visual-contract.md +26 -9
  40. package/docs/context-management.md +22 -0
  41. package/docs/context-optimization-plan.md +353 -0
  42. package/docs/context-public-interface-coverage.md +25 -0
  43. package/docs/durability-design.md +20 -15
  44. package/docs/evaluation.md +41 -6
  45. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  46. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  47. package/docs/installation.md +31 -10
  48. package/docs/program-data-plane.md +5 -3
  49. package/docs/publishing.md +5 -5
  50. package/docs/release-coverage.md +73 -0
  51. package/docs/release-notes.md +1 -1
  52. package/docs/review-procedure.md +56 -0
  53. package/docs/runtime-reference.md +48 -16
  54. package/docs/semantic-validation.md +14 -2
  55. package/docs/stateful-binding-updates-proposal.md +2 -2
  56. package/docs/user-bindings.md +1 -1
  57. package/docs/value-wire.md +11 -1
  58. package/docs/verification-optimization-work.md +26 -15
  59. package/docs/verification.md +30 -88
  60. package/index.js +123 -20
  61. package/internal/binding-toggle-receipt.js +24 -0
  62. package/internal/capability-command-receipt.js +29 -0
  63. package/internal/capability-environment.js +1247 -0
  64. package/internal/capability-plan.js +285 -0
  65. package/internal/capability-policy.js +96 -0
  66. package/internal/capability-statistics-store.js +180 -0
  67. package/internal/capability-statistics.js +210 -0
  68. package/internal/config-spec.js +79 -9
  69. package/internal/direct-surface-owner.js +27 -29
  70. package/internal/dynamic-environment-runtime.js +9 -9
  71. package/internal/edit-transport-owner.js +180 -139
  72. package/internal/execution-provider-owner.js +15 -12
  73. package/internal/execution-seam-compat.js +8 -8
  74. package/internal/kernel-worker.js +104 -32
  75. package/internal/native-root-dynamic.js +12 -13
  76. package/internal/program-bindings.js +190 -0
  77. package/internal/recovery-tips.js +3 -2
  78. package/internal/rejected-cell-editor.js +8 -8
  79. package/internal/repl-observation-interest.js +11 -3
  80. package/internal/rpc-contract.js +1 -0
  81. package/internal/runtime-bridge-owner.js +272 -34
  82. package/internal/runtime-config.js +58 -0
  83. package/internal/runtime-contexts.js +74 -5
  84. package/internal/runtime-intrinsics.js +1 -0
  85. package/internal/runtime-messages.js +41 -0
  86. package/internal/sdk-projection.js +10 -82
  87. package/internal/session-cell-executor.js +42 -19
  88. package/internal/session-events.js +34 -0
  89. package/internal/session-journal-recovery.js +234 -11
  90. package/internal/session-journal-schema.js +6 -3
  91. package/internal/session-journal.js +31 -2
  92. package/internal/session-log-view.js +1 -1
  93. package/internal/session-runtime.js +119 -15
  94. package/internal/settings-compat.js +12 -5
  95. package/internal/stateful-module-compiler.js +3 -2
  96. package/internal/stateful-module-runtime.js +7 -3
  97. package/internal/stateful-root-runtime.js +9 -4
  98. package/internal/user-binding-authoring.js +2 -7
  99. package/internal/user-bindings-owner.js +41 -2
  100. package/internal/validated-parse-repair.js +1 -1
  101. package/internal/value-wire.js +68 -21
  102. package/internal/worker-cwd-virtualization.js +20 -18
  103. package/internal/worker-realm-surfaces.js +3 -3
  104. package/package.json +47 -29
  105. package/src/client-authoring-view.js +67 -30
  106. package/src/client-binding-review.js +2 -1
  107. package/src/client-binding-toggle.js +18 -0
  108. package/src/client-capabilities-view.js +1341 -0
  109. package/src/client-capability-sort.js +29 -0
  110. package/src/client-copy.js +871 -8
  111. package/src/client-feature-gates.js +3 -0
  112. package/src/client-indicator-view.js +4 -6
  113. package/src/client-primitives.js +33 -2
  114. package/src/client-repl-view.js +20 -13
  115. package/src/client-settings-view.js +411 -41
  116. package/src/client-styles.js +108 -15
  117. package/src/client-tool-hover.js +185 -0
  118. package/src/client-tool-view.js +37 -8
  119. package/src/client-transport.js +44 -9
  120. package/src/client-workbench.js +6 -7
  121. package/src/client.js +76 -22
  122. package/client.js +0 -32479
@@ -2,19 +2,23 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- `npm run coverage:focus` provides a bounded diagnostic loop for executable
6
- changes. It checks selected gate-owned sources with the smallest test set that
7
- can distinguish a correction before the repository-wide release gate runs.
5
+ `npm run coverage:focus` provides a bounded diagnostic loop for coverage
6
+ failures while preparing a new version for release. It checks selected
7
+ gate-owned sources with the smallest test set that can distinguish a correction
8
+ before the full release coverage gate runs again.
8
9
 
9
- The focused command does not replace `npm run verify` or `npm run check` and
10
- does not write a verified-tree proof. [Deterministic verification](verification.md)
11
- owns command syntax, thresholds, generated evidence, and the final gate.
10
+ Coverage is not part of ordinary development, review, commit preparation,
11
+ `npm run verify`, or `npm run check`; coverage-specific suites and instrumented cases are also excluded from those daily entries. The focused command does not replace the
12
+ full release coverage gate and does not write a verified-tree proof.
13
+ [Release coverage](release-coverage.md) owns coverage command syntax, thresholds,
14
+ generated evidence, and the final gates; [ADR 0035](adr/0035-run-coverage-only-before-a-new-release.md)
15
+ owns coverage scheduling.
12
16
 
13
17
  ## Contract
14
18
 
15
19
  - Every selected source and test must exist inside the checkout.
16
20
  - Selected sources must belong to the full coverage gate, and selected tests
17
- must be `test/*.test.js` files.
21
+ must be `.test.js` files under `test/`.
18
22
  - Selected but unexecuted sources remain in the report as uncovered.
19
23
  - The report applies the full gate's 100% line/function and 95% branch
20
24
  thresholds.
@@ -52,15 +56,22 @@ preload does not independently reinterpret a caller's entry.
52
56
 
53
57
  ## Workflow
54
58
 
55
- Use focused coverage after the nearest behavioral test passes or after a full
56
- gate identifies a narrow coverage deficit. Select every changed source whose
57
- threshold matters and the tests that exercise its distinct paths. A passing
58
- focused run establishes only that selected relation.
59
+ During ordinary development, use the selection-required behavioral entries in
60
+ [verification](verification.md) without coverage. Executable changes alone do not
61
+ trigger a complete gate; [ADR 0036](adr/0036-separate-focused-development-from-delivery-verification.md)
62
+ owns the development versus delivery boundary.
63
+ During release preparation, use focused coverage after the full release coverage
64
+ gate identifies a narrow deficit. Select every changed source whose threshold
65
+ matters and the tests that exercise its distinct paths. A passing focused run
66
+ establishes only that selected relation; rerun the full coverage gate before
67
+ packing and staging.
59
68
 
60
- Run one final `npm run verify` while the local review ledger has unresolved
61
- findings. Once every finding is terminal, run `npm run check` on a stable tree
62
- to produce the proof consumed by the commit hook. Do not run `verify`
63
- immediately before `check` on the same tree because `check` already invokes it.
69
+ When complete validation is requested for delivery, timing or an identified
70
+ cross-suite risk, run one final command on the stable candidate: `npm run verify`
71
+ with unresolved findings, otherwise `npm run check -- --full`. Terminal findings alone do
72
+ not trigger another gate. A still-matching proof is reused for delivery; focused
73
+ results never replace it. Do not run `verify` immediately before `check` on the
74
+ same tree because the full check already invokes it.
64
75
 
65
76
  Temporary concurrency overrides change scheduling only. Use one only when
66
77
  current evidence identifies scheduling contention, and do not carry it into a
@@ -1,114 +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
- The standard local and CI 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. Focused diagnostics may run `npm run test:client:visual` independently; they do not replace the final gate.
8
-
9
- 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:
10
-
11
- ```bash
12
- npm run review:plan:new
13
- npm run review:plan -- --base <base-commit>
14
- ```
15
-
16
- 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.
17
-
18
- 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:
19
-
20
- ```bash
21
- npm run review:lane -- \
22
- --lane <id> \
23
- --evidence <report-path> \
24
- --expect-head <head> \
25
- --expect-fingerprint <lane-fingerprint>
26
- ```
27
-
28
- 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`.
29
-
30
- 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>` |
31
11
 
32
- 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.
33
13
 
34
- 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.
35
15
 
36
- ```bash
37
- npm run check
38
- npm run review:finalize
39
- git diff --check HEAD --
40
- ```
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.
41
17
 
42
- 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
43
19
 
44
- `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.
45
21
 
46
- 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.
47
23
 
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. 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.
49
25
 
50
- 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
51
27
 
52
- ```bash
53
- npm run coverage:focus -- \
54
- --source internal/source-position-map.js \
55
- --test test/source-position-map.test.js \
56
- --test test/compiler-memory-facts.test.js \
57
- --test test/stateful-source-line-mapping.test.js
58
- ```
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).
59
29
 
60
- 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
61
31
 
62
- 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).
63
33
 
64
- 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.
65
-
66
- 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:
67
-
68
- ```bash
69
- DSH_PTC_TEST_CONCURRENCY=1 npm run verify
70
- ```
71
-
72
- ```powershell
73
- $env:DSH_PTC_TEST_CONCURRENCY = '1'
74
- npm run verify
75
- Remove-Item Env:DSH_PTC_TEST_CONCURRENCY
76
- ```
77
-
78
- 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.
79
-
80
- `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.
81
-
82
- 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.
83
-
84
- 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
85
35
 
86
- 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.
87
37
 
88
- 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
89
39
 
90
- 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.
91
41
 
92
- 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.
93
43
 
94
- 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.
95
45
 
96
- 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.
97
47
 
98
- 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.
99
49
 
100
- 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
101
51
 
102
- 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.
103
53
 
104
- Cell preparation traverses parsed trees and edit mappings without allocating a
105
- temporary object per unit of work. The namespace walk reads a node's own keys
106
- once and indexes node arrays directly instead of materializing an entry pair
107
- per property; the source-map builder validates and writes its four offsets
108
- positionally, and replacement runs reach that builder through a direct emitter
109
- instead of a generator and a per-run segment object. Rehomed compiler messages
110
- write their owned data descriptor directly rather than copying a generic
111
- descriptor first. Every one of these paths produced identical output before and
112
- 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.
113
55
 
114
- 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,11 @@
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'
13
17
  import { createSessionLogOwner } from './internal/session-log-view.js'
14
18
  import { PTC_DELIVERY_CONTEXT } from './internal/runtime-messages.js'
15
19
  import { createCordisToolsOwner } from './internal/cordis-tools-owner.js'
@@ -27,6 +31,7 @@ import { createReplMemoryProjection } from './internal/repl-memory-projection.js
27
31
  import { createUserBindingDraftProjection } from './internal/user-binding-draft-projection.js'
28
32
  import { createUserBindingsOwner } from './internal/user-bindings-owner.js'
29
33
  import { createHostRpc } from './internal/host-rpc.js'
34
+ import { RPC_CONTRACTS } from './internal/rpc-contract.js'
30
35
  import { createReplObservationInterest } from './internal/repl-observation-interest.js'
31
36
  import { guidancePolicies } from './internal/binding-update-policy.js'
32
37
  import * as dshSettings from '@deepseek-ai/dsh-settings'
@@ -77,8 +82,8 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
77
82
  : 'Mixed new/existing top-level destructuring remains unsupported; separate the declaration from the assignment.'
78
83
  const cellConventions = languageSemantics === 'legacy-v1'
79
84
  ? `Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${variableRedeclaration} ${functionClassRedeclaration} ${splitSyntax}`
80
- : `Cells support TypeScript, top-level await, return, static import and top-level export. ${languageSemantics === 'stateful-v1'
81
- ? '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.'
82
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.'}`
83
88
  const recovery = durableReplay
84
89
  ? 'Direct Node/OS access remains live but is not replayed after a kernel restart.'
@@ -86,22 +91,22 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
86
91
  const cordisRecovery = cordisToolsEnabled
87
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.'
88
93
  : ''
89
- 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.
90
95
 
91
- 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.
92
97
 
93
98
  ## Cell conventions
94
- 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}
95
100
 
96
101
  ## Available capabilities
97
- 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.
98
- 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.
99
104
 
100
- 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}` : ''}`
101
106
  }
102
107
 
103
108
  /** Register the session-bound REPL runtime. */
104
- function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessionId, sessionLog) {
109
+ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, settingsForCommand) {
105
110
  const presentationGeneration = randomUUID()
106
111
  const replMemoryProjection = createReplMemoryProjection(presentationGeneration)
107
112
  const userBindingDraftProjection = createUserBindingDraftProjection(presentationGeneration)
@@ -112,6 +117,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
112
117
  let runtimeBridge
113
118
  let editTransport
114
119
  let directSurface
120
+ let capabilityEnvironment
115
121
  let userBindings
116
122
  let observationInterest
117
123
  let ready
@@ -340,6 +346,9 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
340
346
  maxOldGenerationSizeMb: activeConfig.maxOldGenerationSizeMb,
341
347
  valueLimits: valueLimitsFromConfig(activeConfig),
342
348
  })
349
+ capabilityEnvironment = createCapabilityEnvironment({ ctx, config: activeConfig,
350
+ toolSchemasForAgent: toolSdkSchemasForAgent, modelSchemasForAgent: toolSchemasForAgent,
351
+ store: new CapabilityStatisticsStore({ ctx }) })
343
352
  runtimeBridge = createRuntimeBridgeOwner({
344
353
  ctx,
345
354
  seam,
@@ -350,15 +359,18 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
350
359
  maxNestedRunCodeDepth: activeConfig.maxNestedRunCodeDepth,
351
360
  presentationGeneration,
352
361
  sessionId,
353
- toolSchemasForAgent,
362
+ toolSchemasForAgent: toolSdkSchemasForAgent,
363
+ capabilityEnvironment,
354
364
  userBindingDraftForAgent: agent => userBindings.draftCapabilityForAgent(agent),
355
365
  bindingSubmissionForAgent: (agent, ensureLease, onAccepted) => userBindings.submissionForAgent(agent, ensureLease, onAccepted),
356
366
  })
357
367
  editTransport = createEditTransportOwner(ctx, {
358
- readSessionLog: runtimeBridge.readSubmissionLog,
368
+ readSessionLog: runtimeBridge.readNestedDispatchLog,
359
369
  sessionLogView: sessionLog.project,
360
370
  durableReplay: activeConfig.durableReplay,
361
371
  executeTentative: runtimeBridge.executeTentative,
372
+ canScheduleEdit: runtimeBridge.canScheduleEdit,
373
+ scheduleEdit: runtimeBridge.scheduleEdit,
362
374
  presentationGeneration,
363
375
  sessionId,
364
376
  toolSchemasForAgent,
@@ -370,6 +382,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
370
382
  canonicalizeToolCalls: activeConfig.canonicalizeToolCalls,
371
383
  sessionId,
372
384
  toolSchemasForAgent,
385
+ capabilityEnvironment,
373
386
  userBindingsForAgent: () => userBindings.snapshot(),
374
387
  setAgentPresentation: (agent, presentation) => (
375
388
  userBindings.setAgentPresentation(agent, presentation)
@@ -383,9 +396,89 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
383
396
  return replGuidance(guidancePolicies(activeConfig))
384
397
  },
385
398
  }))
386
- disposers.push(ctx.on('system-prompt/assemble', (assembly, context, next) => (
387
- directSurface.assemble(assembly, context, next)
388
- )))
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
+ }
389
482
  disposers.push(ctx.on('llm/stream', (options, next) => directSurface.stream(options, next), { global: true }))
390
483
  disposers.push(ctx.on('tools/execute', (exec, next) => {
391
484
  const rejected = directSurface.executionRejection(exec)
@@ -402,9 +495,12 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
402
495
  .then(result => directSurface.argumentDiagnostic(exec, result))
403
496
  }
404
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)
405
500
  return next()
406
501
  }))
407
502
  disposers.push(ctx.on('tools/result', (exec, result) => {
503
+ capabilityEnvironment.dispatched(exec, result)
408
504
  directSurface.handleResult(exec)
409
505
  if (exec.name === EDIT_RUN_CODE) return editTransport.handleResult(exec, result)
410
506
  if (exec.name === RUN_CODE) return runtimeBridge.handleResult(exec, result)
@@ -423,6 +519,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
423
519
  () => editTransport.disposeAgent(agent),
424
520
  () => runtimeBridge.disposeAgent(agent),
425
521
  () => userBindings.clearAgentPresentation(agent),
522
+ () => capabilityEnvironment.disposeAgent(agent),
426
523
  ], 'ptc-plus: agent disposal failed')))
427
524
  disposers.push(ctx.on('session/disposed', session => disposeLifecycleOwners([
428
525
  () => directSurface.disposeSession(session),
@@ -464,8 +561,6 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
464
561
  try {
465
562
  runtimeBridge.reconfigure(nextConfig)
466
563
  rollbacks.push(() => runtimeBridge.reconfigure(previousConfig))
467
- editTransport.reconfigure(nextConfig)
468
- rollbacks.push(() => editTransport.reconfigure(previousConfig))
469
564
  directSurface.reconfigure(nextConfig)
470
565
  rollbacks.push(() => directSurface.reconfigure(previousConfig))
471
566
  await userBindings.reconfigure(nextConfig)
@@ -495,6 +590,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
495
590
  }
496
591
  }
497
592
  activeConfig = nextConfig
593
+ capabilityEnvironment.reconfigure(nextConfig)
498
594
  observationInterest.reconfigure(nextConfig.replViewEnabled)
499
595
  } catch (error) {
500
596
  const rollbackFailures = []
@@ -534,10 +630,13 @@ export const apply = (ctx, config = {}) => {
534
630
 
535
631
  /** Install the plugin into the scope of the seam the host registered. */
536
632
  function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatileConfig) {
633
+ let settingsWriter
537
634
  const sessionLog = createSessionLogOwner(ctx)
538
635
  const toolSchemasForAgent = agent => typeof ctx.tools.schemas === 'function'
539
636
  ? ctx.tools.schemas(agent)
540
637
  : []
638
+ // SDK contracts include output schemas; model-facing schemas remain unchanged.
639
+ const toolSdkSchemasForAgent = agent => toolSdkSchemas(ctx.tools, agent)
541
640
  const sessionId = agent => {
542
641
  const id = agent?.session?.id ?? agent?.id
543
642
  return id === undefined ? undefined : String(id)
@@ -621,7 +720,7 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
621
720
  }
622
721
  let candidate
623
722
  try {
624
- candidate = installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, sessionId, sessionLog)
723
+ candidate = installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter)
625
724
  record.runtime = candidate
626
725
  } catch (error) {
627
726
  const failedRuntime = error?.[INSTALL_CLEANUP]
@@ -763,9 +862,10 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
763
862
  }
764
863
  ctx.effect(() => async () => controller.dispose(), 'ptc-plus runtime lifecycle')
765
864
 
865
+ const handbackOwners = Object.fromEntries(['sessions', 'sessionPersistence', 'sessionQuery'].map(name => [name, ctx.get(name)]))
766
866
  const messageOwner = createRuntimeMessageOwner(context => (
767
867
  committed?.retiring ? [] : committed?.runtime.contextsForRequest(context) ?? []
768
- ), sessionLog.project)
868
+ ), sessionLog.project, () => committed !== undefined && committed.retiring !== true, handbackOwners.sessionQuery)
769
869
  ctx.effect(() => ctx.systemPrompt.context({
770
870
  name: PTC_DELIVERY_CONTEXT, order: 98, text: '',
771
871
  }), 'ptc-plus dynamic message delivery witness')
@@ -777,10 +877,13 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
777
877
  )), 'ptc-plus accepted messages')
778
878
  ctx.effect(() => ctx.on('agent/disposed', ({ agent }) => messageOwner.disposeAgent(agent)), 'ptc-plus message agent disposal')
779
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')
780
884
 
781
885
  let configSource = () => resolveConfig(volatileConfig ?? resolvedConfig)
782
886
  let configurationGeneration = 0
783
- let settingsWriter
784
887
  let configurationRollback = false
785
888
  const reportActivationFailure = (error) => {
786
889
  ctx.logger?.warn?.('ptc-plus: runtime activation failed', error)