dsh-ptc-plus 0.2.4 → 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.
Files changed (49) hide show
  1. package/README.en.md +177 -0
  2. package/README.md +85 -69
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-settings-en.png +0 -0
  7. package/assets/ptc-plus-settings-zh.png +0 -0
  8. package/client.js +1168 -144
  9. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +1 -1
  10. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  11. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +7 -3
  12. package/docs/adr/0014-persist-cell-rewrite-policy.md +7 -5
  13. package/docs/adr/0019-plugin-settings-and-kill-switch.md +19 -4
  14. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  15. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +5 -3
  16. package/docs/adr/0022-stage-npm-releases-with-oidc.md +2 -2
  17. package/docs/architecture.md +23 -14
  18. package/docs/client-ui.md +29 -11
  19. package/docs/durability-design.md +42 -20
  20. package/docs/installation.md +11 -3
  21. package/docs/program-data-plane.md +6 -0
  22. package/docs/runtime-reference.md +27 -14
  23. package/docs/value-wire.md +1 -1
  24. package/index.js +38 -4
  25. package/internal/cell-analysis.js +89 -12
  26. package/internal/cell-rewriter.js +162 -4
  27. package/internal/config-spec.js +192 -117
  28. package/internal/direct-surface-owner.js +56 -5
  29. package/internal/edit-transport-owner.js +11 -2
  30. package/internal/failure-reporting.js +15 -0
  31. package/internal/kernel-worker.js +11 -0
  32. package/internal/rejected-cell-editor.js +21 -12
  33. package/internal/repl-convenience.js +94 -6
  34. package/internal/repl-memory-projection.js +277 -0
  35. package/internal/repl-rewrite-contract.js +15 -0
  36. package/internal/run-code-description.js +34 -0
  37. package/internal/runtime-bridge-owner.js +63 -15
  38. package/internal/runtime-config.js +3 -0
  39. package/internal/session-cell-executor.js +74 -14
  40. package/internal/session-events.js +7 -0
  41. package/internal/session-journal.js +331 -63
  42. package/internal/session-log-view.js +2 -1
  43. package/internal/session-runtime.js +110 -9
  44. package/internal/session-state.js +116 -4
  45. package/package.json +10 -7
  46. package/src/client-activity.js +501 -0
  47. package/src/client.js +456 -20
  48. package/README.zh.md +0 -161
  49. /package/assets/{ptc-plus-repair.png → ptc-plus-repair-zh.png} +0 -0
@@ -2,19 +2,22 @@
2
2
 
3
3
  ## 状态
4
4
 
5
- 本文描述 `dsh-ptc-plus` 的 journal、两阶段确认和冷恢复行为。代码事实源是 `internal/session-journal.js` 与 `internal/session-runtime.js`。
5
+ 本文拥有 `dsh-ptc-plus` 的 journal、两阶段确认和冷恢复契约;`internal/session-journal.js` 与 `internal/session-runtime.js` 提供实现证据。
6
6
 
7
7
  ## 恢复承诺
8
8
 
9
9
  PTC Plus 的正确承诺是:
10
10
 
11
- > **精确恢复 durable 部分,完整运行 volatile 部分,不自动重放 volatile 副作用。**
11
+ > **只把可证明状态称为已恢复;历史最差可收缩到空 REPL,但当前合法 cell 仍可继续。**
12
12
 
13
13
  - durable cell 的源码、tool transcript、结算顺序和 completion 足以精确重放;
14
14
  - volatile cell 可使用 policy 允许但无法 journal 的 Node 能力,只保证当前 worker 生命周期内延续;
15
15
  - 一旦进入 volatile,整个 live 后缀保持 volatile;
16
16
  - restore 命名的 durable 状态可以显式丢弃 volatile 后缀;
17
- - abort、timeout、worker exit、OOM 和进程恢复都回到最后一个 durable frontier;
17
+ - abort、timeout、worker exit、OOM、进程恢复和损坏的历史 PTC metadata 都收缩到最大可证明 frontier;没有非空 frontier 时从空 REPL 执行当前 cell;
18
+ - 历史恢复拒绝不可信状态证据,不因这项插件自有历史损坏而永久拒绝后续 `run_code`;当前请求自身的 DSH validation、policy、authority、approval 和 cancellation 仍可拒绝执行;
19
+ - 收缩不重新 dispatch、撤销或验证历史 effect;丢失的 binding 由一次 `PTC-R002` 明确提示模型按需重建;
20
+ - 可重建不是默认暴露 binding 的充分条件:恢复 frontier 还必须让 binding 的精确 provenance 保留在生成当前调用的模型可见 surface 中,或由显式选择的有界、模型可见结构化投影公开;不能只为延长隐藏状态而默认注入该投影,raw log 与 UI-only inventory 也不满足这项条件;
18
21
  - `durableReplay: false` 时,新 kernel 从空状态开始且所有已执行 live cell 都是 volatile,作为
19
22
  用户怀疑恢复正确性时的显式逃生模式;
20
23
  - 被跳过的源码仍保存在原始 `tool/call` 中。
@@ -27,13 +30,19 @@ PTC Plus 的正确承诺是:
27
30
 
