@probelabs/probe 0.6.0-rc224 → 0.6.0-rc226

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 (33) hide show
  1. package/bin/binaries/probe-v0.6.0-rc226-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc226-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc226-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc226-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc226-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +361 -36
  7. package/build/agent/index.js +570 -57
  8. package/build/agent/mcp/xmlBridge.js +10 -7
  9. package/build/agent/simpleTelemetry.js +198 -0
  10. package/build/agent/tools.js +8 -5
  11. package/build/tools/analyzeAll.js +6 -1
  12. package/build/tools/bash.js +18 -3
  13. package/build/tools/edit.js +19 -10
  14. package/build/tools/vercel.js +17 -7
  15. package/build/utils/path-validation.js +148 -1
  16. package/cjs/agent/ProbeAgent.cjs +392 -56
  17. package/cjs/agent/simpleTelemetry.cjs +177 -0
  18. package/cjs/index.cjs +569 -56
  19. package/package.json +1 -1
  20. package/src/agent/ProbeAgent.js +361 -36
  21. package/src/agent/mcp/xmlBridge.js +10 -7
  22. package/src/agent/simpleTelemetry.js +198 -0
  23. package/src/agent/tools.js +8 -5
  24. package/src/tools/analyzeAll.js +6 -1
  25. package/src/tools/bash.js +18 -3
  26. package/src/tools/edit.js +19 -10
  27. package/src/tools/vercel.js +17 -7
  28. package/src/utils/path-validation.js +148 -1
  29. package/bin/binaries/probe-v0.6.0-rc224-aarch64-apple-darwin.tar.gz +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc224-aarch64-unknown-linux-musl.tar.gz +0 -0
  31. package/bin/binaries/probe-v0.6.0-rc224-x86_64-apple-darwin.tar.gz +0 -0
  32. package/bin/binaries/probe-v0.6.0-rc224-x86_64-pc-windows-msvc.zip +0 -0
  33. package/bin/binaries/probe-v0.6.0-rc224-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -32675,6 +32675,25 @@ var init_error_types = __esm({
32675
32675
  });
32676
32676
 
32677
32677
  // src/utils/path-validation.js
32678
+ function safeRealpath(inputPath) {
32679
+ try {
32680
+ return (0, import_fs3.realpathSync)(inputPath);
32681
+ } catch (error2) {
32682
+ const normalized = import_path4.default.normalize(inputPath);
32683
+ const parts = normalized.split(import_path4.default.sep);
32684
+ for (let i5 = parts.length - 1; i5 >= 0; i5--) {
32685
+ const ancestorPath = parts.slice(0, i5).join(import_path4.default.sep) || import_path4.default.sep;
32686
+ try {
32687
+ const resolvedAncestor = (0, import_fs3.realpathSync)(ancestorPath);
32688
+ const remainingParts = parts.slice(i5);
32689
+ return import_path4.default.join(resolvedAncestor, ...remainingParts);
32690
+ } catch (ancestorError) {
32691
+ continue;
32692
+ }
32693
+ }
32694
+ return normalized;
32695
+ }
32696
+ }
32678
32697
  async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
32679
32698
  const targetPath = inputPath || defaultPath;
32680
32699
  const normalizedPath = import_path4.default.normalize(import_path4.default.resolve(targetPath));
@@ -32707,6 +32726,53 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
32707
32726
  }
32708
32727
  return normalizedPath;
32709
32728
  }
32729
+ function getCommonPrefix(folders) {
32730
+ if (!folders || folders.length === 0) {
32731
+ return process.cwd();
32732
+ }
32733
+ if (folders.length === 1) {
32734
+ return safeRealpath(folders[0]);
32735
+ }
32736
+ const normalized = folders.map((f5) => safeRealpath(f5));
32737
+ const segments = normalized.map((f5) => f5.split(import_path4.default.sep));
32738
+ const minLen = Math.min(...segments.map((s5) => s5.length));
32739
+ const commonSegments = [];
32740
+ for (let i5 = 0; i5 < minLen; i5++) {
32741
+ const segment = segments[0][i5];
32742
+ if (segments.every((s5) => s5[i5] === segment)) {
32743
+ commonSegments.push(segment);
32744
+ } else {
32745
+ break;
32746
+ }
32747
+ }
32748
+ if (commonSegments.length === 0) {
32749
+ return normalized[0];
32750
+ }
32751
+ if (commonSegments.length === 1 && /^[a-zA-Z]:$/.test(commonSegments[0])) {
32752
+ return normalized[0];
32753
+ }
32754
+ if (commonSegments.length === 1 && commonSegments[0] === "") {
32755
+ return normalized[0];
32756
+ }
32757
+ return commonSegments.join(import_path4.default.sep);
32758
+ }
32759
+ function toRelativePath(absolutePath, workspaceRoot) {
32760
+ if (!absolutePath || !workspaceRoot) {
32761
+ return absolutePath;
32762
+ }
32763
+ let normalized = safeRealpath(absolutePath);
32764
+ let normalizedRoot = safeRealpath(workspaceRoot);
32765
+ while (normalizedRoot.length > 1 && normalizedRoot.endsWith(import_path4.default.sep)) {
32766
+ normalizedRoot = normalizedRoot.slice(0, -1);
32767
+ }
32768
+ if (normalized === normalizedRoot) {
32769
+ return ".";
32770
+ }
32771
+ if (normalized.startsWith(normalizedRoot + import_path4.default.sep)) {
32772
+ return import_path4.default.relative(normalizedRoot, normalized);
32773
+ }
32774
+ return absolutePath;
32775
+ }
32710
32776
  var import_path4, import_fs3;
