zidane 6.5.0 → 6.5.2
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/dist/{acp-B-AOovHz.js → acp-565X8nVP.js} +6 -7
- package/dist/{acp-B-AOovHz.js.map → acp-565X8nVP.js.map} +1 -1
- package/dist/acp-cli.js +6 -6
- package/dist/acp.d.ts +1 -1
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-SQ6rxfpT.js → agent-CvO7UOxv.js} +1427 -610
- package/dist/agent-CvO7UOxv.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -2
- package/dist/{anthropic-D3lkw5mv.js → anthropic-Dov2mC5e.js} +3 -3
- package/dist/{anthropic-D3lkw5mv.js.map → anthropic-Dov2mC5e.js.map} +1 -1
- package/dist/{auth-BzYbbdN_.js → auth-B8ZuBcKC.js} +5 -5
- package/dist/{auth-BzYbbdN_.js.map → auth-B8ZuBcKC.js.map} +1 -1
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +3 -3
- package/dist/chat.d.ts +114 -5
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +6 -6
- package/dist/context-segments-1mcqSCM3.js +494 -0
- package/dist/context-segments-1mcqSCM3.js.map +1 -0
- package/dist/{context-transport-CDBatvQc.js → context-transport-ClvadrlH.js} +3 -3
- package/dist/{context-transport-CDBatvQc.js.map → context-transport-ClvadrlH.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +3 -3
- package/dist/{index-CWBSrqI6.d.ts → index-DfERTrql.d.ts} +178 -6
- package/dist/index-DfERTrql.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/{login-XZDS4K2o.js → login-Cl6ML2XN.js} +2 -2
- package/dist/{login-XZDS4K2o.js.map → login-Cl6ML2XN.js.map} +1 -1
- package/dist/{mcp-D27rP47I.js → mcp-DdntU47g.js} +3 -3
- package/dist/{mcp-D27rP47I.js.map → mcp-DdntU47g.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-C01kKGj0.js → messages-C_H9soi5.js} +2 -3
- package/dist/{messages-C01kKGj0.js.map → messages-C_H9soi5.js.map} +1 -1
- package/dist/{openai-compat-B3c_hzwf.js → openai-compat-CnHeo4Lz.js} +216 -3
- package/dist/openai-compat-CnHeo4Lz.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +2 -2
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-z1vU0b2Q.js → providers-B6mFe7OD.js} +3 -3
- package/dist/{providers-z1vU0b2Q.js.map → providers-B6mFe7OD.js.map} +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +4 -4
- package/dist/restate.d.ts +178 -2
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js +458 -4
- package/dist/restate.js.map +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-cr9VdoEO.js → session-VpLrNaFQ.js} +2 -2
- package/dist/{session-cr9VdoEO.js.map → session-VpLrNaFQ.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +1 -1
- package/dist/{stdio-O-m9WVkE.js → stdio-BQeCMmuK.js} +2 -2
- package/dist/{stdio-O-m9WVkE.js.map → stdio-BQeCMmuK.js.map} +1 -1
- package/dist/{stdio-loader-BSGQmG4L.js → stdio-loader-BN1l1ZAG.js} +2 -2
- package/dist/{stdio-loader-BSGQmG4L.js.map → stdio-loader-BN1l1ZAG.js.map} +1 -1
- package/dist/{tool-formatters-gFqQbzPY.d.ts → tool-formatters-B2gBbKaS.d.ts} +2 -2
- package/dist/{tool-formatters-gFqQbzPY.d.ts.map → tool-formatters-B2gBbKaS.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -2
- package/dist/{transcript-anchors-qCs3wZpu.d.ts → transcript-anchors-CeIxhKP7.d.ts} +2 -2
- package/dist/{transcript-anchors-qCs3wZpu.d.ts.map → transcript-anchors-CeIxhKP7.d.ts.map} +1 -1
- package/dist/{transcript-anchors-D4IxJifV.js → transcript-anchors-CmFKUw8M.js} +139 -19
- package/dist/transcript-anchors-CmFKUw8M.js.map +1 -0
- package/dist/tui.d.ts +74 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +819 -83
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-M9-jUyDd.d.ts → turn-operations-Dyv3Z-Ff.d.ts} +3 -3
- package/dist/{turn-operations-M9-jUyDd.d.ts.map → turn-operations-Dyv3Z-Ff.d.ts.map} +1 -1
- package/dist/{turn-operations-CAvi8NYt.js → turn-operations-iGu5J08j.js} +2 -2
- package/dist/{turn-operations-CAvi8NYt.js.map → turn-operations-iGu5J08j.js.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/{xai-BPwMQSuF.js → xai-Du6Wfnlb.js} +2 -2
- package/dist/{xai-BPwMQSuF.js.map → xai-Du6Wfnlb.js.map} +1 -1
- package/docs/CHAT.md +42 -1
- package/docs/INTERACTIONS.md +39 -0
- package/docs/RESTATE.md +102 -3
- package/docs/TUI.md +28 -2
- package/package.json +1 -1
- package/dist/agent-SQ6rxfpT.js.map +0 -1
- package/dist/aliasing-lVFbm81n.js +0 -217
- package/dist/aliasing-lVFbm81n.js.map +0 -1
- package/dist/context-breakdown-Dzo25N45.js +0 -155
- package/dist/context-breakdown-Dzo25N45.js.map +0 -1
- package/dist/edit-utils-EGosADZq.js +0 -234
- package/dist/edit-utils-EGosADZq.js.map +0 -1
- package/dist/index-CWBSrqI6.d.ts.map +0 -1
- package/dist/openai-compat-B3c_hzwf.js.map +0 -1
- package/dist/tool-state-DTs-3ZHH.js +0 -653
- package/dist/tool-state-DTs-3ZHH.js.map +0 -1
- package/dist/transcript-anchors-D4IxJifV.js.map +0 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as rewriteMessagesToWire, H as toWireName, I as augmentMcpDoubleUnderscoreAliases, L as buildAliasMaps, M as buildPromptMessage, N as canonicalizePrompt, R as buildSanitizedWireAliases, V as toCanonicalName, z as rewriteContentToCanonical } from "./openai-compat-CnHeo4Lz.js";
|
|
2
2
|
import { a as isWireTurn, c as toolOutputByteLength, i as documentBlockMarker, l as toolResultToText, o as sanitizeToolResultMetadata, s as toolOutputBudgetByteLength, t as DEFAULT_AGENT_CLOCK } from "./types-BIarq1qE.js";
|
|
3
|
-
import { i as
|
|
4
|
-
import { a as getReadState, d as noteStaleRejection, f as readStateKey, l as hydrateReadStateFromSessionAsync, m as staleRefusalHint, n as createToolStateController, o as getToolDedupState, p as resolveReadStateMap, s as hashContent, u as markReadStateElided } from "./tool-state-DTs-3ZHH.js";
|
|
3
|
+
import { a as stripLineNumberPrefixes, i as resolveOldString, n as buildContextBreakdown, o as styleReplacementForVia, r as describeVia, t as buildContextSegments } from "./context-segments-1mcqSCM3.js";
|
|
5
4
|
import { n as estimateTokens, r as utf8ByteLength } from "./utils-ngQzYzZD.js";
|
|
6
|
-
import { t as buildContextBreakdown } from "./context-breakdown-Dzo25N45.js";
|
|
7
5
|
import { a as formatDuration, c as previewLine, o as formatTaskStatus, s as formatTaskSummary } from "./format-BF745pLL.js";
|
|
8
6
|
import { i as parseGitLsFiles, r as GIT_LS_FILES_COMMAND } from "./glob-DluQFSw1.js";
|
|
9
7
|
import { i as isOutputExceededMessage } from "./output-cap-C9zukcCj.js";
|
|
@@ -15,11 +13,10 @@ import { n as writeFileAtomicAsync, t as writeFileAtomic } from "./atomic-write-
|
|
|
15
13
|
import { p as resolveStorageDirs, r as loadAgentPlugin } from "./plugins-2PMTVcp-.js";
|
|
16
14
|
import { r as createLogger, t as consoleSink } from "./logger-DBX9uYOw.js";
|
|
17
15
|
import { n as reconcileImageMediaType } from "./media-sniff-UKijEaiQ.js";
|
|
18
|
-
import { f as wrapDiscoveredMcpTools, i as deriveMcpToolMeta, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-
|
|
19
|
-
import { M as buildPromptMessage, N as canonicalizePrompt } from "./openai-compat-B3c_hzwf.js";
|
|
16
|
+
import { f as wrapDiscoveredMcpTools, i as deriveMcpToolMeta, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-DdntU47g.js";
|
|
20
17
|
import { appendStaticSection, renderSystemForWire } from "./prompt.js";
|
|
21
|
-
import { a as detectTurnInterruption, c as ensureToolResultPairing, l as filterUnresolvedToolUses, m as resolveContentRefsInMessages, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, p as remintDuplicateToolCallIds, s as ensureEndsWithUserMessage } from "./messages-
|
|
22
|
-
import { a as getExtensionSessionState, c as setExtensionSessionState, l as withExtensionSession, n as currentExtensionSession, r as deleteExtensionSessionState, s as resumeChainRunIds } from "./session-
|
|
18
|
+
import { a as detectTurnInterruption, c as ensureToolResultPairing, l as filterUnresolvedToolUses, m as resolveContentRefsInMessages, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, p as remintDuplicateToolCallIds, s as ensureEndsWithUserMessage } from "./messages-C_H9soi5.js";
|
|
19
|
+
import { a as getExtensionSessionState, c as setExtensionSessionState, l as withExtensionSession, n as currentExtensionSession, r as deleteExtensionSessionState, s as resumeChainRunIds } from "./session-VpLrNaFQ.js";
|
|
23
20
|
import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
|
|
24
21
|
import { a as withInactivityTimeout, i as settleWithinTimeout, n as linkAbortSignal, o as withTimeout$1, r as resolveTimeoutMs, t as OperationTimeoutError } from "./timeout-BDetbuN6.js";
|
|
25
22
|
import { createRequire } from "node:module";
|
|
@@ -31,6 +28,436 @@ import { copyFile, mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
|
|
|
31
28
|
import { pathToFileURL } from "node:url";
|
|
32
29
|
import { homedir, tmpdir } from "node:os";
|
|
33
30
|
import { createHash } from "node:crypto";
|
|
31
|
+
//#region src/tools/read-state.ts
|
|
32
|
+
const STATE = /* @__PURE__ */ new WeakMap();
|
|
33
|
+
/**
|
|
34
|
+
* Get or lazily create the per-session read-state map. Returns `undefined`
|
|
35
|
+
* when no session is provided.
|
|
36
|
+
*
|
|
37
|
+
* Most tool callers should prefer {@link resolveReadStateMap}, which
|
|
38
|
+
* additionally honors an explicit `ctx.readState` (the path
|
|
39
|
+
* `spawn`'s `shareReadState: true` uses to forward the parent's map
|
|
40
|
+
* into a sessionless child). Use this helper directly only when the
|
|
41
|
+
* Session-keyed map is exactly what you want and you don't need to
|
|
42
|
+
* accept an override.
|
|
43
|
+
*/
|
|
44
|
+
function getReadState(session) {
|
|
45
|
+
if (!session) return void 0;
|
|
46
|
+
let map = STATE.get(session);
|
|
47
|
+
if (!map) {
|
|
48
|
+
map = /* @__PURE__ */ new Map();
|
|
49
|
+
STATE.set(session, map);
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the active read-state map from a tool context. An explicit
|
|
55
|
+
* `ctx.readState` wins (used by `spawn`'s `shareReadState` opt-in to
|
|
56
|
+
* forward the parent's map into a sessionless child); otherwise the
|
|
57
|
+
* usual `Session`-keyed lookup applies.
|
|
58
|
+
*
|
|
59
|
+
* Tools should call this helper instead of `getReadState(ctx.session)`
|
|
60
|
+
* directly so the `shareReadState` plumbing is honored uniformly.
|
|
61
|
+
*/
|
|
62
|
+
function resolveReadStateMap(ctx) {
|
|
63
|
+
return ctx.readState ?? getReadState(ctx.session);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Mark a read-state entry as elided from the on-wire history WITHOUT
|
|
67
|
+
* un-registering it.
|
|
68
|
+
*
|
|
69
|
+
* Used by the loop's `elideStaleReads` pass. The entry must survive —
|
|
70
|
+
* deleting it made the `requireReadBeforeEdit` gate reject the very next
|
|
71
|
+
* edit on a file the model had demonstrably read and just edited (the
|
|
72
|
+
* gate's failure mode then defeats the gate: models degrade to raw shell
|
|
73
|
+
* edits that bypass the guard entirely). The `elided` flag instead
|
|
74
|
+
* defeats only the dedup stub: the next identical re-read returns the
|
|
75
|
+
* full body again, and `rememberRead` clears the flag.
|
|
76
|
+
*
|
|
77
|
+
* No-op when the path has no entry in the resolved map(s).
|
|
78
|
+
*/
|
|
79
|
+
function markReadStateElided(ctx, cwd, path) {
|
|
80
|
+
const key = readStateKey(cwd, path);
|
|
81
|
+
for (const map of [ctx.readState, ctx.session ? STATE.get(ctx.session) : void 0]) {
|
|
82
|
+
const entry = map?.get(key);
|
|
83
|
+
if (entry && entry.elided !== true) map.set(key, {
|
|
84
|
+
...entry,
|
|
85
|
+
elided: true
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Record one `requireReadBeforeEdit` drift refusal against `entry` and
|
|
91
|
+
* return the consecutive count (1 for the first). The caller composes the
|
|
92
|
+
* refusal message with {@link staleRefusalHint}.
|
|
93
|
+
*/
|
|
94
|
+
function noteStaleRejection(map, key, entry) {
|
|
95
|
+
const count = (entry.staleRejections ?? 0) + 1;
|
|
96
|
+
map.set(key, {
|
|
97
|
+
...entry,
|
|
98
|
+
staleRejections: count
|
|
99
|
+
});
|
|
100
|
+
return count;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Escalating suffix for a drift refusal. Empty on the first refusal — the
|
|
104
|
+
* base "re-read the file" advice is correct and sufficient. From the second
|
|
105
|
+
* consecutive refusal on, the model has demonstrably re-read and been
|
|
106
|
+
* refused again, so repeating the same advice reads as a broken tool;
|
|
107
|
+
* name the real hypothesis (an external writer) instead.
|
|
108
|
+
*/
|
|
109
|
+
function staleRefusalHint(count) {
|
|
110
|
+
if (count < 2) return "";
|
|
111
|
+
return ` This is refusal #${count} since your last read — a process outside this session (editor auto-save, formatter, codegen watcher) may be rewriting the file between your reads.`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Canonical read-state key for a `(cwd, path)` pair.
|
|
115
|
+
*
|
|
116
|
+
* `ctx.execution.readFile(handle, path)` resolves the model-provided
|
|
117
|
+
* path against `handle.cwd` before touching disk — so `src/App.tsx`,
|
|
118
|
+
* `./src/App.tsx`, and `/abs/cwd/src/App.tsx` all read the **same**
|
|
119
|
+
* bytes. The read-state map MUST mirror that resolution: without it,
|
|
120
|
+
* a model that reads `src/App.tsx` and then edits `./src/App.tsx`
|
|
121
|
+
* trips the `requireReadBeforeEdit` gate for a file it demonstrably
|
|
122
|
+
* already saw (the gate's "has not been read in this session" message
|
|
123
|
+
* fires on a stale key).
|
|
124
|
+
*
|
|
125
|
+
* `node:path`'s `resolve(cwd, path)` short-circuits when `path` is
|
|
126
|
+
* already absolute, so absolute and relative shapes converge on the
|
|
127
|
+
* same canonical form. We don't `realpath()` symlinks — the file IS
|
|
128
|
+
* the path the model addressed, not the realpath behind it; the host's
|
|
129
|
+
* execution context decides how to dereference.
|
|
130
|
+
*/
|
|
131
|
+
function readStateKey(cwd, path) {
|
|
132
|
+
return resolve(cwd, path);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 64-bit content hash: two independent 32-bit FNV-1a-style lanes (different
|
|
136
|
+
* offset bases + multipliers) concatenated as 16 hex chars. Fast,
|
|
137
|
+
* non-cryptographic — but wide enough that the `requireReadBeforeEdit`
|
|
138
|
+
* drift gate, which silently corrupts a file on a false "unchanged"
|
|
139
|
+
* verdict, isn't betting on a 32-bit birthday bound (~1 in 4 billion per
|
|
140
|
+
* pair; uncomfortably reachable over many files × many sessions). Entries
|
|
141
|
+
* live only in per-session in-memory maps, so widening needs no
|
|
142
|
+
* stored-format migration. Still cheaper than allocating a Buffer and
|
|
143
|
+
* pulling in `crypto`.
|
|
144
|
+
*/
|
|
145
|
+
function hashContent(text) {
|
|
146
|
+
let h1 = 2166136261;
|
|
147
|
+
let h2 = 2128831031;
|
|
148
|
+
for (let i = 0; i < text.length; i++) {
|
|
149
|
+
const c = text.charCodeAt(i);
|
|
150
|
+
h1 = Math.imul(h1 ^ c, 16777619) >>> 0;
|
|
151
|
+
h2 = Math.imul(h2 ^ c, 2246822507) >>> 0;
|
|
152
|
+
}
|
|
153
|
+
return h1.toString(16).padStart(8, "0") + h2.toString(16).padStart(8, "0");
|
|
154
|
+
}
|
|
155
|
+
const READ_FILE_DEFAULT_OFFSET = 1;
|
|
156
|
+
const READ_FILE_DEFAULT_LIMIT = 2e3;
|
|
157
|
+
const READ_FILE_DEFAULT_MAX_BYTES = 262144;
|
|
158
|
+
const READ_FILE_FOOTER_RE = /\n\n…(?:read lines|truncated)/;
|
|
159
|
+
const READ_FILE_LINE_RE = /^\d{1,9}\t(.*)$/;
|
|
160
|
+
/**
|
|
161
|
+
* Rebuild the per-session read-state map from durable session turns.
|
|
162
|
+
*
|
|
163
|
+
* Durable replay runtimes may return recorded tool results without re-running
|
|
164
|
+
* the tool body, so in-memory side effects like `readState.set(...)` can be
|
|
165
|
+
* missing even though the transcript contains the successful `read_file`.
|
|
166
|
+
* This helper is intentionally opt-in: live hosts keep the normal side-effect
|
|
167
|
+
* behavior unless they call it themselves.
|
|
168
|
+
*
|
|
169
|
+
* Hydration only fills missing keys. Live in-memory state may already reflect a
|
|
170
|
+
* newer read or edit from the current run, and durable history cannot prove it
|
|
171
|
+
* is fresher. Relative paths are resolved against the current execution cwd,
|
|
172
|
+
* matching replay hosts that run sessions from a stable workspace.
|
|
173
|
+
*/
|
|
174
|
+
function hydrateReadStateFromSession(session, cwd, options = {}) {
|
|
175
|
+
if (!session) return 0;
|
|
176
|
+
const readState = getReadState(session);
|
|
177
|
+
if (!readState) return 0;
|
|
178
|
+
const normalized = normalizeHydrateReadStateOptions(options);
|
|
179
|
+
const pending = /* @__PURE__ */ new Map();
|
|
180
|
+
const known = /* @__PURE__ */ new Map();
|
|
181
|
+
const preexistingKeys = new Set(readState.keys());
|
|
182
|
+
let hydrated = 0;
|
|
183
|
+
for (const turn of session.turns) for (const block of turn.content) {
|
|
184
|
+
if (block.type === "tool_call") {
|
|
185
|
+
const canonicalName = canonicalToolCallName(block.name, normalized);
|
|
186
|
+
if (canonicalName === normalized.readFileToolName || canonicalName === normalized.writeFileToolName || canonicalName === normalized.editToolName || canonicalName === normalized.multiEditToolName) pending.set(block.id, {
|
|
187
|
+
name: canonicalName,
|
|
188
|
+
input: block.input
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (block.type !== "tool_result") continue;
|
|
193
|
+
const call = pending.get(block.callId);
|
|
194
|
+
if (!call) continue;
|
|
195
|
+
pending.delete(block.callId);
|
|
196
|
+
if (block.isError === true) continue;
|
|
197
|
+
const next = stateFromToolResult(call, toolResultToText(block.output), turn, cwd, normalized, known);
|
|
198
|
+
if (!next) continue;
|
|
199
|
+
if (!preexistingKeys.has(next.key)) {
|
|
200
|
+
readState.set(next.key, next.file.entry);
|
|
201
|
+
hydrated += 1;
|
|
202
|
+
}
|
|
203
|
+
known.set(next.key, next.file);
|
|
204
|
+
}
|
|
205
|
+
return hydrated;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Async replay hydrator for durable hosts that can cheaply read the current
|
|
209
|
+
* full file through their execution context. Transcript-only reconstruction is
|
|
210
|
+
* still preferred for full reads; partial/truncated read_file entries fall back
|
|
211
|
+
* to `readFileForHash` and hash those full current bytes.
|
|
212
|
+
*/
|
|
213
|
+
async function hydrateReadStateFromSessionAsync(session, cwd, options = {}) {
|
|
214
|
+
if (!session) return 0;
|
|
215
|
+
const readState = getReadState(session);
|
|
216
|
+
if (!readState) return 0;
|
|
217
|
+
const normalized = normalizeHydrateReadStateOptions(options);
|
|
218
|
+
const pending = /* @__PURE__ */ new Map();
|
|
219
|
+
const known = /* @__PURE__ */ new Map();
|
|
220
|
+
const preexistingKeys = new Set(readState.keys());
|
|
221
|
+
let hydrated = 0;
|
|
222
|
+
for (const turn of session.turns) for (const block of turn.content) {
|
|
223
|
+
if (block.type === "tool_call") {
|
|
224
|
+
const canonicalName = canonicalToolCallName(block.name, normalized);
|
|
225
|
+
if (canonicalName === normalized.readFileToolName || canonicalName === normalized.writeFileToolName || canonicalName === normalized.editToolName || canonicalName === normalized.multiEditToolName) pending.set(block.id, {
|
|
226
|
+
name: canonicalName,
|
|
227
|
+
input: block.input
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (block.type !== "tool_result") continue;
|
|
232
|
+
const call = pending.get(block.callId);
|
|
233
|
+
if (!call) continue;
|
|
234
|
+
pending.delete(block.callId);
|
|
235
|
+
if (block.isError === true) continue;
|
|
236
|
+
const next = await stateFromToolResultAsync(call, toolResultToText(block.output), turn, cwd, normalized, known);
|
|
237
|
+
if (!next) continue;
|
|
238
|
+
if (!preexistingKeys.has(next.key)) {
|
|
239
|
+
readState.set(next.key, next.file.entry);
|
|
240
|
+
hydrated += 1;
|
|
241
|
+
}
|
|
242
|
+
known.set(next.key, next.file);
|
|
243
|
+
}
|
|
244
|
+
return hydrated;
|
|
245
|
+
}
|
|
246
|
+
function normalizeHydrateReadStateOptions(options) {
|
|
247
|
+
return {
|
|
248
|
+
readFileToolName: options.readFileToolName ?? "read_file",
|
|
249
|
+
writeFileToolName: options.writeFileToolName ?? "write_file",
|
|
250
|
+
editToolName: options.editToolName ?? "edit",
|
|
251
|
+
multiEditToolName: options.multiEditToolName ?? "multi_edit",
|
|
252
|
+
defaultLineNumbers: options.defaultLineNumbers ?? true,
|
|
253
|
+
...options.readFileForHash ? { readFileForHash: options.readFileForHash } : {},
|
|
254
|
+
...options.aliasMaps ? { aliasMaps: options.aliasMaps } : {}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolve a transcript tool-call name to its canonical form for matching.
|
|
259
|
+
* Honors `aliasMaps` (wire → canonical) when present; otherwise the name is
|
|
260
|
+
* already canonical (zidane persists canonical names) and passes through.
|
|
261
|
+
*/
|
|
262
|
+
function canonicalToolCallName(name, options) {
|
|
263
|
+
return options.aliasMaps ? toCanonicalName(name, options.aliasMaps) : name;
|
|
264
|
+
}
|
|
265
|
+
function stateFromToolResult(call, output, resultTurn, cwd, options, known) {
|
|
266
|
+
if (call.name === options.readFileToolName) return readFileStateFromResult(call.input, output, resultTurn, cwd, options.defaultLineNumbers);
|
|
267
|
+
if (call.name === options.writeFileToolName) return writeFileStateFromResult(call.input, output, resultTurn, cwd);
|
|
268
|
+
if (call.name === options.editToolName) return editStateFromResult(call.input, output, resultTurn, cwd, known);
|
|
269
|
+
if (call.name === options.multiEditToolName) return multiEditStateFromResult(call.input, output, resultTurn, cwd, known);
|
|
270
|
+
}
|
|
271
|
+
async function stateFromToolResultAsync(call, output, resultTurn, cwd, options, known) {
|
|
272
|
+
if (call.name === options.readFileToolName) return await readFileStateFromResultAsync(call.input, output, resultTurn, cwd, {
|
|
273
|
+
defaultLineNumbers: options.defaultLineNumbers,
|
|
274
|
+
readFileForHash: options.readFileForHash
|
|
275
|
+
});
|
|
276
|
+
return stateFromToolResult(call, output, resultTurn, cwd, options, known);
|
|
277
|
+
}
|
|
278
|
+
function readFileStateFromResult(input, output, resultTurn, cwd, defaultLineNumbers) {
|
|
279
|
+
const path = typeof input.path === "string" ? input.path : void 0;
|
|
280
|
+
if (!path || !isSuccessfulFullReadOutput(output)) return void 0;
|
|
281
|
+
const lineNumbers = typeof input.lineNumbers === "boolean" ? input.lineNumbers : defaultLineNumbers;
|
|
282
|
+
const content = lineNumbers ? stripReadFileLineNumbers(output) : output;
|
|
283
|
+
if (content === void 0) return void 0;
|
|
284
|
+
const entry = {
|
|
285
|
+
contentHash: hashContent(content),
|
|
286
|
+
offset: normalizeReadInteger(input.offset, READ_FILE_DEFAULT_OFFSET),
|
|
287
|
+
limit: normalizeReadInteger(input.limit, READ_FILE_DEFAULT_LIMIT),
|
|
288
|
+
maxBytes: normalizeReadInteger(input.maxBytes, READ_FILE_DEFAULT_MAX_BYTES),
|
|
289
|
+
lineNumbers,
|
|
290
|
+
mtimeMs: resultTurn.createdAt
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
key: readStateKey(cwd, path),
|
|
294
|
+
file: {
|
|
295
|
+
content,
|
|
296
|
+
entry
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async function readFileStateFromResultAsync(input, output, resultTurn, cwd, options) {
|
|
301
|
+
const reconstructed = readFileStateFromResult(input, output, resultTurn, cwd, options.defaultLineNumbers);
|
|
302
|
+
if (reconstructed) return reconstructed;
|
|
303
|
+
const path = typeof input.path === "string" ? input.path : void 0;
|
|
304
|
+
if (!path || !options.readFileForHash || !isSuccessfulReadOutput(output)) return void 0;
|
|
305
|
+
let content;
|
|
306
|
+
try {
|
|
307
|
+
content = await options.readFileForHash(path);
|
|
308
|
+
} catch {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (typeof content !== "string") return void 0;
|
|
312
|
+
const lineNumbers = typeof input.lineNumbers === "boolean" ? input.lineNumbers : options.defaultLineNumbers;
|
|
313
|
+
const entry = {
|
|
314
|
+
contentHash: hashContent(content),
|
|
315
|
+
offset: normalizeReadInteger(input.offset, READ_FILE_DEFAULT_OFFSET),
|
|
316
|
+
limit: normalizeReadInteger(input.limit, READ_FILE_DEFAULT_LIMIT),
|
|
317
|
+
maxBytes: normalizeReadInteger(input.maxBytes, READ_FILE_DEFAULT_MAX_BYTES),
|
|
318
|
+
lineNumbers,
|
|
319
|
+
mtimeMs: resultTurn.createdAt
|
|
320
|
+
};
|
|
321
|
+
return {
|
|
322
|
+
key: readStateKey(cwd, path),
|
|
323
|
+
file: {
|
|
324
|
+
content,
|
|
325
|
+
entry
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function writeFileStateFromResult(input, output, resultTurn, cwd) {
|
|
330
|
+
const path = typeof input.path === "string" ? input.path : void 0;
|
|
331
|
+
const content = typeof input.content === "string" ? input.content : void 0;
|
|
332
|
+
if (!path || content === void 0) return void 0;
|
|
333
|
+
if (!output.startsWith(`Created ${path} (`) && !output.startsWith(`Updated ${path} (`)) return void 0;
|
|
334
|
+
return {
|
|
335
|
+
key: readStateKey(cwd, path),
|
|
336
|
+
file: {
|
|
337
|
+
content,
|
|
338
|
+
entry: {
|
|
339
|
+
contentHash: hashContent(content),
|
|
340
|
+
offset: 0,
|
|
341
|
+
limit: Number.POSITIVE_INFINITY,
|
|
342
|
+
maxBytes: Number.POSITIVE_INFINITY,
|
|
343
|
+
mtimeMs: resultTurn.createdAt,
|
|
344
|
+
elided: true
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function editStateFromResult(input, output, resultTurn, cwd, known) {
|
|
350
|
+
const path = typeof input.path === "string" ? input.path : void 0;
|
|
351
|
+
const oldString = typeof input.old_string === "string" ? input.old_string : void 0;
|
|
352
|
+
const newString = typeof input.new_string === "string" ? input.new_string : void 0;
|
|
353
|
+
if (!path || oldString === void 0 || newString === void 0) return void 0;
|
|
354
|
+
if (!output.startsWith(`Edited ${path}: replaced `)) return void 0;
|
|
355
|
+
const key = readStateKey(cwd, path);
|
|
356
|
+
const prior = known.get(key);
|
|
357
|
+
if (!prior) return void 0;
|
|
358
|
+
const nextContent = applyEdit(prior.content, oldString, newString, input.replace_all === true);
|
|
359
|
+
if (nextContent === void 0) return void 0;
|
|
360
|
+
return {
|
|
361
|
+
key,
|
|
362
|
+
file: {
|
|
363
|
+
content: nextContent,
|
|
364
|
+
entry: {
|
|
365
|
+
...prior.entry,
|
|
366
|
+
contentHash: hashContent(nextContent),
|
|
367
|
+
mtimeMs: resultTurn.createdAt,
|
|
368
|
+
elided: true
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function multiEditStateFromResult(input, output, resultTurn, cwd, known) {
|
|
374
|
+
const path = typeof input.path === "string" ? input.path : void 0;
|
|
375
|
+
const edits = Array.isArray(input.edits) ? input.edits : void 0;
|
|
376
|
+
if (!path || !edits) return void 0;
|
|
377
|
+
if (!output.startsWith(`Edited ${path}: applied `)) return void 0;
|
|
378
|
+
const key = readStateKey(cwd, path);
|
|
379
|
+
const prior = known.get(key);
|
|
380
|
+
if (!prior) return void 0;
|
|
381
|
+
const applied = parseMultiEditAppliedSteps(output, edits.length);
|
|
382
|
+
let current = prior.content;
|
|
383
|
+
for (let i = 0; i < edits.length; i++) {
|
|
384
|
+
if (!applied.has(i)) continue;
|
|
385
|
+
const step = edits[i];
|
|
386
|
+
if (typeof step.old_string !== "string" || typeof step.new_string !== "string") return void 0;
|
|
387
|
+
const next = applyEdit(current, step.old_string, step.new_string, step.replace_all === true);
|
|
388
|
+
if (next === void 0) return void 0;
|
|
389
|
+
current = next;
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
key,
|
|
393
|
+
file: {
|
|
394
|
+
content: current,
|
|
395
|
+
entry: {
|
|
396
|
+
...prior.entry,
|
|
397
|
+
contentHash: hashContent(current),
|
|
398
|
+
mtimeMs: resultTurn.createdAt,
|
|
399
|
+
elided: true
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function applyEdit(content, oldString, newString, replaceAll) {
|
|
405
|
+
const match = resolveOldString(content, oldString);
|
|
406
|
+
if (!match) return void 0;
|
|
407
|
+
if (match.occurrences > 1 && !replaceAll) return void 0;
|
|
408
|
+
const styledReplacement = styleReplacementForVia(newString, match.via, match.actual);
|
|
409
|
+
return replaceAll ? content.split(match.actual).join(styledReplacement) : content.replace(match.actual, styledReplacement);
|
|
410
|
+
}
|
|
411
|
+
function parseMultiEditAppliedSteps(output, editCount) {
|
|
412
|
+
const annotationMatch = /<edit-outcomes>\n([\s\S]*?)\n<\/edit-outcomes>/.exec(output);
|
|
413
|
+
if (!annotationMatch) return new Set(Array.from({ length: editCount }, (_, i) => i));
|
|
414
|
+
const applied = /* @__PURE__ */ new Set();
|
|
415
|
+
for (const line of annotationMatch[1].split("\n")) {
|
|
416
|
+
const match = /^#(\d+) applied$/.exec(line.trim());
|
|
417
|
+
if (!match) continue;
|
|
418
|
+
const index = Number(match[1]) - 1;
|
|
419
|
+
if (index >= 0 && index < editCount) applied.add(index);
|
|
420
|
+
}
|
|
421
|
+
return applied;
|
|
422
|
+
}
|
|
423
|
+
function isSuccessfulFullReadOutput(output) {
|
|
424
|
+
if (!isSuccessfulReadOutput(output) || READ_FILE_FOOTER_RE.test(output)) return false;
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
function isSuccessfulReadOutput(output) {
|
|
428
|
+
if (output.length === 0) return true;
|
|
429
|
+
if (output.startsWith("File not found:") || output.startsWith("[binary file:") || output.startsWith("Image:") || output.startsWith("Document:") || output.startsWith("[image too large to inline:") || output.startsWith("[document:") || output.startsWith("[document too large to attach:") || output.startsWith("Image read failed:") || output.startsWith("Document read failed:") || output.includes(" unchanged since the previous read in this session ")) return false;
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
function stripReadFileLineNumbers(output) {
|
|
433
|
+
const lines = output.split("\n");
|
|
434
|
+
const stripped = [];
|
|
435
|
+
for (const line of lines) {
|
|
436
|
+
const match = READ_FILE_LINE_RE.exec(line);
|
|
437
|
+
if (!match) return void 0;
|
|
438
|
+
stripped.push(match[1]);
|
|
439
|
+
}
|
|
440
|
+
return stripped.join("\n");
|
|
441
|
+
}
|
|
442
|
+
function normalizeReadInteger(value, fallback) {
|
|
443
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return fallback;
|
|
444
|
+
return Math.floor(value);
|
|
445
|
+
}
|
|
446
|
+
const TOOL_DEDUP_STATE = /* @__PURE__ */ new WeakMap();
|
|
447
|
+
/**
|
|
448
|
+
* Get or lazily create the per-session tool-dedup map. Returns `undefined`
|
|
449
|
+
* when no session is provided — middleware should treat that as "no dedup".
|
|
450
|
+
*/
|
|
451
|
+
function getToolDedupState(session) {
|
|
452
|
+
if (!session) return void 0;
|
|
453
|
+
let map = TOOL_DEDUP_STATE.get(session);
|
|
454
|
+
if (!map) {
|
|
455
|
+
map = /* @__PURE__ */ new Map();
|
|
456
|
+
TOOL_DEDUP_STATE.set(session, map);
|
|
457
|
+
}
|
|
458
|
+
return map;
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
34
461
|
//#region src/behaviors/dedup-tools.ts
|
|
35
462
|
/**
|
|
36
463
|
* Install the per-tool argument-dedup middleware on a hook bus.
|
|
@@ -5028,6 +5455,12 @@ function describeChildRuns(session) {
|
|
|
5028
5455
|
if (childRuns.length === 0) return "No child runs exist in this session.";
|
|
5029
5456
|
return childRuns.map((r, i) => `- ${r.label ?? `child-${i + 1}`} · ${r.status} · ${r.prompt.slice(0, 80)}`).join("\n");
|
|
5030
5457
|
}
|
|
5458
|
+
/**
|
|
5459
|
+
* Reserve the next `child-N` label for this session. Exported so the durable
|
|
5460
|
+
* (child-as-invocation) spawn tool in `src/restate/spawn.ts` allocates labels
|
|
5461
|
+
* from the same session-anchored counter — otherwise the two tools would number
|
|
5462
|
+
* children independently and a session that used both would collide.
|
|
5463
|
+
*/
|
|
5031
5464
|
function reserveChildLabel(session) {
|
|
5032
5465
|
let counter = sessionChildCounters.get(session);
|
|
5033
5466
|
if (counter === void 0) {
|
|
@@ -10109,6 +10542,134 @@ function textForPersistence(output) {
|
|
|
10109
10542
|
return parts.join("\n");
|
|
10110
10543
|
}
|
|
10111
10544
|
//#endregion
|
|
10545
|
+
//#region src/loop-recovery.ts
|
|
10546
|
+
/**
|
|
10547
|
+
* Operation labels used by the provider-stream watchdogs.
|
|
10548
|
+
*
|
|
10549
|
+
* Single source of truth for the strings so `withTimeout` callers and the
|
|
10550
|
+
* failure classifier can never drift: the classifier matches on exactly the
|
|
10551
|
+
* label the watchdog emits.
|
|
10552
|
+
*/
|
|
10553
|
+
const PROVIDER_WATCHDOG_OPERATIONS = {
|
|
10554
|
+
"first response": "provider first response",
|
|
10555
|
+
"total stream": "provider total stream",
|
|
10556
|
+
"stream inactivity": "provider stream inactivity"
|
|
10557
|
+
};
|
|
10558
|
+
/** Watchdog operation names that count as provider-stream failures. */
|
|
10559
|
+
const PROVIDER_WATCHDOG_OPERATION_SET = new Set(Object.values(PROVIDER_WATCHDOG_OPERATIONS));
|
|
10560
|
+
const RETRY_DEFAULTS = {
|
|
10561
|
+
maxAttempts: 3,
|
|
10562
|
+
initialDelayMs: 1e3,
|
|
10563
|
+
maxDelayMs: 3e4
|
|
10564
|
+
};
|
|
10565
|
+
/**
|
|
10566
|
+
* Normalize a user-supplied `RetryConfig` (any field may be missing or invalid)
|
|
10567
|
+
* into a fully-populated config the retry loop can use without per-field guards.
|
|
10568
|
+
*
|
|
10569
|
+
* Invalid values (non-finite, < 1 for `maxAttempts`, < 0 for delays) fall back
|
|
10570
|
+
* to defaults rather than throwing — this is a behavior knob, not a validation
|
|
10571
|
+
* boundary, and a bad value here shouldn't crash a run.
|
|
10572
|
+
*/
|
|
10573
|
+
function resolveRetryConfig(cfg) {
|
|
10574
|
+
return {
|
|
10575
|
+
maxAttempts: Number.isFinite(cfg?.maxAttempts) && cfg.maxAttempts >= 1 ? Math.floor(cfg.maxAttempts) : RETRY_DEFAULTS.maxAttempts,
|
|
10576
|
+
initialDelayMs: Number.isFinite(cfg?.initialDelayMs) && cfg.initialDelayMs >= 0 ? cfg.initialDelayMs : RETRY_DEFAULTS.initialDelayMs,
|
|
10577
|
+
maxDelayMs: Number.isFinite(cfg?.maxDelayMs) && cfg.maxDelayMs >= 0 ? cfg.maxDelayMs : RETRY_DEFAULTS.maxDelayMs
|
|
10578
|
+
};
|
|
10579
|
+
}
|
|
10580
|
+
/**
|
|
10581
|
+
* Classify a failed stream attempt.
|
|
10582
|
+
*
|
|
10583
|
+
* Retry eligibility covers two sources: an error the provider's own classifier
|
|
10584
|
+
* marked retryable, and a provider-stream watchdog timeout. The latter is
|
|
10585
|
+
* retryable BY CONSTRUCTION for the first-response watchdog — zero observable
|
|
10586
|
+
* events arrived, so re-issuing is at least as safe as the mid-stream
|
|
10587
|
+
* `overloaded_error` retries the classifier opts into. The total-stream and
|
|
10588
|
+
* inactivity watchdogs ride the same carve-out but lean on `cleanSlate` for
|
|
10589
|
+
* their safety.
|
|
10590
|
+
*
|
|
10591
|
+
* Fallback excludes two classifications:
|
|
10592
|
+
* - `context_exceeded` — owned by overflow recovery; the transcript is the
|
|
10593
|
+
* problem, not the provider.
|
|
10594
|
+
* - `tool_pairing_corruption` — transcript-level corruption fails identically
|
|
10595
|
+
* on every provider.
|
|
10596
|
+
*
|
|
10597
|
+
* Everything else (rate limit, overload, 5xx, auth/billing, even unclassified
|
|
10598
|
+
* network failures) is provider-side: a sibling target may well succeed, and a
|
|
10599
|
+
* genuinely broken request just exhausts the chain and surfaces the last error.
|
|
10600
|
+
*/
|
|
10601
|
+
function assessStreamFailure(input) {
|
|
10602
|
+
const { err, runAborted, classifyError, progress, attempt, retry, hasNextTarget } = input;
|
|
10603
|
+
const wasAborted = runAborted || err instanceof Error && err.name === "AbortError";
|
|
10604
|
+
const classified = !wasAborted ? classifyError?.(err) ?? null : null;
|
|
10605
|
+
const cleanSlate = progress.text === "" && progress.thinking === "" && !progress.serverToolActivity;
|
|
10606
|
+
const isRetryable = classified?.kind === "provider_error" && classified.retryable === true;
|
|
10607
|
+
const isStreamWatchdogTimeout = !wasAborted && err instanceof OperationTimeoutError && PROVIDER_WATCHDOG_OPERATION_SET.has(err.operation);
|
|
10608
|
+
const canRetry = !wasAborted && (isRetryable || isStreamWatchdogTimeout) && cleanSlate && attempt < retry.maxAttempts;
|
|
10609
|
+
const retryAfterMs = canRetry ? extractRetryAfterMs(err) : void 0;
|
|
10610
|
+
return {
|
|
10611
|
+
wasAborted,
|
|
10612
|
+
classified,
|
|
10613
|
+
cleanSlate,
|
|
10614
|
+
retryAfterMs,
|
|
10615
|
+
canRetry,
|
|
10616
|
+
preferFallbackOverRetry: retryAfterMs !== void 0 && retryAfterMs > 15e3 && hasNextTarget,
|
|
10617
|
+
canRecoverOverflow: !wasAborted && classified?.kind === "context_exceeded" && cleanSlate,
|
|
10618
|
+
canFallback: !wasAborted && cleanSlate && hasNextTarget && classified?.kind !== "context_exceeded" && classified?.kind !== "tool_pairing_corruption"
|
|
10619
|
+
};
|
|
10620
|
+
}
|
|
10621
|
+
/**
|
|
10622
|
+
* Backoff delay for the next attempt.
|
|
10623
|
+
*
|
|
10624
|
+
* A server-declared `retry-after` wins (capped at `maxDelayMs`); otherwise
|
|
10625
|
+
* full-jitter exponential backoff — a uniform draw from `[0, capped)` rather
|
|
10626
|
+
* than a fixed `capped`, so a fleet of clients retrying the same overloaded
|
|
10627
|
+
* endpoint spreads out instead of stampeding in lockstep.
|
|
10628
|
+
*/
|
|
10629
|
+
function computeRetryDelayMs(attempt, cfg, retryAfterMs) {
|
|
10630
|
+
if (retryAfterMs !== void 0 && retryAfterMs >= 0) return Math.min(retryAfterMs, cfg.maxDelayMs);
|
|
10631
|
+
const exponential = cfg.initialDelayMs * 2 ** (attempt - 1);
|
|
10632
|
+
const capped = Math.min(exponential, cfg.maxDelayMs);
|
|
10633
|
+
return Math.floor(Math.random() * capped);
|
|
10634
|
+
}
|
|
10635
|
+
/**
|
|
10636
|
+
* Extract a `retry-after` value (in milliseconds) from an error's `.headers`, if any.
|
|
10637
|
+
*
|
|
10638
|
+
* Supports both `retry-after-ms` (non-standard but Stainless-emitted; already in ms,
|
|
10639
|
+
* always integer) and the standard `retry-after` header (integer seconds, RFC 7231).
|
|
10640
|
+
* Returns `undefined` when neither is present, non-numeric, or negative.
|
|
10641
|
+
*
|
|
10642
|
+
* `parseInt` over `parseFloat` here matches the actual contract — Stainless emits
|
|
10643
|
+
* `retry-after-ms` as an integer and RFC 7231 specifies a non-negative integer for
|
|
10644
|
+
* the seconds form. The HTTP-date form of `retry-after` is deliberately unsupported
|
|
10645
|
+
* (Anthropic + OpenAI both emit numeric seconds; supporting dates invites clock-skew
|
|
10646
|
+
* concerns for negligible benefit).
|
|
10647
|
+
*/
|
|
10648
|
+
function extractRetryAfterMs(err) {
|
|
10649
|
+
if (!err || typeof err !== "object") return void 0;
|
|
10650
|
+
const headers = err.headers;
|
|
10651
|
+
if (!headers || typeof headers !== "object") return void 0;
|
|
10652
|
+
const get = (key) => {
|
|
10653
|
+
const h = headers;
|
|
10654
|
+
if (typeof h.get === "function") {
|
|
10655
|
+
const v = h.get(key);
|
|
10656
|
+
return typeof v === "string" ? v : void 0;
|
|
10657
|
+
}
|
|
10658
|
+
const v = h[key];
|
|
10659
|
+
return typeof v === "string" ? v : void 0;
|
|
10660
|
+
};
|
|
10661
|
+
const ms = get("retry-after-ms");
|
|
10662
|
+
if (ms !== void 0) {
|
|
10663
|
+
const parsed = Number.parseInt(ms, 10);
|
|
10664
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
10665
|
+
}
|
|
10666
|
+
const seconds = get("retry-after");
|
|
10667
|
+
if (seconds !== void 0) {
|
|
10668
|
+
const parsed = Number.parseInt(seconds, 10);
|
|
10669
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1e3;
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10672
|
+
//#endregion
|
|
10112
10673
|
//#region src/loop.ts
|
|
10113
10674
|
const DEFAULT_PROVIDER_STREAM_START_TIMEOUT_MS = 300 * 1e3;
|
|
10114
10675
|
const DEFAULT_PROVIDER_STREAM_TIMEOUT_MS = 1800 * 1e3;
|
|
@@ -10120,7 +10681,7 @@ const DEFAULT_PROVIDER_STREAM_TIMEOUT_MS = 1800 * 1e3;
|
|
|
10120
10681
|
*/
|
|
10121
10682
|
const DEFAULT_HOOK_TIMEOUT_MS = 300 * 1e3;
|
|
10122
10683
|
function hookTimeoutMsFor(ctx) {
|
|
10123
|
-
return resolveTimeoutMs(ctx.hookTimeoutMs, DEFAULT_HOOK_TIMEOUT_MS);
|
|
10684
|
+
return resolveTimeoutMs(ctx.behavior.hookTimeoutMs, DEFAULT_HOOK_TIMEOUT_MS);
|
|
10124
10685
|
}
|
|
10125
10686
|
/**
|
|
10126
10687
|
* Dispatch an already-invoked hook promise under {@link LoopContext.hookTimeoutMs},
|
|
@@ -10916,12 +11477,12 @@ function applyPairingRepair(ctx, messages, turnId) {
|
|
|
10916
11477
|
const repairs = [];
|
|
10917
11478
|
const repaired = ensureToolResultPairing(messages, { onRepair: (repair) => repairs.push(repair) });
|
|
10918
11479
|
if (repairs.length === 0) return repaired;
|
|
10919
|
-
if (ctx.strictToolPairing) throw new AgentToolPairingError({
|
|
11480
|
+
if (ctx.behavior.strictToolPairing) throw new AgentToolPairingError({
|
|
10920
11481
|
message: `Tool pairing corruption detected (${repairs.length} repair${repairs.length === 1 ? "" : "s"}); strict mode is on so the request was not sent.`,
|
|
10921
11482
|
...ctx.providerName ? { provider: ctx.providerName } : {},
|
|
10922
11483
|
repairs
|
|
10923
11484
|
});
|
|
10924
|
-
const rawLimit = ctx.maxPairingRepairsPerTurn;
|
|
11485
|
+
const rawLimit = ctx.behavior.maxPairingRepairsPerTurn;
|
|
10925
11486
|
const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? rawLimit : Number.POSITIVE_INFINITY;
|
|
10926
11487
|
if (repairs.length > limit) throw new AgentToolPairingError({
|
|
10927
11488
|
message: `Pairing-repair storm: ${repairs.length} repairs in a single turn (limit ${limit}). The transcript is systematically corrupt (likely duplicate tool_use ids from the provider); continuing would loop without forward progress.`,
|
|
@@ -10987,19 +11548,19 @@ async function runLoop(ctx) {
|
|
|
10987
11548
|
let totalCacheCreation = 0;
|
|
10988
11549
|
const turnUsages = [];
|
|
10989
11550
|
const startTime = Date.now();
|
|
10990
|
-
const maxTurns = ctx.maxTurns ?? Number.POSITIVE_INFINITY;
|
|
11551
|
+
const maxTurns = ctx.behavior.maxTurns ?? Number.POSITIVE_INFINITY;
|
|
10991
11552
|
let turnsCompleted = 0;
|
|
10992
|
-
const pauseCap = ctx.maxConsecutivePauseTurns ?? 0;
|
|
11553
|
+
const pauseCap = ctx.behavior.maxConsecutivePauseTurns ?? 0;
|
|
10993
11554
|
let consecutiveEmptyPauseTurns = 0;
|
|
10994
|
-
const maxConsecutiveLengthResumes = ctx.maxConsecutiveLengthResumes ?? 8;
|
|
11555
|
+
const maxConsecutiveLengthResumes = ctx.behavior.maxConsecutiveLengthResumes ?? 8;
|
|
10995
11556
|
let consecutiveLengthResumes = 0;
|
|
10996
11557
|
const maxConsecutiveEmptyLengthResumes = 2;
|
|
10997
11558
|
let consecutiveEmptyLengthResumes = 0;
|
|
10998
|
-
const maxConsecutiveRefusalResumes = ctx.maxConsecutiveRefusalResumes ?? 3;
|
|
11559
|
+
const maxConsecutiveRefusalResumes = ctx.behavior.maxConsecutiveRefusalResumes ?? 3;
|
|
10999
11560
|
let consecutiveRefusalResumes = 0;
|
|
11000
|
-
const wallDeadlineActive = typeof ctx.maxWallMs === "number" && Number.isFinite(ctx.maxWallMs) && ctx.maxWallMs > 0;
|
|
11561
|
+
const wallDeadlineActive = typeof ctx.behavior.maxWallMs === "number" && Number.isFinite(ctx.behavior.maxWallMs) && ctx.behavior.maxWallMs > 0;
|
|
11001
11562
|
const wallStartMs = wallDeadlineActive ? await ctx.clock.now() : 0;
|
|
11002
|
-
const maxTurnsWarnAt = Number.isFinite(maxTurns) && typeof ctx.maxTurnsWarning === "number" && Number.isFinite(ctx.maxTurnsWarning) && ctx.maxTurnsWarning > 0 && ctx.maxTurnsWarning < maxTurns ? Math.floor(ctx.maxTurnsWarning) : void 0;
|
|
11563
|
+
const maxTurnsWarnAt = Number.isFinite(maxTurns) && typeof ctx.behavior.maxTurnsWarning === "number" && Number.isFinite(ctx.behavior.maxTurnsWarning) && ctx.behavior.maxTurnsWarning > 0 && ctx.behavior.maxTurnsWarning < maxTurns ? Math.floor(ctx.behavior.maxTurnsWarning) : void 0;
|
|
11003
11564
|
let maxTurnsWarned = false;
|
|
11004
11565
|
const ttft = { mark: void 0 };
|
|
11005
11566
|
const markTtft = () => {
|
|
@@ -11071,9 +11632,9 @@ async function runLoop(ctx) {
|
|
|
11071
11632
|
break;
|
|
11072
11633
|
}
|
|
11073
11634
|
const breach = checkRunBudget({
|
|
11074
|
-
maxCostUsd: ctx.maxCostUsd,
|
|
11075
|
-
maxTotalTokens: ctx.maxTotalTokens,
|
|
11076
|
-
maxWallMs: ctx.maxWallMs,
|
|
11635
|
+
maxCostUsd: ctx.behavior.maxCostUsd,
|
|
11636
|
+
maxTotalTokens: ctx.behavior.maxTotalTokens,
|
|
11637
|
+
maxWallMs: ctx.behavior.maxWallMs,
|
|
11077
11638
|
cost: turnUsages.reduce((s, t) => s + (t.cost ?? 0), 0),
|
|
11078
11639
|
tokens: totalIn + totalOut,
|
|
11079
11640
|
elapsedMs: wallDeadlineActive ? await ctx.clock.now() - wallStartMs : 0
|
|
@@ -11109,7 +11670,7 @@ async function runLoop(ctx) {
|
|
|
11109
11670
|
if (!result.ended && !maxTurnsWarned && maxTurnsWarnAt !== void 0 && maxTurns - (turn + 1) <= maxTurnsWarnAt) {
|
|
11110
11671
|
maxTurnsWarned = true;
|
|
11111
11672
|
const remaining = maxTurns - (turn + 1);
|
|
11112
|
-
const warning = formatMaxTurnsWarning(ctx.maxTurnsWarningMessage, {
|
|
11673
|
+
const warning = formatMaxTurnsWarning(ctx.behavior.maxTurnsWarningMessage, {
|
|
11113
11674
|
remaining,
|
|
11114
11675
|
maxTurns
|
|
11115
11676
|
});
|
|
@@ -11151,7 +11712,7 @@ async function runLoop(ctx) {
|
|
|
11151
11712
|
}
|
|
11152
11713
|
}
|
|
11153
11714
|
let exhaustedOutput;
|
|
11154
|
-
if (ctx.schema && !ctx.signal.aborted && turnsCompleted > 0) {
|
|
11715
|
+
if (ctx.behavior.schema && !ctx.signal.aborted && turnsCompleted > 0) {
|
|
11155
11716
|
const enforcement = await enforceSchemaOutput(ctx, ctx.clock.randomUUID());
|
|
11156
11717
|
exhaustedOutput = enforcement.output;
|
|
11157
11718
|
totalIn += enforcement.usage.input;
|
|
@@ -11282,7 +11843,7 @@ async function enforceSchemaOutput(ctx, turnId) {
|
|
|
11282
11843
|
const outputSpec = {
|
|
11283
11844
|
name: "__output__",
|
|
11284
11845
|
description: "Return the final structured output matching the required schema.",
|
|
11285
|
-
inputSchema: ctx.schema
|
|
11846
|
+
inputSchema: ctx.behavior.schema
|
|
11286
11847
|
};
|
|
11287
11848
|
const schemaMessages = ensureEndsWithUserMessage(applyPairingRepair(ctx, rewriteMessagesToWire(await resolveContentRefsForWire(ctx, turnsToMessages(applyCompactSummaryCutoff(ctx.turns))), ctx.aliasMaps), turnId), ctx.provider);
|
|
11288
11849
|
let schemaResult;
|
|
@@ -11292,8 +11853,8 @@ async function enforceSchemaOutput(ctx, turnId) {
|
|
|
11292
11853
|
system: ctx.system,
|
|
11293
11854
|
tools: ctx.provider.formatTools([outputSpec]),
|
|
11294
11855
|
messages: schemaMessages,
|
|
11295
|
-
maxTokens: ctx.maxTokens,
|
|
11296
|
-
...ctx.modelCatalog ? { modelCatalog: ctx.modelCatalog } : {},
|
|
11856
|
+
maxTokens: ctx.behavior.maxTokens,
|
|
11857
|
+
...ctx.behavior.modelCatalog ? { modelCatalog: ctx.behavior.modelCatalog } : {},
|
|
11297
11858
|
toolChoice: {
|
|
11298
11859
|
type: "tool",
|
|
11299
11860
|
name: "__output__"
|
|
@@ -11310,7 +11871,7 @@ async function enforceSchemaOutput(ctx, turnId) {
|
|
|
11310
11871
|
const output = schemaResult.toolCalls.find((tc) => tc.name === "__output__")?.input;
|
|
11311
11872
|
if (output) await ctx.hooks.callHook("output", {
|
|
11312
11873
|
output,
|
|
11313
|
-
schema: ctx.schema
|
|
11874
|
+
schema: ctx.behavior.schema
|
|
11314
11875
|
});
|
|
11315
11876
|
const schemaTurn = {
|
|
11316
11877
|
id: ctx.clock.randomUUID(),
|
|
@@ -11387,17 +11948,11 @@ function wrapProviderError(err, ctx) {
|
|
|
11387
11948
|
* classifier so the retry carve-out can't silently detach from the errors
|
|
11388
11949
|
* it targets when a label is renamed.
|
|
11389
11950
|
*/
|
|
11390
|
-
const PROVIDER_WATCHDOG_OPERATIONS = {
|
|
11391
|
-
"first response": "provider first response",
|
|
11392
|
-
"total stream": "provider total stream",
|
|
11393
|
-
"stream inactivity": "provider stream inactivity"
|
|
11394
|
-
};
|
|
11395
|
-
const PROVIDER_WATCHDOG_OPERATION_SET = new Set(Object.values(PROVIDER_WATCHDOG_OPERATIONS));
|
|
11396
11951
|
function providerWatchdogTimeouts(ctx) {
|
|
11397
11952
|
return {
|
|
11398
|
-
startTimeoutMs: resolveTimeoutMs(ctx.providerStreamStartTimeoutMs, DEFAULT_PROVIDER_STREAM_START_TIMEOUT_MS, ctx.maxWallMs),
|
|
11399
|
-
totalTimeoutMs: resolveTimeoutMs(ctx.providerStreamTimeoutMs, DEFAULT_PROVIDER_STREAM_TIMEOUT_MS, ctx.maxWallMs),
|
|
11400
|
-
idleTimeoutMs: resolveTimeoutMs(ctx.providerStreamIdleTimeoutMs, 0, ctx.maxWallMs)
|
|
11953
|
+
startTimeoutMs: resolveTimeoutMs(ctx.behavior.providerStreamStartTimeoutMs, DEFAULT_PROVIDER_STREAM_START_TIMEOUT_MS, ctx.behavior.maxWallMs),
|
|
11954
|
+
totalTimeoutMs: resolveTimeoutMs(ctx.behavior.providerStreamTimeoutMs, DEFAULT_PROVIDER_STREAM_TIMEOUT_MS, ctx.behavior.maxWallMs),
|
|
11955
|
+
idleTimeoutMs: resolveTimeoutMs(ctx.behavior.providerStreamIdleTimeoutMs, 0, ctx.behavior.maxWallMs)
|
|
11401
11956
|
};
|
|
11402
11957
|
}
|
|
11403
11958
|
function stripInvalidAnthropicThinking(content) {
|
|
@@ -11707,11 +12262,6 @@ function extractStreamErrorMeta(err) {
|
|
|
11707
12262
|
return out;
|
|
11708
12263
|
}
|
|
11709
12264
|
/** Defaults applied when `behavior.retry` is absent or has missing fields. */
|
|
11710
|
-
const RETRY_DEFAULTS = {
|
|
11711
|
-
maxAttempts: 3,
|
|
11712
|
-
initialDelayMs: 1e3,
|
|
11713
|
-
maxDelayMs: 3e4
|
|
11714
|
-
};
|
|
11715
12265
|
/**
|
|
11716
12266
|
* First-response watchdog cap for RETRY attempts (attempt ≥ 2). The first
|
|
11717
12267
|
* attempt gets the full `providerStreamStartTimeoutMs` window (large-prompt
|
|
@@ -11844,7 +12394,7 @@ async function maybeAutoCompact(ctx, turn, lastTurnUsage, state) {
|
|
|
11844
12394
|
keepTurns: cfg.keepTurns,
|
|
11845
12395
|
signal: ctx.signal,
|
|
11846
12396
|
onProgress,
|
|
11847
|
-
...ctx.providerStreamTimeoutMs !== void 0 ? { providerStreamTimeoutMs: ctx.providerStreamTimeoutMs } : {}
|
|
12397
|
+
...ctx.behavior.providerStreamTimeoutMs !== void 0 ? { providerStreamTimeoutMs: ctx.behavior.providerStreamTimeoutMs } : {}
|
|
11848
12398
|
});
|
|
11849
12399
|
const summaryTurn = summaryToTurn({
|
|
11850
12400
|
summary: result.summary,
|
|
@@ -11877,8 +12427,8 @@ async function maybeAutoCompact(ctx, turn, lastTurnUsage, state) {
|
|
|
11877
12427
|
}
|
|
11878
12428
|
ctx.turns.push(summaryTurn, ...restorationTurns);
|
|
11879
12429
|
await ctx.persistTurns?.();
|
|
11880
|
-
if (cfg.releaseReplacedTurns && ctx.persistDir && result.summarizedTurnIds.length > 0) try {
|
|
11881
|
-
await releaseReplacedTurnContent(ctx.turns, new Set(result.summarizedTurnIds), { persistDir: ctx.persistDir });
|
|
12430
|
+
if (cfg.releaseReplacedTurns && ctx.behavior.persistDir && result.summarizedTurnIds.length > 0) try {
|
|
12431
|
+
await releaseReplacedTurnContent(ctx.turns, new Set(result.summarizedTurnIds), { persistDir: ctx.behavior.persistDir });
|
|
11882
12432
|
} catch (err) {
|
|
11883
12433
|
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/loop] post-compact turn release failed: ${errorMessage(err)}\n`);
|
|
11884
12434
|
}
|
|
@@ -11915,114 +12465,82 @@ async function maybeAutoCompact(ctx, turn, lastTurnUsage, state) {
|
|
|
11915
12465
|
}
|
|
11916
12466
|
}
|
|
11917
12467
|
/**
|
|
11918
|
-
*
|
|
11919
|
-
* into a fully-populated config the retry loop can use without per-field guards.
|
|
12468
|
+
* `setTimeout` that rejects if the given signal aborts before the timer fires.
|
|
11920
12469
|
*
|
|
11921
|
-
*
|
|
11922
|
-
*
|
|
11923
|
-
*
|
|
12470
|
+
* Resolves with `undefined` on normal completion. Rejects with the signal's
|
|
12471
|
+
* `reason` (or a generic AbortError when no reason is set) on abort. Cleans
|
|
12472
|
+
* up its `abort` listener in both paths so callers don't leak handlers.
|
|
11924
12473
|
*/
|
|
11925
|
-
function
|
|
11926
|
-
return
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
12474
|
+
function abortableSleep(ms, signal) {
|
|
12475
|
+
if (signal.aborted) return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
12476
|
+
return new Promise((resolve, reject) => {
|
|
12477
|
+
let timer;
|
|
12478
|
+
const onAbort = () => {
|
|
12479
|
+
clearTimeout(timer);
|
|
12480
|
+
signal.removeEventListener("abort", onAbort);
|
|
12481
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
12482
|
+
};
|
|
12483
|
+
timer = setTimeout(() => {
|
|
12484
|
+
signal.removeEventListener("abort", onAbort);
|
|
12485
|
+
resolve();
|
|
12486
|
+
}, ms);
|
|
12487
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
12488
|
+
});
|
|
11931
12489
|
}
|
|
11932
12490
|
/**
|
|
11933
|
-
*
|
|
11934
|
-
*
|
|
11935
|
-
*
|
|
11936
|
-
*
|
|
11937
|
-
*
|
|
11938
|
-
*
|
|
11939
|
-
*
|
|
11940
|
-
*
|
|
11941
|
-
*
|
|
11942
|
-
*
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
const
|
|
11948
|
-
const
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
if (typeof h.get === "function") {
|
|
11981
|
-
const v = h.get(key);
|
|
11982
|
-
return typeof v === "string" ? v : void 0;
|
|
11983
|
-
}
|
|
11984
|
-
const v = h[key];
|
|
11985
|
-
return typeof v === "string" ? v : void 0;
|
|
11986
|
-
};
|
|
11987
|
-
const ms = get("retry-after-ms");
|
|
11988
|
-
if (ms !== void 0) {
|
|
11989
|
-
const parsed = Number.parseInt(ms, 10);
|
|
11990
|
-
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
11991
|
-
}
|
|
11992
|
-
const seconds = get("retry-after");
|
|
11993
|
-
if (seconds !== void 0) {
|
|
11994
|
-
const parsed = Number.parseInt(seconds, 10);
|
|
11995
|
-
if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1e3;
|
|
11996
|
-
}
|
|
11997
|
-
}
|
|
11998
|
-
/**
|
|
11999
|
-
* `setTimeout` that rejects if the given signal aborts before the timer fires.
|
|
12000
|
-
*
|
|
12001
|
-
* Resolves with `undefined` on normal completion. Rejects with the signal's
|
|
12002
|
-
* `reason` (or a generic AbortError when no reason is set) on abort. Cleans
|
|
12003
|
-
* up its `abort` listener in both paths so callers don't leak handlers.
|
|
12004
|
-
*/
|
|
12005
|
-
function abortableSleep(ms, signal) {
|
|
12006
|
-
if (signal.aborted) return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
12007
|
-
return new Promise((resolve, reject) => {
|
|
12008
|
-
let timer;
|
|
12009
|
-
const onAbort = () => {
|
|
12010
|
-
clearTimeout(timer);
|
|
12011
|
-
signal.removeEventListener("abort", onAbort);
|
|
12012
|
-
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
12013
|
-
};
|
|
12014
|
-
timer = setTimeout(() => {
|
|
12015
|
-
signal.removeEventListener("abort", onAbort);
|
|
12016
|
-
resolve();
|
|
12017
|
-
}, ms);
|
|
12018
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
12019
|
-
});
|
|
12491
|
+
* Advance the run to the next provider+model target and rebind the prepared
|
|
12492
|
+
* request to it.
|
|
12493
|
+
*
|
|
12494
|
+
* Caller contract: only invoke when `assessStreamFailure` returned
|
|
12495
|
+
* `canFallback` — that verdict guarantees an untried target exists, so the
|
|
12496
|
+
* index arithmetic here needs no bounds check of its own.
|
|
12497
|
+
*
|
|
12498
|
+
* Run-sticky: `activeTargetIndex` only advances, so later turns of this run
|
|
12499
|
+
* stay on the fallback target. The next `run()` resolves a fresh target list
|
|
12500
|
+
* starting from the primary.
|
|
12501
|
+
*/
|
|
12502
|
+
async function advanceToFallbackTarget(ctx, streamOptions, turnId, err) {
|
|
12503
|
+
const targets = ctx.targets;
|
|
12504
|
+
const fromIndex = ctx.activeTargetIndex ?? 0;
|
|
12505
|
+
const nextIndex = fromIndex + 1;
|
|
12506
|
+
const from = targets[fromIndex];
|
|
12507
|
+
const to = targets[nextIndex];
|
|
12508
|
+
ctx.activeTargetIndex = nextIndex;
|
|
12509
|
+
ctx.provider = to.provider;
|
|
12510
|
+
ctx.model = to.model;
|
|
12511
|
+
ctx.providerName = to.provider.name;
|
|
12512
|
+
if (ctx.rebuildFormattedTools) ctx.formattedTools = ctx.rebuildFormattedTools(to.provider);
|
|
12513
|
+
(ctx.targetFailures ??= []).push({
|
|
12514
|
+
provider: from.provider.name,
|
|
12515
|
+
model: from.model,
|
|
12516
|
+
err
|
|
12517
|
+
});
|
|
12518
|
+
if (ctx.autoCompact) {
|
|
12519
|
+
const nextWindow = to.contextWindow ?? (ctx.autoCompact.contextWindowFromProviderMeta ? to.provider.meta.contextWindow : void 0);
|
|
12520
|
+
if (nextWindow !== void 0 && Number.isFinite(nextWindow) && nextWindow > 0) ctx.autoCompact.contextWindow = nextWindow;
|
|
12521
|
+
}
|
|
12522
|
+
streamOptions.model = to.model;
|
|
12523
|
+
streamOptions.tools = ctx.formattedTools;
|
|
12524
|
+
streamOptions.messages = sanitizeStoredToolResults(to.provider, streamOptions.messages);
|
|
12525
|
+
await settledHook(ctx, "hook model:fallback", ctx.hooks.callHook("model:fallback", {
|
|
12526
|
+
turnId,
|
|
12527
|
+
from: {
|
|
12528
|
+
provider: from.provider.name,
|
|
12529
|
+
model: from.model
|
|
12530
|
+
},
|
|
12531
|
+
to: {
|
|
12532
|
+
provider: to.provider.name,
|
|
12533
|
+
model: to.model
|
|
12534
|
+
},
|
|
12535
|
+
targetIndex: nextIndex,
|
|
12536
|
+
err
|
|
12537
|
+
}));
|
|
12020
12538
|
}
|
|
12021
12539
|
async function executeTurn(ctx, turn, priorUsage) {
|
|
12022
12540
|
const turnId = ctx.clock.randomUUID();
|
|
12023
12541
|
let canonicalMessages = turnsToMessages(applyCompactSummaryCutoff(ctx.turns));
|
|
12024
12542
|
canonicalMessages = await resolveContentRefsForWire(ctx, canonicalMessages);
|
|
12025
|
-
if (ctx.elideStaleReads === true || ctx.elideStaleReads === "stub") {
|
|
12543
|
+
if (ctx.behavior.elideStaleReads === true || ctx.behavior.elideStaleReads === "stub") {
|
|
12026
12544
|
/**
|
|
12027
12545
|
* Deliberately UNQUANTIZED, unlike tail compaction and media age-out below.
|
|
12028
12546
|
*
|
|
@@ -12050,30 +12568,30 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12050
12568
|
let sanitizedMessages = sanitizeStoredToolResults(ctx.provider, wireMessages);
|
|
12051
12569
|
let callMapsMemo;
|
|
12052
12570
|
const callMaps = () => callMapsMemo ??= collectCallMaps(canonicalMessages);
|
|
12053
|
-
if (ctx.compactStrategy === "tail") {
|
|
12054
|
-
const threshold = typeof ctx.compactThreshold === "number" && ctx.compactThreshold > 0 ? ctx.compactThreshold : 131072;
|
|
12055
|
-
const keep = typeof ctx.compactKeepTurns === "number" && ctx.compactKeepTurns >= 0 ? ctx.compactKeepTurns : 4;
|
|
12571
|
+
if (ctx.behavior.compactStrategy === "tail") {
|
|
12572
|
+
const threshold = typeof ctx.behavior.compactThreshold === "number" && ctx.behavior.compactThreshold > 0 ? ctx.behavior.compactThreshold : 131072;
|
|
12573
|
+
const keep = typeof ctx.behavior.compactKeepTurns === "number" && ctx.behavior.compactKeepTurns >= 0 ? ctx.behavior.compactKeepTurns : 4;
|
|
12056
12574
|
const { readPathByCallId, toolNameByCallId } = callMaps();
|
|
12057
12575
|
const compacted = applyTailCompaction(sanitizedMessages, threshold, keep, {
|
|
12058
12576
|
readPathByCallId,
|
|
12059
12577
|
toolNameByCallId,
|
|
12060
12578
|
chunkTurns: 8,
|
|
12061
|
-
persistElideMinBytes: ctx.persistDir ? PERSISTENCE_PREVIEW_BYTES : 0
|
|
12579
|
+
persistElideMinBytes: ctx.behavior.persistDir ? PERSISTENCE_PREVIEW_BYTES : 0
|
|
12062
12580
|
});
|
|
12063
12581
|
sanitizedMessages = compacted.messages;
|
|
12064
12582
|
markReadStateForElidedPaths(ctx, ctx.handle.cwd, compacted.elidedReadPaths);
|
|
12065
|
-
if (ctx.persistDir && compacted.persistableElided.length > 0) sanitizedMessages = await persistElidedToolResults(sanitizedMessages, compacted.persistableElided, {
|
|
12066
|
-
persistDir: ctx.persistDir,
|
|
12067
|
-
maxBytes: ctx.persistMaxBytes,
|
|
12583
|
+
if (ctx.behavior.persistDir && compacted.persistableElided.length > 0) sanitizedMessages = await persistElidedToolResults(sanitizedMessages, compacted.persistableElided, {
|
|
12584
|
+
persistDir: ctx.behavior.persistDir,
|
|
12585
|
+
maxBytes: ctx.behavior.persistMaxBytes,
|
|
12068
12586
|
writeBlob: executionBlobWriter(ctx)
|
|
12069
12587
|
});
|
|
12070
|
-
} else if (typeof ctx.compactStrategy === "function") {
|
|
12071
|
-
const threshold = typeof ctx.compactThreshold === "number" && ctx.compactThreshold > 0 ? ctx.compactThreshold : 131072;
|
|
12072
|
-
const keep = typeof ctx.compactKeepTurns === "number" && ctx.compactKeepTurns >= 0 ? ctx.compactKeepTurns : 4;
|
|
12588
|
+
} else if (typeof ctx.behavior.compactStrategy === "function") {
|
|
12589
|
+
const threshold = typeof ctx.behavior.compactThreshold === "number" && ctx.behavior.compactThreshold > 0 ? ctx.behavior.compactThreshold : 131072;
|
|
12590
|
+
const keep = typeof ctx.behavior.compactKeepTurns === "number" && ctx.behavior.compactKeepTurns >= 0 ? ctx.behavior.compactKeepTurns : 4;
|
|
12073
12591
|
let totalBytes = 0;
|
|
12074
12592
|
for (const msg of sanitizedMessages) for (const block of msg.content) if (block.type === "tool_result") totalBytes += memoizedToolResultBudgetBytes(block);
|
|
12075
12593
|
try {
|
|
12076
|
-
const compacted = await ctx.compactStrategy(sanitizedMessages, {
|
|
12594
|
+
const compacted = await ctx.behavior.compactStrategy(sanitizedMessages, {
|
|
12077
12595
|
threshold,
|
|
12078
12596
|
keepTurns: keep,
|
|
12079
12597
|
totalBytes
|
|
@@ -12083,9 +12601,9 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12083
12601
|
console.error("[zidane] compactStrategy function threw:", err);
|
|
12084
12602
|
}
|
|
12085
12603
|
}
|
|
12086
|
-
if (typeof ctx.mediaKeepTurns === "number" && ctx.mediaKeepTurns >= 0 && ctx.tools.read_file) {
|
|
12604
|
+
if (typeof ctx.behavior.mediaKeepTurns === "number" && ctx.behavior.mediaKeepTurns >= 0 && ctx.tools.read_file) {
|
|
12087
12605
|
const { readPathByCallId } = callMaps();
|
|
12088
|
-
const aged = applyMediaAgeout(sanitizedMessages, ctx.mediaKeepTurns, {
|
|
12606
|
+
const aged = applyMediaAgeout(sanitizedMessages, ctx.behavior.mediaKeepTurns, {
|
|
12089
12607
|
chunkTurns: 8,
|
|
12090
12608
|
readPathByCallId,
|
|
12091
12609
|
readFileToolName: toWireName("read_file", ctx.aliasMaps),
|
|
@@ -12094,22 +12612,22 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12094
12612
|
sanitizedMessages = aged.messages;
|
|
12095
12613
|
markReadStateForElidedPaths(ctx, ctx.handle.cwd, aged.elidedReadPaths);
|
|
12096
12614
|
}
|
|
12097
|
-
const effectiveThinkingBudget = ctx.thinking === "adaptive" && ctx.provider.meta.capabilities?.adaptiveBudgetIsEnvelope === true ? ctx.thinkingBudget : applyThinkingDecay(ctx.thinkingBudget, ctx.thinkingDecay, turn + 1);
|
|
12615
|
+
const effectiveThinkingBudget = ctx.thinking === "adaptive" && ctx.provider.meta.capabilities?.adaptiveBudgetIsEnvelope === true ? ctx.behavior.thinkingBudget : applyThinkingDecay(ctx.behavior.thinkingBudget, ctx.behavior.thinkingDecay, turn + 1);
|
|
12098
12616
|
const formattedTools = ctx.rebuildFormattedTools ? ctx.rebuildFormattedTools(ctx.provider) : ctx.formattedTools;
|
|
12099
12617
|
const streamOptions = {
|
|
12100
12618
|
model: ctx.model,
|
|
12101
12619
|
system: ctx.system,
|
|
12102
12620
|
tools: formattedTools,
|
|
12103
12621
|
messages: sanitizedMessages,
|
|
12104
|
-
maxTokens: ctx.maxTokens,
|
|
12105
|
-
...ctx.modelCatalog ? { modelCatalog: ctx.modelCatalog } : {},
|
|
12622
|
+
maxTokens: ctx.behavior.maxTokens,
|
|
12623
|
+
...ctx.behavior.modelCatalog ? { modelCatalog: ctx.behavior.modelCatalog } : {},
|
|
12106
12624
|
thinking: ctx.thinking,
|
|
12107
12625
|
thinkingBudget: effectiveThinkingBudget,
|
|
12108
|
-
...ctx.taskBudget !== void 0 ? { taskBudget: ctx.taskBudget } : {},
|
|
12109
|
-
...ctx.effortHint !== void 0 ? { effortHint: ctx.effortHint } : {},
|
|
12626
|
+
...ctx.behavior.taskBudget !== void 0 ? { taskBudget: ctx.behavior.taskBudget } : {},
|
|
12627
|
+
...ctx.behavior.effortHint !== void 0 ? { effortHint: ctx.behavior.effortHint } : {},
|
|
12110
12628
|
...ctx.modelOptions ? { modelOptions: ctx.modelOptions } : {},
|
|
12111
|
-
cache: ctx.cache ?? true,
|
|
12112
|
-
...ctx.cacheTtl !== void 0 ? { cacheTtl: ctx.cacheTtl } : {},
|
|
12629
|
+
cache: ctx.behavior.cache ?? true,
|
|
12630
|
+
...ctx.behavior.cacheTtl !== void 0 ? { cacheTtl: ctx.behavior.cacheTtl } : {},
|
|
12113
12631
|
signal: ctx.signal
|
|
12114
12632
|
};
|
|
12115
12633
|
const transformCtx = { messages: streamOptions.messages };
|
|
@@ -12152,7 +12670,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12152
12670
|
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/loop] stream hook handler threw: ${errorMessage(err)}\n`);
|
|
12153
12671
|
});
|
|
12154
12672
|
};
|
|
12155
|
-
const retryCfg = resolveRetryConfig(ctx.retry);
|
|
12673
|
+
const retryCfg = resolveRetryConfig(ctx.behavior.retry);
|
|
12156
12674
|
let result;
|
|
12157
12675
|
let attempt = 0;
|
|
12158
12676
|
let overflowRecoveries = 0;
|
|
@@ -12208,17 +12726,23 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12208
12726
|
break;
|
|
12209
12727
|
} catch (caught) {
|
|
12210
12728
|
let terminalErr = caught;
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12729
|
+
const failure = assessStreamFailure({
|
|
12730
|
+
err: caught,
|
|
12731
|
+
runAborted: ctx.signal.aborted,
|
|
12732
|
+
classifyError: ctx.provider.classifyError?.bind(ctx.provider),
|
|
12733
|
+
progress: {
|
|
12734
|
+
text: currentText,
|
|
12735
|
+
thinking: currentThinking,
|
|
12736
|
+
serverToolActivity
|
|
12737
|
+
},
|
|
12738
|
+
attempt,
|
|
12739
|
+
retry: retryCfg,
|
|
12740
|
+
hasNextTarget: ctx.targets !== void 0 && (ctx.activeTargetIndex ?? 0) + 1 < ctx.targets.length
|
|
12741
|
+
});
|
|
12742
|
+
let wasAborted = failure.wasAborted;
|
|
12743
|
+
if (failure.canRetry && !failure.preferFallbackOverRetry) {
|
|
12220
12744
|
const meta = extractStreamErrorMeta(caught);
|
|
12221
|
-
const delayMs = computeRetryDelayMs(attempt, retryCfg, retryAfterMs);
|
|
12745
|
+
const delayMs = computeRetryDelayMs(attempt, retryCfg, failure.retryAfterMs);
|
|
12222
12746
|
await settledHook(ctx, "hook stream:retry", ctx.hooks.callHook("stream:retry", {
|
|
12223
12747
|
turnId,
|
|
12224
12748
|
attempt,
|
|
@@ -12238,7 +12762,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12238
12762
|
wasAborted = true;
|
|
12239
12763
|
}
|
|
12240
12764
|
}
|
|
12241
|
-
if (!wasAborted &&
|
|
12765
|
+
if (!wasAborted && failure.canRecoverOverflow && overflowRecoveries < CONTEXT_OVERFLOW_KEEP_TURNS.length) {
|
|
12242
12766
|
const { readPathByCallId, toolNameByCallId } = callMaps();
|
|
12243
12767
|
let recoveredMessages;
|
|
12244
12768
|
const elidedReadPaths = [];
|
|
@@ -12275,45 +12799,10 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12275
12799
|
continue;
|
|
12276
12800
|
}
|
|
12277
12801
|
}
|
|
12278
|
-
if (!wasAborted &&
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
const from = ctx.targets[fromIndex];
|
|
12283
|
-
const to = ctx.targets[nextIndex];
|
|
12284
|
-
ctx.activeTargetIndex = nextIndex;
|
|
12285
|
-
ctx.provider = to.provider;
|
|
12286
|
-
ctx.model = to.model;
|
|
12287
|
-
ctx.providerName = to.provider.name;
|
|
12288
|
-
if (ctx.rebuildFormattedTools) ctx.formattedTools = ctx.rebuildFormattedTools(to.provider);
|
|
12289
|
-
(ctx.targetFailures ??= []).push({
|
|
12290
|
-
provider: from.provider.name,
|
|
12291
|
-
model: from.model,
|
|
12292
|
-
err: caught
|
|
12293
|
-
});
|
|
12294
|
-
if (ctx.autoCompact) {
|
|
12295
|
-
const nextWindow = to.contextWindow ?? (ctx.autoCompact.contextWindowFromProviderMeta ? to.provider.meta.contextWindow : void 0);
|
|
12296
|
-
if (typeof nextWindow === "number" && Number.isFinite(nextWindow) && nextWindow > 0) ctx.autoCompact.contextWindow = nextWindow;
|
|
12297
|
-
}
|
|
12298
|
-
streamOptions.model = to.model;
|
|
12299
|
-
streamOptions.tools = ctx.formattedTools;
|
|
12300
|
-
streamOptions.messages = sanitizeStoredToolResults(to.provider, streamOptions.messages);
|
|
12301
|
-
attempt = 0;
|
|
12302
|
-
await settledHook(ctx, "hook model:fallback", ctx.hooks.callHook("model:fallback", {
|
|
12303
|
-
turnId,
|
|
12304
|
-
from: {
|
|
12305
|
-
provider: from.provider.name,
|
|
12306
|
-
model: from.model
|
|
12307
|
-
},
|
|
12308
|
-
to: {
|
|
12309
|
-
provider: to.provider.name,
|
|
12310
|
-
model: to.model
|
|
12311
|
-
},
|
|
12312
|
-
targetIndex: nextIndex,
|
|
12313
|
-
err: caught
|
|
12314
|
-
}));
|
|
12315
|
-
continue;
|
|
12316
|
-
}
|
|
12802
|
+
if (!wasAborted && failure.canFallback) {
|
|
12803
|
+
await advanceToFallbackTarget(ctx, streamOptions, turnId, caught);
|
|
12804
|
+
attempt = 0;
|
|
12805
|
+
continue;
|
|
12317
12806
|
}
|
|
12318
12807
|
await streamHookChain;
|
|
12319
12808
|
const errorUsage = {
|
|
@@ -12368,7 +12857,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12368
12857
|
name: toCanonicalName(tc.name, ctx.aliasMaps)
|
|
12369
12858
|
}));
|
|
12370
12859
|
let canonicalContent = rewriteContentToCanonical(result.assistantMessage?.content ?? [], ctx.aliasMaps);
|
|
12371
|
-
if (canonicalToolCalls.length > 0 && !ctx.strictToolPairing) {
|
|
12860
|
+
if (canonicalToolCalls.length > 0 && !ctx.behavior.strictToolPairing) {
|
|
12372
12861
|
const priorIds = /* @__PURE__ */ new Set();
|
|
12373
12862
|
for (const t of ctx.turns) for (const block of t.content) if (block.type === "tool_call") priorIds.add(block.id);
|
|
12374
12863
|
const reminted = remintDuplicateToolCallIds(canonicalToolCalls, canonicalContent, priorIds);
|
|
@@ -12410,7 +12899,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12410
12899
|
},
|
|
12411
12900
|
cumulativeUsage: buildCumulativeUsage(priorUsage, result.usage)
|
|
12412
12901
|
}));
|
|
12413
|
-
if (ctx.resumeOnMaxTokens && (ctx.maxConsecutiveLengthResumes ?? 8) > 0 && result.usage.finishReason === "length" && canonicalToolCalls.length === 0 && !ctx.signal.aborted) {
|
|
12902
|
+
if (ctx.behavior.resumeOnMaxTokens && (ctx.behavior.maxConsecutiveLengthResumes ?? 8) > 0 && result.usage.finishReason === "length" && canonicalToolCalls.length === 0 && !ctx.signal.aborted) {
|
|
12414
12903
|
const continueMsg = ctx.provider.userMessage("Please continue.");
|
|
12415
12904
|
ctx.turns.push({
|
|
12416
12905
|
id: await ctx.generateTurnId(),
|
|
@@ -12427,7 +12916,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12427
12916
|
...currentText.length === 0 ? { lengthEmpty: true } : {}
|
|
12428
12917
|
};
|
|
12429
12918
|
}
|
|
12430
|
-
if (result.usage.finishReason === "content-filter" && canonicalToolCalls.length === 0 && !ctx.signal.aborted && (ctx.maxConsecutiveRefusalResumes ?? 3) > 0) {
|
|
12919
|
+
if (result.usage.finishReason === "content-filter" && canonicalToolCalls.length === 0 && !ctx.signal.aborted && (ctx.behavior.maxConsecutiveRefusalResumes ?? 3) > 0) {
|
|
12431
12920
|
const continueMsg = ctx.provider.userMessage("The previous response was interrupted by the provider (refusal stop). Please continue with the task.");
|
|
12432
12921
|
ctx.turns.push({
|
|
12433
12922
|
id: await ctx.generateTurnId(),
|
|
@@ -12445,7 +12934,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12445
12934
|
};
|
|
12446
12935
|
}
|
|
12447
12936
|
if (result.done) {
|
|
12448
|
-
if (ctx.schema && !ctx.signal.aborted) {
|
|
12937
|
+
if (ctx.behavior.schema && !ctx.signal.aborted) {
|
|
12449
12938
|
const enforcement = await enforceSchemaOutput(ctx, turnId);
|
|
12450
12939
|
const sumOptional = (a, b) => ({
|
|
12451
12940
|
has: a !== void 0 || b !== void 0,
|
|
@@ -12494,7 +12983,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12494
12983
|
};
|
|
12495
12984
|
}
|
|
12496
12985
|
const toolResults = await executeToolBatch(ctx, canonicalToolCalls, turnId);
|
|
12497
|
-
if (ctx.elideStaleReads === "notice") appendStaleReadNotices(ctx.turns, canonicalToolCalls, toolResults);
|
|
12986
|
+
if (ctx.behavior.elideStaleReads === "notice") appendStaleReadNotices(ctx.turns, canonicalToolCalls, toolResults);
|
|
12498
12987
|
const toolResultMsg = ctx.provider.toolResultsMessage(toolResults);
|
|
12499
12988
|
const toolResultsTurn = {
|
|
12500
12989
|
id: ctx.clock.randomUUID(),
|
|
@@ -12510,8 +12999,8 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12510
12999
|
message: toolResultsTurn,
|
|
12511
13000
|
results: toolResults
|
|
12512
13001
|
}));
|
|
12513
|
-
if (typeof ctx.toolOutputBudget === "number" && ctx.toolOutputBudget > 0) {
|
|
12514
|
-
const excludeSet = ctx.toolOutputBudgetExcludeTools && ctx.toolOutputBudgetExcludeTools.length > 0 ? new Set(ctx.toolOutputBudgetExcludeTools) : void 0;
|
|
13002
|
+
if (typeof ctx.behavior.toolOutputBudget === "number" && ctx.behavior.toolOutputBudget > 0) {
|
|
13003
|
+
const excludeSet = ctx.behavior.toolOutputBudgetExcludeTools && ctx.behavior.toolOutputBudgetExcludeTools.length > 0 ? new Set(ctx.behavior.toolOutputBudgetExcludeTools) : void 0;
|
|
12515
13004
|
const nameById = excludeSet ? new Map(canonicalToolCalls.map((c) => [c.id, c.name])) : void 0;
|
|
12516
13005
|
const totalBytes = toolResults.reduce((sum, r) => {
|
|
12517
13006
|
if (excludeSet && nameById) {
|
|
@@ -12520,8 +13009,8 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12520
13009
|
}
|
|
12521
13010
|
return sum + toolOutputBudgetByteLength(r.content);
|
|
12522
13011
|
}, 0);
|
|
12523
|
-
if (totalBytes > ctx.toolOutputBudget) {
|
|
12524
|
-
const warning = `[Tool output budget exceeded: ${totalBytes} text-equivalent bytes returned in this turn (cap: ${ctx.toolOutputBudget}). Summarize the salient findings before calling more tools.]`;
|
|
13012
|
+
if (totalBytes > ctx.behavior.toolOutputBudget) {
|
|
13013
|
+
const warning = `[Tool output budget exceeded: ${totalBytes} text-equivalent bytes returned in this turn (cap: ${ctx.behavior.toolOutputBudget}). Summarize the salient findings before calling more tools.]`;
|
|
12525
13014
|
const userMsg = ctx.provider.userMessage(warning);
|
|
12526
13015
|
ctx.turns.push({
|
|
12527
13016
|
id: await ctx.generateTurnId(),
|
|
@@ -12534,7 +13023,7 @@ async function executeTurn(ctx, turn, priorUsage) {
|
|
|
12534
13023
|
turn,
|
|
12535
13024
|
turnId,
|
|
12536
13025
|
bytes: totalBytes,
|
|
12537
|
-
budget: ctx.toolOutputBudget
|
|
13026
|
+
budget: ctx.behavior.toolOutputBudget
|
|
12538
13027
|
});
|
|
12539
13028
|
}
|
|
12540
13029
|
}
|
|
@@ -12609,7 +13098,7 @@ function sanitizeToolOutputForProvider(provider, output) {
|
|
|
12609
13098
|
* Empty object when the agent has no live connection.
|
|
12610
13099
|
*/
|
|
12611
13100
|
function mcpConnectionFor(ctx) {
|
|
12612
|
-
const connection = ctx.
|
|
13101
|
+
const connection = ctx.inheritance.mcpConnection?.();
|
|
12613
13102
|
if (!connection) return {};
|
|
12614
13103
|
return { mcpConnection: nonOwningMcpView(connection) };
|
|
12615
13104
|
}
|
|
@@ -12832,14 +13321,14 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
|
|
|
12832
13321
|
execution: ctx.execution,
|
|
12833
13322
|
handle: ctx.handle,
|
|
12834
13323
|
hooks: ctx.hooks,
|
|
12835
|
-
tools: ctx.
|
|
12836
|
-
...ctx.
|
|
12837
|
-
...ctx.
|
|
12838
|
-
...ctx.
|
|
12839
|
-
...ctx.
|
|
13324
|
+
tools: ctx.inheritance.tools,
|
|
13325
|
+
...ctx.inheritance.name !== void 0 ? { name: ctx.inheritance.name } : {},
|
|
13326
|
+
...ctx.inheritance.system !== void 0 ? { system: ctx.inheritance.system } : {},
|
|
13327
|
+
...ctx.inheritance.toolAliases !== void 0 ? { toolAliases: ctx.inheritance.toolAliases } : {},
|
|
13328
|
+
...ctx.inheritance.mcpServers !== void 0 ? { mcpServers: ctx.inheritance.mcpServers } : {},
|
|
12840
13329
|
...mcpConnectionFor(ctx),
|
|
12841
|
-
...ctx.
|
|
12842
|
-
|
|
13330
|
+
...ctx.inheritance.skills !== void 0 ? { skills: ctx.inheritance.skills } : {},
|
|
13331
|
+
behavior: ctx.behavior,
|
|
12843
13332
|
turnId,
|
|
12844
13333
|
callId,
|
|
12845
13334
|
runId: ctx.runId,
|
|
@@ -12902,7 +13391,7 @@ async function runSingleToolDispatch(ctx, call, turnId, fixed) {
|
|
|
12902
13391
|
isError = true;
|
|
12903
13392
|
} else {
|
|
12904
13393
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
12905
|
-
if (ctx.shouldRethrowToolError?.(error)) {
|
|
13394
|
+
if (ctx.behavior.shouldRethrowToolError?.(error)) {
|
|
12906
13395
|
ctx.toolState?.discard(callId);
|
|
12907
13396
|
throw error;
|
|
12908
13397
|
}
|
|
@@ -13060,8 +13549,8 @@ async function emitToolResult(ctx, params) {
|
|
|
13060
13549
|
const metadata = sanitizedMetadata.metadata;
|
|
13061
13550
|
if (sanitizedMetadata.warnings.length > 0 && process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/loop] sanitized tool metadata for ${name}/${callId}: ${sanitizedMetadata.warnings.join("; ")}\n`);
|
|
13062
13551
|
const finalOutcome = isError && outcome && outcome !== "failed" ? "failed" : outcome;
|
|
13063
|
-
const threshold = ctx.persistThreshold;
|
|
13064
|
-
const persistDir = ctx.persistDir;
|
|
13552
|
+
const threshold = ctx.behavior.persistThreshold;
|
|
13553
|
+
const persistDir = ctx.behavior.persistDir;
|
|
13065
13554
|
if (threshold && threshold > 0 && persistDir) {
|
|
13066
13555
|
const outcome = await maybePersistToolResult({
|
|
13067
13556
|
toolName: name,
|
|
@@ -13069,8 +13558,8 @@ async function emitToolResult(ctx, params) {
|
|
|
13069
13558
|
output,
|
|
13070
13559
|
threshold,
|
|
13071
13560
|
persistDir,
|
|
13072
|
-
...ctx.persistExcludeTools ? { excludeTools: ctx.persistExcludeTools } : {},
|
|
13073
|
-
...typeof ctx.persistMaxBytes === "number" ? { maxBytes: ctx.persistMaxBytes } : {},
|
|
13561
|
+
...ctx.behavior.persistExcludeTools ? { excludeTools: ctx.behavior.persistExcludeTools } : {},
|
|
13562
|
+
...typeof ctx.behavior.persistMaxBytes === "number" ? { maxBytes: ctx.behavior.persistMaxBytes } : {},
|
|
13074
13563
|
writeBlob: executionBlobWriter(ctx)
|
|
13075
13564
|
});
|
|
13076
13565
|
if (outcome.kind === "persisted") output = outcome.output;
|
|
@@ -13160,13 +13649,12 @@ function resolveConcurrencySafe(def, input) {
|
|
|
13160
13649
|
* AND carries the shell-cascade signal — siblings see one signal source.
|
|
13161
13650
|
*/
|
|
13162
13651
|
async function executeToolBatch(ctx, toolCalls, turnId) {
|
|
13163
|
-
if (ctx.toolBatchExecutor) return executeCustomToolBatch(ctx, toolCalls, turnId, ctx.toolBatchExecutor);
|
|
13652
|
+
if (ctx.behavior.toolBatchExecutor && !ctx.suppressBatchExecutor) return executeCustomToolBatch(ctx, toolCalls, turnId, ctx.behavior.toolBatchExecutor);
|
|
13164
13653
|
return executeNativeToolBatch(ctx, toolCalls, turnId);
|
|
13165
13654
|
}
|
|
13166
|
-
|
|
13167
|
-
if (toolCalls.length === 0) return [];
|
|
13655
|
+
function createToolBatchHarness(ctx, toolCalls, turnId, options = {}) {
|
|
13168
13656
|
const N = toolCalls.length;
|
|
13169
|
-
const maxConcurrent = Math.max(1, ctx.maxConcurrentTools ?? DEFAULT_MAX_CONCURRENT_TOOLS);
|
|
13657
|
+
const maxConcurrent = Math.max(1, ctx.behavior.maxConcurrentTools ?? DEFAULT_MAX_CONCURRENT_TOOLS);
|
|
13170
13658
|
const safe = Array.from({ length: N });
|
|
13171
13659
|
for (let i = 0; i < N; i++) safe[i] = resolveConcurrencySafe(ctx.tools[toolCalls[i].name], toolCalls[i].input);
|
|
13172
13660
|
const siblingAbort = new AbortController();
|
|
@@ -13179,14 +13667,20 @@ async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
|
13179
13667
|
const childCtx = {
|
|
13180
13668
|
...ctx,
|
|
13181
13669
|
signal: siblingAbort.signal,
|
|
13182
|
-
|
|
13670
|
+
...options.suppressBatchExecutor ? { suppressBatchExecutor: true } : {}
|
|
13183
13671
|
};
|
|
13672
|
+
/**
|
|
13673
|
+
* Distinguish a shell-cascade kill from a user-issued abort so the model sees
|
|
13674
|
+
* actionable text. When BOTH the parent signal and the sibling signal are
|
|
13675
|
+
* aborted, the parent wins — user-issued aborts take precedence (the model is
|
|
13676
|
+
* being interrupted by the human, not by a sibling's failure).
|
|
13677
|
+
*/
|
|
13184
13678
|
const cancelMessage = () => {
|
|
13185
13679
|
if (ctx.signal.aborted) return INTERRUPT_MESSAGE_FOR_TOOL_USE;
|
|
13186
13680
|
if (siblingAbort.signal.reason === SHELL_CASCADE_REASON) return SHELL_CASCADE_CANCEL_MESSAGE;
|
|
13187
13681
|
return INTERRUPT_MESSAGE_FOR_TOOL_USE;
|
|
13188
13682
|
};
|
|
13189
|
-
const
|
|
13683
|
+
const settle = async (index) => {
|
|
13190
13684
|
const call = toolCalls[index];
|
|
13191
13685
|
try {
|
|
13192
13686
|
const { result } = await executeSingleTool(childCtx, call, turnId);
|
|
@@ -13194,7 +13688,7 @@ async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
|
13194
13688
|
if (result.isError && !isUserCancel && call.name === SHELL_TOOL_NAME && !siblingAbort.signal.aborted) siblingAbort.abort(SHELL_CASCADE_REASON);
|
|
13195
13689
|
return result;
|
|
13196
13690
|
} catch (err) {
|
|
13197
|
-
if (ctx.shouldRethrowToolError?.(err)) throw err;
|
|
13691
|
+
if (ctx.behavior.shouldRethrowToolError?.(err)) throw err;
|
|
13198
13692
|
const isAbort = siblingAbort.signal.aborted || ctx.signal.aborted || err instanceof Error && err.name === "AbortError";
|
|
13199
13693
|
return {
|
|
13200
13694
|
id: call.id,
|
|
@@ -13203,6 +13697,25 @@ async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
|
13203
13697
|
};
|
|
13204
13698
|
}
|
|
13205
13699
|
};
|
|
13700
|
+
return {
|
|
13701
|
+
childCtx,
|
|
13702
|
+
siblingAbort,
|
|
13703
|
+
safe,
|
|
13704
|
+
maxConcurrent,
|
|
13705
|
+
cancelMessage,
|
|
13706
|
+
settle,
|
|
13707
|
+
dispose: () => {
|
|
13708
|
+
if (parentAbortListener) {
|
|
13709
|
+
ctx.signal.removeEventListener("abort", parentAbortListener);
|
|
13710
|
+
parentAbortListener = void 0;
|
|
13711
|
+
}
|
|
13712
|
+
}
|
|
13713
|
+
};
|
|
13714
|
+
}
|
|
13715
|
+
async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
13716
|
+
if (toolCalls.length === 0) return [];
|
|
13717
|
+
const harness = createToolBatchHarness(ctx, toolCalls, turnId, { suppressBatchExecutor: true });
|
|
13718
|
+
const { childCtx, siblingAbort, safe, maxConcurrent, cancelMessage } = harness;
|
|
13206
13719
|
const executeDurable = async (indices, all) => {
|
|
13207
13720
|
const starts = [];
|
|
13208
13721
|
const results = /* @__PURE__ */ new Map();
|
|
@@ -13267,7 +13780,7 @@ async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
|
13267
13780
|
signal: ctx.signal,
|
|
13268
13781
|
steeringQueue: ctx.steeringQueue,
|
|
13269
13782
|
isConcurrencySafe: (index) => safe[index] === true,
|
|
13270
|
-
execute,
|
|
13783
|
+
execute: harness.settle,
|
|
13271
13784
|
canExecuteDurably: (index) => typeof ctx.tools[toolCalls[index].name]?.durableExecute === "function",
|
|
13272
13785
|
executeDurable,
|
|
13273
13786
|
interruptedResult: (index) => ({
|
|
@@ -13281,7 +13794,7 @@ async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
|
13281
13794
|
isError: true
|
|
13282
13795
|
})
|
|
13283
13796
|
};
|
|
13284
|
-
const timeoutMs = typeof ctx.toolBatchTimeoutMs === "number" && Number.isFinite(ctx.toolBatchTimeoutMs) && ctx.toolBatchTimeoutMs > 0 ? ctx.toolBatchTimeoutMs : 0;
|
|
13797
|
+
const timeoutMs = typeof ctx.behavior.toolBatchTimeoutMs === "number" && Number.isFinite(ctx.behavior.toolBatchTimeoutMs) && ctx.behavior.toolBatchTimeoutMs > 0 ? ctx.behavior.toolBatchTimeoutMs : 0;
|
|
13285
13798
|
try {
|
|
13286
13799
|
if (timeoutMs <= 0) return await executor(executionCtx);
|
|
13287
13800
|
return await withTimeout$1(executor(executionCtx), {
|
|
@@ -13298,7 +13811,7 @@ async function executeCustomToolBatch(ctx, toolCalls, turnId, executor) {
|
|
|
13298
13811
|
isError: true
|
|
13299
13812
|
}));
|
|
13300
13813
|
} finally {
|
|
13301
|
-
|
|
13814
|
+
harness.dispose();
|
|
13302
13815
|
}
|
|
13303
13816
|
}
|
|
13304
13817
|
async function startDurableToolExecution(ctx, call, turnId, index) {
|
|
@@ -13433,14 +13946,14 @@ async function startDurableToolExecution(ctx, call, turnId, index) {
|
|
|
13433
13946
|
execution: ctx.execution,
|
|
13434
13947
|
handle: ctx.handle,
|
|
13435
13948
|
hooks: ctx.hooks,
|
|
13436
|
-
tools: ctx.
|
|
13437
|
-
...ctx.
|
|
13438
|
-
...ctx.
|
|
13439
|
-
...ctx.
|
|
13440
|
-
...ctx.
|
|
13949
|
+
tools: ctx.inheritance.tools,
|
|
13950
|
+
...ctx.inheritance.name !== void 0 ? { name: ctx.inheritance.name } : {},
|
|
13951
|
+
...ctx.inheritance.system !== void 0 ? { system: ctx.inheritance.system } : {},
|
|
13952
|
+
...ctx.inheritance.toolAliases !== void 0 ? { toolAliases: ctx.inheritance.toolAliases } : {},
|
|
13953
|
+
...ctx.inheritance.mcpServers !== void 0 ? { mcpServers: ctx.inheritance.mcpServers } : {},
|
|
13441
13954
|
...mcpConnectionFor(ctx),
|
|
13442
|
-
...ctx.
|
|
13443
|
-
|
|
13955
|
+
...ctx.inheritance.skills !== void 0 ? { skills: ctx.inheritance.skills } : {},
|
|
13956
|
+
behavior: ctx.behavior,
|
|
13444
13957
|
turnId,
|
|
13445
13958
|
callId,
|
|
13446
13959
|
runId: ctx.runId,
|
|
@@ -13497,7 +14010,7 @@ async function completeDurableToolExecution(ctx, start, output) {
|
|
|
13497
14010
|
};
|
|
13498
14011
|
}
|
|
13499
14012
|
async function failDurableToolExecution(ctx, start, err, cancelMessage) {
|
|
13500
|
-
if (ctx.shouldRethrowToolError?.(err)) {
|
|
14013
|
+
if (ctx.behavior.shouldRethrowToolError?.(err)) {
|
|
13501
14014
|
ctx.toolState?.discard(start.callId);
|
|
13502
14015
|
throw err;
|
|
13503
14016
|
}
|
|
@@ -13580,49 +14093,14 @@ function errorFromSerializedFailure(failure) {
|
|
|
13580
14093
|
async function executeNativeToolBatch(ctx, toolCalls, turnId) {
|
|
13581
14094
|
if (toolCalls.length === 0) return [];
|
|
13582
14095
|
const N = toolCalls.length;
|
|
13583
|
-
const maxConcurrent = Math.max(1, ctx.maxConcurrentTools ?? DEFAULT_MAX_CONCURRENT_TOOLS);
|
|
13584
14096
|
const results = Array.from({ length: N });
|
|
13585
|
-
const
|
|
13586
|
-
|
|
13587
|
-
const siblingAbort = new AbortController();
|
|
13588
|
-
let parentAbortListener;
|
|
13589
|
-
if (ctx.signal.aborted) siblingAbort.abort(ctx.signal.reason ?? "parent-aborted");
|
|
13590
|
-
else {
|
|
13591
|
-
parentAbortListener = () => siblingAbort.abort(ctx.signal.reason ?? "parent-aborted");
|
|
13592
|
-
ctx.signal.addEventListener("abort", parentAbortListener, { once: true });
|
|
13593
|
-
}
|
|
13594
|
-
const childCtx = {
|
|
13595
|
-
...ctx,
|
|
13596
|
-
signal: siblingAbort.signal
|
|
13597
|
-
};
|
|
14097
|
+
const harness = createToolBatchHarness(ctx, toolCalls, turnId);
|
|
14098
|
+
const { safe, maxConcurrent } = harness;
|
|
13598
14099
|
/** Indices currently in flight. Tracked for fleet-safety + cap checks. */
|
|
13599
14100
|
const inFlight = /* @__PURE__ */ new Map();
|
|
13600
|
-
/**
|
|
13601
|
-
* Distinguish a shell-cascade kill from a user-issued abort so the
|
|
13602
|
-
* model sees actionable text. When BOTH the parent signal and the
|
|
13603
|
-
* sibling signal are aborted, the parent wins — user-issued aborts
|
|
13604
|
-
* take precedence (the model is being interrupted by the human, not
|
|
13605
|
-
* by a sibling's failure).
|
|
13606
|
-
*/
|
|
13607
|
-
const cancelMessage = () => {
|
|
13608
|
-
if (ctx.signal.aborted) return INTERRUPT_MESSAGE_FOR_TOOL_USE;
|
|
13609
|
-
if (siblingAbort.signal.reason === SHELL_CASCADE_REASON) return SHELL_CASCADE_CANCEL_MESSAGE;
|
|
13610
|
-
return INTERRUPT_MESSAGE_FOR_TOOL_USE;
|
|
13611
|
-
};
|
|
13612
14101
|
const dispatch = (index) => {
|
|
13613
|
-
|
|
13614
|
-
return executeSingleTool(childCtx, call, turnId).then(({ result }) => {
|
|
14102
|
+
return harness.settle(index).then((result) => {
|
|
13615
14103
|
results[index] = result;
|
|
13616
|
-
const isUserCancel = typeof result.content === "string" && result.content === "[Tool call cancelled by user]";
|
|
13617
|
-
if (result.isError && !isUserCancel && call.name === SHELL_TOOL_NAME && !siblingAbort.signal.aborted) siblingAbort.abort(SHELL_CASCADE_REASON);
|
|
13618
|
-
}, (err) => {
|
|
13619
|
-
if (ctx.shouldRethrowToolError?.(err)) throw err;
|
|
13620
|
-
const isAbort = siblingAbort.signal.aborted || ctx.signal.aborted || err instanceof Error && err.name === "AbortError";
|
|
13621
|
-
results[index] = {
|
|
13622
|
-
id: call.id,
|
|
13623
|
-
content: isAbort ? cancelMessage() : `Error: ${errorMessage(err)}`,
|
|
13624
|
-
isError: true
|
|
13625
|
-
};
|
|
13626
14104
|
}).finally(() => {
|
|
13627
14105
|
inFlight.delete(index);
|
|
13628
14106
|
});
|
|
@@ -13682,7 +14160,7 @@ async function executeNativeToolBatch(ctx, toolCalls, turnId) {
|
|
|
13682
14160
|
await drain();
|
|
13683
14161
|
return results;
|
|
13684
14162
|
} finally {
|
|
13685
|
-
|
|
14163
|
+
harness.dispose();
|
|
13686
14164
|
}
|
|
13687
14165
|
}
|
|
13688
14166
|
//#endregion
|
|
@@ -13827,6 +14305,221 @@ function createToolCancelRegistry(options = {}) {
|
|
|
13827
14305
|
};
|
|
13828
14306
|
}
|
|
13829
14307
|
//#endregion
|
|
14308
|
+
//#region src/tool-state.ts
|
|
14309
|
+
/**
|
|
14310
|
+
* Durable key/value state for tools.
|
|
14311
|
+
*
|
|
14312
|
+
* A tool reaches this through {@link ToolContext.state} — a plain,
|
|
14313
|
+
* SYNCHRONOUS map view. Synchronous is not a convenience choice: under a
|
|
14314
|
+
* durable-execution runtime the tool body runs inside a journaled action
|
|
14315
|
+
* (Restate's `ctx.run`), and a durable read (`ctx.get`) is illegal in
|
|
14316
|
+
* there. So the flow is split into three phases the runtime can journal
|
|
14317
|
+
* independently:
|
|
14318
|
+
*
|
|
14319
|
+
* 1. **hydrate** — load the snapshot ONCE per agent, outside any
|
|
14320
|
+
* journaled action, before the first tool dispatch.
|
|
14321
|
+
* 2. **stage** — the tool mutates its per-call view. Mutations are
|
|
14322
|
+
* recorded as an ordered {@link ToolStateOp} list, NOT written
|
|
14323
|
+
* through: they're plain data, so a durable adapter can journal them
|
|
14324
|
+
* alongside the tool result.
|
|
14325
|
+
* 3. **commit** — the loop applies the staged ops through
|
|
14326
|
+
* {@link ToolStateStore.commit} after the call settles, outside the
|
|
14327
|
+
* journaled action.
|
|
14328
|
+
*
|
|
14329
|
+
* The invariant that makes this replay-safe:
|
|
14330
|
+
*
|
|
14331
|
+
* > A staged mutation is committed only if it came back OUT of a journal
|
|
14332
|
+
* > entry.
|
|
14333
|
+
*
|
|
14334
|
+
* A durable adapter (see `restateTool`) reads {@link StagedToolState.pending}
|
|
14335
|
+
* from inside its journaled closure, ships the ops out as part of the
|
|
14336
|
+
* journaled value, and calls {@link StagedToolState.restore} outside it. On
|
|
14337
|
+
* replay the tool body never runs, so nothing is staged live — `restore`
|
|
14338
|
+
* re-seeds the exact same ops from the journal and the commit re-issues an
|
|
14339
|
+
* identical write. Paths that produced no journal entry (a fatal error, a
|
|
14340
|
+
* control abort, a dead attempt) call {@link StagedToolState.clear} so live
|
|
14341
|
+
* mutations from an attempt nobody recorded can never reach the store.
|
|
14342
|
+
*
|
|
14343
|
+
* The store seam is deliberately runtime-agnostic: `zidane/restate` ships
|
|
14344
|
+
* `restateToolStateStore`, but a Redis / Postgres / file-backed
|
|
14345
|
+
* implementation is the same two methods. With no store configured the
|
|
14346
|
+
* controller is in-memory only — `ctx.state` still works everywhere
|
|
14347
|
+
* (local CLI, tests, subagents), it just doesn't outlive the process.
|
|
14348
|
+
*/
|
|
14349
|
+
/**
|
|
14350
|
+
* Default per-value size budget: 64 KiB of JSON.
|
|
14351
|
+
*
|
|
14352
|
+
* Deliberately far below any backend's hard ceiling (Restate's journal-entry
|
|
14353
|
+
* limit is 32 MiB) because this is a design guard, not a protocol limit.
|
|
14354
|
+
* Durable tool state is for flags, ids, counters, and cursors; a value that
|
|
14355
|
+
* doesn't fit in 64 KiB is a payload, and payloads belong in the tool result
|
|
14356
|
+
* or in storage the tool owns. Raise it on the store if you disagree — but
|
|
14357
|
+
* know that everything here is rewritten on every commit.
|
|
14358
|
+
*/
|
|
14359
|
+
const DEFAULT_MAX_VALUE_BYTES = 64 * 1024;
|
|
14360
|
+
/** Brand so {@link stagedToolState} can recognise views this module made. */
|
|
14361
|
+
const STAGED = Symbol("zidane.toolState.staged");
|
|
14362
|
+
/**
|
|
14363
|
+
* Resolve the staging handle behind a view. Returns `undefined` for a
|
|
14364
|
+
* missing view or a foreign implementation (a host that supplied its own
|
|
14365
|
+
* `ToolStateView`), which lets adapters degrade to "no durable staging"
|
|
14366
|
+
* instead of throwing.
|
|
14367
|
+
*/
|
|
14368
|
+
function stagedToolState(view) {
|
|
14369
|
+
if (!view) return void 0;
|
|
14370
|
+
return view[STAGED] ?? void 0;
|
|
14371
|
+
}
|
|
14372
|
+
function createToolStateController(store) {
|
|
14373
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
14374
|
+
const views = /* @__PURE__ */ new Map();
|
|
14375
|
+
const maxValueBytes = store?.maxValueBytes ?? 65536;
|
|
14376
|
+
let hydrated;
|
|
14377
|
+
const hydrate = () => {
|
|
14378
|
+
if (hydrated) return hydrated;
|
|
14379
|
+
hydrated = (async () => {
|
|
14380
|
+
if (!store) return;
|
|
14381
|
+
const loaded = await store.load();
|
|
14382
|
+
for (const [key, value] of Object.entries(loaded ?? {})) if (!snapshot.has(key)) snapshot.set(key, value);
|
|
14383
|
+
})().catch((err) => {
|
|
14384
|
+
hydrated = void 0;
|
|
14385
|
+
throw err;
|
|
14386
|
+
});
|
|
14387
|
+
return hydrated;
|
|
14388
|
+
};
|
|
14389
|
+
const begin = (callId) => {
|
|
14390
|
+
const existing = views.get(callId);
|
|
14391
|
+
if (existing) return existing;
|
|
14392
|
+
const view = createStagingView(snapshot, maxValueBytes);
|
|
14393
|
+
views.set(callId, view);
|
|
14394
|
+
return view;
|
|
14395
|
+
};
|
|
14396
|
+
const commit = async (callId) => {
|
|
14397
|
+
const view = views.get(callId);
|
|
14398
|
+
if (!view) return;
|
|
14399
|
+
views.delete(callId);
|
|
14400
|
+
const ops = view[STAGED].pending();
|
|
14401
|
+
if (ops.length === 0) return;
|
|
14402
|
+
await store?.commit(ops);
|
|
14403
|
+
applyOps(snapshot, ops);
|
|
14404
|
+
};
|
|
14405
|
+
return {
|
|
14406
|
+
hydrate,
|
|
14407
|
+
begin,
|
|
14408
|
+
commit,
|
|
14409
|
+
discard: (callId) => {
|
|
14410
|
+
views.delete(callId);
|
|
14411
|
+
},
|
|
14412
|
+
pending: (callId) => views.get(callId)?.[STAGED].pending() ?? [],
|
|
14413
|
+
snapshot: () => Object.fromEntries(snapshot)
|
|
14414
|
+
};
|
|
14415
|
+
}
|
|
14416
|
+
/**
|
|
14417
|
+
* Build a view over `snapshot` that records writes instead of applying
|
|
14418
|
+
* them. Reads layer staged ops on top of the snapshot so a tool sees its
|
|
14419
|
+
* own writes back — including deletions, which read as absent.
|
|
14420
|
+
*/
|
|
14421
|
+
function createStagingView(snapshot, maxValueBytes) {
|
|
14422
|
+
let ops = [];
|
|
14423
|
+
const latest = /* @__PURE__ */ new Map();
|
|
14424
|
+
const stage = (op) => {
|
|
14425
|
+
ops.push(op);
|
|
14426
|
+
latest.set(op.key, op);
|
|
14427
|
+
};
|
|
14428
|
+
const resolve = (key) => {
|
|
14429
|
+
const staged = latest.get(key);
|
|
14430
|
+
if (staged) return staged.delete === true ? { present: false } : {
|
|
14431
|
+
present: true,
|
|
14432
|
+
value: staged.value
|
|
14433
|
+
};
|
|
14434
|
+
return snapshot.has(key) ? {
|
|
14435
|
+
present: true,
|
|
14436
|
+
value: snapshot.get(key)
|
|
14437
|
+
} : { present: false };
|
|
14438
|
+
};
|
|
14439
|
+
const view = {
|
|
14440
|
+
get: (key) => {
|
|
14441
|
+
const resolved = resolve(key);
|
|
14442
|
+
return resolved.present ? resolved.value : void 0;
|
|
14443
|
+
},
|
|
14444
|
+
set: (key, value) => {
|
|
14445
|
+
assertStorable(key, value, maxValueBytes);
|
|
14446
|
+
if (value === void 0) stage({
|
|
14447
|
+
key,
|
|
14448
|
+
delete: true
|
|
14449
|
+
});
|
|
14450
|
+
else stage({
|
|
14451
|
+
key,
|
|
14452
|
+
value
|
|
14453
|
+
});
|
|
14454
|
+
},
|
|
14455
|
+
delete: (key) => {
|
|
14456
|
+
stage({
|
|
14457
|
+
key,
|
|
14458
|
+
delete: true
|
|
14459
|
+
});
|
|
14460
|
+
},
|
|
14461
|
+
has: (key) => resolve(key).present,
|
|
14462
|
+
keys: () => {
|
|
14463
|
+
const keys = new Set(snapshot.keys());
|
|
14464
|
+
for (const [key, op] of latest) if (op.delete === true) keys.delete(key);
|
|
14465
|
+
else keys.add(key);
|
|
14466
|
+
return [...keys].sort();
|
|
14467
|
+
},
|
|
14468
|
+
[STAGED]: {
|
|
14469
|
+
pending: () => [...ops],
|
|
14470
|
+
restore: (restored) => {
|
|
14471
|
+
ops = [...restored];
|
|
14472
|
+
latest.clear();
|
|
14473
|
+
for (const op of ops) latest.set(op.key, op);
|
|
14474
|
+
},
|
|
14475
|
+
clear: () => {
|
|
14476
|
+
ops = [];
|
|
14477
|
+
latest.clear();
|
|
14478
|
+
}
|
|
14479
|
+
}
|
|
14480
|
+
};
|
|
14481
|
+
Object.defineProperty(view, STAGED, {
|
|
14482
|
+
value: view[STAGED],
|
|
14483
|
+
enumerable: false
|
|
14484
|
+
});
|
|
14485
|
+
return view;
|
|
14486
|
+
}
|
|
14487
|
+
/**
|
|
14488
|
+
* Reject a value that can't survive the trip to the store, at the moment the
|
|
14489
|
+
* tool stages it.
|
|
14490
|
+
*
|
|
14491
|
+
* Scalars are skipped: they're bounded, and paying a `JSON.stringify` per
|
|
14492
|
+
* `set` on a hot tool would be a real cost for no signal. Strings and objects
|
|
14493
|
+
* are measured in UTF-8 bytes, which is what both the journal and the store
|
|
14494
|
+
* actually spend.
|
|
14495
|
+
*/
|
|
14496
|
+
function assertStorable(key, value, maxValueBytes) {
|
|
14497
|
+
if (value === void 0 || value === null || typeof value === "boolean" || typeof value === "number") return;
|
|
14498
|
+
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") throw new TypeError(`tool state value for "${key}" is a ${typeof value}, which cannot cross a durable store — use a JSON-serializable value.`);
|
|
14499
|
+
let json;
|
|
14500
|
+
try {
|
|
14501
|
+
json = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
14502
|
+
} catch (err) {
|
|
14503
|
+
throw new TypeError(`tool state value for "${key}" is not JSON-serializable (${err instanceof Error ? err.message : String(err)}) — durable tool state crosses a journal, so cycles and exotic values can't be stored.`);
|
|
14504
|
+
}
|
|
14505
|
+
const bytes = Buffer.byteLength(json, "utf8");
|
|
14506
|
+
if (bytes > maxValueBytes) throw new RangeError(`tool state value for "${key}" is ${formatBytes(bytes)}, over the ${formatBytes(maxValueBytes)} limit. Durable tool state is for flags, ids, counters, and cursors — it is rewritten on every commit and crosses a journal entry. Keep the payload in the tool result or in storage the tool owns, and store a reference here.`);
|
|
14507
|
+
}
|
|
14508
|
+
function formatBytes(bytes) {
|
|
14509
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
14510
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
14511
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
14512
|
+
}
|
|
14513
|
+
/** Apply ops to a snapshot map in order — last write per key wins. */
|
|
14514
|
+
function applyOps(snapshot, ops) {
|
|
14515
|
+
for (const op of ops) if (op.delete === true) snapshot.delete(op.key);
|
|
14516
|
+
else snapshot.set(op.key, op.value);
|
|
14517
|
+
}
|
|
14518
|
+
/** Runtime guard for a journaled op list — adapters validate before restoring. */
|
|
14519
|
+
function isToolStateOps(value) {
|
|
14520
|
+
return Array.isArray(value) && value.every((op) => typeof op === "object" && op !== null && typeof op.key === "string" && (op.delete === true || "value" in op));
|
|
14521
|
+
}
|
|
14522
|
+
//#endregion
|
|
13830
14523
|
//#region src/agent.ts
|
|
13831
14524
|
/**
|
|
13832
14525
|
* Single builder for a {@link ContextAssembly}, shared by the per-run snapshot
|
|
@@ -14428,6 +15121,257 @@ function buildMcpToolNameSeparatorAliases(mcpToolNames, servers, separator) {
|
|
|
14428
15121
|
return Object.keys(aliases).length > 0 ? aliases : void 0;
|
|
14429
15122
|
}
|
|
14430
15123
|
/**
|
|
15124
|
+
* Partition the registry into eager vs lazy buckets and build the per-run
|
|
15125
|
+
* unlock bookkeeping the loop reads when rebuilding the wire-level tool list.
|
|
15126
|
+
*
|
|
15127
|
+
* Native (non-MCP) tools are always eager; `tool_search` itself must be eager
|
|
15128
|
+
* too (the discovery path can't gate on its own surfacing). The `unlocked` set
|
|
15129
|
+
* seeds with every eager canonical tool name and grows as the model surfaces
|
|
15130
|
+
* lazy tools through `tool_search`.
|
|
15131
|
+
*
|
|
15132
|
+
* Aliases are passed through so lazy entries carry their wire-level display
|
|
15133
|
+
* name — what the catalog and `tool_search` results show to the model — while
|
|
15134
|
+
* keeping the canonical name on the entry for unlock-set membership and
|
|
15135
|
+
* dispatch-map lookup.
|
|
15136
|
+
*/
|
|
15137
|
+
function buildRunDisclosureState(input) {
|
|
15138
|
+
const { tools: baseTools, mcpToolNames, mcpServers, toolDisclosure, toolSearch, toolAliases, hasRunToolOverride } = input;
|
|
15139
|
+
const disclosure = partitionToolDisclosure(baseTools, mcpToolNames, mcpServers, toolDisclosure, toolAliases);
|
|
15140
|
+
const unlocked = new Set(disclosure.eagerCanonicalNames);
|
|
15141
|
+
const initialUnlocked = new Set(disclosure.eagerCanonicalNames);
|
|
15142
|
+
const dynamicUnlockOrder = [];
|
|
15143
|
+
const dynamicUnlockSeen = /* @__PURE__ */ new Set();
|
|
15144
|
+
const recordUnlock = (canonical) => {
|
|
15145
|
+
if (initialUnlocked.has(canonical)) return;
|
|
15146
|
+
if (dynamicUnlockSeen.has(canonical)) return;
|
|
15147
|
+
dynamicUnlockSeen.add(canonical);
|
|
15148
|
+
unlocked.add(canonical);
|
|
15149
|
+
dynamicUnlockOrder.push(canonical);
|
|
15150
|
+
};
|
|
15151
|
+
const hostDefinedToolSearch = !!baseTools.tool_search;
|
|
15152
|
+
const injectedToolSearch = !hasRunToolOverride && (toolDisclosure === "lazy" || disclosure.lazyEntries.length > 0) && toolSearch?.tool !== false && !hostDefinedToolSearch;
|
|
15153
|
+
let tools = baseTools;
|
|
15154
|
+
if (injectedToolSearch) {
|
|
15155
|
+
const toolSearchTool = createToolSearchTool({
|
|
15156
|
+
catalog: disclosure.lazyEntries,
|
|
15157
|
+
unlocked,
|
|
15158
|
+
addUnlock: recordUnlock,
|
|
15159
|
+
...toolSearch?.limit !== void 0 ? { defaultLimit: toolSearch.limit } : {}
|
|
15160
|
+
});
|
|
15161
|
+
tools = {
|
|
15162
|
+
...baseTools,
|
|
15163
|
+
[toolSearchTool.spec.name]: toolSearchTool
|
|
15164
|
+
};
|
|
15165
|
+
unlocked.add(toolSearchTool.spec.name);
|
|
15166
|
+
initialUnlocked.add(toolSearchTool.spec.name);
|
|
15167
|
+
}
|
|
15168
|
+
return {
|
|
15169
|
+
disclosure,
|
|
15170
|
+
unlocked,
|
|
15171
|
+
initialUnlocked,
|
|
15172
|
+
dynamicUnlockOrder,
|
|
15173
|
+
recordUnlock,
|
|
15174
|
+
tools,
|
|
15175
|
+
injectedToolSearch,
|
|
15176
|
+
hostDefinedToolSearch
|
|
15177
|
+
};
|
|
15178
|
+
}
|
|
15179
|
+
/**
|
|
15180
|
+
* Choose the prior turns a run starts from, or `undefined` for a fresh start.
|
|
15181
|
+
*
|
|
15182
|
+
* Two resume flavors:
|
|
15183
|
+
* - `resumeRunId` (child-run resume): seed ONLY the referenced prior run's
|
|
15184
|
+
* lineage. Used by the spawn tool to continue a stopped/failed subagent —
|
|
15185
|
+
* the parent's conversation (and every other child's) stays invisible,
|
|
15186
|
+
* mirroring how a fresh child starts from just its task prompt. Allowed
|
|
15187
|
+
* WITH `parentRunId` set (unlike a full-session resume).
|
|
15188
|
+
* - full-session resume: the historical top-level path, on re-runs
|
|
15189
|
+
* (`runs > 0`) or promptless runs (session turns already contain the user
|
|
15190
|
+
* message). **Excluded for child agents**: when the spawn tool runs with
|
|
15191
|
+
* `persist: true`, the child shares the parent's session for *storage* but
|
|
15192
|
+
* its conversation must start fresh from the task prompt. Otherwise the
|
|
15193
|
+
* child would pull in the parent's in-flight assistant turn — including the
|
|
15194
|
+
* parent's pending `spawn` tool_use — and every provider (Anthropic loudly,
|
|
15195
|
+
* OpenAI implicitly) rejects a history where a `tool_use` lacks its
|
|
15196
|
+
* matching `tool_result`.
|
|
15197
|
+
*/
|
|
15198
|
+
function selectSeedTurns(input) {
|
|
15199
|
+
const { session, options, resumeFilteredTurns } = input;
|
|
15200
|
+
if (options.resumeRunId && session) {
|
|
15201
|
+
const chain = resumeChainRunIds(session.runs, options.resumeRunId);
|
|
15202
|
+
return filterUnresolvedToolUses(session.turns.filter((t) => t.runId !== void 0 && chain.has(t.runId)));
|
|
15203
|
+
}
|
|
15204
|
+
if (!(session && session.turns.length > 0 && (session.runs.length > 0 || !options.prompt) && !options.parentRunId && !options.resumeRunId)) return void 0;
|
|
15205
|
+
const childRunIds = childRunIdSet(session);
|
|
15206
|
+
const resumed = childRunIds.size === 0 ? session.turns : session.turns.filter((t) => !t.runId || !childRunIds.has(t.runId));
|
|
15207
|
+
return resumeFilteredTurns && resumed === session.turns ? resumeFilteredTurns : filterUnresolvedToolUses(resumed);
|
|
15208
|
+
}
|
|
15209
|
+
/**
|
|
15210
|
+
* Replay every resolved `tool_search` from a resumed history into the per-run
|
|
15211
|
+
* `unlocked` set.
|
|
15212
|
+
*
|
|
15213
|
+
* Without this seeding a resumed conversation shows the model "These tools are
|
|
15214
|
+
* now callable" in the prior tool_result, but the next turn's `formattedTools`
|
|
15215
|
+
* and the `tool:gate` hook still treat the tools as locked — every subsequent
|
|
15216
|
+
* `tool_use` is rejected with "load via tool_search first", which the model
|
|
15217
|
+
* can't recover from without a fresh `tool_search` round-trip.
|
|
15218
|
+
*
|
|
15219
|
+
* Only replays calls whose result the model actually consumed: a `tool_call`
|
|
15220
|
+
* without a matching successful `tool_result` is either an unresolved pair
|
|
15221
|
+
* `filterUnresolvedToolUses` already stripped or a structural error — either
|
|
15222
|
+
* way, unlocking from it would be speculative.
|
|
15223
|
+
*/
|
|
15224
|
+
function replayToolSearchUnlocks(seededTurns, lazyEntries, unlocked, limit, recordUnlock) {
|
|
15225
|
+
const resolvedCallIds = /* @__PURE__ */ new Set();
|
|
15226
|
+
for (const turn of seededTurns) for (const block of turn.content) if (block.type === "tool_result" && !block.isError) resolvedCallIds.add(block.callId);
|
|
15227
|
+
for (const turn of seededTurns) for (const block of turn.content) {
|
|
15228
|
+
if (block.type !== "tool_call") continue;
|
|
15229
|
+
if (block.name !== "tool_search") continue;
|
|
15230
|
+
if (!resolvedCallIds.has(block.id)) continue;
|
|
15231
|
+
applyToolSearchToUnlocked(lazyEntries, block.input, unlocked, limit, recordUnlock);
|
|
15232
|
+
}
|
|
15233
|
+
}
|
|
15234
|
+
/**
|
|
15235
|
+
* Reconcile + drain pending background-task notifications.
|
|
15236
|
+
*
|
|
15237
|
+
* Each exit becomes a `<task-notification>` text block prepended to the user
|
|
15238
|
+
* turn so the model sees task completions on its very next prompt, without
|
|
15239
|
+
* polling. Two sources, merged by taskId:
|
|
15240
|
+
*
|
|
15241
|
+
* 1. PUSH — the agent-scoped queue populated by `background:exit` since the
|
|
15242
|
+
* last `run()` (in-process contexts fire `onExit`).
|
|
15243
|
+
* 2. PULL — a `listBackground` sweep for terminated tasks the model hasn't
|
|
15244
|
+
* been told about. This is what makes remote / durable contexts work:
|
|
15245
|
+
* they have no legal way to push from a timer into the host (journal
|
|
15246
|
+
* rules), so the loop polls at the run boundary instead.
|
|
15247
|
+
* `endedAt - startedAt` reproduces `durationMs` without depending on
|
|
15248
|
+
* sweep timing.
|
|
15249
|
+
*
|
|
15250
|
+
* The `background:reconcile` hook fires on the merged list INSIDE the run()
|
|
15251
|
+
* await chain — the journalable seam for durable hosts (see the hook's doc).
|
|
15252
|
+
* Entries suppressed earlier (`shell_kill`, `read_file`, `wait_task`) are
|
|
15253
|
+
* absent from both sources via the `notified` latch. See
|
|
15254
|
+
* `docs/RUN_IN_BACKGROUND.md` §Completion notification.
|
|
15255
|
+
*
|
|
15256
|
+
* Durable hosts wrap the live `listBackground` poll in `journalReconcile` so it
|
|
15257
|
+
* records as one journaled side effect (replay returns the recorded snapshot,
|
|
15258
|
+
* keeping `exits.length` — and therefore the conditional hook's journal
|
|
15259
|
+
* position — stable across attempts). Everything downstream of the snapshot is
|
|
15260
|
+
* pure over journaled / replayed inputs (session-turn acks, duration
|
|
15261
|
+
* derivation), so journaling the poll is enough to make the whole reconcile
|
|
15262
|
+
* deterministic.
|
|
15263
|
+
*/
|
|
15264
|
+
async function drainTaskNotifications(input) {
|
|
15265
|
+
const { state, executionContext, executionHandle, behavior, journalReconcile, session, turns, hooks } = input;
|
|
15266
|
+
const reconciled = new Map(state.pending);
|
|
15267
|
+
state.pending.clear();
|
|
15268
|
+
if (executionContext.listBackground && executionHandle && typeof behavior.tasksDir === "string" && behavior.tasksDir.length > 0) try {
|
|
15269
|
+
const listBackground = executionContext.listBackground;
|
|
15270
|
+
const handle = executionHandle;
|
|
15271
|
+
const entries = journalReconcile ? await journalReconcile(() => listBackground(handle)) : await listBackground(handle);
|
|
15272
|
+
const acks = entries.some((e) => e.status !== "running") ? collectTaskAcksFromTurns(session ? session.turns : turns) : void 0;
|
|
15273
|
+
for (const entry of entries) {
|
|
15274
|
+
if (entry.status === "running" || state.notified.has(entry.taskId) || reconciled.has(entry.taskId)) continue;
|
|
15275
|
+
if (acks) {
|
|
15276
|
+
if (acks.taskIds.has(entry.taskId)) continue;
|
|
15277
|
+
const readAt = acks.outputPaths.get(resolve(entry.outputPath));
|
|
15278
|
+
if (readAt !== void 0 && entry.endedAt !== void 0 && readAt >= entry.endedAt + READ_ACK_CLOCK_SKEW_MARGIN_MS) continue;
|
|
15279
|
+
}
|
|
15280
|
+
reconciled.set(entry.taskId, {
|
|
15281
|
+
taskId: entry.taskId,
|
|
15282
|
+
status: entry.status,
|
|
15283
|
+
exitCode: entry.exitCode ?? 0,
|
|
15284
|
+
...entry.signal ? { signal: entry.signal } : {},
|
|
15285
|
+
outputPath: entry.outputPath,
|
|
15286
|
+
durationMs: Math.max(0, (entry.endedAt ?? entry.startedAt) - entry.startedAt),
|
|
15287
|
+
command: entry.command
|
|
15288
|
+
});
|
|
15289
|
+
}
|
|
15290
|
+
} catch (err) {
|
|
15291
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/agent] background reconcile failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
15292
|
+
}
|
|
15293
|
+
const reconcileCtx = { exits: [...reconciled.values()] };
|
|
15294
|
+
if (reconcileCtx.exits.length > 0) await hooks.callHook("background:reconcile", reconcileCtx);
|
|
15295
|
+
for (const taskId of reconciled.keys()) {
|
|
15296
|
+
state.notified.add(taskId);
|
|
15297
|
+
state.pendingStalls.delete(taskId);
|
|
15298
|
+
}
|
|
15299
|
+
const drained = [];
|
|
15300
|
+
for (const notif of reconcileCtx.exits) drained.push({
|
|
15301
|
+
type: "text",
|
|
15302
|
+
text: renderTaskNotificationXml(notif)
|
|
15303
|
+
});
|
|
15304
|
+
if (state.pendingStalls.size > 0) {
|
|
15305
|
+
for (const stall of state.pendingStalls.values()) drained.push({
|
|
15306
|
+
type: "text",
|
|
15307
|
+
text: renderTaskStallXml(stall)
|
|
15308
|
+
});
|
|
15309
|
+
state.pendingStalls.clear();
|
|
15310
|
+
}
|
|
15311
|
+
return drained;
|
|
15312
|
+
}
|
|
15313
|
+
/**
|
|
15314
|
+
* Assemble the tool registry a single run advertises.
|
|
15315
|
+
*
|
|
15316
|
+
* Pipeline: resolve the base set (run override > agent + MCP) → derive the
|
|
15317
|
+
* wire-name aliases → inject the skills tools → apply extension catalog
|
|
15318
|
+
* transforms → index by spec name → rewrite the built-in `shell` for this
|
|
15319
|
+
* run's capabilities.
|
|
15320
|
+
*/
|
|
15321
|
+
function buildRunToolRegistry(input) {
|
|
15322
|
+
const { runOverrideTools, sourceTools, mcpConnection, mcpServers, mcpToolNameSeparator, toolAliases, resolvedSkills, skillsConfig, skillActivationState, hooks, toolCatalogTransforms, executionContext, behavior } = input;
|
|
15323
|
+
const runBaseTools = runOverrideTools !== void 0 ? runOverrideTools : mcpConnection ? {
|
|
15324
|
+
...sourceTools,
|
|
15325
|
+
...mcpConnection.tools
|
|
15326
|
+
} : sourceTools;
|
|
15327
|
+
const mcpToolNames = runOverrideTools === void 0 && mcpConnection ? new Set(Object.keys(mcpConnection.tools)) : /* @__PURE__ */ new Set();
|
|
15328
|
+
const generatedMcpToolAliases = buildMcpToolNameSeparatorAliases(mcpToolNames, mcpServers, mcpToolNameSeparator);
|
|
15329
|
+
const baseToolAliases = generatedMcpToolAliases ? {
|
|
15330
|
+
...generatedMcpToolAliases,
|
|
15331
|
+
...toolAliases ?? {}
|
|
15332
|
+
} : toolAliases;
|
|
15333
|
+
const sanitizedWireAliases = buildSanitizedWireAliases(mcpToolNames, baseToolAliases);
|
|
15334
|
+
const effectiveToolAliases = sanitizedWireAliases ? {
|
|
15335
|
+
...baseToolAliases ?? {},
|
|
15336
|
+
...sanitizedWireAliases
|
|
15337
|
+
} : baseToolAliases;
|
|
15338
|
+
const transformedToolCatalog = applyToolCatalogTransforms(runOverrideTools === void 0 && !!resolvedSkills && resolvedSkills.length > 0 && skillsConfig?.tool !== false ? {
|
|
15339
|
+
skills_use: createSkillsUseTool({
|
|
15340
|
+
catalog: resolvedSkills,
|
|
15341
|
+
state: skillActivationState,
|
|
15342
|
+
hooks,
|
|
15343
|
+
allowShellInterpolation: skillsConfig?.allowShellInterpolation,
|
|
15344
|
+
approveShellInterpolation: skillsConfig?.approveShellInterpolation
|
|
15345
|
+
}),
|
|
15346
|
+
skills_read: createSkillsReadTool({
|
|
15347
|
+
catalog: resolvedSkills,
|
|
15348
|
+
state: skillActivationState
|
|
15349
|
+
}),
|
|
15350
|
+
skills_run_script: createSkillsRunScriptTool({
|
|
15351
|
+
catalog: resolvedSkills,
|
|
15352
|
+
state: skillActivationState,
|
|
15353
|
+
scriptTimeoutMs: skillsConfig?.scriptTimeoutMs
|
|
15354
|
+
}),
|
|
15355
|
+
...runBaseTools
|
|
15356
|
+
} : runBaseTools, toolCatalogTransforms, "run", mcpToolNames);
|
|
15357
|
+
const tools = {};
|
|
15358
|
+
for (const tool of Object.values(transformedToolCatalog)) tools[tool.spec.name] = tool;
|
|
15359
|
+
if (tools.shell === shell) {
|
|
15360
|
+
const detachedTasks = resolveDetachedTasksCapability(executionContext);
|
|
15361
|
+
const bgDisabled = behavior.disableBackgroundTasks === true || behavior.disableBackgroundTasks === "non-durable" && detachedTasks !== "durable";
|
|
15362
|
+
tools.shell = createShellTool({
|
|
15363
|
+
allowBackground: typeof behavior.tasksDir === "string" && behavior.tasksDir.length > 0 && !bgDisabled && detachedTasks !== "none",
|
|
15364
|
+
registeredCanonicals: new Set(Object.keys(tools)),
|
|
15365
|
+
...effectiveToolAliases ? { toolAliases: effectiveToolAliases } : {}
|
|
15366
|
+
});
|
|
15367
|
+
}
|
|
15368
|
+
return {
|
|
15369
|
+
tools,
|
|
15370
|
+
mcpToolNames,
|
|
15371
|
+
toolAliases: effectiveToolAliases
|
|
15372
|
+
};
|
|
15373
|
+
}
|
|
15374
|
+
/**
|
|
14431
15375
|
* Partition a tool registry into eager and lazy buckets.
|
|
14432
15376
|
*
|
|
14433
15377
|
* Native + skill tools always end up in the eager bucket — only MCP tools
|
|
@@ -15008,6 +15952,7 @@ function createAgent(rawOptions) {
|
|
|
15008
15952
|
const externalSignal = options.signal;
|
|
15009
15953
|
running = true;
|
|
15010
15954
|
toolCancels.clearPending();
|
|
15955
|
+
const perRunUnregisters = [];
|
|
15011
15956
|
try {
|
|
15012
15957
|
abortController = new AbortController();
|
|
15013
15958
|
idlePromise = new Promise((resolve) => {
|
|
@@ -15054,10 +15999,9 @@ function createAgent(rawOptions) {
|
|
|
15054
15999
|
externalSignal.addEventListener("abort", externalAbortListener, { once: true });
|
|
15055
16000
|
}
|
|
15056
16001
|
const childrenStats = [];
|
|
15057
|
-
|
|
16002
|
+
perRunUnregisters.push(hooks.hook("spawn:complete", (ctx) => {
|
|
15058
16003
|
childrenStats.push(ctx);
|
|
15059
|
-
});
|
|
15060
|
-
const perRunUnregisters = [];
|
|
16004
|
+
}));
|
|
15061
16005
|
if (options.hooks) for (const [event, handler] of Object.entries(options.hooks)) {
|
|
15062
16006
|
if (!isKnownHookEvent(event)) throw new Error(`Unknown hook event "${event}" passed to run(). See AgentHooks for valid events.`);
|
|
15063
16007
|
const handlerList = Array.isArray(handler) ? handler : [handler];
|
|
@@ -15093,86 +16037,37 @@ function createAgent(rawOptions) {
|
|
|
15093
16037
|
}
|
|
15094
16038
|
const thinking = options.thinking ?? "off";
|
|
15095
16039
|
const model = targets[0].model;
|
|
15096
|
-
if (runProvider.meta
|
|
15097
|
-
|
|
16040
|
+
if (runProvider.meta.clearsContextServerSide) resolvedBehavior.dedupReads = false;
|
|
16041
|
+
Object.freeze(resolvedBehavior);
|
|
16042
|
+
const { thinkingOverrunPolicy, thinkingBudget, modelOptions: behaviorModelOptions, compactKeepTurns, writeFileExistingPolicy, textToolCallRescue, unknownToolRecovery, progressNudges, dedupTools, toolBudgets, repeatGuard, autoCompact, toolDisclosure, mcpToolNameSeparator, toolSearch, surfaceMcpInstructions, persistTurns } = resolvedBehavior;
|
|
15098
16043
|
const modelOptions = options.modelOptions ?? behaviorModelOptions;
|
|
15099
16044
|
let system = options.system || agentSystem || "You are a helpful assistant.";
|
|
15100
16045
|
const baseSystemForBreakdown = renderSystemForWire(system);
|
|
15101
16046
|
if (skillsCatalog) system = appendStaticSection(system, skillsCatalog);
|
|
15102
|
-
const
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
|
|
15115
|
-
|
|
15116
|
-
}
|
|
15117
|
-
const
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
catalog: resolvedSkills,
|
|
15127
|
-
state: skillActivationState
|
|
15128
|
-
}),
|
|
15129
|
-
skills_run_script: createSkillsRunScriptTool({
|
|
15130
|
-
catalog: resolvedSkills,
|
|
15131
|
-
state: skillActivationState,
|
|
15132
|
-
scriptTimeoutMs: skillsConfig?.scriptTimeoutMs
|
|
15133
|
-
}),
|
|
15134
|
-
...runBaseTools
|
|
15135
|
-
} : runBaseTools, toolCatalogTransforms, "run", mcpToolNames);
|
|
15136
|
-
const toolsPreSearch = {};
|
|
15137
|
-
for (const tool of Object.values(transformedToolCatalog)) toolsPreSearch[tool.spec.name] = tool;
|
|
15138
|
-
if (toolsPreSearch.shell === shell) {
|
|
15139
|
-
const detachedTasks = resolveDetachedTasksCapability(executionContext);
|
|
15140
|
-
const bgDisabled = resolvedBehavior?.disableBackgroundTasks === true || resolvedBehavior?.disableBackgroundTasks === "non-durable" && detachedTasks !== "durable";
|
|
15141
|
-
toolsPreSearch.shell = createShellTool({
|
|
15142
|
-
allowBackground: typeof resolvedBehavior?.tasksDir === "string" && resolvedBehavior.tasksDir.length > 0 && !bgDisabled && detachedTasks !== "none",
|
|
15143
|
-
registeredCanonicals: new Set(Object.keys(toolsPreSearch)),
|
|
15144
|
-
...effectiveToolAliases ? { toolAliases: effectiveToolAliases } : {}
|
|
15145
|
-
});
|
|
15146
|
-
}
|
|
15147
|
-
const disclosure = partitionToolDisclosure(toolsPreSearch, mcpToolNames, mcpServers, toolDisclosure, effectiveToolAliases);
|
|
15148
|
-
const unlocked = new Set(disclosure.eagerCanonicalNames);
|
|
15149
|
-
const initialUnlocked = new Set(disclosure.eagerCanonicalNames);
|
|
15150
|
-
const dynamicUnlockOrder = [];
|
|
15151
|
-
const dynamicUnlockSeen = /* @__PURE__ */ new Set();
|
|
15152
|
-
function recordDynamicUnlock(canonical) {
|
|
15153
|
-
if (initialUnlocked.has(canonical)) return;
|
|
15154
|
-
if (dynamicUnlockSeen.has(canonical)) return;
|
|
15155
|
-
dynamicUnlockSeen.add(canonical);
|
|
15156
|
-
unlocked.add(canonical);
|
|
15157
|
-
dynamicUnlockOrder.push(canonical);
|
|
15158
|
-
}
|
|
15159
|
-
const hostDefinedToolSearch = !!toolsPreSearch.tool_search;
|
|
15160
|
-
const shouldInjectToolSearch = options.tools === void 0 && (toolDisclosure === "lazy" || disclosure.lazyEntries.length > 0) && toolSearch?.tool !== false && !hostDefinedToolSearch;
|
|
15161
|
-
let tools = toolsPreSearch;
|
|
15162
|
-
if (shouldInjectToolSearch) {
|
|
15163
|
-
const toolSearchTool = createToolSearchTool({
|
|
15164
|
-
catalog: disclosure.lazyEntries,
|
|
15165
|
-
unlocked,
|
|
15166
|
-
addUnlock: recordDynamicUnlock,
|
|
15167
|
-
...toolSearch?.limit !== void 0 ? { defaultLimit: toolSearch.limit } : {}
|
|
15168
|
-
});
|
|
15169
|
-
tools = {
|
|
15170
|
-
...toolsPreSearch,
|
|
15171
|
-
[toolSearchTool.spec.name]: toolSearchTool
|
|
15172
|
-
};
|
|
15173
|
-
unlocked.add(toolSearchTool.spec.name);
|
|
15174
|
-
initialUnlocked.add(toolSearchTool.spec.name);
|
|
15175
|
-
}
|
|
16047
|
+
const { tools: toolsPreSearch, mcpToolNames, toolAliases: effectiveToolAliases } = buildRunToolRegistry({
|
|
16048
|
+
runOverrideTools: options.tools,
|
|
16049
|
+
sourceTools,
|
|
16050
|
+
mcpConnection,
|
|
16051
|
+
mcpServers,
|
|
16052
|
+
mcpToolNameSeparator,
|
|
16053
|
+
toolAliases,
|
|
16054
|
+
resolvedSkills,
|
|
16055
|
+
skillsConfig,
|
|
16056
|
+
skillActivationState,
|
|
16057
|
+
hooks,
|
|
16058
|
+
toolCatalogTransforms,
|
|
16059
|
+
executionContext,
|
|
16060
|
+
behavior: resolvedBehavior
|
|
16061
|
+
});
|
|
16062
|
+
const { disclosure, unlocked, initialUnlocked, dynamicUnlockOrder, recordUnlock: recordDynamicUnlock, tools, injectedToolSearch: shouldInjectToolSearch, hostDefinedToolSearch } = buildRunDisclosureState({
|
|
16063
|
+
tools: toolsPreSearch,
|
|
16064
|
+
mcpToolNames,
|
|
16065
|
+
mcpServers,
|
|
16066
|
+
toolDisclosure,
|
|
16067
|
+
toolSearch,
|
|
16068
|
+
toolAliases: effectiveToolAliases,
|
|
16069
|
+
hasRunToolOverride: options.tools !== void 0
|
|
16070
|
+
});
|
|
15176
16071
|
const discoveryToolName = shouldInjectToolSearch ? "tool_search" : hostDefinedToolSearch ? effectiveToolAliases?.tool_search ?? "tool_search" : null;
|
|
15177
16072
|
let searchableCatalogText;
|
|
15178
16073
|
if (disclosure.lazyEntries.length > 0) {
|
|
@@ -15257,76 +16152,31 @@ function createAgent(rawOptions) {
|
|
|
15257
16152
|
});
|
|
15258
16153
|
}
|
|
15259
16154
|
const turns = [];
|
|
15260
|
-
const
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
} else if (isResume) {
|
|
15266
|
-
const childRunIds = childRunIdSet(session);
|
|
15267
|
-
const resumed = childRunIds.size === 0 ? session.turns : session.turns.filter((t) => !t.runId || !childRunIds.has(t.runId));
|
|
15268
|
-
seededTurns = resumeFilteredTurns && resumed === session.turns ? resumeFilteredTurns : filterUnresolvedToolUses(resumed);
|
|
15269
|
-
}
|
|
16155
|
+
const seededTurns = selectSeedTurns({
|
|
16156
|
+
session,
|
|
16157
|
+
options,
|
|
16158
|
+
resumeFilteredTurns
|
|
16159
|
+
});
|
|
15270
16160
|
if (seededTurns) {
|
|
15271
16161
|
turns.push(...seededTurns);
|
|
15272
|
-
if (shouldInjectToolSearch && disclosure.lazyEntries.length > 0)
|
|
15273
|
-
const resolvedCallIds = /* @__PURE__ */ new Set();
|
|
15274
|
-
for (const turn of seededTurns) for (const block of turn.content) if (block.type === "tool_result" && !block.isError) resolvedCallIds.add(block.callId);
|
|
15275
|
-
for (const turn of seededTurns) for (const block of turn.content) {
|
|
15276
|
-
if (block.type !== "tool_call") continue;
|
|
15277
|
-
if (block.name !== "tool_search") continue;
|
|
15278
|
-
if (!resolvedCallIds.has(block.id)) continue;
|
|
15279
|
-
applyToolSearchToUnlocked(disclosure.lazyEntries, block.input, unlocked, toolSearch?.limit, recordDynamicUnlock);
|
|
15280
|
-
}
|
|
15281
|
-
}
|
|
16162
|
+
if (shouldInjectToolSearch && disclosure.lazyEntries.length > 0) replayToolSearchUnlocks(seededTurns, disclosure.lazyEntries, unlocked, toolSearch?.limit, recordDynamicUnlock);
|
|
15282
16163
|
}
|
|
15283
16164
|
const runTurnStart = turns.length;
|
|
15284
16165
|
if (options.system) await hooks.callHook("system:before", { system: options.system });
|
|
15285
|
-
const
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
}
|
|
15299
|
-
reconciled.set(entry.taskId, {
|
|
15300
|
-
taskId: entry.taskId,
|
|
15301
|
-
status: entry.status,
|
|
15302
|
-
exitCode: entry.exitCode ?? 0,
|
|
15303
|
-
...entry.signal ? { signal: entry.signal } : {},
|
|
15304
|
-
outputPath: entry.outputPath,
|
|
15305
|
-
durationMs: Math.max(0, (entry.endedAt ?? entry.startedAt) - entry.startedAt),
|
|
15306
|
-
command: entry.command
|
|
15307
|
-
});
|
|
15308
|
-
}
|
|
15309
|
-
} catch (err) {
|
|
15310
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/agent] background reconcile failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
15311
|
-
}
|
|
15312
|
-
const reconcileCtx = { exits: [...reconciled.values()] };
|
|
15313
|
-
if (reconcileCtx.exits.length > 0) await hooks.callHook("background:reconcile", reconcileCtx);
|
|
15314
|
-
for (const taskId of reconciled.keys()) {
|
|
15315
|
-
notifiedTaskIds.add(taskId);
|
|
15316
|
-
pendingStallNotifications.delete(taskId);
|
|
15317
|
-
}
|
|
15318
|
-
const drainedNotifications = [];
|
|
15319
|
-
for (const notif of reconcileCtx.exits) drainedNotifications.push({
|
|
15320
|
-
type: "text",
|
|
15321
|
-
text: renderTaskNotificationXml(notif)
|
|
16166
|
+
const drainedNotifications = await drainTaskNotifications({
|
|
16167
|
+
state: {
|
|
16168
|
+
pending: pendingTaskNotifications,
|
|
16169
|
+
pendingStalls: pendingStallNotifications,
|
|
16170
|
+
notified: notifiedTaskIds
|
|
16171
|
+
},
|
|
16172
|
+
executionContext,
|
|
16173
|
+
executionHandle,
|
|
16174
|
+
behavior: resolvedBehavior,
|
|
16175
|
+
journalReconcile: options.journalReconcile,
|
|
16176
|
+
session,
|
|
16177
|
+
turns,
|
|
16178
|
+
hooks
|
|
15322
16179
|
});
|
|
15323
|
-
if (pendingStallNotifications.size > 0) {
|
|
15324
|
-
for (const stall of pendingStallNotifications.values()) drainedNotifications.push({
|
|
15325
|
-
type: "text",
|
|
15326
|
-
text: renderTaskStallXml(stall)
|
|
15327
|
-
});
|
|
15328
|
-
pendingStallNotifications.clear();
|
|
15329
|
-
}
|
|
15330
16180
|
let lastPersistedTurnCount = turns.length;
|
|
15331
16181
|
const promptParts = canonicalizePrompt(options.prompt);
|
|
15332
16182
|
if (promptParts || drainedNotifications.length > 0) {
|
|
@@ -15373,8 +16223,10 @@ function createAgent(rawOptions) {
|
|
|
15373
16223
|
});
|
|
15374
16224
|
}
|
|
15375
16225
|
const perTurnSync = session && persistTurns !== false;
|
|
15376
|
-
|
|
15377
|
-
|
|
16226
|
+
if (perTurnSync) {
|
|
16227
|
+
perRunUnregisters.push(hooks.hook("turn:after", persistPendingTurns));
|
|
16228
|
+
perRunUnregisters.push(hooks.hook("tool-results:after", persistPendingTurns));
|
|
16229
|
+
}
|
|
15378
16230
|
async function flushTurns(opts = {}) {
|
|
15379
16231
|
if (!session) return;
|
|
15380
16232
|
if (opts.failureFallback) {
|
|
@@ -15419,7 +16271,7 @@ function createAgent(rawOptions) {
|
|
|
15419
16271
|
const run = session?.runs.find((r) => r.id === runId);
|
|
15420
16272
|
if (run) run.endedAt = await clock.now();
|
|
15421
16273
|
}
|
|
15422
|
-
|
|
16274
|
+
perRunUnregisters.push(installAllowedToolsGate(hooks, skillActivationState));
|
|
15423
16275
|
const getExecutionHandle = () => executionHandle ?? void 0;
|
|
15424
16276
|
perRunUnregisters.push(installWriteFileExistingPolicy(hooks, writeFileExistingPolicy, {
|
|
15425
16277
|
execution: executionContext,
|
|
@@ -15430,10 +16282,10 @@ function createAgent(rawOptions) {
|
|
|
15430
16282
|
advertisedToolNames,
|
|
15431
16283
|
enqueueNudge: (message) => nonInterruptingNudgeQueue.push(message)
|
|
15432
16284
|
}));
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
16285
|
+
perRunUnregisters.push(installToolBudgetsGate(hooks, () => toolBudgets, (msg) => nonInterruptingNudgeQueue.push(msg)));
|
|
16286
|
+
perRunUnregisters.push(installRepeatGuard(hooks, () => repeatGuard, () => abortController?.abort()));
|
|
16287
|
+
perRunUnregisters.push(installDedupToolsGate(hooks, () => dedupTools, () => session ?? void 0));
|
|
16288
|
+
perRunUnregisters.push(installLazyDisclosureHydrationGate(hooks, {
|
|
15437
16289
|
enabled: shouldInjectToolSearch && disclosure.lazyEntries.length > 0,
|
|
15438
16290
|
session: session ?? void 0,
|
|
15439
16291
|
lazyCanonicalNames: disclosure.lazyCanonicalNames,
|
|
@@ -15441,20 +16293,20 @@ function createAgent(rawOptions) {
|
|
|
15441
16293
|
unlocked,
|
|
15442
16294
|
recordUnlock: recordDynamicUnlock,
|
|
15443
16295
|
defaultLimit: toolSearch?.limit
|
|
15444
|
-
});
|
|
15445
|
-
|
|
15446
|
-
|
|
16296
|
+
}));
|
|
16297
|
+
perRunUnregisters.push(installLazyDisclosureGate(hooks, disclosure.lazyCanonicalNames, unlocked, discoveryToolName));
|
|
16298
|
+
perRunUnregisters.push(installReadBeforeEditHydrationGate(hooks, resolvedBehavior.requireReadBeforeEdit === true, {
|
|
15447
16299
|
session: session ?? void 0,
|
|
15448
16300
|
getHandle: () => executionHandle,
|
|
15449
16301
|
execution: executionContext,
|
|
15450
16302
|
aliasMaps,
|
|
15451
16303
|
defaultLineNumbers: resolvedBehavior.readLineNumbers ?? true
|
|
15452
|
-
});
|
|
15453
|
-
|
|
16304
|
+
}));
|
|
16305
|
+
perRunUnregisters.push(installSkillsActivationHydrationGate(hooks, {
|
|
15454
16306
|
session: session ?? void 0,
|
|
15455
16307
|
getResolvedSkills: () => resolvedSkills,
|
|
15456
16308
|
state: skillActivationState
|
|
15457
|
-
});
|
|
16309
|
+
}));
|
|
15458
16310
|
perRunUnregisters.push(installThinkingOverrunPolicy(hooks, thinkingOverrunPolicy, {
|
|
15459
16311
|
baseBudget: thinkingBudget,
|
|
15460
16312
|
enqueueNudge: (message) => nonInterruptingNudgeQueue.push(message)
|
|
@@ -15473,25 +16325,100 @@ function createAgent(rawOptions) {
|
|
|
15473
16325
|
turnUsage: []
|
|
15474
16326
|
};
|
|
15475
16327
|
const wallStartMs = Date.now();
|
|
15476
|
-
const providerContextWindow =
|
|
16328
|
+
const providerContextWindow = runProvider.meta.contextWindow;
|
|
15477
16329
|
const resolvedAutoCompact = resolveAutoCompact(autoCompact, {
|
|
15478
16330
|
...providerContextWindow !== void 0 ? { fallbackContextWindow: providerContextWindow } : {},
|
|
15479
16331
|
...compactKeepTurns !== void 0 ? { compactKeepTurns } : {}
|
|
15480
16332
|
});
|
|
16333
|
+
/**
|
|
16334
|
+
* Loop-view stats synthesized from the live usage mirror.
|
|
16335
|
+
*
|
|
16336
|
+
* Used by every exit that did NOT get a clean `AgentStats` back from
|
|
16337
|
+
* `runLoop` (mid-stream abort, budget breach, hard failure): the mirror
|
|
16338
|
+
* carries the usage of every turn that completed before the throw, so the
|
|
16339
|
+
* persisted record and `agent:done` report real spend instead of zeros.
|
|
16340
|
+
* Wall time comes from `wallStartMs` (a real clock) rather than the
|
|
16341
|
+
* possibly-journaled `runStartedAt`, matching `runLoop`'s own accounting.
|
|
16342
|
+
*/
|
|
16343
|
+
function mirroredLoopStats() {
|
|
16344
|
+
return {
|
|
16345
|
+
totalIn: usageMirror.totalIn,
|
|
16346
|
+
totalOut: usageMirror.totalOut,
|
|
16347
|
+
totalCacheRead: usageMirror.totalCacheRead,
|
|
16348
|
+
totalCacheCreation: usageMirror.totalCacheCreation,
|
|
16349
|
+
turns: usageMirror.turns,
|
|
16350
|
+
elapsed: Date.now() - wallStartMs,
|
|
16351
|
+
turnUsage: usageMirror.turnUsage,
|
|
16352
|
+
...usageMirror.cost > 0 ? { cost: usageMirror.cost } : {}
|
|
16353
|
+
};
|
|
16354
|
+
}
|
|
16355
|
+
/**
|
|
16356
|
+
* The single exit path for a run. Every terminal branch — completed,
|
|
16357
|
+
* clean-break abort, mid-stream abort, budget breach, hard error — routes
|
|
16358
|
+
* through here so all five report identically: children folded into the
|
|
16359
|
+
* returned totals, session ledger written from the loop-only view, run row
|
|
16360
|
+
* stamped, `session:end` fired, then `agent:done` with the final stats.
|
|
16361
|
+
*
|
|
16362
|
+
* Callers that must propagate an error still `throw` after awaiting this;
|
|
16363
|
+
* the finalization itself is outcome-independent.
|
|
16364
|
+
*/
|
|
16365
|
+
async function finishRun(disposition) {
|
|
16366
|
+
const { kind, loopStats, parentCost } = disposition;
|
|
16367
|
+
let childrenIn = 0;
|
|
16368
|
+
let childrenOut = 0;
|
|
16369
|
+
let childrenCost = 0;
|
|
16370
|
+
let childrenCacheRead = 0;
|
|
16371
|
+
let childrenCacheCreation = 0;
|
|
16372
|
+
for (const c of childrenStats) {
|
|
16373
|
+
childrenIn += c.stats.totalIn;
|
|
16374
|
+
childrenOut += c.stats.totalOut;
|
|
16375
|
+
childrenCost += c.stats.cost ?? 0;
|
|
16376
|
+
childrenCacheRead += c.stats.totalCacheRead;
|
|
16377
|
+
childrenCacheCreation += c.stats.totalCacheCreation;
|
|
16378
|
+
}
|
|
16379
|
+
const cumulativeCost = parentCost + childrenCost;
|
|
16380
|
+
const finalStats = {
|
|
16381
|
+
...loopStats,
|
|
16382
|
+
totalIn: loopStats.totalIn + childrenIn,
|
|
16383
|
+
totalOut: loopStats.totalOut + childrenOut,
|
|
16384
|
+
totalCacheRead: loopStats.totalCacheRead + childrenCacheRead,
|
|
16385
|
+
totalCacheCreation: loopStats.totalCacheCreation + childrenCacheCreation,
|
|
16386
|
+
...cumulativeCost > 0 ? { cost: cumulativeCost } : {},
|
|
16387
|
+
children: childrenStats.length > 0 ? childrenStats : void 0
|
|
16388
|
+
};
|
|
16389
|
+
if (session) {
|
|
16390
|
+
const ledger = {
|
|
16391
|
+
turns: loopStats.turns,
|
|
16392
|
+
tokensIn: loopStats.totalIn,
|
|
16393
|
+
tokensOut: loopStats.totalOut,
|
|
16394
|
+
turnUsage: loopStats.turnUsage,
|
|
16395
|
+
...parentCost > 0 ? { cost: parentCost } : {}
|
|
16396
|
+
};
|
|
16397
|
+
if (disposition.kind === "completed") session.completeRun(runId, ledger);
|
|
16398
|
+
else if (disposition.kind === "aborted") session.abortRun(runId, ledger);
|
|
16399
|
+
else session.errorRun(runId, disposition.failure, ledger);
|
|
16400
|
+
}
|
|
16401
|
+
await stampSessionRunEnd();
|
|
16402
|
+
await finalizeSession(kind);
|
|
16403
|
+
await settleWithinTimeout("hook agent:done", hooks.callHook("agent:done", finalStats), hookTimeoutMs);
|
|
16404
|
+
return finalStats;
|
|
16405
|
+
}
|
|
15481
16406
|
try {
|
|
15482
16407
|
await toolStateController.hydrate();
|
|
15483
16408
|
const stats = await runLoop({
|
|
15484
16409
|
provider: runProvider,
|
|
15485
16410
|
targets,
|
|
15486
16411
|
hooks,
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
16412
|
+
behavior: resolvedBehavior,
|
|
16413
|
+
inheritance: {
|
|
16414
|
+
...agentName !== void 0 ? { name: agentName } : {},
|
|
16415
|
+
...agentSystem !== void 0 ? { system: agentSystem } : {},
|
|
16416
|
+
tools: sourceTools,
|
|
16417
|
+
...effectiveToolAliases !== void 0 ? { toolAliases: effectiveToolAliases } : {},
|
|
16418
|
+
...allMcpServers.length > 0 ? { mcpServers: allMcpServers } : {},
|
|
16419
|
+
mcpConnection: () => mcpConnection,
|
|
16420
|
+
...agentSkills !== void 0 ? { skills: agentSkills } : {}
|
|
16421
|
+
},
|
|
15495
16422
|
tools,
|
|
15496
16423
|
formattedTools,
|
|
15497
16424
|
rebuildFormattedTools: buildFormattedTools,
|
|
@@ -15499,10 +16426,6 @@ function createAgent(rawOptions) {
|
|
|
15499
16426
|
model,
|
|
15500
16427
|
system,
|
|
15501
16428
|
thinking,
|
|
15502
|
-
...maxConcurrentTools !== void 0 ? { maxConcurrentTools } : {},
|
|
15503
|
-
...toolBatchExecutor !== void 0 ? { toolBatchExecutor } : {},
|
|
15504
|
-
...toolBatchTimeoutMs !== void 0 ? { toolBatchTimeoutMs } : {},
|
|
15505
|
-
...shouldRethrowToolError !== void 0 ? { shouldRethrowToolError } : {},
|
|
15506
16429
|
signal: abortController.signal,
|
|
15507
16430
|
execution: executionContext,
|
|
15508
16431
|
handle: executionHandle,
|
|
@@ -15513,53 +16436,16 @@ function createAgent(rawOptions) {
|
|
|
15513
16436
|
runId,
|
|
15514
16437
|
generateTurnId: mintTurnId,
|
|
15515
16438
|
clock,
|
|
15516
|
-
|
|
15517
|
-
...
|
|
15518
|
-
...maxTurnsWarningMessage !== void 0 ? { maxTurnsWarningMessage } : {},
|
|
15519
|
-
...maxCostUsd !== void 0 ? { maxCostUsd } : {},
|
|
15520
|
-
...maxTotalTokens !== void 0 ? { maxTotalTokens } : {},
|
|
15521
|
-
...maxWallMs !== void 0 ? { maxWallMs } : {},
|
|
15522
|
-
...providerStreamStartTimeoutMs !== void 0 ? { providerStreamStartTimeoutMs } : {},
|
|
15523
|
-
...providerStreamTimeoutMs !== void 0 ? { providerStreamTimeoutMs } : {},
|
|
15524
|
-
...providerStreamIdleTimeoutMs !== void 0 ? { providerStreamIdleTimeoutMs } : {},
|
|
15525
|
-
...resolvedBehavior.hookTimeoutMs !== void 0 ? { hookTimeoutMs: resolvedBehavior.hookTimeoutMs } : {},
|
|
15526
|
-
...retry !== void 0 ? { retry } : {},
|
|
15527
|
-
maxTokens,
|
|
15528
|
-
...modelCatalog !== void 0 ? { modelCatalog } : {},
|
|
16439
|
+
...modelOptions ? { modelOptions } : {},
|
|
16440
|
+
...resolvedAutoCompact ? { autoCompact: resolvedAutoCompact } : {},
|
|
15529
16441
|
...session ? { session } : {},
|
|
15530
16442
|
...agentReadState ? { readState: agentReadState } : {},
|
|
15531
16443
|
toolState: toolStateController,
|
|
15532
16444
|
...options.parentRunId ? { parentRunId: options.parentRunId } : {},
|
|
15533
16445
|
...options.parentCallId ? { parentCallId: options.parentCallId } : {},
|
|
15534
16446
|
depth: runDepth,
|
|
15535
|
-
thinkingBudget,
|
|
15536
|
-
...taskBudget !== void 0 ? { taskBudget } : {},
|
|
15537
|
-
...effortHint !== void 0 ? { effortHint } : {},
|
|
15538
|
-
...modelOptions ? { modelOptions } : {},
|
|
15539
|
-
schema,
|
|
15540
|
-
cache,
|
|
15541
|
-
...cacheTtl !== void 0 ? { cacheTtl } : {},
|
|
15542
|
-
toolOutputBudget,
|
|
15543
|
-
...toolOutputBudgetExcludeTools !== void 0 ? { toolOutputBudgetExcludeTools } : {},
|
|
15544
|
-
compactStrategy,
|
|
15545
|
-
compactThreshold,
|
|
15546
|
-
compactKeepTurns,
|
|
15547
|
-
...elideStaleReads !== void 0 ? { elideStaleReads } : {},
|
|
15548
|
-
...mediaKeepTurns !== void 0 ? { mediaKeepTurns } : {},
|
|
15549
|
-
...resolvedAutoCompact ? { autoCompact: resolvedAutoCompact } : {},
|
|
15550
16447
|
...perTurnSync ? { persistTurns: persistPendingTurns } : {},
|
|
15551
16448
|
getActiveSkills: () => skillActivationState.active(),
|
|
15552
|
-
...thinkingDecay !== void 0 ? { thinkingDecay } : {},
|
|
15553
|
-
...persistThreshold !== void 0 ? { persistThreshold } : {},
|
|
15554
|
-
...persistExcludeTools !== void 0 ? { persistExcludeTools } : {},
|
|
15555
|
-
...persistDir !== void 0 ? { persistDir } : {},
|
|
15556
|
-
...persistMaxBytes !== void 0 ? { persistMaxBytes } : {},
|
|
15557
|
-
...strictToolPairing ? { strictToolPairing: true } : {},
|
|
15558
|
-
...maxPairingRepairsPerTurn !== void 0 ? { maxPairingRepairsPerTurn } : {},
|
|
15559
|
-
...maxConsecutivePauseTurns !== void 0 ? { maxConsecutivePauseTurns } : {},
|
|
15560
|
-
...maxConsecutiveRefusalResumes !== void 0 ? { maxConsecutiveRefusalResumes } : {},
|
|
15561
|
-
...resumeOnMaxTokens !== void 0 ? { resumeOnMaxTokens } : {},
|
|
15562
|
-
...maxConsecutiveLengthResumes !== void 0 ? { maxConsecutiveLengthResumes } : {},
|
|
15563
16449
|
providerName: runProvider.name,
|
|
15564
16450
|
runStartMs,
|
|
15565
16451
|
runToolCounts: {},
|
|
@@ -15567,53 +16453,12 @@ function createAgent(rawOptions) {
|
|
|
15567
16453
|
usageMirror
|
|
15568
16454
|
});
|
|
15569
16455
|
const parentTurnCost = stats.turnUsage?.reduce((sum, t) => sum + (t.cost ?? 0), 0) ?? 0;
|
|
15570
|
-
let childrenIn = 0;
|
|
15571
|
-
let childrenOut = 0;
|
|
15572
|
-
let childrenCost = 0;
|
|
15573
|
-
let childrenCacheRead = 0;
|
|
15574
|
-
let childrenCacheCreation = 0;
|
|
15575
|
-
for (const c of childrenStats) {
|
|
15576
|
-
childrenIn += c.stats.totalIn;
|
|
15577
|
-
childrenOut += c.stats.totalOut;
|
|
15578
|
-
childrenCost += c.stats.cost ?? 0;
|
|
15579
|
-
childrenCacheRead += c.stats.totalCacheRead;
|
|
15580
|
-
childrenCacheCreation += c.stats.totalCacheCreation;
|
|
15581
|
-
}
|
|
15582
|
-
const cumulativeCost = parentTurnCost + childrenCost;
|
|
15583
|
-
const finalStats = {
|
|
15584
|
-
...stats,
|
|
15585
|
-
totalIn: stats.totalIn + childrenIn,
|
|
15586
|
-
totalOut: stats.totalOut + childrenOut,
|
|
15587
|
-
totalCacheRead: stats.totalCacheRead + childrenCacheRead,
|
|
15588
|
-
totalCacheCreation: stats.totalCacheCreation + childrenCacheCreation,
|
|
15589
|
-
...cumulativeCost > 0 ? { cost: cumulativeCost } : {},
|
|
15590
|
-
children: childrenStats.length > 0 ? childrenStats : void 0
|
|
15591
|
-
};
|
|
15592
16456
|
await flushTurns();
|
|
15593
|
-
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
|
|
15597
|
-
tokensOut: stats.totalOut,
|
|
15598
|
-
turnUsage: stats.turnUsage,
|
|
15599
|
-
cost: parentTurnCost > 0 ? parentTurnCost : void 0
|
|
15600
|
-
});
|
|
15601
|
-
await stampSessionRunEnd();
|
|
15602
|
-
await finalizeSession("aborted");
|
|
15603
|
-
await settleWithinTimeout("hook agent:done", hooks.callHook("agent:done", finalStats), hookTimeoutMs);
|
|
15604
|
-
return finalStats;
|
|
15605
|
-
}
|
|
15606
|
-
session?.completeRun(runId, {
|
|
15607
|
-
turns: stats.turns,
|
|
15608
|
-
tokensIn: stats.totalIn,
|
|
15609
|
-
tokensOut: stats.totalOut,
|
|
15610
|
-
turnUsage: stats.turnUsage,
|
|
15611
|
-
cost: parentTurnCost > 0 ? parentTurnCost : void 0
|
|
16457
|
+
return await finishRun({
|
|
16458
|
+
kind: abortController.signal.aborted ? "aborted" : "completed",
|
|
16459
|
+
loopStats: stats,
|
|
16460
|
+
parentCost: parentTurnCost
|
|
15612
16461
|
});
|
|
15613
|
-
await stampSessionRunEnd();
|
|
15614
|
-
await finalizeSession("completed");
|
|
15615
|
-
await settleWithinTimeout("hook agent:done", hooks.callHook("agent:done", finalStats), hookTimeoutMs);
|
|
15616
|
-
return finalStats;
|
|
15617
16462
|
} catch (err) {
|
|
15618
16463
|
await flushTurns({ failureFallback: true });
|
|
15619
16464
|
if (abortController.signal.aborted) {
|
|
@@ -15622,84 +16467,39 @@ function createAgent(rawOptions) {
|
|
|
15622
16467
|
...runId ? { runId } : {},
|
|
15623
16468
|
...typeof abortReason === "string" && abortReason.length > 0 ? { reason: abortReason } : {}
|
|
15624
16469
|
});
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
turnUsage: usageMirror.turnUsage,
|
|
15630
|
-
cost: usageMirror.cost > 0 ? usageMirror.cost : void 0
|
|
16470
|
+
return await finishRun({
|
|
16471
|
+
kind: "aborted",
|
|
16472
|
+
loopStats: mirroredLoopStats(),
|
|
16473
|
+
parentCost: usageMirror.cost
|
|
15631
16474
|
});
|
|
15632
|
-
await stampSessionRunEnd();
|
|
15633
|
-
await finalizeSession("aborted");
|
|
15634
|
-
const stats = {
|
|
15635
|
-
totalIn: usageMirror.totalIn,
|
|
15636
|
-
totalOut: usageMirror.totalOut,
|
|
15637
|
-
totalCacheRead: usageMirror.totalCacheRead,
|
|
15638
|
-
totalCacheCreation: usageMirror.totalCacheCreation,
|
|
15639
|
-
turns: usageMirror.turns,
|
|
15640
|
-
elapsed: Date.now() - wallStartMs,
|
|
15641
|
-
turnUsage: usageMirror.turnUsage,
|
|
15642
|
-
...usageMirror.cost > 0 ? { cost: usageMirror.cost } : {}
|
|
15643
|
-
};
|
|
15644
|
-
await settleWithinTimeout("hook agent:done", hooks.callHook("agent:done", stats), hookTimeoutMs);
|
|
15645
|
-
return stats;
|
|
15646
16475
|
}
|
|
15647
16476
|
if (err instanceof AgentBudgetExceededError) {
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
turnUsage: usageMirror.turnUsage,
|
|
15653
|
-
cost: usageMirror.cost > 0 ? usageMirror.cost : void 0
|
|
16477
|
+
await finishRun({
|
|
16478
|
+
kind: "aborted",
|
|
16479
|
+
loopStats: mirroredLoopStats(),
|
|
16480
|
+
parentCost: usageMirror.cost
|
|
15654
16481
|
});
|
|
15655
|
-
await stampSessionRunEnd();
|
|
15656
|
-
await finalizeSession("aborted");
|
|
15657
|
-
await settleWithinTimeout("hook agent:done", hooks.callHook("agent:done", {
|
|
15658
|
-
totalIn: usageMirror.totalIn,
|
|
15659
|
-
totalOut: usageMirror.totalOut,
|
|
15660
|
-
totalCacheRead: usageMirror.totalCacheRead,
|
|
15661
|
-
totalCacheCreation: usageMirror.totalCacheCreation,
|
|
15662
|
-
turns: usageMirror.turns,
|
|
15663
|
-
elapsed: Date.now() - wallStartMs,
|
|
15664
|
-
turnUsage: usageMirror.turnUsage,
|
|
15665
|
-
...usageMirror.cost > 0 ? { cost: usageMirror.cost } : {}
|
|
15666
|
-
}), hookTimeoutMs);
|
|
15667
16482
|
throw err;
|
|
15668
16483
|
}
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
totalCacheRead: usageMirror.totalCacheRead,
|
|
15676
|
-
totalCacheCreation: usageMirror.totalCacheCreation,
|
|
15677
|
-
turns: usageMirror.turns,
|
|
15678
|
-
elapsed: Date.now() - wallStartMs,
|
|
15679
|
-
turnUsage: usageMirror.turnUsage,
|
|
15680
|
-
...usageMirror.cost > 0 ? { cost: usageMirror.cost } : {}
|
|
15681
|
-
}), hookTimeoutMs);
|
|
16484
|
+
await finishRun({
|
|
16485
|
+
kind: "error",
|
|
16486
|
+
loopStats: mirroredLoopStats(),
|
|
16487
|
+
parentCost: usageMirror.cost,
|
|
16488
|
+
failure: errorMessage(err)
|
|
16489
|
+
});
|
|
15682
16490
|
throw err;
|
|
15683
16491
|
} finally {
|
|
15684
16492
|
await deactivateAllSkills();
|
|
15685
|
-
uninstallAllowedToolsGate();
|
|
15686
|
-
uninstallDedupTools();
|
|
15687
|
-
uninstallRepeatGuard();
|
|
15688
|
-
uninstallToolBudgets();
|
|
15689
|
-
uninstallLazyDisclosureHydration();
|
|
15690
|
-
uninstallLazyDisclosureGate();
|
|
15691
|
-
uninstallReadBeforeEditHydration();
|
|
15692
|
-
uninstallSkillsActivationHydration();
|
|
15693
|
-
unregisterSpawnHook();
|
|
15694
|
-
unregisterTurnSync?.();
|
|
15695
|
-
unregisterToolResultsSync?.();
|
|
15696
16493
|
for (const unregister of perRunUnregisters) unregister();
|
|
16494
|
+
perRunUnregisters.length = 0;
|
|
15697
16495
|
steeringQueue.length = 0;
|
|
15698
16496
|
nonInterruptingNudgeQueue.length = 0;
|
|
15699
16497
|
followUpQueue.length = 0;
|
|
15700
16498
|
resetRunScope();
|
|
15701
16499
|
}
|
|
15702
16500
|
} finally {
|
|
16501
|
+
for (const unregister of perRunUnregisters) unregister();
|
|
16502
|
+
perRunUnregisters.length = 0;
|
|
15703
16503
|
resetRunScope();
|
|
15704
16504
|
if (externalSignal && externalAbortListener) externalSignal.removeEventListener("abort", externalAbortListener);
|
|
15705
16505
|
}
|
|
@@ -16032,6 +16832,22 @@ function createAgent(rawOptions) {
|
|
|
16032
16832
|
wireTools
|
|
16033
16833
|
});
|
|
16034
16834
|
}
|
|
16835
|
+
async function getContextSegments(opts) {
|
|
16836
|
+
if (destroyed) return null;
|
|
16837
|
+
const assembly = lastContextAssembly ?? await buildPreRunAssembly();
|
|
16838
|
+
if (!assembly) return null;
|
|
16839
|
+
const rulesBlock = opts?.rulesBlock ?? assembly.rulesBlock;
|
|
16840
|
+
return buildContextSegments({
|
|
16841
|
+
system: assembly.system,
|
|
16842
|
+
baseSystem: assembly.baseSystem,
|
|
16843
|
+
...rulesBlock ? { rulesBlock } : {},
|
|
16844
|
+
...opts?.rulesFiles?.length ? { rulesFiles: opts.rulesFiles } : {},
|
|
16845
|
+
...opts?.systemPath !== void 0 ? { systemPath: opts.systemPath } : {},
|
|
16846
|
+
...assembly.skillsCatalog ? { skillsCatalog: assembly.skillsCatalog } : {},
|
|
16847
|
+
...assembly.mcpInstructions ? { mcpInstructions: assembly.mcpInstructions } : {},
|
|
16848
|
+
...assembly.subagentDefs ? { subagentDefs: assembly.subagentDefs } : {}
|
|
16849
|
+
});
|
|
16850
|
+
}
|
|
16035
16851
|
async function getContextBreakdown(opts) {
|
|
16036
16852
|
if (destroyed) return null;
|
|
16037
16853
|
const assembly = lastContextAssembly ?? await buildPreRunAssembly(opts?.model);
|
|
@@ -16145,10 +16961,11 @@ function createAgent(rawOptions) {
|
|
|
16145
16961
|
},
|
|
16146
16962
|
meta: Object.freeze({ ...provider.meta }),
|
|
16147
16963
|
getContextBreakdown,
|
|
16964
|
+
getContextSegments,
|
|
16148
16965
|
[Symbol.asyncDispose]: destroy
|
|
16149
16966
|
};
|
|
16150
16967
|
}
|
|
16151
16968
|
//#endregion
|
|
16152
|
-
export {
|
|
16969
|
+
export { installExtensionFromSpec as $, groupBindings as $t, selectFilesFromSession as A, defaultRepeatGuardNormalize as An, createSkillsReadTool as At, buildUpToCompactPrompt as B, matchModelEntries as Bt, resolveLegacySessionDir as C, resolveExtensionConfigValue as Cn, waitTask as Ct, resolveTasksDir as D, OUTPUT_RESERVE_TOKENS as Dn, reserveChildLabel as Dt, resolveSessionDir as E, AUTO_COMPACT_MIN_GROWTH_FRACTION as En, isSpawnTool as Et, TRAILER as F, hashContent as Fn, readFile$1 as Ft, summaryToTurn as G, edit as Gt, anchorPreviewFor as H, createInteractionTool as Ht, buildCompactPrompt as I, hydrateReadStateFromSession as In, looksBinary as It, CompactPromptTooLongError as J, KEYBINDING_DEFS as Jt, truncateHeadForPtlRetry as K, validateToolArgs as Kt, buildFromCompactPrompt as L, hydrateReadStateFromSessionAsync as Ln, multiEdit as Lt, compactConversation as M, normalizeShellCommand as Mn, createShellTool as Mt, BASE_INSTRUCTIONS as N, stableStringify as Nn, shell as Nt, buildPostCompactAttachments as O, effectiveContextWindow as On, createSkillsUseTool as Ot, NO_TOOLS_PREAMBLE as P, getReadState as Pn, tailTruncate as Pt, fetchExtensionSource as Q, formatBindingForDisplay as Qt, buildFullCompactPrompt as R, readStateKey as Rn, createModelSearchTool as Rt, resolveLegacyPersistDir as S, resolveExtensionConfig as Sn, WAIT_TASK_TIMED_OUT_PREFIX as St, resolvePersistDir as T, toWireConfigSchema as Tn, createSpawnTool as Tt, sliceForCompaction as U, grep as Ut, ANCHOR_PREVIEW_MAX_CHARS as V, listFiles as Vt, stripImagesFromTurns as W, glob as Wt, createExtensionPromptBridge as X, KEYBINDING_KEY_COL_WIDTH as Xt, EMPTY_EXTENSION_REGISTRY as Y, KEYBINDING_DEF_BY_ACTION as Yt, ExtensionInstallError as Z, ensureKeybindingsFile as Zt, buildPersistedStub as _, defineExtension as _n, composePresets as _t, stagedToolState as a, readKeybindingsRaw as an, createExtensionHud as at, migrateSessionArtifactDirs as b, isValidExtensionConfigItem as bn, basic_default as bt, INTERRUPT_MESSAGE_FOR_TOOL_USE as c, registerExtensionHostModules as cn, buildExtensionRegistry as ct, TOOL_USE_SKIPPED_MESSAGE as d, discoverExtensions as dn, filterExtensionRegistry as dt, keybindingsPath as en, listExtensionCandidates as et, applyCompactSummaryCutoff as f, inlineLoadedExtension as fn, filterRegistryPlugins as ft, PERSISTENCE_PREVIEW_BYTES as g, createDebugLog as gn, createWorkspaceSurface as gt, PERSISTED_STUB_PREFIX as h, scanExtensionDirectories as hn, createExtensionHostRuntimeBridge as ht, isToolStateOps as i, readKeybindings as in, updateInstalledExtension as it, selectRecentFiles as j, defaultRepeatGuardTracked as jn, shellKill as jt, selectFilesFromReadState as k, shouldAutoCompact as kn, createSkillsRunScriptTool as kt, SHELL_CASCADE_CANCEL_MESSAGE as l, ENTRY_CANDIDATES as ln, composeExtensionPreset as lt, renderStaleReadNotice as m, parseExtensionScanPathEnv as mn, partitionExtensionCatalog as mt, DEFAULT_MAX_VALUE_BYTES as n, mergeKeybindings as nn, readExtensionInstallProvenance as nt, normalizeProviders as o, resolveExtensionKeybindings as on, discoverAndBuildExtensionRegistry as ot, collectNewlyStaleReadPaths as p, isExtensionDefinition as pn, mergeContributions as pt, CompactInvalidInputError as q, DEFAULT_KEYBINDINGS as qt, createToolStateController as r, parseBindingSpec as rn, removeInstalledExtension as rt, resolveModelTargets as s, stripJsonComments as sn, buildExtensionPreset as st, createAgent as t, matchesBinding as tn, parseExtensionInstallSource as tt, TOOL_USE_CANCELLED_MESSAGE as u, defaultExtensionScanPaths as un, disposeExtensionRegistry as ut, cleanupPersistedSession as v, clampExtensionConfigNumber as vn, definePreset as vt, resolveMcpWarningsDir as w, sanitizeExtensionConfigActionPatch as wn, createToolSearchTool as wt, rehomePersistedStubs as x, isValidExtensionConfigValue as xn, writeFile$1 as xt, maybePersistToolResult as y, coerceExtensionConfigInput as yn, basicTools as yt, buildTailCompactPrompt as z, resolveReadStateMap as zn, formatModelLine as zt };
|
|
16153
16970
|
|
|
16154
|
-
//# sourceMappingURL=agent-
|
|
16971
|
+
//# sourceMappingURL=agent-CvO7UOxv.js.map
|