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