@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/index.cjs
CHANGED
|
@@ -27051,14 +27051,64 @@ function detectStuckResponse(response) {
|
|
|
27051
27051
|
}
|
|
27052
27052
|
return false;
|
|
27053
27053
|
}
|
|
27054
|
+
function splitQuotedString(input) {
|
|
27055
|
+
const tokens = [];
|
|
27056
|
+
let current2 = "";
|
|
27057
|
+
let inQuote = null;
|
|
27058
|
+
let i = 0;
|
|
27059
|
+
while (i < input.length) {
|
|
27060
|
+
const ch = input[i];
|
|
27061
|
+
if (inQuote) {
|
|
27062
|
+
if (ch === "\\" && i + 1 < input.length) {
|
|
27063
|
+
current2 += input[i + 1];
|
|
27064
|
+
i += 2;
|
|
27065
|
+
continue;
|
|
27066
|
+
}
|
|
27067
|
+
if (ch === inQuote) {
|
|
27068
|
+
inQuote = null;
|
|
27069
|
+
i++;
|
|
27070
|
+
continue;
|
|
27071
|
+
}
|
|
27072
|
+
current2 += ch;
|
|
27073
|
+
i++;
|
|
27074
|
+
} else {
|
|
27075
|
+
if (ch === '"' || ch === "'") {
|
|
27076
|
+
inQuote = ch;
|
|
27077
|
+
i++;
|
|
27078
|
+
continue;
|
|
27079
|
+
}
|
|
27080
|
+
if (/[\s,]/.test(ch)) {
|
|
27081
|
+
if (current2.length > 0) {
|
|
27082
|
+
tokens.push(current2);
|
|
27083
|
+
current2 = "";
|
|
27084
|
+
}
|
|
27085
|
+
i++;
|
|
27086
|
+
continue;
|
|
27087
|
+
}
|
|
27088
|
+
current2 += ch;
|
|
27089
|
+
i++;
|
|
27090
|
+
}
|
|
27091
|
+
}
|
|
27092
|
+
if (current2.length > 0) {
|
|
27093
|
+
tokens.push(current2);
|
|
27094
|
+
}
|
|
27095
|
+
return tokens;
|
|
27096
|
+
}
|
|
27054
27097
|
function parseTargets(targets) {
|
|
27055
27098
|
if (!targets || typeof targets !== "string") {
|
|
27056
27099
|
return [];
|
|
27057
27100
|
}
|
|
27058
|
-
return targets
|
|
27101
|
+
return splitQuotedString(targets);
|
|
27059
27102
|
}
|
|
27060
27103
|
function parseAndResolvePaths(pathStr, cwd) {
|
|
27061
27104
|
if (!pathStr) return [];
|
|
27105
|
+
if (/["']/.test(pathStr)) {
|
|
27106
|
+
const paths2 = splitQuotedString(pathStr);
|
|
27107
|
+
return paths2.map((p) => {
|
|
27108
|
+
if ((0, import_path5.isAbsolute)(p)) return p;
|
|
27109
|
+
return cwd ? (0, import_path5.resolve)(cwd, p) : p;
|
|
27110
|
+
});
|
|
27111
|
+
}
|
|
27062
27112
|
let paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
27063
27113
|
paths = paths.flatMap((p) => {
|
|
27064
27114
|
if (!/\s/.test(p)) return [p];
|
|
@@ -27068,9 +27118,7 @@ function parseAndResolvePaths(pathStr, cwd) {
|
|
|
27068
27118
|
return allLookLikePaths ? parts : [p];
|
|
27069
27119
|
});
|
|
27070
27120
|
return paths.map((p) => {
|
|
27071
|
-
if ((0, import_path5.isAbsolute)(p))
|
|
27072
|
-
return p;
|
|
27073
|
-
}
|
|
27121
|
+
if ((0, import_path5.isAbsolute)(p)) return p;
|
|
27074
27122
|
return cwd ? (0, import_path5.resolve)(cwd, p) : p;
|
|
27075
27123
|
});
|
|
27076
27124
|
}
|
|
@@ -27103,7 +27151,7 @@ var init_common = __esm({
|
|
|
27103
27151
|
searchSchema = external_exports2.object({
|
|
27104
27152
|
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."),
|
|
27105
27153
|
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.'),
|
|
27106
|
-
exact: external_exports2.boolean().optional().default(false).describe(
|
|
27154
|
+
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.`),
|
|
27107
27155
|
maxTokens: external_exports2.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
|
|
27108
27156
|
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."),
|
|
27109
27157
|
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.")
|
|
@@ -73542,6 +73590,9 @@ function isMethodAllowed(methodName, allowedMethods, blockedMethods) {
|
|
|
73542
73590
|
}
|
|
73543
73591
|
function createTransport(serverConfig) {
|
|
73544
73592
|
const { transport, command, args, url: url2, env } = serverConfig;
|
|
73593
|
+
if (serverConfig.transportInstance) {
|
|
73594
|
+
return serverConfig.transportInstance;
|
|
73595
|
+
}
|
|
73545
73596
|
switch (transport) {
|
|
73546
73597
|
case "stdio":
|
|
73547
73598
|
return new import_stdio.StdioClientTransport({
|
|
@@ -73904,6 +73955,53 @@ var init_client = __esm({
|
|
|
73904
73955
|
throw error40;
|
|
73905
73956
|
}
|
|
73906
73957
|
}
|
|
73958
|
+
/**
|
|
73959
|
+
* Call graceful_stop on all MCP servers that expose it.
|
|
73960
|
+
* This signals agent-type MCP servers to wrap up their work.
|
|
73961
|
+
* @returns {Promise<Array<{server: string, success: boolean, error?: string}>>}
|
|
73962
|
+
*/
|
|
73963
|
+
async callGracefulStopAll() {
|
|
73964
|
+
const results = [];
|
|
73965
|
+
for (const [serverName, clientInfo] of this.clients) {
|
|
73966
|
+
const qualifiedName = `${serverName}_graceful_stop`;
|
|
73967
|
+
if (this.tools.has(qualifiedName)) {
|
|
73968
|
+
if (this.debug) {
|
|
73969
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: calling graceful_stop on server "${serverName}"`);
|
|
73970
|
+
}
|
|
73971
|
+
try {
|
|
73972
|
+
const timeoutMs = 5e3;
|
|
73973
|
+
const timeoutPromise = new Promise(
|
|
73974
|
+
(_, reject2) => setTimeout(() => reject2(new Error("graceful_stop timeout")), timeoutMs)
|
|
73975
|
+
);
|
|
73976
|
+
await Promise.race([
|
|
73977
|
+
clientInfo.client.callTool({ name: "graceful_stop", arguments: {} }, void 0, { timeout: timeoutMs }),
|
|
73978
|
+
timeoutPromise
|
|
73979
|
+
]);
|
|
73980
|
+
results.push({ server: serverName, success: true });
|
|
73981
|
+
if (this.debug) {
|
|
73982
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: server "${serverName}" acknowledged graceful_stop`);
|
|
73983
|
+
}
|
|
73984
|
+
} catch (e) {
|
|
73985
|
+
results.push({ server: serverName, success: false, error: e.message });
|
|
73986
|
+
if (this.debug) {
|
|
73987
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: server "${serverName}" graceful_stop failed: ${e.message}`);
|
|
73988
|
+
}
|
|
73989
|
+
}
|
|
73990
|
+
}
|
|
73991
|
+
}
|
|
73992
|
+
if (this.debug) {
|
|
73993
|
+
const withStop = results.length;
|
|
73994
|
+
const total = this.clients.size;
|
|
73995
|
+
console.log(`[DEBUG] MCP callGracefulStopAll: ${withStop}/${total} servers had graceful_stop tool`);
|
|
73996
|
+
}
|
|
73997
|
+
this.recordMcpEvent("graceful_stop.sweep_completed", {
|
|
73998
|
+
servers_total: this.clients.size,
|
|
73999
|
+
servers_with_graceful_stop: results.length,
|
|
74000
|
+
servers_acknowledged: results.filter((r) => r.success).length,
|
|
74001
|
+
servers_failed: results.filter((r) => !r.success).length
|
|
74002
|
+
});
|
|
74003
|
+
return results;
|
|
74004
|
+
}
|
|
73907
74005
|
/**
|
|
73908
74006
|
* Get all available tools with their schemas
|
|
73909
74007
|
* @returns {Object} Map of tool name to tool definition
|
|
@@ -73931,10 +74029,29 @@ var init_client = __esm({
|
|
|
73931
74029
|
inputSchema: tool6.inputSchema,
|
|
73932
74030
|
execute: async (args) => {
|
|
73933
74031
|
const result = await this.callTool(name15, args);
|
|
73934
|
-
if (result.content
|
|
73935
|
-
return result
|
|
74032
|
+
if (!result.content || !result.content[0]) {
|
|
74033
|
+
return JSON.stringify(result);
|
|
73936
74034
|
}
|
|
73937
|
-
|
|
74035
|
+
const hasImage = result.content.some((block) => block.type === "image");
|
|
74036
|
+
if (hasImage) {
|
|
74037
|
+
return { _mcpContent: result.content };
|
|
74038
|
+
}
|
|
74039
|
+
return result.content[0].text;
|
|
74040
|
+
},
|
|
74041
|
+
// Convert MCP content blocks (including images) to Vercel AI SDK format
|
|
74042
|
+
toModelOutput: ({ output }) => {
|
|
74043
|
+
if (output && typeof output === "object" && output._mcpContent) {
|
|
74044
|
+
const parts = [];
|
|
74045
|
+
for (const block of output._mcpContent) {
|
|
74046
|
+
if (block.type === "text") {
|
|
74047
|
+
parts.push({ type: "text", text: block.text });
|
|
74048
|
+
} else if (block.type === "image") {
|
|
74049
|
+
parts.push({ type: "image-data", data: block.data, mediaType: block.mimeType });
|
|
74050
|
+
}
|
|
74051
|
+
}
|
|
74052
|
+
return { type: "content", value: parts };
|
|
74053
|
+
}
|
|
74054
|
+
return { type: "text", value: typeof output === "string" ? output : JSON.stringify(output) };
|
|
73938
74055
|
}
|
|
73939
74056
|
};
|
|
73940
74057
|
}
|
|
@@ -74114,6 +74231,16 @@ var init_xmlBridge = __esm({
|
|
|
74114
74231
|
isMcpTool(toolName) {
|
|
74115
74232
|
return toolName in this.mcpTools;
|
|
74116
74233
|
}
|
|
74234
|
+
/**
|
|
74235
|
+
* Call graceful_stop on all MCP servers that expose it.
|
|
74236
|
+
* @returns {Promise<Array>}
|
|
74237
|
+
*/
|
|
74238
|
+
async callGracefulStopAll() {
|
|
74239
|
+
if (this.mcpManager) {
|
|
74240
|
+
return this.mcpManager.callGracefulStopAll();
|
|
74241
|
+
}
|
|
74242
|
+
return [];
|
|
74243
|
+
}
|
|
74117
74244
|
/**
|
|
74118
74245
|
* Clean up MCP connections
|
|
74119
74246
|
*/
|
|
@@ -96673,6 +96800,7 @@ var init_ProbeAgent = __esm({
|
|
|
96673
96800
|
this.debug = options.debug || process.env.DEBUG === "1";
|
|
96674
96801
|
this.cancelled = false;
|
|
96675
96802
|
this._abortController = new AbortController();
|
|
96803
|
+
this._activeSubagents = /* @__PURE__ */ new Map();
|
|
96676
96804
|
this.tracer = options.tracer || null;
|
|
96677
96805
|
this.outline = !!options.outline;
|
|
96678
96806
|
this.searchDelegate = options.searchDelegate !== void 0 ? !!options.searchDelegate : true;
|
|
@@ -96784,14 +96912,31 @@ var init_ProbeAgent = __esm({
|
|
|
96784
96912
|
this.timeoutBehavior = options.timeoutBehavior ?? (() => {
|
|
96785
96913
|
const val = process.env.TIMEOUT_BEHAVIOR;
|
|
96786
96914
|
if (val === "hard") return "hard";
|
|
96915
|
+
if (val === "negotiated") return "negotiated";
|
|
96787
96916
|
return "graceful";
|
|
96788
96917
|
})();
|
|
96789
96918
|
this.gracefulTimeoutBonusSteps = options.gracefulTimeoutBonusSteps ?? (() => {
|
|
96790
96919
|
const parsed = parseInt(process.env.GRACEFUL_TIMEOUT_BONUS_STEPS, 10);
|
|
96791
96920
|
return isNaN(parsed) || parsed < 1 || parsed > 20 ? 4 : parsed;
|
|
96792
96921
|
})();
|
|
96922
|
+
this.negotiatedTimeoutBudget = options.negotiatedTimeoutBudget ?? (() => {
|
|
96923
|
+
const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_BUDGET, 10);
|
|
96924
|
+
return isNaN(parsed) || parsed < 6e4 || parsed > 72e5 ? 18e5 : parsed;
|
|
96925
|
+
})();
|
|
96926
|
+
this.negotiatedTimeoutMaxRequests = options.negotiatedTimeoutMaxRequests ?? (() => {
|
|
96927
|
+
const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_MAX_REQUESTS, 10);
|
|
96928
|
+
return isNaN(parsed) || parsed < 1 || parsed > 10 ? 3 : parsed;
|
|
96929
|
+
})();
|
|
96930
|
+
this.negotiatedTimeoutMaxPerRequest = options.negotiatedTimeoutMaxPerRequest ?? (() => {
|
|
96931
|
+
const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_MAX_PER_REQUEST, 10);
|
|
96932
|
+
return isNaN(parsed) || parsed < 6e4 || parsed > 36e5 ? 6e5 : parsed;
|
|
96933
|
+
})();
|
|
96934
|
+
this.gracefulStopDeadline = options.gracefulStopDeadline ?? (() => {
|
|
96935
|
+
const parsed = parseInt(process.env.GRACEFUL_STOP_DEADLINE, 10);
|
|
96936
|
+
return isNaN(parsed) || parsed < 5e3 || parsed > 3e5 ? 45e3 : parsed;
|
|
96937
|
+
})();
|
|
96793
96938
|
if (this.debug) {
|
|
96794
|
-
console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}`);
|
|
96939
|
+
console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}, graceful stop deadline: ${this.gracefulStopDeadline}ms`);
|
|
96795
96940
|
}
|
|
96796
96941
|
this.retryConfig = options.retry || {};
|
|
96797
96942
|
this.retryManager = null;
|
|
@@ -97143,6 +97288,18 @@ var init_ProbeAgent = __esm({
|
|
|
97143
97288
|
// Per-instance delegation limits
|
|
97144
97289
|
parentAbortSignal: this._abortController.signal,
|
|
97145
97290
|
// Propagate cancellation to delegations
|
|
97291
|
+
// Timeout settings for delegate subagents to inherit
|
|
97292
|
+
timeoutBehavior: this.timeoutBehavior,
|
|
97293
|
+
maxOperationTimeout: this.maxOperationTimeout,
|
|
97294
|
+
requestTimeout: this.requestTimeout,
|
|
97295
|
+
gracefulTimeoutBonusSteps: this.gracefulTimeoutBonusSteps,
|
|
97296
|
+
negotiatedTimeoutBudget: this.negotiatedTimeoutBudget,
|
|
97297
|
+
negotiatedTimeoutMaxRequests: this.negotiatedTimeoutMaxRequests,
|
|
97298
|
+
negotiatedTimeoutMaxPerRequest: this.negotiatedTimeoutMaxPerRequest,
|
|
97299
|
+
parentOperationStartTime: this._operationStartTime,
|
|
97300
|
+
// For remaining budget calculation
|
|
97301
|
+
onSubagentCreated: (sid, subagent) => this._registerSubagent(sid, subagent),
|
|
97302
|
+
onSubagentCompleted: (sid) => this._unregisterSubagent(sid),
|
|
97146
97303
|
outputBuffer: this._outputBuffer,
|
|
97147
97304
|
concurrencyLimiter: this.concurrencyLimiter,
|
|
97148
97305
|
// Global AI concurrency limiter
|
|
@@ -97727,7 +97884,7 @@ var init_ProbeAgent = __esm({
|
|
|
97727
97884
|
}
|
|
97728
97885
|
if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
|
|
97729
97886
|
const gts = this._gracefulTimeoutState;
|
|
97730
|
-
if (this.timeoutBehavior === "graceful" && gts) {
|
|
97887
|
+
if ((this.timeoutBehavior === "graceful" || this.timeoutBehavior === "negotiated") && gts) {
|
|
97731
97888
|
} else {
|
|
97732
97889
|
timeoutState.timeoutId = setTimeout(() => {
|
|
97733
97890
|
controller.abort();
|
|
@@ -99114,6 +99271,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99114
99271
|
} else {
|
|
99115
99272
|
options = schemaOrOptions || {};
|
|
99116
99273
|
}
|
|
99274
|
+
this._operationStartTime = Date.now();
|
|
99117
99275
|
try {
|
|
99118
99276
|
const oldHistoryLength = this.history.length;
|
|
99119
99277
|
if (this._outputBuffer && !options?._schemaFormatted && !options?._completionPromptProcessed) {
|
|
@@ -99194,7 +99352,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99194
99352
|
}
|
|
99195
99353
|
}
|
|
99196
99354
|
let currentIteration = 0;
|
|
99197
|
-
let finalResult =
|
|
99355
|
+
let finalResult = null;
|
|
99356
|
+
const DEFAULT_MAX_ITER_MSG = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
|
|
99357
|
+
const _toolCallLog = [];
|
|
99358
|
+
let abortSummaryTaken = false;
|
|
99198
99359
|
const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
|
|
99199
99360
|
const maxIterations = options._maxIterationsOverride ? baseMaxIterations : options.schema ? baseMaxIterations + 4 : baseMaxIterations;
|
|
99200
99361
|
const isClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
@@ -99334,6 +99495,220 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99334
99495
|
bonusStepsMax: this.gracefulTimeoutBonusSteps
|
|
99335
99496
|
};
|
|
99336
99497
|
this._gracefulTimeoutState = gracefulTimeoutState;
|
|
99498
|
+
const negotiatedTimeoutState = {
|
|
99499
|
+
extensionsUsed: 0,
|
|
99500
|
+
totalExtraTimeMs: 0,
|
|
99501
|
+
softTimeoutId: null,
|
|
99502
|
+
hardAbortTimeoutId: null,
|
|
99503
|
+
maxRequests: this.negotiatedTimeoutMaxRequests,
|
|
99504
|
+
maxPerRequestMs: this.negotiatedTimeoutMaxPerRequest,
|
|
99505
|
+
budgetMs: this.negotiatedTimeoutBudget,
|
|
99506
|
+
observerRunning: false,
|
|
99507
|
+
// true while observer LLM call is in flight
|
|
99508
|
+
extensionMessage: null,
|
|
99509
|
+
// message to show in prepareStep after extension granted
|
|
99510
|
+
startTime: Date.now()
|
|
99511
|
+
};
|
|
99512
|
+
this._negotiatedTimeoutState = negotiatedTimeoutState;
|
|
99513
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
99514
|
+
this._activeTools = activeTools;
|
|
99515
|
+
const onToolCall = (event) => {
|
|
99516
|
+
const key = event.toolCallId || `${event.name}:${JSON.stringify(event.args || {}).slice(0, 100)}`;
|
|
99517
|
+
if (event.status === "started") {
|
|
99518
|
+
activeTools.set(key, {
|
|
99519
|
+
name: event.name,
|
|
99520
|
+
args: event.args,
|
|
99521
|
+
startedAt: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
99522
|
+
});
|
|
99523
|
+
} else if (event.status === "completed" || event.status === "error") {
|
|
99524
|
+
activeTools.delete(key);
|
|
99525
|
+
}
|
|
99526
|
+
};
|
|
99527
|
+
this.events.on("toolCall", onToolCall);
|
|
99528
|
+
const runTimeoutObserver = async () => {
|
|
99529
|
+
if (negotiatedTimeoutState.observerRunning) return;
|
|
99530
|
+
negotiatedTimeoutState.observerRunning = true;
|
|
99531
|
+
const remainingRequests = negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed;
|
|
99532
|
+
const remainingBudgetMs = negotiatedTimeoutState.budgetMs - negotiatedTimeoutState.totalExtraTimeMs;
|
|
99533
|
+
const maxPerReqMin = Math.round(negotiatedTimeoutState.maxPerRequestMs / 6e4);
|
|
99534
|
+
const elapsedMin = Math.round((Date.now() - negotiatedTimeoutState.startTime) / 6e4);
|
|
99535
|
+
if (remainingRequests <= 0 || remainingBudgetMs <= 0) {
|
|
99536
|
+
if (this.debug) {
|
|
99537
|
+
console.log(`[DEBUG] Timeout observer: no extensions/budget remaining \u2014 aborting in-flight tools and triggering graceful wind-down`);
|
|
99538
|
+
}
|
|
99539
|
+
if (this.tracer) {
|
|
99540
|
+
this.tracer.addEvent("negotiated_timeout.observer_exhausted", {
|
|
99541
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
99542
|
+
max_requests: negotiatedTimeoutState.maxRequests,
|
|
99543
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
99544
|
+
budget_ms: negotiatedTimeoutState.budgetMs,
|
|
99545
|
+
elapsed_min: elapsedMin,
|
|
99546
|
+
active_tools: Array.from(activeTools.values()).map((t) => t.name)
|
|
99547
|
+
});
|
|
99548
|
+
}
|
|
99549
|
+
await this._initiateGracefulStop(gracefulTimeoutState, "budget/extensions exhausted");
|
|
99550
|
+
negotiatedTimeoutState.observerRunning = false;
|
|
99551
|
+
return;
|
|
99552
|
+
}
|
|
99553
|
+
const activeToolsList = Array.from(activeTools.values());
|
|
99554
|
+
const now = Date.now();
|
|
99555
|
+
const formatDuration = (ms) => {
|
|
99556
|
+
const totalSec = Math.round(ms / 1e3);
|
|
99557
|
+
if (totalSec < 60) return `${totalSec}s`;
|
|
99558
|
+
const min = Math.floor(totalSec / 60);
|
|
99559
|
+
const sec = totalSec % 60;
|
|
99560
|
+
if (min < 60) return `${min}m ${sec}s`;
|
|
99561
|
+
const hr = Math.floor(min / 60);
|
|
99562
|
+
const remainMin = min % 60;
|
|
99563
|
+
return `${hr}h ${remainMin}m`;
|
|
99564
|
+
};
|
|
99565
|
+
const activeToolsDesc = activeToolsList.length > 0 ? activeToolsList.map((t) => {
|
|
99566
|
+
const runningForMs = now - new Date(t.startedAt).getTime();
|
|
99567
|
+
return `- ${t.name}(${JSON.stringify(t.args || {}).slice(0, 200)}) \u2014 running for ${formatDuration(runningForMs)}`;
|
|
99568
|
+
}).join("\n") : "(none currently running)";
|
|
99569
|
+
const recentHistory = this.history.slice(-6).map((msg) => {
|
|
99570
|
+
const content = typeof msg.content === "string" ? msg.content.slice(0, 300) : JSON.stringify(msg.content).slice(0, 300);
|
|
99571
|
+
return `[${msg.role}]: ${content}`;
|
|
99572
|
+
}).join("\n");
|
|
99573
|
+
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.
|
|
99574
|
+
|
|
99575
|
+
## Recent Conversation
|
|
99576
|
+
${recentHistory || "(no history yet)"}
|
|
99577
|
+
|
|
99578
|
+
## Currently Running Tools
|
|
99579
|
+
${activeToolsDesc}
|
|
99580
|
+
|
|
99581
|
+
## Budget
|
|
99582
|
+
- Extensions used: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}
|
|
99583
|
+
- Time budget remaining: ${Math.round(remainingBudgetMs / 6e4)} minutes
|
|
99584
|
+
- Max per extension: ${maxPerReqMin} minutes
|
|
99585
|
+
|
|
99586
|
+
Decide whether the agent should get more time. EXTEND if:
|
|
99587
|
+
- Tools are actively running (especially delegates or complex analysis) \u2014 they need time to finish
|
|
99588
|
+
- The agent is making clear progress on a complex task
|
|
99589
|
+
- New information is being gathered that will improve the final answer
|
|
99590
|
+
|
|
99591
|
+
DO NOT EXTEND if:
|
|
99592
|
+
- The agent appears stuck in a loop (repeating the same tool calls or getting the same errors)
|
|
99593
|
+
- The conversation shows the agent retrying failed operations without changing approach
|
|
99594
|
+
- The agent has enough information to answer but keeps searching for more
|
|
99595
|
+
- Tool calls are returning empty or error results repeatedly
|
|
99596
|
+
- The agent is doing redundant work (searching for things it already found)
|
|
99597
|
+
|
|
99598
|
+
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.
|
|
99599
|
+
|
|
99600
|
+
Respond with ONLY valid JSON (no markdown, no explanation):
|
|
99601
|
+
{"extend": true, "minutes": <1-${maxPerReqMin}>, "reason": "your reason here"}
|
|
99602
|
+
or
|
|
99603
|
+
{"extend": false, "reason": "your reason here"}`;
|
|
99604
|
+
const observerFn = async () => {
|
|
99605
|
+
const modelInstance = this.provider ? this.provider(this.model) : this.model;
|
|
99606
|
+
if (this.debug) {
|
|
99607
|
+
console.log(`[DEBUG] Timeout observer: making LLM call (${activeToolsList.length} active tools, ${elapsedMin} min elapsed)`);
|
|
99608
|
+
}
|
|
99609
|
+
if (this.tracer) {
|
|
99610
|
+
this.tracer.addEvent("negotiated_timeout.observer_invoked", {
|
|
99611
|
+
elapsed_min: elapsedMin,
|
|
99612
|
+
active_tools: activeToolsList.map((t) => t.name),
|
|
99613
|
+
active_tools_detail: activeToolsList.map((t) => ({
|
|
99614
|
+
name: t.name,
|
|
99615
|
+
running_for_ms: now - new Date(t.startedAt).getTime(),
|
|
99616
|
+
args_preview: JSON.stringify(t.args || {}).slice(0, 100)
|
|
99617
|
+
})),
|
|
99618
|
+
active_tools_count: activeToolsList.length,
|
|
99619
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
99620
|
+
remaining_requests: remainingRequests,
|
|
99621
|
+
remaining_budget_ms: remainingBudgetMs,
|
|
99622
|
+
history_length: this.history.length
|
|
99623
|
+
});
|
|
99624
|
+
}
|
|
99625
|
+
const observerResult = await (0, import_ai4.generateText)({
|
|
99626
|
+
model: modelInstance,
|
|
99627
|
+
messages: [{ role: "user", content: observerPrompt }],
|
|
99628
|
+
maxTokens: 500
|
|
99629
|
+
});
|
|
99630
|
+
const responseText = observerResult.text.trim();
|
|
99631
|
+
if (this.tracer) {
|
|
99632
|
+
this.tracer.addEvent("negotiated_timeout.observer_response", {
|
|
99633
|
+
response_text: responseText,
|
|
99634
|
+
usage_prompt_tokens: observerResult.usage?.promptTokens,
|
|
99635
|
+
usage_completion_tokens: observerResult.usage?.completionTokens
|
|
99636
|
+
});
|
|
99637
|
+
}
|
|
99638
|
+
const jsonStr = responseText.replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
|
|
99639
|
+
const decision = JSON.parse(jsonStr);
|
|
99640
|
+
if (decision.extend && decision.minutes > 0) {
|
|
99641
|
+
const requestedMs = Math.min(decision.minutes, maxPerReqMin) * 6e4;
|
|
99642
|
+
const grantedMs = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
|
|
99643
|
+
const grantedMin = Math.round(grantedMs / 6e4 * 10) / 10;
|
|
99644
|
+
negotiatedTimeoutState.extensionsUsed++;
|
|
99645
|
+
negotiatedTimeoutState.totalExtraTimeMs += grantedMs;
|
|
99646
|
+
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.`;
|
|
99647
|
+
negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
|
|
99648
|
+
runTimeoutObserver();
|
|
99649
|
+
}, grantedMs);
|
|
99650
|
+
if (this.debug) {
|
|
99651
|
+
console.log(`[DEBUG] Timeout observer: granted ${grantedMin} min (reason: ${decision.reason}). Extensions: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}`);
|
|
99652
|
+
}
|
|
99653
|
+
if (this.tracer) {
|
|
99654
|
+
this.tracer.addEvent("negotiated_timeout.observer_extended", {
|
|
99655
|
+
decision_reason: decision.reason,
|
|
99656
|
+
requested_minutes: decision.minutes,
|
|
99657
|
+
granted_ms: grantedMs,
|
|
99658
|
+
granted_min: grantedMin,
|
|
99659
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
99660
|
+
max_requests: negotiatedTimeoutState.maxRequests,
|
|
99661
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
99662
|
+
budget_remaining_ms: remainingBudgetMs - grantedMs,
|
|
99663
|
+
active_tools: activeToolsList.map((t) => t.name),
|
|
99664
|
+
active_tools_count: activeToolsList.length
|
|
99665
|
+
});
|
|
99666
|
+
}
|
|
99667
|
+
} else {
|
|
99668
|
+
if (this.debug) {
|
|
99669
|
+
console.log(`[DEBUG] Timeout observer: declined extension (reason: ${decision.reason}). Initiating graceful stop.`);
|
|
99670
|
+
}
|
|
99671
|
+
if (this.tracer) {
|
|
99672
|
+
this.tracer.addEvent("negotiated_timeout.observer_declined", {
|
|
99673
|
+
decision_reason: decision.reason,
|
|
99674
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
99675
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
99676
|
+
elapsed_min: elapsedMin,
|
|
99677
|
+
active_tools: activeToolsList.map((t) => t.name)
|
|
99678
|
+
});
|
|
99679
|
+
}
|
|
99680
|
+
await this._initiateGracefulStop(gracefulTimeoutState, `observer declined: ${decision.reason}`);
|
|
99681
|
+
}
|
|
99682
|
+
};
|
|
99683
|
+
try {
|
|
99684
|
+
if (this.tracer) {
|
|
99685
|
+
await this.tracer.withSpan("negotiated_timeout.observer", observerFn, {
|
|
99686
|
+
"timeout.elapsed_min": elapsedMin,
|
|
99687
|
+
"timeout.extensions_used": negotiatedTimeoutState.extensionsUsed,
|
|
99688
|
+
"timeout.active_tools_count": activeToolsList.length,
|
|
99689
|
+
"timeout.remaining_budget_ms": remainingBudgetMs
|
|
99690
|
+
});
|
|
99691
|
+
} else {
|
|
99692
|
+
await observerFn();
|
|
99693
|
+
}
|
|
99694
|
+
} catch (err) {
|
|
99695
|
+
if (this.debug) {
|
|
99696
|
+
console.log(`[DEBUG] Timeout observer: LLM call failed (${err.message}). Initiating graceful stop.`);
|
|
99697
|
+
}
|
|
99698
|
+
if (this.tracer) {
|
|
99699
|
+
this.tracer.addEvent("negotiated_timeout.observer_error", {
|
|
99700
|
+
error_message: err.message,
|
|
99701
|
+
error_name: err.name,
|
|
99702
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
99703
|
+
elapsed_min: elapsedMin
|
|
99704
|
+
});
|
|
99705
|
+
}
|
|
99706
|
+
await this._initiateGracefulStop(gracefulTimeoutState, `observer error: ${err.message}`);
|
|
99707
|
+
} finally {
|
|
99708
|
+
negotiatedTimeoutState.observerRunning = false;
|
|
99709
|
+
}
|
|
99710
|
+
};
|
|
99711
|
+
negotiatedTimeoutState.runObserver = runTimeoutObserver;
|
|
99337
99712
|
let compactionAttempted = false;
|
|
99338
99713
|
while (true) {
|
|
99339
99714
|
try {
|
|
@@ -99395,6 +99770,14 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99395
99770
|
return false;
|
|
99396
99771
|
},
|
|
99397
99772
|
prepareStep: ({ steps, stepNumber }) => {
|
|
99773
|
+
if (negotiatedTimeoutState.extensionMessage && !gracefulTimeoutState.triggered) {
|
|
99774
|
+
const msg = negotiatedTimeoutState.extensionMessage;
|
|
99775
|
+
negotiatedTimeoutState.extensionMessage = null;
|
|
99776
|
+
if (this.debug) {
|
|
99777
|
+
console.log(`[DEBUG] prepareStep: delivering timeout observer extension message`);
|
|
99778
|
+
}
|
|
99779
|
+
return { userMessage: msg };
|
|
99780
|
+
}
|
|
99398
99781
|
if (gracefulTimeoutState.triggered) {
|
|
99399
99782
|
gracefulTimeoutState.bonusStepsUsed++;
|
|
99400
99783
|
const remaining = gracefulTimeoutState.bonusStepsMax - gracefulTimeoutState.bonusStepsUsed;
|
|
@@ -99420,8 +99803,12 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99420
99803
|
return { toolChoice: "none" };
|
|
99421
99804
|
}
|
|
99422
99805
|
if (stepNumber === maxIterations - 1) {
|
|
99806
|
+
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);
|
|
99807
|
+
const searchSummary = searchesTried.length > 0 ? `
|
|
99808
|
+
Searches attempted: ${searchesTried.join(", ")}` : "";
|
|
99423
99809
|
return {
|
|
99424
|
-
toolChoice: "none"
|
|
99810
|
+
toolChoice: "none",
|
|
99811
|
+
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}`
|
|
99425
99812
|
};
|
|
99426
99813
|
}
|
|
99427
99814
|
if (steps.length >= 2) {
|
|
@@ -99500,6 +99887,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99500
99887
|
const { toolResults, toolCalls, text, reasoningText, finishReason, usage } = stepResult;
|
|
99501
99888
|
currentIteration++;
|
|
99502
99889
|
toolContext.currentIteration = currentIteration;
|
|
99890
|
+
if (toolCalls?.length > 0) {
|
|
99891
|
+
for (const tc of toolCalls) {
|
|
99892
|
+
_toolCallLog.push({ name: tc.toolName, args: tc.args || {} });
|
|
99893
|
+
}
|
|
99894
|
+
}
|
|
99503
99895
|
if (this.tracer) {
|
|
99504
99896
|
const stepEvent = {
|
|
99505
99897
|
"iteration": currentIteration,
|
|
@@ -99591,6 +99983,14 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99591
99983
|
}, 6e4);
|
|
99592
99984
|
}, this.maxOperationTimeout);
|
|
99593
99985
|
}
|
|
99986
|
+
if (this.timeoutBehavior === "negotiated" && this.maxOperationTimeout > 0) {
|
|
99987
|
+
negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
|
|
99988
|
+
if (this.debug) {
|
|
99989
|
+
console.log(`[DEBUG] Soft timeout after ${this.maxOperationTimeout}ms \u2014 invoking timeout observer`);
|
|
99990
|
+
}
|
|
99991
|
+
runTimeoutObserver();
|
|
99992
|
+
}, this.maxOperationTimeout);
|
|
99993
|
+
}
|
|
99594
99994
|
try {
|
|
99595
99995
|
const steps = await result.steps;
|
|
99596
99996
|
let finalText;
|
|
@@ -99611,6 +100011,12 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99611
100011
|
} finally {
|
|
99612
100012
|
if (gracefulTimeoutId) clearTimeout(gracefulTimeoutId);
|
|
99613
100013
|
if (hardAbortTimeoutId) clearTimeout(hardAbortTimeoutId);
|
|
100014
|
+
if (negotiatedTimeoutState.softTimeoutId) clearTimeout(negotiatedTimeoutState.softTimeoutId);
|
|
100015
|
+
if (this._gracefulStopHardAbortId) {
|
|
100016
|
+
clearTimeout(this._gracefulStopHardAbortId);
|
|
100017
|
+
this._gracefulStopHardAbortId = null;
|
|
100018
|
+
}
|
|
100019
|
+
this.events.removeListener("toolCall", onToolCall);
|
|
99614
100020
|
}
|
|
99615
100021
|
};
|
|
99616
100022
|
let aiResult;
|
|
@@ -99650,7 +100056,7 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99650
100056
|
}
|
|
99651
100057
|
if (gracefulTimeoutState.triggered) {
|
|
99652
100058
|
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";
|
|
99653
|
-
if (!finalResult || finalResult === "I was unable to complete your request
|
|
100059
|
+
if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG || finalResult.startsWith("I was unable to complete your request after")) {
|
|
99654
100060
|
try {
|
|
99655
100061
|
const allText = await aiResult.result.text;
|
|
99656
100062
|
if (allText && allText.trim()) {
|
|
@@ -99698,7 +100104,7 @@ ${toolSummaries.join("\n\n---\n\n")}`;
|
|
|
99698
100104
|
currentMessages.push(msg);
|
|
99699
100105
|
}
|
|
99700
100106
|
}
|
|
99701
|
-
if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && finalResult) {
|
|
100107
|
+
if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && !abortSummaryTaken && finalResult) {
|
|
99702
100108
|
completionPromptInjected = true;
|
|
99703
100109
|
preCompletionResult = finalResult;
|
|
99704
100110
|
if (this.debug) {
|
|
@@ -99770,6 +100176,118 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99770
100176
|
}
|
|
99771
100177
|
break;
|
|
99772
100178
|
} catch (error40) {
|
|
100179
|
+
if (gracefulTimeoutState.triggered && error40?.name === "AbortError") {
|
|
100180
|
+
if (this.debug) {
|
|
100181
|
+
console.log(`[DEBUG] Negotiated timeout: abort caught \u2014 making summary LLM call with conversation context`);
|
|
100182
|
+
}
|
|
100183
|
+
if (this.tracer) {
|
|
100184
|
+
this.tracer.addEvent("negotiated_timeout.abort_summary_started", {
|
|
100185
|
+
conversation_messages: currentMessages.length,
|
|
100186
|
+
has_schema: !!options.schema,
|
|
100187
|
+
has_tasks: !!(this.enableTasks && this.taskManager)
|
|
100188
|
+
});
|
|
100189
|
+
}
|
|
100190
|
+
try {
|
|
100191
|
+
let taskContext = "";
|
|
100192
|
+
if (this.enableTasks && this.taskManager) {
|
|
100193
|
+
const taskSummary = this.taskManager.getTaskSummary?.();
|
|
100194
|
+
if (taskSummary) {
|
|
100195
|
+
taskContext = `
|
|
100196
|
+
|
|
100197
|
+
## Task Status
|
|
100198
|
+
${taskSummary}
|
|
100199
|
+
|
|
100200
|
+
Acknowledge which tasks were completed and which were not.`;
|
|
100201
|
+
}
|
|
100202
|
+
}
|
|
100203
|
+
let schemaContext = "";
|
|
100204
|
+
if (options.schema) {
|
|
100205
|
+
try {
|
|
100206
|
+
const parsedSchema = typeof options.schema === "string" ? JSON.parse(options.schema) : options.schema;
|
|
100207
|
+
schemaContext = `
|
|
100208
|
+
|
|
100209
|
+
IMPORTANT: Your response MUST be valid JSON matching this schema:
|
|
100210
|
+
${JSON.stringify(parsedSchema, null, 2)}
|
|
100211
|
+
|
|
100212
|
+
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.`;
|
|
100213
|
+
} catch {
|
|
100214
|
+
}
|
|
100215
|
+
}
|
|
100216
|
+
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.
|
|
100217
|
+
|
|
100218
|
+
Please provide a DETAILED summary of:
|
|
100219
|
+
1. What you were asked to do (the original task)
|
|
100220
|
+
2. What you accomplished \u2014 include ALL findings, code snippets, data, and conclusions you gathered
|
|
100221
|
+
3. What was still in progress or not yet started
|
|
100222
|
+
4. Any partial results or recommendations you can offer based on what you found so far${taskContext}${schemaContext}
|
|
100223
|
+
|
|
100224
|
+
Be thorough \u2014 this is the user's only response. Include all useful information you collected.`;
|
|
100225
|
+
const summaryMessages = [
|
|
100226
|
+
...currentMessages,
|
|
100227
|
+
{ role: "user", content: summaryPrompt }
|
|
100228
|
+
];
|
|
100229
|
+
const modelInstance = this.provider ? this.provider(this.model) : this.model;
|
|
100230
|
+
const summaryFn = async () => {
|
|
100231
|
+
const summaryResult = await (0, import_ai4.generateText)({
|
|
100232
|
+
model: modelInstance,
|
|
100233
|
+
messages: this.prepareMessagesWithImages(summaryMessages),
|
|
100234
|
+
maxTokens: 4e3
|
|
100235
|
+
});
|
|
100236
|
+
if (this.tracer) {
|
|
100237
|
+
this.tracer.addEvent("negotiated_timeout.abort_summary_completed", {
|
|
100238
|
+
summary_length: summaryResult.text?.length || 0,
|
|
100239
|
+
usage_prompt_tokens: summaryResult.usage?.promptTokens,
|
|
100240
|
+
usage_completion_tokens: summaryResult.usage?.completionTokens
|
|
100241
|
+
});
|
|
100242
|
+
}
|
|
100243
|
+
if (summaryResult.usage) {
|
|
100244
|
+
this.tokenCounter.recordUsage(summaryResult.usage);
|
|
100245
|
+
}
|
|
100246
|
+
return summaryResult.text;
|
|
100247
|
+
};
|
|
100248
|
+
let summaryText;
|
|
100249
|
+
if (this.tracer) {
|
|
100250
|
+
summaryText = await this.tracer.withSpan("negotiated_timeout.abort_summary", summaryFn, {
|
|
100251
|
+
"summary.conversation_messages": currentMessages.length
|
|
100252
|
+
});
|
|
100253
|
+
} else {
|
|
100254
|
+
summaryText = await summaryFn();
|
|
100255
|
+
}
|
|
100256
|
+
if (options.schema) {
|
|
100257
|
+
finalResult = summaryText || "{}";
|
|
100258
|
+
} else {
|
|
100259
|
+
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";
|
|
100260
|
+
finalResult = timeoutNotice + (summaryText || "The operation was interrupted before a response could be generated.");
|
|
100261
|
+
}
|
|
100262
|
+
if (options.onStream && finalResult) {
|
|
100263
|
+
options.onStream(finalResult);
|
|
100264
|
+
}
|
|
100265
|
+
if (this.debug) {
|
|
100266
|
+
console.log(`[DEBUG] Negotiated timeout: summary produced ${summaryText?.length || 0} chars`);
|
|
100267
|
+
}
|
|
100268
|
+
} catch (summaryErr) {
|
|
100269
|
+
if (this.debug) {
|
|
100270
|
+
console.log(`[DEBUG] Negotiated timeout: summary call failed (${summaryErr.message}), falling back to partial text`);
|
|
100271
|
+
}
|
|
100272
|
+
if (this.tracer) {
|
|
100273
|
+
this.tracer.addEvent("negotiated_timeout.abort_summary_error", {
|
|
100274
|
+
error_message: summaryErr.message
|
|
100275
|
+
});
|
|
100276
|
+
}
|
|
100277
|
+
const partialTexts = currentMessages.filter((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim()).map((m) => m.content);
|
|
100278
|
+
if (options.schema) {
|
|
100279
|
+
finalResult = partialTexts.length > 0 ? partialTexts[partialTexts.length - 1] : "{}";
|
|
100280
|
+
} else {
|
|
100281
|
+
const timeoutNotice = "**Note: This response was generated under a time constraint. The operation was interrupted and some work was not completed.**\n\n";
|
|
100282
|
+
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.";
|
|
100283
|
+
}
|
|
100284
|
+
if (options.onStream && finalResult) {
|
|
100285
|
+
options.onStream(finalResult);
|
|
100286
|
+
}
|
|
100287
|
+
}
|
|
100288
|
+
abortSummaryTaken = true;
|
|
100289
|
+
break;
|
|
100290
|
+
}
|
|
99773
100291
|
if (!compactionAttempted && handleContextLimitError) {
|
|
99774
100292
|
const compactionResult = handleContextLimitError(error40, currentMessages, {
|
|
99775
100293
|
keepLastSegment: true,
|
|
@@ -99804,6 +100322,36 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99804
100322
|
}
|
|
99805
100323
|
if (currentIteration >= maxIterations) {
|
|
99806
100324
|
console.warn(`[WARN] Max tool iterations (${maxIterations}) reached for session ${this.sessionId}.`);
|
|
100325
|
+
if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG) {
|
|
100326
|
+
try {
|
|
100327
|
+
const searchQueries = [];
|
|
100328
|
+
const toolCounts = {};
|
|
100329
|
+
for (const tc of _toolCallLog) {
|
|
100330
|
+
toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
|
|
100331
|
+
if (tc.name === "search") {
|
|
100332
|
+
const q = tc.args.query || "";
|
|
100333
|
+
const exact = tc.args.exact ? " (exact)" : "";
|
|
100334
|
+
searchQueries.push(`"${q}"${exact}`);
|
|
100335
|
+
}
|
|
100336
|
+
}
|
|
100337
|
+
const toolBreakdown = Object.entries(toolCounts).map(([name15, count]) => `${name15}: ${count}x`).join(", ");
|
|
100338
|
+
const uniqueSearches = [...new Set(searchQueries)];
|
|
100339
|
+
let summary = `I was unable to complete your request after ${currentIteration} tool iterations.
|
|
100340
|
+
|
|
100341
|
+
`;
|
|
100342
|
+
summary += `Tool calls made: ${toolBreakdown || "none"}
|
|
100343
|
+
`;
|
|
100344
|
+
if (uniqueSearches.length > 0) {
|
|
100345
|
+
summary += `Search queries tried: ${uniqueSearches.join(", ")}
|
|
100346
|
+
`;
|
|
100347
|
+
}
|
|
100348
|
+
summary += `
|
|
100349
|
+
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.`;
|
|
100350
|
+
finalResult = summary;
|
|
100351
|
+
} catch {
|
|
100352
|
+
finalResult = DEFAULT_MAX_ITER_MSG;
|
|
100353
|
+
}
|
|
100354
|
+
}
|
|
99807
100355
|
}
|
|
99808
100356
|
this.history = currentMessages.map((msg) => ({ ...msg }));
|
|
99809
100357
|
if (this.history.length > MAX_HISTORY_MESSAGES) {
|
|
@@ -100338,6 +100886,134 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
100338
100886
|
console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
|
|
100339
100887
|
}
|
|
100340
100888
|
}
|
|
100889
|
+
/**
|
|
100890
|
+
* Trigger graceful wind-down from outside (e.g., parent agent).
|
|
100891
|
+
* Unlike cancel(), this does NOT abort — it sets the graceful timeout flag
|
|
100892
|
+
* so the agent finishes its current step and then winds down naturally.
|
|
100893
|
+
*/
|
|
100894
|
+
triggerGracefulWindDown() {
|
|
100895
|
+
if (this._gracefulTimeoutState && !this._gracefulTimeoutState.triggered) {
|
|
100896
|
+
this._gracefulTimeoutState.triggered = true;
|
|
100897
|
+
if (this.debug) {
|
|
100898
|
+
console.log(`[DEBUG] Graceful wind-down triggered externally for session ${this.sessionId}`);
|
|
100899
|
+
}
|
|
100900
|
+
if (this.tracer) {
|
|
100901
|
+
this.tracer.addEvent("graceful_stop.external_trigger", {
|
|
100902
|
+
"session.id": this.sessionId
|
|
100903
|
+
});
|
|
100904
|
+
}
|
|
100905
|
+
} else if (this.debug) {
|
|
100906
|
+
console.log(`[DEBUG] Graceful wind-down already active for session ${this.sessionId}, skipping`);
|
|
100907
|
+
}
|
|
100908
|
+
}
|
|
100909
|
+
/**
|
|
100910
|
+
* Initiate two-phase graceful stop: signal subagents and MCP servers to wind down,
|
|
100911
|
+
* then hard-abort after a deadline if they haven't finished.
|
|
100912
|
+
* @param {Object} gracefulTimeoutState - The graceful timeout state object from run()
|
|
100913
|
+
* @param {string} reason - Why the graceful stop was initiated
|
|
100914
|
+
*/
|
|
100915
|
+
async _initiateGracefulStop(gracefulTimeoutState, reason) {
|
|
100916
|
+
if (gracefulTimeoutState.triggered) return;
|
|
100917
|
+
if (this.debug) {
|
|
100918
|
+
console.log(`[DEBUG] Initiating graceful stop: ${reason} (subagents: ${this._activeSubagents.size}, hasMcpBridge: ${!!this.mcpBridge}, deadline: ${this.gracefulStopDeadline}ms)`);
|
|
100919
|
+
}
|
|
100920
|
+
gracefulTimeoutState.triggered = true;
|
|
100921
|
+
if (this.tracer) {
|
|
100922
|
+
this.tracer.addEvent("graceful_stop.initiated", {
|
|
100923
|
+
"session.id": this.sessionId,
|
|
100924
|
+
"graceful_stop.reason": reason,
|
|
100925
|
+
"graceful_stop.active_subagents": this._activeSubagents.size,
|
|
100926
|
+
"graceful_stop.has_mcp_bridge": !!this.mcpBridge,
|
|
100927
|
+
"graceful_stop.deadline_ms": this.gracefulStopDeadline
|
|
100928
|
+
});
|
|
100929
|
+
}
|
|
100930
|
+
let subagentsSignalled = 0;
|
|
100931
|
+
let subagentErrors = 0;
|
|
100932
|
+
for (const [sid, subagent] of this._activeSubagents) {
|
|
100933
|
+
try {
|
|
100934
|
+
subagent.triggerGracefulWindDown();
|
|
100935
|
+
subagentsSignalled++;
|
|
100936
|
+
if (this.debug) {
|
|
100937
|
+
console.log(`[DEBUG] Triggered graceful wind-down on subagent ${sid}`);
|
|
100938
|
+
}
|
|
100939
|
+
} catch (e) {
|
|
100940
|
+
subagentErrors++;
|
|
100941
|
+
if (this.debug) {
|
|
100942
|
+
console.log(`[DEBUG] Failed to trigger wind-down on subagent ${sid}: ${e.message}`);
|
|
100943
|
+
}
|
|
100944
|
+
}
|
|
100945
|
+
}
|
|
100946
|
+
let mcpResults = [];
|
|
100947
|
+
if (this.mcpBridge) {
|
|
100948
|
+
try {
|
|
100949
|
+
mcpResults = await this.mcpBridge.callGracefulStopAll();
|
|
100950
|
+
if (this.debug && mcpResults.length > 0) {
|
|
100951
|
+
console.log(`[DEBUG] MCP graceful_stop results: ${JSON.stringify(mcpResults)}`);
|
|
100952
|
+
}
|
|
100953
|
+
} catch (e) {
|
|
100954
|
+
if (this.debug) {
|
|
100955
|
+
console.log(`[DEBUG] MCP graceful_stop failed: ${e.message}`);
|
|
100956
|
+
}
|
|
100957
|
+
}
|
|
100958
|
+
}
|
|
100959
|
+
if (this.tracer) {
|
|
100960
|
+
this.tracer.addEvent("graceful_stop.signals_sent", {
|
|
100961
|
+
"session.id": this.sessionId,
|
|
100962
|
+
"graceful_stop.subagents_signalled": subagentsSignalled,
|
|
100963
|
+
"graceful_stop.subagent_errors": subagentErrors,
|
|
100964
|
+
"graceful_stop.mcp_servers_called": mcpResults.filter((r) => r.success).length,
|
|
100965
|
+
"graceful_stop.mcp_servers_failed": mcpResults.filter((r) => !r.success).length,
|
|
100966
|
+
"graceful_stop.mcp_servers_total": mcpResults.length
|
|
100967
|
+
});
|
|
100968
|
+
}
|
|
100969
|
+
this._gracefulStopHardAbortId = setTimeout(() => {
|
|
100970
|
+
if (this.debug) {
|
|
100971
|
+
console.log(`[DEBUG] Graceful stop deadline (${this.gracefulStopDeadline}ms) expired \u2014 hard aborting`);
|
|
100972
|
+
}
|
|
100973
|
+
if (this.tracer) {
|
|
100974
|
+
this.tracer.addEvent("graceful_stop.deadline_expired", {
|
|
100975
|
+
"session.id": this.sessionId,
|
|
100976
|
+
"graceful_stop.deadline_ms": this.gracefulStopDeadline
|
|
100977
|
+
});
|
|
100978
|
+
}
|
|
100979
|
+
if (this._abortController) this._abortController.abort();
|
|
100980
|
+
}, this.gracefulStopDeadline);
|
|
100981
|
+
}
|
|
100982
|
+
/**
|
|
100983
|
+
* Register an active subagent for graceful stop coordination.
|
|
100984
|
+
* @param {string} sessionId
|
|
100985
|
+
* @param {ProbeAgent} subagent
|
|
100986
|
+
*/
|
|
100987
|
+
_registerSubagent(sessionId, subagent) {
|
|
100988
|
+
this._activeSubagents.set(sessionId, subagent);
|
|
100989
|
+
if (this.debug) {
|
|
100990
|
+
console.log(`[DEBUG] Registered subagent ${sessionId} (active: ${this._activeSubagents.size})`);
|
|
100991
|
+
}
|
|
100992
|
+
if (this.tracer) {
|
|
100993
|
+
this.tracer.addEvent("subagent.registered", {
|
|
100994
|
+
"session.id": this.sessionId,
|
|
100995
|
+
"subagent.session_id": sessionId,
|
|
100996
|
+
"subagent.active_count": this._activeSubagents.size
|
|
100997
|
+
});
|
|
100998
|
+
}
|
|
100999
|
+
}
|
|
101000
|
+
/**
|
|
101001
|
+
* Unregister a completed subagent.
|
|
101002
|
+
* @param {string} sessionId
|
|
101003
|
+
*/
|
|
101004
|
+
_unregisterSubagent(sessionId) {
|
|
101005
|
+
this._activeSubagents.delete(sessionId);
|
|
101006
|
+
if (this.debug) {
|
|
101007
|
+
console.log(`[DEBUG] Unregistered subagent ${sessionId} (active: ${this._activeSubagents.size})`);
|
|
101008
|
+
}
|
|
101009
|
+
if (this.tracer) {
|
|
101010
|
+
this.tracer.addEvent("subagent.unregistered", {
|
|
101011
|
+
"session.id": this.sessionId,
|
|
101012
|
+
"subagent.session_id": sessionId,
|
|
101013
|
+
"subagent.active_count": this._activeSubagents.size
|
|
101014
|
+
});
|
|
101015
|
+
}
|
|
101016
|
+
}
|
|
100341
101017
|
/**
|
|
100342
101018
|
* Get the abort signal for this agent.
|
|
100343
101019
|
* Delegations and subagents should check this signal.
|
|
@@ -100380,8 +101056,15 @@ async function delegate({
|
|
|
100380
101056
|
// Optional per-instance manager, falls back to default singleton
|
|
100381
101057
|
concurrencyLimiter = null,
|
|
100382
101058
|
// Optional global AI concurrency limiter
|
|
100383
|
-
parentAbortSignal = null
|
|
101059
|
+
parentAbortSignal = null,
|
|
100384
101060
|
// Optional AbortSignal from parent to cancel this delegation
|
|
101061
|
+
// Timeout settings inherited from parent agent
|
|
101062
|
+
timeoutBehavior = void 0,
|
|
101063
|
+
requestTimeout = void 0,
|
|
101064
|
+
gracefulTimeoutBonusSteps = void 0,
|
|
101065
|
+
// Subagent lifecycle callbacks for graceful stop coordination
|
|
101066
|
+
onSubagentCreated = null,
|
|
101067
|
+
onSubagentCompleted = null
|
|
100385
101068
|
}) {
|
|
100386
101069
|
if (!task || typeof task !== "string") {
|
|
100387
101070
|
throw new Error("Task parameter is required and must be a string");
|
|
@@ -100464,12 +101147,38 @@ async function delegate({
|
|
|
100464
101147
|
// Inherit from parent
|
|
100465
101148
|
mcpConfigPath,
|
|
100466
101149
|
// Inherit from parent
|
|
100467
|
-
concurrencyLimiter
|
|
101150
|
+
concurrencyLimiter,
|
|
100468
101151
|
// Inherit global AI concurrency limiter
|
|
101152
|
+
// Inherit timeout behavior from parent — subagent gets its own graceful wind-down
|
|
101153
|
+
// so it can produce partial results instead of being hard-killed by the external timer.
|
|
101154
|
+
// The external delegate timeout (capped to parent's remaining budget) is the hard limit;
|
|
101155
|
+
// maxOperationTimeout on the subagent is set slightly shorter so its own wind-down
|
|
101156
|
+
// fires before the external kill.
|
|
101157
|
+
maxOperationTimeout: Math.max(1e4, timeout * 1e3 - 15e3),
|
|
101158
|
+
// 15s before external kill
|
|
101159
|
+
timeoutBehavior: timeoutBehavior || "graceful",
|
|
101160
|
+
requestTimeout,
|
|
101161
|
+
gracefulTimeoutBonusSteps: gracefulTimeoutBonusSteps ?? 2
|
|
101162
|
+
// fewer steps for subagents
|
|
100469
101163
|
});
|
|
101164
|
+
if (onSubagentCreated) {
|
|
101165
|
+
onSubagentCreated(sessionId, subagent);
|
|
101166
|
+
}
|
|
100470
101167
|
if (debug) {
|
|
100471
101168
|
console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
|
|
100472
101169
|
console.error(`[DELEGATE] Subagent config: promptType=${promptType}, enableDelegate=false, maxIterations=${remainingIterations}`);
|
|
101170
|
+
console.error(`[DELEGATE] Timeout inheritance: externalTimeout=${timeout}s, maxOperationTimeout=${Math.max(1e4, timeout * 1e3 - 15e3)}ms, behavior=${timeoutBehavior || "graceful"}, bonusSteps=${gracefulTimeoutBonusSteps ?? 2}`);
|
|
101171
|
+
}
|
|
101172
|
+
if (tracer) {
|
|
101173
|
+
tracer.addEvent("delegation.subagent_created", {
|
|
101174
|
+
"delegation.session_id": sessionId,
|
|
101175
|
+
"delegation.parent_session_id": parentSessionId,
|
|
101176
|
+
"delegation.external_timeout_s": timeout,
|
|
101177
|
+
"delegation.internal_timeout_ms": Math.max(1e4, timeout * 1e3 - 15e3),
|
|
101178
|
+
"delegation.timeout_behavior": timeoutBehavior || "graceful",
|
|
101179
|
+
"delegation.bonus_steps": gracefulTimeoutBonusSteps ?? 2,
|
|
101180
|
+
"delegation.max_iterations": remainingIterations
|
|
101181
|
+
});
|
|
100473
101182
|
}
|
|
100474
101183
|
const timeoutPromise = new Promise((_, reject2) => {
|
|
100475
101184
|
timeoutId = setTimeout(() => {
|
|
@@ -100478,6 +101187,7 @@ async function delegate({
|
|
|
100478
101187
|
}, timeout * 1e3);
|
|
100479
101188
|
});
|
|
100480
101189
|
let parentAbortHandler;
|
|
101190
|
+
let parentAbortHardCancelId = null;
|
|
100481
101191
|
const parentAbortPromise = new Promise((_, reject2) => {
|
|
100482
101192
|
if (parentAbortSignal) {
|
|
100483
101193
|
if (parentAbortSignal.aborted) {
|
|
@@ -100486,8 +101196,31 @@ async function delegate({
|
|
|
100486
101196
|
return;
|
|
100487
101197
|
}
|
|
100488
101198
|
parentAbortHandler = () => {
|
|
100489
|
-
subagent.
|
|
100490
|
-
|
|
101199
|
+
subagent.triggerGracefulWindDown();
|
|
101200
|
+
if (debug) {
|
|
101201
|
+
console.error(`[DELEGATE] Parent abort signal received \u2014 triggered graceful wind-down on subagent ${sessionId}`);
|
|
101202
|
+
}
|
|
101203
|
+
if (tracer) {
|
|
101204
|
+
tracer.addEvent("delegation.parent_abort_phase1", {
|
|
101205
|
+
"delegation.session_id": sessionId,
|
|
101206
|
+
"delegation.parent_session_id": parentSessionId,
|
|
101207
|
+
"delegation.action": "graceful_wind_down"
|
|
101208
|
+
});
|
|
101209
|
+
}
|
|
101210
|
+
parentAbortHardCancelId = setTimeout(() => {
|
|
101211
|
+
if (debug) {
|
|
101212
|
+
console.error(`[DELEGATE] Graceful wind-down deadline expired \u2014 hard cancelling subagent ${sessionId}`);
|
|
101213
|
+
}
|
|
101214
|
+
if (tracer) {
|
|
101215
|
+
tracer.addEvent("delegation.parent_abort_phase2", {
|
|
101216
|
+
"delegation.session_id": sessionId,
|
|
101217
|
+
"delegation.parent_session_id": parentSessionId,
|
|
101218
|
+
"delegation.action": "hard_cancel"
|
|
101219
|
+
});
|
|
101220
|
+
}
|
|
101221
|
+
subagent.cancel();
|
|
101222
|
+
reject2(new Error("Delegation cancelled: parent operation was aborted (graceful wind-down deadline expired)"));
|
|
101223
|
+
}, 3e4);
|
|
100491
101224
|
};
|
|
100492
101225
|
parentAbortSignal.addEventListener("abort", parentAbortHandler, { once: true });
|
|
100493
101226
|
}
|
|
@@ -100503,6 +101236,13 @@ async function delegate({
|
|
|
100503
101236
|
if (parentAbortHandler && parentAbortSignal) {
|
|
100504
101237
|
parentAbortSignal.removeEventListener("abort", parentAbortHandler);
|
|
100505
101238
|
}
|
|
101239
|
+
if (parentAbortHardCancelId) {
|
|
101240
|
+
clearTimeout(parentAbortHardCancelId);
|
|
101241
|
+
parentAbortHardCancelId = null;
|
|
101242
|
+
}
|
|
101243
|
+
if (onSubagentCompleted) {
|
|
101244
|
+
onSubagentCompleted(sessionId);
|
|
101245
|
+
}
|
|
100506
101246
|
}
|
|
100507
101247
|
if (timeoutId !== null) {
|
|
100508
101248
|
clearTimeout(timeoutId);
|
|
@@ -101527,6 +102267,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101527
102267
|
"- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
|
|
101528
102268
|
"- exact=true matches the literal string only \u2014 no stemming, no splitting.",
|
|
101529
102269
|
'- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
|
|
102270
|
+
"- IMPORTANT: Use exact=true when searching for strings containing punctuation, quotes, or empty values.",
|
|
102271
|
+
" Default BM25 search strips punctuation and treats quoted empty strings as noise.",
|
|
102272
|
+
` Example: searching for 'description: ""' with exact=false will NOT find empty description fields \u2014 it just matches "description".`,
|
|
102273
|
+
` Use exact=true for literal patterns like 'description: ""', 'value: \\'\\'', or any YAML/config field with specific punctuation.`,
|
|
101530
102274
|
"- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
|
|
101531
102275
|
"",
|
|
101532
102276
|
"Combining searches with OR:",
|
|
@@ -101586,7 +102330,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101586
102330
|
"WHEN TO STOP:",
|
|
101587
102331
|
"- After you have explored the main concept AND related subsystems.",
|
|
101588
102332
|
"- Once you have 5-15 targets covering different aspects of the query.",
|
|
101589
|
-
'- If you get a "DUPLICATE SEARCH BLOCKED" message,
|
|
102333
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, do NOT rephrase the same query \u2014 try a FUNDAMENTALLY different approach:',
|
|
102334
|
+
" * Switch between exact=true and exact=false",
|
|
102335
|
+
" * Search for a broader term and filter results manually",
|
|
102336
|
+
" * Use listFiles to browse the directory structure directly",
|
|
102337
|
+
" * Look for related/surrounding patterns instead of the exact string",
|
|
102338
|
+
"- If 2-3 genuinely different search approaches fail, STOP and report what you tried and why it failed.",
|
|
102339
|
+
" Do NOT keep trying variations of the same failing concept.",
|
|
101590
102340
|
"",
|
|
101591
102341
|
"Strategy:",
|
|
101592
102342
|
"1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
|
|
@@ -101658,8 +102408,8 @@ var init_vercel = __esm({
|
|
|
101658
102408
|
}
|
|
101659
102409
|
return result;
|
|
101660
102410
|
};
|
|
101661
|
-
const previousSearches = /* @__PURE__ */ new
|
|
101662
|
-
|
|
102411
|
+
const previousSearches = /* @__PURE__ */ new Map();
|
|
102412
|
+
const dupBlockCounts = /* @__PURE__ */ new Map();
|
|
101663
102413
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
101664
102414
|
const MAX_PAGES_PER_QUERY = 3;
|
|
101665
102415
|
return (0, import_ai5.tool)({
|
|
@@ -101710,20 +102460,25 @@ var init_vercel = __esm({
|
|
|
101710
102460
|
return await search(searchOptions);
|
|
101711
102461
|
};
|
|
101712
102462
|
if (!searchDelegate) {
|
|
101713
|
-
const searchKey = `${searchQuery}::${exact || false}`;
|
|
102463
|
+
const searchKey = `${searchPath}::${searchQuery}::${exact || false}`;
|
|
101714
102464
|
if (!nextPage) {
|
|
101715
102465
|
if (previousSearches.has(searchKey)) {
|
|
101716
|
-
|
|
102466
|
+
const blockCount = (dupBlockCounts.get(searchKey) || 0) + 1;
|
|
102467
|
+
dupBlockCounts.set(searchKey, blockCount);
|
|
101717
102468
|
if (debug) {
|
|
101718
|
-
console.error(`[DEDUP] Blocked duplicate search (${
|
|
102469
|
+
console.error(`[DEDUP] Blocked duplicate search (${blockCount}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
101719
102470
|
}
|
|
101720
|
-
if (
|
|
101721
|
-
return "STOP. You have been blocked " +
|
|
102471
|
+
if (blockCount >= 3) {
|
|
102472
|
+
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.";
|
|
101722
102473
|
}
|
|
101723
|
-
|
|
102474
|
+
const prev = previousSearches.get(searchKey);
|
|
102475
|
+
if (prev.hadResults) {
|
|
102476
|
+
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.`;
|
|
102477
|
+
}
|
|
102478
|
+
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.`;
|
|
102479
|
+
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.`;
|
|
101724
102480
|
}
|
|
101725
|
-
previousSearches.
|
|
101726
|
-
consecutiveDupBlocks = 0;
|
|
102481
|
+
previousSearches.set(searchKey, { hadResults: false });
|
|
101727
102482
|
paginationCounts.set(searchKey, 0);
|
|
101728
102483
|
} else {
|
|
101729
102484
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
@@ -101737,6 +102492,14 @@ var init_vercel = __esm({
|
|
|
101737
102492
|
}
|
|
101738
102493
|
try {
|
|
101739
102494
|
const result = maybeAnnotate(await runRawSearch());
|
|
102495
|
+
if (typeof result === "string" && result.includes("No results found")) {
|
|
102496
|
+
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
|
|
102497
|
+
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).";
|
|
102498
|
+
}
|
|
102499
|
+
} else if (typeof result === "string") {
|
|
102500
|
+
const entry = previousSearches.get(searchKey);
|
|
102501
|
+
if (entry) entry.hadResults = true;
|
|
102502
|
+
}
|
|
101740
102503
|
if (options.fileTracker && typeof result === "string") {
|
|
101741
102504
|
options.fileTracker.trackFilesFromOutput(result, effectiveSearchCwd).catch(() => {
|
|
101742
102505
|
});
|
|
@@ -102019,7 +102782,31 @@ var init_vercel = __esm({
|
|
|
102019
102782
|
});
|
|
102020
102783
|
};
|
|
102021
102784
|
delegateTool = (options = {}) => {
|
|
102022
|
-
const {
|
|
102785
|
+
const {
|
|
102786
|
+
debug = false,
|
|
102787
|
+
timeout = 300,
|
|
102788
|
+
cwd,
|
|
102789
|
+
allowedFolders,
|
|
102790
|
+
workspaceRoot,
|
|
102791
|
+
enableBash = false,
|
|
102792
|
+
bashConfig,
|
|
102793
|
+
architectureFileName,
|
|
102794
|
+
enableMcp = false,
|
|
102795
|
+
mcpConfig = null,
|
|
102796
|
+
mcpConfigPath = null,
|
|
102797
|
+
delegationManager = null,
|
|
102798
|
+
// Timeout settings inherited from parent agent
|
|
102799
|
+
timeoutBehavior,
|
|
102800
|
+
maxOperationTimeout,
|
|
102801
|
+
requestTimeout,
|
|
102802
|
+
gracefulTimeoutBonusSteps,
|
|
102803
|
+
negotiatedTimeoutBudget,
|
|
102804
|
+
negotiatedTimeoutMaxRequests,
|
|
102805
|
+
negotiatedTimeoutMaxPerRequest,
|
|
102806
|
+
parentOperationStartTime,
|
|
102807
|
+
onSubagentCreated,
|
|
102808
|
+
onSubagentCompleted
|
|
102809
|
+
} = options;
|
|
102023
102810
|
return (0, import_ai5.tool)({
|
|
102024
102811
|
name: "delegate",
|
|
102025
102812
|
description: delegateDescription,
|
|
@@ -102063,9 +102850,30 @@ var init_vercel = __esm({
|
|
|
102063
102850
|
console.error(`Using workspace root: ${effectivePath} (cwd was: ${cwd || "not set"})`);
|
|
102064
102851
|
}
|
|
102065
102852
|
}
|
|
102853
|
+
let effectiveTimeout = timeout;
|
|
102854
|
+
if (parentOperationStartTime && maxOperationTimeout) {
|
|
102855
|
+
const elapsed = Date.now() - parentOperationStartTime;
|
|
102856
|
+
const remaining = maxOperationTimeout - elapsed;
|
|
102857
|
+
const budgetCap = Math.max(30, Math.floor(remaining * 0.9 / 1e3));
|
|
102858
|
+
if (budgetCap < effectiveTimeout) {
|
|
102859
|
+
effectiveTimeout = budgetCap;
|
|
102860
|
+
if (debug) {
|
|
102861
|
+
console.error(`[DELEGATE] Capping timeout from ${timeout}s to ${effectiveTimeout}s (remaining parent budget: ${Math.floor(remaining / 1e3)}s)`);
|
|
102862
|
+
}
|
|
102863
|
+
if (tracer) {
|
|
102864
|
+
tracer.addEvent("delegation.budget_capped", {
|
|
102865
|
+
"delegation.original_timeout_s": timeout,
|
|
102866
|
+
"delegation.effective_timeout_s": effectiveTimeout,
|
|
102867
|
+
"delegation.parent_elapsed_ms": elapsed,
|
|
102868
|
+
"delegation.parent_remaining_ms": remaining,
|
|
102869
|
+
"delegation.parent_session_id": parentSessionId
|
|
102870
|
+
});
|
|
102871
|
+
}
|
|
102872
|
+
}
|
|
102873
|
+
}
|
|
102066
102874
|
const result = await delegate({
|
|
102067
102875
|
task,
|
|
102068
|
-
timeout,
|
|
102876
|
+
timeout: effectiveTimeout,
|
|
102069
102877
|
debug,
|
|
102070
102878
|
currentIteration: currentIteration || 0,
|
|
102071
102879
|
maxIterations: maxIterations || 30,
|
|
@@ -102084,7 +102892,14 @@ var init_vercel = __esm({
|
|
|
102084
102892
|
mcpConfigPath,
|
|
102085
102893
|
delegationManager,
|
|
102086
102894
|
// Per-instance delegation limits
|
|
102087
|
-
parentAbortSignal
|
|
102895
|
+
parentAbortSignal,
|
|
102896
|
+
// Inherit timeout settings for subagent
|
|
102897
|
+
timeoutBehavior,
|
|
102898
|
+
requestTimeout,
|
|
102899
|
+
gracefulTimeoutBonusSteps,
|
|
102900
|
+
// Subagent lifecycle callbacks for graceful stop coordination
|
|
102901
|
+
onSubagentCreated,
|
|
102902
|
+
onSubagentCompleted
|
|
102088
102903
|
});
|
|
102089
102904
|
return result;
|
|
102090
102905
|
}
|
|
@@ -103563,6 +104378,121 @@ var init_file_lister = __esm({
|
|
|
103563
104378
|
}
|
|
103564
104379
|
});
|
|
103565
104380
|
|
|
104381
|
+
// src/agent/otelLogBridge.js
|
|
104382
|
+
function getOtelApi() {
|
|
104383
|
+
if (otelApiAttempted) return otelApi;
|
|
104384
|
+
otelApiAttempted = true;
|
|
104385
|
+
try {
|
|
104386
|
+
otelApi = (function(name15) {
|
|
104387
|
+
return _require(name15);
|
|
104388
|
+
})("@opentelemetry/api");
|
|
104389
|
+
} catch {
|
|
104390
|
+
}
|
|
104391
|
+
return otelApi;
|
|
104392
|
+
}
|
|
104393
|
+
function getOtelLogger() {
|
|
104394
|
+
if (otelLoggerAttempted) return otelLogger;
|
|
104395
|
+
otelLoggerAttempted = true;
|
|
104396
|
+
try {
|
|
104397
|
+
const { logs } = (function(name15) {
|
|
104398
|
+
return _require(name15);
|
|
104399
|
+
})("@opentelemetry/api-logs");
|
|
104400
|
+
otelLogger = logs.getLogger("probe-agent");
|
|
104401
|
+
} catch {
|
|
104402
|
+
}
|
|
104403
|
+
return otelLogger;
|
|
104404
|
+
}
|
|
104405
|
+
function getTraceSuffix() {
|
|
104406
|
+
try {
|
|
104407
|
+
const api2 = getOtelApi();
|
|
104408
|
+
if (!api2) return "";
|
|
104409
|
+
const span = api2.trace.getSpan(api2.context.active());
|
|
104410
|
+
const ctx = span?.spanContext?.();
|
|
104411
|
+
if (!ctx?.traceId) return "";
|
|
104412
|
+
return ` [trace_id=${ctx.traceId} span_id=${ctx.spanId}]`;
|
|
104413
|
+
} catch {
|
|
104414
|
+
return "";
|
|
104415
|
+
}
|
|
104416
|
+
}
|
|
104417
|
+
function emitOtelLog(msg, level) {
|
|
104418
|
+
try {
|
|
104419
|
+
const logger = getOtelLogger();
|
|
104420
|
+
if (!logger) return;
|
|
104421
|
+
const api2 = getOtelApi();
|
|
104422
|
+
let traceId, spanId;
|
|
104423
|
+
if (api2) {
|
|
104424
|
+
const span = api2.trace.getSpan(api2.context.active());
|
|
104425
|
+
const ctx = span?.spanContext?.();
|
|
104426
|
+
if (ctx?.traceId) {
|
|
104427
|
+
traceId = ctx.traceId;
|
|
104428
|
+
spanId = ctx.spanId;
|
|
104429
|
+
}
|
|
104430
|
+
}
|
|
104431
|
+
logger.emit({
|
|
104432
|
+
severityNumber: OTEL_SEVERITY[level] || 9,
|
|
104433
|
+
severityText: level.toUpperCase(),
|
|
104434
|
+
body: msg,
|
|
104435
|
+
attributes: {
|
|
104436
|
+
"probe.logger": true,
|
|
104437
|
+
...traceId ? { trace_id: traceId, span_id: spanId } : {}
|
|
104438
|
+
}
|
|
104439
|
+
});
|
|
104440
|
+
} catch {
|
|
104441
|
+
}
|
|
104442
|
+
}
|
|
104443
|
+
function patchConsole() {
|
|
104444
|
+
if (patched) return;
|
|
104445
|
+
const methods = ["log", "info", "warn", "error"];
|
|
104446
|
+
const c = globalThis.console;
|
|
104447
|
+
for (const m of methods) {
|
|
104448
|
+
const orig = c[m].bind(c);
|
|
104449
|
+
originals[m] = orig;
|
|
104450
|
+
c[m] = (...args) => {
|
|
104451
|
+
const msgParts = args.map(
|
|
104452
|
+
(a) => typeof a === "string" ? a : a instanceof Error ? a.message : JSON.stringify(a)
|
|
104453
|
+
);
|
|
104454
|
+
const msg = msgParts.join(" ");
|
|
104455
|
+
emitOtelLog(msg, m === "log" ? "log" : m);
|
|
104456
|
+
const suffix = getTraceSuffix();
|
|
104457
|
+
if (suffix) {
|
|
104458
|
+
if (typeof args[0] === "string") {
|
|
104459
|
+
args[0] = args[0] + suffix;
|
|
104460
|
+
} else {
|
|
104461
|
+
args.push(suffix);
|
|
104462
|
+
}
|
|
104463
|
+
}
|
|
104464
|
+
return orig(...args);
|
|
104465
|
+
};
|
|
104466
|
+
}
|
|
104467
|
+
patched = true;
|
|
104468
|
+
}
|
|
104469
|
+
var import_module, _require, OTEL_SEVERITY, patched, originals, otelApi, otelApiAttempted, otelLogger, otelLoggerAttempted;
|
|
104470
|
+
var init_otelLogBridge = __esm({
|
|
104471
|
+
"src/agent/otelLogBridge.js"() {
|
|
104472
|
+
"use strict";
|
|
104473
|
+
import_module = require("module");
|
|
104474
|
+
_require = (0, import_module.createRequire)("file:///");
|
|
104475
|
+
OTEL_SEVERITY = {
|
|
104476
|
+
log: 9,
|
|
104477
|
+
// INFO
|
|
104478
|
+
info: 9,
|
|
104479
|
+
// INFO
|
|
104480
|
+
warn: 13,
|
|
104481
|
+
// WARN
|
|
104482
|
+
error: 17,
|
|
104483
|
+
// ERROR
|
|
104484
|
+
debug: 5
|
|
104485
|
+
// DEBUG
|
|
104486
|
+
};
|
|
104487
|
+
patched = false;
|
|
104488
|
+
originals = {};
|
|
104489
|
+
otelApi = null;
|
|
104490
|
+
otelApiAttempted = false;
|
|
104491
|
+
otelLogger = null;
|
|
104492
|
+
otelLoggerAttempted = false;
|
|
104493
|
+
}
|
|
104494
|
+
});
|
|
104495
|
+
|
|
103566
104496
|
// src/agent/simpleTelemetry.js
|
|
103567
104497
|
function initializeSimpleTelemetryFromOptions(options) {
|
|
103568
104498
|
const telemetry = new SimpleTelemetry({
|
|
@@ -103571,6 +104501,7 @@ function initializeSimpleTelemetryFromOptions(options) {
|
|
|
103571
104501
|
enableConsole: options.traceConsole,
|
|
103572
104502
|
filePath: options.traceFile || "./traces.jsonl"
|
|
103573
104503
|
});
|
|
104504
|
+
patchConsole();
|
|
103574
104505
|
return telemetry;
|
|
103575
104506
|
}
|
|
103576
104507
|
var import_fs15, import_path18, SimpleTelemetry, SimpleAppTracer;
|
|
@@ -103579,6 +104510,7 @@ var init_simpleTelemetry = __esm({
|
|
|
103579
104510
|
"use strict";
|
|
103580
104511
|
import_fs15 = require("fs");
|
|
103581
104512
|
import_path18 = require("path");
|
|
104513
|
+
init_otelLogBridge();
|
|
103582
104514
|
SimpleTelemetry = class {
|
|
103583
104515
|
constructor(options = {}) {
|
|
103584
104516
|
this.serviceName = options.serviceName || "probe-agent";
|
|
@@ -104029,6 +104961,9 @@ var init_hooks = __esm({
|
|
|
104029
104961
|
var index_exports = {};
|
|
104030
104962
|
__export(index_exports, {
|
|
104031
104963
|
DEFAULT_SYSTEM_MESSAGE: () => DEFAULT_SYSTEM_MESSAGE,
|
|
104964
|
+
ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
|
|
104965
|
+
ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
|
|
104966
|
+
ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
|
|
104032
104967
|
FileTracker: () => FileTracker,
|
|
104033
104968
|
HOOK_TYPES: () => HOOK_TYPES,
|
|
104034
104969
|
HookManager: () => HookManager,
|
|
@@ -104115,6 +105050,9 @@ init_index();
|
|
|
104115
105050
|
// Annotate the CommonJS export names for ESM import in node:
|
|
104116
105051
|
0 && (module.exports = {
|
|
104117
105052
|
DEFAULT_SYSTEM_MESSAGE,
|
|
105053
|
+
ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
|
|
105054
|
+
ENGINE_ACTIVITY_TIMEOUT_MAX,
|
|
105055
|
+
ENGINE_ACTIVITY_TIMEOUT_MIN,
|
|
104118
105056
|
FileTracker,
|
|
104119
105057
|
HOOK_TYPES,
|
|
104120
105058
|
HookManager,
|