fraim 2.0.322 → 2.0.323
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/src/cli/doctor/checks/agent-cli-health-checks.js +18 -12
- package/dist/src/cli/mcp/command-resolution.js +7 -2
- package/dist/src/cli/setup/ide-invocation-surfaces.js +1 -1
- package/dist/src/cli/utils/machine-path.js +165 -0
- package/dist/src/cli/utils/managed-agent-install.js +6 -1
- package/dist/src/cli/utils/managed-agent-paths.js +54 -10
- package/dist/src/first-run/session-service.js +11 -7
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ exports.checkAgentCliHealthByCommand = checkAgentCliHealthByCommand;
|
|
|
14
14
|
const child_process_1 = require("child_process");
|
|
15
15
|
const path_1 = __importDefault(require("path"));
|
|
16
16
|
const managed_agent_paths_1 = require("../../utils/managed-agent-paths");
|
|
17
|
+
const machine_path_1 = require("../../utils/machine-path");
|
|
17
18
|
const command_resolution_1 = require("../../mcp/command-resolution");
|
|
18
19
|
// CLIs with a managed-install fallback (npm install -g into FRAIM's portable
|
|
19
20
|
// Node when no system install is found). Add gemini/copilot here when their
|
|
@@ -57,18 +58,14 @@ function probeVersion(commandPath) {
|
|
|
57
58
|
// shell already open before the fix landed still uses) — issue #1285's
|
|
58
59
|
// exact "update said success but --version looked wrong" ambiguity.
|
|
59
60
|
// Windows-only; there is no equivalent persisted-PATH registry on macOS/Linux.
|
|
61
|
+
// Issue #1748: this used to shell out to PowerShell for HKCU Environment Path on its own,
|
|
62
|
+
// a second independently-written Windows registry PATH reader. Two implementations of the
|
|
63
|
+
// same read drift apart, and a parsing fix applied to one is easy to miss on the other,
|
|
64
|
+
// which is the same PATH-resolution-drift bug class this check exists to report. Delegates
|
|
65
|
+
// to the shared reader instead, which also covers HKLM rather than only the user hive and
|
|
66
|
+
// expands REG_EXPAND_SZ references.
|
|
60
67
|
function readPersistedUserPath() {
|
|
61
|
-
|
|
62
|
-
return null;
|
|
63
|
-
try {
|
|
64
|
-
const result = (0, child_process_1.spawnSync)('powershell', ['-NoProfile', '-NonInteractive', '-Command', "[Environment]::GetEnvironmentVariable('PATH','User')"], { encoding: 'utf8', timeout: 5000 });
|
|
65
|
-
if (result.status !== 0 || result.error)
|
|
66
|
-
return null;
|
|
67
|
-
return (result.stdout || '').trim() || null;
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
68
|
+
return (0, machine_path_1.resolveMachinePathCached)();
|
|
72
69
|
}
|
|
73
70
|
async function runAgentCliHealthCheck(cli) {
|
|
74
71
|
// "Ambient" is this process's own raw, unmodified PATH — exactly what a
|
|
@@ -83,7 +80,16 @@ async function runAgentCliHealthCheck(cli) {
|
|
|
83
80
|
// is system-PATH-first for launch purposes and would silently agree with
|
|
84
81
|
// a stale ambient entry instead of surfacing the drift this check exists
|
|
85
82
|
// to catch.
|
|
86
|
-
|
|
83
|
+
// Issue #1748: process.env.PATH no longer carries FRAIM's managed bin dirs (the
|
|
84
|
+
// module-load mutation that put them there was the defect). Without a fallback,
|
|
85
|
+
// `ambientPath` is null whenever the only install is FRAIM-managed, and both clauses
|
|
86
|
+
// of `versionMismatch` below short-circuit on it, so this check would return
|
|
87
|
+
// "consistent" while the #1285 stale-shim drift it exists to catch went unreported.
|
|
88
|
+
// The machine PATH is what a newly opened shell resolves, which is the notion of
|
|
89
|
+
// "ambient" this check actually wants once the process is no longer self-mutating.
|
|
90
|
+
const machinePathForAmbient = (0, machine_path_1.resolveMachinePathCached)();
|
|
91
|
+
const ambientPath = (0, command_resolution_1.getSystemCommandPath)(cli.command)
|
|
92
|
+
|| (machinePathForAmbient ? (0, command_resolution_1.getSystemCommandPath)(cli.command, machinePathForAmbient) : null);
|
|
87
93
|
const managedSearchPath = (0, managed_agent_paths_1.buildPathWithManagedAgentBins)(process.env.PATH);
|
|
88
94
|
const managedPath = (0, command_resolution_1.getSystemCommandPath)(cli.command, managedSearchPath);
|
|
89
95
|
if (!ambientPath && !managedPath) {
|
|
@@ -87,8 +87,13 @@ const resolveManagedCommand = (command) => {
|
|
|
87
87
|
// path and the status-check path. Fall back to the FRAIM-managed portable
|
|
88
88
|
// copy only when no system install is found by either check. Last resort:
|
|
89
89
|
// bare command name.
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// Issue #1748: resolve ONLY against the recovered system PATH. The previous first tier
|
|
91
|
+
// scanned this process's ambient PATH, which carries FRAIM's own managed dirs (registered
|
|
92
|
+
// on the machine PATH at install, and formerly appended again at module load), so a
|
|
93
|
+
// FRAIM-managed binary was returned as if it were a system install and the tiers below
|
|
94
|
+
// never ran. buildRecoveredSystemPath() re-reads the machine PATH and excludes the managed
|
|
95
|
+
// dirs, so FRAIM's bundled copy is reachable only through the explicit tier below.
|
|
96
|
+
return (0, exports.getSystemCommandPath)(command, (0, managed_agent_paths_1.buildRecoveredSystemPath)(process.env.PATH))
|
|
92
97
|
|| (0, exports.getPortableManagedCommandPath)(command)
|
|
93
98
|
|| command;
|
|
94
99
|
};
|
|
@@ -72,7 +72,7 @@ ${buildDeferredToolBootstrapSection(profile)}1. **Confirm FRAIM activation**:
|
|
|
72
72
|
If local FRAIM job stubs are present in the workspace, inspect those first and match the request locally. Also inspect \`fraim/personalized-employee/jobs/\` for local overrides or repo-specific jobs. If local files are missing or you cannot inspect workspace files, call \`list_fraim_jobs()\` to view the full catalog, including any proxy-discoverable personalized jobs.
|
|
73
73
|
|
|
74
74
|
3. **Find the match**:
|
|
75
|
-
If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly. Otherwise, match the user's request to a FRAIM job from the local stub catalog, \`fraim/personalized-employee/jobs/\`, or the full \`list_fraim_jobs()\` response. If no exact or high-confidence job match exists,
|
|
75
|
+
If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly. Otherwise, match the user's request to a FRAIM job from the local stub catalog, \`fraim/personalized-employee/jobs/\`, or the full \`list_fraim_jobs()\` response. If no exact or high-confidence job match exists, call \`get_fraim_job({ job: "adhoc-prompt" })\` directly and execute it with the user's instructions as the task input — no confirmation question, and do not pick the nearest catalog job.
|
|
76
76
|
|
|
77
77
|
4. **Load the full content**:
|
|
78
78
|
- For jobs, call \`get_fraim_job({ job: "<matched-job-name>" })\`.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseRegQueryPathValue = parseRegQueryPathValue;
|
|
7
|
+
exports.expandWindowsEnvRefs = expandWindowsEnvRefs;
|
|
8
|
+
exports.composeMachinePath = composeMachinePath;
|
|
9
|
+
exports.resolveMachinePathCached = resolveMachinePathCached;
|
|
10
|
+
exports.clearMachinePathCache = clearMachinePathCache;
|
|
11
|
+
exports.setMachinePathResolver = setMachinePathResolver;
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
// ─── Issue #1748: Windows machine-PATH recovery ─────────────────────────────
|
|
15
|
+
//
|
|
16
|
+
// A Windows process inherits its environment at creation and never observes later
|
|
17
|
+
// registry changes (Node/Electron do not handle WM_SETTINGCHANGE). A Hub running for
|
|
18
|
+
// hours therefore holds the PATH as of its launch. When a user installs an agent CLI
|
|
19
|
+
// after that (Claude Code's native installer adding ~/.local/bin, say), the Hub cannot
|
|
20
|
+
// see it while a freshly opened terminal can, so the Hub falls through to FRAIM's own
|
|
21
|
+
// managed copy and runs a different CLI than the user installed and signed into.
|
|
22
|
+
//
|
|
23
|
+
// `defaultResolveLoginShellPath()` in managed-agent-paths.ts returns null on win32 (there
|
|
24
|
+
// is no login-shell PATH concept there), so before this the only recovery on Windows was
|
|
25
|
+
// the npm-prefix lookup: nothing re-read HKLM/HKCU. This module is the win32 counterpart
|
|
26
|
+
// to that POSIX recovery. Ask the OS what the PATH is now, rather than trusting the
|
|
27
|
+
// snapshot this process was handed.
|
|
28
|
+
//
|
|
29
|
+
// Lives in its own module rather than inside managed-agent-paths.ts so that file stays
|
|
30
|
+
// under the 500-line architecture threshold, and because "read the OS environment" is a
|
|
31
|
+
// distinct concern from "compose the PATH FRAIM resolves agents against". The dependency
|
|
32
|
+
// runs one way only (managed-agent-paths imports this), so there is no import cycle.
|
|
33
|
+
// Per-read ceiling. Both registry reads run sequentially (spawnSync is synchronous by
|
|
34
|
+
// definition, so they cannot overlap without making the whole resolution path async,
|
|
35
|
+
// which every caller is sync today), so the worst case is twice this. Measured cost is
|
|
36
|
+
// ~25ms per read, so 2s is already ~80x headroom while bounding a pathological stall at
|
|
37
|
+
// 4s rather than 10s.
|
|
38
|
+
const MACHINE_PATH_TIMEOUT_MS = 2000;
|
|
39
|
+
// The machine PATH changes when the user installs or moves a CLI, which is exactly the
|
|
40
|
+
// case this issue exists to handle, so this cache must expire rather than live for the
|
|
41
|
+
// life of the process. A Hub can run for days; a permanent cache would mean "install a
|
|
42
|
+
// CLI, then use it" still required a restart. Bounded by a TTL rather than an explicit
|
|
43
|
+
// invalidation call because the install that matters most is the one made OUTSIDE FRAIM
|
|
44
|
+
// (a vendor installer adding ~/.local/bin), which FRAIM never observes and so cannot
|
|
45
|
+
// invalidate on. Measured cost of a refresh on Windows is ~50ms for both registry reads,
|
|
46
|
+
// far below the blocking-spawn budget issue #1010 set (the POSIX login-shell read it sits
|
|
47
|
+
// beside costs 1-3s and is cached for the whole process).
|
|
48
|
+
const MACHINE_PATH_CACHE_TTL_MS = 60_000;
|
|
49
|
+
let machinePathResolverOverride = null;
|
|
50
|
+
let cachedMachinePath;
|
|
51
|
+
let cachedMachinePathAtMs = 0;
|
|
52
|
+
// The two registry values Windows itself composes a process PATH from, in the order it
|
|
53
|
+
// composes them: Machine first, then User.
|
|
54
|
+
const WINDOWS_PATH_REGISTRY_KEYS = [
|
|
55
|
+
[String.raw `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment`, 'Path'],
|
|
56
|
+
[String.raw `HKCU\Environment`, 'Path'],
|
|
57
|
+
];
|
|
58
|
+
// Resolve reg.exe by absolute path rather than letting the OS search PATH for it. This
|
|
59
|
+
// module's whole purpose is to stop a PATH entry from deciding which binary FRAIM runs,
|
|
60
|
+
// so invoking its own helper by bare name through that same untrusted PATH would undercut
|
|
61
|
+
// the control it implements. Falls back to the bare name only if SystemRoot is unset,
|
|
62
|
+
// which on a real Windows install it is not.
|
|
63
|
+
function resolveRegExePath() {
|
|
64
|
+
const systemRoot = process.env.SystemRoot || process.env.SYSTEMROOT;
|
|
65
|
+
return systemRoot ? path_1.default.join(systemRoot, 'System32', 'reg.exe') : 'reg';
|
|
66
|
+
}
|
|
67
|
+
// `reg query` prints a line shaped like " Path REG_EXPAND_SZ C:\foo;C:\bar".
|
|
68
|
+
// Split on the value type rather than on whitespace, because a PATH entry may itself
|
|
69
|
+
// contain spaces (C:\Program Files\...).
|
|
70
|
+
function parseRegQueryPathValue(stdout) {
|
|
71
|
+
for (const line of (stdout || '').split(/\r?\n/)) {
|
|
72
|
+
const match = line.match(/\s+(?:REG_EXPAND_SZ|REG_SZ)\s+(.*)$/);
|
|
73
|
+
if (match && match[1] !== undefined) {
|
|
74
|
+
const value = match[1].trim();
|
|
75
|
+
if (value)
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
// REG_EXPAND_SZ keeps %VAR% references verbatim; Windows expands them when it builds a
|
|
82
|
+
// process environment. Reading the raw value means expanding them here too, or an entry
|
|
83
|
+
// like %USERPROFILE%\.local\bin never resolves. Unknown variables are left as written
|
|
84
|
+
// rather than blanked, so a failed expansion cannot silently collapse an entry into a
|
|
85
|
+
// different directory.
|
|
86
|
+
function expandWindowsEnvRefs(value, env = process.env) {
|
|
87
|
+
return value.replace(/%([^%]+)%/g, (whole, name) => {
|
|
88
|
+
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
89
|
+
const resolved = key === undefined ? undefined : env[key];
|
|
90
|
+
return resolved === undefined ? whole : resolved;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Composition is separated from the registry I/O so the ordering contract can be tested
|
|
94
|
+
// without spawning reg.exe: Machine entries precede User entries, because that is the
|
|
95
|
+
// order Windows itself composes a process PATH in, and %VAR% references are expanded
|
|
96
|
+
// before splitting so an expanded value containing a delimiter still splits correctly.
|
|
97
|
+
function composeMachinePath(rawValues, env = process.env) {
|
|
98
|
+
const parts = [];
|
|
99
|
+
for (const raw of rawValues) {
|
|
100
|
+
if (!raw)
|
|
101
|
+
continue;
|
|
102
|
+
for (const entry of expandWindowsEnvRefs(raw, env).split(path_1.default.delimiter)) {
|
|
103
|
+
const trimmed = entry.trim();
|
|
104
|
+
if (trimmed)
|
|
105
|
+
parts.push(trimmed);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Not de-duplicated here: every consumer composes this through appendBinDirsToPath,
|
|
109
|
+
// which already collapses repeats. A second dedupe would be duplicated logic.
|
|
110
|
+
return parts.length > 0 ? parts.join(path_1.default.delimiter) : null;
|
|
111
|
+
}
|
|
112
|
+
function defaultResolveMachinePath() {
|
|
113
|
+
if (process.platform !== 'win32')
|
|
114
|
+
return null;
|
|
115
|
+
// Issue #1692 established that a sandboxed test run must never WRITE the real,
|
|
116
|
+
// registry-backed Windows User PATH. Reading it is the same isolation break in the
|
|
117
|
+
// other direction: it lets whatever agent CLIs happen to be installed on the build
|
|
118
|
+
// machine shadow a test's own isolated fixtures, which is the cross-contamination
|
|
119
|
+
// hazard already documented on resolveNpmGlobalBinDirsCached. Tests that need
|
|
120
|
+
// machine-PATH behavior inject it through __setMachinePathResolverForTests.
|
|
121
|
+
if (process.env.FRAIM_TEST_SANDBOX === '1')
|
|
122
|
+
return null;
|
|
123
|
+
const rawValues = WINDOWS_PATH_REGISTRY_KEYS.map(([key, valueName]) => {
|
|
124
|
+
const result = (0, child_process_1.spawnSync)(resolveRegExePath(), ['query', key, '/v', valueName], {
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
timeout: MACHINE_PATH_TIMEOUT_MS,
|
|
127
|
+
windowsHide: true,
|
|
128
|
+
});
|
|
129
|
+
if (result.status !== 0 || result.error)
|
|
130
|
+
return null;
|
|
131
|
+
return parseRegQueryPathValue(result.stdout || '');
|
|
132
|
+
});
|
|
133
|
+
return composeMachinePath(rawValues);
|
|
134
|
+
}
|
|
135
|
+
function resolveMachinePathUncached() {
|
|
136
|
+
if (machinePathResolverOverride)
|
|
137
|
+
return machinePathResolverOverride();
|
|
138
|
+
return defaultResolveMachinePath();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The PATH the machine reports, read fresh instead of inherited, memoized for
|
|
142
|
+
* `MACHINE_PATH_CACHE_TTL_MS`. `nowMs` is injectable so a test can prove the entry expires
|
|
143
|
+
* without sleeping.
|
|
144
|
+
*/
|
|
145
|
+
function resolveMachinePathCached(nowMs = Date.now()) {
|
|
146
|
+
if (cachedMachinePath !== undefined && nowMs - cachedMachinePathAtMs < MACHINE_PATH_CACHE_TTL_MS) {
|
|
147
|
+
return cachedMachinePath;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
cachedMachinePath = resolveMachinePathUncached();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
cachedMachinePath = null;
|
|
154
|
+
}
|
|
155
|
+
cachedMachinePathAtMs = nowMs;
|
|
156
|
+
return cachedMachinePath;
|
|
157
|
+
}
|
|
158
|
+
function clearMachinePathCache() {
|
|
159
|
+
cachedMachinePath = undefined;
|
|
160
|
+
cachedMachinePathAtMs = 0;
|
|
161
|
+
}
|
|
162
|
+
function setMachinePathResolver(resolver) {
|
|
163
|
+
machinePathResolverOverride = resolver;
|
|
164
|
+
clearMachinePathCache();
|
|
165
|
+
}
|
|
@@ -112,6 +112,11 @@ async function installManagedAgent(option, systemPath, deps) {
|
|
|
112
112
|
NPM_CONFIG_PREFIX: undefined,
|
|
113
113
|
});
|
|
114
114
|
const standardVersion = deps.commandVersion(option.launchCommand, undefined, systemPath);
|
|
115
|
+
// Only look up npm global bin dirs when the CLI was NOT already runnable: that lookup
|
|
116
|
+
// is a blocking npm-prefix subprocess, and on the success path there is nothing to find.
|
|
117
|
+
// (Commit 16cea56ac made this unconditional so the caller could persist the directory onto
|
|
118
|
+
// process.env.PATH; issue #1748 removed that in-process PATH write, so the unconditional
|
|
119
|
+
// lookup has no consumer and only costs the happy path a blocking subprocess.)
|
|
115
120
|
const npmGlobalBinDirs = standardVersion
|
|
116
121
|
? []
|
|
117
122
|
: (0, managed_agent_paths_1.resolveNpmGlobalBinDirs)(systemPath, {
|
|
@@ -123,7 +128,7 @@ async function installManagedAgent(option, systemPath, deps) {
|
|
|
123
128
|
? deps.commandVersion(option.launchCommand, npmGlobalBinDirs, systemPath)
|
|
124
129
|
: null);
|
|
125
130
|
if (standardVersionWithNpmBin) {
|
|
126
|
-
return { outcome: 'standard'
|
|
131
|
+
return { outcome: 'standard' };
|
|
127
132
|
}
|
|
128
133
|
standardInstallError = `${option.label} standard install completed, but the CLI is not runnable from the user PATH.`;
|
|
129
134
|
}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveMachinePathCached = exports.expandWindowsEnvRefs = exports.parseRegQueryPathValue = void 0;
|
|
6
7
|
exports.getManagedNodeRoot = getManagedNodeRoot;
|
|
7
8
|
exports.getPortableNodeBinPath = getPortableNodeBinPath;
|
|
8
9
|
exports.getManagedAgentBinDirs = getManagedAgentBinDirs;
|
|
@@ -13,10 +14,11 @@ exports.appendBinDirsToPath = appendBinDirsToPath;
|
|
|
13
14
|
exports.getNpmGlobalBinDirsFromPrefix = getNpmGlobalBinDirsFromPrefix;
|
|
14
15
|
exports.resolveNpmGlobalBinDirs = resolveNpmGlobalBinDirs;
|
|
15
16
|
exports.buildPathWithManagedAgentBins = buildPathWithManagedAgentBins;
|
|
16
|
-
exports.appendManagedAgentBinDirsToProcessPath = appendManagedAgentBinDirsToProcessPath;
|
|
17
17
|
exports.__setLoginShellPathResolverForTests = __setLoginShellPathResolverForTests;
|
|
18
18
|
exports.resolveLoginShellPathCached = resolveLoginShellPathCached;
|
|
19
19
|
exports.__clearLoginShellPathCacheForTests = __clearLoginShellPathCacheForTests;
|
|
20
|
+
exports.invalidateMachinePathCache = invalidateMachinePathCache;
|
|
21
|
+
exports.__setMachinePathResolverForTests = __setMachinePathResolverForTests;
|
|
20
22
|
exports.buildRecoveredSystemPath = buildRecoveredSystemPath;
|
|
21
23
|
exports.buildRecoveredAgentPath = buildRecoveredAgentPath;
|
|
22
24
|
exports.versionFromProbeOutput = versionFromProbeOutput;
|
|
@@ -24,6 +26,13 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
24
26
|
const path_1 = __importDefault(require("path"));
|
|
25
27
|
const child_process_1 = require("child_process");
|
|
26
28
|
const script_sync_utils_1 = require("./script-sync-utils");
|
|
29
|
+
const machine_path_1 = require("./machine-path");
|
|
30
|
+
// Re-exported so the machine-PATH surface stays reachable from this module, which is
|
|
31
|
+
// where every other PATH helper already lives.
|
|
32
|
+
var machine_path_2 = require("./machine-path");
|
|
33
|
+
Object.defineProperty(exports, "parseRegQueryPathValue", { enumerable: true, get: function () { return machine_path_2.parseRegQueryPathValue; } });
|
|
34
|
+
Object.defineProperty(exports, "expandWindowsEnvRefs", { enumerable: true, get: function () { return machine_path_2.expandWindowsEnvRefs; } });
|
|
35
|
+
Object.defineProperty(exports, "resolveMachinePathCached", { enumerable: true, get: function () { return machine_path_2.resolveMachinePathCached; } });
|
|
27
36
|
function getManagedNodeRoot() {
|
|
28
37
|
return path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'node');
|
|
29
38
|
}
|
|
@@ -185,9 +194,6 @@ function resolveNpmGlobalBinDirs(basePath, env) {
|
|
|
185
194
|
function buildPathWithManagedAgentBins(basePath) {
|
|
186
195
|
return appendBinDirsToPath(stripManagedAgentBinDirsFromPath(basePath), getManagedAgentBinDirs());
|
|
187
196
|
}
|
|
188
|
-
function appendManagedAgentBinDirsToProcessPath() {
|
|
189
|
-
process.env.PATH = buildPathWithManagedAgentBins(process.env.PATH);
|
|
190
|
-
}
|
|
191
197
|
// ─── Issue #1256 (slice a): location-agnostic PATH recovery ─────────────────
|
|
192
198
|
//
|
|
193
199
|
// A Hub launched from Launchpad/Finder/a Windows shortcut inherits the OS's minimal
|
|
@@ -350,17 +356,55 @@ function resolveNpmGlobalBinDirsCached(basePath) {
|
|
|
350
356
|
// — resolve against this path for the system-install check, then consult
|
|
351
357
|
// `getPortableManagedCommandPath()` (never the flat dir) as an explicit,
|
|
352
358
|
// separate fallback tier.
|
|
359
|
+
// Issue #1748: the win32 machine-PATH read lives in ./machine-path so this file stays
|
|
360
|
+
// under the 500-line architecture threshold. These wrappers exist because changing the
|
|
361
|
+
// machine PATH also invalidates npm-global dirs, which are resolved against the PATH
|
|
362
|
+
// derived from it and cached here. Callers get one call that keeps both caches coherent
|
|
363
|
+
// rather than having to remember to clear a second one.
|
|
364
|
+
function invalidateMachinePathCache() {
|
|
365
|
+
(0, machine_path_1.clearMachinePathCache)();
|
|
366
|
+
cachedNpmGlobalBinDirsForRecovery.clear();
|
|
367
|
+
}
|
|
368
|
+
function __setMachinePathResolverForTests(resolver) {
|
|
369
|
+
(0, machine_path_1.setMachinePathResolver)(resolver);
|
|
370
|
+
cachedNpmGlobalBinDirsForRecovery.clear();
|
|
371
|
+
}
|
|
353
372
|
function buildRecoveredSystemPath(basePath) {
|
|
354
373
|
const withoutManaged = stripManagedAgentBinDirsFromPath(basePath);
|
|
355
|
-
const
|
|
374
|
+
const inherited = stripProjectLocalNodeBinDirs(withoutManaged);
|
|
375
|
+
// Issue #1748 (win32 only): the PATH the machine reports right now. This process may
|
|
376
|
+
// predate the user's install, so its own inherited PATH is a stale copy of the same
|
|
377
|
+
// registry. Machine entries go FIRST, because entries present in both would otherwise
|
|
378
|
+
// keep their stale relative position and an entry existing only on the machine PATH
|
|
379
|
+
// would land last, which is how resolution picked a different install than the one a
|
|
380
|
+
// fresh terminal resolves.
|
|
381
|
+
//
|
|
382
|
+
// Deliberately NOT applied to the POSIX login-shell tier below, which keeps its
|
|
383
|
+
// original append semantics. The staleness this reorders around is a Windows
|
|
384
|
+
// registry-snapshot problem; on POSIX a GUI-launched process has a minimal inherited
|
|
385
|
+
// PATH that carries no competing entry to out-order, so appending already resolves it.
|
|
386
|
+
// Promoting login-shell entries above inherited ones there would let the login shell
|
|
387
|
+
// silently override a directory a wrapper deliberately prepended for that launch,
|
|
388
|
+
// which is the same class of "wrong binary launched" bug this change exists to fix.
|
|
389
|
+
const machinePath = process.platform === 'win32' ? (0, machine_path_1.resolveMachinePathCached)() : null;
|
|
390
|
+
const machineEntries = machinePath ? machinePath.split(path_1.default.delimiter).filter(Boolean) : [];
|
|
391
|
+
const withMachine = machineEntries.length > 0
|
|
392
|
+
? appendBinDirsToPath(machineEntries.join(path_1.default.delimiter), inherited.split(path_1.default.delimiter).filter(Boolean))
|
|
393
|
+
: inherited;
|
|
356
394
|
const loginShellPath = resolveLoginShellPathCached();
|
|
357
395
|
const loginShellEntries = loginShellPath ? loginShellPath.split(path_1.default.delimiter).filter(Boolean) : [];
|
|
358
396
|
const withLoginShell = loginShellEntries.length > 0
|
|
359
|
-
? appendBinDirsToPath(
|
|
360
|
-
:
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
397
|
+
? appendBinDirsToPath(withMachine, loginShellEntries)
|
|
398
|
+
: withMachine;
|
|
399
|
+
const cleaned = stripProjectLocalNodeBinDirs(withLoginShell);
|
|
400
|
+
// Strip managed dirs a second time. FRAIM registers its own directory on the machine
|
|
401
|
+
// PATH at install (persistShellPath), so the recovered PATH legitimately contains it,
|
|
402
|
+
// but this helper's contract is "what the real machine provides, excluding FRAIM's
|
|
403
|
+
// bundled copy" - which is what lets callers tell a user install apart from FRAIM's
|
|
404
|
+
// fallback. Without this the machine read would smuggle the managed dir back in.
|
|
405
|
+
const systemOnly = stripManagedAgentBinDirsFromPath(cleaned);
|
|
406
|
+
const npmGlobalBinDirs = resolveNpmGlobalBinDirsCached(systemOnly);
|
|
407
|
+
return appendBinDirsToPath(systemOnly, npmGlobalBinDirs);
|
|
364
408
|
}
|
|
365
409
|
function buildRecoveredAgentPath(basePath) {
|
|
366
410
|
return appendBinDirsToPath(buildRecoveredSystemPath(basePath), getManagedAgentBinDirs());
|
|
@@ -89,11 +89,18 @@ function ensureOutputDirs() {
|
|
|
89
89
|
fs_1.default.mkdirSync((0, script_sync_utils_1.getUserFraimDir)(), { recursive: true });
|
|
90
90
|
fs_1.default.mkdirSync(path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'last-install'), { recursive: true });
|
|
91
91
|
}
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
92
|
+
// Issue #1748: this no longer touches process.env.PATH. It used to append FRAIM's
|
|
93
|
+
// managed bin dirs here so spawnSync callers would find them, but desktop-main imports
|
|
94
|
+
// this module, so every Hub inherited them and command resolution could no longer tell
|
|
95
|
+
// a user install from FRAIM's bundled copy. Anything that needs to find a managed
|
|
96
|
+
// binary must route through buildRecoveredAgentPath()/resolveManagedCommand() rather
|
|
97
|
+
// than relying on ambient process.env.PATH. What remains here is shim cleanup only.
|
|
95
98
|
(function bootstrapFraimNodeBin() {
|
|
96
|
-
|
|
99
|
+
// Issue #1748: this used to call appendManagedAgentBinDirsToProcessPath(), putting FRAIM's
|
|
100
|
+
// managed dirs on this process's own PATH. desktop-main imports this module, so every Hub
|
|
101
|
+
// inherited that, and command resolution could no longer tell a user install from FRAIM's
|
|
102
|
+
// bundled copy. Resolution now re-reads the machine PATH instead, and FRAIM's directory is
|
|
103
|
+
// reachable there because persistShellPath registers it with the OS at install time.
|
|
97
104
|
// Issue #1285 (Implementation Strategy §3): remove any managed-agent shim
|
|
98
105
|
// orphaned in the legacy flat directory by an older FRAIM version, so it
|
|
99
106
|
// can no longer shadow the current versioned build.
|
|
@@ -914,9 +921,6 @@ class FirstRunSessionService {
|
|
|
914
921
|
});
|
|
915
922
|
const pathWithNpm = (0, managed_agent_paths_1.appendBinDirsToPath)(systemPath, [npm.binDir]);
|
|
916
923
|
const outcome = await (0, managed_agent_install_1.installManagedAgent)({ label: option.label, installPackage: option.installPackage, launchCommand: option.launchCommand }, pathWithNpm, { runProcess, commandVersion });
|
|
917
|
-
if (outcome.outcome === 'standard' && outcome.npmGlobalBinDirs.length > 0) {
|
|
918
|
-
process.env.PATH = (0, managed_agent_paths_1.appendBinDirsToPath)(pathWithNpm, outcome.npmGlobalBinDirs);
|
|
919
|
-
}
|
|
920
924
|
this.setAgentInstallStatus(agentId, 'needs-sign-in', `Sign in to ${option.label} to activate it.`);
|
|
921
925
|
appendInstallLog(outcome.outcome === 'standard' ? `agent-installed-standard ${agentId}` : `agent-installed-managed ${agentId}`);
|
|
922
926
|
this.persist();
|