@probelabs/probe 0.6.0-rc290 → 0.6.0-rc291

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.
@@ -1444,11 +1444,13 @@ export class ProbeAgent {
1444
1444
  result = await this._executeWithVercelProvider(options, controller);
1445
1445
  }
1446
1446
 
1447
- // Wrap textStream so limiter slot is held until stream completes
1447
+ // Wrap textStream so limiter slot is held until stream completes.
1448
+ // result.textStream is a read-only getter on DefaultStreamTextResult,
1449
+ // so we wrap the result in a Proxy that intercepts the textStream property.
1448
1450
  if (limiter && result.textStream) {
1449
1451
  const originalStream = result.textStream;
1450
1452
  const debug = this.debug;
1451
- result.textStream = (async function* () {
1453
+ const wrappedStream = (async function* () {
1452
1454
  try {
1453
1455
  for await (const chunk of originalStream) {
1454
1456
  yield chunk;
@@ -1461,6 +1463,13 @@ export class ProbeAgent {
1461
1463
  }
1462
1464
  }
1463
1465
  })();
1466
+ return new Proxy(result, {
1467
+ get(target, prop) {
1468
+ if (prop === 'textStream') return wrappedStream;
1469
+ const value = target[prop];
1470
+ return typeof value === 'function' ? value.bind(target) : value;
1471
+ }
1472
+ });
1464
1473
  } else if (limiter) {
1465
1474
  // No textStream (shouldn't happen, but release just in case)
1466
1475
  limiter.release(null);
@@ -3499,6 +3508,7 @@ Follow these instructions carefully:
3499
3508
  return true;
3500
3509
  }
3501
3510
  }
3511
+
3502
3512
  }
3503
3513
 
3504
3514
  return false;
@@ -3529,6 +3539,24 @@ Follow these instructions carefully:
3529
3539
  }
3530
3540
  }
3531
3541
 
3542
+ // Force text-only response after 3 consecutive tool errors
3543
+ // (e.g. workspace deleted mid-run — let the model produce its answer)
3544
+ if (steps.length >= 3) {
3545
+ const last3 = steps.slice(-3);
3546
+ const allErrors = last3.every(s =>
3547
+ s.toolResults?.length > 0 && s.toolResults.every(tr => {
3548
+ const r = typeof tr.result === 'string' ? tr.result : '';
3549
+ return r.includes('<error ') || r.includes('does not exist');
3550
+ })
3551
+ );
3552
+ if (allErrors) {
3553
+ if (this.debug) {
3554
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
3555
+ }
3556
+ return { toolChoice: 'none' };
3557
+ }
3558
+ }
3559
+
3532
3560
  const lastStep = steps[steps.length - 1];
3533
3561
  const modelJustStopped = lastStep?.finishReason === 'stop'
3534
3562
  && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
@@ -3565,7 +3593,8 @@ Here is the result to review:
3565
3593
  ${resultToReview}
3566
3594
  </result>
3567
3595
 
3568
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
3596
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
3597
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action — NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
3569
3598
 
3570
3599
  return {
3571
3600
  userMessage: completionPromptMessage,
@@ -3774,7 +3803,8 @@ Here is the result to review:
3774
3803
  ${finalResult}
3775
3804
  </result>
3776
3805
 
3777
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
3806
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
3807
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action — NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
3778
3808
 
3779
3809
  currentMessages.push({ role: 'user', content: completionPromptMessage });
3780
3810
 
@@ -478,7 +478,11 @@ export const searchTool = (options = {}) => {
478
478
  return result;
479
479
  } catch (error) {
480
480
  console.error('Error executing search command:', error);
481
- return formatErrorForAI(error);
481
+ const formatted = formatErrorForAI(error);
482
+ if (error.category === 'path_error' || error.message?.includes('does not exist')) {
483
+ return formatted + '\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.';
484
+ }
485
+ return formatted;
482
486
  }
483
487
  }
484
488
 
