@probelabs/probe 0.6.0-rc225 → 0.6.0-rc227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/bin/binaries/probe-v0.6.0-rc227-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc227-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc227-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc227-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc227-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +24 -0
  7. package/build/agent/ProbeAgent.js +310 -141
  8. package/build/agent/engines/enhanced-claude-code.js +72 -3
  9. package/build/agent/index.js +386 -129
  10. package/build/tools/analyzeAll.js +6 -1
  11. package/build/tools/bash.js +18 -3
  12. package/build/tools/edit.js +19 -10
  13. package/build/tools/vercel.js +17 -7
  14. package/build/utils/path-validation.js +148 -1
  15. package/cjs/agent/ProbeAgent.cjs +683 -389
  16. package/cjs/index.cjs +680 -389
  17. package/package.json +1 -1
  18. package/src/agent/ProbeAgent.d.ts +24 -0
  19. package/src/agent/ProbeAgent.js +310 -141
  20. package/src/agent/engines/enhanced-claude-code.js +72 -3
  21. package/src/tools/analyzeAll.js +6 -1
  22. package/src/tools/bash.js +18 -3
  23. package/src/tools/edit.js +19 -10
  24. package/src/tools/vercel.js +17 -7
  25. package/src/utils/path-validation.js +148 -1
  26. package/bin/binaries/probe-v0.6.0-rc225-aarch64-apple-darwin.tar.gz +0 -0
  27. package/bin/binaries/probe-v0.6.0-rc225-aarch64-unknown-linux-musl.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc225-x86_64-apple-darwin.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc225-x86_64-pc-windows-msvc.zip +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc225-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 = {
@@ -16765,18 +16849,18 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
16765
16849
  import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
16766
16850
  import * as actualFS from "node:fs";
16767
16851
  import { lstat, readdir, readlink, realpath } from "node:fs/promises";
16768
- 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;
16769
16853
  var init_esm4 = __esm({
16770
16854
  "node_modules/path-scurry/dist/esm/index.js"() {
16771
16855
  init_esm2();
16772
16856
  init_esm3();
16773
- realpathSync = rps.native;
16857
+ realpathSync2 = rps.native;
16774
16858
  defaultFS = {
16775
16859
  lstatSync,
16776
16860
  readdir: readdirCB,
16777
16861
  readdirSync,
16778
16862
  readlinkSync,
16779
- realpathSync,
16863
+ realpathSync: realpathSync2,
16780
16864
  promises: {
16781
16865
  lstat,
16782
16866
  readdir,
@@ -69367,7 +69451,7 @@ import path8 from "path";
69367
69451
  import os3 from "os";
69368
69452
  import { EventEmitter as EventEmitter4 } from "events";
69369
69453
  async function createEnhancedClaudeCLIEngine(options = {}) {
69370
- const { agent, systemPrompt, customPrompt, debug, sessionId, allowedTools } = options;
69454
+ const { agent, systemPrompt, customPrompt, debug, sessionId, allowedTools, timeout = 12e4 } = options;
69371
69455
  const session = new Session(
69372
69456
  sessionId || randomBytes(8).toString("hex"),
69373
69457
  debug
@@ -69466,6 +69550,30 @@ ${opts.schema}`;
69466
69550
  stdio: ["ignore", "pipe", "pipe"]
69467
69551
  // Ignore stdin since echo handles it
69468
69552
  });
69553
+ let killed = false;
69554
+ let timeoutHandle;
69555
+ let sigkillHandle;
69556
+ if (timeout > 0) {
69557
+ timeoutHandle = setTimeout(() => {
69558
+ if (!killed) {
69559
+ killed = true;
69560
+ processEnded = true;
69561
+ proc2.kill("SIGTERM");
69562
+ if (debug) {
69563
+ console.log(`[DEBUG] Process timed out after ${timeout}ms, sending SIGTERM`);
69564
+ }
69565
+ sigkillHandle = setTimeout(() => {
69566
+ if (proc2.exitCode === null) {
69567
+ proc2.kill("SIGKILL");
69568
+ if (debug) {
69569
+ console.log("[DEBUG] Process did not exit, sending SIGKILL");
69570
+ }
69571
+ }
69572
+ }, 5e3);
69573
+ emitter.emit("error", new Error(`Claude CLI process timed out after ${timeout}ms`));
69574
+ }
69575
+ }, timeout);
69576
+ }
69469
69577
  proc2.stdout.on("data", (data) => {
69470
69578
  buffer += data.toString();
69471
69579
  processJsonBuffer(buffer, emitter, session, debug, toolCollector);
@@ -69482,10 +69590,20 @@ ${opts.schema}`;
69482
69590
  }
69483
69591
  });
69484
69592
  proc2.on("close", (code) => {
69593
+ if (timeoutHandle) {
69594
+ clearTimeout(timeoutHandle);
69595
+ }
69596
+ if (sigkillHandle) {
69597
+ clearTimeout(sigkillHandle);
69598
+ }
69485
69599
  processEnded = true;
69486
69600
  if (code !== 0 && debug) {
69487
69601
  console.log(`[DEBUG] Process exited with code ${code}`);
69488
69602
  }
69603
+ if (killed) {
69604
+ emitter.emit("end");
69605
+ return;
69606
+ }
69489
69607
  if (buffer.trim()) {
69490
69608
  processJsonBuffer(buffer, emitter, session, debug, toolCollector);
69491
69609
  }
@@ -69501,6 +69619,13 @@ ${opts.schema}`;
69501
69619
  emitter.emit("end");
69502
69620
  });
69503
69621
  proc2.on("error", (error) => {
69622
+ if (timeoutHandle) {
69623
+ clearTimeout(timeoutHandle);
69624
+ }
69625
+ if (sigkillHandle) {
69626
+ clearTimeout(sigkillHandle);
69627
+ }
69628
+ processEnded = true;
69504
69629
  emitter.emit("error", error);
69505
69630
  });
69506
69631
  const messageQueue = [];
@@ -69546,7 +69671,22 @@ ${opts.schema}`;
69546
69671
  \u{1F527} Using ${msg.name}: ${JSON.stringify(msg.input)}
69547
69672
  `
69548
69673
  };
69549
- const result = await executeProbleTool(agent, msg.name, msg.input);
69674
+ const toolTimeout = 3e4;
69675
+ let toolTimeoutId;
69676
+ const timeoutPromise = new Promise((_, reject2) => {
69677
+ toolTimeoutId = setTimeout(() => reject2(new Error(`Tool ${msg.name} timed out after ${toolTimeout}ms`)), toolTimeout);
69678
+ });
69679
+ let result;
69680
+ try {
69681
+ result = await Promise.race([
69682
+ executeProbleTool(agent, msg.name, msg.input),
69683
+ timeoutPromise
69684
+ ]);
69685
+ } catch (error) {
69686
+ result = `Tool error: ${error.message}`;
69687
+ } finally {
69688
+ clearTimeout(toolTimeoutId);
69689
+ }
69550
69690
  yield { type: "text", content: `${result}
69551
69691
  ` };
69552
69692
  } else if (msg.type === "toolBatch") {
@@ -70113,6 +70253,9 @@ var init_enhanced_vercel = __esm({
70113
70253
  // src/agent/ProbeAgent.js
70114
70254
  var ProbeAgent_exports = {};
70115
70255
  __export(ProbeAgent_exports, {
70256
+ ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
70257
+ ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
70258
+ ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
70116
70259
  ProbeAgent: () => ProbeAgent
70117
70260
  });
70118
70261
  import dotenv2 from "dotenv";
@@ -70147,7 +70290,7 @@ Your content here
70147
70290
 
70148
70291
  Do NOT wrap in other tags like <api_call>, <tool_name>, <function>, etc.`;
70149
70292
  }
70150
- var MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
70293
+ var ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
70151
70294
  var init_ProbeAgent = __esm({
70152
70295
  "src/agent/ProbeAgent.js"() {
70153
70296
  "use strict";
@@ -70171,10 +70314,14 @@ var init_ProbeAgent = __esm({
70171
70314
  init_FallbackManager();
70172
70315
  init_contextCompactor();
70173
70316
  init_error_types();
70317
+ init_path_validation();
70174
70318
  init_outputTruncator();
70175
70319
  init_delegate();
70176
70320
  init_tasks();
70177
70321
  dotenv2.config();
70322
+ ENGINE_ACTIVITY_TIMEOUT_DEFAULT = 18e4;
70323
+ ENGINE_ACTIVITY_TIMEOUT_MIN = 5e3;
70324
+ ENGINE_ACTIVITY_TIMEOUT_MAX = 6e5;
70178
70325
  MAX_TOOL_ITERATIONS = (() => {
70179
70326
  const val = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
70180
70327
  if (isNaN(val) || val < 1 || val > 200) {
@@ -70233,6 +70380,8 @@ var init_ProbeAgent = __esm({
70233
70380
  * @param {number} [options.fallback.maxTotalAttempts=10] - Maximum total attempts across all providers
70234
70381
  * @param {string} [options.completionPrompt] - Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation)
70235
70382
  * @param {number} [options.maxOutputTokens] - Maximum tokens for tool output before truncation (default: 20000, can also be set via PROBE_MAX_OUTPUT_TOKENS env var)
70383
+ * @param {number} [options.requestTimeout] - Timeout in ms for AI requests (default: 120000 or REQUEST_TIMEOUT env var). Used to abort hung requests.
70384
+ * @param {number} [options.maxOperationTimeout] - Maximum timeout in ms for the entire operation including all retries and fallbacks (default: 300000 or MAX_OPERATION_TIMEOUT env var). This is the absolute maximum time for streamTextWithRetryAndFallback.
70236
70385
  */
70237
70386
  constructor(options = {}) {
70238
70387
  this.sessionId = options.sessionId || randomUUID6();
@@ -70288,7 +70437,8 @@ var init_ProbeAgent = __esm({
70288
70437
  } else {
70289
70438
  this.allowedFolders = [process.cwd()];
70290
70439
  }
70291
- this.cwd = options.cwd || null;
70440
+ this.workspaceRoot = getCommonPrefix(this.allowedFolders);
70441
+ this.cwd = options.cwd || this.workspaceRoot;
70292
70442
  this.clientApiProvider = options.provider || null;
70293
70443
  this.clientApiModel = options.model || null;
70294
70444
  this.clientApiKey = null;
@@ -70300,6 +70450,8 @@ var init_ProbeAgent = __esm({
70300
70450
  console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
70301
70451
  console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
70302
70452
  console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
70453
+ console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
70454
+ console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
70303
70455
  }
70304
70456
  this.initializeTools();
70305
70457
  this.history = [];
@@ -70315,6 +70467,32 @@ var init_ProbeAgent = __esm({
70315
70467
  this.enableTasks = !!options.enableTasks;
70316
70468
  this.taskManager = null;
70317
70469
  this.delegationManager = new DelegationManager();
70470
+ this.requestTimeout = options.requestTimeout ?? (() => {
70471
+ if (process.env.REQUEST_TIMEOUT) {
70472
+ const parsed = parseInt(process.env.REQUEST_TIMEOUT, 10);
70473
+ if (isNaN(parsed) || parsed < 1e3 || parsed > 36e5) {
70474
+ return 12e4;
70475
+ }
70476
+ return parsed;
70477
+ }
70478
+ return 12e4;
70479
+ })();
70480
+ if (this.debug) {
70481
+ console.log(`[DEBUG] Request timeout: ${this.requestTimeout}ms`);
70482
+ }
70483
+ this.maxOperationTimeout = options.maxOperationTimeout ?? (() => {
70484
+ if (process.env.MAX_OPERATION_TIMEOUT) {
70485
+ const parsed = parseInt(process.env.MAX_OPERATION_TIMEOUT, 10);
70486
+ if (isNaN(parsed) || parsed < 1e3 || parsed > 72e5) {
70487
+ return 3e5;
70488
+ }
70489
+ return parsed;
70490
+ }
70491
+ return 3e5;
70492
+ })();
70493
+ if (this.debug) {
70494
+ console.log(`[DEBUG] Max operation timeout: ${this.maxOperationTimeout}ms`);
70495
+ }
70318
70496
  this.retryConfig = options.retry || {};
70319
70497
  this.retryManager = null;
70320
70498
  this.fallbackConfig = options.fallback || null;
@@ -70647,8 +70825,9 @@ var init_ProbeAgent = __esm({
70647
70825
  const configOptions = {
70648
70826
  sessionId: this.sessionId,
70649
70827
  debug: this.debug,
70650
- // Use explicit cwd if set, otherwise fall back to first allowed folder
70651
- cwd: this.cwd || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd()),
70828
+ // Use cwd (which defaults to workspaceRoot in constructor)
70829
+ cwd: this.cwd,
70830
+ workspaceRoot: this.workspaceRoot,
70652
70831
  allowedFolders: this.allowedFolders,
70653
70832
  outline: this.outline,
70654
70833
  searchDelegate: this.searchDelegate,
@@ -70933,88 +71112,98 @@ var init_ProbeAgent = __esm({
70933
71112
  }
70934
71113
  }
70935
71114
  /**
70936
- * Execute streamText with retry and fallback support
70937
- * @param {Object} options - streamText options
70938
- * @returns {Promise<Object>} - streamText result
71115
+ * Create a streamText-compatible result from an engine stream with timeout handling
71116
+ * @param {AsyncGenerator} engineStream - The engine's query result
71117
+ * @param {AbortSignal} abortSignal - Signal for aborting the operation
71118
+ * @param {number} requestTimeout - Per-request timeout in ms
71119
+ * @param {Object} timeoutState - Object with timeoutId property (mutable for cleanup)
71120
+ * @returns {Object} - streamText-compatible result with textStream
70939
71121
  * @private
70940
71122
  */
70941
- async streamTextWithRetryAndFallback(options) {
70942
- if (this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true") {
71123
+ _createEngineTextStreamResult(engineStream, abortSignal, requestTimeout, timeoutState) {
71124
+ const activityTimeout = (() => {
71125
+ const parsed = parseInt(process.env.ENGINE_ACTIVITY_TIMEOUT, 10);
71126
+ return isNaN(parsed) || parsed < ENGINE_ACTIVITY_TIMEOUT_MIN || parsed > ENGINE_ACTIVITY_TIMEOUT_MAX ? ENGINE_ACTIVITY_TIMEOUT_DEFAULT : parsed;
71127
+ })();
71128
+ const startTime = Date.now();
71129
+ async function* createTextStream() {
71130
+ let lastActivity = Date.now();
70943
71131
  try {
70944
- const engine = await this.getEngine();
70945
- if (engine && engine.query) {
70946
- const userMessages = options.messages.filter(
70947
- (m) => m.role === "user" && !m.content.includes("WARNING: You have reached the maximum tool iterations limit")
70948
- );
70949
- const lastUserMessage = userMessages[userMessages.length - 1];
70950
- const prompt = lastUserMessage ? lastUserMessage.content : "";
70951
- const engineOptions = {
70952
- maxTokens: options.maxTokens,
70953
- temperature: options.temperature,
70954
- messages: options.messages,
70955
- systemPrompt: options.messages.find((m) => m.role === "system")?.content
70956
- };
70957
- const engineStream = engine.query(prompt, engineOptions);
70958
- async function* createTextStream() {
70959
- for await (const message of engineStream) {
70960
- if (message.type === "text" && message.content) {
70961
- yield message.content;
70962
- } else if (typeof message === "string") {
70963
- yield message;
70964
- }
70965
- }
71132
+ for await (const message of engineStream) {
71133
+ if (abortSignal.aborted) {
71134
+ const abortError = new Error("Operation aborted");
71135
+ abortError.name = "AbortError";
71136
+ throw abortError;
70966
71137
  }
70967
- return {
70968
- textStream: createTextStream(),
70969
- usage: Promise.resolve({})
70970
- // Engine should handle its own usage tracking
70971
- // Add other streamText-compatible properties as needed
70972
- };
70973
- }
70974
- } catch (error) {
70975
- if (this.debug) {
70976
- console.log(`[DEBUG] Failed to use Claude Code engine, falling back to Vercel:`, error.message);
70977
- }
70978
- }
70979
- }
70980
- if (this.clientApiProvider === "codex" || process.env.USE_CODEX === "true") {
70981
- try {
70982
- const engine = await this.getEngine();
70983
- if (engine && engine.query) {
70984
- const userMessages = options.messages.filter(
70985
- (m) => m.role === "user" && !m.content.includes("WARNING: You have reached the maximum tool iterations limit")
70986
- );
70987
- const lastUserMessage = userMessages[userMessages.length - 1];
70988
- const prompt = lastUserMessage ? lastUserMessage.content : "";
70989
- const engineOptions = {
70990
- maxTokens: options.maxTokens,
70991
- temperature: options.temperature,
70992
- messages: options.messages,
70993
- systemPrompt: options.messages.find((m) => m.role === "system")?.content
70994
- };
70995
- const engineStream = engine.query(prompt, engineOptions);
70996
- async function* createTextStream() {
70997
- for await (const message of engineStream) {
70998
- if (message.type === "text" && message.content) {
70999
- yield message.content;
71000
- } else if (typeof message === "string") {
71001
- yield message;
71002
- }
71003
- }
71138
+ const now = Date.now();
71139
+ if (now - lastActivity > activityTimeout) {
71140
+ throw new Error(`Engine stream timeout - no activity for ${activityTimeout}ms`);
71141
+ }
71142
+ if (requestTimeout > 0 && now - startTime > requestTimeout) {
71143
+ throw new Error(`Engine stream timeout - request exceeded ${requestTimeout}ms`);
71144
+ }
71145
+ lastActivity = now;
71146
+ if (message.type === "text" && message.content) {
71147
+ yield message.content;
71148
+ } else if (typeof message === "string") {
71149
+ yield message;
71004
71150
  }
71005
- return {
71006
- textStream: createTextStream(),
71007
- usage: Promise.resolve({})
71008
- // Engine should handle its own usage tracking
71009
- // Add other streamText-compatible properties as needed
71010
- };
71011
71151
  }
71012
- } catch (error) {
71013
- if (this.debug) {
71014
- console.log(`[DEBUG] Failed to use Codex engine, falling back to Vercel:`, error.message);
71152
+ } finally {
71153
+ if (timeoutState.timeoutId) {
71154
+ clearTimeout(timeoutState.timeoutId);
71155
+ timeoutState.timeoutId = null;
71015
71156
  }
71016
71157
  }
71017
71158
  }
71159
+ return {
71160
+ textStream: createTextStream(),
71161
+ usage: Promise.resolve({})
71162
+ // Engine should handle its own usage tracking
71163
+ // Add other streamText-compatible properties as needed
71164
+ };
71165
+ }
71166
+ /**
71167
+ * Try to use an engine (claude-code or codex) for streaming
71168
+ * @param {Object} options - streamText options
71169
+ * @param {AbortController} controller - Abort controller for the operation
71170
+ * @param {Object} timeoutState - Mutable timeout state for cleanup
71171
+ * @returns {Promise<Object|null>} - Stream result or null if engine unavailable
71172
+ * @private
71173
+ */
71174
+ async _tryEngineStreamPath(options, controller, timeoutState) {
71175
+ const engine = await this.getEngine();
71176
+ if (!engine || !engine.query) {
71177
+ return null;
71178
+ }
71179
+ const userMessages = options.messages.filter(
71180
+ (m) => m.role === "user" && !m.content.includes("WARNING: You have reached the maximum tool iterations limit")
71181
+ );
71182
+ const lastUserMessage = userMessages[userMessages.length - 1];
71183
+ const prompt = lastUserMessage ? lastUserMessage.content : "";
71184
+ const engineOptions = {
71185
+ maxTokens: options.maxTokens,
71186
+ temperature: options.temperature,
71187
+ messages: options.messages,
71188
+ systemPrompt: options.messages.find((m) => m.role === "system")?.content,
71189
+ abortSignal: controller.signal
71190
+ };
71191
+ const engineStream = engine.query(prompt, engineOptions);
71192
+ return this._createEngineTextStreamResult(
71193
+ engineStream,
71194
+ controller.signal,
71195
+ this.requestTimeout,
71196
+ timeoutState
71197
+ );
71198
+ }
71199
+ /**
71200
+ * Execute streamText with Vercel AI SDK using retry/fallback logic
71201
+ * @param {Object} options - streamText options
71202
+ * @param {AbortController} controller - Abort controller for the operation
71203
+ * @returns {Promise<Object>} - Stream result
71204
+ * @private
71205
+ */
71206
+ async _executeWithVercelProvider(options, controller) {
71018
71207
  if (!this.retryManager) {
71019
71208
  this.retryManager = new RetryManager({
71020
71209
  maxRetries: this.retryConfig.maxRetries ?? 3,
@@ -71027,10 +71216,11 @@ var init_ProbeAgent = __esm({
71027
71216
  }
71028
71217
  if (!this.fallbackManager) {
71029
71218
  return await this.retryManager.executeWithRetry(
71030
- () => streamText2(options),
71219
+ () => streamText2({ ...options, abortSignal: controller.signal }),
71031
71220
  {
71032
71221
  provider: this.apiType,
71033
- model: this.model
71222
+ model: this.model,
71223
+ signal: controller.signal
71034
71224
  }
71035
71225
  );
71036
71226
  }
@@ -71038,7 +71228,8 @@ var init_ProbeAgent = __esm({
71038
71228
  async (provider, model, config) => {
71039
71229
  const fallbackOptions = {
71040
71230
  ...options,
71041
- model: provider(model)
71231
+ model: provider(model),
71232
+ abortSignal: controller.signal
71042
71233
  };
71043
71234
  const providerRetryManager = new RetryManager({
71044
71235
  maxRetries: config.maxRetries ?? this.retryConfig.maxRetries ?? 3,
@@ -71052,12 +71243,54 @@ var init_ProbeAgent = __esm({
71052
71243
  () => streamText2(fallbackOptions),
71053
71244
  {
71054
71245
  provider: config.provider,
71055
- model
71246
+ model,
71247
+ signal: controller.signal
71056
71248
  }
71057
71249
  );
71058
71250
  }
71059
71251
  );
71060
71252
  }
71253
+ /**
71254
+ * Execute streamText with retry and fallback support
71255
+ * @param {Object} options - streamText options
71256
+ * @returns {Promise<Object>} - streamText result
71257
+ * @private
71258
+ */
71259
+ async streamTextWithRetryAndFallback(options) {
71260
+ const controller = new AbortController();
71261
+ const timeoutState = { timeoutId: null };
71262
+ if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
71263
+ timeoutState.timeoutId = setTimeout(() => {
71264
+ controller.abort();
71265
+ if (this.debug) {
71266
+ console.log(`[DEBUG] Operation timed out after ${this.maxOperationTimeout}ms (max operation timeout)`);
71267
+ }
71268
+ }, this.maxOperationTimeout);
71269
+ }
71270
+ try {
71271
+ const useClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
71272
+ const useCodex = this.clientApiProvider === "codex" || process.env.USE_CODEX === "true";
71273
+ if (useClaudeCode || useCodex) {
71274
+ try {
71275
+ const result = await this._tryEngineStreamPath(options, controller, timeoutState);
71276
+ if (result) {
71277
+ return result;
71278
+ }
71279
+ } catch (error) {
71280
+ if (this.debug) {
71281
+ const engineType = useClaudeCode ? "Claude Code" : "Codex";
71282
+ console.log(`[DEBUG] Failed to use ${engineType} engine, falling back to Vercel:`, error.message);
71283
+ }
71284
+ }
71285
+ }
71286
+ return await this._executeWithVercelProvider(options, controller);
71287
+ } finally {
71288
+ if (timeoutState.timeoutId) {
71289
+ clearTimeout(timeoutState.timeoutId);
71290
+ timeoutState.timeoutId = null;
71291
+ }
71292
+ }
71293
+ }
71061
71294
  /**
71062
71295
  * Initialize Anthropic model
71063
71296
  */
@@ -71335,16 +71568,16 @@ var init_ProbeAgent = __esm({
71335
71568
  let absolutePath;
71336
71569
  let isPathAllowed2 = false;
71337
71570
  if (isAbsolute5(imagePath)) {
71338
- absolutePath = normalize2(resolve6(imagePath));
71571
+ absolutePath = safeRealpath(resolve6(imagePath));
71339
71572
  isPathAllowed2 = allowedDirs.some((dir) => {
71340
- const normalizedDir = normalize2(resolve6(dir));
71341
- return absolutePath === normalizedDir || absolutePath.startsWith(normalizedDir + sep5);
71573
+ const resolvedDir = safeRealpath(dir);
71574
+ return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep5);
71342
71575
  });
71343
71576
  } else {
71344
71577
  for (const dir of allowedDirs) {
71345
- const normalizedDir = normalize2(resolve6(dir));
71346
- const resolvedPath = normalize2(resolve6(dir, imagePath));
71347
- if (resolvedPath === normalizedDir || resolvedPath.startsWith(normalizedDir + sep5)) {
71578
+ const resolvedDir = safeRealpath(dir);
71579
+ const resolvedPath = safeRealpath(resolve6(dir, imagePath));
71580
+ if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep5)) {
71348
71581
  absolutePath = resolvedPath;
71349
71582
  isPathAllowed2 = true;
71350
71583
  break;
@@ -71527,7 +71760,7 @@ var init_ProbeAgent = __esm({
71527
71760
  if (this._architectureContextLoaded) {
71528
71761
  return this.architectureContext;
71529
71762
  }
71530
- const rootDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
71763
+ const rootDirectory = this.workspaceRoot || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd());
71531
71764
  const configuredName = typeof this.architectureFileName === "string" ? this.architectureFileName.trim() : "";
71532
71765
  const hasConfiguredName = !!configuredName;
71533
71766
  let guidanceCandidates = [];
@@ -71664,6 +71897,9 @@ ${this.architectureContext.content}
71664
71897
  `;
71665
71898
  }
71666
71899
  _getSkillsRepoRoot() {
71900
+ if (this.workspaceRoot) {
71901
+ return resolve6(this.workspaceRoot);
71902
+ }
71667
71903
  if (this.allowedFolders && this.allowedFolders.length > 0) {
71668
71904
  return resolve6(this.allowedFolders[0]);
71669
71905
  }
@@ -71731,7 +71967,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
71731
71967
 
71732
71968
  # Repository Structure
71733
71969
  `;
71734
- systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}
71970
+ systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
71735
71971
 
71736
71972
  `;
71737
71973
  systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
@@ -71784,7 +72020,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
71784
72020
 
71785
72021
  # Repository Structure
71786
72022
  `;
71787
- systemPrompt += `You are working with a repository located at: ${this.allowedFolders[0]}
72023
+ systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
71788
72024
 
71789
72025
  `;
71790
72026
  systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
@@ -72083,21 +72319,34 @@ For MCP tools, use JSON format within the params tag, e.g.:
72083
72319
  `;
72084
72320
  }
72085
72321
  }
72086
- const searchDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
72322
+ const searchDirectory = this.workspaceRoot;
72087
72323
  if (this.debug) {
72088
- console.log(`[DEBUG] Generating file list for base directory: ${searchDirectory}...`);
72324
+ console.log(`[DEBUG] Generating file list for workspace root: ${searchDirectory}...`);
72325
+ }
72326
+ const relativeWorkspaces = this.allowedFolders.map((f) => {
72327
+ const rel = toRelativePath(f, this.workspaceRoot);
72328
+ if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
72329
+ return "./" + rel;
72330
+ }
72331
+ return rel;
72332
+ }).filter((f) => f && f !== ".");
72333
+ let workspaceDesc;
72334
+ if (relativeWorkspaces.length === 0) {
72335
+ workspaceDesc = ". (current directory)";
72336
+ } else {
72337
+ workspaceDesc = relativeWorkspaces.join(", ");
72089
72338
  }
72090
72339
  try {
72091
72340
  const files = await listFilesByLevel({
72092
72341
  directory: searchDirectory,
72093
72342
  maxFiles: 100,
72094
72343
  respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === "",
72095
- cwd: process.cwd()
72344
+ cwd: this.workspaceRoot
72096
72345
  });
72097
72346
  systemMessage += `
72098
72347
  # Repository Structure
72099
72348
 
72100
- You are working with a repository located at: ${searchDirectory}
72349
+ You are working with a workspace. Available paths: ${workspaceDesc}
72101
72350
 
72102
72351
  Here's an overview of the repository structure (showing up to 100 most relevant files):
72103
72352
 
@@ -72113,15 +72362,22 @@ ${files}
72113
72362
  systemMessage += `
72114
72363
  # Repository Structure
72115
72364
 
72116
- You are working with a repository located at: ${searchDirectory}
72365
+ You are working with a workspace. Available paths: ${workspaceDesc}
72117
72366
 
72118
72367
  `;
72119
72368
  }
72120
72369
  await this.loadArchitectureContext();
72121
72370
  systemMessage += this.getArchitectureSection();
72122
72371
  if (this.allowedFolders.length > 0) {
72372
+ const relativeAllowed = this.allowedFolders.map((f) => {
72373
+ const rel = toRelativePath(f, this.workspaceRoot);
72374
+ if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
72375
+ return "./" + rel;
72376
+ }
72377
+ return rel;
72378
+ });
72123
72379
  systemMessage += `
72124
- **Important**: For security reasons, you can only search within these allowed folders: ${this.allowedFolders.join(", ")}
72380
+ **Important**: For security reasons, you can only access these paths: ${relativeAllowed.join(", ")}
72125
72381
 
72126
72382
  `;
72127
72383
  }
@@ -72635,6 +72891,7 @@ You are working with a repository located at: ${searchDirectory}
72635
72891
  console.error(`[DEBUG] ========================================
72636
72892
  `);
72637
72893
  }
72894
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
72638
72895
  currentMessages.push({ role: "user", content: `<tool_result>
72639
72896
  ${toolResultContent}
72640
72897
  </tool_result>` });
@@ -72655,17 +72912,18 @@ ${toolResultContent}
72655
72912
  `);
72656
72913
  }
72657
72914
  const errorXml = formatErrorForAI(error);
72915
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
72658
72916
  currentMessages.push({ role: "user", content: `<tool_result>
72659
72917
  ${errorXml}
72660
72918
  </tool_result>` });
72661
72919
  }
72662
72920
  } else if (this.toolImplementations[toolName]) {
72663
72921
  try {
72664
- let resolvedWorkingDirectory = this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
72922
+ let resolvedWorkingDirectory = this.workspaceRoot || this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
72665
72923
  if (params.workingDirectory) {
72666
- const requestedDir = isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory);
72924
+ const requestedDir = safeRealpath(isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory));
72667
72925
  const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 || this.allowedFolders.some((folder) => {
72668
- const resolvedFolder = resolve6(folder);
72926
+ const resolvedFolder = safeRealpath(folder);
72669
72927
  return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + sep5);
72670
72928
  });
72671
72929
  if (isWithinAllowed) {
@@ -73134,7 +73392,7 @@ Convert your previous response content into actual JSON data that follows this s
73134
73392
  }
73135
73393
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
73136
73394
  debug: this.debug,
73137
- path: this.allowedFolders[0],
73395
+ path: this.workspaceRoot || this.allowedFolders[0],
73138
73396
  provider: this.clientApiProvider,
73139
73397
  model: this.model,
73140
73398
  tracer: this.tracer
@@ -73207,7 +73465,7 @@ Convert your previous response content into actual JSON data that follows this s
73207
73465
  }
73208
73466
  const { JsonFixingAgent: JsonFixingAgent2 } = await Promise.resolve().then(() => (init_schemaUtils(), schemaUtils_exports));
73209
73467
  const jsonFixer = new JsonFixingAgent2({
73210
- path: this.allowedFolders[0],
73468
+ path: this.workspaceRoot || this.allowedFolders[0],
73211
73469
  provider: this.clientApiProvider,
73212
73470
  model: this.model,
73213
73471
  debug: this.debug,
@@ -73276,7 +73534,7 @@ Convert your previous response content into actual JSON data that follows this s
73276
73534
  }
73277
73535
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
73278
73536
  debug: this.debug,
73279
- path: this.allowedFolders[0],
73537
+ path: this.workspaceRoot || this.allowedFolders[0],
73280
73538
  provider: this.clientApiProvider,
73281
73539
  model: this.model,
73282
73540
  tracer: this.tracer
@@ -73408,7 +73666,7 @@ Convert your previous response content into actual JSON data that follows this s
73408
73666
  }
73409
73667
  const finalMermaidValidation = await validateAndFixMermaidResponse(finalResult, {
73410
73668
  debug: this.debug,
73411
- path: this.allowedFolders[0],
73669
+ path: this.workspaceRoot || this.allowedFolders[0],
73412
73670
  provider: this.clientApiProvider,
73413
73671
  model: this.model,
73414
73672
  tracer: this.tracer
@@ -73558,8 +73816,7 @@ Convert your previous response content into actual JSON data that follows this s
73558
73816
  allowEdit: this.allowEdit,
73559
73817
  enableDelegate: this.enableDelegate,
73560
73818
  architectureFileName: this.architectureFileName,
73561
- path: this.allowedFolders[0],
73562
- // Use first allowed folder as primary path
73819
+ // Pass allowedFolders which will recompute workspaceRoot correctly
73563
73820
  allowedFolders: [...this.allowedFolders],
73564
73821
  cwd: this.cwd,
73565
73822
  // Preserve explicit working directory