28
31
  ```ts
29
32
  {
30
- version: 3,
31
- bindingMode: "loose" | "strict",
33
+ version: 4,
34
+ bindingPolicy: {
35
+ variableRedeclarations: boolean,
36
+ functionClassRedeclarations: boolean
37
+ },
32
38
  rewritePolicy: {
33
39
  autoRewriteImports: boolean,
34
40
  autoStripExports: boolean,
35
41
  autoSplitRedeclarations: boolean
36
42
  },
43
+ moduleSemantics: {
44
+ defaultExportBinding: "legacy-variable" | "live-readonly"
45
+ },
37
46
  status: "durable" | "volatile" | "discarded" | "noop",
38
47
  calls: CapabilityCall[],
39
48
  operations: StateOperation[],
@@ -50,8 +59,9 @@ PTC Plus 的正确承诺是:
50
59
  字段含义:
51
60
 
52
61
  - `durable`:创建可重放 node,推进 `durableHead`;
53
- - `bindingMode`:记录该 cell 实际采用的顶层 binding 语义;冷重放读取每个 node 的记录值,不读取恢复时的 profile 配置;
62
+ - `bindingPolicy`:记录该 cell 实际采用的变量重声明和 function/class 重声明语义;冷重放读取每个 node 的完整记录值,并用它重建 binding 可写性,不读取恢复时的 profile 配置;
54
63
  - `rewritePolicy`:记录该 cell 解析和 lowering 时使用的三个 AST rewrite 开关;冷重放读取每个 node 的记录值,不读取恢复时的 profile 配置;
64
+ - `moduleSemantics`:记录不可由 profile 选择的源码 lowering 代际;当前 cell 固定写入 `live-readonly`,冷重放按记录值恢复 `export default` 的公开 binding 模型;
55
65
  - `volatile`:只推进 live heap,不推进 `durableHead`;
56
66
  - `discarded`:基础设施失败,calls 和 operations 必须为空;若中止时仍有未结算的 program binding call,则以首个 `global.member` 保留 `volatileReason`,恢复时不能把这个 possible-effect boundary 折叠为 no-op;
57
67
  - `noop`:程序未执行,calls 和 operations 必须为空;
@@ -69,7 +79,7 @@ SessionRuntime 创建 kernel 时完全忽略历史 nodes、head、checkpoints
69
79
 
70
80
  journal、diagnostic、source、cause、call、operation、completion 和 completion error 都使用封闭字段集合;未知、symbol 或非枚举自有字段会使 journal 无效。capability-call `args`/`value` 与 return completion `value` 都是封闭、规范化的 `ptc-value-graph/v1` envelope。诊断结构、source frame 依赖和稳定代码见[架构说明](architecture.md#journal-与恢复)。
71
81
 
72
- 当前实现写入 `version: 3` schema。`version: 2` 且 `confirms` 为空的 journal 可无歧义规范化为 v3;v2 中非空的 call-id confirmation 无法可靠映射到可能重复的 session event,必须形成 unknown suffix,不能猜测迁移。包括 `bindingMode`、`rewritePolicy`、`diagnostics` 在内的必需字段缺失时 journal 必须失效,否则会削弱最终持久值与 tentative journal 的严格一致性确认。profile 后续切换宽松/严格模式或 AST rewrite 开关只影响新 cell,历史 node 始终按自身记录的模式重放。
82
+ 当前实现只写入 `version: 4` schema。v1-v3 作为封闭 predecessor 输入规范化为 v4:旧 `bindingMode` 精确映射到 `bindingPolicy.variableRedeclarations`,而 `functionClassRedeclarations` 固定为 `false`,因为旧 pipeline 不具备该语义;`moduleSemantics.defaultExportBinding` 固定迁移为 `legacy-variable`,使 loose 历史保留旧的可写生成 binding、strict 历史保留旧的 const 行为。v1 仍使用三个 rewrite 开关全关的历史默认值,并只在 session log 唯一证明 call identity 时迁移字符串 confirmation;v2/v3 保留自身 `rewritePolicy`,无法表示为非负 event sequence 的 confirmation 必须形成 unknown boundary 并触发状态收缩。包括 `bindingPolicy`、`rewritePolicy`、`moduleSemantics`、`diagnostics` 在内的必需字段缺失时 journal 必须失效,否则会削弱最终持久值与 tentative journal 的严格一致性确认;失效 journal 不证明旧 binding,但也不单独成为后续当前调用的 availability gate。profile 后续切换任一 binding 或 AST rewrite 开关只影响新 cell,历史 node 始终按自身记录的 policy 和 module semantics 重放。
73
83
 
74
84
  ## Capability Call Transcript
75
85
 
@@ -117,9 +127,9 @@ pre-execute -> tools/execute -> post-execute
117
127
  | 观察 | Live 行为 | 冷恢复行为 |
118
128
  | --- | --- | --- |
119
129
  | valid journal | 按 status 提交 | 按 journal 折叠 |
120
- | 已执行,最终 journal 被删除 | cell 与后缀 volatile | unknown boundary |
130
+ | 已执行,最终 journal 被删除 | cell 与后缀 volatile | 收缩到此前可证明 frontier,继续当前 cell |
121
131
  | 未进入 runtime | pending no-op | 后续 `confirms` 存在时忽略 |
122
- | 无法判断且未被确认 | 保守处理 | 回到此前 durable frontier |
132
+ | 无法判断且未被确认 | 保守处理 | 拒绝其状态证据;必要时空 REPL,继续当前 cell |
123
133
 
124
134
  比较先严格规范化 journal,再使用 PTC value graph 的扁平 wire 表示,不对深层参数做递归遍历。额外无关 metadata 不影响确认,但 `dshPtcPlus` 自身任何可观察差异都拒绝确认。
125
135
 
@@ -133,20 +143,32 @@ pre-execute -> tools/execute -> post-execute
133
143
 
134
144
  ## 日志折叠
135
145
 
136
- 恢复按 session event 顺序处理外层 `run_code` 和带明确 derived-run metadata 的 `edit_run_code`。恢复入口先把当前 call id 解析为持久化 `tool/call.seq`,再把该序号作为 live boundary 传入折叠器;对应的在途 call event 不属于历史。每个 `edit_run_code` 在其 call event 处,根据此前已经结算的调用确定并固定可编辑目标;之后出现的 result 只影响随后发起的 edit:
146
+ 恢复以当前请求需要的 head 为目标,沿可证明 parent 关系向更早 frontier 收缩;实现可以前向折叠 event 建图,但不得把 unknown boundary 两侧的日志位置当作状态依赖证据。恢复入口先把当前 call id 解析为持久化 `tool/call.seq`,再把该序号作为 live boundary 传入折叠器;对应的在途 call event 不属于历史。每个 `edit_run_code` 在其 call event 处,根据此前已经结算的调用确定并固定可编辑目标;之后出现的 result 只影响随后发起的 edit:
137
147
 
138
148
  1. 用 `sourceEventSeqs[0]` 关联 `tool/result` 与 `tool/call`;
139
149
  2. 预收集 valid journal 中的 `confirms`;
140
150
  3. 排除与 live boundary 序号相同的在途 call,并让已确认 no-op 的无 journal call 不改变状态;
141
- 4. 缺失源码、缺失/损坏 journal 进入 untrusted suffix;
151
+ 4. 缺失源码、缺失/损坏 journal 或无法验证的 call/result/prune 关联建立 untrusted boundary,不作为旧 binding 的证据;
142
152
  5. `noop` 与不含 `volatileReason` 的 `discarded` 不改变语言状态;带 `volatileReason` 的 `discarded` 表示 heap 已回滚但外部 effect 未知,进入 untrusted suffix;
143
153
  6. `volatile` 进入 untrusted suffix,只应用可独立持久的 delete/restore 操作;
144
154
  7. `restore` 命名状态重新建立 trusted durable head;
145
- 8. untrusted suffix 后的首个 `durable` journal从当前 durable head 建立新分支,并清除旧 suffix;
155
+ 8. 只有 ancestry 或独立性有插件自有机械证据的 post-boundary durable 记录才可保留;否则整个受影响 cell 与依赖后缀收缩;
146
156
  9. durable node 保存 parent link,命名状态保存 node index;
147
- 10. `meta.dshPtcPlusRecoveryBoundaries` 在 `tool/result` event sequence 参与排序前完成规范化;任一损坏 boundary 使恢复失败,合法 boundary 再通过失败 call seq 选择 node,并要求记录的 frontier 恰好是该 node 的 parent;折叠器剪除该 node 及依赖后代,按剩余记录重算 checkpoints,再把 head 设为记录的 frontier。
157
+ 10. `meta.dshPtcPlusRecoveryBoundaries` 在 `tool/result` event sequence 参与排序前完成规范化;损坏 boundary 被拒绝为状态证据并建立保守收缩点,合法 boundary 通过失败 call seq 选择 node,并要求记录的 frontier 恰好是该 node 的 parent;
158
+ 11. 折叠器剪除不可证明的 node、binding 与依赖后代,按剩余记录重算 checkpoints;runtime 重置 worker 并验证候选 frontier,逐级收缩到最大可重放 frontier,必要时到空 REPL;
159
+ 12. 当前 cell 只在 worker 实际拥有该 frontier 后执行,成为它的新 child 或空 REPL 的新 root;当前结果持久记录本次收缩并投影一次恢复诊断。
160
+
161
+ 当前 journal 没有完整的逐 binding 读写与传递依赖图。源码存在、重放未抛错或 completion 相同,都不足以证明闭包捕获、属性 mutation、动态名称访问或运行时值与原执行一致。因此恢复不能任意挑选 unknown boundary 后看似独立的 binding;缺少机械证明时以 cell 为原子并丢弃受影响后缀。将来若要保留更细粒度状态,必须通过版本化、owner-owned 的依赖证据扩展契约,不能从名称或成功重放猜测。
162
+
163
+ 第 12 步是可用性与真实性共同的不变量:旧 unknown 调用不能被伪装成已恢复 heap,也不能在每次重启时永久吞掉所有后续 `run_code`。当前 cell 是在已经实际物化的 frontier 上重新执行,所以它可以安全建立持久新分支;这不表示被剪除 cell 的外部 effect 没有发生。
164
+
165
+ ## 模型可知状态
166
+
167
+ DSH session 同时提供两个不同投影:append-only event log 保存审计、journal 与 replay evidence;ordered surface/derived request history 是模型请求实际可见的消息历史。PTC Plus 的默认状态 frontier 是“结构可重建”和“模型可知”的交集,不能用 raw log 的存在替代 surface evidence。模型可知只采用外部可验证事实,不猜模型内部记忆:精确声明源码仍在当前 request surface,或插件/宿主通过有界结构把准确 binding identity 与 provenance 放入模型上下文。
168
+
169
+ surface replacement 是否删除状态取决于模型失去了什么,而不取决于是否出现 `compaction/prune` 这个名字。只替换过长 tool result、仍保留 assistant `run_code`/`edit_run_code` 调用及源码时,模型仍知道该 cell,可继续使用其 journal。若 replacement 或 summary 遮蔽了声明 provenance,raw journal 即使完整也不能让 binding 默认继续存在;自然语言 summary 不解析为依赖或 binding 证明。当前没有模型可见的完整 binding inventory,因此缺少精确 provenance 时按 cell 与依赖后缀收缩。
148
170
 
149
- 第 8 步是必要不变量:冷恢复已经实际丢弃 volatile/unknown heap,因此此后执行成功的 durable cell 不依赖该 heap。如果不把它作为可信重基点,旧 unknown 调用会在每次重启时永久吞掉所有后续状态。
171
+ 该规则也约束 live worker。runtime 在下一 cell 前观察 DSH public surface generation;发生 replacement 后,若现存 binding 超出模型可知 frontier,先重置 worker、重建交集 frontier,再执行当前 cell。Client header card 的 `dshPtcPlusBindings` 只帮助用户检查,不进入模型上下文,也不延长 binding 生命周期;相同 contraction 应让 UI inventory 进入不可确认状态。未来若新增模型可见状态投影,它必须由用户、模型或明确配置选择,有固定 schema、硬预算、append-only runtime-context 证据和明确清除条件,不能仅为延长隐藏状态而默认注入,也不能污染稳定 prompt prefix。
150
172
 
151
173
  ## Completion 校验
152
174
 
@@ -164,7 +186,7 @@ pre-execute -> tools/execute -> post-execute
164
186
  - recovery divergence;
165
187
  - durable replay 触发 volatile capability。
166
188
 
167
- 基础设施失败会终止当前 worker,并把 log-only recovery boundary 放入当前已结算 `tool/result` 的私有 metadata。kernel 从失败 node 的 parent 重新重放;若该 frontier 仍失败则继续向 parent 收缩,直到某个 frontier 验证成功或到达空 REPL。触发恢复的当前 `run_code` 在验证成功前不执行,因此可以在同一次请求中安全继续;如果 boundary 无效或历史本身无法折叠,则返回 recovery error。结构损坏的日志没有可证明的 frontier,后续调用会重新读取日志而不会接受一个无法持久表达 ancestry 的新分支。已经写入旧版自定义 boundary event 的日志必须先通过显式迁移移除该事件并把 boundary 合并到后续 `tool/result` metadata,迁移失败时不得覆盖原始日志。
189
+ 基础设施失败会终止当前 worker,并把 log-only recovery boundary 放入当前已结算 `tool/result` 的私有 metadata。kernel 从失败 node 的 parent 重新重放;若该 frontier 仍失败则继续向 parent 收缩,直到最大可验证 frontier 或空 REPL。触发恢复的当前 `run_code` 在候选 frontier 验证成功前不执行,验证后在同一次请求中继续。boundary 无效或历史本身无法完整折叠时,相关记录和可能依赖它的状态不得进入 frontier;这类历史 PTC metadata 损坏本身不返回永久 recovery error,而是继续收缩,最差用空 worker 执行当前 cell,并在其最终 result 持久表达新 ancestry。已经写入旧版自定义 boundary event 的日志必须先通过显式、非破坏迁移转换为 DSH 可加载的 metadata;迁移失败时不得覆盖原始日志。
168
190
 
169
191
  ## State Operations
170
192
 
@@ -206,7 +228,7 @@ worker 不继承 Electron 的工作目录语义。插件通过现有 `tools/exec
206
228
 
