@probelabs/probe 0.6.0-rc307 → 0.6.0-rc309

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 (30) hide show
  1. package/README.md +25 -0
  2. package/bin/binaries/{probe-v0.6.0-rc307-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc309-aarch64-apple-darwin.tar.gz} +0 -0
  3. package/bin/binaries/{probe-v0.6.0-rc307-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc309-aarch64-unknown-linux-musl.tar.gz} +0 -0
  4. package/bin/binaries/{probe-v0.6.0-rc307-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc309-x86_64-apple-darwin.tar.gz} +0 -0
  5. package/bin/binaries/{probe-v0.6.0-rc307-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc309-x86_64-pc-windows-msvc.zip} +0 -0
  6. package/bin/binaries/{probe-v0.6.0-rc307-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc309-x86_64-unknown-linux-musl.tar.gz} +0 -0
  7. package/build/agent/ProbeAgent.js +17 -4
  8. package/build/agent/probeTool.js +9 -0
  9. package/build/agent/shared/prompts.js +4 -1
  10. package/build/agent/tools.js +6 -0
  11. package/build/index.js +6 -1
  12. package/build/symbols.js +86 -0
  13. package/build/tools/common.js +4 -0
  14. package/build/tools/index.js +2 -1
  15. package/build/tools/system-message.js +4 -0
  16. package/build/tools/vercel.js +36 -1
  17. package/cjs/agent/ProbeAgent.cjs +180 -40
  18. package/cjs/index.cjs +188 -40
  19. package/index.d.ts +63 -0
  20. package/package.json +1 -1
  21. package/src/agent/ProbeAgent.js +17 -4
  22. package/src/agent/probeTool.js +9 -0
  23. package/src/agent/shared/prompts.js +4 -1
  24. package/src/agent/tools.js +6 -0
  25. package/src/index.js +6 -1
  26. package/src/symbols.js +86 -0
  27. package/src/tools/common.js +4 -0
  28. package/src/tools/index.js +2 -1
  29. package/src/tools/system-message.js +4 -0
  30. package/src/tools/vercel.js +36 -1
package/cjs/index.cjs CHANGED
@@ -2187,6 +2187,70 @@ var init_extract = __esm({
2187
2187
  }
2188
2188
  });
2189
2189
 
2190
+ // src/symbols.js
2191
+ async function symbols(options) {
2192
+ if (!options) {
2193
+ throw new Error("Options object is required");
2194
+ }
2195
+ if (!options.files || !Array.isArray(options.files) || options.files.length === 0) {
2196
+ throw new Error("At least one file path is required");
2197
+ }
2198
+ const binaryPath = await getBinaryPath(options.binaryOptions || {});
2199
+ const cwd = await validateCwdPath(options.cwd);
2200
+ const args = ["symbols", "--format", "json"];
2201
+ if (options.allowTests) {
2202
+ args.push("--allow-tests");
2203
+ }
2204
+ for (const file2 of options.files) {
2205
+ args.push(escapeString(file2));
2206
+ }
2207
+ if (process.env.DEBUG === "1") {
2208
+ console.error(`
2209
+ Symbols: files="${options.files.join(", ")}" cwd="${cwd}"`);
2210
+ }
2211
+ return new Promise((resolve9, reject2) => {
2212
+ const childProcess = (0, import_child_process5.spawn)(binaryPath, args, {
2213
+ stdio: ["pipe", "pipe", "pipe"],
2214
+ cwd
2215
+ });
2216
+ let stdout = "";
2217
+ let stderr = "";
2218
+ childProcess.stdout.on("data", (data2) => {
2219
+ stdout += data2.toString();
2220
+ });
2221
+ childProcess.stderr.on("data", (data2) => {
2222
+ stderr += data2.toString();
2223
+ });
2224
+ childProcess.on("close", (code) => {
2225
+ if (stderr && process.env.DEBUG === "1") {
2226
+ console.error(`stderr: ${stderr}`);
2227
+ }
2228
+ if (code !== 0) {
2229
+ reject2(new Error(`Symbols command failed with exit code ${code}: ${stderr}`));
2230
+ return;
2231
+ }
2232
+ try {
2233
+ const result = JSON.parse(stdout);
2234
+ resolve9(result);
2235
+ } catch (error40) {
2236
+ reject2(new Error(`Failed to parse symbols output: ${error40.message}. Output: ${stdout.substring(0, 200)}`));
2237
+ }
2238
+ });
2239
+ childProcess.on("error", (error40) => {
2240
+ reject2(new Error(`Failed to spawn symbols process: ${error40.message}`));
2241
+ });
2242
+ });
2243
+ }
2244
+ var import_child_process5;
2245
+ var init_symbols = __esm({
2246
+ "src/symbols.js"() {
2247
+ "use strict";
2248
+ import_child_process5 = require("child_process");
2249
+ init_utils();
2250
+ init_path_validation();
2251
+ }
2252
+ });
2253
+
2190
2254
  // src/grep.js
