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
@@ -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,9 +18,12 @@ 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',
26
+ 'looseTopLevelFunctionClassRedeclarations',
24
27
  'durableReplay',
25
28
  'autoRewriteImports',
26
29
  'autoStripExports',
@@ -12,11 +12,13 @@ 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'
18
19
  import { assertStateName } from './session-journal.js'
19
20
  import { PreflightError, prepareProgram } from './cell-analysis.js'
21
+ import { LIVE_DEFAULT_EXPORT_BINDING } from './repl-rewrite-contract.js'
20
22
  import { ModuleRewriteError } from './cell-rewriter.js'
21
23
  import { mapSourcePosition } from './source-position-map.js'
22
24
  import { durabilityState, transitionDurability } from './session-state.js'
@@ -104,9 +106,47 @@ function preflightDiagnostic(error) {
104
106
  })
105
107
  }
106
108
 
109
+ function committedRedeclarationSet(message, prepared) {
110
+ if (!Array.isArray(message.committedRedeclarations)) {
111
+ throw new Error('kernel returned an invalid committed redeclaration set')
112
+ }
113
+ const allowed = prepared.commitTargets
114
+ const committed = new Set()
115
+ for (const name of message.committedRedeclarations) {
116
+ if (typeof name !== 'string' || committed.has(name) || !allowed.has(name)) {
117
+ throw new Error('kernel returned an invalid committed redeclaration set')
118
+ }
119
+ committed.add(name)
120
+ }
121
+ return committed
122
+ }
123
+
107
124
  function collisionDiagnostic(collisions) {
108
125
  const names = [...new Set(collisions.map(item => item.name))]
126
+ const reasons = new Set(collisions.map(item => item.reason).filter(Boolean))
127
+ const disabledKinds = new Set(collisions
128
+ .filter(item => item.reason === 'function-class-redeclarations-disabled')
129
+ .map(item => item.kind))
109
130
  const first = collisions[0]
131
+ const alternatives = [
132
+ ...(disabledKinds.size === 2
133
+ ? ['assign function or class expressions to the existing writable bindings']
134
+ : disabledKinds.has('function')
135
+ ? ['assign a function expression to the existing writable binding']
136
+ : disabledKinds.has('class')
137
+ ? ['assign a class expression to the existing writable binding']
138
+ : []),
139
+ ...((reasons.size === 0 || reasons.has('variable-redeclarations-disabled'))
140
+ ? ['reuse the existing bindings']
141
+ : []),
142
+ ]
143
+ const help = [
144
+ ...(reasons.has('binding-not-writable')
145
+ ? ['use a fresh name because the existing binding is immutable']
146
+ : []),
147
+ ...(alternatives.length === 0 ? [] : [alternatives.join('; ')]),
148
+ 'place one-off declarations inside a block',
149
+ ]
110
150
  return diagnostic({
111
151
  code: 'PTC-N001',
112
152
  severity: 'error',
@@ -114,10 +154,7 @@ function collisionDiagnostic(collisions) {
114
154
  message: `top-level bindings already exist: ${names.join(', ')}. This cell was not executed; the REPL state is unchanged.`,
115
155
  stateEffect: 'unchanged',
116
156
  source: { cell: 'current', start: first.start, end: first.end },
117
- help: [
118
- 'reuse the existing bindings',
119
- 'place one-off declarations inside a block',
120
- ],
157
+ help,
121
158
  })
122
159
  }
123
160
 
@@ -128,7 +165,11 @@ function exceptionDiagnostic({
128
165
  declared,
129
166
  longCellFailure = false,
130
167
  }) {
131
- const message = firstLine(error.message, 'Unknown exception')
168
+ const missingPath = error.name === 'ToolCallError' ? missingDescriptionPath(error) : undefined
169
+ const missingDescription = missingPath !== undefined
170
+ const message = missingDescription
171
+ ? `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`
172
+ : firstLine(error.message, 'Unknown exception')
132
173
  const rawName = typeof error.name === 'string' && error.name.length > 0
133
174
  ? error.name
134
175
  /* c8 ignore next */
@@ -147,7 +188,9 @@ function exceptionDiagnostic({
147
188
  ...(cause === undefined ? {} : { cause }),
148
189
  ...(source === undefined ? {} : { source }),
149
190
  help: [
150
- 'inspect existing bindings and retry only the failing expression',
191
+ ...(missingDescription
192
+ ? ['add a string `description` property to the nested native-tool argument object']
193
+ : ['inspect existing bindings and retry only the failing expression']),
151
194
  ...(error.name === 'ToolCallError'
152
195
  && typeof error.toolName === 'string'
153
196
  && error.toolName.startsWith('cordis_')
@@ -211,22 +254,28 @@ export class SessionCellExecutor {
211
254
  }
212
255
 
213
256
  const catalog = kernel.bindingCatalog.inputs()
214
- const looseTopLevelRedeclarations = replayRecord === undefined
215
- ? config.looseTopLevelRedeclarations
216
- : replayRecord.bindingMode === 'loose'
257
+ const bindingPolicy = replayRecord === undefined ? {
258
+ variableRedeclarations: config.looseTopLevelRedeclarations,
259
+ functionClassRedeclarations: config.looseTopLevelFunctionClassRedeclarations,
260
+ } : replayRecord.bindingPolicy
217
261
  const rewritesEnabled = replayRecord === undefined ? {
218
262
  autoRewriteImports: config.autoRewriteImports,
219
263
  autoStripExports: config.autoStripExports,
220
264
  autoSplitRedeclarations: config.autoSplitRedeclarations,
221
265
  } : replayRecord.rewritePolicy
266
+ const moduleSemantics = replayRecord === undefined ? {
267
+ defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING,
268
+ } : replayRecord.moduleSemantics
222
269
  const prepareCell = program => prepareProgram(
223
270
  program,
224
271
  catalog.knownBindings,
225
- looseTopLevelRedeclarations,
272
+ bindingPolicy,
226
273
  request.bindingDescriptors.reservedNames,
227
274
  rewritesEnabled,
228
275
  catalog.importBindings,
229
276
  catalog.importNamespaces,
277
+ catalog.writableBindings,
278
+ moduleSemantics,
230
279
  )
231
280
  let prepared
232
281
  try {
@@ -341,6 +390,7 @@ export class SessionCellExecutor {
341
390
  type: 'run', id, program: prepared.code, namespaces: bindings.workerDescriptors,
342
391
  moduleLoads: prepared.moduleLoads,
343
392
  returnSignal: prepared.returnSignal,
393
+ commitSignal: prepared.commitSignal,
344
394
  maxOutputBytes: config.maxOutputBytes,
345
395
  valueLimits,
346
396
  durability,
@@ -473,6 +523,20 @@ export class SessionCellExecutor {
473
523
  }, true)
474
524
  return
475
525
  }
526
+ let committed
527
+ try {
528
+ committed = committedRedeclarationSet(message, active.prepared)
529
+ } catch (error) {
530
+ active.resolve(earlyResult('worker-exit', messageOf(error)), true)
531
+ return
532
+ }
533
+ active.appliedBindingCatalog = message.moduleLoadFailed === true
534
+ ? active.priorBindingCatalog
535
+ : active.priorBindingCatalog.advance(
536
+ active.prepared,
537
+ active.request.program,
538
+ committed,
539
+ )
476
540
  if (typeof message.error === 'string') {
477
541
  const rawError = {
478
542
  kind: 'exception',
@@ -494,9 +558,6 @@ export class SessionCellExecutor {
494
558
  declared: message.moduleLoadFailed === true ? new Set() : active.prepared.declared,
495
559
  longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
496
560
  })
497
- active.appliedBindingCatalog = message.moduleLoadFailed === true
498
- ? active.priorBindingCatalog
499
- : active.priorBindingCatalog.advance(active.prepared)
500
561
  const recordedFailure = active.replay?.diagnostics?.find(item => item.code === 'PTC-X001')
501
562
  const failure = recordedFailure?.message === actualFailure.message ? recordedFailure : actualFailure
502
563
  const error = {
@@ -525,7 +586,6 @@ export class SessionCellExecutor {
525
586
  hasValue: message.hasValue,
526
587
  ...(message.hasValue ? { value } : {}),
527
588
  }
528
- active.appliedBindingCatalog = active.priorBindingCatalog.advance(active.prepared)
529
589
  if (active.replay?.completion?.kind === 'return'
530
590
  && (active.replay.completion.hasValue !== message.hasValue
531
591
  || (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
+ }