207
229
  ## 恢复通知
208
230
 
209
- 构造 kernel 时若折叠结果含 volatile/unknown suffix,或本次重放产生 recovery boundary,第一次实际执行的 `run_code` 记录并投影 `PTC-R002`。它只统计当前 call 之前实际跳过的历史 cell:
231
+ 构造 kernel 时若折叠结果含 volatile/unknown boundary、模型可见 surface 收缩,或本次重放产生 recovery contraction,第一次实际执行的 `run_code` 记录并投影 `PTC-R002`。它只统计当前 call 之前实际未恢复的历史 cell,并明确旧 binding 可能需要重新声明:
210
232
 
211
233
  ```text
212
234
  warning[PTC-R002]: restored the durable head and skipped N unreconstructable historical cells
@@ -215,7 +237,7 @@ state: ...
215
237
  help: ...
216
238
  ```
217
239
 
218
- 通知进入正常 CodeRuntime logs,结构化值进入当前 journal,因此成功结果和错误结果都能呈现并从 session log 重建。每个 kernel 只发送一次,避免污染后续上下文。
240
+ 通知进入正常 CodeRuntime logs,结构化值进入当前 journal,因此成功结果和错误结果都能呈现并从 session log 重建。收缩边界随当前 result 持久化,后续 cold start 从新分支恢复,不重复撞击同一损坏历史;每个 kernel 只发送一次,避免污染后续上下文。
219
241
 
