@indigoai-us/hq-cloud 6.18.19 → 6.18.20-alpha.732c

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.
package/dist/watcher.js CHANGED
@@ -356,8 +356,9 @@ function errorPath(err) {
356
356
  * Which backend a platform gets.
357
357
  *
358
358
  * - macOS (FSEvents) and Windows (ReadDirectoryChangesW) watch a directory
359
- * recursively with ONE OS handle, so the scoped planner places a few
360
- * recursive watches over the in-scope tree.
359
+ * recursively with ONE OS handle. Windows uses the exact scoped plan;
360
+ * macOS uses a depth-bounded plan to keep its shared FSEvents stream's
361
+ * registered path set small while the event filter preserves upload scope.
361
362
  * - Linux has no recursive handle: a directory watch reports only its own
362
363
  * direct contents. The same scoped backend runs with one shallow inotify
363
364
  * watch per in-scope directory instead — and NONE per file, because the
@@ -370,7 +371,9 @@ function errorPath(err) {
370
371
  export function selectTreeWatchBackend(platform, forcePolling) {
371
372
  if (forcePolling)
372
373
  return "polling";
373
- if (platform === "darwin" || platform === "win32")
374
+ if (platform === "darwin")
375
+ return "native-coarse";
376
+ if (platform === "win32")
374
377
  return "native-recursive";
375
378
  if (platform === "linux")
376
379
  return "native-shallow";
@@ -390,6 +393,12 @@ function startChokidarTreeWatch(hqRoot, shouldEmit, onEvent, onError, maxWatched
390
393
  const readyListeners = new Set();
391
394
  try {
392
395
  cw = watch(hqRoot, {
396
+ // Push walkers record a directory symlink as a link entry and never
397
+ // traverse its target. Keep polling discovery on the same boundary so
398
+ // aliased trees do not consume the path budget or produce events for
399
+ // content that cannot upload. Chokidar still reports changes to the
400
+ // symlink entry itself.
401
+ followSymlinks: false,
393
402
  // On Linux chokidar adds one fs.watch/inotify watch per descended dir.
394
403
  // The predicate therefore enforces both scope and the hard per-runner cap.
395
404
  ignored: toChokidarIgnored(shouldEmit, hqRoot, budget),
@@ -642,7 +651,7 @@ export function startScopedRecursiveWatch(hqRoot, shouldEmit, onEvent, onError,
642
651
  const openedOn = directoryIdentity(dir);
643
652
  let handle;
644
653
  try {
645
- handle = fs.watch(dir, { recursive, persistent: true }, (eventType, filename) => {
654
+ handle = (opts.watchDirectory ?? fs.watch)(dir, { recursive, persistent: true }, (eventType, filename) => {
646
655
  // `filename` is relative to THIS watch's directory (or null/empty if
647
656
  // the platform couldn't provide it — nothing actionable then).
648
657
  // `String()` coerces both the string and (older @types/node) Buffer
@@ -680,6 +689,11 @@ export function startScopedRecursiveWatch(hqRoot, shouldEmit, onEvent, onError,
680
689
  }
681
690
  handle.on("error", (err) => {
682
691
  onError(err);
692
+ // TreeWatcher handles systemic watch exhaustion by closing this backend
693
+ // and moving to polling. Reopening the failed path before that transition
694
+ // runs only repeats the same EMFILE/ENFILE failure.
695
+ if (isSystemicWatchError(err))
696
+ return;
683
697
  // A handle a re-plan already replaced (retiring) must not tear down its
684
698
  // replacement at the same path.
685
699
  if (closed || watchers.get(dir) !== handle)
@@ -730,6 +744,7 @@ export function startScopedRecursiveWatch(hqRoot, shouldEmit, onEvent, onError,
730
744
  }
731
745
  : onDirectorySeen,
732
746
  recursiveWatches,
747
+ maxDepth: opts.maxDepth,
733
748
  listChildDirs: opts.listChildDirs,
734
749
  }));
735
750
  }
@@ -1002,13 +1017,39 @@ export function startScopedRecursiveWatch(hqRoot, shouldEmit, onEvent, onError,
1002
1017
  * ancestor-descent pruning that the chokidar backend needs
1003
1018
  * {@link toChokidarIgnored} to avoid.
1004
1019
  */
1005
- function startTreeWatch(hqRoot, shouldEmit, onEvent, onError, maxWatchedPaths, onWatchBudgetExceeded, onDirectorySeen, forcePolling = false) {
1020
+ /** @internal Exported for backend-level regression tests. */
1021
+ export function startTreeWatch(hqRoot, shouldEmit, onEvent, onError, maxWatchedPaths, onWatchBudgetExceeded, onDirectorySeen, forcePolling = false) {
1022
+ // Resolve the root once before the backend starts. Chokidar with
1023
+ // followSymlinks:false treats descendant links as leaf entries, but an
1024
+ // explicitly supplied root may itself be a symlink. Watch its real tree and
1025
+ // map observed paths back to the caller's root spelling so the caller's
1026
+ // filter and path routing retain their existing contract.
1027
+ const displayRoot = path.resolve(hqRoot);
1028
+ const root = fs.realpathSync(displayRoot);
1029
+ const displayPath = (absolutePath) => root === displayRoot
1030
+ ? absolutePath
1031
+ : path.join(displayRoot, path.relative(root, absolutePath));
1032
+ const watchFilter = root === displayRoot
1033
+ ? shouldEmit
1034
+ : (absolutePath, isDir) => shouldEmit(displayPath(absolutePath), isDir);
1035
+ const watchEvent = root === displayRoot
1036
+ ? onEvent
1037
+ : (absolutePath, kind) => onEvent(displayPath(absolutePath), kind);
1038
+ const watchDirectorySeen = onDirectorySeen
1039
+ ? root === displayRoot
1040
+ ? onDirectorySeen
1041
+ : (absolutePath) => onDirectorySeen(displayPath(absolutePath))
1042
+ : undefined;
1006
1043
  const choice = selectTreeWatchBackend(process.platform, forcePolling);
1007
- if (choice === "native-recursive" || choice === "native-shallow") {
1044
+ if (choice === "native-recursive" ||
1045
+ choice === "native-coarse" ||
1046
+ choice === "native-shallow") {
1008
1047
  try {
1009
- const scoped = startScopedRecursiveWatch(hqRoot, shouldEmit, onEvent, onError, onDirectorySeen, choice === "native-shallow"
1048
+ const scoped = startScopedRecursiveWatch(root, watchFilter, watchEvent, onError, watchDirectorySeen, choice === "native-shallow"
1010
1049
  ? { recursiveWatches: false, maxWatchedPaths, onWatchBudgetExceeded }
1011
- : {});
1050
+ : choice === "native-coarse"
1051
+ ? { maxDepth: 2 }
1052
+ : {});
1012
1053
  // Partial coverage is worse than a different backend: a planned root we
1013
1054
  // could not attach is silently unwatched forever, and the operator only
1014
1055
  // sees it as "instant sync stopped working for that subtree". Accept the
@@ -1024,7 +1065,7 @@ function startTreeWatch(hqRoot, shouldEmit, onEvent, onError, maxWatchedPaths, o
1024
1065
  onError(err);
1025
1066
  }
1026
1067
  }
1027
- return startChokidarTreeWatch(hqRoot, shouldEmit, onEvent, onError, maxWatchedPaths, onWatchBudgetExceeded, forcePolling);
1068
+ return startChokidarTreeWatch(root, watchFilter, watchEvent, onError, maxWatchedPaths, onWatchBudgetExceeded, forcePolling);
1028
1069
  }
1029
1070
  /**
1030
1071
  * Build the composite emit-decision predicate. Returns true when a change to