@probelabs/probe 0.6.0-rc285 → 0.6.0-rc286

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.
@@ -93,7 +93,7 @@ import { formatAvailableSkillsXml as formatAvailableSkills } from './skills/form
93
93
  import { createSkillToolInstances } from './skills/tools.js';
94
94
  import { RetryManager, createRetryManagerFromEnv } from './RetryManager.js';
95
95
  import { FallbackManager, createFallbackManagerFromEnv, buildFallbackProvidersFromEnv } from './FallbackManager.js';
96
- import { handleContextLimitError } from './contextCompactor.js';
96
+ import { handleContextLimitError, compactMessages, calculateCompactionStats } from './contextCompactor.js';
97
97
  import { formatErrorForAI, ParameterError } from '../utils/error-types.js';
98
98
  import { getCommonPrefix, toRelativePath, safeRealpath } from '../utils/path-validation.js';
99
99
  import { truncateIfNeeded, getMaxOutputTokens } from './outputTruncator.js';
@@ -3227,6 +3227,25 @@ Follow these instructions carefully:
3227
3227
  ];
3228
3228
  }
3229
3229
 
3230
+ // Proactively compact for multi-turn conversations.
3231
+ // On turn 2+, previous turns contain full tool call/result history which can
3232
+ // be 50K+ tokens. This drowns out the new user message and causes the model to
3233
+ // focus on prior context rather than the new question.
3234
+ // compactMessages strips intermediate monologue from completed segments,
3235
+ // keeping user messages + final answers from prior turns.
3236
+ // Must run AFTER adding the new user message so the compactor sees 2+ segments
3237
+ // (completed prior turns + the new incomplete turn), preserving the latest segment.
3238
+ if (this.history.length > 0) {
3239
+ const compacted = compactMessages(currentMessages, { keepLastSegment: true, minSegmentsToKeep: 1 });
3240
+ if (compacted.length < currentMessages.length) {
3241
+ const stats = calculateCompactionStats(currentMessages, compacted);
3242
+ if (this.debug) {
3243
+ console.log(`[DEBUG] Proactive history compaction: ${currentMessages.length} → ${compacted.length} messages (${stats.reductionPercent}% reduction, ~${stats.tokensSaved} tokens saved)`);
3244
+ }
3245
+ currentMessages = compacted;
3246
+ }
3247
+ }
3248
+
3230
3249
  let currentIteration = 0;
3231
3250
  let finalResult = 'I was unable to complete your request due to reaching the maximum number of tool iterations.';
3232
3251
 
@@ -4141,8 +4160,6 @@ Double-check your response based on the criteria above. If everything looks good
4141
4160
  * @returns {Object} Compaction statistics
4142
4161
  */
