dsh-ptc-plus 0.2.4 → 0.3.1

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 (37) hide show
  1. package/README.en.md +171 -0
  2. package/README.md +79 -69
  3. package/assets/ptc-plus-repair-en.png +0 -0
  4. package/assets/ptc-plus-settings-en.png +0 -0
  5. package/assets/ptc-plus-settings-zh.png +0 -0
  6. package/client.js +1134 -144
  7. package/docs/adr/0019-plugin-settings-and-kill-switch.md +19 -4
  8. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  9. package/docs/adr/0022-stage-npm-releases-with-oidc.md +2 -2
  10. package/docs/architecture.md +10 -9
  11. package/docs/client-ui.md +29 -11
  12. package/docs/installation.md +11 -3
  13. package/docs/runtime-reference.md +17 -11
  14. package/docs/value-wire.md +1 -1
  15. package/index.js +30 -1
  16. package/internal/cell-analysis.js +20 -3
  17. package/internal/cell-rewriter.js +36 -2
  18. package/internal/config-spec.js +182 -117
  19. package/internal/direct-surface-owner.js +56 -5
  20. package/internal/edit-transport-owner.js +10 -1
  21. package/internal/failure-reporting.js +15 -0
  22. package/internal/rejected-cell-editor.js +21 -12
  23. package/internal/repl-memory-projection.js +277 -0
  24. package/internal/run-code-description.js +34 -0
  25. package/internal/runtime-bridge-owner.js +63 -15
  26. package/internal/runtime-config.js +2 -0
  27. package/internal/session-cell-executor.js +14 -4
  28. package/internal/session-events.js +7 -0
  29. package/internal/session-journal.js +3 -2
  30. package/internal/session-log-view.js +2 -1
  31. package/internal/session-runtime.js +19 -2
  32. package/internal/session-state.js +83 -4
  33. package/package.json +4 -3
  34. package/src/client-activity.js +472 -0
  35. package/src/client.js +456 -20
  36. package/README.zh.md +0 -161
  37. /package/assets/{ptc-plus-repair.png → ptc-plus-repair-zh.png} +0 -0
@@ -20,14 +20,95 @@ 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: 'autoRewriteImports',
70
+ type: 'boolean',
71
+ default: true,
72
+ label: '支持静态 import 声明',
73
+ labelEn: 'Support static import declarations',
74
+ description: '允许在 run_code 中使用静态 import,并保留模块解析和只读 binding 语义。',
75
+ descriptionEn: 'Allows static import declarations in run_code while preserving module resolution and read-only binding semantics.',
76
+ },
77
+ {
78
+ key: 'autoStripExports',
79
+ type: 'boolean',
80
+ default: true,
81
+ label: '支持顶层 export 修饰符',
82
+ labelEn: 'Support top-level export modifiers',
83
+ description: '允许在 run_code 中使用顶层 export,并保留对应的本地声明或模块副作用。',
84
+ descriptionEn: 'Allows top-level export syntax in run_code while preserving the corresponding local declaration or module side effect.',
85
+ },
86
+ {
87
+ key: 'autoSplitRedeclarations',
88
+ type: 'boolean',
89
+ default: true,
90
+ label: '支持混合解构重声明',
91
+ labelEn: 'Support mixed destructuring redeclarations',
92
+ description: '允许顶层解构声明同时包含新变量和已有变量。',
93
+ descriptionEn: 'Allows top-level destructuring declarations to contain both new and existing variables.',
94
+ },
95
+ {
96
+ key: 'durableReplay',
97
+ type: 'boolean',
98
+ default: true,
99
+ label: '启用持久 REPL 恢复',
100
+ labelEn: 'Enable durable REPL recovery',
101
+ description: 'worker 重启后从 session journal 重建可恢复的 REPL 状态。',
102
+ descriptionEn: 'Rebuilds recoverable REPL state from the session journal after a worker restart.',
103
+ },
104
+ {
105
+ key: 'tipsEnabled',
106
+ type: 'boolean',
107
+ default: true,
108
+ label: '启用失败恢复提示',
109
+ labelEn: 'Enable failure recovery tips',
110
+ description: '在符合条件的失败后显示有冷却间隔的恢复提示。',
111
+ descriptionEn: 'Shows recovery tips with a cooldown after qualifying failures.',
31
112
  },
