dsh-ptc-plus 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.en.md +3 -1
  2. package/README.md +3 -1
  3. package/client.cjs +165 -29
  4. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -2
  5. package/docs/adr/0019-plugin-settings-and-kill-switch.md +2 -2
  6. package/docs/adr/0023-global-user-bindings.md +12 -0
  7. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +1 -1
  8. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
  9. package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +4 -4
  10. package/docs/adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md +1 -1
  11. package/docs/adr/0048-supersede-a-rewritten-cell.md +48 -0
  12. package/docs/adr/0049-prove-a-settlement-against-its-published-record.md +53 -0
  13. package/docs/adr/0050-read-host-surfaces-through-their-published-contract.md +48 -0
  14. package/docs/adr/README.md +7 -1
  15. package/docs/architecture.md +1 -1
  16. package/docs/durability-design.md +9 -2
  17. package/docs/installation.md +9 -9
  18. package/docs/releases/v0.5.2.md +26 -14
  19. package/docs/releases/v0.5.3.md +77 -0
  20. package/docs/runtime-reference.md +7 -1
  21. package/docs/user-bindings.en.md +15 -0
  22. package/docs/user-bindings.md +15 -0
  23. package/docs/v0.5.3-goals.md +117 -0
  24. package/docs/verification.md +4 -4
  25. package/index.js +67 -19
  26. package/internal/binding-adoption.js +136 -0
  27. package/internal/capability-command-receipt.js +18 -4
  28. package/internal/capability-delivery-proof.js +70 -0
  29. package/internal/capability-environment.js +38 -1
  30. package/internal/config-spec.js +14 -5
  31. package/internal/cordis-tools-owner.js +2 -1
  32. package/internal/direct-surface-owner.js +27 -13
  33. package/internal/edit-transport-owner.js +6 -5
  34. package/internal/execution-provider-owner.js +6 -0
  35. package/internal/execution-seam-compat.js +25 -0
  36. package/internal/host-tool-sdk.js +308 -0
  37. package/internal/official-repl-priority.js +31 -10
  38. package/internal/program-bindings.js +12 -1
  39. package/internal/repl-memory-projection.js +21 -2
  40. package/internal/runtime-bridge-owner.js +113 -8
  41. package/internal/runtime-messages.js +5 -0
  42. package/internal/session-journal-recovery.js +210 -32
  43. package/internal/session-journal-schema.js +3 -0
  44. package/internal/session-journal.js +46 -9
  45. package/internal/session-log-view.js +22 -2
  46. package/internal/session-runtime.js +106 -39
  47. package/internal/user-bindings-owner.js +109 -6
  48. package/package.json +15 -7
  49. package/src/client-capabilities-view.js +45 -3
  50. package/src/client-copy.js +24 -0
  51. package/src/client-indicator-view.js +2 -1
  52. package/src/client-repl-view.js +35 -16
  53. package/src/client.js +3 -1
package/README.en.md CHANGED
@@ -114,6 +114,8 @@ An `accepted: true, scheduled: true` receipt means the edit is scheduled: the en
114
114
 
115
115
  When an accepted edit cannot obtain a compute baseline before it runs, the receipt stays a receipt: the revision does not execute, publishes no new binding, and the outer cell keeps its own result, so a restart does not lose what that cell already saved as a durable checkpoint.
116
116
 
117
+ The `editRewritesHistory` setting (off by default) decides how a rebuilt environment reads that edit. Off keeps append semantics: the edited cell and its revision both stay in the environment, and the revision only overwrites the names it declares again. On leaves the edited cell in the session log but stops it from building the environment, replaced by the revision; names the revision no longer declares are reported as superseded (`PTC-R007`) rather than lost. The names the log can prove are named; where it cannot, only the relation is stated.
118
+
117
119
  The first two behaviors are on by default and can be disabled in settings. Syntax suggestions never execute on their own, and they say nothing about whether the code matches the task's intent. A volatile state cannot be saved as a durable checkpoint, but a rejected save leaves the current variables usable; restore does not undo external effects or change global binding configuration.
118
120
 
119
121
  ## Project files and tool capabilities
@@ -170,7 +172,7 @@ Before a new session starts, capability and global binding changes only update i
170
172
 
171
173
  The host execution provider must use TypeScript; changing it to another language during a reload withdraws the plugin and reports a diagnostic, and switching back re-enables it.
172
174
 
173
- With DSH's official `executionMode: persistent-in-memory` enabled, **Prioritize the official REPL mode** under State and recovery chooses who owns the PTC session: disabled by default, so PTC Plus owns `run_code`; enabled, the official runtime owns execution and PTC Plus leaves execution, REPL state semantics, and observation to it. The setting has no effect while the official mode is off. Ownership changes take effect immediately and leaving plugin execution disposes its in-memory environment. Official in-memory state follows DSH's Agent/provider lifecycle; changing priority does not guarantee clearing it. Official-mode calls stay outside the plugin's records, so they never count as recoverable state after switching back to the plugin.
175
+ When the official runtime keeps a program environment per state owner, **Prioritize the official REPL mode** under State and recovery chooses who owns the PTC session: disabled by default, so PTC Plus owns `run_code`; enabled, the official runtime owns execution and PTC Plus leaves execution, REPL state semantics, and observation to it. The decision follows what the runtime declares: a provider that publishes `retainsState` answers directly (an older one expresses the same fact with `repl` source), and the setting has no effect on a runtime that keeps no state. Ownership changes take effect immediately and leaving plugin execution disposes its in-memory environment. Official in-memory state follows DSH's Agent/provider lifecycle; changing priority does not guarantee clearing it. Official-mode calls stay outside the plugin's records, so they never count as recoverable state after switching back to the plugin.
174
176
 
175
177
  ![PTC Plus settings window](assets/ptc-plus-settings-en.png)
176
178
 
package/README.md CHANGED
@@ -114,6 +114,8 @@ return receipt
114
114
 
115
115
  被接受的编辑若在真正执行前因为没有取得计算基线而未能运行,收据仍然是收据:修订不执行、不产生新变量,外层 cell 自己的结果照常保存,重启恢复时也不会再因为这次编辑而连带丢掉它已保存为 durable 检查点的状态。
116
116
 
117
+ 设置里的“编辑时替换原单元格”(`editRewritesHistory`,默认关闭)决定日志重建环境时怎么看待这次编辑。关闭时保持追加语义:被编辑的单元格和修订都留在环境里,修订只是覆盖了它重新声明的名字。打开后被编辑的单元格仍然留在会话日志里,但不再参与环境重建,改由修订代替它;修订不再声明的名字按“被取代”报告(`PTC-R007`),而不是按“丢失”报告。日志能证明的具体名字会被点名,证明不了时只陈述取代关系。
118
+
117
119
  前两项默认开启,可以在设置里关掉。语法建议不会自动执行,也不代表代码符合任务意图。volatile 状态存不成 durable 检查点,但保存被拒绝后当前变量还能接着用;恢复不撤销外部效果,也不改全局绑定配置。
118
120
 
119
121
  ## 项目文件与工具能力
@@ -170,7 +172,7 @@ return capabilities.search({ query: 'read file content', budgetBytes: 8192 })
170
172
 
171
173
  宿主执行 provider 必须使用 TypeScript;热重载换成别的语言时插件会撤出并报出诊断,换回来就重新启用。
172
174
 
173
- 开启 DSH 官方的 `executionMode: persistent-in-memory` 后,可以在“状态与恢复”里用**官方 REPL 模式优先**选择谁接管 PTC 会话:默认关闭,`run_code` 由 PTC Plus 接管;开启后由官方运行时接管,PTC Plus 让出执行、REPL 状态语义与观察,官方模式没开启时该设置不生效。切换立即生效,离开插件执行时会释放插件的内存环境;官方内存状态由 DSH 的 Agent/provider 生命周期管理,切换优先设置不保证清空它。官方执行期间的调用不进入插件记录,切回插件后不会当作可恢复状态。
175
+ 当官方运行时按状态所有者保留程序环境时,可以在“状态与恢复”里用**官方 REPL 模式优先**选择谁接管 PTC 会话:默认关闭,`run_code` 由 PTC Plus 接管;开启后由官方运行时接管,PTC Plus 让出执行、REPL 状态语义与观察。判断依据是运行时自己的声明(`retainsState`;更早的运行时通过 `repl` 源码形式表达同一件事),不是某个执行模式名或版本号,无状态运行时上该设置不生效。切换立即生效,离开插件执行时会释放插件的内存环境;官方内存状态由 DSH 的 Agent/provider 生命周期管理,切换优先设置不保证清空它。官方执行期间的调用不进入插件记录,切回插件后不会当作可恢复状态。
174
176
 
175
177
  ![PTC Plus 设置窗口](assets/ptc-plus-settings-zh.png)
176
178
 