220
242
  live kernel 首次进入 volatile 只更新 journal 的 `status` / `volatileReason` 和 `repl.state(list)`,不向模型投影 warning/note。该转换不要求当前任务采取行动;只有 cold recovery 已实际跳过 volatile、unknown 或 replay-abandoned 历史时才发送 `PTC-R002`。worker 在首次观察到直接 Node/OS 边界时立即通知主线程,因此后续 hard abort、timeout 或 worker exit 仍能把原因写入 discarded journal。
221
243
 
@@ -244,7 +266,7 @@ live kernel 首次进入 volatile 只更新 journal 的 `status` / `volatileReas
244
266
 
245
267
  以上是当前已实现的 PTC journal 协议。native `tools.*` 保持 owner contract;cell program binding 不得变成新的模型 transport。
246
268
 
247
- 任何公共扩展面无法持久确认的状态都必须收缩为 volatile/unknown 边界。修改或 fork DSH、patch 私有
269
+ 任何公共扩展面无法持久确认的状态都必须收缩为 volatile/unknown 边界,但边界否定的是旧状态证明,不是当前合法调用的可用性;最大可证明 frontier 为空时,插件仍应让当前 cell 从空 REPL 建立新分支。修改或 fork DSH、patch 私有
248
270
  scheduler、复制 policy/event protocol 或伪造事件不属于本协议。若上游以后提供 owner-declared
249
271
  typed binding,它可以使用 DSH nested dispatch,但不得伪装成 journal confirmation,也不得绕过
250
272
  scope、policy 或 settlement。
@@ -256,10 +278,10 @@ scope、policy 或 settlement。
256
278
  - volatile live continuation、冷恢复跳过和 durable 重基;
257
279
  - post-execute metadata 删除与 pre-dispatch no-op 确认;
258
280
  - hard abort、冷 worker 启动取消和未结算 program binding 的 possible-effect boundary;
259
- - replay timeout fail closed;
281
+ - replay timeout 逐级收缩到可验证 frontier;
260
282
  - Math intrinsic、局部 ambient 名称、CWD 相关 `node:path`;
261
283
  - 命名状态 save/restore/delete 与 volatile restore;
262
284
  - 深层 graph value、own `__proto__` key、`undefined`、special number、BigInt、hole、alias 与 cycle;
263
- - 宽松变量重声明与严格/混合名称 collision preflight;
285
+ - 独立的变量与 function/class 重声明 policy、不可写目标 preflight、声明位置替换及 cold replay;
264
286
  - runtime throw、invalid output 和真实 recovery 的结构化诊断与模型可见投影;普通 volatile transition 保持安静;
265
287
  - 未修改 DSH 的真实公共扩展面,并在全新 session runtime 中从日志恢复。
@@ -23,7 +23,7 @@ The optional `cordisToolsEnabled` integration requires the current DSH installat
23
23
  Use this form after the selected version is available from the npm registry:
24
24
 
25
25
  ```sh
26
- dsh plugin --profile <profile> add dsh-ptc-plus@0.2.4
26
+ dsh plugin --profile <profile> add dsh-ptc-plus@0.3.1
27
27
  dsh --profile <profile> --dump-config
28
28
  ```
29
29
 
@@ -60,7 +60,7 @@ pnpm dsh --profile <profile> --dump-config
60
60
 
61
61
  ```sh
