dsh-ptc-plus 0.4.10 → 0.5.0

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 (122) hide show
  1. package/README.en.md +60 -130
  2. package/README.md +60 -130
  3. package/assets/dsh-ptc-plus-banner-en.webp +0 -0
  4. package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
  5. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  6. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  7. package/assets/ptc-plus-settings-en.png +0 -0
  8. package/assets/ptc-plus-settings-zh.png +0 -0
  9. package/client.cjs +42336 -0
  10. package/cordis.patch.yml +1 -0
  11. package/docs/README.md +6 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
  13. package/docs/adr/0008-define-agent-operating-contract.md +5 -1
  14. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
  15. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  16. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
  17. package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +2 -0
  21. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
  22. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
  23. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  24. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  25. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  26. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  27. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  28. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  29. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  30. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  31. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  32. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  33. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  34. package/docs/adr/README.md +6 -0
  35. package/docs/architecture.md +23 -25
  36. package/docs/capability-planning-components.md +93 -0
  37. package/docs/capability-projection.md +7 -0
  38. package/docs/client-ui.md +70 -23
  39. package/docs/client-visual-contract.md +26 -9
  40. package/docs/context-management.md +22 -0
  41. package/docs/context-optimization-plan.md +353 -0
  42. package/docs/context-public-interface-coverage.md +25 -0
  43. package/docs/durability-design.md +20 -15
  44. package/docs/evaluation.md +41 -6
  45. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  46. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  47. package/docs/installation.md +31 -10
  48. package/docs/program-data-plane.md +5 -3
  49. package/docs/publishing.md +5 -5
  50. package/docs/release-coverage.md +73 -0
  51. package/docs/release-notes.md +1 -1
  52. package/docs/review-procedure.md +56 -0
  53. package/docs/runtime-reference.md +48 -16
  54. package/docs/semantic-validation.md +14 -2
  55. package/docs/stateful-binding-updates-proposal.md +2 -2
  56. package/docs/user-bindings.md +1 -1
  57. package/docs/value-wire.md +11 -1
  58. package/docs/verification-optimization-work.md +26 -15
  59. package/docs/verification.md +30 -88
  60. package/index.js +123 -20
  61. package/internal/binding-toggle-receipt.js +24 -0
  62. package/internal/capability-command-receipt.js +29 -0
  63. package/internal/capability-environment.js +1247 -0
  64. package/internal/capability-plan.js +285 -0
  65. package/internal/capability-policy.js +96 -0
  66. package/internal/capability-statistics-store.js +180 -0
  67. package/internal/capability-statistics.js +210 -0
  68. package/internal/config-spec.js +79 -9
  69. package/internal/direct-surface-owner.js +27 -29
  70. package/internal/dynamic-environment-runtime.js +9 -9
  71. package/internal/edit-transport-owner.js +180 -139
  72. package/internal/execution-provider-owner.js +15 -12
  73. package/internal/execution-seam-compat.js +8 -8
  74. package/internal/kernel-worker.js +104 -32
  75. package/internal/native-root-dynamic.js +12 -13
  76. package/internal/program-bindings.js +190 -0
  77. package/internal/recovery-tips.js +3 -2
  78. package/internal/rejected-cell-editor.js +8 -8
  79. package/internal/repl-observation-interest.js +11 -3
  80. package/internal/rpc-contract.js +1 -0
  81. package/internal/runtime-bridge-owner.js +272 -34
  82. package/internal/runtime-config.js +58 -0
  83. package/internal/runtime-contexts.js +74 -5
  84. package/internal/runtime-intrinsics.js +1 -0
  85. package/internal/runtime-messages.js +41 -0
  86. package/internal/sdk-projection.js +10 -82
  87. package/internal/session-cell-executor.js +42 -19
  88. package/internal/session-events.js +34 -0
  89. package/internal/session-journal-recovery.js +234 -11
  90. package/internal/session-journal-schema.js +6 -3
  91. package/internal/session-journal.js +31 -2
  92. package/internal/session-log-view.js +1 -1
  93. package/internal/session-runtime.js +119 -15
  94. package/internal/settings-compat.js +12 -5
  95. package/internal/stateful-module-compiler.js +3 -2
  96. package/internal/stateful-module-runtime.js +7 -3
  97. package/internal/stateful-root-runtime.js +9 -4
  98. package/internal/user-binding-authoring.js +2 -7
  99. package/internal/user-bindings-owner.js +41 -2
  100. package/internal/validated-parse-repair.js +1 -1
  101. package/internal/value-wire.js +68 -21
  102. package/internal/worker-cwd-virtualization.js +20 -18
  103. package/internal/worker-realm-surfaces.js +3 -3
  104. package/package.json +47 -29
  105. package/src/client-authoring-view.js +67 -30
  106. package/src/client-binding-review.js +2 -1
  107. package/src/client-binding-toggle.js +18 -0
  108. package/src/client-capabilities-view.js +1341 -0
  109. package/src/client-capability-sort.js +29 -0
  110. package/src/client-copy.js +871 -8
  111. package/src/client-feature-gates.js +3 -0
  112. package/src/client-indicator-view.js +4 -6
  113. package/src/client-primitives.js +33 -2
  114. package/src/client-repl-view.js +20 -13
  115. package/src/client-settings-view.js +411 -41
  116. package/src/client-styles.js +108 -15
  117. package/src/client-tool-hover.js +185 -0
  118. package/src/client-tool-view.js +37 -8
  119. package/src/client-transport.js +44 -9
  120. package/src/client-workbench.js +6 -7
  121. package/src/client.js +76 -22
  122. package/client.js +0 -32479
