dsh-ptc-plus 0.4.10 → 0.5.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 (125) hide show
  1. package/README.en.md +65 -127
  2. package/README.md +65 -127
  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 +42702 -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 +4 -4
  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/0044-coexist-with-the-official-persistent-repl.md +35 -0
  35. package/docs/adr/README.md +8 -0
  36. package/docs/architecture.md +23 -25
  37. package/docs/capability-planning-components.md +93 -0
  38. package/docs/capability-projection.md +7 -0
  39. package/docs/client-ui.md +70 -23
  40. package/docs/client-visual-contract.md +26 -9
  41. package/docs/context-management.md +22 -0
  42. package/docs/context-optimization-plan.md +353 -0
  43. package/docs/context-public-interface-coverage.md +25 -0
  44. package/docs/durability-design.md +20 -15
  45. package/docs/evaluation.md +41 -6
  46. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  47. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  48. package/docs/installation.md +33 -10
  49. package/docs/program-data-plane.md +5 -3
  50. package/docs/publishing.md +7 -7
  51. package/docs/release-coverage.md +73 -0
  52. package/docs/release-notes.md +1 -1
  53. package/docs/review-procedure.md +56 -0
  54. package/docs/runtime-reference.md +53 -16
  55. package/docs/semantic-validation.md +14 -2
  56. package/docs/stateful-binding-updates-proposal.md +2 -2
  57. package/docs/user-bindings.md +1 -1
  58. package/docs/value-wire.md +11 -1
  59. package/docs/verification-optimization-work.md +26 -15
  60. package/docs/verification.md +30 -88
  61. package/index.js +146 -20
  62. package/internal/binding-toggle-receipt.js +24 -0
  63. package/internal/capability-command-receipt.js +31 -0
  64. package/internal/capability-environment.js +1253 -0
  65. package/internal/capability-plan.js +285 -0
  66. package/internal/capability-policy.js +96 -0
  67. package/internal/capability-statistics-store.js +180 -0
  68. package/internal/capability-statistics.js +214 -0
  69. package/internal/config-spec.js +89 -10
  70. package/internal/direct-surface-owner.js +27 -29
  71. package/internal/dynamic-environment-runtime.js +9 -9
  72. package/internal/edit-transport-owner.js +195 -140
  73. package/internal/execution-provider-owner.js +40 -12
  74. package/internal/execution-seam-compat.js +26 -8
  75. package/internal/kernel-worker.js +104 -32
  76. package/internal/native-root-dynamic.js +12 -13
  77. package/internal/official-repl-priority.js +51 -0
  78. package/internal/program-bindings.js +190 -0
  79. package/internal/recovery-tips.js +3 -2
  80. package/internal/rejected-cell-editor.js +8 -8
  81. package/internal/repl-observation-interest.js +11 -3
  82. package/internal/rpc-contract.js +1 -0
  83. package/internal/runtime-bridge-owner.js +275 -34
  84. package/internal/runtime-config.js +58 -0
  85. package/internal/runtime-contexts.js +74 -5
  86. package/internal/runtime-intrinsics.js +1 -0
  87. package/internal/runtime-messages.js +41 -0
  88. package/internal/sdk-projection.js +10 -82
  89. package/internal/session-cell-executor.js +42 -19
  90. package/internal/session-events.js +34 -0
  91. package/internal/session-journal-recovery.js +234 -11
  92. package/internal/session-journal-schema.js +6 -3
  93. package/internal/session-journal.js +31 -2
  94. package/internal/session-log-view.js +1 -1
  95. package/internal/session-runtime.js +119 -15
  96. package/internal/settings-compat.js +12 -5
  97. package/internal/stateful-module-compiler.js +3 -2
  98. package/internal/stateful-module-runtime.js +7 -3
  99. package/internal/stateful-root-runtime.js +9 -4
  100. package/internal/user-binding-authoring.js +2 -7
  101. package/internal/user-bindings-owner.js +19 -2
  102. package/internal/validated-parse-repair.js +1 -1
  103. package/internal/value-wire.js +68 -21
  104. package/internal/worker-cwd-virtualization.js +20 -18
  105. package/internal/worker-realm-surfaces.js +3 -3
  106. package/package.json +49 -30
  107. package/src/client-activity.js +3 -2
  108. package/src/client-authoring-view.js +67 -30
  109. package/src/client-binding-review.js +2 -1
  110. package/src/client-binding-toggle.js +18 -0
  111. package/src/client-capabilities-view.js +1555 -0
  112. package/src/client-capability-sort.js +29 -0
  113. package/src/client-copy.js +923 -8
  114. package/src/client-feature-gates.js +3 -0
  115. package/src/client-indicator-view.js +4 -6
  116. package/src/client-primitives.js +33 -2
  117. package/src/client-repl-view.js +20 -13
  118. package/src/client-settings-view.js +411 -41
  119. package/src/client-styles.js +112 -15
  120. package/src/client-tool-hover.js +185 -0
  121. package/src/client-tool-view.js +45 -9
  122. package/src/client-transport.js +44 -9
  123. package/src/client-workbench.js +6 -7
  124. package/src/client.js +76 -22
  125. package/client.js +0 -32479
