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.
- package/README.en.md +65 -127
- package/README.md +65 -127
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.cjs +42702 -0
- package/cordis.patch.yml +1 -0
- package/docs/README.md +6 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
- package/docs/adr/0008-define-agent-operating-contract.md +5 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -4
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +2 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
- package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
- package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
- package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
- package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
- package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
- package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
- package/docs/adr/0041-record-cell-execution-directories.md +65 -0
- package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
- package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/README.md +8 -0
- package/docs/architecture.md +23 -25
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +70 -23
- package/docs/client-visual-contract.md +26 -9
- package/docs/context-management.md +22 -0
- package/docs/context-optimization-plan.md +353 -0
- package/docs/context-public-interface-coverage.md +25 -0
- package/docs/durability-design.md +20 -15
- package/docs/evaluation.md +41 -6
- package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
- package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
- package/docs/installation.md +33 -10
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +7 -7
- package/docs/release-coverage.md +73 -0
- package/docs/release-notes.md +1 -1
- package/docs/review-procedure.md +56 -0
- package/docs/runtime-reference.md +53 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.md +1 -1
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +26 -15
- package/docs/verification.md +30 -88
- package/index.js +146 -20
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +31 -0
- package/internal/capability-environment.js +1253 -0
- package/internal/capability-plan.js +285 -0
- package/internal/capability-policy.js +96 -0
- package/internal/capability-statistics-store.js +180 -0
- package/internal/capability-statistics.js +214 -0
- package/internal/config-spec.js +89 -10
- package/internal/direct-surface-owner.js +27 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +195 -140
- package/internal/execution-provider-owner.js +40 -12
- package/internal/execution-seam-compat.js +26 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/official-repl-priority.js +51 -0
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +3 -2
- package/internal/rejected-cell-editor.js +8 -8
- package/internal/repl-observation-interest.js +11 -3
- package/internal/rpc-contract.js +1 -0
- package/internal/runtime-bridge-owner.js +275 -34
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +74 -5
- package/internal/runtime-intrinsics.js +1 -0
- package/internal/runtime-messages.js +41 -0
- package/internal/sdk-projection.js +10 -82
- package/internal/session-cell-executor.js +42 -19
- package/internal/session-events.js +34 -0
- package/internal/session-journal-recovery.js +234 -11
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +1 -1
- package/internal/session-runtime.js +119 -15
- package/internal/settings-compat.js +12 -5
- package/internal/stateful-module-compiler.js +3 -2
- package/internal/stateful-module-runtime.js +7 -3
- package/internal/stateful-root-runtime.js +9 -4
- package/internal/user-binding-authoring.js +2 -7
- package/internal/user-bindings-owner.js +19 -2
- package/internal/validated-parse-repair.js +1 -1
- package/internal/value-wire.js +68 -21
- package/internal/worker-cwd-virtualization.js +20 -18
- package/internal/worker-realm-surfaces.js +3 -3
- package/package.json +49 -30
- package/src/client-activity.js +3 -2
- package/src/client-authoring-view.js +67 -30
- package/src/client-binding-review.js +2 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1555 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-copy.js +923 -8
- package/src/client-feature-gates.js +3 -0
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +33 -2
- package/src/client-repl-view.js +20 -13
- package/src/client-settings-view.js +411 -41
- package/src/client-styles.js +112 -15
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -9
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +6 -7
- package/src/client.js +76 -22
- package/client.js +0 -32479
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
JOURNAL_KEY,
|
|
6
6
|
RECOVERY_BOUNDARY_KEY,
|
|
7
7
|
REWRITES_KEY,
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
133
|
+
for (const [exec, pending] of pendingAcceptances) {
|
|
87
134
|
if (id !== undefined && pending.id !== id) continue
|
|
88
|
-
|
|
89
|
-
pending.
|
|
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
|
-
|
|
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: ['
|
|
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
|
|
150
|
-
if (
|
|
151
|
-
throw new Error('ptc-plus: edit_run_code requires a unique persisted
|
|
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 =
|
|
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 {
|
|
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)
|
|
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
|
-
|
|
165
|
-
|
|
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 {
|
|
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, '
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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 ['
|
|
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.
|
|
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?.
|
|
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'
|
|
302
|
-
throw new Error('ptc-plus: DSH agent-scoped tools.register
|
|
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 =
|
|
337
|
-
pendingSettlements.delete(exec)
|
|
396
|
+
const pending = pendingAcceptances.get(exec)
|
|
338
397
|
if (pending === undefined) return
|
|
339
|
-
const
|
|
340
|
-
result
|
|
341
|
-
|
|
342
|
-
|
|
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?.[
|
|
17
|
-
if (!isolated()) throw new Error(`ptc-plus: original entry ${originalEntryId} must isolate ${
|
|
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(
|
|
35
|
+
const original = isolated() ? originalContext.get(serviceName) : undefined
|
|
33
36
|
if (stopped || original === undefined) {
|
|
34
|
-
throw new Error(`ptc-plus: original ${
|
|
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,
|
|
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(
|
|
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([
|
|
140
|
+
const observer = originalContext.inject([serviceName], scope => {
|
|
113
141
|
if (stopped || !isolated()) return
|
|
114
|
-
createExecutionSeam(scope.get(
|
|
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 ${
|
|
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
|
|
1
|
+
/** Resolved-spec Cordis execution contract. */
|
|
2
2
|
export const EXECUTION_SEAM_SERVICE = 'ptcRuntime'
|
|
3
3
|
|
|
4
|
-
/**
|
|
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
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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
|
|
136
|
-
*
|
|
137
|
-
*
|
|
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
|