@@ -181,14 +181,14 @@ export function categorizeError(error) {
181
181
  errorCode === 'enoent') {
182
182
  return new PathError(message, {
183
183
  originalError: error,
184
- suggestion: 'The specified path does not exist. Please verify the path or use a different directory.'
184
+ suggestion: 'The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.'
185
185
  });
186
186
  }
187
187
 
188
188
  if (lowerMessage.includes('not a directory') || errorCode === 'enotdir') {
189
189
  return new PathError(message, {
190
190
  originalError: error,
191
- suggestion: 'The path is not a directory. Please provide a valid directory path.'
191
+ suggestion: 'The path is not a directory. Use the listFiles tool to find the correct directory, then retry.'
192
192
  });
193
193
  }
194
194
 
@@ -110,7 +110,7 @@ export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
110
110
  }
111
111
  if (error.code === 'ENOENT') {
112
112
  throw new PathError(`Path does not exist: ${normalizedPath}`, {
113
- suggestion: 'The specified path does not exist. Please verify the path is correct or use a different directory.',
113
+ suggestion: 'The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.',
114
114
  details: { path: normalizedPath }
115
115
  });
116
116
  }
@@ -25324,13 +25324,13 @@ function categorizeError(error40) {
25324
25324
  if (lowerMessage.includes("path does not exist") || lowerMessage.includes("no such file or directory") || errorCode === "enoent") {
25325
25325
  return new PathError(message, {
25326
25326
  originalError: error40,
25327
- suggestion: "The specified path does not exist. Please verify the path or use a different directory."
25327
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path."
25328
25328
  });
25329
25329
  }
25330
25330
  if (lowerMessage.includes("not a directory") || errorCode === "enotdir") {
25331
25331
  return new PathError(message, {
25332
25332
  originalError: error40,
25333
- suggestion: "The path is not a directory. Please provide a valid directory path."
25333
+ suggestion: "The path is not a directory. Use the listFiles tool to find the correct directory, then retry."
25334
25334
  });
25335
25335
  }
25336
25336
  if (lowerMessage.includes("permission denied") || errorCode === "eacces") {
@@ -25586,7 +25586,7 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
25586
25586
  }
25587
25587
  if (error40.code === "ENOENT") {
25588
25588
  throw new PathError(`Path does not exist: ${normalizedPath}`, {
25589
- suggestion: "The specified path does not exist. Please verify the path is correct or use a different directory.",
25589
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.",
25590
25590
  details: { path: normalizedPath }
25591
25591
  });
25592
25592
  }
@@ -27643,7 +27643,11 @@ var init_vercel = __esm({
27643
27643
  return result;
27644
27644
  } catch (error40) {
27645
27645
  console.error("Error executing search command:", error40);
27646
- return formatErrorForAI(error40);
27646
+ const formatted = formatErrorForAI(error40);
27647
+ if (error40.category === "path_error" || error40.message?.includes("does not exist")) {
27648
+ return formatted + "\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.";
27649
+ }
27650
+ return formatted;
27647
27651
  }
27648
27652
  }
27649
27653
  try {
@@ -31008,6 +31012,9 @@ var require_utils = __commonJS({
31008
31012
  sandboxGlobal
31009
31013
  };
31010
31014
  context.prototypeWhitelist.set(Object.getPrototypeOf([][Symbol.iterator]()), /* @__PURE__ */ new Set());
31015
+ context.prototypeWhitelist.set(Object.getPrototypeOf(""[Symbol.iterator]()), /* @__PURE__ */ new Set());
31016
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Set())[Symbol.iterator]()), /* @__PURE__ */ new Set());
31017
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Map())[Symbol.iterator]()), /* @__PURE__ */ new Set());
31011
31018
  return context;
31012
31019
  }
