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
@@ -0,0 +1,277 @@
1
+ import { isRecord } from './record-utils.js'
2
+
3
+ export const REPL_MEMORY_KEY = 'ptcPlusRepl'
4
+ export const REPL_MEMORY_META_KEY = 'dshPtcPlusBindings'
5
+
6
+ const BINDING_KINDS = new Set(['variable', 'function', 'class', 'import'])
7
+ const MAX_BINDINGS = 128
8
+ const MAX_BINDING_NAME_LENGTH = 128
9
+ const MAX_DEFINITION_SOURCE_LENGTH = 1024
10
+ const MAX_DEFINITION_SOURCE_TOTAL_LENGTH = 16 * 1024
11
+ const MAX_PENDING_REPL_CALLS = 256
12
+ const MAX_CALL_ID_LENGTH = 512
13
+ const MAX_GENERATION_LENGTH = 128
14
+ const SNAPSHOT_FIELDS = new Set(['available', 'entries', 'total', 'omitted'])
15
+ const ENTRY_FIELDS = new Set(['name', 'kind', 'definition'])
16
+ const DEFINITION_FIELDS = new Set(['source', 'line', 'column'])
17
+ const MEMORY_META_FIELDS = new Set(['version', 'generation', 'memory'])
18
+ const PROJECTION_STATE_FIELDS = new Set(['generation', 'memory', 'pendingReplCalls'])
19
+ const PENDING_CALL_FIELDS = new Set(['callId', 'seq'])
20
+ const REPL_TOOL_NAMES = new Set(['run_code', 'edit_run_code'])
21
+ const REPL_MEMORY_META_VERSION = 3
22
+
23
+ function exactFields(value, fields) {
24
+ if (!isRecord(value)) return false
25
+ const keys = Reflect.ownKeys(value)
26
+ return keys.length === fields.size && keys.every(key => (
27
+ typeof key === 'string' && fields.has(key)
28
+ && Object.prototype.propertyIsEnumerable.call(value, key)
29
+ ))
30
+ }
31
+
32
+ const EMPTY_REPL_MEMORY = Object.freeze({
33
+ available: false,
34
+ entries: Object.freeze([]),
35
+ total: 0,
36
+ omitted: 0,
37
+ })
38
+
39
+ export function unavailableReplMemorySnapshot() {
40
+ return EMPTY_REPL_MEMORY
41
+ }
42
+
43
+ function normalizeGeneration(value) {
44
+ if (typeof value !== 'string' || value.length === 0 || value.length > MAX_GENERATION_LENGTH) {
45
+ throw new Error('invalid dsh-ptc-plus REPL memory generation')
46
+ }
47
+ return value
48
+ }
49
+
50
+ function normalizeDefinition(value) {
51
+ if (!exactFields(value, DEFINITION_FIELDS)
52
+ || typeof value.source !== 'string' || value.source.length === 0
53
+ || value.source.length > MAX_DEFINITION_SOURCE_LENGTH
54
+ || !Number.isSafeInteger(value.line) || value.line < 1
55
+ || !Number.isSafeInteger(value.column) || value.column < 1) {
56
+ throw new Error('invalid dsh-ptc-plus REPL binding definition')
57
+ }
58
+ return Object.freeze({ source: value.source, line: value.line, column: value.column })
59
+ }
60
+
61
+ function normalizeBinding(value) {
62
+ if (!exactFields(value, ENTRY_FIELDS) || typeof value.name !== 'string'
63
+ || value.name.length === 0 || value.name.length > MAX_BINDING_NAME_LENGTH
64
+ || !BINDING_KINDS.has(value.kind)) {
65
+ throw new Error('invalid dsh-ptc-plus REPL memory binding')
66
+ }
67
+ return Object.freeze({
68
+ name: value.name,
69
+ kind: value.kind,
70
+ definition: normalizeDefinition(value.definition),
71
+ })
72
+ }
73
+
74
+ /** Produce bounded, value-independent presentation metadata from the live binding catalog. */
75
+ export function createReplMemorySnapshot(bindings) {
76
+ const all = Array.isArray(bindings) ? bindings : []
77
+ const candidates = []
78
+ const names = new Set()
79
+ for (const binding of all) {
80
+ try {
81
+ const normalized = normalizeBinding(binding)
82
+ if (names.has(normalized.name)) continue
83
+ names.add(normalized.name)
84
+ candidates.push(normalized)
85
+ } catch {}
86
+ }
87
+ const entries = []
88
+ let sourceLength = 0
89
+ for (const binding of candidates) {
90
+ if (entries.length >= MAX_BINDINGS) break
91
+ if (sourceLength + binding.definition.source.length > MAX_DEFINITION_SOURCE_TOTAL_LENGTH) continue
92
+ entries.push(binding)
93
+ sourceLength += binding.definition.source.length
94
+ }
95
+ return Object.freeze({
96
+ available: true,
97
+ entries: Object.freeze(entries),
98
+ total: candidates.length,
99
+ omitted: candidates.length - entries.length,
100
+ })
101
+ }
102
+
103
+ /** Validate one complete session-projection value without accepting unknown fields. */
104
+ export function normalizeReplMemorySnapshot(value) {
105
+ if (!exactFields(value, SNAPSHOT_FIELDS) || typeof value.available !== 'boolean'
106
+ || !Array.isArray(value.entries) || value.entries.length > MAX_BINDINGS
107
+ || !Number.isSafeInteger(value.total) || value.total < 0
108
+ || !Number.isSafeInteger(value.omitted) || value.omitted < 0
109
+ || value.total !== value.entries.length + value.omitted) {
110
+ throw new Error('invalid dsh-ptc-plus REPL memory snapshot')
111
+ }
112
+ if (!value.available && (value.total !== 0 || value.entries.length !== 0)) {
113
+ throw new Error('unavailable dsh-ptc-plus REPL memory snapshot must be empty')
114
+ }
115
+ const names = new Set()
116
+ let sourceLength = 0
117
+ const entries = value.entries.map((entry) => {
118
+ const normalized = normalizeBinding(entry)
119
+ if (names.has(normalized.name)) {
120
+ throw new Error('invalid dsh-ptc-plus REPL memory binding')
121
+ }
122
+ sourceLength += normalized.definition.source.length
123
+ if (sourceLength > MAX_DEFINITION_SOURCE_TOTAL_LENGTH) {
124
+ throw new Error('dsh-ptc-plus REPL binding definitions exceed the presentation budget')
125
+ }
126
+ names.add(normalized.name)
127
+ return normalized
128
+ })
129
+ return Object.freeze({
130
+ available: value.available,
131
+ entries: Object.freeze(entries),
132
+ total: value.total,
133
+ omitted: value.omitted,
134
+ })
135
+ }
136
+
137
+ function normalizeReplMemoryMetadata(value) {
138
+ if (!exactFields(value, MEMORY_META_FIELDS) || value.version !== REPL_MEMORY_META_VERSION) {
139
+ throw new Error('invalid dsh-ptc-plus REPL memory metadata')
140
+ }
141
+ return Object.freeze({
142
+ version: REPL_MEMORY_META_VERSION,
143
+ generation: normalizeGeneration(value.generation),
144
+ memory: normalizeReplMemorySnapshot(value.memory),
145
+ })
146
+ }
147
+
148
+ /** Read current-owner presentation metadata without affecting tool settlement. */
149
+ export function validatedReplMemorySnapshot(meta, generation) {
150
+ if (!isRecord(meta) || !Object.hasOwn(meta, REPL_MEMORY_META_KEY)) return undefined
151
+ try {
152
+ const normalized = normalizeReplMemoryMetadata(meta[REPL_MEMORY_META_KEY])
153
+ return normalized.generation === normalizeGeneration(generation)
154
+ ? normalized.memory
155
+ : undefined
156
+ } catch {
157
+ return undefined
158
+ }
159
+ }
160
+
161
+ function projectionState(generation, memory, pendingReplCalls) {
162
+ return Object.freeze({
163
+ generation,
164
+ memory,
165
+ pendingReplCalls: pendingReplCalls === null
166
+ ? null
167
+ : Object.freeze(pendingReplCalls),
168
+ })
169
+ }
170
+
171
+ function emptyProjectionState(generation) {
172
+ return projectionState(normalizeGeneration(generation), EMPTY_REPL_MEMORY, [])
173
+ }
174
+
175
+ function normalizeProjectionState(value, generation) {
176
+ if (!exactFields(value, PROJECTION_STATE_FIELDS)) {
177
+ throw new Error('invalid dsh-ptc-plus REPL memory projection state')
178
+ }
179
+ const storedGeneration = normalizeGeneration(value.generation)
180
+ const currentGeneration = normalizeGeneration(generation)
181
+ if (storedGeneration !== currentGeneration) return emptyProjectionState(currentGeneration)
182
+ const memory = normalizeReplMemorySnapshot(value.memory)
183
+ if (value.pendingReplCalls === null) {
184
+ if (memory.available) {
185
+ throw new Error('untracked dsh-ptc-plus REPL calls require unavailable memory')
186
+ }
187
+ return projectionState(currentGeneration, memory, null)
188
+ }
189
+ if (!Array.isArray(value.pendingReplCalls)
190
+ || value.pendingReplCalls.length > MAX_PENDING_REPL_CALLS) {
191
+ throw new Error('invalid dsh-ptc-plus pending REPL calls')
192
+ }
193
+ let previousSeq
194
+ const callIds = new Set()
195
+ const pendingReplCalls = value.pendingReplCalls.map((pending) => {
196
+ if (!exactFields(pending, PENDING_CALL_FIELDS)
197
+ || typeof pending.callId !== 'string' || pending.callId.length === 0
198
+ || pending.callId.length > MAX_CALL_ID_LENGTH || callIds.has(pending.callId)
199
+ || !Number.isSafeInteger(pending.seq) || pending.seq < 0
200
+ || (previousSeq !== undefined && previousSeq >= pending.seq)) {
201
+ throw new Error('invalid dsh-ptc-plus pending REPL call')
202
+ }
203
+ callIds.add(pending.callId)
204
+ previousSeq = pending.seq
205
+ return Object.freeze({ callId: pending.callId, seq: pending.seq })
206
+ })
207
+ return projectionState(currentGeneration, memory, pendingReplCalls)
208
+ }
209
+
210
+ function eventCallId(value) {
211
+ return typeof value === 'string' && value.length > 0 && value.length <= MAX_CALL_ID_LENGTH
212
+ ? value
213
+ : undefined
214
+ }
215
+
216
+ function trackReplCall(state, event) {
217
+ if (!REPL_TOOL_NAMES.has(event.data?.name)) return state
218
+ const seq = event.seq
219
+ const callId = eventCallId(event.data?.callId)
220
+ if (!Number.isSafeInteger(seq) || seq < 0 || callId === undefined) return state
221
+ const pending = state.pendingReplCalls
222
+ if (pending === null || pending.some(call => call.callId === callId || call.seq === seq)) return state
223
+ if (pending.length >= MAX_PENDING_REPL_CALLS) {
224
+ return projectionState(state.generation, EMPTY_REPL_MEMORY, null)
225
+ }
226
+ return projectionState(state.generation, state.memory, [
227
+ ...pending,
228
+ Object.freeze({ callId, seq }),
229
+ ].sort((left, right) => left.seq - right.seq))
230
+ }
231
+
232
+ function settleReplCall(state, event, generation) {
233
+ if (event.surfaceOp !== undefined && event.surfaceOp !== 'append') return state
234
+ const pending = state.pendingReplCalls
235
+ if (pending === null) return state
236
+ const callId = eventCallId(event.data?.message?.source?.callId)
237
+ if (callId === undefined) return state
238
+ const index = pending.findIndex(call => call.callId === callId)
239
+ if (index < 0) return state
240
+ const remaining = pending.filter((_call, pendingIndex) => pendingIndex !== index)
241
+ const memory = validatedReplMemorySnapshot(event.data?.meta, generation) ?? state.memory
242
+ return projectionState(state.generation, memory, remaining)
243
+ }
244
+
245
+ /** Merge the binding snapshot into private, model-invisible result presentation metadata. */
246
+ export function withReplMemorySnapshot(meta, snapshot, generation) {
247
+ const base = isRecord(meta) ? { ...meta } : meta === undefined ? {} : { value: meta }
248
+ base[REPL_MEMORY_META_KEY] = normalizeReplMemoryMetadata({
249
+ version: REPL_MEMORY_META_VERSION,
250
+ generation: normalizeGeneration(generation),
251
+ memory: snapshot,
252
+ })
253
+ return base
254
+ }
255
+
256
+ /** Build the current runtime owner's value-independent session projection. */
257
+ export function createReplMemoryProjection(generation) {
258
+ const currentGeneration = normalizeGeneration(generation)
259
+ return Object.freeze({
260
+ key: REPL_MEMORY_KEY,
261
+ stateVersion: 3,
262
+ stateSchema: Object.freeze({
263
+ parse: value => normalizeProjectionState(value, currentGeneration),
264
+ }),
265
+ init: () => emptyProjectionState(currentGeneration),
266
+ apply(state, event) {
267
+ if (event?.type === 'session/end-seed') return emptyProjectionState(currentGeneration)
268
+ if (event?.type === 'tool/call') return trackReplCall(state, event)
269
+ if (event?.type === 'tool/result') return settleReplCall(state, event, currentGeneration)
270
+ return state
271
+ },
272
+ wire: Object.freeze({
273
+ viewSchema: Object.freeze({ parse: normalizeReplMemorySnapshot }),
274
+ view: state => state.memory,
275
+ }),
276
+ })
277
+ }
@@ -0,0 +1,34 @@
1
+ const GENERATED_RUN_CODE_DESCRIPTION = 'Execute the next TypeScript cell in this session'
2
+ export const GENERATED_RUN_CODE_DESCRIPTION_KEY = 'dshPtcPlusRunCodeDescription'
3
+
4
+ // Keep presentation provenance outside the canonical JSON argument graph.
5
+ const generatedArguments = new WeakSet()
6
+
7
+ export function markGeneratedRunCodeArguments(argumentsValue) {
8
+ if (argumentsValue !== null
9
+ && (typeof argumentsValue === 'object' || typeof argumentsValue === 'function')) {
10
+ generatedArguments.add(argumentsValue)
11
+ }
12
+ return argumentsValue
13
+ }
14
+
15
+ export function generatedRunCodeExecutionArguments(argumentsValue) {
16
+ markGeneratedRunCodeArguments(argumentsValue)
17
+ return markGeneratedRunCodeArguments({
18
+ ...argumentsValue,
19
+ description: GENERATED_RUN_CODE_DESCRIPTION,
20
+ })
21
+ }
22
+
23
+ export function generatedRunCodeDescriptionMeta(argumentsValue, meta) {
24
+ if (!generatedArguments.has(argumentsValue)) return meta
25
+ const base = meta !== null && typeof meta === 'object' && !Array.isArray(meta)
26
+ ? meta
27
+ : meta === undefined ? {} : { value: meta }
28
+ return {
29
+ ...base,
30
+ [GENERATED_RUN_CODE_DESCRIPTION_KEY]: GENERATED_RUN_CODE_DESCRIPTION,
31
+ }
32
+ }
33
+
34
+ export { GENERATED_RUN_CODE_DESCRIPTION }
@@ -19,6 +19,8 @@ import {
19
19
  toolCapabilityMetadata,
20
20
  } from './program-bindings.js'
