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
@@ -20,14 +20,104 @@ export const CONFIG_FIELDS = Object.freeze([
20
20
  description: '',
21
21
  descriptionEn: '',
22
22
  },
23
+ {
24
+ key: 'enhancedToolView',
25
+ type: 'boolean',
26
+ default: true,
27
+ label: '使用 PTC Plus 增强工具卡片',
28
+ labelEn: 'Use enhanced PTC Plus tool cards',
29
+ description: '开启时使用增强工具卡片;关闭后使用 DSH 原生工具卡片。',
30
+ descriptionEn: 'Uses enhanced tool cards when enabled and DSH native tool cards when disabled.',
31
+ },
32
+ {
33
+ key: 'autoDescribeRunCode',
34
+ type: 'boolean',
35
+ default: true,
36
+ label: '允许执行缺少摘要的 run_code',
37
+ labelEn: 'Allow run_code execution without a summary',
38
+ description: '允许 run_code 缺少外层 description;关闭后恢复 DSH 校验。模型请求和原始调用参数保持不变。',
39
+ descriptionEn: 'Allows run_code to omit its outer description; disabling restores DSH validation. Model requests and original call arguments remain unchanged.',
40
+ },
41
+ {
42
+ key: 'canonicalizeToolCalls',
43
+ type: 'boolean',
44
+ default: true,
45
+ label: '修复可识别的顶层 native 工具误调用',
46
+ labelEn: 'Repair recognizable top-level native tool mis-calls',
47
+ description: '仅当当前 schema 能唯一确认目标工具且参数有效时,才把顶层 native 工具误调用修复为对应的 run_code 调用。',
48
+ descriptionEn: 'Repairs a top-level native tool mis-call as the corresponding run_code call only when the current schema uniquely identifies the tool and validates its arguments.',
49
+ },
23
50
  {
24
51
  key: 'cordisToolsEnabled',
25
52
  type: 'boolean',
26
53
  default: false,
27
- label: '在 PTC 模式中启用 Cordis 工具',
28
- labelEn: 'Enable Cordis tools in PTC mode',
29
- description: '即时为 PTC agent 加入或移除官方 Cordis 工具。',
30
- descriptionEn: 'Adds or removes the official Cordis tools for the PTC agent immediately.',
54
+ label: '在 PTC 模式中启用官方 Cordis 工具',
55
+ labelEn: 'Enable official Cordis tools in PTC mode',
56
+ description: '开启后,PTC agent 可使用官方 Cordis 工具、配套指引和开发 Skill;关闭后移除这些能力。Cordis 工具以 DSH 进程权限运行。',
57
+ descriptionEn: 'When enabled, PTC agents can use the official Cordis tools, guidance, and development Skill; disabling removes them. Cordis tools run with the DSH process permissions.',
58
+ },
59
+ {
60
+ key: 'looseTopLevelRedeclarations',
61
+ type: 'boolean',
62
+ default: true,
63
+ label: '允许顶层变量重声明',
64
+ labelEn: 'Allow top-level variable redeclarations',
65
+ description: '允许顶层 const 或 let 声明替换已有变量;关闭后,重声明会在执行前被拒绝。',
66
+ descriptionEn: 'Allows top-level const or let declarations to replace existing variables; disabling rejects redeclarations before execution.',
67
+ },
68
+ {
69
+ key: 'looseTopLevelFunctionClassRedeclarations',
70
+ type: 'boolean',
71
+ default: false,
72
+ label: '允许顶层函数/类重声明',
73
+ labelEn: 'Allow top-level function/class redeclarations',
74
+ description: '允许顶层 function 或 class 声明替换已有的可写 binding;不可写或保留 binding 仍会在执行前被拒绝。',
75
+ descriptionEn: 'Allows top-level function or class declarations to replace existing writable bindings; immutable and reserved bindings are still rejected before execution.',
76
+ },
77
+ {
78
+ key: 'autoRewriteImports',
79
+ type: 'boolean',
80
+ default: true,
81
+ label: '支持静态 import 声明',
82
+ labelEn: 'Support static import declarations',
83
+ description: '允许在 run_code 中使用静态 import,并保留模块解析和只读 binding 语义。',
84
+ descriptionEn: 'Allows static import declarations in run_code while preserving module resolution and read-only binding semantics.',
85
+ },
86
+ {
87
+ key: 'autoStripExports',
88
+ type: 'boolean',
89
+ default: true,
90
+ label: '支持顶层 export 修饰符',
91
+ labelEn: 'Support top-level export modifiers',
92
+ description: '允许在 run_code 中使用顶层 export,并保留对应的本地声明或模块副作用。',
93
+ descriptionEn: 'Allows top-level export syntax in run_code while preserving the corresponding local declaration or module side effect.',
94
+ },
95
+ {
96
+ key: 'autoSplitRedeclarations',
97
+ type: 'boolean',
98
+ default: true,
99
+ label: '支持混合解构重声明',
100
+ labelEn: 'Support mixed destructuring redeclarations',
101
+ description: '允许顶层解构声明同时包含新变量和已有变量。',
102
+ descriptionEn: 'Allows top-level destructuring declarations to contain both new and existing variables.',
103
+ },
104
+ {
105
+ key: 'durableReplay',
106
+ type: 'boolean',
107
+ default: true,
108
+ label: '启用持久 REPL 恢复',
109
+ labelEn: 'Enable durable REPL recovery',
110
+ description: 'worker 重启后从 session journal 重建可恢复的 REPL 状态。',
111
+ descriptionEn: 'Rebuilds recoverable REPL state from the session journal after a worker restart.',
112
+ },
113
+ {
114
+ key: 'tipsEnabled',
115
+ type: 'boolean',
116
+ default: true,
117
+ label: '启用失败恢复提示',
118
+ labelEn: 'Enable failure recovery tips',
119
+ description: '在符合条件的失败后显示有冷却间隔的恢复提示。',
120
+ descriptionEn: 'Shows recovery tips with a cooldown after qualifying failures.',
31
121
  },
32
122
  {
33
123
  key: 'computeMs',
@@ -35,10 +125,10 @@ export const CONFIG_FIELDS = Object.freeze([
35
125
  default: 60_000,
36
126
  min: 1,
37
127
  max: Number.MAX_SAFE_INTEGER,
38
- label: '单 cell 最大 CPU 时间 (ms)',
39
- labelEn: 'Max CPU time per cell (ms)',
40
- description: '同步计算超过该预算的 cell 会被中断。',
41
- descriptionEn: 'Cells whose synchronous computation exceeds this budget are interrupted.',
128
+ label: '单 cell CPU 时间上限 (ms)',
129
+ labelEn: 'CPU time limit per cell (ms)',
130
+ description: '同步计算超过该上限时中断当前 cell。',
131
+ descriptionEn: 'Interrupts the current cell when synchronous computation exceeds this limit.',
42
132
  },
43
133
  {
44
134
  key: 'maxWallMs',
@@ -46,32 +136,43 @@ export const CONFIG_FIELDS = Object.freeze([
46
136
  default: 600_000,
47
137
  min: 1,
48
138
  max: MAX_TIMER_DELAY_MS,
49
- label: '单 cell 最大墙钟时间 (ms)',
50
- labelEn: 'Max wall-clock time per cell (ms)',
51
- description: '完整 cell 执行(含异步等待)的最长耗时。',
52
- descriptionEn: 'The longest a full cell execution may take, including async waits.',
139
+ label: '单 cell 总耗时上限 (ms)',
140
+ labelEn: 'Elapsed time limit per cell (ms)',
141
+ description: '限制完整 cell 执行的耗时,包括异步等待。',
142
+ descriptionEn: 'Limits the complete cell execution time, including asynchronous waits.',
53
143
  },
54
144
  {
55
- key: 'maxOutputBytes',
145
+ key: 'maxOldGenerationSizeMb',
56
146
  type: 'integer',
57
- default: 64 * 1024 * 1024,
147
+ default: 512,
58
148
  min: 1,
59
149
  max: Number.MAX_SAFE_INTEGER,
60
- label: '最大输出字节',
61
- labelEn: 'Max output bytes',
62
- description: '限制单个 cell 的输出和结果数据总大小。',
63
- descriptionEn: 'Caps the combined size of one cell output and result data.',
150
+ label: '每个 worker 旧生代内存上限 (MiB)',
151
+ labelEn: 'Old-generation memory limit per worker (MiB)',
152
+ description: '限制每个 worker 的 V8 旧生代;活动 worker 存在时不能修改。',
153
+ descriptionEn: 'Limits the V8 old generation for each worker; this setting cannot change while a worker is active.',
64
154
  },
65
155
  {
66
- key: 'maxOldGenerationSizeMb',
156
+ key: 'maxNestedRunCodeDepth',
67
157
  type: 'integer',
68
- default: 512,
158
+ default: 8,
159
+ min: 1,
160
+ max: Number.MAX_SAFE_INTEGER,
161
+ label: 'code.run 嵌套层数上限',
162
+ labelEn: 'code.run nesting depth limit',
163
+ description: '限制单个顶层 cell 中 code.run 的最大嵌套层数。',
164
+ descriptionEn: 'Limits the maximum code.run nesting depth within one top-level cell.',
165
+ },
166
+ {
167
+ key: 'maxOutputBytes',
168
+ type: 'integer',
169
+ default: 64 * 1024 * 1024,
69
170
  min: 1,
70
171
  max: Number.MAX_SAFE_INTEGER,
71
- label: 'worker 旧生代内存上限 (MiB)',
72
- labelEn: 'Worker old-generation memory cap (MiB)',
73
- description: '每个 worker 的 V8 旧生代上限;活动 worker 存在时修改会被拒绝。',
74
- descriptionEn: 'Per-worker V8 old-generation limit; changes are rejected while an active worker exists.',
172
+ label: '单 cell 输出上限 (bytes)',
173
+ labelEn: 'Output limit per cell (bytes)',
174
+ description: '限制单个 cell 的日志和返回结果的合计大小。',
175
+ descriptionEn: 'Limits the combined size of one cell\'s logs and returned result.',
75
176
  },
76
177
  {
77
178
  key: 'maxValueNodes',
@@ -79,10 +180,10 @@ export const CONFIG_FIELDS = Object.freeze([
79
180
  default: 100_000,
80
181
  min: 1,
81
182
  max: Number.MAX_SAFE_INTEGER,
82
- label: 'Value Graph 最大节点数',
83
- labelEn: 'Max Value Graph nodes',
84
- description: '限制单次返回值的节点数。',
85
- descriptionEn: 'Caps the node count of a single returned value.',
183
+ label: '返回值节点数上限',
184
+ labelEn: 'Returned-value node limit',
185
+ description: '限制单次返回值的 Value Graph 节点数。',
186
+ descriptionEn: 'Limits the Value Graph node count for one returned value.',
86
187
  },
87
188
  {
88
189
  key: 'maxValueEdges',
@@ -90,10 +191,10 @@ export const CONFIG_FIELDS = Object.freeze([
90
191
  default: 1_000_000,
91
192
  min: 1,
92
193
  max: Number.MAX_SAFE_INTEGER,
93
- label: 'Value Graph 最大边数',
94
- labelEn: 'Max Value Graph edges',
95
- description: '限制单次返回值的引用关系数。',
96
- descriptionEn: 'Caps the reference-edge count of a single returned value.',
194
+ label: '返回值引用边数上限',
195
+ labelEn: 'Returned-value reference-edge limit',
196
+ description: '限制单次返回值的 Value Graph 引用边数。',
197
+ descriptionEn: 'Limits the Value Graph reference-edge count for one returned value.',
97
198
  },
98
199
  {
99
200
  key: 'maxValueArrayLength',
@@ -101,10 +202,10 @@ export const CONFIG_FIELDS = Object.freeze([
101
202
  default: 1_000_000,
102
203
  min: 1,
103
204
  max: Number.MAX_SAFE_INTEGER,
104
- label: '数组最大声明长度',
105
- labelEn: 'Max declared array length',
106
- description: '限制返回数组的最大长度。',
107
- descriptionEn: 'Caps the maximum length of a returned array.',
205
+ label: '返回数组长度上限',
206
+ labelEn: 'Returned-array length limit',
207
+ description: '限制返回数组的最大声明长度。',
208
+ descriptionEn: 'Limits the maximum declared length of a returned array.',
108
209
  },
109
210
  {
110
211
  key: 'maxValueBigIntDigits',
@@ -112,84 +213,10 @@ export const CONFIG_FIELDS = Object.freeze([
112
213
  default: 100_000,
113
214
  min: 1,
114
215
  max: Number.MAX_SAFE_INTEGER,
115
- label: 'BigInt 最大十进制位数',
116
- labelEn: 'Max BigInt decimal digits',
216
+ label: '返回值 BigInt 十进制位数上限',
217
+ labelEn: 'Returned-value BigInt decimal-digit limit',
117
218
  description: '限制返回值中 BigInt 的十进制位数。',
118
- descriptionEn: 'Caps the decimal digits of BigInt values in a returned value.',
119
- },
120
- {
121
- key: 'maxNestedRunCodeDepth',
122
- type: 'integer',
123
- default: 8,
124
- min: 1,
125
- max: Number.MAX_SAFE_INTEGER,
126
- label: 'code.run 最大递归深度',
127
- labelEn: 'Max code.run recursion depth',
128
- description: '限制 code.run 的嵌套层数。',
129
- descriptionEn: 'Caps how deeply code.run may nest.',
130
- },
131
- {
132
- key: 'canonicalizeToolCalls',
133
- type: 'boolean',
134
- default: true,
135
- label: '规范顶层 native 误调',
136
- labelEn: 'Canonicalize top-level native mis-calls',
137
- description: '修正可以明确识别的顶层 native 误调。',
138
- descriptionEn: 'Repairs top-level native mis-calls that can be identified unambiguously.',
139
- },
140
- {
141
- key: 'looseTopLevelRedeclarations',
142
- type: 'boolean',
143
- default: true,
144
- label: '宽松顶层重声明',
145
- labelEn: 'Loose top-level redeclarations',
146
- description: '允许顶层 const/let 重声明已有变量。',
147
- descriptionEn: 'Allows top-level const/let to redeclare existing variables.',
148
- },
149
- {
150
- key: 'durableReplay',
151
- type: 'boolean',
152
- default: true,
153
- label: '持久重放',
154
- labelEn: 'Durable replay',
155
- description: 'worker 重启后恢复可以重建的 REPL 状态。',
156
- descriptionEn: 'Rebuilds recoverable REPL state after a worker restart.',
157
- },
158
- {
159
- key: 'autoRewriteImports',
160
- type: 'boolean',
161
- default: true,
162
- label: '自动改写 import',
163
- labelEn: 'Auto-rewrite import',
164
- description: '允许在 run_code 中使用静态 import。',
165
- descriptionEn: 'Allows static import declarations inside run_code.',
166
- },
167
- {
168
- key: 'autoStripExports',
169
- type: 'boolean',
170
- default: true,
171
- label: '自动剥离 export',
172
- labelEn: 'Auto-strip export',
173
- description: '允许在 run_code 中使用顶层 export。',
174
- descriptionEn: 'Allows top-level export modifiers inside run_code.',
175
- },
176
- {
177
- key: 'autoSplitRedeclarations',
178
- type: 'boolean',
179
- default: true,
180
- label: '自动拆分混合重声明',
181
- labelEn: 'Auto-split mixed redeclarations',
182
- description: '允许顶层解构声明同时包含新旧变量。',
183
- descriptionEn: 'Allows top-level destructuring declarations that mix new and existing variables.',
184
- },
185
- {
186
- key: 'tipsEnabled',
187
- type: 'boolean',
188
- default: true,
189
- label: '启用恢复提示',
190
- labelEn: 'Enable recovery tips',
191
- description: '在符合条件的失败后显示恢复提示。',
192
- descriptionEn: 'Shows recovery tips after qualifying failures.',
219
+ descriptionEn: 'Limits the decimal-digit count of BigInt values in one returned value.',
193
220
  },
194
221
  {
195
222
  key: 'tipCooldownMessages',
@@ -197,8 +224,8 @@ export const CONFIG_FIELDS = Object.freeze([
197
224
  default: 3,
198
225
  min: 1,
199
226
  max: Number.MAX_SAFE_INTEGER,
200
- label: '恢复提示冷却步数',
201
- labelEn: 'Recovery tip cooldown (messages)',
227
+ label: '同类恢复提示间隔 (messages)',
228
+ labelEn: 'Same-kind recovery tip interval (messages)',
202
229
  description: '同类恢复提示之间的最小间隔。',
203
230
  descriptionEn: 'The minimum gap between recovery tips of the same kind.',
204
231
  },
@@ -208,13 +235,61 @@ export const CONFIG_FIELDS = Object.freeze([
208
235
  default: 2,
209
236
  min: 1,
210
237
  max: Number.MAX_SAFE_INTEGER,
211
- label: '恢复提示升级失败次数',
212
- labelEn: 'Recovery tip escalation failures',
238
+ label: '详细恢复提示阈值 (failures)',
239
+ labelEn: 'Detailed recovery tip threshold (failures)',
213
240
  description: '连续失败达到此次数后显示更详细的恢复提示。',
214
241
  descriptionEn: 'After this many consecutive failures, a more detailed recovery tip is shown.',
215
242
  },
216
243
  ])
217
244
 
245
+ /** Presentation groups keep high-attention decisions separate from advanced policy and limits. */
246
+ export const CONFIG_GROUPS = Object.freeze([
247
+ {
248
+ key: 'core',
249
+ label: '常用与兼容性',
250
+ labelEn: 'Common and compatibility',
251
+ fields: Object.freeze(['enabled', 'enhancedToolView', 'autoDescribeRunCode', 'canonicalizeToolCalls']),
252
+ },
253
+ {
254
+ key: 'optional',
255
+ label: '可选能力',
256
+ labelEn: 'Optional capabilities',
257
+ fields: Object.freeze(['cordisToolsEnabled']),
258
+ },
259
+ {
260
+ key: 'advanced',
261
+ label: '高级行为',
262
+ labelEn: 'Advanced behavior',
263
+ fields: Object.freeze([
264
+ 'looseTopLevelRedeclarations',
265
+ 'looseTopLevelFunctionClassRedeclarations',
266
+ 'autoRewriteImports',
267
+ 'autoStripExports',
268
+ 'autoSplitRedeclarations',
269
+ 'durableReplay',
270
+ 'tipsEnabled',
271
+ ]),
272
+ },
273
+ {
274
+ key: 'limits',
275
+ label: '资源限制',
276
+ labelEn: 'Resource limits',
277
+ fields: Object.freeze([
278
+ 'computeMs',
279
+ 'maxWallMs',
280
+ 'maxOldGenerationSizeMb',
281
+ 'maxNestedRunCodeDepth',
282
+ 'maxOutputBytes',
283
+ 'maxValueNodes',
284
+ 'maxValueEdges',
285
+ 'maxValueArrayLength',
286
+ 'maxValueBigIntDigits',
287
+ 'tipCooldownMessages',
288
+ 'tipEscalationFailures',
289
+ ]),
290
+ },
291
+ ])
292
+
218
293
  /** Resolved defaults, derived once from the field definitions. */
219
294
  export const CONFIG_DEFAULTS = Object.freeze(
220
295
  Object.fromEntries(CONFIG_FIELDS.map(field => [field.key, field.default])),
@@ -6,6 +6,11 @@ import { projectProgramSdk } from './sdk-projection.js'
6
6
  import { EDIT_RUN_CODE } from './edit-transport-owner.js'
7
7
  import { RUN_CODE } from './runtime-bridge-owner.js'
8
8
  import { isRecord } from './record-utils.js'
9
+ import { missingDescriptionPath } from './failure-reporting.js'
10
+ import {
11
+ generatedRunCodeExecutionArguments,
12
+ generatedRunCodeDescriptionMeta,
13
+ } from './run-code-description.js'
9
14
 
10
15
  const RUN_CODE_TOOL_DESCRIPTION = 'Evaluate the next TypeScript cell in this session-bound persistent REPL. Earlier top-level bindings remain available, so this call extends the current environment instead of creating a fresh one. Use `code` for the async-function body and `description` for its short UI summary. Successful image-bearing subtool results are attached after the cell.'
11
16
  const RUN_CODE_CODE_DESCRIPTION = 'Code for the next REPL cell, parsed as the body of an async TypeScript function.'
@@ -147,6 +152,7 @@ export function createDirectSurfaceOwner({
147
152
  const sessions = new Map()
148
153
  const cordisRecovery = createCordisRecoveryPolicy(runtimeConfig.cordisToolsEnabled)
149
154
  let currentCanonicalizeToolCalls = canonicalizeToolCalls
155
+ let currentAutoDescribeRunCode = runtimeConfig.autoDescribeRunCode
150
156
  const tipConfig = {
151
157
  enabled: runtimeConfig.tipsEnabled,
152
158
  cooldownMessages: runtimeConfig.tipCooldownMessages,
@@ -187,9 +193,9 @@ export function createDirectSurfaceOwner({
187
193
  return undefined
188
194
  }
189
195
 
190
- const rememberRequest = (id, signal, presentation, nativeSchemas) => {
196
+ const rememberRequest = (id, signal, presentation, nativeSchemas, autoDescribeRunCode) => {
191
197
  const owner = sessionOwner(id)
192
- const request = { presentation, nativeSchemas, owner }
198
+ const request = { presentation, nativeSchemas, autoDescribeRunCode, owner }
193
199
  owner.latestRequest = request
194
200
  if (signal !== undefined) canonicalRequests.set(signal, request)
195
201
  }
@@ -211,12 +217,15 @@ export function createDirectSurfaceOwner({
211
217
  return Object.freeze({
212
218
  reconfigure(nextConfig) {
213
219
  currentCanonicalizeToolCalls = nextConfig.canonicalizeToolCalls
220
+ currentAutoDescribeRunCode = nextConfig.autoDescribeRunCode
214
221
  cordisRecovery.reconfigure(nextConfig.cordisToolsEnabled)
215
222
  tipConfig.enabled = nextConfig.tipsEnabled
216
223
  tipConfig.cooldownMessages = nextConfig.tipCooldownMessages
217
224
  tipConfig.escalationFailures = nextConfig.tipEscalationFailures
218
225
  },
219
226
  async assemble(initialAssembly, context, next) {
227
+ // Keep schema projection and later execution on one configuration snapshot.
228
+ const autoDescribeRunCode = currentAutoDescribeRunCode
220
229
  const agent = context?.agent
221
230
  const id = sessionId(agent)
222
231
  const initialState = presentationState(initialAssembly)
@@ -252,7 +261,7 @@ export function createDirectSurfaceOwner({
252
261
  throw new Error(`ptc-plus: ${presentation} agent composition assembled without run_code`)
253
262
  }
254
263
  if (id !== undefined) {
255
- rememberRequest(id, requestSignal, 'native', new Map())
264
+ rememberRequest(id, requestSignal, 'native', new Map(), autoDescribeRunCode)
256
265
  }
257
266
  return assembly
258
267
  }
@@ -300,14 +309,16 @@ export function createDirectSurfaceOwner({
300
309
  && schema.name !== RUN_CODE && schema.name !== EDIT_RUN_CODE)
301
310
  .map(schema => [schema.name, schema]))
302
311
  : new Map()
303
- rememberRequest(id, requestSignal, presentation, nativeSchemas)
312
+ rememberRequest(id, requestSignal, presentation, nativeSchemas, autoDescribeRunCode)
304
313
  }
305
314
 
306
315
  return {
307
316
  ...assembly,
308
317
  tools: sessionPtcProjection
309
318
  ? directTools
310
- : directTools.map(tool => tool?.name === RUN_CODE ? adaptRunCodeSchema(tool) : tool),
319
+ : directTools.map(tool => tool?.name === RUN_CODE
320
+ ? adaptRunCodeSchema(tool)
321
+ : tool),
311
322
  sections,
312
323
  contexts,
313
324
  }
@@ -343,6 +354,46 @@ export function createDirectSurfaceOwner({
343
354
  if (policy?.presentation !== 'ptc') return undefined
344
355
  return rejection(`tool ${exec.name} is not a direct PTC tool; use run_code or edit_run_code directly, and call native tools from inside run_code`)
345
356
  },
357
+ executionArguments(exec) {
358
+ const id = sessionId(exec.agent)
359
+ const policy = executionPolicy(id, exec.callId)
360
+ ?? requestPolicy(exec?.signal, id)
361
+ const originalArguments = exec.arguments
362
+ if (exec.name !== RUN_CODE || exec.parent !== undefined
363
+ || policy?.autoDescribeRunCode !== true || policy.presentation === 'native'
364
+ || !isRecord(originalArguments) || Object.hasOwn(originalArguments, 'description')
365
+ || typeof originalArguments.code !== 'string') {
366
+ return originalArguments
367
+ }
368
+ return generatedRunCodeExecutionArguments(originalArguments)
369
+ },
370
+ argumentDiagnostic(exec, result) {
371
+ // DSH uses success-result identity to avoid re-running output projectors
372
+ // after this middleware's settlement scope has ended.
373
+ if (exec?.name !== RUN_CODE || result?.isError !== true) return result
374
+ const meta = generatedRunCodeDescriptionMeta(exec.arguments, result.meta)
375
+ const diagnosed = meta === result.meta
376
+ ? result
377
+ : { ...result, ...(meta === undefined ? {} : { meta }) }
378
+ const missingPath = missingDescriptionPath(diagnosed.error)
379
+ if (missingPath === undefined) return diagnosed
380
+ const id = sessionId(exec.agent)
381
+ const policy = executionPolicy(id, exec.callId)
382
+ ?? requestPolicy(exec?.signal, id)
383
+ const outer = missingPath === 'description'
384
+ && policy?.autoDescribeRunCode !== true
385
+ && !(isRecord(exec.arguments) && typeof exec.arguments.description === 'string')
386
+ const context = {
387
+ name: 'tools:ptc-plus-run-code-arguments',
388
+ text: outer
389
+ ? 'The run_code outer transport arguments are invalid at JSON path $.description. Add a sibling string `description` field to run_code; a description nested inside a native-tool argument does not satisfy this outer requirement.'
390
+ : `A nested native-tool argument is missing required property "${missingPath}". Add a string description at that path in the native-tool arguments; the outer run_code description does not satisfy this nested requirement.`,
391
+ }
392
+ return {
393
+ ...diagnosed,
394
+ additionalContexts: [...diagnosed.additionalContexts ?? [], context],
395
+ }
396
+ },
346
397
  handleResult(exec) {
347
398
  const id = sessionId(exec.agent)
348
399
  if (id === undefined || typeof exec.callId !== 'string') return
@@ -20,6 +20,10 @@ import {
20
20
  import { editTargetForCall, projectSessionLog } from './session-log-view.js'
21
21
  import { RUN_CODE } from './runtime-bridge-owner.js'
22
22
  import { isRecord } from './record-utils.js'
23
+ import {
24
+ validatedReplMemorySnapshot,
25
+ withReplMemorySnapshot,
26
+ } from './repl-memory-projection.js'
23
27
 
24
28
  export const EDIT_RUN_CODE = 'edit_run_code'
25
29
 
@@ -52,6 +56,7 @@ function derivedEditResult(inner) {
52
56
  export function createEditTransportOwner(ctx, {
53
57
  durableReplay,
54
58
  executeTentative,
59
+ presentationGeneration,
55
60
  sessionId,
56
61
  toolSchemasForAgent,
57
62
  }) {
@@ -91,7 +96,9 @@ export function createEditTransportOwner(ctx, {
91
96
  if (derived.recoveryBoundaries !== undefined) {
92
97
  meta[RECOVERY_BOUNDARY_KEY] = normalizeRecoveryBoundaries(derived.recoveryBoundaries)
93
98
  }
94
- return meta
99
+ return derived.replMemory === undefined
100
+ ? meta
101
+ : withReplMemorySnapshot(meta, derived.replMemory, presentationGeneration)
95
102
  }
96
103
  definition.output = {
97
104
  schema: {
@@ -140,7 +147,7 @@ export function createEditTransportOwner(ctx, {
140
147
  if (typeof ctx.tools.execute !== 'function') {
141
148
  throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
142
149
  }
143
- const persistedCallSeq = currentDurableReplay ? persistedTargetCallSeq : undefined
150
+ const persistedCallSeq = persistedTargetCallSeq
144
151
  const id = sessionId(agent)
145
152
  let claims = editClaims.get(id)
146
153
  if (claims === undefined) {
@@ -178,12 +185,14 @@ export function createEditTransportOwner(ctx, {
178
185
  && Object.hasOwn(inner.meta, RECOVERY_BOUNDARY_KEY)
179
186
  ? normalizeRecoveryBoundaries(inner.meta[RECOVERY_BOUNDARY_KEY])
180
187
  : undefined
188
+ const replMemory = validatedReplMemorySnapshot(inner?.meta, presentationGeneration)
181
189
  const value = derivedEditResult(inner)
182
190
  const derived = {
183
191
  targetCallSeq: target.callSeq,
184
192
  journal,
185
193
  rewrites,
186
194
  recoveryBoundaries,
195
+ replMemory,
187
196
  code: edited.code,
188
197
  description: edited.description,
189
198
  }
@@ -64,6 +64,21 @@ export function oneLineMessage(error) {
64
64
  return firstLine(messageOf(error), 'Unknown error').replace(/\s+\(\d+:\d+\)$/, '')
65
65
  }
66
66
 
67
+ /** Return DSH's reported missing-description property path when it is actionable. */
68
+ export function missingDescriptionPath(error) {
69
+ const violations = messageOf(error).matchAll(/missing required property ["']([^"']+)["']/g)
70
+ for (const match of violations) {
71
+ const path = match[1]
72
+ if (path === 'description' || path.endsWith('.description')) return path
73
+ }
74
+ return undefined
75
+ }
76
+
77
+ /** Identify DSH's stable missing-description validation fact without parsing arbitrary errors. */
78
+ export function hasMissingDescriptionError(error) {
79
+ return missingDescriptionPath(error) !== undefined
80
+ }
81
+
67
82
  /** Keep only the newest log entries so an output-limit error cannot flood the model. */
68
83
  export function limitLogs(logs) {
69
84
  let bytes = 0
@@ -366,6 +366,7 @@ async function runCell(message) {
366
366
  pendingVolatileReason = undefined
367
367
  activeExecution = execution
368
368
  const cellGlobals = []
369
+ const committedRedeclarations = new Set()
369
370
 
370
371
  try {
371
372
  let completion
@@ -376,6 +377,13 @@ async function runCell(message) {
376
377
  value: CellReturn,
377
378
  })
378
379
  cellGlobals.push(message.returnSignal)
380
+ Object.defineProperty(context, message.commitSignal, {
381
+ configurable: true,
382
+ value(name) {
383
+ committedRedeclarations.add(name)
384
+ },
385
+ })
386
+ cellGlobals.push(message.commitSignal)
379
387
  for (const load of message.moduleLoads ?? []) {
380
388
  let namespace
381
389
  try {
@@ -421,6 +429,7 @@ async function runCell(message) {
421
429
  ...(position === undefined ? {} : { position }),
422
430
  ...(detail.cause === undefined ? {} : { cause: detail.cause }),
423
431
  ...completionDurability(execution),
432
+ committedRedeclarations: [...committedRedeclarations],
424
433
  })
425
434
  return
426
435
  }
@@ -437,6 +446,7 @@ async function runCell(message) {
437
446
  hasValue: completion.hasValue,
438
447
  ...(encodedValue === undefined ? {} : { value: encodedValue }),
439
448
  ...completionDurability(execution),
449
+ committedRedeclarations: [...committedRedeclarations],
440
450
  }
441
451
  } catch (error) {
442
452
  const detail = messageOf(error)
@@ -446,6 +456,7 @@ async function runCell(message) {
446
456
  logs: execution.logs,
447
457
  invalidOutput: detail,
448
458
  ...completionDurability(execution),
459
+ committedRedeclarations: [...committedRedeclarations],
449
460
  }
450
461
  }
451
462
  channel.postMessage(response)