dsh-ptc-plus 0.2.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/README.zh.md +137 -0
  4. package/assets/dsh-ptc-plus-banner.webp +0 -0
  5. package/assets/ptc-plus-repair.png +0 -0
  6. package/assets/ptc-plus-settings.png +0 -0
  7. package/client.js +366 -0
  8. package/cordis.patch.yml +5 -0
  9. package/docs/README.md +21 -0
  10. package/docs/adr/0001-delegate-governance-to-dsh.md +3 -0
  11. package/docs/adr/0002-limit-work-map-scope.md +3 -0
  12. package/docs/adr/0003-prefer-native-program-surfaces.md +3 -0
  13. package/docs/adr/0004-declare-program-bindings-at-the-host.md +5 -0
  14. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +129 -0
  15. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +45 -0
  16. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +39 -0
  17. package/docs/adr/0008-define-agent-operating-contract.md +25 -0
  18. package/docs/adr/0009-bound-expensive-acceptance-default.md +39 -0
  19. package/docs/adr/0010-session-log-derived-recovery-tips.md +26 -0
  20. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +20 -0
  21. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +23 -0
  22. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +21 -0
  23. package/docs/adr/0014-persist-cell-rewrite-policy.md +25 -0
  24. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +25 -0
  25. package/docs/adr/0016-archive-resolved-review-findings.md +43 -0
  26. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +23 -0
  27. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +25 -0
  28. package/docs/adr/0019-plugin-settings-and-kill-switch.md +32 -0
  29. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +36 -0
  30. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +68 -0
  31. package/docs/adr/README.md +18 -0
  32. package/docs/architecture.md +134 -0
  33. package/docs/capability-projection.md +48 -0
  34. package/docs/client-ui.md +47 -0
  35. package/docs/durability-design.md +265 -0
  36. package/docs/evaluation.md +62 -0
  37. package/docs/installation.md +82 -0
  38. package/docs/program-data-plane.md +61 -0
  39. package/docs/publishing.md +37 -0
  40. package/docs/runtime-reference.md +119 -0
  41. package/docs/value-wire.md +61 -0
  42. package/index.js +532 -0
  43. package/internal/ast-traversal.js +20 -0
  44. package/internal/binding-descriptors.js +95 -0
  45. package/internal/binding-pattern.js +54 -0
  46. package/internal/builtin-esm-sync.js +9 -0
  47. package/internal/cell-analysis.js +467 -0
  48. package/internal/cell-rewriter.js +520 -0
  49. package/internal/config-spec.js +179 -0
  50. package/internal/cordis-tools-owner.js +363 -0
  51. package/internal/diagnostic.js +133 -0
  52. package/internal/direct-surface-owner.js +326 -0
  53. package/internal/edit-transport-owner.js +287 -0
  54. package/internal/failure-reporting.js +139 -0
  55. package/internal/kernel-worker.js +484 -0
  56. package/internal/module-policy.js +46 -0
  57. package/internal/program-bindings.js +187 -0
  58. package/internal/record-utils.js +53 -0
  59. package/internal/recovery-tips.js +81 -0
  60. package/internal/rejected-cell-editor.js +328 -0
  61. package/internal/repl-convenience.js +201 -0
  62. package/internal/runtime-bridge-owner.js +417 -0
  63. package/internal/runtime-config.js +77 -0
  64. package/internal/runtime-contexts.js +31 -0
  65. package/internal/sdk-projection.js +88 -0
  66. package/internal/session-cell-executor.js +612 -0
  67. package/internal/session-journal.js +889 -0
  68. package/internal/session-log-view.js +80 -0
  69. package/internal/session-runtime.js +536 -0
  70. package/internal/session-state.js +45 -0
  71. package/internal/source-position-map.js +224 -0
  72. package/internal/tool-call-canonicalizer.js +339 -0
  73. package/internal/value-wire.js +421 -0
  74. package/internal/worker-client.js +218 -0
  75. package/internal/worker-cwd-virtualization.js +155 -0
  76. package/package.json +100 -0
  77. package/src/client.js +165 -0