package/client.cjs CHANGED
@@ -1984,10 +1984,10 @@ var CONFIG_FIELDS = Object.freeze([
1984
1984
  type: "enum",
1985
1985
  options: Object.freeze(["stateful", "protected"]),
1986
1986
  default: "stateful",
1987
- label: "\u5141\u8BB8\u91CD\u58F0\u660E\u548C\u8986\u76D6",
1988
- labelEn: "Allow redeclarations and overrides",
1989
- description: "\u540C\u4E00\u4F5C\u7528\u57DF\u7684\u58F0\u660E\u548C\u8D4B\u503C\u53EF\u66F4\u65B0\u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u53CA\u5BFC\u5165\u3002\u5173\u95ED\u540E\u68C0\u6D4B\u8DE8 cell \u91CD\u58F0\u660E\uFF0C\u5E76\u4FDD\u62A4\u65B0\u5EFA\u7684 const \u548C import\uFF1B\u9759\u6001 import/export \u59CB\u7EC8\u53EF\u7528\u3002",
1990
- descriptionEn: "Declarations and assignments update variables, functions, classes and imports in the same scope. Disabling detects cross-cell redeclarations and protects newly created const and import bindings; static import/export remains available."
1987
+ label: "\u8DE8 cell \u58F0\u660E\u7B56\u7565",
1988
+ labelEn: "Cross-cell declaration policy",
1989
+ description: "\u540C\u4E00\u4F5C\u7528\u57DF\u7684\u58F0\u660E\u548C\u8D4B\u503C\u53EF\u66F4\u65B0\u53D8\u91CF\u3001\u51FD\u6570\u3001\u7C7B\u53CA\u5BFC\u5165\u3002\u9009\u201C\u542F\u7528\u540D\u79F0\u4FDD\u62A4\u201D\u65F6\u6539\u4E3A\u68C0\u6D4B\u8DE8 cell \u91CD\u58F0\u660E\uFF0C\u5E76\u4FDD\u62A4\u65B0\u5EFA\u7684 const \u548C import\uFF1B\u9759\u6001 import/export \u59CB\u7EC8\u53EF\u7528\u3002",
1990
+ descriptionEn: "Declarations and assignments update variables, functions, classes and imports in the same scope. With \u201CProtect names\u201D selected, cross-cell redeclarations are detected instead and newly created const and import bindings are protected; static import/export remains available."
1991
1991
  },
1992
1992
  {
1993
1993
  key: "canonicalizeToolCalls",
@@ -2141,6 +2141,15 @@ var CONFIG_FIELDS = Object.freeze([
2141
2141
  description: "\u68C0\u6D4B\u5230\u5B98\u65B9\u6301\u4E45 REPL \u6A21\u5F0F\u65F6\u7531\u5B98\u65B9\u8FD0\u884C\u65F6\u63A5\u7BA1 PTC \u4F1A\u8BDD\u6267\u884C\uFF1BPTC Plus \u8BA9\u51FA run_code \u53CA\u5176\u8FDB\u7A0B\u5185\u4F1A\u8BDD\u72B6\u6001\u8BED\u4E49\uFF0C\u5173\u95ED\u65F6\u59CB\u7EC8\u7531 PTC Plus \u63A5\u7BA1\u3002",
2142
2142
  descriptionEn: "Lets the official runtime own PTC session execution when the official persistent REPL mode is active; PTC Plus then leaves run_code and its in-process session-state semantics to DSH. Disabled keeps PTC Plus in charge."
2143
2143
  },
2144
+ {
2145
+ key: "editRewritesHistory",
2146
+ type: "boolean",
2147
+ default: false,
2148
+ label: "\u7F16\u8F91\u65F6\u66FF\u6362\u539F\u5355\u5143\u683C",
2149
+ labelEn: "Replace the edited cell in place",
2150
+ description: '\u9ED8\u8BA4\u5173\u95ED\uFF0C\u4E00\u822C\u4E5F\u4E0D\u9700\u8981\u6253\u5F00\uFF1A\u7F16\u8F91\u53EA\u4F1A\u8FFD\u52A0\u4E00\u6B21\u6267\u884C\uFF0C\u539F\u5355\u5143\u683C\u548C\u5B83\u521B\u5EFA\u7684\u7ED1\u5B9A\u90FD\u8FD8\u5728\u3002\u5F00\u542F\u540E\uFF0C\u7F16\u8F91\u4F1A\u66FF\u6362\u539F\u5355\u5143\u683C\uFF0C\u4E4B\u540E\u7684\u91CD\u5EFA\u6309\u65B0\u7248\u672C\u8FDB\u884C\uFF0C\u65B0\u7248\u672C\u91CC\u4E0D\u518D\u58F0\u660E\u7684\u540D\u5B57\u4F1A\u88AB\u70B9\u540D"\u5DF2\u88AB\u66FF\u6362"\uFF0C\u800C\u4E0D\u662F\u6084\u6084\u6D88\u5931\u3002\u53EA\u6709\u5F53\u4F60\u5E0C\u671B\u73AF\u5883\u8868\u73B0\u5F97"\u8FD9\u4E00\u683C\u4E00\u76F4\u5C31\u662F\u65B0\u7248\u672C"\u65F6\u624D\u9700\u8981\u6253\u5F00\u3002',
2151
+ descriptionEn: "Off by default, and usually unnecessary: an edit appends one more execution, leaving the original cell and the bindings it created in place. When on, the edit replaces the original cell, later rebuilds follow the new version, and a name the new version no longer declares is reported as replaced rather than silently disappearing. Turn it on only when the environment should behave as if that cell had always been the new version."
2152
+ },
2144
2153
  {
2145
2154
  key: "tipsEnabled",
2146
2155
  type: "boolean",
@@ -2335,7 +2344,7 @@ var CONFIG_GROUPS = Object.freeze([
2335
2344
  key: "recovery",
2336
2345
  label: "\u72B6\u6001\u4E0E\u6062\u590D",
2337
2346
  labelEn: "State and recovery",
2338
- fields: Object.freeze(["durableReplay", "unmeasuredCellLimit", "officialReplPriority", "tipsEnabled", "tipCooldownMessages", "tipEscalationFailures"])
2347
+ fields: Object.freeze(["durableReplay", "editRewritesHistory", "unmeasuredCellLimit", "officialReplPriority", "tipsEnabled", "tipCooldownMessages", "tipEscalationFailures"])
2339
2348
  },
2340
2349
  {
2341
2350
  key: "extensions",
@@ -3604,6 +3613,44 @@ function registerGated(scope, { subscribe, isEnabled, register }) {
3604
3613
  });
3605
3614
  }
3606
3615
 
3616
+ // internal/capability-delivery-proof.js
3617
+ var CAPABILITY_PROOF_SCHEMA = "ptc-capability-proof/v1";
3618
+ var CAPABILITY_DELIVERY_STATES = /* @__PURE__ */ new Set(["delivered", "pending", "absent"]);
3619
+ var CAPABILITY_USABILITY_STATES = /* @__PURE__ */ new Set(["proven", "unproven"]);
3620
+ var CAPABILITY_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/;
3621
+ function isRecord4(value) {
3622
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3623
+ }
3624
+ function normalizeCapabilityProof(value) {
3625
+ if (!isRecord4(value) || Reflect.ownKeys(value).length !== 5 || !Object.hasOwn(value, "schema") || !Object.hasOwn(value, "id") || !Object.hasOwn(value, "saved") || !Object.hasOwn(value, "delivery") || !Object.hasOwn(value, "usability")) {
3626
+ throw new TypeError("invalid capability proof");
3627
+ }
3628
+ if (value.schema !== CAPABILITY_PROOF_SCHEMA) throw new TypeError("invalid capability proof schema");
3629
+ if (typeof value.id !== "string" || !CAPABILITY_ID.test(value.id)) throw new TypeError("invalid capability proof id");
3630
+ if (typeof value.saved !== "boolean") throw new TypeError("invalid capability proof saved fact");
3631
+ if (!CAPABILITY_DELIVERY_STATES.has(value.delivery)) throw new TypeError("invalid capability proof delivery state");
3632
+ if (!CAPABILITY_USABILITY_STATES.has(value.usability)) throw new TypeError("invalid capability proof usability state");
3633
+ return Object.freeze({
3634
+ schema: CAPABILITY_PROOF_SCHEMA,
3635
+ id: value.id,
3636
+ saved: value.saved,
3637
+ delivery: value.delivery,
3638
+ usability: value.usability
3639
+ });
3640
+ }
3641
+ function capabilityProofText(proof) {
3642
+ const normalized = normalizeCapabilityProof(proof);
3643
+ const id2 = normalized.id;
3644
+ const saved = normalized.saved ? `Saved: yes. The global configuration records a choice for ${id2}.` : `Saved: no. The global configuration records no choice for ${id2}, so this save did not happen; the session choice below is the only change.`;
3645
+ const delivery = {
3646
+ delivered: `Delivered: yes. The model-visible capability catalog published ${id2} at an accepted step.`,
3647
+ pending: `Delivered: not yet. The saved configuration reaches the model-visible catalog at the next accepted step, so ${id2} is not in it now.`,
3648
+ absent: `Delivered: no. The published capability catalog does not list ${id2}, so the model never received it.`
3649
+ }[normalized.delivery];
3650
+ const usability = normalized.usability === "proven" ? `Usable: proven. A completed execution in this session reached ${id2}.` : `Usable: unproven. No completed execution in this session reached ${id2}; only such a result would prove it, and a saved or published entry does not.`;
3651
+ return `${saved} ${delivery} ${usability}`;
3652
+ }
3653
+
3607
3654
  // internal/capability-command-receipt.js
3608
3655
  var PREFIX = "\n\nPTC Plus capability refresh receipt:\n";
3609
3656
  var FALLBACKS = Object.freeze({
@@ -3616,17 +3663,25 @@ var FALLBACKS = Object.freeze({
3616
3663
  off: "Capability management is off; the session stays with the Host capabilities for session"
3617
3664
  });
3618
3665
  var STATES = /* @__PURE__ */ new Set(["recorded", "pending", "unavailable", "usage", "saved-only", "paused", "off"]);
3619
- function capabilityCommandReceipt(state, sessionId, error, proposal) {
3666
+ function capabilityCommandReceipt(state, sessionId, error, proposal, proof) {
3620
3667
  if (!STATES.has(state) || typeof sessionId !== "string" || !sessionId) throw new TypeError("invalid capability command receipt");
3621
3668
  if (error !== void 0 && (state !== "saved-only" || typeof error !== "string" || !error)) throw new TypeError("invalid capability command error");
3622
3669
  if (proposal !== void 0 && (state !== "pending" || !proposal || typeof proposal.commandId !== "string" || typeof proposal.rawInput !== "string" || !proposal.message)) throw new TypeError("invalid capability proposal receipt");
3623
- return `${FALLBACKS[state]} ${sessionId}.${error ? ` ${error}` : ""}` + PREFIX + JSON.stringify({ schema: "ptc-capability-command/v1", state, sessionId, ...error === void 0 ? {} : { error }, ...proposal === void 0 ? {} : { proposal } });
3670
+ const layers = proof === void 0 ? void 0 : normalizeCapabilityProof(proof);
3671
+ return `${FALLBACKS[state]} ${sessionId}.${error ? ` ${error}` : ""}` + (layers === void 0 ? "" : ` ${capabilityProofText(layers)}`) + PREFIX + JSON.stringify({
3672
+ schema: "ptc-capability-command/v1",
3673
+ state,
3674
+ sessionId,
3675
+ ...error === void 0 ? {} : { error },
3676
+ ...proposal === void 0 ? {} : { proposal },
3677
+ ...layers === void 0 ? {} : { proof: layers }
3678
+ });
3624
3679
  }
3625
3680
  function readCapabilityCommandReceipt(text) {
3626
3681
  if (typeof text !== "string" || !text.includes(PREFIX)) return void 0;
3627
3682
  try {
3628
3683
  const receipt = JSON.parse(text.slice(text.indexOf(PREFIX) + PREFIX.length));
3629
- return receipt?.schema === "ptc-capability-command/v1" && STATES.has(receipt.state) && typeof receipt.sessionId === "string" && receipt.sessionId && capabilityCommandReceipt(receipt.state, receipt.sessionId, receipt.error, receipt.proposal) === text ? receipt : void 0;
3684
+ return receipt?.schema === "ptc-capability-command/v1" && STATES.has(receipt.state) && typeof receipt.sessionId === "string" && receipt.sessionId && capabilityCommandReceipt(receipt.state, receipt.sessionId, receipt.error, receipt.proposal, receipt.proof) === text ? receipt : void 0;
3630
3685
  } catch {
3631
3686
  return void 0;
3632
3687
  }
@@ -9154,10 +9209,22 @@ function sortCapabilityEntries(entries, current, valueFor) {
9154
9209
  }
9155
9210
 
9156
9211
  // src/client-capabilities-view.js
9212
+ function capabilityProofCopy(t2, proof) {
9213
+ const delivery = ["delivered", "pending", "absent"].includes(proof?.delivery) ? proof.delivery : void 0;
9214
+ const usability = ["proven", "unproven"].includes(proof?.usability) ? proof.usability : void 0;
9215
+ if (delivery === void 0 || usability === void 0) return t2("capabilities.quick.unknown");
9216
+ return t2("capabilities.proof.line", {
9217
+ saved: t2(proof.saved === true ? "capabilities.proof.saved.yes" : "capabilities.proof.saved.no"),
9218
+ delivery: t2(`capabilities.proof.delivery.${delivery}`),
9219
+ usability: t2(`capabilities.proof.usability.${usability}`)
9220
+ });
9221
+ }
9157
9222
  function capabilityCommandText(t2, text) {
9158
9223
  if (text?.key) return t2(text.key, text.params);
9159
9224
  const receipt = readCapabilityCommandReceipt(text);
9160
- return receipt ? t2(`capabilities.receipt.${receipt.state}`, { session: receipt.sessionId, error: receipt.error ?? "" }) : text;
9225
+ if (!receipt) return text;
9226
+ const line = t2(`capabilities.receipt.${receipt.state}`, { session: receipt.sessionId, error: receipt.error ?? "" });
9227
+ return receipt.proof === void 0 ? line : `${line} ${capabilityProofCopy(t2, receipt.proof)}`;
9161
9228
  }
9162
9229
  var HOST_CONTEXT_SECTIONS = Object.freeze([
9163
9230
  Object.freeze({ id: "host:system-prompt", presence: "capabilities.prompt.hostPresenceEvery" }),
@@ -9210,11 +9277,12 @@ function createCapabilityCommandCard(React32) {
9210
9277
  const legacyRefresh = text === "Session capability environment refreshed; the model-visible notification is recorded.";
9211
9278
  if (receipt || String(node.args ?? "").trim() === "refresh" || legacyRefresh) {
9212
9279
  const outcome2 = receipt ? t2(`capabilities.card.${receipt.state}`, { error: receipt.error ?? "" }) : legacyRefresh ? t2("capabilities.card.recorded") : node.outcome ? (typeof text === "string" && !text.trim() ? void 0 : capabilityCommandText(t2, text)) ?? t2(node.outcome.kind === "error" ? "capabilities.commandFailed" : "capabilities.refreshAcknowledged") : t2("capabilities.refreshing");
9280
+ const notice = receipt?.proof === void 0 || outcome2 === void 0 ? outcome2 : `${outcome2} ${capabilityProofCopy(t2, receipt.proof)}`;
9213
9281
  return h("p", {
9214
9282
  className: "ptcPlusCapabilityCommandNotice",
9215
9283
  role: node.outcome?.kind === "error" ? "alert" : "status",
9216
9284
  "data-state": node.outcome?.kind ?? "pending"
9217
- }, outcome2);
9285
+ }, notice);
9218
9286
  }
9219
9287
  let json;
9220
9288
  if (typeof text === "string") {
@@ -11403,6 +11471,7 @@ function createCapabilityRefreshView(React32, commands) {
11403
11471
  function createSessionCapabilityControlsView(React32, { commands, Modal, Select: Select2, inspectSessionCatalog }) {
11404
11472
  const h = React32.createElement;
11405
11473
  const emptyDrafts = () => ({});
11474
+ const emptyProofs = () => ({});
11406
11475
  return function SessionCapabilityControls({ sessionId, open, enabled, t: t2, onClose }) {
11407
11476
  const source = React32.useMemo(() => commands.source(sessionId), [sessionId]);
11408
11477
  const available = React32.useSyncExternalStore(source.subscribe, source.getSnapshot);
@@ -11424,6 +11493,7 @@ function createSessionCapabilityControlsView(React32, { commands, Modal, Select:
11424
11493
  const [sort2, setSort] = React32.useState(null);
11425
11494
  const [focusDimension, setFocusDimension] = React32.useState(null);
11426
11495
  const [status, setStatus] = React32.useState(null);
11496
+ const [proofs, setProofs] = React32.useState(emptyProofs);
11427
11497
  const [pending, setPending] = React32.useState(false);
11428
11498
  const request = React32.useRef(null);
11429
11499
  const applying = React32.useRef(false);
@@ -11458,6 +11528,7 @@ function createSessionCapabilityControlsView(React32, { commands, Modal, Select:
11458
11528
  setPane("list");
11459
11529
  setFocusDimension(null);
11460
11530
  setStatus(null);
11531
+ setProofs(emptyProofs());
11461
11532
  setPending(false);
11462
11533
  setError(null);
11463
11534
  }, [sessionId]);
@@ -11588,15 +11659,18 @@ function createSessionCapabilityControlsView(React32, { commands, Modal, Select:
11588
11659
  let pendingCount = 0;
11589
11660
  let failure = null;
11590
11661
  let savedOnly = null;
11662
+ const observed = {};
11591
11663
  for (const [id2, selection, dimension] of staged) {
11592
11664
  if (controller.signal.aborted) break;
11665
+ const line = `set ${targetScope} ${encodeURIComponent(id2)} ${dimension ? dimension + " " : ""}${selection}`;
11593
11666
  try {
11594
- const result = await commands.run(sessionId, `set ${targetScope} ${encodeURIComponent(id2)} ${dimension ? dimension + " " : ""}${selection}`, controller.signal);
11667
+ const result = await commands.run(sessionId, line, controller.signal);
11595
11668
  const receipt = readCapabilityCommandReceipt(result.text);
11596
11669
  if (!receipt || receipt.sessionId !== sessionId || !["recorded", "pending"].includes(receipt.state)) {
11597
11670
  throw new Error(receipt ? capabilityCommandText(t2, result.text) : t2("capabilities.quick.unknownReceipt"));
11598
11671
  }
11599
11672
  if (receipt?.state === "pending") pendingCount += 1;
11673
+ if (receipt.proof !== void 0) observed[receipt.proof.id] = { proof: receipt.proof, command: `/capabilities ${line}` };
11600
11674
  completed += 1;
11601
11675
  } catch (cause) {
11602
11676
  failure = String(cause?.message ?? cause);
@@ -11604,6 +11678,7 @@ function createSessionCapabilityControlsView(React32, { commands, Modal, Select:
11604
11678
  break;
11605
11679
  }
11606
11680
  }
11681
+ if (mounted.current && Object.keys(observed).length) setProofs((current) => ({ ...current, ...observed }));
11607
11682
  if (request.current !== controller || controller.signal.aborted || !mounted.current) return;
11608
11683
  const refreshed = await load(controller.signal);
11609
11684
  if (request.current !== controller || controller.signal.aborted || !mounted.current) return;
@@ -11997,6 +12072,17 @@ function createSessionCapabilityControlsView(React32, { commands, Modal, Select:
11997
12072
  selected.sourceDimensions ? h(React32.Fragment, null, h("dt", null, t2("capabilities.policy.frozenSource")), h("dd", null, dimensionFacts(selected.sourceDimensions))) : null,
11998
12073
  selected.effectiveDimensions ? h(React32.Fragment, null, h("dt", null, t2("capabilities.policy.effectiveDimensions")), h("dd", null, dimensionFacts(selected.effectiveDimensions))) : null
11999
12074
  ),
12075
+ h(
12076
+ "div",
12077
+ { className: "ptcPlusCapabilityProof", "data-capability-proof": selected.id },
12078
+ h("h4", null, t2("capabilities.proof.title")),
12079
+ proofs[selected.id] ? h(
12080
+ React32.Fragment,
12081
+ null,
12082
+ h("p", { className: "ptcPlusDetail", role: "status" }, capabilityProofCopy(t2, proofs[selected.id].proof)),
12083
+ h("small", null, t2("capabilities.proof.observed", { command: proofs[selected.id].command }))
12084
+ ) : h("p", { className: "ptcPlusDetail" }, t2("capabilities.proof.unobserved"))
12085
+ ),
12000
12086
  selected.source?.kind === "mcp" && selected.category !== "server" ? (() => {
12001
12087
  const server = catalog.find((entry) => entry.category === "server" && entry.source?.server === selected.source.server);
12002
12088
  if (!server) return null;
@@ -12993,6 +13079,7 @@ function createReplView(React32, deps) {
12993
13079
  Select: Select2,
12994
13080
  featureEnabled: featureEnabled2,
12995
13081
  normalizeReplMemorySnapshot: normalizeReplMemorySnapshot2,
13082
+ normalizeReplMemoryView: normalizeReplMemoryView2,
12996
13083
  unavailableReplMemorySnapshot: unavailableReplMemorySnapshot2,
12997
13084
  useSessionPreset: useSessionPreset2,
12998
13085
  sessionUsesPtcPreset: sessionUsesPtcPreset2,
@@ -13005,23 +13092,29 @@ function createReplView(React32, deps) {
13005
13092
  return null;
13006
13093
  }
13007
13094
  function useObservedReplMemory({ sessionId, projected, active, elementRef, observeRepl }) {
13008
- const memory = React32.useMemo(() => {
13095
+ const view = React32.useMemo(() => {
13009
13096
  try {
13010
- return normalizeReplMemorySnapshot2(projected);
13097
+ return normalizeReplMemoryView2(projected);
13011
13098
  } catch {
13012
- return unavailableReplMemorySnapshot2();
13099
+ return void 0;
13013
13100
  }
13014
13101
  }, [projected]);
13102
+ const memory = view?.memory ?? unavailableReplMemorySnapshot2();
13015
13103
  const [observed, setObserved] = React32.useState(null);
13016
13104
  React32.useEffect(() => active && typeof observeRepl === "function" ? observeRepl(
13017
13105
  sessionId,
13018
13106
  elementRef.current,
13019
13107
  memory,
13020
- (value) => setObserved({ sessionId, source: memory, value })
13021
- ) : void 0, [active, observeRepl, sessionId, memory, elementRef]);
13022
- if (observed?.sessionId !== sessionId) return memory;
13023
- if (!memory.available && observed.value?.available === true) return observed.value;
13024
- return observed.source === memory ? observed.value : memory;
13108
+ (value) => setObserved({ sessionId, runtime: view?.runtime, source: memory, value })
13109
+ ) : void 0, [active, observeRepl, sessionId, memory, view, elementRef]);
13110
+ if (observed?.sessionId !== sessionId) return { memory, provenance: "current" };
13111
+ if (view !== void 0 && observed.runtime !== view.runtime && !(observed.runtime === void 0 && view.runtime === void 0)) {
13112
+ return { memory, provenance: "previous-runtime" };
13113
+ }
13114
+ if (observed.runtime === view?.runtime && !memory.available && observed.value?.available === true) {
13115
+ return { memory: observed.value, provenance: "refreshed" };
13116
+ }
13117
+ return { memory: observed.source === memory ? observed.value : memory, provenance: "current" };
13025
13118
  }
13026
13119
  function ReplBindingInspector({ entry, preview, t: t2 }) {
13027
13120
  const readable = preview?.status === "readable";
@@ -13051,7 +13144,7 @@ function createReplView(React32, deps) {
13051
13144
  readable ? h("pre", { className: "ptcPlusObservationPreview" }, preview.text) : h("p", { className: "ptcPlusObservationUnavailable" }, t2(preview === void 0 ? "console.unobservedValue" : "console.unreadable"))
13052
13145
  );
13053
13146
  }
13054
- function ReplSessionBindings({ memory, t: t2 }) {
13147
+ function ReplSessionBindings({ memory, provenance, t: t2 }) {
13055
13148
  const [query, setQuery] = React32.useState("");
13056
13149
  const [kind, setKind] = React32.useState("all");
13057
13150
  const [selectedName, setSelectedName] = React32.useState(null);
@@ -13085,7 +13178,7 @@ function createReplView(React32, deps) {
13085
13178
  memory.entries.length === 0 ? h(
13086
13179
  "div",
13087
13180
  { className: "ptcPlusSessionEmpty" },
13088
- t2(memory.available ? "memory.empty" : "memory.unavailable")
13181
+ t2(!memory.available ? provenance === "previous-runtime" ? "memory.previousRuntime" : "memory.unavailable" : "memory.empty")
13089
13182
  ) : h(
13090
13183
  "div",
13091
13184
  { className: "ptcPlusObservationGrid", "data-empty": selected === void 0 },
@@ -13176,7 +13269,7 @@ function createReplView(React32, deps) {
13176
13269
  const workbench = useWorkbenchController({ enabled: globalEnabled, active: true, callUserBindings });
13177
13270
  const eligible = featureEnabled2(settings, "replView") && sessionUsesPtcPreset2(preset);
13178
13271
  const observationRegion = React32.useRef(null);
13179
- const memory = useObservedReplMemory({
13272
+ const { memory, provenance } = useObservedReplMemory({
13180
13273
  sessionId,
13181
13274
  projected,
13182
13275
  active: eligible,
@@ -13191,6 +13284,7 @@ function createReplView(React32, deps) {
13191
13284
  h("div", { className: "ptcPlusConsoleSection", ref: observationRegion }, h(ReplSessionBindings, {
13192
13285
  key: sessionId,
13193
13286
  memory,
13287
+ provenance,
13194
13288
  t: t2
13195
13289
  })),
13196
13290
  globalEnabled ? h(
@@ -13231,6 +13325,7 @@ function createReplView(React32, deps) {
13231
13325
  }
13232
13326
  function ReplMemoryCard({
13233
13327
  memory,
13328
+ provenance,
13234
13329
  globalEnabled,
13235
13330
  globalBindings,
13236
13331
  loadGlobalBinding,
@@ -13330,7 +13425,7 @@ function createReplView(React32, deps) {
13330
13425
  ),
13331
13426
  globalSource?.id !== entry.id ? null : globalSource.error === true ? h("span", { className: "ptcPlusReplEmpty" }, t2("memory.globalUnavailable")) : h("pre", { className: "ptcPlusGlobalSource" }, globalSource.source)
13332
13427
  )))
13333
- ) : !memory.available ? h("span", { className: "ptcPlusReplEmpty" }, t2("memory.unavailable")) : memory.entries.length === 0 ? h("span", { className: "ptcPlusReplEmpty" }, t2("memory.empty")) : h("ul", { className: "ptcPlusReplList" }, memory.entries.map((binding, index2) => {
13428
+ ) : !memory.available ? h("span", { className: "ptcPlusReplEmpty" }, t2(provenance === "previous-runtime" ? "memory.previousRuntime" : "memory.unavailable")) : memory.entries.length === 0 ? h("span", { className: "ptcPlusReplEmpty" }, t2("memory.empty")) : h("ul", { className: "ptcPlusReplList" }, memory.entries.map((binding, index2) => {
13334
13429
  const expanded = expandedBinding === binding.name;
13335
13430
  const preview = binding.definition.source.replace(/\s+/g, " ").trim();
13336
13431
  const definitionId = `${id2}-binding-${index2}`;
@@ -13595,7 +13690,7 @@ var {
13595
13690
  appendArray: appendArray2,
13596
13691
  popArray
13597
13692
  } = runtimeIntrinsics;
13598
- function isRecord4(value) {
13693
+ function isRecord5(value) {
13599
13694
  return value !== null && typeof value === "object" && !isArray2(value);
13600
13695
  }
13601
13696
  function assertOwnFields(value, allowed, label) {
@@ -13612,7 +13707,7 @@ function assertOwnFields(value, allowed, label) {
13612
13707
  var FIELDS = /* @__PURE__ */ new Set(["includeDeclaration", "instructions", "enabled", "declaration"]);
13613
13708
  function normalizeBindingModelContext(value) {
13614
13709
  if (value === void 0) return void 0;
13615
- if (!isRecord4(value)) throw new TypeError("binding modelContext must be an object");
13710
+ if (!isRecord5(value)) throw new TypeError("binding modelContext must be an object");
13616
13711
  assertOwnFields(value, FIELDS, "binding modelContext");
13617
13712
  const { includeDeclaration = true, instructions = "", enabled = true, declaration = "" } = value;
13618
13713
  if (typeof includeDeclaration !== "boolean") throw new TypeError("binding modelContext.includeDeclaration must be a boolean");
@@ -13645,7 +13740,7 @@ var MAX_COMMAND_ID_LENGTH = 256;
13645
13740
  var VIEW_FIELDS = /* @__PURE__ */ new Set(["phase", "capability", "commandId"]);
13646
13741
  var PHASES2 = /* @__PURE__ */ new Set(["idle", "pending", "ready", "failed"]);
13647
13742
  function normalizeCandidate(value) {
13648
- if (!isRecord4(value) || typeof value.requestId !== "string" || value.requestId.length === 0 || value.requestId.length > 128 || !Number.isSafeInteger(value.version) || value.version < 1 || !["new", "edit"].includes(value.mode) || !isRecord4(value.entry) || !["namespace", "top-level"].includes(value.entry.scope) || value.entry.enabled !== false || !Array.isArray(value.entry.symbols) || value.entry.symbols.some((symbol) => typeof symbol !== "string") || ["id", "name", "purpose", "source"].some((key) => typeof value.entry[key] !== "string") || value.entry.source.length === 0 || value.entry.source.length > 65536) {
13743
+ if (!isRecord5(value) || typeof value.requestId !== "string" || value.requestId.length === 0 || value.requestId.length > 128 || !Number.isSafeInteger(value.version) || value.version < 1 || !["new", "edit"].includes(value.mode) || !isRecord5(value.entry) || !["namespace", "top-level"].includes(value.entry.scope) || value.entry.enabled !== false || !Array.isArray(value.entry.symbols) || value.entry.symbols.some((symbol) => typeof symbol !== "string") || ["id", "name", "purpose", "source"].some((key) => typeof value.entry[key] !== "string") || value.entry.source.length === 0 || value.entry.source.length > 65536) {
13649
13744
  throw new Error("invalid dsh-ptc-plus accepted binding candidate");
13650
13745
  }
13651
13746
  const modelContext = normalizeBindingModelContext(value.entry.modelContext);
@@ -13694,7 +13789,7 @@ function historyFields(value) {
13694
13789
  return value.history === void 0 ? {} : { history: normalizeHistory(value.history) };
13695
13790
  }
13696
13791
  function exactFields(value, fields) {
13697
- if (!isRecord4(value)) return false;
13792
+ if (!isRecord5(value)) return false;
13698
13793
  const keys2 = Reflect.ownKeys(value);
13699
13794
  return keys2.length === fields.size && keys2.every((key) => typeof key === "string" && fields.has(key) && Object.prototype.propertyIsEnumerable.call(value, key));
13700
13795
  }
@@ -15183,7 +15278,7 @@ function createIndicatorView(React32, deps) {
15183
15278
  }
15184
15279
  };
15185
15280
  }, [cancelClose, cancelOpen, schedulePlacement]);
15186
- const memory = useObservedReplMemory({
15281
+ const { memory, provenance } = useObservedReplMemory({
15187
15282
  sessionId,
15188
15283
  projected: projectionMemory,
15189
15284
  active: expanded && sessionUsesPtcPreset2(preset) && featureEnabled2(settings, "plugin"),
@@ -15223,6 +15318,7 @@ function createIndicatorView(React32, deps) {
15223
15318
  }, h("span", { className: "ptcPlusActiveLabel" }, "PTC Plus")),
15224
15319
  h(ReplMemoryCard, {
15225
15320
  memory,
15321
+ provenance,
15226
15322
  globalEnabled,
15227
15323
  globalBindings,
15228
15324
  loadGlobalBinding: (id2) => callUserBindings("load", { id: id2 }),
@@ -41092,6 +41188,7 @@ var MAX_BINDINGS = 128;
41092
41188
  var MAX_BINDING_NAME_LENGTH = 128;
41093
41189
  var MAX_DEFINITION_SOURCE_LENGTH = 1024;
41094
41190
  var MAX_DEFINITION_SOURCE_TOTAL_LENGTH = 16 * 1024;
41191
+ var MAX_GENERATION_LENGTH = 128;
41095
41192
  var SNAPSHOT_FIELDS = /* @__PURE__ */ new Set(["available", "entries", "total", "omitted"]);
41096
41193
  var COUNTED_SNAPSHOT_FIELDS = /* @__PURE__ */ new Set([...SNAPSHOT_FIELDS, "reuseTotal"]);
41097
41194
  var OBSERVED_SNAPSHOT_FIELDS = /* @__PURE__ */ new Set([...SNAPSHOT_FIELDS, "observation"]);
@@ -41101,11 +41198,12 @@ var PREVIEW_FIELDS = /* @__PURE__ */ new Set(["name", "status", "text", "truncat
41101
41198
  var ENTRY_FIELDS = /* @__PURE__ */ new Set(["name", "kind", "definition"]);
41102
41199
  var COUNTED_ENTRY_FIELDS = /* @__PURE__ */ new Set([...ENTRY_FIELDS, "reuseCount"]);
41103
41200
  var DEFINITION_FIELDS = /* @__PURE__ */ new Set(["source", "line", "column"]);
41201
+ var VIEW_FIELDS2 = /* @__PURE__ */ new Set(["runtime", "memory"]);
41104
41202
  function isReuseCount(value) {
41105
41203
  return Number.isSafeInteger(value) && value >= 0;
41106
41204
  }
41107
41205
  function exactFields2(value, fields) {
41108
- if (!isRecord4(value)) return false;
41206
+ if (!isRecord5(value)) return false;
41109
41207
  const keys2 = Reflect.ownKeys(value);
41110
41208
  return keys2.length === fields.size && keys2.every((key) => typeof key === "string" && fields.has(key) && Object.prototype.propertyIsEnumerable.call(value, key));
41111
41209
  }
@@ -41119,6 +41217,12 @@ var EMPTY_REPL_MEMORY = Object.freeze({
41119
41217
  function unavailableReplMemorySnapshot() {
41120
41218
  return EMPTY_REPL_MEMORY;
41121
41219
  }
41220
+ function normalizeGeneration(value) {
41221
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_GENERATION_LENGTH) {
41222
+ throw new Error("invalid dsh-ptc-plus REPL memory generation");
41223
+ }
41224
+ return value;
41225
+ }
41122
41226
  function normalizeDefinition(value) {
41123
41227
  if (!exactFields2(value, DEFINITION_FIELDS) || typeof value.source !== "string" || value.source.length === 0 || value.source.length > MAX_DEFINITION_SOURCE_LENGTH || !Number.isSafeInteger(value.line) || value.line < 1 || !Number.isSafeInteger(value.column) || value.column < 1) {
41124
41228
  throw new Error("invalid dsh-ptc-plus REPL binding definition");
@@ -41193,6 +41297,13 @@ function normalizeReplMemorySnapshot(value) {
41193
41297
  ...observation === void 0 ? {} : { observation }
41194
41298
  });
41195
41299
  }
41300
+ function normalizeReplMemoryView(value) {
41301
+ if (!exactFields2(value, VIEW_FIELDS2)) throw new Error("invalid dsh-ptc-plus REPL memory view");
41302
+ return Object.freeze({
41303
+ runtime: normalizeGeneration(value.runtime),
41304
+ memory: normalizeReplMemorySnapshot(value.memory)
41305
+ });
41306
+ }
41196
41307
 
41197
41308
  // src/client-copy.js
41198
41309
  var LOCALE_NS = "settings.ptcPlus";
@@ -41436,6 +41547,17 @@ var CHROME_COPY = Object.freeze({
41436
41547
  "capabilities.receipt.off": "\u80FD\u529B\u7BA1\u7406\u5DF2\u5173\u95ED\uFF1B\u4F1A\u8BDD {session} \u6CBF\u7528\u5BBF\u4E3B\u80FD\u529B\u3002",
41437
41548
  "capabilities.receipt.unavailable": "\u8BF7\u9009\u62E9\u4ECD\u6D3B\u8DC3\u7684 PTC \u4F1A\u8BDD\u3002",
41438
41549
  "capabilities.receipt.usage": "\u4F7F\u7528 /capabilities \u67E5\u770B\uFF0C\u6216 /capabilities refresh \u540C\u6B65\u5DF2\u4FDD\u5B58\u8BBE\u7F6E\u3002",
41550
+ "capabilities.proof.title": "\u4E09\u5C42\u4E8B\u5B9E",
41551
+ "capabilities.proof.line": "\u5DF2\u4FDD\u5B58\uFF1A{saved}\uFF1B\u5DF2\u6295\u9012\uFF1A{delivery}\uFF1B\u5DF2\u53EF\u7528\uFF1A{usability}\u3002",
41552
+ "capabilities.proof.saved.yes": "\u662F\uFF0C\u5168\u5C40\u8BBE\u7F6E\u8BB0\u5F55\u4E86\u8FD9\u9879\u9009\u62E9",
41553
+ "capabilities.proof.saved.no": "\u5426\uFF0C\u8FD9\u6761\u547D\u4EE4\u6CA1\u6709\u6539\u52A8\u5168\u5C40\u8BBE\u7F6E",
41554
+ "capabilities.proof.delivery.delivered": "\u662F\uFF0C\u6A21\u578B\u53EF\u89C1\u76EE\u5F55\u5DF2\u5728\u53D7\u7406\u7684\u6B65\u9AA4\u53D1\u5E03\u8BE5\u9879",
41555
+ "capabilities.proof.delivery.pending": "\u5C1A\u672A\uFF0C\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u8981\u5230\u4E0B\u4E00\u4E2A\u53D7\u7406\u6B65\u9AA4\u624D\u8FDB\u5165\u76EE\u5F55",
41556
+ "capabilities.proof.delivery.absent": "\u5426\uFF0C\u76EE\u5F55\u6CA1\u6709\u5217\u51FA\u8BE5\u9879\uFF0C\u6A21\u578B\u6CA1\u6709\u6536\u5230\u5B83",
41557
+ "capabilities.proof.usability.proven": "\u5DF2\u8BC1\u660E\uFF0C\u672C\u4F1A\u8BDD\u6709\u5B8C\u6210\u7684\u6267\u884C\u53D6\u5230\u4E86\u5B83",
41558
+ "capabilities.proof.usability.unproven": "\u672A\u8BC1\u660E\uFF0C\u672C\u4F1A\u8BDD\u6CA1\u6709\u5B8C\u6210\u7684\u6267\u884C\u53D6\u5230\u8FC7\u5B83\uFF1B\u4FDD\u5B58\u6216\u53D1\u5E03\u90FD\u4E0D\u6784\u6210\u8BC1\u660E",
41559
+ "capabilities.proof.observed": "\u6765\u81EA {command} \u8FD4\u56DE\u7684\u6536\u636E\u3002",
41560
+ "capabilities.proof.unobserved": "\u672C\u9762\u677F\u8FD8\u6CA1\u6709\u8FD9\u9879\u80FD\u529B\u7684\u6295\u9012\u4E0E\u53EF\u7528\u6027\u8BC1\u636E\uFF1B\u5E94\u7528\u4E00\u6B21\u6539\u52A8\uFF0C\u6216\u56DE\u5230\u4F1A\u8BDD\u67E5\u770B\u547D\u4EE4\u6536\u636E\u3002",
41439
41561
  "bindings.toggleEnabled": "\u5DF2\u4FDD\u5B58\u201C{name}\u201D\u7684\u542F\u7528\u9009\u62E9\uFF1B\u6A21\u578B\u76EE\u5F55\u5728\u4E0B\u4E00\u6B21\u83B7\u51C6\u6B65\u9AA4\u66F4\u65B0\uFF0C\u7A0B\u5E8F\u503C\u5C1A\u672A\u8BC1\u660E\u521D\u59CB\u5316\u3002",
41440
41562
  "bindings.toggleDisabled": "\u5DF2\u4FDD\u5B58\u201C{name}\u201D\u7684\u505C\u7528\u9009\u62E9\uFF1B\u6A21\u578B\u76EE\u5F55\u5728\u4E0B\u4E00\u6B21\u83B7\u51C6\u6B65\u9AA4\u64A4\u9500\uFF0C\u4E0D\u64A4\u9500\u5DF2\u7ECF\u53D1\u751F\u7684\u6548\u679C\u3002",
41441
41563
  "capabilities.observationGroup": "\u53EA\u8BFB\u4F1A\u8BDD\u89C2\u5BDF",
@@ -41875,6 +41997,7 @@ var CHROME_COPY = Object.freeze({
41875
41997
  "memory.globalCount": "{count} \u4E2A\u5168\u5C40\u6761\u76EE",
41876
41998
  "memory.empty": "\u5F53\u524D\u6CA1\u6709\u53EF\u590D\u7528\u7ED1\u5B9A",
41877
41999
  "memory.unavailable": "\u5F53\u524D\u7ED1\u5B9A\u72B6\u6001\u5C1A\u4E0D\u53EF\u786E\u8BA4",
42000
+ "memory.previousRuntime": "\u8FD9\u4EFD\u6E05\u5355\u6765\u81EA\u4E0A\u4E00\u4E2A\u8FD0\u884C\u671F\uFF0C\u4E0D\u518D\u4EE3\u8868\u5F53\u524D\u73AF\u5883\uFF1B\u6267\u884C\u4E00\u683C\u5373\u53EF\u91CD\u65B0\u89C2\u5BDF",
41878
42001
  "memory.more": "\u53E6\u6709 {count} \u4E2A\u7ED1\u5B9A\u672A\u663E\u793A",
41879
42002
  "memory.reuseCount": "\u590D\u7528 {count} \u6B21",
41880
42003
  "memory.reuseTotal": "\u5171\u590D\u7528 {count} \u6B21",
@@ -42149,6 +42272,17 @@ var CHROME_COPY = Object.freeze({
42149
42272
  "capabilities.receipt.off": "Capability management is off; session {session} stays with the Host capabilities.",
42150
42273
  "capabilities.receipt.unavailable": "Select an active PTC session.",
42151
42274
  "capabilities.receipt.usage": "Use /capabilities to inspect or /capabilities refresh to apply saved settings.",
42275
+ "capabilities.proof.title": "Three separate facts",
42276
+ "capabilities.proof.line": "Saved: {saved}; delivered: {delivery}; usable: {usability}.",
42277
+ "capabilities.proof.saved.yes": "yes, the global configuration records this choice",
42278
+ "capabilities.proof.saved.no": "no, this command did not change the global configuration",
42279
+ "capabilities.proof.delivery.delivered": "yes, the model-visible catalog published it at an accepted step",
42280
+ "capabilities.proof.delivery.pending": "not yet, the saved configuration reaches the catalog at the next accepted step",
42281
+ "capabilities.proof.delivery.absent": "no, the catalog does not list it, so the model never received it",
42282
+ "capabilities.proof.usability.proven": "proven, a completed execution in this session reached it",
42283
+ "capabilities.proof.usability.unproven": "unproven, no completed execution in this session reached it; a save or a published entry does not prove it",
42284
+ "capabilities.proof.observed": "From the receipt {command} returned.",
42285
+ "capabilities.proof.unobserved": "This panel holds no delivery or usability evidence for this capability yet; apply a change, or read the command receipt in the conversation.",
42152
42286
  "bindings.toggleEnabled": "Enabled \u201C{name}\u201D in saved configuration. The model catalog updates at the next accepted step; worker initialization is not yet established.",
42153
42287
  "bindings.toggleDisabled": "Disabled \u201C{name}\u201D in saved configuration. The model catalog withdraws it at the next accepted step; earlier effects are unchanged.",
42154
42288
  "capabilities.observationGroup": "Read-only session observation",
@@ -42585,6 +42719,7 @@ var CHROME_COPY = Object.freeze({
42585
42719
  "memory.globalCount": "{count} global entries",
42586
42720
  "memory.empty": "No reusable bindings",
42587
42721
  "memory.unavailable": "Current binding state cannot be confirmed",
42722
+ "memory.previousRuntime": "This inventory came from a previous runtime and no longer describes the current environment; run a cell to observe it again",
42588
42723
  "memory.more": "{count} more bindings not shown",
42589
42724
  // Number-neutral so a single reuse stays grammatical without a plural rule.
42590
42725
  "memory.reuseCount": "reused {count}\xD7",
@@ -43844,6 +43979,7 @@ function createClientPlugin(require2) {
43844
43979
  Select: Select2,
43845
43980
  featureEnabled,
43846
43981
  normalizeReplMemorySnapshot,
43982
+ normalizeReplMemoryView,
43847
43983
  unavailableReplMemorySnapshot,
43848
43984
  useSessionPreset,
43849
43985
  sessionUsesPtcPreset,
@@ -10,9 +10,11 @@ PTC Plus must remain independently installable on unmodified official DSH distri
10
10
 
11
11
  The compatibility target is the latest available DSH release. Production behavior, configuration, tests, installation guidance, and current compatibility claims depend on public extension contracts and observed live schemas, never on a DSH version allowlist or comparison. An upstream default projection is not an extension ceiling: when public scoped registration and presentation support a plugin-owned capability, PTC Plus preserves that capability and its model-visible identity instead of deleting or renaming it to match the default. Deterministic normalization remains valid for a call that is outside the declared direct surface when the live schema proves one native target and the lowering preserves arguments, call identity, authority, and result semantics. Deterministic tests exercise the consumed contracts; model-backed and packaging acceptance runners use the installed DSH and record `dsh --version` in generated reports so failures remain attributable without turning the observed value into policy. Concrete DSH release numbers may appear only in source comments that identify historical integration or evaluation evidence and cannot influence execution or acceptance.
12
12
 
13
- Release discovery is separated from candidate verification. The root manifest declares the official `next` channel for the DSH packages used by development and freezes the resolved cohort in the root lockfile. `compat/latest` declares only the official DSH meta-package on `latest` and freezes that complete graph in its own lockfile. Resolved versions in these generated lock snapshots are reproducibility evidence selected by the channel declarations; they are not compatibility allowlists or runtime branches. `compat/current` separately freezes the cohort of the newest published official GitHub release, including prereleases. Its generated manifest selects the matching official npm channel, and `release.json` records the discovery provenance. This published-release identity, rather than a fixed list of channel names, owns the current target. Ordinary `verify` and the full branch of `check` make no registry request and fail quickly when the installed root graph differs from the root lock; they also validate the published cohort against its frozen provenance.
13
+ Release discovery is separated from candidate verification. Support is expressed as channels, not versions. `compat/channels.json` is generated from the official dist-tags and records, per channel, the resolved host release, the tarball integrity of every host package whose published surface the plugin probes, and whether that channel may block a release: `latest` (newest published release) and `next` (in-flight release candidate) block, while `alpha` (a pre-adaptation for a line the host has not released yet) only reports. A probe package the release does not publish is recorded with the version and integrity that release's own dependency range resolves to, so the channel still names the bytes an install receives; a probe package the release neither publishes nor depends on is recorded as absent, and the install proof rejects a graph that contains one. These records are reproducibility evidence selected by the channel declarations; they are not compatibility allowlists or runtime branches. `npm run host:channels:update` is the only way to refresh them and starts from the registry; ordinary `verify` and the full branch of `check` make no registry request and validate the tracked manifest offline. The root manifest declares the official `next` channel for the DSH packages used by development, and `host:channels:graph` moves the lock onto the release that channel resolves to; the scheduled channel workflow runs it in the same commit as a channel change. Ordinary gates read whichever graph the lock froze and never resolve a tag.
14
14
 
15
- The scheduled baseline refresh queries the official registry. It also queries official GitHub releases and matches the newest non-draft DSH release to the official meta-package dist-tags. A missing distribution fails discovery instead of silently using an older Host. When either channel or the published-release cohort moves, it updates the affected lock snapshots on an automation-owned branch, opens or updates one pull request, and dispatches the normal CI workflow once for that frozen candidate. GitHub suppresses workflow runs from branch and pull-request events produced by the workflow's `GITHUB_TOKEN`, so the explicit dispatch is the candidate's only CI run. If neither tag nor the published-release cohort moved, it does not resolve or install the dependency graph. The Host contract suite tests the packed plugin against the root `next` graph and creates a repository-external consumer from the tracked `latest` lock. That consumer exposes the public packages from the selected Host graph to the plugin and runs the execution-seam, RPC-codec, and compressed Session persistence contracts. The suite also creates a repository-external consumer from `compat/current` and tests the packed plugin on that unmodified published graph. `npm run test:host-contract -- current` selects this object alone. No compatibility run independently selects tags for DSH service packages, because their dist-tags need not move as one cohort.
15
+ The scheduled channel refresh resolves every channel from its dist-tag. When a channel moves it commits the regenerated manifest on an automation-owned branch, opens or updates one pull request, and dispatches the normal CI workflow once for that candidate. GitHub suppresses workflow runs from branch and pull-request events produced by the workflow's `GITHUB_TOKEN`, so the explicit dispatch is the candidate's only CI run. If no channel moved, the refresh writes the identical document and nothing is committed. The Host contract suite installs each channel on demand from the manifest into an ignored cache, proves the installed versions against the manifest, packs the plugin, and runs the execution-seam, RPC-codec, compressed Session persistence, and pathless Cordis companion contracts through a repository-external consumer that exposes the selected Host's public packages. `npm run test:host-contract -- <channel>` selects one channel, and the default selection is the blocking set. CI runs one job per channel and lets the `alpha` job report without failing the workflow. No compatibility run independently selects tags for DSH service packages, because their dist-tags need not move as one cohort.
16
+
17
+ The development-lock move is a mechanical convenience with one known limit. `host:channels:graph` re-pins the DSH declarations in `package.json` and then asks npm to resolve the graph, which works when the cohort already moved together in the lock. A development graph that still carries a large previous-release cohort (the lock holds far more `@deepseek-ai/dsh-*` packages than the manifest declares) makes npm reject the half-moved peer graph with `ERESOLVE`; the command then throws, and both `package.json` and `package-lock.json` are restored to the bytes the command found, so no partly moved graph is left behind. Because that command runs before the refresh workflow commits, a channel move waits for a maintainer to install the whole cohort and re-run the check, and `host:channels:verify` keeps reporting the drift until then. This limit touches release tooling only: the plugin's behaviour, its install proof and the compatibility claim a consumer reads are unaffected, and every gate that ships the package reads the tracked manifest rather than the development lock.
16
18
 
17
19
  Preceding public contracts remain supported where capability detection is
18
20
  unambiguous. Tools presentation is selected by the installed public schema;
@@ -18,7 +18,7 @@ The settings card belongs to plugin configuration. Current DSH renders a bundle'
18
18
 
19
19
  The settings form groups controls into core workspace (REPL syntax, state and recovery), extensions and tolerance (tool extensions, tool call tolerance), interface and resources (interface display, resource limits), and experimental (capabilities and context). Page and dialog navigation integrate the single main switch into a borderless PTC Plus title row without a duplicate enabled badge; static module captions differ from section links with icons, arrows and selected markers. The legacy disclosure card retains its first switch section. Experimental controls remain last and carry an explicit marker. The wide settings dialog presents the groups as a two-column workbench with independently scrolling navigation and content; settings wheel scrolling and section selection leave the rail and shared modal frame in place. Narrow viewports use horizontal navigation above independently scrolling content. Recovery tips and their thresholds stay together, and global binding management appears directly below its enable switch. `CONFIG_MODULES` and `CONFIG_GROUPS` own this presentation grouping and order independently of Host schema field order and defaults. The session capability-plan section inside capabilities and context shows effective selections and pending configuration changes only while capability management is enabled; disabling it cancels plan inspection and discards stale replies while manual configuration and catalog reads remain available. Capabilities and context uses four tabs for manual preferences, statistics, budgets and session plans. MCP lists group tools/resources under a server master switch; closing the server preserves child strategies and takes precedence when the frozen plan is derived. Typed catalog rows keep common preferences, manual strategies and selected frozen-session results separate, with source/strategy/common/availability filters and stable within-group sorting. Display names and source metadata belong only to the catalog RPC view: Skill provider/source and public MCP server directory and captured MCP owner sections supply evidence, while ordinary tools are attributed to the Host registry without guessing their package origin. Catalog errors preserve last-observed entries as unconfirmed, and unavailable saved preferences remain editable/removable. The core execution surface is read-only. Capability controls serialize structured selections and retain invalid numeric drafts without committing them; saving capability settings affects new sessions, while existing sessions retain their frozen plan until `/capabilities refresh` as defined by [ADR 0038](0038-freeze-session-capability-plans.md). The sparkle menu refreshes its current session through the authenticated Host command; settings require an explicit target from a read-only active-PTC identity view. This identity view does not prepare or inspect plans and remains available with capability management off so users can synchronize the disabled configuration. Reloading catalog/plans is read-only. Global binding disablement hides binding actions while settings and capability refresh remain accessible; the plugin switch and configured sparkle visibility still gate the menu. Refresh receipts preserve Host outcomes, and cancellation stops waiting without claiming rollback. `autoDescribeRunCode` defaults to true and controls only a request-bound local execution policy. When enabled, a call that omits the outer `description` uses derived arguments for local DSH validation, and the fallback summary enters presentation metadata only; when disabled, DSH validates the original arguments. Both states produce byte-stable model-visible tool schemas, order, and system sections with the required `description` declaration. Original call arguments, explicit summaries, cell source, and nested native-tool arguments remain unchanged.
20
20
 
21
- The configurable set is the Host runtime configuration plus `enabled: boolean` (default true). `enabled` is the kill switch: when false the Host keeps the settings namespace, client card, and passive message cleanup defined in [ADR 0010](0010-session-log-derived-recovery-tips.md). The next permitted accepted step may clear earlier PTC declarations. It does not install the runtime bridge, edit transport, direct-surface owner, system prompt section, tool registrations, or execution hooks, and does not read binding storage. When true the same runtime as pre-settings PTC Plus is installed. On the generations that expose a section installer, every field is live when its owner can reconcile it: the Host observes the settings scope and installs, uninstalls, or reconfigures owners without replacing existing session-bound bindings. The volatile-only settings shape exposed by both frozen Host graphs has no section installer and serves only fields marked volatile in the plugin's exported `Config`; the Host provides the form and commits volatile-only changes into the live references, notifying the owning fiber through the public `loader/volatile-update` event. The plugin subscribes and reconciles through its existing update path instead of running a per-field settings watch. Node fixes a worker's V8 `maxOldGenerationSizeMb` limit at construction, so changing that field while a session worker is active or a submitted cell is preparing to execute is rejected and the settings provider is restored to the previous value; other reconfiguration failures use the same compensation path. The TypeScript language check occurs at activation, allowing disabled settings to load on a host using another code runtime while rejecting and rolling back an enable attempt. If the settings service is absent, the Host falls back to composition config and enables the runtime.
21
+ The configurable set is the Host runtime configuration plus `enabled: boolean` (default true). `enabled` is the kill switch: when false the Host keeps the settings namespace, client card, and passive message cleanup defined in [ADR 0010](0010-session-log-derived-recovery-tips.md). The next permitted accepted step may clear earlier PTC declarations. It does not install the runtime bridge, edit transport, direct-surface owner, system prompt section, tool registrations, or execution hooks, and does not read binding storage. When true the same runtime as pre-settings PTC Plus is installed. On the generations that expose a section installer, every field is live when its owner can reconcile it: the Host observes the settings scope and installs, uninstalls, or reconfigures owners without replacing existing session-bound bindings. The volatile-only settings shape exposed by both installed channel graphs has no section installer and serves only fields marked volatile in the plugin's exported `Config`; the Host provides the form and commits volatile-only changes into the live references, notifying the owning fiber through the public `loader/volatile-update` event. The plugin subscribes and reconciles through its existing update path instead of running a per-field settings watch. Node fixes a worker's V8 `maxOldGenerationSizeMb` limit at construction, so changing that field while a session worker is active or a submitted cell is preparing to execute is rejected and the settings provider is restored to the previous value; other reconfiguration failures use the same compensation path. The TypeScript language check occurs at activation, allowing disabled settings to load on a host using another code runtime while rejecting and rolling back an enable attempt. If the settings service is absent, the Host falls back to composition config and enables the runtime.
22
22
 
23
23
  The host-only log-fact projection remains owned by the plugin/controller lifecycle when the runtime is disabled, as specified in [ADR 0032](0032-own-session-log-facts-in-a-host-only-projection.md). It supports passive message cleanup without retaining a worker, tool or default model-visible state projection.
24
24
 
@@ -43,7 +43,7 @@ namespace. Enable-failure and live-reconfiguration compensation use that same
43
43
  bound writer, not a fixed namespace substituted for the owning entry. The
44
44
  writer and page policy are released with the settings provider.
45
45
 
46
- Compatibility evidence and the consequences of removing historical branches are bounded by [Installation: Compatibility Evidence](../installation.md#compatibility-evidence). The two frozen graphs do not establish full Host coverage of the section-installer or settingsScope shapes.
46
+ Compatibility evidence and the consequences of removing historical branches are bounded by [Installation: Compatibility Evidence](../installation.md#compatibility-evidence). The two installed channel graphs do not establish full Host coverage of the section-installer or settingsScope shapes.
47
47
 
48
48
  ## Alternatives Considered
49
49
 
@@ -22,6 +22,18 @@ Journal version 6 introduced the required `userBindingsReusePolicy`: new cells w
22
22
 
23
23
  Namespace call names and selected export names must parse as exactly one Identifier whose AST name equals the complete supplied string. Whitespace, comments, binding patterns, extra declarations and escaped spellings cannot change identifier identity between validation, declarations, conflict checks, activation and recovery. Supported Unicode identifiers keep their exact spelling. Top-level entry display names remain separate from callable export names.
24
24
 
25
+ ## Supply, Adoption, Materialization and Proof
26
+
27
+ Global bindings answer four different questions, and each layer may report only its own answer. **Supply** is which entries the global configuration offers; `bindings.json` and each entry's `enabled` switch own it, and an entry disabled there is simply absent from the catalog. **Adoption** is which of those entries one session uses; the session owns it. **Materialization** is which adopted entries the worker actually installed; the activated set and its per-entry failures own it. **Proof** is what the model can check; the tool result, its journal and the diagnostics it names own that.
28
+
29
+ Adoption is a two-state decision per entry plus a default: a session uses an entry unless it declined it. "Adopted explicitly" and "never mentioned" therefore produce the same environment, so they are one state rather than two, and no sentinel value is needed. The capability pane's `automatic` is not borrowed here: that word means "the host derives this value", while a binding has no host-derived value to defer to.
30
+
31
+ Adoption can only withdraw a supplied entry. It cannot add, enable or revive one, because an entry the global configuration disabled does not exist in the catalog for a session to name; `/binding skip <id>` rejects an entry the catalog does not supply instead of recording a decision that would claim authority over supply. No rule forbids "an override revives a deleted entry", because the record has no encoding that could express it.
32
+
33
+ `/binding status|skip <id>|use <id>|reset` states the decision as one `plugin:ptc-plus` `binding-adoption` message, and the session-log projection reads the newest record back. A restart therefore reconstructs the same environment from the log; no second store or process-local map exists to migrate. A session that never states a decision receives exactly the supplied catalog, so the previous release's environment is unchanged.
34
+
35
+ Materialization remains a separate report. A declined entry is never offered to the worker, while an entry the worker could not install is named by its execution diagnostic (`PTC-R002`, and `PTC-R006` for a name that was live and then disappeared). An adoption receipt claims neither delivery nor initialization.
36
+
25
37
  ## Storage and Source Ownership
26
38
 
27
39
  The Host owns one document at `$DSH_HOME/ptc-plus/bindings.json`. The store serializes mutations, holds a file lock, compares the last-read disk bytes, requires an expected revision, and atomically replaces the file with owner-only permissions. The complete enabled declaration budget is validated before replacement; an externally written document that exceeds the same budget is treated as damaged storage. A malformed document remains an explicit error, and stale or external changes require reload instead of blind overwrite. A new Agent draft uses create-only persistence so a concurrent entry with the same ID cannot be replaced.