62
62
  npm pack
63
- dsh plugin --profile <profile> add /absolute/path/to/dsh-ptc-plus-0.2.4.tgz
63
+ dsh plugin --profile <profile> add /absolute/path/to/dsh-ptc-plus-0.3.1.tgz
64
64
  dsh --profile <profile> --dump-config
65
65
  ```
66
66
 
@@ -70,6 +70,14 @@ Windows development checkouts can create and install an immutable content-addres
70
70
  scripts\install-dev.cmd <profile>
71
71
  ```
72
72
 
73
+ ## Isolated latest-DSH development launcher
74
+
75
+ To test this checkout in a separate DSH installation, double-click `scripts\run-dev-dsh.cmd`. It resolves the current `@deepseek-ai/dsh@alpha` dist-tag, installs that exact version only when the cached version changes, creates the shipped `web` profile inside an isolated `DSH_HOME`, installs this checkout, and starts DSH. If no port is supplied, it selects a free loopback port automatically, so another DSH on port 3080 does not block the test instance. The launcher does not modify the repository or your normal DSH home.
76
+
77
+ The default cache is `%LOCALAPPDATA%\dsh-ptc-plus-dev`. It contains the isolated `DSH_HOME`, DSH installations, immutable plugin snapshots, and a pnpm store. Only the newest three DSH versions and plugin snapshots are retained, and the dedicated pnpm store is pruned after each install. Set `DSH_DEV_CACHE` to move the cache, `DSH_DEV_PROFILE` to change the profile name, `DSH_DEV_VERSION` to select another npm dist-tag or version, `DSH_DEV_PORT` to choose a fixed Web port, or `DSH_DEV_MAX_VERSIONS` to change the retention count (1-10).
78
+
79
+ The Windows launchers normalize the process, machine, and user `PATH` values in memory, remove duplicate entries, and add the Node/cache directories they need. They do not create drive mappings, junction trees, or persistent environment changes. If a genuinely unique PATH is still too long for `cmd.exe`, the launcher stops before npm or DSH runs and asks you to shorten the relevant PATH value.
80
+
73
81
  ## DSH Desktop
74
82
 
75
83
  On Windows or macOS, choose **Open DSH Terminal** from the Desktop tray. Bare commands in that terminal target the active profile:
@@ -79,4 +87,4 @@ dsh plugin add github:muyuanjin/dsh-ptc-plus#main
79
87
  dsh --dump-config
80
88
  ```
81
89
 
82
- After an npm release, the package spec may instead be `dsh-ptc-plus@0.2.4`. For a local package, use its absolute tarball path. Restart DSH Desktop after installation. Linux Desktop is not a current DSH Desktop release target; use DSH CLI/Web on Linux.
90
+ After an npm release, the package spec may instead be `dsh-ptc-plus@0.3.1`. For a local package, use its absolute tarball path. Restart DSH Desktop after installation. Linux Desktop is not a current DSH Desktop release target; use DSH CLI/Web on Linux.
@@ -49,6 +49,12 @@ return source.length
49
49
 
50
50
  普通已知程序优先 argv spawn;只有命令本身需要 shell 语义时才使用 shell。shell 不是权限系统,也不是 REPL 前置条件;PTY/ConPTY 只用于交互进程。Windows、WSL 与 POSIX 环境的 executable、路径、resolver、signal 和 TTY 必须分别探查,不能由一个 execution world 推断另一个。
51
51
 
52
+ ## Stateful visibility
53
+
54
+ journal 值、模型请求和 Client 展示是三种不同的数据面。append-only session log 中存在完整 journal,只能证明插件有材料尝试重建;DSH ordered surface/derived request history 才证明生成当前调用的模型看到过哪些声明。Client 的 `meta.dshPtcPlusBindings` inventory 只对用户展示,不进入模型上下文;当前 `repl.state(list)` 只描述命名 checkpoint 与 durability mode,也不让隐藏 binding 自动变成模型可知状态。
55
+
56
+ 因此默认 REPL frontier 必须同时可重建且模型可知。result-only 剪裁若仍保留带精确源码的 assistant call,不改变该 cell 的模型 provenance;若 compaction 遮蔽了声明 call,raw journal 不足以继续暴露其 binding。插件不解析自然语言 summary 推断名称、值或依赖,而是收缩相关 cell 与依赖后缀,必要时从空 REPL 继续当前 cell。未来可以用显式选择、有界、结构化的 model-visible state projection 保留被压缩的状态,但它必须从 session log 重建、进入实际 request context,并遵守稳定 prompt prefix;不能仅为延长隐藏状态而默认注入,UI metadata 也不能替代它。
57
+
52
58
  ## Metadata boundaries
53
59
 
54
60
  能力 explorer 分别报告:
@@ -4,7 +4,7 @@
4
4
 
5
5
  Each top-level `run_code` input is parsed as an async function body using modern JavaScript and erasable TypeScript syntax. Top-level bindings survive across cells, block scope and top-level `await` work normally, and only explicit `return` or printed values appear in the result. Static `import` and `export` declarations are adapted when their corresponding `auto*` options are enabled; named and default imports remain live, read-only bindings across later cells. Otherwise use dynamic `import()` or `require()` and omit module declarations.
6
6
 
7
- The default loose binding mode lets a complete top-level `const` or `let` declarator replace existing names. New declarators create bindings. With `autoSplitRedeclarations` enabled, mixed new/existing destructuring is split while preserving its assignment semantics; disabling that option, function/class redeclaration, or any redeclaration in strict binding mode is rejected before execution.
7
+ The default variable policy lets a complete top-level `const` or `let` declarator replace existing names. New declarators create bindings. With `autoSplitRedeclarations` enabled, mixed new/existing destructuring is split while preserving its assignment semantics; disabling either option rejects the corresponding variable form before execution. The independent `looseTopLevelFunctionClassRedeclarations` option defaults to `false`. When enabled, a later named top-level function or class declaration replaces an existing binding only when its recorded source provenance proves it writable. Replacement commits at the declaration position, so earlier statements see the old function/class and later statements see the new one; it is not native function hoisting. Imports, reserved/private names, source `const` bindings not lowered by the variable policy, and unknown-provenance targets are rejected before execution. Same-cell and block-scope rules remain native JavaScript/TypeScript rules.
8
8
 
9
9
  Every capability namespace is leased to one cell. Captured `tools`, `capabilities`, `repl`, `code`, or member functions expire when that cell ends.
10
10
 
@@ -45,28 +45,35 @@ For commands, prefer project-declared scripts and use an available typed tool. W
45
45
 
46
46
  The configuration uses `ptc-plus` as the DSH plugin ID and `dsh-ptc-plus` as the package name. The distinction is intentional: the former is the runtime/settings identity, while the latter is the repository and npm package identity.
47
47
 
48
+ `enhancedToolView` defaults to `true` and controls only the browser presentation of `run_code` and `edit_run_code`. Turning it off unregisters PTC Plus's keyed tool views so DSH's native generic row renders the calls; turning it on restores the compatibility renderer without changing execution, prompt, or session behavior.
49
+
50
+ `autoDescribeRunCode` defaults to `true` and appears as “Allow run_code execution without a summary.” When enabled, a call that omits the outer `run_code.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 expose byte-identical model requests with the required `description` declaration; original call arguments, existing summaries, cell source, and nested native-tool JSON remain unchanged.
51
+
48
52
  ```yaml
49
53
  - id: ptc-plus
50
54
  name: dsh-ptc-plus
51
55
  config:
52
56
  enabled: true
57
+ enhancedToolView: true
58
+ canonicalizeToolCalls: true
59
+ autoDescribeRunCode: true
60
+ looseTopLevelRedeclarations: true
61
+ looseTopLevelFunctionClassRedeclarations: false
62
+ autoRewriteImports: true
63
+ autoStripExports: true
64
+ autoSplitRedeclarations: true
65
+ durableReplay: true
66
+ tipsEnabled: true
53
67
  cordisToolsEnabled: false
54
68
  computeMs: 60000
55
69
  maxWallMs: 600000
56
- maxOutputBytes: 67108864
57
70
  maxOldGenerationSizeMb: 512
71
+ maxNestedRunCodeDepth: 8
72
+ maxOutputBytes: 67108864
58
73
  maxValueNodes: 100000
59
74
  maxValueEdges: 1000000
60
75
  maxValueArrayLength: 1000000
61
76
  maxValueBigIntDigits: 100000
62
- maxNestedRunCodeDepth: 8
63
- canonicalizeToolCalls: true
64
- looseTopLevelRedeclarations: true
65
- durableReplay: true
66
- autoRewriteImports: true
67
- autoStripExports: true
68
- autoSplitRedeclarations: true
69
- tipsEnabled: true
70
77
  tipCooldownMessages: 3
71
78
  tipEscalationFailures: 2
72
79
  ```
