dsh-ptc-plus 0.5.1 → 0.5.2
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 +7 -3
- package/README.md +7 -3
- package/client.cjs +1467 -84
- package/compiler-core.cjs +132 -132
- package/compiler-core.cjs.map +3 -3
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +2 -0
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +11 -1
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +31 -1
- package/docs/adr/0045-customize-model-visible-prompt-text.md +38 -0
- package/docs/adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md +35 -0
- package/docs/adr/0047-store-journal-argument-digests-in-the-projection.md +88 -0
- package/docs/architecture.md +2 -2
- package/docs/client-ui.md +5 -2
- package/docs/durability-design.md +7 -1
- package/docs/releases/v0.5.2.md +79 -0
- package/docs/runtime-reference.md +36 -2
- package/docs/unmeasured-cell-recovery-repair-proposal.md +387 -0
- package/docs/verification.md +6 -2
- package/index.js +36 -15
- package/internal/bounded-literal.js +85 -0
- package/internal/capability-environment.js +162 -10
- package/internal/capability-plan.js +11 -8
- package/internal/cell-error.js +13 -0
- package/internal/config-spec.js +113 -2
- package/internal/direct-surface-owner.js +14 -10
- package/internal/edit-transport-owner.js +79 -14
- package/internal/isolated-worker.js +134 -17
- package/internal/kernel-worker.js +167 -3
- package/internal/legacy-cell-analysis.js +14 -2
- package/internal/legacy-repl-convenience.js +16 -3
- package/internal/projection-retention.js +238 -0
- package/internal/prompt-text.js +858 -0
- package/internal/recovery-tips.js +50 -11
- package/internal/rejected-cell-editor.js +3 -2
- package/internal/runtime-bridge-owner.js +1 -1
- package/internal/runtime-config.js +2 -0
- package/internal/runtime-contexts.js +14 -5
- package/internal/runtime-messages.js +43 -15
- package/internal/session-cell-executor.js +132 -11
- package/internal/session-journal-recovery.js +1009 -99
- package/internal/session-journal-schema.js +7 -5
- package/internal/session-journal.js +177 -14
- package/internal/session-log-view.js +111 -24
- package/internal/session-runtime.js +674 -33
- package/internal/session-state.js +27 -0
- package/internal/stateful-root-compiler.js +11 -0
- package/internal/stateful-root-runtime.js +17 -0
- package/internal/user-binding-console.js +7 -0
- package/internal/user-bindings-owner.js +3 -0
- package/internal/user-bindings.js +2 -2
- package/internal/validated-parse-repair.js +6 -1
- package/internal/worker-client.js +27 -4
- package/package.json +3 -1
- package/src/client-activity.js +24 -4
- package/src/client-capabilities-view.js +459 -24
- package/src/client-copy.js +163 -2
- package/src/client-repl-view.js +6 -1
- package/src/client-styles.js +64 -2
|
@@ -28,6 +28,8 @@ If the public Host never invokes `presentationMeta` for this owned nested succes
|
|
|
28
28
|
|
|
29
29
|
The root result's private `dshPtcPlusPostEdit` evidence keeps the editor cause in `startSeq` and the actual derived execution in `executionStartSeq`, alongside the derived source, journal and target sequence. Recovery validates both start/settle pairs, the root journal's accepted editor arguments and receipt, the canonical derived outcome and the transform against the captured target. It folds the derived journal after the root cell using the actual execution-start sequence. Model-visible provenance comes from the root source and the original target's source; raw dispatch records alone do not prove it. Losing either source or a required execution fact contracts the derived binding frontier. A failed start append prevents dispatch; a failed settlement append invalidates future recovery evidence without repeating or undoing an execution. Cold replay uses recorded nested results and does not redispatch the editor or repeat external effects. A successfully published derived source becomes the next editable target through its own execution-start sequence.
|
|
30
30
|
|
|
31
|
+
The derived execution can also settle without running: when the cell cannot obtain a compute baseline before its run frame, its journal records an unmeasured `noop` settlement. That settlement is admissible evidence for the accepted edit, because it proves the dispatch relation while claiming no state: the root cell keeps its own journal and stays provable, no revision binding is published, no successful revision is recorded, and the unexecuted source never becomes the next editable target. Only the unmeasured route qualifies; a rejected source, an aborted cell or a lost helper keep the rule that a derived edit journal must record an execution, and cold recovery of an unexecuted settlement folds no derived state and replays no editor.
|
|
32
|
+
|
|
31
33
|
The derived source itself may contain an editor call. Such a call does not gain a fresh root edit authority during the derived execution: it receives an unavailable receipt rather than recursively scheduling another derived run. Each model-authored root call can accept at most one deferred edit. This bounds recursion and prevents a derived call from resetting the root edit lifecycle.
|
|
32
34
|
|
|
33
35
|
## Provider and diagnostic handling
|
|
@@ -14,7 +14,7 @@ The session kernel runs inside a plugin-owned helper process that the host may k
|
|
|
14
14
|
|
|
15
15
|
Caller and helper environment boundaries are explicit. Each creation site projects the host environment with `normalizeWorkerEnvironment`, which keeps the user-visible contract and strips host-only instrumentation such as `NODE_V8_COVERAGE`. `IsolatedWorker` snapshots that projected object before `child_process.fork`; the fork call may mutate its own env object by copying `NODE_V8_COVERAGE` into the helper, so the pre-fork snapshot is what `kernel-child.js` passes to `new Worker({ env: ... })`. When the Host executable is Electron, `IsolatedWorker` overlays `ELECTRON_RUN_AS_NODE=1` only on the fork environment so the packaged executable starts the helper module instead of reopening the desktop application. The inner user Worker and the child processes it creates still receive the original projected snapshot, without that helper-only override. An explicit transport/coverage test can opt into instrumentation to verify worker branches; production paths do not change the user environment to collect coverage.
|
|
16
16
|
|
|
17
|
-
The helper answers an on-demand utilization-sample request from its own loop, and `IsolatedWorker` exposes `sampleUtilization()`. `SessionCellExecutor` starts the wall bound
|
|
17
|
+
The helper answers an on-demand utilization-sample request from its own loop, and `IsolatedWorker` exposes `sampleUtilization()`. `SessionCellExecutor` starts the wall bound when the cell's budgets start, after preparation and worker acquisition, awaits that fresh baseline, then starts the compute interval before it posts the run frame, so work completed before that point is excluded while prepare blocking remains counted. The exception owned by [ADR 0024](0024-repl-console-observation.md) is a confirmed unfinished observation on the same worker: the executor may post prepare before starting budgets so the observation can finish, and the matching observation-completion/readiness acknowledgement starts them. The sampling bound is decided only after the helper's channel has been read out, not when the host's own clock reaches it: a blocked host runs expired timers before it reads its IPC pipe, Node delivers one helper message per event-loop turn, and the answer follows every message the helper wrote earlier, so the transport concludes only after a turn delivers nothing. A read-out bound ends a backlog at or above that many messages and reports only that no answer was observed. A sample that cannot be sent, disconnects or exits is a bounded worker-exit failure: pending requests are cleared, no run frame is dispatched, and the worker is reset through the owner's close path, which leaves a refused reclamation visible on the failed cell result. Any other sample the transport could not observe is reported as that fact alone and decided one level up, because the transport owns the observation and the kernel owns the worker: the cell settles under `budget-unavailable` with a no-op journal since nothing was dispatched, and the live worker keeps its timeline and bindings until the configured `unmeasuredCellLimit` consecutive cells reach the dropping threshold. The wall and compute bounds behave the same way when they expire before the run frame is posted, because the work they measured belongs to earlier cells; after that frame they keep their existing discard and reset route. One unmeasured cell is not evidence about the worker, and the configured `unmeasuredCellLimit` (default 2) is the consecutive cell that drops the live worker while keeping the proved frontier; the next cell replays that frontier. A stopped helper's exit is confirmed within `workerReclaimMs` (default 5000 ms) measured from the close start, with the release grace capped so the kill keeps a confirmation window; the output pipes then get the same bound from their own start. A replay pass that ends because a recorded cell could not be observed likewise keeps the frontier and retries, while a replay that fails for any other reason keeps the existing containment; the reset such a pass performs goes through the same reclamation owner, so a refused stop stays recorded, blocks later computation until it is handled, and is reported on the cell that ends. A cell blocked by that recorded failure is reported as blocked: it never contracts or blames the recorded frontier. The ready handshake still contributes an additional startup sample, but it is not the per-cell baseline and it does not reset or defer budgets.
|
|
18
18
|
|
|
19
19
|
Helper stdout and stderr are part of cell settlement, not unowned process noise. For every run, the Host chooses a nonce and arms one output round before posting source. The kernel writes nonce-bearing start markers to both native descriptors and waits for the Host to observe both before executing the cell. It writes matching end markers after evaluation, pending program calls and value encoding settle, but before posting `done`. The Host releases `done` only after both end markers arrive. Bytes between those fences, including direct fd 1/2 writes and inherited child stdio, belong to that physical run and share its aggregate output budget. Structured console logs retain their own order; stdout and stderr each retain pipe order, but the merged presentation does not claim a total chronology across the independent control and descriptor channels.
|
|
20
20
|
|
|
@@ -32,6 +32,12 @@ Bytes before a new start fence prove that a settled run still produced descripto
|
|
|
32
32
|
|
|
33
33
|
**Move all user computation into a general child-process sandbox or permission layer.** That would replace DSH authority and sandbox decisions with a second system, which the project explicitly does not own.
|
|
34
34
|
|
|
35
|
+
**Treat every unanswered sample as a worker loss.** An expired deadline only proves that the host was unable to observe an answer, and reclaiming the worker then destroys bindings the cell never touched. Reclamation stays limited to a helper that leaves consecutive samples unanswered with no successful sample between them.
|
|
36
|
+
|
|
37
|
+
**Read one already-queued message before concluding.** Node delivers one helper message per event-loop turn, so the answer can sit behind messages the helper wrote earlier; the transport therefore reads until a turn delivers nothing.
|
|
38
|
+
|
|
39
|
+
**Reuse the helper's last periodic sample as the baseline.** It can predate work that already finished before submission, so the first cell after a completed background callback would be charged for that earlier work, which `test/isolated-budget-baseline.test.js` covers.
|
|
40
|
+
|
|
35
41
|
## Consequences
|
|
36
42
|
|
|
37
43
|
A synchronously stuck kernel is reclaimed by killing a plugin-owned helper process; DSH survives and observes a real child exit, output close, and cleanup outcome. This containment keeps session-worker teardown out of DSH, but it does not repair, characterize, or claim the same cause for every upstream Node teardown path.
|
|
@@ -39,3 +45,7 @@ A synchronously stuck kernel is reclaimed by killing a plugin-owned helper proce
|
|
|
39
45
|
The transport gains one process and an IPC hop. Ports, shared memory, and transferred buffers cannot cross the process boundary, so they stay inside the helper or become plain data; output continues over the helper's stdio pipes. The helper's own `execArgv` is emptied so it does not inherit host startup arguments.
|
|
40
46
|
|
|
41
47
|
Environment provenance is explicit in code: the user Worker receives the caller's projected environment, while coverage instrumentation and an Electron Host's Node startup override stay on the helper side. A reclamation refusal stays owned and observable instead of being reported as success or surfacing as an unhandled rejection.
|
|
48
|
+
|
|
49
|
+
An unmeasured cell keeps the state earlier cells proved. A derived `edit_run_code` execution that is never measured publishes an unexecuted settlement, which proves the accepted edit without claiming state: the enclosing root cell keeps its own journal and stays provable, the revision publishes no binding and no editable source, and recovery folds no derived state. Only the unmeasured route qualifies; a rejected source, an aborted cell or a lost helper keep the rule that a derived edit journal must record an execution.
|
|
50
|
+
|
|
51
|
+
The settlement suite proves the unmeasured routes, the consecutive keep/drop, the counter reset, the budget release, both cancellation-listener releases, the replay retry, the recovery notice of a replay pass that contracts a frontier and then ends early, and the no-op state effect. It does not separately prove the transport's handling of a message the parser still holds in parts ahead of an answer, nor a replay pass whose recovery cannot contract and is then followed by a blocked current cell; both are covered by their normal-path evidence and by inspection.
|
|
@@ -16,12 +16,42 @@ The log-fact owner belongs to the plugin/controller lifecycle rather than an ena
|
|
|
16
16
|
|
|
17
17
|
Result republication preserves the original settlement position and normalized evidence. A content-only prune clone, including a clone of another clone, is not a new execution, edit target, success or claim settlement. If the original result is absent, the original call position bounds the newly reconstructed chronology; later executions remain later. Tip identities retain their delivery positions so discovering an earlier success does not erase warnings delivered afterward.
|
|
18
18
|
|
|
19
|
-
Projection checkpoints are fold shortcuts only. State version 8 retains each settlement's independent publication index, first-observed index, ordinary/reconstructed classification and normalized run evidence, including ordinary runs without journals. Ordinary-result deduplication retains the first observation's call, event sequence and timeline position; admission requires matching ordinary settlement evidence or an identity-conflict clear, and every ordinary settlement requires its observation. It also retains the processed timeline position and ordered selection clears for turn resets and invalidations. The current frontier must agree with that retained lifecycle history and remain within the processed cut. Latest and editable runs are derived from the newest current-scope publication beyond that frontier; the success index is derived from retained successful publications. Edit targets and claim eligibility at dispatch are reconstructed from first-observed settlements and clears, using the same selection and target capture rules as the live transition. Their source must match the eligible ordinary or derived publication, and both current claim tables must match the reconstructed lifecycle. A late prune reconstruction cannot establish a target for an earlier dispatch. Selected fields and redundant target copies cannot certify their own provenance or chronology, and clones cannot revive a selection across an invalidation frontier. Normalized event facts retain call identity/source, result identity digests and formal source/replacement relations, public prune and turn inputs, journal presence, normalized settlement and recovery-boundary outcomes, and validated rewrites. They retain no result content. Checkpoint admission reconstructs the
|
|
19
|
+
Projection checkpoints are fold shortcuts only. State version 10 stores the executable call events exactly once: `executableCalls` owns the event, the ordered `calls` list keeps only canonical sequence numbers, and an ordinary-result observation keeps the call sequence plus a sha256 digest of that stored event instead of a second copy. A digest is compared where an inline copy used to be compared, so the admission decision is unchanged while the checkpoint no longer serializes each call once per referencing table. State version 9 and earlier refold from the session log, because the shape, not just the content, changed. State version 8 retains each settlement's independent publication index, first-observed index, ordinary/reconstructed classification and normalized run evidence, including ordinary runs without journals. Ordinary-result deduplication retains the first observation's call, event sequence and timeline position; admission requires matching ordinary settlement evidence or an identity-conflict clear, and every ordinary settlement requires its observation. It also retains the processed timeline position and ordered selection clears for turn resets and invalidations. The current frontier must agree with that retained lifecycle history and remain within the processed cut. Latest and editable runs are derived from the newest current-scope publication beyond that frontier; the success index is derived from retained successful publications. Edit targets and claim eligibility at dispatch are reconstructed from first-observed settlements and clears, using the same selection and target capture rules as the live transition. Their source must match the eligible ordinary or derived publication, and both current claim tables must match the reconstructed lifecycle. A late prune reconstruction cannot establish a target for an earlier dispatch. Selected fields and redundant target copies cannot certify their own provenance or chronology, and clones cannot revive a selection across an invalidation frontier. Normalized event facts retain call identity/source, result identity digests and formal source/replacement relations, public prune and turn inputs, journal presence, normalized settlement and recovery-boundary outcomes, and validated rewrites. They retain no result content. One limit remains: a settlement still carries the argument text of its own call, because the published run needs the value (its source) rather than a digest, and event facts for a tool call still carry that call payload so admission can replay the same transition. This change bounds the number of copies, not the per-call payload. Checkpoint admission reconstructs the timeline with the same single-event transition used by live folding while the retained payload stays inside a fixed budget, then compares every materialized field; beyond that budget it proves digest-bound continuity and frontier agreement instead of byte-for-byte replay equality of argument text. Every retained timeline fact carries a sha256 digest over its own serialized bytes, so a payload edit is rejected without folding the session a second time, and a checkpoint written before digests existed is still admissible (the host refolds it on the state-version mismatch). State version 11 adds that per-fact digest; version 10 stored the executable call events exactly once and kept canonical sequence numbers in calls. Journal/derived/binding facts, ordinary observations, settlements, selection, claims, transcript counters, unavailable evidence, pending/seen identities and the active prune window therefore share one lifecycle interpretation. Typed payload validation and the existing journal/derived/boundary owners validate the retained normalized inputs. The projection also retains presentation input facts: public append/replacement intents, context-step source categories and recognized runtime-message protocol inputs. Ordinary user and assistant content is omitted. These facts align with timeline event identity and drive the same projection transition at admission. The complete reconstructed projection must agree with the checkpoint, including ordered surface nodes, visible records/facts, message indexes, context steps and notice history. A historical catalog record therefore cannot certify its own visibility, and removing all visibility caches cannot suppress an actual append. Prompt consumers continue reading incremental state without raw-log scans. Missing settlements or valid-shaped journal divergences cannot be hidden by deleting their dependent caches. The timeline representation owner validates these relations together with typed table payloads, call/entry identities, normalized journals, derived runs and transcript counts before projection admission. Obsolete v1–v7 state requires refolding the complete log; a nonzero-start suffix alone is rejected with a seq-0 reread diagnostic. The Host owns watermark validation, suffix restore, fork inheritance and refolding. Raw journal ancestry and exact model-visible source relations still own recovery evidence. UI facts, cached state and matching recorded completions are not substitutes.
|
|
20
20
|
|
|
21
21
|
Submitting a cell reserves its memory configuration with the SessionRuntime owner before the first asynchronous observation on either the run or edit entry. The bridge and derived edit carry that same submission and its captured cut into execution rather than creating a second admission after reading. Runtime or session disposal invalidates that preparation, and an invalidated preparation cannot create a kernel or worker after the reader resumes. Reader failure releases the reservation; a genuinely new submission can use a fresh environment. Caller cancellation with an identified cut is settled by the existing cell owner as a no-op without starting a worker; it retains the caller's reason and does not create an unexplained recovery boundary.
|
|
22
22
|
|
|
23
|
+
Projection payloads are retained by two cumulative bounds (`internal/projection-retention.js`): the newest `retentionPayloadRuns` settlements keep their payload while the retained payload total stays under `retentionPayloadBytes`, and a cluster beyond either bound keeps its identity — the settlement, its executable call, its ordinary observation, its normalized result and its call/result event facts — while the argument text of that cluster becomes digests (`argsDigest`/`argsBytes` in the journal call records, `argumentsDigest`/`argumentsBytes` in the call events). A single cell larger than the byte budget is digested the same way rather than kept whole, and the edit path refolds it from the log. Only a cluster beyond `retentionClusters` (default 8; follows `retentionPayloadRuns` when unset; `0` means unlimited) is folded away: the state then keeps one chained `retentionSummary {fromSeq, toSeq, count, chainDigest, entries}` (state version 13), where `entries` keeps one compact `{resultSeq, callSeq, argumentsDigest}` record per folded cluster. A per-settlement digest stub was the previous shape and is not enough: 450 stubs cost roughly 1.7 MB before any payload, so the checkpoint still grew with how many cells the session ran. The byte cap is cumulative over the retained set, which is a function of the window rather than of the session: a session writing 400 KB per cell keeps only as many clusters textual as the budget allows (8 MiB / 400 KB ≈ 20, fewer once the selected run's copies are counted), so the checkpoint stays in the same order of magnitude as the small-cell rows instead of growing with what the session wrote.
|
|
24
|
+
|
|
25
|
+
Recovery tips stay silent when the newest cell alone exceeds the byte budget and its payload is digested: a tip is generated from journal diagnostics, and a cell whose text was digested has none, so emitting one would be indistinguishable from a real failure. The runtime continuation feedback carries that notice instead (`最新单元格超出保留预算,已从检查点移除` / `The newest cell exceeded the retention budget and was removed from the checkpoint`, `HOT_CELL_DROPPED` in `internal/projection-retention.js`). Do not turn that silence into a tip.
|
|
26
|
+
|
|
27
|
+
Admission receives no session log, so it cannot recompute the summary from the events; it proves what the records can prove: no settlement exists at or below `toSeq`, every retained settlement keeps its call entry, the record list has one entry per counted cluster, is ordered by result sequence, spans exactly `fromSeq`…`toSeq`, and reproduces `chainDigest`. A rolling digest alone was forgeable — editing the digest, the count or the first sequence passed — and a test pins that it no longer does. The claim that a record still describes the cluster the log holds is checked where the two sides meet: when a dropped cell is refolded for an edit, `refoldEditableSettlement` compares the refolded settlement's record with the stored one and refuses the edit with `保留摘要与被重折的结算不一致` / `Retention summary disagrees with the refolded settlement` when they differ. That is the stated, weaker claim for compacted checkpoints — digest-bound continuity, frontier agreement and a record that matches the log at refold time, not byte-for-byte replay equality of the dropped payloads. Uncompacted checkpoints keep the full replay equality, and a checkpoint written before version 13 refolds from the log.
|
|
28
|
+
|
|
29
|
+
The newest entry is not exempt from the byte bound. A single cell larger than the whole byte budget is digested like any other: the bound wins over keeping the hot cell's text, so a `run_code` call carrying megabytes of code cannot re-inflate the checkpoint. Its cluster, identity and result metadata stay, the edit path refolds it from the log, and the cached `latestRun`/`editableRun` are cleared and `hotCellDropped` is set so no consumer claims to hold text the checkpoint no longer carries. Measured with the shipped defaults and a single 2 MB cell: the checkpoint stays inside the documented bound, `hotCellDropped` is set, and a full-retention fold recovers the arguments exactly.
|
|
30
|
+
|
|
31
|
+
Digesting a cluster's payload is not permanent, and folding one away is not either. `withFullRetention` refolds the same session-log slice with pruning disabled, and the edit path uses it to recover the text of a cell the window digested or folded, so an edit or republication that needs an old cell still succeeds while the log can supply it. Only when the log itself cannot supply the cell do consumers report `retention-pruned` with the edit or republication wording above.
|
|
32
|
+
|
|
33
|
+
The window is enabled by default and is user configuration: `retentionPayloadRuns` (8), `retentionPayloadBytes` (8 MiB, maximum 16 MiB) and `retentionClusters` (defaults to the retained-cells window, so 8; `0` means unlimited). A cluster beyond the count window or the payload budget keeps its identity and has its argument text digested; only a cluster beyond `retentionClusters` is folded into the summary, which is why the default keeps the checkpoint from growing with session length. The `DSH_PTC_RETENTION_*` environment levers may only tighten a configured value — they exist for tests and can never widen the bound.
|
|
34
|
+
|
|
35
|
+
The count and byte floors are `8` cells and `1 MiB`, because a window smaller than the cells a fold has in flight cannot keep the stored publication and the replay equal: at 2 cells the recovery suites reject their own checkpoint, at 8 every recovery suite passes at both ends of the range. A request outside the declared bounds is not clamped to the floor: it falls back to the shipped default, because a floor-sized window is itself unprovable and a default is the only value the recovery suites prove.
|
|
36
|
+
|
|
37
|
+
Measured with `G:\Temp\measure-projection.mjs` against this tree, with the harness's own settlement counts (`counts.settlements`, `counts.summaryCount`); the retained column is the payload window (`retentionPayloadRuns`, default 8), not a settlement count:
|
|
38
|
+
|
|
39
|
+
| Session (`RUNS` × `CODE_BYTES` + `WRITE_BYTES`) | Checkpoint | Retained clusters | Folded clusters |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| 450 × 20 KB code + 20 KB write | 622,989 B | 8 | 442 |
|
|
42
|
+
| 900 × 20 KB code + 20 KB write | 827,206 B | 8 | 892 |
|
|
43
|
+
| 450 × 400 KB code + 400 KB write | 4,222,998 B | 8 | 442 |
|
|
44
|
+
|
|
45
|
+
A retained cell's argument text exists more than once. `executableCalls` owns the call event, the `tool/call` event fact keeps only its digest and byte length, the settlement keeps its own journal text, and the selected run objects (`latestRun`, `editableRun`) embed the parsed arguments, the source and the journal of the cell they name. `retentionPayloadBytes` bounds the *retained payload* — the settlement and executable copies — not the serialized checkpoint: the run copies are stored per selection and are not charged to that budget, because charging them would have to happen at a prune point where the payload is not yet attached, and moving the prune later would digest the very cell the user is looking at. The serialized total can therefore approach roughly twice the budget plus the per-cluster metadata, and that is the shipped bound. Measured: the two rows above, plus 450 × 400 KB ≈ 4.2 MB with an 8 MiB budget and an 8-cluster window, and about 1.7 KB of per-cluster metadata beyond the window.
|
|
46
|
+
|
|
47
|
+
The byte budget binds before the count window does for large cells (8 MiB / 400 KB ≈ 20 clusters, and fewer once the selected run's copies are counted), so the checkpoint is a function of the window rather than of what the session wrote. The residue that still grows with session length is not payload: every folded cluster leaves one ~150 B summary record (442 of them at 450 cells, 892 at 900, which is 67 KB → 136 KB) and the per-event identity tables grow with the event count (`seenCallIds` 21 KB → 42 KB over the same range), which is why 900 cells cost 33% more than 450 rather than twice as much. `retained + summaryCount == settlements` is expressed by the digest-in-place policy: a cluster inside the window keeps its records, a folded one keeps only its summary record, and no settlement is silently dropped without either.
|
|
48
|
+
|
|
23
49
|
## Consequences
|
|
24
50
|
|
|
25
51
|
pre-step and assembly read current projected facts rather than materializing history. Recovery and authoring receipt operations are explicit asynchronous reader consumers. Honest checkpoint restore and fork must preserve the same timeline, edit and runtime-message decisions as batch replay. Tests cover serialized transition boundaries, duplicate identities, prune replacement, observation/surface races, real SessionStore/query/registry composition and long unrelated histories.
|
|
26
52
|
|
|
27
53
|
This migration does not change tool names, journal generations, Host policy authority, output governance or the selected PTC language contract. Public execution-provider composition is owned by [ADR 0028](0028-attach-to-the-host-ptc-execution-seam.md), not this projection. Successful-result metadata and two-tool collapse integration remain separate boundaries.
|
|
54
|
+
|
|
55
|
+
## Release coverage exemptions (0.5.2)
|
|
56
|
+
|
|
57
|
+
Four recovery arms in `internal/session-runtime.js` carry `c8 ignore` directives so the release gate keeps its 100% lines and functions rule for every other line: the `recoverJournal` catch body, the volatile-suffix notice, the first-fold recovery boundary, the `alreadyReported` notice arm and the `droppedNames` notice arm. Each directive names the condition that keeps it out of a fixture today. Exit condition for 0.5.3: either a fixture reaches the arm, or its unreachability is proven by the fold arithmetic and the arm is deleted with that proof. Non-deterministic residual gaps observed across two identical full-coverage runs mean a global 100% floor cannot be a release stopping point until those long-standing error branches are addressed; the exemptions are recorded per line rather than by lowering the threshold.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 0045 Customize model-visible prompt text through one registry
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
The plugin authors model-visible text in a dozen places: the stable REPL guidance, the `run_code` description, the `edit_run_code` schema, the capability plan's navigation and category lines, the recovery and rewrite notices, the binding catalog, and the budget and diagnostic wording. Every one of them is behaviourally load-bearing, yet users can only choose policy modes in 能力与上下文; they cannot reword a single sentence. Requests to add project conventions, shorten recovery prose, or translate an instruction therefore end in forking the plugin.
|
|
6
|
+
|
|
7
|
+
Two naive fixes are refused. Replacing the whole system prompt removes the sentences that keep the tool contract executable and makes a failure impossible to attribute. Letting users edit diagnostics or message envelopes breaks the recognizers that decide which notice to deliver and which snapshot to reconcile.
|
|
8
|
+
|
|
9
|
+
## Decision
|
|
10
|
+
|
|
11
|
+
One registry of anchors and slots, one settings document, one total resolver.
|
|
12
|
+
|
|
13
|
+
- `internal/prompt-text.js` owns every anchor: an id, a delivery channel, a presence condition, an owner class and its slots. An anchor is what a composer delivers; a slot is what a user edits, and it carries a kind (`literal`, `aggregate` or `projected`), an owner (`A` editable, `B` code-owned, `C` owned by another surface), a reason code when read-only, an `omittable` flag and the byte-identical default literal. Composers import their default from this registry instead of declaring it, so the default text has one owner.
|
|
14
|
+
- An `aggregate` slot is a frozen item set: its container owns no bytes and holds the item slots, so item text stays editable while adding or removing an item — which would move the neighbours' bytes and connectors — does not exist as an operation.
|
|
15
|
+
- The `promptText` setting is a `ptc-prompt-text/v1` JSON document mapping slot id to replacement text. An absent id keeps the built-in text; an empty value omits an `omittable` slot and is rejected for a required one.
|
|
16
|
+
- Limits are measured in effective UTF-8 bytes: 4096 per slot, 16384 per anchor and 65536 for the document. They are enforced twice. `validatePromptTextDocument` runs on the save path and in `resolveConfig` and rejects the offending slot by id; `resolvePromptText` never throws, re-measures the same ceilings on the effective bytes of a document that never passed validation (the settings draft is such a caller), drops an offending override back to its default and reports `slot-exceeds-limit`, `anchor-exceeds-limit`, `document-exceeds-limit`, `missing-hole`, `not-writable` or `required-slot-empty`. A projected slot must keep exactly one of each registered hole at render time as well.
|
|
17
|
+
- The six read-only reason codes are `fact-derived`, `aggregate-frozen`, `protocol-exact-text`, `protocol-structural`, `shadows-host` and `other-owner`. An override of a read-only slot is refused rather than silently ignored.
|
|
18
|
+
- An unknown slot id stays in the document, is excluded from rendering and is reported, so a document written by another plugin version still loads.
|
|
19
|
+
- Five channels are wired: `prompt` (stable guidance and the `run_code` schema, rebuilt on the next request), `install` (`edit_run_code`, resolved when an agent installs the tool surface), `plan` (navigation, five category lines and the overage sentence, delivered to a new session or an explicit refresh and recorded by value as `ptc-capability-environment/v5`), `runtime` (binding catalog, recovery snapshot prefixes, tip bodies) and `result` (rewrite descriptions, diagnostics prose, the output-limit envelope).
|
|
20
|
+
- Recognition never depends on the sentence being recognized. A catalog message records whether it delivered an empty catalog in its own source, and a record without that flag is compared against frozen historical constants, so `catalog.empty` is editable without making a customized sentence unrecognizable. Snapshot prefixes and message identities stay code-owned.
|
|
21
|
+
- Derived policy sentences (variable redeclaration, module syntax, replay, Cordis recovery) are generated by code, marked `fact-derived` and are not editable. Diagnostics codes and their structure, tool names, `required` lists and enums stay protocol-owned.
|
|
22
|
+
|
|
23
|
+
## Consequences
|
|
24
|
+
|
|
25
|
+
An empty or absent override reproduces today's prompt byte for byte, so the existing guidance contract and keyless prefix tests remain the regression oracle. An override changes the cached prompt prefix from its first changed token on the next request; that is the accepted cost of a deliberate configuration change, not a defect.
|
|
26
|
+
|
|
27
|
+
Each channel's default is delivered byte-identically when the document is empty, and every channel has a test that pins that equality. `edit_run_code` schema text resolves at install time, so its overrides apply to a new session and never mutate a live schema mid-session. The plan text is recorded by value in the capability environment, so replay never depends on current settings.
|
|
28
|
+
|
|
29
|
+
User text is unvalidated prose. It can claim a capability the profile closed or describe the wrong execution world, and the panel can only warn. The bound is structural: read-only slots stay owned by code, the surface gate fails when a captured request contains text no anchor claims, and the coverage test fails when a delivered literal is missing from the registry, so "every fixed prompt is customizable" stays checkable.
|
|
30
|
+
|
|
31
|
+
## Alternatives considered
|
|
32
|
+
|
|
33
|
+
- Replace the whole system prompt. Rejected: it discards the sentences that keep `run_code` calls legal and destroys failure attribution.
|
|
34
|
+
- Flat per-asset rows with one ceiling each. Rejected: a single delivered paragraph is composed of several slots, so a per-asset ceiling cannot bound what the model receives, and a mixed anchor has no way to say which part is editable.
|
|
35
|
+
- Per-anchor slots with append/inherit modes. Rejected: append needs a placement rule relative to the existing paragraph order, and inherit gives two owners to one span.
|
|
36
|
+
- Typed holes (`{cellConventions}`) inside editable prose. Rejected: a second input path whose result cannot be validated against the configuration that generates the clause.
|
|
37
|
+
- A revision counter beside the recorded text. Rejected: a second source of truth that can disagree with the bytes actually delivered.
|
|
38
|
+
- Carrying prompt text inside named capability profiles. Rejected: switching a disclosure profile would silently rewrite the prompt prefix.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Gate the Commit With the Semantic-Evidence Tier
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
`npm run check` is the pre-commit gate: it classifies the candidate, runs the appropriate verification, and binds a source proof to the tree. Its full branch ran two complete inventories at once — the whole ordinary backend suite (every root `test/*.test.js`, one Node process per file) plus the complete browser visual suite. On a 24-CPU Windows workstation the backend inventory alone measured 880–908 s, so the gate took about 15 minutes before any failure surfaced, and it was not reliable there: two backend cases hard-coded a POSIX workspace cwd, which makes the kernel worker reject the derived filename on Windows, so the gate could never pass on that platform. A gate that is both slow and platform-red is not used as a gate: contributors either skip it or run it after the change is already pushed.
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
The commit gate runs the static prerequisites plus the **semantic-evidence backend tier**; the complete behavioral inventories stay behind `npm run verify`.
|
|
10
|
+
|
|
11
|
+
- `npm run check` records its proof from `npm run verify:check`: `review:validate`, `host:baseline:verify`, `semantic:check`, `build:check`, the syntax checks and `deterministic-test-groups.mjs --check`.
|
|
12
|
+
- `--check` selects `test:client` plus `test:backend:fast`. The Client units stay in the gate because a change can be covered only by them; leaving them out let a red Client suite pass, which is how the copy-key and layout regressions were first caught. The backend tier is the test files that `semantic-obligations.json` names as evidence for at least one obligation, minus coverage-only paths and the verification-tooling probes that fail by construction outside their own harness. Each obligation keeps at least one executable owner, and the fast tier is derived from the ownership graph rather than a hand-maintained list.
|
|
13
|
+
- `npm run verify` keeps the complete gate: `test:client:visual` plus the whole ordinary backend inventory. CI runs `verify:ci`, release preparation runs `verify` (and coverage), and any change that touches a semantic owner missing from the graph must extend the graph — which extends the fast tier at the same time.
|
|
14
|
+
- Browser suites are no longer part of the commit gate. They remain the complete visual contract and run in `verify`, in CI, and on demand.
|
|
15
|
+
- The backend runner rejects `--file` together with `--fast`, so a caller cannot mistake a focused selection for the tier.
|
|
16
|
+
|
|
17
|
+
Measured on the same Windows workstation (24 logical CPUs): `verify:check` completes in about 164 s wall clock once the Client units are included, because the Client and backend groups run in parallel (serial estimate ~231 s: Client ~128 s, fast tier ~96 s, static prerequisites ~7 s); the complete gate remains ~900 s for the backend inventory plus ~186 s of browser suites. Linux/WSL runs the complete backend inventory in 376 s at `DSH_PTC_TEST_CONCURRENCY=2`, so the tier split is a Windows-scale decision, not a suite that is inherently slow.
|
|
18
|
+
|
|
19
|
+
Two portability defects surfaced while making the gate reliable and are part of this decision's evidence: the two `plugin-settings` fixtures used `cwd: '/workspace'`, which produced a `file:///workspace/repl` worker filename that Windows rejects; they now use a platform-neutral cwd. The failing assertion also prints the runtime's own diagnostic instead of a bare boolean.
|
|
20
|
+
|
|
21
|
+
## Alternatives considered
|
|
22
|
+
|
|
23
|
+
**Keep the complete inventories in the commit gate.** Rejected: 15 minutes on the target workstation, and the same gate cannot be green there at all while a fixture is platform-specific. Contributors route around a gate they cannot run.
|
|
24
|
+
|
|
25
|
+
**Skip the two Windows-failing cases with a platform reason.** Rejected: the failures were a fixture defect, not a platform-specific behaviour. Fixing the fixture keeps the protection and removes the red.
|
|
26
|
+
|
|
27
|
+
**Run the whole backend inventory in one process (`--test-isolation=none`).** Rejected after measurement: the suite hung when files that own workers and kernels shared one process, so isolation is load-bearing.
|
|
28
|
+
|
|
29
|
+
**Move only the browser suite out.** Rejected after measurement: browsers cost 186 s, the backend inventory costs ~900 s. Moving browsers alone leaves the gate far outside its budget.
|
|
30
|
+
|
|
31
|
+
**Keep a hand-maintained fast list.** Rejected: it drifts from the ownership graph and silently stops covering new owners. Deriving the tier from `semantic-obligations.json` makes coverage and the gate the same fact.
|
|
32
|
+
|
|
33
|
+
## Consequences
|
|
34
|
+
|
|
35
|
+
The commit gate proves the static contract and the semantic-evidence tier, not the complete inventories; complete behavioral validation moves to `verify`, CI and release preparation. A regression that only a non-evidence test file can detect now surfaces there rather than at the commit gate. Adding an obligation or an evidence file widens the fast tier, so expanding protection remains a one-line graph change. [ADR 0036](0036-separate-focused-development-from-delivery-verification.md) still owns scheduling, [ADR 0033](0033-gate-client-visual-usability-in-a-browser.md) still owns complete browser validation, and [ADR 0035](0035-run-coverage-only-before-a-new-release.md) still owns release coverage.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Store journal argument digests in the projection
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`ptcPlusSessionLog` keeps one settlement cluster per settled cell. The cluster
|
|
10
|
+
carries the cell's call event, its normalized result and its publication, and each
|
|
11
|
+
of those carries the cell's journal. A journal's call records hold the argument
|
|
12
|
+
wire produced by `encodeValue`, which for a cell that writes a file or assembles a
|
|
13
|
+
large value is tens of kilobytes — the same order as the cell source itself. At
|
|
14
|
+
450 settled cells a single journal copy is about 20 KB per cell, so keeping every
|
|
15
|
+
cluster with its journal would put 9 MB back into the checkpoint at 450 cells and
|
|
16
|
+
roughly 40 MB at 2000, which is the growth issue #10 fixed.
|
|
17
|
+
|
|
18
|
+
The retention window exists for that reason. While it removed whole clusters, it
|
|
19
|
+
also removed the recovery evidence those clusters held: a cell beyond the window
|
|
20
|
+
could only be restored by refolding the session log, and when the log could not
|
|
21
|
+
supply it the cell became unavailable. Recovery therefore depended on a cache
|
|
22
|
+
setting.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
A cluster outside the payload window keeps its identity, its observation, its call
|
|
27
|
+
entry and its result, and replaces the payload text with digests:
|
|
28
|
+
|
|
29
|
+
- the call event's `data.arguments` becomes `{ argumentsDigest, argumentsBytes }`;
|
|
30
|
+
- the settlement's `call` carries the same digest pair;
|
|
31
|
+
- each journal call record replaces `args` with `{ argsDigest, argsBytes }`, and
|
|
32
|
+
keeps `global`, `member`, `ok`, `settle`, `value` and `error`, so status,
|
|
33
|
+
operations, user binding evidence, confirms and completion stay readable;
|
|
34
|
+
- a derived record replaces `code` with `{ codeDigest, codeBytes }` and keeps
|
|
35
|
+
`description`, `targetCallSeq` and the rewrite metadata.
|
|
36
|
+
|
|
37
|
+
A record carries either the text or its digest, never both. Digests are SHA-256
|
|
38
|
+
over the same wire bytes the text form holds, and byte counts are canonical
|
|
39
|
+
non-negative integers. A mixed checkpoint — recent clusters in text, older ones in
|
|
40
|
+
digests — is the normal shape.
|
|
41
|
+
|
|
42
|
+
## What admission proves
|
|
43
|
+
|
|
44
|
+
Admission no longer replays the whole log, so it proves the properties the shapes
|
|
45
|
+
themselves carry:
|
|
46
|
+
|
|
47
|
+
- every retained cluster is complete: call entry, observation, result and
|
|
48
|
+
positions agree;
|
|
49
|
+
- a cluster that carries digests carries digests on both sides of each relation
|
|
50
|
+
(settlement call and executable entry, publication journal and normalized
|
|
51
|
+
journal), and the digest pair is well formed;
|
|
52
|
+
- the interval summary, when present, reproduces its own chain and its range
|
|
53
|
+
matches the clusters it replaces;
|
|
54
|
+
- the frontier and selection history agree with the retained positions.
|
|
55
|
+
|
|
56
|
+
It does not prove that a digested record's digest describes the text the log
|
|
57
|
+
holds. That claim is checked where the two sides meet: refolding a dropped or
|
|
58
|
+
digested cluster for an edit or for recovery compares the refolded record with the
|
|
59
|
+
stored one, and rejects a mismatch.
|
|
60
|
+
|
|
61
|
+
## Recovery and refolding
|
|
62
|
+
|
|
63
|
+
Recovery rebuilds bindings from cell sources and journals. When the checkpoint
|
|
64
|
+
holds digests, `recoverJournal` repeats the fold with retention disabled on the
|
|
65
|
+
same event slice and reads the text from the log. The checkpoint stays bounded;
|
|
66
|
+
the log is the authority. The extra fold happens only when a digested cluster is
|
|
67
|
+
present.
|
|
68
|
+
|
|
69
|
+
## What is lost
|
|
70
|
+
|
|
71
|
+
- Byte-for-byte equality between the checkpoint's payload and the log's payload:
|
|
72
|
+
the checkpoint keeps the digest, not the text.
|
|
73
|
+
- A cell whose text cannot be refolded — because the log does not cover that range
|
|
74
|
+
— is unavailable rather than silently different. Consumers report
|
|
75
|
+
`该单元格超出保留窗口,已不可编辑` / `This cell is outside the retention window and
|
|
76
|
+
can no longer be edited`, and the generic `retention-pruned` for any other read.
|
|
77
|
+
|
|
78
|
+
## Consequences
|
|
79
|
+
|
|
80
|
+
The checkpoint's size is a function of the window and of the number of clusters,
|
|
81
|
+
not of how many bytes the session wrote. Folding a cluster into the interval
|
|
82
|
+
summary remains available through the `retentionClusters` configuration, but it is
|
|
83
|
+
no longer the only way to bound the checkpoint, so its default can be unlimited.
|
|
84
|
+
|
|
85
|
+
## Related
|
|
86
|
+
|
|
87
|
+
- ADR 0032 owns the projection and its admission claim.
|
|
88
|
+
- ADR 0045 owns the model-visible prompt text customization.
|
package/docs/architecture.md
CHANGED
|
@@ -13,7 +13,7 @@ PTC Plus 把 DSH PTC 模式的顶层 `run_code` 变成与 session 绑定的连
|
|
|
13
13
|
| Journal / replay | PTC Plus + session log | 记录 call transcript、settlement、completion 和恢复边界 |
|
|
14
14
|
| Presentation | DSH + PTC Plus | 保留 native guidance,并追加 REPL 指引与最小 explorer 声明 |
|
|
15
15
|
|
|
16
|
-
求值层由插件自有的 helper 进程承载:Host 只持有 `IsolatedWorker` transport,真实 `node:worker_threads` session worker 与其私有 `MessagePort` 都留在 helper 内,IPC 只传输 plain data。宿主回收卡住的计算时只强杀自己的 helper process,DSH 进程内没有需要终止的 session worker,因此不会触达已测得会落入 Node 24/26 cppgc teardown fault 的 in-host worker 强杀路径;`IsolatedOwner` 只在该实例的 real exit、output close 和 cleanup 都完成后释放。kernel、console、candidate 三个入口各自拥有 owner。helper 的 `init` 携带 fork 前 snapshot 的投影环境,user Worker 及其子进程保持原 env 契约,不会继承 `NODE_V8_COVERAGE` 等宿主 instrumentation;Electron Host 只在 helper fork 环境覆盖 `ELECTRON_RUN_AS_NODE=1`,该启动变量不进入 user Worker。inner Worker 的 compute budget 在每次请求提交时通过 helper 的 on-demand sample 取新鲜 `eventLoopUtilization` baseline,ready handshake
|
|
16
|
+
求值层由插件自有的 helper 进程承载:Host 只持有 `IsolatedWorker` transport,真实 `node:worker_threads` session worker 与其私有 `MessagePort` 都留在 helper 内,IPC 只传输 plain data。宿主回收卡住的计算时只强杀自己的 helper process,DSH 进程内没有需要终止的 session worker,因此不会触达已测得会落入 Node 24/26 cppgc teardown fault 的 in-host worker 强杀路径;`IsolatedOwner` 只在该实例的 real exit、output close 和 cleanup 都完成后释放。kernel、console、candidate 三个入口各自拥有 owner。helper 的 `init` 携带 fork 前 snapshot 的投影环境,user Worker 及其子进程保持原 env 契约,不会继承 `NODE_V8_COVERAGE` 等宿主 instrumentation;Electron Host 只在 helper fork 环境覆盖 `ELECTRON_RUN_AS_NODE=1`,该启动变量不进入 user Worker。inner Worker 的 compute budget 在每次请求提交时通过 helper 的 on-demand sample 取新鲜 `eventLoopUtilization` baseline,ready handshake 另有启动样本;采样等待(`sampleBoundMs`,默认 1000ms)在把 helper 通道读空之后才下结论,阻塞期间写入通道的答复即使排在此前消息之后也不会因过期定时器被判缺失,读数上限只报告"没有观察到答复";helper 退出、断连或无法接收请求仍按 worker-exit 有界失败并重置 worker,其他无法观察到答复的采样、以及 run 帧发出之前到达的墙钟或 compute 上限,都按未测量的 no-op 结算:默认 2 时在首次保留 worker 与既有 binding,连续达到 `unmeasuredCellLimit` 个则丢弃 live worker、保留已证明 frontier 由下一 cell 重放;历史重放中无法观察的 cell 同样保留 frontier 并重试。唯一例外由 [ADR 0024](adr/0024-repl-console-observation.md) 所有:同一 worker 已确认存在未完成的 observation 时,prepare 可先于预算启动,确认 observation 完成后才开始计时。
|
|
17
17
|
|
|
18
18
|
主入口接管模型直接发起的顶层 `run_code`,并通过 DSH tool registry 真实注册 `edit_run_code`。
|
|
19
19
|
使用 code-only direct-tool projection 的 PTC request 只暴露 `[run_code]`;插件用 agent scope 的
|
|
@@ -52,7 +52,7 @@ Client half 在侧栏 **插件 → 已安装 → dsh-ptc-plus** 的插件行配
|
|
|
52
52
|
总开关关闭时不注册 runtime、执行 hook、tool surface 或 system prompt section,也不读取 binding 存储;`ptc` 与兼容 `code` preset 会话在头部单独显示 `PTC Plus` 指示器。设置卡片显示“已启用/已停用”,稳定指引不包含 UI 品牌名。
|
|
53
53
|
`Config` 的字段形态由 `internal/settings-compat.js` 按已安装代际的公共能力决定:仍提供 section installer 的代际保留普通描述字段,由自身文档路径提交校验后的具体默认值;只有自管设置文档的代际才加 volatile 标记,字段构建器不具备该能力的代际保持可加载。版本号不参与该判断。
|
|
54
54
|
`autoDescribeRunCode` 默认开启且只控制请求绑定的本地执行策略。开启时,缺少外层 `run_code.description` 的调用使用派生参数通过本地 DSH 校验,备用摘要仅进入 presentation metadata;关闭时由 DSH 校验原始参数。两种状态的模型可见 tool schema、tool order 和 system sections 保持字节稳定并包含 required `description`;原始调用参数、cell 与嵌套 native 参数保持不变。
|
|
55
|
-
在 section-installer
|
|
55
|
+
在 section-installer 代际,除只由插件激活期读取的 `executionSeamTimeoutMs` 外,所有字段都由 settings watch 即时交给各自 owner,且不替换已有 session-bound binding;在 host-owned 代际,宿主把 volatile-only 变更提交进导出 `Config` 的 retained handles,运行中的 fiber 经公开的 `loader/volatile-update` 事件就地 reconcile,同样保留已有 session-bound REPL、binding 与 journal。每个已提交 cell 的预算与策略固定其提交时的配置代际,timer、worker 消息、program binding bridge、结果校验和诊断共同消费该快照;重配置更新随后提交 cell 的默认值。worker 生命周期上界(`maxOldGenerationSizeMb`、`workerReclaimMs`、`startupProbeMs`)在 worker 创建或关闭时从当前配置读取。`maxOldGenerationSizeMb` 在活动 worker 存在时因 Node 的创建期限制而拒绝并回滚。settings 服务缺失时 Host 回退到 composition config,并保持相同的运行时语义。
|
|
56
56
|
|
|
57
57
|
`bindingUpdates` 是新配置的统一入口,默认 `stateful`,也可选择 `protected`。Host schema 保留该字段原始缺省状态,再由 `runtime-config.js` 把完整旧策略映射到相应新策略;混合旧开关保留显式 `legacyBindingSettings` 兼容态。Client 复用同一 resolver,仅在兼容态展示旧开关;用户选择统一开关时,一次 settings update 同时写入新策略并清除兼容标记。
|
|
58
58
|
|
package/docs/client-ui.md
CHANGED
|
@@ -51,6 +51,7 @@ Client 按数据、传输、注册与视图分文件,沿用既有 `createX(Rea
|
|
|
51
51
|
| 核心工作区 | REPL 语法 | `bindingUpdates` | 默认 `stateful`;同作用域的声明与赋值更新变量、函数、类和导入。选择 `protected` 后保护新建 const/import,并检测跨 cell 重声明。两种策略均支持静态 import/export。 |
|
|
52
52
|
| 核心工作区 | 状态与恢复 | `durableReplay` / `tipsEnabled` | 默认开启;控制 worker 重启后的状态恢复与失败恢复提示。 |
|
|
53
53
|
| 核心工作区 | 状态与恢复 | `tipCooldownMessages` / `tipEscalationFailures` | 同类提示间隔与详细提示阈值。 |
|
|
54
|
+
| 核心工作区 | 状态与恢复 | `unmeasuredCellLimit` | 连续第几个未测量 cell 触发丢弃(默认 2:第一个保留,第二个丢弃);已证明 frontier 留给下一 cell 重放。 |
|
|
54
55
|
| 扩展与容错 | 工具扩展 | `userBindingsEnabled` | 默认开启,显式关闭仍有效;开启时加载跨会话 TypeScript helper,并在开关下方显示管理按钮,同时启用全局绑定工作台与 `/binding` Agent 编写入口。 |
|
|
55
56
|
| 扩展与容错 | 工具扩展 | `cordisToolsEnabled` | 默认关闭;开启后为 PTC agent 加入官方 Cordis 工具、指引与精确的 `cordis-plugin-development` companion Skill,不发布同目录 sibling。 |
|
|
56
57
|
| 扩展与容错 | 调用容错 | `autoDescribeRunCode` / `canonicalizeToolCalls` | 默认开启;允许缺少外层摘要的 `run_code` 执行,并修复 schema 可唯一确认的顶层 native 工具误调用。 |
|
|
@@ -61,6 +62,8 @@ Client 按数据、传输、注册与视图分文件,沿用既有 `createX(Rea
|
|
|
61
62
|
| 界面与资源 | 资源限制 | `computeMs` / `maxWallMs` | 单 cell event-loop active(包括同步阻塞)与总耗时预算;前者不证明 CPU 消耗。 |
|
|
62
63
|
| 界面与资源 | 资源限制 | `maxOldGenerationSizeMb` / `maxNestedRunCodeDepth` | kernel worker 内存与嵌套执行深度。 |
|
|
63
64
|
| 界面与资源 | 资源限制 | `maxOutputBytes` | 单 cell 日志与返回结果的合计字节上限。 |
|
|
65
|
+
| 界面与资源 | 资源限制 | `sampleBoundMs` / `workerReclaimMs` | 计算基线采样等待(未观察到的采样按未测量结算;transport 报告退出/断连/无法发送时仍走 worker-exit);worker 关闭阶段上限(默认 5000ms、最小 1000ms;exit 确认与其后的输出关闭各自以此为上界):cell 触发的 session kernel exit 超时保留 helper 并阻止该会话后续 cell;startup、idle 或输出关闭路径的失败,以及 binding console 与 candidate 只保留实例并在释放时报告。 |
|
|
66
|
+
| 界面与资源 | 资源限制 | `startupProbeMs` / `executionSeamTimeoutMs` | worker 启动探针上限;等待宿主执行入口的上限(只在插件激活或重载时读取)。 |
|
|
64
67
|
| 界面与资源 | 资源限制 | `maxValueNodes` / `maxValueEdges` / `maxValueArrayLength` / `maxValueBigIntDigits` | Value Graph 与大数组、BigInt 的返回上限;数组长度限制同时约束单数组和一份 graph 的累计槽位。 |
|
|
65
68
|
| 实验性 | 能力与上下文 | `capabilityManagementEnabled` | 默认关闭;开启实验性能力管理,冻结新会话的能力方案。 |
|
|
66
69
|
| 实验性 | 能力与上下文 | `capabilityBaseline` / `capabilityPolicy` | 可搜索的能力列表、常用勾选、独立可用性与说明规则;默认/类别/来源/单项覆盖,分配依据及预算分别选择。 |
|
|
@@ -75,7 +78,7 @@ Client 按数据、传输、注册与视图分文件,沿用既有 `createX(Rea
|
|
|
75
78
|
|
|
76
79
|
## enabled 开关与正文功能标记
|
|
77
80
|
|
|
78
|
-
保存设置后,Host 在 settings watch 中安装、卸载或重配置对应 owner。能力与上下文的独立职责输入用于新会话;已有会话保持冻结方案,执行 `/capabilities refresh`
|
|
81
|
+
保存设置后,Host 在 settings watch 中安装、卸载或重配置对应 owner。能力与上下文的独立职责输入用于新会话;已有会话保持冻结方案,执行 `/capabilities refresh` 后才同步说明、发现与执行成员。其他可重配置字段即时生效;`executionSeamTimeoutMs` 由激活期等待拥有,只在插件激活或重载时读取。已提交的 cell 的预算与策略固定使用提交时的配置快照,运行中的更新从随后提交的 cell 开始使用;worker 生命周期上界(`maxOldGenerationSizeMb`、`workerReclaimMs`、`startupProbeMs`)在 worker 创建或关闭时从当前配置读取;这不会替换 session-bound REPL 或已有 binding。`maxOldGenerationSizeMb` 在活动 worker 存在时因 Node 的创建期限制而拒绝并回滚,其他可重配置字段照常交给 owner。关闭后唯一的宿主副作用是注册 settings 命名空间,
|
|
79
82
|
保证设置卡片仍然可读;此时只有 `enabled` 可写,其他控件被禁用。运行时重配置失败时回滚到上一次已应用值。
|
|
80
83
|
|
|
81
84
|
`cordisToolsEnabled` 默认关闭并即时生效。它不切换 preset,而是把官方 Cordis 工具、owner guidance 与 `cordis-plugin-development` Skill 作为一个 agent-scoped mount 加入或移出同时暴露 `run_code`、`skill` 工具与 preset/Skill service 的 PTC agent;顶层仍只有 `run_code`,编辑器通过程序内的 `tools.edit_run_code` 调用,普通 agent 不继承。暴露面齐全但挂载失败时,启用会完整回滚;agent scope 缺少 companion surface 时只是延后,不报错也不发布。完整运行要求见 [运行时参考](runtime-reference.md)。
|
|
@@ -92,7 +95,7 @@ Client 按数据、传输、注册与视图分文件,沿用既有 `createX(Rea
|
|
|
92
95
|
|
|
93
96
|
代码控制台直接执行当前条目的未保存源码草稿。用户可输入 `readText("./example.txt")`、声明变量或使用顶层 `await`,通过运行按钮或 Ctrl/Cmd+Enter 执行;下一条命令可继续使用临时变量。控制台通过 `console-run` 取得 Host 生成的 opaque environment handle,并以 `console-release` 释放;单次候选导出调用通过兼容的 `run` RPC 提供。控制台 worker 使用共同 root 编译与逻辑状态机制,源码草稿使用共同模块编译入口,Node REPL 承担底层求值;它不连接 Agent kernel,也不写入其 journal。模块导出可直接按名称调用,相对 import 从 binding 存储目录解析。运行、停止、重置环境、清空记录分别是独立动作;折叠不重置环境。普通语法或运行错误保留状态和此前修改,停止、超时、输出超限与 worker 故障会释放环境。执行代码具有 DSH 进程的 Node/OS 权限,这种状态隔离不构成 sandbox。
|
|
94
97
|
|
|
95
|
-
首次执行才创建 worker。源码草稿改动后的下一次执行、切换条目、保存修改后的源码、离开工作台或手动重置都会清空旧环境。连续 10 分钟没有执行代码时自动释放,输入输出记录保留;下一次从当前草稿开始,绝不自动重放旧命令。Host 最多保留 4 个临时环境,每个环境一次仅执行一个命令,并沿用 `maxWallMs`、`maxOutputBytes` 和 `
|
|
98
|
+
首次执行才创建 worker。源码草稿改动后的下一次执行、切换条目、保存修改后的源码、离开工作台或手动重置都会清空旧环境。连续 10 分钟没有执行代码时自动释放,输入输出记录保留;下一次从当前草稿开始,绝不自动重放旧命令。Host 最多保留 4 个临时环境,每个环境一次仅执行一个命令,并沿用 `maxWallMs`、`maxOutputBytes`、`maxOldGenerationSizeMb` 和 `workerReclaimMs`。源码与单次命令各限制 1 MiB;Client 最多保留 40 条、合计 131072 个 UTF-16 code unit 的展示记录,单条最多 65536 个,截断有明确标记。关闭全局绑定功能或插件时释放全部临时环境。
|
|
96
99
|
|
|
97
100
|
每次执行的命令与输出分开展示。输出沿用会话工具结果的灰色背景、边框和 12px 圆角,带独立标签;长结果在至多 240px 高的内容区内滚动,窄屏自动换行,错误使用错误色。设置弹窗与 REPL 页复用同一输出组件。
|
|
98
101
|
|
|
@@ -106,7 +106,11 @@ SessionRuntime 创建 kernel 时完全忽略历史 nodes、head、checkpoints
|
|
|
106
106
|
求值的 cell 无论静态分类如何都记录为 `volatile`。这是已知配置态,不是运行时降级,因此不
|
|
107
107
|
发送模型可见警告;system prompt 直接告知模型 binding 仅在当前进程可复用,`repl.state(list)`
|
|
108
108
|
返回同一状态。未进入 evaluator 的调用仍是 `noop`,取消、超时和 worker failure 仍是
|
|
109
|
-
`discarded
|
|
109
|
+
`discarded`;例外是无法测量而在 run 帧发出前结束的 cell(未观察到采样答复、墙钟或 compute 上限先到),
|
|
110
|
+
它以 `noop` 结算:默认 2 时第一次保留 live worker 与既有 binding,连续达到 `unmeasuredCellLimit` 次才丢弃 live worker 并保留可重放的
|
|
111
|
+
已证明 frontier;transport 报告 helper 退出、断连或无法接收请求时仍按 worker-exit 丢弃并重置 worker,见
|
|
112
|
+
[ADR 0027](adr/0027-run-the-session-kernel-in-a-killable-helper-process.md)。关闭期间不能保存
|
|
113
|
+
durable named state;restore 后的下一 cell 仍被强制为 volatile。
|
|
110
114
|
|
|
111
115
|
journal、diagnostic、source、cause、call、operation、completion 和 completion error 都使用封闭字段集合;未知、symbol 或非枚举自有字段会使 journal 无效。capability-call `args`/`value` 与 return completion `value` 都是封闭、规范化的 `ptc-value-graph/v1` envelope。诊断结构、source frame 依赖和稳定代码见[架构说明](architecture.md#journal-与恢复)。
|
|
112
116
|
|
|
@@ -221,6 +225,8 @@ surface replacement 是否删除状态取决于模型失去了什么,而不取
|
|
|
221
225
|
|
|
222
226
|
基础设施失败会终止当前 worker,并把 log-only recovery boundary 放入当前已结算 `tool/result` 的私有 metadata。kernel 从失败 node 的 parent 重新重放;若该 frontier 仍失败则继续向 parent 收缩,直到最大可验证 frontier 或空 REPL。触发恢复的当前 `run_code` 在候选 frontier 验证成功前不执行,验证后在同一次请求中继续。boundary 的失败身份可为顶层 call,或已经由共享 timeline 证明两组 dispatch pairs 和根最终关联的真实派生 start 序号;原始 nested start 不足以证明该身份。收缩保留失败 node 的已证明 parent,之后独立的有效 carrier 仍可进入新 ancestry。boundary 无效或历史本身无法完整折叠时,相关记录和可能依赖它的状态不得进入 frontier;这类历史 PTC metadata 损坏本身不返回永久 recovery error,而是继续收缩,最差用空 worker 执行当前 cell,并在其最终 result 持久表达新 ancestry。已经写入旧版自定义 boundary event 的日志必须先通过显式、非破坏迁移转换为 DSH 可加载的 metadata;迁移失败时不得覆盖原始日志。
|
|
223
227
|
|
|
228
|
+
记录的 diagnostics 与 completion 是同一 settlement 的两份表述:`completion.error.message` 由第一条 diagnostic 渲染得到,因此两者一致只证明这份记录内部自洽,不能证明文字未被改写(编辑 diagnostic 后重新渲染 completion 仍一致)。发布 `tool/result` 私有 metadata 时,runtime 同时写入这些 diagnostic 所述事实的摘要 `dshPtcPlusDiagnosticWording.digest`,覆盖 `message` 以及 code、severity、phase、stateEffect、dispatchState、cause、collisions;`help` 与 `source` 属于呈现(历史记录的 help 措辞和 caret 位置可来自更早 generation),不进入摘要。恢复折叠在把该记录当作证据前重算并比较摘要:摘要缺失表示记录发布于该字段引入之前,仍按旧证据规则处理;摘要存在而不再匹配时,该 cell 不能证明自己的 binding,与其它损坏 metadata 一样建立收缩点。该摘要证明记录未被部分改写,不是不可伪造的边界。
|
|
229
|
+
|
|
224
230
|
## State Operations
|
|
225
231
|
|
|
226
232
|
```ts
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[中文](#cn-v052) | [English](#en-v052)
|
|
2
|
+
|
|
3
|
+
<h3 id="cn-v052">宿主适配</h3>
|
|
4
|
+
|
|
5
|
+
* 目标发行仍是当前官方 DSH,插件继续使用它的公共扩展面(设置卡片座位、会话投影和公共 `Menu`),本版本不要求宿主侧改动
|
|
6
|
+
* 升级前写下的会话日志检查点会按新格式重新折叠,旧会话照常恢复,不需要先清空会话或配置
|
|
7
|
+
|
|
8
|
+
<h3>新增</h3>
|
|
9
|
+
|
|
10
|
+
* 「能力与上下文 → 提示文本」可以逐句改写插件写给模型的固定提示:每一行标明所属通道、存在条件、字节数和只读原因,只读段落与宿主段落(系统提示与工具说明)只展示、不能改
|
|
11
|
+
* 提示文本是一张紧凑表格,点一行展开该行的编辑区;编辑先进草稿,**保存**才生效,**取消**丢弃,保存前可以看到逐条差异及其生效时机(下一次请求、新会话或下次触发)
|
|
12
|
+
* 检查点的保留窗口在设置里可调:保留最近单元格数(8–512,默认 8)、保留载荷预算(1 MiB–16 MiB,默认 8 MiB,最坏情况约 32 MB)、单元格簇上限(默认与保留单元格数一致,`0` 表示只由载荷预算约束)
|
|
13
|
+
* 计算基线采样、worker 回收、启动探针与宿主执行入口的等待上限,以及连续未测量 cell 的丢弃阈值,都可以按机器情况调整
|
|
14
|
+
|
|
15
|
+
<h3>修复</h3>
|
|
16
|
+
|
|
17
|
+
* 会话日志检查点的体积由窗口与预算决定,不再随会话累计写入量无限增长:窗口按条数与字节双重上限把旧结算折成摘要记录,超出预算的单个单元格整簇移除。以每格 20 KB 代码加 20 KB 写入的会话实测,450 格为 0.62 MB、900 格为 0.83 MB
|
|
18
|
+
* 被移出检查点的单元格在需要时从会话日志重新折叠,因此仍可编辑和查看;日志无法提供时,编辑会提示「该单元格超出保留窗口,已不可编辑」
|
|
19
|
+
* 单个单元格本身就超过载荷预算时,它会连同参数原文一起移出检查点并提示「最新单元格超出保留预算,已从检查点移除」,而不是让检查点无限变大
|
|
20
|
+
* 提示文本面板里的原因码与槽位种类按界面语言显示,中文界面不再出现内部代码;只读槽位的说明与按钮样式与设置页其余部分一致
|
|
21
|
+
* 选中提示文本的某一行后,编辑区会滚动到可见位置,不再出现「点了行却没看到编辑器」
|
|
22
|
+
|
|
23
|
+
<h3>升级</h3>
|
|
24
|
+
|
|
25
|
+
* 需要 Node.js `^22.19.0 || >=24.0.0`;升级插件后请重启 DSH
|
|
26
|
+
* 现有配置不需要迁移:没有显式设置的保留参数使用上述默认值,已有配置里的其它字段照旧生效
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
dsh plugin --profile <profile> add dsh-ptc-plus@0.5.2
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<h3 id="en-v052">Host Compatibility</h3>
|
|
35
|
+
|
|
36
|
+
* This release targets the current official DSH release and keeps using its public extension surfaces (the settings card seat, the session projection and the public `Menu`); no host-side change is required
|
|
37
|
+
* Session-log checkpoints written before this version are refolded in the new format, so existing sessions keep recovering without clearing the session or the configuration
|
|
38
|
+
|
|
39
|
+
<h3>New Features</h3>
|
|
40
|
+
|
|
41
|
+
* **Capabilities & context → Prompt text** now lets you rewrite, sentence by sentence, the fixed text the plugin writes for the model. Each row states its channel, when it applies, its byte size and why it is read-only; read-only and host-owned sections (system prompt and tool descriptions) are shown only
|
|
42
|
+
* The prompt text surface is a compact table: select a row to expand its editor. Edits stay in a draft, **Save** applies them, **Cancel** discards them, and the pending diff with its effect time (next request, new session or next trigger) is visible before saving
|
|
43
|
+
* The checkpoint retention window is configurable: retained recent cells (8–512, default 8), retained payload budget (1 MiB–16 MiB, default 8 MiB, worst case about 32 MB) and the retained cluster limit (defaults to the retained cells; `0` means the payload budget is the only bound)
|
|
44
|
+
* The bounds for compute-baseline sampling, worker reclamation, the startup probe and the host execution seam, plus the threshold for consecutive unmeasured cells, can be tuned for the machine
|
|
45
|
+
|
|
46
|
+
<h3>Fixes</h3>
|
|
47
|
+
|
|
48
|
+
* The session-log checkpoint is bounded by the window and the budget instead of growing without limit with what the session writes: a count and byte window folds older settlements into summary records and removes a single cell that exceeds the budget on its own. Measured on sessions writing 20 KB of code plus 20 KB per cell: 0.62 MB at 450 cells and 0.83 MB at 900
|
|
49
|
+
* A cell removed from the checkpoint is refolded from the session log when it is needed, so it stays editable and readable; when the log cannot provide it, editing reports "This cell is outside the retention window and can no longer be edited"
|
|
50
|
+
* A single cell larger than the payload budget is removed with its argument text and reported as "The newest cell exceeded the retention budget and was removed from the checkpoint", instead of letting the checkpoint grow without bound
|
|
51
|
+
* The reason codes and slot kinds in the prompt text surface follow the interface language, so Chinese users no longer see internal identifiers; read-only slot descriptions and buttons match the rest of the settings page
|
|
52
|
+
* Selecting a prompt text row scrolls its editor into view, so a selected row always shows the editor it expands
|
|
53
|
+
|
|
54
|
+
<h3>Upgrade</h3>
|
|
55
|
+
|
|
56
|
+
* Requires Node.js `^22.19.0 || >=24.0.0`; restart DSH after upgrading the plugin
|
|
57
|
+
* No configuration migration is needed: retention fields that are not set use the defaults above, and other configured fields keep their values
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
dsh plugin --profile <profile> add dsh-ptc-plus@0.5.2
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
Full Changelog: https://github.com/muyuanjin/dsh-ptc-plus/compare/v0.5.1...v0.5.2
|
|
66
|
+
|
|
67
|
+
<h3>稳定性</h3>
|
|
68
|
+
|
|
69
|
+
* 表面重发布(例如切换工作目录、能力观测、宿主重渲染)不再重建内核:只要 worker 还活着,绑定就在原地保留,视图变化不影响计算状态
|
|
70
|
+
* 恢复提示只在真的需要时出现:完整恢复不再发通知,只有确实无法恢复的绑定才被点名(`PTC-R002`),曾被宣布可用之后消失的绑定另有 `PTC-R006` 点名
|
|
71
|
+
* 编辑命令是追加语义:被接受的编辑新增一次调用,原有单元格与其绑定保持不变;改写历史是可选的未来能力,默认关闭
|
|
72
|
+
* 长会话实测:163 个 run_code 单元格的会话中恢复类诊断数为 0
|
|
73
|
+
|
|
74
|
+
<h3 id="en-stability">Stability</h3>
|
|
75
|
+
|
|
76
|
+
* A surface republish (working-directory change, capability observation, host re-render) no longer rebuilds the kernel: while the worker is alive the bindings stay where they are, and a change of view does not change computation state
|
|
77
|
+
* Recovery notices appear only when they are needed: a complete restore is silent, a binding that truly cannot be restored is named (`PTC-R002`), and one that was announced live and later vanished is named separately (`PTC-R006`)
|
|
78
|
+
* An accepted edit appends a call: the original cell and its bindings stay unchanged; rewriting history remains a future opt-in, off by default
|
|
79
|
+
* Measured on a long session: 163 run_code cells produced zero recovery diagnostics
|