dsh-ptc-plus 0.4.10 → 0.5.1
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.
- package/README.en.md +65 -127
- package/README.md +65 -127
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.cjs +42702 -0
- package/cordis.patch.yml +1 -0
- package/docs/README.md +6 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
- package/docs/adr/0008-define-agent-operating-contract.md +5 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -4
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +2 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
- package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
- package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
- package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
- package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
- package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
- package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
- package/docs/adr/0041-record-cell-execution-directories.md +65 -0
- package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
- package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/README.md +8 -0
- package/docs/architecture.md +23 -25
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +70 -23
- package/docs/client-visual-contract.md +26 -9
- package/docs/context-management.md +22 -0
- package/docs/context-optimization-plan.md +353 -0
- package/docs/context-public-interface-coverage.md +25 -0
- package/docs/durability-design.md +20 -15
- package/docs/evaluation.md +41 -6
- package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
- package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
- package/docs/installation.md +33 -10
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +7 -7
- package/docs/release-coverage.md +73 -0
- package/docs/release-notes.md +1 -1
- package/docs/review-procedure.md +56 -0
- package/docs/runtime-reference.md +53 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.md +1 -1
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +26 -15
- package/docs/verification.md +30 -88
- package/index.js +146 -20
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +31 -0
- package/internal/capability-environment.js +1253 -0
- package/internal/capability-plan.js +285 -0
- package/internal/capability-policy.js +96 -0
- package/internal/capability-statistics-store.js +180 -0
- package/internal/capability-statistics.js +214 -0
- package/internal/config-spec.js +89 -10
- package/internal/direct-surface-owner.js +27 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +195 -140
- package/internal/execution-provider-owner.js +40 -12
- package/internal/execution-seam-compat.js +26 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/official-repl-priority.js +51 -0
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +3 -2
- package/internal/rejected-cell-editor.js +8 -8
- package/internal/repl-observation-interest.js +11 -3
- package/internal/rpc-contract.js +1 -0
- package/internal/runtime-bridge-owner.js +275 -34
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +74 -5
- package/internal/runtime-intrinsics.js +1 -0
- package/internal/runtime-messages.js +41 -0
- package/internal/sdk-projection.js +10 -82
- package/internal/session-cell-executor.js +42 -19
- package/internal/session-events.js +34 -0
- package/internal/session-journal-recovery.js +234 -11
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +1 -1
- package/internal/session-runtime.js +119 -15
- package/internal/settings-compat.js +12 -5
- package/internal/stateful-module-compiler.js +3 -2
- package/internal/stateful-module-runtime.js +7 -3
- package/internal/stateful-root-runtime.js +9 -4
- package/internal/user-binding-authoring.js +2 -7
- package/internal/user-bindings-owner.js +19 -2
- package/internal/validated-parse-repair.js +1 -1
- package/internal/value-wire.js +68 -21
- package/internal/worker-cwd-virtualization.js +20 -18
- package/internal/worker-realm-surfaces.js +3 -3
- package/package.json +49 -30
- package/src/client-activity.js +3 -2
- package/src/client-authoring-view.js +67 -30
- package/src/client-binding-review.js +2 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1555 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-copy.js +923 -8
- package/src/client-feature-gates.js +3 -0
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +33 -2
- package/src/client-repl-view.js +20 -13
- package/src/client-settings-view.js +411 -41
- package/src/client-styles.js +112 -15
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -9
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +6 -7
- package/src/client.js +76 -22
- package/client.js +0 -32479
package/docs/verification.md
CHANGED
|
@@ -1,114 +1,56 @@
|
|
|
1
1
|
# Deterministic verification
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
+
## Change classification
|
|
43
19
|
|
|
44
|
-
`npm run verify`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
## Reading test output
|
|
51
27
|
|
|
52
|
-
|
|
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
|
-
|
|
30
|
+
## Findings ledger
|
|
61
31
|
|
|
62
|
-
|
|
32
|
+
Before creating or updating findings, read [Findings ledger](review-procedure.md#findings-ledger).
|
|
63
33
|
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
## Complete behavioral validation
|
|
89
39
|
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
+
## Release coverage
|
|
101
51
|
|
|
102
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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'
|
|
@@ -22,11 +26,13 @@ import {
|
|
|
22
26
|
} from './internal/config-spec.js'
|
|
23
27
|
import { configFieldSchema, installSettingsSectionCompat } from './internal/settings-compat.js'
|
|
24
28
|
import { installExecutionSeam } from './internal/execution-seam-compat.js'
|
|
29
|
+
import { createDeferredRuntime, defersToOfficialRepl } from './internal/official-repl-priority.js'
|
|
25
30
|
import { valueLimitsFromConfig } from './internal/value-wire-schema.js'
|
|
26
31
|
import { createReplMemoryProjection } from './internal/repl-memory-projection.js'
|
|
27
32
|
import { createUserBindingDraftProjection } from './internal/user-binding-draft-projection.js'
|
|
28
33
|
import { createUserBindingsOwner } from './internal/user-bindings-owner.js'
|
|
29
34
|
import { createHostRpc } from './internal/host-rpc.js'
|
|
35
|
+
import { RPC_CONTRACTS } from './internal/rpc-contract.js'
|
|
30
36
|
import { createReplObservationInterest } from './internal/repl-observation-interest.js'
|
|
31
37
|
import { guidancePolicies } from './internal/binding-update-policy.js'
|
|
32
38
|
import * as dshSettings from '@deepseek-ai/dsh-settings'
|
|
@@ -77,8 +83,8 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
|
|
|
77
83
|
: 'Mixed new/existing top-level destructuring remains unsupported; separate the declaration from the assignment.'
|
|
78
84
|
const cellConventions = languageSemantics === 'legacy-v1'
|
|
79
85
|
? `Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${variableRedeclaration} ${functionClassRedeclaration} ${splitSyntax}`
|
|
80
|
-
|
|
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;
|
|
86
|
+
: `Cells support TypeScript, top-level await, return, static import and top-level export. ${languageSemantics === 'stateful-v1'
|
|
87
|
+
? '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
88
|
: '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
89
|
const recovery = durableReplay
|
|
84
90
|
? 'Direct Node/OS access remains live but is not replayed after a kernel restart.'
|
|
@@ -86,22 +92,22 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
|
|
|
86
92
|
const cordisRecovery = cordisToolsEnabled
|
|
87
93
|
? '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
94
|
: ''
|
|
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.
|
|
95
|
+
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
96
|
|
|
91
|
-
After an execution error, earlier statements may have taken effect. Inspect relevant values in a short cell
|
|
97
|
+
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
98
|
|
|
93
99
|
## 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}
|
|
100
|
+
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
101
|
|
|
96
102
|
## Available capabilities
|
|
97
|
-
Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` for unfamiliar program APIs
|
|
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) }\`,
|
|
103
|
+
Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` for unfamiliar program APIs; Reserve \`code.run\` for isolated execution of source held as data.
|
|
104
|
+
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
105
|
|
|
100
|
-
Native tool availability
|
|
106
|
+
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
107
|
}
|
|
102
108
|
|
|
103
109
|
/** Register the session-bound REPL runtime. */
|
|
104
|
-
function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessionId, sessionLog) {
|
|
110
|
+
function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, settingsForCommand) {
|
|
105
111
|
const presentationGeneration = randomUUID()
|
|
106
112
|
const replMemoryProjection = createReplMemoryProjection(presentationGeneration)
|
|
107
113
|
const userBindingDraftProjection = createUserBindingDraftProjection(presentationGeneration)
|
|
@@ -112,6 +118,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
112
118
|
let runtimeBridge
|
|
113
119
|
let editTransport
|
|
114
120
|
let directSurface
|
|
121
|
+
let capabilityEnvironment
|
|
115
122
|
let userBindings
|
|
116
123
|
let observationInterest
|
|
117
124
|
let ready
|
|
@@ -340,6 +347,9 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
340
347
|
maxOldGenerationSizeMb: activeConfig.maxOldGenerationSizeMb,
|
|
341
348
|
valueLimits: valueLimitsFromConfig(activeConfig),
|
|
342
349
|
})
|
|
350
|
+
capabilityEnvironment = createCapabilityEnvironment({ ctx, config: activeConfig,
|
|
351
|
+
toolSchemasForAgent: toolSdkSchemasForAgent, modelSchemasForAgent: toolSchemasForAgent,
|
|
352
|
+
store: new CapabilityStatisticsStore({ ctx }) })
|
|
343
353
|
runtimeBridge = createRuntimeBridgeOwner({
|
|
344
354
|
ctx,
|
|
345
355
|
seam,
|
|
@@ -350,15 +360,18 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
350
360
|
maxNestedRunCodeDepth: activeConfig.maxNestedRunCodeDepth,
|
|
351
361
|
presentationGeneration,
|
|
352
362
|
sessionId,
|
|
353
|
-
toolSchemasForAgent,
|
|
363
|
+
toolSchemasForAgent: toolSdkSchemasForAgent,
|
|
364
|
+
capabilityEnvironment,
|
|
354
365
|
userBindingDraftForAgent: agent => userBindings.draftCapabilityForAgent(agent),
|
|
355
366
|
bindingSubmissionForAgent: (agent, ensureLease, onAccepted) => userBindings.submissionForAgent(agent, ensureLease, onAccepted),
|
|
356
367
|
})
|
|
357
368
|
editTransport = createEditTransportOwner(ctx, {
|
|
358
|
-
readSessionLog: runtimeBridge.
|
|
369
|
+
readSessionLog: runtimeBridge.readNestedDispatchLog,
|
|
359
370
|
sessionLogView: sessionLog.project,
|
|
360
371
|
durableReplay: activeConfig.durableReplay,
|
|
361
372
|
executeTentative: runtimeBridge.executeTentative,
|
|
373
|
+
canScheduleEdit: runtimeBridge.canScheduleEdit,
|
|
374
|
+
scheduleEdit: runtimeBridge.scheduleEdit,
|
|
362
375
|
presentationGeneration,
|
|
363
376
|
sessionId,
|
|
364
377
|
toolSchemasForAgent,
|
|
@@ -370,6 +383,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
370
383
|
canonicalizeToolCalls: activeConfig.canonicalizeToolCalls,
|
|
371
384
|
sessionId,
|
|
372
385
|
toolSchemasForAgent,
|
|
386
|
+
capabilityEnvironment,
|
|
373
387
|
userBindingsForAgent: () => userBindings.snapshot(),
|
|
374
388
|
setAgentPresentation: (agent, presentation) => (
|
|
375
389
|
userBindings.setAgentPresentation(agent, presentation)
|
|
@@ -383,9 +397,94 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
383
397
|
return replGuidance(guidancePolicies(activeConfig))
|
|
384
398
|
},
|
|
385
399
|
}))
|
|
386
|
-
disposers.push(ctx.on('system-prompt/assemble', (assembly, context, next) =>
|
|
387
|
-
directSurface.assemble(assembly, context, next)
|
|
388
|
-
|
|
400
|
+
disposers.push(ctx.on('system-prompt/assemble', async (assembly, context, next) => {
|
|
401
|
+
const projected = await directSurface.assemble(assembly, context, next)
|
|
402
|
+
if (ctx.tools.get(RUN_CODE, context?.agent) !== undefined) runtimeBridge.prepareAgent(context?.agent)
|
|
403
|
+
return projected
|
|
404
|
+
}))
|
|
405
|
+
disposers.push(() => capabilityEnvironment.dispose())
|
|
406
|
+
disposers.push(ctx.on('agent/pre-step', (payload, next) => capabilityEnvironment.preStep(payload, next), { prepend: true }))
|
|
407
|
+
disposers.push(ctx.on('agent/created', ({ agent }) => {
|
|
408
|
+
if (ctx.tools.get(RUN_CODE, agent) !== undefined) {
|
|
409
|
+
runtimeBridge.prepareAgent(agent)
|
|
410
|
+
editTransport.ensureInstalled(agent)
|
|
411
|
+
}
|
|
412
|
+
}))
|
|
413
|
+
for (const agent of ctx.agents?.list?.() ?? []) {
|
|
414
|
+
if (ctx.tools.get(RUN_CODE, agent) !== undefined) {
|
|
415
|
+
runtimeBridge.prepareAgent(agent)
|
|
416
|
+
editTransport.ensureInstalled(agent)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
disposers.push(ctx.on('tools/post-execute', (exec, result, next) => capabilityEnvironment.postExecute(exec, result, next), { prepend: true }))
|
|
420
|
+
disposers.push(ctx.on('tools/ptc-dispatch-log', (dispatch, next) => capabilityEnvironment.dispatchLog(dispatch, next), { prepend: true }))
|
|
421
|
+
disposers.push(ctx.on('agent/status', payload => { if (payload?.agent) capabilityEnvironment.status(payload.agent, payload.status) }))
|
|
422
|
+
disposers.push(ctx.on('agent/created', ({ agent }) => capabilityEnvironment.status(agent, agent.status)))
|
|
423
|
+
if (typeof ctx.inject === 'function') {
|
|
424
|
+
const capabilitiesRpc = ctx.inject(['ptcPlusRpc'], scope => {
|
|
425
|
+
scope.effect(() => scope.ptcPlusRpc.register(RPC_CONTRACTS.capabilities, async (operation, payload, signal) => {
|
|
426
|
+
signal.throwIfAborted()
|
|
427
|
+
if (operation === 'catalog') return { ok: true, value: await capabilityEnvironment.describeCatalog(signal) }
|
|
428
|
+
if (operation === 'targets') return { ok: true, value: capabilityEnvironment.describeTargets() }
|
|
429
|
+
if (operation === 'session-catalog') return { ok: true, value: await capabilityEnvironment.describeSessionCatalog(payload, signal) }
|
|
430
|
+
if (operation === 'allocation') return { ok: true, value: await capabilityEnvironment.observeAllocation(payload, signal) }
|
|
431
|
+
if (operation !== 'inspect') return { ok: false, error: { code: 'UNKNOWN_OPERATION', message: 'Unknown capability plan operation', details: null } }
|
|
432
|
+
return { ok: true, value: capabilityEnvironment.describeSessions() }
|
|
433
|
+
}), 'ptc-plus: capability plan observation')
|
|
434
|
+
})
|
|
435
|
+
if (typeof capabilitiesRpc === 'function') disposers.push(capabilitiesRpc)
|
|
436
|
+
else if (typeof capabilitiesRpc?.dispose === 'function') disposers.push(() => capabilitiesRpc.dispose())
|
|
437
|
+
const commands = ctx.inject(['commands'], scope => {
|
|
438
|
+
scope.effect(() => scope.commands.register({ name: 'capabilities',
|
|
439
|
+
description: 'Inspect, search or change session capabilities; global choices also save settings.',
|
|
440
|
+
input: { hint: 'refresh | list [all|skills|tools|mcp|mcp_tools] [query] | set session|global <id> [availability|disclosure] <choice>' },
|
|
441
|
+
handler: async ({ commandId, agent, rawInput, signal }) => {
|
|
442
|
+
const input = rawInput.trim()
|
|
443
|
+
const receipt = (state, error) => capabilityCommandReceipt(state, String(agent.session.id), error,
|
|
444
|
+
state === 'pending' && capabilityEnvironment.pendingChoiceMessage(agent)
|
|
445
|
+
? { commandId, rawInput, message: capabilityEnvironment.pendingChoiceMessage(agent) } : undefined)
|
|
446
|
+
const selection = /^set (session|global) (\S+) (?:(availability|disclosure) )?(full|on-demand|closed|automatic|inherit|enabled|host|adaptive)$/.exec(input)
|
|
447
|
+
if (selection) {
|
|
448
|
+
if (!capabilityEnvironment.canRefresh(agent)) return { kind: 'error', text: receipt('unavailable') }
|
|
449
|
+
const changed = await capabilityEnvironment.change(agent, {
|
|
450
|
+
scope: selection[1], id: decodeURIComponent(selection[2]), dimension: selection[3], selection: selection[4], commandId,
|
|
451
|
+
}, signal, settingsForCommand?.())
|
|
452
|
+
return { kind: changed.sessionApplied ? 'success' : 'error',
|
|
453
|
+
text: receipt(changed.sessionApplied ? changed.publication === 'pending' ? 'pending' : 'recorded' : 'saved-only', changed.error) }
|
|
454
|
+
}
|
|
455
|
+
const listing = /^list(?: (all|skills|tools|mcp|mcp_tools))?(?: (.*))?$/.exec(input)
|
|
456
|
+
if (listing) {
|
|
457
|
+
const entries = await capabilityEnvironment.catalogFor(agent, signal)
|
|
458
|
+
const query = (listing[2] ?? '').toLocaleLowerCase()
|
|
459
|
+
const kind = listing[1] ?? 'all'
|
|
460
|
+
return { kind: 'success', text: JSON.stringify({ schema: 'ptc-capability-catalog/v1', entries: entries.filter(entry => (
|
|
461
|
+
kind === 'all' || kind === 'skills' && entry.category === 'skill'
|
|
462
|
+
|| kind === 'tools' && entry.category === 'tool' && entry.source?.kind !== 'mcp'
|
|
463
|
+
|| kind === 'mcp' && ['server', 'resource'].includes(entry.category)
|
|
464
|
+
|| kind === 'mcp_tools' && entry.category === 'tool' && entry.source?.kind === 'mcp'
|
|
465
|
+
) && (!query || `${entry.id} ${entry.description}`.toLocaleLowerCase().includes(query))) }) }
|
|
466
|
+
}
|
|
467
|
+
if (input !== '' && input !== 'refresh') return { kind: 'error', text: receipt('usage') }
|
|
468
|
+
if (input === 'refresh') {
|
|
469
|
+
if (!capabilityEnvironment.canRefresh(agent)) {
|
|
470
|
+
return { kind: 'error', text: receipt('unavailable') }
|
|
471
|
+
}
|
|
472
|
+
await capabilityEnvironment.refresh(agent, undefined, signal, commandId)
|
|
473
|
+
// A refresh always republishes the saved configuration; when that
|
|
474
|
+
// configuration has capability management or context off, report
|
|
475
|
+
// the off state instead of claiming an updated environment.
|
|
476
|
+
const described = capabilityEnvironment.describe(agent)
|
|
477
|
+
const state = described.effective?.enabled !== true
|
|
478
|
+
? described.sourceSettings?.contextEnabled === false ? 'paused' : 'off'
|
|
479
|
+
: described.publication === 'pending' ? 'pending' : 'recorded'
|
|
480
|
+
return { kind: 'success', text: receipt(state) }
|
|
481
|
+
}
|
|
482
|
+
return { kind: 'success', text: JSON.stringify(capabilityEnvironment.describe(agent), null, 2) }
|
|
483
|
+
} }), 'ptc-plus capability command')
|
|
484
|
+
})
|
|
485
|
+
if (typeof commands === 'function') disposers.push(commands)
|
|
486
|
+
else if (typeof commands?.dispose === 'function') disposers.push(() => commands.dispose())
|
|
487
|
+
}
|
|
389
488
|
disposers.push(ctx.on('llm/stream', (options, next) => directSurface.stream(options, next), { global: true }))
|
|
390
489
|
disposers.push(ctx.on('tools/execute', (exec, next) => {
|
|
391
490
|
const rejected = directSurface.executionRejection(exec)
|
|
@@ -402,9 +501,12 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
402
501
|
.then(result => directSurface.argumentDiagnostic(exec, result))
|
|
403
502
|
}
|
|
404
503
|
if (exec.name === EDIT_RUN_CODE) return runtimeBridge.withSubmission(exec, next)
|
|
504
|
+
capabilityEnvironment.validateSupply(exec.agent, exec.name, exec.arguments)
|
|
505
|
+
capabilityEnvironment.admitted(exec)
|
|
405
506
|
return next()
|
|
406
507
|
}))
|
|
407
508
|
disposers.push(ctx.on('tools/result', (exec, result) => {
|
|
509
|
+
capabilityEnvironment.dispatched(exec, result)
|
|
408
510
|
directSurface.handleResult(exec)
|
|
409
511
|
if (exec.name === EDIT_RUN_CODE) return editTransport.handleResult(exec, result)
|
|
410
512
|
if (exec.name === RUN_CODE) return runtimeBridge.handleResult(exec, result)
|
|
@@ -423,6 +525,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
423
525
|
() => editTransport.disposeAgent(agent),
|
|
424
526
|
() => runtimeBridge.disposeAgent(agent),
|
|
425
527
|
() => userBindings.clearAgentPresentation(agent),
|
|
528
|
+
() => capabilityEnvironment.disposeAgent(agent),
|
|
426
529
|
], 'ptc-plus: agent disposal failed')))
|
|
427
530
|
disposers.push(ctx.on('session/disposed', session => disposeLifecycleOwners([
|
|
428
531
|
() => directSurface.disposeSession(session),
|
|
@@ -464,8 +567,6 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
464
567
|
try {
|
|
465
568
|
runtimeBridge.reconfigure(nextConfig)
|
|
466
569
|
rollbacks.push(() => runtimeBridge.reconfigure(previousConfig))
|
|
467
|
-
editTransport.reconfigure(nextConfig)
|
|
468
|
-
rollbacks.push(() => editTransport.reconfigure(previousConfig))
|
|
469
570
|
directSurface.reconfigure(nextConfig)
|
|
470
571
|
rollbacks.push(() => directSurface.reconfigure(previousConfig))
|
|
471
572
|
await userBindings.reconfigure(nextConfig)
|
|
@@ -495,6 +596,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessi
|
|
|
495
596
|
}
|
|
496
597
|
}
|
|
497
598
|
activeConfig = nextConfig
|
|
599
|
+
capabilityEnvironment.reconfigure(nextConfig)
|
|
498
600
|
observationInterest.reconfigure(nextConfig.replViewEnabled)
|
|
499
601
|
} catch (error) {
|
|
500
602
|
const rollbackFailures = []
|
|
@@ -534,10 +636,13 @@ export const apply = (ctx, config = {}) => {
|
|
|
534
636
|
|
|
535
637
|
/** Install the plugin into the scope of the seam the host registered. */
|
|
536
638
|
function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatileConfig) {
|
|
639
|
+
let settingsWriter
|
|
537
640
|
const sessionLog = createSessionLogOwner(ctx)
|
|
538
641
|
const toolSchemasForAgent = agent => typeof ctx.tools.schemas === 'function'
|
|
539
642
|
? ctx.tools.schemas(agent)
|
|
540
643
|
: []
|
|
644
|
+
// SDK contracts include output schemas; model-facing schemas remain unchanged.
|
|
645
|
+
const toolSdkSchemasForAgent = agent => toolSdkSchemas(ctx.tools, agent)
|
|
541
646
|
const sessionId = agent => {
|
|
542
647
|
const id = agent?.session?.id ?? agent?.id
|
|
543
648
|
return id === undefined ? undefined : String(id)
|
|
@@ -611,6 +716,11 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
611
716
|
)
|
|
612
717
|
return cleanup
|
|
613
718
|
}
|
|
719
|
+
// Execution ownership is a per-attachment property: `owned` installs the
|
|
720
|
+
// session runtime, `deferred` leaves run_code to the official provider.
|
|
721
|
+
const executionShape = nextConfig => (
|
|
722
|
+
defersToOfficialRepl(nextConfig, seam.provider) ? 'deferred' : 'owned'
|
|
723
|
+
)
|
|
614
724
|
const startActivation = record => {
|
|
615
725
|
if (record.cancelled || disposed || activating !== record) {
|
|
616
726
|
record.completion.resolve({ status: 'superseded' })
|
|
@@ -621,7 +731,9 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
621
731
|
}
|
|
622
732
|
let candidate
|
|
623
733
|
try {
|
|
624
|
-
candidate =
|
|
734
|
+
candidate = record.shape === 'deferred'
|
|
735
|
+
? createDeferredRuntime()
|
|
736
|
+
: installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter)
|
|
625
737
|
record.runtime = candidate
|
|
626
738
|
} catch (error) {
|
|
627
739
|
const failedRuntime = error?.[INSTALL_CLEANUP]
|
|
@@ -666,6 +778,7 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
666
778
|
const record = {
|
|
667
779
|
config: nextConfig,
|
|
668
780
|
generation,
|
|
781
|
+
shape: executionShape(nextConfig),
|
|
669
782
|
runtime: undefined,
|
|
670
783
|
disposal: undefined,
|
|
671
784
|
disposed: false,
|
|
@@ -715,6 +828,15 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
715
828
|
return beginActivation(nextConfig, generation, cleanup)
|
|
716
829
|
}
|
|
717
830
|
const current = committed
|
|
831
|
+
if (current.shape !== executionShape(nextConfig)) {
|
|
832
|
+
// Ownership changes are installation changes: dispose the live kernels
|
|
833
|
+
// and install the other shape, so no committed cell can mix the two
|
|
834
|
+
// execution contracts or extend state the new owner cannot see.
|
|
835
|
+
current.retiring = true
|
|
836
|
+
void disposeRuntime(current)
|
|
837
|
+
const cleanup = disposeRetainedRuntimes()
|
|
838
|
+
return beginActivation(nextConfig, generation, cleanup)
|
|
839
|
+
}
|
|
718
840
|
if (!nextConfig.cordisToolsEnabled) current.runtime.cancelCordisActivation()
|
|
719
841
|
return enqueueTransition(async () => {
|
|
720
842
|
if (disposed || committed !== current) return { status: 'superseded' }
|
|
@@ -763,9 +885,10 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
763
885
|
}
|
|
764
886
|
ctx.effect(() => async () => controller.dispose(), 'ptc-plus runtime lifecycle')
|
|
765
887
|
|
|
888
|
+
const handbackOwners = Object.fromEntries(['sessions', 'sessionPersistence', 'sessionQuery'].map(name => [name, ctx.get(name)]))
|
|
766
889
|
const messageOwner = createRuntimeMessageOwner(context => (
|
|
767
890
|
committed?.retiring ? [] : committed?.runtime.contextsForRequest(context) ?? []
|
|
768
|
-
), sessionLog.project)
|
|
891
|
+
), sessionLog.project, () => committed !== undefined && committed.retiring !== true, handbackOwners.sessionQuery)
|
|
769
892
|
ctx.effect(() => ctx.systemPrompt.context({
|
|
770
893
|
name: PTC_DELIVERY_CONTEXT, order: 98, text: '',
|
|
771
894
|
}), 'ptc-plus dynamic message delivery witness')
|
|
@@ -777,10 +900,13 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
777
900
|
)), 'ptc-plus accepted messages')
|
|
778
901
|
ctx.effect(() => ctx.on('agent/disposed', ({ agent }) => messageOwner.disposeAgent(agent)), 'ptc-plus message agent disposal')
|
|
779
902
|
ctx.effect(() => () => messageOwner.dispose(), 'ptc-plus dynamic message lifecycle')
|
|
903
|
+
ctx.effect(() => async () => {
|
|
904
|
+
await controller.dispose()
|
|
905
|
+
await handBackRuntimeSessions(ctx, handbackOwners)
|
|
906
|
+
}, 'ptc-plus provider handback')
|
|
780
907
|
|
|
781
908
|
let configSource = () => resolveConfig(volatileConfig ?? resolvedConfig)
|
|
782
909
|
let configurationGeneration = 0
|
|
783
|
-
let settingsWriter
|
|
784
910
|
let configurationRollback = false
|
|
785
911
|
const reportActivationFailure = (error) => {
|
|
786
912
|
ctx.logger?.warn?.('ptc-plus: runtime activation failed', error)
|
|
@@ -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
|
+
}
|