@@ -0,0 +1,326 @@
1
+ /** Own PTC direct presentation, prompt projection, and stream normalization. */
2
+ import { canonicalizeToolCallStream } from './tool-call-canonicalizer.js'
3
+ import { editRunCodeSchema } from './rejected-cell-editor.js'
4
+ import { sessionRuntimeContexts } from './runtime-contexts.js'
5
+ import { projectProgramSdk } from './sdk-projection.js'
6
+ import { EDIT_RUN_CODE } from './edit-transport-owner.js'
7
+ import { RUN_CODE } from './runtime-bridge-owner.js'
8
+ import { isRecord } from './record-utils.js'
9
+
10
+ const RUN_CODE_TOOL_DESCRIPTION = 'Evaluate the next TypeScript cell in this session-bound persistent REPL. Earlier top-level bindings remain available, so this call extends the current environment instead of creating a fresh one. Use `code` for the async-function body and `description` for its short UI summary. Successful image-bearing subtool results are attached after the cell.'
11
+ const RUN_CODE_CODE_DESCRIPTION = 'Code for the next REPL cell, parsed as the body of an async TypeScript function.'
12
+ const RUN_CODE_DESCRIPTION_DESCRIPTION = 'Short active-voice summary of what this cell does, 5-10 words (shown in the UI).'
13
+ const CODE_TRANSPORT_INSTRUCTION = '`run_code` and `edit_run_code` are the only tools callable directly. Call every native tool declared by the SDK from inside a program.'
14
+
15
+ function adaptRunCodeSchema(tool) {
16
+ const parameters = tool.parameters
17
+ const properties = isRecord(parameters) ? parameters.properties : undefined
18
+ const code = isRecord(properties) ? properties.code : undefined
19
+ const description = isRecord(properties) ? properties.description : undefined
20
+ if (!isRecord(parameters) || parameters.type !== 'object' || !isRecord(properties)
21
+ || !isRecord(code) || code.type !== 'string'
22
+ || !isRecord(description) || description.type !== 'string') {
23
+ throw new Error('ptc-plus: incompatible run_code schema; expected object parameters with string code and description properties')
24
+ }
25
+ return {
26
+ ...tool,
27
+ description: RUN_CODE_TOOL_DESCRIPTION,
28
+ parameters: {
29
+ ...parameters,
30
+ properties: {
31
+ ...properties,
32
+ code: { ...code, description: RUN_CODE_CODE_DESCRIPTION },
33
+ description: { ...description, description: RUN_CODE_DESCRIPTION_DESCRIPTION },
34
+ },
35
+ },
36
+ }
37
+ }
38
+
39
+ function capabilitySdk(nativeSdk) {
40
+ return `${projectProgramSdk(nativeSdk)}
41
+
42
+ ## PTC Plus program capabilities
43
+
44
+ \`\`\`ts
45
+ declare class CapabilityExplorationError extends Error { readonly operation: "tree" | "find" | "inspect" }
46
+ declare const capabilities: {
47
+ tree(): Promise<Array<{ namespace: string; members: string[] }>>
48
+ find(query: string): Promise<Array<{ symbol: string; description?: string; completeness: string; effect: string; replay: string }>>
49
+ inspect(args?: { symbols?: string[]; budget?: number }): Promise<{ symbols: unknown[]; omitted: number; unknown: string[]; budget: number }>
50
+ }
51
+ \`\`\``
52
+ }
53
+
54
+ function rejection(message) {
55
+ return {
56
+ isError: true,
57
+ content: [{ type: 'text', text: message }],
58
+ error: { message },
59
+ }
60
+ }
61
+
62
+ function presentationState(assembly) {
63
+ const tools = Array.isArray(assembly?.tools) ? assembly.tools : []
64
+ if (!tools.some(tool => tool?.name === RUN_CODE)) {
65
+ return { presentation: 'native', ownerProven: false }
66
+ }
67
+ const collapse = assembly?.sections?.find(section => section?.name === 'tools:code-only')
68
+ if (collapse !== undefined) {
69
+ const presentation = typeof collapse.text === 'string' && collapse.text.trim().length > 0
70
+ ? 'code'
71
+ : 'both'
72
+ return { presentation, ownerProven: true }
73
+ }
74
+ const presentation = tools.every(tool => tool?.name === RUN_CODE || tool?.name === EDIT_RUN_CODE)
75
+ ? 'code'
76
+ : 'both'
77
+ return { presentation, ownerProven: false }
78
+ }
79
+
80
+ async function* bindCallPolicies(source, policy, calls) {
81
+ const pending = new Map()
82
+ for await (const chunk of source) {
83
+ if (chunk?.type === 'tool-call-delta'
84
+ && typeof chunk.id === 'string' && chunk.id.length > 0) {
85
+ pending.set(chunk.index, chunk.id)
86
+ } else if (chunk?.type === 'block-end' && chunk.block?.type === 'tool-call') {
87
+ const callId = chunk.block.id
88
+ if (typeof callId === 'string' && callId.length > 0) calls.set(callId, policy)
89
+ pending.delete(chunk.index)
90
+ } else if (chunk?.type === 'finish') {
91
+ for (const callId of pending.values()) calls.set(callId, policy)
92
+ pending.clear()
93
+ }
94
+ yield chunk
95
+ }
96
+ }
97
+
98
+ export function createDirectSurfaceOwner({
99
+ editTransport,
100
+ runtimeConfig,
101
+ canonicalizeToolCalls,
102
+ sessionId,
103
+ toolSchemasForAgent,
104
+ }) {
105
+ // Composition is anchored to Agent identity because DSH selects presentation
106
+ // once per composed agent. Request signals bind the exact assembly to stream
107
+ // execution, while session identity is the fallback for hosts that replace a
108
+ // signal between those stages. Calls retain the resolved request policy by id
109
+ // so a later assembly cannot reinterpret an in-flight dispatch.
110
+ const compositions = new Map()
111
+ const canonicalRequests = new WeakMap()
112
+ const sessions = new Map()
113
+ let currentCanonicalizeToolCalls = canonicalizeToolCalls
114
+ const tipConfig = {
115
+ enabled: runtimeConfig.tipsEnabled,
116
+ cooldownMessages: runtimeConfig.tipCooldownMessages,
117
+ escalationFailures: runtimeConfig.tipEscalationFailures,
118
+ }
119
+
120
+ const sessionOwner = (id) => {
121
+ let owner = sessions.get(id)
122
+ if (owner === undefined) {
123
+ owner = { active: true, calls: new Map(), id, latestRequest: undefined }
124
+ sessions.set(id, owner)
125
+ }
126
+ return owner
127
+ }
128
+
129
+ const clearSession = (id) => {
130
+ const owner = sessions.get(id)
131
+ if (owner !== undefined) owner.active = false
132
+ sessions.delete(id)
133
+ }
134
+
135
+ const requestPolicy = (signal, id) => {
136
+ if (signal !== null && typeof signal === 'object') {
137
+ const request = canonicalRequests.get(signal)
138
+ return request?.owner.active === true && (id === undefined || request.owner.id === id)
139
+ ? request
140
+ : undefined
141
+ }
142
+ return id === undefined ? undefined : sessions.get(id)?.latestRequest
143
+ }
144
+
145
+ const executionPolicy = (id, callId) => {
146
+ if (id === undefined) return undefined
147
+ const owner = sessions.get(id)
148
+ if (typeof callId === 'string' && owner?.calls.has(callId)) {
149
+ return owner.calls.get(callId)
150
+ }
151
+ return undefined
152
+ }
153
+
154
+ const rememberRequest = (id, signal, presentation, nativeSchemas) => {
155
+ const owner = sessionOwner(id)
156
+ const request = { presentation, nativeSchemas, owner }
157
+ owner.latestRequest = request
158
+ if (signal !== undefined) canonicalRequests.set(signal, request)
159
+ }
160
+
161
+ const captureComposition = (agent, id, presentation) => {
162
+ let composition = compositions.get(agent)
163
+ if (composition !== undefined) return composition
164
+ composition = { presentation, sessionId: id }
165
+ compositions.set(agent, composition)
166
+ return composition
167
+ }
168
+
169
+ const clearCompositionsForSession = (id) => {
170
+ for (const [agent, composition] of compositions) {
171
+ if (composition.sessionId === id) compositions.delete(agent)
172
+ }
173
+ }
174
+
175
+ return Object.freeze({
176
+ reconfigure(nextConfig) {
177
+ currentCanonicalizeToolCalls = nextConfig.canonicalizeToolCalls
178
+ tipConfig.enabled = nextConfig.tipsEnabled
179
+ tipConfig.cooldownMessages = nextConfig.tipCooldownMessages
180
+ tipConfig.escalationFailures = nextConfig.tipEscalationFailures
181
+ },
182
+ async assemble(initialAssembly, context, next) {
183
+ const agent = context?.agent
184
+ const id = sessionId(agent)
185
+ const initialState = presentationState(initialAssembly)
186
+ let composition = compositions.get(agent)
187
+ if (composition === undefined && id !== undefined
188
+ && (initialState.ownerProven || initialState.presentation === 'code')) {
189
+ composition = captureComposition(agent, id, initialState.presentation)
190
+ }
191
+ if (composition?.presentation === 'code' && !editTransport.isInstalled(agent)) {
192
+ editTransport.ensureInstalled(agent)
193
+ }
194
+
195
+ const assembly = await next()
196
+ const tools = assembly.tools
197
+ if (!Array.isArray(tools)) {
198
+ throw new Error('ptc-plus: incompatible prompt assembly; expected a tools array')
199
+ }
200
+ const completedState = presentationState(assembly)
201
+ if (composition === undefined && id !== undefined) {
202
+ const state = initialState.ownerProven ? initialState : completedState
203
+ composition = captureComposition(agent, id, state.presentation)
204
+ if (composition.presentation === 'code' && !editTransport.isInstalled(agent)) {
205
+ editTransport.ensureInstalled(agent)
206
+ }
207
+ }
208
+ const presentation = composition?.presentation
209
+ ?? (initialState.ownerProven ? initialState.presentation : completedState.presentation)
210
+ const requestSignal = context?.signal !== null && typeof context?.signal === 'object'
211
+ ? context.signal
212
+ : undefined
213
+ if (!tools.some(tool => tool?.name === RUN_CODE)) {
214
+ if (presentation !== 'native') {
215
+ throw new Error(`ptc-plus: ${presentation} agent composition assembled without run_code`)
216
+ }
217
+ if (id !== undefined) {
218
+ rememberRequest(id, requestSignal, 'native', new Map())
219
+ }
220
+ return assembly
221
+ }
222
+ if (presentation === 'native') {
223
+ throw new Error('ptc-plus: native agent composition assembled with run_code')
224
+ }
225
+ const sessionPtc = presentation !== 'native' && id !== undefined
226
+ const sessionCodeOnlyProjection = presentation === 'code' && id !== undefined
227
+ const runCode = tools.find(tool => tool?.name === RUN_CODE)
228
+ let directTools = tools
229
+ if (sessionCodeOnlyProjection) {
230
+ editTransport.ensureInstalled(agent)
231
+ directTools = [adaptRunCodeSchema(runCode), editRunCodeSchema()]
232
+ }
233
+ const runtimeContexts = sessionPtc
234
+ ? sessionRuntimeContexts(agent, tipConfig)
235
+ : { contexts: [] }
236
+ const contexts = sessionPtc && Array.isArray(assembly.contexts)
237
+ && runtimeContexts.contexts.length > 0
238
+ ? [...assembly.contexts, ...runtimeContexts.contexts]
239
+ : assembly.contexts
240
+ const projectsSections = presentation !== 'native' && Array.isArray(assembly.sections)
241
+ && assembly.sections.some(section => section?.name === 'tools:sdk'
242
+ || (sessionCodeOnlyProjection && section?.name === 'tools:code-only'))
243
+ const sections = projectsSections
244
+ ? assembly.sections.map(section => {
245
+ if (section?.name === 'tools:sdk') {
246
+ return { ...section, text: capabilitySdk(section.text) }
247
+ }
248
+ if (sessionCodeOnlyProjection && section?.name === 'tools:code-only') {
249
+ return { ...section, text: CODE_TRANSPORT_INSTRUCTION }
250
+ }
251
+ return section
252
+ })
253
+ : assembly.sections
254
+
255
+ if (id !== undefined) {
256
+ const nativeSchemas = presentation === 'code'
257
+ ? new Map(toolSchemasForAgent(agent)
258
+ .filter(schema => typeof schema?.name === 'string'
259
+ && schema.name !== RUN_CODE && schema.name !== EDIT_RUN_CODE)
260
+ .map(schema => [schema.name, schema]))
261
+ : new Map()
262
+ rememberRequest(id, requestSignal, presentation, nativeSchemas)
263
+ }
264
+
265
+ return {
266
+ ...assembly,
267
+ tools: sessionCodeOnlyProjection
268
+ ? directTools
269
+ : directTools.map(tool => tool?.name === RUN_CODE ? adaptRunCodeSchema(tool) : tool),
270
+ sections,
271
+ contexts,
272
+ }
273
+ },
274
+ stream(options, next) {
275
+ const optionSessionId = options.sessionId === undefined ? undefined : String(options.sessionId)
276
+ const policy = requestPolicy(options?.signal, optionSessionId)
277
+ if (policy === undefined || optionSessionId === undefined) return next()
278
+ const source = policy.presentation === 'code'
279
+ ? canonicalizeToolCallStream(next(), {
280
+ tools: options.tools,
281
+ nativeSchemas: currentCanonicalizeToolCalls ? policy.nativeSchemas : new Map(),
282
+ editToolName: EDIT_RUN_CODE,
283
+ })
284
+ : next()
285
+ return bindCallPolicies(source, policy, policy.owner.calls)
286
+ },
287
+ executionRejection(exec) {
288
+ if (exec.name === EDIT_RUN_CODE && exec.parent !== undefined) {
289
+ return rejection(`tool ${EDIT_RUN_CODE} is only callable directly in PTC mode; call native tools from inside run_code`)
290
+ }
291
+ const id = sessionId(exec.agent)
292
+ const policy = executionPolicy(id, exec.callId)
293
+ ?? requestPolicy(exec?.signal, id)
294
+ if (exec.name === EDIT_RUN_CODE) {
295
+ return policy?.presentation === 'code'
296
+ ? undefined
297
+ : rejection(`tool ${EDIT_RUN_CODE} is not declared for this request`)
298
+ }
299
+ if (exec.name === RUN_CODE || exec.parent !== undefined) {
300
+ return undefined
301
+ }
302
+ if (policy?.presentation !== 'code') return undefined
303
+ return rejection(`tool ${exec.name} is not a direct PTC tool; use run_code or edit_run_code directly, and call native tools from inside run_code`)
304
+ },
305
+ handleResult(exec) {
306
+ const id = sessionId(exec.agent)
307
+ if (id === undefined || typeof exec.callId !== 'string') return
308
+ sessions.get(id)?.calls.delete(exec.callId)
309
+ },
310
+ disposeAgent(agent) {
311
+ const id = sessionId(agent)
312
+ compositions.delete(agent)
313
+ if (id !== undefined) clearSession(id)
314
+ },
315
+ disposeSession(session) {
316
+ const id = String(session.id)
317
+ clearCompositionsForSession(id)
318
+ clearSession(id)
319
+ },
320
+ dispose() {
321
+ compositions.clear()
322
+ for (const owner of sessions.values()) owner.active = false
323
+ sessions.clear()
324
+ },
325
+ })
326
+ }
@@ -0,0 +1,287 @@
1
+ /** Own the scoped edit tool, target claims, derived metadata, and paired disposers. */
2
+ import {
3
+ DERIVED_RUN_KEY,
4
+ EDIT_TARGET_KEY,
5
+ JOURNAL_KEY,
6
+ RECOVERY_BOUNDARY_KEY,
7
+ REWRITES_KEY,
8
+ derivedEditResultsEqual,
9
+ liveToolCallSeq,
10
+ normalizeJournal,
11
+ normalizeRecoveryBoundaries,
12
+ normalizeRewrites,
13
+ validatedRewrites,
14
+ } from './session-journal.js'
15
+ import { editRejectedCell, editRunCodeSchema } from './rejected-cell-editor.js'
16
+ import { editTargetForCall, projectSessionLog } from './session-log-view.js'
17
+ import { RUN_CODE } from './runtime-bridge-owner.js'
18
+ import { isRecord } from './record-utils.js'
19
+
20
+ export const EDIT_RUN_CODE = 'edit_run_code'
21
+
22
+ function unavailableDerivedResult(inner) {
23
+ if (inner?.isError !== true) {
24
+ throw new Error('ptc-plus: derived run_code result did not contain a valid execution journal')
25
+ }
26
+ return {
27
+ edited: false,
28
+ error: inner.error?.message ?? 'derived run_code did not enter the runtime',
29
+ logs: [],
30
+ }
31
+ }
32
+
33
+ function derivedEditResult(inner) {
34
+ const result = {
35
+ edited: true,
36
+ logs: Array.isArray(inner?.value?.logs) ? inner.value.logs : [],
37
+ }
38
+ if (inner?.isError === true) {
39
+ result.error = inner.error?.message ?? 'derived run_code execution failed'
40
+ } else if (inner?.value !== undefined && !isRecord(inner.value)) {
41
+ result.value = inner.value
42
+ } else if (inner?.value?.result !== undefined) {
43
+ result.value = inner.value.result
44
+ }
45
+ return result
46
+ }
47
+
48
+ export function createEditTransportOwner(ctx, {
49
+ durableReplay,
50
+ executeTentative,
51
+ sessionId,
52
+ toolSchemasForAgent,
53
+ }) {
54
+ let currentDurableReplay = durableReplay
55
+ const editExecutionMetadata = new WeakMap()
56
+ const editClaims = new Map()
57
+ const pendingSettlements = new Map()
58
+ const installedScopes = new Map()
59
+
60
+ const releaseClaim = (id, targetCallSeq) => {
61
+ const claims = editClaims.get(id)
62
+ claims?.delete(targetCallSeq)
63
+ if (claims?.size === 0) editClaims.delete(id)
64
+ }
65
+
66
+ const discardSettlements = (id) => {
67
+ for (const [exec, pending] of pendingSettlements) {
68
+ if (id !== undefined && pending.id !== id) continue
69
+ pendingSettlements.delete(exec)
70
+ pending.finalize(false)
71
+ }
72
+ }
73
+
74
+ const definition = editRunCodeSchema()
75
+ const derivedMetadata = (derived) => {
76
+ const meta = {
77
+ [JOURNAL_KEY]: normalizeJournal(derived.journal),
78
+ [EDIT_TARGET_KEY]: { targetCallSeq: derived.targetCallSeq },
79
+ [DERIVED_RUN_KEY]: {
80
+ code: derived.code,
81
+ description: derived.description,
82
+ },
83
+ }
84
+ if (derived.rewrites !== undefined) {
85
+ meta[REWRITES_KEY] = normalizeRewrites(derived.rewrites)
86
+ }
87
+ if (derived.recoveryBoundaries !== undefined) {
88
+ meta[RECOVERY_BOUNDARY_KEY] = normalizeRecoveryBoundaries(derived.recoveryBoundaries)
89
+ }
90
+ return meta
91
+ }
92
+ definition.output = {
93
+ schema: {
94
+ type: 'object',
95
+ additionalProperties: false,
96
+ properties: {
97
+ edited: { type: 'boolean' },
98
+ reason: { type: 'string' },
99
+ error: { type: 'string' },
100
+ value: {},
101
+ logs: { type: 'array', items: { type: 'string' } },
102
+ },
103
+ required: ['edited'],
104
+ },
105
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value ?? {}) }],
106
+ presentationMeta: (args) => {
107
+ const meta = {}
108
+ if (!isRecord(args)) return meta
109
+ const derived = editExecutionMetadata.get(args)
110
+ if (derived === undefined) return meta
111
+ return derivedMetadata(derived)
112
+ },
113
+ }
114
+ definition.execute = async (args, exec) => {
115
+ const agent = exec?.agent
116
+ const callId = typeof exec?.callId === 'string' ? exec.callId : undefined
117
+ const persistedTargetCallSeq = liveToolCallSeq(agent?.session, callId, EDIT_RUN_CODE)
118
+ if (persistedTargetCallSeq === undefined && projectSessionLog(agent).editableRun !== undefined) {
119
+ throw new Error('ptc-plus: edit_run_code requires a unique persisted tool/call event')
120
+ }
121
+ const target = persistedTargetCallSeq === undefined
122
+ ? undefined
123
+ : editTargetForCall(agent, callId, persistedTargetCallSeq)
124
+ if (target?.source === undefined || target.callSeq === undefined) {
125
+ return { edited: false, reason: 'no run_code cell is currently eligible for safe editing' }
126
+ }
127
+ const edited = editRejectedCell(args, target.source)
128
+ if (!edited.edited) return edited
129
+ if (typeof ctx.tools.execute !== 'function') {
130
+ throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
131
+ }
132
+ const persistedCallSeq = currentDurableReplay ? persistedTargetCallSeq : undefined
133
+ const id = sessionId(agent)
134
+ let claims = editClaims.get(id)
135
+ if (claims === undefined) {
136
+ claims = new Map()
137
+ editClaims.set(id, claims)
138
+ } else {
139
+ for (const [callSeq, phase] of claims) {
140
+ if (phase === 'settled' && callSeq !== target.callSeq) claims.delete(callSeq)
141
+ }
142
+ }
143
+ if (claims.has(target.callSeq)) {
144
+ return { edited: false, reason: 'the current edit target already has a derived execution' }
145
+ }
146
+ claims.set(target.callSeq, 'executing')
147
+ let retainClaim = false
148
+ let finalizeTentative
149
+ try {
150
+ const dispatch = () => ctx.tools.execute({
151
+ callId: `${String(exec.callId)}:derived`,
152
+ name: RUN_CODE,
153
+ arguments: { code: edited.code, description: edited.description },
154
+ agent,
155
+ signal: exec.signal,
156
+ })
157
+ const tentative = await executeTentative(persistedCallSeq, dispatch)
158
+ const inner = tentative.result
159
+ finalizeTentative = tentative.finalize
160
+ const journalValue = isRecord(inner?.meta) && Object.hasOwn(inner.meta, JOURNAL_KEY)
161
+ ? inner.meta[JOURNAL_KEY]
162
+ : undefined
163
+ const journal = journalValue === undefined ? undefined : normalizeJournal(journalValue)
164
+ if (journal === undefined || journal.status === 'noop') return unavailableDerivedResult(inner)
165
+ const rewrites = validatedRewrites(inner.meta)
166
+ const recoveryBoundaries = isRecord(inner?.meta)
167
+ && Object.hasOwn(inner.meta, RECOVERY_BOUNDARY_KEY)
168
+ ? normalizeRecoveryBoundaries(inner.meta[RECOVERY_BOUNDARY_KEY])
169
+ : undefined
170
+ const value = derivedEditResult(inner)
171
+ const derived = {
172
+ targetCallSeq: target.callSeq,
173
+ journal,
174
+ rewrites,
175
+ recoveryBoundaries,
176
+ code: edited.code,
177
+ description: edited.description,
178
+ }
179
+ editExecutionMetadata.set(args, derived)
180
+ claims.set(target.callSeq, 'settled')
181
+ pendingSettlements.set(exec, {
182
+ id,
183
+ targetCallSeq: target.callSeq,
184
+ expectedMeta: derivedMetadata(derived),
185
+ finalize: finalizeTentative,
186
+ })
187
+ finalizeTentative = undefined
188
+ retainClaim = true
189
+ return value
190
+ } finally {
191
+ finalizeTentative?.(false)
192
+ if (!retainClaim) releaseClaim(id, target.callSeq)
193
+ }
194
+ }
195
+
196
+ const uninstall = (agent) => {
197
+ const installed = installedScopes.get(agent)
198
+ if (installed === undefined) return
199
+ installedScopes.delete(agent)
200
+ installed.dispose()
201
+ }
202
+
203
+ return Object.freeze({
204
+ definition,
205
+ reconfigure(nextConfig) {
206
+ currentDurableReplay = nextConfig.durableReplay
207
+ },
208
+ isInstalled(agent) {
209
+ return installedScopes.has(agent)
210
+ },
211
+ ensureInstalled(agent) {
212
+ const installed = installedScopes.get(agent)
213
+ if (installed !== undefined) return installed.definition
214
+ const liveSchemas = typeof toolSchemasForAgent === 'function'
215
+ ? toolSchemasForAgent(agent)
216
+ : []
217
+ if (Array.isArray(liveSchemas)
218
+ && liveSchemas.some(schema => schema?.name === EDIT_RUN_CODE)) {
219
+ throw new Error('ptc-plus: cannot install edit_run_code; the agent scope already owns that tool name')
220
+ }
221
+ const scopedTools = agent?.ctx?.tools
222
+ if (typeof scopedTools?.register !== 'function' || typeof scopedTools.presentAs !== 'function') {
223
+ throw new Error('ptc-plus: DSH agent-scoped tools.register and tools.presentAs are required to expose edit_run_code')
224
+ }
225
+ const disposeRegistration = scopedTools.register(definition)
226
+ if (typeof disposeRegistration !== 'function') {
227
+ throw new Error('ptc-plus: DSH tools.register did not return a disposer')
228
+ }
229
+ let disposePresentation
230
+ try {
231
+ disposePresentation = scopedTools.presentAs('both')
232
+ if (typeof disposePresentation !== 'function') {
233
+ throw new Error('ptc-plus: DSH tools.presentAs did not return a disposer')
234
+ }
235
+ } catch (error) {
236
+ disposeRegistration()
237
+ throw error
238
+ }
239
+ let active = true
240
+ const dispose = () => {
241
+ if (!active) return
242
+ active = false
243
+ disposePresentation()
244
+ disposeRegistration()
245
+ }
246
+ installedScopes.set(agent, { definition, dispose, sessionId: sessionId(agent) })
247
+ return definition
248
+ },
249
+ handleResult(exec, result) {
250
+ const pending = pendingSettlements.get(exec)
251
+ pendingSettlements.delete(exec)
252
+ if (pending === undefined) return
253
+ const persisted = derivedEditResultsEqual(
254
+ result?.meta,
255
+ pending.expectedMeta,
256
+ pending.targetCallSeq,
257
+ )
258
+ pending.finalize(persisted)
259
+ if (persisted) return
260
+ releaseClaim(pending.id, pending.targetCallSeq)
261
+ },
262
+ disposeAgent(agent) {
263
+ uninstall(agent)
264
+ const id = sessionId(agent)
265
+ if (id !== undefined) {
266
+ discardSettlements(id)
267
+ editClaims.delete(id)
268
+ }
269
+ },
270
+ disposeSession(session) {
271
+ const id = String(session.id)
272
+ for (const [agent, installed] of installedScopes) {
273
+ if (installed.sessionId !== id) continue
274
+ installedScopes.delete(agent)
275
+ installed.dispose()
276
+ }
277
+ discardSettlements(id)
278
+ editClaims.delete(id)
279
+ },
280
+ dispose() {
281
+ discardSettlements()
282
+ editClaims.clear()
283
+ for (const installed of installedScopes.values()) installed.dispose()
284
+ installedScopes.clear()
285
+ },
286
+ })
287
+ }