31013
31020
  function createExecContext(sandbox, executionTree, evalContext) {
@@ -32146,6 +32153,18 @@ var require_executor = __commonJS({
32146
32153
  a = void 0;
32147
32154
  }
32148
32155
  }
32156
+ if (op === 29 && !a) {
32157
+ done(void 0, a);
32158
+ return;
32159
+ }
32160
+ if (op === 30 && a) {
32161
+ done(void 0, a);
32162
+ return;
32163
+ }
32164
+ if (op === 85 && a !== null && a !== void 0) {
32165
+ done(void 0, a);
32166
+ return;
32167
+ }
32149
32168
  let bobj;
32150
32169
  try {
32151
32170
  let ad;
@@ -32208,6 +32227,18 @@ var require_executor = __commonJS({
32208
32227
  a = void 0;
32209
32228
  }
32210
32229
  }
32230
+ if (op === 29 && !a) {
32231
+ done(void 0, a);
32232
+ return;
32233
+ }
32234
+ if (op === 30 && a) {
32235
+ done(void 0, a);
32236
+ return;
32237
+ }
32238
+ if (op === 85 && a !== null && a !== void 0) {
32239
+ done(void 0, a);
32240
+ return;
32241
+ }
32211
32242
  let bobj;
32212
32243
  try {
32213
32244
  bobj = syncDone((d) => execSync(ticks, tree[2], scope, context, d, inLoopOrSwitch)).result;
@@ -100588,7 +100619,7 @@ var init_ProbeAgent = __esm({
100588
100619
  if (limiter && result.textStream) {
100589
100620
  const originalStream = result.textStream;
100590
100621
  const debug = this.debug;
100591
- result.textStream = (async function* () {
100622
+ const wrappedStream = (async function* () {
100592
100623
  try {
100593
100624
  for await (const chunk of originalStream) {
100594
100625
  yield chunk;
@@ -100601,6 +100632,13 @@ var init_ProbeAgent = __esm({
100601
100632
  }
100602
100633
  }
100603
100634
  })();
100635
+ return new Proxy(result, {
100636
+ get(target, prop) {
100637
+ if (prop === "textStream") return wrappedStream;
100638
+ const value = target[prop];
100639
+ return typeof value === "function" ? value.bind(target) : value;
100640
+ }
100641
+ });
100604
100642
  } else if (limiter) {
100605
100643
  limiter.release(null);
100606
100644
  }
@@ -102254,6 +102292,21 @@ You are working with a workspace. Available paths: ${workspaceDesc}
102254
102292
  }
102255
102293
  }
102256
102294
  }
102295
+ if (steps.length >= 3) {
102296
+ const last3 = steps.slice(-3);
102297
+ const allErrors = last3.every(
102298
+ (s) => s.toolResults?.length > 0 && s.toolResults.every((tr) => {
102299
+ const r = typeof tr.result === "string" ? tr.result : "";
102300
+ return r.includes("<error ") || r.includes("does not exist");
102301
+ })
102302
+ );
102303
+ if (allErrors) {
102304
+ if (this.debug) {
102305
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
102306
+ }
102307
+ return { toolChoice: "none" };
102308
+ }
102309
+ }
102257
102310
  const lastStep = steps[steps.length - 1];
102258
102311
  const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
102259
102312
  if (modelJustStopped) {
@@ -102282,7 +102335,8 @@ Here is the result to review:
102282
102335
  ${resultToReview}
102283
102336
  </result>
102284
102337
 
102285
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
102338
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
102339
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
102286
102340
  return {
102287
102341
  userMessage: completionPromptMessage,
102288
102342
  toolChoice: "none"
@@ -102441,7 +102495,8 @@ Here is the result to review:
102441
102495
  ${finalResult}
102442
102496
  </result>
102443
102497
 
102444
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
102498
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
102499
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
102445
102500
  currentMessages.push({ role: "user", content: completionPromptMessage });
102446
102501
  const completionStreamOptions = {
102447
102502
  model: this.provider ? this.provider(this.model) : this.model,
package/cjs/index.cjs CHANGED
@@ -1447,13 +1447,13 @@ function categorizeError(error40) {
1447
1447
  if (lowerMessage.includes("path does not exist") || lowerMessage.includes("no such file or directory") || errorCode === "enoent") {
1448
1448
  return new PathError(message, {
1449
1449
  originalError: error40,
1450
- suggestion: "The specified path does not exist. Please verify the path or use a different directory."
1450
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path."
1451
1451
  });
1452
1452
  }
1453
1453
  if (lowerMessage.includes("not a directory") || errorCode === "enotdir") {
1454
1454
  return new PathError(message, {
1455
1455
  originalError: error40,
1456
- suggestion: "The path is not a directory. Please provide a valid directory path."
1456
+ suggestion: "The path is not a directory. Use the listFiles tool to find the correct directory, then retry."
1457
1457
  });
1458
1458
  }
1459
1459
  if (lowerMessage.includes("permission denied") || errorCode === "eacces") {
@@ -1709,7 +1709,7 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
1709
1709
  }
1710
1710
  if (error40.code === "ENOENT") {
1711
1711
  throw new PathError(`Path does not exist: ${normalizedPath}`, {
1712
- suggestion: "The specified path does not exist. Please verify the path is correct or use a different directory.",
1712
+ suggestion: "The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.",
1713
1713
  details: { path: normalizedPath }
1714
1714
  });
1715
1715
  }
@@ -82700,6 +82700,9 @@ var require_utils2 = __commonJS({
82700
82700
  sandboxGlobal
82701
82701
  };
82702
82702
  context.prototypeWhitelist.set(Object.getPrototypeOf([][Symbol.iterator]()), /* @__PURE__ */ new Set());
82703
+ context.prototypeWhitelist.set(Object.getPrototypeOf(""[Symbol.iterator]()), /* @__PURE__ */ new Set());
82704
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Set())[Symbol.iterator]()), /* @__PURE__ */ new Set());
82705
+ context.prototypeWhitelist.set(Object.getPrototypeOf((/* @__PURE__ */ new Map())[Symbol.iterator]()), /* @__PURE__ */ new Set());
82703
82706
  return context;
82704
82707
  }
82705
82708
  function createExecContext(sandbox, executionTree, evalContext) {
@@ -83838,6 +83841,18 @@ var require_executor = __commonJS({
83838
83841
  a = void 0;
83839
83842
  }
83840
83843
  }
83844
+ if (op === 29 && !a) {
83845
+ done(void 0, a);
83846
+ return;
83847
+ }
83848
+ if (op === 30 && a) {
83849
+ done(void 0, a);
83850
+ return;
83851
+ }
83852
+ if (op === 85 && a !== null && a !== void 0) {
83853
+ done(void 0, a);
83854
+ return;
83855
+ }
83841
83856
  let bobj;
83842
83857
  try {
83843
83858
  let ad;
@@ -83900,6 +83915,18 @@ var require_executor = __commonJS({
83900
83915
  a = void 0;
83901
83916
  }
83902
83917
  }
83918
+ if (op === 29 && !a) {
83919
+ done(void 0, a);
83920
+ return;
83921
+ }
83922
+ if (op === 30 && a) {
83923
+ done(void 0, a);
83924
+ return;
83925
+ }
83926
+ if (op === 85 && a !== null && a !== void 0) {
83927
+ done(void 0, a);
83928
+ return;
83929
+ }
83903
83930
  let bobj;
83904
83931
  try {
83905
83932
  bobj = syncDone((d) => execSync(ticks, tree[2], scope, context, d, inLoopOrSwitch)).result;
@@ -97538,7 +97565,7 @@ var init_ProbeAgent = __esm({
97538
97565
  if (limiter && result.textStream) {
97539
97566
  const originalStream = result.textStream;
97540
97567
  const debug = this.debug;
97541
- result.textStream = (async function* () {
97568
+ const wrappedStream = (async function* () {
97542
97569
  try {
97543
97570
  for await (const chunk of originalStream) {
97544
97571
  yield chunk;
@@ -97551,6 +97578,13 @@ var init_ProbeAgent = __esm({
97551
97578
  }
97552
97579
  }
97553
97580
  })();
97581
+ return new Proxy(result, {
97582
+ get(target, prop) {
97583
+ if (prop === "textStream") return wrappedStream;
97584
+ const value = target[prop];
97585
+ return typeof value === "function" ? value.bind(target) : value;
97586
+ }
97587
+ });
97554
97588
  } else if (limiter) {
97555
97589
  limiter.release(null);
97556
97590
  }
@@ -99204,6 +99238,21 @@ You are working with a workspace. Available paths: ${workspaceDesc}
99204
99238
  }
99205
99239
  }
99206
99240
  }
99241
+ if (steps.length >= 3) {
99242
+ const last3 = steps.slice(-3);
99243
+ const allErrors = last3.every(
99244
+ (s) => s.toolResults?.length > 0 && s.toolResults.every((tr) => {
99245
+ const r = typeof tr.result === "string" ? tr.result : "";
99246
+ return r.includes("<error ") || r.includes("does not exist");
99247
+ })
99248
+ );
99249
+ if (allErrors) {
99250
+ if (this.debug) {
99251
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
99252
+ }
99253
+ return { toolChoice: "none" };
99254
+ }
99255
+ }
99207
99256
  const lastStep = steps[steps.length - 1];
99208
99257
  const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
99209
99258
  if (modelJustStopped) {
@@ -99232,7 +99281,8 @@ Here is the result to review:
99232
99281
  ${resultToReview}
99233
99282
  </result>
99234
99283
 
99235
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
99284
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
99285
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
99236
99286
  return {
99237
99287
  userMessage: completionPromptMessage,
99238
99288
  toolChoice: "none"
@@ -99391,7 +99441,8 @@ Here is the result to review:
99391
99441
  ${finalResult}
99392
99442
  </result>
99393
99443
 
99394
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
99444
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
99445
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action \u2014 NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
99395
99446
  currentMessages.push({ role: "user", content: completionPromptMessage });
99396
99447
  const completionStreamOptions = {
99397
99448
  model: this.provider ? this.provider(this.model) : this.model,
@@ -101417,7 +101468,11 @@ var init_vercel = __esm({
101417
101468
  return result;
101418
101469
  } catch (error40) {
101419
101470
  console.error("Error executing search command:", error40);
101420
- return formatErrorForAI(error40);
101471
+ const formatted = formatErrorForAI(error40);
101472
+ if (error40.category === "path_error" || error40.message?.includes("does not exist")) {
101473
+ return formatted + "\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.";
101474
+ }
101475
+ return formatted;
101421
101476
  }
101422
101477
  }
101423
101478
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc290",
3
+ "version": "0.6.0-rc291",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -1444,11 +1444,13 @@ export class ProbeAgent {
1444
1444
  result = await this._executeWithVercelProvider(options, controller);
1445
1445
  }
1446
1446
 
1447
- // Wrap textStream so limiter slot is held until stream completes
1447
+ // Wrap textStream so limiter slot is held until stream completes.
1448
+ // result.textStream is a read-only getter on DefaultStreamTextResult,
1449
+ // so we wrap the result in a Proxy that intercepts the textStream property.
1448
1450
  if (limiter && result.textStream) {
1449
1451
  const originalStream = result.textStream;
1450
1452
  const debug = this.debug;
1451
- result.textStream = (async function* () {
1453
+ const wrappedStream = (async function* () {
1452
1454
  try {
1453
1455
  for await (const chunk of originalStream) {
1454
1456
  yield chunk;
@@ -1461,6 +1463,13 @@ export class ProbeAgent {
1461
1463
  }
1462
1464
  }
1463
1465
  })();
1466
+ return new Proxy(result, {
1467
+ get(target, prop) {
1468
+ if (prop === 'textStream') return wrappedStream;
1469
+ const value = target[prop];
1470
+ return typeof value === 'function' ? value.bind(target) : value;
1471
+ }
1472
+ });
1464
1473
  } else if (limiter) {
1465
1474
  // No textStream (shouldn't happen, but release just in case)
1466
1475
  limiter.release(null);
@@ -3499,6 +3508,7 @@ Follow these instructions carefully:
3499
3508
  return true;
3500
3509
  }
3501
3510
  }
3511
+
3502
3512
  }
3503
3513
 
3504
3514
  return false;
@@ -3529,6 +3539,24 @@ Follow these instructions carefully:
3529
3539
  }
3530
3540
  }
3531
3541
 
3542
+ // Force text-only response after 3 consecutive tool errors
3543
+ // (e.g. workspace deleted mid-run — let the model produce its answer)
3544
+ if (steps.length >= 3) {
3545
+ const last3 = steps.slice(-3);
3546
+ const allErrors = last3.every(s =>
3547
+ s.toolResults?.length > 0 && s.toolResults.every(tr => {
3548
+ const r = typeof tr.result === 'string' ? tr.result : '';
3549
+ return r.includes('<error ') || r.includes('does not exist');
3550
+ })
3551
+ );
3552
+ if (allErrors) {
3553
+ if (this.debug) {
3554
+ console.log(`[DEBUG] prepareStep: 3 consecutive tool errors, forcing toolChoice=none`);
3555
+ }
3556
+ return { toolChoice: 'none' };
3557
+ }
3558
+ }
3559
+
3532
3560
  const lastStep = steps[steps.length - 1];
3533
3561
  const modelJustStopped = lastStep?.finishReason === 'stop'
3534
3562
  && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
@@ -3565,7 +3593,8 @@ Here is the result to review:
3565
3593
  ${resultToReview}
3566
3594
  </result>
3567
3595
 
3568
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
3596
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
3597
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action — NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
3569
3598
 
3570
3599
  return {
3571
3600
  userMessage: completionPromptMessage,
@@ -3774,7 +3803,8 @@ Here is the result to review:
3774
3803
  ${finalResult}
3775
3804
  </result>
3776
3805
 
3777
- Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. 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).`;
3806
+ IMPORTANT: First review ALL completed work in the conversation above before taking any action.
3807
+ Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If your text has inaccuracies, fix the text. Only call a tool if you find a genuinely MISSING action — NEVER redo work that was already completed successfully. Respond with the COMPLETE corrected answer.`;
3778
3808
 
3779
3809
  currentMessages.push({ role: 'user', content: completionPromptMessage });
3780
3810
 
@@ -478,7 +478,11 @@ export const searchTool = (options = {}) => {
478
478
  return result;
479
479
  } catch (error) {
480
480
  console.error('Error executing search command:', error);
481
- return formatErrorForAI(error);
481
+ const formatted = formatErrorForAI(error);
482
+ if (error.category === 'path_error' || error.message?.includes('does not exist')) {
483
+ return formatted + '\n\nThe path does not exist. Use the listFiles tool to verify the correct directory structure before retrying. If the workspace itself is gone, output your final answer with whatever information you have.';
484
+ }
485
+ return formatted;
482
486
  }
483
487
  }
484
488
 
@@ -181,14 +181,14 @@ export function categorizeError(error) {
181
181
  errorCode === 'enoent') {
182
182
  return new PathError(message, {
183
183
  originalError: error,
184
- suggestion: 'The specified path does not exist. Please verify the path or use a different directory.'
184
+ suggestion: 'The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.'
185
185
  });
186
186
  }
187
187
 
188
188
  if (lowerMessage.includes('not a directory') || errorCode === 'enotdir') {
189
189
  return new PathError(message, {
190
190
  originalError: error,
191
- suggestion: 'The path is not a directory. Please provide a valid directory path.'
191
+ suggestion: 'The path is not a directory. Use the listFiles tool to find the correct directory, then retry.'
192
192
  });
193
193
  }
194
194
 
@@ -110,7 +110,7 @@ export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
110
110
  }
111
111
  if (error.code === 'ENOENT') {
112
112
  throw new PathError(`Path does not exist: ${normalizedPath}`, {
113
- suggestion: 'The specified path does not exist. Please verify the path is correct or use a different directory.',
113
+ suggestion: 'The specified path does not exist. Use the listFiles tool to check the correct directory structure, then retry with a valid path.',
114
114
  details: { path: normalizedPath }
115
115
  });
116
116
  }