@@ -1,5 +1,8 @@
1
1
  /** Own the execution-seam takeover, per-cell lease, journal projection, and session settlement. */
2
2
  import { AsyncLocalStorage } from 'node:async_hooks'
3
+ import { deepEqualJson, snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
4
+ import { decodeValue, renderValueWire } from './value-wire.js'
5
+ import { valueLimitsFromConfig } from './value-wire-schema.js'
3
6
  import { executionPolicies } from './binding-update-policy.js'
4
7
  import { assertObjectJsonSchema, validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
5
8
  import { normalizeBindingDescriptors } from './binding-descriptors.js'
@@ -17,11 +20,13 @@ import {
17
20
  capabilityFind,
18
21
  capabilityInspect,
19
22
  capabilityTree,
23
+ createCapabilitySearcher,
20
24
  toolCapabilityMetadata,
21
25
  } from './program-bindings.js'
22
26
  import { deepFreeze } from './record-utils.js'
23
- import { withReplMemorySnapshot } from './repl-memory-projection.js'
27
+ import { REPL_MEMORY_META_KEY, withReplMemorySnapshot } from './repl-memory-projection.js'
24
28
  import { withUserBindingDraftCapability } from './user-binding-draft-projection.js'
29
+ import { POST_EDIT_KEY } from './session-journal-schema.js'
25
30
  import { generatedRunCodeDescriptionMeta } from './run-code-description.js'
26
31
  import { BINDING_SUBMISSION } from './user-binding-authoring.js'
27
32
  import {
@@ -71,7 +76,7 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
71
76
  namespace: 'repl',
72
77
  members: [{
73
78
  name: 'state',
74
- description: 'Manage named REPL checkpoints. list returns checkpoint names, not variable names. Save captures the final durable state of this cell, including subsequent mutations; it is lost if the cell becomes volatile or is discarded. Save, restore and delete take effect after cell settlement and journal confirmation, not when the receipt is awaited. Restore changes bindings for subsequent cells and does not undo external effects.',
79
+ description: 'Manage named REPL checkpoints. list returns checkpoint names, not variable names. Save requires a durable cell; a volatile save rejection leaves live bindings usable. Save captures the final durable state of this cell, including subsequent mutations; it is lost if the cell becomes volatile or is discarded. Save, restore and delete take effect after cell settlement and journal confirmation, not when the receipt is awaited. Restore discards bindings beyond the selected durable state for subsequent cells; an omitted name selects the prior durable head, or an empty REPL if none exists. Restore does not undo external effects or change enabled binding configuration; subsequent cells may remain volatile.',
75
80
  parameters: {
76
81
  type: 'object',
77
82
  properties: {
@@ -128,7 +133,7 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
128
133
  description: 'Child console output in emission order.',
129
134
  },
130
135
  result: {
131
- description: 'Child return value; omitted when the child returns undefined.',
136
+ description: 'Plugin-owned isolated execution preserves supported PTC Value V1 types and reference relationships; omitted for no return, present for explicit undefined. Native delegation and legacy upstream execution retain their original result contracts.',
132
137
  },
133
138
  },
134
139
  required: ['logs'],
@@ -154,6 +159,7 @@ export function createRuntimeBridgeOwner({
154
159
  toolSchemasForAgent,
155
160
  userBindingDraftForAgent,
156
161
  bindingSubmissionForAgent,
162
+ capabilityEnvironment,
157
163
  }) {
158
164
  const scope = new AsyncLocalStorage()
159
165
  const childScope = new AsyncLocalStorage()
@@ -173,22 +179,27 @@ export function createRuntimeBridgeOwner({
173
179
  const withInitiator = ctx.agents === undefined || typeof ctx.agents.withInitiator !== 'function'
174
180
  ? undefined
175
181
  : (agent, operation) => ctx.agents.withInitiator(agent, operation)
176
- const sessions = new SessionRuntime(sessionConfig, { withInitiator, userBindingsCwd, observeSession, readSessionLog })
182
+ const sessions = new SessionRuntime(sessionConfig, {
183
+ withInitiator, userBindingsCwd, observeSession, readSessionLog,
184
+ sessionForId: id => ctx.get('sessions')?.get(id),
185
+ })
177
186
  let currentConfig = sessions.config
178
187
  const patchedDefinitions = new Map()
179
188
  const pending = new WeakMap()
180
189
  const nativeRunCodeHandoffs = new Set()
181
190
  let active = true
182
- const runIsolated = async (request, executionToken, depth, cellConfig, inheritedTools) => {
191
+ const runIsolated = async (request, executionToken, depth, cellConfig, inheritedTools, programCompletion = false) => {
183
192
  const child = new SessionRuntime({ ...cellConfig, durableReplay: false, userBindingsEnabled: false,
184
193
  replViewEnabled: false }, { withInitiator, userBindingsCwd })
185
194
  childRuntimes.add(child)
186
195
  const projected = projectBindings(request, depth, executionToken, inheritedTools, cellConfig)
187
196
  childFinalizers.add(child)
188
197
  try {
189
- return await child.run({ id: 'isolated-cell', session: { header: {
190
- cwd: executionToken?.session?.header?.cwd,
191
- } } }, { ...projected.request, executionToken })
198
+ const execution = await child.runTentative({ id: 'isolated-cell', session: { header: {
199
+ cwd: request.cwd ?? executionToken?.session?.header?.cwd,
200
+ } } }, { ...projected.request, executionToken }, { programCompletion })
201
+ if (execution.settlement !== undefined) child.finalize(execution.settlement, true)
202
+ return { result: execution.result, completion: execution.settlement?.journal.completion }
192
203
  } finally {
193
204
  projected.release()
194
205
  try {
@@ -263,11 +274,12 @@ export function createRuntimeBridgeOwner({
263
274
  }
264
275
  if (languageSemantics !== 'legacy-v1') {
265
276
  const child = await runIsolated({ ...request, program: args.code }, executionToken,
266
- depth + 1, cellConfig, functions)
267
- if (child.error !== undefined) {
268
- throw new Error(`nested run_code failed (${child.error.kind}): ${child.error.message}`)
277
+ depth + 1, cellConfig, functions, true)
278
+ if (child.result.error !== undefined) {
279
+ throw new Error(`nested run_code failed (${child.result.error.kind}): ${child.result.error.message}`)
269
280
  }
270
- return { logs: child.logs, ...(child.value === undefined ? {} : { result: child.value }) }
281
+ return { logs: child.result.logs, ...(child.completion.hasValue
282
+ ? { result: decodeValue(child.completion.value, valueLimitsFromConfig(cellConfig)) } : {}) }
271
283
  }
272
284
  const childProjected = projectBindings(
273
285
  { ...request, program: args.code }, depth + 1, executionToken, functions, cellConfig,
@@ -284,7 +296,8 @@ export function createRuntimeBridgeOwner({
284
296
  return { logs: child.logs, ...(child.value === undefined ? {} : { result: child.value }) }
285
297
  }
286
298
 
287
- const schemas = toolSchemasForAgent(executionToken?.agent)
299
+ const nativeSchemas = toolSchemasForAgent(executionToken?.agent)
300
+ const schemas = (capabilityEnvironment?.visibleSchemas(executionToken?.agent, nativeSchemas) ?? nativeSchemas)
288
301
  .filter(schema => schema?.name === RUN_CODE
289
302
  || (schema?.name !== EDIT_RUN_CODE && typeof functions[schema?.name] === 'function'))
290
303
  const emptyObjectTools = new Set(schemas.flatMap(schema => (
@@ -294,20 +307,30 @@ export function createRuntimeBridgeOwner({
294
307
  )))
295
308
  const projected = bindingDescriptors.namespaces.map((binding) => {
296
309
  const wrapped = Object.create(null)
297
- for (const key of binding.members) {
310
+ const members = binding.global === 'tools'
311
+ ? capabilityEnvironment?.projectMembers(executionToken?.agent, binding.members) ?? binding.members
312
+ : binding.members
313
+ for (const key of members) {
314
+ const original = (...args) => binding.functions[key](...args)
315
+ const invoke = binding.global === 'tools'
316
+ ? capabilityEnvironment?.adaptFunction(executionToken?.agent, key, original) ?? original
317
+ : original
298
318
  Object.defineProperty(wrapped, key, {
299
319
  enumerable: true,
300
320
  value: async (...args) => {
301
321
  ensureLease()
302
- return binding.functions[key](...args)
322
+ return binding.global === 'tools' && key === EDIT_RUN_CODE
323
+ ? scope.run(executionToken, () => invoke(...args))
324
+ : invoke(...args)
303
325
  },
304
326
  })
305
327
  }
306
328
  const emptyObjectMembers = binding.global === 'tools'
307
- ? binding.members.filter(member => emptyObjectTools.has(member))
329
+ ? members.filter(member => emptyObjectTools.has(member))
308
330
  : []
309
331
  return {
310
332
  ...binding,
333
+ members,
311
334
  functions: wrapped,
312
335
  ...(binding.global === 'tools' ? { emptyObjectMembers } : {}),
313
336
  }
@@ -318,12 +341,14 @@ export function createRuntimeBridgeOwner({
318
341
  : []
319
342
  )))
320
343
  const metadata = Object.freeze([
321
- ...toolCapabilityMetadata(schemas, annotations),
344
+ ...(capabilityEnvironment?.toolMetadata(executionToken?.agent, toolCapabilityMetadata(schemas, annotations), schemas) ?? toolCapabilityMetadata(schemas, annotations)),
345
+ ...(capabilityEnvironment?.metadata(executionToken?.agent) ?? []),
322
346
  ...PROGRAM_CAPABILITY_METADATA.map(entry => entry.namespace === 'code'
323
347
  && cellConfig.userBindingsEnabled === true
324
348
  ? { ...entry, members: [...entry.members, BINDING_SUBMISSION] }
325
349
  : entry),
326
350
  ])
351
+ let searchCapabilities
327
352
  projected.push(namespace('capabilities', {
328
353
  tree: async value => {
329
354
  if (value !== undefined) throw new TypeError('capabilities.tree does not accept arguments')
@@ -337,11 +362,24 @@ export function createRuntimeBridgeOwner({
337
362
  },
338
363
  inspect: async value => {
339
364
  ensureLease()
340
- if (value === undefined) return capabilityInspect(metadata)
365
+ if (value === undefined) {
366
+ const result = capabilityInspect(metadata)
367
+ capabilityEnvironment?.explored(executionToken?.agent, result)
368
+ return result
369
+ }
341
370
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
342
371
  throw new TypeError('capabilities.inspect expects an object')
343
372
  }
344
- return capabilityInspect(metadata, value.symbols, value.budget)
373
+ const result = capabilityInspect(metadata, value.symbols, value.budget)
374
+ capabilityEnvironment?.explored(executionToken?.agent, result)
375
+ return result
376
+ },
377
+ search: async value => {
378
+ ensureLease()
379
+ searchCapabilities ??= createCapabilitySearcher(metadata)
380
+ const result = searchCapabilities(value)
381
+ capabilityEnvironment?.explored(executionToken?.agent, result)
382
+ return result
345
383
  },
346
384
  }, 'CapabilityExplorationError', 'operation'))
347
385
  projected.push(namespace('code', {
@@ -367,6 +405,10 @@ export function createRuntimeBridgeOwner({
367
405
  const output = definition.output
368
406
  const ownPresentationMeta = Object.getOwnPropertyDescriptor(output, 'presentationMeta')
369
407
  const patchedPresentationMeta = (args, value) => {
408
+ const projectedCurrent = scope.getStore()
409
+ if (projectedCurrent !== undefined) {
410
+ projectedCurrent.presentationMetaCalls = (projectedCurrent.presentationMetaCalls ?? 0) + 1
411
+ }
370
412
  const prototype = Object.getPrototypeOf(output)
371
413
  const originalPresentationMeta = ownPresentationMeta === undefined
372
414
  ? prototype === null ? undefined : Reflect.get(prototype, 'presentationMeta', output)
@@ -374,8 +416,15 @@ export function createRuntimeBridgeOwner({
374
416
  ? ownPresentationMeta.value
375
417
  : ownPresentationMeta.get?.call(output)
376
418
  const base = originalPresentationMeta === undefined
377
- ? undefined
378
- : originalPresentationMeta.call(output, args, value)
419
+ // Installing a projector replaces an optional Host callback with a
420
+ // required JSON-producing callback, including policy reprojection
421
+ // after the execution scope has ended. An empty object conveys no
422
+ // computation evidence; a stale settlement must never fill that gap.
423
+ ? {}
424
+ : snapshotJsonValue(originalPresentationMeta.call(output, args, value))
425
+ // An invalid upstream projection is still the Host's output failure;
426
+ // adding plugin metadata must not make it appear valid.
427
+ if (base === undefined) return undefined
379
428
  if (!active) return base
380
429
  const current = scope.getStore()
381
430
  const settlement = current?.settlement
@@ -388,9 +437,50 @@ export function createRuntimeBridgeOwner({
388
437
  if (settlement.userBindings !== undefined) {
389
438
  meta = withUserBindingsSnapshot(meta, settlement.userBindings)
390
439
  }
440
+ if (current.postEdit?.provenance !== undefined) {
441
+ meta = { ...meta, [POST_EDIT_KEY]: current.postEdit.provenance }
442
+ }
391
443
  meta = generatedRunCodeDescriptionMeta(args, meta)
392
444
  meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
393
- return withDraftCapability(meta, current)
445
+ if (current.postEdit?.provenance?.meta?.[REPL_MEMORY_META_KEY] !== undefined) {
446
+ meta = { ...meta,
447
+ [REPL_MEMORY_META_KEY]: current.postEdit.provenance.meta[REPL_MEMORY_META_KEY] }
448
+ }
449
+ meta = withDraftCapability(meta, current)
450
+ return capabilityEnvironment?.complete(current, { meta }).meta ?? meta
451
+ }
452
+ const ownProjectContent = Object.getOwnPropertyDescriptor(definition, 'projectContent')
453
+ const patchedProjectContent = (exec, result) => {
454
+ const prototype = Object.getPrototypeOf(definition)
455
+ const originalProjectContent = ownProjectContent === undefined
456
+ ? prototype === null ? undefined : Reflect.get(prototype, 'projectContent', definition)
457
+ : Object.hasOwn(ownProjectContent, 'value')
458
+ ? ownProjectContent.value : ownProjectContent.get?.call(definition)
459
+ const original = originalProjectContent?.call(definition, exec, result)
460
+ const current = active ? pending.get(exec) : undefined
461
+ const derived = current?.derivedExecution
462
+ // Some public Host implementations omit the metadata projector for a
463
+ // nested success. Capture its canonical value before policy; the private
464
+ // relation is admitted only if policy retains that same frozen value.
465
+ if (current?.deferredSettlement?.current === current && derived !== undefined
466
+ && current.exec === exec && exec.token !== undefined && exec.parent !== undefined
467
+ && current.id === sessionId(exec.agent) && derived.agent === exec.agent
468
+ && derived.callId === exec.callId && derived.rootCallId === exec.rootCallId
469
+ && derived.parent === exec.parent && deepEqualJson(derived.arguments, exec.arguments)
470
+ && current.presentationMetaCalls === undefined && result.isError === false
471
+ && result.value !== null && typeof result.value === 'object'
472
+ && Object.isFrozen(result.value) && result.meta?.[JOURNAL_KEY] === undefined
473
+ && current.settlement !== undefined) {
474
+ const metadata = scope.run(current, () => patchedPresentationMeta(exec.arguments, result.value))
475
+ if (metadata?.[JOURNAL_KEY] !== undefined) {
476
+ current.deferredSettlement.projection = { exec, token: exec.token,
477
+ value: result.value, meta: snapshotJsonValue(result.meta), metadata,
478
+ presentationMetaCalls: current.presentationMetaCalls }
479
+ }
480
+ }
481
+ const presentation = active ? pending.get(exec)?.postEdit?.presentation : undefined
482
+ if (presentation === undefined) return original
483
+ return [...original ?? result.content, ...presentation.content]
394
484
  }
395
485
  const ownExecute = Object.getOwnPropertyDescriptor(definition, 'execute')
396
486
  const originalExecute = definition.execute
@@ -409,6 +499,10 @@ export function createRuntimeBridgeOwner({
409
499
  value: patchedPresentationMeta,
410
500
  })
411
501
  presentationPatched = true
502
+ Object.defineProperty(definition, 'projectContent', {
503
+ configurable: true, enumerable: ownProjectContent?.enumerable ?? true,
504
+ writable: true, value: patchedProjectContent,
505
+ })
412
506
  if (patchedExecute !== undefined) {
413
507
  Object.defineProperty(definition, 'execute', {
414
508
  configurable: true,
@@ -422,10 +516,20 @@ export function createRuntimeBridgeOwner({
422
516
  output,
423
517
  ownPresentationMeta,
424
518
  patchedPresentationMeta,
519
+ ownProjectContent,
520
+ patchedProjectContent,
425
521
  ownExecute,
426
522
  patchedExecute,
427
523
  })
428
524
  } catch (error) {
525
+ if (definition.projectContent === patchedProjectContent) {
526
+ if (ownProjectContent === undefined) delete definition.projectContent
527
+ else Object.defineProperty(definition, 'projectContent', ownProjectContent)
528
+ }
529
+ if (patchedExecute !== undefined && definition.execute === patchedExecute) {
530
+ if (ownExecute === undefined) delete definition.execute
531
+ else Object.defineProperty(definition, 'execute', ownExecute)
532
+ }
429
533
  if (presentationPatched && output.presentationMeta === patchedPresentationMeta) {
430
534
  if (ownPresentationMeta === undefined) delete output.presentationMeta
431
535
  else Object.defineProperty(output, 'presentationMeta', ownPresentationMeta)
@@ -440,6 +544,7 @@ export function createRuntimeBridgeOwner({
440
544
  if (!active || !child.active) return Promise.reject(new Error('PTC execution lease expired'))
441
545
  if (executionPolicies(child.cellConfig).languageSemantics !== 'legacy-v1') {
442
546
  return runIsolated(request, child.executionToken, child.depth, child.cellConfig, child.functions)
547
+ .then(execution => execution.result)
443
548
  }
444
549
  }
445
550
  if (!active) return seam.runUpstream(request)
@@ -451,22 +556,90 @@ export function createRuntimeBridgeOwner({
451
556
  executionToken: current,
452
557
  ...(current.userBindings === undefined ? {} : { userBindings: current.userBindings }),
453
558
  })
454
- .then((execution) => {
559
+ .then(async (execution) => {
455
560
  current.settlement = execution.settlement
456
- return execution.result
561
+ const pendingEdit = current.pendingEdit
562
+ if (pendingEdit === undefined) return execution.result
563
+ if (!pendingEdit.confirmed || current.signal.aborted) {
564
+ pendingEdit.finish(false)
565
+ return execution.result
566
+ }
567
+ try {
568
+ const postEdit = await pendingEdit.dispatch(current.signal, current.exec)
569
+ current.postEdit = postEdit
570
+ for (const context of postEdit.presentation?.additionalContexts ?? []) {
571
+ current.exec.deferContext?.(context)
572
+ }
573
+ if (postEdit.presentation?.concludesTurn === true && execution.result.error === undefined) {
574
+ current.exec.concludeTurn?.()
575
+ }
576
+ const outcome = postEdit.outcome
577
+ const value = postEdit.provenance?.meta?.[JOURNAL_KEY]?.completion
578
+ const hasRenderedContent = postEdit.presentation?.content?.length > 0
579
+ let valueText = ''
580
+ if (!hasRenderedContent && value?.kind === 'return' && value.hasValue) {
581
+ try {
582
+ valueText = `; value: ${renderValueWire(value.value, {
583
+ ...valueLimitsFromConfig(current.submission?.config ?? currentConfig),
584
+ maxStringBytes: 2048,
585
+ })}`
586
+ } catch {
587
+ valueText = '; value omitted from preview'
588
+ }
589
+ }
590
+ const status = outcome.edited
591
+ ? `edit_run_code settled${outcome.error === undefined ? '' : ' with execution error'}`
592
+ : 'edit_run_code did not execute the accepted revision'
593
+ const detail = [
594
+ ...(hasRenderedContent || !Array.isArray(outcome.logs) ? [] : outcome.logs),
595
+ ...(!hasRenderedContent && typeof outcome.error === 'string' ? [outcome.error] : []),
596
+ ...(!hasRenderedContent && typeof outcome.reason === 'string' ? [outcome.reason] : []),
597
+ ].filter(item => typeof item === 'string').join('\n')
598
+ return { ...execution.result,
599
+ logs: [...execution.result.logs, `${status}${valueText}${detail ? `: ${detail}` : ''}`] }
600
+ } catch (error) {
601
+ pendingEdit.finish(false)
602
+ return { ...execution.result,
603
+ logs: [...execution.result.logs,
604
+ `edit_run_code failed after acceptance: ${error instanceof Error ? error.message : String(error)}`] }
605
+ }
457
606
  })
458
607
  .finally(projected.release)
459
608
  }
460
609
 
461
610
  const releaseSeam = seam.installExecute(patchedRun)
462
611
 
612
+ const ownedDerivedScope = (exec, executionArguments = exec.arguments) => {
613
+ const inherited = scope.getStore()
614
+ const derived = inherited?.derivedExecution
615
+ if (inherited?.deferredSettlement === undefined
616
+ || inherited.id !== sessionId(exec.agent) || derived?.agent !== exec.agent
617
+ || derived.callId !== exec.callId || derived.rootCallId !== exec.rootCallId
618
+ || derived.parent !== exec.parent || exec.name !== RUN_CODE
619
+ || !deepEqualJson(derived.arguments, executionArguments)) return undefined
620
+ return inherited
621
+ }
622
+
463
623
  const withSubmission = async (exec, operation) => {
464
624
  const id = sessionId(exec.agent)
465
625
  const inherited = scope.getStore()
466
- if (exec.parent !== undefined || id === undefined || inherited?.submission?.sessionId === id) return operation()
626
+ const ownsDerived = ownedDerivedScope(exec) !== undefined
627
+ if ((exec.parent !== undefined && !ownsDerived) || id === undefined
628
+ || inherited?.submission?.sessionId === id) return operation()
467
629
  const submission = sessions.beginSubmission({ id }, exec.signal)
630
+ const composite = inherited?.deferredSettlement !== undefined && inherited.id === id
631
+ ? {
632
+ persistedCallSeq: inherited.persistedCallSeq,
633
+ sourceCallSeq: inherited.sourceCallSeq,
634
+ sessionLog: inherited.sessionLog,
635
+ deferredSettlement: inherited.deferredSettlement,
636
+ derivedExecution: inherited.derivedExecution,
637
+ }
638
+ : {}
468
639
  try {
469
- return await scope.run({ ...inherited, submission }, operation)
640
+ // Derived edits retain their proved source under a fresh admission for
641
+ // the same session. Delegated agents own a fresh log and no child handoff.
642
+ return await scope.run({ ...composite, submission }, () => childScope.run(undefined, operation))
470
643
  } finally {
471
644
  sessions.releaseSubmission(submission)
472
645
  }
@@ -479,6 +652,25 @@ export function createRuntimeBridgeOwner({
479
652
  return sessionLog
480
653
  }
481
654
 
655
+ const readNestedDispatchLog = async (session, signal) => {
656
+ const inherited = scope.getStore()
657
+ if (inherited === undefined || inherited.session !== session
658
+ || inherited.signal.aborted || signal?.aborted) {
659
+ throw new Error('ptc-plus: nested edit has no active root run_code cell')
660
+ }
661
+ return readSessionLog(session, signal)
662
+ }
663
+
664
+ const rootEditScope = exec => {
665
+ const current = scope.getStore()
666
+ if (!active || current === undefined || childScope.getStore() !== undefined
667
+ || current.deferredSettlement !== undefined
668
+ || current.callId !== exec.rootCallId || current.agent !== exec.agent
669
+ || current.session !== exec.agent?.session || current.signal.aborted
670
+ || exec.signal?.aborted) return undefined
671
+ return current
672
+ }
673
+
482
674
  return Object.freeze({
483
675
  config: sessions.config,
484
676
  reconfigure(nextConfig) {
@@ -488,8 +680,18 @@ export function createRuntimeBridgeOwner({
488
680
  observeRepl(id, memory, signal) { return sessions.observe(id, memory, signal) },
489
681
  withSubmission,
490
682
  readSubmissionLog,
683
+ readNestedDispatchLog,
684
+ prepareAgent(agent) { patchRunCodeDefinition(agent) },
685
+ canScheduleEdit(exec) { return rootEditScope(exec) !== undefined },
686
+ scheduleEdit(exec, pendingEdit) {
687
+ const current = rootEditScope(exec)
688
+ if (current === undefined || current.pendingEdit !== undefined) return false
689
+ current.pendingEdit = pendingEdit
690
+ return true
691
+ },
491
692
  // A composite tool's outer result owns the final durability decision.
492
- async executeTentative(callSeq, operation, sessionLog) {
693
+ async executeTentative(callSeq, sourceCallSeq, operation, sessionLog, derivedExecution) {
694
+ const id = scope.getStore()?.id
493
695
  const settlement = {
494
696
  current: undefined,
495
697
  innerConfirmed: false,
@@ -504,22 +706,27 @@ export function createRuntimeBridgeOwner({
504
706
  }
505
707
  try {
506
708
  const result = await scope.run({
507
- ...(scope.getStore()?.submission === undefined ? {} : { submission: scope.getStore().submission }),
709
+ id,
508
710
  ...(callSeq === undefined ? {} : { persistedCallSeq: callSeq }),
711
+ ...(sourceCallSeq === undefined ? {} : { sourceCallSeq }),
509
712
  ...(sessionLog === undefined ? {} : { sessionLog }),
510
713
  deferredSettlement: settlement,
714
+ derivedExecution,
511
715
  }, operation)
512
- return Object.freeze({ result, finalize })
716
+ return Object.freeze({ result, finalize,
717
+ ...(settlement.metadata === undefined ? {} : { metadata: settlement.metadata }) })
513
718
  } catch (error) {
514
719
  finalize(false)
515
720
  throw error
516
721
  }
517
722
  },
518
723
  async handleExecute(exec, next, executionArguments = exec.arguments, userBindings = undefined) {
519
- if (exec.parent !== undefined) return scope.run(undefined, next)
724
+ const ownsDerived = ownedDerivedScope(exec, executionArguments) !== undefined
725
+ if (exec.parent !== undefined && !ownsDerived) return scope.run(undefined, next)
520
726
  return withSubmission(exec, async () => {
521
727
  const id = sessionId(exec.agent)
522
728
  if (id === undefined) return next()
729
+ await capabilityEnvironment?.prepare(exec.agent)
523
730
  patchRunCodeDefinition(exec.agent)
524
731
  const inherited = scope.getStore()
525
732
  const persistedCallSeq = inherited?.persistedCallSeq
@@ -528,12 +735,16 @@ export function createRuntimeBridgeOwner({
528
735
  submission: inherited?.submission,
529
736
  callId: String(exec.callId),
530
737
  ...(persistedCallSeq === undefined ? {} : { persistedCallSeq }),
738
+ ...(inherited?.sourceCallSeq === undefined ? {} : { sourceCallSeq: inherited.sourceCallSeq }),
531
739
  ...(inherited?.deferredSettlement === undefined
532
740
  ? {}
533
- : { deferredSettlement: inherited.deferredSettlement }),
741
+ : { deferredSettlement: inherited.deferredSettlement,
742
+ derivedExecution: inherited.derivedExecution }),
534
743
  session: exec.agent?.session,
744
+ signal: exec.signal,
535
745
  sessionLog: await readSubmissionLog(exec.agent?.session, exec.signal),
536
746
  agent: exec.agent,
747
+ exec,
537
748
  executionArguments,
538
749
  ...(userBindings === undefined ? {} : { userBindings }),
539
750
  }
@@ -554,26 +765,47 @@ export function createRuntimeBridgeOwner({
554
765
  if (settlement.userBindings !== undefined) {
555
766
  meta = withUserBindingsSnapshot(meta, settlement.userBindings)
556
767
  }
768
+ if (current.postEdit?.provenance !== undefined) {
769
+ meta = { ...meta, [POST_EDIT_KEY]: current.postEdit.provenance }
770
+ }
557
771
  meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
772
+ if (current.postEdit?.provenance?.meta?.[REPL_MEMORY_META_KEY] !== undefined) {
773
+ meta = { ...meta,
774
+ [REPL_MEMORY_META_KEY]: current.postEdit.provenance.meta[REPL_MEMORY_META_KEY] }
775
+ }
558
776
  meta = withDraftCapability(meta, current)
559
- return { ...result, meta }
777
+ return capabilityEnvironment?.complete(current, { ...result, meta }) ?? { ...result, meta }
560
778
  }
561
779
  return result
562
780
  })
563
781
  })
564
782
  },
565
783
  handleResult(exec, result) {
566
- if (exec.parent !== undefined) return
784
+ if (exec.parent !== undefined && !pending.has(exec)) return
567
785
  const id = sessionId(exec.agent)
568
786
  if (id === undefined) return
569
787
  const current = pending.get(exec)
570
788
  pending.delete(exec)
571
789
  const settlement = current?.settlement
572
790
  if (settlement === undefined) {
791
+ current?.pendingEdit?.finish(false)
573
792
  sessions.noteNoop(id, current?.sessionLog, exec.callId)
574
793
  return
575
794
  }
576
- const meta = result?.meta
795
+ const deferred = current.deferredSettlement
796
+ const projection = deferred?.projection
797
+ const derived = current.derivedExecution
798
+ const privateMetadata = projection !== undefined && projection.exec === exec
799
+ && deferred.current === current && current.exec === exec && current.id === id
800
+ && derived?.agent === exec.agent && derived.callId === exec.callId
801
+ && derived.rootCallId === exec.rootCallId && derived.parent === exec.parent
802
+ && deepEqualJson(derived.arguments, exec.arguments)
803
+ && projection.token === exec.token && projection.value === result?.value
804
+ && result.isError === false && deepEqualJson(projection.meta, result.meta)
805
+ && projection.presentationMetaCalls === current.presentationMetaCalls
806
+ ? projection.metadata : undefined
807
+ if (deferred !== undefined) deferred.metadata = privateMetadata
808
+ const meta = privateMetadata ?? result?.meta
577
809
  const confirmed = meta !== null && typeof meta === 'object' && !Array.isArray(meta)
578
810
  && Object.hasOwn(meta, JOURNAL_KEY)
579
811
  && journalsEqual(meta[JOURNAL_KEY], settlement.journal)
@@ -592,6 +824,8 @@ export function createRuntimeBridgeOwner({
592
824
  return
593
825
  }
594
826
  sessions.finalize(settlement, confirmed)
827
+ current.pendingEdit?.finish(confirmed && current.postEdit?.provenance !== undefined
828
+ && deepEqualJson(meta?.[POST_EDIT_KEY], current.postEdit.provenance))
595
829
  },
596
830
  disposeAgent(agent) {
597
831
  return sessions.disposeSession(sessionId(agent) ?? String(agent.id))
@@ -620,6 +854,10 @@ export function createRuntimeBridgeOwner({
620
854
  if (patched.ownPresentationMeta === undefined) delete patched.output.presentationMeta
621
855
  else Object.defineProperty(patched.output, 'presentationMeta', patched.ownPresentationMeta)
622
856
  }
857
+ if (patched.definition.projectContent === patched.patchedProjectContent) {
858
+ if (patched.ownProjectContent === undefined) delete patched.definition.projectContent
859
+ else Object.defineProperty(patched.definition, 'projectContent', patched.ownProjectContent)
860
+ }
623
861
  if (patched.patchedExecute !== undefined
624
862
  && patched.definition.execute === patched.patchedExecute) {
625
863
  if (patched.ownExecute === undefined) delete patched.definition.execute
@@ -1,3 +1,4 @@
1
+ import { capabilityPolicyForConfig } from './capability-policy.js'
1
2
  import {
2
3
  CONFIG_DEFAULTS,
3
4
  CONFIG_FIELDS,
@@ -6,6 +7,43 @@ import {
6
7
 
7
8
  export { MAX_TIMER_DELAY_MS }
8
9
 
10
+ /** Reusable configurations contain capability settings, never session evidence. */
11
+ export const CAPABILITY_CONFIGURATION_KEYS = Object.freeze(CONFIG_FIELDS
12
+ .filter(field => (field.key.startsWith('capability') && field.key !== 'capabilityProfiles') || ['modelPreviewTokens', 'modelPreviewEnabled'].includes(field.key))
13
+ .map(field => field.key))
14
+
15
+ export function capabilityProfileConfiguration(value, { allowInvalidStatistics = false } = {}) {
16
+ if (!value || typeof value !== 'object' || Array.isArray(value)
17
+ || Object.keys(value).some(key => !CAPABILITY_CONFIGURATION_KEYS.includes(key))) {
18
+ throw new TypeError('ptc-plus: invalid capability configuration fields')
19
+ }
20
+ const resolved = resolveConfig(value)
21
+ let statistics
22
+ try { statistics = JSON.parse(resolved.capabilityStatistics) } catch {}
23
+ if (!allowInvalidStatistics && (!statistics || typeof statistics.enabled !== 'boolean'
24
+ || !Number.isSafeInteger(statistics.windowSize) || statistics.windowSize < 1
25
+ || !Number.isSafeInteger(statistics.minimumSamples) || statistics.minimumSamples < 1 || statistics.minimumSamples > statistics.windowSize
26
+ || !Number.isFinite(statistics.fullThreshold) || statistics.fullThreshold < 0 || statistics.fullThreshold > 1)) {
27
+ throw new TypeError('ptc-plus: invalid saved capability statistics settings')
28
+ }
29
+ return Object.fromEntries(CAPABILITY_CONFIGURATION_KEYS.map(key => [key, resolved[key]]))
30
+ }
31
+
32
+ export function validateCapabilityProfiles(value) {
33
+ if (!value || value.schema !== 'ptc-capability-profiles/v1' || !Array.isArray(value.profiles) || value.profiles.length > 100) {
34
+ throw new TypeError('ptc-plus: invalid capability profiles document')
35
+ }
36
+ const ids = new Set()
37
+ const names = new Set()
38
+ return { schema: value.schema, profiles: value.profiles.map(profile => {
39
+ if (!profile || typeof profile.id !== 'string' || !profile.id.trim() || profile.id.length > 100
40
+ || typeof profile.name !== 'string' || !profile.name.trim() || profile.name.length > 80
41
+ || ids.has(profile.id) || names.has(profile.name.trim())) throw new TypeError('ptc-plus: invalid or duplicate capability profile')
42
+ ids.add(profile.id); names.add(profile.name.trim())
43
+ return { id: profile.id, name: profile.name.trim(), configuration: capabilityProfileConfiguration(profile.configuration) }
44
+ }) }
45
+ }
46
+
9
47
  /** The wall-clock ceiling field carries the platform timer limit as its maximum. */
10
48
  const MAX_WALL_MS_FIELD = CONFIG_FIELDS.find(field => field.key === 'maxWallMs')
11
49
 
@@ -21,6 +59,10 @@ function assertIntegerField(field, value) {
21
59
 
22
60
  /** Validate one field against its CONFIG_FIELDS declaration. */
23
61
  function assertField(field, value) {
62
+ if (field.type === 'string') {
63
+ if (typeof value !== 'string') throw new TypeError(`ptc-plus: ${field.key} must be a string`)
64
+ return
65
+ }
24
66
  if (field.type === 'boolean') {
25
67
  if (typeof value !== 'boolean') {
26
68
  throw new TypeError(`ptc-plus: ${field.key} must be a boolean`)
@@ -74,6 +116,22 @@ export function resolveConfig(config = {}) {
74
116
  resolved[field.key] = liveConfigValue(resolved[field.key], CONFIG_DEFAULTS[field.key])
75
117
  }
76
118
  for (const field of CONFIG_FIELDS) assertField(field, resolved[field.key])
119
+ validateCapabilityProfiles(JSON.parse(resolved.capabilityProfiles))
120
+ for (const [key, array] of [['capabilityBaseline', true], ['capabilityOverrides', false]]) {
121
+ let value
122
+ try { value = JSON.parse(resolved[key]) } catch { throw new TypeError(`ptc-plus: ${key} must contain valid JSON`) }
123
+ if (array ? !Array.isArray(value) || value.some(id => typeof id !== 'string' || !id.trim())
124
+ : value === null || typeof value !== 'object' || Array.isArray(value)
125
+ || Object.entries(value).some(([id, selection]) => !id.trim() || !['full', 'on-demand', 'closed'].includes(selection)
126
+ || (id.startsWith('mcpServers.') && (id === 'mcpServers.' || selection !== 'closed')))) {
127
+ throw new TypeError(`ptc-plus: invalid ${key}`)
128
+ }
129
+ }
130
+ resolved.capabilityPolicy = JSON.stringify(capabilityPolicyForConfig(provided.has('capabilityPolicy') ? resolved : { ...resolved, capabilityPolicy: undefined }))
131
+ if (!provided.has('capabilityPolicy') && resolved.capabilityManagementEnabled) {
132
+ if (!provided.has('capabilityRecoveryEnabled')) resolved.capabilityRecoveryEnabled = true
133
+ if (!provided.has('modelPreviewEnabled')) resolved.modelPreviewEnabled = true
134
+ }
77
135
  // Migrate complete old policies; mixed choices retain their explicit
78
136
  // compatibility state until the user selects the unified language.
79
137
  const legacyKeys = [