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
@@ -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,34 @@ import {
35
39
 
36
40
  export const EDIT_RUN_CODE = 'edit_run_code'
37
41
 
42
+ function nestedEditStart(sessionLog, exec) {
43
+ const events = sessionEvents(sessionLog)
44
+ if (!Array.isArray(events) || typeof exec?.callId !== 'string'
45
+ || typeof exec?.rootCallId !== 'string') return undefined
46
+ const rootCallSeq = liveToolCallSeq(sessionLog, exec.rootCallId, RUN_CODE)
47
+ if (rootCallSeq === undefined) return undefined
48
+ const roots = events.flatMap((event, index) => event?.seq === rootCallSeq
49
+ ? [{ event, index }] : [])
50
+ if (roots.length !== 1 || roots[0].event?.type !== 'tool/call'
51
+ || roots[0].event.data?.callId !== exec.rootCallId
52
+ || roots[0].event.data?.name !== RUN_CODE) return undefined
53
+ const matches = events.flatMap((event, index) => (
54
+ index > roots[0].index && event?.type === 'tool/ptc-dispatch-start'
55
+ && event.data?.subCallId === exec.callId
56
+ && event.data?.name === EDIT_RUN_CODE
57
+ ? [{ event, index }] : []
58
+ ))
59
+ if (matches.length !== 1) return undefined
60
+ const { event, index } = matches[0]
61
+ if (!isCanonicalSequence(event.seq) || event.seq <= rootCallSeq
62
+ || events.filter(candidate => candidate?.seq === event.seq).length !== 1
63
+ || event.data.rootCallId !== exec.rootCallId
64
+ || event.data.parentCallId !== exec.rootCallId
65
+ || !deepEqualJson(event.data.arguments, exec.arguments)) return undefined
66
+ const target = projectSessionLog({ session: { events: events.slice(0, index + 1) } }).editableRun
67
+ return { startSeq: event.seq, rootCallSeq, target }
68
+ }
69
+
38
70
  function unavailableDerivedResult(inner) {
39
71
  if (inner?.isError !== true) {
40
72
  throw new Error('ptc-plus: derived run_code result did not contain a valid execution journal')
@@ -61,19 +93,25 @@ function derivedEditResult(inner) {
61
93
  return result
62
94
  }
63
95
 
96
+ function derivedPresentation(inner) {
97
+ return {
98
+ content: inner.content,
99
+ ...(inner.additionalContexts === undefined ? {} : { additionalContexts: inner.additionalContexts }),
100
+ ...(inner.concludesTurn === true ? { concludesTurn: true } : {}),
101
+ }
102
+ }
103
+
64
104
  export function createEditTransportOwner(ctx, {
65
- durableReplay,
105
+ canScheduleEdit,
66
106
  executeTentative,
67
107
  presentationGeneration,
108
+ scheduleEdit,
68
109
  sessionId,
69
110
  toolSchemasForAgent,
70
111
  readSessionLog,
71
- sessionLogView = projectSessionLog,
72
112
  }) {
73
- let currentDurableReplay = durableReplay
74
- const editExecutionMetadata = new WeakMap()
75
113
  const editClaims = new Map()
76
- const pendingSettlements = new Map()
114
+ const pendingAcceptances = new Map()
77
115
  const installedScopes = new Map()
78
116
 
79
117
  const releaseClaim = (id, targetCallSeq) => {
@@ -83,10 +121,10 @@ export function createEditTransportOwner(ctx, {
83
121
  }
84
122
 
85
123
  const discardSettlements = (id) => {
86
- for (const [exec, pending] of pendingSettlements) {
124
+ for (const [exec, pending] of pendingAcceptances) {
87
125
  if (id !== undefined && pending.id !== id) continue
88
- pendingSettlements.delete(exec)
89
- pending.finalize(false)
126
+ pendingAcceptances.delete(exec)
127
+ pending.finish(false)
90
128
  }
91
129
  }
92
130
 
@@ -100,6 +138,7 @@ export function createEditTransportOwner(ctx, {
100
138
  description: derived.description,
101
139
  },
102
140
  }
141
+ if (derived.capabilityEvidence !== undefined) meta[CAPABILITY_EVIDENCE_KEY] = derived.capabilityEvidence
103
142
  if (derived.rewrites !== undefined) {
104
143
  meta[REWRITES_KEY] = normalizeRewrites(derived.rewrites)
105
144
  }
@@ -125,125 +164,162 @@ export function createEditTransportOwner(ctx, {
125
164
  type: 'object',
126
165
  additionalProperties: false,
127
166
  properties: {
128
- edited: { type: 'boolean' },
167
+ accepted: { type: 'boolean' },
168
+ scheduled: { type: 'boolean' },
169
+ target_call_seq: { type: 'integer' },
129
170
  reason: { type: 'string' },
130
- error: { type: 'string' },
131
- value: {},
132
- logs: { type: 'array', items: { type: 'string' } },
133
171
  },
134
- required: ['edited'],
172
+ required: ['accepted', 'scheduled'],
135
173
  },
136
174
  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
175
  }
145
176
  definition.execute = async (args, exec) => {
177
+ if (canScheduleEdit?.(exec) !== true) {
178
+ return { accepted: false, scheduled: false, reason: 'edit requires an active root run_code cell' }
179
+ }
146
180
  const agent = exec?.agent
147
- const callId = typeof exec?.callId === 'string' ? exec.callId : undefined
148
181
  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')
182
+ const start = nestedEditStart(sessionLog, exec)
183
+ if (start === undefined) {
184
+ throw new Error('ptc-plus: edit_run_code requires a unique persisted PTC sub-call start')
152
185
  }
153
- const target = persistedTargetCallSeq === undefined
154
- ? undefined
155
- : sessionLogView(agent, { callId, callSeq: persistedTargetCallSeq }).requestedEditTarget
186
+ const target = start.target
156
187
  if (target?.source === undefined || target.callSeq === undefined) {
157
- return { edited: false, reason: 'no run_code cell is currently eligible for safe editing' }
188
+ return { accepted: false, scheduled: false, reason: 'no run_code cell is currently eligible for safe editing' }
158
189
  }
159
190
  const edited = editRejectedCell(args, target.source)
160
- if (!edited.edited) return edited
191
+ if (!edited.edited) return { accepted: false, scheduled: false, reason: edited.reason }
161
192
  const expectedTargetCallSeq = args[EXPECTED_TARGET_CALL_SEQ]
162
193
  if (expectedTargetCallSeq !== undefined && expectedTargetCallSeq !== target.callSeq) {
163
194
  return {
164
- edited: false,
195
+ accepted: false,
196
+ scheduled: false,
165
197
  reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured call ${target.callSeq}`,
166
198
  }
167
199
  }
168
200
  if (typeof ctx.tools.execute !== 'function') {
169
201
  throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
170
202
  }
171
- const persistedCallSeq = persistedTargetCallSeq
172
203
  const id = sessionId(agent)
173
204
  let claims = editClaims.get(id)
174
205
  if (claims === undefined) {
175
206
  claims = new Map()
176
207
  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
208
  }
182
209
  if (claims.has(target.callSeq)) {
183
- return { edited: false, reason: 'the current edit target already has a derived execution' }
210
+ return { accepted: false, scheduled: false, reason: 'the current edit target already has a derived execution' }
184
211
  }
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)
212
+ claims.set(target.callSeq, 'scheduled')
213
+ const receipt = { accepted: true, scheduled: true, target_call_seq: target.callSeq }
214
+ const pending = {
215
+ id,
216
+ receipt,
217
+ confirmed: false,
218
+ startSeq: start.startSeq,
219
+ rootCallSeq: start.rootCallSeq,
220
+ subCallId: exec.callId,
221
+ targetCallSeq: target.callSeq,
222
+ finalized: false,
223
+ finalizeTentative: undefined,
224
+ finish(confirmed) {
225
+ if (this.finalized) return
226
+ this.finalized = true
227
+ pendingAcceptances.delete(exec)
228
+ this.finalizeTentative?.(confirmed)
229
+ releaseClaim(id, target.callSeq)
230
+ },
231
+ async dispatch(rootSignal, rootExec) {
232
+ if (!this.confirmed || this.finalized || rootSignal.aborted) {
233
+ this.finish(false)
234
+ return { outcome: { edited: false, reason: 'accepted edit was cancelled before derived execution' } }
235
+ }
236
+ if (rootExec?.name !== RUN_CODE || rootExec.callId !== exec.rootCallId
237
+ || rootExec.agent !== agent || rootExec.token === undefined) {
238
+ throw new Error('ptc-plus: derived edit requires its actual root transport execution')
239
+ }
240
+ const argumentsValue = snapshotJsonValue({ code: edited.code, description: edited.description })
241
+ if (argumentsValue === undefined) throw new Error('ptc-plus: derived source is not losslessly JSON-serializable')
242
+ const executionLog = await readSessionLog(agent.session, rootSignal)
243
+ if (rootSignal.aborted || this.finalized) {
244
+ this.finish(false)
245
+ return { outcome: { edited: false, reason: 'accepted edit was cancelled before derived execution' } }
246
+ }
247
+ const data = { rootCallId: exec.rootCallId, parentCallId: rootExec.callId,
248
+ subCallId: `${String(exec.callId)}:derived`, name: RUN_CODE, arguments: argumentsValue }
249
+ const executionStart = agent.session.append('tool/ptc-dispatch-start', data)
250
+ const derivedExec = { callId: data.subCallId, rootCallId: data.rootCallId,
251
+ parent: rootExec.token, name: RUN_CODE, arguments: argumentsValue, agent, signal: rootSignal }
252
+ const dispatch = () => ctx.tools.execute(derivedExec)
253
+ const tentative = await executeTentative(executionStart.seq, start.rootCallSeq,
254
+ dispatch, executionLog, derivedExec)
255
+ const inner = tentative.result
256
+ const metadata = tentative.metadata ?? inner.meta
257
+ this.finalizeTentative = tentative.finalize
258
+ let content = inner.content
259
+ try {
260
+ content = await ctx.waterfall(scopeTarget(ctx.tools, agent), 'tools/ptc-dispatch-log', {
261
+ exec: rootExec, agent, subCallId: data.subCallId, name: RUN_CODE,
262
+ isError: inner.isError, content: inner.content,
263
+ }, () => Promise.resolve(inner.content))
264
+ } catch {
265
+ // A log projector cannot change the settled execution or omit its fact.
266
+ content = inner.content
267
+ }
268
+ try {
269
+ agent.session.append('tool/ptc-dispatch', { ...data, isError: inner.isError,
270
+ ...(inner.error?.info === undefined ? {} : { error: inner.error.info }),
271
+ content,
272
+ ...(metadata === undefined ? {} : { meta: metadata }),
273
+ })
274
+ } catch (error) {
275
+ this.finish(false)
276
+ throw error
277
+ }
278
+ const journalValue = isRecord(metadata) && Object.hasOwn(metadata, JOURNAL_KEY)
279
+ ? metadata[JOURNAL_KEY] : undefined
280
+ const journal = journalValue === undefined ? undefined : normalizeJournal(journalValue)
281
+ if (journal === undefined || journal.status === 'noop'
282
+ || inner.isError !== (journal.completion?.kind === 'throw' || journal.status === 'discarded')) {
283
+ this.finish(false)
284
+ return { outcome: unavailableDerivedResult(inner), presentation: derivedPresentation(inner) }
285
+ }
286
+ const derived = {
287
+ targetCallSeq: target.callSeq,
288
+ journal,
289
+ rewrites: validatedRewrites(metadata),
290
+ recoveryBoundaries: isRecord(metadata) && Object.hasOwn(metadata, RECOVERY_BOUNDARY_KEY)
291
+ ? normalizeRecoveryBoundaries(metadata[RECOVERY_BOUNDARY_KEY]) : undefined,
292
+ replMemory: validatedReplMemorySnapshot(metadata, presentationGeneration),
293
+ userBindings: userBindingsSnapshotFromMeta(metadata),
294
+ draftCapability: userBindingDraftCapabilityFromMeta(metadata, presentationGeneration),
295
+ capabilityEvidence: metadata?.[CAPABILITY_EVIDENCE_KEY],
296
+ code: edited.code,
297
+ description: edited.description,
298
+ }
299
+ return {
300
+ outcome: derivedEditResult(inner),
301
+ presentation: derivedPresentation(inner),
302
+ provenance: {
303
+ startSeq: start.startSeq,
304
+ executionStartSeq: executionStart.seq,
305
+ rootCallSeq: start.rootCallSeq,
306
+ subCallId: exec.callId,
307
+ meta: derivedMetadata(derived),
308
+ },
309
+ }
310
+ },
311
+ }
312
+ if (scheduleEdit?.(exec, pending) !== true) {
313
+ pending.finish(false)
314
+ return { accepted: false, scheduled: false, reason: 'edit requires an active root run_code cell' }
241
315
  }
316
+ pendingAcceptances.set(exec, pending)
317
+ return receipt
242
318
  }
243
319
 
244
320
  const disposeInstalled = (installed) => {
245
321
  const failures = []
246
- for (const key of ['disposePresentation', 'disposeRegistration']) {
322
+ for (const key of ['disposeRegistration']) {
247
323
  const dispose = installed[key]
248
324
  if (dispose === undefined) continue
249
325
  try {
@@ -264,8 +340,7 @@ export function createEditTransportOwner(ctx, {
264
340
  try {
265
341
  disposeInstalled(installed)
266
342
  } finally {
267
- if (installed.disposePresentation === undefined
268
- && installed.disposeRegistration === undefined) {
343
+ if (installed.disposeRegistration === undefined) {
269
344
  installedScopes.delete(agent)
270
345
  }
271
346
  }
@@ -273,23 +348,13 @@ export function createEditTransportOwner(ctx, {
273
348
 
274
349
  return Object.freeze({
275
350
  definition,
276
- reconfigure(nextConfig) {
277
- currentDurableReplay = nextConfig.durableReplay
278
- },
279
351
  isInstalled(agent) {
280
352
  const installed = installedScopes.get(agent)
281
- return installed?.disposePresentation !== undefined
282
- && installed.disposeRegistration !== undefined
353
+ return installed?.disposeRegistration !== undefined
283
354
  },
284
355
  ensureInstalled(agent) {
285
356
  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
- }
357
+ if (installed !== undefined) return installed.definition
293
358
  const liveSchemas = typeof toolSchemasForAgent === 'function'
294
359
  ? toolSchemasForAgent(agent)
295
360
  : []
@@ -298,8 +363,8 @@ export function createEditTransportOwner(ctx, {
298
363
  throw new Error('ptc-plus: cannot install edit_run_code; the agent scope already owns that tool name')
299
364
  }
300
365
  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')
366
+ if (typeof scopedTools?.register !== 'function') {
367
+ throw new Error('ptc-plus: DSH agent-scoped tools.register is required to expose edit_run_code')
303
368
  }
304
369
  const disposeRegistration = scopedTools.register(definition)
305
370
  if (typeof disposeRegistration !== 'function') {
@@ -307,43 +372,19 @@ export function createEditTransportOwner(ctx, {
307
372
  }
308
373
  const nextInstalled = {
309
374
  definition,
310
- disposePresentation: undefined,
311
375
  disposeRegistration,
312
376
  sessionId: sessionId(agent),
313
377
  }
314
378
  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
379
  return definition
334
380
  },
335
381
  handleResult(exec, result) {
336
- const pending = pendingSettlements.get(exec)
337
- pendingSettlements.delete(exec)
382
+ const pending = pendingAcceptances.get(exec)
338
383
  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)
384
+ const accepted = result?.isError === false
385
+ && deepEqualJson(result.value, pending.receipt)
386
+ pending.confirmed = accepted
387
+ if (!accepted) pending.finish(false)
347
388
  },
348
389
  disposeAgent(agent) {
349
390
  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
  }
@@ -69,7 +72,7 @@ export function apply(ctx, config = {}) {
69
72
  get timeout() { return readOriginal().timeout },
70
73
  resolve(request) {
71
74
  const original = readOriginal()
72
- const spec = original.resolve(request)
75
+ const spec = serviceName === EXECUTION_SEAM_SERVICE ? original.resolve(request) : request
73
76
  if (spec !== null && typeof spec === 'object') {
74
77
  resolutions.set(spec, { original, generation })
75
78
  }
@@ -82,7 +85,7 @@ export function apply(ctx, config = {}) {
82
85
  return original.run(spec)
83
86
  },
84
87
  }
85
- const seam = createExecutionSeam(delegate, EXECUTION_SEAM_SERVICE)
88
+ const seam = createExecutionSeam(delegate, serviceName)
86
89
  const entry = { seam }
87
90
  live = entry
88
91
  entry.disposeChild = ctx.effect(() => {
@@ -91,7 +94,7 @@ export function apply(ctx, config = {}) {
91
94
  apply(childContext) {
92
95
  childContext.effect(() => {
93
96
  if (stopped || live !== entry || !isolated()) return () => {}
94
- entry.unregister = childContext.provide(EXECUTION_SEAM_SERVICE, seam.provider)
97
+ entry.unregister = childContext.provide(serviceName, seam.provider)
95
98
  return () => entry.unregister()
96
99
  }, 'ptc-plus owned execution registration')
97
100
  },
@@ -109,9 +112,9 @@ export function apply(ctx, config = {}) {
109
112
  const watch = () => {
110
113
  if (stopped || watcher !== undefined || !isolated()) return
111
114
  ctx.effect(() => {
112
- const observer = originalContext.inject([EXECUTION_SEAM_SERVICE], scope => {
115
+ const observer = originalContext.inject([serviceName], scope => {
113
116
  if (stopped || !isolated()) return
114
- createExecutionSeam(scope.get(EXECUTION_SEAM_SERVICE), EXECUTION_SEAM_SERVICE)
117
+ createExecutionSeam(scope.get(serviceName), serviceName)
115
118
  const currentGeneration = { active: true }
116
119
  generation = currentGeneration
117
120
  scope.effect(() => () => {
@@ -146,7 +149,7 @@ export function apply(ctx, config = {}) {
146
149
  }, 'ptc-plus execution provider composition')
147
150
  if (originalEntry.disabled) resolveAttachment()
148
151
  const timer = setTimeout(() => rejectAttachment(new Error(
149
- `ptc-plus: original execution entry ${originalEntryId} did not publish ${EXECUTION_SEAM_SERVICE}`,
152
+ `ptc-plus: original execution entry ${originalEntryId} did not publish ${serviceName}`,
150
153
  )), 30_000)
151
154
  ctx.effect(() => () => clearTimeout(timer), 'ptc-plus original execution provider wait')
152
155
  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])
@@ -86,9 +86,9 @@ export function createExecutionSeam(service, serviceName) {
86
86
  return Promise.all([...sourceListeners].map(listener => listener())).then(() => undefined)
87
87
  },
88
88
  /**
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.
89
+ * Run one program through the provider this plugin wraps. The resolved-spec
90
+ * contract resolves the request before running it, exactly as its consumer
91
+ * does; the request-only contract accepts the request directly.
92
92
  */
93
93
  invokeUpstream(request) {
94
94
  requireCompatible()
@@ -132,9 +132,9 @@ function registeredExecutionSeam(service, serviceName) {
132
132
  /**
133
133
  * Wait for the host's execution seam and attach the plugin to the live one.
134
134
  *
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.
135
+ * The resolved-spec service is preferred whenever both contracts are live; the
136
+ * request-only service attaches when it is absent. A resolved-spec service
137
+ * registered later retires the request-only injection before attaching.
138
138
  * The injected scope requires the plugin's own core
139
139
  * services as well, so the callback receives a context the plugin can work in,
140
140
  * and everything the plugin registers there belongs to the seam the host