dsh-ptc-plus 0.3.1 → 0.3.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 CHANGED
@@ -133,8 +133,14 @@ Settings are separated into common and compatibility controls, optional capabili
133
133
 
134
134
  “Allow `run_code` execution without a summary” is on by default. Code still executes when the model omits the outer `description`; with enhanced tool cards enabled, the UI shows a fallback summary. When this setting is disabled, DSH validates the field normally. This setting does not change model requests or original call arguments.
135
135
 
136
+ “Allow top-level function/class redeclarations” is independent from the existing variable-redeclaration switch and defaults to off. When enabled, a later cell can use an ordinary named `function` or `class` declaration to replace an existing writable binding. Replacement takes effect at the declaration position and does not emulate function hoisting. Imports, immutable `const` bindings, reserved names, and bindings with unknown provenance are still rejected before execution. Live changes affect subsequently submitted cells; cold recovery always uses the policy recorded for each cell.
137
+
136
138
  In an enabled `ptc` session, the header shows a green `PTC Plus` indicator. Hover, focus, or click it to inspect the variables, functions, classes, and imports available to the next cell and expand their bounded definition source. The card reads submitted source only; it does not inspect runtime values, trigger getters, or execute code. `run_code` and `edit_run_code` remain expandable in the conversation body. A feature marker appears only when result metadata proves that the feature affected that execution.
137
139
 
140
+ ![Reusable REPL bindings](assets/ptc-plus-bindings-en.png)
141
+
142
+ *A real session: “Reusable REPL bindings” shows the bindings available to the next cell.*
143
+
138
144
  Every setting applies live and keeps existing bindings. A submitted cell uses one configuration for its complete execution; changes made while it runs apply to cells submitted afterward. A failed change rolls back. Node fixes a worker's V8 old-generation limit when the worker starts, so that one setting is rejected while a session worker is active and can be changed after the session is disposed. A failed enable is rolled back and persisted as disabled.
139
145
 
140
146
  `cordisToolsEnabled` is off by default. Turning it on atomically adds DSH's official Cordis tools, owner guidance, and exactly the `cordis-plugin-development` companion Skill to PTC agents; sibling Skills in the shipped preset are not exposed. Turning it off removes all three. It neither switches presets nor changes the direct `run_code`/`edit_run_code` surface. Cordis runs model-written plugins against the live DSH runtime, so enabling it requires shell-level trust.
package/README.md CHANGED
@@ -133,8 +133,14 @@ import { readFile } from 'node:fs/promises'
133
133
 
134
134
  “允许执行缺少摘要的 `run_code`”默认开启。模型没有提供外层 `description` 时,代码仍会执行;启用增强工具卡片时,界面显示备用摘要。关闭本设置后按 DSH 原生规则校验。这个开关不改变模型请求或原始调用参数。
135
135
 
136
+ “允许顶层函数/类重声明”与已有的变量重声明开关相互独立,默认关闭。开启后,后续 cell 可以用普通 named `function` / `class` 声明替换已有的可写 binding;替换在声明所在位置生效,不模拟函数提升。import、不可写 `const`、保留名称和来源不明的 binding 仍会在执行前拒绝。开关变化只影响随后提交的 cell,cold recovery 始终按每个 cell 已记录的策略重放。
137
+
136
138
  插件启用且会话使用 `ptc` preset 时,会话头部显示绿色 `PTC Plus` 标识。悬浮、聚焦或点击后可以查看下一 cell 可复用的变量、函数、类和 import,并展开其有界定义源码。卡片只读取已提交的源码,不读取运行时值、不触发 getter,也不执行代码。正文中的 `run_code` 与 `edit_run_code` 仍可展开查看源码和结果;只有结果 metadata 能证明某项功能确实生效时,预览才显示对应标记。
137
139
 
140
+ ![REPL 可复用绑定](assets/ptc-plus-bindings-zh.png)
141
+
142
+ *真实会话:“REPL 可复用绑定”卡片展示下一 cell 可直接复用的 binding。*
143
+
138
144
  所有设置都会实时应用,并保留已有 binding。已提交的 cell 在完整执行期间使用同一份配置;执行期间发生的更新用于随后提交的 cell。更新失败会回滚。Node 在 worker 创建时固定 V8 old-generation 上限,因此活动 session worker 存在时修改这一项会被拒绝,释放 session 后才能修改。启用失败时,运行时会回滚并把设置保持为停用。
139
145
 
140
146
  `cordisToolsEnabled` 默认关闭。打开后,DSH 官方 Cordis 工具、owner guidance 与精确的 `cordis-plugin-development` companion Skill 会作为一个整体加入 PTC agent;同一 shipped preset 目录中的其他 Skill 不会随之暴露。关闭时这三项也会一起移除;它不切换 preset,也不改变 `run_code`/`edit_run_code` 的直接调用面。Cordis 能在实时 DSH runtime 中运行模型编写的插件,开启它需要接受 shell 级信任。
Binary file
Binary file
package/client.js CHANGED
@@ -57,6 +57,15 @@
57
57
  description: "\u5141\u8BB8\u9876\u5C42 const \u6216 let \u58F0\u660E\u66FF\u6362\u5DF2\u6709\u53D8\u91CF\uFF1B\u5173\u95ED\u540E\uFF0C\u91CD\u58F0\u660E\u4F1A\u5728\u6267\u884C\u524D\u88AB\u62D2\u7EDD\u3002",
58
58
  descriptionEn: "Allows top-level const or let declarations to replace existing variables; disabling rejects redeclarations before execution."
59
59
  },