21
21
  import { deepFreeze } from './record-utils.js'
22
+ import { withReplMemorySnapshot } from './repl-memory-projection.js'
23
+ import { generatedRunCodeDescriptionMeta } from './run-code-description.js'
22
24
 
23
25
  export const RUN_CODE = 'run_code'
24
26
 
@@ -123,6 +125,7 @@ export function createRuntimeBridgeOwner({
123
125
  ctx,
124
126
  sessionConfig,
125
127
  maxNestedRunCodeDepth,
128
+ presentationGeneration,
126
129
  sessionId,
127
130
  toolSchemasForAgent,
128
131
  }) {
@@ -247,7 +250,7 @@ export function createRuntimeBridgeOwner({
247
250
  return { request: { ...request, bindings: projected }, release }
248
251
  }
249
252
 
250
- const patchResultMetadata = (agent) => {
253
+ const patchRunCodeDefinition = (agent) => {
251
254
  const definition = ctx.tools.get(RUN_CODE, agent)
252
255
  if (definition === undefined) {
253
256
  throw new Error('ptc-plus: run_code definition is unavailable for the owning session')
@@ -257,28 +260,65 @@ export function createRuntimeBridgeOwner({
257
260
  }
258
261
  if (patchedDefinitions.has(definition)) return
259
262
  const output = definition.output
260
- const original = output.presentationMeta
261
- const patched = (args, value) => {
262
- if (!active) return original === undefined ? undefined : original(args, value)
263
- const base = original === undefined ? undefined : original(args, value)
263
+ const originalPresentationMeta = output.presentationMeta
264
+ const patchedPresentationMeta = (args, value) => {
265
+ if (!active) {
266
+ return originalPresentationMeta === undefined
267
+ ? undefined
268
+ : originalPresentationMeta(args, value)
269
+ }
270
+ const base = originalPresentationMeta === undefined
271
+ ? undefined
272
+ : originalPresentationMeta(args, value)
264
273
  const current = scope.getStore()
265
274
  const settlement = current?.settlement
266
- if (settlement === undefined) return base
275
+ if (settlement === undefined) return generatedRunCodeDescriptionMeta(args, base)
267
276
  let meta = withJournal(base, settlement.journal)
268
277
  if (settlement.recoveryBoundaries !== undefined) {
269
278
  meta = withRecoveryBoundaries(meta, settlement.recoveryBoundaries)
270
279
  }
271
- return settlement.rewrites === undefined ? meta : withRewrites(meta, settlement.rewrites)
280
+ if (settlement.rewrites !== undefined) meta = withRewrites(meta, settlement.rewrites)
281
+ meta = generatedRunCodeDescriptionMeta(args, meta)
282
+ return withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
272
283
  }
284
+ const ownExecute = Object.getOwnPropertyDescriptor(definition, 'execute')
285
+ const originalExecute = definition.execute
286
+ const patchedExecute = typeof originalExecute !== 'function'
287
+ ? undefined
288
+ : function (args, exec) {
289
+ const executionArguments = scope.getStore()?.executionArguments ?? args
290
+ return originalExecute.call(this, executionArguments, exec)
291
+ }
292
+ let presentationPatched = false
273
293
  try {
274
294
  Object.defineProperty(output, 'presentationMeta', {
275
295
  configurable: true,
276
296
  enumerable: true,
277
297
  writable: true,
278
- value: patched,
298
+ value: patchedPresentationMeta,
299
+ })
300
+ presentationPatched = true
301
+ if (patchedExecute !== undefined) {
302
+ Object.defineProperty(definition, 'execute', {
303
+ configurable: true,
304
+ enumerable: ownExecute?.enumerable ?? true,
305
+ writable: true,
306
+ value: patchedExecute,
307
+ })
308
+ }
309
+ patchedDefinitions.set(definition, {
310
+ definition,
311
+ output,
312
+ originalPresentationMeta,
313
+ patchedPresentationMeta,
314
+ ownExecute,
315
+ patchedExecute,
279
316
  })
280
- patchedDefinitions.set(definition, { output, original, patched })
281
317
  } catch (error) {
318
+ if (presentationPatched && output.presentationMeta === patchedPresentationMeta) {
319
+ if (originalPresentationMeta === undefined) delete output.presentationMeta
320
+ else output.presentationMeta = originalPresentationMeta
321
+ }
282
322
  throw new Error(`ptc-plus: cannot attach the session journal to run_code results: ${error.message}`)
283
323
  }
284
324
  }
@@ -333,11 +373,11 @@ export function createRuntimeBridgeOwner({
333
373
  throw error
334
374
  }
335
375
  },
336
- handleExecute(exec, next) {
376
+ handleExecute(exec, next, executionArguments = exec.arguments) {
337
377
  if (exec.parent !== undefined) return scope.run(undefined, next)
338
378
  const id = sessionId(exec.agent)
339
379
  if (id === undefined) return next()
340
- patchResultMetadata(exec.agent)
380
+ patchRunCodeDefinition(exec.agent)
341
381
  const inherited = scope.getStore()
342
382
  const persistedCallSeq = inherited?.persistedCallSeq
343
383
  const current = {
@@ -349,6 +389,7 @@ export function createRuntimeBridgeOwner({
349
389
  : { deferredSettlement: inherited.deferredSettlement }),
350
390
  session: exec.agent?.session,
351
391
  agent: exec.agent,
392
+ executionArguments,
352
393
  }
353
394
  if (current.deferredSettlement !== undefined) {
354
395
  current.deferredSettlement.current = current
@@ -363,6 +404,7 @@ export function createRuntimeBridgeOwner({
363
404
  meta = withRecoveryBoundaries(meta, settlement.recoveryBoundaries)
364
405
  }
365
406
  if (settlement.rewrites !== undefined) meta = withRewrites(meta, settlement.rewrites)
407
+ meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
366
408
  return { ...result, meta }
367
409
  }
368
410
  return result
@@ -405,10 +447,16 @@ export function createRuntimeBridgeOwner({
405
447
  if (ownRun === undefined) delete runtime.run
406
448
  else Object.defineProperty(runtime, 'run', ownRun)
407
449
  }
408
- for (const { output, original, patched } of patchedDefinitions.values()) {
409
- if (output.presentationMeta !== patched) continue
410
- if (original === undefined) delete output.presentationMeta
411
- else output.presentationMeta = original
450
+ for (const patched of patchedDefinitions.values()) {
451
+ if (patched.output.presentationMeta === patched.patchedPresentationMeta) {
452
+ if (patched.originalPresentationMeta === undefined) delete patched.output.presentationMeta
453
+ else patched.output.presentationMeta = patched.originalPresentationMeta
454
+ }
455
+ if (patched.patchedExecute !== undefined
456
+ && patched.definition.execute === patched.patchedExecute) {
457
+ if (patched.ownExecute === undefined) delete patched.definition.execute
458
+ else Object.defineProperty(patched.definition, 'execute', patched.ownExecute)
459
+ }
412
460
  }
413
461
  patchedDefinitions.clear()
414
462
  await sessions.dispose()
@@ -18,6 +18,8 @@ const BUDGET_KEYS = Object.freeze([
18
18
 
19
19
  /** Boolean runtime behavior switches. */
20
20
  const BOOLEAN_KEYS = Object.freeze([
21
+ 'enhancedToolView',
22
+ 'autoDescribeRunCode',
21
23
  'cordisToolsEnabled',
22
24
  'canonicalizeToolCalls',
23
25
  'looseTopLevelRedeclarations',
@@ -12,6 +12,7 @@ import {
12
12
  LONG_CELL_CODE_UNITS,
13
13
  markBindingFailure,
14
14
  messageOf,
15
+ missingDescriptionPath,
15
16
  oneLineMessage,
16
17
  safeProperty,
17
18
  } from './failure-reporting.js'
@@ -128,7 +129,11 @@ function exceptionDiagnostic({
128
129
  declared,
129
130
  longCellFailure = false,
130
131
  }) {
131
- const message = firstLine(error.message, 'Unknown exception')
132
+ const missingPath = error.name === 'ToolCallError' ? missingDescriptionPath(error) : undefined
133
+ const missingDescription = missingPath !== undefined
134
+ const message = missingDescription
135
+ ? `nested native tool arguments are missing required \`description\` at JSON path $.${missingPath} (${error.toolName ?? 'unknown tool'}); this does not satisfy the outer run_code transport`
136
+ : firstLine(error.message, 'Unknown exception')
132
137
  const rawName = typeof error.name === 'string' && error.name.length > 0
133
138
  ? error.name
134
139
  /* c8 ignore next */
@@ -147,7 +152,9 @@ function exceptionDiagnostic({
147
152
  ...(cause === undefined ? {} : { cause }),
148
153
  ...(source === undefined ? {} : { source }),
149
154
  help: [
150
- 'inspect existing bindings and retry only the failing expression',
155
+ ...(missingDescription
156
+ ? ['add a string `description` property to the nested native-tool argument object']
157
+ : ['inspect existing bindings and retry only the failing expression']),
151
158
  ...(error.name === 'ToolCallError'
152
159
  && typeof error.toolName === 'string'
153
160
  && error.toolName.startsWith('cordis_')
@@ -496,7 +503,7 @@ export class SessionCellExecutor {
496
503
  })
497
504
  active.appliedBindingCatalog = message.moduleLoadFailed === true
498
505
  ? active.priorBindingCatalog
499
- : active.priorBindingCatalog.advance(active.prepared)
506
+ : active.priorBindingCatalog.advance(active.prepared, active.request.program)
500
507
  const recordedFailure = active.replay?.diagnostics?.find(item => item.code === 'PTC-X001')
501
508
  const failure = recordedFailure?.message === actualFailure.message ? recordedFailure : actualFailure
502
509
  const error = {
@@ -525,7 +532,10 @@ export class SessionCellExecutor {
525
532
  hasValue: message.hasValue,
526
533
  ...(message.hasValue ? { value } : {}),
527
534
  }
528
- active.appliedBindingCatalog = active.priorBindingCatalog.advance(active.prepared)
535
+ active.appliedBindingCatalog = active.priorBindingCatalog.advance(
536
+ active.prepared,
537
+ active.request.program,
538
+ )
529
539
  if (active.replay?.completion?.kind === 'return'
530
540
  && (active.replay.completion.hasValue !== message.hasValue
531
541
  || (message.hasValue && !valueWiresEqual(active.replay.completion.value, value, active.valueLimits)))) {
@@ -0,0 +1,7 @@
1
+ /** Read the immutable session log across current and legacy DSH session APIs. */
2
+ export function sessionEvents(session) {
3
+ if (typeof session?.snapshotEvents === 'function') {
4
+ return session.snapshotEvents()
5
+ }
6
+ return session?.events
7
+ }
@@ -1,6 +1,7 @@
1
1
  import { decodeValue, encodeValue, normalizeValueWire } from './value-wire.js'
2
2
  import { normalizeDiagnostic } from './diagnostic.js'
3
3
  import { assertOwnFields, isRecord } from './record-utils.js'
4
+ import { sessionEvents } from './session-events.js'
4
5
 
5
6
  export const JOURNAL_KEY = 'dshPtcPlus'
6
7
  export const EDIT_TARGET_KEY = 'dshPtcPlusEdit'
@@ -365,7 +366,7 @@ export function migrateRecoveryBoundaryEvents(events) {
365
366
 
366
367
  /** Resolve the persisted event identity for one named tool call being dispatched. */
367
368
  export function liveToolCallSeq(session, callId, toolName) {
368
- const events = session?.events
369
+ const events = sessionEvents(session)
369
370
  if (!Array.isArray(events) || typeof callId !== 'string' || callId.length === 0
370
371
  || typeof toolName !== 'string' || toolName.length === 0) return undefined
371
372
 
@@ -741,7 +742,7 @@ export function foldSessionTimeline(events) {
741
742
 
742
743
  /** Fold the session log into the last exactly replayable frontier. */
743
744
  export function recoverJournal(session, currentCallSeq, options = {}) {
744
- const events = session?.events
745
+ const events = sessionEvents(session)
745
746
  if (!Array.isArray(events)) {
746
747
  return { nodes: [], head: undefined, checkpoints: new Map(), volatileSuffix: [], available: true }
747
748
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  foldSessionTimeline,
3
3
  } from './session-journal.js'
4
+ import { sessionEvents } from './session-events.js'
4
5
 
5
6
  const SYSTEM_PROMPT_PLUGIN = '@deepseek-ai/dsh-system-prompt'
6
7
 
@@ -43,7 +44,7 @@ function cordisTranscriptFacts(timeline) {
43
44
 
44
45
  /** Project session events into immutable facts consumed by prompt presentation. */
45
46
  export function projectSessionLog(agent, requestedEdit) {
46
- const events = agent?.session?.events
47
+ const events = sessionEvents(agent?.session)
47
48
  if (!Array.isArray(events)) {
48
49
  return Object.freeze({
49
50
  openTurn: false,
@@ -13,6 +13,10 @@ import { resolveConfig } from './runtime-config.js'
13
13
  import { WorkerClient } from './worker-client.js'
14
14
  import { BindingCatalog, durabilityState, transitionDurability } from './session-state.js'
15
15
  import { SessionCellExecutor } from './session-cell-executor.js'
16
+ import {
17
+ createReplMemorySnapshot,
18
+ unavailableReplMemorySnapshot,
19
+ } from './repl-memory-projection.js'
16
20
 
17
21
  const WORKER_URL = new URL('./kernel-worker.js', import.meta.url)
18
22
  function recoveryDiagnostic(count) {
@@ -300,14 +304,14 @@ class SessionKernel {
300
304
  callSeq: request.callSeq,
301
305
  program: request.program,
302
306
  bindingCatalog: active.appliedBindingCatalog
303
- ?? active.priorBindingCatalog.advance(prepared),
307
+ ?? active.priorBindingCatalog.advance(prepared, request.program),
304
308
  worker,
305
309
  })
306
310
  }
307
311
  }
308
312
  if (replay !== undefined && !terminate) {
309
313
  this.bindingCatalog = active.appliedBindingCatalog
310
- ?? active.priorBindingCatalog.advance(prepared)
314
+ ?? active.priorBindingCatalog.advance(prepared, request.program)
311
315
  }
312
316
  this.active = undefined
313
317
  if (terminate) void this.client.reset(worker)
@@ -353,6 +357,18 @@ class SessionKernel {
353
357
  }
354
358
  }
355
359
 
360
+ replMemoryFor(journal) {
361
+ const tentative = this.tentatives.get(journal)
362
+ if (journal.status === 'discarded'
363
+ || journal.operations.some(operation => operation.action === 'restore')) {
364
+ return unavailableReplMemorySnapshot()
365
+ }
366
+ if (tentative === undefined) {
367
+ return createReplMemorySnapshot(this.bindingCatalog.snapshot())
368
+ }
369
+ return createReplMemorySnapshot(tentative.bindingCatalog.snapshot())
370
+ }
371
+
356
372
  finishStateOperations(operations, index, worker) {
357
373
  const transition = reduceStateOperations(this.history, operations, index)
358
374
  this.history.head = transition.head
@@ -487,6 +503,7 @@ export class SessionRuntime {
487
503
  journal,
488
504
  kernel,
489
505
  sessionId,
506
+ replMemory: kernel.replMemoryFor(journal),
490
507
  ...(result.recoveryBoundaries === undefined
491
508
  ? {}
492
509
  : { recoveryBoundaries: result.recoveryBoundaries }),