fraim-hub 2.0.298 → 2.0.299
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/configured-agent-command.js +3 -3
- package/dist/src/ai-hub/configured-agents.js +13 -20
- package/dist/src/ai-hub/hosts.js +19 -6
- package/dist/src/ai-hub/hub-latest-version.js +11 -0
- package/dist/src/ai-hub/run-working-directory.js +68 -0
- package/dist/src/ai-hub/server.js +407 -69
- package/dist/src/cli/utils/user-config.js +20 -0
- package/dist/src/config/persona-capability-bundles.js +10 -1
- package/dist/src/config/persona-hiring.js +10 -0
- package/dist/src/core/resolve-phase-edge.js +3 -1
- package/dist/src/local-mcp-server/learning-context-builder.js +11 -7
- package/package.json +2 -2
- package/public/ai-hub/index.html +16 -2
- package/public/ai-hub/script.js +132 -19
- package/public/ai-hub/styles.css +21 -0
- package/public/portfolio/index.html +22 -0
- package/public/portfolio/travis.html +106 -0
|
@@ -42,12 +42,12 @@ function parseConfiguredCommand(runner, commandText) {
|
|
|
42
42
|
// the only way to catch this class of error is to check, at readiness time,
|
|
43
43
|
// that what the user typed there actually was that same base binary — not
|
|
44
44
|
// to attempt to execute or otherwise interpret it.
|
|
45
|
-
function describeConfiguredCommandInvocationProblem(parsed, baseHostId) {
|
|
45
|
+
function describeConfiguredCommandInvocationProblem(parsed, baseHostId, agentLabel = 'Configured agent') {
|
|
46
46
|
if (!parsed.invocationHead) {
|
|
47
|
-
return
|
|
47
|
+
return `${agentLabel} command has no invocation after the environment prefix. Run the create-hub-configured-agent job to repair this profile with an environment prefix followed by "${baseHostId}".`;
|
|
48
48
|
}
|
|
49
49
|
if (parsed.invocationHead !== baseHostId) {
|
|
50
|
-
return `
|
|
50
|
+
return `Configured agent "${agentLabel}" invokes "${parsed.invocationHead}", but Hub launches the "${baseHostId}" base CLI and cannot apply that wrapper's environment. Run the create-hub-configured-agent job to repair this profile with an environment prefix followed by "${baseHostId}".`;
|
|
51
51
|
}
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
@@ -178,6 +178,17 @@ function checkConfiguredAgentAvailability(agent, employees, env = process.env) {
|
|
|
178
178
|
if (!agent.command.command.trim()) {
|
|
179
179
|
reasons.push('Command is empty.');
|
|
180
180
|
}
|
|
181
|
+
else {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = (0, configured_agent_command_1.parseConfiguredCommand)(agent.command.runner, agent.command.command);
|
|
184
|
+
const problem = (0, configured_agent_command_1.describeConfiguredCommandInvocationProblem)(parsed, agent.baseHostId, agent.label);
|
|
185
|
+
if (problem)
|
|
186
|
+
reasons.push(problem);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
reasons.push(error instanceof Error ? error.message : 'Configured agent command could not be parsed.');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
181
192
|
}
|
|
182
193
|
else if (agent.setupScript) {
|
|
183
194
|
const command = agent.setupScript.command;
|
|
@@ -195,26 +206,8 @@ function checkConfiguredAgentReadiness(agent, employees, env = process.env) {
|
|
|
195
206
|
const check = checkConfiguredAgentAvailability(agent, employees, env);
|
|
196
207
|
if (!check.available)
|
|
197
208
|
return check;
|
|
198
|
-
if (agent.command)
|
|
199
|
-
|
|
200
|
-
const parsed = (0, configured_agent_command_1.parseConfiguredCommand)(agent.command.runner, agent.command.command);
|
|
201
|
-
const problem = (0, configured_agent_command_1.describeConfiguredCommandInvocationProblem)(parsed, agent.baseHostId);
|
|
202
|
-
if (problem) {
|
|
203
|
-
return { ...check, available: false, reasons: [...check.reasons, problem] };
|
|
204
|
-
}
|
|
205
|
-
return check;
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
return {
|
|
209
|
-
...check,
|
|
210
|
-
available: false,
|
|
211
|
-
reasons: [
|
|
212
|
-
...check.reasons,
|
|
213
|
-
error instanceof Error ? error.message : 'Configured agent command could not be parsed.',
|
|
214
|
-
],
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
}
|
|
209
|
+
if (agent.command)
|
|
210
|
+
return check;
|
|
218
211
|
if (!agent.setupScript)
|
|
219
212
|
return check;
|
|
220
213
|
try {
|
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -43,6 +43,7 @@ const command_resolution_1 = require("../cli/mcp/command-resolution");
|
|
|
43
43
|
const agent_token_prices_1 = require("../local-mcp-server/agent-token-prices");
|
|
44
44
|
const configured_agents_1 = require("./configured-agents");
|
|
45
45
|
const pack_home_1 = require("../cli/utils/pack-home");
|
|
46
|
+
const run_working_directory_1 = require("./run-working-directory");
|
|
46
47
|
// Parse a single line of host stdout looking for a seekMentoring tool-use
|
|
47
48
|
// signal. Returns null if the line does not contain one. Supports both
|
|
48
49
|
// hosts FRAIM ships against today:
|
|
@@ -1916,7 +1917,7 @@ function detectCodexCompactTaskFailure(line) {
|
|
|
1916
1917
|
}
|
|
1917
1918
|
return null;
|
|
1918
1919
|
}
|
|
1919
|
-
function wireHostProcess(hostId, child, handlers) {
|
|
1920
|
+
function wireHostProcess(hostId, child, handlers, spawnCwd) {
|
|
1920
1921
|
const wire = (buffer, channel) => {
|
|
1921
1922
|
let pending = '';
|
|
1922
1923
|
let suppressCodexModelRefreshStderr = false;
|
|
@@ -1971,12 +1972,23 @@ function wireHostProcess(hostId, child, handlers) {
|
|
|
1971
1972
|
// message names the cause. 'close' still fires after 'error' for spawn
|
|
1972
1973
|
// failures, so handlers.onExit is called by the close listener above.
|
|
1973
1974
|
child.on('error', (err) => {
|
|
1974
|
-
|
|
1975
|
+
const spawnError = err;
|
|
1976
|
+
console.warn('[ai-hub] hub.host_spawn_error', {
|
|
1977
|
+
hostId,
|
|
1978
|
+
cwd: spawnCwd,
|
|
1979
|
+
cwdExists: spawnCwd ? (0, run_working_directory_1.workingDirectoryExists)(spawnCwd) : undefined,
|
|
1980
|
+
errorCode: spawnError.code,
|
|
1981
|
+
path: spawnError.path,
|
|
1982
|
+
});
|
|
1983
|
+
handlers.onEvent({ hostError: describeSpawnError(spawnError, spawnCwd) }, 'system');
|
|
1975
1984
|
});
|
|
1976
1985
|
return child;
|
|
1977
1986
|
}
|
|
1978
|
-
function describeSpawnError(err) {
|
|
1987
|
+
function describeSpawnError(err, cwd) {
|
|
1979
1988
|
if (err.code === 'ENOENT') {
|
|
1989
|
+
if (cwd && !(0, run_working_directory_1.workingDirectoryExists)(cwd)) {
|
|
1990
|
+
return `Agent working directory not found (${cwd}). The conversation scope may have been used as a path.`;
|
|
1991
|
+
}
|
|
1980
1992
|
return `Agent binary not found${err.path ? ` (${err.path})` : ''}. Ensure the agent CLI is installed and on PATH.`;
|
|
1981
1993
|
}
|
|
1982
1994
|
if (err.code === 'EACCES') {
|
|
@@ -2003,9 +2015,10 @@ function buildHostSpawnEnv(plan) {
|
|
|
2003
2015
|
}
|
|
2004
2016
|
function spawnHostProcess(hostId, plan, projectPath, handlers) {
|
|
2005
2017
|
const invocation = resolveHostInvocation(plan);
|
|
2018
|
+
const spawnCwd = (0, run_working_directory_1.resolveHostWorkingDirectory)(projectPath);
|
|
2006
2019
|
const startedAtMs = Date.now();
|
|
2007
2020
|
const child = (0, child_process_1.spawn)(invocation.command, invocation.args, {
|
|
2008
|
-
cwd:
|
|
2021
|
+
cwd: spawnCwd,
|
|
2009
2022
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2010
2023
|
env: buildHostSpawnEnv(plan),
|
|
2011
2024
|
});
|
|
@@ -2015,13 +2028,13 @@ function spawnHostProcess(hostId, plan, projectPath, handlers) {
|
|
|
2015
2028
|
child.stdin.end();
|
|
2016
2029
|
if (typeof plan.stdin === 'string' && !plan.args.includes('--resume')) {
|
|
2017
2030
|
child.once('close', () => {
|
|
2018
|
-
const sessionId = discoverSessionIdAfterStart(hostId,
|
|
2031
|
+
const sessionId = discoverSessionIdAfterStart(hostId, spawnCwd, plan.stdin || '', startedAtMs);
|
|
2019
2032
|
if (sessionId) {
|
|
2020
2033
|
handlers.onEvent({ sessionId, raw: `${hostId}-session:${sessionId}` }, 'system');
|
|
2021
2034
|
}
|
|
2022
2035
|
});
|
|
2023
2036
|
}
|
|
2024
|
-
return wireHostProcess(hostId, child, handlers);
|
|
2037
|
+
return wireHostProcess(hostId, child, handlers, spawnCwd);
|
|
2025
2038
|
}
|
|
2026
2039
|
function discoverSessionIdAfterStart(hostId, projectPath, prompt, startedAtMs) {
|
|
2027
2040
|
if (hostId !== 'gemini')
|
|
@@ -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.BADGE_POLL_INTERVAL_MS = void 0;
|
|
6
7
|
exports.getLatestPublishedVersion = getLatestPublishedVersion;
|
|
7
8
|
exports.getCachedLatestPublishedVersion = getCachedLatestPublishedVersion;
|
|
8
9
|
exports.refreshLatestPublishedVersionInBackground = refreshLatestPublishedVersionInBackground;
|
|
@@ -21,6 +22,16 @@ const https_1 = __importDefault(require("https"));
|
|
|
21
22
|
const TTL_MS = 5 * 60 * 1000;
|
|
22
23
|
const FAILED_RETRY_MS = 5 * 60 * 1000;
|
|
23
24
|
const REGISTRY_URL = 'https://registry.npmjs.org/fraim/latest';
|
|
25
|
+
// A resident Hub (hidden in the tray, login-item autostart) can go days without any of
|
|
26
|
+
// this module's other callers ever firing: they're all reactive (a page load, a
|
|
27
|
+
// persona-identity retry, a tray-click relaunch decision), and none of them are timer-driven.
|
|
28
|
+
// If nothing calls in, this cache never refreshes and the badge never learns about a new
|
|
29
|
+
// release, no matter how far behind it drifts. AiHubServer.start() below runs this on its
|
|
30
|
+
// own interval, independent of any request or window, so the cache — and therefore the
|
|
31
|
+
// badge, on its next client-side poll — stays within 15 minutes of the truth even when the
|
|
32
|
+
// Hub is otherwise fully idle. Lives here (not in the Electron shell) so it ships to
|
|
33
|
+
// existing installs via a normal npm publish, not a new signed installer.
|
|
34
|
+
exports.BADGE_POLL_INTERVAL_MS = 15 * 60 * 1000;
|
|
24
35
|
let cache = { value: null, at: 0 };
|
|
25
36
|
let lastAttemptAt = 0;
|
|
26
37
|
let inFlight = null;
|
|
@@ -0,0 +1,68 @@
|
|
|
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.resolveRunWorkingDirectory = resolveRunWorkingDirectory;
|
|
7
|
+
exports.executionScopeFromConversation = executionScopeFromConversation;
|
|
8
|
+
exports.resolveHostWorkingDirectory = resolveHostWorkingDirectory;
|
|
9
|
+
exports.workingDirectoryExists = workingDirectoryExists;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const pack_home_1 = require("../cli/utils/pack-home");
|
|
13
|
+
const conversation_store_1 = require("./conversation-store");
|
|
14
|
+
function ensureExistingDirectory(directoryPath, label) {
|
|
15
|
+
const trimmed = (directoryPath || '').trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
throw new Error(label === 'Project' ? 'Project path is required.' : `${label} working directory is required.`);
|
|
18
|
+
}
|
|
19
|
+
const resolved = path_1.default.resolve(trimmed);
|
|
20
|
+
const stat = fs_1.default.existsSync(resolved) ? fs_1.default.statSync(resolved) : null;
|
|
21
|
+
if (!stat || !stat.isDirectory()) {
|
|
22
|
+
throw new Error(label === 'Project'
|
|
23
|
+
? 'Project path must point to an existing directory.'
|
|
24
|
+
: `${label} working directory is not available: ${resolved}`);
|
|
25
|
+
}
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
function scopeHome(scope) {
|
|
29
|
+
return scope === 'manager'
|
|
30
|
+
? (0, pack_home_1.resolvePackHome)('manager').contentRoot
|
|
31
|
+
: (0, pack_home_1.resolvePackHome)('org').contentRoot;
|
|
32
|
+
}
|
|
33
|
+
function resolveRunWorkingDirectory(scope, candidateProjectPath, defaultProjectPath) {
|
|
34
|
+
if (scope === 'manager') {
|
|
35
|
+
return ensureExistingDirectory(scopeHome('manager'), 'Manager');
|
|
36
|
+
}
|
|
37
|
+
if (scope === 'company') {
|
|
38
|
+
return ensureExistingDirectory(scopeHome('company'), 'Company');
|
|
39
|
+
}
|
|
40
|
+
return ensureExistingDirectory(candidateProjectPath || defaultProjectPath || '', 'Project');
|
|
41
|
+
}
|
|
42
|
+
function executionScopeFromConversation(scope, projectPath) {
|
|
43
|
+
if (scope === 'manager' || projectPath === conversation_store_1.MANAGER_SCOPE_KEY)
|
|
44
|
+
return 'manager';
|
|
45
|
+
if (scope === 'company' || projectPath === conversation_store_1.COMPANY_SCOPE_KEY)
|
|
46
|
+
return 'company';
|
|
47
|
+
return 'project';
|
|
48
|
+
}
|
|
49
|
+
function resolveHostWorkingDirectory(candidateProjectPath) {
|
|
50
|
+
const trimmed = (candidateProjectPath || '').trim();
|
|
51
|
+
if (trimmed === conversation_store_1.MANAGER_SCOPE_KEY) {
|
|
52
|
+
return ensureExistingDirectory(scopeHome('manager'), 'Manager');
|
|
53
|
+
}
|
|
54
|
+
if (trimmed === conversation_store_1.COMPANY_SCOPE_KEY) {
|
|
55
|
+
return ensureExistingDirectory(scopeHome('company'), 'Company');
|
|
56
|
+
}
|
|
57
|
+
return ensureExistingDirectory(trimmed, 'Agent');
|
|
58
|
+
}
|
|
59
|
+
function workingDirectoryExists(directoryPath) {
|
|
60
|
+
if (!directoryPath)
|
|
61
|
+
return false;
|
|
62
|
+
try {
|
|
63
|
+
return fs_1.default.statSync(path_1.default.resolve(directoryPath)).isDirectory();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|