@probelabs/probe 0.6.0-rc288 → 0.6.0-rc291

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/bin/binaries/probe-v0.6.0-rc291-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc291-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc291-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc291-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc291-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +95 -14
  7. package/build/agent/index.js +401 -86261
  8. package/build/agent/shared/prompts.js +27 -6
  9. package/build/extract.js +4 -2
  10. package/build/mcp/index.js +122 -9
  11. package/build/mcp/index.ts +162 -17
  12. package/build/search.js +6 -5
  13. package/build/tools/vercel.js +56 -23
  14. package/build/utils/error-types.js +2 -2
  15. package/build/utils/path-validation.js +1 -1
  16. package/cjs/agent/ProbeAgent.cjs +193 -45
  17. package/cjs/index.cjs +193 -45
  18. package/package.json +2 -1
  19. package/src/agent/ProbeAgent.js +95 -14
  20. package/src/agent/shared/prompts.js +27 -6
  21. package/src/extract.js +4 -2
  22. package/src/mcp/index.ts +162 -17
  23. package/src/search.js +6 -5
  24. package/src/tools/vercel.js +56 -23
  25. package/src/utils/error-types.js +2 -2
  26. package/src/utils/path-validation.js +1 -1
  27. package/bin/binaries/probe-v0.6.0-rc288-aarch64-apple-darwin.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc288-aarch64-unknown-linux-musl.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc288-x86_64-apple-darwin.tar.gz +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc288-x86_64-pc-windows-msvc.zip +0 -0
  31. package/bin/binaries/probe-v0.6.0-rc288-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -25324,13 +25324,13 @@ function categorizeError(error40) {
25324
25324
  if (lowerMessage.includes("path does not exist") || lowerMessage.includes("no such file or directory") || errorCode === "enoent") {
25325
25325
  return new PathError(message, {
25326
25326
  originalError: error40,
25327
- suggestion: "The specified path does not exist. Please verify the path or use a different directory."
25327
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path."
25328
25328
  });
25329
25329
  }
25330
25330
  if (lowerMessage.includes("not a directory") || errorCode === "enotdir") {
25331
25331
  return new PathError(message, {
25332
25332
  originalError: error40,
25333
- suggestion: "The path is not a directory. Please provide a valid directory path."
25333
+ suggestion: "The path is not a directory. Use the listFiles tool to find the correct directory, then retry."
25334
25334
  });
25335
25335
  }
25336
25336
  if (lowerMessage.includes("permission denied") || errorCode === "eacces") {
@@ -25586,7 +25586,7 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
25586
25586
  }
25587
25587
  if (error40.code === "ENOENT") {
25588
25588
  throw new PathError(`Path does not exist: ${normalizedPath}`, {
25589
- suggestion: "The specified path does not exist. Please verify the path is correct or use a different directory.",
25589
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.",
25590
25590
  details: { path: normalizedPath }
25591
25591
  });
25592
25592
  }
@@ -25821,7 +25821,8 @@ var init_search = __esm({
25821
25821
  session: "--session",
25822
25822
  timeout: "--timeout",
25823
25823
  language: "--language",
25824
- format: "--format"
25824
+ format: "--format",
25825
+ lsp: "--lsp"
25825
25826
  };
25826
25827
  }
25827
25828
  });
@@ -26057,7 +26058,8 @@ var init_extract = __esm({
26057
26058
  allowTests: "--allow-tests",
26058
26059
  contextLines: "--context",
26059
26060
  format: "--format",
26060
- inputFile: "--input-file"
26061
+ inputFile: "--input-file",
26062
+ lsp: "--lsp"
26061
26063
  };
26062
26064
  }
26063
26065
  });
