dsh-ptc-plus 0.4.2 → 0.4.8
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 +170 -76
- package/README.md +171 -77
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-icon.svg +5 -0
- package/client.js +599 -310
- package/compiler-core.cjs +162 -160
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +4 -1
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +49 -11
- package/docs/adr/0008-define-agent-operating-contract.md +2 -2
- package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +5 -1
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +3 -1
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
- package/docs/adr/README.md +4 -4
- package/docs/architecture.md +8 -5
- package/docs/binding-review-placement-design.md +1 -1
- package/docs/client-ui.md +8 -4
- package/docs/durability-design.md +7 -7
- package/docs/installation.md +48 -12
- package/docs/publishing.md +4 -4
- package/docs/release-notes.md +109 -0
- package/docs/runtime-reference.md +25 -12
- package/docs/semantic-validation.md +7 -3
- package/docs/user-bindings.en.md +7 -6
- package/docs/user-bindings.md +6 -5
- package/docs/value-wire.md +1 -1
- package/docs/verification.md +43 -2
- package/index.js +39 -34
- package/internal/async-local-storage-intrinsics.js +12 -7
- package/internal/callable-source-facts.js +37 -1
- package/internal/cell-analysis.js +64 -9
- package/internal/config-spec.js +1 -1
- package/internal/cordis-tools-owner.js +81 -15
- package/internal/direct-surface-owner.js +331 -7
- package/internal/edit-transport-owner.js +80 -20
- package/internal/execution-seam-compat.js +192 -30
- package/internal/kernel-worker.js +162 -72
- package/internal/message-sources.js +14 -0
- package/internal/module-compilation.js +2 -1
- package/internal/rejected-cell-editor.js +11 -21
- package/internal/repl-memory-projection.js +10 -0
- package/internal/repl-scope-normalizer.js +72 -35
- package/internal/repl-value-observer.js +141 -45
- package/internal/runtime-config.js +62 -2
- package/internal/runtime-intrinsics.js +3 -0
- package/internal/runtime-messages.js +6 -4
- package/internal/session-cell-executor.js +24 -1
- package/internal/session-journal-recovery.js +267 -67
- package/internal/session-journal.js +466 -40
- package/internal/session-log-view.js +5 -4
- package/internal/session-runtime.js +45 -21
- package/internal/session-state.js +7 -29
- package/internal/settings-compat.js +66 -1
- package/internal/source-position-map.js +19 -0
- package/internal/user-binding-console-worker.js +23 -9
- package/internal/user-binding-draft-projection.js +3 -2
- package/internal/user-bindings-owner.js +2 -1
- package/internal/user-bindings.js +6 -1
- package/internal/validated-parse-repair.js +6 -2
- package/internal/value-wire.js +95 -30
- package/internal/worker-client.js +99 -8
- package/internal/worker-cwd-virtualization.js +10 -9
- package/internal/worker-output-capture.js +187 -0
- package/internal/worker-output-fence.js +26 -0
- package/internal/worker-realm-surfaces.js +197 -0
- package/internal/worker-repl-realm.js +10 -4
- package/package.json +42 -4
- package/src/client-activity.js +4 -1
- package/src/client-authoring-view.js +121 -62
- package/src/client-console.js +4 -2
- package/src/client-copy.js +4 -2
- package/src/client-feature-gates.js +7 -9
- package/src/client-host-compat.js +104 -27
- package/src/client-repl-view.js +3 -1
- package/src/client-styles.js +12 -1
- package/src/client-tool-view.js +3 -1
- package/src/client-transport.js +2 -2
- package/src/client-workbench.js +10 -7
- package/src/client.js +56 -17
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** Own PTC direct presentation, prompt projection, and stream normalization. */
|
|
2
|
+
import { boundContextSummary, createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
3
|
+
import { validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
|
|
4
|
+
import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
|
|
2
5
|
import { canonicalizeToolCallStream } from './tool-call-canonicalizer.js'
|
|
3
6
|
import { editRunCodeSchema } from './rejected-cell-editor.js'
|
|
4
7
|
import { sessionRuntimeContexts } from './runtime-contexts.js'
|
|
@@ -19,6 +22,12 @@ const RUN_CODE_CODE_DESCRIPTION = 'Code for the next REPL cell, parsed as the bo
|
|
|
19
22
|
const RUN_CODE_DESCRIPTION_DESCRIPTION = 'Short active-voice summary of what this cell does, 5-10 words (shown in the UI).'
|
|
20
23
|
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.'
|
|
21
24
|
const PTC_COLLAPSE_SECTION_NAMES = Object.freeze(['tools:ptc-only', 'tools:code-only'])
|
|
25
|
+
const INVALID_TOOL_JSON_FAILURE = Object.freeze({
|
|
26
|
+
code: 'MALFORMED_RESPONSE',
|
|
27
|
+
message: 'DeepSeek Messages stream: tool input is invalid JSON',
|
|
28
|
+
})
|
|
29
|
+
const MALFORMED_TOOL_REJECTION_ID = 'dsh-ptc-plus-malformed-tool-json'
|
|
30
|
+
const MALFORMED_TOOL_REJECTION_ARGUMENTS = '{"dsh_ptc_plus_malformed_tool_json":'
|
|
22
31
|
|
|
23
32
|
function adaptRunCodeSchema(tool) {
|
|
24
33
|
const parameters = tool.parameters
|
|
@@ -139,6 +148,314 @@ async function* bindCallPolicies(source, policy, calls) {
|
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
function invalidToolJsonFinish(chunk) {
|
|
152
|
+
const failure = chunk?.reason?.failure
|
|
153
|
+
return chunk?.type === 'finish'
|
|
154
|
+
&& chunk.reason.kind === 'error'
|
|
155
|
+
&& failure?.code === INVALID_TOOL_JSON_FAILURE.code
|
|
156
|
+
&& failure?.message === INVALID_TOOL_JSON_FAILURE.message
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function invalidToolJsonError(error) {
|
|
160
|
+
const failure = error?.failure ?? error
|
|
161
|
+
return failure?.code === INVALID_TOOL_JSON_FAILURE.code
|
|
162
|
+
&& failure?.message === INVALID_TOOL_JSON_FAILURE.message
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function directToolSchemas(tools) {
|
|
166
|
+
const schemas = new Map()
|
|
167
|
+
if (!Array.isArray(tools)) return schemas
|
|
168
|
+
for (const tool of tools) {
|
|
169
|
+
if (tool?.name !== RUN_CODE && tool?.name !== EDIT_RUN_CODE) continue
|
|
170
|
+
if (schemas.has(tool.name) || !isRecord(tool.parameters)) return new Map()
|
|
171
|
+
schemas.set(tool.name, tool.parameters)
|
|
172
|
+
}
|
|
173
|
+
return schemas
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function repairableDirectCall(chunks, tools) {
|
|
177
|
+
const finish = chunks.at(-1)
|
|
178
|
+
if (!invalidToolJsonFinish(finish)) return undefined
|
|
179
|
+
const completed = completedToolBlocks(chunks)
|
|
180
|
+
if (completed?.length !== 1) return undefined
|
|
181
|
+
const body = chunks.slice(0, -1)
|
|
182
|
+
const starts = body.filter(chunk => chunk?.type === 'block-start' && chunk.blockType === 'tool-call')
|
|
183
|
+
const deltas = body.filter(chunk => chunk?.type === 'tool-call-delta')
|
|
184
|
+
const ends = body.filter(chunk => chunk?.type === 'block-end' && chunk.block?.type === 'tool-call')
|
|
185
|
+
const usages = body.filter(chunk => chunk?.type === 'usage')
|
|
186
|
+
if (starts.length !== 1 || deltas.length === 0 || ends.length !== 1
|
|
187
|
+
|| usages.length > 1
|
|
188
|
+
|| body.some(chunk => chunk?.type !== 'usage' && !toolStreamChunk(chunk))) {
|
|
189
|
+
return undefined
|
|
190
|
+
}
|
|
191
|
+
const index = starts[0].index
|
|
192
|
+
const end = ends[0]
|
|
193
|
+
const id = end.block.id
|
|
194
|
+
const name = end.block.name
|
|
195
|
+
if (!Number.isSafeInteger(index) || index < 0 || typeof id !== 'string' || id.length === 0
|
|
196
|
+
|| (name !== RUN_CODE && name !== EDIT_RUN_CODE)
|
|
197
|
+
|| end.index !== index
|
|
198
|
+
|| typeof end.block.arguments !== 'string'
|
|
199
|
+
|| deltas.some(chunk => chunk.index !== index || chunk.id !== id
|
|
200
|
+
|| (chunk.name !== undefined && chunk.name !== name)
|
|
201
|
+
|| typeof chunk.argumentsDelta !== 'string')) {
|
|
202
|
+
return undefined
|
|
203
|
+
}
|
|
204
|
+
const raw = deltas.map(chunk => chunk.argumentsDelta).join('')
|
|
205
|
+
if (raw !== end.block.arguments || !raw.endsWith('}')) return undefined
|
|
206
|
+
const candidate = raw.slice(0, -1)
|
|
207
|
+
let value
|
|
208
|
+
try {
|
|
209
|
+
value = JSON.parse(candidate)
|
|
210
|
+
} catch {
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
const schema = directToolSchemas(tools).get(name)
|
|
214
|
+
if (!isRecord(value) || schema === undefined
|
|
215
|
+
|| validateJsonSchemaValue(schema, value).length !== 0) return undefined
|
|
216
|
+
return { index, candidate }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function canonicalToolBlock(index, block) {
|
|
220
|
+
return [
|
|
221
|
+
{ type: 'block-start', index, blockType: 'tool-call' },
|
|
222
|
+
{
|
|
223
|
+
type: 'tool-call-delta', index, id: block.id, name: block.name,
|
|
224
|
+
argumentsDelta: block.arguments,
|
|
225
|
+
},
|
|
226
|
+
{ type: 'block-end', index, block },
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function repairedDirectChunks(chunks, repair, usageAlreadyEmitted) {
|
|
231
|
+
const end = chunks.find(chunk => chunk?.type === 'block-end'
|
|
232
|
+
&& chunk.index === repair.index && chunk.block?.type === 'tool-call')
|
|
233
|
+
const block = { ...end.block, arguments: repair.candidate }
|
|
234
|
+
const usage = usageAlreadyEmitted ? undefined : firstUsageChunk(chunks)
|
|
235
|
+
return [
|
|
236
|
+
...canonicalToolBlock(repair.index, block),
|
|
237
|
+
...usage === undefined ? [] : [usage],
|
|
238
|
+
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function completedToolBlocks(chunks) {
|
|
243
|
+
const body = chunks.slice(0, -1)
|
|
244
|
+
if (body.filter(chunk => chunk?.type === 'usage').length > 1
|
|
245
|
+
|| body.some(chunk => chunk?.type !== 'usage' && !toolStreamChunk(chunk))) {
|
|
246
|
+
return undefined
|
|
247
|
+
}
|
|
248
|
+
const calls = new Map()
|
|
249
|
+
for (const chunk of chunks) {
|
|
250
|
+
if (chunk?.type === 'block-start' && chunk.blockType === 'tool-call') {
|
|
251
|
+
if (!Number.isSafeInteger(chunk.index) || chunk.index < 0) return undefined
|
|
252
|
+
const call = calls.get(chunk.index)
|
|
253
|
+
if (call?.started === true || call?.block !== undefined) return undefined
|
|
254
|
+
if (call === undefined) calls.set(chunk.index, {
|
|
255
|
+
arguments: '', hasDelta: false, started: true,
|
|
256
|
+
})
|
|
257
|
+
else call.started = true
|
|
258
|
+
} else if (chunk?.type === 'tool-call-delta') {
|
|
259
|
+
if (!Number.isSafeInteger(chunk.index) || chunk.index < 0) return undefined
|
|
260
|
+
let call = calls.get(chunk.index)
|
|
261
|
+
if (call === undefined) {
|
|
262
|
+
call = { arguments: '', hasDelta: false, started: false }
|
|
263
|
+
calls.set(chunk.index, call)
|
|
264
|
+
}
|
|
265
|
+
if (call.block !== undefined
|
|
266
|
+
|| typeof chunk.id !== 'string' || chunk.id.length === 0
|
|
267
|
+
|| (call.id !== undefined && call.id !== chunk.id)
|
|
268
|
+
|| (chunk.name !== undefined && (typeof chunk.name !== 'string' || chunk.name.length === 0
|
|
269
|
+
|| (call.name !== undefined && call.name !== chunk.name)))
|
|
270
|
+
|| typeof chunk.argumentsDelta !== 'string') return undefined
|
|
271
|
+
call.id ??= chunk.id
|
|
272
|
+
if (chunk.name !== undefined) call.name ??= chunk.name
|
|
273
|
+
call.hasDelta = true
|
|
274
|
+
call.arguments += chunk.argumentsDelta
|
|
275
|
+
} else if (chunk?.type === 'block-end' && chunk.block?.type === 'tool-call') {
|
|
276
|
+
if (!Number.isSafeInteger(chunk.index) || chunk.index < 0) return undefined
|
|
277
|
+
let call = calls.get(chunk.index)
|
|
278
|
+
if (call === undefined) {
|
|
279
|
+
call = { arguments: '', hasDelta: false, started: false }
|
|
280
|
+
calls.set(chunk.index, call)
|
|
281
|
+
}
|
|
282
|
+
if (call.block !== undefined
|
|
283
|
+
|| typeof chunk.block.id !== 'string' || chunk.block.id.length === 0
|
|
284
|
+
|| typeof chunk.block.name !== 'string' || chunk.block.name.length === 0
|
|
285
|
+
|| typeof chunk.block.arguments !== 'string'
|
|
286
|
+
|| (call.id !== undefined && call.id !== chunk.block.id)
|
|
287
|
+
|| (call.name !== undefined && call.name !== chunk.block.name)
|
|
288
|
+
|| (call.hasDelta && call.arguments !== chunk.block.arguments)) return undefined
|
|
289
|
+
call.block = chunk.block
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (calls.size === 0 || [...calls.values()].some(call => call.block === undefined)) {
|
|
293
|
+
return undefined
|
|
294
|
+
}
|
|
295
|
+
const completed = [...calls].map(([index, call]) => ({ index, block: call.block }))
|
|
296
|
+
if (new Set(completed.map(call => call.block.id)).size !== completed.length) return undefined
|
|
297
|
+
return completed
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function firstUsageChunk(chunks) {
|
|
301
|
+
return chunks.find(chunk => chunk?.type === 'usage')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function settleMalformedToolCalls(chunks, priorIndexes, usageAlreadyEmitted) {
|
|
305
|
+
if (!invalidToolJsonFinish(chunks.at(-1))) return undefined
|
|
306
|
+
const calls = completedToolBlocks(chunks)
|
|
307
|
+
if (calls === undefined || calls.some(call => priorIndexes.has(call.index))) return undefined
|
|
308
|
+
const usage = usageAlreadyEmitted ? undefined : firstUsageChunk(chunks)
|
|
309
|
+
return [
|
|
310
|
+
...calls.flatMap(call => canonicalToolBlock(call.index, call.block)),
|
|
311
|
+
...usage === undefined ? [] : [usage],
|
|
312
|
+
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function toolStreamChunk(chunk) {
|
|
317
|
+
return chunk?.type === 'tool-call-delta'
|
|
318
|
+
|| (chunk?.type === 'block-start' && chunk.blockType === 'tool-call')
|
|
319
|
+
|| (chunk?.type === 'block-end' && chunk.block?.type === 'tool-call')
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function unusedString(base, used) {
|
|
323
|
+
let value = base
|
|
324
|
+
for (let suffix = 2; used.has(value); suffix++) value = `${base}-${suffix}`
|
|
325
|
+
return value
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function visibleBlockClosures(openBlocks, chunks) {
|
|
329
|
+
const closing = new Map([...openBlocks].map(([index, block]) => [index, { ...block }]))
|
|
330
|
+
for (const chunk of chunks) {
|
|
331
|
+
const block = closing.get(chunk?.index)
|
|
332
|
+
if (block === undefined) continue
|
|
333
|
+
if (chunk.type === `${block.type}-delta` && typeof chunk.text === 'string') {
|
|
334
|
+
block.text += chunk.text
|
|
335
|
+
} else if (chunk.type === 'block-end' && chunk.block?.type === block.type) {
|
|
336
|
+
block.final = chunk.block
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return [...closing].map(([index, block]) => ({
|
|
340
|
+
type: 'block-end',
|
|
341
|
+
index,
|
|
342
|
+
block: block.final ?? { type: block.type, text: block.text },
|
|
343
|
+
}))
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function rejectedMalformedToolStream(
|
|
347
|
+
chunks,
|
|
348
|
+
priorIndexes = new Set(),
|
|
349
|
+
openBlocks = new Map(),
|
|
350
|
+
usageAlreadyEmitted = false,
|
|
351
|
+
) {
|
|
352
|
+
const usedIndexes = new Set(priorIndexes)
|
|
353
|
+
const usedIds = new Set()
|
|
354
|
+
for (const chunk of chunks) {
|
|
355
|
+
if (Number.isSafeInteger(chunk?.index) && chunk.index >= 0) usedIndexes.add(chunk.index)
|
|
356
|
+
if (typeof chunk?.id === 'string') usedIds.add(chunk.id)
|
|
357
|
+
if (typeof chunk?.block?.id === 'string') usedIds.add(chunk.block.id)
|
|
358
|
+
}
|
|
359
|
+
let index = 0
|
|
360
|
+
while (usedIndexes.has(index)) index++
|
|
361
|
+
const id = unusedString(MALFORMED_TOOL_REJECTION_ID, usedIds)
|
|
362
|
+
const usage = usageAlreadyEmitted ? undefined : firstUsageChunk(chunks)
|
|
363
|
+
return [
|
|
364
|
+
...visibleBlockClosures(openBlocks, chunks),
|
|
365
|
+
{ type: 'block-start', index, blockType: 'tool-call' },
|
|
366
|
+
{
|
|
367
|
+
type: 'tool-call-delta', index, id, name: RUN_CODE,
|
|
368
|
+
argumentsDelta: MALFORMED_TOOL_REJECTION_ARGUMENTS,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
type: 'block-end', index,
|
|
372
|
+
block: {
|
|
373
|
+
type: 'tool-call', id, name: RUN_CODE,
|
|
374
|
+
arguments: MALFORMED_TOOL_REJECTION_ARGUMENTS,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
...usage === undefined ? [] : [usage],
|
|
378
|
+
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function recoveredMalformedChunks(chunks, tools, priorIndexes, openBlocks, usageAlreadyEmitted) {
|
|
383
|
+
if (usageAlreadyEmitted && firstUsageChunk(chunks) !== undefined) {
|
|
384
|
+
return rejectedMalformedToolStream(chunks, priorIndexes, openBlocks, true)
|
|
385
|
+
}
|
|
386
|
+
const repair = repairableDirectCall(chunks, tools)
|
|
387
|
+
const settled = repair !== undefined && !priorIndexes.has(repair.index)
|
|
388
|
+
? repairedDirectChunks(chunks, repair, usageAlreadyEmitted)
|
|
389
|
+
: settleMalformedToolCalls(chunks, priorIndexes, usageAlreadyEmitted)
|
|
390
|
+
return settled === undefined
|
|
391
|
+
? rejectedMalformedToolStream(chunks, priorIndexes, openBlocks, usageAlreadyEmitted)
|
|
392
|
+
: [...visibleBlockClosures(openBlocks, chunks), ...settled]
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function observeVisibleBlock(openBlocks, chunk) {
|
|
396
|
+
if (chunk?.type === 'block-start'
|
|
397
|
+
&& (chunk.blockType === 'text' || chunk.blockType === 'reasoning')) {
|
|
398
|
+
openBlocks.set(chunk.index, { type: chunk.blockType, text: '' })
|
|
399
|
+
} else if (chunk?.type === 'text-delta' || chunk?.type === 'reasoning-delta') {
|
|
400
|
+
const block = openBlocks.get(chunk.index)
|
|
401
|
+
const type = chunk.type === 'text-delta' ? 'text' : 'reasoning'
|
|
402
|
+
if (block?.type === type) block.text += chunk.text
|
|
403
|
+
} else if (chunk?.type === 'block-end') {
|
|
404
|
+
openBlocks.delete(chunk.index)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function* recoverMalformedToolCallStream(source, tools) {
|
|
409
|
+
let pending = []
|
|
410
|
+
const priorIndexes = new Set()
|
|
411
|
+
const openBlocks = new Map()
|
|
412
|
+
let usageSeen = false
|
|
413
|
+
try {
|
|
414
|
+
for await (const chunk of source) {
|
|
415
|
+
if (pending.length === 0) {
|
|
416
|
+
if (toolStreamChunk(chunk)) pending.push(chunk)
|
|
417
|
+
else if (chunk?.type === 'usage' && usageSeen) pending.push(chunk)
|
|
418
|
+
else if (invalidToolJsonFinish(chunk)) {
|
|
419
|
+
yield* rejectedMalformedToolStream([chunk], priorIndexes, openBlocks, usageSeen)
|
|
420
|
+
openBlocks.clear()
|
|
421
|
+
} else {
|
|
422
|
+
if (Number.isSafeInteger(chunk?.index) && chunk.index >= 0) priorIndexes.add(chunk.index)
|
|
423
|
+
observeVisibleBlock(openBlocks, chunk)
|
|
424
|
+
if (chunk?.type === 'usage') usageSeen = true
|
|
425
|
+
yield chunk
|
|
426
|
+
}
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
pending.push(chunk)
|
|
430
|
+
if (chunk?.type !== 'finish') continue
|
|
431
|
+
const output = invalidToolJsonFinish(chunk)
|
|
432
|
+
? recoveredMalformedChunks(pending, tools, priorIndexes, openBlocks, usageSeen)
|
|
433
|
+
: pending
|
|
434
|
+
for (const outputChunk of output) {
|
|
435
|
+
if (Number.isSafeInteger(outputChunk?.index) && outputChunk.index >= 0) {
|
|
436
|
+
priorIndexes.add(outputChunk.index)
|
|
437
|
+
}
|
|
438
|
+
if (outputChunk?.type === 'usage') usageSeen = true
|
|
439
|
+
yield outputChunk
|
|
440
|
+
}
|
|
441
|
+
if (invalidToolJsonFinish(chunk)) openBlocks.clear()
|
|
442
|
+
pending = []
|
|
443
|
+
}
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (invalidToolJsonError(error)) {
|
|
446
|
+
yield* recoveredMalformedChunks([
|
|
447
|
+
...pending,
|
|
448
|
+
{ type: 'finish', reason: { kind: 'error', failure: INVALID_TOOL_JSON_FAILURE } },
|
|
449
|
+
], tools, priorIndexes, openBlocks, usageSeen)
|
|
450
|
+
return
|
|
451
|
+
} else {
|
|
452
|
+
yield* pending
|
|
453
|
+
throw error
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
yield* pending
|
|
457
|
+
}
|
|
458
|
+
|
|
142
459
|
export function createDirectSurfaceOwner({
|
|
143
460
|
editTransport,
|
|
144
461
|
runtimeConfig,
|
|
@@ -383,7 +700,8 @@ export function createDirectSurfaceOwner({
|
|
|
383
700
|
editToolName: EDIT_RUN_CODE,
|
|
384
701
|
})
|
|
385
702
|
: next()
|
|
386
|
-
|
|
703
|
+
const recovered = recoverMalformedToolCallStream(source, options.tools)
|
|
704
|
+
return bindCallPolicies(recovered, policy, policy.owner.calls)
|
|
387
705
|
},
|
|
388
706
|
executionRejection(exec) {
|
|
389
707
|
if (exec.name === EDIT_RUN_CODE && exec.parent !== undefined) {
|
|
@@ -430,12 +748,18 @@ export function createDirectSurfaceOwner({
|
|
|
430
748
|
const outer = missingPath === 'description'
|
|
431
749
|
&& policy?.autoDescribeRunCode !== true
|
|
432
750
|
&& !(isRecord(exec.arguments) && typeof exec.arguments.description === 'string')
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
751
|
+
const text = outer
|
|
752
|
+
? 'The run_code outer transport arguments are invalid at JSON path $.description. Add a sibling string `description` field to run_code; a description nested inside a native-tool argument does not satisfy this outer requirement.'
|
|
753
|
+
: `A nested native-tool argument is missing required property "${missingPath}". Add a string description at that path in the native-tool arguments; the outer run_code description does not satisfy this nested requirement.`
|
|
754
|
+
// additionalContexts enter the durable inbox as messages, not prompt sections.
|
|
755
|
+
const context = createUserMessage({
|
|
756
|
+
source: {
|
|
757
|
+
kind: PTC_MESSAGE_SOURCE_KIND,
|
|
758
|
+
form: 'notice',
|
|
759
|
+
summary: boundContextSummary('tools:ptc-plus-run-code-arguments'),
|
|
760
|
+
},
|
|
761
|
+
content: [{ type: 'text', text }],
|
|
762
|
+
})
|
|
439
763
|
return {
|
|
440
764
|
...diagnosed,
|
|
441
765
|
additionalContexts: [...diagnosed.additionalContexts ?? [], context],
|
|
@@ -238,11 +238,34 @@ export function createEditTransportOwner(ctx, {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
const disposeInstalled = (installed) => {
|
|
242
|
+
const failures = []
|
|
243
|
+
for (const key of ['disposePresentation', 'disposeRegistration']) {
|
|
244
|
+
const dispose = installed[key]
|
|
245
|
+
if (dispose === undefined) continue
|
|
246
|
+
try {
|
|
247
|
+
dispose()
|
|
248
|
+
installed[key] = undefined
|
|
249
|
+
} catch (error) {
|
|
250
|
+
failures.push(error)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (failures.length > 0) {
|
|
254
|
+
throw new AggregateError(failures, 'ptc-plus: edit_run_code scope disposal failed')
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
241
258
|
const uninstall = (agent) => {
|
|
242
259
|
const installed = installedScopes.get(agent)
|
|
243
260
|
if (installed === undefined) return
|
|
244
|
-
|
|
245
|
-
|
|
261
|
+
try {
|
|
262
|
+
disposeInstalled(installed)
|
|
263
|
+
} finally {
|
|
264
|
+
if (installed.disposePresentation === undefined
|
|
265
|
+
&& installed.disposeRegistration === undefined) {
|
|
266
|
+
installedScopes.delete(agent)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
246
269
|
}
|
|
247
270
|
|
|
248
271
|
return Object.freeze({
|
|
@@ -251,11 +274,19 @@ export function createEditTransportOwner(ctx, {
|
|
|
251
274
|
currentDurableReplay = nextConfig.durableReplay
|
|
252
275
|
},
|
|
253
276
|
isInstalled(agent) {
|
|
254
|
-
|
|
277
|
+
const installed = installedScopes.get(agent)
|
|
278
|
+
return installed?.disposePresentation !== undefined
|
|
279
|
+
&& installed.disposeRegistration !== undefined
|
|
255
280
|
},
|
|
256
281
|
ensureInstalled(agent) {
|
|
257
282
|
const installed = installedScopes.get(agent)
|
|
258
|
-
if (installed !== undefined)
|
|
283
|
+
if (installed !== undefined) {
|
|
284
|
+
if (installed.disposePresentation !== undefined
|
|
285
|
+
&& installed.disposeRegistration !== undefined) {
|
|
286
|
+
return installed.definition
|
|
287
|
+
}
|
|
288
|
+
uninstall(agent)
|
|
289
|
+
}
|
|
259
290
|
const liveSchemas = typeof toolSchemasForAgent === 'function'
|
|
260
291
|
? toolSchemasForAgent(agent)
|
|
261
292
|
: []
|
|
@@ -271,24 +302,31 @@ export function createEditTransportOwner(ctx, {
|
|
|
271
302
|
if (typeof disposeRegistration !== 'function') {
|
|
272
303
|
throw new Error('ptc-plus: DSH tools.register did not return a disposer')
|
|
273
304
|
}
|
|
274
|
-
|
|
305
|
+
const nextInstalled = {
|
|
306
|
+
definition,
|
|
307
|
+
disposePresentation: undefined,
|
|
308
|
+
disposeRegistration,
|
|
309
|
+
sessionId: sessionId(agent),
|
|
310
|
+
}
|
|
311
|
+
installedScopes.set(agent, nextInstalled)
|
|
275
312
|
try {
|
|
276
|
-
disposePresentation = scopedTools.presentAs('both')
|
|
313
|
+
const disposePresentation = scopedTools.presentAs('both')
|
|
277
314
|
if (typeof disposePresentation !== 'function') {
|
|
278
315
|
throw new Error('ptc-plus: DSH tools.presentAs did not return a disposer')
|
|
279
316
|
}
|
|
317
|
+
nextInstalled.disposePresentation = disposePresentation
|
|
280
318
|
} catch (error) {
|
|
281
|
-
|
|
319
|
+
try {
|
|
320
|
+
uninstall(agent)
|
|
321
|
+
} catch (cleanupError) {
|
|
322
|
+
throw new AggregateError(
|
|
323
|
+
[error, cleanupError],
|
|
324
|
+
'ptc-plus: edit_run_code installation and rollback failed',
|
|
325
|
+
{ cause: error },
|
|
326
|
+
)
|
|
327
|
+
}
|
|
282
328
|
throw error
|
|
283
329
|
}
|
|
284
|
-
let active = true
|
|
285
|
-
const dispose = () => {
|
|
286
|
-
if (!active) return
|
|
287
|
-
active = false
|
|
288
|
-
disposePresentation()
|
|
289
|
-
disposeRegistration()
|
|
290
|
-
}
|
|
291
|
-
installedScopes.set(agent, { definition, dispose, sessionId: sessionId(agent) })
|
|
292
330
|
return definition
|
|
293
331
|
},
|
|
294
332
|
handleResult(exec, result) {
|
|
@@ -305,28 +343,50 @@ export function createEditTransportOwner(ctx, {
|
|
|
305
343
|
releaseClaim(pending.id, pending.targetCallSeq)
|
|
306
344
|
},
|
|
307
345
|
disposeAgent(agent) {
|
|
308
|
-
|
|
346
|
+
let failure
|
|
347
|
+
try {
|
|
348
|
+
uninstall(agent)
|
|
349
|
+
} catch (error) {
|
|
350
|
+
failure = error
|
|
351
|
+
}
|
|
309
352
|
const id = sessionId(agent)
|
|
310
353
|
if (id !== undefined) {
|
|
311
354
|
discardSettlements(id)
|
|
312
355
|
editClaims.delete(id)
|
|
313
356
|
}
|
|
357
|
+
if (failure !== undefined) throw failure
|
|
314
358
|
},
|
|
315
359
|
disposeSession(session) {
|
|
316
360
|
const id = String(session.id)
|
|
361
|
+
const failures = []
|
|
317
362
|
for (const [agent, installed] of installedScopes) {
|
|
318
363
|
if (installed.sessionId !== id) continue
|
|
319
|
-
|
|
320
|
-
|
|
364
|
+
try {
|
|
365
|
+
uninstall(agent)
|
|
366
|
+
} catch (error) {
|
|
367
|
+
failures.push(error)
|
|
368
|
+
}
|
|
321
369
|
}
|
|
322
370
|
discardSettlements(id)
|
|
323
371
|
editClaims.delete(id)
|
|
372
|
+
if (failures.length > 0) {
|
|
373
|
+
throw new AggregateError(failures, 'ptc-plus: edit_run_code session disposal failed')
|
|
374
|
+
}
|
|
324
375
|
},
|
|
325
376
|
dispose() {
|
|
326
377
|
discardSettlements()
|
|
327
378
|
editClaims.clear()
|
|
328
|
-
|
|
329
|
-
installedScopes.
|
|
379
|
+
const failures = []
|
|
380
|
+
for (const agent of [...installedScopes.keys()]) {
|
|
381
|
+
try {
|
|
382
|
+
uninstall(agent)
|
|
383
|
+
} catch (error) {
|
|
384
|
+
failures.push(error)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (failures.length > 0) {
|
|
388
|
+
throw new AggregateError(failures, 'ptc-plus: edit_run_code owner disposal failed')
|
|
389
|
+
}
|
|
330
390
|
},
|
|
331
391
|
})
|
|
332
392
|
}
|