@@ -77,16 +84,22 @@ The configuration uses `ptc-plus` as the DSH plugin ID and `dsh-ptc-plus` as the
77
84
 
78
85
  Keep large Cordis host or client source in a top-level binding before calling a Cordis tool. A parse or validation rejection is a runtime failure, so bindings assigned before it remain live for a short retry cell; reuse the binding instead of resending the source.
79
86
 
80
- Cordis Plugins and Runs are process-local. On a resumed agent or after Cordis is re-enabled, normalized journal calls can restore their recorded return values but do not prove that prior IDs, approvals, Runs, or Inspect observations are live. The fixed `tools:ptc-plus-cordis-recovery` context remains present until a new successful `cordis_inspect*` call is settled. Use that live read-only inspection before a stateful Cordis decision; do not rerun a mutating historical call merely to rebuild state.
87
+ Cordis Plugins and Runs are process-local. On a resumed agent or after Cordis is re-enabled, normalized journal calls can restore their recorded return values but do not prove that prior IDs, approvals, Runs, or Inspect observations are live. The fixed `tools:ptc-plus-cordis-recovery` context remains present until a new successful `cordis_inspect*` call is settled. Use that live read-only inspection before a stateful Cordis decision; do not rerun a recorded mutating call merely to rebuild state.
88
+
89
+ `durableReplay: false` starts new kernels without recorded REPL state while preserving live bindings in the current process. It does not delete session logs.
90
+
91
+ With `durableReplay: true`, recovery claims only bindings backed by both verified journal ancestry and exact provenance available to the model that produced the current call. DSH's append-only event log proves reconstructability; its ordered model-visible surface proves awareness. Raw events shadowed from that surface and UI-only `dshPtcPlusBindings` metadata do not make a binding model-knowable. A result-only replacement may retain a cell when the assistant call containing its source remains visible. If compaction shadows that call, the live worker and cold recovery contract the affected state unless an explicitly selected bounded structured projection exposes the exact bindings in model context. Such a projection is not injected by default merely to retain hidden state. Natural-language summaries are not parsed as state evidence.
92
+
93
+ An invalid historical association, prune replacement, or recovery boundary is also rejected as evidence for its bindings; it is not by itself a reason to reject every later valid `run_code`. Recovery takes the intersection of the model-knowable and structurally verified frontiers, then discards cells and bindings that may depend on an unknown boundary. Because the current journal does not contain a complete per-binding dependency graph, the conservative unit is an affected cell and suffix unless stronger owned evidence exists. If no non-empty frontier is provable, the worker starts from an empty REPL and executes the current cell as a new root.
81
94
 
82
- `durableReplay: false` starts new kernels without historical REPL state while preserving live bindings in the current process. It does not delete session logs.
95
+ The first continued cell reports `PTC-R002` once so the model can redeclare any missing bindings, and its result persists the contraction for later cold starts. Recovery never redispatches, reverses, or certifies effects from discarded history. This availability fallback applies only to historical PTC Plus recovery data; validation, policy, authority, approval, cancellation, and other failures of the current request retain their normal DSH behavior.
83
96
 