2191
2255
  async function grep(options) {
2192
2256
  if (!options || !options.pattern) {
@@ -2230,14 +2294,14 @@ async function grep(options) {
2230
2294
  throw new Error(`Grep failed: ${errorMessage}`);
2231
2295
  }
2232
2296
  }
2233
- var import_child_process5, import_util5, execFileAsync2, GREP_FLAG_MAP;
2297
+ var import_child_process6, import_util5, execFileAsync2, GREP_FLAG_MAP;
2234
2298
  var init_grep = __esm({
2235
2299
  "src/grep.js"() {
2236
2300
  "use strict";
2237
- import_child_process5 = require("child_process");
2301
+ import_child_process6 = require("child_process");
2238
2302
  import_util5 = require("util");
2239
2303
  init_utils();
2240
- execFileAsync2 = (0, import_util5.promisify)(import_child_process5.execFile);
2304
+ execFileAsync2 = (0, import_util5.promisify)(import_child_process6.execFile);
2241
2305
  GREP_FLAG_MAP = {
2242
2306
  ignoreCase: "-i",
2243
2307
  lineNumbers: "-n",
@@ -27645,6 +27709,9 @@ function createTools(configOptions) {
27645
27709
  if (isToolAllowed("extract")) {
27646
27710
  tools2.extractTool = extractTool(configOptions);
27647
27711
  }
27712
+ if (isToolAllowed("symbols")) {
27713
+ tools2.symbolsTool = symbolsTool(configOptions);
27714
+ }
27648
27715
  if (configOptions.enableDelegate && isToolAllowed("delegate")) {
27649
27716
  tools2.delegateTool = delegateTool(configOptions);
27650
27717
  }
@@ -27813,7 +27880,7 @@ function resolveTargetPath(target, cwd) {
27813
27880
  }
27814
27881
  return filePart + suffix;
27815
27882
  }
27816
- var import_path6, searchDelegateSchema, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, readMediaSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
27883
+ var import_path6, searchDelegateSchema, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, readMediaSchema, symbolsSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
27817
27884
  var init_common = __esm({
27818
27885
  "src/tools/common.js"() {
27819
27886
  "use strict";
@@ -27872,6 +27939,9 @@ var init_common = __esm({
27872
27939
  readMediaSchema = external_exports2.object({
27873
27940
  path: external_exports2.string().describe("Path to the media file to read. Supports images (png, jpg, jpeg, webp, bmp, svg) and documents (pdf).")
27874
27941
  });
27942
+ symbolsSchema = external_exports2.object({
27943
+ file: external_exports2.string().describe("Path to the file to list symbols from. Returns a hierarchical tree of functions, classes, structs, constants, etc. with line numbers and nesting (e.g., methods inside classes/impl blocks).")
27944
+ });
27875
27945
  bashSchema = external_exports2.object({
27876
27946
  command: external_exports2.string().describe("The bash command to execute"),
27877
27947
  workingDirectory: external_exports2.string().optional().describe("Directory to execute the command in (optional)"),
@@ -35281,14 +35351,21 @@ function createWrappedTools(baseTools) {
35281
35351
  baseTools.multiEditTool.execute
35282
35352
  );
35283
35353
  }
35354
+ if (baseTools.symbolsTool) {
35355
+ wrappedTools.symbolsToolInstance = wrapToolWithEmitter(
35356
+ baseTools.symbolsTool,
35357
+ "symbols",
35358
+ baseTools.symbolsTool.execute
35359
+ );
35360
+ }
35284
35361
  return wrappedTools;
35285
35362
  }
35286
- var import_child_process6, import_util13, import_crypto3, import_events, import_fs6, import_fs7, import_path8, toolCallEmitter, activeToolExecutions, wrapToolWithEmitter, listFilesTool, searchFilesTool, listFilesToolInstance, searchFilesToolInstance;
35363
+ var import_child_process7, import_util13, import_crypto3, import_events, import_fs6, import_fs7, import_path8, toolCallEmitter, activeToolExecutions, wrapToolWithEmitter, listFilesTool, searchFilesTool, listFilesToolInstance, searchFilesToolInstance;
35287
35364
  var init_probeTool = __esm({
35288
35365
  "src/agent/probeTool.js"() {
35289
35366
  "use strict";
35290
35367
  init_index();
35291
- import_child_process6 = require("child_process");
35368
+ import_child_process7 = require("child_process");
35292
35369
  import_util13 = require("util");
35293
35370
  import_crypto3 = require("crypto");
35294
35371
  import_events = require("events");
@@ -73938,6 +74015,7 @@ CRITICAL - ALWAYS search before answering:
73938
74015
  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.
73939
74016
 
73940
74017
  When exploring code:
74018
+ - Use the symbols tool to get a quick overview of a file's structure (functions, classes, constants) before diving into details with extract
73941
74019
  - Provide clear, concise explanations based on user request
73942
74020
  - Find and highlight the most relevant code snippets, if required
73943
74021
  - Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
@@ -73968,6 +74046,7 @@ You think like a code explorer \u2014 you understand that codebases have layers:
73968
74046
 
73969
74047
  When searching:
73970
74048
  - Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
74049
+ - Use symbols to get a file's table of contents (functions, classes, constants with line numbers) before extracting \u2014 it helps you pick the right symbols to extract
73971
74050
  - Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
73972
74051
  - If you find middleware, check: are there org-level or tenant-level variants?
73973
74052
  - If you find algorithms, check: are there different storage backends?
@@ -74011,6 +74090,7 @@ If the solution is clear, you can jump to implementation right away. If not, ask
74011
74090
  - Do not add code comments unless the logic is genuinely complex and non-obvious.
74012
74091
 
74013
74092
  # Before Implementation
74093
+ - Use symbols to get a quick overview of file structure (functions, classes, constants with line numbers) before reading or editing files.
74014
74094
  - Read tests first \u2014 find existing test files for the module you're changing. They reveal expected behavior, edge cases, and the project's testing patterns.
74015
74095
  - Read neighboring files \u2014 understand naming conventions, error handling patterns, import style, and existing utilities before creating new ones.
74016
74096
  - Trace the call chain \u2014 follow how the code you're changing is called and what depends on it. Check interfaces, types, and consumers.
@@ -74047,7 +74127,7 @@ Use the right tool:
74047
74127
  1. To MODIFY existing code \u2192 \`edit\` tool (old_string \u2192 new_string, or start_line/end_line)
74048
74128
  2. To CREATE a new file \u2192 \`create\` tool
74049
74129
  3. To CHANGE multiple files at once \u2192 \`multi_edit\` tool
74050
- 4. To READ code \u2192 \`extract\` or \`search\` tools
74130
+ 4. To READ code \u2192 \`extract\` or \`search\` tools. Use \`symbols\` to get a file's table of contents (functions, classes, constants with line numbers) before extracting.
74051
74131
  5. If \`edit\` fails with "file has not been read yet" \u2192 use \`extract\` with the EXACT same file path you will pass to \`edit\`. Relative vs absolute path mismatch causes this error. Use the same path format consistently. If it still fails, use bash \`cat\` to read the file, then use \`create\` to write the entire modified file. Do NOT fall back to sed.
74052
74132
 
74053
74133
  Bash is fine for: formatters (gofmt, prettier, black), build/test/lint commands, git operations, and read-only file inspection (cat, head, tail). sed/awk should ONLY be used for trivial non-code tasks (e.g., config file tweaks) where the replacement is a simple literal string swap.
@@ -76501,6 +76581,7 @@ var require_stringify = __commonJS({
76501
76581
  nullStr: "null",
76502
76582
  simpleKeys: false,
76503
76583
  singleQuote: null,
76584
+ trailingComma: false,
76504
76585
  trueStr: "true",
76505
76586
  verifyAliasOrder: true
76506
76587
  }, doc.schema.toStringOptions, options);
@@ -77018,12 +77099,19 @@ ${indent}${line}` : "\n";
77018
77099
  if (comment)
77019
77100
  reqNewline = true;
77020
77101
  let str = stringify.stringify(item, itemCtx, () => comment = null);
77021
- if (i < items.length - 1)
77102
+ reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
77103
+ if (i < items.length - 1) {
77022
77104
  str += ",";
77105
+ } else if (ctx.options.trailingComma) {
77106
+ if (ctx.options.lineWidth > 0) {
77107
+ reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
77108
+ }
77109
+ if (reqNewline) {
77110
+ str += ",";
77111
+ }
77112
+ }
77023
77113
  if (comment)
77024
77114
  str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
77025
- if (!reqNewline && (lines.length > linesAtValue || str.includes("\n")))
77026
- reqNewline = true;
77027
77115
  lines.push(str);
77028
77116
  linesAtValue = lines.length;
77029
77117
  }
@@ -80037,17 +80125,22 @@ var require_compose_node = __commonJS({
80037
80125
  case "block-map":
80038
80126
  case "block-seq":
80039
80127
  case "flow-collection":
80040
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
80041
- if (anchor)
80042
- node.anchor = anchor.source.substring(1);
80128
+ try {
80129
+ node = composeCollection.composeCollection(CN, ctx, token, props, onError);
80130
+ if (anchor)
80131
+ node.anchor = anchor.source.substring(1);
80132
+ } catch (error40) {
80133
+ const message = error40 instanceof Error ? error40.message : String(error40);
80134
+ onError(token, "RESOURCE_EXHAUSTION", message);
80135
+ }
80043
80136
  break;
80044
80137
  default: {
80045
80138
  const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
80046
80139
  onError(token, "UNEXPECTED_TOKEN", message);
80047
- node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError);
80048
80140
  isSrcToken = false;
80049
80141
  }
80050
80142
  }
80143
+ node ?? (node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError));
80051
80144
  if (anchor && node.anchor === "")
80052
80145
  onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
80053
80146
  if (atKey && ctx.options.stringKeys && (!identity2.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
@@ -95036,7 +95129,7 @@ async function executeBashCommand(command, options = {}) {
95036
95129
  [cmd, ...cmdArgs] = args;
95037
95130
  useShell = false;
95038
95131
  }
95039
- const child = (0, import_child_process7.spawn)(cmd, cmdArgs, {
95132
+ const child = (0, import_child_process8.spawn)(cmd, cmdArgs, {
95040
95133
  cwd,
95041
95134
  env: processEnv,
95042
95135
  stdio: ["ignore", "pipe", "pipe"],
@@ -95232,11 +95325,11 @@ function validateExecutionOptions(options = {}) {
95232
95325
  warnings
95233
95326
  };
95234
95327
  }
95235
- var import_child_process7, import_path13, import_fs10;
95328
+ var import_child_process8, import_path13, import_fs10;
95236
95329
  var init_bashExecutor = __esm({
95237
95330
  "src/agent/bashExecutor.js"() {
95238
95331
  "use strict";
95239
- import_child_process7 = require("child_process");
95332
+ import_child_process8 = require("child_process");
95240
95333
  import_path13 = require("path");
95241
95334
  import_fs10 = require("fs");
95242
95335
  init_bashCommandUtils();
@@ -96762,7 +96855,7 @@ ${opts.schema}`;
96762
96855
  }
96763
96856
  }
96764
96857
  const toolCollector = [];
96765
- const proc2 = (0, import_child_process8.spawn)("sh", ["-c", shellCmd], {
96858
+ const proc2 = (0, import_child_process9.spawn)("sh", ["-c", shellCmd], {
96766
96859
  env: { ...process.env, FORCE_COLOR: "0" },
96767
96860
  stdio: ["ignore", "pipe", "pipe"]
96768
96861
  // Ignore stdin since echo handles it
@@ -97117,11 +97210,11 @@ function combinePrompts(systemPrompt, customPrompt, agent) {
97117
97210
  }
97118
97211
  return systemPrompt || "";
97119
97212
  }
97120
- var import_child_process8, import_crypto6, import_promises5, import_path15, import_os5, import_events3;
97213
+ var import_child_process9, import_crypto6, import_promises5, import_path15, import_os5, import_events3;
97121
97214
  var init_enhanced_claude_code = __esm({
97122
97215
  "src/agent/engines/enhanced-claude-code.js"() {
97123
97216
  "use strict";
97124
- import_child_process8 = require("child_process");
97217
+ import_child_process9 = require("child_process");
97125
97218
  import_crypto6 = require("crypto");
97126
97219
  import_promises5 = __toESM(require("fs/promises"), 1);
97127
97220
  import_path15 = __toESM(require("path"), 1);
@@ -97163,7 +97256,7 @@ async function createCodexEngine(options = {}) {
97163
97256
  if (debug) {
97164
97257
  console.log("[DEBUG] Starting Codex MCP server...");
97165
97258
  }
97166
- const codexProcess = (0, import_child_process9.spawn)("codex", ["mcp-server"], {
97259
+ const codexProcess = (0, import_child_process10.spawn)("codex", ["mcp-server"], {
97167
97260
  stdio: ["pipe", "pipe", "pipe"]
97168
97261
  });
97169
97262
  let requestId = 0;
@@ -97395,11 +97488,11 @@ function combinePrompts2(systemPrompt, customPrompt, agent) {
97395
97488
  }
97396
97489
  return systemPrompt || "";
97397
97490
  }
97398
- var import_child_process9, import_crypto7, import_readline;
97491
+ var import_child_process10, import_crypto7, import_readline;
97399
97492
  var init_codex = __esm({
97400
97493
  "src/agent/engines/codex.js"() {
97401
97494
  "use strict";
97402
- import_child_process9 = require("child_process");
97495
+ import_child_process10 = require("child_process");
97403
97496
  import_crypto7 = require("crypto");
97404
97497
  import_readline = require("readline");
97405
97498
  init_built_in_server();
@@ -98165,6 +98258,9 @@ var init_ProbeAgent = __esm({
98165
98258
  if (isToolAllowed("searchFiles")) {
98166
98259
  this.toolImplementations.searchFiles = searchFilesToolInstance;
98167
98260
  }
98261
+ if (wrappedTools.symbolsToolInstance && isToolAllowed("symbols")) {
98262
+ this.toolImplementations.symbols = wrappedTools.symbolsToolInstance;
98263
+ }
98168
98264
  if (this.enableSkills) {
98169
98265
  const registry2 = this._getSkillsRegistry();
98170
98266
  const { listSkillsToolInstance, useSkillToolInstance } = createSkillToolInstances({
@@ -99196,6 +99292,10 @@ var init_ProbeAgent = __esm({
99196
99292
  schema: searchFilesSchema,
99197
99293
  description: "Find files matching a glob pattern with recursive search capability."
99198
99294
  },
99295
+ symbols: {
99296
+ schema: symbolsSchema,
99297
+ description: "List all symbols (functions, classes, structs, constants, etc.) in a file. Returns a hierarchical tree with line numbers \u2014 like a table of contents for code."
99298
+ },
99199
99299
  readMedia: {
99200
99300
  schema: readMediaSchema,
99201
99301
  description: "Read and load a media file (image or PDF document) for AI analysis. Supports: png, jpg, jpeg, webp, bmp, svg, pdf."
@@ -99851,7 +99951,8 @@ ${this.architectureContext.content}
99851
99951
  ${searchToolDesc1}
99852
99952
  - extract: Extract specific code sections with context
99853
99953
  - listFiles: Browse directory contents
99854
- - searchFiles: Find files by name patterns`;
99954
+ - searchFiles: Find files by name patterns
99955
+ - symbols: List all symbols in a file (functions, classes, constants, etc.) with line numbers`;
99855
99956
  if (this.enableBash) {
99856
99957
  systemPrompt += `
99857
99958
  - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
@@ -99906,7 +100007,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
99906
100007
  ${searchToolDesc2}
99907
100008
  - extract: Extract specific code sections with context
99908
100009
  - listFiles: Browse directory contents
99909
- - searchFiles: Find files by name patterns`;
100010
+ - searchFiles: Find files by name patterns
100011
+ - symbols: List all symbols in a file (functions, classes, constants, etc.) with line numbers`;
99910
100012
  if (this.enableBash) {
99911
100013
  systemPrompt += `
99912
100014
  - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
@@ -100485,40 +100587,42 @@ or
100485
100587
  }
100486
100588
  const jsonStr = responseText.replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
100487
100589
  const decision = JSON.parse(jsonStr);
100590
+ let grantedMs = 0;
100591
+ let grantedMin = 0;
100488
100592
  if (decision.extend && decision.minutes > 0) {
100489
100593
  const requestedMs = Math.min(decision.minutes, maxPerReqMin) * 6e4;
100490
- const grantedMs2 = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
100491
- const grantedMin2 = Math.round(grantedMs2 / 6e4 * 10) / 10;
100594
+ grantedMs = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
100595
+ grantedMin = Math.round(grantedMs / 6e4 * 10) / 10;
100492
100596
  negotiatedTimeoutState.extensionsUsed++;
100493
- negotiatedTimeoutState.totalExtraTimeMs += grantedMs2;
100494
- negotiatedTimeoutState.extensionMessage = `\u23F0 Time limit was reached. The timeout observer granted ${grantedMin2} more minute(s) (reason: ${decision.reason || "work in progress"}). Extensions remaining: ${negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed}. Continue your work efficiently.`;
100597
+ negotiatedTimeoutState.totalExtraTimeMs += grantedMs;
100598
+ 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.`;
100495
100599
  negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
100496
100600
  runTimeoutObserver();
100497
- }, grantedMs2);
100601
+ }, grantedMs);
100498
100602
  if (this.debug) {
100499
- console.log(`[DEBUG] Timeout observer: granted ${grantedMin2} min (reason: ${decision.reason}). Extensions: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}`);
100603
+ console.log(`[DEBUG] Timeout observer: granted ${grantedMin} min (reason: ${decision.reason}). Extensions: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}`);
100500
100604
  }
100501
100605
  if (this.tracer) {
100502
100606
  this.tracer.addEvent("negotiated_timeout.observer_extended", {
100503
100607
  decision_reason: decision.reason,
100504
100608
  requested_minutes: decision.minutes,
100505
- granted_ms: grantedMs2,
100506
- granted_min: grantedMin2,
100609
+ granted_ms: grantedMs,
100610
+ granted_min: grantedMin,
100507
100611
  extensions_used: negotiatedTimeoutState.extensionsUsed,
100508
100612
  max_requests: negotiatedTimeoutState.maxRequests,
100509
100613
  total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
100510
- budget_remaining_ms: remainingBudgetMs - grantedMs2,
100614
+ budget_remaining_ms: remainingBudgetMs - grantedMs,
100511
100615
  active_tools: activeToolsList.map((t) => t.name),
100512
100616
  active_tools_count: activeToolsList.length
100513
100617
  });
100514
100618
  }
100515
100619
  this.events.emit("timeout.extended", {
100516
- grantedMs: grantedMs2,
100620
+ grantedMs,
100517
100621
  reason: decision.reason || "work in progress",
100518
100622
  extensionsUsed: negotiatedTimeoutState.extensionsUsed,
100519
100623
  extensionsRemaining: negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed,
100520
100624
  totalExtraTimeMs: negotiatedTimeoutState.totalExtraTimeMs,
100521
- budgetRemainingMs: remainingBudgetMs - grantedMs2
100625
+ budgetRemainingMs: remainingBudgetMs - grantedMs
100522
100626
  });
100523
100627
  } else {
100524
100628
  if (this.debug) {
@@ -103349,7 +103453,7 @@ The "searches" field helps the caller understand what was attempted.
103349
103453
  - Deduplicate files across groups.
103350
103454
  </output-rules>`;
103351
103455
  }
103352
- var import_ai5, import_fs12, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
103456
+ var import_ai5, import_fs12, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool, symbolsTool;
103353
103457
  var init_vercel = __esm({
103354
103458
  "src/tools/vercel.js"() {
103355
103459
  "use strict";
@@ -103357,6 +103461,7 @@ var init_vercel = __esm({
103357
103461
  init_search();
103358
103462
  init_query();
103359
103463
  init_extract();
103464
+ init_symbols();
103360
103465
  init_delegate();
103361
103466
  init_analyzeAll();
103362
103467
  init_common();
@@ -104079,6 +104184,36 @@ Do NOT search for analogies or loosely related concepts. If the feature does not
104079
104184
  }
104080
104185
  });
104081
104186
  };
104187
+ symbolsTool = (options = {}) => {
104188
+ return (0, import_ai5.tool)({
104189
+ name: "symbols",
104190
+ description: "List all symbols (functions, classes, structs, constants, etc.) in a file. Returns a hierarchical tree with line numbers \u2014 like a table of contents for code files.",
104191
+ inputSchema: symbolsSchema,
104192
+ execute: async ({ file: file2 }) => {
104193
+ try {
104194
+ let filePath = file2;
104195
+ if (options.cwd) {
104196
+ const resolvedPaths = parseAndResolvePaths(file2, options.cwd);
104197
+ if (resolvedPaths.length > 0) {
104198
+ filePath = resolvedPaths[0];
104199
+ }
104200
+ }
104201
+ const result = await symbols({
104202
+ files: [filePath],
104203
+ cwd: options.cwd,
104204
+ binaryOptions: options.binaryOptions
104205
+ });
104206
+ if (result && result.length > 0) {
104207
+ return JSON.stringify(result[0], null, 2);
104208
+ }
104209
+ return JSON.stringify({ file: file2, symbols: [] }, null, 2);
104210
+ } catch (error40) {
104211
+ console.error("Error executing symbols:", error40);
104212
+ return formatErrorForAI(error40);
104213
+ }
104214
+ }
104215
+ });
104216
+ };
104082
104217
  }
104083
104218
  });
104084
104219
 
@@ -105239,6 +105374,10 @@ You are Probe, a specialized code intelligence assistant. Your objective is to a
105239
105374
  * **Purpose:** Retrieve specific code blocks or entire files *after* \`search\` or \`query\` identifies the target.
105240
105375
  * **Syntax:** Optional \`#symbol\` (e.g., \`#MyClass\`), \`#Lstart-Lend\` (e.g., \`#L50-L75\`).
105241
105376
  * **Mandatory Argument:** \`path\` (specific file path, e.g., \`"src/utils/helpers.go"\`, or dependency file like \`"go:github.com/gin-gonic/gin/context.go"\`).
105377
+ * \`symbols\`
105378
+ * **Purpose:** List all symbols (functions, classes, structs, constants, etc.) in a file \u2014 a table of contents with line numbers and nesting.
105379
+ * **Syntax:** \`file\` (path to the file to list symbols from).
105380
+ * **Use When:** You need to understand a file's structure before extracting specific parts, or to find the right symbol name/line number for \`extract\`.
105242
105381
 
105243
105382
  [Examples]
105244
105383
 
@@ -105373,6 +105512,8 @@ __export(tools_exports, {
105373
105512
  searchFilesSchema: () => searchFilesSchema,
105374
105513
  searchSchema: () => searchSchema,
105375
105514
  searchTool: () => searchTool,
105515
+ symbolsSchema: () => symbolsSchema,
105516
+ symbolsTool: () => symbolsTool,
105376
105517
  tools: () => tools,
105377
105518
  useSkillSchema: () => useSkillSchema
105378
105519
  });
@@ -105496,16 +105637,16 @@ function shouldIgnore(filePath, ignorePatterns) {
105496
105637
  }
105497
105638
  return false;
105498
105639
  }
105499
- var import_fs15, import_path18, import_util14, import_child_process10, execAsync3;
105640
+ var import_fs15, import_path18, import_util14, import_child_process11, execAsync3;
105500
105641
  var init_file_lister = __esm({
105501
105642
  "src/utils/file-lister.js"() {
105502
105643
  "use strict";
105503
105644
  import_fs15 = __toESM(require("fs"), 1);
105504
105645
  import_path18 = __toESM(require("path"), 1);
105505
105646
  import_util14 = require("util");
105506
- import_child_process10 = require("child_process");
105647
+ import_child_process11 = require("child_process");
105507
105648
  init_symlink_utils();
105508
- execAsync3 = (0, import_util14.promisify)(import_child_process10.exec);
105649
+ execAsync3 = (0, import_util14.promisify)(import_child_process11.exec);
105509
105650
  }
105510
105651
  });
105511
105652
 
@@ -105581,6 +105722,9 @@ __export(index_exports, {
105581
105722
  searchSchema: () => searchSchema,
105582
105723
  searchTool: () => searchTool,
105583
105724
  setBinaryPath: () => setBinaryPath,
105725
+ symbols: () => symbols,
105726
+ symbolsSchema: () => symbolsSchema,
105727
+ symbolsTool: () => symbolsTool,
105584
105728
  taskSchema: () => taskSchema,
105585
105729
  taskSystemPrompt: () => taskSystemPrompt,
105586
105730
  tools: () => tools_exports,
@@ -105594,6 +105738,7 @@ var init_index = __esm({
105594
105738
  init_search();
105595
105739
  init_query();
105596
105740
  init_extract();
105741
+ init_symbols();
105597
105742
  init_grep();
105598
105743
  init_delegate();
105599
105744
  init_utils();
@@ -105671,6 +105816,9 @@ init_index();
105671
105816
  searchSchema,
105672
105817
  searchTool,
105673
105818
  setBinaryPath,
105819
+ symbols,
105820
+ symbolsSchema,
105821
+ symbolsTool,
105674
105822
  taskSchema,
105675
105823
  taskSystemPrompt,
105676
105824
  tools,
package/index.d.ts CHANGED
@@ -408,6 +408,58 @@ export interface QueryParams {
408
408
  sessionId?: string;
409
409
  }
410
410
 
411
+ /**
412
+ * Symbols tool parameters
413
+ */
414
+ export interface SymbolsParams {
415
+ /** Files to list symbols from */
416
+ files: string[];
417
+ /** Working directory for resolving relative paths */
418
+ cwd?: string;
419
+ /** Include test functions/methods */
420
+ allowTests?: boolean;
421
+ /** Binary options */
422
+ binaryOptions?: {
423
+ forceDownload?: boolean;
424
+ version?: string;
425
+ };
426
+ }
427
+
428
+ /**
429
+ * A node in the symbol tree
430
+ */
431
+ export interface SymbolNode {
432
+ /** Symbol name */
433
+ name: string;
434
+ /** Symbol kind (function, struct, class, const, etc.) */
435
+ kind: string;
436
+ /** Clean signature without body */
437
+ signature: string;
438
+ /** Start line (1-indexed) */
439
+ line: number;
440
+ /** End line (1-indexed) */
441
+ end_line: number;
442
+ /** Nested symbols (methods inside classes/impl blocks, etc.) */
443
+ children?: SymbolNode[];
444
+ }
445
+
446
+ /**
447
+ * Symbols extracted from a single file
448
+ */
449
+ export interface FileSymbols {
450
+ /** File path */
451
+ file: string;
452
+ /** Top-level symbols with optional nesting */
453
+ symbols: SymbolNode[];
454
+ }
455
+
456
+ /**
457
+ * Symbols tool function type
458
+ */
459
+ export type SymbolsTool = (options?: SearchOptions) => {
460
+ execute(params: { file: string }): Promise<string>;
461
+ };
462
+
411
463
  /**
412
464
  * Extract tool parameters
413
465
  */
@@ -537,6 +589,11 @@ export declare function grep(options: {
537
589
  };
538
590
  }): Promise<string>;
539
591
 
592
+ /**
593
+ * List symbols (functions, structs, classes, constants, etc.) in files
594
+ */
595
+ export declare function symbols(options: SymbolsParams): Promise<FileSymbols[]>;
596
+
540
597
  /**
541
598
  * Extract code blocks from files
542
599
  */
@@ -564,6 +621,11 @@ export declare function queryTool(options?: SearchOptions): ReturnType<QueryTool
564
621
  */
565
622
  export declare function extractTool(options?: SearchOptions): ReturnType<ExtractTool>;
566
623
 
624
+ /**
625
+ * Create symbols tool instance
626
+ */
627
+ export declare function symbolsTool(options?: SearchOptions): ReturnType<SymbolsTool>;
628
+
567
629
  /**
568
630
  * Get the path to the probe binary
569
631
  */
@@ -778,6 +840,7 @@ export declare function createTool(options?: EditToolOptions): {
778
840
  export declare const searchSchema: any;
779
841
  export declare const querySchema: any;
780
842
  export declare const extractSchema: any;
843
+ export declare const symbolsSchema: any;
781
844
  export declare const editSchema: any;
782
845
  export declare const createSchema: any;
783
846
  export declare const attemptCompletionSchema: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc307",
3
+ "version": "0.6.0-rc309",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -56,6 +56,7 @@ import {
56
56
  searchFilesSchema,
57
57
  readImageSchema,
58
58
  readMediaSchema,
59
+ symbolsSchema,
59
60
  listSkillsSchema,
60
61
  useSkillSchema
61
62
  } from './tools.js';
@@ -945,6 +946,9 @@ export class ProbeAgent {
945
946
  if (isToolAllowed('searchFiles')) {
946
947
  this.toolImplementations.searchFiles = searchFilesToolInstance;
947
948
  }
949
+ if (wrappedTools.symbolsToolInstance && isToolAllowed('symbols')) {
950
+ this.toolImplementations.symbols = wrappedTools.symbolsToolInstance;
951
+ }
948
952
 
949
953
  if (this.enableSkills) {
950
954
  const registry = this._getSkillsRegistry();
@@ -2225,6 +2229,10 @@ export class ProbeAgent {
2225
2229
  schema: searchFilesSchema,
2226
2230
  description: 'Find files matching a glob pattern with recursive search capability.'
2227
2231
  },
2232
+ symbols: {
2233
+ schema: symbolsSchema,
2234
+ description: 'List all symbols (functions, classes, structs, constants, etc.) in a file. Returns a hierarchical tree with line numbers — like a table of contents for code.'
2235
+ },
2228
2236
  readMedia: {
2229
2237
  schema: readMediaSchema,
2230
2238
  description: 'Read and load a media file (image or PDF document) for AI analysis. Supports: png, jpg, jpeg, webp, bmp, svg, pdf.'
@@ -3019,7 +3027,8 @@ export class ProbeAgent {
3019
3027
  ${searchToolDesc1}
3020
3028
  - extract: Extract specific code sections with context
3021
3029
  - listFiles: Browse directory contents
3022
- - searchFiles: Find files by name patterns`;
3030
+ - searchFiles: Find files by name patterns
3031
+ - symbols: List all symbols in a file (functions, classes, constants, etc.) with line numbers`;
3023
3032
 
3024
3033
  if (this.enableBash) {
3025
3034
  systemPrompt += `\n- bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) — always use search and extract tools instead, they are faster and more accurate.`;
@@ -3085,7 +3094,8 @@ ${extractGuidance1}
3085
3094
  ${searchToolDesc2}
3086
3095
  - extract: Extract specific code sections with context
3087
3096
  - listFiles: Browse directory contents
3088
- - searchFiles: Find files by name patterns`;
3097
+ - searchFiles: Find files by name patterns
3098
+ - symbols: List all symbols in a file (functions, classes, constants, etc.) with line numbers`;
3089
3099
 
3090
3100
  if (this.enableBash) {
3091
3101
  systemPrompt += `\n- bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) — always use search and extract tools instead, they are faster and more accurate.`;
@@ -3814,10 +3824,13 @@ or
3814
3824
  const jsonStr = responseText.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, '');
3815
3825
  const decision = JSON.parse(jsonStr);
3816
3826
 
3827
+ let grantedMs = 0;
3828
+ let grantedMin = 0;
3829
+
3817
3830
  if (decision.extend && decision.minutes > 0) {
3818
3831
  const requestedMs = Math.min(decision.minutes, maxPerReqMin) * 60000;
3819
- const grantedMs = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
3820
- const grantedMin = Math.round(grantedMs / 60000 * 10) / 10;
3832
+ grantedMs = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
3833
+ grantedMin = Math.round(grantedMs / 60000 * 10) / 10;
3821
3834
 
3822
3835
  // Update state
3823
3836
  negotiatedTimeoutState.extensionsUsed++;