@namzu/sdk 56.2.0 → 57.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +1 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/plan.d.ts +20 -0
- package/dist/agents/plan.d.ts.map +1 -0
- package/dist/agents/plan.js +27 -0
- package/dist/agents/plan.js.map +1 -0
- package/dist/compaction/dangling.d.ts +12 -1
- package/dist/compaction/dangling.d.ts.map +1 -1
- package/dist/compaction/dangling.js +17 -3
- package/dist/compaction/dangling.js.map +1 -1
- package/dist/compaction/index.d.ts +3 -2
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/manager.d.ts +16 -0
- package/dist/compaction/manager.d.ts.map +1 -1
- package/dist/compaction/manager.js +55 -0
- package/dist/compaction/manager.js.map +1 -1
- package/dist/compaction/manual.d.ts +9 -0
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +64 -16
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/model-summary.d.ts +101 -0
- package/dist/compaction/model-summary.d.ts.map +1 -0
- package/dist/compaction/model-summary.js +333 -0
- package/dist/compaction/model-summary.js.map +1 -0
- package/dist/compaction/restore.d.ts +51 -0
- package/dist/compaction/restore.d.ts.map +1 -0
- package/dist/compaction/restore.js +123 -0
- package/dist/compaction/restore.js.map +1 -0
- package/dist/compaction/types.d.ts +22 -0
- package/dist/compaction/types.d.ts.map +1 -1
- package/dist/compaction/verifier.d.ts +6 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +3 -3
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/compaction/wire.d.ts +3 -1
- package/dist/compaction/wire.d.ts.map +1 -1
- package/dist/compaction/wire.js +16 -0
- package/dist/compaction/wire.js.map +1 -1
- package/dist/config/runtime.d.ts +62 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +16 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/shell-hook.d.ts +19 -1
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +21 -5
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +2 -2
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/errors.d.ts +3 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +23 -3
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +34 -3
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +12 -5
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +22 -8
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +5 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +4 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.d.ts +42 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +77 -0
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/query/calls-that-never-started.d.ts +12 -0
- package/dist/runtime/query/calls-that-never-started.d.ts.map +1 -0
- package/dist/runtime/query/calls-that-never-started.js +41 -0
- package/dist/runtime/query/calls-that-never-started.js.map +1 -0
- package/dist/runtime/query/executor/tool-call-admission.d.ts +2 -2
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +13 -9
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +11 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +48 -0
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/file-evidence-context.js +1 -1
- package/dist/runtime/query/file-evidence-context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +12 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +9 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +66 -1
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +45 -25
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +10 -1
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/scheduler/completion-inbox.js +3 -3
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/scheduler/local.js +1 -1
- package/dist/scheduler/local.js.map +1 -1
- package/dist/tools/admit-facts.d.ts.map +1 -1
- package/dist/tools/admit-facts.js +1 -0
- package/dist/tools/admit-facts.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +42 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +115 -48
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +55 -5
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +28 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/types/agent/base.d.ts +14 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +8 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/errors/catalog.d.ts +3 -0
- package/dist/types/errors/catalog.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +24 -2
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/plugin/index.d.ts +2 -2
- package/dist/types/provider/error.d.ts +7 -0
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/errors.d.ts +24 -0
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +101 -1
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +8 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/tool/index.d.ts +54 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +4 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +2 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/plan.ts +30 -0
- package/src/compaction/dangling.ts +35 -3
- package/src/compaction/index.ts +5 -0
- package/src/compaction/manager.ts +53 -0
- package/src/compaction/manual.ts +110 -40
- package/src/compaction/model-summary.ts +412 -0
- package/src/compaction/restore.ts +159 -0
- package/src/compaction/types.ts +25 -0
- package/src/compaction/verifier.ts +3 -3
- package/src/compaction/wire.ts +26 -1
- package/src/config/runtime.ts +16 -0
- package/src/index.ts +2 -0
- package/src/plugin/shell-hook.ts +33 -5
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/provider/errors.ts +27 -3
- package/src/provider/fallback.ts +34 -2
- package/src/provider/retry.ts +30 -10
- package/src/public-runtime.ts +12 -0
- package/src/public-types.ts +3 -0
- package/src/runtime/jobs/registry.ts +105 -0
- package/src/runtime/query/calls-that-never-started.ts +37 -0
- package/src/runtime/query/executor/tool-call-admission.ts +12 -9
- package/src/runtime/query/executor.ts +51 -0
- package/src/runtime/query/file-evidence-context.ts +1 -1
- package/src/runtime/query/index.ts +23 -0
- package/src/runtime/query/iteration/phases/compaction.ts +65 -1
- package/src/runtime/query/iteration/phases/context.ts +7 -0
- package/src/runtime/query/iteration/stream-turn.ts +47 -25
- package/src/runtime/query/prepare-turn.ts +10 -1
- package/src/scheduler/completion-inbox.ts +3 -3
- package/src/scheduler/local.ts +1 -1
- package/src/tools/admit-facts.ts +1 -0
- package/src/tools/builtins/bash.ts +149 -54
- package/src/tools/builtins/read-file.ts +66 -4
- package/src/tools/file-read-tracker.ts +25 -1
- package/src/types/agent/base.ts +16 -0
- package/src/types/agent/scheduler.ts +9 -0
- package/src/types/errors/catalog.ts +31 -2
- package/src/types/provider/error.ts +7 -0
- package/src/types/provider/errors.ts +120 -1
- package/src/types/provider/stream.ts +6 -0
- package/src/types/session/events.ts +8 -0
- package/src/types/tool/index.ts +56 -0
- package/src/types/tool/presentation.ts +4 -0
|
@@ -4,6 +4,7 @@ import { SANDBOX_KILL_GRACE_MS } from '../../constants/sandbox/index.js'
|
|
|
4
4
|
import { DANGEROUS_PATTERNS } from '../../constants/tools/index.js'
|
|
5
5
|
import { killTree } from '../../process/kill-tree.js'
|
|
6
6
|
import { genericLabel } from '../../registry/tool/presentation.js'
|
|
7
|
+
import { cancelCauseOf } from '../../types/session/cancel-cause.js'
|
|
7
8
|
import type { ToolFacts } from '../../types/tool/presentation.js'
|
|
8
9
|
import { subscribeToAbort } from '../../utils/abort.js'
|
|
9
10
|
import { readPositiveIntEnv } from '../../utils/env.js'
|
|
@@ -93,6 +94,28 @@ export function movedToBackgroundMessage(jobId: string, timeoutMs: number): stri
|
|
|
93
94
|
return `Command did not complete within its ${seconds}s timeout and was moved to the background as job ${jobId}; it was not stopped. You will be told when it ends. Its output so far is above.`
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* How long a foreground command runs before it is offered for hand-over to
|
|
99
|
+
* the background. A command that ends sooner is never interrupted by a stray
|
|
100
|
+
* key press, and a host that shows a hint shows it from this moment.
|
|
101
|
+
*/
|
|
102
|
+
export const FOREGROUND_HANDOFF_AFTER_MS = 2_000
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* What the model is told when the person moved a running foreground command to
|
|
106
|
+
* the background instead of stopping it. Exported so the test states the same
|
|
107
|
+
* words.
|
|
108
|
+
*/
|
|
109
|
+
export function handedOffMessage(jobId: string, reason: 'user' | 'message' | 'interrupt'): string {
|
|
110
|
+
const why =
|
|
111
|
+
reason === 'user'
|
|
112
|
+
? "at the person's request"
|
|
113
|
+
: reason === 'message'
|
|
114
|
+
? 'so a message from the person could reach you'
|
|
115
|
+
: 'because the person interrupted the turn'
|
|
116
|
+
return `Command was moved to the background as job ${jobId} ${why}; it was not stopped. You will be told when it ends. Its output so far is above.`
|
|
117
|
+
}
|
|
118
|
+
|
|
96
119
|
const inputSchema = z.object({
|
|
97
120
|
command: z
|
|
98
121
|
.string()
|
|
@@ -240,10 +263,34 @@ export function execHostShell(
|
|
|
240
263
|
child: ReturnType<typeof spawnHostShell>
|
|
241
264
|
initialOutput: readonly { stream: 'stdout' | 'stderr'; text: string }[]
|
|
242
265
|
}) => string | undefined
|
|
266
|
+
/**
|
|
267
|
+
* Takes the live process over for a hand-over the person asked for
|
|
268
|
+
* (a key press, a message that must reach the model, an interrupt),
|
|
269
|
+
* with the same contract as `onTimeout`. Absent, those hand-overs are
|
|
270
|
+
* not offered and an interrupt kills the command as it always did.
|
|
271
|
+
*/
|
|
272
|
+
adopt?: (running: {
|
|
273
|
+
child: ReturnType<typeof spawnHostShell>
|
|
274
|
+
initialOutput: readonly { stream: 'stdout' | 'stderr'; text: string }[]
|
|
275
|
+
}) => string | undefined
|
|
276
|
+
/**
|
|
277
|
+
* An interrupt the person caused (Esc, Ctrl+C) hands the command to
|
|
278
|
+
* `adopt` instead of killing it. Any other abort, a parent stopping a
|
|
279
|
+
* child or a budget stop, still kills it.
|
|
280
|
+
*/
|
|
281
|
+
adoptOnInterrupt?: boolean
|
|
282
|
+
/**
|
|
283
|
+
* Called once, right after the process is spawned, with the means to
|
|
284
|
+
* hand it over later. `background` returns whether the process was
|
|
285
|
+
* taken over; it settles the call with `backgroundedJobId` set.
|
|
286
|
+
*/
|
|
287
|
+
onRunning?: (control: { background(reason: 'user' | 'message'): boolean }) => void
|
|
243
288
|
},
|
|
244
289
|
): Promise<{
|
|
245
|
-
/** Set when
|
|
290
|
+
/** Set when the process was taken over; the command was not stopped. */
|
|
246
291
|
backgroundedJobId?: string
|
|
292
|
+
/** Why it was taken over. */
|
|
293
|
+
backgroundedBy?: 'timeout' | 'user' | 'message' | 'interrupt'
|
|
247
294
|
stdout: string
|
|
248
295
|
stderr: string
|
|
249
296
|
/** Bytes captured on each stream, before decoding; absent from a runner that does not count them. */
|
|
@@ -401,44 +448,64 @@ export function execHostShell(
|
|
|
401
448
|
)
|
|
402
449
|
}
|
|
403
450
|
child.once('close', onClose)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
closed = true
|
|
421
|
-
disposeAbort?.()
|
|
422
|
-
child.stdout?.off('data', onStdout)
|
|
423
|
-
child.stderr?.off('data', onStderr)
|
|
424
|
-
child.off('close', onClose)
|
|
425
|
-
child.off('error', onError)
|
|
426
|
-
child.off('spawn', onSpawn)
|
|
427
|
-
resolve({
|
|
428
|
-
backgroundedJobId: jobId,
|
|
429
|
-
stdout: decodeCapture('stdout'),
|
|
430
|
-
stderr: decodeCapture('stderr'),
|
|
431
|
-
})
|
|
432
|
-
return
|
|
433
|
-
}
|
|
451
|
+
// Hands the live process to the host's job registry, once. `onTimeout`
|
|
452
|
+
// answers for the deadline; every other reason goes to `adopt`.
|
|
453
|
+
const takeOver = (reason: 'timeout' | 'user' | 'message' | 'interrupt'): boolean => {
|
|
454
|
+
const adopter =
|
|
455
|
+
reason === 'timeout' ? options.onTimeout : (options.adopt ?? options.onTimeout)
|
|
456
|
+
if (!adopter || closed || cause !== undefined) return false
|
|
457
|
+
let jobId: string | undefined
|
|
458
|
+
try {
|
|
459
|
+
jobId = adopter({
|
|
460
|
+
child,
|
|
461
|
+
initialOutput: (['stdout', 'stderr'] as const)
|
|
462
|
+
.map((stream) => ({ stream, text: decodeCapture(stream) }))
|
|
463
|
+
.filter((part) => part.text !== ''),
|
|
464
|
+
})
|
|
465
|
+
} catch {
|
|
466
|
+
// A host that cannot take the process over leaves the process where it is.
|
|
434
467
|
}
|
|
468
|
+
if (jobId === undefined) return false
|
|
469
|
+
// The registry owns the process from here: stop listening to it,
|
|
470
|
+
// so a later Stop or its exit is not this call's business.
|
|
471
|
+
closed = true
|
|
472
|
+
disposeAbort?.()
|
|
473
|
+
clearTimeout(deadline)
|
|
474
|
+
child.stdout?.off('data', onStdout)
|
|
475
|
+
child.stderr?.off('data', onStderr)
|
|
476
|
+
child.off('close', onClose)
|
|
477
|
+
child.off('error', onError)
|
|
478
|
+
child.off('spawn', onSpawn)
|
|
479
|
+
resolve({
|
|
480
|
+
backgroundedJobId: jobId,
|
|
481
|
+
backgroundedBy: reason,
|
|
482
|
+
stdout: decodeCapture('stdout'),
|
|
483
|
+
stderr: decodeCapture('stderr'),
|
|
484
|
+
})
|
|
485
|
+
return true
|
|
486
|
+
}
|
|
487
|
+
const deadline = setTimeout(() => {
|
|
488
|
+
if (takeOver('timeout')) return
|
|
435
489
|
cancel('timeout')
|
|
436
490
|
}, options.timeout)
|
|
437
491
|
deadline.unref?.()
|
|
438
492
|
if (options.signal) {
|
|
439
|
-
|
|
440
|
-
|
|
493
|
+
const signal = options.signal
|
|
494
|
+
const onAbort = () => {
|
|
495
|
+
// Esc or Ctrl+C keeps what the command was doing; any other stop
|
|
496
|
+
// (a parent cancelling a child, a budget) still ends it.
|
|
497
|
+
if (
|
|
498
|
+
options.adoptOnInterrupt === true &&
|
|
499
|
+
cancelCauseOf(signal.reason) === 'user' &&
|
|
500
|
+
takeOver('interrupt')
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
cancel('caller')
|
|
504
|
+
}
|
|
505
|
+
disposeAbort = subscribeToAbort(signal, onAbort)
|
|
506
|
+
if (signal.aborted) onAbort()
|
|
441
507
|
}
|
|
508
|
+
options.onRunning?.({ background: (reason) => takeOver(reason) })
|
|
442
509
|
})
|
|
443
510
|
}
|
|
444
511
|
|
|
@@ -714,51 +781,76 @@ const BashToolBase = defineTool({
|
|
|
714
781
|
// registry, a sandboxed turn, a one-off sandbox escape, a plain wait)
|
|
715
782
|
// is there anything to hand the process to, so those keep the kill.
|
|
716
783
|
const adopt = context.backgroundJobs?.adopt?.bind(context.backgroundJobs)
|
|
717
|
-
const
|
|
718
|
-
adopt !== undefined &&
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
784
|
+
const canHandOff =
|
|
785
|
+
adopt !== undefined && context.sandbox === undefined && !backgroundTasksDisabled()
|
|
786
|
+
const canMoveOnTimeout = canHandOff && !startsWithSleep(input.command)
|
|
787
|
+
const adoptRunning = (running: {
|
|
788
|
+
child: Parameters<NonNullable<typeof adopt>>[0]['child']
|
|
789
|
+
initialOutput: Parameters<NonNullable<typeof adopt>>[0]['initialOutput']
|
|
790
|
+
}) =>
|
|
791
|
+
adopt?.({
|
|
792
|
+
command: input.command,
|
|
793
|
+
child: running.child,
|
|
794
|
+
workingDirectory: context.workingDirectory,
|
|
795
|
+
initialOutput: running.initialOutput,
|
|
796
|
+
}).id
|
|
797
|
+
// Offered for hand-over once it has run long enough to be worth moving.
|
|
798
|
+
let offerTimer: ReturnType<typeof setTimeout> | undefined
|
|
799
|
+
let withdrawOffer: (() => void) | undefined
|
|
800
|
+
const offer = context.backgroundJobs?.registerForeground?.bind(context.backgroundJobs)
|
|
722
801
|
|
|
723
802
|
try {
|
|
724
|
-
const { stdout, stderr, stdoutBytes, stderrBytes, backgroundedJobId } =
|
|
725
|
-
input.command,
|
|
726
|
-
{
|
|
803
|
+
const { stdout, stderr, stdoutBytes, stderrBytes, backgroundedJobId, backgroundedBy } =
|
|
804
|
+
await execHostShell(input.command, {
|
|
727
805
|
cwd: context.workingDirectory,
|
|
728
806
|
timeout: input.timeout,
|
|
729
807
|
env: { ...inherited.env, ...context.env },
|
|
730
808
|
maxBuffer: DEFAULT_BASH_MAX_BUFFER_BYTES,
|
|
731
809
|
signal: context.abortSignal,
|
|
732
810
|
onOutput,
|
|
733
|
-
...(
|
|
811
|
+
...(canMoveOnTimeout ? { onTimeout: adoptRunning } : {}),
|
|
812
|
+
...(canHandOff
|
|
734
813
|
? {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
814
|
+
adopt: adoptRunning,
|
|
815
|
+
adoptOnInterrupt: true,
|
|
816
|
+
...(offer
|
|
817
|
+
? {
|
|
818
|
+
onRunning: (control: {
|
|
819
|
+
background(reason: 'user' | 'message'): boolean
|
|
820
|
+
}) => {
|
|
821
|
+
offerTimer = setTimeout(() => {
|
|
822
|
+
withdrawOffer = offer({
|
|
823
|
+
command: input.command,
|
|
824
|
+
background: (reason) => control.background(reason),
|
|
825
|
+
})
|
|
826
|
+
}, FOREGROUND_HANDOFF_AFTER_MS)
|
|
827
|
+
offerTimer.unref?.()
|
|
828
|
+
},
|
|
829
|
+
}
|
|
830
|
+
: {}),
|
|
742
831
|
}
|
|
743
832
|
: {}),
|
|
744
|
-
}
|
|
745
|
-
)
|
|
833
|
+
})
|
|
746
834
|
if (backgroundedJobId !== undefined) {
|
|
835
|
+
const timedOut = backgroundedBy === 'timeout'
|
|
747
836
|
return {
|
|
748
837
|
success: true,
|
|
749
838
|
output: [
|
|
750
839
|
formatShellOutput(stdout, stderr),
|
|
751
|
-
|
|
840
|
+
timedOut || backgroundedBy === undefined
|
|
841
|
+
? movedToBackgroundMessage(backgroundedJobId, input.timeout)
|
|
842
|
+
: handedOffMessage(backgroundedJobId, backgroundedBy),
|
|
752
843
|
]
|
|
753
844
|
.filter(Boolean)
|
|
754
845
|
.join('\n\n'),
|
|
755
846
|
data: {
|
|
756
847
|
jobId: backgroundedJobId,
|
|
757
848
|
background: true,
|
|
758
|
-
timedOut: true,
|
|
849
|
+
...(timedOut || backgroundedBy === undefined ? { timedOut: true } : {}),
|
|
759
850
|
movedToBackground: true,
|
|
851
|
+
...(backgroundedBy ? { backgroundedBy } : {}),
|
|
760
852
|
},
|
|
761
|
-
facts: { kind: 'command', command: input.command, background: true },
|
|
853
|
+
facts: { kind: 'command', command: input.command, background: true, continued: true },
|
|
762
854
|
}
|
|
763
855
|
}
|
|
764
856
|
return {
|
|
@@ -856,6 +948,9 @@ const BashToolBase = defineTool({
|
|
|
856
948
|
...(failure.stderrBytes !== undefined ? { stderrBytes: failure.stderrBytes } : {}),
|
|
857
949
|
}),
|
|
858
950
|
}
|
|
951
|
+
} finally {
|
|
952
|
+
if (offerTimer !== undefined) clearTimeout(offerTimer)
|
|
953
|
+
withdrawOffer?.()
|
|
859
954
|
}
|
|
860
955
|
},
|
|
861
956
|
})
|
|
@@ -102,7 +102,9 @@ export const ReadFileTool = defineTool({
|
|
|
102
102
|
const capped = renderCappedRead(content, buffer.length, input, capOptions(input, context))
|
|
103
103
|
if (!capped.ok) return refusedRead(input.path, buffer.length, capped.message, true)
|
|
104
104
|
const rendered = capped.rendered
|
|
105
|
-
|
|
105
|
+
const unchanged = unchangedRead(context, input.path, input, content, buffer.length)
|
|
106
|
+
if (unchanged) return unchanged
|
|
107
|
+
recordObservedRead(context, input.path, content, rendered, input)
|
|
106
108
|
|
|
107
109
|
return {
|
|
108
110
|
success: true,
|
|
@@ -149,7 +151,12 @@ export const ReadFileTool = defineTool({
|
|
|
149
151
|
const capped = renderCappedRead(content, buffer.length, input, capOptions(input, context))
|
|
150
152
|
if (!capped.ok) return refusedRead(filePath, buffer.length, capped.message, false)
|
|
151
153
|
const rendered = capped.rendered
|
|
152
|
-
|
|
154
|
+
const unchanged =
|
|
155
|
+
retained === undefined
|
|
156
|
+
? unchangedRead(context, filePath, input, content, buffer.length)
|
|
157
|
+
: undefined
|
|
158
|
+
if (unchanged) return unchanged
|
|
159
|
+
recordObservedRead(context, filePath, content, rendered, input)
|
|
153
160
|
|
|
154
161
|
return {
|
|
155
162
|
success: true,
|
|
@@ -243,14 +250,69 @@ function recordObservedRead(
|
|
|
243
250
|
key: string,
|
|
244
251
|
content: string,
|
|
245
252
|
rendered: RenderedRead,
|
|
253
|
+
input: ReadWindowInput,
|
|
246
254
|
): void {
|
|
247
255
|
const tracker = context.fileReadTracker
|
|
248
256
|
if (!tracker) return
|
|
249
257
|
if (!rendered.partial && tracker.recordFullRead && context.toolUseId) {
|
|
250
258
|
tracker.recordFullRead(key, content, context.toolUseId, fingerprintContent(rendered.output))
|
|
251
|
-
|
|
259
|
+
} else {
|
|
260
|
+
tracker.recordRead(key, content)
|
|
261
|
+
}
|
|
262
|
+
if (tracker.recordReadWindow && context.toolUseId) {
|
|
263
|
+
tracker.recordReadWindow(key, windowKey(input), {
|
|
264
|
+
callId: context.toolUseId,
|
|
265
|
+
renderedFingerprint: fingerprintContent(rendered.output),
|
|
266
|
+
contentFingerprint: fingerprintContent(content),
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
type ReadWindowInput = {
|
|
272
|
+
readonly readRange?: readonly number[] | undefined
|
|
273
|
+
readonly offset?: number | undefined
|
|
274
|
+
readonly limit?: number | undefined
|
|
275
|
+
readonly allow_large?: boolean | undefined
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** The lines a read asked for, as one comparable string. */
|
|
279
|
+
function windowKey(input: ReadWindowInput): string {
|
|
280
|
+
const range = input.readRange
|
|
281
|
+
const lines = range
|
|
282
|
+
? `r${range[0]}-${range[1]}`
|
|
283
|
+
: `o${input.offset ?? 0}l${input.limit === undefined ? 'all' : input.limit}`
|
|
284
|
+
return input.allow_large === true ? `${lines}+large` : lines
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The answer to a repeat read of the same lines of a file that has not changed.
|
|
289
|
+
*
|
|
290
|
+
* The earlier result is still in front of the model, so returning the lines a
|
|
291
|
+
* second time only spends the window. It is given ONLY while that is true:
|
|
292
|
+
* the body must fingerprint the same as at the earlier read, and the earlier
|
|
293
|
+
* receipt must still be in the conversation exactly as the tool returned it
|
|
294
|
+
* (the host says so through `receiptVisible`). A receipt that was cleared,
|
|
295
|
+
* summarised away or annotated is no answer, and the lines are returned again.
|
|
296
|
+
*/
|
|
297
|
+
function unchangedRead(
|
|
298
|
+
context: ToolContext,
|
|
299
|
+
key: string,
|
|
300
|
+
input: ReadWindowInput,
|
|
301
|
+
content: string,
|
|
302
|
+
bytes: number,
|
|
303
|
+
) {
|
|
304
|
+
const tracker = context.fileReadTracker
|
|
305
|
+
if (!tracker?.readWindow || !context.receiptVisible) return undefined
|
|
306
|
+
const earlier = tracker.readWindow(key, windowKey(input))
|
|
307
|
+
if (!earlier || earlier.contentFingerprint !== fingerprintContent(content)) return undefined
|
|
308
|
+
if (!context.receiptVisible(earlier.callId, earlier.renderedFingerprint)) return undefined
|
|
309
|
+
return {
|
|
310
|
+
success: true as const,
|
|
311
|
+
output:
|
|
312
|
+
'Unchanged since your earlier read of the same lines. That result is still current; use it instead of reading again.',
|
|
313
|
+
data: { path: key, unchanged: true },
|
|
314
|
+
facts: { kind: 'read' as const, path: key, bytes, unchanged: true },
|
|
252
315
|
}
|
|
253
|
-
tracker.recordRead(key, content)
|
|
254
316
|
}
|
|
255
317
|
|
|
256
318
|
function describeStructuredBinaryRead(path: string, buffer: Buffer): string | null {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FileReadTracker } from '../types/tool/index.js'
|
|
1
|
+
import type { FileReadTracker, ReadWindowEntry } from '../types/tool/index.js'
|
|
2
2
|
import { fingerprintContent } from './builtins/content-fingerprint.js'
|
|
3
3
|
|
|
4
4
|
/** Host-owned observation ledger. Share only across turns of the same conversation and filesystem. */
|
|
@@ -48,7 +48,31 @@ export function createFileReadTracker(): FileReadTracker {
|
|
|
48
48
|
chains.delete(key)
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
// The last few windows read of each file, newest last. Bounded because a
|
|
52
|
+
// long session reads many files and a window key is cheap to repeat.
|
|
53
|
+
const windows = new Map<string, Map<string, ReadWindowEntry>>()
|
|
54
|
+
const MAX_WINDOWS_PER_FILE = 6
|
|
55
|
+
const MAX_WINDOWED_FILES = 512
|
|
51
56
|
return {
|
|
57
|
+
recordReadWindow(key, window, entry) {
|
|
58
|
+
let forFile = windows.get(key)
|
|
59
|
+
if (forFile) windows.delete(key)
|
|
60
|
+
else forFile = new Map()
|
|
61
|
+
forFile.delete(window)
|
|
62
|
+
forFile.set(window, entry)
|
|
63
|
+
while (forFile.size > MAX_WINDOWS_PER_FILE) {
|
|
64
|
+
const oldest = forFile.keys().next().value
|
|
65
|
+
if (oldest === undefined) break
|
|
66
|
+
forFile.delete(oldest)
|
|
67
|
+
}
|
|
68
|
+
windows.set(key, forFile)
|
|
69
|
+
while (windows.size > MAX_WINDOWED_FILES) {
|
|
70
|
+
const oldest = windows.keys().next().value
|
|
71
|
+
if (oldest === undefined) break
|
|
72
|
+
windows.delete(oldest)
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
readWindow: (key, window) => windows.get(key)?.get(window),
|
|
52
76
|
recordRead: observe,
|
|
53
77
|
recordFullRead(key, content, callId, renderedFingerprint) {
|
|
54
78
|
observe(key, content)
|
package/src/types/agent/base.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CompactionSummaryRouter } from '../../compaction/model-summary.js'
|
|
1
2
|
import type { ProjectInstructionContext } from '../../runtime/query/project-instructions.js'
|
|
2
3
|
import type { SessionPaths } from '../../session/paths.js'
|
|
3
4
|
import type { SessionTokenBudget } from '../../store/budget/index.js'
|
|
@@ -118,6 +119,21 @@ export interface BaseAgentConfig {
|
|
|
118
119
|
/** Live project policy, flushed independently of human continuation text. */
|
|
119
120
|
projectInstructionContext?: ProjectInstructionContext
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Text the host wants carried across a compaction, such as where its plan
|
|
124
|
+
* file is. Asked once per pass, after the summary is written; the answer is
|
|
125
|
+
* appended to the restored context. Returning nothing adds nothing.
|
|
126
|
+
*/
|
|
127
|
+
postCompactionContext?: () => string | undefined | Promise<string | undefined>
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Names the model that writes a compaction summary, asked once per pass.
|
|
131
|
+
* Absent, or resolving nothing, the summary is the extracted working state;
|
|
132
|
+
* a call that fails falls back to it too. The call's cost is charged to the
|
|
133
|
+
* turn.
|
|
134
|
+
*/
|
|
135
|
+
compactionSummaryRoute?: CompactionSummaryRouter
|
|
136
|
+
|
|
121
137
|
allowedTools?: readonly string[]
|
|
122
138
|
|
|
123
139
|
/**
|
|
@@ -132,6 +132,15 @@ export interface CreateTaskOptions {
|
|
|
132
132
|
|
|
133
133
|
prompt: string
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Messages the child starts from, placed before {@link prompt}. A host that
|
|
137
|
+
* delegates work which continues the caller's conversation passes that
|
|
138
|
+
* conversation here; the prompt then arrives as the last user message.
|
|
139
|
+
* Absent, the child sees only the prompt. The caller is responsible for the
|
|
140
|
+
* list being a valid conversation (every tool call answered).
|
|
141
|
+
*/
|
|
142
|
+
readonly inheritedMessages?: readonly import('../message/index.js').Message[]
|
|
143
|
+
|
|
135
144
|
workingDirectory: string
|
|
136
145
|
|
|
137
146
|
runtimeContext?: AgentRuntimeContext
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ProviderError,
|
|
3
|
+
type TransportCause,
|
|
4
|
+
classifyProviderError,
|
|
5
|
+
readTransportCause,
|
|
6
|
+
} from '../provider/errors.js'
|
|
2
7
|
import { isNamzuError } from './index.js'
|
|
3
8
|
|
|
4
9
|
/**
|
|
@@ -33,6 +38,8 @@ export interface ErrorFacts {
|
|
|
33
38
|
readonly retryAfterMs?: number
|
|
34
39
|
/** A hint the throw site attached directly. See {@link readHint}. */
|
|
35
40
|
readonly hint?: string
|
|
41
|
+
/** When the failure is the network path rather than the provider: which part of it. */
|
|
42
|
+
readonly transport?: TransportCause
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
export interface ErrorCatalogRule {
|
|
@@ -91,10 +98,12 @@ export function factsOf(err: unknown): ErrorFacts {
|
|
|
91
98
|
? classifyProviderError(err)
|
|
92
99
|
: undefined
|
|
93
100
|
if (providerFailure) {
|
|
101
|
+
const transport = readTransportCause(providerFailure)
|
|
94
102
|
return {
|
|
95
103
|
code: providerFailure.code,
|
|
96
104
|
message,
|
|
97
105
|
name: providerFailure.name,
|
|
106
|
+
...(transport !== undefined ? { transport } : {}),
|
|
98
107
|
...(providerFailure.status !== undefined ? { status: providerFailure.status } : {}),
|
|
99
108
|
...(providerFailure.retryAfterMs !== undefined
|
|
100
109
|
? { retryAfterMs: providerFailure.retryAfterMs }
|
|
@@ -159,7 +168,7 @@ export const DEFAULT_ERROR_RULES: readonly ErrorCatalogRule[] = [
|
|
|
159
168
|
id: 'provider.rate_limit',
|
|
160
169
|
when: (facts) => facts.code === 'rate_limit' || facts.status === 429,
|
|
161
170
|
message: 'The provider is rate limiting this turn.',
|
|
162
|
-
hint: '
|
|
171
|
+
hint: 'Wait for the provider retry delay when reported, or for the quota window to reset, before resuming. When the wait is long, continue on another model or provider.',
|
|
163
172
|
},
|
|
164
173
|
{
|
|
165
174
|
id: 'provider.context_overflow',
|
|
@@ -176,6 +185,26 @@ export const DEFAULT_ERROR_RULES: readonly ErrorCatalogRule[] = [
|
|
|
176
185
|
message: 'The provider is failing on its own side.',
|
|
177
186
|
hint: 'Nothing in the turn is wrong. Resume from the last checkpoint once the provider recovers.',
|
|
178
187
|
},
|
|
188
|
+
{
|
|
189
|
+
// Before the general network rule: a proxy that wants a sign-in and a
|
|
190
|
+
// certificate nobody trusts each have a fix the general advice does not name.
|
|
191
|
+
id: 'provider.proxy_auth',
|
|
192
|
+
when: (facts) => facts.transport === 'proxy_auth_required',
|
|
193
|
+
message: 'The network proxy asked for a sign-in before it would pass the request on.',
|
|
194
|
+
hint: 'Put the proxy user name and password in the proxy address (for example HTTPS_PROXY=http://user:password@host:port), or ask whoever runs the proxy for access.',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'provider.proxy_refused',
|
|
198
|
+
when: (facts) => facts.transport === 'proxy_refused',
|
|
199
|
+
message: 'The network proxy refused to pass the request on.',
|
|
200
|
+
hint: 'Check the proxy address and the NO_PROXY list, and ask whoever runs the proxy whether this host is allowed to reach the provider.',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'provider.untrusted_certificate',
|
|
204
|
+
when: (facts) => facts.transport === 'untrusted_certificate',
|
|
205
|
+
message: 'The connection was refused because its certificate is not trusted.',
|
|
206
|
+
hint: 'If a proxy or firewall inspects encrypted traffic, set NODE_EXTRA_CA_CERTS to the file holding its certificate authority and start again.',
|
|
207
|
+
},
|
|
179
208
|
{
|
|
180
209
|
id: 'provider.network',
|
|
181
210
|
when: (facts) => facts.code === 'network' || facts.code === 'timeout',
|
|
@@ -51,4 +51,11 @@ export interface ProviderRequestErrorInit extends ProviderErrorInfo {
|
|
|
51
51
|
* is believed over the status code, so the retry loop leaves it alone.
|
|
52
52
|
*/
|
|
53
53
|
readonly shouldRetry?: boolean
|
|
54
|
+
/**
|
|
55
|
+
* Which part of the network path the request tripped on (a proxy that wants a
|
|
56
|
+
* sign-in, a certificate nobody trusts), when the vendor error's causes say.
|
|
57
|
+
* Data and not a `cause`: the raw error is never attached, but the diagnosis
|
|
58
|
+
* has to survive the wrapping or no one downstream can act on it.
|
|
59
|
+
*/
|
|
60
|
+
readonly transport?: import('./errors.js').TransportCause
|
|
54
61
|
}
|