@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
@@ -3288,7 +3288,26 @@ var init_error_types = __esm({
3288
3288
 
3289
3289
  // src/utils/path-validation.js
3290
3290
  import path4 from "path";
3291
- import { promises as fs5 } from "fs";
3291
+ import { promises as fs5, realpathSync } from "fs";
3292
+ function safeRealpath(inputPath) {
3293
+ try {
3294
+ return realpathSync(inputPath);
3295
+ } catch (error) {
3296
+ const normalized = path4.normalize(inputPath);
3297
+ const parts = normalized.split(path4.sep);
3298
+ for (let i = parts.length - 1; i >= 0; i--) {
3299
+ const ancestorPath = parts.slice(0, i).join(path4.sep) || path4.sep;
3300
+ try {
3301
+ const resolvedAncestor = realpathSync(ancestorPath);
3302
+ const remainingParts = parts.slice(i);
3303
+ return path4.join(resolvedAncestor, ...remainingParts);
3304
+ } catch (ancestorError) {
3305
+ continue;
3306
+ }
3307
+ }
3308
+ return normalized;
3309
+ }
3310
+ }
3292
3311
  async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
3293
3312
  const targetPath = inputPath || defaultPath;
3294
3313
  const normalizedPath = path4.normalize(path4.resolve(targetPath));
@@ -3321,6 +3340,53 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
3321
3340
  }
3322
3341
  return normalizedPath;
3323
3342
  }
