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.
@@ -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 'Command has no invocation to run after the environment prefix.';
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 `Command must invoke "${baseHostId}" (found "${parsed.invocationHead}"). A setup script should be registered as a setup script, not composed into this field — see hub-configured-agent-setup.md.`;
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
- try {
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 {
@@ -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
- handlers.onEvent({ hostError: describeSpawnError(err) }, 'system');
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: projectPath,
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, projectPath, plan.stdin || '', startedAtMs);
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
+ }