@portel/photon 1.34.0 → 1.34.1

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.
@@ -1734,7 +1734,9 @@ function watchBaseForProactiveMetadata(basePath, _isDefaultBase) {
1734
1734
  watchDebounce.set(debounceKey, timer);
1735
1735
  };
1736
1736
  try {
1737
- const watcher = safeWatchDir(basePath, (_eventType, filename) => onChange(filename));
1737
+ const watcher = safeWatchDir(basePath, (_eventType, filename) => onChange(filename), {
1738
+ bunPollMaxEntries: BUN_DIR_POLL_MAX_ENTRIES,
1739
+ });
1738
1740
  baseDirWatchers.set(basePath, watcher);
1739
1741
  }
1740
1742
  catch (err) {
@@ -2968,7 +2970,7 @@ async function handleRequest(request, socket) {
2968
2970
  const key = compositeKey(photonName, request.workingDir);
2969
2971
  const sessionManager = sessionManagers.get(key);
2970
2972
  if (sessionManager) {
2971
- await sessionManager.clearInstances();
2973
+ await sessionManager.clearInstances('clear-instances');
2972
2974
  }
2973
2975
  return { type: 'result', id: request.id, success: true, data: { cleared: !!sessionManager } };
2974
2976
  }
@@ -4638,6 +4640,7 @@ async function applyPatchToInstance(instance, photonName, ops, workingDir) {
4638
4640
  */
4639
4641
  const IS_BUN = !!process.versions.bun;
4640
4642
  const POLL_INTERVAL_MS = 2000; // 2s — good balance between latency and CPU
4643
+ const BUN_DIR_POLL_MAX_ENTRIES = parseNonNegativeEnvInt('PHOTON_BUN_DIR_POLL_MAX_ENTRIES', 512);
4641
4644
  /** Track active poll timers so they can be cleaned up on shutdown */
4642
4645
  const pollTimers = new Set();
4643
4646
  function safeWatchFile(filePath, callback) {
@@ -4686,12 +4689,37 @@ function safeWatchFile(filePath, callback) {
4686
4689
  },
4687
4690
  };
4688
4691
  }
4689
- function safeWatchDir(dirPath, callback) {
4692
+ function countDirEntries(dirPath) {
4693
+ try {
4694
+ return fs.readdirSync(dirPath).length;
4695
+ }
4696
+ catch {
4697
+ return null;
4698
+ }
4699
+ }
4700
+ function shouldSkipBunDirPoll(dirPath, maxEntries) {
4701
+ if (!IS_BUN || maxEntries === 0)
4702
+ return false;
4703
+ const count = countDirEntries(dirPath);
4704
+ if (count === null || count <= maxEntries)
4705
+ return false;
4706
+ logger.warn('Skipping Bun directory polling watcher for oversized directory', {
4707
+ dir: dirPath,
4708
+ entries: count,
4709
+ maxEntries,
4710
+ });
4711
+ return true;
4712
+ }
4713
+ function safeWatchDir(dirPath, callback, options = {}) {
4690
4714
  if (!IS_BUN) {
4691
4715
  const w = fs.watch(dirPath, (eventType, filename) => callback(eventType, filename));
4692
4716
  return w;
4693
4717
  }
4694
4718
  // Bun fallback: readdir snapshot diffing
4719
+ if (options.bunPollMaxEntries !== undefined &&
4720
+ shouldSkipBunDirPoll(dirPath, options.bunPollMaxEntries)) {
4721
+ return { close() { } };
4722
+ }
4695
4723
  let prevEntries = new Map(); // name -> mtimeMs
4696
4724
  try {
4697
4725
  for (const entry of fs.readdirSync(dirPath)) {
@@ -4836,7 +4864,7 @@ function watchPhotonFile(photonName, photonPath) {
4836
4864
  for (const key of keysToDelete) {
4837
4865
  const manager = sessionManagers.get(key);
4838
4866
  if (manager)
4839
- await manager.clearInstances();
4867
+ await manager.clearInstances('photon-deleted');
4840
4868
  sessionManagers.delete(key);
4841
4869
  photonPaths.delete(key);
4842
4870
  workingDirs.delete(key);
@@ -4914,6 +4942,22 @@ function unwatchPhotonFile(watchPath) {
4914
4942
  watchDebounce.delete(watchPath);
4915
4943
  }
4916
4944
  }
4945
+ function unwatchPhotonPath(photonPath) {
4946
+ let watchPath = photonPath;
4947
+ try {
4948
+ watchPath = fs.realpathSync(photonPath);
4949
+ }
4950
+ catch {
4951
+ // Symlink target may already be gone; fall back to caller path.
4952
+ }
4953
+ unwatchPhotonFile(watchPath);
4954
+ const debounceKey = path.resolve(photonPath);
4955
+ const timer = watchDebounce.get(debounceKey);
4956
+ if (timer) {
4957
+ clearTimeout(timer);
4958
+ watchDebounce.delete(debounceKey);
4959
+ }
4960
+ }
4917
4961
  /**
4918
4962
  * Detect whether a missing workingDir was renamed (moved) or deleted.
4919
4963
  *
@@ -5041,7 +5085,7 @@ function watchWorkingDir(workingDir) {
5041
5085
  for (const key of deletedDirKeys) {
5042
5086
  const manager = sessionManagers.get(key);
5043
5087
  if (manager)
5044
- await manager.clearInstances();
5088
+ await manager.clearInstances('working-dir-deleted');
5045
5089
  sessionManagers.delete(key);
5046
5090
  photonPaths.delete(key);
5047
5091
  workingDirs.delete(key);
@@ -5108,7 +5152,7 @@ function watchStateDir(workingDir) {
5108
5152
  const key = compositeKey(filename, workingDir);
5109
5153
  const manager = sessionManagers.get(key);
5110
5154
  if (manager) {
5111
- await manager.clearInstances();
5155
+ await manager.clearInstances('state-dir-deleted');
5112
5156
  }
5113
5157
  })();
5114
5158
  }, 150);
@@ -5602,13 +5646,11 @@ function startupWatchPhotonDir(photonDir, defaultBase) {
5602
5646
  if (photonPaths.has(photonKey) && !fs.existsSync(filePath)) {
5603
5647
  photonPaths.delete(photonKey);
5604
5648
  // fileWatchers is keyed by file path, not composite key
5605
- const watcher = fileWatchers.get(filePath);
5606
- if (watcher) {
5607
- watcher.close();
5608
- fileWatchers.delete(filePath);
5609
- }
5649
+ unwatchPhotonPath(filePath);
5610
5650
  logger.info('Photon file removed', { photonName, path: filePath });
5611
5651
  }
5652
+ }, {
5653
+ bunPollMaxEntries: BUN_DIR_POLL_MAX_ENTRIES,
5612
5654
  });
5613
5655
  if (typeof dirWatcher.on === 'function') {
5614
5656
  dirWatcher.on('error', () => { }); // Non-fatal