4143
4162
  async compactHistory(options = {}) {
4144
- const { compactMessages, calculateCompactionStats } = await import('./contextCompactor.js');
4145
-
4146
4163
  if (this.history.length === 0) {
4147
4164
  if (this.debug) {
4148
4165
  console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
@@ -2755,7 +2755,11 @@ async function extractBinary(assetPath, outputDir) {
2755
2755
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2756
2756
  console.log(`Extracting zip to ${extractDir}...`);
2757
2757
  }
2758
- await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
2758
+ if (isWindows) {
2759
+ await exec(`powershell -NoProfile -Command "Expand-Archive -Path '${assetPath}' -DestinationPath '${extractDir}' -Force"`);
2760
+ } else {
2761
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
2762
+ }
2759
2763
  } else {
2760
2764
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2761
2765
  console.log(`Copying binary directly to ${binaryPath}`);
@@ -9651,14 +9655,15 @@ var init_vercel = __esm({
9651
9655
  const parsedTargets = parseTargets(targets);
9652
9656
  extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
9653
9657
  if (options.allowedFolders && options.allowedFolders.length > 0) {
9658
+ const { join: pathJoin, sep: pathSep } = await import("path");
9654
9659
  extractFiles = extractFiles.map((target) => {
9655
9660
  const { filePart, suffix } = splitTargetSuffix(target);
9656
9661
  if (existsSync(filePart)) return target;
9657
- const cwdPrefix = effectiveCwd.endsWith("/") ? effectiveCwd : effectiveCwd + "/";
9662
+ const cwdPrefix = effectiveCwd.endsWith(pathSep) ? effectiveCwd : effectiveCwd + pathSep;
9658
9663
  const relativePart = filePart.startsWith(cwdPrefix) ? filePart.slice(cwdPrefix.length) : null;
9659
9664
  if (relativePart) {
9660
9665
  for (const folder of options.allowedFolders) {
9661
- const candidate = folder + "/" + relativePart;
9666
+ const candidate = pathJoin(folder, relativePart);
9662
9667
  if (existsSync(candidate)) {
9663
9668
  if (debug) console.error(`[extract] Auto-fixed path: ${filePart} \u2192 ${candidate}`);
9664
9669
  return candidate + suffix;
@@ -9666,11 +9671,12 @@ var init_vercel = __esm({
9666
9671
  }
9667
9672
  }
9668
9673
  for (const folder of options.allowedFolders) {
9669
- const folderPrefix = folder.endsWith("/") ? folder : folder + "/";
9670
- const wsParent = folderPrefix.replace(/[^/]+\/$/, "");
9674
+ const folderPrefix = folder.endsWith(pathSep) ? folder : folder + pathSep;
9675
+ const sepEscaped = pathSep === "\\" ? "\\\\" : pathSep;
9676
+ const wsParent = folderPrefix.replace(new RegExp("[^" + sepEscaped + "]+" + sepEscaped + "$"), "");
9671
9677
  if (filePart.startsWith(wsParent)) {
9672
9678
  const tail = filePart.slice(wsParent.length);
9673
- const candidate = folderPrefix + tail;
9679
+ const candidate = pathJoin(folderPrefix, tail);
9674
9680
  if (candidate !== filePart && existsSync(candidate)) {
9675
9681
  if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} \u2192 ${candidate}`);
9676
9682
  return candidate + suffix;
@@ -79910,14 +79916,6 @@ var init_FallbackManager = __esm({
79910
79916
  });
79911
79917
 
79912
79918
  // src/agent/contextCompactor.js
79913
- var contextCompactor_exports = {};
79914
- __export(contextCompactor_exports, {
79915
- calculateCompactionStats: () => calculateCompactionStats,
79916
- compactMessages: () => compactMessages,
79917
- handleContextLimitError: () => handleContextLimitError,
79918
- identifyMessageSegments: () => identifyMessageSegments,
79919
- isContextLimitError: () => isContextLimitError
79920
- });
79921
79919
  function isContextLimitError(error) {
79922
79920
  if (!error) return false;
79923
79921
  const errorMessage = (typeof error === "string" ? error : error?.message || "").toLowerCase();
@@ -84234,6 +84232,16 @@ You are working with a workspace. Available paths: ${workspaceDesc}
84234
84232
  userMessage
84235
84233
  ];
84236
84234
  }
84235
+ if (this.history.length > 0) {
84236
+ const compacted = compactMessages(currentMessages, { keepLastSegment: true, minSegmentsToKeep: 1 });
84237
+ if (compacted.length < currentMessages.length) {
84238
+ const stats = calculateCompactionStats(currentMessages, compacted);
84239
+ if (this.debug) {
84240
+ console.log(`[DEBUG] Proactive history compaction: ${currentMessages.length} \u2192 ${compacted.length} messages (${stats.reductionPercent}% reduction, ~${stats.tokensSaved} tokens saved)`);
84241
+ }
84242
+ currentMessages = compacted;
84243
+ }
84244
+ }
84237
84245
  let currentIteration = 0;
84238
84246
  let finalResult = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
84239
84247
  const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
@@ -84934,7 +84942,6 @@ Double-check your response based on the criteria above. If everything looks good
84934
84942
  * @returns {Object} Compaction statistics
84935
84943
  */
84936
84944
  async compactHistory(options = {}) {
84937
- const { compactMessages: compactMessages2, calculateCompactionStats: calculateCompactionStats2 } = await Promise.resolve().then(() => (init_contextCompactor(), contextCompactor_exports));
84938
84945
  if (this.history.length === 0) {
84939
84946
  if (this.debug) {
84940
84947
  console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
@@ -84949,8 +84956,8 @@ Double-check your response based on the criteria above. If everything looks good
84949
84956
  tokensSaved: 0
84950
84957
  };
84951
84958
  }
84952
- const compactedMessages = compactMessages2(this.history, options);
84953
- const stats = calculateCompactionStats2(this.history, compactedMessages);
84959
+ const compactedMessages = compactMessages(this.history, options);
84960
+ const stats = calculateCompactionStats(this.history, compactedMessages);
84954
84961
  this.history = compactedMessages;
84955
84962
  try {
84956
84963
  await this.storageAdapter.clearHistory(this.sessionId);
@@ -743,7 +743,11 @@ async function extractBinary(assetPath, outputDir) {
743
743
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
744
744
  console.log(`Extracting zip to ${extractDir}...`);
745
745
  }
746
- await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
746
+ if (isWindows) {
747
+ await exec(`powershell -NoProfile -Command "Expand-Archive -Path '${assetPath}' -DestinationPath '${extractDir}' -Force"`);
748
+ } else {
749
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
750
+ }
747
751
  } else {
748
752
  // Assume it's a direct binary
749
753
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
@@ -691,19 +691,20 @@ export const extractTool = (options = {}) => {
691
691
  // model constructs wrong absolute paths (e.g., /workspace/gateway/file.go
692
692
  // instead of /workspace/tyk/gateway/file.go)
693
693
  if (options.allowedFolders && options.allowedFolders.length > 0) {
694
+ const { join: pathJoin, sep: pathSep } = await import('path');
694
695
  extractFiles = extractFiles.map(target => {
695
696
  const { filePart, suffix } = splitTargetSuffix(target);
696
697
  if (existsSync(filePart)) return target;
697
698
 
698
699
  // Try resolving the relative tail against each allowedFolder
699
- const cwdPrefix = (effectiveCwd.endsWith('/') ? effectiveCwd : effectiveCwd + '/');
700
+ const cwdPrefix = effectiveCwd.endsWith(pathSep) ? effectiveCwd : effectiveCwd + pathSep;
700
701
  const relativePart = filePart.startsWith(cwdPrefix)
701
702
  ? filePart.slice(cwdPrefix.length)
702
703
  : null;
703
704
 
704
705
  if (relativePart) {
705
706
  for (const folder of options.allowedFolders) {
706
- const candidate = folder + '/' + relativePart;
707
+ const candidate = pathJoin(folder, relativePart);
707
708
  if (existsSync(candidate)) {
708
709
  if (debug) console.error(`[extract] Auto-fixed path: ${filePart} → ${candidate}`);
709
710
  return candidate + suffix;
@@ -714,11 +715,12 @@ export const extractTool = (options = {}) => {
714
715
  // Try stripping workspace prefix and resolving against allowedFolders
715
716
  // e.g., /tmp/visor-workspaces/abc/gateway/file.go → try each folder + gateway/file.go
716
717
  for (const folder of options.allowedFolders) {
717
- const folderPrefix = folder.endsWith('/') ? folder : folder + '/';
718
- const wsParent = folderPrefix.replace(/[^/]+\/$/, '');
718
+ const folderPrefix = folder.endsWith(pathSep) ? folder : folder + pathSep;
719
+ const sepEscaped = pathSep === '\\' ? '\\\\' : pathSep;
720
+ const wsParent = folderPrefix.replace(new RegExp('[^' + sepEscaped + ']+' + sepEscaped + '$'), '');
719
721
  if (filePart.startsWith(wsParent)) {
720
722
  const tail = filePart.slice(wsParent.length);
721
- const candidate = folderPrefix + tail;
723
+ const candidate = pathJoin(folderPrefix, tail);
722
724
  if (candidate !== filePart && existsSync(candidate)) {
723
725
  if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} → ${candidate}`);
724
726
  return candidate + suffix;
@@ -24981,7 +24981,11 @@ async function extractBinary(assetPath, outputDir) {
24981
24981
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
24982
24982
  console.log(`Extracting zip to ${extractDir}...`);
24983
24983
  }
24984
- await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
24984
+ if (isWindows) {
24985
+ await exec(`powershell -NoProfile -Command "Expand-Archive -Path '${assetPath}' -DestinationPath '${extractDir}' -Force"`);
24986
+ } else {
24987
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
24988
+ }
24985
24989
  } else {
24986
24990
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
24987
24991
  console.log(`Copying binary directly to ${binaryPath}`);
@@ -27789,14 +27793,15 @@ var init_vercel = __esm({
27789
27793
  const parsedTargets = parseTargets(targets);
27790
27794
  extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
27791
27795
  if (options.allowedFolders && options.allowedFolders.length > 0) {
27796
+ const { join: pathJoin, sep: pathSep } = await import("path");
27792
27797
  extractFiles = extractFiles.map((target) => {
27793
27798
  const { filePart, suffix } = splitTargetSuffix(target);
27794
27799
  if ((0, import_fs4.existsSync)(filePart)) return target;
27795
- const cwdPrefix = effectiveCwd.endsWith("/") ? effectiveCwd : effectiveCwd + "/";
27800
+ const cwdPrefix = effectiveCwd.endsWith(pathSep) ? effectiveCwd : effectiveCwd + pathSep;
27796
27801
  const relativePart = filePart.startsWith(cwdPrefix) ? filePart.slice(cwdPrefix.length) : null;
27797
27802
  if (relativePart) {
27798
27803
  for (const folder of options.allowedFolders) {
27799
- const candidate = folder + "/" + relativePart;
27804
+ const candidate = pathJoin(folder, relativePart);
27800
27805
  if ((0, import_fs4.existsSync)(candidate)) {
27801
27806
  if (debug) console.error(`[extract] Auto-fixed path: ${filePart} \u2192 ${candidate}`);
27802
27807
  return candidate + suffix;
@@ -27804,11 +27809,12 @@ var init_vercel = __esm({
27804
27809
  }
27805
27810
  }
27806
27811
  for (const folder of options.allowedFolders) {
27807
- const folderPrefix = folder.endsWith("/") ? folder : folder + "/";
27808
- const wsParent = folderPrefix.replace(/[^/]+\/$/, "");
27812
+ const folderPrefix = folder.endsWith(pathSep) ? folder : folder + pathSep;
27813
+ const sepEscaped = pathSep === "\\" ? "\\\\" : pathSep;
27814
+ const wsParent = folderPrefix.replace(new RegExp("[^" + sepEscaped + "]+" + sepEscaped + "$"), "");
27809
27815
  if (filePart.startsWith(wsParent)) {
27810
27816
  const tail = filePart.slice(wsParent.length);
27811
- const candidate = folderPrefix + tail;
27817
+ const candidate = pathJoin(folderPrefix, tail);
27812
27818
  if (candidate !== filePart && (0, import_fs4.existsSync)(candidate)) {
27813
27819
  if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} \u2192 ${candidate}`);
27814
27820
  return candidate + suffix;
@@ -97556,14 +97562,6 @@ var init_FallbackManager = __esm({
97556
97562
  });
97557
97563
 
97558
97564
  // src/agent/contextCompactor.js
97559
- var contextCompactor_exports = {};
97560
- __export(contextCompactor_exports, {
97561
- calculateCompactionStats: () => calculateCompactionStats,
97562
- compactMessages: () => compactMessages,
97563
- handleContextLimitError: () => handleContextLimitError,
97564
- identifyMessageSegments: () => identifyMessageSegments,
97565
- isContextLimitError: () => isContextLimitError
97566
- });
97567
97565
  function isContextLimitError(error40) {
97568
97566
  if (!error40) return false;
97569
97567
  const errorMessage = (typeof error40 === "string" ? error40 : error40?.message || "").toLowerCase();
@@ -101879,6 +101877,16 @@ You are working with a workspace. Available paths: ${workspaceDesc}
101879
101877
  userMessage
101880
101878
  ];
101881
101879
  }
101880
+ if (this.history.length > 0) {
101881
+ const compacted = compactMessages(currentMessages, { keepLastSegment: true, minSegmentsToKeep: 1 });
101882
+ if (compacted.length < currentMessages.length) {
101883
+ const stats = calculateCompactionStats(currentMessages, compacted);
101884
+ if (this.debug) {
101885
+ console.log(`[DEBUG] Proactive history compaction: ${currentMessages.length} \u2192 ${compacted.length} messages (${stats.reductionPercent}% reduction, ~${stats.tokensSaved} tokens saved)`);
101886
+ }
101887
+ currentMessages = compacted;
101888
+ }
101889
+ }
101882
101890
  let currentIteration = 0;
101883
101891
  let finalResult = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
101884
101892
  const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
@@ -102579,7 +102587,6 @@ Double-check your response based on the criteria above. If everything looks good
102579
102587
  * @returns {Object} Compaction statistics
102580
102588
  */
102581
102589
  async compactHistory(options = {}) {
102582
- const { compactMessages: compactMessages2, calculateCompactionStats: calculateCompactionStats2 } = await Promise.resolve().then(() => (init_contextCompactor(), contextCompactor_exports));
102583
102590
  if (this.history.length === 0) {
102584
102591
  if (this.debug) {
102585
102592
  console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
@@ -102594,8 +102601,8 @@ Double-check your response based on the criteria above. If everything looks good
102594
102601
  tokensSaved: 0
102595
102602
  };
102596
102603
  }
102597
- const compactedMessages = compactMessages2(this.history, options);
102598
- const stats = calculateCompactionStats2(this.history, compactedMessages);
102604
+ const compactedMessages = compactMessages(this.history, options);
102605
+ const stats = calculateCompactionStats(this.history, compactedMessages);
102599
102606
  this.history = compactedMessages;
102600
102607
  try {
102601
102608
  await this.storageAdapter.clearHistory(this.sessionId);
package/cjs/index.cjs CHANGED
@@ -1098,7 +1098,11 @@ async function extractBinary(assetPath, outputDir) {
1098
1098
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1099
1099
  console.log(`Extracting zip to ${extractDir}...`);
1100
1100
  }
1101
- await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
1101
+ if (isWindows) {
1102
+ await exec(`powershell -NoProfile -Command "Expand-Archive -Path '${assetPath}' -DestinationPath '${extractDir}' -Force"`);
1103
+ } else {
1104
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
1105
+ }
1102
1106
  } else {
1103
1107
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1104
1108
  console.log(`Copying binary directly to ${binaryPath}`);
@@ -82266,14 +82270,6 @@ var init_FallbackManager = __esm({
82266
82270
  });
82267
82271
 
82268
82272
  // src/agent/contextCompactor.js
82269
- var contextCompactor_exports = {};
82270
- __export(contextCompactor_exports, {
82271
- calculateCompactionStats: () => calculateCompactionStats,
82272
- compactMessages: () => compactMessages,
82273
- handleContextLimitError: () => handleContextLimitError,
82274
- identifyMessageSegments: () => identifyMessageSegments,
82275
- isContextLimitError: () => isContextLimitError
82276
- });
82277
82273
  function isContextLimitError(error40) {
82278
82274
  if (!error40) return false;
82279
82275
  const errorMessage = (typeof error40 === "string" ? error40 : error40?.message || "").toLowerCase();
@@ -98870,6 +98866,16 @@ You are working with a workspace. Available paths: ${workspaceDesc}
98870
98866
  userMessage
98871
98867
  ];
98872
98868
  }
98869
+ if (this.history.length > 0) {
98870
+ const compacted = compactMessages(currentMessages, { keepLastSegment: true, minSegmentsToKeep: 1 });
98871
+ if (compacted.length < currentMessages.length) {
98872
+ const stats = calculateCompactionStats(currentMessages, compacted);
98873
+ if (this.debug) {
98874
+ console.log(`[DEBUG] Proactive history compaction: ${currentMessages.length} \u2192 ${compacted.length} messages (${stats.reductionPercent}% reduction, ~${stats.tokensSaved} tokens saved)`);
98875
+ }
98876
+ currentMessages = compacted;
98877
+ }
98878
+ }
98873
98879
  let currentIteration = 0;
98874
98880
  let finalResult = "I was unable to complete your request due to reaching the maximum number of tool iterations.";
98875
98881
  const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
@@ -99570,7 +99576,6 @@ Double-check your response based on the criteria above. If everything looks good
99570
99576
  * @returns {Object} Compaction statistics
99571
99577
  */
99572
99578
  async compactHistory(options = {}) {
99573
- const { compactMessages: compactMessages2, calculateCompactionStats: calculateCompactionStats2 } = await Promise.resolve().then(() => (init_contextCompactor(), contextCompactor_exports));
99574
99579
  if (this.history.length === 0) {
99575
99580
  if (this.debug) {
99576
99581
  console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
@@ -99585,8 +99590,8 @@ Double-check your response based on the criteria above. If everything looks good
99585
99590
  tokensSaved: 0
99586
99591
  };
99587
99592
  }
99588
- const compactedMessages = compactMessages2(this.history, options);
99589
- const stats = calculateCompactionStats2(this.history, compactedMessages);
99593
+ const compactedMessages = compactMessages(this.history, options);
99594
+ const stats = calculateCompactionStats(this.history, compactedMessages);
99590
99595
  this.history = compactedMessages;
99591
99596
  try {
99592
99597
  await this.storageAdapter.clearHistory(this.sessionId);
@@ -101405,14 +101410,15 @@ var init_vercel = __esm({
101405
101410
  const parsedTargets = parseTargets(targets);
101406
101411
  extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
101407
101412
  if (options.allowedFolders && options.allowedFolders.length > 0) {
101413
+ const { join: pathJoin, sep: pathSep } = await import("path");
101408
101414
  extractFiles = extractFiles.map((target) => {
101409
101415
  const { filePart, suffix } = splitTargetSuffix(target);
101410
101416
  if ((0, import_fs11.existsSync)(filePart)) return target;
101411
- const cwdPrefix = effectiveCwd.endsWith("/") ? effectiveCwd : effectiveCwd + "/";
101417
+ const cwdPrefix = effectiveCwd.endsWith(pathSep) ? effectiveCwd : effectiveCwd + pathSep;
101412
101418
  const relativePart = filePart.startsWith(cwdPrefix) ? filePart.slice(cwdPrefix.length) : null;
101413
101419
  if (relativePart) {
101414
101420
  for (const folder of options.allowedFolders) {
101415
- const candidate = folder + "/" + relativePart;
101421
+ const candidate = pathJoin(folder, relativePart);
101416
101422
  if ((0, import_fs11.existsSync)(candidate)) {
101417
101423
  if (debug) console.error(`[extract] Auto-fixed path: ${filePart} \u2192 ${candidate}`);
101418
101424
  return candidate + suffix;
@@ -101420,11 +101426,12 @@ var init_vercel = __esm({
101420
101426
  }
101421
101427
  }
101422
101428
  for (const folder of options.allowedFolders) {
101423
- const folderPrefix = folder.endsWith("/") ? folder : folder + "/";
101424
- const wsParent = folderPrefix.replace(/[^/]+\/$/, "");
101429
+ const folderPrefix = folder.endsWith(pathSep) ? folder : folder + pathSep;
101430
+ const sepEscaped = pathSep === "\\" ? "\\\\" : pathSep;
101431
+ const wsParent = folderPrefix.replace(new RegExp("[^" + sepEscaped + "]+" + sepEscaped + "$"), "");
101425
101432
  if (filePart.startsWith(wsParent)) {
101426
101433
  const tail = filePart.slice(wsParent.length);
101427
- const candidate = folderPrefix + tail;
101434
+ const candidate = pathJoin(folderPrefix, tail);
101428
101435
  if (candidate !== filePart && (0, import_fs11.existsSync)(candidate)) {
101429
101436
  if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} \u2192 ${candidate}`);
101430
101437
  return candidate + suffix;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc285",
3
+ "version": "0.6.0-rc286",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -93,7 +93,7 @@ import { formatAvailableSkillsXml as formatAvailableSkills } from './skills/form
93
93
  import { createSkillToolInstances } from './skills/tools.js';
94
94
  import { RetryManager, createRetryManagerFromEnv } from './RetryManager.js';
95
95
  import { FallbackManager, createFallbackManagerFromEnv, buildFallbackProvidersFromEnv } from './FallbackManager.js';
96
- import { handleContextLimitError } from './contextCompactor.js';
96
+ import { handleContextLimitError, compactMessages, calculateCompactionStats } from './contextCompactor.js';
97
97
  import { formatErrorForAI, ParameterError } from '../utils/error-types.js';
98
98
  import { getCommonPrefix, toRelativePath, safeRealpath } from '../utils/path-validation.js';
99
99
  import { truncateIfNeeded, getMaxOutputTokens } from './outputTruncator.js';
@@ -3227,6 +3227,25 @@ Follow these instructions carefully:
3227
3227
  ];
3228
3228
  }
3229
3229
 
3230
+ // Proactively compact for multi-turn conversations.
3231
+ // On turn 2+, previous turns contain full tool call/result history which can
3232
+ // be 50K+ tokens. This drowns out the new user message and causes the model to
3233
+ // focus on prior context rather than the new question.
3234
+ // compactMessages strips intermediate monologue from completed segments,
3235
+ // keeping user messages + final answers from prior turns.
3236
+ // Must run AFTER adding the new user message so the compactor sees 2+ segments
3237
+ // (completed prior turns + the new incomplete turn), preserving the latest segment.
3238
+ if (this.history.length > 0) {
3239
+ const compacted = compactMessages(currentMessages, { keepLastSegment: true, minSegmentsToKeep: 1 });
3240
+ if (compacted.length < currentMessages.length) {
3241
+ const stats = calculateCompactionStats(currentMessages, compacted);
3242
+ if (this.debug) {
3243
+ console.log(`[DEBUG] Proactive history compaction: ${currentMessages.length} → ${compacted.length} messages (${stats.reductionPercent}% reduction, ~${stats.tokensSaved} tokens saved)`);
3244
+ }
3245
+ currentMessages = compacted;
3246
+ }
3247
+ }
3248
+
3230
3249
  let currentIteration = 0;
3231
3250
  let finalResult = 'I was unable to complete your request due to reaching the maximum number of tool iterations.';
3232
3251
 
@@ -4141,8 +4160,6 @@ Double-check your response based on the criteria above. If everything looks good
4141
4160
  * @returns {Object} Compaction statistics
4142
4161
  */
4143
4162
  async compactHistory(options = {}) {
4144
- const { compactMessages, calculateCompactionStats } = await import('./contextCompactor.js');
4145
-
4146
4163
  if (this.history.length === 0) {
4147
4164
  if (this.debug) {
4148
4165
  console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
package/src/downloader.js CHANGED
@@ -743,7 +743,11 @@ async function extractBinary(assetPath, outputDir) {
743
743
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
744
744
  console.log(`Extracting zip to ${extractDir}...`);
745
745
  }
746
- await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
746
+ if (isWindows) {
747
+ await exec(`powershell -NoProfile -Command "Expand-Archive -Path '${assetPath}' -DestinationPath '${extractDir}' -Force"`);
748
+ } else {
749
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
750
+ }
747
751
  } else {
748
752
  // Assume it's a direct binary
749
753
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
@@ -691,19 +691,20 @@ export const extractTool = (options = {}) => {
691
691
  // model constructs wrong absolute paths (e.g., /workspace/gateway/file.go
692
692
  // instead of /workspace/tyk/gateway/file.go)
693
693
  if (options.allowedFolders && options.allowedFolders.length > 0) {
694
+ const { join: pathJoin, sep: pathSep } = await import('path');
694
695
  extractFiles = extractFiles.map(target => {
695
696
  const { filePart, suffix } = splitTargetSuffix(target);
696
697
  if (existsSync(filePart)) return target;
697
698
 
698
699
  // Try resolving the relative tail against each allowedFolder
699
- const cwdPrefix = (effectiveCwd.endsWith('/') ? effectiveCwd : effectiveCwd + '/');
700
+ const cwdPrefix = effectiveCwd.endsWith(pathSep) ? effectiveCwd : effectiveCwd + pathSep;
700
701
  const relativePart = filePart.startsWith(cwdPrefix)
701
702
  ? filePart.slice(cwdPrefix.length)
702
703
  : null;
703
704
 
704
705
  if (relativePart) {
705
706
  for (const folder of options.allowedFolders) {
706
- const candidate = folder + '/' + relativePart;
707
+ const candidate = pathJoin(folder, relativePart);
707
708
  if (existsSync(candidate)) {
708
709
  if (debug) console.error(`[extract] Auto-fixed path: ${filePart} → ${candidate}`);
709
710
  return candidate + suffix;
@@ -714,11 +715,12 @@ export const extractTool = (options = {}) => {
714
715
  // Try stripping workspace prefix and resolving against allowedFolders
715
716
  // e.g., /tmp/visor-workspaces/abc/gateway/file.go → try each folder + gateway/file.go
716
717
  for (const folder of options.allowedFolders) {
717
- const folderPrefix = folder.endsWith('/') ? folder : folder + '/';
718
- const wsParent = folderPrefix.replace(/[^/]+\/$/, '');
718
+ const folderPrefix = folder.endsWith(pathSep) ? folder : folder + pathSep;
719
+ const sepEscaped = pathSep === '\\' ? '\\\\' : pathSep;
720
+ const wsParent = folderPrefix.replace(new RegExp('[^' + sepEscaped + ']+' + sepEscaped + '$'), '');
719
721
  if (filePart.startsWith(wsParent)) {
720
722
  const tail = filePart.slice(wsParent.length);
721
- const candidate = folderPrefix + tail;
723
+ const candidate = pathJoin(folderPrefix, tail);
722
724
  if (candidate !== filePart && existsSync(candidate)) {
723
725
  if (debug) console.error(`[extract] Auto-fixed path via workspace: ${filePart} → ${candidate}`);
724
726
  return candidate + suffix;