@probelabs/probe 0.6.0-rc269 → 0.6.0-rc271

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.
@@ -3582,23 +3582,24 @@ Follow these instructions carefully:
3582
3582
  // Continue even if storage fails
3583
3583
  }
3584
3584
 
3585
- // Completion prompt handling - run a follow-up prompt after attempt_completion for validation/review
3586
- // This runs BEFORE mermaid validation and JSON schema validation
3587
- // Skip if we're already in a completion prompt follow-up call or if no completion prompt is configured
3585
+ // Completion prompt handling - inject one more user message into the existing conversation
3586
+ // This continues the SAME agentic session (same tools, same TaskManager, same history)
3587
+ // rather than spawning a recursive this.answer() call which would reset state
3588
3588
  if (completionAttempted && this.completionPrompt && !options._completionPromptProcessed) {
3589
3589
  if (this.debug) {
3590
- console.log('[DEBUG] Running completion prompt for post-completion validation/review...');
3590
+ console.log('[DEBUG] Running completion prompt as continuation of current session...');
3591
3591
  }
3592
3592
 
3593
3593
  try {
3594
- // Record completion prompt start in telemetry
3594
+ const originalResult = finalResult;
3595
+
3595
3596
  if (this.tracer) {
3596
3597
  this.tracer.recordEvent('completion_prompt.started', {
3597
3598
  'completion_prompt.original_result_length': finalResult?.length || 0
3598
3599
  });
3599
3600
  }
3600
3601
 
3601
- // Create the completion prompt with the current result as context
3602
+ // Append completion prompt as a user message to the existing conversation
3602
3603
  const completionPromptMessage = `${this.completionPrompt}
3603
3604
 
3604
3605
  Here is the result to review:
@@ -3606,34 +3607,82 @@ Here is the result to review:
3606
3607
  ${finalResult}
3607
3608
  </result>
3608
3609
 
3609
- After reviewing, provide your final answer using attempt_completion.`;
3610
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is using attempt_completion. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix) using attempt_completion.`;
3610
3611
 
3611
- // Make a follow-up call with the completion prompt
3612
- // Pass _completionPromptProcessed to prevent infinite loops
3613
- // Save output buffers the recursive answer() must not destroy DSL output() content
3614
- const savedOutputItems = this._outputBuffer ? [...this._outputBuffer.items] : [];
3615
- const savedExtractedBlocks = this._extractedRawBlocks ? [...this._extractedRawBlocks] : [];
3616
- const completionResult = await this.answer(completionPromptMessage, [], {
3617
- ...options,
3618
- _completionPromptProcessed: true
3619
- });
3620
- // Restore output buffers so the parent call can append them to the final result
3621
- if (this._outputBuffer) {
3622
- this._outputBuffer.items = savedOutputItems;
3612
+ currentMessages.push({ role: 'user', content: completionPromptMessage });
3613
+
3614
+ // Reset completion tracking for the follow-up turn
3615
+ completionResult = null;
3616
+ completionAttempted = false;
3617
+
3618
+ // Run one more streamText pass with the same tools and conversation context
3619
+ // Give a small number of extra iterations for the follow-up
3620
+ const completionMaxIterations = 5;
3621
+ const completionStreamOptions = {
3622
+ model: this.provider ? this.provider(this.model) : this.model,
3623
+ messages: this.prepareMessagesWithImages(currentMessages),
3624
+ tools,
3625
+ stopWhen: stepCountIs(completionMaxIterations),
3626
+ maxTokens: maxResponseTokens,
3627
+ temperature: 0.3,
3628
+ onStepFinish: ({ toolResults, text, finishReason, usage }) => {
3629
+ if (usage) {
3630
+ this.tokenCounter.recordUsage(usage);
3631
+ }
3632
+ if (options.onStream && text) {
3633
+ options.onStream(text);
3634
+ }
3635
+ if (this.debug) {
3636
+ console.log(`[DEBUG] Completion prompt step finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
3637
+ }
3638
+ }
3639
+ };
3640
+
3641
+ const providerOpts = this._buildThinkingProviderOptions(maxResponseTokens);
3642
+ if (providerOpts) {
3643
+ completionStreamOptions.providerOptions = providerOpts;
3644
+ }
3645
+
3646
+ const cpResult = await this.streamTextWithRetryAndFallback(completionStreamOptions);
3647
+ const cpFinalText = await cpResult.text;
3648
+ const cpUsage = await cpResult.usage;
3649
+ if (cpUsage) {
3650
+ this.tokenCounter.recordUsage(cpUsage, cpResult.experimental_providerMetadata);
3651
+ }
3652
+
3653
+ // Append follow-up messages to conversation history
3654
+ const cpMessages = await cpResult.response?.messages;
3655
+ if (cpMessages) {
3656
+ for (const msg of cpMessages) {
3657
+ currentMessages.push(msg);
3658
+ }
3623
3659
  }