60
+ {
61
+ key: "looseTopLevelFunctionClassRedeclarations",
62
+ type: "boolean",
63
+ default: false,
64
+ label: "\u5141\u8BB8\u9876\u5C42\u51FD\u6570/\u7C7B\u91CD\u58F0\u660E",
65
+ labelEn: "Allow top-level function/class redeclarations",
66
+ description: "\u5141\u8BB8\u9876\u5C42 function \u6216 class \u58F0\u660E\u66FF\u6362\u5DF2\u6709\u7684\u53EF\u5199 binding\uFF1B\u4E0D\u53EF\u5199\u6216\u4FDD\u7559 binding \u4ECD\u4F1A\u5728\u6267\u884C\u524D\u88AB\u62D2\u7EDD\u3002",
67
+ descriptionEn: "Allows top-level function or class declarations to replace existing writable bindings; immutable and reserved bindings are still rejected before execution."
68
+ },
60
69
  {
61
70
  key: "autoRewriteImports",
62
71
  type: "boolean",
@@ -243,6 +252,7 @@
243
252
  labelEn: "Advanced behavior",
244
253
  fields: Object.freeze([
245
254
  "looseTopLevelRedeclarations",
255
+ "looseTopLevelFunctionClassRedeclarations",
246
256
  "autoRewriteImports",
247
257
  "autoStripExports",
248
258
  "autoSplitRedeclarations",
@@ -274,10 +284,23 @@
274
284
  );
275
285
 
276
286
  // src/client-activity.js
277
- var JOURNAL_VERSIONS = /* @__PURE__ */ new Set([1, 2, 3]);
287
+ var JOURNAL_VERSIONS = /* @__PURE__ */ new Set([1, 2, 3, 4]);
278
288
  var JOURNAL_STATUSES = /* @__PURE__ */ new Set(["durable", "volatile", "discarded", "noop"]);
279
289
  var BINDING_MODES = /* @__PURE__ */ new Set(["loose", "strict"]);
280
290
  var JOURNAL_FIELDS = /* @__PURE__ */ new Set([
291
+ "version",
292
+ "bindingPolicy",
293
+ "rewritePolicy",
294
+ "moduleSemantics",
295
+ "status",
296
+ "calls",
297
+ "operations",
298
+ "confirms",
299
+ "diagnostics",
300
+ "completion",
301
+ "volatileReason"
302
+ ]);
303
+ var PREDECESSOR_JOURNAL_FIELDS = /* @__PURE__ */ new Set([
281
304
  "version",
282
305
  "bindingMode",
283
306
  "rewritePolicy",
@@ -289,12 +312,15 @@
289
312
  "completion",
290
313
  "volatileReason"
291
314
  ]);
292
- var LEGACY_JOURNAL_FIELDS = new Set([...JOURNAL_FIELDS].filter((key) => key !== "rewritePolicy"));
315
+ var LEGACY_JOURNAL_FIELDS = new Set([...PREDECESSOR_JOURNAL_FIELDS].filter((key) => key !== "rewritePolicy"));
316
+ var BINDING_POLICY_FIELDS = /* @__PURE__ */ new Set(["variableRedeclarations", "functionClassRedeclarations"]);
293
317
  var REWRITE_POLICY_FIELDS = /* @__PURE__ */ new Set([
294
318
  "autoRewriteImports",
295
319
  "autoStripExports",
296
320
  "autoSplitRedeclarations"
297
321
  ]);
322
+ var MODULE_SEMANTICS_FIELDS = /* @__PURE__ */ new Set(["defaultExportBinding"]);
323
+ var DEFAULT_EXPORT_BINDINGS = /* @__PURE__ */ new Set(["legacy-variable", "live-readonly"]);
298
324
  var CALL_SUCCESS_FIELDS = /* @__PURE__ */ new Set(["global", "member", "args", "ok", "value", "settle"]);
299
325
  var CALL_ERROR_FIELDS = /* @__PURE__ */ new Set(["global", "member", "args", "ok", "error", "settle"]);
300
326
  var OPERATION_FIELDS = /* @__PURE__ */ new Set(["action", "name"]);
@@ -323,9 +349,11 @@
323
349
  var REWRITE_KINDS = /* @__PURE__ */ new Set(["import", "redeclaration", "export"]);
324
350
  var REWRITE_POLICY_BY_KIND = Object.freeze({
325
351
  import: "autoRewriteImports",
326
- redeclaration: "autoSplitRedeclarations",
327
352
  export: "autoStripExports"
328
353
  });
354
+ var MIXED_REDECLARATION = "split a mixed top-level declaration while preserving native pattern initialization";
355
+ var FUNCTION_REDECLARATION = "reassigned an existing top-level function declaration for REPL continuity";
356
+ var CLASS_REDECLARATION = "reassigned an existing top-level class declaration for REPL continuity";
329
357
  var EDIT_TARGET_FIELDS = /* @__PURE__ */ new Set(["targetCallSeq"]);
330
358
  var DERIVED_RUN_FIELDS = /* @__PURE__ */ new Set(["code", "description"]);
331
359
  var RECOVERY_BOUNDARY_FIELDS = /* @__PURE__ */ new Set(["failedCallSeq", "frontierCallSeq"]);
@@ -466,6 +494,12 @@
466
494
  function isValidRewritePolicy(value) {
467
495
  return hasExactFields(value, REWRITE_POLICY_FIELDS) && [...REWRITE_POLICY_FIELDS].every((key) => typeof value[key] === "boolean");
468
496
  }
497
+ function isValidBindingPolicy(value) {
498
+ return hasExactFields(value, BINDING_POLICY_FIELDS) && [...BINDING_POLICY_FIELDS].every((key) => typeof value[key] === "boolean");
499
+ }
500
+ function isValidModuleSemantics(value) {
501
+ return hasExactFields(value, MODULE_SEMANTICS_FIELDS) && DEFAULT_EXPORT_BINDINGS.has(value.defaultExportBinding);
502
+ }
469
503
  function isValidCall(value) {
470
504
  if (!isRecord(value) || value.ok !== true && value.ok !== false) return false;
471
505
  const fields = value.ok ? CALL_SUCCESS_FIELDS : CALL_ERROR_FIELDS;
@@ -495,10 +529,11 @@
495
529
  if (!isRecord(value) || !JOURNAL_VERSIONS.has(value.version) || !JOURNAL_STATUSES.has(value.status)) {
496
530
  return false;
497
531
  }
498
- const fields = value.version === 1 ? LEGACY_JOURNAL_FIELDS : JOURNAL_FIELDS;
499
- const required = ["version", "bindingMode", "status", "calls", "operations", "diagnostics"];
500
- if (value.version !== 1) required.push("rewritePolicy");
501
- if (!hasClosedFields(value, fields, required) || !BINDING_MODES.has(value.bindingMode) || value.version !== 1 && !isValidRewritePolicy(value.rewritePolicy) || !Array.isArray(value.calls) || !value.calls.every(isValidCall) || !Array.isArray(value.operations) || !value.operations.every(isValidOperation) || !isValidConfirms(value.confirms, value.version) || !Array.isArray(value.diagnostics) || !value.diagnostics.every(isValidDiagnostic)) return false;
532
+ const predecessor = value.version !== 4;
533
+ const fields = value.version === 1 ? LEGACY_JOURNAL_FIELDS : predecessor ? PREDECESSOR_JOURNAL_FIELDS : JOURNAL_FIELDS;
534
+ const required = predecessor ? ["version", "bindingMode", "status", "calls", "operations", "diagnostics"] : ["version", "bindingPolicy", "rewritePolicy", "moduleSemantics", "status", "calls", "operations", "diagnostics"];
535
+ if (value.version !== 1 && value.version !== 4) required.push("rewritePolicy");
536
+ if (!hasClosedFields(value, fields, required) || predecessor && !BINDING_MODES.has(value.bindingMode) || value.version === 4 && !isValidBindingPolicy(value.bindingPolicy) || value.version === 4 && !isValidModuleSemantics(value.moduleSemantics) || value.version !== 1 && !isValidRewritePolicy(value.rewritePolicy) || !Array.isArray(value.calls) || !value.calls.every(isValidCall) || !Array.isArray(value.operations) || !value.operations.every(isValidOperation) || !isValidConfirms(value.confirms, value.version) || !Array.isArray(value.diagnostics) || !value.diagnostics.every(isValidDiagnostic)) return false;
502
537
  const settlementOrder = value.calls.map((call) => call.settle).sort((left, right) => left - right);
503
538
  if (settlementOrder.some((settle, index) => settle !== index)) return false;
504
539
  const requiresCompletion = value.status === "durable" || value.status === "volatile";
@@ -519,7 +554,7 @@
519
554
  }
520
555
  function isValidRewrites(value, journal) {
521
556
  try {
522
- return journal.status !== "noop" && isValidRewritePolicy(journal.rewritePolicy) && Array.isArray(value) && value.every((rewrite) => hasClosedFields(rewrite, REWRITE_FIELDS, ["kind", "description"]) && REWRITE_KINDS.has(rewrite.kind) && journal.rewritePolicy[REWRITE_POLICY_BY_KIND[rewrite.kind]] === true && typeof rewrite.description === "string" && rewrite.description.length > 0 && (rewrite.source === void 0 || typeof rewrite.source === "string"));
557
+ return journal.status !== "noop" && isValidRewritePolicy(journal.rewritePolicy) && Array.isArray(value) && value.every((rewrite) => hasClosedFields(rewrite, REWRITE_FIELDS, ["kind", "description"]) && REWRITE_KINDS.has(rewrite.kind) && (rewrite.kind === "redeclaration" ? rewrite.description === FUNCTION_REDECLARATION || rewrite.description === CLASS_REDECLARATION ? journal.version === 4 && isValidBindingPolicy(journal.bindingPolicy) && journal.bindingPolicy.functionClassRedeclarations === true : journal.rewritePolicy.autoSplitRedeclarations === true : journal.rewritePolicy[REWRITE_POLICY_BY_KIND[rewrite.kind]] === true) && typeof rewrite.description === "string" && rewrite.description.length > 0 && (rewrite.source === void 0 || typeof rewrite.source === "string"));
523
558
  } catch {
524
559
  return false;
525
560
  }
@@ -578,7 +613,6 @@
578
613
  return "";
579
614
  }
580
615
  }
581
- var MIXED_REDECLARATION = "split a mixed top-level declaration while preserving native pattern initialization";
582
616
  var GENERATED_RUN_CODE_DESCRIPTION_KEY = "dshPtcPlusRunCodeDescription";
583
617
  function rewriteFeature(rewrites, predicate, key) {
584
618
  const matching = rewrites.filter(predicate);
@@ -8,7 +8,7 @@ PTC Plus evaluates each cell as an async function body, where static `import` an
8
8
 
9
9
  [`internal/cell-rewriter.js`](../../internal/cell-rewriter.js) owns module-syntax adaptation. `@babel/parser` parses the original cell with its TypeScript plugin under script semantics while explicitly allowing top-level module declarations, `return`, and `await`; recognizing a module declaration therefore does not impose module strict mode on the surrounding async-function body. Before any edit or module preload record is produced, `@babel/traverse` builds the original Program scope. Its declaration and hoisting rules reject same-scope collisions between value imports and lexical, `var`, function, or other import bindings while retaining block shadowing and the separate TypeScript type namespace. The same scope validates local exports and drives imported-reference rewriting, supplemented by top-level TypeScript declaration names because Babel intentionally excludes the type namespace from runtime scope. The rewriter derives every accepted import, export, type-only declaration, specifier, source value, and edit range from that AST. One batch edit assembler validates non-overlapping ranges, produces generated source, and composes source mappings without rebuilding either artifact per edit. [`internal/cell-analysis.js`](../../internal/cell-analysis.js) then wraps the rewritten cell as an async function body and first parses it with Acorn. Already-valid JavaScript proceeds unchanged; only a cell that Acorn cannot parse is passed through Node's `stripTypeScriptTypes` and parsed again for binding and durability analysis.
10
10
 
11
- Each static import or remote re-export creates an ordered module preload record. A value-bearing record carries its source, import attributes, original position, generated temporary-global identifier, and the default or named exports required by its AST specifiers. The dedicated worker uses Node's synchronous module resolve hook to give REPL imports and static-link adapters one parent at the session cwd, falling back to the worker process cwd when the session has no recorded cwd. Node's ESM resolver therefore owns relative URLs, bare packages, conditional `exports`, URL schemes, import conditions, and platform path conversion; plugin and target-module dependency imports retain their natural parents. Each ephemeral data-URL adapter expresses the required members as static re-exports, so Node completes export linkage before evaluating the target module, then returns the namespace for a value-bearing preload. The worker resolves records before compiling the cell and exposes each namespace under its temporary identifier. One generated lexical prologue after the original directive prologue and before every executable cell statement captures each temporary identifier into a distinct persistent namespace identifier in source order; import declarations are erased at their original ranges, and the temporary globals are removed when the cell settles. Both identities are allocated outside current source identifiers, persistent REPL bindings, reserved program bindings, and every live private namespace. The prologue references the generated host identifier directly, so a lexical `globalThis` binding has ordinary JavaScript semantics and cannot redirect capture. A binding catalog stores only the persistent namespace identity and maps public import aliases to it and the export name; scope-aware rewriting routes unshadowed reads through the namespace, keeps bare, optional, and tagged calls detached from the namespace receiver with the language's comma-reference form, and lowers writes to a deterministic `TypeError`. Private namespace names remain reserved after a public alias is redeclared, so closures from earlier cells cannot be redirected by a later import. The catalog is rebuilt during journal replay. In loose REPL mode an explicit later declaration replaces the alias for future cells, while closures compiled before that declaration retain their namespace read. Destructuring, loop, nested, and delete writes that cannot preserve native evaluation semantics are rejected before execution. Direct `eval` and `WithStatement` are rejected on the original Program while value import aliases are active because generated namespace reads cannot reproduce their dynamic lexical resolution; this validation precedes module preload. Indirect or locally shadowed eval, non-strict `with` without value aliases, and cells containing only type imports retain ordinary JavaScript semantics. Side-effect, default, namespace, named, default-plus-named, and default-plus-namespace forms remain supported. Export modifiers are removed while declarations remain; default exports bind the fixed public name `__default`; anonymous default function and class lowering emits an explicit statement terminator, while default expressions retain their parsed continuation; remote re-exports become preload-only side-effect modules; local export lists are removed; and declaration-level or specifier-level type-only syntax is erased.
11
+ Each static import or remote re-export creates an ordered module preload record. A value-bearing record carries its source, import attributes, original position, generated temporary-global identifier, and the default or named exports required by its AST specifiers. The dedicated worker uses Node's synchronous module resolve hook to give REPL imports and static-link adapters one parent at the session cwd, falling back to the worker process cwd when the session has no recorded cwd. Node's ESM resolver therefore owns relative URLs, bare packages, conditional `exports`, URL schemes, import conditions, and platform path conversion; plugin and target-module dependency imports retain their natural parents. Each ephemeral data-URL adapter expresses the required members as static re-exports, so Node completes export linkage before evaluating the target module, then returns the namespace for a value-bearing preload. The worker resolves records before compiling the cell and exposes each namespace under its temporary identifier. One generated lexical prologue after the original directive prologue and before every executable cell statement captures each temporary identifier into a distinct persistent namespace identifier in source order; import declarations are erased at their original ranges, and the temporary globals are removed when the cell settles. Both identities are allocated outside current source identifiers, persistent REPL bindings, reserved program bindings, and every live private namespace. The prologue references the generated host identifier directly, so a lexical `globalThis` binding has ordinary JavaScript semantics and cannot redirect capture. A binding catalog stores only the persistent namespace identity and maps public import aliases to it and the export name; scope-aware rewriting routes unshadowed reads through the namespace, keeps bare, optional, and tagged calls detached from the namespace receiver with the language's comma-reference form, and lowers writes to a deterministic `TypeError`. Private namespace names remain reserved after a public alias is redeclared, so closures from earlier cells cannot be redirected by a later import. The catalog is rebuilt during journal replay. In loose REPL mode an explicit later declaration replaces the alias for future cells, while closures compiled before that declaration retain their namespace read. Destructuring, loop, nested, and delete writes that cannot preserve native evaluation semantics are rejected before execution. Direct `eval` and `WithStatement` are rejected on the original Program while value import aliases are active because generated namespace reads cannot reproduce their dynamic lexical resolution; this validation precedes module preload. Indirect or locally shadowed eval, non-strict `with` without active aliases, and cells containing only type imports retain ordinary JavaScript semantics. Side-effect, default, namespace, named, default-plus-named, and default-plus-namespace forms remain supported. Export modifiers are removed while declarations remain; default exports map the fixed public name `__default` through the same alias lowering to a generated persistent private slot. Later default exports update that slot at their source position independently of variable-redeclaration policy, and the binding catalog adopts the alias definition only after the worker reports the generated post-update commit signal. This keeps `__default` live and read-only while preventing an early return or failed class evaluation from publishing an update that did not execute. Anonymous default function and class lowering emits an explicit statement terminator, while default expressions retain their parsed continuation; remote re-exports become preload-only side-effect modules; local export lists are removed; and declaration-level or specifier-level type-only syntax is erased.
12
12
 
13
13
  The rewriter normalizes Babel syntax and local-export validation failures as original-cell positions before they cross the module boundary. Executable wrapper parsing retains Acorn's wrapper-relative positions, and only those positions receive the one-line wrapper subtraction during `PTC-C001` rendering. [`internal/cell-analysis.js`](../../internal/cell-analysis.js) then applies return, redeclaration, collision, and durability analysis to the successfully rewritten program. Return control-flow lowering addresses its per-cell worker-global signal through the top-level `this` receiver, which bypasses both lexical `globalThis` shadowing and `with` object-environment resolution; worker startup validates that this receiver is the evaluator's global context, and settlement removes the signal property. Lowered returns emit an explicit statement terminator so a return in a `switch` clause remains valid after replacement. Rewrite provenance remains parallel to the closed journal schema under `meta.dshPtcPlusRewrites` and does not alter replay equality. A successful rewrite remains private audit metadata; only a failed rewrite or missing valid journal may project its description as a bounded recovery context.
14
14
 
@@ -8,7 +8,11 @@ The session runtime coordinates two different lifecycles: transport ownership fo
8
8
 
9
9
  [`internal/worker-client.js`](../../internal/worker-client.js) exclusively owns worker creation, the readiness handshake, the private port, bounded stderr capture, reset, concurrent termination tracking, scratch allocation, and scratch cleanup. It reports private-channel messages and terminal failures through callbacks. [`internal/session-runtime.js`](../../internal/session-runtime.js) owns the active cell and decides how a reported message or failure settles the journal, durability state, replay transaction, or binding call. Messages reach the kernel only from the client's current worker, while each active semantic transaction retains its worker identity for lease and replay checks.
10
10
 
11
- Cross-process policy that must agree on both sides has one source. [`internal/module-policy.js`](../../internal/module-policy.js) defines durable imports, forbidden kernel-control imports, and ambient globals for both static analysis and the worker runtime. [`internal/failure-reporting.js`](../../internal/failure-reporting.js) defines hostile-error normalization, active-cell stack extraction, bounded diagnostic logs, and repeat-failure tracking for the host and worker. Session-log recovery belongs to [`internal/session-journal.js`](../../internal/session-journal.js), which owns journal shape, event association, and branch folding. Live durable nodes, current-call exclusion, no-op confirmations, recovery boundaries, and cold nodes all use persisted `tool/call.seq` identity. During live dispatch, the journal owner uses `callId` only to resolve the unique unpaired top-level call, then passes its sequence into semantic state. Sessionless direct runtime calls do not fabricate a persisted identity. Journal v3 stores confirmed no-ops as sequences; v2 journals with empty confirmations migrate without ambiguity, while a v2 call-id confirmation forms an unknown suffix. A failed durable replay records one log-only recovery boundary in the current settled `tool/result` metadata under `meta.dshPtcPlusRecoveryBoundaries`; its payload identifies the failed call event and its parent frontier without inventing a DSH event type. Every boundary is normalized as it enters recovery, before its event sequence participates in ordering; malformed boundary data therefore rejects reconstruction instead of disappearing from the fold. A completed record enters branch folding at its `tool/result` event position because that event confirms durability. Folding leaves immutable call/result events intact, removes the failed node and dependent descendants, and recomputes checkpoints from the remaining records. [`internal/session-runtime.js`](../../internal/session-runtime.js) resets the worker and retries the contracted frontier, recursively reaching the empty REPL only if every earlier candidate also fails. The exported migration helper converts old custom boundary events into this metadata before DSH restores a session and never mutates its input.
11
+ Cross-process policy that must agree on both sides has one source. [`internal/module-policy.js`](../../internal/module-policy.js) defines durable imports, forbidden kernel-control imports, and ambient globals for both static analysis and the worker runtime. [`internal/failure-reporting.js`](../../internal/failure-reporting.js) defines hostile-error normalization, active-cell stack extraction, bounded diagnostic logs, and repeat-failure tracking for the host and worker. Session-log recovery belongs to [`internal/session-journal.js`](../../internal/session-journal.js), which owns journal shape, event association, and branch folding. Live durable nodes, current-call exclusion, no-op confirmations, recovery boundaries, and cold nodes all use persisted `tool/call.seq` identity. During live dispatch, the journal owner uses `callId` only to resolve the unique unpaired top-level call, then passes its sequence into semantic state. Sessionless direct runtime calls do not fabricate a persisted identity. Journal v3 stores confirmed no-ops as sequences; v2 journals with empty confirmations migrate without ambiguity, while a v2 call-id confirmation forms an unknown boundary. A failed durable replay records one log-only recovery boundary in the current settled `tool/result` metadata under `meta.dshPtcPlusRecoveryBoundaries`; its payload identifies the failed call event and its parent frontier without inventing a DSH event type. Every boundary is normalized as it enters recovery before its event sequence participates in ordering. Malformed boundary or association data is rejected as evidence for old state, but it does not turn historical plugin metadata into an availability gate for the current valid call. A completed record enters branch folding at its `tool/result` event position because that event confirms durability. Folding leaves immutable call/result events intact, removes unproved nodes and dependent descendants, and recomputes checkpoints from the remaining records. [`internal/session-runtime.js`](../../internal/session-runtime.js) resets the worker and retries the greatest verified frontier, recursively reaching the empty REPL if every non-empty candidate fails, then executes the current cell against that actual state. The contraction is recorded on the current settled result and reported once so later recovery does not repeatedly encounter the same boundary. The exported migration helper converts old custom boundary events into this metadata before DSH restores a session and never mutates its input.
12
+
13
+ Recovery is conservative about state and best-effort about availability. A post-boundary cell or binding may be retained only when owned evidence mechanically proves that its ancestry or independence does not cross the unknown boundary. The current journal does not contain a complete per-binding dependency graph, so recovery treats a cell as atomic and discards its affected suffix when it cannot prove a finer partition. This can lose bindings, including all bindings in the empty-frontier case, but never claims that an unverified heap or historical effect was restored. DSH policy and failures of the current request remain outside this fallback.
14
+
15
+ The selected frontier must also agree with the state the model can know when it produces the current call. DSH's append-only session log owns durable evidence, while its ordered surface and derived request history own model visibility; raw events shadowed from that surface do not become model knowledge merely because replay can still read them. UI-only `dshPtcPlusBindings` metadata also does not qualify. A cell remains eligible when a result-only replacement leaves the assistant tool call carrying its exact source on the model-visible surface. When compaction shadows that provenance, the live or cold runtime contracts the affected cell and descendants unless an explicitly selected bounded model-visible structure publishes the exact surviving state. Such a projection is not injected by default merely to retain hidden bindings. Recovery never parses a natural-language summary into binding proof. Surface replacement generation is checked before a later cell uses an already-live worker, so this rule is not cold-start-only.
12
16
 
13
17
  ## Alternatives considered
14
18
 
@@ -32,8 +36,16 @@ Cross-process policy that must agree on both sides has one source. [`internal/mo
32
36
 
33
37
  **Always recover from an empty REPL.** This guarantees a clean worker but discards verified bindings and checkpoints unnecessarily. Parent-by-parent contraction retains the largest frontier whose replay actually succeeds.
34
38
 
35
- **Compare raw boundary sequences before decoding boundary events.** This appears to avoid work for boundaries after the current record, but an absent or nonnumeric sequence is not orderable and can evade the decoder entirely. Normalizing at session-log ingestion gives the fold one trusted boundary shape and makes damaged history fail closed.
39
+ **Reject every future cell until malformed history is repaired.** This preserves a simple reconstruction invariant but lets plugin-owned optimization metadata permanently disable `run_code` in an otherwise usable DSH conversation. Rejecting an unproved binding is required; refusing the current valid call is not. The runtime instead contracts to verified state or an empty REPL and lets the current cell establish a new branch.
40
+
41
+ **Recover arbitrary bindings after an unknown boundary.** This retains more names in favorable cases, but source text and completion equality do not prove the values, mutations, closures, or transitive reads that produced those bindings. Fine-grained retention requires explicit owned dependency evidence; absent that evidence, the whole affected cell or suffix is discarded.
42
+
43
+ **Treat every raw-log durable node as model-knowable.** This maximizes continuity after compaction, but can expose names and values whose declaration is absent from the request history that produced the current call. Durability proves reconstruction, not awareness. The runtime uses the intersection of durable and model-visible provenance instead.
44
+
45
+ **Parse the compaction summary for binding names.** A natural-language summary can omit, rename, or ambiguously describe bindings and does not carry an owner-proven relation to journal nodes. Only an explicit structured model-visible projection can preserve state after exact source provenance leaves the surface.
46
+
47
+ **Compare raw boundary sequences before decoding boundary events.** This appears to avoid work for boundaries after the current record, but an absent or nonnumeric sequence is not orderable and can evade the decoder entirely. Normalizing at session-log ingestion lets the fold reject the damaged record as state evidence and place a conservative contraction boundary without silently accepting it.
36
48
 
37
49
  ## Consequences
38
50
 
39
- Worker resource cleanup and startup failures are independently testable, while the kernel retains one owner for journal and replay meaning. The client callback is deliberately narrow: it carries transport facts, not journal decisions. Recovery metadata stays private to the settled `tool/result`; it does not change tool schemas, model messages, truthful top-level tool identity, `edit_run_code`, or recorded-value replay. Adding a private protocol message still requires a kernel handler and contract tests because the client does not interpret semantic payloads. Shared policy modules become load-bearing single sources and must remain usable in both the main thread and worker without importing session state.
51
+ Worker resource cleanup and startup failures are independently testable, while the kernel retains one owner for journal and replay meaning. The client callback is deliberately narrow: it carries transport facts, not journal decisions. Recovery metadata stays private to the settled `tool/result`; it does not change tool schemas, truthful top-level tool identity, `edit_run_code`, or recorded-value replay. The runtime consumes DSH's public surface contract without copying its projection algorithm or adding a DSH version whitelist. A contraction caused by damaged or no-longer-model-visible history can remove previously available bindings, so the first continued cell receives one bounded diagnostic and may need to redeclare them; historical program-binding effects are neither replayed nor described as undone. Adding a private protocol message still requires a kernel handler and contract tests because the client does not interpret semantic payloads. Shared policy modules become load-bearing single sources and must remain usable in both the main thread and worker without importing session state.
@@ -2,11 +2,15 @@
2
2
 
3
3
  ## Problem
4
4
 
5
- The persistent REPL uses one worker lexical environment so later cells can refer to earlier top-level bindings. That convenience is not JavaScript's module or function semantics, and mixed old/new destructuring requires a policy for combining declaration and assignment. Keeping that policy inside `internal/cell-analysis.js` makes core parsing and durability analysis depend on one replaceable persistence strategy.
5
+ The persistent REPL uses one worker lexical environment so later cells can refer to earlier top-level bindings. That convenience is not JavaScript's module or function semantics. Mixed old/new destructuring needs a policy for combining declaration and assignment, while ordinary iterative work also needs an explicit policy for replacing an earlier function or class declaration. Keeping either policy inside `internal/cell-analysis.js` makes core parsing and durability analysis depend on one replaceable persistence strategy.
6
6
 
7
7
  ## Decision
8
8
 
9
- `internal/cell-analysis.js` owns cell parsing, binding inventory, durability classification, return rewriting, and execution preparation. Shared binding-pattern traversal lives in `internal/binding-pattern.js`. `internal/repl-convenience.js` owns the optional loose-redeclaration policy, including all-existing replacement and mixed destructuring lowering; it returns executable source, collisions, redeclaration metadata, and rewrite provenance through one narrow function. The current persistent REPL continues to enable this policy, so the user-facing convenience and journal behavior remain unchanged. A future state/frame persistence implementation can replace this policy without changing the cell language analyzer.
9
+ `internal/cell-analysis.js` owns cell parsing, binding inventory, durability classification, return rewriting, and execution preparation. Shared binding-pattern traversal lives in `internal/binding-pattern.js`. `internal/repl-convenience.js` owns two independent, optional cross-cell policies: variable replacement (including mixed destructuring lowering) and named function/class replacement. It returns executable source, collisions, redeclaration metadata, and rewrite provenance through one narrow function. The variable policy remains enabled by default; function/class replacement is disabled by default and must be enabled with `looseTopLevelFunctionClassRedeclarations`.
10
+
11
+ Only a top-level named `FunctionDeclaration` or `ClassDeclaration` whose name existed before preparation is eligible. Its catalog provenance must prove that the existing binding is writable: `let`, `var`, function, and class are writable; a source `const` is writable only when that cell's variable policy lowered it to `let`; imports and reserved/private bindings are not writable. Unknown provenance fails closed. A function is lowered at its declaration position to assignment of an anonymous function expression, so recursive reads resolve through the replaceable outer binding. A class is lowered to assignment of a named class expression, preserving its inner self-reference and ensuring a throw during class evaluation leaves the old outer value unchanged. Both forms deliberately take effect at the declaration position rather than acquiring native declaration hoisting. A terminated `void 0` statement prevents the assignment value from becoming the cell completion.
12
+
13
+ The original Program is validated before lowering. Same-cell duplicates, nested or block declarations, parsing, TypeScript scope rules, and source positions therefore remain owned by the language pipeline. A future state/frame persistence implementation can replace these policies without changing the cell language analyzer.
10
14
 
11
15
  ## Alternatives considered
12
16
 
@@ -18,4 +22,4 @@ The persistent REPL uses one worker lexical environment so later cells can refer
18
22
 
19
23
  ## Consequences
20
24
 
21
- The core analyzer has no mixed-redeclaration lowering helpers and can be tested independently of the REPL convenience policy. The compatibility module remains responsible for its custom hybrid semantics and its focused tests; it must not be described as native JavaScript semantics. Future persistence work can introduce fresh cell lexical frames while retaining or replacing the convenience adapter at one explicit boundary.
25
+ The core analyzer has no redeclaration lowering helpers and can be tested independently of the REPL convenience policy. The compatibility module remains responsible for its custom replacement semantics and focused tests; neither policy is native JavaScript semantics. Binding assignability is reconstructed from prepared source and each cell's persisted policy rather than runtime-value inspection. Future persistence work can introduce fresh cell lexical frames while retaining or replacing the convenience adapter at one explicit boundary.
@@ -2,13 +2,13 @@
2
2
 
3
3
  ## Problem
4
4
 
5
- Durable replay prepares the original cell source again. AST module and REPL convenience rewrites are configuration-controlled, so using the current profile during replay can reject a cell that was durable when it was recorded. Replay would then discard recoverable bindings even though the journal still proves the cell's calls and completion.
5
+ Durable replay prepares the original cell source again. AST module rewrites and both REPL redeclaration conveniences are configuration-controlled, so using the current profile during replay can reject a cell that was durable when it was recorded or infer the wrong binding assignability. Replay would then discard recoverable bindings even though the journal still proves the cell's calls and completion.
6
6
 
7
7
  ## Decision
8
8
 
9
- Journal version 3 records `rewritePolicy` beside `bindingMode` with the three boolean rewrite settings used to prepare the cell. Live journals capture the active profile; replay passes each node's recorded policy to `prepareProgram`. The policy is part of journal normalization and equality, is frozen with the journal, and is required by the closed schema. A profile change therefore affects only new cells; existing durable nodes retain the language policy under which they were evaluated.
9
+ Journal version 4 replaces `bindingMode` with the closed `bindingPolicy` object containing the exact `variableRedeclarations` and `functionClassRedeclarations` booleans used to prepare the cell. It retains `rewritePolicy` with the three module and mixed-declaration rewrite booleans, and adds the closed `moduleSemantics.defaultExportBinding` value. Current cells record `live-readonly`; this is a source-lowering generation, not a user setting. Live journals capture both active policies and the fixed module semantics; replay passes every recorded value to `prepareProgram`. These objects participate in normalization and semantic equality, are frozen with the journal, and are required by the closed schema. A profile change therefore affects only new cells; existing durable nodes retain the language policy, module lowering, and writable-binding provenance under which they were evaluated.
10
10
 
11
- The journal decoder also owns the predecessor adapters. Version 1 has its original closed field set and migrates with all three later rewrite switches disabled, matching the language pipeline that emitted it; `bindingMode` continues to preserve its top-level redeclaration policy. Its string call-ID confirmations become version 3 event sequences only when the enclosing session log contains exactly one earlier unjournaled `run_code` call with that identity. Missing or repeated candidates are ambiguous and fail closed. Version 2 already contains `rewritePolicy` and normalizes to version 3 only when its confirmed no-op list is absent or empty; version 2 call-ID confirmations remain unsupported.
11
+ The journal decoder also owns the predecessor adapters. Versions 1 through 3 retain their original closed field sets as inputs and normalize to version 4. Their `bindingMode` maps exactly to `variableRedeclarations`; `functionClassRedeclarations` is always `false` because those language pipelines did not implement the feature. They receive `legacy-variable` default-export semantics so loose journals preserve the historically writable generated binding and strict journals preserve its const behavior. Version 1 receives all three later rewrite switches as disabled and its string call-ID confirmations become event sequences only when the enclosing session log contains exactly one earlier unjournaled `run_code` call with that identity. Missing or repeated candidates are ambiguous, so they cannot prove old state and form an unknown recovery boundary. Versions 2 and 3 retain their recorded `rewritePolicy`; unsupported non-numeric predecessor confirmations receive the same conservative contraction. Per [ADR 0007](0007-separate-worker-transport-from-session-semantics.md), rejecting predecessor state evidence does not make historical metadata an availability gate for the current valid call.
12
12
 
13
13
  ## Alternatives considered
14
14
 
@@ -18,8 +18,10 @@ The journal decoder also owns the predecessor adapters. Version 1 has its origin
18
18
 
19
19
  **Read the current profile during replay.** This keeps the journal small, but makes durable recovery depend on mutable deployment configuration and causes valid historical cells to become unrecoverable after a configuration change.
20
20
 
21
- **Accept every version 2 confirmation by call ID.** Preserving those values would require replay to guess which event a reused provider call ID denotes. Only the empty-confirmation subset has an identity-independent meaning, so broader migration would weaken the version 3 event-sequence contract owned by [ADR 0007](0007-separate-worker-transport-from-session-semantics.md).
21
+ **Infer the default-export lowering from source or journal version during replay.** Source does not identify which implementation generation evaluated it, while version-derived behavior becomes implicit protocol state. Persisting the closed semantic value makes migration explicit and keeps replay deterministic.
22
+
23
+ **Accept every predecessor confirmation by call ID.** Preserving those values would require replay to guess which event a reused provider call ID denotes. Only the version 1 adapter with a unique enclosing session event has sufficient evidence, so broader migration would weaken the event-sequence contract owned by [ADR 0007](0007-separate-worker-transport-from-session-semantics.md).
22
24
 
23
25
  ## Consequences
24
26
 
25
- The current journal schema is version 3. Replay remains source-based and deterministic while configuration changes no longer invalidate durable cells. Version 1 histories retain the predecessor language behavior and migrate only uniquely identified confirmations; version 2 histories without call-ID confirmations retain their recorded rewrite policy. Ambiguous identity remains unsupported recovery input rather than receiving guessed defaults. New profile settings apply at the next live cell boundary.
27
+ The current journal schema is version 4. Replay remains source-based and deterministic while configuration changes no longer invalidate durable cells or alter reconstructed binding assignability. Version 1 histories retain the predecessor rewrite behavior and migrate only uniquely identified confirmations; versions 2 and 3 retain their recorded rewrite policy. Every predecessor fixes function/class replacement to disabled and default-export lowering to `legacy-variable` instead of guessing from current behavior. Ambiguous identity remains unsupported recovery input rather than receiving guessed defaults. New profile settings apply at the next live cell boundary.
@@ -20,13 +20,13 @@ The configurable set is the Host runtime configuration plus `enabled: boolean` (
20
20
 
21
21
  Every settings-card and PTC Plus tool-row string is registered into the DSH client locale namespace `settings.ptcPlus` and follows the active DSH client locale. Field copy exists in both locales from the single `internal/config-spec.js` source (`label`/`labelEn`, `description`/`descriptionEn`); display chrome copy is owned by the Client half. The stable REPL guidance remains plugin-owned protocol text without UI branding. The transport names remain `run_code`/`edit_run_code`; DSH owns their identity.
22
22
 
23
- The existing `conversation.session.header.actions` entry with id `ptc-plus-active` remains the lightweight enabled indicator. Hovering it, or focusing it from the keyboard, opens a compact reusable-binding card. The card lists the top-level bindings the current runtime owner has proved reusable by the agent, classified as variable, function, class, or import. Each entry includes its original declaration line and column, a one-line preview, and an accessible inline action that expands the declaration source. Preparation owns declaration spans before execution and `BindingCatalog` retains that provenance as bindings are replaced or replayed; the presentation path never reads, serializes, previews, or invokes the bound runtime values or getters. The runtime emits a complete, value-independent `meta.dshPtcPlusBindings` presentation envelope after each settled cell. The enclosed inventory is bounded to 128 entries, 1024 UTF-16 code units per declaration source, and 16384 code units of declaration source in aggregate, and reports its exact valid total and omitted count. Volatile is a cold-recovery classification, not a live-availability classification, so a live volatile worker continues to publish all bindings it retains. A restore-bearing or discarded settlement resets the worker and reports the binding surface as unconfirmed until a later cell materializes and proves the effective state.
23
+ The existing `conversation.session.header.actions` entry with id `ptc-plus-active` remains the lightweight enabled indicator. Hovering it, or focusing it from the keyboard, opens a compact reusable-binding card. The card lists the top-level bindings the current runtime owner has proved reusable by the agent, classified as variable, function, class, or import. Each entry includes its original declaration line and column, a one-line preview, and an accessible inline action that expands the declaration source. Preparation owns declaration spans before execution and `BindingCatalog` retains that provenance as bindings are replaced or replayed; the presentation path never reads, serializes, previews, or invokes the bound runtime values or getters. The runtime emits a complete, value-independent `meta.dshPtcPlusBindings` presentation envelope after each settled cell. The enclosed inventory is bounded to 128 entries, 1024 UTF-16 code units per declaration source, and 16384 code units of declaration source in aggregate, and reports its exact valid total and omitted count. Volatile is a cold-recovery classification, not a live-availability classification, so a live volatile worker continues to publish retained bindings while their exact provenance remains in the model-visible state frontier. A restore-bearing or discarded settlement, or a DSH surface contraction that hides binding provenance, contracts the worker and reports the binding surface as unconfirmed until a later cell materializes and proves the effective state.
24
24
 
25
25
  The inventory preserves `BindingCatalog`'s last-declared-first stack order instead of sorting by name. Declarations later in one cell appear above earlier declarations, and redeclaring an existing binding moves it back to the top. The Client aligns every entry through a fixed-width type column followed by its name and definition preview.
26
26
 
27
- The Host optionally registers `ptcPlusRepl` through DSH's public `sessionProjections` seam. Each enabled runtime activation owns an opaque presentation generation shared by its metadata producer and projection definition. The projection's pure fold pairs public `run_code` and `edit_run_code` events through the formal `tool/call.data.callId` and `tool/result.data.message.source.callId` identity; optional `sourceEventSeqs` provenance is not required. It accepts only complete snapshots from its own generation, clears live proof at `session/end-seed`, and serves only the inventory to the Client. A paired result with absent, malformed, or foreign-generation metadata cannot overwrite an already proved inventory; unrelated tool results and surface replacements are no-ops. The bounded call-correlation state is checkpointed separately from the wire value, carries the generation, and has its own invalidation version. A cached state from another generation normalizes to an unconfirmed inventory without replaying stale live claims. DSH deployments without the service, hosts exposing an incompatible registration contract, and unsupported versioned envelopes retain the indicator without binding data while the core runtime stays enabled. Existing logs require no migration. The DSH header-action kit exposes no public navigation command for opening a tool call or source location, so the card uses inline inspection and does not reach through private stores or query Host DOM. Source navigation requires a public Host capability. The projection generation and private metadata are presentation-only: neither enters model messages nor changes the closed journal schema, canonical result value, runtime context, tool schema, replay reader, authority, or compatibility rules.
27
+ The Host optionally registers `ptcPlusRepl` through DSH's public `sessionProjections` seam. Each enabled runtime activation owns an opaque presentation generation shared by its metadata producer and projection definition. The projection's pure fold pairs public `run_code` and `edit_run_code` events through the formal `tool/call.data.callId` and `tool/result.data.message.source.callId` identity; optional `sourceEventSeqs` provenance is not required. It accepts only complete snapshots from its own generation, clears live proof at `session/end-seed`, and serves only the inventory to the Client. A paired result with absent, malformed, or foreign-generation metadata cannot overwrite an already proved inventory. Unrelated tool results and result-only surface replacements that leave binding provenance model-visible are also no-ops for this UI fold; a replacement that contracts the runtime's model-visible state frontier invalidates the corresponding inventory through the same owner decision, so presentation cannot claim a binding the next cell will not own. The bounded call-correlation state is checkpointed separately from the wire value, carries the generation, and has its own invalidation version. A cached state from another generation normalizes to an unconfirmed inventory without replaying stale live claims. DSH deployments without the service, hosts exposing an incompatible registration contract, and unsupported versioned envelopes retain the indicator without binding data while the core runtime stays enabled. Existing logs require no migration. The DSH header-action kit exposes no public navigation command for opening a tool call or source location, so the card uses inline inspection and does not reach through private stores or query Host DOM. Source navigation requires a public Host capability. The projection generation and private metadata are presentation-only: neither enters model messages nor changes the closed journal schema, canonical result value, runtime context, tool schema, replay reader, authority, or compatibility rules. In particular, the UI inventory does not prove model awareness or keep a binding alive after the runtime's model-visible frontier contracts.
28
28
 
29
- While the setting is ready and enabled, the Client registers `run_code` and `edit_run_code` in the public keyed `tool.call.toolview` slot; disabling disposes both entries and restores DSH's owner fallback. Each replacement row preserves expandable source, result, visible lifecycle state, and Inspect access, then derives optional feature notices from existing PTC Plus presentation metadata. A non-PTC session uses a neutral Code title unless a settled result carries a valid PTC journal. The view never folds a conversation window or displays cumulative counts. Unknown or malformed metadata fails closed without hiding the base row; v1 journals with non-empty confirms also fail closed because the Client deliberately does not load the session identity needed to resolve them.
29
+ While the setting is ready and enabled, the Client registers `run_code` and `edit_run_code` in the public keyed `tool.call.toolview` slot; disabling disposes both entries and restores DSH's owner fallback. Each replacement row preserves expandable source, result, visible lifecycle state, and Inspect access, then derives optional feature notices from existing PTC Plus presentation metadata. A non-PTC session uses a neutral Code title unless a settled result carries a valid PTC journal. The view never folds a conversation window or displays cumulative counts. Unknown or malformed metadata is ignored as feature evidence without hiding the base row; v1 journals with non-empty confirms receive the same treatment because the Client deliberately does not load the session identity needed to resolve them.
30
30
 
31
31
  Feature notices name actual per-result events rather than configuration defaults: validated import/export/mixed-redeclaration provenance, a complete safe-edit target/derived-run/non-noop/optional-recovery-boundary relation consistent with any target precondition in the outer call, successful `code.run` calls, a completed durable recovery proven by the stable `PTC-R002` warning/recover/rolled-back tuple, and `repl.state` operations. Volatile-retained and discarded state remain recovery classifications, not user-facing feature notices. Recovery boundaries are validated when present but deliberately not displayed. Features without an existing Client-only fact, including ordinary loose redeclarations, canonicalizer provenance, official ownership of a `cordis_*` member, and whether a model recovery tip was selected, are not guessed from code shapes, name prefixes, or settings. The body feature row remains read-only and adds no Host owner. The REPL-memory card adds only the public session projection and parallel presentation snapshot described above; it adds no tool, prompt section, runtime context, session event type, journal field, value inspection, or migration behavior.
32
32
 
@@ -58,11 +58,13 @@ assembly pattern remains available for future UI or host features. A plugin
58
58
  update alone cannot repair an already-running DSH process that has failed while
59
59
  loading legacy artifacts; the process must be stopped and the artifacts
60
60
  migrated before restore. Existing valid sessions are unaffected by the
61
- migration, while malformed legacy artifacts must fail closed and remain
62
- diagnosable.
61
+ migration, while malformed legacy artifacts must remain unchanged and
62
+ diagnosable. This migration rule rejects an unsafe overwrite of the source
63
+ artifact; it does not authorize the session runtime to reject later valid cells
64
+ once DSH has supplied a loadable session. Runtime contraction follows
65
+ [ADR 0007](0007-separate-worker-transport-from-session-semantics.md).
63
66
 
64
67
  The long-term upstream requirement is one of: a restore-preflight migration
65
68
  hook, an atomic raw-artifact rewrite API, or a contract that isolates one bad
66
69
  session from workspace registry initialization. None of these is emulated by
67
70
  PTC Plus in the absence of a public DSH extension point.
68
-
@@ -87,9 +87,9 @@ CodeRuntime request 已携带的 owner-provided program namespace 会被原样
87
87
 
88
88
  ## REPL 生命周期
89
89
 
90
- 每个顶层 `run_code` 是同一 session kernel 的下一格。顶层 binding 跨 cell 保留;默认宽松模式允许一个完整 declarator 全部替换已有变量,严格模式拒绝重声明,关闭 `autoSplitRedeclarations` 时混合新旧名称的解构也在执行前拒绝。cell 始终作为 async function body 求值,支持 block scope、top-level `await` 和普通控制流 return;return lowering 通过顶层 `this` receiver 访问每个 cell 独立生成并在 settlement 清理的 worker-global signal property,因此 lexical `globalThis` 与 `with` object environment 都不能重定向控制流。worker 启动时验证该 receiver 指向 evaluator 的 global context。跨 cell 重声明是 REPL 便利策略,由 `internal/repl-convenience.js` 独立实现;它不是 cell 语言或原生 JavaScript 语义的组成部分。
90
+ 每个顶层 `run_code` 是同一 session kernel 的下一格。顶层 binding 跨 cell 保留;默认开启的变量策略允许一个完整 declarator 全部替换已有变量,关闭时拒绝变量重声明,关闭 `autoSplitRedeclarations` 时混合新旧名称的解构也在执行前拒绝。独立的 `looseTopLevelFunctionClassRedeclarations` 默认关闭;开启后,重复的 named function/class 声明只在已有 binding 经源码 provenance 证明可写时,于声明位置 lowering 为赋值。函数使用匿名 function expression,使递归读取可替换的 outer binding;class 使用 named class expression,保留内部 self-reference,且 class 求值失败不会覆盖旧值。该 lowering 不保留函数声明 hoisting;不可写 const/import、保留名称与来源不明目标在执行前拒绝。cell 始终作为 async function body 求值,支持 block scope、top-level `await` 和普通控制流 return;return lowering 通过顶层 `this` receiver 访问每个 cell 独立生成并在 settlement 清理的 worker-global signal property,因此 lexical `globalThis` 与 `with` object environment 都不能重定向控制流。worker 启动时验证该 receiver 指向 evaluator 的 global context。跨 cell 重声明是 REPL 便利策略,由 `internal/repl-convenience.js` 独立实现;它不是 cell 语言或原生 JavaScript 语义的组成部分。
91
91
 
92
- 包裹前的 AST 重写适配模块语法(cell 是 async function body,模块声明在函数体内非法):全部默认开启且可用 config 关闭(`autoRewriteImports` / `autoStripExports` / `autoSplitRedeclarations`)。原始 Babel Program 在任何 lowering 或 preload 前完成 scope validation;同一 cell 的 value import 与 lexical、hoisted `var`、function 或其他 import 重名会按模块 binding 规则拒绝,block shadow 和 type-only 名称仍合法。静态 `import` 声明产生有序 module preload record;worker 通过短生命周期 ESM adapter 让 Node 从 session cwd 完成解析和 export linkage,再在 cell 编译前通过生成的临时 global identifier 把 namespace 交给另一个持久 lexical identifier。两者都避开当前源码、持久 binding、program binding 与活跃 private namespace,生成的 prologue 直接引用临时 identifier,因此 lexical `globalThis` shadowing 不参与 host capture;临时 global 在 cell settlement 清理。session 未记录 cwd 时,解析基准回退到 worker process cwd;目标模块的依赖保持各自的自然 parent。scope-aware alias lowering 保留 named/default binding 的 live read、只读、shadowing 和跨 cell 重放语义。value import alias 活跃时,原始 Program 上的 direct `eval` 与 `WithStatement` 在任何 preload record 生效前拒绝,因为生成的 namespace property read 无法复现它们的动态 lexical resolution;没有 value alias 的非严格 `with`、type-only import 和局部 shadow 的 `eval` 仍使用普通 JavaScript 语义。顶层 `export` 修饰符剥离(声明保留、`export default` 转为 `__default` 绑定、re-export 转为预加载的副作用模块、type-only export 擦除);REPL 便利层在 `autoSplitRedeclarations` 开启时按插件定义的兼容规则 lowering 混合新旧名称的顶层解构,关闭时保持原文走既有解析失败路径。无法精确适配的形态在执行前拒绝。改写来源以 `meta.dshPtcPlusRewrites` 平行记录(journal schema 封闭)。成功改写不改变下一步决策,因此不产生 runtime context;改写后失败或缺少有效 journal 时,`tools:ptc-plus-rewrite-info` 才在下一轮说明恢复边界。
92
+ 包裹前的 AST 重写适配模块语法(cell 是 async function body,模块声明在函数体内非法):全部默认开启且可用 config 关闭(`autoRewriteImports` / `autoStripExports` / `autoSplitRedeclarations`)。原始 Babel Program 在任何 lowering 或 preload 前完成 scope validation;同一 cell 的 value import 与 lexical、hoisted `var`、function 或其他 import 重名会按模块 binding 规则拒绝,block shadow 和 type-only 名称仍合法。静态 `import` 声明产生有序 module preload record;worker 通过短生命周期 ESM adapter 让 Node 从 session cwd 完成解析和 export linkage,再在 cell 编译前通过生成的临时 global identifier 把 namespace 交给另一个持久 lexical identifier。两者都避开当前源码、持久 binding、program binding 与活跃 private namespace,生成的 prologue 直接引用临时 identifier,因此 lexical `globalThis` shadowing 不参与 host capture;临时 global 在 cell settlement 清理。session 未记录 cwd 时,解析基准回退到 worker process cwd;目标模块的依赖保持各自的自然 parent。scope-aware alias lowering 保留 named/default binding 的 live read、只读、shadowing 和跨 cell 重放语义。`export default` 的固定公开名 `__default` 通过同一 alias lowering 指向持久 private slot;只有既有 binding 明确带有 synthetic-default provenance 时才可复用该 slot,普通 binding、import alias 或 reserved name 冲突都在执行前拒绝。后续合法 default export 在原声明位置更新该 slot,不依赖变量重声明开关,并且只有 worker 在更新后发出 commit signal,BindingCatalog 才更新公开定义来源。function/class replacement 的 commit identity 进一步绑定到每个 declaration occurrence,同一 cell 的同名后续声明若因 return 或异常未执行,不会覆盖 inventory 中最后实际提交的定义。提前 return 或 class 求值失败因此不会发布未执行的 alias 或 replacement 更新。value alias 活跃时,原始 Program 上的 direct `eval` 与 `WithStatement` 在任何 preload record 生效前拒绝,因为生成的 namespace property read 无法复现它们的动态 lexical resolution;没有 value alias 的非严格 `with`、type-only import 和局部 shadow 的 `eval` 仍使用普通 JavaScript 语义。顶层 `export` 修饰符剥离(声明保留、`export default` 转为 `__default` 绑定、re-export 转预加载的副作用模块、type-only 擦除);REPL 便利层在 `autoSplitRedeclarations` 开启时按插件定义的兼容规则 lowering 混合新旧名称的顶层解构,关闭时保持原文走既有解析失败路径。无法精确适配的形态在执行前拒绝。改写来源以 `meta.dshPtcPlusRewrites` 平行记录(journal schema 封闭)。成功改写不改变下一步决策,因此不产生 runtime context;改写后失败或缺少有效 journal 时,`tools:ptc-plus-rewrite-info` 才在下一轮说明恢复边界。
93
93
 
94
94
  可确定的计算和 recorded-value capability call 可以推进 durable head。未进入 journal 的 Node/OS 能力、环境输入、时钟、随机数和 timer 进入 sticky `volatile`;live worker 继续可用,cold replay 回到最后 durable frontier。`require(...)` 与动态导入按同一白名单分类:白名单内置模块(assert/buffer/querystring/string_decoder/stream/util/url/zlib)保持 durable,其余 volatile,`worker_threads`/`cluster` 等内核控制模块在执行前以 PTC-C002 拒绝。durable 只描述冷恢复对已结算历史的重放能力;失败 cell 仍可能已经修改 binding 或产生外部 effect,不能由 durability 推导出安全重试。worker thread 是生命周期隔离,不是安全沙箱。
95
95
 
@@ -101,9 +101,13 @@ CodeRuntime request 已携带的 owner-provided program namespace 会被原样
101
101
 
102
102
  ```ts
103
103
  {
104
- version: 3,
105
- bindingMode: "loose" | "strict",
104
+ version: 4,
105
+ bindingPolicy: {
106
+ variableRedeclarations: boolean,
107
+ functionClassRedeclarations: boolean
108
+ },
106
109
  rewritePolicy: { autoRewriteImports, autoStripExports, autoSplitRedeclarations },
110
+ moduleSemantics: { defaultExportBinding: "legacy-variable" | "live-readonly" },
107
111
  status: "durable" | "volatile" | "discarded" | "noop",
108
112
  calls: CallTranscript[],
109
113
  operations: StateOperation[],
@@ -114,11 +118,15 @@ CodeRuntime request 已携带的 owner-provided program namespace 会被原样
114
118
  }
115
119
  ```
116
120
 
117
- `calls` 只保存 global、member、PTC Value Graph 编码的 args/result 或 error,以及 settlement 序号。cold replay 校验调用名称、参数、数量和提交顺序,并按 recorded settlement order 释放 recorded result;不会重新 dispatch program binding 或重做外部 effect。该规则同样适用于 native tools、owner-provided namespace 和 `code.run`,不按名称分支。Cordis 的进程内对象不会因此被宣称已恢复;presentation 可以从已验证 transcript 派生重新检查要求,但不能改变 replay。若基础设施终止时仍有未结算 binding,heap 回滚到 durable frontier,discarded journal 以最先观察到的 `global.member` 保留 possible-effect boundary。effect、completeness 和 source metadata 属于 capability explorer,不伪装成 journal 字段。
121
+ `bindingPolicy` 与 `rewritePolicy` 固化可配置的 cell 语言行为;`moduleSemantics` 固化不可配置的 lowering 代际。当前 journal 写入 `live-readonly` default-export alias,v1-v3 迁移为 `legacy-variable`,因此 cold replay 不从当前实现或源码猜测历史 `__default` 的可写性。`calls` 只保存 global、member、PTC Value Graph 编码的 args/result 或 error,以及 settlement 序号。cold replay 校验调用名称、参数、数量和提交顺序,并按 recorded settlement order 释放 recorded result;不会重新 dispatch program binding 或重做外部 effect。该规则同样适用于 native tools、owner-provided namespace 和 `code.run`,不按名称分支。Cordis 的进程内对象不会因此被宣称已恢复;presentation 可以从已验证 transcript 派生重新检查要求,但不能改变 replay。若基础设施终止时仍有未结算 binding,heap 回滚到 durable frontier,discarded journal 以最先观察到的 `global.member` 保留 possible-effect boundary。effect、completeness 和 source metadata 属于 capability explorer,不伪装成 journal 字段。
118
122
 
119
123
  journal 通过 `run_code.output.presentationMeta` 附着到最终 result,再由 `tools/result` 做两阶段确认。缺失、损坏或被替换的 journal 形成 unknown/volatile 边界;未进入 runtime 的 call 由后续 `confirms` 以对应 `tool/call.seq` 证明为 no-op。volatile 源码保留在原 session log,但不参与 cold replay。
120
124
 
121
- durable replay 无法验证某个 node 时,当前已结算 `tool/result` 的私有 `meta.dshPtcPlusRecoveryBoundaries` 记录失败 call 与其 parent frontier,不修改冻结的历史 result。boundary 在进入排序前必须完成 schema 与 event-sequence validation;损坏 metadata 使恢复 fail closed,不能从 fold 中静默消失。折叠器剪除该 node 及依赖后代、重算可重建 checkpoints;kernel 重置 worker 并逐级验证更早 frontier,随后执行当前 live cell。新 durable node 的 parent 始终是 worker 实际拥有的 frontier。自定义 boundary event 不由运行时生成,迁移工具必须在 DSH restore 前显式转换它。
125
+ 恢复从当前请求对应的 head 沿 parent 关系向更早状态收缩,而不是越过损坏记录继续假定原 heap 存在。候选 frontier 同时受两种证据约束:append-only session log 中的 journal 证明状态可重建;生成当前调用的 DSH ordered surface/derived request history 证明模型可以知道其精确 binding provenance。raw event 未删除不等于模型仍可见,Client 的 `dshPtcPlusBindings` inventory 也只是 UI presentation。只裁剪 tool result、仍把携带源码的 assistant call 留在模型 surface 中时不必删除对应状态;assistant provenance 被遮蔽且没有显式、有界、模型可见的结构化状态投影时,live worker 与 cold replay 都必须收缩。插件不从自然语言 compaction summary 推断 binding。
126
+
127
+ durable replay 无法验证某个 node 时,当前已结算 `tool/result` 的私有 `meta.dshPtcPlusRecoveryBoundaries` 记录失败 call 与其 parent frontier,不修改冻结的历史 result。boundary 在进入排序前必须完成 schema 与 event-sequence validation;损坏 metadata 不能从 fold 中静默消失,也不能证明任何旧状态。折叠器剪除结构不可证明或模型不可知的 node、binding 与依赖后代,重算可重建 checkpoints;只有 ancestry、独立性和模型可知 provenance 都有 owner evidence 的部分可以保留。现有 journal 没有完整的逐 binding 依赖图,因此不能证明细粒度剪枝时按 cell 与受影响后缀收缩。已经存在的 live kernel 在执行下一 cell 前检查 surface replacement generation;若模型可见历史已经收缩,先重置并物化对齐后的 frontier。
128
+
129
+ kernel 重置 worker 并逐级验证更早 frontier;最大非空 frontier 仍不可证明时使用空 REPL。这个最差情况会丢失全部旧 binding,但历史 PTC Plus metadata 损坏本身不得阻止当前合法 cell 执行。当前 cell 在实际重建的 frontier 上建立新分支,并通过一次 `PTC-R002` 说明哪些历史状态未恢复;收缩不重新派发或撤销历史 program binding effect,也不把未知状态说成已恢复。DSH 对当前请求的 validation、policy、authority、approval 与 cancellation 错误不属于该可用性降级。新 durable node 的 parent 始终是 worker 实际拥有的 frontier。自定义 boundary event 不由运行时生成,迁移工具必须在 DSH restore 前显式转换它。
122
130
 
123
131
  诊断由封闭结构确定性渲染,包括语法、preflight、绑定冲突、输出、运行异常和恢复边界。普通成功与首次进入 volatile 不投影 warning/note;恢复分类保留在 journal 和 `repl.state(list)` 中。
124
132
 
package/docs/client-ui.md CHANGED
@@ -15,7 +15,7 @@ Host half 通过 DSH 公共 `settings` 服务注册命名空间 `ptc-plus`。字
15
15
  | 展示 | `enhancedToolView` | 默认开启;关闭后注销 PTC Plus 的两个 keyed tool view,恢复 DSH 原生 generic row。 |
16
16
  | 常用与兼容性 | `autoDescribeRunCode` / `canonicalizeToolCalls` | 默认开启;允许缺少外层摘要的 `run_code` 执行,并修复 schema 可唯一确认的顶层 native 工具误调用。 |
17
17
  | 可选能力 | `cordisToolsEnabled` | 默认关闭;开启后为 PTC agent 加入官方 Cordis 工具、指引与精确的 `cordis-plugin-development` companion Skill,不发布同目录 sibling。 |
18
- | 高级行为 | `looseTopLevelRedeclarations` / `autoRewriteImports` / `autoStripExports` / `autoSplitRedeclarations` / `durableReplay` / `tipsEnabled` | 默认开启;控制跨 cell 重声明、模块语法适配、worker 重启后的状态恢复与失败提示。 |
18
+ | 高级行为 | `looseTopLevelRedeclarations` / `looseTopLevelFunctionClassRedeclarations` / `autoRewriteImports` / `autoStripExports` / `autoSplitRedeclarations` / `durableReplay` / `tipsEnabled` | function/class 重声明默认关闭,其余默认开启;分别控制变量重声明、可写 function/class binding 的声明位置替换、模块语法适配、worker 重启后的状态恢复与失败提示。 |
19
19
  | 计算 | `computeMs` / `maxWallMs` | 单 cell CPU 与总耗时预算。 |
20
20
  | Worker | `maxOldGenerationSizeMb` / `maxNestedRunCodeDepth` | kernel worker 内存与嵌套执行深度。 |
21
21
  | 输出 | `maxOutputBytes` | 单 cell 日志与返回结果的合计字节上限。 |
@@ -35,17 +35,17 @@ Host half 通过 DSH 公共 `settings` 服务注册命名空间 `ptc-plus`。字
35
35
 
36
36
  设置卡片、正文 tool view 与 REPL 可复用 binding 卡片的全部文案都注册到 DSH client locale 的 `settings.ptcPlus` 命名空间,随当前界面语言在中文与 English 之间切换;字段名称与说明的两种语言文本同样来自 `internal/config-spec.js`(`label`/`labelEn`、`description`/`descriptionEn`,某一字段的说明要么两种语言都有,要么都没有),展示 chrome 文案由 client half 拥有。稳定 REPL 指引不承载 UI 品牌名。启用且会话选择 `ptc` 或兼容的 `code` preset 时,`conversation.session.header.actions` 以稳定 id `ptc-plus-active` 显示简洁的 `PTC Plus` 标识;当前 session projection 与兼容顶层字段冲突时以前者为准。关闭时不注入任何 PTC 指引或工具 surface。
37
37
 
38
- 绿色 `PTC Plus` 活动标识支持鼠标悬浮、键盘聚焦和点击。卡片通过浏览器 Popover top layer 脱离普通 stacking context,按触发器和当前视口的可用空间在上方或下方定位,因此不受会话侧栏覆盖;长列表只在卡片内部滚动。变量、函数、类和导入使用可区分的类型色彩。卡片读取公共 `sessionProjections` 携带的 `projectionValues.ptcPlusRepl`,展示当前 runtime owner 已证明可供该 agent 后续 cell 复用的 binding 名称、类别、单行定义预览以及原始行列;点击整行即可在卡片内展开有界 TypeScript 声明源码,并以抽屉式过渡动画显示。声明来源由 AST preparation 从已经提交的 cell 文本确定,BindingCatalog 在替换和 replay 时更新,不读取值、不执行代码、不触发 getter。Host 在每个结算结果的并行私有 `meta.dshPtcPlusBindings` envelope 中写入 runtime generation 和完整的 value-independent inventory;最多携带 128 项、名称最长 128 字符、每段声明源码至多 1024 个 UTF-16 code unit、声明源码合计至多 16384 个 code unit,同时保留精确有效总数和省略数。volatile cell 的 live binding 继续显示;restore 或 discarded settlement 重置 worker,在后续 cell 重新物化有效 binding surface 前显示不可确认。
38
+ 绿色 `PTC Plus` 活动标识支持鼠标悬浮、键盘聚焦和点击。卡片通过浏览器 Popover top layer 脱离普通 stacking context,按触发器和当前视口的可用空间在上方或下方定位,因此不受会话侧栏覆盖;长列表只在卡片内部滚动。变量、函数、类和导入使用可区分的类型色彩。卡片读取公共 `sessionProjections` 携带的 `projectionValues.ptcPlusRepl`,展示当前 runtime owner 已证明可供该 agent 后续 cell 复用的 binding 名称、类别、单行定义预览以及原始行列;点击整行即可在卡片内展开有界 TypeScript 声明源码,并以抽屉式过渡动画显示。声明来源由 AST preparation 从已经提交的 cell 文本确定,BindingCatalog 在替换和 replay 时更新,不读取值、不执行代码、不触发 getter。Host 在每个结算结果的并行私有 `meta.dshPtcPlusBindings` envelope 中写入 runtime generation 和完整的 value-independent inventory;最多携带 128 项、名称最长 128 字符、每段声明源码至多 1024 个 UTF-16 code unit、声明源码合计至多 16384 个 code unit,同时保留精确有效总数和省略数。volatile cell 的 live binding 只在其精确 provenance 仍属于模型可见 state frontier 时继续显示;restore、discarded settlement 或 model-visible surface contraction 会收缩 worker,在后续 cell 重新物化有效 binding surface 前显示不可确认。
39
39
 
40
40
  清单按最近声明或重声明优先的栈顺序排列,同一 cell 内靠后的声明位于靠前声明之上。BindingCatalog 保留这个顺序,presentation snapshot 和 projection 原样传递,不根据名称重新排序。
41
41
 
42
- 投影通过 DSH 正式的 `tool/call.data.callId` / `tool/result.data.message.source.callId` 身份配对 `run_code` 与 `edit_run_code`,不依赖可选 `sourceEventSeqs`。它只接受当前 runtime generation 的 envelope,并在 `session/end-seed` 清除旧 lifecycle 的 live 证明;缺少、损坏、其他 generation 的 metadata、无关 tool result 与 surface replacement 都不能覆盖仍有效的最后已证明清单。缺少有效 metadata、projection service 或兼容 registration contract 时只显示不可确认,不主动读取历史、不迁移日志,也不影响继续会话。来源跳转要求 Host 提供打开 tool call 或 source location 的公共 capability;缺少该 capability 时,卡片只提供内联检查,不访问私有 store 或宿主 DOM。
42
+ 投影通过 DSH 正式的 `tool/call.data.callId` / `tool/result.data.message.source.callId` 身份配对 `run_code` 与 `edit_run_code`,不依赖可选 `sourceEventSeqs`。它只接受当前 runtime generation 的 envelope,并在 `session/end-seed` 清除旧 lifecycle 的 live 证明;缺少、损坏、其他 generation 的 metadata、无关 tool result 与不影响 binding provenance 的 result-only replacement 都不能覆盖仍有效的最后已证明清单。若 surface replacement 遮蔽了声明 provenance,runtime 与 projection 共用的 model-visible frontier 判定会让相关 inventory 失效。缺少有效 metadata、projection service 或兼容 registration contract 时只显示不可确认,不主动读取历史、不迁移日志,也不影响继续会话。来源跳转要求 Host 提供打开 tool call 或 source location 的公共 capability;缺少该 capability 时,卡片只提供内联检查,不访问私有 store 或宿主 DOM。
43
43
 
44
44
  `enhancedToolView` 开启且插件启用时,Client 通过公共 keyed `tool.call.toolview` 注册 `run_code` 与 `edit_run_code`;任一开关关闭时实时 dispose 两个注册,让 owner 恢复 DSH 原生 fallback。这个正文 slot 只依赖 Host 提供的 key、`toolName`、`sessionId`、`useSessions`、运行中/已结算 `block` 与 `inspect` 公共契约。增强行保留 Code / Code edit 标题、可展开的原始源码(`arguments` 与 `argsRaw` 任一公共形态均可读)、结果、可见且可访问的运行/失败/中断状态和 Inspect 入口;在提供时优先使用公共 `DisclosureRow` 与 `CodeBlock`,缺少 `DisclosureRow` 时回退到插件自有行,缺少 `CodeBlock` 时回退为纯文本源码。`DisclosureRow` 路径把运行/失败/中断状态、摘要和已证明的功能标记放进同一行始终可见的单行预览;完成态由标题与摘要表达,运行/失败/中断状态由文字 chip 承担并保持可访问。预览间距由插件自有类对称提供,并按单行内容布局,不依赖 Host primitive 的行内几何;源码、结果和 Inspect 只出现在展开主体。结果内容与原生 ioCard 采用相同的灰色圆角背景(`--dsw-alias-markdown-code-block` 加 12px 圆角与边框),失败态使用错误色文字;代码块的语言/复制栏保持原生 CodeBlock 的常亮可见;`检查调用` 入口采用原生 inspectButton 同款胶囊样式,默认 opacity 0,仅在卡片 `:hover` 或 `:focus-visible` 时淡入(opacity .1s),不触发任何布局位移。窄屏下回退路径的摘要允许换行。
45
45
 
46
- 功能标记不是计数,也不把“插件已启用”冒充成一次功能收益。Client 只在完整 v1-v3 journal 和对应附属 metadata 能证明时显示:自动改写 import(附模块名)、自动剥离 export、自动拆分混合重声明(附已有 binding)、带完整 target/derived-run/non-noop/可选恢复边界关系且与调用目标前置条件一致的安全编辑执行、成功的 `code.run`、由 `PTC-R002` 的 `warning/recover/rolled-back` 语义元组证明本次确实发生的持久重放,以及 `repl.state` 操作。进程保留或 discarded 状态不作为正文功能标记。普通宽松重声明、顶层 native 调用是否来自 canonicalizer、`cordis_*` 成员是否由官方可选 Cordis mount 提供,以及是否实际选择了 prompt 恢复提示没有独立的 Client-only 事实,因此不根据可复制的代码形状、工具名前缀或设置默认值推测;恢复边界本身不展示。
46
+ 功能标记不是计数,也不把“插件已启用”冒充成一次功能收益。Client 只在完整 v1-v4 journal 和对应附属 metadata 能证明时显示:自动改写 import(附模块名)、自动剥离 export、自动拆分混合重声明(附已有 binding)、带完整 target/derived-run/non-noop/可选恢复边界关系且与调用目标前置条件一致的安全编辑执行、成功的 `code.run`、由 `PTC-R002` 的 `warning/recover/rolled-back` 语义元组证明本次确实发生的持久重放,以及 `repl.state` 操作。Client 对 v4 同时封闭校验 `bindingPolicy`、`rewritePolicy` 与 `moduleSemantics`;任一缺失或畸形都不产生功能标记。进程保留或 discarded 状态不作为正文功能标记。普通变量或 function/class 重声明、顶层 native 调用是否来自 canonicalizer、`cordis_*` 成员是否由官方可选 Cordis mount 提供,以及是否实际选择了 prompt 恢复提示没有独立的 Client-only 事实,因此不根据可复制的代码形状、工具名前缀或设置默认值推测;恢复边界本身不展示。
47
47
 
48
- 这条路径不检查或改写宿主 DOM,不调用 Host、不主动加载历史。binding 卡片只消费随结果持久化的 value-independent presentation metadata,其中源码只是已提交 cell 的有界声明片段,不是运行时值;它不改变 canonical result、tool schema、system prompt、runtime context、journal schema 或迁移器。未知 journal 版本、未知 metadata 字段和损坏 metadata 只会让对应功能标记或 binding 列表缺席,不会隐藏源码或结果;无法脱离 session call identity 解析非空 v1 `confirms` 时同样只省略标记,不触发历史读取或迁移。因此 UI 是否安装、能否渲染以及用户是否悬浮、聚焦或展开正文行,都不会改变模型请求或会话继续语义。
48
+ 这条路径不检查或改写宿主 DOM,不调用 Host、不主动加载历史。binding 卡片只消费随结果持久化的 value-independent presentation metadata,其中源码只是已提交 cell 的有界声明片段,不是运行时值;它不改变 canonical result、tool schema、system prompt、runtime context、journal schema 或迁移器,也不向模型公开状态。未知 journal 版本、未知 metadata 字段和损坏 metadata 只会让对应功能标记或 binding 列表缺席,不会隐藏源码或结果;无法脱离 session call identity 解析非空 v1 `confirms` 时同样只省略标记,不触发历史读取或迁移。因此 UI 是否安装、能否渲染以及用户是否悬浮、聚焦或展开正文行,都不会改变模型请求或会话继续语义,也不能作为在模型 surface 收缩后继续保留 runtime binding 的证据。
49
49
 
50
50
  live 配置若因宿主能力缺失或 runtime 安装/重配置失败,会先回滚所有已创建或更新的 owner,再把持久设置回写为上一次已应用值;回滚写入失败时 Host 记录 activation diagnostic,避免静默把配置显示成不存在的 runtime。
51
51