32711
32777
  var init_path_validation = __esm({
32712
32778
  "src/utils/path-validation.js"() {
@@ -33904,6 +33970,7 @@ async function analyzeAll(options) {
33904
33970
  sessionId,
33905
33971
  debug = false,
33906
33972
  cwd,
33973
+ workspaceRoot,
33907
33974
  allowedFolders,
33908
33975
  provider,
33909
33976
  model,
@@ -33916,10 +33983,11 @@ async function analyzeAll(options) {
33916
33983
  if (!question) {
33917
33984
  throw new Error('The "question" parameter is required.');
33918
33985
  }
33986
+ const effectiveWorkspaceRoot = workspaceRoot || cwd || allowedFolders?.[0] || path9;
33919
33987
  const delegateOptions = {
33920
33988
  debug,
33921
33989
  sessionId,
33922
- path: allowedFolders?.[0] || cwd || path9,
33990
+ path: effectiveWorkspaceRoot,
33923
33991
  allowedFolders,
33924
33992
  provider,
33925
33993
  model,
@@ -38132,21 +38200,24 @@ var init_zod = __esm({
38132
38200
  // src/tools/edit.js
38133
38201
  function isPathAllowed(filePath, allowedFolders) {
38134
38202
  if (!allowedFolders || allowedFolders.length === 0) {
38135
- const resolvedPath3 = (0, import_path5.resolve)(filePath);
38136
- const cwd = (0, import_path5.resolve)(process.cwd());
38203
+ const resolvedPath3 = safeRealpath(filePath);
38204
+ const cwd = safeRealpath(process.cwd());
38137
38205
  return resolvedPath3 === cwd || resolvedPath3.startsWith(cwd + import_path5.sep);
38138
38206
  }
38139
- const resolvedPath2 = (0, import_path5.resolve)(filePath);
38207
+ const resolvedPath2 = safeRealpath(filePath);
38140
38208
  return allowedFolders.some((folder) => {
38141
- const allowedPath = (0, import_path5.resolve)(folder);
38209
+ const allowedPath = safeRealpath(folder);
38142
38210
  return resolvedPath2 === allowedPath || resolvedPath2.startsWith(allowedPath + import_path5.sep);
38143
38211
  });
38144
38212
  }
38145
38213
  function parseFileToolOptions(options = {}) {
38214
+ const allowedFolders = options.allowedFolders || [];
38146
38215
  return {
38147
38216
  debug: options.debug || false,
38148
- allowedFolders: options.allowedFolders || [],
38149
- cwd: options.cwd
38217
+ allowedFolders,
38218
+ cwd: options.cwd,
38219
+ // Consistent fallback chain: workspaceRoot > cwd > allowedFolders[0] > process.cwd()
38220
+ workspaceRoot: options.workspaceRoot || options.cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd()
38150
38221
  };
38151
38222
  }
38152
38223
  var import_ai, import_fs4, import_path5, import_fs5, editTool, createTool, editSchema, createSchema, editDescription, createDescription, editToolDefinition, createToolDefinition;
@@ -38157,8 +38228,9 @@ var init_edit = __esm({
38157
38228
  import_fs4 = require("fs");
38158
38229
  import_path5 = require("path");
38159
38230
  import_fs5 = require("fs");
38231
+ init_path_validation();
38160
38232
  editTool = (options = {}) => {
38161
- const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
38233
+ const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
38162
38234
  return (0, import_ai.tool)({
38163
38235
  name: "edit",
38164
38236
  description: `Edit files using exact string replacement (Claude Code style).
@@ -38214,7 +38286,8 @@ Important:
38214
38286
  console.error(`[Edit] Attempting to edit file: ${resolvedPath2}`);
38215
38287
  }
38216
38288
  if (!isPathAllowed(resolvedPath2, allowedFolders)) {
38217
- return `Error editing file: Permission denied - ${file_path} is outside allowed directories`;
38289
+ const relativePath = toRelativePath(resolvedPath2, workspaceRoot);
38290
+ return `Error editing file: Permission denied - ${relativePath} is outside allowed directories`;
38218
38291
  }
38219
38292
  if (!(0, import_fs5.existsSync)(resolvedPath2)) {
38220
38293
  return `Error editing file: File not found - ${file_path}`;
@@ -38250,7 +38323,7 @@ Important:
38250
38323
  });
38251
38324
  };
38252
38325
  createTool = (options = {}) => {
38253
- const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
38326
+ const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
38254
38327
  return (0, import_ai.tool)({
38255
38328
  name: "create",
38256
38329
  description: `Create new files with specified content.
@@ -38298,7 +38371,8 @@ Important:
38298
38371
  console.error(`[Create] Attempting to create file: ${resolvedPath2}`);
38299
38372
  }
38300
38373
  if (!isPathAllowed(resolvedPath2, allowedFolders)) {
38301
- return `Error creating file: Permission denied - ${file_path} is outside allowed directories`;
38374
+ const relativePath = toRelativePath(resolvedPath2, workspaceRoot);
38375
+ return `Error creating file: Permission denied - ${relativePath} is outside allowed directories`;
38302
38376
  }
38303
38377
  if ((0, import_fs5.existsSync)(resolvedPath2) && !overwrite) {
38304
38378
  return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
@@ -39860,7 +39934,7 @@ var init_vercel = __esm({
39860
39934
  });
39861
39935
  };
39862
39936
  delegateTool = (options = {}) => {
39863
- const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
39937
+ const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
39864
39938
  return (0, import_ai2.tool)({
39865
39939
  name: "delegate",
39866
39940
  description: delegateDescription,
@@ -39893,8 +39967,8 @@ var init_vercel = __esm({
39893
39967
  if (searchDelegate !== void 0 && typeof searchDelegate !== "boolean") {
39894
39968
  throw new TypeError("searchDelegate must be a boolean if provided");
39895
39969
  }
39896
- const workspaceRoot = allowedFolders && allowedFolders[0];
39897
- const effectivePath = path9 || workspaceRoot || cwd;
39970
+ const effectiveWorkspaceRoot = workspaceRoot || allowedFolders && allowedFolders[0];
39971
+ const effectivePath = path9 || effectiveWorkspaceRoot || cwd;
39898
39972
  if (debug) {
39899
39973
  console.error(`Executing delegate with task: "${task.substring(0, 100)}${task.length > 100 ? "..." : ""}"`);
39900
39974
  if (parentSessionId) {
@@ -39931,7 +40005,7 @@ var init_vercel = __esm({
39931
40005
  });
39932
40006
  };
39933
40007
  analyzeAllTool = (options = {}) => {
39934
- const { sessionId, debug = false, delegationManager = null } = options;
40008
+ const { sessionId, debug = false, delegationManager = null, workspaceRoot } = options;
39935
40009
  return (0, import_ai2.tool)({
39936
40010
  name: "analyze_all",
39937
40011
  description: analyzeAllDescription,
@@ -39950,12 +40024,14 @@ var init_vercel = __esm({
39950
40024
  console.error(`[analyze_all] Question: ${question}`);
39951
40025
  console.error(`[analyze_all] Path: ${searchPath}`);
39952
40026
  }
40027
+ const effectiveWorkspaceRoot = workspaceRoot || options.cwd || options.allowedFolders && options.allowedFolders[0];
39953
40028
  const result = await analyzeAll({
39954
40029
  question,
39955
40030
  path: searchPath,
39956
40031
  sessionId,
39957
40032
  debug,
39958
40033
  cwd: options.cwd,
40034
+ workspaceRoot: effectiveWorkspaceRoot,
39959
40035
  allowedFolders: options.allowedFolders,
39960
40036
  provider: options.provider,
39961
40037
  model: options.model,
@@ -41432,14 +41508,17 @@ var init_bash = __esm({
41432
41508
  import_path8 = require("path");
41433
41509
  init_bashPermissions();
41434
41510
  init_bashExecutor();
41511
+ init_path_validation();
41435
41512
  bashTool = (options = {}) => {
41436
41513
  const {
41437
41514
  bashConfig = {},
41438
41515
  debug = false,
41439
41516
  cwd,
41440
41517
  allowedFolders = [],
41518
+ workspaceRoot: providedWorkspaceRoot,
41441
41519
  tracer = null
41442
41520
  } = options;
41521
+ const workspaceRoot = providedWorkspaceRoot || cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd();
41443
41522
  const permissionChecker = new BashPermissionChecker({
41444
41523
  allow: bashConfig.allow,
41445
41524
  deny: bashConfig.deny,
@@ -41455,6 +41534,9 @@ var init_bash = __esm({
41455
41534
  if (cwd) {
41456
41535
  return cwd;
41457
41536
  }
41537
+ if (workspaceRoot) {
41538
+ return workspaceRoot;
41539
+ }
41458
41540
  if (allowedFolders && allowedFolders.length > 0) {
41459
41541
  return allowedFolders[0];
41460
41542
  }
@@ -41542,13 +41624,15 @@ For code exploration, try these safe alternatives:
41542
41624
  const defaultDir = getDefaultWorkingDirectory();
41543
41625
  const workingDir = workingDirectory ? (0, import_path8.isAbsolute)(workingDirectory) ? (0, import_path8.resolve)(workingDirectory) : (0, import_path8.resolve)(defaultDir, workingDirectory) : defaultDir;
41544
41626
  if (allowedFolders && allowedFolders.length > 0) {
41545
- const resolvedWorkingDir = (0, import_path8.resolve)(workingDir);
41627
+ const resolvedWorkingDir = safeRealpath(workingDir);
41546
41628
  const isAllowed = allowedFolders.some((folder) => {
41547
- const resolvedFolder = (0, import_path8.resolve)(folder);
41629
+ const resolvedFolder = safeRealpath(folder);
41548
41630
  return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + import_path8.sep);
41549
41631
  });
41550
41632
  if (!isAllowed) {
41551
- return `Error: Working directory "${workingDir}" is not within allowed folders: ${allowedFolders.join(", ")}`;
41633
+ const relativeDir = toRelativePath(workingDir, workspaceRoot);
41634
+ const relativeAllowed = allowedFolders.map((f5) => toRelativePath(f5, workspaceRoot));
41635
+ return `Error: Working directory "${relativeDir}" is not within allowed folders: ${relativeAllowed.join(", ")}`;
41552
41636
  }
41553
41637
  }
41554
41638
  const executionOptions = {
@@ -45715,7 +45799,7 @@ var init_esm3 = __esm({
45715
45799
  });
45716
45800
 
45717
45801
  // node_modules/path-scurry/dist/esm/index.js
45718
- var import_node_path, import_node_url, import_fs9, actualFS, import_promises, realpathSync, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
45802
+ var import_node_path, import_node_url, import_fs9, actualFS, import_promises, realpathSync2, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
45719
45803
  var init_esm4 = __esm({
45720
45804
  "node_modules/path-scurry/dist/esm/index.js"() {
45721
45805
  init_esm2();
@@ -45725,13 +45809,13 @@ var init_esm4 = __esm({
45725
45809
  actualFS = __toESM(require("node:fs"), 1);
45726
45810
  import_promises = require("node:fs/promises");
45727
45811
  init_esm3();
45728
- realpathSync = import_fs9.realpathSync.native;
45812
+ realpathSync2 = import_fs9.realpathSync.native;
45729
45813
  defaultFS = {
45730
45814
  lstatSync: import_fs9.lstatSync,
45731
45815
  readdir: import_fs9.readdir,
45732
45816
  readdirSync: import_fs9.readdirSync,
45733
45817
  readlinkSync: import_fs9.readlinkSync,
45734
- realpathSync,
45818
+ realpathSync: realpathSync2,
45735
45819
  promises: {
45736
45820
  lstat: import_promises.lstat,
45737
45821
  readdir: import_promises.readdir,
@@ -49551,11 +49635,12 @@ function createTools(configOptions) {
49551
49635
  return tools2;
49552
49636
  }
49553
49637
  function parseXmlToolCallWithThinking(xmlString, validTools) {
49554
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
49638
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
49555
49639
  if (recoveryResult) {
49556
- return recoveryResult;
49640
+ return { ...recoveryResult, thinkingContent };
49557
49641
  }
49558
- return parseXmlToolCall(cleanedXmlString, validTools);
49642
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
49643
+ return toolCall ? { ...toolCall, thinkingContent } : null;
49559
49644
  }
49560
49645
  var import_crypto4, implementToolDefinition, listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
49561
49646
  var init_tools2 = __esm({
@@ -88850,25 +88935,27 @@ function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
88850
88935
  function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
88851
88936
  const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
88852
88937
  if (nativeResult) {
88853
- return { ...nativeResult, type: "native" };
88938
+ const { thinkingContent, ...rest } = nativeResult;
88939
+ return { ...rest, type: "native", thinkingContent };
88854
88940
  }
88855
88941
  if (mcpBridge) {
88856
88942
  const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
88857
88943
  if (mcpResult) {
88858
- return { ...mcpResult, type: "mcp" };
88944
+ const { thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, []);
88945
+ return { ...mcpResult, type: "mcp", thinkingContent };
88859
88946
  }
88860
88947
  }
88861
88948
  return null;
88862
88949
  }
88863
88950
  function parseNativeXmlToolWithThinking(xmlString, validTools) {
88864
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
88951
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
88865
88952
  if (recoveryResult) {
88866
- return recoveryResult;
88953
+ return { ...recoveryResult, thinkingContent };
88867
88954
  }
88868
88955
  for (const toolName of validTools) {
88869
88956
  const result = parseNativeXmlTool(cleanedXmlString, toolName);
88870
88957
  if (result) {
88871
- return result;
88958
+ return { ...result, thinkingContent };
88872
88959
  }
88873
88960
  }
88874
88961
  return null;
@@ -99123,6 +99210,7 @@ var init_ProbeAgent = __esm({
99123
99210
  init_FallbackManager();
99124
99211
  init_contextCompactor();
99125
99212
  init_error_types();
99213
+ init_path_validation();
99126
99214
  init_outputTruncator();
99127
99215
  init_delegate();
99128
99216
  init_tasks();
@@ -99240,7 +99328,8 @@ var init_ProbeAgent = __esm({
99240
99328
  } else {
99241
99329
  this.allowedFolders = [process.cwd()];
99242
99330
  }
99243
- this.cwd = options.cwd || null;
99331
+ this.workspaceRoot = getCommonPrefix(this.allowedFolders);
99332
+ this.cwd = options.cwd || this.workspaceRoot;
99244
99333
  this.clientApiProvider = options.provider || null;
99245
99334
  this.clientApiModel = options.model || null;
99246
99335
  this.clientApiKey = null;
@@ -99252,6 +99341,8 @@ var init_ProbeAgent = __esm({
99252
99341
  console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
99253
99342
  console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
99254
99343
  console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
99344
+ console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
99345
+ console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
99255
99346
  }
99256
99347
  this.initializeTools();
99257
99348
  this.history = [];
@@ -99329,6 +99420,181 @@ var init_ProbeAgent = __esm({
99329
99420
  _filterMcpTools(mcpToolNames) {
99330
99421
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
99331
99422
  }
99423
+ /**
99424
+ * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
99425
+ * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
99426
+ * @private
99427
+ */
99428
+ _isAppTracerStyle() {
99429
+ return this.tracer && typeof this.tracer.sessionSpans !== "undefined";
99430
+ }
99431
+ /**
99432
+ * Record an error classification event for telemetry
99433
+ * Provides unified error recording across all error types
99434
+ * @param {string} errorType - Error type (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker)
99435
+ * @param {string} message - Error message
99436
+ * @param {Object} context - Additional context data
99437
+ * @param {number} iteration - Current iteration number
99438
+ * @private
99439
+ */
99440
+ _recordErrorTelemetry(errorType, message, context, iteration) {
99441
+ if (!this.tracer) return;
99442
+ if (this._isAppTracerStyle() && typeof this.tracer.recordErrorClassification === "function") {
99443
+ this.tracer.recordErrorClassification(this.sessionId, iteration, errorType, {
99444
+ message,
99445
+ context
99446
+ });
99447
+ } else if (typeof this.tracer.recordErrorEvent === "function") {
99448
+ this.tracer.recordErrorEvent(errorType, {
99449
+ message,
99450
+ context: { ...context, iteration }
99451
+ });
99452
+ } else {
99453
+ this.tracer.addEvent(`error.${errorType}`, {
99454
+ "error.type": errorType,
99455
+ "error.message": message,
99456
+ "error.recoverable": errorType !== "circuit_breaker",
99457
+ "error.context": JSON.stringify(context).substring(0, 1e3),
99458
+ "iteration": iteration
99459
+ });
99460
+ }
99461
+ }
99462
+ /**
99463
+ * Record AI thinking content for telemetry
99464
+ * @param {string} thinkingContent - The thinking content
99465
+ * @param {number} iteration - Current iteration number
99466
+ * @private
99467
+ */
99468
+ _recordThinkingTelemetry(thinkingContent, iteration) {
99469
+ if (!this.tracer || !thinkingContent) return;
99470
+ if (this._isAppTracerStyle() && typeof this.tracer.recordThinkingContent === "function") {
99471
+ this.tracer.recordThinkingContent(this.sessionId, iteration, thinkingContent);
99472
+ } else if (typeof this.tracer.recordThinkingContent === "function") {
99473
+ this.tracer.recordThinkingContent(thinkingContent, { iteration });
99474
+ } else {
99475
+ this.tracer.addEvent("ai.thinking", {
99476
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
99477
+ "ai.thinking.length": thinkingContent.length,
99478
+ "iteration": iteration
99479
+ });
99480
+ }
99481
+ }
99482
+ /**
99483
+ * Record AI tool decision for telemetry
99484
+ * @param {string} toolName - The tool name
99485
+ * @param {Object} params - Tool parameters
99486
+ * @param {number} responseLength - Length of AI response
99487
+ * @param {number} iteration - Current iteration number
99488
+ * @private
99489
+ */
99490
+ _recordToolDecisionTelemetry(toolName, params, responseLength, iteration) {
99491
+ if (!this.tracer) return;
99492
+ if (this._isAppTracerStyle() && typeof this.tracer.recordAIToolDecision === "function") {
99493
+ this.tracer.recordAIToolDecision(this.sessionId, iteration, toolName, params);
99494
+ } else if (typeof this.tracer.recordToolDecision === "function") {
99495
+ this.tracer.recordToolDecision(toolName, params, {
99496
+ iteration,
99497
+ "ai.tool_decision.raw_response_length": responseLength
99498
+ });
99499
+ } else {
99500
+ this.tracer.addEvent("ai.tool_decision", {
99501
+ "ai.tool_decision.name": toolName,
99502
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
99503
+ "ai.tool_decision.raw_response_length": responseLength,
99504
+ "iteration": iteration
99505
+ });
99506
+ }
99507
+ }
99508
+ /**
99509
+ * Record tool result for telemetry
99510
+ * @param {string} toolName - The tool name
99511
+ * @param {string|Object} result - Tool result
99512
+ * @param {boolean} success - Whether tool succeeded
99513
+ * @param {number} durationMs - Execution duration in milliseconds
99514
+ * @param {number} iteration - Current iteration number
99515
+ * @private
99516
+ */
99517
+ _recordToolResultTelemetry(toolName, result, success, durationMs, iteration) {
99518
+ if (!this.tracer) return;
99519
+ if (this._isAppTracerStyle() && typeof this.tracer.recordToolResult === "function") {
99520
+ this.tracer.recordToolResult(this.sessionId, iteration, toolName, result, success, durationMs);
99521
+ } else if (typeof this.tracer.recordToolResult === "function") {
99522
+ this.tracer.recordToolResult(toolName, result, success, durationMs, { iteration });
99523
+ } else {
99524
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
99525
+ this.tracer.addEvent("tool.result", {
99526
+ "tool.name": toolName,
99527
+ "tool.result": resultStr.substring(0, 1e4),
99528
+ "tool.result.length": resultStr.length,
99529
+ "tool.duration_ms": durationMs,
99530
+ "tool.success": success,
99531
+ "iteration": iteration
99532
+ });
99533
+ }
99534
+ }
99535
+ /**
99536
+ * Record MCP tool lifecycle event for telemetry
99537
+ * @param {string} phase - 'start' or 'end'
99538
+ * @param {string} toolName - MCP tool name
99539
+ * @param {Object} params - Tool parameters (for start) or null (for end)
99540
+ * @param {number} iteration - Current iteration number
99541
+ * @param {Object} [endData] - Additional data for end phase (result, success, durationMs, error)
99542
+ * @private
99543
+ */
99544
+ _recordMcpToolTelemetry(phase, toolName, params, iteration, endData = null) {
99545
+ if (!this.tracer) return;
99546
+ if (phase === "start") {
99547
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolStart === "function") {
99548
+ this.tracer.recordMcpToolStart(this.sessionId, iteration, toolName, "mcp", params);
99549
+ } else if (typeof this.tracer.recordMcpToolStart === "function") {
99550
+ this.tracer.recordMcpToolStart(toolName, "mcp", params, { iteration });
99551
+ } else {
99552
+ this.tracer.addEvent("mcp.tool.start", {
99553
+ "mcp.tool.name": toolName,
99554
+ "mcp.tool.server": "mcp",
99555
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
99556
+ "iteration": iteration
99557
+ });
99558
+ }
99559
+ } else if (phase === "end" && endData) {
99560
+ const { result, success, durationMs, error: error2 } = endData;
99561
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolEnd === "function") {
99562
+ this.tracer.recordMcpToolEnd(this.sessionId, iteration, toolName, "mcp", result, success, durationMs, error2);
99563
+ } else if (typeof this.tracer.recordMcpToolEnd === "function") {
99564
+ this.tracer.recordMcpToolEnd(toolName, "mcp", result, success, durationMs, error2, { iteration });
99565
+ } else {
99566
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
99567
+ this.tracer.addEvent("mcp.tool.end", {
99568
+ "mcp.tool.name": toolName,
99569
+ "mcp.tool.server": "mcp",
99570
+ "mcp.tool.result": resultStr.substring(0, 1e4),
99571
+ "mcp.tool.result.length": resultStr.length,
99572
+ "mcp.tool.duration_ms": durationMs,
99573
+ "mcp.tool.success": success,
99574
+ "mcp.tool.error": error2,
99575
+ "iteration": iteration
99576
+ });
99577
+ }
99578
+ }
99579
+ }
99580
+ /**
99581
+ * Record iteration lifecycle event for telemetry
99582
+ * @param {string} phase - 'end' (start is already handled elsewhere)
99583
+ * @param {number} iteration - Current iteration number
99584
+ * @param {Object} data - Additional iteration data
99585
+ * @private
99586
+ */
99587
+ _recordIterationTelemetry(phase, iteration, data2 = {}) {
99588
+ if (!this.tracer) return;
99589
+ if (typeof this.tracer.recordIterationEvent === "function") {
99590
+ this.tracer.recordIterationEvent(phase, iteration, data2);
99591
+ } else {
99592
+ this.tracer.addEvent(`iteration.${phase}`, {
99593
+ "iteration": iteration,
99594
+ ...data2
99595
+ });
99596
+ }
99597
+ }
99332
99598
  /**
99333
99599
  * Initialize the agent asynchronously (must be called after constructor)
99334
99600
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -99424,8 +99690,9 @@ var init_ProbeAgent = __esm({
99424
99690
  const configOptions = {
99425
99691
  sessionId: this.sessionId,
99426
99692
  debug: this.debug,
99427
- // Use explicit cwd if set, otherwise fall back to first allowed folder
99428
- cwd: this.cwd || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd()),
99693
+ // Use cwd (which defaults to workspaceRoot in constructor)
99694
+ cwd: this.cwd,
99695
+ workspaceRoot: this.workspaceRoot,
99429
99696
  allowedFolders: this.allowedFolders,
99430
99697
  outline: this.outline,
99431
99698
  searchDelegate: this.searchDelegate,
@@ -100112,16 +100379,16 @@ var init_ProbeAgent = __esm({
100112
100379
  let absolutePath;
100113
100380
  let isPathAllowed2 = false;
100114
100381
  if ((0, import_path17.isAbsolute)(imagePath)) {
100115
- absolutePath = (0, import_path17.normalize)((0, import_path17.resolve)(imagePath));
100382
+ absolutePath = safeRealpath((0, import_path17.resolve)(imagePath));
100116
100383
  isPathAllowed2 = allowedDirs.some((dir) => {
100117
- const normalizedDir = (0, import_path17.normalize)((0, import_path17.resolve)(dir));
100118
- return absolutePath === normalizedDir || absolutePath.startsWith(normalizedDir + import_path17.sep);
100384
+ const resolvedDir = safeRealpath(dir);
100385
+ return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + import_path17.sep);
100119
100386
  });
100120
100387
  } else {
100121
100388
  for (const dir of allowedDirs) {
100122
- const normalizedDir = (0, import_path17.normalize)((0, import_path17.resolve)(dir));
100123
- const resolvedPath2 = (0, import_path17.normalize)((0, import_path17.resolve)(dir, imagePath));
100124
- if (resolvedPath2 === normalizedDir || resolvedPath2.startsWith(normalizedDir + import_path17.sep)) {
100389
+ const resolvedDir = safeRealpath(dir);
100390
+ const resolvedPath2 = safeRealpath((0, import_path17.resolve)(dir, imagePath));
100391
+ if (resolvedPath2 === resolvedDir || resolvedPath2.startsWith(resolvedDir + import_path17.sep)) {
100125
100392
  absolutePath = resolvedPath2;
100126
100393
  isPathAllowed2 = true;
100127
100394
  break;
@@ -100304,7 +100571,7 @@ var init_ProbeAgent = __esm({
100304
100571
  if (this._architectureContextLoaded) {
100305
100572
  return this.architectureContext;
100306
100573
  }
100307
- const rootDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
100574
+ const rootDirectory = this.workspaceRoot || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd());
100308
100575
  const configuredName = typeof this.architectureFileName === "string" ? this.architectureFileName.trim() : "";
100309
100576
  const hasConfiguredName = !!configuredName;
100310
100577
  let guidanceCandidates = [];
@@ -100441,6 +100708,9 @@ ${this.architectureContext.content}
100441
100708
  `;
100442
100709
  }
100443
100710
  _getSkillsRepoRoot() {
100711
+ if (this.workspaceRoot) {
100712
+ return (0, import_path17.resolve)(this.workspaceRoot);
100713
+ }
100444
100714
  if (this.allowedFolders && this.allowedFolders.length > 0) {
100445
100715
  return (0, import_path17.resolve)(this.allowedFolders[0]);
100446
100716
  }
@@ -100508,7 +100778,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
100508
100778
 
100509
100779
  # Repository Structure
100510
100780
  `;
100511
- systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}
100781
+ systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
100512
100782
 
100513
100783
  `;
100514
100784
  systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
@@ -100561,7 +100831,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
100561
100831
 
100562
100832
  # Repository Structure
100563
100833
  `;
100564
- systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}
100834
+ systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
100565
100835
 
100566
100836
  `;
100567
100837
  systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
@@ -100860,21 +101130,34 @@ For MCP tools, use JSON format within the params tag, e.g.:
100860
101130
  `;
100861
101131
  }
100862
101132
  }
100863
- const searchDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
101133
+ const searchDirectory = this.workspaceRoot;
100864
101134
  if (this.debug) {
100865
- console.log(`[DEBUG] Generating file list for base directory: ${searchDirectory}...`);
101135
+ console.log(`[DEBUG] Generating file list for workspace root: ${searchDirectory}...`);
101136
+ }
101137
+ const relativeWorkspaces = this.allowedFolders.map((f5) => {
101138
+ const rel = toRelativePath(f5, this.workspaceRoot);
101139
+ if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
101140
+ return "./" + rel;
101141
+ }
101142
+ return rel;
101143
+ }).filter((f5) => f5 && f5 !== ".");
101144
+ let workspaceDesc;
101145
+ if (relativeWorkspaces.length === 0) {
101146
+ workspaceDesc = ". (current directory)";
101147
+ } else {
101148
+ workspaceDesc = relativeWorkspaces.join(", ");
100866
101149
  }
100867
101150
  try {
100868
101151
  const files = await listFilesByLevel({
100869
101152
  directory: searchDirectory,
100870
101153
  maxFiles: 100,
100871
101154
  respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === "",
100872
- cwd: process.cwd()
101155
+ cwd: this.workspaceRoot
100873
101156
  });
100874
101157
  systemMessage += `
100875
101158
  # Repository Structure
100876
101159
 
100877
- You are working with a repository located at: ${searchDirectory}
101160
+ You are working with a workspace. Available paths: ${workspaceDesc}
100878
101161
 
100879
101162
  Here's an overview of the repository structure (showing up to 100 most relevant files):
100880
101163
 
@@ -100890,15 +101173,22 @@ ${files}
100890
101173
  systemMessage += `
100891
101174
  # Repository Structure
100892
101175
 
100893
- You are working with a repository located at: ${searchDirectory}
101176
+ You are working with a workspace. Available paths: ${workspaceDesc}
100894
101177
 
100895
101178
  `;
100896
101179
  }
100897
101180
  await this.loadArchitectureContext();
100898
101181
  systemMessage += this.getArchitectureSection();
100899
101182
  if (this.allowedFolders.length > 0) {
101183
+ const relativeAllowed = this.allowedFolders.map((f5) => {
101184
+ const rel = toRelativePath(f5, this.workspaceRoot);
101185
+ if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
101186
+ return "./" + rel;
101187
+ }
101188
+ return rel;
101189
+ });
100900
101190
  systemMessage += `
100901
- **Important**: For security reasons, you can only search within these allowed folders: ${this.allowedFolders.join(", ")}
101191
+ **Important**: For security reasons, you can only access these paths: ${relativeAllowed.join(", ")}
100902
101192
 
100903
101193
  `;
100904
101194
  }
@@ -101283,8 +101573,12 @@ You are working with a repository located at: ${searchDirectory}
101283
101573
  }
101284
101574
  const nativeTools = validTools;
101285
101575
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
101576
+ if (parsedTool?.thinkingContent) {
101577
+ this._recordThinkingTelemetry(parsedTool.thinkingContent, currentIteration);
101578
+ }
101286
101579
  if (parsedTool) {
101287
101580
  const { toolName, params } = parsedTool;
101581
+ this._recordToolDecisionTelemetry(toolName, params, assistantResponseContent.length, currentIteration);
101288
101582
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
101289
101583
  if (toolName === "attempt_completion") {
101290
101584
  completionAttempted = true;
@@ -101361,6 +101655,8 @@ You are working with a repository located at: ${searchDirectory}
101361
101655
  } else {
101362
101656
  const { type } = parsedTool;
101363
101657
  if (type === "mcp" && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
101658
+ const mcpStartTime = Date.now();
101659
+ this._recordMcpToolTelemetry("start", toolName, params, currentIteration);
101364
101660
  try {
101365
101661
  if (this.debug) {
101366
101662
  console.error(`
@@ -101390,6 +101686,13 @@ You are working with a repository located at: ${searchDirectory}
101390
101686
  } catch (truncateError) {
101391
101687
  console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
101392
101688
  }
101689
+ const mcpDurationMs = Date.now() - mcpStartTime;
101690
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
101691
+ result: toolResultContent,
101692
+ success: true,
101693
+ durationMs: mcpDurationMs,
101694
+ error: null
101695
+ });
101393
101696
  if (this.debug) {
101394
101697
  const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + "..." : toolResultContent;
101395
101698
  console.error(`[DEBUG] ========================================`);
@@ -101399,10 +101702,18 @@ You are working with a repository located at: ${searchDirectory}
101399
101702
  console.error(`[DEBUG] ========================================
101400
101703
  `);
101401
101704
  }
101705
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
101402
101706
  currentMessages.push({ role: "user", content: `<tool_result>
101403
101707
  ${toolResultContent}
101404
101708
  </tool_result>` });
101405
101709
  } catch (error2) {
101710
+ const mcpDurationMs = Date.now() - mcpStartTime;
101711
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
101712
+ result: null,
101713
+ success: false,
101714
+ durationMs: mcpDurationMs,
101715
+ error: error2.message
101716
+ });
101406
101717
  console.error(`Error executing MCP tool ${toolName}:`, error2);
101407
101718
  if (this.debug) {
101408
101719
  console.error(`[DEBUG] ========================================`);
@@ -101412,17 +101723,18 @@ ${toolResultContent}
101412
101723
  `);
101413
101724
  }
101414
101725
  const errorXml = formatErrorForAI(error2);
101726
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
101415
101727
  currentMessages.push({ role: "user", content: `<tool_result>
101416
101728
  ${errorXml}
101417
101729
  </tool_result>` });
101418
101730
  }
101419
101731
  } else if (this.toolImplementations[toolName]) {
101420
101732
  try {
101421
- let resolvedWorkingDirectory = this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
101733
+ let resolvedWorkingDirectory = this.workspaceRoot || this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
101422
101734
  if (params.workingDirectory) {
101423
- const requestedDir = (0, import_path17.isAbsolute)(params.workingDirectory) ? (0, import_path17.resolve)(params.workingDirectory) : (0, import_path17.resolve)(resolvedWorkingDirectory, params.workingDirectory);
101735
+ const requestedDir = safeRealpath((0, import_path17.isAbsolute)(params.workingDirectory) ? (0, import_path17.resolve)(params.workingDirectory) : (0, import_path17.resolve)(resolvedWorkingDirectory, params.workingDirectory));
101424
101736
  const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 || this.allowedFolders.some((folder) => {
101425
- const resolvedFolder = (0, import_path17.resolve)(folder);
101737
+ const resolvedFolder = safeRealpath(folder);
101426
101738
  return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + import_path17.sep);
101427
101739
  });
101428
101740
  if (isWithinAllowed) {
@@ -101494,6 +101806,7 @@ ${errorXml}
101494
101806
  return await this.toolImplementations[toolName].execute(toolParams);
101495
101807
  };
101496
101808
  let toolResult;
101809
+ const toolStartTime = Date.now();
101497
101810
  try {
101498
101811
  if (this.tracer) {
101499
101812
  toolResult = await this.tracer.withSpan("tool.call", executeToolCall, {
@@ -101504,6 +101817,8 @@ ${errorXml}
101504
101817
  } else {
101505
101818
  toolResult = await executeToolCall();
101506
101819
  }
101820
+ const toolDurationMs = Date.now() - toolStartTime;
101821
+ this._recordToolResultTelemetry(toolName, toolResult, true, toolDurationMs, currentIteration);
101507
101822
  if (this.debug) {
101508
101823
  const resultPreview = typeof toolResult === "string" ? toolResult.length > 500 ? toolResult.substring(0, 500) + "..." : toolResult : toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + "..." : "No Result";
101509
101824
  console.error(`[DEBUG] ========================================`);
@@ -101560,6 +101875,20 @@ ${toolResultContent}
101560
101875
  role: "user",
101561
101876
  content: toolResultMessage
101562
101877
  });
101878
+ if (this.tracer) {
101879
+ if (typeof this.tracer.recordConversationTurn === "function") {
101880
+ this.tracer.recordConversationTurn("assistant", assistantResponseContent, {
101881
+ iteration: currentIteration,
101882
+ has_tool_call: true,
101883
+ tool_name: toolName
101884
+ });
101885
+ this.tracer.recordConversationTurn("tool_result", toolResultContent, {
101886
+ iteration: currentIteration,
101887
+ tool_name: toolName,
101888
+ tool_success: true
101889
+ });
101890
+ }
101891
+ }
101563
101892
  if (this.debug) {
101564
101893
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
101565
101894
  }
@@ -101630,6 +101959,7 @@ ${errorXml}
101630
101959
  if (this.debug) {
101631
101960
  console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
101632
101961
  }
101962
+ this._recordErrorTelemetry("wrapped_tool", "Tool call wrapped in markdown", { toolName: wrappedToolName }, currentIteration);
101633
101963
  const toolError = new ParameterError(
101634
101964
  `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
101635
101965
  {
@@ -101655,6 +101985,7 @@ ${formatErrorForAI(toolError)}
101655
101985
  if (this.debug) {
101656
101986
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
101657
101987
  }
101988
+ this._recordErrorTelemetry("unrecognized_tool", `Unknown tool: ${unrecognizedTool}`, { toolName: unrecognizedTool, validTools }, currentIteration);
101658
101989
  const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
101659
101990
  suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
101660
101991
  });
@@ -101662,6 +101993,7 @@ ${formatErrorForAI(toolError)}
101662
101993
  ${formatErrorForAI(toolError)}
101663
101994
  </tool_result>`;
101664
101995
  } else {
101996
+ this._recordErrorTelemetry("no_tool_call", "AI response did not contain tool call", { responsePreview: assistantResponseContent.substring(0, 500) }, currentIteration);
101665
101997
  if (currentIteration >= maxIterations) {
101666
101998
  let cleanedResponse = assistantResponseContent;
101667
101999
  cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
@@ -101737,6 +102069,7 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
101737
102069
  sameFormatErrorCount++;
101738
102070
  if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
101739
102071
  const errorDesc = isWrapped ? "wrapped tool format" : unrecognizedTool;
102072
+ this._recordErrorTelemetry("circuit_breaker", "Format error limit exceeded", { formatErrorCount: sameFormatErrorCount, errorCategory }, currentIteration);
101740
102073
  console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
101741
102074
  finalResult = `Error: Unable to complete request. The AI model repeatedly used incorrect tool call format (${errorDesc}). Please try rephrasing your question or using a different model.`;
101742
102075
  break;
@@ -101750,6 +102083,10 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
101750
102083
  sameFormatErrorCount = 0;
101751
102084
  }
101752
102085
  }
102086
+ this._recordIterationTelemetry("end", currentIteration, {
102087
+ "iteration.completed": completionAttempted,
102088
+ "iteration.message_count": currentMessages.length
102089
+ });
101753
102090
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
101754
102091
  const messagesBefore = currentMessages.length;
101755
102092
  const systemMsg = currentMessages[0];
@@ -101866,7 +102203,7 @@ Convert your previous response content into actual JSON data that follows this s
101866
102203
  }
101867
102204
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
101868
102205
  debug: this.debug,
101869
- path: this.allowedFolders[0],
102206
+ path: this.workspaceRoot || this.allowedFolders[0],
101870
102207
  provider: this.clientApiProvider,
101871
102208
  model: this.model,
101872
102209
  tracer: this.tracer
@@ -101939,7 +102276,7 @@ Convert your previous response content into actual JSON data that follows this s
101939
102276
  }
101940
102277
  const { JsonFixingAgent: JsonFixingAgent2 } = await Promise.resolve().then(() => (init_schemaUtils(), schemaUtils_exports));
101941
102278
  const jsonFixer = new JsonFixingAgent2({
101942
- path: this.allowedFolders[0],
102279
+ path: this.workspaceRoot || this.allowedFolders[0],
101943
102280
  provider: this.clientApiProvider,
101944
102281
  model: this.model,
101945
102282
  debug: this.debug,
@@ -102008,7 +102345,7 @@ Convert your previous response content into actual JSON data that follows this s
102008
102345
  }
102009
102346
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
102010
102347
  debug: this.debug,
102011
- path: this.allowedFolders[0],
102348
+ path: this.workspaceRoot || this.allowedFolders[0],
102012
102349
  provider: this.clientApiProvider,
102013
102350
  model: this.model,
102014
102351
  tracer: this.tracer
@@ -102140,7 +102477,7 @@ Convert your previous response content into actual JSON data that follows this s
102140
102477
  }
102141
102478
  const finalMermaidValidation = await validateAndFixMermaidResponse(finalResult, {
102142
102479
  debug: this.debug,
102143
- path: this.allowedFolders[0],
102480
+ path: this.workspaceRoot || this.allowedFolders[0],
102144
102481
  provider: this.clientApiProvider,
102145
102482
  model: this.model,
102146
102483
  tracer: this.tracer
@@ -102290,8 +102627,7 @@ Convert your previous response content into actual JSON data that follows this s
102290
102627
  allowEdit: this.allowEdit,
102291
102628
  enableDelegate: this.enableDelegate,
102292
102629
  architectureFileName: this.architectureFileName,
102293
- path: this.allowedFolders[0],
102294
- // Use first allowed folder as primary path
102630
+ // Pass allowedFolders which will recompute workspaceRoot correctly
102295
102631
  allowedFolders: [...this.allowedFolders],
102296
102632
  cwd: this.cwd,
102297
102633
  // Preserve explicit working directory