3624
- this._extractedRawBlocks = savedExtractedBlocks;
3625
3660
 
3626
- // Update finalResult with the result from the completion prompt
3627
- finalResult = completionResult;
3661
+ // Use new completion result if the agent called attempt_completion again,
3662
+ // otherwise keep the original result (the follow-up may have just done side-effects)
3663
+ if (completionResult) {
3664
+ finalResult = completionResult;
3665
+ completionAttempted = true;
3666
+ } else if (cpFinalText && cpFinalText.trim().length > 0) {
3667
+ finalResult = cpFinalText;
3668
+ completionAttempted = true;
3669
+ } else {
3670
+ // Follow-up produced nothing useful — keep the original
3671
+ finalResult = originalResult;
3672
+ completionAttempted = true;
3673
+ if (this.debug) {
3674
+ console.log('[DEBUG] Completion prompt returned empty result, keeping original.');
3675
+ }
3676
+ }
3628
3677
 
3629
3678
  if (this.debug) {
3630
- console.log(`[DEBUG] Completion prompt finished. New result length: ${finalResult?.length || 0}`);
3679
+ console.log(`[DEBUG] Completion prompt finished. Final result length: ${finalResult?.length || 0}`);
3631
3680
  }
3632
3681
 
3633
- // Record completion prompt completion in telemetry
3634
3682
  if (this.tracer) {
3635
3683
  this.tracer.recordEvent('completion_prompt.completed', {
3636
- 'completion_prompt.final_result_length': finalResult?.length || 0
3684
+ 'completion_prompt.final_result_length': finalResult?.length || 0,
3685
+ 'completion_prompt.used_original': finalResult === originalResult
3637
3686
  });
3638
3687
  }