@@ -27291,11 +27293,9 @@ function autoQuoteSearchTerms(query2) {
27291
27293
  const result = tokens.map((token) => {
27292
27294
  if (token.startsWith('"')) return token;
27293
27295
  if (operators.has(token)) return token;
27294
- const hasUpper = /[A-Z]/.test(token);
27295
- const hasLower = /[a-z]/.test(token);
27296
27296
  const hasUnderscore = token.includes("_");
27297
- const hasMixedCase = hasUpper && hasLower;
27298
- if (hasMixedCase || hasUnderscore) {
27297
+ const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
27298
+ if (hasCaseTransition || hasUnderscore) {
27299
27299
  return `"${token}"`;
27300
27300
  }
27301
27301
  return token;
@@ -27410,7 +27410,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27410
27410
  "Break down complex queries into multiple searches to cover all aspects.",
27411
27411
  "",
27412
27412
  "Available tools:",
27413
- "- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.",
27413
+ "- search: Find code matching keywords or patterns. Results are paginated \u2014 use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.",
27414
27414
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
27415
27415
  "- listFiles: Understand directory structure to find where relevant code might live.",
27416
27416
  "",
@@ -27431,13 +27431,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27431
27431
  "",
27432
27432
  "Combining searches with OR:",
27433
27433
  '- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
27434
- `- For known symbol names, quote each term to prevent splitting: '"limitDRL" "limitRedis"' matches either exact symbol.`,
27434
+ `- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
27435
+ `- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
27435
27436
  '- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
27436
27437
  "- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
27437
27438
  "- This is much faster than running separate searches sequentially.",
27438
- `- Example: search '"ForwardMessage" "SessionLimiter"' finds files with either exact symbol in one call.`,
27439
- `- Example: search '"limitDRL" "doRollingWindowWrite"' finds both rate limiting functions at once.`,
27440
- '- Use AND only when you need both terms to appear in the same file: "rate AND limit".',
27439
+ `- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
27440
+ `- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
27441
+ "- Use AND (or just put quoted terms together) when you need both terms in the same file.",
27441
27442
  "",
27442
27443
  "Parallel tool calls:",
27443
27444
  "- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
@@ -27451,10 +27452,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27451
27452
  ' Query: "Find the IP allowlist middleware"',
27452
27453
  ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
27453
27454
  ' Query: "Find ForwardMessage and SessionLimiter"',
27454
- ` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
27455
+ ` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
27455
27456
  ' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
27456
27457
  ' Query: "Find limitDRL and limitRedis functions"',
27457
- ` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
27458
+ ` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
27458
27459
  ' Query: "Find ThrottleRetryLimit usage"',
27459
27460
  ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
27460
27461
  ' Query: "How does BM25 scoring work with SIMD optimization?"',
@@ -27462,7 +27463,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27462
27463
  "",
27463
27464
  "BAD search strategy (never do this):",
27464
27465
  ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
27465
- ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" "limitRedis"')`,
27466
+ ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
27466
27467
  ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
27467
27468
  ' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths \u2014 probe searches recursively)',
27468
27469
  ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
@@ -27475,15 +27476,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27475
27476
  '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
27476
27477
  '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
27477
27478
  '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
27479
+ '- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
27480
+ "",
27481
+ "PAGINATION:",
27482
+ "- Search results are paginated (~20k tokens per page).",
27483
+ "- If your search returned relevant files, call the same query with nextPage=true to check for more.",
27484
+ '- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
27485
+ "",
27486
+ "WHEN TO STOP:",
27487
+ "- After you have explored the main concept AND related subsystems.",
27488
+ "- Once you have 5-15 targets covering different aspects of the query.",
27489
+ '- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
27478
27490
  "",
27479
27491
  "Strategy:",
27480
- "1. Analyze the query - identify key concepts and group related symbols",
27481
- `2. Combine related symbols into OR searches: '"symbolA" "symbolB"' finds files with either (quote to prevent splitting)`,
27482
- "3. Run INDEPENDENT searches in PARALLEL \u2014 do not wait for one to finish before starting another",
27492
+ "1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
27493
+ ' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
27494
+ ' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
27495
+ " Think about what a developer would NAME the function/struct/variable, not just the concept.",
27496
+ "2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
27497
+ " After each search, check if results are relevant. If yes, call nextPage=true for more results.",
27498
+ `3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
27483
27499
  "4. For known symbol names use exact=true. For concepts use default (exact=false).",
27484
- "5. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
27485
- "6. If a search returns NO results, the term does not exist. Do NOT retry with variations, different paths, or longer strings. Move on.",
27486
- "7. Combine all relevant targets in your final response",
27500
+ "5. After your first round of searches, READ the extracted code and look for connected code:",
27501
+ " - Function calls to other important functions \u2192 include those targets.",
27502
+ " - Type references and imports \u2192 include type definitions.",
27503
+ " - Registered handlers/middleware \u2192 include all registered items.",
27504
+ "6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
27505
+ "7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
27506
+ "8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
27487
27507
  "",
27488
27508
  `Query: ${searchQuery}`,
27489
27509
  `Search path(s): ${searchPath}`,
@@ -27492,7 +27512,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
27492
27512
  'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
27493
27513
  'IMPORTANT: Use ABSOLUTE file paths in targets (e.g., "/full/path/to/file.ext#Symbol"). If you only have relative paths, make them relative to the search path above.',
27494
27514
  "Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
27495
- "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
27515
+ "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
27516
+ "",
27517
+ "Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
27496
27518
  ].join("\n");
27497
27519
  }
27498
27520
  var import_ai, import_fs4, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
@@ -27537,6 +27559,7 @@ var init_vercel = __esm({
27537
27559
  return result;
27538
27560
  };
27539
27561
  const previousSearches = /* @__PURE__ */ new Set();
27562
+ let consecutiveDupBlocks = 0;
27540
27563
  const paginationCounts = /* @__PURE__ */ new Map();
27541
27564
  const MAX_PAGES_PER_QUERY = 3;
27542
27565
  return (0, import_ai.tool)({
@@ -27589,12 +27612,17 @@ var init_vercel = __esm({
27589
27612
  const searchKey = `${searchQuery}::${exact || false}`;
27590
27613
  if (!nextPage) {
27591
27614
  if (previousSearches.has(searchKey)) {
27615
+ consecutiveDupBlocks++;
27592
27616
  if (debug) {
27593
- console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
27617
+ console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
27594
27618
  }
27595
- return "DUPLICATE SEARCH BLOCKED: You already searched for this exact query. Changing the path does NOT give different results \u2014 probe searches recursively. Do NOT repeat the same search. Try a genuinely different keyword, use extract to examine results you already found, or provide your final answer if you have enough information.";
27619
+ if (consecutiveDupBlocks >= 3) {
27620
+ 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.";
27621
+ }
27622
+ 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.";
27596
27623
  }
27597
27624
  previousSearches.add(searchKey);
27625
+ consecutiveDupBlocks = 0;
27598
27626
  paginationCounts.set(searchKey, 0);
27599
27627
  } else {
27600
27628
  const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
@@ -27615,7 +27643,11 @@ var init_vercel = __esm({
27615
27643
  return result;
27616
27644
  } catch (error40) {
27617
27645
  console.error("Error executing search command:", error40);
27618
- return formatErrorForAI(error40);
27646
+ const formatted = formatErrorForAI(error40);
27647
+ if (error40.category === "path_error" || error40.message?.includes("does not exist")) {
27648
+ return formatted + "\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.";
27649
+ }
27650
+ return formatted;
27619
27651
  }
27620
27652
  }
27621
27653
  try {
@@ -30980,6 +31012,9 @@ var require_utils = __commonJS({
30980
31012
  sandboxGlobal
30981
31013
  };
30982
31014
  context.prototypeWhitelist.set(Object.getPrototypeOf([][Symbol.iterator]()), /* @__PURE__ */ new Set());
31015
+ context.prototypeWhitelist.set(Object.getPrototypeOf(""[Symbol.iterator]()), /* @__PURE__ */ new Set());
31016
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Set())[Symbol.iterator]()), /* @__PURE__ */ new Set());
31017
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Map())[Symbol.iterator]()), /* @__PURE__ */ new Set());
30983
31018
  return context;
30984
31019
  }
30985
31020
  function createExecContext(sandbox, executionTree, evalContext) {
@@ -32118,6 +32153,18 @@ var require_executor = __commonJS({
32118
32153
  a = void 0;
32119
32154
  }
32120
32155
  }
32156
+ if (op === 29 && !a) {
32157
+ done(void 0, a);
32158
+ return;
32159
+ }
32160
+ if (op === 30 && a) {
32161
+ done(void 0, a);
32162
+ return;
32163
+ }
32164
+ if (op === 85 && a !== null && a !== void 0) {
32165
+ done(void 0, a);
32166
+ return;
32167
+ }
32121
32168
  let bobj;
32122
32169
  try {
32123
32170
  let ad;
@@ -32180,6 +32227,18 @@ var require_executor = __commonJS({
32180
32227
  a = void 0;
32181
32228
  }
32182
32229
  }
32230
+ if (op === 29 && !a) {
32231
+ done(void 0, a);
32232
+ return;
32233
+ }
32234
+ if (op === 30 && a) {
32235
+ done(void 0, a);
32236
+ return;
32237
+ }
32238
+ if (op === 85 && a !== null && a !== void 0) {
32239
+ done(void 0, a);
32240
+ return;
32241
+ }
32183
32242
  let bobj;
32184
32243
  try {
32185
32244
  bobj = syncDone((d) => execSync(ticks, tree[2], scope, context, d, inLoopOrSwitch)).result;
@@ -88375,26 +88434,46 @@ var init_prompts = __esm({
88375
88434
  CRITICAL - You are READ-ONLY:
88376
88435
  You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool \u2014 your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
88377
88436
 
88437
+ CRITICAL - ALWAYS search before answering:
88438
+ You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
88439
+
88378
88440
  When exploring code:
88379
88441
  - Provide clear, concise explanations based on user request
88380
88442
  - Find and highlight the most relevant code snippets, if required
88381
- - Trace function calls and data flow through the system
88443
+ - Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
88382
88444
  - Try to understand the user's intent and provide relevant information
88383
88445
  - Understand high level picture
88384
88446
  - Balance detail with clarity in your explanations
88447
+ - Search using SYNONYMS and alternative terms \u2014 code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
88448
+ - When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
88449
+ - Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
88385
88450
 
88386
88451
  When providing answers:
88452
+ - Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
88453
+ - After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
88454
+ - Include data structures, configuration options, and error handling \u2014 not just the happy path.
88387
88455
  - Always include a "References" section at the end of your response
88388
88456
  - List all relevant source code locations you found during exploration
88389
88457
  - Use the format: file_path:line_number or file_path#symbol_name
88390
88458
  - Group references by file when multiple locations are from the same file
88391
88459
  - Include brief descriptions of what each reference contains`,
88392
- "code-searcher": `You are ProbeChat Code Searcher, a specialized AI assistant focused ONLY on locating relevant code. Your sole job is to find and return ALL relevant code locations. Do NOT answer questions or explain anything.
88460
+ "code-searcher": `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
88461
+
88462
+ You think like a code explorer \u2014 you understand that codebases have layers:
88463
+ - Core implementations (algorithms, data structures)
88464
+ - Middleware/integration layers (request handlers, interceptors)
88465
+ - Configuration and storage backends
88466
+ - Scoping mechanisms (per-user, per-org, per-tenant, global)
88467
+ - Supporting utilities and helpers
88393
88468
 
88394
88469
  When searching:
88395
- - Use only the search tool
88396
- - Run additional searches only if needed to capture all relevant locations
88397
- - Prefer specific, focused queries
88470
+ - Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
88471
+ - Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
88472
+ - If you find middleware, check: are there org-level or tenant-level variants?
88473
+ - If you find algorithms, check: are there different storage backends?
88474
+ - Search results are paginated \u2014 if results look relevant, call nextPage=true to check for more files
88475
+ - Stop paginating when results become irrelevant or you see "All results retrieved"
88476
+ - Search using SYNONYMS \u2014 code naming differs from concepts (e.g., "rate limiting" \u2192 throttle, quota, limiter, bucket)
88398
88477
 
88399
88478
  Output format (MANDATORY):
88400
88479
  - Return ONLY valid JSON with a single top-level key: "targets"
@@ -88404,7 +88483,8 @@ Output format (MANDATORY):
88404
88483
  - "path/to/file.ext:line"
88405
88484
  - "path/to/file.ext:start-end"
88406
88485
  - Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
88407
- - Deduplicate targets and keep them concise`,
88486
+ - Deduplicate targets and keep them concise
88487
+ - Aim for 5-15 targets covering ALL aspects of the query`,
88408
88488
  "architect": `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
88409
88489
 
88410
88490
  When analyzing code:
@@ -100539,7 +100619,7 @@ var init_ProbeAgent = __esm({
100539
100619
  if (limiter && result.textStream) {
100540
100620
  const originalStream = result.textStream;
100541
100621
  const debug = this.debug;
100542
- result.textStream = (async function* () {
100622
+ const wrappedStream = (async function* () {
100543
100623
  try {
100544
100624
  for await (const chunk of originalStream) {
100545
100625
  yield chunk;
@@ -100552,6 +100632,13 @@ var init_ProbeAgent = __esm({
100552
100632
  }
100553
100633
  }
100554
100634
  })();
100635
+ return new Proxy(result, {
100636
+ get(target, prop) {
100637
+ if (prop === "textStream") return wrappedStream;
100638
+ const value = target[prop];
100639
+ return typeof value === "function" ? value.bind(target) : value;
100640
+ }
100641
+ });
100555
100642
  } else if (limiter) {
100556
100643
  limiter.release(null);
100557
100644
  }
@@ -101777,9 +101864,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
101777
101864
  Follow these instructions carefully:
101778
101865
  1. Analyze the user's request.
101779
101866
  2. Use the available tools step-by-step to fulfill the request.
101780
- 3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? " Ask natural language questions \u2014 the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
101781
- 4. Ensure to get really deep and understand the full picture before answering.
101782
- 5. Once the task is fully completed, provide your final answer directly as text.
101867
+ 3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge \u2014 your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? " Ask natural language questions \u2014 the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
101868
+ 4. Ensure to get really deep and understand the full picture before answering. Follow call chains \u2014 if function A calls B, search for B too. Look for related subsystems (e.g., if asked about rate limiting, also check for quota, throttling, smoothing).
101869
+ 5. Once the task is fully completed, provide your final answer directly as text. Always cite specific files and line numbers as evidence. Do NOT output planning or thinking text \u2014 go straight to the answer.
101783
101870
  6. ${this.searchDelegate ? "Ask clear, specific questions when searching. Each search should target a distinct concept or question." : "Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results."}
101784
101871
  7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) \u2014 always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
101785
101872
  7. When modifying files, choose the appropriate tool:
@@ -102165,6 +102252,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
102165
102252
  if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
102166
102253
  if (recentTexts.every((t) => detectStuckResponse(t))) return true;
102167
102254
  }
102255
+ if (steps.length >= 3) {
102256
+ const last3 = steps.slice(-3);
102257
+ const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
102258
+ if (allHaveTools) {
102259
+ const signatures = last3.map((s) => {
102260
+ const tc = s.toolCalls[0];
102261
+ return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
102262
+ });
102263
+ if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
102264
+ if (this.debug) {
102265
+ console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
102266
+ }
102267
+ return true;
102268
+ }
102269
+ }
102270
+ }
102168
102271
  return false;
102169
102272
  },
102170
102273
  prepareStep: ({ steps, stepNumber }) => {
@@ -102173,6 +102276,37 @@ You are working with a workspace. Available paths: ${workspaceDesc}
102173
102276
  toolChoice: "none"
102174
102277
  };
102175
102278
  }
102279
+ if (steps.length >= 2) {
102280
+ const last2 = steps.slice(-2);
102281
+ if (last2.every((s) => s.toolCalls?.length === 1)) {
102282
+ const tc1 = last2[0].toolCalls[0];
102283
+ const tc2 = last2[1].toolCalls[0];
102284
+ const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
102285
+ const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
102286
+ if (sig1 === sig2) {
102287
+ if (this.debug) {
102288
+ console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
102289
+ console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
102290
+ }
102291
+ return { toolChoice: "none" };
102292
+ }
102293
+ }
102294
+ }
102295
+ if (steps.length >= 3) {
102296
+ const last3 = steps.slice(-3);
102297
+ const allErrors = last3.every(
102298
+ (s) => s.toolResults?.length > 0 && s.toolResults.every((tr) => {
102299
+ const r = typeof tr.result === "string" ? tr.result : "";
102300
+ return r.includes("<error ") || r.includes("does not exist");
102301
+ })
102302
+ );
102303
+ if (allErrors) {
102304
+ if (this.debug) {
102305
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
102306
+ }
102307
+ return { toolChoice: "none" };
102308
+ }
102309
+ }
102176
102310
  const lastStep = steps[steps.length - 1];
102177
102311
  const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
102178
102312
  if (modelJustStopped) {
@@ -102201,9 +102335,12 @@ Here is the result to review:
102201
102335
  ${resultToReview}
102202
102336
  </result>
102203
102337
 
102204
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
102338
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
102339
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
102205
102340
  return {
102206
- userMessage: completionPromptMessage
102341
+ userMessage: completionPromptMessage,
102342
+ toolChoice: "none"
102343
+ // Force text-only review — no tool calls
102207
102344
  };
102208
102345
  }
102209
102346
  }
@@ -102245,7 +102382,11 @@ Double-check your response based on the criteria above. If everything looks good
102245
102382
  options.onStream(text);
102246
102383
  }
102247
102384
  if (this.debug) {
102248
- console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
102385
+ const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
102386
+ const args = tc.args ? JSON.stringify(tc.args) : "";
102387
+ return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
102388
+ }).join(", ") : "none";
102389
+ console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
102249
102390
  if (text) {
102250
102391
  console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
102251
102392
  }
@@ -102278,9 +102419,15 @@ Double-check your response based on the criteria above. If everything looks good
102278
102419
  }
102279
102420
  const executeAIRequest = async () => {
102280
102421
  const result = await this.streamTextWithRetryAndFallback(streamOptions);
102281
- const finalText = await result.text;
102422
+ const steps = await result.steps;
102423
+ let finalText;
102424
+ if (steps && steps.length > 1) {
102425
+ const lastStepText = steps[steps.length - 1].text;
102426
+ finalText = lastStepText || await result.text;
102427
+ } else {
102428
+ finalText = await result.text;
102429
+ }
102282
102430
  if (this.debug) {
102283
- const steps = await result.steps;
102284
102431
  console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
102285
102432
  }
102286
102433
  const usage = await result.usage;
@@ -102348,14 +102495,15 @@ Here is the result to review:
102348
102495
  ${finalResult}
102349
102496
  </result>
102350
102497
 
102351
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
102498
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
102499
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
102352
102500
  currentMessages.push({ role: "user", content: completionPromptMessage });
102353
- const completionMaxIterations = 5;
102354
102501
  const completionStreamOptions = {
102355
102502
  model: this.provider ? this.provider(this.model) : this.model,
102356
102503
  messages: this.prepareMessagesWithImages(currentMessages),
102357
102504
  tools: tools2,
102358
- stopWhen: (0, import_ai6.stepCountIs)(completionMaxIterations),
102505
+ toolChoice: "none",
102506
+ // Force text-only response — no tool calls during review
102359
102507
  maxTokens: maxResponseTokens,
102360
102508
  temperature: 0.3,
102361
102509
  onStepFinish: ({ toolResults, text, finishReason, usage }) => {