fraim 2.0.351 → 2.0.352
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.
|
@@ -227,6 +227,7 @@ function buildPathWithManagedAgentBins(basePath) {
|
|
|
227
227
|
// launches already inherit the registry-composed PATH, so this returns null immediately on
|
|
228
228
|
// win32 rather than attempting a login-shell equivalent that doesn't exist.
|
|
229
229
|
const LOGIN_SHELL_PATH_TIMEOUT_MS = 3000;
|
|
230
|
+
const LOGIN_SHELL_PATH_CACHE_TTL_MS = 60_000;
|
|
230
231
|
function defaultResolveLoginShellPath() {
|
|
231
232
|
if (process.platform === 'win32')
|
|
232
233
|
return null;
|
|
@@ -279,18 +280,20 @@ let loginShellPathResolverOverride = null;
|
|
|
279
280
|
*/
|
|
280
281
|
function __setLoginShellPathResolverForTests(resolver) {
|
|
281
282
|
loginShellPathResolverOverride = resolver;
|
|
282
|
-
|
|
283
|
-
cachedNpmGlobalBinDirsForRecovery.clear();
|
|
283
|
+
__clearLoginShellPathCacheForTests();
|
|
284
284
|
}
|
|
285
285
|
let cachedLoginShellPath;
|
|
286
|
+
let cachedLoginShellPathAtMs = 0;
|
|
286
287
|
/**
|
|
287
|
-
* Resolves
|
|
288
|
-
*
|
|
289
|
-
* recomputing per call would multiply one shell spawn by the number of agents probed.
|
|
288
|
+
* Resolves the user's login PATH at most once per minute, including failed reads. A
|
|
289
|
+
* running Hub can discover later installations without spawning a shell per agent probe.
|
|
290
290
|
*/
|
|
291
291
|
function resolveLoginShellPathCached() {
|
|
292
|
-
|
|
292
|
+
const nowMs = Date.now();
|
|
293
|
+
const ageMs = nowMs - cachedLoginShellPathAtMs;
|
|
294
|
+
if (cachedLoginShellPath !== undefined && ageMs >= 0 && ageMs < LOGIN_SHELL_PATH_CACHE_TTL_MS) {
|
|
293
295
|
return cachedLoginShellPath;
|
|
296
|
+
}
|
|
294
297
|
const resolver = loginShellPathResolverOverride ?? defaultResolveLoginShellPath;
|
|
295
298
|
// AC-A5: a resolver failing must degrade to "no recovery", never propagate. The default
|
|
296
299
|
// resolver already guards its own spawnSync internally; this also protects against an
|
|
@@ -301,13 +304,20 @@ function resolveLoginShellPathCached() {
|
|
|
301
304
|
catch {
|
|
302
305
|
cachedLoginShellPath = null;
|
|
303
306
|
}
|
|
307
|
+
cachedLoginShellPathAtMs = nowMs;
|
|
308
|
+
// npm's configured global prefix can change even when the recovered PATH does not.
|
|
309
|
+
// Refresh both tiers together so an old prefix cannot outlive login-PATH recovery.
|
|
310
|
+
cachedNpmGlobalBinDirsForRecovery.clear();
|
|
304
311
|
return cachedLoginShellPath;
|
|
305
312
|
}
|
|
306
313
|
/** Test seam only. Clears the memoized login-shell PATH so the next call recomputes. */
|
|
307
314
|
function __clearLoginShellPathCacheForTests() {
|
|
308
315
|
cachedLoginShellPath = undefined;
|
|
316
|
+
cachedLoginShellPathAtMs = 0;
|
|
317
|
+
cachedNpmGlobalBinDirsForRecovery.clear();
|
|
309
318
|
}
|
|
310
319
|
const cachedNpmGlobalBinDirsForRecovery = new Map();
|
|
320
|
+
const NPM_GLOBAL_RECOVERY_CACHE_MAX_ENTRIES = 64;
|
|
311
321
|
/**
|
|
312
322
|
* Memoized wrapper around resolveNpmGlobalBinDirs(), scoped to buildRecoveredAgentPath()'s
|
|
313
323
|
* own use only — NOT a change to resolveNpmGlobalBinDirs() itself, which fraim doctor and the
|
|
@@ -321,8 +331,8 @@ const cachedNpmGlobalBinDirsForRecovery = new Map();
|
|
|
321
331
|
* and because each call sits inside a Promise executor that runs synchronously up to its own
|
|
322
332
|
* spawnSync, the "concurrent" probes actually serialized behind it — up to 5 agents × 5s of
|
|
323
333
|
* blocking spawnSync is exactly the event-loop-freezing regression issue #1010 eliminated,
|
|
324
|
-
* reintroduced one layer deeper. Memoizing
|
|
325
|
-
*
|
|
334
|
+
* reintroduced one layer deeper. Memoizing between login-PATH refreshes keeps repeated
|
|
335
|
+
* probes in the same round from duplicating that work.
|
|
326
336
|
*
|
|
327
337
|
* Keyed by `basePath` (not a single scalar): `resolveManagedCommand()` also routes through this
|
|
328
338
|
* cache now, and unlike `detectEmployeesAsync()`'s repeated same-ambient-PATH probing, its
|
|
@@ -330,7 +340,8 @@ const cachedNpmGlobalBinDirsForRecovery = new Map();
|
|
|
330
340
|
* isolated tests that swap `FRAIM_USER_DIR`/`PATH` per test case). A single scalar cache would
|
|
331
341
|
* let the FIRST basePath's answer silently leak into every later, differently-scoped lookup —
|
|
332
342
|
* concretely, a real machine's actual npm-global `codex` shadowing a test's own isolated managed
|
|
333
|
-
* shim. Per-basePath keys
|
|
343
|
+
* shim. Per-basePath keys avoid that cross-contamination; a size limit bounds memory when
|
|
344
|
+
* callers supply many distinct PATHs before the next refresh.
|
|
334
345
|
*/
|
|
335
346
|
function resolveNpmGlobalBinDirsCached(basePath) {
|
|
336
347
|
const cached = cachedNpmGlobalBinDirsForRecovery.get(basePath);
|
|
@@ -343,6 +354,11 @@ function resolveNpmGlobalBinDirsCached(basePath) {
|
|
|
343
354
|
catch {
|
|
344
355
|
dirs = [];
|
|
345
356
|
}
|
|
357
|
+
if (cachedNpmGlobalBinDirsForRecovery.size >= NPM_GLOBAL_RECOVERY_CACHE_MAX_ENTRIES) {
|
|
358
|
+
const oldestKey = cachedNpmGlobalBinDirsForRecovery.keys().next().value;
|
|
359
|
+
if (oldestKey !== undefined)
|
|
360
|
+
cachedNpmGlobalBinDirsForRecovery.delete(oldestKey);
|
|
361
|
+
}
|
|
346
362
|
cachedNpmGlobalBinDirsForRecovery.set(basePath, dirs);
|
|
347
363
|
return dirs;
|
|
348
364
|
}
|