84
97
  Recovery tips are disabled with `tipsEnabled: false`. When enabled, a bounded runtime context in the reserved `tools:ptc-plus-tip/<trigger>/<ordinal>` name family may appear after a repeated binding failure or a diagnostic that identifies an executable, shell, or path problem in the current execution world. The trigger and per-trigger ordinal are reconstructed from canonical named system-prompt snapshots; repeated aggregate copies and visible wording do not advance the history. A tip is subject to `tipCooldownMessages` and becomes detailed only after `tipEscalationFailures` unresolved matches; it never changes the code-only direct-tool list or schema. `edit_run_code` does not emit a runtime context because its real call and result already carry the relevant fact.
85
98
 
86
99
  The three `auto*` toggles control text-level AST rewriting applied before cell wrapping, all on by default:
87
100
 
88
101
  - `autoRewriteImports` adapts static `import` declarations (`import x from 'm'`, named, namespace, side-effect, mixed, and type-only forms) through worker-preloaded module namespaces. Static and dynamic imports use Node's ESM resolver from the session cwd, or the worker process cwd when the session has no recorded cwd; relative project files, bare packages, conditional exports, URL imports, and import attributes retain Node semantics. Modules resolve in source order before the cell body is compiled, and a missing requested export fails during linking before that target module evaluates. Named and default aliases keep live reads and reject assignment; a later explicit loose-mode declaration may replace an alias for future cells while closures compiled earlier retain the import. Unsupported destructuring, loop, and delete writes are rejected before execution. Direct `eval` and `with` are unavailable while value import aliases are active because their dynamic name resolution cannot be preserved by alias rewriting; rejection occurs before module preload. Indirect or locally shadowed eval, `with` without active value aliases, and cells containing only type imports retain ordinary JavaScript behavior.
89
- - `autoStripExports` strips top-level `export` modifiers: declarations survive without the keyword, `export default` becomes a local `__default` binding, re-exports become side-effect imports, and type-only exports are erased. A cell that separately declares the fixed public name `__default` and also has a default export is rejected before execution; a default function or class already named `__default` needs no alias.
102
+ - `autoStripExports` strips top-level `export` modifiers: declarations survive without the keyword, `export default` becomes a live read-only `__default` alias updated at the declaration position, re-exports become side-effect imports, and type-only exports are erased. A cell that separately declares the fixed public name `__default` and also has a default export is rejected before execution. A default function or class whose own name is `__default` keeps that inner name for recursion or class self-reference while the public alias still uses the same private, commit-gated slot as every other default export.
90
103
  - `autoSplitRedeclarations` allows a top-level destructuring declaration to mix existing and new names. The original pattern runs under native JavaScript initialization semantics, then existing names are committed and fresh names remain available to later cells.
91
104
 
92
105
  Rewrites are recorded as `meta.dshPtcPlusRewrites` on the tool result (parallel to the journal, a closed schema). Successful rewrites do not emit runtime context. If the rewritten cell fails or has no valid journal, `tools:ptc-plus-rewrite-info` describes the recovery boundary in the next model request.