3639
3688
  } catch (error) {
@@ -9045,6 +9045,7 @@ var init_hashline = __esm({
9045
9045
 
9046
9046
  // src/tools/vercel.js
9047
9047
  import { tool } from "ai";
9048
+ import { existsSync } from "fs";
9048
9049
  function normalizeTargets(targets) {
9049
9050
  if (!Array.isArray(targets)) return [];
9050
9051
  const seen = /* @__PURE__ */ new Set();
@@ -9121,6 +9122,17 @@ function parseDelegatedTargets(rawResponse) {
9121
9122
  }
9122
9123
  return normalizeTargets(fallbackTargetsFromText(trimmed));
9123
9124
  }
9125
+ function splitTargetSuffix(target) {
9126
+ const searchStart = target.length > 2 && target[1] === ":" && /[a-zA-Z]/.test(target[0]) ? 2 : 0;
9127
+ const colonIdx = target.indexOf(":", searchStart);
9128
+ const hashIdx = target.indexOf("#");
9129
+ if (colonIdx !== -1 && (hashIdx === -1 || colonIdx < hashIdx)) {
9130
+ return { filePart: target.substring(0, colonIdx), suffix: target.substring(colonIdx) };
9131
+ } else if (hashIdx !== -1) {
9132
+ return { filePart: target.substring(0, hashIdx), suffix: target.substring(hashIdx) };
9133
+ }
9134
+ return { filePart: target, suffix: "" };
9135
+ }
9124
9136
  function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, allowTests }) {
9125
9137
  return [
9126
9138
  "You are a code-search subagent. Your job is to find ALL relevant code locations for the given query.",
@@ -9284,10 +9296,47 @@ var init_vercel = __esm({
9284
9296
  }
9285
9297
  return fallbackResult;
9286
9298
  }
9299
+ const delegateBase = options.allowedFolders?.[0] || options.cwd || ".";
9287
9300
  const resolutionBase = searchPaths[0] || options.cwd || ".";
9288
- const resolvedTargets = targets.map((target) => resolveTargetPath(target, resolutionBase));
9301
+ const resolvedTargets = targets.map((target) => resolveTargetPath(target, delegateBase));
9302
+ const validatedTargets = [];
9303
+ for (const target of resolvedTargets) {
9304
+ const { filePart, suffix } = splitTargetSuffix(target);
9305
+ if (existsSync(filePart)) {
9306
+ validatedTargets.push(target);
9307
+ continue;
9308
+ }
9309
+ let fixed = false;
9310
+ const parts = filePart.split("/").filter(Boolean);
9311
+ for (let i = 0; i < parts.length - 1; i++) {
9312
+ if (parts[i] === parts[i + 1]) {
9313
+ const candidate = "/" + [...parts.slice(0, i), ...parts.slice(i + 1)].join("/");
9314
+ if (existsSync(candidate)) {
9315
+ validatedTargets.push(candidate + suffix);
9316
+ if (debug) console.error(`[search-delegate] Fixed doubled path segment: ${filePart} \u2192 ${candidate}`);
9317
+ fixed = true;
9318
+ break;
9319
+ }
9320
+ }
9321
+ }
9322
+ if (fixed) continue;
9323
+ for (const altBase of [resolutionBase, options.cwd].filter(Boolean)) {
9324
+ if (altBase === delegateBase) continue;
9325
+ const altResolved = resolveTargetPath(target, altBase);
9326
+ const { filePart: altFile } = splitTargetSuffix(altResolved);
9327
+ if (existsSync(altFile)) {
9328
+ validatedTargets.push(altResolved);
9329
+ if (debug) console.error(`[search-delegate] Resolved with alt base: ${filePart} \u2192 ${altFile}`);
9330
+ fixed = true;
9331
+ break;
9332
+ }
9333
+ }
9334
+ if (fixed) continue;
9335
+ if (debug) console.error(`[search-delegate] Warning: target may not exist: ${filePart}`);
9336
+ validatedTargets.push(target);
9337
+ }
9289
9338
  const extractOptions = {
9290
- files: resolvedTargets,
9339
+ files: validatedTargets,
9291
9340
  cwd: resolutionBase,
9292
9341
  allowTests: allow_tests ?? true
9293
9342
  };
@@ -10805,7 +10854,7 @@ var init_bashPermissions = __esm({
10805
10854
  // src/agent/bashExecutor.js
10806
10855
  import { spawn as spawn2 } from "child_process";
10807
10856
  import { resolve as resolve2, join } from "path";
10808
- import { existsSync } from "fs";
10857
+ import { existsSync as existsSync2 } from "fs";
10809
10858
  function splitCommandComponents(command) {
10810
10859
  const parts = [];
10811
10860
  let current2 = "";
@@ -10935,7 +10984,7 @@ async function executeBashCommand(command, options = {}) {
10935
10984
  let cwd = workingDirectory;
10936
10985
  try {
10937
10986
  cwd = resolve2(cwd);
10938
- if (!existsSync(cwd)) {
10987
+ if (!existsSync2(cwd)) {
10939
10988
  throw new Error(`Working directory does not exist: ${cwd}`);
10940
10989
  }
10941
10990
  } catch (error) {
@@ -11191,7 +11240,7 @@ function validateExecutionOptions(options = {}) {
11191
11240
  if (options.workingDirectory) {
11192
11241
  if (typeof options.workingDirectory !== "string") {
11193
11242
  errors.push("workingDirectory must be a string");
11194
- } else if (!existsSync(options.workingDirectory)) {
11243
+ } else if (!existsSync2(options.workingDirectory)) {
11195
11244
  errors.push(`workingDirectory does not exist: ${options.workingDirectory}`);
11196
11245
  }
11197
11246
  }
@@ -11716,7 +11765,7 @@ var init_lineEditHeuristics = __esm({
11716
11765
  import { tool as tool3 } from "ai";
11717
11766
  import { promises as fs6 } from "fs";
11718
11767
  import { dirname, resolve as resolve4, isAbsolute as isAbsolute3, sep as sep2 } from "path";
11719
- import { existsSync as existsSync2 } from "fs";
11768
+ import { existsSync as existsSync3 } from "fs";
11720
11769
  function isPathAllowed(filePath, allowedFolders) {
11721
11770
  if (!allowedFolders || allowedFolders.length === 0) {
11722
11771
  const resolvedPath2 = safeRealpath(filePath);
@@ -11994,7 +12043,7 @@ Parameters:
11994
12043
  const relativePath = toRelativePath(resolvedPath, workspaceRoot);
11995
12044
  return `Error editing file: Permission denied - ${relativePath} is outside allowed directories. Use a file path within the project workspace.`;
11996
12045
  }
11997
- if (!existsSync2(resolvedPath)) {
12046
+ if (!existsSync3(resolvedPath)) {
11998
12047
  return `Error editing file: File not found - ${file_path}. Verify the path is correct and the file exists. Use 'search' to find files by name, or 'create' to make a new file.`;
11999
12048
  }
12000
12049
  if (options.fileTracker && !options.fileTracker.isFileSeen(resolvedPath)) {
@@ -12127,10 +12176,10 @@ Important:
12127
12176
  const relativePath = toRelativePath(resolvedPath, workspaceRoot);
12128
12177
  return `Error creating file: Permission denied - ${relativePath} is outside allowed directories. Use a file path within the project workspace.`;
12129
12178
  }
12130
- if (existsSync2(resolvedPath) && !overwrite) {
12179
+ if (existsSync3(resolvedPath) && !overwrite) {
12131
12180
  return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
12132
12181
  }
12133
- const existed = existsSync2(resolvedPath);
12182
+ const existed = existsSync3(resolvedPath);
12134
12183
  const dir = dirname(resolvedPath);
12135
12184
  await fs6.mkdir(dir, { recursive: true });
12136
12185
  await fs6.writeFile(resolvedPath, content, "utf-8");
@@ -30374,7 +30423,7 @@ var init_fileTracker = __esm({
30374
30423
  });
30375
30424
 
30376
30425
  // src/agent/simpleTelemetry.js
30377
- import { existsSync as existsSync3, mkdirSync, createWriteStream } from "fs";
30426
+ import { existsSync as existsSync4, mkdirSync, createWriteStream } from "fs";
30378
30427
  import { dirname as dirname2 } from "path";
30379
30428
  function initializeSimpleTelemetryFromOptions(options) {
30380
30429
  const telemetry = new SimpleTelemetry({
@@ -30403,7 +30452,7 @@ var init_simpleTelemetry = __esm({
30403
30452
  initializeFileExporter() {
30404
30453
  try {
30405
30454
  const dir = dirname2(this.filePath);
30406
- if (!existsSync3(dir)) {
30455
+ if (!existsSync4(dir)) {
30407
30456
  mkdirSync(dir, { recursive: true });
30408
30457
  }
30409
30458
  this.stream = createWriteStream(this.filePath, { flags: "a" });
@@ -70834,7 +70883,7 @@ When troubleshooting:
70834
70883
  });
70835
70884
 
70836
70885
  // src/agent/mcp/config.js
70837
- import { readFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync } from "fs";
70886
+ import { readFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync } from "fs";
70838
70887
  import { join as join2, dirname as dirname3 } from "path";
70839
70888
  import { homedir } from "os";
70840
70889
  import { fileURLToPath as fileURLToPath6 } from "url";
@@ -70889,7 +70938,7 @@ function loadMCPConfigurationFromPath(configPath) {
70889
70938
  if (!configPath) {
70890
70939
  throw new Error("Config path is required");
70891
70940
  }
70892
- if (!existsSync4(configPath)) {
70941
+ if (!existsSync5(configPath)) {
70893
70942
  throw new Error(`MCP configuration file not found: ${configPath}`);
70894
70943
  }
70895
70944
  try {
@@ -70918,7 +70967,7 @@ function loadMCPConfiguration() {
70918
70967
  ].filter(Boolean);
70919
70968
  let config = null;
70920
70969
  for (const configPath of configPaths) {
70921
- if (existsSync4(configPath)) {
70970
+ if (existsSync5(configPath)) {
70922
70971
  try {
70923
70972
  const content = readFileSync(configPath, "utf8");
70924
70973
  config = JSON.parse(content);
@@ -79122,7 +79171,7 @@ var init_parser7 = __esm({
79122
79171
  });
79123
79172
 
79124
79173
  // src/agent/skills/registry.js
79125
- import { existsSync as existsSync5 } from "fs";
79174
+ import { existsSync as existsSync6 } from "fs";
79126
79175
  import { readdir as readdir2, readFile as readFile2, realpath as realpath2, lstat as lstat2 } from "fs/promises";
79127
79176
  import { resolve as resolve6, join as join3, isAbsolute as isAbsolute5, sep as sep4, relative } from "path";
79128
79177
  function isPathInside(basePath, targetPath) {
@@ -79215,7 +79264,7 @@ var init_registry = __esm({
79215
79264
  return resolvedReal;
79216
79265
  }
79217
79266
  async _scanSkillDir(dirPath) {
79218
- if (!existsSync5(dirPath)) return [];
79267
+ if (!existsSync6(dirPath)) return [];
79219
79268
  let entries;
79220
79269
  try {
79221
79270
  entries = await readdir2(dirPath, { withFileTypes: true });
@@ -79255,7 +79304,7 @@ var init_registry = __esm({
79255
79304
  }
79256
79305
  continue;
79257
79306
  }
79258
- if (!existsSync5(skillFilePath)) continue;
79307
+ if (!existsSync6(skillFilePath)) continue;
79259
79308
  const { skill, error } = await parseSkillFile(skillFilePath, entry.name);
79260
79309
  if (!skill) {
79261
79310
  if (error) {
@@ -81771,7 +81820,7 @@ import { createAmazonBedrock as createAmazonBedrock2 } from "@ai-sdk/amazon-bedr
81771
81820
  import { streamText as streamText2, tool as tool5, stepCountIs, jsonSchema } from "ai";
81772
81821
  import { randomUUID as randomUUID5 } from "crypto";
81773
81822
  import { EventEmitter as EventEmitter5 } from "events";
81774
- import { existsSync as existsSync6 } from "fs";
81823
+ import { existsSync as existsSync7 } from "fs";
81775
81824
  import { readFile as readFile3, stat, readdir as readdir3 } from "fs/promises";
81776
81825
  import { resolve as resolve7, isAbsolute as isAbsolute6, dirname as dirname5, basename, normalize as normalize2, sep as sep5 } from "path";
81777
81826
  var ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
@@ -83782,7 +83831,7 @@ var init_ProbeAgent = __esm({
83782
83831
  } else {
83783
83832
  guidanceCandidates = ["agents.md", "claude.md"];
83784
83833
  }
83785
- if (!existsSync6(rootDirectory)) {
83834
+ if (!existsSync7(rootDirectory)) {
83786
83835
  this._architectureContextLoaded = true;
83787
83836
  return null;
83788
83837
  }
@@ -84554,9 +84603,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
84554
84603
  }
84555
84604
  if (completionAttempted && this.completionPrompt && !options._completionPromptProcessed) {
84556
84605
  if (this.debug) {
84557
- console.log("[DEBUG] Running completion prompt for post-completion validation/review...");
84606
+ console.log("[DEBUG] Running completion prompt as continuation of current session...");
84558
84607
  }
84559
84608
  try {
84609
+ const originalResult = finalResult;
84560
84610
  if (this.tracer) {
84561
84611
  this.tracer.recordEvent("completion_prompt.started", {
84562
84612
  "completion_prompt.original_result_length": finalResult?.length || 0
@@ -84569,24 +84619,66 @@ Here is the result to review:
84569
84619
  ${finalResult}
84570
84620
  </result>
84571
84621
 
84572
- After reviewing, provide your final answer using attempt_completion.`;
84573
- const savedOutputItems = this._outputBuffer ? [...this._outputBuffer.items] : [];
84574
- const savedExtractedBlocks = this._extractedRawBlocks ? [...this._extractedRawBlocks] : [];
84575
- const completionResult2 = await this.answer(completionPromptMessage, [], {
84576
- ...options,
84577
- _completionPromptProcessed: true
84578
- });
84579
- if (this._outputBuffer) {
84580
- this._outputBuffer.items = savedOutputItems;
84622
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is using attempt_completion. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix) using attempt_completion.`;
84623
+ currentMessages.push({ role: "user", content: completionPromptMessage });
84624
+ completionResult = null;
84625
+ completionAttempted = false;
84626
+ const completionMaxIterations = 5;
84627
+ const completionStreamOptions = {
84628
+ model: this.provider ? this.provider(this.model) : this.model,
84629
+ messages: this.prepareMessagesWithImages(currentMessages),
84630
+ tools: tools2,
84631
+ stopWhen: stepCountIs(completionMaxIterations),
84632
+ maxTokens: maxResponseTokens,
84633
+ temperature: 0.3,
84634
+ onStepFinish: ({ toolResults, text, finishReason, usage }) => {
84635
+ if (usage) {
84636
+ this.tokenCounter.recordUsage(usage);
84637
+ }
84638
+ if (options.onStream && text) {
84639
+ options.onStream(text);
84640
+ }
84641
+ if (this.debug) {
84642
+ console.log(`[DEBUG] Completion prompt step finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
84643
+ }
84644
+ }
84645
+ };
84646
+ const providerOpts = this._buildThinkingProviderOptions(maxResponseTokens);
84647
+ if (providerOpts) {
84648
+ completionStreamOptions.providerOptions = providerOpts;
84649
+ }
84650
+ const cpResult = await this.streamTextWithRetryAndFallback(completionStreamOptions);
84651
+ const cpFinalText = await cpResult.text;
84652
+ const cpUsage = await cpResult.usage;
84653
+ if (cpUsage) {
84654
+ this.tokenCounter.recordUsage(cpUsage, cpResult.experimental_providerMetadata);
84655
+ }
84656
+ const cpMessages = await cpResult.response?.messages;
84657
+ if (cpMessages) {
84658
+ for (const msg of cpMessages) {
84659
+ currentMessages.push(msg);
84660
+ }
84661
+ }
84662
+ if (completionResult) {
84663
+ finalResult = completionResult;
84664
+ completionAttempted = true;
84665
+ } else if (cpFinalText && cpFinalText.trim().length > 0) {
84666
+ finalResult = cpFinalText;
84667
+ completionAttempted = true;
84668
+ } else {
84669
+ finalResult = originalResult;
84670
+ completionAttempted = true;
84671
+ if (this.debug) {
84672
+ console.log("[DEBUG] Completion prompt returned empty result, keeping original.");
84673
+ }
84581
84674
  }
84582
- this._extractedRawBlocks = savedExtractedBlocks;
84583
- finalResult = completionResult2;
84584
84675
  if (this.debug) {
84585
- console.log(`[DEBUG] Completion prompt finished. New result length: ${finalResult?.length || 0}`);
84676
+ console.log(`[DEBUG] Completion prompt finished. Final result length: ${finalResult?.length || 0}`);
84586
84677
  }
84587
84678
  if (this.tracer) {
84588
84679
  this.tracer.recordEvent("completion_prompt.completed", {
84589
- "completion_prompt.final_result_length": finalResult?.length || 0
84680
+ "completion_prompt.final_result_length": finalResult?.length || 0,
84681
+ "completion_prompt.used_original": finalResult === originalResult
84590
84682
  });
84591
84683
  }
84592
84684
  } catch (error) {
@@ -85308,7 +85400,7 @@ import {
85308
85400
  ListToolsRequestSchema as ListToolsRequestSchema2,
85309
85401
  McpError
85310
85402
  } from "@modelcontextprotocol/sdk/types.js";
85311
- import { readFileSync as readFileSync2, existsSync as existsSync7 } from "fs";
85403
+ import { readFileSync as readFileSync2, existsSync as existsSync8 } from "fs";
85312
85404
  import { resolve as resolve8 } from "path";
85313
85405
 
85314
85406
  // src/agent/acp/server.js
@@ -85984,7 +86076,7 @@ function readInputContent(input) {
85984
86076
  if (!input) return null;
85985
86077
  try {
85986
86078
  const resolvedPath = resolve8(input);
85987
- if (existsSync7(resolvedPath)) {
86079
+ if (existsSync8(resolvedPath)) {
85988
86080
  return readFileSync2(resolvedPath, "utf-8").trim();
85989
86081
  }
85990
86082
  } catch (error) {
@@ -86672,7 +86764,7 @@ async function main() {
86672
86764
  bashConfig.timeout = timeout;
86673
86765
  }
86674
86766
  if (config.bashWorkingDir) {
86675
- if (!existsSync7(config.bashWorkingDir)) {
86767
+ if (!existsSync8(config.bashWorkingDir)) {
86676
86768
  console.error(`Error: Bash working directory does not exist: ${config.bashWorkingDir}`);
86677
86769
  process.exit(1);
86678
86770
  }
@@ -10,6 +10,7 @@ import { extract } from '../extract.js';
10
10
  import { delegate } from '../delegate.js';
11
11
  import { analyzeAll } from './analyzeAll.js';
12
12
  import { searchSchema, querySchema, extractSchema, delegateSchema, analyzeAllSchema, searchDescription, queryDescription, extractDescription, delegateDescription, analyzeAllDescription, parseTargets, parseAndResolvePaths, resolveTargetPath } from './common.js';
13
+ import { existsSync } from 'fs';
13
14
  import { formatErrorForAI } from '../utils/error-types.js';
14
15
  import { annotateOutputWithHashes } from './hashline.js';
15
16
 
@@ -118,6 +119,18 @@ function parseDelegatedTargets(rawResponse) {
118
119
  return normalizeTargets(fallbackTargetsFromText(trimmed));
119
120
  }
120
121
 
122
+ function splitTargetSuffix(target) {
123
+ const searchStart = (target.length > 2 && target[1] === ':' && /[a-zA-Z]/.test(target[0])) ? 2 : 0;
124
+ const colonIdx = target.indexOf(':', searchStart);
125
+ const hashIdx = target.indexOf('#');
126
+ if (colonIdx !== -1 && (hashIdx === -1 || colonIdx < hashIdx)) {
127
+ return { filePart: target.substring(0, colonIdx), suffix: target.substring(colonIdx) };
128
+ } else if (hashIdx !== -1) {
129
+ return { filePart: target.substring(0, hashIdx), suffix: target.substring(hashIdx) };
130
+ }
131
+ return { filePart: target, suffix: '' };
132
+ }
133
+
121
134
  function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, allowTests }) {
122
135
  return [
123
136
  'You are a code-search subagent. Your job is to find ALL relevant code locations for the given query.',
@@ -286,13 +299,61 @@ export const searchTool = (options = {}) => {
286
299
  return fallbackResult;
287
300
  }
288
301
 
289
- // Resolve relative paths against the actual search directory, not the general cwd.
290
- // The delegate returns paths relative to where the search was performed (searchPaths[0]),
291
- // which may differ from options.cwd when the user specifies a path parameter.
302
+ // The delegate runs from workspace root (allowedFolders[0] or cwd), NOT from searchPaths[0].
303
+ // It returns paths relative to that workspace root. Resolve against the same base.
304
+ const delegateBase = options.allowedFolders?.[0] || options.cwd || '.';
292
305
  const resolutionBase = searchPaths[0] || options.cwd || '.';
293
- const resolvedTargets = targets.map(target => resolveTargetPath(target, resolutionBase));
306
+ const resolvedTargets = targets.map(target => resolveTargetPath(target, delegateBase));
307
+
308
+ // Auto-fix: detect and repair invalid paths (doubled segments, AI hallucinations)
309
+ const validatedTargets = [];
310
+ for (const target of resolvedTargets) {
311
+ const { filePart, suffix } = splitTargetSuffix(target);
312
+
313
+ // 1. Path exists as-is
314
+ if (existsSync(filePart)) {
315
+ validatedTargets.push(target);
316
+ continue;
317
+ }
318
+
319
+ // 2. Detect doubled directory segments: /ws/proj/proj/src → /ws/proj/src
320
+ let fixed = false;
321
+ const parts = filePart.split('/').filter(Boolean);
322
+ for (let i = 0; i < parts.length - 1; i++) {
323
+ if (parts[i] === parts[i + 1]) {
324
+ const candidate = '/' + [...parts.slice(0, i), ...parts.slice(i + 1)].join('/');
325
+ if (existsSync(candidate)) {
326
+ validatedTargets.push(candidate + suffix);
327
+ if (debug) console.error(`[search-delegate] Fixed doubled path segment: ${filePart} → ${candidate}`);
328
+ fixed = true;
329
+ break;
330
+ }
331
+ }
332
+ }
333
+ if (fixed) continue;
334
+
335
+ // 3. Try resolving against alternative bases (searchPaths[0], cwd)
336
+ for (const altBase of [resolutionBase, options.cwd].filter(Boolean)) {
337
+ if (altBase === delegateBase) continue;
338
+ const altResolved = resolveTargetPath(target, altBase);
339
+ const { filePart: altFile } = splitTargetSuffix(altResolved);
340
+ if (existsSync(altFile)) {
341
+ validatedTargets.push(altResolved);
342
+ if (debug) console.error(`[search-delegate] Resolved with alt base: ${filePart} → ${altFile}`);
343
+ fixed = true;
344
+ break;
345
+ }
346
+ }
347
+ if (fixed) continue;
348
+
349
+ // 4. Keep target anyway (probe binary will report the error)
350
+ // but log a warning
351
+ if (debug) console.error(`[search-delegate] Warning: target may not exist: ${filePart}`);
352
+ validatedTargets.push(target);
353
+ }
354
+
294
355
  const extractOptions = {
295
- files: resolvedTargets,
356
+ files: validatedTargets,
296
357
  cwd: resolutionBase,
297
358
  allowTests: allow_tests ?? true
298
359
  };