@probelabs/probe 0.6.0-rc295 → 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-rc295-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc296-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc295-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-rc295-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-rc295-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-rc295-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 +8 -2
- package/build/agent/ProbeAgent.js +683 -10
- 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 +858 -32
- package/cjs/agent/simpleTelemetry.cjs +112 -0
- package/cjs/index.cjs +970 -32
- package/index.d.ts +26 -0
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +8 -2
- package/src/agent/ProbeAgent.js +683 -10
- 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}")`);
|
|
27619
27742
|
}
|
|
27620
|
-
if (
|
|
27621
|
-
return "STOP. You have been blocked " +
|
|
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.";
|
|
27622
27745
|
}
|
|
27623
|
-
|
|
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.`;
|
|
27749
|
+
}
|
|
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;
|
|
@@ -99873,14 +100151,31 @@ var init_ProbeAgent = __esm({
|
|
|
99873
100151
|
this.timeoutBehavior = options.timeoutBehavior ?? (() => {
|
|
99874
100152
|
const val = process.env.TIMEOUT_BEHAVIOR;
|
|
99875
100153
|
if (val === "hard") return "hard";
|
|
100154
|
+
if (val === "negotiated") return "negotiated";
|
|
99876
100155
|
return "graceful";
|
|
99877
100156
|
})();
|
|
99878
100157
|
this.gracefulTimeoutBonusSteps = options.gracefulTimeoutBonusSteps ?? (() => {
|
|
99879
100158
|
const parsed = parseInt(process.env.GRACEFUL_TIMEOUT_BONUS_STEPS, 10);
|
|
99880
100159
|
return isNaN(parsed) || parsed < 1 || parsed > 20 ? 4 : parsed;
|
|
99881
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
|
+
})();
|
|
99882
100177
|
if (this.debug) {
|
|
99883
|
-
console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}`);
|
|
100178
|
+
console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}, graceful stop deadline: ${this.gracefulStopDeadline}ms`);
|
|
99884
100179
|
}
|
|
99885
100180
|
this.retryConfig = options.retry || {};
|
|
99886
100181
|
this.retryManager = null;
|
|
@@ -100232,6 +100527,18 @@ var init_ProbeAgent = __esm({
|
|
|
100232
100527
|
// Per-instance delegation limits
|
|
100233
100528
|
parentAbortSignal: this._abortController.signal,
|
|
100234
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),
|
|
100235
100542
|
outputBuffer: this._outputBuffer,
|
|
100236
100543
|
concurrencyLimiter: this.concurrencyLimiter,
|
|
100237
100544
|
// Global AI concurrency limiter
|
|
@@ -100816,7 +101123,7 @@ var init_ProbeAgent = __esm({
|
|
|
100816
101123
|
}
|
|
100817
101124
|
if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
|
|
100818
101125
|
const gts = this._gracefulTimeoutState;
|
|
100819
|
-
if (this.timeoutBehavior === "graceful" && gts) {
|
|
101126
|
+
if ((this.timeoutBehavior === "graceful" || this.timeoutBehavior === "negotiated") && gts) {
|
|
100820
101127
|
} else {
|
|
100821
101128
|
timeoutState.timeoutId = setTimeout(() => {
|
|
100822
101129
|
controller.abort();
|
|
@@ -102203,6 +102510,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102203
102510
|
} else {
|
|
102204
102511
|
options = schemaOrOptions || {};
|
|
102205
102512
|
}
|
|
102513
|
+
this._operationStartTime = Date.now();
|
|
102206
102514
|
try {
|
|
102207
102515
|
const oldHistoryLength = this.history.length;
|
|
102208
102516
|
if (this._outputBuffer && !options?._schemaFormatted && !options?._completionPromptProcessed) {
|
|
@@ -102283,7 +102591,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102283
102591
|
}
|
|
102284
102592
|
}
|
|
102285
102593
|
let currentIteration = 0;
|
|
102286
|
-
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;
|
|
102287
102598
|
const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
|
|
102288
102599
|
const maxIterations = options._maxIterationsOverride ? baseMaxIterations : options.schema ? baseMaxIterations + 4 : baseMaxIterations;
|
|
102289
102600
|
const isClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
@@ -102423,6 +102734,220 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102423
102734
|
bonusStepsMax: this.gracefulTimeoutBonusSteps
|
|
102424
102735
|
};
|
|
102425
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;
|
|
102426
102951
|
let compactionAttempted = false;
|
|
102427
102952
|
while (true) {
|
|
102428
102953
|
try {
|
|
@@ -102484,6 +103009,14 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102484
103009
|
return false;
|
|
102485
103010
|
},
|
|
102486
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
|
+
}
|
|
102487
103020
|
if (gracefulTimeoutState.triggered) {
|
|
102488
103021
|
gracefulTimeoutState.bonusStepsUsed++;
|
|
102489
103022
|
const remaining = gracefulTimeoutState.bonusStepsMax - gracefulTimeoutState.bonusStepsUsed;
|
|
@@ -102509,8 +103042,12 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102509
103042
|
return { toolChoice: "none" };
|
|
102510
103043
|
}
|
|
102511
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(", ")}` : "";
|
|
102512
103048
|
return {
|
|
102513
|
-
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}`
|
|
102514
103051
|
};
|
|
102515
103052
|
}
|
|
102516
103053
|
if (steps.length >= 2) {
|
|
@@ -102589,6 +103126,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102589
103126
|
const { toolResults, toolCalls, text, reasoningText, finishReason, usage } = stepResult;
|
|
102590
103127
|
currentIteration++;
|
|
102591
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
|
+
}
|
|
102592
103134
|
if (this.tracer) {
|
|
102593
103135
|
const stepEvent = {
|
|
102594
103136
|
"iteration": currentIteration,
|
|
@@ -102680,6 +103222,14 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102680
103222
|
}, 6e4);
|
|
102681
103223
|
}, this.maxOperationTimeout);
|
|
102682
103224
|
}
|
|
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);
|
|
103232
|
+
}
|
|
102683
103233
|
try {
|
|
102684
103234
|
const steps = await result.steps;
|
|
102685
103235
|
let finalText;
|
|
@@ -102700,6 +103250,12 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102700
103250
|
} finally {
|
|
102701
103251
|
if (gracefulTimeoutId) clearTimeout(gracefulTimeoutId);
|
|
102702
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);
|
|
102703
103259
|
}
|
|
102704
103260
|
};
|
|
102705
103261
|
let aiResult;
|
|
@@ -102739,7 +103295,7 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102739
103295
|
}
|
|
102740
103296
|
if (gracefulTimeoutState.triggered) {
|
|
102741
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";
|
|
102742
|
-
if (!finalResult || finalResult === "I was unable to complete your request
|
|
103298
|
+
if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG || finalResult.startsWith("I was unable to complete your request after")) {
|
|
102743
103299
|
try {
|
|
102744
103300
|
const allText = await aiResult.result.text;
|
|
102745
103301
|
if (allText && allText.trim()) {
|
|
@@ -102787,7 +103343,7 @@ ${toolSummaries.join("\n\n---\n\n")}`;
|
|
|
102787
103343
|
currentMessages.push(msg);
|
|
102788
103344
|
}
|
|
102789
103345
|
}
|
|
102790
|
-
if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && finalResult) {
|
|
103346
|
+
if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && !abortSummaryTaken && finalResult) {
|
|
102791
103347
|
completionPromptInjected = true;
|
|
102792
103348
|
preCompletionResult = finalResult;
|
|
102793
103349
|
if (this.debug) {
|
|
@@ -102859,6 +103415,118 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102859
103415
|
}
|
|
102860
103416
|
break;
|
|
102861
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
|
+
}
|
|
102862
103530
|
if (!compactionAttempted && handleContextLimitError) {
|
|
102863
103531
|
const compactionResult = handleContextLimitError(error40, currentMessages, {
|
|
102864
103532
|
keepLastSegment: true,
|
|
@@ -102893,6 +103561,36 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102893
103561
|
}
|
|
102894
103562
|
if (currentIteration >= maxIterations) {
|
|
102895
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
|
+
}
|
|
102896
103594
|
}
|
|
102897
103595
|
this.history = currentMessages.map((msg) => ({ ...msg }));
|
|
102898
103596
|
if (this.history.length > MAX_HISTORY_MESSAGES) {
|
|
@@ -103427,6 +104125,134 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
103427
104125
|
console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
|
|
103428
104126
|
}
|
|
103429
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
|
+
}
|
|
103430
104256
|
/**
|
|
103431
104257
|
* Get the abort signal for this agent.
|
|
103432
104258
|
* Delegations and subagents should check this signal.
|