fraim-hub 2.0.329 → 2.0.333
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/ai-hub/background-command.js +87 -0
- package/dist/src/ai-hub/cert-store.js +30 -5
- package/dist/src/ai-hub/cli.js +2 -1
- package/dist/src/ai-hub/configured-agent-command.js +52 -0
- package/dist/src/ai-hub/configured-agents.js +11 -2
- package/dist/src/ai-hub/desktop-admission.js +53 -0
- package/dist/src/ai-hub/desktop-auto-updater.js +15 -9
- package/dist/src/ai-hub/desktop-installer-setup.js +19 -9
- package/dist/src/ai-hub/desktop-launcher.js +22 -3
- package/dist/src/ai-hub/desktop-main.js +149 -42
- package/dist/src/ai-hub/hosts.js +103 -7
- package/dist/src/ai-hub/hub-app-materializer.js +6 -5
- package/dist/src/ai-hub/hub-install.js +1 -1
- package/dist/src/ai-hub/hub-instance-reconciliation.js +65 -42
- package/dist/src/ai-hub/hub-latest-version.js +24 -7
- package/dist/src/ai-hub/hub-restart-plan.js +62 -0
- package/dist/src/ai-hub/office-sideload.js +47 -9
- package/dist/src/ai-hub/process-liveness.js +5 -0
- package/dist/src/ai-hub/server.js +18 -15
- package/dist/src/cli/doctor/checks/agent-cli-health-checks.js +16 -3
- package/dist/src/cli/doctor/redact-paths.js +78 -0
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +1 -1
- package/dist/src/cli/setup/ide-detector.js +5 -5
- package/dist/src/cli/utils/machine-path.js +73 -5
- package/dist/src/cli/utils/managed-agent-paths.js +1 -0
- package/dist/src/cli/utils/persist-shell-path.js +1 -1
- package/dist/src/cli/utils/win32-cmd-spawn.js +1 -0
- package/dist/src/core/utils/anthropic-auth-route.js +243 -0
- package/dist/src/core/utils/claude-subscription-login.js +92 -0
- package/dist/src/core/utils/env-var-names.js +64 -0
- package/dist/src/first-run/session-service.js +3 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +86 -14
- package/package.json +8 -3
- package/public/ai-hub/index.html +3 -3
- package/public/ai-hub/script.js +31 -3
- package/public/ai-hub/styles.css +31 -6
|
@@ -0,0 +1,87 @@
|
|
|
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.killBackgroundProcessTree = killBackgroundProcessTree;
|
|
7
|
+
exports.runBackgroundCommand = runBackgroundCommand;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const hub_main_diagnostics_1 = require("./hub-main-diagnostics");
|
|
12
|
+
function killBackgroundProcessTree(pid, signal = 'SIGTERM', callback = () => undefined) {
|
|
13
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || pid === process.pid) {
|
|
14
|
+
callback(new Error('Refusing invalid or current process termination'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (process.platform === 'win32') {
|
|
18
|
+
(0, child_process_1.execFile)('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, timeout: 5000 }, callback);
|
|
19
|
+
}
|
|
20
|
+
else
|
|
21
|
+
(0, tree_kill_1.default)(pid, signal, callback);
|
|
22
|
+
}
|
|
23
|
+
/** Captured, bounded infrastructure work. Completion follows child close, including timeout cleanup. */
|
|
24
|
+
function runBackgroundCommand(command, args, options = {}) {
|
|
25
|
+
const started = performance.now();
|
|
26
|
+
// Log only an allowlisted executable category, never paths, arguments or output.
|
|
27
|
+
const executable = node_path_1.default.basename(command).toLowerCase().replace(/\.(exe|cmd|bat)$/, '');
|
|
28
|
+
const category = ['node', 'electron', 'npm', 'cmd', 'powershell', 'pwsh', 'reg', 'certutil', 'security'].includes(executable)
|
|
29
|
+
? executable : 'other';
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
32
|
+
env: options.env ?? process.env, windowsHide: true,
|
|
33
|
+
windowsVerbatimArguments: options.windowsVerbatimArguments,
|
|
34
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
35
|
+
});
|
|
36
|
+
let stdout = '';
|
|
37
|
+
let stderr = '';
|
|
38
|
+
let error;
|
|
39
|
+
let outcome;
|
|
40
|
+
let termination;
|
|
41
|
+
const terminate = () => {
|
|
42
|
+
if (termination || !child.pid || child.exitCode !== null)
|
|
43
|
+
return;
|
|
44
|
+
const pid = child.pid;
|
|
45
|
+
termination = new Promise((done) => {
|
|
46
|
+
const ended = (failure) => {
|
|
47
|
+
if (failure && child.exitCode === null) {
|
|
48
|
+
error = failure;
|
|
49
|
+
child.kill('SIGKILL');
|
|
50
|
+
}
|
|
51
|
+
done();
|
|
52
|
+
};
|
|
53
|
+
killBackgroundProcessTree(pid, 'SIGKILL', ended);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
outcome = 'timeout';
|
|
58
|
+
error = new Error(`Background command exceeded ${options.timeoutMs ?? 15000}ms deadline`);
|
|
59
|
+
terminate();
|
|
60
|
+
}, options.timeoutMs ?? 15000);
|
|
61
|
+
const capture = (chunk, stream) => {
|
|
62
|
+
if (stream === 'stdout')
|
|
63
|
+
stdout += chunk.toString();
|
|
64
|
+
else
|
|
65
|
+
stderr += chunk.toString();
|
|
66
|
+
if (stdout.length + stderr.length > 1024 * 1024) {
|
|
67
|
+
outcome = 'output-limit';
|
|
68
|
+
stdout = stdout.slice(0, 512 * 1024);
|
|
69
|
+
stderr = stderr.slice(0, 512 * 1024);
|
|
70
|
+
error = new Error('Background command exceeded output limit');
|
|
71
|
+
terminate();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
child.stdout.on('data', chunk => capture(chunk, 'stdout'));
|
|
75
|
+
child.stderr.on('data', chunk => capture(chunk, 'stderr'));
|
|
76
|
+
child.on('error', failure => { error = failure; outcome = 'spawn-error'; });
|
|
77
|
+
child.on('close', async (status, signal) => {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
await termination;
|
|
80
|
+
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('hub.background_command', {
|
|
81
|
+
category, outcome: outcome ?? (status === 0 ? 'success' : signal ? 'terminated' : 'nonzero-exit'),
|
|
82
|
+
exitCode: status, signal, durationMs: performance.now() - started,
|
|
83
|
+
});
|
|
84
|
+
resolve({ status: error ? null : status, stdout, stderr, error });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports._internal = void 0;
|
|
7
|
+
exports.isCertTrustedAsync = isCertTrustedAsync;
|
|
8
|
+
exports.trustCertAsync = trustCertAsync;
|
|
7
9
|
exports.certPaths = certPaths;
|
|
8
10
|
exports.loadOrCreateCert = loadOrCreateCert;
|
|
9
11
|
exports.trustCert = trustCert;
|
|
@@ -15,6 +17,29 @@ const path_1 = __importDefault(require("path"));
|
|
|
15
17
|
const os_1 = __importDefault(require("os"));
|
|
16
18
|
const crypto_1 = __importDefault(require("crypto"));
|
|
17
19
|
const child_process_1 = require("child_process");
|
|
20
|
+
const background_command_1 = require("./background-command");
|
|
21
|
+
async function isCertTrustedAsync(certPath) {
|
|
22
|
+
if (!fs_1.default.existsSync(certPath))
|
|
23
|
+
return false;
|
|
24
|
+
const fingerprint = computeCertFingerprint(fs_1.default.readFileSync(certPath, 'utf8'));
|
|
25
|
+
const keychain = path_1.default.join(os_1.default.homedir(), 'Library', 'Keychains', 'login.keychain-db');
|
|
26
|
+
if (process.platform !== 'win32' && process.platform !== 'darwin')
|
|
27
|
+
return false;
|
|
28
|
+
const result = process.platform === 'win32'
|
|
29
|
+
? await exports._internal.runBackgroundCommand('certutil', ['-store', '-user', 'Root'])
|
|
30
|
+
: await exports._internal.runBackgroundCommand('security', ['find-certificate', '-a', '-c', 'localhost', '-Z', keychain]);
|
|
31
|
+
return result.status === 0 && result.stdout.toLowerCase().replace(/:/g, '').includes(fingerprint);
|
|
32
|
+
}
|
|
33
|
+
async function trustCertAsync(certPath) {
|
|
34
|
+
if (process.platform !== 'win32' && process.platform !== 'darwin')
|
|
35
|
+
return;
|
|
36
|
+
const keychain = path_1.default.join(os_1.default.homedir(), 'Library', 'Keychains', 'login.keychain-db');
|
|
37
|
+
const result = process.platform === 'win32'
|
|
38
|
+
? await exports._internal.runBackgroundCommand('certutil', ['-addstore', '-user', 'Root', certPath], { timeoutMs: 120000 })
|
|
39
|
+
: await exports._internal.runBackgroundCommand('security', ['add-trusted-cert', '-r', 'trustRoot', '-k', keychain, certPath], { timeoutMs: 120000 });
|
|
40
|
+
if (result.status !== 0)
|
|
41
|
+
throw result.error ?? new Error(result.stderr || 'Certificate trust failed');
|
|
42
|
+
}
|
|
18
43
|
function certDir() {
|
|
19
44
|
const base = process.env.APPDATA ||
|
|
20
45
|
(process.platform === 'darwin'
|
|
@@ -58,7 +83,7 @@ async function loadOrCreateCert() {
|
|
|
58
83
|
* Swapping the export on Node's built-in `child_process` module itself is not reliable for
|
|
59
84
|
* this (confirmed empirically while fixing this issue) - this repo-owned seam is.
|
|
60
85
|
*/
|
|
61
|
-
exports._internal = { spawnSync: child_process_1.spawnSync };
|
|
86
|
+
exports._internal = { spawnSync: child_process_1.spawnSync, runBackgroundCommand: background_command_1.runBackgroundCommand };
|
|
62
87
|
/**
|
|
63
88
|
* Installs the cert as a trusted root CA in the user's OS certificate store.
|
|
64
89
|
* ONLY call this for explicit Word-Online support — it prompts a Windows security
|
|
@@ -69,7 +94,7 @@ exports._internal = { spawnSync: child_process_1.spawnSync };
|
|
|
69
94
|
*/
|
|
70
95
|
function trustCert(certPath) {
|
|
71
96
|
if (process.platform === 'win32') {
|
|
72
|
-
const result = exports._internal.spawnSync('certutil', ['-addstore', '-user', 'Root', certPath], { encoding: 'utf8' });
|
|
97
|
+
const result = exports._internal.spawnSync('certutil', ['-addstore', '-user', 'Root', certPath], { encoding: 'utf8', windowsHide: true, timeout: 15000 });
|
|
73
98
|
if (result.status !== 0) {
|
|
74
99
|
console.warn('[fraim] could not trust localhost certificate:', result.stderr || result.stdout);
|
|
75
100
|
}
|
|
@@ -83,7 +108,7 @@ function trustCert(certPath) {
|
|
|
83
108
|
/** Removes a specific cert from the trust store, by fingerprint. Test-only helper. */
|
|
84
109
|
function untrustCert(fingerprint) {
|
|
85
110
|
if (process.platform === 'win32') {
|
|
86
|
-
exports._internal.spawnSync('certutil', ['-delstore', '-user', 'Root', fingerprint], { encoding: 'utf8' });
|
|
111
|
+
exports._internal.spawnSync('certutil', ['-delstore', '-user', 'Root', fingerprint], { encoding: 'utf8', windowsHide: true, timeout: 15000 });
|
|
87
112
|
return;
|
|
88
113
|
}
|
|
89
114
|
if (process.platform === 'darwin') {
|
|
@@ -116,14 +141,14 @@ function isCertTrusted(certPath) {
|
|
|
116
141
|
return false;
|
|
117
142
|
const fingerprint = computeCertFingerprint(fs_1.default.readFileSync(certPath, 'utf8'));
|
|
118
143
|
if (process.platform === 'win32') {
|
|
119
|
-
const result = (0, child_process_1.spawnSync)('certutil', ['-store', '-user', 'Root'], { encoding: 'utf8' });
|
|
144
|
+
const result = (0, child_process_1.spawnSync)('certutil', ['-store', '-user', 'Root'], { encoding: 'utf8', windowsHide: true, timeout: 15000 });
|
|
120
145
|
if (result.status !== 0)
|
|
121
146
|
return false;
|
|
122
147
|
return result.stdout.toLowerCase().includes(fingerprint);
|
|
123
148
|
}
|
|
124
149
|
if (process.platform === 'darwin') {
|
|
125
150
|
const keychain = path_1.default.join(os_1.default.homedir(), 'Library', 'Keychains', 'login.keychain-db');
|
|
126
|
-
const result = (0, child_process_1.spawnSync)('security', ['find-certificate', '-a', '-c', 'localhost', '-Z', keychain], { encoding: 'utf8' });
|
|
151
|
+
const result = (0, child_process_1.spawnSync)('security', ['find-certificate', '-a', '-c', 'localhost', '-Z', keychain], { encoding: 'utf8', windowsHide: true, timeout: 15000 });
|
|
127
152
|
if (result.status !== 0)
|
|
128
153
|
return false;
|
|
129
154
|
return result.stdout.toLowerCase().replace(/:/g, '').includes(fingerprint);
|
package/dist/src/ai-hub/cli.js
CHANGED
|
@@ -90,6 +90,7 @@ async function openDesktopWindow(projectPath, preferredPort, runtimeId) {
|
|
|
90
90
|
args.push('--hub-runtime-id', runtimeId);
|
|
91
91
|
}
|
|
92
92
|
const child = (0, child_process_1.spawn)(electron.binaryPath, args, {
|
|
93
|
+
windowsHide: true,
|
|
93
94
|
detached: true,
|
|
94
95
|
stdio: 'ignore',
|
|
95
96
|
});
|
|
@@ -97,7 +98,7 @@ async function openDesktopWindow(projectPath, preferredPort, runtimeId) {
|
|
|
97
98
|
}
|
|
98
99
|
function openBrowser(url) {
|
|
99
100
|
if (process.platform === 'win32') {
|
|
100
|
-
const child = (0, child_process_1.spawn)('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' });
|
|
101
|
+
const child = (0, child_process_1.spawn)('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
101
102
|
child.unref();
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
@@ -5,10 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.parseConfiguredCommand = parseConfiguredCommand;
|
|
7
7
|
exports.describeConfiguredCommandInvocationProblem = describeConfiguredCommandInvocationProblem;
|
|
8
|
+
exports.__setCommandResolutionProbeForTests = __setCommandResolutionProbeForTests;
|
|
9
|
+
exports.probeCommandResolvesNonInteractively = probeCommandResolvesNonInteractively;
|
|
8
10
|
exports.commandContainsLiteralSecret = commandContainsLiteralSecret;
|
|
9
11
|
exports.decorateHostPlanWithConfiguredAgent = decorateHostPlanWithConfiguredAgent;
|
|
10
12
|
const fs_1 = __importDefault(require("fs"));
|
|
11
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
12
15
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
13
16
|
const BASH_ENV_PREFIX = /^([A-Za-z_][A-Za-z0-9_]*)=(?:\$\([^)]*\)|"[^"]*"|'[^']*'|\S*)\s+/;
|
|
14
17
|
// The value may be a parenthesized PowerShell expression followed by a
|
|
@@ -51,6 +54,55 @@ function describeConfiguredCommandInvocationProblem(parsed, baseHostId, agentLab
|
|
|
51
54
|
}
|
|
52
55
|
return null;
|
|
53
56
|
}
|
|
57
|
+
let commandResolutionProbeOverride = null;
|
|
58
|
+
function __setCommandResolutionProbeForTests(stub) {
|
|
59
|
+
commandResolutionProbeOverride = stub;
|
|
60
|
+
}
|
|
61
|
+
const PROBE_CACHE_TTL_MS = 60_000;
|
|
62
|
+
const NON_INTERACTIVE_PROBE_TIMEOUT_MS = 3_000;
|
|
63
|
+
const probeCache = new Map();
|
|
64
|
+
// Probes whether commandName is resolvable in the non-interactive shell context
|
|
65
|
+
// that Hub actually uses to launch configured agents (bash --noprofile --norc,
|
|
66
|
+
// or powershell -NoProfile). Commands defined only as profile functions or
|
|
67
|
+
// aliases are unavailable in that context and would cause silent launch failure.
|
|
68
|
+
// Results are cached for PROBE_CACHE_TTL_MS to avoid repeated spawnSync calls
|
|
69
|
+
// on frequent availability checks.
|
|
70
|
+
function probeCommandResolvesNonInteractively(runner, commandName) {
|
|
71
|
+
if (commandResolutionProbeOverride !== null)
|
|
72
|
+
return commandResolutionProbeOverride(runner, commandName);
|
|
73
|
+
const key = `${runner}\x00${commandName}`;
|
|
74
|
+
const cached = probeCache.get(key);
|
|
75
|
+
if (cached && Date.now() - cached.at < PROBE_CACHE_TTL_MS)
|
|
76
|
+
return cached.result;
|
|
77
|
+
let result;
|
|
78
|
+
if (runner === 'powershell') {
|
|
79
|
+
if (process.platform !== 'win32') {
|
|
80
|
+
result = null;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const r = (0, child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `Get-Command -Name ${JSON.stringify(commandName)} -ErrorAction Stop`], { encoding: 'utf8', timeout: NON_INTERACTIVE_PROBE_TIMEOUT_MS, windowsHide: true });
|
|
84
|
+
result = r.error || r.status !== 0
|
|
85
|
+
? `Command '${commandName}' not found in non-interactive PowerShell (powershell -NoProfile). ` +
|
|
86
|
+
`This command may be a PowerShell profile function or alias that is unavailable without a profile. ` +
|
|
87
|
+
`Create a .cmd wrapper on PATH that calls the real executable directly.`
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const r = (0, child_process_1.spawnSync)('bash', ['--noprofile', '--norc', '-c', `command -v ${commandName}`], {
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
timeout: NON_INTERACTIVE_PROBE_TIMEOUT_MS,
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
});
|
|
97
|
+
result = r.error || r.status !== 0
|
|
98
|
+
? `Command '${commandName}' not found in non-interactive bash (bash --noprofile --norc). ` +
|
|
99
|
+
`This command may be defined only in a shell profile. ` +
|
|
100
|
+
`Add the command to a PATH location accessible without sourcing any profile.`
|
|
101
|
+
: null;
|
|
102
|
+
}
|
|
103
|
+
probeCache.set(key, { result, at: Date.now() });
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
54
106
|
function tokenizeShellLike(text) {
|
|
55
107
|
const tokens = [];
|
|
56
108
|
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
@@ -3,7 +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.describeConfiguredCommandInvocationProblem = exports.decorateHostPlanWithConfiguredAgent = exports.commandContainsLiteralSecret = exports.parseConfiguredCommand = exports.AiHubConfiguredAgentStore = void 0;
|
|
6
|
+
exports.__setCommandResolutionProbeForTests = exports.probeCommandResolvesNonInteractively = exports.describeConfiguredCommandInvocationProblem = exports.decorateHostPlanWithConfiguredAgent = exports.commandContainsLiteralSecret = exports.parseConfiguredCommand = exports.AiHubConfiguredAgentStore = void 0;
|
|
7
7
|
exports.synthesizeDefaultConfiguredAgents = synthesizeDefaultConfiguredAgents;
|
|
8
8
|
exports.checkConfiguredAgentAvailability = checkConfiguredAgentAvailability;
|
|
9
9
|
exports.checkConfiguredAgentReadiness = checkConfiguredAgentReadiness;
|
|
@@ -182,8 +182,14 @@ function checkConfiguredAgentAvailability(agent, hosts, env = process.env) {
|
|
|
182
182
|
try {
|
|
183
183
|
const parsed = (0, configured_agent_command_1.parseConfiguredCommand)(agent.command.runner, agent.command.command);
|
|
184
184
|
const problem = (0, configured_agent_command_1.describeConfiguredCommandInvocationProblem)(parsed, agent.baseHostId, agent.label);
|
|
185
|
-
if (problem)
|
|
185
|
+
if (problem) {
|
|
186
186
|
reasons.push(problem);
|
|
187
|
+
}
|
|
188
|
+
else if (parsed.invocationHead) {
|
|
189
|
+
const probeError = (0, configured_agent_command_1.probeCommandResolvesNonInteractively)(agent.command.runner, parsed.invocationHead);
|
|
190
|
+
if (probeError)
|
|
191
|
+
reasons.push(probeError);
|
|
192
|
+
}
|
|
187
193
|
}
|
|
188
194
|
catch (error) {
|
|
189
195
|
reasons.push(error instanceof Error ? error.message : 'Configured agent command could not be parsed.');
|
|
@@ -304,6 +310,7 @@ function runSetupScript(setupScript) {
|
|
|
304
310
|
...args,
|
|
305
311
|
]];
|
|
306
312
|
const result = (0, child_process_1.spawnSync)(invocation[0], invocation[1], {
|
|
313
|
+
windowsHide: true,
|
|
307
314
|
encoding: 'utf8',
|
|
308
315
|
timeout: setupScript.timeoutMs || 30_000,
|
|
309
316
|
env: captureEnv,
|
|
@@ -348,3 +355,5 @@ Object.defineProperty(exports, "parseConfiguredCommand", { enumerable: true, get
|
|
|
348
355
|
Object.defineProperty(exports, "commandContainsLiteralSecret", { enumerable: true, get: function () { return configured_agent_command_2.commandContainsLiteralSecret; } });
|
|
349
356
|
Object.defineProperty(exports, "decorateHostPlanWithConfiguredAgent", { enumerable: true, get: function () { return configured_agent_command_2.decorateHostPlanWithConfiguredAgent; } });
|
|
350
357
|
Object.defineProperty(exports, "describeConfiguredCommandInvocationProblem", { enumerable: true, get: function () { return configured_agent_command_2.describeConfiguredCommandInvocationProblem; } });
|
|
358
|
+
Object.defineProperty(exports, "probeCommandResolvesNonInteractively", { enumerable: true, get: function () { return configured_agent_command_2.probeCommandResolvesNonInteractively; } });
|
|
359
|
+
Object.defineProperty(exports, "__setCommandResolutionProbeForTests", { enumerable: true, get: function () { return configured_agent_command_2.__setCommandResolutionProbeForTests; } });
|
|
@@ -0,0 +1,53 @@
|
|
|
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.admitDesktopInstance = admitDesktopInstance;
|
|
7
|
+
exports.attachDesktopFocusHandler = attachDesktopFocusHandler;
|
|
8
|
+
const electron_1 = require("electron");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
// A materialized payload has its own module cache. The process owns admission,
|
|
11
|
+
// so both copies share it across the launcher-to-application handoff.
|
|
12
|
+
const admissionKey = Symbol.for('fraim.desktop.admission');
|
|
13
|
+
const processState = globalThis;
|
|
14
|
+
function admitDesktopInstance() {
|
|
15
|
+
if (processState[admissionKey])
|
|
16
|
+
return processState[admissionKey].admitted;
|
|
17
|
+
const runtimeArg = process.argv.indexOf('--hub-runtime-id');
|
|
18
|
+
const runtimeId = (runtimeArg >= 0 ? process.argv[runtimeArg + 1] : undefined)
|
|
19
|
+
|| process.env.FRAIM_HUB_RUNTIME_ID || 'hub';
|
|
20
|
+
electron_1.app.setName(process.env.FRAIM_HUB_DISPLAY_NAME || (runtimeId === 'hub2' ? 'FRAIM Hub 2' : 'FRAIM Hub'));
|
|
21
|
+
electron_1.app.setAppUserModelId('ai.fraim.hub');
|
|
22
|
+
const userDataDir = process.env.FRAIM_AI_HUB_USER_DATA_DIR;
|
|
23
|
+
if (userDataDir) {
|
|
24
|
+
fs_1.default.mkdirSync(userDataDir, { recursive: true });
|
|
25
|
+
electron_1.app.setPath('userData', userDataDir);
|
|
26
|
+
}
|
|
27
|
+
const state = {
|
|
28
|
+
admitted: process.env.FRAIM_AI_HUB_FAKE_HOST === '1' || electron_1.app.requestSingleInstanceLock(),
|
|
29
|
+
pendingFocus: false,
|
|
30
|
+
};
|
|
31
|
+
processState[admissionKey] = state;
|
|
32
|
+
if (!state.admitted) {
|
|
33
|
+
electron_1.app.quit();
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
electron_1.app.on('second-instance', () => {
|
|
37
|
+
if (state.focus)
|
|
38
|
+
state.focus();
|
|
39
|
+
else
|
|
40
|
+
state.pendingFocus = true;
|
|
41
|
+
});
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function attachDesktopFocusHandler(focus) {
|
|
45
|
+
const state = processState[admissionKey];
|
|
46
|
+
if (!state?.admitted)
|
|
47
|
+
return;
|
|
48
|
+
state.focus = focus;
|
|
49
|
+
if (state.pendingFocus) {
|
|
50
|
+
state.pendingFocus = false;
|
|
51
|
+
focus();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runMandatoryDesktopUpdateCheck = runMandatoryDesktopUpdateCheck;
|
|
4
|
+
function beforeDeadline(operation, timeoutMs, onTimeout) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const timer = setTimeout(() => {
|
|
7
|
+
reject(new Error(`Desktop update exceeded ${timeoutMs}ms deadline`));
|
|
8
|
+
onTimeout?.();
|
|
9
|
+
}, timeoutMs);
|
|
10
|
+
operation.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
4
13
|
let updateCheckInFlight = null;
|
|
5
14
|
function runMandatoryDesktopUpdateCheck(options) {
|
|
6
15
|
if (!options.isPackaged)
|
|
@@ -17,7 +26,7 @@ async function runMandatoryDesktopUpdateCheckOnce(options) {
|
|
|
17
26
|
const { updater, prompt, currentVersion, logger = console } = options;
|
|
18
27
|
updater.autoDownload = false;
|
|
19
28
|
updater.autoInstallOnAppQuit = false;
|
|
20
|
-
const checkResult = await checkForDesktopUpdate(updater, prompt, currentVersion, logger);
|
|
29
|
+
const checkResult = await checkForDesktopUpdate(updater, prompt, currentVersion, logger, options.checkTimeoutMs ?? 4000);
|
|
21
30
|
if (checkResult.action === 'check-failed')
|
|
22
31
|
return checkResult;
|
|
23
32
|
if (!checkResult.update?.isUpdateAvailable) {
|
|
@@ -25,14 +34,11 @@ async function runMandatoryDesktopUpdateCheckOnce(options) {
|
|
|
25
34
|
return { action: 'current', availableVersion: checkResult.update?.updateInfo?.version };
|
|
26
35
|
}
|
|
27
36
|
const availableVersion = checkResult.update.updateInfo?.version;
|
|
37
|
+
const cancellationToken = checkResult.update.cancellationToken
|
|
38
|
+
?? new (require('builder-util-runtime').CancellationToken)();
|
|
28
39
|
try {
|
|
29
40
|
logger.info(`[fraim] desktop update ${availableVersion ?? 'unknown'} available; downloading`);
|
|
30
|
-
|
|
31
|
-
await checkResult.update.downloadPromise;
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
await updater.downloadUpdate();
|
|
35
|
-
}
|
|
41
|
+
await beforeDeadline(checkResult.update.downloadPromise ?? updater.downloadUpdate(cancellationToken), options.downloadTimeoutMs ?? 120000, () => cancellationToken.cancel());
|
|
36
42
|
}
|
|
37
43
|
catch (error) {
|
|
38
44
|
const message = errorMessage(error);
|
|
@@ -58,10 +64,10 @@ async function runMandatoryDesktopUpdateCheckOnce(options) {
|
|
|
58
64
|
return { action: 'install-failed', availableVersion, error: message };
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
|
-
async function checkForDesktopUpdate(updater, prompt, currentVersion, logger) {
|
|
67
|
+
async function checkForDesktopUpdate(updater, prompt, currentVersion, logger, timeoutMs) {
|
|
62
68
|
try {
|
|
63
69
|
logger.info(`[fraim] checking for desktop update from ${currentVersion}`);
|
|
64
|
-
return { action: 'checked', update: await updater.checkForUpdates() };
|
|
70
|
+
return { action: 'checked', update: await beforeDeadline(updater.checkForUpdates(), timeoutMs) };
|
|
65
71
|
}
|
|
66
72
|
catch (error) {
|
|
67
73
|
const message = errorMessage(error);
|
|
@@ -3,18 +3,28 @@ 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.isDesktopRuntimeComplete = isDesktopRuntimeComplete;
|
|
6
7
|
exports.readInstallerSetupApiKey = readInstallerSetupApiKey;
|
|
7
8
|
exports.repairInstallerIdeMcpEntries = repairInstallerIdeMcpEntries;
|
|
9
|
+
exports.runDesktopRuntimeSetup = runDesktopRuntimeSetup;
|
|
8
10
|
exports.runDesktopInstallerSetup = runDesktopInstallerSetup;
|
|
9
11
|
const fs_1 = __importDefault(require("fs"));
|
|
10
12
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const child_process_1 = require("child_process");
|
|
12
13
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
13
14
|
const ide_detector_1 = require("../cli/setup/ide-detector");
|
|
14
15
|
const auto_mcp_setup_1 = require("../cli/setup/auto-mcp-setup");
|
|
15
16
|
const fraim_mcp_latest_launcher_1 = require("../cli/mcp/fraim-mcp-latest-launcher");
|
|
16
17
|
const managed_node_runtime_1 = require("../cli/utils/managed-node-runtime");
|
|
17
|
-
const
|
|
18
|
+
const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
19
|
+
const hub_app_materializer_1 = require("./hub-app-materializer");
|
|
20
|
+
function isDesktopRuntimeComplete() {
|
|
21
|
+
if (![(0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)(), (0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)(), (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)(), (0, fraim_mcp_latest_launcher_1.getFraimMcpLatestLauncherPath)()]
|
|
22
|
+
.every(file => fs_1.default.existsSync(file)))
|
|
23
|
+
return false;
|
|
24
|
+
const systemNpm = (0, managed_node_runtime_1.findExecutableOnPath)((0, managed_node_runtime_1.npmExecutableName)());
|
|
25
|
+
return (systemNpm !== null && (0, managed_node_runtime_1.hasWorkingNodeAndNpm)(path_1.default.dirname(systemNpm)))
|
|
26
|
+
|| (0, managed_node_runtime_1.hasWorkingNodeAndNpm)((0, managed_agent_paths_1.getPortableNodeBinPath)());
|
|
27
|
+
}
|
|
18
28
|
function readInstallerSetupApiKey(configPath = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'config.json')) {
|
|
19
29
|
if (!fs_1.default.existsSync(configPath))
|
|
20
30
|
return null;
|
|
@@ -58,22 +68,22 @@ async function repairInstallerIdeMcpEntries(options = {}) {
|
|
|
58
68
|
process.env.FRAIM_NON_INTERACTIVE = previousNonInteractive;
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
|
-
function warmInstallerCliCache() {
|
|
71
|
+
async function warmInstallerCliCache() {
|
|
62
72
|
const cliShim = (0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)();
|
|
63
|
-
const
|
|
64
|
-
const result = process.platform === 'win32'
|
|
65
|
-
? (0, win32_cmd_spawn_1.spawnViaCmdExe)(cliShim, ['--version'], options)
|
|
66
|
-
: (0, child_process_1.spawnSync)(cliShim, ['--version'], options);
|
|
73
|
+
const result = await (0, hub_app_materializer_1.runNpmInstall)(cliShim, ['--version']);
|
|
67
74
|
if (result.status !== 0) {
|
|
68
75
|
throw new Error(`FRAIM CLI bootstrap exited with code ${result.status ?? 'unknown'}.`);
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
|
-
async function
|
|
78
|
+
async function runDesktopRuntimeSetup(logger = console) {
|
|
72
79
|
await (0, managed_node_runtime_1.ensureManagedNpm)({
|
|
73
80
|
onProgress: (message) => logger.log(`[fraim-installer-setup] ${message}`),
|
|
74
81
|
});
|
|
75
82
|
(0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
|
|
76
|
-
warmInstallerCliCache();
|
|
83
|
+
await warmInstallerCliCache();
|
|
84
|
+
}
|
|
85
|
+
async function runDesktopInstallerSetup(logger = console) {
|
|
86
|
+
await runDesktopRuntimeSetup(logger);
|
|
77
87
|
await repairInstallerIdeMcpEntries({ logger });
|
|
78
88
|
logger.log('[fraim-installer-setup] stable CLI and MCP launchers are ready');
|
|
79
89
|
}
|
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const electron_1 = require("electron");
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const hub_app_materializer_1 = require("./hub-app-materializer");
|
|
9
|
+
const desktop_admission_1 = require("./desktop-admission");
|
|
10
|
+
const hub_main_diagnostics_1 = require("./hub-main-diagnostics");
|
|
9
11
|
// Issue #1415: this is the packaged Electron `main` entry (see packages/fraim-hub/package.json
|
|
10
12
|
// `"main"`). It is deliberately thin and changes rarely: its only job is to resolve which version
|
|
11
13
|
// of the actual Hub application (the code in desktop-main.ts) to run — the latest published to
|
|
@@ -20,10 +22,16 @@ async function bootstrapFrom(entryPath) {
|
|
|
20
22
|
await entry.bootstrap();
|
|
21
23
|
}
|
|
22
24
|
async function launch() {
|
|
23
|
-
if (process.argv.includes('--fraim-installer-setup')) {
|
|
25
|
+
if (process.argv.includes('--fraim-installer-setup') || process.argv.includes('--fraim-runtime-repair')) {
|
|
24
26
|
await bootstrapFrom(BUNDLED_ENTRY_PATH);
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
29
|
+
const admissionStarted = performance.now();
|
|
30
|
+
if (!(0, desktop_admission_1.admitDesktopInstance)())
|
|
31
|
+
return;
|
|
32
|
+
(0, hub_main_diagnostics_1.installHubMainConsoleTee)();
|
|
33
|
+
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('hub.startup_stage', { stage: 'admission', durationMs: performance.now() - admissionStarted });
|
|
34
|
+
const resolutionStarted = performance.now();
|
|
27
35
|
// Issue #1415: deterministic escape hatch for integration testing — skips the real npm
|
|
28
36
|
// resolution (and its dependence on whatever happens to be published at test time) so a test can
|
|
29
37
|
// prove the Electron main-process boot chain itself (resolveHubAppEntry -> require -> bootstrap
|
|
@@ -42,8 +50,16 @@ async function launch() {
|
|
|
42
50
|
return { entryPath: BUNDLED_ENTRY_PATH, source: 'bundled', version: electron_1.app.getVersion() };
|
|
43
51
|
});
|
|
44
52
|
console.log(`[fraim-hub-launcher] running FRAIM Hub ${resolved.version} (${resolved.source})`);
|
|
53
|
+
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('hub.startup_stage', {
|
|
54
|
+
stage: 'payload-resolution', durationMs: performance.now() - resolutionStarted,
|
|
55
|
+
selectedVersion: resolved.version, source: resolved.source,
|
|
56
|
+
outcome: resolved.materializationError ? 'fallback' : 'success',
|
|
57
|
+
});
|
|
58
|
+
let entry;
|
|
45
59
|
try {
|
|
46
|
-
|
|
60
|
+
entry = require(resolved.entryPath);
|
|
61
|
+
if (typeof entry.bootstrap !== 'function')
|
|
62
|
+
throw new Error('Hub payload has no bootstrap entry');
|
|
47
63
|
}
|
|
48
64
|
catch (error) {
|
|
49
65
|
if (resolved.entryPath === BUNDLED_ENTRY_PATH)
|
|
@@ -52,8 +68,11 @@ async function launch() {
|
|
|
52
68
|
// dependency introduces) must not strand the user on a broken launch — fall back to the
|
|
53
69
|
// build that shipped with this installer, which is always known-good for this Electron binary.
|
|
54
70
|
console.error(`[fraim-hub-launcher] materialized FRAIM Hub ${resolved.version} failed to load; falling back to the bundled build:`, error);
|
|
55
|
-
|
|
71
|
+
entry = require(BUNDLED_ENTRY_PATH);
|
|
56
72
|
}
|
|
73
|
+
// Once bootstrap starts it can own servers/windows. Do not start a second
|
|
74
|
+
// application over those resources if initialization subsequently fails.
|
|
75
|
+
await entry.bootstrap();
|
|
57
76
|
}
|
|
58
77
|
if (process.versions.electron && process.type !== 'renderer') {
|
|
59
78
|
launch().catch((error) => {
|