@probelabs/probe 0.6.0-rc294 → 0.6.0-rc296
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/bin/binaries/{probe-v0.6.0-rc294-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc296-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc294-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc296-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc294-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc296-x86_64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc294-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc296-x86_64-pc-windows-msvc.zip} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc294-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc296-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/agent/ProbeAgent.d.ts +10 -0
- package/build/agent/ProbeAgent.js +868 -29
- package/build/agent/mcp/client.js +81 -4
- package/build/agent/mcp/xmlBridge.js +11 -0
- package/build/agent/otelLogBridge.js +184 -0
- package/build/agent/simpleTelemetry.js +8 -0
- package/build/delegate.js +75 -6
- package/build/index.js +6 -2
- package/build/tools/common.js +84 -11
- package/build/tools/vercel.js +78 -18
- package/cjs/agent/ProbeAgent.cjs +1004 -48
- package/cjs/agent/simpleTelemetry.cjs +112 -0
- package/cjs/index.cjs +1116 -48
- package/index.d.ts +26 -0
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +10 -0
- package/src/agent/ProbeAgent.js +868 -29
- package/src/agent/mcp/client.js +81 -4
- package/src/agent/mcp/xmlBridge.js +11 -0
- package/src/agent/otelLogBridge.js +184 -0
- package/src/agent/simpleTelemetry.js +8 -0
- package/src/delegate.js +75 -6
- package/src/index.js +6 -2
- package/src/tools/common.js +84 -11
- package/src/tools/vercel.js +78 -18
package/cjs/agent/ProbeAgent.cjs
CHANGED
|
@@ -26106,8 +26106,15 @@ async function delegate({
|
|
|
26106
26106
|
// Optional per-instance manager, falls back to default singleton
|
|
26107
26107
|
concurrencyLimiter = null,
|
|
26108
26108
|
// Optional global AI concurrency limiter
|
|
26109
|
-
parentAbortSignal = null
|
|
26109
|
+
parentAbortSignal = null,
|
|
26110
26110
|
// Optional AbortSignal from parent to cancel this delegation
|
|
26111
|
+
// Timeout settings inherited from parent agent
|
|
26112
|
+
timeoutBehavior = void 0,
|
|
26113
|
+
requestTimeout = void 0,
|
|
26114
|
+
gracefulTimeoutBonusSteps = void 0,
|
|
26115
|
+
// Subagent lifecycle callbacks for graceful stop coordination
|
|
26116
|
+
onSubagentCreated = null,
|
|
26117
|
+
onSubagentCompleted = null
|
|
26111
26118
|
}) {
|
|
26112
26119
|
if (!task || typeof task !== "string") {
|
|
26113
26120
|
throw new Error("Task parameter is required and must be a string");
|
|
@@ -26190,12 +26197,38 @@ async function delegate({
|
|
|
26190
26197
|
// Inherit from parent
|
|
26191
26198
|
mcpConfigPath,
|
|
26192
26199
|
// Inherit from parent
|
|
26193
|
-
concurrencyLimiter
|
|
26200
|
+
concurrencyLimiter,
|
|
26194
26201
|
// Inherit global AI concurrency limiter
|
|
26202
|
+
// Inherit timeout behavior from parent — subagent gets its own graceful wind-down
|
|
26203
|
+
// so it can produce partial results instead of being hard-killed by the external timer.
|
|
26204
|
+
// The external delegate timeout (capped to parent's remaining budget) is the hard limit;
|
|
26205
|
+
// maxOperationTimeout on the subagent is set slightly shorter so its own wind-down
|
|
26206
|
+
// fires before the external kill.
|
|
26207
|
+
maxOperationTimeout: Math.max(1e4, timeout * 1e3 - 15e3),
|
|
26208
|
+
// 15s before external kill
|
|
26209
|
+
timeoutBehavior: timeoutBehavior || "graceful",
|
|
26210
|
+
requestTimeout,
|
|
26211
|
+
gracefulTimeoutBonusSteps: gracefulTimeoutBonusSteps ?? 2
|
|
26212
|
+
// fewer steps for subagents
|
|
26195
26213
|
});
|
|
26214
|
+
if (onSubagentCreated) {
|
|
26215
|
+
onSubagentCreated(sessionId, subagent);
|
|
26216
|
+
}
|
|
26196
26217
|
if (debug) {
|
|
26197
26218
|
console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
|
|
26198
26219
|
console.error(`[DELEGATE] Subagent config: promptType=${promptType}, enableDelegate=false, maxIterations=${remainingIterations}`);
|
|
26220
|
+
console.error(`[DELEGATE] Timeout inheritance: externalTimeout=${timeout}s, maxOperationTimeout=${Math.max(1e4, timeout * 1e3 - 15e3)}ms, behavior=${timeoutBehavior || "graceful"}, bonusSteps=${gracefulTimeoutBonusSteps ?? 2}`);
|
|
26221
|
+
}
|
|
26222
|
+
if (tracer) {
|
|
26223
|
+
tracer.addEvent("delegation.subagent_created", {
|
|
26224
|
+
"delegation.session_id": sessionId,
|
|
26225
|
+
"delegation.parent_session_id": parentSessionId,
|
|
26226
|
+
"delegation.external_timeout_s": timeout,
|
|
26227
|
+
"delegation.internal_timeout_ms": Math.max(1e4, timeout * 1e3 - 15e3),
|
|
26228
|
+
"delegation.timeout_behavior": timeoutBehavior || "graceful",
|
|
26229
|
+
"delegation.bonus_steps": gracefulTimeoutBonusSteps ?? 2,
|
|
26230
|
+
"delegation.max_iterations": remainingIterations
|
|
26231
|
+
});
|
|
26199
26232
|
}
|
|
26200
26233
|
const timeoutPromise = new Promise((_, reject2) => {
|
|
26201
26234
|
timeoutId = setTimeout(() => {
|
|
@@ -26204,6 +26237,7 @@ async function delegate({
|
|
|
26204
26237
|
}, timeout * 1e3);
|
|
26205
26238
|
});
|
|
26206
26239
|
let parentAbortHandler;
|
|
26240
|
+
let parentAbortHardCancelId = null;
|
|
26207
26241
|
const parentAbortPromise = new Promise((_, reject2) => {
|
|
26208
26242
|
if (parentAbortSignal) {
|
|
26209
26243
|
if (parentAbortSignal.aborted) {
|
|
@@ -26212,8 +26246,31 @@ async function delegate({
|
|
|
26212
26246
|
return;
|
|
26213
26247
|
}
|
|
26214
26248
|
parentAbortHandler = () => {
|
|
26215
|
-
subagent.
|
|
26216
|
-
|
|
26249
|
+
subagent.triggerGracefulWindDown();
|
|
26250
|
+
if (debug) {
|
|
26251
|
+
console.error(`[DELEGATE] Parent abort signal received \u2014 triggered graceful wind-down on subagent ${sessionId}`);
|
|
26252
|
+
}
|
|
26253
|
+
if (tracer) {
|
|
26254
|
+
tracer.addEvent("delegation.parent_abort_phase1", {
|
|
26255
|
+
"delegation.session_id": sessionId,
|
|
26256
|
+
"delegation.parent_session_id": parentSessionId,
|
|
26257
|
+
"delegation.action": "graceful_wind_down"
|
|
26258
|
+
});
|
|
26259
|
+
}
|
|
26260
|
+
parentAbortHardCancelId = setTimeout(() => {
|
|
26261
|
+
if (debug) {
|
|
26262
|
+
console.error(`[DELEGATE] Graceful wind-down deadline expired \u2014 hard cancelling subagent ${sessionId}`);
|
|
26263
|
+
}
|
|
26264
|
+
if (tracer) {
|
|
26265
|
+
tracer.addEvent("delegation.parent_abort_phase2", {
|
|
26266
|
+
"delegation.session_id": sessionId,
|
|
26267
|
+
"delegation.parent_session_id": parentSessionId,
|
|
26268
|
+
"delegation.action": "hard_cancel"
|
|
26269
|
+
});
|
|
26270
|
+
}
|
|
26271
|
+
subagent.cancel();
|
|
26272
|
+
reject2(new Error("Delegation cancelled: parent operation was aborted (graceful wind-down deadline expired)"));
|
|
26273
|
+
}, 3e4);
|
|
26217
26274
|
};
|
|
26218
26275
|
parentAbortSignal.addEventListener("abort", parentAbortHandler, { once: true });
|
|
26219
26276
|
}
|
|
@@ -26229,6 +26286,13 @@ async function delegate({
|
|
|
26229
26286
|
if (parentAbortHandler && parentAbortSignal) {
|
|
26230
26287
|
parentAbortSignal.removeEventListener("abort", parentAbortHandler);
|
|
26231
26288
|
}
|
|
26289
|
+
if (parentAbortHardCancelId) {
|
|
26290
|
+
clearTimeout(parentAbortHardCancelId);
|
|
26291
|
+
parentAbortHardCancelId = null;
|
|
26292
|
+
}
|
|
26293
|
+
if (onSubagentCompleted) {
|
|
26294
|
+
onSubagentCompleted(sessionId);
|
|
26295
|
+
}
|
|
26232
26296
|
}
|
|
26233
26297
|
if (timeoutId !== null) {
|
|
26234
26298
|
clearTimeout(timeoutId);
|
|
@@ -27064,14 +27128,64 @@ function detectStuckResponse(response) {
|
|
|
27064
27128
|
}
|
|
27065
27129
|
return false;
|
|
27066
27130
|
}
|
|
27131
|
+
function splitQuotedString(input) {
|
|
27132
|
+
const tokens = [];
|
|
27133
|
+
let current2 = "";
|
|
27134
|
+
let inQuote = null;
|
|
27135
|
+
let i = 0;
|
|
27136
|
+
while (i < input.length) {
|
|
27137
|
+
const ch = input[i];
|
|
27138
|
+
if (inQuote) {
|
|
27139
|
+
if (ch === "\\" && i + 1 < input.length) {
|
|
27140
|
+
current2 += input[i + 1];
|
|
27141
|
+
i += 2;
|
|
27142
|
+
continue;
|
|
27143
|
+
}
|
|
27144
|
+
if (ch === inQuote) {
|
|
27145
|
+
inQuote = null;
|
|
27146
|
+
i++;
|
|
27147
|
+
continue;
|
|
27148
|
+
}
|
|
27149
|
+
current2 += ch;
|
|
27150
|
+
i++;
|
|
27151
|
+
} else {
|
|
27152
|
+
if (ch === '"' || ch === "'") {
|
|
27153
|
+
inQuote = ch;
|
|
27154
|
+
i++;
|
|
27155
|
+
continue;
|
|
27156
|
+
}
|
|
27157
|
+
if (/[\s,]/.test(ch)) {
|
|
27158
|
+
if (current2.length > 0) {
|
|
27159
|
+
tokens.push(current2);
|
|
27160
|
+
current2 = "";
|
|
27161
|
+
}
|
|
27162
|
+
i++;
|
|
27163
|
+
continue;
|
|
27164
|
+
}
|
|
27165
|
+
current2 += ch;
|
|
27166
|
+
i++;
|
|
27167
|
+
}
|
|
27168
|
+
}
|
|
27169
|
+
if (current2.length > 0) {
|
|
27170
|
+
tokens.push(current2);
|
|
27171
|
+
}
|
|
27172
|
+
return tokens;
|
|
27173
|
+
}
|
|
27067
27174
|
function parseTargets(targets) {
|
|
27068
27175
|
if (!targets || typeof targets !== "string") {
|
|
27069
27176
|
return [];
|
|
27070
27177
|
}
|
|
27071
|
-
return targets
|
|
27178
|
+
return splitQuotedString(targets);
|
|
27072
27179
|
}
|
|
27073
27180
|
function parseAndResolvePaths(pathStr, cwd) {
|
|
27074
27181
|
if (!pathStr) return [];
|
|
27182
|
+
if (/["']/.test(pathStr)) {
|
|
27183
|
+
const paths2 = splitQuotedString(pathStr);
|
|
27184
|
+
return paths2.map((p) => {
|
|
27185
|
+
if ((0, import_path5.isAbsolute)(p)) return p;
|
|
27186
|
+
return cwd ? (0, import_path5.resolve)(cwd, p) : p;
|
|
27187
|
+
});
|
|
27188
|
+
}
|
|
27075
27189
|
let paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
27076
27190
|
paths = paths.flatMap((p) => {
|
|
27077
27191
|
if (!/\s/.test(p)) return [p];
|
|
@@ -27081,9 +27195,7 @@ function parseAndResolvePaths(pathStr, cwd) {
|
|
|
27081
27195
|
return allLookLikePaths ? parts : [p];
|
|
27082
27196
|
});
|
|
27083
27197
|
return paths.map((p) => {
|
|
27084
|
-
if ((0, import_path5.isAbsolute)(p))
|
|
27085
|
-
return p;
|
|
27086
|
-
}
|
|
27198
|
+
if ((0, import_path5.isAbsolute)(p)) return p;
|
|
27087
27199
|
return cwd ? (0, import_path5.resolve)(cwd, p) : p;
|
|
27088
27200
|
});
|
|
27089
27201
|
}
|
|
@@ -27116,7 +27228,7 @@ var init_common = __esm({
|
|
|
27116
27228
|
searchSchema = external_exports2.object({
|
|
27117
27229
|
query: external_exports2.string().describe("Search query \u2014 natural language questions or Elasticsearch-style keywords both work. For keywords: use quotes for exact phrases, AND/OR for boolean logic, - for negation. Probe handles stemming and camelCase/snake_case splitting automatically, so do NOT try case or style variations of the same keyword."),
|
|
27118
27230
|
path: external_exports2.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.'),
|
|
27119
|
-
exact: external_exports2.boolean().optional().default(false).describe(
|
|
27231
|
+
exact: external_exports2.boolean().optional().default(false).describe(`Default (false) enables stemming and keyword splitting for exploratory search - "getUserData" matches "get", "user", "data", etc. Set true for precise symbol lookup OR when searching for strings with punctuation/quotes/empty values (e.g. 'description: ""' \u2014 BM25 strips punctuation so exact=true is required for literal matching). Use true when you know the exact symbol name or need literal string matching.`),
|
|
27120
27232
|
maxTokens: external_exports2.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
|
|
27121
27233
|
session: external_exports2.string().optional().describe("Session ID for result caching and pagination. Pass the session ID from a previous search to get additional results (next page). Results already shown in a session are automatically excluded. Omit for a fresh search."),
|
|
27122
27234
|
nextPage: external_exports2.boolean().optional().default(false).describe("Set to true when requesting the next page of results. Requires passing the same session ID from the previous search output.")
|
|
@@ -27427,6 +27539,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27427
27539
|
"- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
|
|
27428
27540
|
"- exact=true matches the literal string only \u2014 no stemming, no splitting.",
|
|
27429
27541
|
'- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
|
|
27542
|
+
"- IMPORTANT: Use exact=true when searching for strings containing punctuation, quotes, or empty values.",
|
|
27543
|
+
" Default BM25 search strips punctuation and treats quoted empty strings as noise.",
|
|
27544
|
+
` Example: searching for 'description: ""' with exact=false will NOT find empty description fields \u2014 it just matches "description".`,
|
|
27545
|
+
` Use exact=true for literal patterns like 'description: ""', 'value: \\'\\'', or any YAML/config field with specific punctuation.`,
|
|
27430
27546
|
"- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
|
|
27431
27547
|
"",
|
|
27432
27548
|
"Combining searches with OR:",
|
|
@@ -27486,7 +27602,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27486
27602
|
"WHEN TO STOP:",
|
|
27487
27603
|
"- After you have explored the main concept AND related subsystems.",
|
|
27488
27604
|
"- Once you have 5-15 targets covering different aspects of the query.",
|
|
27489
|
-
'- If you get a "DUPLICATE SEARCH BLOCKED" message,
|
|
27605
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, do NOT rephrase the same query \u2014 try a FUNDAMENTALLY different approach:',
|
|
27606
|
+
" * Switch between exact=true and exact=false",
|
|
27607
|
+
" * Search for a broader term and filter results manually",
|
|
27608
|
+
" * Use listFiles to browse the directory structure directly",
|
|
27609
|
+
" * Look for related/surrounding patterns instead of the exact string",
|
|
27610
|
+
"- If 2-3 genuinely different search approaches fail, STOP and report what you tried and why it failed.",
|
|
27611
|
+
" Do NOT keep trying variations of the same failing concept.",
|
|
27490
27612
|
"",
|
|
27491
27613
|
"Strategy:",
|
|
27492
27614
|
"1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
|
|
@@ -27558,8 +27680,8 @@ var init_vercel = __esm({
|
|
|
27558
27680
|
}
|
|
27559
27681
|
return result;
|
|
27560
27682
|
};
|
|
27561
|
-
const previousSearches = /* @__PURE__ */ new
|
|
27562
|
-
|
|
27683
|
+
const previousSearches = /* @__PURE__ */ new Map();
|
|
27684
|
+
const dupBlockCounts = /* @__PURE__ */ new Map();
|
|
27563
27685
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
27564
27686
|
const MAX_PAGES_PER_QUERY = 3;
|
|
27565
27687
|
return (0, import_ai.tool)({
|
|
@@ -27610,20 +27732,25 @@ var init_vercel = __esm({
|
|
|
27610
27732
|
return await search(searchOptions);
|
|
27611
27733
|
};
|
|
27612
27734
|
if (!searchDelegate) {
|
|
27613
|
-
const searchKey = `${searchQuery}::${exact || false}`;
|
|
27735
|
+
const searchKey = `${searchPath}::${searchQuery}::${exact || false}`;
|
|
27614
27736
|
if (!nextPage) {
|
|
27615
27737
|
if (previousSearches.has(searchKey)) {
|
|
27616
|
-
|
|
27738
|
+
const blockCount = (dupBlockCounts.get(searchKey) || 0) + 1;
|
|
27739
|
+
dupBlockCounts.set(searchKey, blockCount);
|
|
27617
27740
|
if (debug) {
|
|
27618
|
-
console.error(`[DEDUP] Blocked duplicate search (${
|
|
27741
|
+
console.error(`[DEDUP] Blocked duplicate search (${blockCount}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
27742
|
+
}
|
|
27743
|
+
if (blockCount >= 3) {
|
|
27744
|
+
return "STOP. You have been blocked " + blockCount + " times for repeating the same search. You MUST provide your final answer NOW with whatever information you have. Do NOT call any more tools.";
|
|
27619
27745
|
}
|
|
27620
|
-
|
|
27621
|
-
|
|
27746
|
+
const prev = previousSearches.get(searchKey);
|
|
27747
|
+
if (prev.hadResults) {
|
|
27748
|
+
return `DUPLICATE SEARCH BLOCKED (${blockCount}x). You already searched for "${searchQuery}" in this path and found results. Do NOT repeat. Use extract to examine the files you already found, try a COMPLETELY different keyword, or provide your final answer.`;
|
|
27622
27749
|
}
|
|
27623
|
-
|
|
27750
|
+
const exactHint = exact ? "You used exact=true. Try a broader search with exact=false, or use listFiles to browse the directory structure." : `Try exact=true if you need literal/punctuation matching (e.g. 'description: ""'), or use listFiles to explore directories, or search for a broader/related term and filter manually.`;
|
|
27751
|
+
return `DUPLICATE SEARCH BLOCKED (${blockCount}x). You already searched for "${searchQuery}" in this path and got NO results. This term does not appear in the codebase. Do NOT repeat or rephrase \u2014 try a FUNDAMENTALLY different approach: ${exactHint} If multiple approaches have failed, provide your final answer with what you know.`;
|
|
27624
27752
|
}
|
|
27625
|
-
previousSearches.
|
|
27626
|
-
consecutiveDupBlocks = 0;
|
|
27753
|
+
previousSearches.set(searchKey, { hadResults: false });
|
|
27627
27754
|
paginationCounts.set(searchKey, 0);
|
|
27628
27755
|
} else {
|
|
27629
27756
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
@@ -27637,6 +27764,14 @@ var init_vercel = __esm({
|
|
|
27637
27764
|
}
|
|
27638
27765
|
try {
|
|
27639
27766
|
const result = maybeAnnotate(await runRawSearch());
|
|
27767
|
+
if (typeof result === "string" && result.includes("No results found")) {
|
|
27768
|
+
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
|
|
27769
|
+
return result + "\n\n\u26A0\uFE0F Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).";
|
|
27770
|
+
}
|
|
27771
|
+
} else if (typeof result === "string") {
|
|
27772
|
+
const entry = previousSearches.get(searchKey);
|
|
27773
|
+
if (entry) entry.hadResults = true;
|
|
27774
|
+
}
|
|
27640
27775
|
if (options.fileTracker && typeof result === "string") {
|
|
27641
27776
|
options.fileTracker.trackFilesFromOutput(result, effectiveSearchCwd).catch(() => {
|
|
27642
27777
|
});
|
|
@@ -27919,7 +28054,31 @@ var init_vercel = __esm({
|
|
|
27919
28054
|
});
|
|
27920
28055
|
};
|
|
27921
28056
|
delegateTool = (options = {}) => {
|
|
27922
|
-
const {
|
|
28057
|
+
const {
|
|
28058
|
+
debug = false,
|
|
28059
|
+
timeout = 300,
|
|
28060
|
+
cwd,
|
|
28061
|
+
allowedFolders,
|
|
28062
|
+
workspaceRoot,
|
|
28063
|
+
enableBash = false,
|
|
28064
|
+
bashConfig,
|
|
28065
|
+
architectureFileName,
|
|
28066
|
+
enableMcp = false,
|
|
28067
|
+
mcpConfig = null,
|
|
28068
|
+
mcpConfigPath = null,
|
|
28069
|
+
delegationManager = null,
|
|
28070
|
+
// Timeout settings inherited from parent agent
|
|
28071
|
+
timeoutBehavior,
|
|
28072
|
+
maxOperationTimeout,
|
|
28073
|
+
requestTimeout,
|
|
28074
|
+
gracefulTimeoutBonusSteps,
|
|
28075
|
+
negotiatedTimeoutBudget,
|
|
28076
|
+
negotiatedTimeoutMaxRequests,
|
|
28077
|
+
negotiatedTimeoutMaxPerRequest,
|
|
28078
|
+
parentOperationStartTime,
|
|
28079
|
+
onSubagentCreated,
|
|
28080
|
+
onSubagentCompleted
|
|
28081
|
+
} = options;
|
|
27923
28082
|
return (0, import_ai.tool)({
|
|
27924
28083
|
name: "delegate",
|
|
27925
28084
|
description: delegateDescription,
|
|
@@ -27963,9 +28122,30 @@ var init_vercel = __esm({
|
|
|
27963
28122
|
console.error(`Using workspace root: ${effectivePath} (cwd was: ${cwd || "not set"})`);
|
|
27964
28123
|
}
|
|
27965
28124
|
}
|
|
28125
|
+
let effectiveTimeout = timeout;
|
|
28126
|
+
if (parentOperationStartTime && maxOperationTimeout) {
|
|
28127
|
+
const elapsed = Date.now() - parentOperationStartTime;
|
|
28128
|
+
const remaining = maxOperationTimeout - elapsed;
|
|
28129
|
+
const budgetCap = Math.max(30, Math.floor(remaining * 0.9 / 1e3));
|
|
28130
|
+
if (budgetCap < effectiveTimeout) {
|
|
28131
|
+
effectiveTimeout = budgetCap;
|
|
28132
|
+
if (debug) {
|
|
28133
|
+
console.error(`[DELEGATE] Capping timeout from ${timeout}s to ${effectiveTimeout}s (remaining parent budget: ${Math.floor(remaining / 1e3)}s)`);
|
|
28134
|
+
}
|
|
28135
|
+
if (tracer) {
|
|
28136
|
+
tracer.addEvent("delegation.budget_capped", {
|
|
28137
|
+
"delegation.original_timeout_s": timeout,
|
|
28138
|
+
"delegation.effective_timeout_s": effectiveTimeout,
|
|
28139
|
+
"delegation.parent_elapsed_ms": elapsed,
|
|
28140
|
+
"delegation.parent_remaining_ms": remaining,
|
|
28141
|
+
"delegation.parent_session_id": parentSessionId
|
|
28142
|
+
});
|
|
28143
|
+
}
|
|
28144
|
+
}
|
|
28145
|
+
}
|
|
27966
28146
|
const result = await delegate({
|
|
27967
28147
|
task,
|
|
27968
|
-
timeout,
|
|
28148
|
+
timeout: effectiveTimeout,
|
|
27969
28149
|
debug,
|
|
27970
28150
|
currentIteration: currentIteration || 0,
|
|
27971
28151
|
maxIterations: maxIterations || 30,
|
|
@@ -27984,7 +28164,14 @@ var init_vercel = __esm({
|
|
|
27984
28164
|
mcpConfigPath,
|
|
27985
28165
|
delegationManager,
|
|
27986
28166
|
// Per-instance delegation limits
|
|
27987
|
-
parentAbortSignal
|
|
28167
|
+
parentAbortSignal,
|
|
28168
|
+
// Inherit timeout settings for subagent
|
|
28169
|
+
timeoutBehavior,
|
|
28170
|
+
requestTimeout,
|
|
28171
|
+
gracefulTimeoutBonusSteps,
|
|
28172
|
+
// Subagent lifecycle callbacks for graceful stop coordination
|
|
28173
|
+
onSubagentCreated,
|
|
28174
|
+
onSubagentCompleted
|
|
27988
28175
|
});
|
|
27989
28176
|
return result;
|
|
27990
28177
|
}
|
|
@@ -48935,6 +49122,16 @@ var init_fileTracker = __esm({
|
|
|
48935
49122
|
}
|
|
48936
49123
|
});
|
|
48937
49124
|
|
|
49125
|
+
// src/agent/otelLogBridge.js
|
|
49126
|
+
var import_module, _require;
|
|
49127
|
+
var init_otelLogBridge = __esm({
|
|
49128
|
+
"src/agent/otelLogBridge.js"() {
|
|
49129
|
+
"use strict";
|
|
49130
|
+
import_module = require("module");
|
|
49131
|
+
_require = (0, import_module.createRequire)("file:///");
|
|
49132
|
+
}
|
|
49133
|
+
});
|
|
49134
|
+
|
|
48938
49135
|
// src/agent/simpleTelemetry.js
|
|
48939
49136
|
var import_fs10, import_path11;
|
|
48940
49137
|
var init_simpleTelemetry = __esm({
|
|
@@ -48942,6 +49139,7 @@ var init_simpleTelemetry = __esm({
|
|
|
48942
49139
|
"use strict";
|
|
48943
49140
|
import_fs10 = require("fs");
|
|
48944
49141
|
import_path11 = require("path");
|
|
49142
|
+
init_otelLogBridge();
|
|
48945
49143
|
}
|
|
48946
49144
|
});
|
|
48947
49145
|
|
|
@@ -88952,6 +89150,9 @@ function isMethodAllowed(methodName, allowedMethods, blockedMethods) {
|
|
|
88952
89150
|
}
|
|
88953
89151
|
function createTransport(serverConfig) {
|
|
88954
89152
|
const { transport, command, args, url: url2, env } = serverConfig;
|
|
89153
|
+
if (serverConfig.transportInstance) {
|
|
89154
|
+
return serverConfig.transportInstance;
|
|
89155
|
+
}
|
|
88955
89156
|
switch (transport) {
|
|
88956
89157
|
case "stdio":
|
|
88957
89158
|
return new import_stdio.StdioClientTransport({
|
|
@@ -89314,6 +89515,53 @@ var init_client = __esm({
|
|
|
89314
89515
|
throw error40;
|
|
89315
89516
|
}
|
|
89316
89517
|
}
|
|
89518
|
+
/**
|
|
89519
|
+
* Call graceful_stop on all MCP servers that expose it.
|
|
89520
|
+
* This signals agent-type MCP servers to wrap up their work.
|
|
89521
|
+
* @returns {Promise<Array<{server: string, success: boolean, error?: string}>>}
|
|
89522
|
+
*/
|
|
89523
|
+
async callGracefulStopAll() {
|
|
89524
|
+
const results = [];
|
|
89525
|
+
for (const [serverName, clientInfo] of this.clients) {
|
|
89526
|
+
const qualifiedName = `${serverName}_graceful_stop`;
|
|
89527
|
+
if (this.tools.has(qualifiedName)) {
|
|
89528
|
+
if (this.debug) {
|
|
89529
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: calling graceful_stop on server "${serverName}"`);
|
|
89530
|
+
}
|
|
89531
|
+
try {
|
|
89532
|
+
const timeoutMs = 5e3;
|
|
89533
|
+
const timeoutPromise = new Promise(
|
|
89534
|
+
(_, reject2) => setTimeout(() => reject2(new Error("graceful_stop timeout")), timeoutMs)
|
|
89535
|
+
);
|
|
89536
|
+
await Promise.race([
|
|
89537
|
+
clientInfo.client.callTool({ name: "graceful_stop", arguments: {} }, void 0, { timeout: timeoutMs }),
|
|
89538
|
+
timeoutPromise
|
|
89539
|
+
]);
|
|
89540
|
+
results.push({ server: serverName, success: true });
|
|
89541
|
+
if (this.debug) {
|
|
89542
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: server "${serverName}" acknowledged graceful_stop`);
|
|
89543
|
+
}
|
|
89544
|
+
} catch (e) {
|
|
89545
|
+
results.push({ server: serverName, success: false, error: e.message });
|
|
89546
|
+
if (this.debug) {
|
|
89547
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: server "${serverName}" graceful_stop failed: ${e.message}`);
|
|
89548
|
+
}
|
|
89549
|
+
}
|
|
89550
|
+
}
|
|
89551
|
+
}
|
|
89552
|
+
if (this.debug) {
|
|
89553
|
+
const withStop = results.length;
|
|
89554
|
+
const total = this.clients.size;
|
|
89555
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: ${withStop}/${total} servers had graceful_stop tool`);
|
|
89556
|
+
}
|
|
89557
|
+
this.recordMcpEvent("graceful_stop.sweep_completed", {
|
|
89558
|
+
servers_total: this.clients.size,
|
|
89559
|
+
servers_with_graceful_stop: results.length,
|
|
89560
|
+
servers_acknowledged: results.filter((r) => r.success).length,
|
|
89561
|
+
servers_failed: results.filter((r) => !r.success).length
|
|
89562
|
+
});
|
|
89563
|
+
return results;
|
|
89564
|
+
}
|
|
89317
89565
|
/**
|
|
89318
89566
|
* Get all available tools with their schemas
|
|
89319
89567
|
* @returns {Object} Map of tool name to tool definition
|
|
@@ -89341,10 +89589,29 @@ var init_client = __esm({
|
|
|
89341
89589
|
inputSchema: tool6.inputSchema,
|
|
89342
89590
|
execute: async (args) => {
|
|
89343
89591
|
const result = await this.callTool(name15, args);
|
|
89344
|
-
if (result.content
|
|
89345
|
-
return result
|
|
89592
|
+
if (!result.content || !result.content[0]) {
|
|
89593
|
+
return JSON.stringify(result);
|
|
89594
|
+
}
|
|
89595
|
+
const hasImage = result.content.some((block) => block.type === "image");
|
|
89596
|
+
if (hasImage) {
|
|
89597
|
+
return { _mcpContent: result.content };
|
|
89598
|
+
}
|
|
89599
|
+
return result.content[0].text;
|
|
89600
|
+
},
|
|
89601
|
+
// Convert MCP content blocks (including images) to Vercel AI SDK format
|
|
89602
|
+
toModelOutput: ({ output }) => {
|
|
89603
|
+
if (output && typeof output === "object" && output._mcpContent) {
|
|
89604
|
+
const parts = [];
|
|
89605
|
+
for (const block of output._mcpContent) {
|
|
89606
|
+
if (block.type === "text") {
|
|
89607
|
+
parts.push({ type: "text", text: block.text });
|
|
89608
|
+
} else if (block.type === "image") {
|
|
89609
|
+
parts.push({ type: "image-data", data: block.data, mediaType: block.mimeType });
|
|
89610
|
+
}
|
|
89611
|
+
}
|
|
89612
|
+
return { type: "content", value: parts };
|
|
89346
89613
|
}
|
|
89347
|
-
return JSON.stringify(
|
|
89614
|
+
return { type: "text", value: typeof output === "string" ? output : JSON.stringify(output) };
|
|
89348
89615
|
}
|
|
89349
89616
|
};
|
|
89350
89617
|
}
|
|
@@ -89524,6 +89791,16 @@ var init_xmlBridge = __esm({
|
|
|
89524
89791
|
isMcpTool(toolName) {
|
|
89525
89792
|
return toolName in this.mcpTools;
|
|
89526
89793
|
}
|
|
89794
|
+
/**
|
|
89795
|
+
* Call graceful_stop on all MCP servers that expose it.
|
|
89796
|
+
* @returns {Promise<Array>}
|
|
89797
|
+
*/
|
|
89798
|
+
async callGracefulStopAll() {
|
|
89799
|
+
if (this.mcpManager) {
|
|
89800
|
+
return this.mcpManager.callGracefulStopAll();
|
|
89801
|
+
}
|
|
89802
|
+
return [];
|
|
89803
|
+
}
|
|
89527
89804
|
/**
|
|
89528
89805
|
* Clean up MCP connections
|
|
89529
89806
|
*/
|
|
@@ -99762,6 +100039,7 @@ var init_ProbeAgent = __esm({
|
|
|
99762
100039
|
this.debug = options.debug || process.env.DEBUG === "1";
|
|
99763
100040
|
this.cancelled = false;
|
|
99764
100041
|
this._abortController = new AbortController();
|
|
100042
|
+
this._activeSubagents = /* @__PURE__ */ new Map();
|
|
99765
100043
|
this.tracer = options.tracer || null;
|
|
99766
100044
|
this.outline = !!options.outline;
|
|
99767
100045
|
this.searchDelegate = options.searchDelegate !== void 0 ? !!options.searchDelegate : true;
|
|
@@ -99870,6 +100148,35 @@ var init_ProbeAgent = __esm({
|
|
|
99870
100148
|
if (this.debug) {
|
|
99871
100149
|
console.log(`[DEBUG] Max operation timeout: ${this.maxOperationTimeout}ms`);
|
|
99872
100150
|
}
|
|
100151
|
+
this.timeoutBehavior = options.timeoutBehavior ?? (() => {
|
|
100152
|
+
const val = process.env.TIMEOUT_BEHAVIOR;
|
|
100153
|
+
if (val === "hard") return "hard";
|
|
100154
|
+
if (val === "negotiated") return "negotiated";
|
|
100155
|
+
return "graceful";
|
|
100156
|
+
})();
|
|
100157
|
+
this.gracefulTimeoutBonusSteps = options.gracefulTimeoutBonusSteps ?? (() => {
|
|
100158
|
+
const parsed = parseInt(process.env.GRACEFUL_TIMEOUT_BONUS_STEPS, 10);
|
|
100159
|
+
return isNaN(parsed) || parsed < 1 || parsed > 20 ? 4 : parsed;
|
|
100160
|
+
})();
|
|
100161
|
+
this.negotiatedTimeoutBudget = options.negotiatedTimeoutBudget ?? (() => {
|
|
100162
|
+
const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_BUDGET, 10);
|
|
100163
|
+
return isNaN(parsed) || parsed < 6e4 || parsed > 72e5 ? 18e5 : parsed;
|
|
100164
|
+
})();
|
|
100165
|
+
this.negotiatedTimeoutMaxRequests = options.negotiatedTimeoutMaxRequests ?? (() => {
|
|
100166
|
+
const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_MAX_REQUESTS, 10);
|
|
100167
|
+
return isNaN(parsed) || parsed < 1 || parsed > 10 ? 3 : parsed;
|
|
100168
|
+
})();
|
|
100169
|
+
this.negotiatedTimeoutMaxPerRequest = options.negotiatedTimeoutMaxPerRequest ?? (() => {
|
|
100170
|
+
const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_MAX_PER_REQUEST, 10);
|
|
100171
|
+
return isNaN(parsed) || parsed < 6e4 || parsed > 36e5 ? 6e5 : parsed;
|
|
100172
|
+
})();
|
|
100173
|
+
this.gracefulStopDeadline = options.gracefulStopDeadline ?? (() => {
|
|
100174
|
+
const parsed = parseInt(process.env.GRACEFUL_STOP_DEADLINE, 10);
|
|
100175
|
+
return isNaN(parsed) || parsed < 5e3 || parsed > 3e5 ? 45e3 : parsed;
|
|
100176
|
+
})();
|
|
100177
|
+
if (this.debug) {
|
|
100178
|
+
console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}, graceful stop deadline: ${this.gracefulStopDeadline}ms`);
|
|
100179
|
+
}
|
|
99873
100180
|
this.retryConfig = options.retry || {};
|
|
99874
100181
|
this.retryManager = null;
|
|
99875
100182
|
this.fallbackConfig = options.fallback || null;
|
|
@@ -100220,6 +100527,18 @@ var init_ProbeAgent = __esm({
|
|
|
100220
100527
|
// Per-instance delegation limits
|
|
100221
100528
|
parentAbortSignal: this._abortController.signal,
|
|
100222
100529
|
// Propagate cancellation to delegations
|
|
100530
|
+
// Timeout settings for delegate subagents to inherit
|
|
100531
|
+
timeoutBehavior: this.timeoutBehavior,
|
|
100532
|
+
maxOperationTimeout: this.maxOperationTimeout,
|
|
100533
|
+
requestTimeout: this.requestTimeout,
|
|
100534
|
+
gracefulTimeoutBonusSteps: this.gracefulTimeoutBonusSteps,
|
|
100535
|
+
negotiatedTimeoutBudget: this.negotiatedTimeoutBudget,
|
|
100536
|
+
negotiatedTimeoutMaxRequests: this.negotiatedTimeoutMaxRequests,
|
|
100537
|
+
negotiatedTimeoutMaxPerRequest: this.negotiatedTimeoutMaxPerRequest,
|
|
100538
|
+
parentOperationStartTime: this._operationStartTime,
|
|
100539
|
+
// For remaining budget calculation
|
|
100540
|
+
onSubagentCreated: (sid, subagent) => this._registerSubagent(sid, subagent),
|
|
100541
|
+
onSubagentCompleted: (sid) => this._unregisterSubagent(sid),
|
|
100223
100542
|
outputBuffer: this._outputBuffer,
|
|
100224
100543
|
concurrencyLimiter: this.concurrencyLimiter,
|
|
100225
100544
|
// Global AI concurrency limiter
|
|
@@ -100803,12 +101122,16 @@ var init_ProbeAgent = __esm({
|
|
|
100803
101122
|
}, { once: true });
|
|
100804
101123
|
}
|
|
100805
101124
|
if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
|
|
100806
|
-
|
|
100807
|
-
|
|
100808
|
-
|
|
100809
|
-
|
|
100810
|
-
|
|
100811
|
-
|
|
101125
|
+
const gts = this._gracefulTimeoutState;
|
|
101126
|
+
if ((this.timeoutBehavior === "graceful" || this.timeoutBehavior === "negotiated") && gts) {
|
|
101127
|
+
} else {
|
|
101128
|
+
timeoutState.timeoutId = setTimeout(() => {
|
|
101129
|
+
controller.abort();
|
|
101130
|
+
if (this.debug) {
|
|
101131
|
+
console.log(`[DEBUG] Operation timed out after ${this.maxOperationTimeout}ms (max operation timeout)`);
|
|
101132
|
+
}
|
|
101133
|
+
}, this.maxOperationTimeout);
|
|
101134
|
+
}
|
|
100812
101135
|
}
|
|
100813
101136
|
try {
|
|
100814
101137
|
const useClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
@@ -102187,6 +102510,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102187
102510
|
} else {
|
|
102188
102511
|
options = schemaOrOptions || {};
|
|
102189
102512
|
}
|
|
102513
|
+
this._operationStartTime = Date.now();
|
|
102190
102514
|
try {
|
|
102191
102515
|
const oldHistoryLength = this.history.length;
|
|
102192
102516
|
if (this._outputBuffer && !options?._schemaFormatted && !options?._completionPromptProcessed) {
|
|
@@ -102267,7 +102591,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102267
102591
|
}
|
|
102268
102592
|
}
|
|
102269
102593
|
let currentIteration = 0;
|
|
102270
|
-
let finalResult =
|
|
102594
|
+
let finalResult = null;
|
|
102595
|
+
const DEFAULT_MAX_ITER_MSG = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
|
|
102596
|
+
const _toolCallLog = [];
|
|
102597
|
+
let abortSummaryTaken = false;
|
|
102271
102598
|
const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
|
|
102272
102599
|
const maxIterations = options._maxIterationsOverride ? baseMaxIterations : options.schema ? baseMaxIterations + 4 : baseMaxIterations;
|
|
102273
102600
|
const isClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
@@ -102399,6 +102726,228 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102399
102726
|
}
|
|
102400
102727
|
let completionPromptInjected = false;
|
|
102401
102728
|
let preCompletionResult = null;
|
|
102729
|
+
const gracefulTimeoutState = {
|
|
102730
|
+
triggered: false,
|
|
102731
|
+
// Set to true when soft timeout fires
|
|
102732
|
+
bonusStepsUsed: 0,
|
|
102733
|
+
// Steps taken after soft timeout
|
|
102734
|
+
bonusStepsMax: this.gracefulTimeoutBonusSteps
|
|
102735
|
+
};
|
|
102736
|
+
this._gracefulTimeoutState = gracefulTimeoutState;
|
|
102737
|
+
const negotiatedTimeoutState = {
|
|
102738
|
+
extensionsUsed: 0,
|
|
102739
|
+
totalExtraTimeMs: 0,
|
|
102740
|
+
softTimeoutId: null,
|
|
102741
|
+
hardAbortTimeoutId: null,
|
|
102742
|
+
maxRequests: this.negotiatedTimeoutMaxRequests,
|
|
102743
|
+
maxPerRequestMs: this.negotiatedTimeoutMaxPerRequest,
|
|
102744
|
+
budgetMs: this.negotiatedTimeoutBudget,
|
|
102745
|
+
observerRunning: false,
|
|
102746
|
+
// true while observer LLM call is in flight
|
|
102747
|
+
extensionMessage: null,
|
|
102748
|
+
// message to show in prepareStep after extension granted
|
|
102749
|
+
startTime: Date.now()
|
|
102750
|
+
};
|
|
102751
|
+
this._negotiatedTimeoutState = negotiatedTimeoutState;
|
|
102752
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
102753
|
+
this._activeTools = activeTools;
|
|
102754
|
+
const onToolCall = (event) => {
|
|
102755
|
+
const key = event.toolCallId || `${event.name}:${JSON.stringify(event.args || {}).slice(0, 100)}`;
|
|
102756
|
+
if (event.status === "started") {
|
|
102757
|
+
activeTools.set(key, {
|
|
102758
|
+
name: event.name,
|
|
102759
|
+
args: event.args,
|
|
102760
|
+
startedAt: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
102761
|
+
});
|
|
102762
|
+
} else if (event.status === "completed" || event.status === "error") {
|
|
102763
|
+
activeTools.delete(key);
|
|
102764
|
+
}
|
|
102765
|
+
};
|
|
102766
|
+
this.events.on("toolCall", onToolCall);
|
|
102767
|
+
const runTimeoutObserver = async () => {
|
|
102768
|
+
if (negotiatedTimeoutState.observerRunning) return;
|
|
102769
|
+
negotiatedTimeoutState.observerRunning = true;
|
|
102770
|
+
const remainingRequests = negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed;
|
|
102771
|
+
const remainingBudgetMs = negotiatedTimeoutState.budgetMs - negotiatedTimeoutState.totalExtraTimeMs;
|
|
102772
|
+
const maxPerReqMin = Math.round(negotiatedTimeoutState.maxPerRequestMs / 6e4);
|
|
102773
|
+
const elapsedMin = Math.round((Date.now() - negotiatedTimeoutState.startTime) / 6e4);
|
|
102774
|
+
if (remainingRequests <= 0 || remainingBudgetMs <= 0) {
|
|
102775
|
+
if (this.debug) {
|
|
102776
|
+
console.log(`[DEBUG] Timeout observer: no extensions/budget remaining \u2014 aborting in-flight tools and triggering graceful wind-down`);
|
|
102777
|
+
}
|
|
102778
|
+
if (this.tracer) {
|
|
102779
|
+
this.tracer.addEvent("negotiated_timeout.observer_exhausted", {
|
|
102780
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
102781
|
+
max_requests: negotiatedTimeoutState.maxRequests,
|
|
102782
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
102783
|
+
budget_ms: negotiatedTimeoutState.budgetMs,
|
|
102784
|
+
elapsed_min: elapsedMin,
|
|
102785
|
+
active_tools: Array.from(activeTools.values()).map((t) => t.name)
|
|
102786
|
+
});
|
|
102787
|
+
}
|
|
102788
|
+
await this._initiateGracefulStop(gracefulTimeoutState, "budget/extensions exhausted");
|
|
102789
|
+
negotiatedTimeoutState.observerRunning = false;
|
|
102790
|
+
return;
|
|
102791
|
+
}
|
|
102792
|
+
const activeToolsList = Array.from(activeTools.values());
|
|
102793
|
+
const now = Date.now();
|
|
102794
|
+
const formatDuration = (ms) => {
|
|
102795
|
+
const totalSec = Math.round(ms / 1e3);
|
|
102796
|
+
if (totalSec < 60) return `${totalSec}s`;
|
|
102797
|
+
const min = Math.floor(totalSec / 60);
|
|
102798
|
+
const sec = totalSec % 60;
|
|
102799
|
+
if (min < 60) return `${min}m ${sec}s`;
|
|
102800
|
+
const hr = Math.floor(min / 60);
|
|
102801
|
+
const remainMin = min % 60;
|
|
102802
|
+
return `${hr}h ${remainMin}m`;
|
|
102803
|
+
};
|
|
102804
|
+
const activeToolsDesc = activeToolsList.length > 0 ? activeToolsList.map((t) => {
|
|
102805
|
+
const runningForMs = now - new Date(t.startedAt).getTime();
|
|
102806
|
+
return `- ${t.name}(${JSON.stringify(t.args || {}).slice(0, 200)}) \u2014 running for ${formatDuration(runningForMs)}`;
|
|
102807
|
+
}).join("\n") : "(none currently running)";
|
|
102808
|
+
const recentHistory = this.history.slice(-6).map((msg) => {
|
|
102809
|
+
const content = typeof msg.content === "string" ? msg.content.slice(0, 300) : JSON.stringify(msg.content).slice(0, 300);
|
|
102810
|
+
return `[${msg.role}]: ${content}`;
|
|
102811
|
+
}).join("\n");
|
|
102812
|
+
const observerPrompt = `You are a timeout observer for an AI coding agent. The agent has been working for ${elapsedMin} minute(s) and has reached its time limit.
|
|
102813
|
+
|
|
102814
|
+
## Recent Conversation
|
|
102815
|
+
${recentHistory || "(no history yet)"}
|
|
102816
|
+
|
|
102817
|
+
## Currently Running Tools
|
|
102818
|
+
${activeToolsDesc}
|
|
102819
|
+
|
|
102820
|
+
## Budget
|
|
102821
|
+
- Extensions used: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}
|
|
102822
|
+
- Time budget remaining: ${Math.round(remainingBudgetMs / 6e4)} minutes
|
|
102823
|
+
- Max per extension: ${maxPerReqMin} minutes
|
|
102824
|
+
|
|
102825
|
+
Decide whether the agent should get more time. EXTEND if:
|
|
102826
|
+
- Tools are actively running (especially delegates or complex analysis) \u2014 they need time to finish
|
|
102827
|
+
- The agent is making clear progress on a complex task
|
|
102828
|
+
- New information is being gathered that will improve the final answer
|
|
102829
|
+
|
|
102830
|
+
DO NOT EXTEND if:
|
|
102831
|
+
- The agent appears stuck in a loop (repeating the same tool calls or getting the same errors)
|
|
102832
|
+
- The conversation shows the agent retrying failed operations without changing approach
|
|
102833
|
+
- The agent has enough information to answer but keeps searching for more
|
|
102834
|
+
- Tool calls are returning empty or error results repeatedly
|
|
102835
|
+
- The agent is doing redundant work (searching for things it already found)
|
|
102836
|
+
|
|
102837
|
+
A stuck agent will not recover with more time \u2014 it will just burn the budget. Better to force it to answer with what it has.
|
|
102838
|
+
|
|
102839
|
+
Respond with ONLY valid JSON (no markdown, no explanation):
|
|
102840
|
+
{"extend": true, "minutes": <1-${maxPerReqMin}>, "reason": "your reason here"}
|
|
102841
|
+
or
|
|
102842
|
+
{"extend": false, "reason": "your reason here"}`;
|
|
102843
|
+
const observerFn = async () => {
|
|
102844
|
+
const modelInstance = this.provider ? this.provider(this.model) : this.model;
|
|
102845
|
+
if (this.debug) {
|
|
102846
|
+
console.log(`[DEBUG] Timeout observer: making LLM call (${activeToolsList.length} active tools, ${elapsedMin} min elapsed)`);
|
|
102847
|
+
}
|
|
102848
|
+
if (this.tracer) {
|
|
102849
|
+
this.tracer.addEvent("negotiated_timeout.observer_invoked", {
|
|
102850
|
+
elapsed_min: elapsedMin,
|
|
102851
|
+
active_tools: activeToolsList.map((t) => t.name),
|
|
102852
|
+
active_tools_detail: activeToolsList.map((t) => ({
|
|
102853
|
+
name: t.name,
|
|
102854
|
+
running_for_ms: now - new Date(t.startedAt).getTime(),
|
|
102855
|
+
args_preview: JSON.stringify(t.args || {}).slice(0, 100)
|
|
102856
|
+
})),
|
|
102857
|
+
active_tools_count: activeToolsList.length,
|
|
102858
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
102859
|
+
remaining_requests: remainingRequests,
|
|
102860
|
+
remaining_budget_ms: remainingBudgetMs,
|
|
102861
|
+
history_length: this.history.length
|
|
102862
|
+
});
|
|
102863
|
+
}
|
|
102864
|
+
const observerResult = await (0, import_ai6.generateText)({
|
|
102865
|
+
model: modelInstance,
|
|
102866
|
+
messages: [{ role: "user", content: observerPrompt }],
|
|
102867
|
+
maxTokens: 500
|
|
102868
|
+
});
|
|
102869
|
+
const responseText = observerResult.text.trim();
|
|
102870
|
+
if (this.tracer) {
|
|
102871
|
+
this.tracer.addEvent("negotiated_timeout.observer_response", {
|
|
102872
|
+
response_text: responseText,
|
|
102873
|
+
usage_prompt_tokens: observerResult.usage?.promptTokens,
|
|
102874
|
+
usage_completion_tokens: observerResult.usage?.completionTokens
|
|
102875
|
+
});
|
|
102876
|
+
}
|
|
102877
|
+
const jsonStr = responseText.replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
|
|
102878
|
+
const decision = JSON.parse(jsonStr);
|
|
102879
|
+
if (decision.extend && decision.minutes > 0) {
|
|
102880
|
+
const requestedMs = Math.min(decision.minutes, maxPerReqMin) * 6e4;
|
|
102881
|
+
const grantedMs = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
|
|
102882
|
+
const grantedMin = Math.round(grantedMs / 6e4 * 10) / 10;
|
|
102883
|
+
negotiatedTimeoutState.extensionsUsed++;
|
|
102884
|
+
negotiatedTimeoutState.totalExtraTimeMs += grantedMs;
|
|
102885
|
+
negotiatedTimeoutState.extensionMessage = `\u23F0 Time limit was reached. The timeout observer granted ${grantedMin} more minute(s) (reason: ${decision.reason || "work in progress"}). Extensions remaining: ${negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed}. Continue your work efficiently.`;
|
|
102886
|
+
negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
|
|
102887
|
+
runTimeoutObserver();
|
|
102888
|
+
}, grantedMs);
|
|
102889
|
+
if (this.debug) {
|
|
102890
|
+
console.log(`[DEBUG] Timeout observer: granted ${grantedMin} min (reason: ${decision.reason}). Extensions: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}`);
|
|
102891
|
+
}
|
|
102892
|
+
if (this.tracer) {
|
|
102893
|
+
this.tracer.addEvent("negotiated_timeout.observer_extended", {
|
|
102894
|
+
decision_reason: decision.reason,
|
|
102895
|
+
requested_minutes: decision.minutes,
|
|
102896
|
+
granted_ms: grantedMs,
|
|
102897
|
+
granted_min: grantedMin,
|
|
102898
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
102899
|
+
max_requests: negotiatedTimeoutState.maxRequests,
|
|
102900
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
102901
|
+
budget_remaining_ms: remainingBudgetMs - grantedMs,
|
|
102902
|
+
active_tools: activeToolsList.map((t) => t.name),
|
|
102903
|
+
active_tools_count: activeToolsList.length
|
|
102904
|
+
});
|
|
102905
|
+
}
|
|
102906
|
+
} else {
|
|
102907
|
+
if (this.debug) {
|
|
102908
|
+
console.log(`[DEBUG] Timeout observer: declined extension (reason: ${decision.reason}). Initiating graceful stop.`);
|
|
102909
|
+
}
|
|
102910
|
+
if (this.tracer) {
|
|
102911
|
+
this.tracer.addEvent("negotiated_timeout.observer_declined", {
|
|
102912
|
+
decision_reason: decision.reason,
|
|
102913
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
102914
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
102915
|
+
elapsed_min: elapsedMin,
|
|
102916
|
+
active_tools: activeToolsList.map((t) => t.name)
|
|
102917
|
+
});
|
|
102918
|
+
}
|
|
102919
|
+
await this._initiateGracefulStop(gracefulTimeoutState, `observer declined: ${decision.reason}`);
|
|
102920
|
+
}
|
|
102921
|
+
};
|
|
102922
|
+
try {
|
|
102923
|
+
if (this.tracer) {
|
|
102924
|
+
await this.tracer.withSpan("negotiated_timeout.observer", observerFn, {
|
|
102925
|
+
"timeout.elapsed_min": elapsedMin,
|
|
102926
|
+
"timeout.extensions_used": negotiatedTimeoutState.extensionsUsed,
|
|
102927
|
+
"timeout.active_tools_count": activeToolsList.length,
|
|
102928
|
+
"timeout.remaining_budget_ms": remainingBudgetMs
|
|
102929
|
+
});
|
|
102930
|
+
} else {
|
|
102931
|
+
await observerFn();
|
|
102932
|
+
}
|
|
102933
|
+
} catch (err) {
|
|
102934
|
+
if (this.debug) {
|
|
102935
|
+
console.log(`[DEBUG] Timeout observer: LLM call failed (${err.message}). Initiating graceful stop.`);
|
|
102936
|
+
}
|
|
102937
|
+
if (this.tracer) {
|
|
102938
|
+
this.tracer.addEvent("negotiated_timeout.observer_error", {
|
|
102939
|
+
error_message: err.message,
|
|
102940
|
+
error_name: err.name,
|
|
102941
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
102942
|
+
elapsed_min: elapsedMin
|
|
102943
|
+
});
|
|
102944
|
+
}
|
|
102945
|
+
await this._initiateGracefulStop(gracefulTimeoutState, `observer error: ${err.message}`);
|
|
102946
|
+
} finally {
|
|
102947
|
+
negotiatedTimeoutState.observerRunning = false;
|
|
102948
|
+
}
|
|
102949
|
+
};
|
|
102950
|
+
negotiatedTimeoutState.runObserver = runTimeoutObserver;
|
|
102402
102951
|
let compactionAttempted = false;
|
|
102403
102952
|
while (true) {
|
|
102404
102953
|
try {
|
|
@@ -102408,6 +102957,15 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102408
102957
|
messages: messagesForAI,
|
|
102409
102958
|
tools: tools2,
|
|
102410
102959
|
stopWhen: ({ steps }) => {
|
|
102960
|
+
if (gracefulTimeoutState.triggered) {
|
|
102961
|
+
if (gracefulTimeoutState.bonusStepsUsed >= gracefulTimeoutState.bonusStepsMax) {
|
|
102962
|
+
if (this.debug) {
|
|
102963
|
+
console.log(`[DEBUG] stopWhen: graceful timeout bonus steps exhausted (${gracefulTimeoutState.bonusStepsUsed}/${gracefulTimeoutState.bonusStepsMax}), forcing stop`);
|
|
102964
|
+
}
|
|
102965
|
+
return true;
|
|
102966
|
+
}
|
|
102967
|
+
return false;
|
|
102968
|
+
}
|
|
102411
102969
|
if (steps.length >= maxIterations) return true;
|
|
102412
102970
|
const lastStep = steps[steps.length - 1];
|
|
102413
102971
|
const modelWantsToStop = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
|
|
@@ -102451,9 +103009,45 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102451
103009
|
return false;
|
|
102452
103010
|
},
|
|
102453
103011
|
prepareStep: ({ steps, stepNumber }) => {
|
|
103012
|
+
if (negotiatedTimeoutState.extensionMessage && !gracefulTimeoutState.triggered) {
|
|
103013
|
+
const msg = negotiatedTimeoutState.extensionMessage;
|
|
103014
|
+
negotiatedTimeoutState.extensionMessage = null;
|
|
103015
|
+
if (this.debug) {
|
|
103016
|
+
console.log(`[DEBUG] prepareStep: delivering timeout observer extension message`);
|
|
103017
|
+
}
|
|
103018
|
+
return { userMessage: msg };
|
|
103019
|
+
}
|
|
103020
|
+
if (gracefulTimeoutState.triggered) {
|
|
103021
|
+
gracefulTimeoutState.bonusStepsUsed++;
|
|
103022
|
+
const remaining = gracefulTimeoutState.bonusStepsMax - gracefulTimeoutState.bonusStepsUsed;
|
|
103023
|
+
if (gracefulTimeoutState.bonusStepsUsed === 1) {
|
|
103024
|
+
if (this.debug) {
|
|
103025
|
+
console.log(`[DEBUG] prepareStep: graceful timeout wind-down step 1/${gracefulTimeoutState.bonusStepsMax}`);
|
|
103026
|
+
}
|
|
103027
|
+
if (this.tracer) {
|
|
103028
|
+
this.tracer.addEvent("graceful_timeout.wind_down_started", {
|
|
103029
|
+
bonus_steps_max: gracefulTimeoutState.bonusStepsMax,
|
|
103030
|
+
current_iteration: currentIteration,
|
|
103031
|
+
max_iterations: maxIterations
|
|
103032
|
+
});
|
|
103033
|
+
}
|
|
103034
|
+
return {
|
|
103035
|
+
toolChoice: "none",
|
|
103036
|
+
userMessage: `\u26A0\uFE0F TIME LIMIT REACHED. You are running out of time. You have ${remaining} step(s) remaining. Provide your BEST answer NOW using the information you have already gathered. Do NOT call any more tools. Summarize your findings and respond completely. If something was not completed, honestly state what was not done and provide any partial results or recommendations you can offer.`
|
|
103037
|
+
};
|
|
103038
|
+
}
|
|
103039
|
+
if (this.debug) {
|
|
103040
|
+
console.log(`[DEBUG] prepareStep: graceful timeout wind-down step ${gracefulTimeoutState.bonusStepsUsed}/${gracefulTimeoutState.bonusStepsMax} (${remaining} remaining)`);
|
|
103041
|
+
}
|
|
103042
|
+
return { toolChoice: "none" };
|
|
103043
|
+
}
|
|
102454
103044
|
if (stepNumber === maxIterations - 1) {
|
|
103045
|
+
const searchesTried = _toolCallLog.filter((tc) => tc.name === "search").map((tc) => `"${tc.args.query || ""}"${tc.args.exact ? " (exact)" : ""}`).filter((v, i, a) => a.indexOf(v) === i);
|
|
103046
|
+
const searchSummary = searchesTried.length > 0 ? `
|
|
103047
|
+
Searches attempted: ${searchesTried.join(", ")}` : "";
|
|
102455
103048
|
return {
|
|
102456
|
-
toolChoice: "none"
|
|
103049
|
+
toolChoice: "none",
|
|
103050
|
+
userMessage: `\u26A0\uFE0F LAST ITERATION \u2014 you are out of tool calls. Provide your BEST answer NOW with the information gathered so far. If you could not find what was requested, explain exactly what you searched for and why it did not work, so the caller can try a different approach.${searchSummary}`
|
|
102457
103051
|
};
|
|
102458
103052
|
}
|
|
102459
103053
|
if (steps.length >= 2) {
|
|
@@ -102532,6 +103126,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102532
103126
|
const { toolResults, toolCalls, text, reasoningText, finishReason, usage } = stepResult;
|
|
102533
103127
|
currentIteration++;
|
|
102534
103128
|
toolContext.currentIteration = currentIteration;
|
|
103129
|
+
if (toolCalls?.length > 0) {
|
|
103130
|
+
for (const tc of toolCalls) {
|
|
103131
|
+
_toolCallLog.push({ name: tc.toolName, args: tc.args || {} });
|
|
103132
|
+
}
|
|
103133
|
+
}
|
|
102535
103134
|
if (this.tracer) {
|
|
102536
103135
|
const stepEvent = {
|
|
102537
103136
|
"iteration": currentIteration,
|
|
@@ -102554,6 +103153,12 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102554
103153
|
}));
|
|
102555
103154
|
}
|
|
102556
103155
|
this.tracer.addEvent("iteration.step", stepEvent);
|
|
103156
|
+
if (gracefulTimeoutState.triggered) {
|
|
103157
|
+
this.tracer.addEvent("graceful_timeout.wind_down_step", {
|
|
103158
|
+
bonus_step: gracefulTimeoutState.bonusStepsUsed,
|
|
103159
|
+
bonus_max: gracefulTimeoutState.bonusStepsMax
|
|
103160
|
+
});
|
|
103161
|
+
}
|
|
102557
103162
|
}
|
|
102558
103163
|
if (usage) {
|
|
102559
103164
|
this.tokenCounter.recordUsage(usage);
|
|
@@ -102599,22 +103204,59 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102599
103204
|
}
|
|
102600
103205
|
const executeAIRequest = async () => {
|
|
102601
103206
|
const result = await this.streamTextWithRetryAndFallback(streamOptions);
|
|
102602
|
-
|
|
102603
|
-
let
|
|
102604
|
-
if (
|
|
102605
|
-
|
|
102606
|
-
|
|
102607
|
-
|
|
102608
|
-
|
|
103207
|
+
let gracefulTimeoutId = null;
|
|
103208
|
+
let hardAbortTimeoutId = null;
|
|
103209
|
+
if (this.timeoutBehavior === "graceful" && gracefulTimeoutState && this.maxOperationTimeout > 0) {
|
|
103210
|
+
gracefulTimeoutId = setTimeout(() => {
|
|
103211
|
+
gracefulTimeoutState.triggered = true;
|
|
103212
|
+
if (this.debug) {
|
|
103213
|
+
console.log(`[DEBUG] Soft timeout after ${this.maxOperationTimeout}ms \u2014 entering wind-down mode (${gracefulTimeoutState.bonusStepsMax} bonus steps)`);
|
|
103214
|
+
}
|
|
103215
|
+
hardAbortTimeoutId = setTimeout(() => {
|
|
103216
|
+
if (this._abortController) {
|
|
103217
|
+
this._abortController.abort();
|
|
103218
|
+
}
|
|
103219
|
+
if (this.debug) {
|
|
103220
|
+
console.log(`[DEBUG] Hard abort \u2014 wind-down safety net expired after 60s`);
|
|
103221
|
+
}
|
|
103222
|
+
}, 6e4);
|
|
103223
|
+
}, this.maxOperationTimeout);
|
|
102609
103224
|
}
|
|
102610
|
-
if (this.
|
|
102611
|
-
|
|
103225
|
+
if (this.timeoutBehavior === "negotiated" && this.maxOperationTimeout > 0) {
|
|
103226
|
+
negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
|
|
103227
|
+
if (this.debug) {
|
|
103228
|
+
console.log(`[DEBUG] Soft timeout after ${this.maxOperationTimeout}ms \u2014 invoking timeout observer`);
|
|
103229
|
+
}
|
|
103230
|
+
runTimeoutObserver();
|
|
103231
|
+
}, this.maxOperationTimeout);
|
|
102612
103232
|
}
|
|
102613
|
-
|
|
102614
|
-
|
|
102615
|
-
|
|
103233
|
+
try {
|
|
103234
|
+
const steps = await result.steps;
|
|
103235
|
+
let finalText;
|
|
103236
|
+
if (steps && steps.length > 1) {
|
|
103237
|
+
const lastStepText = steps[steps.length - 1].text;
|
|
103238
|
+
finalText = lastStepText || await result.text;
|
|
103239
|
+
} else {
|
|
103240
|
+
finalText = await result.text;
|
|
103241
|
+
}
|
|
103242
|
+
if (this.debug) {
|
|
103243
|
+
console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
|
|
103244
|
+
}
|
|
103245
|
+
const usage = await result.usage;
|
|
103246
|
+
if (usage) {
|
|
103247
|
+
this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
|
|
103248
|
+
}
|
|
103249
|
+
return { finalText, result };
|
|
103250
|
+
} finally {
|
|
103251
|
+
if (gracefulTimeoutId) clearTimeout(gracefulTimeoutId);
|
|
103252
|
+
if (hardAbortTimeoutId) clearTimeout(hardAbortTimeoutId);
|
|
103253
|
+
if (negotiatedTimeoutState.softTimeoutId) clearTimeout(negotiatedTimeoutState.softTimeoutId);
|
|
103254
|
+
if (this._gracefulStopHardAbortId) {
|
|
103255
|
+
clearTimeout(this._gracefulStopHardAbortId);
|
|
103256
|
+
this._gracefulStopHardAbortId = null;
|
|
103257
|
+
}
|
|
103258
|
+
this.events.removeListener("toolCall", onToolCall);
|
|
102616
103259
|
}
|
|
102617
|
-
return { finalText, result };
|
|
102618
103260
|
};
|
|
102619
103261
|
let aiResult;
|
|
102620
103262
|
if (this.tracer) {
|
|
@@ -102651,13 +103293,57 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102651
103293
|
} else if (aiResult.finalText) {
|
|
102652
103294
|
finalResult = aiResult.finalText;
|
|
102653
103295
|
}
|
|
103296
|
+
if (gracefulTimeoutState.triggered) {
|
|
103297
|
+
const timeoutNotice = "**Note: This response was generated under a time constraint. The research may be incomplete, and some planned searches or analysis steps were not completed.**\n\n";
|
|
103298
|
+
if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG || finalResult.startsWith("I was unable to complete your request after")) {
|
|
103299
|
+
try {
|
|
103300
|
+
const allText = await aiResult.result.text;
|
|
103301
|
+
if (allText && allText.trim()) {
|
|
103302
|
+
finalResult = timeoutNotice + allText;
|
|
103303
|
+
if (this.debug) {
|
|
103304
|
+
console.log(`[DEBUG] Graceful timeout: using concatenated step text (${allText.length} chars)`);
|
|
103305
|
+
}
|
|
103306
|
+
} else {
|
|
103307
|
+
const steps = await aiResult.result.steps;
|
|
103308
|
+
const toolSummaries = [];
|
|
103309
|
+
for (const step of steps || []) {
|
|
103310
|
+
if (step.toolResults?.length > 0) {
|
|
103311
|
+
for (const tr of step.toolResults) {
|
|
103312
|
+
const resultText = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
|
|
103313
|
+
if (resultText && resultText.length > 0 && resultText.length < 5e3) {
|
|
103314
|
+
toolSummaries.push(resultText.substring(0, 2e3));
|
|
103315
|
+
}
|
|
103316
|
+
}
|
|
103317
|
+
}
|
|
103318
|
+
}
|
|
103319
|
+
if (toolSummaries.length > 0) {
|
|
103320
|
+
finalResult = `${timeoutNotice}The operation timed out before a complete answer could be generated. Here is the partial information gathered:
|
|
103321
|
+
|
|
103322
|
+
${toolSummaries.join("\n\n---\n\n")}`;
|
|
103323
|
+
if (this.debug) {
|
|
103324
|
+
console.log(`[DEBUG] Graceful timeout: built fallback from ${toolSummaries.length} tool results`);
|
|
103325
|
+
}
|
|
103326
|
+
} else {
|
|
103327
|
+
finalResult = "The operation timed out before enough information could be gathered to provide an answer. Please try again with a simpler query or increase the timeout.";
|
|
103328
|
+
}
|
|
103329
|
+
}
|
|
103330
|
+
} catch (e) {
|
|
103331
|
+
if (this.debug) {
|
|
103332
|
+
console.log(`[DEBUG] Graceful timeout fallback error: ${e.message}`);
|
|
103333
|
+
}
|
|
103334
|
+
finalResult = "The operation timed out before enough information could be gathered to provide an answer. Please try again with a simpler query or increase the timeout.";
|
|
103335
|
+
}
|
|
103336
|
+
} else {
|
|
103337
|
+
finalResult = timeoutNotice + finalResult;
|
|
103338
|
+
}
|
|
103339
|
+
}
|
|
102654
103340
|
const resultMessages = await aiResult.result.response?.messages;
|
|
102655
103341
|
if (resultMessages) {
|
|
102656
103342
|
for (const msg of resultMessages) {
|
|
102657
103343
|
currentMessages.push(msg);
|
|
102658
103344
|
}
|
|
102659
103345
|
}
|
|
102660
|
-
if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && finalResult) {
|
|
103346
|
+
if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && !abortSummaryTaken && finalResult) {
|
|
102661
103347
|
completionPromptInjected = true;
|
|
102662
103348
|
preCompletionResult = finalResult;
|
|
102663
103349
|
if (this.debug) {
|
|
@@ -102729,6 +103415,118 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102729
103415
|
}
|
|
102730
103416
|
break;
|
|
102731
103417
|
} catch (error40) {
|
|
103418
|
+
if (gracefulTimeoutState.triggered && error40?.name === "AbortError") {
|
|
103419
|
+
if (this.debug) {
|
|
103420
|
+
console.log(`[DEBUG] Negotiated timeout: abort caught \u2014 making summary LLM call with conversation context`);
|
|
103421
|
+
}
|
|
103422
|
+
if (this.tracer) {
|
|
103423
|
+
this.tracer.addEvent("negotiated_timeout.abort_summary_started", {
|
|
103424
|
+
conversation_messages: currentMessages.length,
|
|
103425
|
+
has_schema: !!options.schema,
|
|
103426
|
+
has_tasks: !!(this.enableTasks && this.taskManager)
|
|
103427
|
+
});
|
|
103428
|
+
}
|
|
103429
|
+
try {
|
|
103430
|
+
let taskContext = "";
|
|
103431
|
+
if (this.enableTasks && this.taskManager) {
|
|
103432
|
+
const taskSummary = this.taskManager.getTaskSummary?.();
|
|
103433
|
+
if (taskSummary) {
|
|
103434
|
+
taskContext = `
|
|
103435
|
+
|
|
103436
|
+
## Task Status
|
|
103437
|
+
${taskSummary}
|
|
103438
|
+
|
|
103439
|
+
Acknowledge which tasks were completed and which were not.`;
|
|
103440
|
+
}
|
|
103441
|
+
}
|
|
103442
|
+
let schemaContext = "";
|
|
103443
|
+
if (options.schema) {
|
|
103444
|
+
try {
|
|
103445
|
+
const parsedSchema = typeof options.schema === "string" ? JSON.parse(options.schema) : options.schema;
|
|
103446
|
+
schemaContext = `
|
|
103447
|
+
|
|
103448
|
+
IMPORTANT: Your response MUST be valid JSON matching this schema:
|
|
103449
|
+
${JSON.stringify(parsedSchema, null, 2)}
|
|
103450
|
+
|
|
103451
|
+
Respond with ONLY valid JSON \u2014 no markdown, no explanation, no text outside the JSON object. Include all findings and partial results within the JSON structure. If fields cannot be fully populated due to the interruption, use partial data or null values as appropriate.`;
|
|
103452
|
+
} catch {
|
|
103453
|
+
}
|
|
103454
|
+
}
|
|
103455
|
+
const summaryPrompt = `Your operation was interrupted by a timeout observer because the time limit was reached. Some of your tool calls were cancelled mid-execution.
|
|
103456
|
+
|
|
103457
|
+
Please provide a DETAILED summary of:
|
|
103458
|
+
1. What you were asked to do (the original task)
|
|
103459
|
+
2. What you accomplished \u2014 include ALL findings, code snippets, data, and conclusions you gathered
|
|
103460
|
+
3. What was still in progress or not yet started
|
|
103461
|
+
4. Any partial results or recommendations you can offer based on what you found so far${taskContext}${schemaContext}
|
|
103462
|
+
|
|
103463
|
+
Be thorough \u2014 this is the user's only response. Include all useful information you collected.`;
|
|
103464
|
+
const summaryMessages = [
|
|
103465
|
+
...currentMessages,
|
|
103466
|
+
{ role: "user", content: summaryPrompt }
|
|
103467
|
+
];
|
|
103468
|
+
const modelInstance = this.provider ? this.provider(this.model) : this.model;
|
|
103469
|
+
const summaryFn = async () => {
|
|
103470
|
+
const summaryResult = await (0, import_ai6.generateText)({
|
|
103471
|
+
model: modelInstance,
|
|
103472
|
+
messages: this.prepareMessagesWithImages(summaryMessages),
|
|
103473
|
+
maxTokens: 4e3
|
|
103474
|
+
});
|
|
103475
|
+
if (this.tracer) {
|
|
103476
|
+
this.tracer.addEvent("negotiated_timeout.abort_summary_completed", {
|
|
103477
|
+
summary_length: summaryResult.text?.length || 0,
|
|
103478
|
+
usage_prompt_tokens: summaryResult.usage?.promptTokens,
|
|
103479
|
+
usage_completion_tokens: summaryResult.usage?.completionTokens
|
|
103480
|
+
});
|
|
103481
|
+
}
|
|
103482
|
+
if (summaryResult.usage) {
|
|
103483
|
+
this.tokenCounter.recordUsage(summaryResult.usage);
|
|
103484
|
+
}
|
|
103485
|
+
return summaryResult.text;
|
|
103486
|
+
};
|
|
103487
|
+
let summaryText;
|
|
103488
|
+
if (this.tracer) {
|
|
103489
|
+
summaryText = await this.tracer.withSpan("negotiated_timeout.abort_summary", summaryFn, {
|
|
103490
|
+
"summary.conversation_messages": currentMessages.length
|
|
103491
|
+
});
|
|
103492
|
+
} else {
|
|
103493
|
+
summaryText = await summaryFn();
|
|
103494
|
+
}
|
|
103495
|
+
if (options.schema) {
|
|
103496
|
+
finalResult = summaryText || "{}";
|
|
103497
|
+
} else {
|
|
103498
|
+
const timeoutNotice = "**Note: This response was generated under a time constraint. The timeout observer interrupted the operation because the time budget was exhausted.**\n\n";
|
|
103499
|
+
finalResult = timeoutNotice + (summaryText || "The operation was interrupted before a response could be generated.");
|
|
103500
|
+
}
|
|
103501
|
+
if (options.onStream && finalResult) {
|
|
103502
|
+
options.onStream(finalResult);
|
|
103503
|
+
}
|
|
103504
|
+
if (this.debug) {
|
|
103505
|
+
console.log(`[DEBUG] Negotiated timeout: summary produced ${summaryText?.length || 0} chars`);
|
|
103506
|
+
}
|
|
103507
|
+
} catch (summaryErr) {
|
|
103508
|
+
if (this.debug) {
|
|
103509
|
+
console.log(`[DEBUG] Negotiated timeout: summary call failed (${summaryErr.message}), falling back to partial text`);
|
|
103510
|
+
}
|
|
103511
|
+
if (this.tracer) {
|
|
103512
|
+
this.tracer.addEvent("negotiated_timeout.abort_summary_error", {
|
|
103513
|
+
error_message: summaryErr.message
|
|
103514
|
+
});
|
|
103515
|
+
}
|
|
103516
|
+
const partialTexts = currentMessages.filter((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim()).map((m) => m.content);
|
|
103517
|
+
if (options.schema) {
|
|
103518
|
+
finalResult = partialTexts.length > 0 ? partialTexts[partialTexts.length - 1] : "{}";
|
|
103519
|
+
} else {
|
|
103520
|
+
const timeoutNotice = "**Note: This response was generated under a time constraint. The operation was interrupted and some work was not completed.**\n\n";
|
|
103521
|
+
finalResult = partialTexts.length > 0 ? timeoutNotice + partialTexts[partialTexts.length - 1] : timeoutNotice + "The operation was interrupted before enough information could be gathered. Please try again with a simpler query or increase the timeout.";
|
|
103522
|
+
}
|
|
103523
|
+
if (options.onStream && finalResult) {
|
|
103524
|
+
options.onStream(finalResult);
|
|
103525
|
+
}
|
|
103526
|
+
}
|
|
103527
|
+
abortSummaryTaken = true;
|
|
103528
|
+
break;
|
|
103529
|
+
}
|
|
102732
103530
|
if (!compactionAttempted && handleContextLimitError) {
|
|
102733
103531
|
const compactionResult = handleContextLimitError(error40, currentMessages, {
|
|
102734
103532
|
keepLastSegment: true,
|
|
@@ -102763,6 +103561,36 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102763
103561
|
}
|
|
102764
103562
|
if (currentIteration >= maxIterations) {
|
|
102765
103563
|
console.warn(`[WARN] Max tool iterations (${maxIterations}) reached for session ${this.sessionId}.`);
|
|
103564
|
+
if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG) {
|
|
103565
|
+
try {
|
|
103566
|
+
const searchQueries = [];
|
|
103567
|
+
const toolCounts = {};
|
|
103568
|
+
for (const tc of _toolCallLog) {
|
|
103569
|
+
toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
|
|
103570
|
+
if (tc.name === "search") {
|
|
103571
|
+
const q = tc.args.query || "";
|
|
103572
|
+
const exact = tc.args.exact ? " (exact)" : "";
|
|
103573
|
+
searchQueries.push(`"${q}"${exact}`);
|
|
103574
|
+
}
|
|
103575
|
+
}
|
|
103576
|
+
const toolBreakdown = Object.entries(toolCounts).map(([name15, count]) => `${name15}: ${count}x`).join(", ");
|
|
103577
|
+
const uniqueSearches = [...new Set(searchQueries)];
|
|
103578
|
+
let summary = `I was unable to complete your request after ${currentIteration} tool iterations.
|
|
103579
|
+
|
|
103580
|
+
`;
|
|
103581
|
+
summary += `Tool calls made: ${toolBreakdown || "none"}
|
|
103582
|
+
`;
|
|
103583
|
+
if (uniqueSearches.length > 0) {
|
|
103584
|
+
summary += `Search queries tried: ${uniqueSearches.join(", ")}
|
|
103585
|
+
`;
|
|
103586
|
+
}
|
|
103587
|
+
summary += `
|
|
103588
|
+
The search approach may be fundamentally wrong for this query. Consider: using exact=true for literal string matching, using bash/grep for pattern-based file searches, or trying a completely different strategy instead of repeating similar searches.`;
|
|
103589
|
+
finalResult = summary;
|
|
103590
|
+
} catch {
|
|
103591
|
+
finalResult = DEFAULT_MAX_ITER_MSG;
|
|
103592
|
+
}
|
|
103593
|
+
}
|
|
102766
103594
|
}
|
|
102767
103595
|
this.history = currentMessages.map((msg) => ({ ...msg }));
|
|
102768
103596
|
if (this.history.length > MAX_HISTORY_MESSAGES) {
|
|
@@ -103297,6 +104125,134 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
103297
104125
|
console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
|
|
103298
104126
|
}
|
|
103299
104127
|
}
|
|
104128
|
+
/**
|
|
104129
|
+
* Trigger graceful wind-down from outside (e.g., parent agent).
|
|
104130
|
+
* Unlike cancel(), this does NOT abort — it sets the graceful timeout flag
|
|
104131
|
+
* so the agent finishes its current step and then winds down naturally.
|
|
104132
|
+
*/
|
|
104133
|
+
triggerGracefulWindDown() {
|
|
104134
|
+
if (this._gracefulTimeoutState && !this._gracefulTimeoutState.triggered) {
|
|
104135
|
+
this._gracefulTimeoutState.triggered = true;
|
|
104136
|
+
if (this.debug) {
|
|
104137
|
+
console.log(`[DEBUG] Graceful wind-down triggered externally for session ${this.sessionId}`);
|
|
104138
|
+
}
|
|
104139
|
+
if (this.tracer) {
|
|
104140
|
+
this.tracer.addEvent("graceful_stop.external_trigger", {
|
|
104141
|
+
"session.id": this.sessionId
|
|
104142
|
+
});
|
|
104143
|
+
}
|
|
104144
|
+
} else if (this.debug) {
|
|
104145
|
+
console.log(`[DEBUG] Graceful wind-down already active for session ${this.sessionId}, skipping`);
|
|
104146
|
+
}
|
|
104147
|
+
}
|
|
104148
|
+
/**
|
|
104149
|
+
* Initiate two-phase graceful stop: signal subagents and MCP servers to wind down,
|
|
104150
|
+
* then hard-abort after a deadline if they haven't finished.
|
|
104151
|
+
* @param {Object} gracefulTimeoutState - The graceful timeout state object from run()
|
|
104152
|
+
* @param {string} reason - Why the graceful stop was initiated
|
|
104153
|
+
*/
|
|
104154
|
+
async _initiateGracefulStop(gracefulTimeoutState, reason) {
|
|
104155
|
+
if (gracefulTimeoutState.triggered) return;
|
|
104156
|
+
if (this.debug) {
|
|
104157
|
+
console.log(`[DEBUG] Initiating graceful stop: ${reason} (subagents: ${this._activeSubagents.size}, hasMcpBridge: ${!!this.mcpBridge}, deadline: ${this.gracefulStopDeadline}ms)`);
|
|
104158
|
+
}
|
|
104159
|
+
gracefulTimeoutState.triggered = true;
|
|
104160
|
+
if (this.tracer) {
|
|
104161
|
+
this.tracer.addEvent("graceful_stop.initiated", {
|
|
104162
|
+
"session.id": this.sessionId,
|
|
104163
|
+
"graceful_stop.reason": reason,
|
|
104164
|
+
"graceful_stop.active_subagents": this._activeSubagents.size,
|
|
104165
|
+
"graceful_stop.has_mcp_bridge": !!this.mcpBridge,
|
|
104166
|
+
"graceful_stop.deadline_ms": this.gracefulStopDeadline
|
|
104167
|
+
});
|
|
104168
|
+
}
|
|
104169
|
+
let subagentsSignalled = 0;
|
|
104170
|
+
let subagentErrors = 0;
|
|
104171
|
+
for (const [sid, subagent] of this._activeSubagents) {
|
|
104172
|
+
try {
|
|
104173
|
+
subagent.triggerGracefulWindDown();
|
|
104174
|
+
subagentsSignalled++;
|
|
104175
|
+
if (this.debug) {
|
|
104176
|
+
console.log(`[DEBUG] Triggered graceful wind-down on subagent ${sid}`);
|
|
104177
|
+
}
|
|
104178
|
+
} catch (e) {
|
|
104179
|
+
subagentErrors++;
|
|
104180
|
+
if (this.debug) {
|
|
104181
|
+
console.log(`[DEBUG] Failed to trigger wind-down on subagent ${sid}: ${e.message}`);
|
|
104182
|
+
}
|
|
104183
|
+
}
|
|
104184
|
+
}
|
|
104185
|
+
let mcpResults = [];
|
|
104186
|
+
if (this.mcpBridge) {
|
|
104187
|
+
try {
|
|
104188
|
+
mcpResults = await this.mcpBridge.callGracefulStopAll();
|
|
104189
|
+
if (this.debug && mcpResults.length > 0) {
|
|
104190
|
+
console.log(`[DEBUG] MCP graceful_stop results: ${JSON.stringify(mcpResults)}`);
|
|
104191
|
+
}
|
|
104192
|
+
} catch (e) {
|
|
104193
|
+
if (this.debug) {
|
|
104194
|
+
console.log(`[DEBUG] MCP graceful_stop failed: ${e.message}`);
|
|
104195
|
+
}
|
|
104196
|
+
}
|
|
104197
|
+
}
|
|
104198
|
+
if (this.tracer) {
|
|
104199
|
+
this.tracer.addEvent("graceful_stop.signals_sent", {
|
|
104200
|
+
"session.id": this.sessionId,
|
|
104201
|
+
"graceful_stop.subagents_signalled": subagentsSignalled,
|
|
104202
|
+
"graceful_stop.subagent_errors": subagentErrors,
|
|
104203
|
+
"graceful_stop.mcp_servers_called": mcpResults.filter((r) => r.success).length,
|
|
104204
|
+
"graceful_stop.mcp_servers_failed": mcpResults.filter((r) => !r.success).length,
|
|
104205
|
+
"graceful_stop.mcp_servers_total": mcpResults.length
|
|
104206
|
+
});
|
|
104207
|
+
}
|
|
104208
|
+
this._gracefulStopHardAbortId = setTimeout(() => {
|
|
104209
|
+
if (this.debug) {
|
|
104210
|
+
console.log(`[DEBUG] Graceful stop deadline (${this.gracefulStopDeadline}ms) expired \u2014 hard aborting`);
|
|
104211
|
+
}
|
|
104212
|
+
if (this.tracer) {
|
|
104213
|
+
this.tracer.addEvent("graceful_stop.deadline_expired", {
|
|
104214
|
+
"session.id": this.sessionId,
|
|
104215
|
+
"graceful_stop.deadline_ms": this.gracefulStopDeadline
|
|
104216
|
+
});
|
|
104217
|
+
}
|
|
104218
|
+
if (this._abortController) this._abortController.abort();
|
|
104219
|
+
}, this.gracefulStopDeadline);
|
|
104220
|
+
}
|
|
104221
|
+
/**
|
|
104222
|
+
* Register an active subagent for graceful stop coordination.
|
|
104223
|
+
* @param {string} sessionId
|
|
104224
|
+
* @param {ProbeAgent} subagent
|
|
104225
|
+
*/
|
|
104226
|
+
_registerSubagent(sessionId, subagent) {
|
|
104227
|
+
this._activeSubagents.set(sessionId, subagent);
|
|
104228
|
+
if (this.debug) {
|
|
104229
|
+
console.log(`[DEBUG] Registered subagent ${sessionId} (active: ${this._activeSubagents.size})`);
|
|
104230
|
+
}
|
|
104231
|
+
if (this.tracer) {
|
|
104232
|
+
this.tracer.addEvent("subagent.registered", {
|
|
104233
|
+
"session.id": this.sessionId,
|
|
104234
|
+
"subagent.session_id": sessionId,
|
|
104235
|
+
"subagent.active_count": this._activeSubagents.size
|
|
104236
|
+
});
|
|
104237
|
+
}
|
|
104238
|
+
}
|
|
104239
|
+
/**
|
|
104240
|
+
* Unregister a completed subagent.
|
|
104241
|
+
* @param {string} sessionId
|
|
104242
|
+
*/
|
|
104243
|
+
_unregisterSubagent(sessionId) {
|
|
104244
|
+
this._activeSubagents.delete(sessionId);
|
|
104245
|
+
if (this.debug) {
|
|
104246
|
+
console.log(`[DEBUG] Unregistered subagent ${sessionId} (active: ${this._activeSubagents.size})`);
|
|
104247
|
+
}
|
|
104248
|
+
if (this.tracer) {
|
|
104249
|
+
this.tracer.addEvent("subagent.unregistered", {
|
|
104250
|
+
"session.id": this.sessionId,
|
|
104251
|
+
"subagent.session_id": sessionId,
|
|
104252
|
+
"subagent.active_count": this._activeSubagents.size
|
|
104253
|
+
});
|
|
104254
|
+
}
|
|
104255
|
+
}
|
|
103300
104256
|
/**
|
|
103301
104257
|
* Get the abort signal for this agent.
|
|
103302
104258
|
* Delegations and subagents should check this signal.
|