@@ -102,7 +115,7 @@ Rewrites are recorded as `meta.dshPtcPlusRewrites` on the tool result (parallel
102
115
  | `PTC-N001` | Top-level binding conflict | Not executed; REPL unchanged |
103
116
  | `PTC-O001` | Unsupported or over-budget output | Cell executed; earlier mutations may exist |
104
117
  | `PTC-X001` | Uncaught runtime exception, located at the cell source line | Mutations before the throw may exist |
105
- | `PTC-R002` | Cold recovery skipped volatile, unconfirmed, or replay-abandoned history | Restored the last reconstructable durable frontier |
118
+ | `PTC-R002` | Cold recovery discarded volatile, unconfirmed, damaged, or replay-abandoned history | Continued from the greatest verified frontier, possibly an empty REPL; missing bindings may need redeclaration |
106
119
  | `PTC-W001` | The same cell failed 3 consecutive times with an identical binding error | One-shot binding-recovery warning appended to the failing cell's logs; no state change |
107
120
  | `PTC-W002` | The same cell failed 3 consecutive times with another identical error | One-shot cause-specific warning appended to the failing cell's logs; no state change |
108
121
 
@@ -52,7 +52,7 @@ DSH 的公共 `CodeRuntime`/tool output 只接受 JSON-safe structured value。
52
52
  - renderer 只是模型展示,不参与 hydrate;需要继续计算时应保留 live REPL binding;
53
53
  - journal 保存 canonical graph envelope,不保存 renderer 反解析结果,也不把 decoded rich value 交给外层 `JSON.stringify`。
54
54
 
55
- unsupported value 或预算超限继续使用 `PTC-O001`,但帮助文本只要求返回受支持的 PTC value 或缩小结果;`undefined` 不再是错误,也不要求模型用 `null` 替换。
55
+ unsupported value 或预算超限使用 `PTC-O001`,帮助文本只要求返回受支持的 PTC value 或缩小结果;`undefined` 属于受支持值,不要求模型用 `null` 替换。
56
56
 
57
57
  cell program binding 的参数和结果经过同一 graph IPC,因此 owner-provided binding 可以接收和返回
58
58
  完整 `PTCValueV1` 支持域,包括 `undefined`、special number、BigInt、hole、shared identity 与 cycle。
package/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import Schema from '@deepseek-ai/schemastery'
10
+ import { randomUUID } from 'node:crypto'
10
11
  import { createDirectSurfaceOwner } from './internal/direct-surface-owner.js'
11
12
  import { createCordisToolsOwner } from './internal/cordis-tools-owner.js'
12
13
  import { createEditTransportOwner, EDIT_RUN_CODE } from './internal/edit-transport-owner.js'
@@ -17,6 +18,7 @@ import {
17
18
  SETTINGS_NAMESPACE,
18
19
  } from './internal/config-spec.js'
19
20
  import { installSettingsSectionCompat } from './internal/settings-compat.js'
21
+ import { createReplMemoryProjection } from './internal/repl-memory-projection.js'
20
22
  import * as dshSettings from '@deepseek-ai/dsh-settings'
21
23
 
22
24
  const INSTALL_CLEANUP = Symbol('ptc-plus install cleanup')
@@ -41,15 +43,19 @@ export const inject = ['tools', 'codeRuntime', 'systemPrompt', 'agents', 'llm']
41
43
 
42
44
  function replGuidance(
43
45
  looseTopLevelRedeclarations,
46
+ looseTopLevelFunctionClassRedeclarations,
44
47
  durableReplay,
45
48
  autoRewriteImports,
46
49
  autoStripExports,
47
50
  autoSplitRedeclarations,
48
51
  cordisToolsEnabled,
49
52
  ) {
50
- const redeclaration = looseTopLevelRedeclarations
53
+ const variableRedeclaration = looseTopLevelRedeclarations
51
54
  ? 'Repeated top-level `const`/`let` declarations replace existing bindings.'
52
- : 'Redeclaring an existing top-level name fails before execution, so reuse it or place one-off declarations inside a block.'
55
+ : 'Repeated top-level variable declarations fail before execution, so reuse existing bindings or place one-off declarations inside a block.'
56
+ const functionClassRedeclaration = looseTopLevelFunctionClassRedeclarations
57
+ ? 'Repeated top-level named `function`/`class` declarations replace existing writable bindings at their declaration position. Do not rely on function hoisting or class TDZ; define a replacement before using that name in the cell.'
58
+ : 'Repeated top-level `function`/`class` declarations remain unsupported; assign a function or class expression to an existing writable binding, or place one-off declarations inside a block.'
53
59
  const moduleSyntax = autoRewriteImports && autoStripExports
54
60
  ? 'static `import` declarations are adapted with live, read-only bindings and top-level `export` modifiers are stripped automatically.'
55
61
  : autoRewriteImports
@@ -71,7 +77,7 @@ function replGuidance(
71
77
  The host may append a bounded recovery context after a qualifying failure. Treat that context as a session-log-derived diagnostic: use \`edit_run_code\` only when it explicitly proves a complete-cell rerun is safe; otherwise inspect live state in a new short \`run_code\` cell.
72
78
 
73
79
  ## Cell conventions
74
- Expressions that are neither returned nor printed produce no output. Keep large inspection results in bindings or reduce them to targeted excerpts: \`tools.read\` is bounded inspection, not a lossless whole-file reader. Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${redeclaration} ${splitSyntax}
80
+ Expressions that are neither returned nor printed produce no output. Keep large inspection results in bindings or reduce them to targeted excerpts: \`tools.read\` is bounded inspection, not a lossless whole-file reader. Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${variableRedeclaration} ${functionClassRedeclaration} ${splitSyntax}
75
81
 
76
82
  ## Available capabilities
77
83
  Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` to discover the current request's live \`tools.*\` members before calling an unfamiliar binding. Prefer direct current-cell work; reserve \`code.run\` for source already held as data.
@@ -81,6 +87,8 @@ Native tool availability, executable names, shells, and path syntax depend on th
81
87
 
82
88
  /** Register the session-bound REPL runtime. */
83
89
  function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId) {
90
+ const presentationGeneration = randomUUID()
91
+ const replMemoryProjection = createReplMemoryProjection(presentationGeneration)
84
92
  let activeConfig = resolvedConfig
85
93
  let cordisTools
86
94
  let runtimeBridge
@@ -151,6 +159,25 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
151
159
  }
152
160
  const cancelCordisActivation = () => pendingCordisActivation?.cancel()
153
161
  try {
162
+ if (typeof ctx.inject === 'function') {
163
+ const projectionInjection = ctx.inject(['sessionProjections'], (scope) => {
164
+ if (disposed) return
165
+ try {
166
+ const unregister = scope.sessionProjections?.register?.(replMemoryProjection)
167
+ if (typeof unregister !== 'function') {
168
+ throw new Error('ptc-plus: sessionProjections.register did not return a disposer')
169
+ }
170
+ disposers.push(unregister)
171
+ } catch (error) {
172
+ ctx.logger?.warn?.('ptc-plus: REPL memory projection unavailable', error)
173
+ }
174
+ })
175
+ if (typeof projectionInjection === 'function') {
176
+ disposers.push(projectionInjection)
177
+ } else if (typeof projectionInjection?.dispose === 'function') {
178
+ disposers.push(() => projectionInjection.dispose())
179
+ }
180
+ }
154
181
  cordisTools = activeConfig.cordisToolsEnabled
155
182
  ? createCordisToolsOwner(ctx)
156
183
  : undefined
@@ -165,12 +192,14 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
165
192
  ctx,
166
193
  sessionConfig: activeConfig,
167
194
  maxNestedRunCodeDepth: activeConfig.maxNestedRunCodeDepth,
195
+ presentationGeneration,
168
196
  sessionId,
169
197
  toolSchemasForAgent,
170
198
  })
171
199
  editTransport = createEditTransportOwner(ctx, {
172
200
  durableReplay: activeConfig.durableReplay,
173
201
  executeTentative: runtimeBridge.executeTentative,
202
+ presentationGeneration,
174
203
  sessionId,
175
204
  toolSchemasForAgent,
176
205
  })
@@ -188,6 +217,7 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
188
217
  if (ctx.tools.get(RUN_CODE, context?.scope) === undefined) return ''
189
218
  return replGuidance(
190
219
  activeConfig.looseTopLevelRedeclarations,
220
+ activeConfig.looseTopLevelFunctionClassRedeclarations,
191
221
  activeConfig.durableReplay,
192
222
  activeConfig.autoRewriteImports,
193
223
  activeConfig.autoStripExports,
@@ -203,7 +233,11 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
203
233
  disposers.push(ctx.on('tools/execute', (exec, next) => {
204
234
  const rejected = directSurface.executionRejection(exec)
205
235
  if (rejected !== undefined) return rejected
206
- if (exec.name === RUN_CODE) return runtimeBridge.handleExecute(exec, next)
236
+ if (exec.name === RUN_CODE) {
237
+ const executionArguments = directSurface.executionArguments(exec)
238
+ return Promise.resolve(runtimeBridge.handleExecute(exec, next, executionArguments))
239
+ .then(result => directSurface.argumentDiagnostic(exec, result))
240
+ }
207
241
  return next()
208
242
  }))
209
243
  disposers.push(ctx.on('tools/result', (exec, result) => {