3343
+ function getCommonPrefix(folders) {
3344
+ if (!folders || folders.length === 0) {
3345
+ return process.cwd();
3346
+ }
3347
+ if (folders.length === 1) {
3348
+ return safeRealpath(folders[0]);
3349
+ }
3350
+ const normalized = folders.map((f) => safeRealpath(f));
3351
+ const segments = normalized.map((f) => f.split(path4.sep));
3352
+ const minLen = Math.min(...segments.map((s) => s.length));
3353
+ const commonSegments = [];
3354
+ for (let i = 0; i < minLen; i++) {
3355
+ const segment = segments[0][i];
3356
+ if (segments.every((s) => s[i] === segment)) {
3357
+ commonSegments.push(segment);
3358
+ } else {
3359
+ break;
3360
+ }
3361
+ }
3362
+ if (commonSegments.length === 0) {
3363
+ return normalized[0];
3364
+ }
3365
+ if (commonSegments.length === 1 && /^[a-zA-Z]:$/.test(commonSegments[0])) {
3366
+ return normalized[0];
3367
+ }
3368
+ if (commonSegments.length === 1 && commonSegments[0] === "") {
3369
+ return normalized[0];
3370
+ }
3371
+ return commonSegments.join(path4.sep);
3372
+ }
3373
+ function toRelativePath(absolutePath, workspaceRoot) {
3374
+ if (!absolutePath || !workspaceRoot) {
3375
+ return absolutePath;
3376
+ }
3377
+ let normalized = safeRealpath(absolutePath);
3378
+ let normalizedRoot = safeRealpath(workspaceRoot);
3379
+ while (normalizedRoot.length > 1 && normalizedRoot.endsWith(path4.sep)) {
3380
+ normalizedRoot = normalizedRoot.slice(0, -1);
3381
+ }
3382
+ if (normalized === normalizedRoot) {
3383
+ return ".";
3384
+ }
3385
+ if (normalized.startsWith(normalizedRoot + path4.sep)) {
3386
+ return path4.relative(normalizedRoot, normalized);
3387
+ }
3388
+ return absolutePath;
3389
+ }
3324
3390
  var init_path_validation = __esm({
3325
3391
  "src/utils/path-validation.js"() {
3326
3392
  "use strict";
@@ -4515,6 +4581,7 @@ async function analyzeAll(options) {
4515
4581
  sessionId,
4516
4582
  debug = false,
4517
4583
  cwd,
4584
+ workspaceRoot,
4518
4585
  allowedFolders,
4519
4586
  provider,
4520
4587
  model,
@@ -4527,10 +4594,11 @@ async function analyzeAll(options) {
4527
4594
  if (!question) {
4528
4595
  throw new Error('The "question" parameter is required.');
4529
4596
  }
4597
+ const effectiveWorkspaceRoot = workspaceRoot || cwd || allowedFolders?.[0] || path9;
4530
4598
  const delegateOptions = {
4531
4599
  debug,
4532
4600
  sessionId,
4533
- path: allowedFolders?.[0] || cwd || path9,
4601
+ path: effectiveWorkspaceRoot,
4534
4602
  allowedFolders,
4535
4603
  provider,
4536
4604
  model,
@@ -8747,29 +8815,33 @@ import { dirname, resolve, isAbsolute, sep } from "path";
8747
8815
  import { existsSync } from "fs";
8748
8816
  function isPathAllowed(filePath, allowedFolders) {
8749
8817
  if (!allowedFolders || allowedFolders.length === 0) {
8750
- const resolvedPath2 = resolve(filePath);
8751
- const cwd = resolve(process.cwd());
8818
+ const resolvedPath2 = safeRealpath(filePath);
8819
+ const cwd = safeRealpath(process.cwd());
8752
8820
  return resolvedPath2 === cwd || resolvedPath2.startsWith(cwd + sep);
8753
8821
  }
8754
- const resolvedPath = resolve(filePath);
8822
+ const resolvedPath = safeRealpath(filePath);
8755
8823
  return allowedFolders.some((folder) => {
8756
- const allowedPath = resolve(folder);
8824
+ const allowedPath = safeRealpath(folder);
8757
8825
  return resolvedPath === allowedPath || resolvedPath.startsWith(allowedPath + sep);
8758
8826
  });
8759
8827
  }
8760
8828
  function parseFileToolOptions(options = {}) {
8829
+ const allowedFolders = options.allowedFolders || [];
8761
8830
  return {
8762
8831
  debug: options.debug || false,
8763
- allowedFolders: options.allowedFolders || [],
8764
- cwd: options.cwd
8832
+ allowedFolders,
8833
+ cwd: options.cwd,
8834
+ // Consistent fallback chain: workspaceRoot > cwd > allowedFolders[0] > process.cwd()
8835
+ workspaceRoot: options.workspaceRoot || options.cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd()
8765
8836
  };
8766
8837
  }
8767
8838
  var editTool, createTool, editSchema, createSchema, editDescription, createDescription, editToolDefinition, createToolDefinition;
8768
8839
  var init_edit = __esm({
8769
8840
  "src/tools/edit.js"() {
8770
8841
  "use strict";
8842
+ init_path_validation();
8771
8843
  editTool = (options = {}) => {
8772
- const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
8844
+ const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
8773
8845
  return tool({
8774
8846
  name: "edit",
8775
8847
  description: `Edit files using exact string replacement (Claude Code style).
@@ -8825,7 +8897,8 @@ Important:
8825
8897
  console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
8826
8898
  }
8827
8899
  if (!isPathAllowed(resolvedPath, allowedFolders)) {
8828
- return `Error editing file: Permission denied - ${file_path} is outside allowed directories`;
8900
+ const relativePath = toRelativePath(resolvedPath, workspaceRoot);
8901
+ return `Error editing file: Permission denied - ${relativePath} is outside allowed directories`;
8829
8902
  }
8830
8903
  if (!existsSync(resolvedPath)) {
8831
8904
  return `Error editing file: File not found - ${file_path}`;
@@ -8861,7 +8934,7 @@ Important:
8861
8934
  });
8862
8935
  };
8863
8936
  createTool = (options = {}) => {
8864
- const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
8937
+ const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
8865
8938
  return tool({
8866
8939
  name: "create",
8867
8940
  description: `Create new files with specified content.
@@ -8909,7 +8982,8 @@ Important:
8909
8982
  console.error(`[Create] Attempting to create file: ${resolvedPath}`);
8910
8983
  }
8911
8984
  if (!isPathAllowed(resolvedPath, allowedFolders)) {
8912
- return `Error creating file: Permission denied - ${file_path} is outside allowed directories`;
8985
+ const relativePath = toRelativePath(resolvedPath, workspaceRoot);
8986
+ return `Error creating file: Permission denied - ${relativePath} is outside allowed directories`;
8913
8987
  }
8914
8988
  if (existsSync(resolvedPath) && !overwrite) {
8915
8989
  return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
@@ -10471,7 +10545,7 @@ var init_vercel = __esm({
10471
10545
  });
10472
10546
  };
10473
10547
  delegateTool = (options = {}) => {
10474
- const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
10548
+ const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
10475
10549
  return tool2({
10476
10550
  name: "delegate",
10477
10551
  description: delegateDescription,
@@ -10504,8 +10578,8 @@ var init_vercel = __esm({
10504
10578
  if (searchDelegate !== void 0 && typeof searchDelegate !== "boolean") {
10505
10579
  throw new TypeError("searchDelegate must be a boolean if provided");
10506
10580
  }
10507
- const workspaceRoot = allowedFolders && allowedFolders[0];
10508
- const effectivePath = path9 || workspaceRoot || cwd;
10581
+ const effectiveWorkspaceRoot = workspaceRoot || allowedFolders && allowedFolders[0];
10582
+ const effectivePath = path9 || effectiveWorkspaceRoot || cwd;
10509
10583
  if (debug) {
10510
10584
  console.error(`Executing delegate with task: "${task.substring(0, 100)}${task.length > 100 ? "..." : ""}"`);
10511
10585
  if (parentSessionId) {
@@ -10542,7 +10616,7 @@ var init_vercel = __esm({
10542
10616
  });
10543
10617
  };
10544
10618
  analyzeAllTool = (options = {}) => {
10545
- const { sessionId, debug = false, delegationManager = null } = options;
10619
+ const { sessionId, debug = false, delegationManager = null, workspaceRoot } = options;
10546
10620
  return tool2({
10547
10621
  name: "analyze_all",
10548
10622
  description: analyzeAllDescription,
@@ -10561,12 +10635,14 @@ var init_vercel = __esm({
10561
10635
  console.error(`[analyze_all] Question: ${question}`);
10562
10636
  console.error(`[analyze_all] Path: ${searchPath}`);
10563
10637
  }
10638
+ const effectiveWorkspaceRoot = workspaceRoot || options.cwd || options.allowedFolders && options.allowedFolders[0];
10564
10639
  const result = await analyzeAll({
10565
10640
  question,
10566
10641
  path: searchPath,
10567
10642
  sessionId,
10568
10643
  debug,
10569
10644
  cwd: options.cwd,
10645
+ workspaceRoot: effectiveWorkspaceRoot,
10570
10646
  allowedFolders: options.allowedFolders,
10571
10647
  provider: options.provider,
10572
10648
  model: options.model,
@@ -12042,14 +12118,17 @@ var init_bash = __esm({
12042
12118
  "use strict";
12043
12119
  init_bashPermissions();
12044
12120
  init_bashExecutor();
12121
+ init_path_validation();
12045
12122
  bashTool = (options = {}) => {
12046
12123
  const {
12047
12124
  bashConfig = {},
12048
12125
  debug = false,
12049
12126
  cwd,
12050
12127
  allowedFolders = [],
12128
+ workspaceRoot: providedWorkspaceRoot,
12051
12129
  tracer = null
12052
12130
  } = options;
12131
+ const workspaceRoot = providedWorkspaceRoot || cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd();
12053
12132
  const permissionChecker = new BashPermissionChecker({
12054
12133
  allow: bashConfig.allow,
12055
12134
  deny: bashConfig.deny,
@@ -12065,6 +12144,9 @@ var init_bash = __esm({
12065
12144
  if (cwd) {
12066
12145
  return cwd;
12067
12146
  }
12147
+ if (workspaceRoot) {
12148
+ return workspaceRoot;
12149
+ }
12068
12150
  if (allowedFolders && allowedFolders.length > 0) {
12069
12151
  return allowedFolders[0];
12070
12152
  }
@@ -12152,13 +12234,15 @@ For code exploration, try these safe alternatives:
12152
12234
  const defaultDir = getDefaultWorkingDirectory();
12153
12235
  const workingDir = workingDirectory ? isAbsolute3(workingDirectory) ? resolve4(workingDirectory) : resolve4(defaultDir, workingDirectory) : defaultDir;
12154
12236
  if (allowedFolders && allowedFolders.length > 0) {
12155
- const resolvedWorkingDir = resolve4(workingDir);
12237
+ const resolvedWorkingDir = safeRealpath(workingDir);
12156
12238
  const isAllowed = allowedFolders.some((folder) => {
12157
- const resolvedFolder = resolve4(folder);
12239
+ const resolvedFolder = safeRealpath(folder);
12158
12240
  return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + sep2);
12159
12241
  });
12160
12242
  if (!isAllowed) {
12161
- return `Error: Working directory "${workingDir}" is not within allowed folders: ${allowedFolders.join(", ")}`;
12243
+ const relativeDir = toRelativePath(workingDir, workspaceRoot);
12244
+ const relativeAllowed = allowedFolders.map((f) => toRelativePath(f, workspaceRoot));
12245
+ return `Error: Working directory "${relativeDir}" is not within allowed folders: ${relativeAllowed.join(", ")}`;
12162
12246
  }
12163
12247
  }
12164
12248
  const executionOptions = {
@@ -12713,6 +12797,183 @@ var init_simpleTelemetry = __esm({
12713
12797
  console.log("[Attributes]", attributes);
12714
12798
  }
12715
12799
  }
12800
+ /**
12801
+ * Hash content for deduplication/comparison purposes
12802
+ * @param {string} content - The content to hash
12803
+ * @returns {string} - Hex string hash
12804
+ */
12805
+ hashContent(content) {
12806
+ let hash = 0;
12807
+ const len = Math.min(content.length, 1e3);
12808
+ for (let i = 0; i < len; i++) {
12809
+ hash = (hash << 5) - hash + content.charCodeAt(i);
12810
+ hash |= 0;
12811
+ }
12812
+ return hash.toString(16);
12813
+ }
12814
+ /**
12815
+ * Record a conversation turn (assistant response or tool result)
12816
+ * @param {string} role - The role (assistant, tool_result)
12817
+ * @param {string} content - The turn content
12818
+ * @param {Object} metadata - Additional metadata
12819
+ */
12820
+ recordConversationTurn(role, content, metadata = {}) {
12821
+ if (!this.isEnabled()) return;
12822
+ this.addEvent(`conversation.turn.${role}`, {
12823
+ "session.id": this.sessionId,
12824
+ "conversation.role": role,
12825
+ "conversation.content": content.substring(0, 1e4),
12826
+ "conversation.content.length": content.length,
12827
+ "conversation.content.hash": this.hashContent(content),
12828
+ ...metadata
12829
+ });
12830
+ }
12831
+ /**
12832
+ * Record error events with classification
12833
+ * @param {string} errorType - The type of error (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker, etc.)
12834
+ * @param {Object} errorDetails - Error details including message, stack, context
12835
+ */
12836
+ recordErrorEvent(errorType, errorDetails = {}) {
12837
+ if (!this.isEnabled()) return;
12838
+ this.addEvent(`error.${errorType}`, {
12839
+ "session.id": this.sessionId,
12840
+ "error.type": errorType,
12841
+ "error.message": errorDetails.message?.substring(0, 1e3) || null,
12842
+ "error.stack": errorDetails.stack?.substring(0, 2e3) || null,
12843
+ "error.recoverable": errorDetails.recoverable ?? true,
12844
+ "error.context": JSON.stringify(errorDetails.context || {}).substring(0, 1e3),
12845
+ ...Object.fromEntries(
12846
+ Object.entries(errorDetails).filter(([k]) => !["message", "stack", "context", "recoverable"].includes(k)).map(([k, v]) => [`error.${k}`, v])
12847
+ )
12848
+ });
12849
+ }
12850
+ /**
12851
+ * Record AI thinking/reasoning content
12852
+ * @param {string} thinkingContent - The thinking content from AI response
12853
+ * @param {Object} metadata - Additional metadata
12854
+ */
12855
+ recordThinkingContent(thinkingContent, metadata = {}) {
12856
+ if (!this.isEnabled() || !thinkingContent) return;
12857
+ this.addEvent("ai.thinking", {
12858
+ "session.id": this.sessionId,
12859
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
12860
+ "ai.thinking.length": thinkingContent.length,
12861
+ "ai.thinking.hash": this.hashContent(thinkingContent),
12862
+ ...metadata
12863
+ });
12864
+ }
12865
+ /**
12866
+ * Record AI tool call decision
12867
+ * @param {string} toolName - The tool name AI decided to call
12868
+ * @param {Object} params - The parameters AI provided
12869
+ * @param {Object} metadata - Additional metadata
12870
+ */
12871
+ recordToolDecision(toolName, params, metadata = {}) {
12872
+ if (!this.isEnabled()) return;
12873
+ this.addEvent("ai.tool_decision", {
12874
+ "session.id": this.sessionId,
12875
+ "ai.tool_decision.name": toolName,
12876
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
12877
+ ...metadata
12878
+ });
12879
+ }
12880
+ /**
12881
+ * Record tool result after execution
12882
+ * @param {string} toolName - The tool that was executed
12883
+ * @param {string|Object} result - The tool result
12884
+ * @param {boolean} success - Whether the tool succeeded
12885
+ * @param {number} durationMs - Execution duration in milliseconds
12886
+ * @param {Object} metadata - Additional metadata
12887
+ */
12888
+ recordToolResult(toolName, result, success, durationMs, metadata = {}) {
12889
+ if (!this.isEnabled()) return;
12890
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
12891
+ this.addEvent("tool.result", {
12892
+ "session.id": this.sessionId,
12893
+ "tool.name": toolName,
12894
+ "tool.result": resultStr.substring(0, 1e4),
12895
+ "tool.result.length": resultStr.length,
12896
+ "tool.result.hash": this.hashContent(resultStr),
12897
+ "tool.duration_ms": durationMs,
12898
+ "tool.success": success,
12899
+ ...metadata
12900
+ });
12901
+ }
12902
+ /**
12903
+ * Record MCP tool execution start
12904
+ * @param {string} toolName - MCP tool name
12905
+ * @param {string} serverName - MCP server name
12906
+ * @param {Object} params - Tool parameters
12907
+ * @param {Object} metadata - Additional metadata
12908
+ */
12909
+ recordMcpToolStart(toolName, serverName, params, metadata = {}) {
12910
+ if (!this.isEnabled()) return;
12911
+ this.addEvent("mcp.tool.start", {
12912
+ "session.id": this.sessionId,
12913
+ "mcp.tool.name": toolName,
12914
+ "mcp.tool.server": serverName || "unknown",
12915
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
12916
+ ...metadata
12917
+ });
12918
+ }
12919
+ /**
12920
+ * Record MCP tool execution end
12921
+ * @param {string} toolName - MCP tool name
12922
+ * @param {string} serverName - MCP server name
12923
+ * @param {string|Object} result - Tool result
12924
+ * @param {boolean} success - Whether succeeded
12925
+ * @param {number} durationMs - Execution duration
12926
+ * @param {string} errorMessage - Error message if failed
12927
+ * @param {Object} metadata - Additional metadata
12928
+ */
12929
+ recordMcpToolEnd(toolName, serverName, result, success, durationMs, errorMessage = null, metadata = {}) {
12930
+ if (!this.isEnabled()) return;
12931
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
12932
+ this.addEvent("mcp.tool.end", {
12933
+ "session.id": this.sessionId,
12934
+ "mcp.tool.name": toolName,
12935
+ "mcp.tool.server": serverName || "unknown",
12936
+ "mcp.tool.result": resultStr.substring(0, 1e4),
12937
+ "mcp.tool.result.length": resultStr.length,
12938
+ "mcp.tool.duration_ms": durationMs,
12939
+ "mcp.tool.success": success,
12940
+ "mcp.tool.error": errorMessage,
12941
+ ...metadata
12942
+ });
12943
+ }
12944
+ /**
12945
+ * Record iteration lifecycle event
12946
+ * @param {string} eventType - start or end
12947
+ * @param {number} iteration - Iteration number
12948
+ * @param {Object} data - Additional data
12949
+ */
12950
+ recordIterationEvent(eventType, iteration, data = {}) {
12951
+ if (!this.isEnabled()) return;
12952
+ this.addEvent(`iteration.${eventType}`, {
12953
+ "session.id": this.sessionId,
12954
+ "iteration": iteration,
12955
+ ...data
12956
+ });
12957
+ }
12958
+ /**
12959
+ * Record per-turn token breakdown
12960
+ * @param {number} iteration - Iteration number
12961
+ * @param {Object} tokenData - Token metrics
12962
+ */
12963
+ recordTokenTurn(iteration, tokenData = {}) {
12964
+ if (!this.isEnabled()) return;
12965
+ this.addEvent("tokens.turn", {
12966
+ "session.id": this.sessionId,
12967
+ "iteration": iteration,
12968
+ "tokens.input": tokenData.inputTokens || 0,
12969
+ "tokens.output": tokenData.outputTokens || 0,
12970
+ "tokens.total": (tokenData.inputTokens || 0) + (tokenData.outputTokens || 0),
12971
+ "tokens.cache_read": tokenData.cacheReadTokens || 0,
12972
+ "tokens.cache_write": tokenData.cacheWriteTokens || 0,
12973
+ "tokens.context_used": tokenData.contextTokens || 0,
12974
+ "tokens.context_remaining": tokenData.maxContextTokens ? tokenData.maxContextTokens - (tokenData.contextTokens || 0) : null
12975
+ });
12976
+ }
12716
12977
  async withSpan(spanName, fn, attributes = {}) {
12717
12978
  if (!this.isEnabled()) {
12718
12979
  return fn();
@@ -16588,18 +16849,18 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
16588
16849
  import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
16589
16850
  import * as actualFS from "node:fs";
16590
16851
  import { lstat, readdir, readlink, realpath } from "node:fs/promises";
16591
- var 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;
16852
+ var 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;
16592
16853
  var init_esm4 = __esm({
16593
16854
  "node_modules/path-scurry/dist/esm/index.js"() {
16594
16855
  init_esm2();
16595
16856
  init_esm3();
16596
- realpathSync = rps.native;
16857
+ realpathSync2 = rps.native;
16597
16858
  defaultFS = {
16598
16859
  lstatSync,
16599
16860
  readdir: readdirCB,
16600
16861
  readdirSync,
16601
16862
  readlinkSync,
16602
- realpathSync,
16863
+ realpathSync: realpathSync2,
16603
16864
  promises: {
16604
16865
  lstat,
16605
16866
  readdir,
@@ -20419,11 +20680,12 @@ function createTools(configOptions) {
20419
20680
  return tools2;
20420
20681
  }
20421
20682
  function parseXmlToolCallWithThinking(xmlString, validTools) {
20422
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
20683
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
20423
20684
  if (recoveryResult) {
20424
- return recoveryResult;
20685
+ return { ...recoveryResult, thinkingContent };
20425
20686
  }
20426
- return parseXmlToolCall(cleanedXmlString, validTools);
20687
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
20688
+ return toolCall ? { ...toolCall, thinkingContent } : null;
20427
20689
  }
20428
20690
  var implementToolDefinition, listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
20429
20691
  var init_tools2 = __esm({
@@ -59717,25 +59979,27 @@ function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
59717
59979
  function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
59718
59980
  const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
59719
59981
  if (nativeResult) {
59720
- return { ...nativeResult, type: "native" };
59982
+ const { thinkingContent, ...rest } = nativeResult;
59983
+ return { ...rest, type: "native", thinkingContent };
59721
59984
  }
59722
59985
  if (mcpBridge) {
59723
59986
  const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
59724
59987
  if (mcpResult) {
59725
- return { ...mcpResult, type: "mcp" };
59988
+ const { thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, []);
59989
+ return { ...mcpResult, type: "mcp", thinkingContent };
59726
59990
  }
59727
59991
  }
59728
59992
  return null;
59729
59993
  }
59730
59994
  function parseNativeXmlToolWithThinking(xmlString, validTools) {
59731
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
59995
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
59732
59996
  if (recoveryResult) {
59733
- return recoveryResult;
59997
+ return { ...recoveryResult, thinkingContent };
59734
59998
  }
59735
59999
  for (const toolName of validTools) {
59736
60000
  const result = parseNativeXmlTool(cleanedXmlString, toolName);
59737
60001
  if (result) {
59738
- return result;
60002
+ return { ...result, thinkingContent };
59739
60003
  }
59740
60004
  }
59741
60005
  return null;
@@ -69991,6 +70255,7 @@ var init_ProbeAgent = __esm({
69991
70255
  init_FallbackManager();
69992
70256
  init_contextCompactor();
69993
70257
  init_error_types();
70258
+ init_path_validation();
69994
70259
  init_outputTruncator();
69995
70260
  init_delegate();
69996
70261
  init_tasks();
@@ -70108,7 +70373,8 @@ var init_ProbeAgent = __esm({
70108
70373
  } else {
70109
70374
  this.allowedFolders = [process.cwd()];
70110
70375
  }
70111
- this.cwd = options.cwd || null;
70376
+ this.workspaceRoot = getCommonPrefix(this.allowedFolders);
70377
+ this.cwd = options.cwd || this.workspaceRoot;
70112
70378
  this.clientApiProvider = options.provider || null;
70113
70379
  this.clientApiModel = options.model || null;
70114
70380
  this.clientApiKey = null;
@@ -70120,6 +70386,8 @@ var init_ProbeAgent = __esm({
70120
70386
  console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
70121
70387
  console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
70122
70388
  console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
70389
+ console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
70390
+ console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
70123
70391
  }
70124
70392
  this.initializeTools();
70125
70393
  this.history = [];
@@ -70197,6 +70465,181 @@ var init_ProbeAgent = __esm({
70197
70465
  _filterMcpTools(mcpToolNames) {
70198
70466
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
70199
70467
  }
70468
+ /**
70469
+ * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
70470
+ * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
70471
+ * @private
70472
+ */
70473
+ _isAppTracerStyle() {
70474
+ return this.tracer && typeof this.tracer.sessionSpans !== "undefined";
70475
+ }
70476
+ /**
70477
+ * Record an error classification event for telemetry
70478
+ * Provides unified error recording across all error types
70479
+ * @param {string} errorType - Error type (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker)
70480
+ * @param {string} message - Error message
70481
+ * @param {Object} context - Additional context data
70482
+ * @param {number} iteration - Current iteration number
70483
+ * @private
70484
+ */
70485
+ _recordErrorTelemetry(errorType, message, context, iteration) {
70486
+ if (!this.tracer) return;
70487
+ if (this._isAppTracerStyle() && typeof this.tracer.recordErrorClassification === "function") {
70488
+ this.tracer.recordErrorClassification(this.sessionId, iteration, errorType, {
70489
+ message,
70490
+ context
70491
+ });
70492
+ } else if (typeof this.tracer.recordErrorEvent === "function") {
70493
+ this.tracer.recordErrorEvent(errorType, {
70494
+ message,
70495
+ context: { ...context, iteration }
70496
+ });
70497
+ } else {
70498
+ this.tracer.addEvent(`error.${errorType}`, {
70499
+ "error.type": errorType,
70500
+ "error.message": message,
70501
+ "error.recoverable": errorType !== "circuit_breaker",
70502
+ "error.context": JSON.stringify(context).substring(0, 1e3),
70503
+ "iteration": iteration
70504
+ });
70505
+ }
70506
+ }
70507
+ /**
70508
+ * Record AI thinking content for telemetry
70509
+ * @param {string} thinkingContent - The thinking content
70510
+ * @param {number} iteration - Current iteration number
70511
+ * @private
70512
+ */
70513
+ _recordThinkingTelemetry(thinkingContent, iteration) {
70514
+ if (!this.tracer || !thinkingContent) return;
70515
+ if (this._isAppTracerStyle() && typeof this.tracer.recordThinkingContent === "function") {
70516
+ this.tracer.recordThinkingContent(this.sessionId, iteration, thinkingContent);
70517
+ } else if (typeof this.tracer.recordThinkingContent === "function") {
70518
+ this.tracer.recordThinkingContent(thinkingContent, { iteration });
70519
+ } else {
70520
+ this.tracer.addEvent("ai.thinking", {
70521
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
70522
+ "ai.thinking.length": thinkingContent.length,
70523
+ "iteration": iteration
70524
+ });
70525
+ }
70526
+ }
70527
+ /**
70528
+ * Record AI tool decision for telemetry
70529
+ * @param {string} toolName - The tool name
70530
+ * @param {Object} params - Tool parameters
70531
+ * @param {number} responseLength - Length of AI response
70532
+ * @param {number} iteration - Current iteration number
70533
+ * @private
70534
+ */
70535
+ _recordToolDecisionTelemetry(toolName, params, responseLength, iteration) {
70536
+ if (!this.tracer) return;
70537
+ if (this._isAppTracerStyle() && typeof this.tracer.recordAIToolDecision === "function") {
70538
+ this.tracer.recordAIToolDecision(this.sessionId, iteration, toolName, params);
70539
+ } else if (typeof this.tracer.recordToolDecision === "function") {
70540
+ this.tracer.recordToolDecision(toolName, params, {
70541
+ iteration,
70542
+ "ai.tool_decision.raw_response_length": responseLength
70543
+ });
70544
+ } else {
70545
+ this.tracer.addEvent("ai.tool_decision", {
70546
+ "ai.tool_decision.name": toolName,
70547
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
70548
+ "ai.tool_decision.raw_response_length": responseLength,
70549
+ "iteration": iteration
70550
+ });
70551
+ }
70552
+ }
70553
+ /**
70554
+ * Record tool result for telemetry
70555
+ * @param {string} toolName - The tool name
70556
+ * @param {string|Object} result - Tool result
70557
+ * @param {boolean} success - Whether tool succeeded
70558
+ * @param {number} durationMs - Execution duration in milliseconds
70559
+ * @param {number} iteration - Current iteration number
70560
+ * @private
70561
+ */
70562
+ _recordToolResultTelemetry(toolName, result, success, durationMs, iteration) {
70563
+ if (!this.tracer) return;
70564
+ if (this._isAppTracerStyle() && typeof this.tracer.recordToolResult === "function") {
70565
+ this.tracer.recordToolResult(this.sessionId, iteration, toolName, result, success, durationMs);
70566
+ } else if (typeof this.tracer.recordToolResult === "function") {
70567
+ this.tracer.recordToolResult(toolName, result, success, durationMs, { iteration });
70568
+ } else {
70569
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
70570
+ this.tracer.addEvent("tool.result", {
70571
+ "tool.name": toolName,
70572
+ "tool.result": resultStr.substring(0, 1e4),
70573
+ "tool.result.length": resultStr.length,
70574
+ "tool.duration_ms": durationMs,
70575
+ "tool.success": success,
70576
+ "iteration": iteration
70577
+ });
70578
+ }
70579
+ }
70580
+ /**
70581
+ * Record MCP tool lifecycle event for telemetry
70582
+ * @param {string} phase - 'start' or 'end'
70583
+ * @param {string} toolName - MCP tool name
70584
+ * @param {Object} params - Tool parameters (for start) or null (for end)
70585
+ * @param {number} iteration - Current iteration number
70586
+ * @param {Object} [endData] - Additional data for end phase (result, success, durationMs, error)
70587
+ * @private
70588
+ */
70589
+ _recordMcpToolTelemetry(phase, toolName, params, iteration, endData = null) {
70590
+ if (!this.tracer) return;
70591
+ if (phase === "start") {
70592
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolStart === "function") {
70593
+ this.tracer.recordMcpToolStart(this.sessionId, iteration, toolName, "mcp", params);
70594
+ } else if (typeof this.tracer.recordMcpToolStart === "function") {
70595
+ this.tracer.recordMcpToolStart(toolName, "mcp", params, { iteration });
70596
+ } else {
70597
+ this.tracer.addEvent("mcp.tool.start", {
70598
+ "mcp.tool.name": toolName,
70599
+ "mcp.tool.server": "mcp",
70600
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
70601
+ "iteration": iteration
70602
+ });
70603
+ }
70604
+ } else if (phase === "end" && endData) {
70605
+ const { result, success, durationMs, error } = endData;
70606
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolEnd === "function") {
70607
+ this.tracer.recordMcpToolEnd(this.sessionId, iteration, toolName, "mcp", result, success, durationMs, error);
70608
+ } else if (typeof this.tracer.recordMcpToolEnd === "function") {
70609
+ this.tracer.recordMcpToolEnd(toolName, "mcp", result, success, durationMs, error, { iteration });
70610
+ } else {
70611
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
70612
+ this.tracer.addEvent("mcp.tool.end", {
70613
+ "mcp.tool.name": toolName,
70614
+ "mcp.tool.server": "mcp",
70615
+ "mcp.tool.result": resultStr.substring(0, 1e4),
70616
+ "mcp.tool.result.length": resultStr.length,
70617
+ "mcp.tool.duration_ms": durationMs,
70618
+ "mcp.tool.success": success,
70619
+ "mcp.tool.error": error,
70620
+ "iteration": iteration
70621
+ });
70622
+ }
70623
+ }
70624
+ }
70625
+ /**
70626
+ * Record iteration lifecycle event for telemetry
70627
+ * @param {string} phase - 'end' (start is already handled elsewhere)
70628
+ * @param {number} iteration - Current iteration number
70629
+ * @param {Object} data - Additional iteration data
70630
+ * @private
70631
+ */
70632
+ _recordIterationTelemetry(phase, iteration, data = {}) {
70633
+ if (!this.tracer) return;
70634
+ if (typeof this.tracer.recordIterationEvent === "function") {
70635
+ this.tracer.recordIterationEvent(phase, iteration, data);
70636
+ } else {
70637
+ this.tracer.addEvent(`iteration.${phase}`, {
70638
+ "iteration": iteration,
70639
+ ...data
70640
+ });
70641
+ }
70642
+ }
70200
70643
  /**
70201
70644
  * Initialize the agent asynchronously (must be called after constructor)
70202
70645
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -70292,8 +70735,9 @@ var init_ProbeAgent = __esm({
70292
70735
  const configOptions = {
70293
70736
  sessionId: this.sessionId,
70294
70737
  debug: this.debug,
70295
- // Use explicit cwd if set, otherwise fall back to first allowed folder
70296
- cwd: this.cwd || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd()),
70738
+ // Use cwd (which defaults to workspaceRoot in constructor)
70739
+ cwd: this.cwd,
70740
+ workspaceRoot: this.workspaceRoot,
70297
70741
  allowedFolders: this.allowedFolders,
70298
70742
  outline: this.outline,
70299
70743
  searchDelegate: this.searchDelegate,
@@ -70980,16 +71424,16 @@ var init_ProbeAgent = __esm({
70980
71424
  let absolutePath;
70981
71425
  let isPathAllowed2 = false;
70982
71426
  if (isAbsolute5(imagePath)) {
70983
- absolutePath = normalize2(resolve6(imagePath));
71427
+ absolutePath = safeRealpath(resolve6(imagePath));
70984
71428
  isPathAllowed2 = allowedDirs.some((dir) => {
70985
- const normalizedDir = normalize2(resolve6(dir));
70986
- return absolutePath === normalizedDir || absolutePath.startsWith(normalizedDir + sep5);
71429
+ const resolvedDir = safeRealpath(dir);
71430
+ return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep5);
70987
71431
  });
70988
71432
  } else {
70989
71433
  for (const dir of allowedDirs) {
70990
- const normalizedDir = normalize2(resolve6(dir));
70991
- const resolvedPath = normalize2(resolve6(dir, imagePath));
70992
- if (resolvedPath === normalizedDir || resolvedPath.startsWith(normalizedDir + sep5)) {
71434
+ const resolvedDir = safeRealpath(dir);
71435
+ const resolvedPath = safeRealpath(resolve6(dir, imagePath));
71436
+ if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep5)) {
70993
71437
  absolutePath = resolvedPath;
70994
71438
  isPathAllowed2 = true;
70995
71439
  break;
@@ -71172,7 +71616,7 @@ var init_ProbeAgent = __esm({
71172
71616
  if (this._architectureContextLoaded) {
71173
71617
  return this.architectureContext;
71174
71618
  }
71175
- const rootDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
71619
+ const rootDirectory = this.workspaceRoot || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd());
71176
71620
  const configuredName = typeof this.architectureFileName === "string" ? this.architectureFileName.trim() : "";
71177
71621
  const hasConfiguredName = !!configuredName;
71178
71622
  let guidanceCandidates = [];
@@ -71309,6 +71753,9 @@ ${this.architectureContext.content}
71309
71753
  `;
71310
71754
  }
71311
71755
  _getSkillsRepoRoot() {
71756
+ if (this.workspaceRoot) {
71757
+ return resolve6(this.workspaceRoot);
71758
+ }
71312
71759
  if (this.allowedFolders && this.allowedFolders.length > 0) {
71313
71760
  return resolve6(this.allowedFolders[0]);
71314
71761
  }
@@ -71376,7 +71823,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
71376
71823
 
71377
71824
  # Repository Structure
71378
71825
  `;
71379
- systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}
71826
+ systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
71380
71827
 
71381
71828
  `;
71382
71829
  systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
@@ -71429,7 +71876,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
71429
71876
 
71430
71877
  # Repository Structure
71431
71878
  `;
71432
- systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}
71879
+ systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
71433
71880
 
71434
71881
  `;
71435
71882
  systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
@@ -71728,21 +72175,34 @@ For MCP tools, use JSON format within the params tag, e.g.:
71728
72175
  `;
71729
72176
  }
71730
72177
  }
71731
- const searchDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
72178
+ const searchDirectory = this.workspaceRoot;
71732
72179
  if (this.debug) {
71733
- console.log(`[DEBUG] Generating file list for base directory: ${searchDirectory}...`);
72180
+ console.log(`[DEBUG] Generating file list for workspace root: ${searchDirectory}...`);
72181
+ }
72182
+ const relativeWorkspaces = this.allowedFolders.map((f) => {
72183
+ const rel = toRelativePath(f, this.workspaceRoot);
72184
+ if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
72185
+ return "./" + rel;
72186
+ }
72187
+ return rel;
72188
+ }).filter((f) => f && f !== ".");
72189
+ let workspaceDesc;
72190
+ if (relativeWorkspaces.length === 0) {
72191
+ workspaceDesc = ". (current directory)";
72192
+ } else {
72193
+ workspaceDesc = relativeWorkspaces.join(", ");
71734
72194
  }
71735
72195
  try {
71736
72196
  const files = await listFilesByLevel({
71737
72197
  directory: searchDirectory,
71738
72198
  maxFiles: 100,
71739
72199
  respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === "",
71740
- cwd: process.cwd()
72200
+ cwd: this.workspaceRoot
71741
72201
  });
71742
72202
  systemMessage += `
71743
72203
  # Repository Structure
71744
72204
 
71745
- You are working with a repository located at: ${searchDirectory}
72205
+ You are working with a workspace. Available paths: ${workspaceDesc}
71746
72206
 
71747
72207
  Here's an overview of the repository structure (showing up to 100 most relevant files):
71748
72208
 
@@ -71758,15 +72218,22 @@ ${files}
71758
72218
  systemMessage += `
71759
72219
  # Repository Structure
71760
72220
 
71761
- You are working with a repository located at: ${searchDirectory}
72221
+ You are working with a workspace. Available paths: ${workspaceDesc}
71762
72222
 
71763
72223
  `;
71764
72224
  }
71765
72225
  await this.loadArchitectureContext();
71766
72226
  systemMessage += this.getArchitectureSection();
71767
72227
  if (this.allowedFolders.length > 0) {
72228
+ const relativeAllowed = this.allowedFolders.map((f) => {
72229
+ const rel = toRelativePath(f, this.workspaceRoot);
72230
+ if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
72231
+ return "./" + rel;
72232
+ }
72233
+ return rel;
72234
+ });
71768
72235
  systemMessage += `
71769
- **Important**: For security reasons, you can only search within these allowed folders: ${this.allowedFolders.join(", ")}
72236
+ **Important**: For security reasons, you can only access these paths: ${relativeAllowed.join(", ")}
71770
72237
 
71771
72238
  `;
71772
72239
  }
@@ -72151,8 +72618,12 @@ You are working with a repository located at: ${searchDirectory}
72151
72618
  }
72152
72619
  const nativeTools = validTools;
72153
72620
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
72621
+ if (parsedTool?.thinkingContent) {
72622
+ this._recordThinkingTelemetry(parsedTool.thinkingContent, currentIteration);
72623
+ }
72154
72624
  if (parsedTool) {
72155
72625
  const { toolName, params } = parsedTool;
72626
+ this._recordToolDecisionTelemetry(toolName, params, assistantResponseContent.length, currentIteration);
72156
72627
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
72157
72628
  if (toolName === "attempt_completion") {
72158
72629
  completionAttempted = true;
@@ -72229,6 +72700,8 @@ You are working with a repository located at: ${searchDirectory}
72229
72700
  } else {
72230
72701
  const { type } = parsedTool;
72231
72702
  if (type === "mcp" && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
72703
+ const mcpStartTime = Date.now();
72704
+ this._recordMcpToolTelemetry("start", toolName, params, currentIteration);
72232
72705
  try {
72233
72706
  if (this.debug) {
72234
72707
  console.error(`
@@ -72258,6 +72731,13 @@ You are working with a repository located at: ${searchDirectory}
72258
72731
  } catch (truncateError) {
72259
72732
  console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
72260
72733
  }
72734
+ const mcpDurationMs = Date.now() - mcpStartTime;
72735
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
72736
+ result: toolResultContent,
72737
+ success: true,
72738
+ durationMs: mcpDurationMs,
72739
+ error: null
72740
+ });
72261
72741
  if (this.debug) {
72262
72742
  const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + "..." : toolResultContent;
72263
72743
  console.error(`[DEBUG] ========================================`);
@@ -72267,10 +72747,18 @@ You are working with a repository located at: ${searchDirectory}
72267
72747
  console.error(`[DEBUG] ========================================
72268
72748
  `);
72269
72749
  }
72750
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
72270
72751
  currentMessages.push({ role: "user", content: `<tool_result>
72271
72752
  ${toolResultContent}
72272
72753
  </tool_result>` });
72273
72754
  } catch (error) {
72755
+ const mcpDurationMs = Date.now() - mcpStartTime;
72756
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
72757
+ result: null,
72758
+ success: false,
72759
+ durationMs: mcpDurationMs,
72760
+ error: error.message
72761
+ });
72274
72762
  console.error(`Error executing MCP tool ${toolName}:`, error);
72275
72763
  if (this.debug) {
72276
72764
  console.error(`[DEBUG] ========================================`);
@@ -72280,17 +72768,18 @@ ${toolResultContent}
72280
72768
  `);
72281
72769
  }
72282
72770
  const errorXml = formatErrorForAI(error);
72771
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
72283
72772
  currentMessages.push({ role: "user", content: `<tool_result>
72284
72773
  ${errorXml}
72285
72774
  </tool_result>` });
72286
72775
  }
72287
72776
  } else if (this.toolImplementations[toolName]) {
72288
72777
  try {
72289
- let resolvedWorkingDirectory = this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
72778
+ let resolvedWorkingDirectory = this.workspaceRoot || this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
72290
72779
  if (params.workingDirectory) {
72291
- const requestedDir = isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory);
72780
+ const requestedDir = safeRealpath(isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory));
72292
72781
  const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 || this.allowedFolders.some((folder) => {
72293
- const resolvedFolder = resolve6(folder);
72782
+ const resolvedFolder = safeRealpath(folder);
72294
72783
  return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + sep5);
72295
72784
  });
72296
72785
  if (isWithinAllowed) {
@@ -72362,6 +72851,7 @@ ${errorXml}
72362
72851
  return await this.toolImplementations[toolName].execute(toolParams);
72363
72852
  };
72364
72853
  let toolResult;
72854
+ const toolStartTime = Date.now();
72365
72855
  try {
72366
72856
  if (this.tracer) {
72367
72857
  toolResult = await this.tracer.withSpan("tool.call", executeToolCall, {
@@ -72372,6 +72862,8 @@ ${errorXml}
72372
72862
  } else {
72373
72863
  toolResult = await executeToolCall();
72374
72864
  }
72865
+ const toolDurationMs = Date.now() - toolStartTime;
72866
+ this._recordToolResultTelemetry(toolName, toolResult, true, toolDurationMs, currentIteration);
72375
72867
  if (this.debug) {
72376
72868
  const resultPreview = typeof toolResult === "string" ? toolResult.length > 500 ? toolResult.substring(0, 500) + "..." : toolResult : toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + "..." : "No Result";
72377
72869
  console.error(`[DEBUG] ========================================`);
@@ -72428,6 +72920,20 @@ ${toolResultContent}
72428
72920
  role: "user",
72429
72921
  content: toolResultMessage
72430
72922
  });
72923
+ if (this.tracer) {
72924
+ if (typeof this.tracer.recordConversationTurn === "function") {
72925
+ this.tracer.recordConversationTurn("assistant", assistantResponseContent, {
72926
+ iteration: currentIteration,
72927
+ has_tool_call: true,
72928
+ tool_name: toolName
72929
+ });
72930
+ this.tracer.recordConversationTurn("tool_result", toolResultContent, {
72931
+ iteration: currentIteration,
72932
+ tool_name: toolName,
72933
+ tool_success: true
72934
+ });
72935
+ }
72936
+ }
72431
72937
  if (this.debug) {
72432
72938
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
72433
72939
  }
@@ -72498,6 +73004,7 @@ ${errorXml}
72498
73004
  if (this.debug) {
72499
73005
  console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
72500
73006
  }
73007
+ this._recordErrorTelemetry("wrapped_tool", "Tool call wrapped in markdown", { toolName: wrappedToolName }, currentIteration);
72501
73008
  const toolError = new ParameterError(
72502
73009
  `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
72503
73010
  {
@@ -72523,6 +73030,7 @@ ${formatErrorForAI(toolError)}
72523
73030
  if (this.debug) {
72524
73031
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
72525
73032
  }
73033
+ this._recordErrorTelemetry("unrecognized_tool", `Unknown tool: ${unrecognizedTool}`, { toolName: unrecognizedTool, validTools }, currentIteration);
72526
73034
  const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
72527
73035
  suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
72528
73036
  });
@@ -72530,6 +73038,7 @@ ${formatErrorForAI(toolError)}
72530
73038
  ${formatErrorForAI(toolError)}
72531
73039
  </tool_result>`;
72532
73040
  } else {
73041
+ this._recordErrorTelemetry("no_tool_call", "AI response did not contain tool call", { responsePreview: assistantResponseContent.substring(0, 500) }, currentIteration);
72533
73042
  if (currentIteration >= maxIterations) {
72534
73043
  let cleanedResponse = assistantResponseContent;
72535
73044
  cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
@@ -72605,6 +73114,7 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
72605
73114
  sameFormatErrorCount++;
72606
73115
  if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
72607
73116
  const errorDesc = isWrapped ? "wrapped tool format" : unrecognizedTool;
73117
+ this._recordErrorTelemetry("circuit_breaker", "Format error limit exceeded", { formatErrorCount: sameFormatErrorCount, errorCategory }, currentIteration);
72608
73118
  console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
72609
73119
  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.`;
72610
73120
  break;
@@ -72618,6 +73128,10 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
72618
73128
  sameFormatErrorCount = 0;
72619
73129
  }
72620
73130
  }
73131
+ this._recordIterationTelemetry("end", currentIteration, {
73132
+ "iteration.completed": completionAttempted,
73133
+ "iteration.message_count": currentMessages.length
73134
+ });
72621
73135
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
72622
73136
  const messagesBefore = currentMessages.length;
72623
73137
  const systemMsg = currentMessages[0];
@@ -72734,7 +73248,7 @@ Convert your previous response content into actual JSON data that follows this s
72734
73248
  }
72735
73249
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
72736
73250
  debug: this.debug,
72737
- path: this.allowedFolders[0],
73251
+ path: this.workspaceRoot || this.allowedFolders[0],
72738
73252
  provider: this.clientApiProvider,
72739
73253
  model: this.model,
72740
73254
  tracer: this.tracer
@@ -72807,7 +73321,7 @@ Convert your previous response content into actual JSON data that follows this s
72807
73321
  }
72808
73322
  const { JsonFixingAgent: JsonFixingAgent2 } = await Promise.resolve().then(() => (init_schemaUtils(), schemaUtils_exports));
72809
73323
  const jsonFixer = new JsonFixingAgent2({
72810
- path: this.allowedFolders[0],
73324
+ path: this.workspaceRoot || this.allowedFolders[0],
72811
73325
  provider: this.clientApiProvider,
72812
73326
  model: this.model,
72813
73327
  debug: this.debug,
@@ -72876,7 +73390,7 @@ Convert your previous response content into actual JSON data that follows this s
72876
73390
  }
72877
73391
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
72878
73392
  debug: this.debug,
72879
- path: this.allowedFolders[0],
73393
+ path: this.workspaceRoot || this.allowedFolders[0],
72880
73394
  provider: this.clientApiProvider,
72881
73395
  model: this.model,
72882
73396
  tracer: this.tracer
@@ -73008,7 +73522,7 @@ Convert your previous response content into actual JSON data that follows this s
73008
73522
  }
73009
73523
  const finalMermaidValidation = await validateAndFixMermaidResponse(finalResult, {
73010
73524
  debug: this.debug,
73011
- path: this.allowedFolders[0],
73525
+ path: this.workspaceRoot || this.allowedFolders[0],
73012
73526
  provider: this.clientApiProvider,
73013
73527
  model: this.model,
73014
73528
  tracer: this.tracer
@@ -73158,8 +73672,7 @@ Convert your previous response content into actual JSON data that follows this s
73158
73672
  allowEdit: this.allowEdit,
73159
73673
  enableDelegate: this.enableDelegate,
73160
73674
  architectureFileName: this.architectureFileName,
73161
- path: this.allowedFolders[0],
73162
- // Use first allowed folder as primary path
73675
+ // Pass allowedFolders which will recompute workspaceRoot correctly
73163
73676
  allowedFolders: [...this.allowedFolders],
73164
73677
  cwd: this.cwd,
73165
73678
  // Preserve explicit working directory