@probelabs/probe 0.6.0-rc295 → 0.6.0-rc297

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.
Files changed (31) hide show
  1. package/README.md +7 -0
  2. package/bin/binaries/{probe-v0.6.0-rc295-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc297-aarch64-apple-darwin.tar.gz} +0 -0
  3. package/bin/binaries/{probe-v0.6.0-rc295-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc297-aarch64-unknown-linux-musl.tar.gz} +0 -0
  4. package/bin/binaries/{probe-v0.6.0-rc295-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc297-x86_64-apple-darwin.tar.gz} +0 -0
  5. package/bin/binaries/{probe-v0.6.0-rc295-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc297-x86_64-pc-windows-msvc.zip} +0 -0
  6. package/bin/binaries/{probe-v0.6.0-rc295-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc297-x86_64-unknown-linux-musl.tar.gz} +0 -0
  7. package/build/agent/ProbeAgent.d.ts +40 -2
  8. package/build/agent/ProbeAgent.js +703 -11
  9. package/build/agent/mcp/client.js +115 -4
  10. package/build/agent/mcp/xmlBridge.js +13 -1
  11. package/build/agent/otelLogBridge.js +184 -0
  12. package/build/agent/simpleTelemetry.js +8 -0
  13. package/build/delegate.js +75 -6
  14. package/build/index.js +6 -2
  15. package/build/tools/common.js +84 -11
  16. package/build/tools/vercel.js +78 -18
  17. package/cjs/agent/ProbeAgent.cjs +1095 -185
  18. package/cjs/agent/simpleTelemetry.cjs +112 -0
  19. package/cjs/index.cjs +1207 -185
  20. package/index.d.ts +26 -0
  21. package/package.json +2 -2
  22. package/src/agent/ProbeAgent.d.ts +40 -2
  23. package/src/agent/ProbeAgent.js +703 -11
  24. package/src/agent/mcp/client.js +115 -4
  25. package/src/agent/mcp/xmlBridge.js +13 -1
  26. package/src/agent/otelLogBridge.js +184 -0
  27. package/src/agent/simpleTelemetry.js +8 -0
  28. package/src/delegate.js +75 -6
  29. package/src/index.js +6 -2
  30. package/src/tools/common.js +84 -11
  31. 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.split(/[\s,]+/).filter((f) => f.length > 0);
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('Default (false) enables stemming and keyword splitting for exploratory search - "getUserData" matches "get", "user", "data", etc. Set true for precise symbol lookup where "getUserData" matches only "getUserData". Use true when you know the exact symbol name.'),
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.")
@@ -48010,7 +48058,22 @@ function mapFlowchartParserError(err, text) {
48010
48058
  const lineContent = allLines[Math.max(0, line - 1)] || "";
48011
48059
  const beforeQuote = lineContent.slice(0, Math.max(0, column - 1));
48012
48060
  const hasLinkBefore = beforeQuote.match(/--\s*$|==\s*$|-\.\s*$|-\.-\s*$|\[\s*$/);
48061
+ const caret0 = Math.max(0, column - 1);
48062
+ const firstBar = lineContent.lastIndexOf("|", caret0);
48063
+ const secondBar = firstBar >= 0 ? lineContent.indexOf("|", caret0 + 1) : -1;
48064
+ const inPipeLabel = firstBar >= 0 && secondBar > firstBar && firstBar < caret0 && secondBar > caret0;
48013
48065
  if (inLinkRule || hasLinkBefore) {
48066
+ if (tokType === "QuotedString" && inPipeLabel) {
48067
+ return {
48068
+ line,
48069
+ column,
48070
+ severity: "error",
48071
+ code: "FL-EDGE-LABEL-QUOTE-IN-PIPES",
48072
+ message: "Quotes are not supported inside pipe-delimited edge labels.",
48073
+ hint: "Use &quot; inside |...|, e.g., --|e.g. &quot;navigate to example.com&quot;|-->",
48074
+ length: len
48075
+ };
48076
+ }
48014
48077
  if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
48015
48078
  return {
48016
48079
  line,
@@ -48612,17 +48675,6 @@ ${br.example}`,
48612
48675
  if (inRule("arrow") && err.name === "NoViableAltException") {
48613
48676
  return { line, column, severity: "error", code: "SE-ARROW-INVALID", message: `Invalid sequence arrow near '${found}'.`, hint: "Use ->, -->, ->>, -->>, -x, --x, -), --), <<->>, or <<-->>", length: len };
48614
48677
  }
48615
- if ((err.name === "NoViableAltException" || err.name === "MismatchedTokenException") && tokType === "Minus") {
48616
- return {
48617
- line,
48618
- column,
48619
- severity: "error",
48620
- code: "SE-BULLET-LINE-UNSUPPORTED",
48621
- message: "Bullet list lines starting with '-' are not supported in sequence diagrams.",
48622
- hint: "Wrap free\u2011form text in a note block instead, for example:\nNote over A : Item 1\nNote over A\n - Item 1\n - Item 2\nend note",
48623
- length: len
48624
- };
48625
- }
48626
48678
  if (inRule("noteStmt")) {
48627
48679
  if (err.name === "MismatchedTokenException" && exp("Colon")) {
48628
48680
  return { line, column, severity: "error", code: "SE-NOTE-MALFORMED", message: "Malformed note: missing colon before the note text.", hint: "Example: Note right of Alice: Hello", length: len };
@@ -48631,6 +48683,22 @@ ${br.example}`,
48631
48683
  return { line, column, severity: "error", code: "SE-NOTE-MALFORMED", message: "Malformed note statement. Use left|right of X or over X[,Y]: text", hint: "Examples: Note over A,B: hi", length: len };
48632
48684
  }
48633
48685
  }
48686
+ if ((err.name === "NoViableAltException" || err.name === "MismatchedTokenException") && tokType === "Minus") {
48687
+ const nonWs = ltxt.search(/\S/);
48688
+ const minusAtLineStart = nonWs >= 0 && ltxt[nonWs] === "-" && column === nonWs + 1;
48689
+ if (!minusAtLineStart) {
48690
+ } else {
48691
+ return {
48692
+ line,
48693
+ column,
48694
+ severity: "error",
48695
+ code: "SE-BULLET-LINE-UNSUPPORTED",
48696
+ message: "Bullet list lines starting with '-' are not supported in sequence diagrams.",
48697
+ hint: "Wrap free\u2011form text in a note block instead, for example:\nNote over A : Item 1\nNote over A\n - Item 1\n - Item 2\nend note",
48698
+ length: len
48699
+ };
48700
+ }
48701
+ }
48634
48702
  if (tokType === "ElseKeyword" && isInRule(err, "criticalBlock")) {
48635
48703
  return {
48636
48704
  line,
@@ -49430,7 +49498,7 @@ var Identifier2, NumberLiteral3, SequenceKeyword, ParticipantKeyword, ActorKeywo
49430
49498
  var init_lexer4 = __esm({
49431
49499
  "node_modules/@probelabs/maid/out/diagrams/sequence/lexer.js"() {
49432
49500
  init_api6();
49433
- Identifier2 = createToken({ name: "Identifier", pattern: /[A-Za-z_][A-Za-z0-9_]*/ });
49501
+ Identifier2 = createToken({ name: "Identifier", pattern: /[A-Za-z_][A-Za-z0-9_.]*(?:-(?![>x)\s])[A-Za-z0-9_.]+)*/ });
49434
49502
  NumberLiteral3 = createToken({ name: "NumberLiteral", pattern: /[0-9]+/ });
49435
49503
  SequenceKeyword = createToken({ name: "SequenceKeyword", pattern: /sequenceDiagram/, longer_alt: Identifier2 });
49436
49504
  ParticipantKeyword = createToken({ name: "ParticipantKeyword", pattern: /participant/i, longer_alt: Identifier2 });
@@ -51250,6 +51318,135 @@ var init_validate5 = __esm({
51250
51318
  }
51251
51319
  });
51252
51320
 
51321
+ // node_modules/@probelabs/maid/out/core/frontmatter.js
51322
+ function parseFrontmatter(input) {
51323
+ const text = input.startsWith("\uFEFF") ? input.slice(1) : input;
51324
+ const lines = text.split(/\r?\n/);
51325
+ if (lines.length < 3 || lines[0].trim() !== "---")
51326
+ return null;
51327
+ let i = 1;
51328
+ const block = [];
51329
+ while (i < lines.length && lines[i].trim() !== "---") {
51330
+ block.push(lines[i]);
51331
+ i++;
51332
+ }
51333
+ if (i >= lines.length)
51334
+ return null;
51335
+ const body = lines.slice(i + 1).join("\n");
51336
+ const bodyStartLine = i + 2;
51337
+ const raw = block.join("\n");
51338
+ const config2 = {};
51339
+ const themeVars = {};
51340
+ let themeUnderConfig = false;
51341
+ let ctx = "root";
51342
+ for (const line of block) {
51343
+ if (!line.trim())
51344
+ continue;
51345
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
51346
+ const mKey = line.match(/^\s*([A-Za-z0-9_\-]+):\s*(.*)$/);
51347
+ if (!mKey)
51348
+ continue;
51349
+ const key = mKey[1];
51350
+ let value = mKey[2] || "";
51351
+ if (indent === 0) {
51352
+ if (key === "config") {
51353
+ ctx = "config";
51354
+ continue;
51355
+ }
51356
+ if (key === "themeVariables") {
51357
+ ctx = "theme";
51358
+ continue;
51359
+ }
51360
+ ctx = "root";
51361
+ continue;
51362
+ }
51363
+ if (ctx === "config") {
51364
+ if (indent <= 2 && key !== "pie" && key !== "themeVariables")
51365
+ continue;
51366
+ if (key === "pie") {
51367
+ ctx = "config.pie";
51368
+ ensure(config2, "pie", {});
51369
+ continue;
51370
+ }
51371
+ if (key === "themeVariables") {
51372
+ ctx = "theme";
51373
+ themeUnderConfig = true;
51374
+ continue;
51375
+ }
51376
+ continue;
51377
+ }
51378
+ if (ctx === "config.pie") {
51379
+ if (indent < 4) {
51380
+ if (key === "pie") {
51381
+ ctx = "config.pie";
51382
+ ensure(config2, "pie", {});
51383
+ continue;
51384
+ }
51385
+ if (key === "themeVariables") {
51386
+ ctx = "theme";
51387
+ themeUnderConfig = true;
51388
+ continue;
51389
+ }
51390
+ ctx = "config";
51391
+ continue;
51392
+ }
51393
+ setKV(config2.pie, key, value);
51394
+ continue;
51395
+ }
51396
+ if (ctx === "theme") {
51397
+ if (indent < 2) {
51398
+ ctx = "root";
51399
+ continue;
51400
+ }
51401
+ setKV(themeVars, key, value);
51402
+ continue;
51403
+ }
51404
+ }
51405
+ if (themeUnderConfig && Object.keys(themeVars).length) {
51406
+ ensure(config2, "themeVariables", {});
51407
+ Object.assign(config2.themeVariables, themeVars);
51408
+ }
51409
+ return {
51410
+ raw,
51411
+ body,
51412
+ bodyStartLine,
51413
+ config: Object.keys(config2).length ? config2 : void 0,
51414
+ themeVariables: Object.keys(themeVars).length ? themeVars : void 0
51415
+ };
51416
+ }
51417
+ function ensure(obj, key, def) {
51418
+ if (obj[key] == null)
51419
+ obj[key] = def;
51420
+ }
51421
+ function unquote(val) {
51422
+ const v = val.trim();
51423
+ if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
51424
+ return v.slice(1, -1);
51425
+ }
51426
+ return v;
51427
+ }
51428
+ function setKV(target, key, rawValue) {
51429
+ const v = unquote(rawValue);
51430
+ if (v === "") {
51431
+ target[key] = "";
51432
+ return;
51433
+ }
51434
+ const num = Number(v);
51435
+ if (!Number.isNaN(num) && /^-?[0-9]+(\.[0-9]+)?$/.test(v)) {
51436
+ target[key] = num;
51437
+ return;
51438
+ }
51439
+ if (/^(true|false)$/i.test(v)) {
51440
+ target[key] = /^true$/i.test(v);
51441
+ return;
51442
+ }
51443
+ target[key] = v;
51444
+ }
51445
+ var init_frontmatter = __esm({
51446
+ "node_modules/@probelabs/maid/out/core/frontmatter.js"() {
51447
+ }
51448
+ });
51449
+
51253
51450
  // node_modules/@probelabs/maid/out/core/router.js
51254
51451
  function firstNonCommentLine(text) {
51255
51452
  const lines = text.split(/\r?\n/);
@@ -51264,7 +51461,8 @@ function firstNonCommentLine(text) {
51264
51461
  return void 0;
51265
51462
  }
51266
51463
  function detectDiagramType(text) {
51267
- const header = firstNonCommentLine(text);
51464
+ const { content } = stripFrontmatter(text);
51465
+ const header = firstNonCommentLine(content);
51268
51466
  if (!header)
51269
51467
  return "unknown";
51270
51468
  if (/^(flowchart|graph)\b/i.test(header))
@@ -51316,20 +51514,26 @@ function isOtherMermaidDiagram(headerLine) {
51316
51514
  return OTHER.has(t);
51317
51515
  }
51318
51516
  function validate(text, options = {}) {
51319
- const type = detectDiagramType(text);
51517
+ const { content, lineOffset } = stripFrontmatter(text);
51518
+ const type = detectDiagramType(content);
51519
+ const withOffset = (errors) => {
51520
+ if (lineOffset === 0)
51521
+ return errors;
51522
+ return errors.map((e) => ({ ...e, line: Math.max(1, (e.line || 1) + lineOffset) }));
51523
+ };
51320
51524
  switch (type) {
51321
51525
  case "flowchart":
51322
- return { type, errors: validateFlowchart(text, options) };
51526
+ return { type, errors: withOffset(validateFlowchart(content, options)) };
51323
51527
  case "pie":
51324
- return { type, errors: validatePie(text, options) };
51528
+ return { type, errors: withOffset(validatePie(content, options)) };
51325
51529
  case "sequence":
51326
- return { type, errors: validateSequence(text, options) };
51530
+ return { type, errors: withOffset(validateSequence(content, options)) };
51327
51531
  case "class":
51328
- return { type, errors: validateClass(text, options) };
51532
+ return { type, errors: withOffset(validateClass(content, options)) };
51329
51533
  case "state":
51330
- return { type, errors: validateState(text, options) };
51534
+ return { type, errors: withOffset(validateState(content, options)) };
51331
51535
  default:
51332
- const header = firstNonCommentLine(text);
51536
+ const header = firstNonCommentLine(content);
51333
51537
  if (isOtherMermaidDiagram(header)) {
51334
51538
  return { type, errors: [] };
51335
51539
  }
@@ -51337,7 +51541,7 @@ function validate(text, options = {}) {
51337
51541
  type,
51338
51542
  errors: [
51339
51543
  {
51340
- line: 1,
51544
+ line: lineOffset + 1,
51341
51545
  column: 1,
51342
51546
  message: 'Diagram must start with "graph", "flowchart", "pie", "sequenceDiagram", "classDiagram" or "stateDiagram[-v2]"',
51343
51547
  severity: "error",
@@ -51348,6 +51552,12 @@ function validate(text, options = {}) {
51348
51552
  };
51349
51553
  }
51350
51554
  }
51555
+ function stripFrontmatter(text) {
51556
+ const fm = parseFrontmatter(text);
51557
+ if (!fm)
51558
+ return { content: text, lineOffset: 0 };
51559
+ return { content: fm.body, lineOffset: fm.bodyStartLine - 1 };
51560
+ }
51351
51561
  var init_router = __esm({
51352
51562
  "node_modules/@probelabs/maid/out/core/router.js"() {
51353
51563
  init_validate();
@@ -51355,6 +51565,7 @@ var init_router = __esm({
51355
51565
  init_validate3();
51356
51566
  init_validate4();
51357
51567
  init_validate5();
51568
+ init_frontmatter();
51358
51569
  }
51359
51570
  });
51360
51571
 
@@ -51566,16 +51777,18 @@ function computeFixes(text, errors, level = "safe") {
51566
51777
  }
51567
51778
  continue;
51568
51779
  }
51569
- if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e)) {
51780
+ if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e) || is("FL-EDGE-LABEL-QUOTE-IN-PIPES", e)) {
51570
51781
  const lineText = lineTextAt(text, e.line);
51571
- const firstBar = lineText.indexOf("|");
51572
- const secondBar = firstBar >= 0 ? lineText.indexOf("|", firstBar + 1) : -1;
51782
+ const col = Math.max(0, e.column - 1);
51783
+ const firstBar = lineText.lastIndexOf("|", col);
51784
+ const secondBar = firstBar >= 0 ? lineText.indexOf("|", col + 1) : -1;
51573
51785
  if (firstBar >= 0 && secondBar > firstBar) {
51574
51786
  const before = lineText.slice(0, firstBar + 1);
51575
51787
  const label = lineText.slice(firstBar + 1, secondBar);
51576
51788
  const after = lineText.slice(secondBar);
51577
51789
  let fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
51578
51790
  fixedLabel = fixedLabel.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
51791
+ fixedLabel = fixedLabel.replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
51579
51792
  const fixedLine = before + fixedLabel + after;
51580
51793
  const finalLine = fixedLine.replace(/\[([^\]]*)\]/g, (m, seg) => "[" + String(seg).replace(/`/g, "") + "]");
51581
51794
  edits.push({ start: { line: e.line, column: 1 }, end: { line: e.line, column: lineText.length + 1 }, newText: finalLine });
@@ -62965,7 +63178,7 @@ ${overlay}</g>`;
62965
63178
  });
62966
63179
 
62967
63180
  // node_modules/@probelabs/maid/out/renderer/pie-builder.js
62968
- function unquote(s) {
63181
+ function unquote2(s) {
62969
63182
  if (!s)
62970
63183
  return s;
62971
63184
  const first2 = s.charAt(0);
@@ -63015,7 +63228,7 @@ function buildPieModel(text) {
63015
63228
  const collect = (k) => {
63016
63229
  const arr = tnode.children?.[k] ?? [];
63017
63230
  for (const tok of arr)
63018
- parts.push(unquote(tok.image));
63231
+ parts.push(unquote2(tok.image));
63019
63232
  };
63020
63233
  collect("QuotedString");
63021
63234
  collect("Text");
@@ -63028,7 +63241,7 @@ function buildPieModel(text) {
63028
63241
  const labelTok = snode.children?.sliceLabel?.[0]?.children?.QuotedString?.[0];
63029
63242
  const numTok = snode.children?.NumberLiteral?.[0];
63030
63243
  if (labelTok && numTok) {
63031
- const label = unquote(labelTok.image).trim();
63244
+ const label = unquote2(labelTok.image).trim();
63032
63245
  const value = Number(numTok.image);
63033
63246
  if (!Number.isNaN(value)) {
63034
63247
  model.slices.push({ label, value });
@@ -64184,128 +64397,6 @@ var init_state_renderer = __esm({
64184
64397
  }
64185
64398
  });
64186
64399
 
64187
- // node_modules/@probelabs/maid/out/core/frontmatter.js
64188
- function parseFrontmatter(input) {
64189
- const text = input.startsWith("\uFEFF") ? input.slice(1) : input;
64190
- const lines = text.split(/\r?\n/);
64191
- if (lines.length < 3 || lines[0].trim() !== "---")
64192
- return null;
64193
- let i = 1;
64194
- const block = [];
64195
- while (i < lines.length && lines[i].trim() !== "---") {
64196
- block.push(lines[i]);
64197
- i++;
64198
- }
64199
- if (i >= lines.length)
64200
- return null;
64201
- const body = lines.slice(i + 1).join("\n");
64202
- const raw = block.join("\n");
64203
- const config2 = {};
64204
- const themeVars = {};
64205
- let themeUnderConfig = false;
64206
- let ctx = "root";
64207
- for (const line of block) {
64208
- if (!line.trim())
64209
- continue;
64210
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
64211
- const mKey = line.match(/^\s*([A-Za-z0-9_\-]+):\s*(.*)$/);
64212
- if (!mKey)
64213
- continue;
64214
- const key = mKey[1];
64215
- let value = mKey[2] || "";
64216
- if (indent === 0) {
64217
- if (key === "config") {
64218
- ctx = "config";
64219
- continue;
64220
- }
64221
- if (key === "themeVariables") {
64222
- ctx = "theme";
64223
- continue;
64224
- }
64225
- ctx = "root";
64226
- continue;
64227
- }
64228
- if (ctx === "config") {
64229
- if (indent <= 2 && key !== "pie" && key !== "themeVariables")
64230
- continue;
64231
- if (key === "pie") {
64232
- ctx = "config.pie";
64233
- ensure(config2, "pie", {});
64234
- continue;
64235
- }
64236
- if (key === "themeVariables") {
64237
- ctx = "theme";
64238
- themeUnderConfig = true;
64239
- continue;
64240
- }
64241
- continue;
64242
- }
64243
- if (ctx === "config.pie") {
64244
- if (indent < 4) {
64245
- if (key === "pie") {
64246
- ctx = "config.pie";
64247
- ensure(config2, "pie", {});
64248
- continue;
64249
- }
64250
- if (key === "themeVariables") {
64251
- ctx = "theme";
64252
- themeUnderConfig = true;
64253
- continue;
64254
- }
64255
- ctx = "config";
64256
- continue;
64257
- }
64258
- setKV(config2.pie, key, value);
64259
- continue;
64260
- }
64261
- if (ctx === "theme") {
64262
- if (indent < 2) {
64263
- ctx = "root";
64264
- continue;
64265
- }
64266
- setKV(themeVars, key, value);
64267
- continue;
64268
- }
64269
- }
64270
- if (themeUnderConfig && Object.keys(themeVars).length) {
64271
- ensure(config2, "themeVariables", {});
64272
- Object.assign(config2.themeVariables, themeVars);
64273
- }
64274
- return { raw, body, config: Object.keys(config2).length ? config2 : void 0, themeVariables: Object.keys(themeVars).length ? themeVars : void 0 };
64275
- }
64276
- function ensure(obj, key, def) {
64277
- if (obj[key] == null)
64278
- obj[key] = def;
64279
- }
64280
- function unquote2(val) {
64281
- const v = val.trim();
64282
- if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
64283
- return v.slice(1, -1);
64284
- }
64285
- return v;
64286
- }
64287
- function setKV(target, key, rawValue) {
64288
- const v = unquote2(rawValue);
64289
- if (v === "") {
64290
- target[key] = "";
64291
- return;
64292
- }
64293
- const num = Number(v);
64294
- if (!Number.isNaN(num) && /^-?[0-9]+(\.[0-9]+)?$/.test(v)) {
64295
- target[key] = num;
64296
- return;
64297
- }
64298
- if (/^(true|false)$/i.test(v)) {
64299
- target[key] = /^true$/i.test(v);
64300
- return;
64301
- }
64302
- target[key] = v;
64303
- }
64304
- var init_frontmatter = __esm({
64305
- "node_modules/@probelabs/maid/out/core/frontmatter.js"() {
64306
- }
64307
- });
64308
-
64309
64400
  // node_modules/@probelabs/maid/out/renderer/class-builder.js
64310
64401
  function textFromTokens3(tokens) {
64311
64402
  if (!tokens || tokens.length === 0)
@@ -73542,6 +73633,9 @@ function isMethodAllowed(methodName, allowedMethods, blockedMethods) {
73542
73633
  }
73543
73634
  function createTransport(serverConfig) {
73544
73635
  const { transport, command, args, url: url2, env } = serverConfig;
73636
+ if (serverConfig.transportInstance) {
73637
+ return serverConfig.transportInstance;
73638
+ }
73545
73639
  switch (transport) {
73546
73640
  case "stdio":
73547
73641
  return new import_stdio.StdioClientTransport({
@@ -73625,6 +73719,7 @@ var init_client = __esm({
73625
73719
  this.debug = options.debug || process.env.DEBUG_MCP === "1";
73626
73720
  this.config = null;
73627
73721
  this.tracer = options.tracer || null;
73722
+ this.agentEvents = options.agentEvents || null;
73628
73723
  }
73629
73724
  /**
73630
73725
  * Record an MCP telemetry event if tracer is available
@@ -73852,11 +73947,21 @@ var init_client = __esm({
73852
73947
  throw new Error(`Server ${tool6.serverName} not connected`);
73853
73948
  }
73854
73949
  const startTime = Date.now();
73950
+ const toolCallId = `mcp-${toolName}-${startTime}`;
73855
73951
  this.recordMcpEvent("tool.call_started", {
73856
73952
  toolName,
73857
73953
  serverName: tool6.serverName,
73858
73954
  originalToolName: tool6.originalName
73859
73955
  });
73956
+ if (this.agentEvents) {
73957
+ this.agentEvents.emit("toolCall", {
73958
+ toolCallId,
73959
+ name: toolName,
73960
+ args,
73961
+ status: "started",
73962
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
73963
+ });
73964
+ }
73860
73965
  try {
73861
73966
  if (this.debug) {
73862
73967
  console.error(`[MCP DEBUG] Calling ${toolName} with args:`, JSON.stringify(args, null, 2));
@@ -73886,6 +73991,14 @@ var init_client = __esm({
73886
73991
  originalToolName: tool6.originalName,
73887
73992
  durationMs
73888
73993
  });
73994
+ if (this.agentEvents) {
73995
+ this.agentEvents.emit("toolCall", {
73996
+ toolCallId,
73997
+ name: toolName,
73998
+ status: "completed",
73999
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
74000
+ });
74001
+ }
73889
74002
  return result;
73890
74003
  } catch (error40) {
73891
74004
  const durationMs = Date.now() - startTime;
@@ -73901,9 +74014,64 @@ var init_client = __esm({
73901
74014
  durationMs,
73902
74015
  isTimeout: error40.message.includes("timeout")
73903
74016
  });
74017
+ if (this.agentEvents) {
74018
+ this.agentEvents.emit("toolCall", {
74019
+ toolCallId,
74020
+ name: toolName,
74021
+ status: "error",
74022
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
74023
+ });
74024
+ }
73904
74025
  throw error40;
73905
74026
  }
73906
74027
  }
74028
+ /**
74029
+ * Call graceful_stop on all MCP servers that expose it.
74030
+ * This signals agent-type MCP servers to wrap up their work.
74031
+ * @returns {Promise<Array<{server: string, success: boolean, error?: string}>>}
74032
+ */
74033
+ async callGracefulStopAll() {
74034
+ const results = [];
74035
+ for (const [serverName, clientInfo] of this.clients) {
74036
+ const qualifiedName = `${serverName}_graceful_stop`;
74037
+ if (this.tools.has(qualifiedName)) {
74038
+ if (this.debug) {
74039
+ console.log(`[DEBUG] MCP callGracefulStopAll: calling graceful_stop on server "${serverName}"`);
74040
+ }
74041
+ try {
74042
+ const timeoutMs = 5e3;
74043
+ const timeoutPromise = new Promise(
74044
+ (_, reject2) => setTimeout(() => reject2(new Error("graceful_stop timeout")), timeoutMs)
74045
+ );
74046
+ await Promise.race([
74047
+ clientInfo.client.callTool({ name: "graceful_stop", arguments: {} }, void 0, { timeout: timeoutMs }),
74048
+ timeoutPromise
74049
+ ]);
74050
+ results.push({ server: serverName, success: true });
74051
+ if (this.debug) {
74052
+ console.log(`[DEBUG] MCP callGracefulStopAll: server "${serverName}" acknowledged graceful_stop`);
74053
+ }
74054
+ } catch (e) {
74055
+ results.push({ server: serverName, success: false, error: e.message });
74056
+ if (this.debug) {
74057
+ console.log(`[DEBUG] MCP callGracefulStopAll: server "${serverName}" graceful_stop failed: ${e.message}`);
74058
+ }
74059
+ }
74060
+ }
74061
+ }
74062
+ if (this.debug) {
74063
+ const withStop = results.length;
74064
+ const total = this.clients.size;
74065
+ console.log(`[DEBUG] MCP callGracefulStopAll: ${withStop}/${total} servers had graceful_stop tool`);
74066
+ }
74067
+ this.recordMcpEvent("graceful_stop.sweep_completed", {
74068
+ servers_total: this.clients.size,
74069
+ servers_with_graceful_stop: results.length,
74070
+ servers_acknowledged: results.filter((r) => r.success).length,
74071
+ servers_failed: results.filter((r) => !r.success).length
74072
+ });
74073
+ return results;
74074
+ }
73907
74075
  /**
73908
74076
  * Get all available tools with their schemas
73909
74077
  * @returns {Object} Map of tool name to tool definition
@@ -73931,10 +74099,29 @@ var init_client = __esm({
73931
74099
  inputSchema: tool6.inputSchema,
73932
74100
  execute: async (args) => {
73933
74101
  const result = await this.callTool(name15, args);
73934
- if (result.content && result.content[0]) {
73935
- return result.content[0].text;
74102
+ if (!result.content || !result.content[0]) {
74103
+ return JSON.stringify(result);
74104
+ }
74105
+ const hasImage = result.content.some((block) => block.type === "image");
74106
+ if (hasImage) {
74107
+ return { _mcpContent: result.content };
74108
+ }
74109
+ return result.content[0].text;
74110
+ },
74111
+ // Convert MCP content blocks (including images) to Vercel AI SDK format
74112
+ toModelOutput: ({ output }) => {
74113
+ if (output && typeof output === "object" && output._mcpContent) {
74114
+ const parts = [];
74115
+ for (const block of output._mcpContent) {
74116
+ if (block.type === "text") {
74117
+ parts.push({ type: "text", text: block.text });
74118
+ } else if (block.type === "image") {
74119
+ parts.push({ type: "image-data", data: block.data, mediaType: block.mimeType });
74120
+ }
74121
+ }
74122
+ return { type: "content", value: parts };
73936
74123
  }
73937
- return JSON.stringify(result);
74124
+ return { type: "text", value: typeof output === "string" ? output : JSON.stringify(output) };
73938
74125
  }
73939
74126
  };
73940
74127
  }
@@ -74020,6 +74207,7 @@ var init_xmlBridge = __esm({
74020
74207
  constructor(options = {}) {
74021
74208
  this.debug = options.debug || false;
74022
74209
  this.tracer = options.tracer || null;
74210
+ this.agentEvents = options.agentEvents || null;
74023
74211
  this.mcpTools = {};
74024
74212
  this.mcpManager = null;
74025
74213
  this.toolDescriptions = {};
@@ -74062,7 +74250,7 @@ var init_xmlBridge = __esm({
74062
74250
  if (this.debug) {
74063
74251
  console.error("[MCP DEBUG] Initializing MCP client manager...");
74064
74252
  }
74065
- this.mcpManager = new MCPClientManager({ debug: this.debug, tracer: this.tracer });
74253
+ this.mcpManager = new MCPClientManager({ debug: this.debug, tracer: this.tracer, agentEvents: this.agentEvents });
74066
74254
  const result = await this.mcpManager.initialize(mcpConfigs);
74067
74255
  const vercelTools = this.mcpManager.getVercelTools();
74068
74256
  this.mcpTools = vercelTools;
@@ -74114,6 +74302,16 @@ var init_xmlBridge = __esm({
74114
74302
  isMcpTool(toolName) {
74115
74303
  return toolName in this.mcpTools;
74116
74304
  }
74305
+ /**
74306
+ * Call graceful_stop on all MCP servers that expose it.
74307
+ * @returns {Promise<Array>}
74308
+ */
74309
+ async callGracefulStopAll() {
74310
+ if (this.mcpManager) {
74311
+ return this.mcpManager.callGracefulStopAll();
74312
+ }
74313
+ return [];
74314
+ }
74117
74315
  /**
74118
74316
  * Clean up MCP connections
74119
74317
  */
@@ -81499,7 +81697,7 @@ function deriveDescription(rawDescription, body) {
81499
81697
  }
81500
81698
  return truncateDescription(description);
81501
81699
  }
81502
- function stripFrontmatter(content) {
81700
+ function stripFrontmatter2(content) {
81503
81701
  const { body } = extractFrontmatter(content);
81504
81702
  return body.trim();
81505
81703
  }
@@ -81640,7 +81838,7 @@ var init_registry = __esm({
81640
81838
  const skill = this.skillsByName.get(name15);
81641
81839
  if (!skill) return null;
81642
81840
  const content = await (0, import_promises3.readFile)(skill.skillFilePath, "utf8");
81643
- return stripFrontmatter(content);
81841
+ return stripFrontmatter2(content);
81644
81842
  }
81645
81843
  async _resolveRealPath(target) {
81646
81844
  try {
@@ -96673,6 +96871,7 @@ var init_ProbeAgent = __esm({
96673
96871
  this.debug = options.debug || process.env.DEBUG === "1";
96674
96872
  this.cancelled = false;
96675
96873
  this._abortController = new AbortController();
96874
+ this._activeSubagents = /* @__PURE__ */ new Map();
96676
96875
  this.tracer = options.tracer || null;
96677
96876
  this.outline = !!options.outline;
96678
96877
  this.searchDelegate = options.searchDelegate !== void 0 ? !!options.searchDelegate : true;
@@ -96784,14 +96983,31 @@ var init_ProbeAgent = __esm({
96784
96983
  this.timeoutBehavior = options.timeoutBehavior ?? (() => {
96785
96984
  const val = process.env.TIMEOUT_BEHAVIOR;
96786
96985
  if (val === "hard") return "hard";
96986
+ if (val === "negotiated") return "negotiated";
96787
96987
  return "graceful";
96788
96988
  })();
96789
96989
  this.gracefulTimeoutBonusSteps = options.gracefulTimeoutBonusSteps ?? (() => {
96790
96990
  const parsed = parseInt(process.env.GRACEFUL_TIMEOUT_BONUS_STEPS, 10);
96791
96991
  return isNaN(parsed) || parsed < 1 || parsed > 20 ? 4 : parsed;
96792
96992
  })();
96993
+ this.negotiatedTimeoutBudget = options.negotiatedTimeoutBudget ?? (() => {
96994
+ const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_BUDGET, 10);
96995
+ return isNaN(parsed) || parsed < 6e4 || parsed > 72e5 ? 18e5 : parsed;
96996
+ })();
96997
+ this.negotiatedTimeoutMaxRequests = options.negotiatedTimeoutMaxRequests ?? (() => {
96998
+ const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_MAX_REQUESTS, 10);
96999
+ return isNaN(parsed) || parsed < 1 || parsed > 10 ? 3 : parsed;
97000
+ })();
97001
+ this.negotiatedTimeoutMaxPerRequest = options.negotiatedTimeoutMaxPerRequest ?? (() => {
97002
+ const parsed = parseInt(process.env.NEGOTIATED_TIMEOUT_MAX_PER_REQUEST, 10);
97003
+ return isNaN(parsed) || parsed < 6e4 || parsed > 36e5 ? 6e5 : parsed;
97004
+ })();
97005
+ this.gracefulStopDeadline = options.gracefulStopDeadline ?? (() => {
97006
+ const parsed = parseInt(process.env.GRACEFUL_STOP_DEADLINE, 10);
97007
+ return isNaN(parsed) || parsed < 5e3 || parsed > 3e5 ? 45e3 : parsed;
97008
+ })();
96793
97009
  if (this.debug) {
96794
- console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}`);
97010
+ console.log(`[DEBUG] Timeout behavior: ${this.timeoutBehavior}, bonus steps: ${this.gracefulTimeoutBonusSteps}, graceful stop deadline: ${this.gracefulStopDeadline}ms`);
96795
97011
  }
96796
97012
  this.retryConfig = options.retry || {};
96797
97013
  this.retryManager = null;
@@ -97143,6 +97359,18 @@ var init_ProbeAgent = __esm({
97143
97359
  // Per-instance delegation limits
97144
97360
  parentAbortSignal: this._abortController.signal,
97145
97361
  // Propagate cancellation to delegations
97362
+ // Timeout settings for delegate subagents to inherit
97363
+ timeoutBehavior: this.timeoutBehavior,
97364
+ maxOperationTimeout: this.maxOperationTimeout,
97365
+ requestTimeout: this.requestTimeout,
97366
+ gracefulTimeoutBonusSteps: this.gracefulTimeoutBonusSteps,
97367
+ negotiatedTimeoutBudget: this.negotiatedTimeoutBudget,
97368
+ negotiatedTimeoutMaxRequests: this.negotiatedTimeoutMaxRequests,
97369
+ negotiatedTimeoutMaxPerRequest: this.negotiatedTimeoutMaxPerRequest,
97370
+ parentOperationStartTime: this._operationStartTime,
97371
+ // For remaining budget calculation
97372
+ onSubagentCreated: (sid, subagent) => this._registerSubagent(sid, subagent),
97373
+ onSubagentCompleted: (sid) => this._unregisterSubagent(sid),
97146
97374
  outputBuffer: this._outputBuffer,
97147
97375
  concurrencyLimiter: this.concurrencyLimiter,
97148
97376
  // Global AI concurrency limiter
@@ -97727,7 +97955,7 @@ var init_ProbeAgent = __esm({
97727
97955
  }
97728
97956
  if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
97729
97957
  const gts = this._gracefulTimeoutState;
97730
- if (this.timeoutBehavior === "graceful" && gts) {
97958
+ if ((this.timeoutBehavior === "graceful" || this.timeoutBehavior === "negotiated") && gts) {
97731
97959
  } else {
97732
97960
  timeoutState.timeoutId = setTimeout(() => {
97733
97961
  controller.abort();
@@ -98631,7 +98859,7 @@ var init_ProbeAgent = __esm({
98631
98859
  }
98632
98860
  mcpConfig = null;
98633
98861
  }
98634
- this.mcpBridge = new MCPXmlBridge({ debug: this.debug });
98862
+ this.mcpBridge = new MCPXmlBridge({ debug: this.debug, agentEvents: this.events });
98635
98863
  await this.mcpBridge.initialize(mcpConfig);
98636
98864
  const mcpToolNames = this.mcpBridge.getToolNames();
98637
98865
  const mcpToolCount = mcpToolNames.length;
@@ -99114,6 +99342,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99114
99342
  } else {
99115
99343
  options = schemaOrOptions || {};
99116
99344
  }
99345
+ this._operationStartTime = Date.now();
99117
99346
  try {
99118
99347
  const oldHistoryLength = this.history.length;
99119
99348
  if (this._outputBuffer && !options?._schemaFormatted && !options?._completionPromptProcessed) {
@@ -99194,7 +99423,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99194
99423
  }
99195
99424
  }
99196
99425
  let currentIteration = 0;
99197
- let finalResult = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
99426
+ let finalResult = null;
99427
+ const DEFAULT_MAX_ITER_MSG = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
99428
+ const _toolCallLog = [];
99429
+ let abortSummaryTaken = false;
99198
99430
  const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
99199
99431
  const maxIterations = options._maxIterationsOverride ? baseMaxIterations : options.schema ? baseMaxIterations + 4 : baseMaxIterations;
99200
99432
  const isClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
@@ -99334,6 +99566,233 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99334
99566
  bonusStepsMax: this.gracefulTimeoutBonusSteps
99335
99567
  };
99336
99568
  this._gracefulTimeoutState = gracefulTimeoutState;
99569
+ const negotiatedTimeoutState = {
99570
+ extensionsUsed: 0,
99571
+ totalExtraTimeMs: 0,
99572
+ softTimeoutId: null,
99573
+ hardAbortTimeoutId: null,
99574
+ maxRequests: this.negotiatedTimeoutMaxRequests,
99575
+ maxPerRequestMs: this.negotiatedTimeoutMaxPerRequest,
99576
+ budgetMs: this.negotiatedTimeoutBudget,
99577
+ observerRunning: false,
99578
+ // true while observer LLM call is in flight
99579
+ extensionMessage: null,
99580
+ // message to show in prepareStep after extension granted
99581
+ startTime: Date.now()
99582
+ };
99583
+ this._negotiatedTimeoutState = negotiatedTimeoutState;
99584
+ const activeTools = /* @__PURE__ */ new Map();
99585
+ this._activeTools = activeTools;
99586
+ const onToolCall = (event) => {
99587
+ const key = event.toolCallId || `${event.name}:${JSON.stringify(event.args || {}).slice(0, 100)}`;
99588
+ if (event.status === "started") {
99589
+ activeTools.set(key, {
99590
+ name: event.name,
99591
+ args: event.args,
99592
+ startedAt: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
99593
+ });
99594
+ } else if (event.status === "completed" || event.status === "error") {
99595
+ activeTools.delete(key);
99596
+ }
99597
+ };
99598
+ this.events.on("toolCall", onToolCall);
99599
+ const runTimeoutObserver = async () => {
99600
+ if (negotiatedTimeoutState.observerRunning) return;
99601
+ negotiatedTimeoutState.observerRunning = true;
99602
+ const remainingRequests = negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed;
99603
+ const remainingBudgetMs = negotiatedTimeoutState.budgetMs - negotiatedTimeoutState.totalExtraTimeMs;
99604
+ const maxPerReqMin = Math.round(negotiatedTimeoutState.maxPerRequestMs / 6e4);
99605
+ const elapsedMin = Math.round((Date.now() - negotiatedTimeoutState.startTime) / 6e4);
99606
+ if (remainingRequests <= 0 || remainingBudgetMs <= 0) {
99607
+ if (this.debug) {
99608
+ console.log(`[DEBUG] Timeout observer: no extensions/budget remaining \u2014 aborting in-flight tools and triggering graceful wind-down`);
99609
+ }
99610
+ if (this.tracer) {
99611
+ this.tracer.addEvent("negotiated_timeout.observer_exhausted", {
99612
+ extensions_used: negotiatedTimeoutState.extensionsUsed,
99613
+ max_requests: negotiatedTimeoutState.maxRequests,
99614
+ total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
99615
+ budget_ms: negotiatedTimeoutState.budgetMs,
99616
+ elapsed_min: elapsedMin,
99617
+ active_tools: Array.from(activeTools.values()).map((t) => t.name)
99618
+ });
99619
+ }
99620
+ await this._initiateGracefulStop(gracefulTimeoutState, "budget/extensions exhausted");
99621
+ negotiatedTimeoutState.observerRunning = false;
99622
+ return;
99623
+ }
99624
+ const activeToolsList = Array.from(activeTools.values());
99625
+ const now = Date.now();
99626
+ const formatDuration = (ms) => {
99627
+ const totalSec = Math.round(ms / 1e3);
99628
+ if (totalSec < 60) return `${totalSec}s`;
99629
+ const min = Math.floor(totalSec / 60);
99630
+ const sec = totalSec % 60;
99631
+ if (min < 60) return `${min}m ${sec}s`;
99632
+ const hr = Math.floor(min / 60);
99633
+ const remainMin = min % 60;
99634
+ return `${hr}h ${remainMin}m`;
99635
+ };
99636
+ const activeToolsDesc = activeToolsList.length > 0 ? activeToolsList.map((t) => {
99637
+ const runningForMs = now - new Date(t.startedAt).getTime();
99638
+ return `- ${t.name}(${JSON.stringify(t.args || {}).slice(0, 200)}) \u2014 running for ${formatDuration(runningForMs)}`;
99639
+ }).join("\n") : "(none currently running)";
99640
+ const recentHistory = this.history.slice(-6).map((msg) => {
99641
+ const content = typeof msg.content === "string" ? msg.content.slice(0, 300) : JSON.stringify(msg.content).slice(0, 300);
99642
+ return `[${msg.role}]: ${content}`;
99643
+ }).join("\n");
99644
+ 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.
99645
+
99646
+ ## Recent Conversation
99647
+ ${recentHistory || "(no history yet)"}
99648
+
99649
+ ## Currently Running Tools
99650
+ ${activeToolsDesc}
99651
+
99652
+ ## Budget
99653
+ - Extensions used: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}
99654
+ - Time budget remaining: ${Math.round(remainingBudgetMs / 6e4)} minutes
99655
+ - Max per extension: ${maxPerReqMin} minutes
99656
+
99657
+ Decide whether the agent should get more time. EXTEND if:
99658
+ - Tools are actively running (especially delegates or complex analysis) \u2014 they need time to finish
99659
+ - The agent is making clear progress on a complex task
99660
+ - New information is being gathered that will improve the final answer
99661
+
99662
+ DO NOT EXTEND if:
99663
+ - The agent appears stuck in a loop (repeating the same tool calls or getting the same errors)
99664
+ - The conversation shows the agent retrying failed operations without changing approach
99665
+ - The agent has enough information to answer but keeps searching for more
99666
+ - Tool calls are returning empty or error results repeatedly
99667
+ - The agent is doing redundant work (searching for things it already found)
99668
+
99669
+ 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.
99670
+
99671
+ Respond with ONLY valid JSON (no markdown, no explanation):
99672
+ {"extend": true, "minutes": <1-${maxPerReqMin}>, "reason": "your reason here"}
99673
+ or
99674
+ {"extend": false, "reason": "your reason here"}`;
99675
+ const observerFn = async () => {
99676
+ const modelInstance = this.provider ? this.provider(this.model) : this.model;
99677
+ if (this.debug) {
99678
+ console.log(`[DEBUG] Timeout observer: making LLM call (${activeToolsList.length} active tools, ${elapsedMin} min elapsed)`);
99679
+ }
99680
+ if (this.tracer) {
99681
+ this.tracer.addEvent("negotiated_timeout.observer_invoked", {
99682
+ elapsed_min: elapsedMin,
99683
+ active_tools: activeToolsList.map((t) => t.name),
99684
+ active_tools_detail: activeToolsList.map((t) => ({
99685
+ name: t.name,
99686
+ running_for_ms: now - new Date(t.startedAt).getTime(),
99687
+ args_preview: JSON.stringify(t.args || {}).slice(0, 100)
99688
+ })),
99689
+ active_tools_count: activeToolsList.length,
99690
+ extensions_used: negotiatedTimeoutState.extensionsUsed,
99691
+ remaining_requests: remainingRequests,
99692
+ remaining_budget_ms: remainingBudgetMs,
99693
+ history_length: this.history.length
99694
+ });
99695
+ }
99696
+ const observerResult = await (0, import_ai4.generateText)({
99697
+ model: modelInstance,
99698
+ messages: [{ role: "user", content: observerPrompt }],
99699
+ maxTokens: 500
99700
+ });
99701
+ const responseText = observerResult.text.trim();
99702
+ if (this.tracer) {
99703
+ this.tracer.addEvent("negotiated_timeout.observer_response", {
99704
+ response_text: responseText,
99705
+ usage_prompt_tokens: observerResult.usage?.promptTokens,
99706
+ usage_completion_tokens: observerResult.usage?.completionTokens
99707
+ });
99708
+ }
99709
+ const jsonStr = responseText.replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
99710
+ const decision = JSON.parse(jsonStr);
99711
+ if (decision.extend && decision.minutes > 0) {
99712
+ const requestedMs = Math.min(decision.minutes, maxPerReqMin) * 6e4;
99713
+ const grantedMs = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
99714
+ const grantedMin = Math.round(grantedMs / 6e4 * 10) / 10;
99715
+ negotiatedTimeoutState.extensionsUsed++;
99716
+ negotiatedTimeoutState.totalExtraTimeMs += grantedMs;
99717
+ 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.`;
99718
+ negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
99719
+ runTimeoutObserver();
99720
+ }, grantedMs);
99721
+ if (this.debug) {
99722
+ console.log(`[DEBUG] Timeout observer: granted ${grantedMin} min (reason: ${decision.reason}). Extensions: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}`);
99723
+ }
99724
+ if (this.tracer) {
99725
+ this.tracer.addEvent("negotiated_timeout.observer_extended", {
99726
+ decision_reason: decision.reason,
99727
+ requested_minutes: decision.minutes,
99728
+ granted_ms: grantedMs,
99729
+ granted_min: grantedMin,
99730
+ extensions_used: negotiatedTimeoutState.extensionsUsed,
99731
+ max_requests: negotiatedTimeoutState.maxRequests,
99732
+ total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
99733
+ budget_remaining_ms: remainingBudgetMs - grantedMs,
99734
+ active_tools: activeToolsList.map((t) => t.name),
99735
+ active_tools_count: activeToolsList.length
99736
+ });
99737
+ }
99738
+ this.events.emit("timeout.extended", {
99739
+ grantedMs,
99740
+ reason: decision.reason || "work in progress",
99741
+ extensionsUsed: negotiatedTimeoutState.extensionsUsed,
99742
+ extensionsRemaining: negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed,
99743
+ totalExtraTimeMs: negotiatedTimeoutState.totalExtraTimeMs,
99744
+ budgetRemainingMs: remainingBudgetMs - grantedMs
99745
+ });
99746
+ } else {
99747
+ if (this.debug) {
99748
+ console.log(`[DEBUG] Timeout observer: declined extension (reason: ${decision.reason}). Initiating graceful stop.`);
99749
+ }
99750
+ if (this.tracer) {
99751
+ this.tracer.addEvent("negotiated_timeout.observer_declined", {
99752
+ decision_reason: decision.reason,
99753
+ extensions_used: negotiatedTimeoutState.extensionsUsed,
99754
+ total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
99755
+ elapsed_min: elapsedMin,
99756
+ active_tools: activeToolsList.map((t) => t.name)
99757
+ });
99758
+ }
99759
+ this.events.emit("timeout.windingDown", {
99760
+ reason: decision.reason || "observer declined",
99761
+ extensionsUsed: negotiatedTimeoutState.extensionsUsed,
99762
+ totalExtraTimeMs: negotiatedTimeoutState.totalExtraTimeMs
99763
+ });
99764
+ await this._initiateGracefulStop(gracefulTimeoutState, `observer declined: ${decision.reason}`);
99765
+ }
99766
+ };
99767
+ try {
99768
+ if (this.tracer) {
99769
+ await this.tracer.withSpan("negotiated_timeout.observer", observerFn, {
99770
+ "timeout.elapsed_min": elapsedMin,
99771
+ "timeout.extensions_used": negotiatedTimeoutState.extensionsUsed,
99772
+ "timeout.active_tools_count": activeToolsList.length,
99773
+ "timeout.remaining_budget_ms": remainingBudgetMs
99774
+ });
99775
+ } else {
99776
+ await observerFn();
99777
+ }
99778
+ } catch (err) {
99779
+ if (this.debug) {
99780
+ console.log(`[DEBUG] Timeout observer: LLM call failed (${err.message}). Initiating graceful stop.`);
99781
+ }
99782
+ if (this.tracer) {
99783
+ this.tracer.addEvent("negotiated_timeout.observer_error", {
99784
+ error_message: err.message,
99785
+ error_name: err.name,
99786
+ extensions_used: negotiatedTimeoutState.extensionsUsed,
99787
+ elapsed_min: elapsedMin
99788
+ });
99789
+ }
99790
+ await this._initiateGracefulStop(gracefulTimeoutState, `observer error: ${err.message}`);
99791
+ } finally {
99792
+ negotiatedTimeoutState.observerRunning = false;
99793
+ }
99794
+ };
99795
+ negotiatedTimeoutState.runObserver = runTimeoutObserver;
99337
99796
  let compactionAttempted = false;
99338
99797
  while (true) {
99339
99798
  try {
@@ -99395,6 +99854,14 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99395
99854
  return false;
99396
99855
  },
99397
99856
  prepareStep: ({ steps, stepNumber }) => {
99857
+ if (negotiatedTimeoutState.extensionMessage && !gracefulTimeoutState.triggered) {
99858
+ const msg = negotiatedTimeoutState.extensionMessage;
99859
+ negotiatedTimeoutState.extensionMessage = null;
99860
+ if (this.debug) {
99861
+ console.log(`[DEBUG] prepareStep: delivering timeout observer extension message`);
99862
+ }
99863
+ return { userMessage: msg };
99864
+ }
99398
99865
  if (gracefulTimeoutState.triggered) {
99399
99866
  gracefulTimeoutState.bonusStepsUsed++;
99400
99867
  const remaining = gracefulTimeoutState.bonusStepsMax - gracefulTimeoutState.bonusStepsUsed;
@@ -99420,8 +99887,12 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99420
99887
  return { toolChoice: "none" };
99421
99888
  }
99422
99889
  if (stepNumber === maxIterations - 1) {
99890
+ 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);
99891
+ const searchSummary = searchesTried.length > 0 ? `
99892
+ Searches attempted: ${searchesTried.join(", ")}` : "";
99423
99893
  return {
99424
- toolChoice: "none"
99894
+ toolChoice: "none",
99895
+ 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
99896
  };
99426
99897
  }
99427
99898
  if (steps.length >= 2) {
@@ -99500,6 +99971,11 @@ Double-check your response based on the criteria above. If everything looks good
99500
99971
  const { toolResults, toolCalls, text, reasoningText, finishReason, usage } = stepResult;
99501
99972
  currentIteration++;
99502
99973
  toolContext.currentIteration = currentIteration;
99974
+ if (toolCalls?.length > 0) {
99975
+ for (const tc of toolCalls) {
99976
+ _toolCallLog.push({ name: tc.toolName, args: tc.args || {} });
99977
+ }
99978
+ }
99503
99979
  if (this.tracer) {
99504
99980
  const stepEvent = {
99505
99981
  "iteration": currentIteration,
@@ -99591,6 +100067,14 @@ Double-check your response based on the criteria above. If everything looks good
99591
100067
  }, 6e4);
99592
100068
  }, this.maxOperationTimeout);
99593
100069
  }
100070
+ if (this.timeoutBehavior === "negotiated" && this.maxOperationTimeout > 0) {
100071
+ negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
100072
+ if (this.debug) {
100073
+ console.log(`[DEBUG] Soft timeout after ${this.maxOperationTimeout}ms \u2014 invoking timeout observer`);
100074
+ }
100075
+ runTimeoutObserver();
100076
+ }, this.maxOperationTimeout);
100077
+ }
99594
100078
  try {
99595
100079
  const steps = await result.steps;
99596
100080
  let finalText;
@@ -99611,6 +100095,12 @@ Double-check your response based on the criteria above. If everything looks good
99611
100095
  } finally {
99612
100096
  if (gracefulTimeoutId) clearTimeout(gracefulTimeoutId);
99613
100097
  if (hardAbortTimeoutId) clearTimeout(hardAbortTimeoutId);
100098
+ if (negotiatedTimeoutState.softTimeoutId) clearTimeout(negotiatedTimeoutState.softTimeoutId);
100099
+ if (this._gracefulStopHardAbortId) {
100100
+ clearTimeout(this._gracefulStopHardAbortId);
100101
+ this._gracefulStopHardAbortId = null;
100102
+ }
100103
+ this.events.removeListener("toolCall", onToolCall);
99614
100104
  }
99615
100105
  };
99616
100106
  let aiResult;
@@ -99650,7 +100140,7 @@ Double-check your response based on the criteria above. If everything looks good
99650
100140
  }
99651
100141
  if (gracefulTimeoutState.triggered) {
99652
100142
  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 due to reaching the maximum number of tool iterations.") {
100143
+ if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG || finalResult.startsWith("I was unable to complete your request after")) {
99654
100144
  try {
99655
100145
  const allText = await aiResult.result.text;
99656
100146
  if (allText && allText.trim()) {
@@ -99698,7 +100188,7 @@ ${toolSummaries.join("\n\n---\n\n")}`;
99698
100188
  currentMessages.push(msg);
99699
100189
  }
99700
100190
  }
99701
- if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && finalResult) {
100191
+ if (this.completionPrompt && !options._completionPromptProcessed && !completionPromptInjected && !abortSummaryTaken && finalResult) {
99702
100192
  completionPromptInjected = true;
99703
100193
  preCompletionResult = finalResult;
99704
100194
  if (this.debug) {
@@ -99770,6 +100260,118 @@ Double-check your response based on the criteria above. If everything looks good
99770
100260
  }
99771
100261
  break;
99772
100262
  } catch (error40) {
100263
+ if (gracefulTimeoutState.triggered && error40?.name === "AbortError") {
100264
+ if (this.debug) {
100265
+ console.log(`[DEBUG] Negotiated timeout: abort caught \u2014 making summary LLM call with conversation context`);
100266
+ }
100267
+ if (this.tracer) {
100268
+ this.tracer.addEvent("negotiated_timeout.abort_summary_started", {
100269
+ conversation_messages: currentMessages.length,
100270
+ has_schema: !!options.schema,
100271
+ has_tasks: !!(this.enableTasks && this.taskManager)
100272
+ });
100273
+ }
100274
+ try {
100275
+ let taskContext = "";
100276
+ if (this.enableTasks && this.taskManager) {
100277
+ const taskSummary = this.taskManager.getTaskSummary?.();
100278
+ if (taskSummary) {
100279
+ taskContext = `
100280
+
100281
+ ## Task Status
100282
+ ${taskSummary}
100283
+
100284
+ Acknowledge which tasks were completed and which were not.`;
100285
+ }
100286
+ }
100287
+ let schemaContext = "";
100288
+ if (options.schema) {
100289
+ try {
100290
+ const parsedSchema = typeof options.schema === "string" ? JSON.parse(options.schema) : options.schema;
100291
+ schemaContext = `
100292
+
100293
+ IMPORTANT: Your response MUST be valid JSON matching this schema:
100294
+ ${JSON.stringify(parsedSchema, null, 2)}
100295
+
100296
+ 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.`;
100297
+ } catch {
100298
+ }
100299
+ }
100300
+ 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.
100301
+
100302
+ Please provide a DETAILED summary of:
100303
+ 1. What you were asked to do (the original task)
100304
+ 2. What you accomplished \u2014 include ALL findings, code snippets, data, and conclusions you gathered
100305
+ 3. What was still in progress or not yet started
100306
+ 4. Any partial results or recommendations you can offer based on what you found so far${taskContext}${schemaContext}
100307
+
100308
+ Be thorough \u2014 this is the user's only response. Include all useful information you collected.`;
100309
+ const summaryMessages = [
100310
+ ...currentMessages,
100311
+ { role: "user", content: summaryPrompt }
100312
+ ];
100313
+ const modelInstance = this.provider ? this.provider(this.model) : this.model;
100314
+ const summaryFn = async () => {
100315
+ const summaryResult = await (0, import_ai4.generateText)({
100316
+ model: modelInstance,
100317
+ messages: this.prepareMessagesWithImages(summaryMessages),
100318
+ maxTokens: 4e3
100319
+ });
100320
+ if (this.tracer) {
100321
+ this.tracer.addEvent("negotiated_timeout.abort_summary_completed", {
100322
+ summary_length: summaryResult.text?.length || 0,
100323
+ usage_prompt_tokens: summaryResult.usage?.promptTokens,
100324
+ usage_completion_tokens: summaryResult.usage?.completionTokens
100325
+ });
100326
+ }
100327
+ if (summaryResult.usage) {
100328
+ this.tokenCounter.recordUsage(summaryResult.usage);
100329
+ }
100330
+ return summaryResult.text;
100331
+ };
100332
+ let summaryText;
100333
+ if (this.tracer) {
100334
+ summaryText = await this.tracer.withSpan("negotiated_timeout.abort_summary", summaryFn, {
100335
+ "summary.conversation_messages": currentMessages.length
100336
+ });
100337
+ } else {
100338
+ summaryText = await summaryFn();
100339
+ }
100340
+ if (options.schema) {
100341
+ finalResult = summaryText || "{}";
100342
+ } else {
100343
+ 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";
100344
+ finalResult = timeoutNotice + (summaryText || "The operation was interrupted before a response could be generated.");
100345
+ }
100346
+ if (options.onStream && finalResult) {
100347
+ options.onStream(finalResult);
100348
+ }
100349
+ if (this.debug) {
100350
+ console.log(`[DEBUG] Negotiated timeout: summary produced ${summaryText?.length || 0} chars`);
100351
+ }
100352
+ } catch (summaryErr) {
100353
+ if (this.debug) {
100354
+ console.log(`[DEBUG] Negotiated timeout: summary call failed (${summaryErr.message}), falling back to partial text`);
100355
+ }
100356
+ if (this.tracer) {
100357
+ this.tracer.addEvent("negotiated_timeout.abort_summary_error", {
100358
+ error_message: summaryErr.message
100359
+ });
100360
+ }
100361
+ const partialTexts = currentMessages.filter((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim()).map((m) => m.content);
100362
+ if (options.schema) {
100363
+ finalResult = partialTexts.length > 0 ? partialTexts[partialTexts.length - 1] : "{}";
100364
+ } else {
100365
+ const timeoutNotice = "**Note: This response was generated under a time constraint. The operation was interrupted and some work was not completed.**\n\n";
100366
+ 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.";
100367
+ }
100368
+ if (options.onStream && finalResult) {
100369
+ options.onStream(finalResult);
100370
+ }
100371
+ }
100372
+ abortSummaryTaken = true;
100373
+ break;
100374
+ }
99773
100375
  if (!compactionAttempted && handleContextLimitError) {
99774
100376
  const compactionResult = handleContextLimitError(error40, currentMessages, {
99775
100377
  keepLastSegment: true,
@@ -99804,6 +100406,36 @@ Double-check your response based on the criteria above. If everything looks good
99804
100406
  }
99805
100407
  if (currentIteration >= maxIterations) {
99806
100408
  console.warn(`[WARN] Max tool iterations (${maxIterations}) reached for session ${this.sessionId}.`);
100409
+ if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG) {
100410
+ try {
100411
+ const searchQueries = [];
100412
+ const toolCounts = {};
100413
+ for (const tc of _toolCallLog) {
100414
+ toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
100415
+ if (tc.name === "search") {
100416
+ const q = tc.args.query || "";
100417
+ const exact = tc.args.exact ? " (exact)" : "";
100418
+ searchQueries.push(`"${q}"${exact}`);
100419
+ }
100420
+ }
100421
+ const toolBreakdown = Object.entries(toolCounts).map(([name15, count]) => `${name15}: ${count}x`).join(", ");
100422
+ const uniqueSearches = [...new Set(searchQueries)];
100423
+ let summary = `I was unable to complete your request after ${currentIteration} tool iterations.
100424
+
100425
+ `;
100426
+ summary += `Tool calls made: ${toolBreakdown || "none"}
100427
+ `;
100428
+ if (uniqueSearches.length > 0) {
100429
+ summary += `Search queries tried: ${uniqueSearches.join(", ")}
100430
+ `;
100431
+ }
100432
+ summary += `
100433
+ 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.`;
100434
+ finalResult = summary;
100435
+ } catch {
100436
+ finalResult = DEFAULT_MAX_ITER_MSG;
100437
+ }
100438
+ }
99807
100439
  }
99808
100440
  this.history = currentMessages.map((msg) => ({ ...msg }));
99809
100441
  if (this.history.length > MAX_HISTORY_MESSAGES) {
@@ -100338,6 +100970,134 @@ Double-check your response based on the criteria above. If everything looks good
100338
100970
  console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
100339
100971
  }
100340
100972
  }
100973
+ /**
100974
+ * Trigger graceful wind-down from outside (e.g., parent agent).
100975
+ * Unlike cancel(), this does NOT abort — it sets the graceful timeout flag
100976
+ * so the agent finishes its current step and then winds down naturally.
100977
+ */
100978
+ triggerGracefulWindDown() {
100979
+ if (this._gracefulTimeoutState && !this._gracefulTimeoutState.triggered) {
100980
+ this._gracefulTimeoutState.triggered = true;
100981
+ if (this.debug) {
100982
+ console.log(`[DEBUG] Graceful wind-down triggered externally for session ${this.sessionId}`);
100983
+ }
100984
+ if (this.tracer) {
100985
+ this.tracer.addEvent("graceful_stop.external_trigger", {
100986
+ "session.id": this.sessionId
100987
+ });
100988
+ }
100989
+ } else if (this.debug) {
100990
+ console.log(`[DEBUG] Graceful wind-down already active for session ${this.sessionId}, skipping`);
100991
+ }
100992
+ }
100993
+ /**
100994
+ * Initiate two-phase graceful stop: signal subagents and MCP servers to wind down,
100995
+ * then hard-abort after a deadline if they haven't finished.
100996
+ * @param {Object} gracefulTimeoutState - The graceful timeout state object from run()
100997
+ * @param {string} reason - Why the graceful stop was initiated
100998
+ */
100999
+ async _initiateGracefulStop(gracefulTimeoutState, reason) {
101000
+ if (gracefulTimeoutState.triggered) return;
101001
+ if (this.debug) {
101002
+ console.log(`[DEBUG] Initiating graceful stop: ${reason} (subagents: ${this._activeSubagents.size}, hasMcpBridge: ${!!this.mcpBridge}, deadline: ${this.gracefulStopDeadline}ms)`);
101003
+ }
101004
+ gracefulTimeoutState.triggered = true;
101005
+ if (this.tracer) {
101006
+ this.tracer.addEvent("graceful_stop.initiated", {
101007
+ "session.id": this.sessionId,
101008
+ "graceful_stop.reason": reason,
101009
+ "graceful_stop.active_subagents": this._activeSubagents.size,
101010
+ "graceful_stop.has_mcp_bridge": !!this.mcpBridge,
101011
+ "graceful_stop.deadline_ms": this.gracefulStopDeadline
101012
+ });
101013
+ }
101014
+ let subagentsSignalled = 0;
101015
+ let subagentErrors = 0;
101016
+ for (const [sid, subagent] of this._activeSubagents) {
101017
+ try {
101018
+ subagent.triggerGracefulWindDown();
101019
+ subagentsSignalled++;
101020
+ if (this.debug) {
101021
+ console.log(`[DEBUG] Triggered graceful wind-down on subagent ${sid}`);
101022
+ }
101023
+ } catch (e) {
101024
+ subagentErrors++;
101025
+ if (this.debug) {
101026
+ console.log(`[DEBUG] Failed to trigger wind-down on subagent ${sid}: ${e.message}`);
101027
+ }
101028
+ }
101029
+ }
101030
+ let mcpResults = [];
101031
+ if (this.mcpBridge) {
101032
+ try {
101033
+ mcpResults = await this.mcpBridge.callGracefulStopAll();
101034
+ if (this.debug && mcpResults.length > 0) {
101035
+ console.log(`[DEBUG] MCP graceful_stop results: ${JSON.stringify(mcpResults)}`);
101036
+ }
101037
+ } catch (e) {
101038
+ if (this.debug) {
101039
+ console.log(`[DEBUG] MCP graceful_stop failed: ${e.message}`);
101040
+ }
101041
+ }
101042
+ }
101043
+ if (this.tracer) {
101044
+ this.tracer.addEvent("graceful_stop.signals_sent", {
101045
+ "session.id": this.sessionId,
101046
+ "graceful_stop.subagents_signalled": subagentsSignalled,
101047
+ "graceful_stop.subagent_errors": subagentErrors,
101048
+ "graceful_stop.mcp_servers_called": mcpResults.filter((r) => r.success).length,
101049
+ "graceful_stop.mcp_servers_failed": mcpResults.filter((r) => !r.success).length,
101050
+ "graceful_stop.mcp_servers_total": mcpResults.length
101051
+ });
101052
+ }
101053
+ this._gracefulStopHardAbortId = setTimeout(() => {
101054
+ if (this.debug) {
101055
+ console.log(`[DEBUG] Graceful stop deadline (${this.gracefulStopDeadline}ms) expired \u2014 hard aborting`);
101056
+ }
101057
+ if (this.tracer) {
101058
+ this.tracer.addEvent("graceful_stop.deadline_expired", {
101059
+ "session.id": this.sessionId,
101060
+ "graceful_stop.deadline_ms": this.gracefulStopDeadline
101061
+ });
101062
+ }
101063
+ if (this._abortController) this._abortController.abort();
101064
+ }, this.gracefulStopDeadline);
101065
+ }
101066
+ /**
101067
+ * Register an active subagent for graceful stop coordination.
101068
+ * @param {string} sessionId
101069
+ * @param {ProbeAgent} subagent
101070
+ */
101071
+ _registerSubagent(sessionId, subagent) {
101072
+ this._activeSubagents.set(sessionId, subagent);
101073
+ if (this.debug) {
101074
+ console.log(`[DEBUG] Registered subagent ${sessionId} (active: ${this._activeSubagents.size})`);
101075
+ }
101076
+ if (this.tracer) {
101077
+ this.tracer.addEvent("subagent.registered", {
101078
+ "session.id": this.sessionId,
101079
+ "subagent.session_id": sessionId,
101080
+ "subagent.active_count": this._activeSubagents.size
101081
+ });
101082
+ }
101083
+ }
101084
+ /**
101085
+ * Unregister a completed subagent.
101086
+ * @param {string} sessionId
101087
+ */
101088
+ _unregisterSubagent(sessionId) {
101089
+ this._activeSubagents.delete(sessionId);
101090
+ if (this.debug) {
101091
+ console.log(`[DEBUG] Unregistered subagent ${sessionId} (active: ${this._activeSubagents.size})`);
101092
+ }
101093
+ if (this.tracer) {
101094
+ this.tracer.addEvent("subagent.unregistered", {
101095
+ "session.id": this.sessionId,
101096
+ "subagent.session_id": sessionId,
101097
+ "subagent.active_count": this._activeSubagents.size
101098
+ });
101099
+ }
101100
+ }
100341
101101
  /**
100342
101102
  * Get the abort signal for this agent.
100343
101103
  * Delegations and subagents should check this signal.
@@ -100380,8 +101140,15 @@ async function delegate({
100380
101140
  // Optional per-instance manager, falls back to default singleton
100381
101141
  concurrencyLimiter = null,
100382
101142
  // Optional global AI concurrency limiter
100383
- parentAbortSignal = null
101143
+ parentAbortSignal = null,
100384
101144
  // Optional AbortSignal from parent to cancel this delegation
101145
+ // Timeout settings inherited from parent agent
101146
+ timeoutBehavior = void 0,
101147
+ requestTimeout = void 0,
101148
+ gracefulTimeoutBonusSteps = void 0,
101149
+ // Subagent lifecycle callbacks for graceful stop coordination
101150
+ onSubagentCreated = null,
101151
+ onSubagentCompleted = null
100385
101152
  }) {
100386
101153
  if (!task || typeof task !== "string") {
100387
101154
  throw new Error("Task parameter is required and must be a string");
@@ -100464,12 +101231,38 @@ async function delegate({
100464
101231
  // Inherit from parent
100465
101232
  mcpConfigPath,
100466
101233
  // Inherit from parent
100467
- concurrencyLimiter
101234
+ concurrencyLimiter,
100468
101235
  // Inherit global AI concurrency limiter
101236
+ // Inherit timeout behavior from parent — subagent gets its own graceful wind-down
101237
+ // so it can produce partial results instead of being hard-killed by the external timer.
101238
+ // The external delegate timeout (capped to parent's remaining budget) is the hard limit;
101239
+ // maxOperationTimeout on the subagent is set slightly shorter so its own wind-down
101240
+ // fires before the external kill.
101241
+ maxOperationTimeout: Math.max(1e4, timeout * 1e3 - 15e3),
101242
+ // 15s before external kill
101243
+ timeoutBehavior: timeoutBehavior || "graceful",
101244
+ requestTimeout,
101245
+ gracefulTimeoutBonusSteps: gracefulTimeoutBonusSteps ?? 2
101246
+ // fewer steps for subagents
100469
101247
  });
101248
+ if (onSubagentCreated) {
101249
+ onSubagentCreated(sessionId, subagent);
101250
+ }
100470
101251
  if (debug) {
100471
101252
  console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
100472
101253
  console.error(`[DELEGATE] Subagent config: promptType=${promptType}, enableDelegate=false, maxIterations=${remainingIterations}`);
101254
+ console.error(`[DELEGATE] Timeout inheritance: externalTimeout=${timeout}s, maxOperationTimeout=${Math.max(1e4, timeout * 1e3 - 15e3)}ms, behavior=${timeoutBehavior || "graceful"}, bonusSteps=${gracefulTimeoutBonusSteps ?? 2}`);
101255
+ }
101256
+ if (tracer) {
101257
+ tracer.addEvent("delegation.subagent_created", {
101258
+ "delegation.session_id": sessionId,
101259
+ "delegation.parent_session_id": parentSessionId,
101260
+ "delegation.external_timeout_s": timeout,
101261
+ "delegation.internal_timeout_ms": Math.max(1e4, timeout * 1e3 - 15e3),
101262
+ "delegation.timeout_behavior": timeoutBehavior || "graceful",
101263
+ "delegation.bonus_steps": gracefulTimeoutBonusSteps ?? 2,
101264
+ "delegation.max_iterations": remainingIterations
101265
+ });
100473
101266
  }
100474
101267
  const timeoutPromise = new Promise((_, reject2) => {
100475
101268
  timeoutId = setTimeout(() => {
@@ -100478,6 +101271,7 @@ async function delegate({
100478
101271
  }, timeout * 1e3);
100479
101272
  });
100480
101273
  let parentAbortHandler;
101274
+ let parentAbortHardCancelId = null;
100481
101275
  const parentAbortPromise = new Promise((_, reject2) => {
100482
101276
  if (parentAbortSignal) {
100483
101277
  if (parentAbortSignal.aborted) {
@@ -100486,8 +101280,31 @@ async function delegate({
100486
101280
  return;
100487
101281
  }
100488
101282
  parentAbortHandler = () => {
100489
- subagent.cancel();
100490
- reject2(new Error("Delegation cancelled: parent operation was aborted"));
101283
+ subagent.triggerGracefulWindDown();
101284
+ if (debug) {
101285
+ console.error(`[DELEGATE] Parent abort signal received \u2014 triggered graceful wind-down on subagent ${sessionId}`);
101286
+ }
101287
+ if (tracer) {
101288
+ tracer.addEvent("delegation.parent_abort_phase1", {
101289
+ "delegation.session_id": sessionId,
101290
+ "delegation.parent_session_id": parentSessionId,
101291
+ "delegation.action": "graceful_wind_down"
101292
+ });
101293
+ }
101294
+ parentAbortHardCancelId = setTimeout(() => {
101295
+ if (debug) {
101296
+ console.error(`[DELEGATE] Graceful wind-down deadline expired \u2014 hard cancelling subagent ${sessionId}`);
101297
+ }
101298
+ if (tracer) {
101299
+ tracer.addEvent("delegation.parent_abort_phase2", {
101300
+ "delegation.session_id": sessionId,
101301
+ "delegation.parent_session_id": parentSessionId,
101302
+ "delegation.action": "hard_cancel"
101303
+ });
101304
+ }
101305
+ subagent.cancel();
101306
+ reject2(new Error("Delegation cancelled: parent operation was aborted (graceful wind-down deadline expired)"));
101307
+ }, 3e4);
100491
101308
  };
100492
101309
  parentAbortSignal.addEventListener("abort", parentAbortHandler, { once: true });
100493
101310
  }
@@ -100503,6 +101320,13 @@ async function delegate({
100503
101320
  if (parentAbortHandler && parentAbortSignal) {
100504
101321
  parentAbortSignal.removeEventListener("abort", parentAbortHandler);
100505
101322
  }
101323
+ if (parentAbortHardCancelId) {
101324
+ clearTimeout(parentAbortHardCancelId);
101325
+ parentAbortHardCancelId = null;
101326
+ }
101327
+ if (onSubagentCompleted) {
101328
+ onSubagentCompleted(sessionId);
101329
+ }
100506
101330
  }
100507
101331
  if (timeoutId !== null) {
100508
101332
  clearTimeout(timeoutId);
@@ -101527,6 +102351,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101527
102351
  "- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
101528
102352
  "- exact=true matches the literal string only \u2014 no stemming, no splitting.",
101529
102353
  '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
102354
+ "- IMPORTANT: Use exact=true when searching for strings containing punctuation, quotes, or empty values.",
102355
+ " Default BM25 search strips punctuation and treats quoted empty strings as noise.",
102356
+ ` Example: searching for 'description: ""' with exact=false will NOT find empty description fields \u2014 it just matches "description".`,
102357
+ ` Use exact=true for literal patterns like 'description: ""', 'value: \\'\\'', or any YAML/config field with specific punctuation.`,
101530
102358
  "- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
101531
102359
  "",
101532
102360
  "Combining searches with OR:",
@@ -101586,7 +102414,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
101586
102414
  "WHEN TO STOP:",
101587
102415
  "- After you have explored the main concept AND related subsystems.",
101588
102416
  "- Once you have 5-15 targets covering different aspects of the query.",
101589
- '- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
102417
+ '- If you get a "DUPLICATE SEARCH BLOCKED" message, do NOT rephrase the same query \u2014 try a FUNDAMENTALLY different approach:',
102418
+ " * Switch between exact=true and exact=false",
102419
+ " * Search for a broader term and filter results manually",
102420
+ " * Use listFiles to browse the directory structure directly",
102421
+ " * Look for related/surrounding patterns instead of the exact string",
102422
+ "- If 2-3 genuinely different search approaches fail, STOP and report what you tried and why it failed.",
102423
+ " Do NOT keep trying variations of the same failing concept.",
101590
102424
  "",
101591
102425
  "Strategy:",
101592
102426
  "1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
@@ -101658,8 +102492,8 @@ var init_vercel = __esm({
101658
102492
  }
101659
102493
  return result;
101660
102494
  };
101661
- const previousSearches = /* @__PURE__ */ new Set();
101662
- let consecutiveDupBlocks = 0;
102495
+ const previousSearches = /* @__PURE__ */ new Map();
102496
+ const dupBlockCounts = /* @__PURE__ */ new Map();
101663
102497
  const paginationCounts = /* @__PURE__ */ new Map();
101664
102498
  const MAX_PAGES_PER_QUERY = 3;
101665
102499
  return (0, import_ai5.tool)({
@@ -101710,20 +102544,25 @@ var init_vercel = __esm({
101710
102544
  return await search(searchOptions);
101711
102545
  };
101712
102546
  if (!searchDelegate) {
101713
- const searchKey = `${searchQuery}::${exact || false}`;
102547
+ const searchKey = `${searchPath}::${searchQuery}::${exact || false}`;
101714
102548
  if (!nextPage) {
101715
102549
  if (previousSearches.has(searchKey)) {
101716
- consecutiveDupBlocks++;
102550
+ const blockCount = (dupBlockCounts.get(searchKey) || 0) + 1;
102551
+ dupBlockCounts.set(searchKey, blockCount);
101717
102552
  if (debug) {
101718
- console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
102553
+ console.error(`[DEDUP] Blocked duplicate search (${blockCount}x): "${searchQuery}" (path: "${searchPath}")`);
102554
+ }
102555
+ if (blockCount >= 3) {
102556
+ 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.";
101719
102557
  }
101720
- if (consecutiveDupBlocks >= 3) {
101721
- return "STOP. You have been blocked " + consecutiveDupBlocks + " times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.";
102558
+ const prev = previousSearches.get(searchKey);
102559
+ if (prev.hadResults) {
102560
+ 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.`;
101722
102561
  }
101723
- return "DUPLICATE SEARCH BLOCKED (" + consecutiveDupBlocks + "x). You already searched for this. Do NOT repeat \u2014 probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.";
102562
+ 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.`;
102563
+ 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
102564
  }
101725
- previousSearches.add(searchKey);
101726
- consecutiveDupBlocks = 0;
102565
+ previousSearches.set(searchKey, { hadResults: false });
101727
102566
  paginationCounts.set(searchKey, 0);
101728
102567
  } else {
101729
102568
  const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
@@ -101737,6 +102576,14 @@ var init_vercel = __esm({
101737
102576
  }
101738
102577
  try {
101739
102578
  const result = maybeAnnotate(await runRawSearch());
102579
+ if (typeof result === "string" && result.includes("No results found")) {
102580
+ if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
102581
+ 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).";
102582
+ }
102583
+ } else if (typeof result === "string") {
102584
+ const entry = previousSearches.get(searchKey);
102585
+ if (entry) entry.hadResults = true;
102586
+ }
101740
102587
  if (options.fileTracker && typeof result === "string") {
101741
102588
  options.fileTracker.trackFilesFromOutput(result, effectiveSearchCwd).catch(() => {
101742
102589
  });
@@ -102019,7 +102866,31 @@ var init_vercel = __esm({
102019
102866
  });
102020
102867
  };
102021
102868
  delegateTool = (options = {}) => {
102022
- const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
102869
+ const {
102870
+ debug = false,
102871
+ timeout = 300,
102872
+ cwd,
102873
+ allowedFolders,
102874
+ workspaceRoot,
102875
+ enableBash = false,
102876
+ bashConfig,
102877
+ architectureFileName,
102878
+ enableMcp = false,
102879
+ mcpConfig = null,
102880
+ mcpConfigPath = null,
102881
+ delegationManager = null,
102882
+ // Timeout settings inherited from parent agent
102883
+ timeoutBehavior,
102884
+ maxOperationTimeout,
102885
+ requestTimeout,
102886
+ gracefulTimeoutBonusSteps,
102887
+ negotiatedTimeoutBudget,
102888
+ negotiatedTimeoutMaxRequests,
102889
+ negotiatedTimeoutMaxPerRequest,
102890
+ parentOperationStartTime,
102891
+ onSubagentCreated,
102892
+ onSubagentCompleted
102893
+ } = options;
102023
102894
  return (0, import_ai5.tool)({
102024
102895
  name: "delegate",
102025
102896
  description: delegateDescription,
@@ -102063,9 +102934,30 @@ var init_vercel = __esm({
102063
102934
  console.error(`Using workspace root: ${effectivePath} (cwd was: ${cwd || "not set"})`);
102064
102935
  }
102065
102936
  }
102937
+ let effectiveTimeout = timeout;
102938
+ if (parentOperationStartTime && maxOperationTimeout) {
102939
+ const elapsed = Date.now() - parentOperationStartTime;
102940
+ const remaining = maxOperationTimeout - elapsed;
102941
+ const budgetCap = Math.max(30, Math.floor(remaining * 0.9 / 1e3));
102942
+ if (budgetCap < effectiveTimeout) {
102943
+ effectiveTimeout = budgetCap;
102944
+ if (debug) {
102945
+ console.error(`[DELEGATE] Capping timeout from ${timeout}s to ${effectiveTimeout}s (remaining parent budget: ${Math.floor(remaining / 1e3)}s)`);
102946
+ }
102947
+ if (tracer) {
102948
+ tracer.addEvent("delegation.budget_capped", {
102949
+ "delegation.original_timeout_s": timeout,
102950
+ "delegation.effective_timeout_s": effectiveTimeout,
102951
+ "delegation.parent_elapsed_ms": elapsed,
102952
+ "delegation.parent_remaining_ms": remaining,
102953
+ "delegation.parent_session_id": parentSessionId
102954
+ });
102955
+ }
102956
+ }
102957
+ }
102066
102958
  const result = await delegate({
102067
102959
  task,
102068
- timeout,
102960
+ timeout: effectiveTimeout,
102069
102961
  debug,
102070
102962
  currentIteration: currentIteration || 0,
102071
102963
  maxIterations: maxIterations || 30,
@@ -102084,7 +102976,14 @@ var init_vercel = __esm({
102084
102976
  mcpConfigPath,
102085
102977
  delegationManager,
102086
102978
  // Per-instance delegation limits
102087
- parentAbortSignal
102979
+ parentAbortSignal,
102980
+ // Inherit timeout settings for subagent
102981
+ timeoutBehavior,
102982
+ requestTimeout,
102983
+ gracefulTimeoutBonusSteps,
102984
+ // Subagent lifecycle callbacks for graceful stop coordination
102985
+ onSubagentCreated,
102986
+ onSubagentCompleted
102088
102987
  });
102089
102988
  return result;
102090
102989
  }
@@ -103563,6 +104462,121 @@ var init_file_lister = __esm({
103563
104462
  }
103564
104463
  });
103565
104464
 
104465
+ // src/agent/otelLogBridge.js
104466
+ function getOtelApi() {
104467
+ if (otelApiAttempted) return otelApi;
104468
+ otelApiAttempted = true;
104469
+ try {
104470
+ otelApi = (function(name15) {
104471
+ return _require(name15);
104472
+ })("@opentelemetry/api");
104473
+ } catch {
104474
+ }
104475
+ return otelApi;
104476
+ }
104477
+ function getOtelLogger() {
104478
+ if (otelLoggerAttempted) return otelLogger;
104479
+ otelLoggerAttempted = true;
104480
+ try {
104481
+ const { logs } = (function(name15) {
104482
+ return _require(name15);
104483
+ })("@opentelemetry/api-logs");
104484
+ otelLogger = logs.getLogger("probe-agent");
104485
+ } catch {
104486
+ }
104487
+ return otelLogger;
104488
+ }
104489
+ function getTraceSuffix() {
104490
+ try {
104491
+ const api2 = getOtelApi();
104492
+ if (!api2) return "";
104493
+ const span = api2.trace.getSpan(api2.context.active());
104494
+ const ctx = span?.spanContext?.();
104495
+ if (!ctx?.traceId) return "";
104496
+ return ` [trace_id=${ctx.traceId} span_id=${ctx.spanId}]`;
104497
+ } catch {
104498
+ return "";
104499
+ }
104500
+ }
104501
+ function emitOtelLog(msg, level) {
104502
+ try {
104503
+ const logger = getOtelLogger();
104504
+ if (!logger) return;
104505
+ const api2 = getOtelApi();
104506
+ let traceId, spanId;
104507
+ if (api2) {
104508
+ const span = api2.trace.getSpan(api2.context.active());
104509
+ const ctx = span?.spanContext?.();
104510
+ if (ctx?.traceId) {
104511
+ traceId = ctx.traceId;
104512
+ spanId = ctx.spanId;
104513
+ }
104514
+ }
104515
+ logger.emit({
104516
+ severityNumber: OTEL_SEVERITY[level] || 9,
104517
+ severityText: level.toUpperCase(),
104518
+ body: msg,
104519
+ attributes: {
104520
+ "probe.logger": true,
104521
+ ...traceId ? { trace_id: traceId, span_id: spanId } : {}
104522
+ }
104523
+ });
104524
+ } catch {
104525
+ }
104526
+ }
104527
+ function patchConsole() {
104528
+ if (patched) return;
104529
+ const methods = ["log", "info", "warn", "error"];
104530
+ const c = globalThis.console;
104531
+ for (const m of methods) {
104532
+ const orig = c[m].bind(c);
104533
+ originals[m] = orig;
104534
+ c[m] = (...args) => {
104535
+ const msgParts = args.map(
104536
+ (a) => typeof a === "string" ? a : a instanceof Error ? a.message : JSON.stringify(a)
104537
+ );
104538
+ const msg = msgParts.join(" ");
104539
+ emitOtelLog(msg, m === "log" ? "log" : m);
104540
+ const suffix = getTraceSuffix();
104541
+ if (suffix) {
104542
+ if (typeof args[0] === "string") {
104543
+ args[0] = args[0] + suffix;
104544
+ } else {
104545
+ args.push(suffix);
104546
+ }
104547
+ }
104548
+ return orig(...args);
104549
+ };
104550
+ }
104551
+ patched = true;
104552
+ }
104553
+ var import_module, _require, OTEL_SEVERITY, patched, originals, otelApi, otelApiAttempted, otelLogger, otelLoggerAttempted;
104554
+ var init_otelLogBridge = __esm({
104555
+ "src/agent/otelLogBridge.js"() {
104556
+ "use strict";
104557
+ import_module = require("module");
104558
+ _require = (0, import_module.createRequire)("file:///");
104559
+ OTEL_SEVERITY = {
104560
+ log: 9,
104561
+ // INFO
104562
+ info: 9,
104563
+ // INFO
104564
+ warn: 13,
104565
+ // WARN
104566
+ error: 17,
104567
+ // ERROR
104568
+ debug: 5
104569
+ // DEBUG
104570
+ };
104571
+ patched = false;
104572
+ originals = {};
104573
+ otelApi = null;
104574
+ otelApiAttempted = false;
104575
+ otelLogger = null;
104576
+ otelLoggerAttempted = false;
104577
+ }
104578
+ });
104579
+
103566
104580
  // src/agent/simpleTelemetry.js
103567
104581
  function initializeSimpleTelemetryFromOptions(options) {
103568
104582
  const telemetry = new SimpleTelemetry({
@@ -103571,6 +104585,7 @@ function initializeSimpleTelemetryFromOptions(options) {
103571
104585
  enableConsole: options.traceConsole,
103572
104586
  filePath: options.traceFile || "./traces.jsonl"
103573
104587
  });
104588
+ patchConsole();
103574
104589
  return telemetry;
103575
104590
  }
103576
104591
  var import_fs15, import_path18, SimpleTelemetry, SimpleAppTracer;
@@ -103579,6 +104594,7 @@ var init_simpleTelemetry = __esm({
103579
104594
  "use strict";
103580
104595
  import_fs15 = require("fs");
103581
104596
  import_path18 = require("path");
104597
+ init_otelLogBridge();
103582
104598
  SimpleTelemetry = class {
103583
104599
  constructor(options = {}) {
103584
104600
  this.serviceName = options.serviceName || "probe-agent";
@@ -104029,6 +105045,9 @@ var init_hooks = __esm({
104029
105045
  var index_exports = {};
104030
105046
  __export(index_exports, {
104031
105047
  DEFAULT_SYSTEM_MESSAGE: () => DEFAULT_SYSTEM_MESSAGE,
105048
+ ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
105049
+ ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
105050
+ ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
104032
105051
  FileTracker: () => FileTracker,
104033
105052
  HOOK_TYPES: () => HOOK_TYPES,
104034
105053
  HookManager: () => HookManager,
@@ -104115,6 +105134,9 @@ init_index();
104115
105134
  // Annotate the CommonJS export names for ESM import in node:
104116
105135
  0 && (module.exports = {
104117
105136
  DEFAULT_SYSTEM_MESSAGE,
105137
+ ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
105138
+ ENGINE_ACTIVITY_TIMEOUT_MAX,
105139
+ ENGINE_ACTIVITY_TIMEOUT_MIN,
104118
105140
  FileTracker,
104119
105141
  HOOK_TYPES,
104120
105142
  HookManager,