@@ -5,7 +5,7 @@ import {
5
5
  JOURNAL_KEY,
6
6
  RECOVERY_BOUNDARY_KEY,
7
7
  REWRITES_KEY,
8
- derivedEditResultsEqual,
8
+ isCanonicalSequence,
9
9
  liveToolCallSeq,
10
10
  normalizeJournal,
11
11
  normalizeRecoveryBoundaries,
@@ -18,7 +18,11 @@ import {
18
18
  editRunCodeSchema,
19
19
  } from './rejected-cell-editor.js'
20
20
  import { projectSessionLog } from './session-log-view.js'
21
+ import { sessionEvents } from './session-events.js'
22
+ import { deepEqualJson, snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
23
+ import { scopeTarget } from '@deepseek-ai/dsh-scope'
21
24
  import { RUN_CODE } from './runtime-bridge-owner.js'
25
+ import { CAPABILITY_EVIDENCE_KEY } from './capability-environment.js'
22
26
  import { isRecord } from './record-utils.js'
23
27
  import {
24
28
  userBindingDraftCapabilityFromMeta,
@@ -35,6 +39,43 @@ import {
35
39
 
36
40
  export const EDIT_RUN_CODE = 'edit_run_code'
37
41
 
42
+ const TARGET_FIRST_LINE_LIMIT = 120
43
+
44
+ /** Bounded identity of the cell an edit actually captured, for model-facing reasons. */
45
+ function targetSummary(target) {
46
+ const line = typeof target?.source === 'string' ? target.source.split(/\r?\n/, 1)[0].trim() : ''
47
+ const firstLine = line.length > TARGET_FIRST_LINE_LIMIT ? `${line.slice(0, TARGET_FIRST_LINE_LIMIT)}…` : line
48
+ return `run_code call ${target?.callSeq}${firstLine === '' ? '' : `, first line: ${firstLine}`}`
49
+ }
50
+
51
+ function nestedEditStart(sessionLog, exec) {
52
+ const events = sessionEvents(sessionLog)
53
+ if (!Array.isArray(events) || typeof exec?.callId !== 'string'
54
+ || typeof exec?.rootCallId !== 'string') return undefined
55
+ const rootCallSeq = liveToolCallSeq(sessionLog, exec.rootCallId, RUN_CODE)
56
+ if (rootCallSeq === undefined) return undefined
57
+ const roots = events.flatMap((event, index) => event?.seq === rootCallSeq
58
+ ? [{ event, index }] : [])
59
+ if (roots.length !== 1 || roots[0].event?.type !== 'tool/call'
60
+ || roots[0].event.data?.callId !== exec.rootCallId
61
+ || roots[0].event.data?.name !== RUN_CODE) return undefined
62
+ const matches = events.flatMap((event, index) => (
63
+ index > roots[0].index && event?.type === 'tool/ptc-dispatch-start'
64
+ && event.data?.subCallId === exec.callId
65
+ && event.data?.name === EDIT_RUN_CODE
66
+ ? [{ event, index }] : []
67
+ ))
68
+ if (matches.length !== 1) return undefined
69
+ const { event, index } = matches[0]
70
+ if (!isCanonicalSequence(event.seq) || event.seq <= rootCallSeq
71
+ || events.filter(candidate => candidate?.seq === event.seq).length !== 1
72
+ || event.data.rootCallId !== exec.rootCallId
73
+ || event.data.parentCallId !== exec.rootCallId
74
+ || !deepEqualJson(event.data.arguments, exec.arguments)) return undefined
75
+ const target = projectSessionLog({ session: { events: events.slice(0, index + 1) } }).editableRun
76
+ return { startSeq: event.seq, rootCallSeq, target }
77
+ }
78
+
38
79
  function unavailableDerivedResult(inner) {
39
80
  if (inner?.isError !== true) {
40
81
  throw new Error('ptc-plus: derived run_code result did not contain a valid execution journal')
@@ -61,19 +102,25 @@ function derivedEditResult(inner) {
61
102
  return result
62
103
  }
63
104
 
105
+ function derivedPresentation(inner) {
106
+ return {
107
+ content: inner.content,
108
+ ...(inner.additionalContexts === undefined ? {} : { additionalContexts: inner.additionalContexts }),
109
+ ...(inner.concludesTurn === true ? { concludesTurn: true } : {}),
110
+ }
111
+ }
112
+
64
113
  export function createEditTransportOwner(ctx, {
65
- durableReplay,
114
+ canScheduleEdit,
66
115
  executeTentative,
67
116
  presentationGeneration,
117
+ scheduleEdit,
68
118
  sessionId,
69
119
  toolSchemasForAgent,
70
120
  readSessionLog,
71
- sessionLogView = projectSessionLog,
72
121
  }) {
73
- let currentDurableReplay = durableReplay
74
- const editExecutionMetadata = new WeakMap()
75
122
  const editClaims = new Map()
76
- const pendingSettlements = new Map()
123
+ const pendingAcceptances = new Map()
77
124
  const installedScopes = new Map()
78
125
 
79
126
  const releaseClaim = (id, targetCallSeq) => {
@@ -83,10 +130,10 @@ export function createEditTransportOwner(ctx, {
83
130
  }
84
131
 
85
132
  const discardSettlements = (id) => {
86
- for (const [exec, pending] of pendingSettlements) {
133
+ for (const [exec, pending] of pendingAcceptances) {
87
134
  if (id !== undefined && pending.id !== id) continue
88
- pendingSettlements.delete(exec)
89
- pending.finalize(false)
135
+ pendingAcceptances.delete(exec)
136
+ pending.finish(false)
90
137
  }
91
138
  }
92
139
 
@@ -100,6 +147,7 @@ export function createEditTransportOwner(ctx, {
100
147
  description: derived.description,
101
148
  },
102
149
  }
150
+ if (derived.capabilityEvidence !== undefined) meta[CAPABILITY_EVIDENCE_KEY] = derived.capabilityEvidence
103
151
  if (derived.rewrites !== undefined) {
104
152
  meta[REWRITES_KEY] = normalizeRewrites(derived.rewrites)
105
153
  }
@@ -125,125 +173,167 @@ export function createEditTransportOwner(ctx, {
125
173
  type: 'object',
126
174
  additionalProperties: false,
127
175
  properties: {
128
- edited: { type: 'boolean' },
176
+ accepted: { type: 'boolean' },
177
+ scheduled: { type: 'boolean' },
178
+ target_call_seq: { type: 'integer' },
129
179
  reason: { type: 'string' },
130
- error: { type: 'string' },
131
- value: {},
132
- logs: { type: 'array', items: { type: 'string' } },
133
180
  },
134
- required: ['edited'],
181
+ required: ['accepted', 'scheduled'],
135
182
  },
136
183
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value ?? {}) }],
137
- presentationMeta: (args) => {
138
- const meta = {}
139
- if (!isRecord(args)) return meta
140
- const derived = editExecutionMetadata.get(args)
141
- if (derived === undefined) return meta
142
- return derivedMetadata(derived)
143
- },
144
184
  }
145
185
  definition.execute = async (args, exec) => {
186
+ if (canScheduleEdit?.(exec) !== true) {
187
+ return { accepted: false, scheduled: false, reason: 'edit requires an active root run_code cell' }
188
+ }
146
189
  const agent = exec?.agent
147
- const callId = typeof exec?.callId === 'string' ? exec.callId : undefined
148
190
  const sessionLog = await readSessionLog(agent?.session, exec.signal)
149
- const persistedTargetCallSeq = liveToolCallSeq(sessionLog, callId, EDIT_RUN_CODE)
150
- if (persistedTargetCallSeq === undefined && sessionLogView(agent).editableRun !== undefined) {
151
- throw new Error('ptc-plus: edit_run_code requires a unique persisted tool/call event')
191
+ const start = nestedEditStart(sessionLog, exec)
192
+ if (start === undefined) {
193
+ throw new Error('ptc-plus: edit_run_code requires a unique persisted PTC sub-call start')
152
194
  }
153
- const target = persistedTargetCallSeq === undefined
154
- ? undefined
155
- : sessionLogView(agent, { callId, callSeq: persistedTargetCallSeq }).requestedEditTarget
195
+ const target = start.target
156
196
  if (target?.source === undefined || target.callSeq === undefined) {
157
- return { edited: false, reason: 'no run_code cell is currently eligible for safe editing' }
197
+ return { accepted: false, scheduled: false, reason: 'no run_code cell is currently eligible for safe editing' }
158
198
  }
159
199
  const edited = editRejectedCell(args, target.source)
160
- if (!edited.edited) return edited
200
+ if (!edited.edited) {
201
+ return { accepted: false, scheduled: false, reason: `${edited.reason} (edit target: ${targetSummary(target)})` }
202
+ }
161
203
  const expectedTargetCallSeq = args[EXPECTED_TARGET_CALL_SEQ]
162
204
  if (expectedTargetCallSeq !== undefined && expectedTargetCallSeq !== target.callSeq) {
163
205
  return {
164
- edited: false,
165
- reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured call ${target.callSeq}`,
206
+ accepted: false,
207
+ scheduled: false,
208
+ reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured ${targetSummary(target)}.`
209
+ + ' Only the most recent settled cell stays editable; if the intended cell is no longer editable, re-submit its corrected source with run_code instead.',
166
210
  }
167
211
  }
168
212
  if (typeof ctx.tools.execute !== 'function') {
169
213
  throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
170
214
  }
171
- const persistedCallSeq = persistedTargetCallSeq
172
215
  const id = sessionId(agent)
173
216
  let claims = editClaims.get(id)
174
217
  if (claims === undefined) {
175
218
  claims = new Map()
176
219
  editClaims.set(id, claims)
177
- } else {
178
- for (const [callSeq, phase] of claims) {
179
- if (phase === 'settled' && callSeq !== target.callSeq) claims.delete(callSeq)
180
- }
181
220
  }
182
221
  if (claims.has(target.callSeq)) {
183
- return { edited: false, reason: 'the current edit target already has a derived execution' }
222
+ return { accepted: false, scheduled: false,
223
+ reason: `the current edit target already has a derived execution (edit target: ${targetSummary(target)})` }
184
224
  }
185
- claims.set(target.callSeq, 'executing')
186
- let retainClaim = false
187
- let finalizeTentative
188
- try {
189
- const dispatch = () => ctx.tools.execute({
190
- callId: `${String(exec.callId)}:derived`,
191
- name: RUN_CODE,
192
- arguments: { code: edited.code, description: edited.description },
193
- agent,
194
- signal: exec.signal,
195
- })
196
- const tentative = await executeTentative(persistedCallSeq, dispatch, sessionLog)
197
- const inner = tentative.result
198
- finalizeTentative = tentative.finalize
199
- const journalValue = isRecord(inner?.meta) && Object.hasOwn(inner.meta, JOURNAL_KEY)
200
- ? inner.meta[JOURNAL_KEY]
201
- : undefined
202
- const journal = journalValue === undefined ? undefined : normalizeJournal(journalValue)
203
- if (journal === undefined || journal.status === 'noop') return unavailableDerivedResult(inner)
204
- const rewrites = validatedRewrites(inner.meta)
205
- const recoveryBoundaries = isRecord(inner?.meta)
206
- && Object.hasOwn(inner.meta, RECOVERY_BOUNDARY_KEY)
207
- ? normalizeRecoveryBoundaries(inner.meta[RECOVERY_BOUNDARY_KEY])
208
- : undefined
209
- const replMemory = validatedReplMemorySnapshot(inner?.meta, presentationGeneration)
210
- const userBindings = userBindingsSnapshotFromMeta(inner?.meta)
211
- const draftCapability = userBindingDraftCapabilityFromMeta(
212
- inner?.meta,
213
- presentationGeneration,
214
- )
215
- const value = derivedEditResult(inner)
216
- const derived = {
217
- targetCallSeq: target.callSeq,
218
- journal,
219
- rewrites,
220
- recoveryBoundaries,
221
- replMemory,
222
- userBindings,
223
- draftCapability,
224
- code: edited.code,
225
- description: edited.description,
226
- }
227
- editExecutionMetadata.set(args, derived)
228
- claims.set(target.callSeq, 'settled')
229
- pendingSettlements.set(exec, {
230
- id,
231
- targetCallSeq: target.callSeq,
232
- expectedMeta: derivedMetadata(derived),
233
- finalize: finalizeTentative,
234
- })
235
- finalizeTentative = undefined
236
- retainClaim = true
237
- return value
238
- } finally {
239
- finalizeTentative?.(false)
240
- if (!retainClaim) releaseClaim(id, target.callSeq)
225
+ claims.set(target.callSeq, 'scheduled')
226
+ const receipt = { accepted: true, scheduled: true, target_call_seq: target.callSeq }
227
+ const pending = {
228
+ id,
229
+ receipt,
230
+ confirmed: false,
231
+ startSeq: start.startSeq,
232
+ rootCallSeq: start.rootCallSeq,
233
+ subCallId: exec.callId,
234
+ targetCallSeq: target.callSeq,
235
+ targetDescription: targetSummary(target),
236
+ finalized: false,
237
+ finalizeTentative: undefined,
238
+ finish(confirmed) {
239
+ if (this.finalized) return
240
+ this.finalized = true
241
+ pendingAcceptances.delete(exec)
242
+ this.finalizeTentative?.(confirmed)
243
+ releaseClaim(id, target.callSeq)
244
+ },
245
+ async dispatch(rootSignal, rootExec) {
246
+ if (!this.confirmed || this.finalized || rootSignal.aborted) {
247
+ this.finish(false)
248
+ return { outcome: { edited: false, reason: 'accepted edit was cancelled before derived execution' } }
249
+ }
250
+ if (rootExec?.name !== RUN_CODE || rootExec.callId !== exec.rootCallId
251
+ || rootExec.agent !== agent || rootExec.token === undefined) {
252
+ throw new Error('ptc-plus: derived edit requires its actual root transport execution')
253
+ }
254
+ const argumentsValue = snapshotJsonValue({ code: edited.code, description: edited.description })
255
+ if (argumentsValue === undefined) throw new Error('ptc-plus: derived source is not losslessly JSON-serializable')
256
+ const executionLog = await readSessionLog(agent.session, rootSignal)
257
+ if (rootSignal.aborted || this.finalized) {
258
+ this.finish(false)
259
+ return { outcome: { edited: false, reason: 'accepted edit was cancelled before derived execution' } }
260
+ }
261
+ const data = { rootCallId: exec.rootCallId, parentCallId: rootExec.callId,
262
+ subCallId: `${String(exec.callId)}:derived`, name: RUN_CODE, arguments: argumentsValue }
263
+ const executionStart = agent.session.append('tool/ptc-dispatch-start', data)
264
+ const derivedExec = { callId: data.subCallId, rootCallId: data.rootCallId,
265
+ parent: rootExec.token, name: RUN_CODE, arguments: argumentsValue, agent, signal: rootSignal }
266
+ const dispatch = () => ctx.tools.execute(derivedExec)
267
+ const tentative = await executeTentative(executionStart.seq, start.rootCallSeq,
268
+ dispatch, executionLog, derivedExec)
269
+ const inner = tentative.result
270
+ const metadata = tentative.metadata ?? inner.meta
271
+ this.finalizeTentative = tentative.finalize
272
+ let content = inner.content
273
+ try {
274
+ content = await ctx.waterfall(scopeTarget(ctx.tools, agent), 'tools/ptc-dispatch-log', {
275
+ exec: rootExec, agent, subCallId: data.subCallId, name: RUN_CODE,
276
+ isError: inner.isError, content: inner.content,
277
+ }, () => Promise.resolve(inner.content))
278
+ } catch {
279
+ // A log projector cannot change the settled execution or omit its fact.
280
+ content = inner.content
281
+ }
282
+ try {
283
+ agent.session.append('tool/ptc-dispatch', { ...data, isError: inner.isError,
284
+ ...(inner.error?.info === undefined ? {} : { error: inner.error.info }),
285
+ content,
286
+ ...(metadata === undefined ? {} : { meta: metadata }),
287
+ })
288
+ } catch (error) {
289
+ this.finish(false)
290
+ throw error
291
+ }
292
+ const journalValue = isRecord(metadata) && Object.hasOwn(metadata, JOURNAL_KEY)
293
+ ? metadata[JOURNAL_KEY] : undefined
294
+ const journal = journalValue === undefined ? undefined : normalizeJournal(journalValue)
295
+ if (journal === undefined || journal.status === 'noop'
296
+ || inner.isError !== (journal.completion?.kind === 'throw' || journal.status === 'discarded')) {
297
+ this.finish(false)
298
+ return { outcome: unavailableDerivedResult(inner), presentation: derivedPresentation(inner) }
299
+ }
300
+ const derived = {
301
+ targetCallSeq: target.callSeq,
302
+ journal,
303
+ rewrites: validatedRewrites(metadata),
304
+ recoveryBoundaries: isRecord(metadata) && Object.hasOwn(metadata, RECOVERY_BOUNDARY_KEY)
305
+ ? normalizeRecoveryBoundaries(metadata[RECOVERY_BOUNDARY_KEY]) : undefined,
306
+ replMemory: validatedReplMemorySnapshot(metadata, presentationGeneration),
307
+ userBindings: userBindingsSnapshotFromMeta(metadata),
308
+ draftCapability: userBindingDraftCapabilityFromMeta(metadata, presentationGeneration),
309
+ capabilityEvidence: metadata?.[CAPABILITY_EVIDENCE_KEY],
310
+ code: edited.code,
311
+ description: edited.description,
312
+ }
313
+ return {
314
+ outcome: derivedEditResult(inner),
315
+ presentation: derivedPresentation(inner),
316
+ provenance: {
317
+ startSeq: start.startSeq,
318
+ executionStartSeq: executionStart.seq,
319
+ rootCallSeq: start.rootCallSeq,
320
+ subCallId: exec.callId,
321
+ meta: derivedMetadata(derived),
322
+ },
323
+ }
324
+ },
241
325
  }
326
+ if (scheduleEdit?.(exec, pending) !== true) {
327
+ pending.finish(false)
328
+ return { accepted: false, scheduled: false, reason: 'edit requires an active root run_code cell' }
329
+ }
330
+ pendingAcceptances.set(exec, pending)
331
+ return receipt
242
332
  }
243
333
 
244
334
  const disposeInstalled = (installed) => {
245
335
  const failures = []
246
- for (const key of ['disposePresentation', 'disposeRegistration']) {
336
+ for (const key of ['disposeRegistration']) {
247
337
  const dispose = installed[key]
248
338
  if (dispose === undefined) continue
249
339
  try {
@@ -264,8 +354,7 @@ export function createEditTransportOwner(ctx, {
264
354
  try {
265
355
  disposeInstalled(installed)
266
356
  } finally {
267
- if (installed.disposePresentation === undefined
268
- && installed.disposeRegistration === undefined) {
357
+ if (installed.disposeRegistration === undefined) {
269
358
  installedScopes.delete(agent)
270
359
  }
271
360
  }
@@ -273,23 +362,13 @@ export function createEditTransportOwner(ctx, {
273
362
 
274
363
  return Object.freeze({
275
364
  definition,
276
- reconfigure(nextConfig) {
277
- currentDurableReplay = nextConfig.durableReplay
278
- },
279
365
  isInstalled(agent) {
280
366
  const installed = installedScopes.get(agent)
281
- return installed?.disposePresentation !== undefined
282
- && installed.disposeRegistration !== undefined
367
+ return installed?.disposeRegistration !== undefined
283
368
  },
284
369
  ensureInstalled(agent) {
285
370
  const installed = installedScopes.get(agent)
286
- if (installed !== undefined) {
287
- if (installed.disposePresentation !== undefined
288
- && installed.disposeRegistration !== undefined) {
289
- return installed.definition
290
- }
291
- uninstall(agent)
292
- }
371
+ if (installed !== undefined) return installed.definition
293
372
  const liveSchemas = typeof toolSchemasForAgent === 'function'
294
373
  ? toolSchemasForAgent(agent)
295
374
  : []
@@ -298,8 +377,8 @@ export function createEditTransportOwner(ctx, {
298
377
  throw new Error('ptc-plus: cannot install edit_run_code; the agent scope already owns that tool name')
299
378
  }
300
379
  const scopedTools = agent?.ctx?.tools
301
- if (typeof scopedTools?.register !== 'function' || typeof scopedTools.presentAs !== 'function') {
302
- throw new Error('ptc-plus: DSH agent-scoped tools.register and tools.presentAs are required to expose edit_run_code')
380
+ if (typeof scopedTools?.register !== 'function') {
381
+ throw new Error('ptc-plus: DSH agent-scoped tools.register is required to expose edit_run_code')
303
382
  }
304
383
  const disposeRegistration = scopedTools.register(definition)
305
384
  if (typeof disposeRegistration !== 'function') {
@@ -307,43 +386,19 @@ export function createEditTransportOwner(ctx, {
307
386
  }
308
387
  const nextInstalled = {
309
388
  definition,
310
- disposePresentation: undefined,
311
389
  disposeRegistration,
312
390
  sessionId: sessionId(agent),
313
391
  }
314
392
  installedScopes.set(agent, nextInstalled)
315
- try {
316
- const disposePresentation = scopedTools.presentAs('both')
317
- if (typeof disposePresentation !== 'function') {
318
- throw new Error('ptc-plus: DSH tools.presentAs did not return a disposer')
319
- }
320
- nextInstalled.disposePresentation = disposePresentation
321
- } catch (error) {
322
- try {
323
- uninstall(agent)
324
- } catch (cleanupError) {
325
- throw new AggregateError(
326
- [error, cleanupError],
327
- 'ptc-plus: edit_run_code installation and rollback failed',
328
- { cause: error },
329
- )
330
- }
331
- throw error
332
- }
333
393
  return definition
334
394
  },
335
395
  handleResult(exec, result) {
336
- const pending = pendingSettlements.get(exec)
337
- pendingSettlements.delete(exec)
396
+ const pending = pendingAcceptances.get(exec)
338
397
  if (pending === undefined) return
339
- const persisted = derivedEditResultsEqual(
340
- result?.meta,
341
- pending.expectedMeta,
342
- pending.targetCallSeq,
343
- )
344
- pending.finalize(persisted)
345
- if (persisted) return
346
- releaseClaim(pending.id, pending.targetCallSeq)
398
+ const accepted = result?.isError === false
399
+ && deepEqualJson(result.value, pending.receipt)
400
+ pending.confirmed = accepted
401
+ if (!accepted) pending.finish(false)
347
402
  },
348
403
  disposeAgent(agent) {
349
404
  let failure
@@ -1,20 +1,23 @@
1
- import { createExecutionSeam, EXECUTION_SEAM_SERVICE } from './execution-seam-compat.js'
1
+ import { createExecutionSeam, EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE } from './execution-seam-compat.js'
2
2
 
3
3
  export const name = 'ptc-plus-execution-provider'
4
4
  export const inject = ['loader']
5
5
 
6
6
  export function apply(ctx, config = {}) {
7
7
  const ownerFiber = ctx.fiber
8
- const { originalEntryId = 'ptc-runtime', isolation = 'ptc-plus-original' } = config
8
+ const { originalEntryId = 'ptc-runtime', isolation = 'ptc-plus-original', serviceName = EXECUTION_SEAM_SERVICE } = config
9
9
  if (typeof originalEntryId !== 'string' || originalEntryId.length === 0
10
10
  || typeof isolation !== 'string' || isolation.length === 0) {
11
11
  throw new TypeError('ptc-plus: execution provider requires an original entry id and named isolation')
12
12
  }
13
+ if (![EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE].includes(serviceName)) {
14
+ throw new TypeError('ptc-plus: execution provider serviceName must be codeRuntime or ptcRuntime')
15
+ }
13
16
  // Profile rows belong to the Include's tree; the global Loader resolves root IDs.
14
17
  const entryTree = ownerFiber.entry?.parent.tree ?? ctx.loader
15
18
  const originalEntry = entryTree.resolve(originalEntryId)
16
- const isolated = () => originalEntry.options.isolate?.[EXECUTION_SEAM_SERVICE] === isolation
17
- if (!isolated()) throw new Error(`ptc-plus: original entry ${originalEntryId} must isolate ${EXECUTION_SEAM_SERVICE} as ${isolation}`)
19
+ const isolated = () => originalEntry.options.isolate?.[serviceName] === isolation
20
+ if (!isolated()) throw new Error(`ptc-plus: original entry ${originalEntryId} must isolate ${serviceName} as ${isolation}`)
18
21
  const originalContext = originalEntry.context.extend({ [originalEntry.constructor.key]: undefined })
19
22
  let watcher
20
23
  let live
@@ -29,9 +32,9 @@ export function apply(ctx, config = {}) {
29
32
  rejectAttachment = reject
30
33
  })
31
34
  const readOriginal = () => {
32
- const original = isolated() ? originalContext.get(EXECUTION_SEAM_SERVICE) : undefined
35
+ const original = isolated() ? originalContext.get(serviceName) : undefined
33
36
  if (stopped || original === undefined) {
34
- throw new Error(`ptc-plus: original ${EXECUTION_SEAM_SERVICE} generation is unavailable`)
37
+ throw new Error(`ptc-plus: original ${serviceName} generation is unavailable`)
35
38
  }
36
39
  return original
37
40
  }
@@ -61,15 +64,35 @@ export function apply(ctx, config = {}) {
61
64
  }
62
65
  const publish = () => {
63
66
  const resolutions = new WeakMap()
67
+ const ownerOriginals = new WeakMap()
68
+ const isOwner = owner => owner !== null && (typeof owner === 'object' || typeof owner === 'function')
64
69
  const delegate = {
65
70
  get language() { return readOriginal().language },
66
71
  get isolation() { return readOriginal().isolation },
72
+ get programFormat() { return readOriginal().programFormat ?? 'function-body' },
67
73
  get executionInstructions() { return readOriginal().executionInstructions },
68
74
  get sandboxMode() { return readOriginal().sandboxMode },
69
75
  get timeout() { return readOriginal().timeout },
76
+ release(owner) {
77
+ const originals = isOwner(owner) ? ownerOriginals.get(owner) : undefined
78
+ if (originals !== undefined) {
79
+ return Promise.all([...originals].map(original => Promise.resolve()
80
+ .then(() => original.release(owner))
81
+ .then(() => originals.delete(original)))).then(() => undefined)
82
+ }
83
+ let original
84
+ try {
85
+ original = readOriginal()
86
+ } catch {
87
+ // No retained dispatch belongs to this owner and execution is unavailable.
88
+ return Promise.resolve()
89
+ }
90
+ if (typeof original.release !== 'function') return Promise.resolve()
91
+ return Promise.resolve(original.release.call(original, owner))
92
+ },
70
93
  resolve(request) {
71
94
  const original = readOriginal()
72
- const spec = original.resolve(request)
95
+ const spec = serviceName === EXECUTION_SEAM_SERVICE ? original.resolve(request) : request
73
96
  if (spec !== null && typeof spec === 'object') {
74
97
  resolutions.set(spec, { original, generation })
75
98
  }
@@ -79,10 +102,15 @@ export function apply(ctx, config = {}) {
79
102
  const resolved = resolutions.get(spec)
80
103
  if (resolved?.generation?.active === false) throw new Error('ptc-plus: resolved spec belongs to an unavailable original generation')
81
104
  const original = resolved?.original ?? readOriginal()
105
+ if (isOwner(spec?.stateOwner) && typeof original.release === 'function') {
106
+ let originals = ownerOriginals.get(spec.stateOwner)
107
+ if (originals === undefined) ownerOriginals.set(spec.stateOwner, originals = new Set())
108
+ originals.add(original)
109
+ }
82
110
  return original.run(spec)
83
111
  },
84
112
  }
85
- const seam = createExecutionSeam(delegate, EXECUTION_SEAM_SERVICE)
113
+ const seam = createExecutionSeam(delegate, serviceName)
86
114
  const entry = { seam }
87
115
  live = entry
88
116
  entry.disposeChild = ctx.effect(() => {
@@ -91,7 +119,7 @@ export function apply(ctx, config = {}) {
91
119
  apply(childContext) {
92
120
  childContext.effect(() => {
93
121
  if (stopped || live !== entry || !isolated()) return () => {}
94
- entry.unregister = childContext.provide(EXECUTION_SEAM_SERVICE, seam.provider)
122
+ entry.unregister = childContext.provide(serviceName, seam.provider)
95
123
  return () => entry.unregister()
96
124
  }, 'ptc-plus owned execution registration')
97
125
  },
@@ -109,9 +137,9 @@ export function apply(ctx, config = {}) {
109
137
  const watch = () => {
110
138
  if (stopped || watcher !== undefined || !isolated()) return
111
139
  ctx.effect(() => {
112
- const observer = originalContext.inject([EXECUTION_SEAM_SERVICE], scope => {
140
+ const observer = originalContext.inject([serviceName], scope => {
113
141
  if (stopped || !isolated()) return
114
- createExecutionSeam(scope.get(EXECUTION_SEAM_SERVICE), EXECUTION_SEAM_SERVICE)
142
+ createExecutionSeam(scope.get(serviceName), serviceName)
115
143
  const currentGeneration = { active: true }
116
144
  generation = currentGeneration
117
145
  scope.effect(() => () => {
@@ -146,7 +174,7 @@ export function apply(ctx, config = {}) {
146
174
  }, 'ptc-plus execution provider composition')
147
175
  if (originalEntry.disabled) resolveAttachment()
148
176
  const timer = setTimeout(() => rejectAttachment(new Error(
149
- `ptc-plus: original execution entry ${originalEntryId} did not publish ${EXECUTION_SEAM_SERVICE}`,
177
+ `ptc-plus: original execution entry ${originalEntryId} did not publish ${serviceName}`,
150
178
  )), 30_000)
151
179
  ctx.effect(() => () => clearTimeout(timer), 'ptc-plus original execution provider wait')
152
180
  const originalReady = originalEntry.fiber?.await() ?? Promise.resolve()
@@ -1,7 +1,7 @@
1
- /** Cordis service name registered by the current DSH generation. */
1
+ /** Resolved-spec Cordis execution contract. */
2
2
  export const EXECUTION_SEAM_SERVICE = 'ptcRuntime'
3
3
 
4
- /** Historical request-only service name, retained with fixture evidence. */
4
+ /** Request-only Cordis execution contract. */
5
5
  export const LEGACY_EXECUTION_SEAM_SERVICE = 'codeRuntime'
6
6
 
7
7
  const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE])
@@ -60,6 +60,10 @@ export function createExecutionSeam(service, serviceName) {
60
60
  const provider = Object.freeze({
61
61
  get language() { requireCompatible(); return service.language },
62
62
  get isolation() { requireCompatible(); return installed === undefined ? service.isolation : 'worker-thread' },
63
+ // Source evaluation form belongs to the provider that actually receives
64
+ // the program. Program-format consumers delegate to the original provider,
65
+ // so this descriptor always follows the original generation.
66
+ get programFormat() { requireCompatible(); return service.programFormat ?? 'function-body' },
63
67
  get executionInstructions() { requireCompatible(); return installed === undefined ? service.executionInstructions ?? '' : '' },
64
68
  get sandboxMode() { requireCompatible(); return installed === undefined ? service.sandboxMode : undefined },
65
69
  get timeout() { requireCompatible(); return installed === undefined ? service.timeout : undefined },
@@ -71,6 +75,20 @@ export function createExecutionSeam(service, serviceName) {
71
75
  requireCompatible()
72
76
  return installed === undefined ? upstreamRun.call(service, input) : installed.execute(input)
73
77
  },
78
+ /**
79
+ * Release one owner's retained environment. The resources live in the
80
+ * original generation (the session kernel releases its state through its
81
+ * own lifecycle). Adapter retirement only closes execution; the original
82
+ * still owns cleanup for references retained by its consumers.
83
+ */
84
+ release(owner) {
85
+ if (typeof service.release !== 'function') return Promise.resolve()
86
+ try {
87
+ return Promise.resolve(service.release.call(service, owner))
88
+ } catch (error) {
89
+ return Promise.reject(error)
90
+ }
91
+ },
74
92
  [PLUGIN_EXECUTION_SEAM]() { return seam },
75
93
  })
76
94
  const seam = Object.freeze({
@@ -86,9 +104,9 @@ export function createExecutionSeam(service, serviceName) {
86
104
  return Promise.all([...sourceListeners].map(listener => listener())).then(() => undefined)
87
105
  },
88
106
  /**
89
- * Run one program through the provider this plugin wraps. The current
90
- * generation resolves the request before running it, exactly as its own
91
- * consumer does; the preceding generation accepts the request directly.
107
+ * Run one program through the provider this plugin wraps. The resolved-spec
108
+ * contract resolves the request before running it, exactly as its consumer
109
+ * does; the request-only contract accepts the request directly.
92
110
  */
93
111
  invokeUpstream(request) {
94
112
  requireCompatible()
@@ -132,9 +150,9 @@ function registeredExecutionSeam(service, serviceName) {
132
150
  /**
133
151
  * Wait for the host's execution seam and attach the plugin to the live one.
134
152
  *
135
- * The current service is preferred whenever both are live; the preceding
136
- * generation attaches only when the current one is absent, and a current
137
- * service registered later retires the live legacy injection before attaching.
153
+ * The resolved-spec service is preferred whenever both contracts are live; the
154
+ * request-only service attaches when it is absent. A resolved-spec service
155
+ * registered later retires the request-only injection before attaching.
138
156
  * The injected scope requires the plugin's own core
139
157
  * services as well, so the callback receives a context the plugin can work in,
140
158
  * and everything the plugin registers there belongs to the seam the host