32
113
  {
33
114
  key: 'computeMs',
@@ -35,10 +116,10 @@ export const CONFIG_FIELDS = Object.freeze([
35
116
  default: 60_000,
36
117
  min: 1,
37
118
  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.',
119
+ label: '单 cell CPU 时间上限 (ms)',
120
+ labelEn: 'CPU time limit per cell (ms)',
121
+ description: '同步计算超过该上限时中断当前 cell。',
122
+ descriptionEn: 'Interrupts the current cell when synchronous computation exceeds this limit.',
42
123
  },
43
124
  {
44
125
  key: 'maxWallMs',
@@ -46,32 +127,43 @@ export const CONFIG_FIELDS = Object.freeze([
46
127
  default: 600_000,
47
128
  min: 1,
48
129
  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.',
130
+ label: '单 cell 总耗时上限 (ms)',
131
+ labelEn: 'Elapsed time limit per cell (ms)',
132
+ description: '限制完整 cell 执行的耗时,包括异步等待。',
133
+ descriptionEn: 'Limits the complete cell execution time, including asynchronous waits.',
53
134
  },
54
135
  {
55
- key: 'maxOutputBytes',
136
+ key: 'maxOldGenerationSizeMb',
56
137
  type: 'integer',
57
- default: 64 * 1024 * 1024,
138
+ default: 512,
58
139
  min: 1,
59
140
  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.',
141
+ label: '每个 worker 旧生代内存上限 (MiB)',
142
+ labelEn: 'Old-generation memory limit per worker (MiB)',
143
+ description: '限制每个 worker 的 V8 旧生代;活动 worker 存在时不能修改。',
144
+ descriptionEn: 'Limits the V8 old generation for each worker; this setting cannot change while a worker is active.',
64
145
  },
65
146
  {
66
- key: 'maxOldGenerationSizeMb',
147
+ key: 'maxNestedRunCodeDepth',
67
148
  type: 'integer',
68
- default: 512,
149
+ default: 8,
150
+ min: 1,
151
+ max: Number.MAX_SAFE_INTEGER,
152
+ label: 'code.run 嵌套层数上限',
153
+ labelEn: 'code.run nesting depth limit',
154
+ description: '限制单个顶层 cell 中 code.run 的最大嵌套层数。',
155
+ descriptionEn: 'Limits the maximum code.run nesting depth within one top-level cell.',
156
+ },
157
+ {
158
+ key: 'maxOutputBytes',
159
+ type: 'integer',
160
+ default: 64 * 1024 * 1024,
69
161
  min: 1,
70
162
  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.',
163
+ label: '单 cell 输出上限 (bytes)',
164
+ labelEn: 'Output limit per cell (bytes)',
165
+ description: '限制单个 cell 的日志和返回结果的合计大小。',
166
+ descriptionEn: 'Limits the combined size of one cell\'s logs and returned result.',
75
167
  },
76
168
  {
77
169
  key: 'maxValueNodes',
@@ -79,10 +171,10 @@ export const CONFIG_FIELDS = Object.freeze([
79
171
  default: 100_000,
80
172
  min: 1,
81
173
  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.',
174
+ label: '返回值节点数上限',
175
+ labelEn: 'Returned-value node limit',
176
+ description: '限制单次返回值的 Value Graph 节点数。',
177
+ descriptionEn: 'Limits the Value Graph node count for one returned value.',
86
178
  },
87
179
  {
88
180
  key: 'maxValueEdges',
@@ -90,10 +182,10 @@ export const CONFIG_FIELDS = Object.freeze([
90
182
  default: 1_000_000,
91
183
  min: 1,
92
184
  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.',
185
+ label: '返回值引用边数上限',
186
+ labelEn: 'Returned-value reference-edge limit',
187
+ description: '限制单次返回值的 Value Graph 引用边数。',
188
+ descriptionEn: 'Limits the Value Graph reference-edge count for one returned value.',
97
189
  },
98
190
  {
99
191
  key: 'maxValueArrayLength',
@@ -101,10 +193,10 @@ export const CONFIG_FIELDS = Object.freeze([
101
193
  default: 1_000_000,
102
194
  min: 1,
103
195
  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.',
196
+ label: '返回数组长度上限',
197
+ labelEn: 'Returned-array length limit',
198
+ description: '限制返回数组的最大声明长度。',
199
+ descriptionEn: 'Limits the maximum declared length of a returned array.',
108
200
  },
109
201
  {
110
202
  key: 'maxValueBigIntDigits',
@@ -112,84 +204,10 @@ export const CONFIG_FIELDS = Object.freeze([
112
204
  default: 100_000,
113
205
  min: 1,
114
206
  max: Number.MAX_SAFE_INTEGER,
115
- label: 'BigInt 最大十进制位数',
116
- labelEn: 'Max BigInt decimal digits',
207
+ label: '返回值 BigInt 十进制位数上限',
208
+ labelEn: 'Returned-value BigInt decimal-digit limit',
117
209
  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.',
210
+ descriptionEn: 'Limits the decimal-digit count of BigInt values in one returned value.',
193
211
  },
194
212
  {
195
213
  key: 'tipCooldownMessages',
@@ -197,8 +215,8 @@ export const CONFIG_FIELDS = Object.freeze([
197
215
  default: 3,
198
216
  min: 1,
199
217
  max: Number.MAX_SAFE_INTEGER,
200
- label: '恢复提示冷却步数',
201
- labelEn: 'Recovery tip cooldown (messages)',
218
+ label: '同类恢复提示间隔 (messages)',
219
+ labelEn: 'Same-kind recovery tip interval (messages)',
202
220
  description: '同类恢复提示之间的最小间隔。',
203
221
  descriptionEn: 'The minimum gap between recovery tips of the same kind.',
204
222
  },
@@ -208,13 +226,60 @@ export const CONFIG_FIELDS = Object.freeze([
208
226
  default: 2,
209
227
  min: 1,
210
228
  max: Number.MAX_SAFE_INTEGER,
211
- label: '恢复提示升级失败次数',
212
- labelEn: 'Recovery tip escalation failures',
229
+ label: '详细恢复提示阈值 (failures)',
230
+ labelEn: 'Detailed recovery tip threshold (failures)',
213
231
  description: '连续失败达到此次数后显示更详细的恢复提示。',
214
232
  descriptionEn: 'After this many consecutive failures, a more detailed recovery tip is shown.',
215
233
  },
216
234
  ])
217
235
 
236
+ /** Presentation groups keep high-attention decisions separate from advanced policy and limits. */
237
+ export const CONFIG_GROUPS = Object.freeze([
238
+ {
239
+ key: 'core',
240
+ label: '常用与兼容性',
241
+ labelEn: 'Common and compatibility',
242
+ fields: Object.freeze(['enabled', 'enhancedToolView', 'autoDescribeRunCode', 'canonicalizeToolCalls']),
243
+ },
244
+ {
245
+ key: 'optional',
246
+ label: '可选能力',
247
+ labelEn: 'Optional capabilities',
248
+ fields: Object.freeze(['cordisToolsEnabled']),
249
+ },
250
+ {
251
+ key: 'advanced',
252
+ label: '高级行为',
253
+ labelEn: 'Advanced behavior',
254
+ fields: Object.freeze([
255
+ 'looseTopLevelRedeclarations',
256
+ 'autoRewriteImports',
257
+ 'autoStripExports',
258
+ 'autoSplitRedeclarations',
259
+ 'durableReplay',
260
+ 'tipsEnabled',
261
+ ]),
262
+ },
263
+ {
264
+ key: 'limits',
265
+ label: '资源限制',
266
+ labelEn: 'Resource limits',
267
+ fields: Object.freeze([
268
+ 'computeMs',
269
+ 'maxWallMs',
270
+ 'maxOldGenerationSizeMb',
271
+ 'maxNestedRunCodeDepth',
272
+ 'maxOutputBytes',
273
+ 'maxValueNodes',
274
+ 'maxValueEdges',
275
+ 'maxValueArrayLength',
276
+ 'maxValueBigIntDigits',
277
+ 'tipCooldownMessages',
278
+ 'tipEscalationFailures',
279
+ ]),
280
+ },
281
+ ])
282
+
218
283
  /** Resolved defaults, derived once from the field definitions. */
219
284
  export const CONFIG_DEFAULTS = Object.freeze(
220
285
  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: {
@@ -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
@@ -252,15 +252,29 @@ function assemble(source, replacements) {
252
252
  return changed ? { code: chunks.join('') } : { error: 'edits must change at least one matched fragment' }
253
253
  }
254
254
 
255
+ function editRunCodeParameterBranch(operation, operationSchema) {
256
+ return {
257
+ type: 'object',
258
+ additionalProperties: false,
259
+ properties: {
260
+ [operation]: operationSchema,
261
+ [EXPECTED_TARGET_CALL_SEQ]: {
262
+ type: 'integer', minimum: 0,
263
+ description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
264
+ },
265
+ },
266
+ required: [operation],
267
+ }
268
+ }
269
+
255
270
  export function editRunCodeSchema() {
256
271
  return {
257
272
  name: 'edit_run_code',
258
273
  description: 'Make a small exact or regular-expression change to the most recent eligible cell captured when this edit call is dispatched, then run the complete corrected cell. A successful edit becomes the next eligible cell. Use this only when replaying the whole cell is safe. If earlier code may already have caused an external effect, use a new run_code cell and its existing variables instead; this tool does not resume at the error location. Send exactly one atomic edits or regex_edits array, plus expected_target_call_seq only when a diagnostic supplies it to bind a validated repair to its rejected cell. Choose edits for a few unique literal fragments; choose regex_edits when one counted pattern covers repeated fragments. Every resolved range must be non-overlapping in the original cell.',
259
274
  parameters: {
260
275
  type: 'object',
261
- additionalProperties: false,
262
- properties: {
263
- edits: {
276
+ oneOf: [
277
+ editRunCodeParameterBranch('edits', {
264
278
  type: 'array', minItems: 1, maxItems: EDIT_LIMITS.exactEdits,
265
279
  description: 'Atomic exact replacements, all resolved against the original target cell.',
266
280
  items: {
@@ -271,8 +285,8 @@ export function editRunCodeSchema() {
271
285
  },
272
286
  required: ['old_string', 'new_string'],
273
287
  },
274
- },
275
- regex_edits: {
288
+ }),
289
+ editRunCodeParameterBranch('regex_edits', {
276
290
  type: 'array', minItems: 1, maxItems: EDIT_LIMITS.regexEdits,
277
291
  description: 'Atomic regular-expression replacements, all matched against the original target cell.',
278
292
  items: {
@@ -285,13 +299,8 @@ export function editRunCodeSchema() {
285
299
  },
286
300
  required: ['pattern', 'flags', 'replacement', 'expected_matches'],
287
301
  },
288
- },
289
- [EXPECTED_TARGET_CALL_SEQ]: {
290
- type: 'integer', minimum: 0,
291
- description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
292
- },
293
- },
294
- oneOf: [{ required: ['edits'] }, { required: ['regex_edits'] }],
302
+ }),
303
+ ],
295
304
  },
296
305
  }
297
306
  }