fraim-hub 2.0.287 → 2.0.288
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/hosts.js
CHANGED
|
@@ -82,6 +82,22 @@ function parseSeekMentoringSignal(line) {
|
|
|
82
82
|
return sig;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
// Copilot CLI shape (issue #1399): { type: 'tool.execution_start', data: {
|
|
86
|
+
// mcpServerName, mcpToolName, arguments } }. Live-verified via a direct
|
|
87
|
+
// spike against the installed CLI under --output-format json (see
|
|
88
|
+
// docs/rfcs/1399-copilot-host-chattiness-technical-design.md) — Copilot
|
|
89
|
+
// reports the MCP server and tool name as separate, unambiguous fields
|
|
90
|
+
// rather than a qualifier string, so this reads data.mcpToolName directly
|
|
91
|
+
// instead of going through canonicalToolName's __/./: splitting.
|
|
92
|
+
if (obj.type === 'tool.execution_start' && typeof obj.data === 'object' && obj.data !== null) {
|
|
93
|
+
const data = obj.data;
|
|
94
|
+
if (isFraimTool(data.mcpToolName, 'seekMentoring')) {
|
|
95
|
+
const args = normalizeToolArgs(data.arguments) || undefined;
|
|
96
|
+
const sig = extractSignalFromArgs(args);
|
|
97
|
+
if (sig)
|
|
98
|
+
return sig;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
85
101
|
// Claude Code shape: tool_use blocks live inside parsed.message.content.
|
|
86
102
|
// The name is MCP-prefixed (e.g. 'mcp__fraim__seekMentoring').
|
|
87
103
|
const candidates = [obj];
|
|
@@ -133,6 +149,16 @@ function parseFraimJobLoadSignal(line) {
|
|
|
133
149
|
return sig;
|
|
134
150
|
}
|
|
135
151
|
}
|
|
152
|
+
// Copilot CLI shape (issue #1399): see parseSeekMentoringSignal's Copilot
|
|
153
|
+
// block above for the field-shape rationale.
|
|
154
|
+
if (obj.type === 'tool.execution_start' && typeof obj.data === 'object' && obj.data !== null) {
|
|
155
|
+
const data = obj.data;
|
|
156
|
+
if (isFraimTool(data.mcpToolName, 'get_fraim_job')) {
|
|
157
|
+
const sig = readFraimJobFromArgs(data.arguments);
|
|
158
|
+
if (sig)
|
|
159
|
+
return sig;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
136
162
|
// Claude Code shape: tool_use blocks live inside message.content with
|
|
137
163
|
// an MCP-prefixed name such as mcp__fraim__get_fraim_job.
|
|
138
164
|
const candidates = [obj];
|
|
@@ -289,6 +315,14 @@ function parseAgentIdentitySignal(line) {
|
|
|
289
315
|
return readAgentFromArgs(normalizeToolArgs(item.arguments) || undefined);
|
|
290
316
|
}
|
|
291
317
|
}
|
|
318
|
+
// Copilot CLI shape (issue #1399): see parseSeekMentoringSignal's Copilot
|
|
319
|
+
// block for the field-shape rationale.
|
|
320
|
+
if (obj.type === 'tool.execution_start' && typeof obj.data === 'object' && obj.data !== null) {
|
|
321
|
+
const data = obj.data;
|
|
322
|
+
if (isFraimTool(data.mcpToolName, 'fraim_connect')) {
|
|
323
|
+
return readAgentFromArgs(normalizeToolArgs(data.arguments) || undefined);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
292
326
|
// Claude Code shape.
|
|
293
327
|
const candidates = [obj];
|
|
294
328
|
if (typeof obj.message === 'object' && obj.message !== null) {
|
|
@@ -1434,7 +1468,12 @@ function buildStartPlan(hostId, message, sessionId) {
|
|
|
1434
1468
|
const browser = sharedBrowserHostConfig('copilot');
|
|
1435
1469
|
return {
|
|
1436
1470
|
command: COPILOT_BINARY,
|
|
1437
|
-
|
|
1471
|
+
// Issue #1399: request Copilot's structured JSONL output mode
|
|
1472
|
+
// (one JSON object per line) instead of its default human-
|
|
1473
|
+
// readable terminal rendering, so parseHostLine can classify
|
|
1474
|
+
// tool calls/results/deltas separately from genuine reply text
|
|
1475
|
+
// instead of pattern-matching rendered chrome characters.
|
|
1476
|
+
args: ['--yolo', '--output-format', 'json', ...browser.args],
|
|
1438
1477
|
stdin: transformHeadlessFraimMessage(message, 'start'),
|
|
1439
1478
|
env: browser.env,
|
|
1440
1479
|
};
|
|
@@ -1495,7 +1534,8 @@ function buildContinuePlan(hostId, sessionId, message) {
|
|
|
1495
1534
|
const browser = sharedBrowserHostConfig('copilot');
|
|
1496
1535
|
return {
|
|
1497
1536
|
command: COPILOT_BINARY,
|
|
1498
|
-
|
|
1537
|
+
// Issue #1399: see buildStartPlan's copilot branch for rationale.
|
|
1538
|
+
args: ['--yolo', '--output-format', 'json', '--resume', sessionId, ...browser.args],
|
|
1499
1539
|
stdin: transformHeadlessFraimMessage(message, 'continue'),
|
|
1500
1540
|
env: browser.env,
|
|
1501
1541
|
};
|
|
@@ -1562,9 +1602,10 @@ function buildDirectStartPlan(hostId, message, sessionId) {
|
|
|
1562
1602
|
}
|
|
1563
1603
|
if (hostId === 'copilot') {
|
|
1564
1604
|
// Direct (A/B) mode for Copilot: headless, no FRAIM MCP wiring.
|
|
1605
|
+
// Issue #1399: --output-format json for the same reason as buildStartPlan.
|
|
1565
1606
|
return {
|
|
1566
1607
|
command: COPILOT_BINARY,
|
|
1567
|
-
args: ['--yolo'],
|
|
1608
|
+
args: ['--yolo', '--output-format', 'json'],
|
|
1568
1609
|
stdin: DIRECT_PREAMBLE + message,
|
|
1569
1610
|
};
|
|
1570
1611
|
}
|
|
@@ -1608,9 +1649,10 @@ function buildDirectContinuePlan(hostId, sessionId, message) {
|
|
|
1608
1649
|
}
|
|
1609
1650
|
if (hostId === 'copilot') {
|
|
1610
1651
|
// Direct continue mode for Copilot: resume session, no FRAIM MCP wiring.
|
|
1652
|
+
// Issue #1399: --output-format json for the same reason as buildStartPlan.
|
|
1611
1653
|
return {
|
|
1612
1654
|
command: COPILOT_BINARY,
|
|
1613
|
-
args: ['--yolo', '--resume', sessionId],
|
|
1655
|
+
args: ['--yolo', '--output-format', 'json', '--resume', sessionId],
|
|
1614
1656
|
stdin: DIRECT_PREAMBLE + message,
|
|
1615
1657
|
};
|
|
1616
1658
|
}
|
|
@@ -1723,31 +1765,69 @@ function parseHostLine(hostId, line) {
|
|
|
1723
1765
|
if (hostId === 'antigravity') {
|
|
1724
1766
|
return withSignal({ raw: trimmed });
|
|
1725
1767
|
}
|
|
1726
|
-
// GitHub Copilot CLI output
|
|
1727
|
-
//
|
|
1728
|
-
//
|
|
1729
|
-
//
|
|
1730
|
-
//
|
|
1731
|
-
//
|
|
1732
|
-
//
|
|
1733
|
-
//
|
|
1768
|
+
// GitHub Copilot CLI output (issue #1399): Hub invokes Copilot with
|
|
1769
|
+
// --output-format json (see buildStartPlan's copilot branch), which
|
|
1770
|
+
// reports every event as one JSON object per line. Two shapes from the
|
|
1771
|
+
// original #531 integration are kept as defensive fallbacks — never
|
|
1772
|
+
// confirmed against real traffic, but harmless to keep recognizing if a
|
|
1773
|
+
// future CLI version or mode emits them:
|
|
1774
|
+
// { "type": "session.started", "session_id": "..." }
|
|
1775
|
+
// { "type": "message", "role": "assistant", "content": "..." }
|
|
1776
|
+
// The real vocabulary below was captured via a live spike against the
|
|
1777
|
+
// installed CLI binary (see "Spike Findings" in
|
|
1778
|
+
// docs/rfcs/1399-copilot-host-chattiness-technical-design.md):
|
|
1779
|
+
// { "type": "assistant.message", "data": { "content": "...", "toolRequests": [...] } }
|
|
1780
|
+
// — the genuine reply. data.content is the message text; toolRequests
|
|
1781
|
+
// is metadata about an about-to-run tool call, not display text.
|
|
1782
|
+
// { "type": "result", "sessionId": "...", "exitCode": 0, "usage": {...} }
|
|
1783
|
+
// — terminal turn/session summary. This is the only place sessionId
|
|
1784
|
+
// was observed in a single-shot run (no early session-id event fired).
|
|
1785
|
+
// { "type": "tool.execution_start" | "tool.execution_complete", "data": {...} },
|
|
1786
|
+
// { "type": "model.tool_execution", "data": {...} }
|
|
1787
|
+
// — tool invocation and result. Diagnostic only, never a message.
|
|
1788
|
+
// seekMentoring/get_fraim_job/fraim_connect signal extraction (via
|
|
1789
|
+
// withSignal below) reads the call arguments off tool.execution_start's
|
|
1790
|
+
// data.arguments/data.mcpServerName/data.mcpToolName.
|
|
1791
|
+
// { "type": "assistant.message_delta" | "assistant.tool_call_delta", ...,
|
|
1792
|
+
// "ephemeral": true }
|
|
1793
|
+
// — streaming deltas. The CLI's own schema marks these ephemeral, an
|
|
1794
|
+
// explicit signal (not a heuristic) that they are safe to drop.
|
|
1795
|
+
// session.*/model.*/user.message/assistant.turn_*/assistant.idle
|
|
1796
|
+
// — setup, bookkeeping, and input-echo diagnostics.
|
|
1797
|
+
// Any other/unrecognized type, and any line that fails JSON.parse entirely,
|
|
1798
|
+
// fails CLOSED (raw only) — matching Codex's and Claude's existing
|
|
1799
|
+
// fail-closed default. This replaces the prior fail-OPEN catch-all that
|
|
1800
|
+
// promoted arbitrary unrecognized Copilot text (in practice, ~80% of a
|
|
1801
|
+
// real run's lines — the CLI's own tool-call/result terminal rendering) to
|
|
1802
|
+
// a manager-visible message.
|
|
1734
1803
|
if (hostId === 'copilot') {
|
|
1804
|
+
let parsed;
|
|
1735
1805
|
try {
|
|
1736
|
-
|
|
1737
|
-
if (parsed.type === 'session.started' && typeof parsed.session_id === 'string' && parsed.session_id.length > 0) {
|
|
1738
|
-
return withSignal({ sessionId: parsed.session_id, raw: trimmed });
|
|
1739
|
-
}
|
|
1740
|
-
if (parsed.type === 'message' && parsed.role === 'assistant' && typeof parsed.content === 'string') {
|
|
1741
|
-
return withSignal({ message: parsed.content, raw: trimmed });
|
|
1742
|
-
}
|
|
1743
|
-
// All other JSON events: apply signal scanning and surface as raw.
|
|
1744
|
-
return withSignal({ raw: trimmed });
|
|
1806
|
+
parsed = JSON.parse(trimmed);
|
|
1745
1807
|
}
|
|
1746
1808
|
catch {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1809
|
+
return withSignal({ raw: trimmed });
|
|
1810
|
+
}
|
|
1811
|
+
if (!parsed || typeof parsed !== 'object')
|
|
1812
|
+
return withSignal({ raw: trimmed });
|
|
1813
|
+
if (parsed.type === 'session.started' && typeof parsed.session_id === 'string' && parsed.session_id.length > 0) {
|
|
1814
|
+
return withSignal({ sessionId: parsed.session_id, raw: trimmed });
|
|
1815
|
+
}
|
|
1816
|
+
if (parsed.type === 'message' && parsed.role === 'assistant' && typeof parsed.content === 'string') {
|
|
1817
|
+
return withSignal({ message: parsed.content, raw: trimmed });
|
|
1818
|
+
}
|
|
1819
|
+
if (parsed.type === 'assistant.message') {
|
|
1820
|
+
const content = typeof parsed.data?.content === 'string' ? parsed.data.content : undefined;
|
|
1821
|
+
return content ? withSignal({ message: content, raw: trimmed }) : withSignal({ raw: trimmed });
|
|
1750
1822
|
}
|
|
1823
|
+
if (parsed.type === 'result') {
|
|
1824
|
+
const sessionId = typeof parsed.sessionId === 'string' && parsed.sessionId.length > 0 ? parsed.sessionId : undefined;
|
|
1825
|
+
return withSignal({ sessionId, raw: trimmed });
|
|
1826
|
+
}
|
|
1827
|
+
// tool.execution_start/complete, model.tool_execution, the *_delta
|
|
1828
|
+
// events, and session/model/user/turn bookkeeping all fall here:
|
|
1829
|
+
// diagnostic-only, but still signal-scanned by withSignal above.
|
|
1830
|
+
return withSignal({ raw: trimmed });
|
|
1751
1831
|
}
|
|
1752
1832
|
try {
|
|
1753
1833
|
const parsed = JSON.parse(trimmed);
|
|
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.findAvailablePortExcluding = exports.findAvailablePort = exports.AiHubServer = exports.HubConnectorStatusStore = exports.HostConfigStore = exports.DeploymentStore = void 0;
|
|
40
|
+
exports.selfHealBrokenManagedAgents = selfHealBrokenManagedAgents;
|
|
40
41
|
exports.configureFraimForHubAgent = configureFraimForHubAgent;
|
|
41
42
|
exports.hubCommandVersion = hubCommandVersion;
|
|
42
43
|
exports.buildOpenFileInvocation = buildOpenFileInvocation;
|
|
@@ -1699,6 +1700,35 @@ function hubAgentOption(hubId) {
|
|
|
1699
1700
|
const frId = HUB_TO_FIRST_RUN_ID[hubId];
|
|
1700
1701
|
return frId ? types_1.FIRST_RUN_AGENT_OPTIONS.find((o) => o.id === frId) : undefined;
|
|
1701
1702
|
}
|
|
1703
|
+
const defaultSelfHealDeps = {
|
|
1704
|
+
checkHealth: agent_cli_health_checks_1.checkAgentCliHealthByCommand,
|
|
1705
|
+
install: (option, systemPath) => (0, managed_agent_install_1.installManagedAgent)(option, systemPath, { runProcess: hubRunProcess, commandVersion: hubCommandVersion }),
|
|
1706
|
+
invalidateCache: hosts_1.invalidateEmployeeDetectionCache,
|
|
1707
|
+
};
|
|
1708
|
+
async function selfHealBrokenManagedAgents(deps = defaultSelfHealDeps) {
|
|
1709
|
+
for (const hubId of ['claude', 'codex']) {
|
|
1710
|
+
try {
|
|
1711
|
+
const health = await deps.checkHealth(hubId);
|
|
1712
|
+
if (!health || health.status !== 'error')
|
|
1713
|
+
continue;
|
|
1714
|
+
const details = health.details;
|
|
1715
|
+
if (!details?.managedPath || details.managedVersion)
|
|
1716
|
+
continue;
|
|
1717
|
+
const option = hubAgentOption(hubId);
|
|
1718
|
+
if (!option || !option.installPackage)
|
|
1719
|
+
continue;
|
|
1720
|
+
console.warn(`[ai-hub] self-heal: ${option.label}'s FRAIM-managed install is broken (${health.message}) — attempting automatic reinstall`);
|
|
1721
|
+
const systemPath = (0, managed_agent_paths_1.stripManagedAgentBinDirsFromPath)(process.env.PATH);
|
|
1722
|
+
await deps.install({ label: option.label, installPackage: option.installPackage, launchCommand: option.launchCommand }, systemPath);
|
|
1723
|
+
deps.invalidateCache();
|
|
1724
|
+
const verified = await deps.checkHealth(hubId);
|
|
1725
|
+
console.warn(`[ai-hub] self-heal: ${option.label} reinstall ${verified?.status === 'error' ? 'did NOT fix it — manual reinstall needed' : 'succeeded'}`);
|
|
1726
|
+
}
|
|
1727
|
+
catch (error) {
|
|
1728
|
+
console.warn(`[ai-hub] self-heal check failed for ${hubId}:`, error instanceof Error ? error.message : String(error));
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1702
1732
|
/**
|
|
1703
1733
|
* Issue #747: after the Hub installs an agent CLI, run the `add-ide` command for that agent so the
|
|
1704
1734
|
* FRAIM MCP (plus slash commands / rules) is wired into its config and its first run works.
|
|
@@ -2699,6 +2729,17 @@ class AiHubServer {
|
|
|
2699
2729
|
void (0, hosts_1.detectEmployeesAsync)({ force: true }).catch((error) => {
|
|
2700
2730
|
console.warn('[ai-hub] agent availability priming failed:', error?.message || error);
|
|
2701
2731
|
});
|
|
2732
|
+
// Issue #1412: fire-and-forget self-heal of any broken FRAIM-managed CLI
|
|
2733
|
+
// install (e.g. an interrupted Claude Code/Codex update left a
|
|
2734
|
+
// non-executable launcher). Never blocks or fails startup. Skipped in
|
|
2735
|
+
// NODE_ENV=test so the test suite's many short-lived Hub instances never
|
|
2736
|
+
// trigger a real `npm install -g`, matching this file's existing
|
|
2737
|
+
// NODE_ENV-gated test-safety pattern (see hubCommandVersion/hubRunProcess).
|
|
2738
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
2739
|
+
void selfHealBrokenManagedAgents().catch((error) => {
|
|
2740
|
+
console.warn('[ai-hub] self-heal pass failed:', error?.message || error);
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2702
2743
|
// Issue #578: rehydrate active scheduled deployments from disk. Test and preview
|
|
2703
2744
|
// servers that inject a fake host but not a deployment store should not run the
|
|
2704
2745
|
// user's real scheduled deployments from the default store.
|
|
@@ -6083,6 +6124,13 @@ class AiHubServer {
|
|
|
6083
6124
|
if (reviewApprovalSystemEventText)
|
|
6084
6125
|
current.events.push((0, hosts_1.createHubEvent)('system', reviewApprovalSystemEventText));
|
|
6085
6126
|
current.events.push((0, hosts_1.createHubEvent)('system', 'No resumable session - starting a fresh agent turn from conversation context.'));
|
|
6127
|
+
// Issue #1373 (Defect 2): nextJobRecommendations can only be stale
|
|
6128
|
+
// here — it is only ever set at retrospective (issue #848), and a
|
|
6129
|
+
// job that just received coaching has not reached retrospective
|
|
6130
|
+
// again yet. Clear it at send-time so a coached Done job stops
|
|
6131
|
+
// offering next-job recommendations immediately, not only once the
|
|
6132
|
+
// resumed session's next seekMentoring signal arrives.
|
|
6133
|
+
current.nextJobRecommendations = null;
|
|
6086
6134
|
});
|
|
6087
6135
|
const startedFresh = this.runRegistry.get(run.id);
|
|
6088
6136
|
if (startedFresh)
|
|
@@ -6139,6 +6187,9 @@ class AiHubServer {
|
|
|
6139
6187
|
current.messages.push((0, hosts_1.createHubMessage)('manager', prepared.display || message, deliveryStatus));
|
|
6140
6188
|
if (reviewApprovalSystemEventText)
|
|
6141
6189
|
current.events.push((0, hosts_1.createHubEvent)('system', reviewApprovalSystemEventText));
|
|
6190
|
+
// Issue #1373 (Defect 2): see the no-session branch above for why
|
|
6191
|
+
// this can only be stale at coaching-send time.
|
|
6192
|
+
current.nextJobRecommendations = null;
|
|
6142
6193
|
});
|
|
6143
6194
|
const started = this.runRegistry.get(run.id);
|
|
6144
6195
|
if (started)
|
|
@@ -6281,18 +6332,28 @@ class AiHubServer {
|
|
|
6281
6332
|
messages: persistedConversation ? persistedMessagesForRun(persistedConversation) : [],
|
|
6282
6333
|
events: resumeEvents,
|
|
6283
6334
|
eventLogRefs: persistedConversation?.eventLogRefs || [],
|
|
6284
|
-
//
|
|
6285
|
-
//
|
|
6286
|
-
//
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6335
|
+
// Issue #1373: a conversation is permanently scoped to one job for its
|
|
6336
|
+
// lifetime, including after that job's conversation is marked
|
|
6337
|
+
// 'completed' — a coaching turn sent to a Done job resumes the SAME
|
|
6338
|
+
// job's phase history, it does not start unrelated new work. Always
|
|
6339
|
+
// carry the persisted run projection forward regardless of the
|
|
6340
|
+
// conversation's prior status. If the resumed session genuinely has
|
|
6341
|
+
// nothing left to do, it simply reaches the same completed state
|
|
6342
|
+
// again on its own the next time seekMentoring reports its phase.
|
|
6343
|
+
currentPhase: persistedRun?.currentPhase || null,
|
|
6344
|
+
phaseHistory: persistedRun?.phaseHistory || [],
|
|
6345
|
+
phaseVisits: persistedRun?.phaseVisits || [],
|
|
6290
6346
|
totals: persistedRun?.totals || emptyTotals(),
|
|
6291
6347
|
lastStatusChangeAt: now,
|
|
6292
|
-
runDiscriminant:
|
|
6348
|
+
runDiscriminant: persistedRun?.runDiscriminant || undefined,
|
|
6293
6349
|
// Issue #1357: consult custom persona owner before falling back to catalog.
|
|
6294
6350
|
personaKey: getCustomPersonaForJob(projectPath, jobId) ?? getHubPersonaForJob(jobId),
|
|
6295
6351
|
continuityDecision: conversationId ? 'same_continuity' : 'new_conversation',
|
|
6352
|
+
// Issue #1373 (Defect 2): nextJobRecommendations are only ever set at
|
|
6353
|
+
// retrospective/completion (issue #848); a resumed run has not reached
|
|
6354
|
+
// retrospective, so this is always null here. Explicit for readability
|
|
6355
|
+
// and to make the invariant visible next to the Defect 1 fix above.
|
|
6356
|
+
nextJobRecommendations: null,
|
|
6296
6357
|
};
|
|
6297
6358
|
host_session_state_1.hostSessionState.applySession(run, { configuredAgentId: configuredAgent.id, baseHostId: hostId }, sessionId, { sourceRunId: run.id, status: resolvedHostSession?.status || 'suspect' });
|
|
6298
6359
|
// Continue-turn message (FRAIM invocation for the job + instructions) plus
|
|
@@ -97,6 +97,27 @@ async function runAgentCliHealthCheck(cli) {
|
|
|
97
97
|
: managedPath === ambientPath
|
|
98
98
|
? ambientVersion
|
|
99
99
|
: probeVersion(managedPath);
|
|
100
|
+
// Issue #1412: a resolved file that fails to execute at all (e.g. an
|
|
101
|
+
// interrupted update left a non-executable fallback launcher) is a harder
|
|
102
|
+
// failure than drift — `getSystemCommandPath()` only proves the file
|
|
103
|
+
// exists, not that it can run. Without this check, a single broken install
|
|
104
|
+
// with nothing else on PATH to disagree with (ambientPath === managedPath,
|
|
105
|
+
// both versions null) falls straight through the mismatch check below and
|
|
106
|
+
// reads as "consistent". Check this before computing npm-global/drift
|
|
107
|
+
// details that don't matter once the CLI can't run at all.
|
|
108
|
+
const brokenPath = ambientPath && !ambientVersion
|
|
109
|
+
? ambientPath
|
|
110
|
+
: managedPath && !managedVersion
|
|
111
|
+
? managedPath
|
|
112
|
+
: null;
|
|
113
|
+
if (brokenPath) {
|
|
114
|
+
return {
|
|
115
|
+
status: 'error',
|
|
116
|
+
message: `${cli.label} is installed at ${brokenPath} but failed to run (\`${cli.command} --version\` produced no output). An interrupted or partial update may have left a non-functional launcher.`,
|
|
117
|
+
suggestion: `Reinstall ${cli.label}, then run "${cli.command} --version" again to confirm it's fixed.`,
|
|
118
|
+
details: { ambientPath, ambientVersion, managedPath, managedVersion },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
100
121
|
const npmGlobalBinDirs = (0, managed_agent_paths_1.resolveNpmGlobalBinDirs)();
|
|
101
122
|
const npmGlobalPath = npmGlobalBinDirs.length > 0
|
|
102
123
|
? (0, command_resolution_1.getSystemCommandPath)(cli.command, npmGlobalBinDirs.join(path_1.default.delimiter))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.288",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"author": "Sid Mathur <sid.mathur@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/mathursrus/FRAIM#readme",
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"electron-updater": "^6.8.9",
|
|
211
211
|
"express": "^5.2.1",
|
|
212
212
|
"extract-zip": "^2.0.1",
|
|
213
|
-
"fraim": "2.0.
|
|
213
|
+
"fraim": "2.0.288",
|
|
214
214
|
"mongodb": "^7.0.0",
|
|
215
215
|
"node-cron": "4.2.1",
|
|
216
216
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/script.js
CHANGED
|
@@ -15,11 +15,12 @@ const TREE_WIDTH_MIN = 176;
|
|
|
15
15
|
const TREE_WIDTH_MAX = 380;
|
|
16
16
|
const TREE_WIDTH_DEFAULT = 216;
|
|
17
17
|
const PAGE_SCOPED_JOBS = new Set(['organization-onboarding', 'manager-agreements', 'project-onboarding', 'organizational-learning-synthesis', 'create-hub-configured-agent']);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
18
|
+
// Issue #1408: there is no fixed per-job-id area home. Every job's placement, including
|
|
19
|
+
// organization-onboarding/organizational-learning-synthesis/manager-agreements/
|
|
20
|
+
// create-hub-configured-agent, is decided per-invocation by conv.invokedArea/convScope,
|
|
21
|
+
// exactly like persona jobs (e.g. Ashley's, #702) always were. A conversation renders
|
|
22
|
+
// wherever it was actually started; forcing these job ids to a fixed home regardless of
|
|
23
|
+
// invokedArea left runs started from the "wrong" tab unreachable from either rail.
|
|
23
24
|
// Issue #702: personas whose home is the MANAGER tab because their work is inherently
|
|
24
25
|
// cross-project and manager-facing (not project work). Per the spec (R1b), Ashley — the
|
|
25
26
|
// AI Executive Assistant — is Manager-scoped. Every other hired persona works inside
|
|
@@ -1426,10 +1427,11 @@ async function hydrateConversationsFromServer() {
|
|
|
1426
1427
|
const enc = encodeURIComponent(projectPath);
|
|
1427
1428
|
const pickActive = (list, serverActiveId) => {
|
|
1428
1429
|
const activeCandidates = [state.activeId, serverActiveId].filter(Boolean);
|
|
1429
|
-
//
|
|
1430
|
-
//
|
|
1431
|
-
|
|
1432
|
-
const
|
|
1430
|
+
// Issue #1408: a manager/company-scoped conversation must not become the Projects
|
|
1431
|
+
// workspace active conversation — it's shown in its own Company/Manager panel via
|
|
1432
|
+
// tfActiveOrgConv/tfActiveMgrConv instead. Decided by scope, not by job id.
|
|
1433
|
+
const projectConv = (id) => list.find((c) => c.id === id && convScope(c) === 'project');
|
|
1434
|
+
const firstProjectConv = list.find((c) => convScope(c) === 'project');
|
|
1433
1435
|
const bestActive = activeCandidates.map(projectConv).find(Boolean) || firstProjectConv || null;
|
|
1434
1436
|
state.activeId = bestActive ? bestActive.id : null;
|
|
1435
1437
|
};
|
|
@@ -2341,8 +2343,8 @@ function renderRail() {
|
|
|
2341
2343
|
const list = allProjectConversations.filter((conv) =>
|
|
2342
2344
|
(!state.selectedPersonaKey || conv.personaKey === state.selectedPersonaKey) &&
|
|
2343
2345
|
!isManagedDelegationChild(conv) &&
|
|
2344
|
-
|
|
2345
|
-
//
|
|
2346
|
+
// #702/#708/#1408: manager/company-scoped runs surface on those tabs, not in a
|
|
2347
|
+
// project, decided by scope alone — no job id is ever excluded from Projects.
|
|
2346
2348
|
convScope(conv) === 'project'
|
|
2347
2349
|
);
|
|
2348
2350
|
|
|
@@ -9045,21 +9047,24 @@ function buildConfiguredAgentForm(agent, opts) {
|
|
|
9045
9047
|
if (agent?.command?.command) {
|
|
9046
9048
|
command.value = agent.command.command;
|
|
9047
9049
|
}
|
|
9048
|
-
//
|
|
9049
|
-
//
|
|
9050
|
-
//
|
|
9050
|
+
// Setup-script profiles display a launch preview in the same Command field
|
|
9051
|
+
// but still round-trip as setupScript unless the user edits the preview.
|
|
9052
|
+
// Sourcing a script and launching the base
|
|
9051
9053
|
// agent with extra flags are structurally different operations — the
|
|
9052
9054
|
// launch decorator only ever keeps the trailing flags of `command` and
|
|
9053
9055
|
// discards the leading token, so a script path placed there is silently
|
|
9054
9056
|
// dropped as a bogus flag rather than ever sourced (PR #1209 review).
|
|
9055
|
-
// Leaving Command blank and round-tripping the existing `setupScript`
|
|
9056
|
-
// unchanged on save (below) keeps the agent working; typing a real single
|
|
9057
|
-
// command here is a deliberate migration away from the script.
|
|
9058
9057
|
const legacySetupScript = !agent?.command && agent?.setupScript ? agent.setupScript : null;
|
|
9058
|
+
const legacyLaunchPreview = legacySetupScript
|
|
9059
|
+
? configuredAgentSetupLaunchPreview(legacySetupScript, agent.baseHostId)
|
|
9060
|
+
: '';
|
|
9061
|
+
if (legacyLaunchPreview) {
|
|
9062
|
+
command.value = legacyLaunchPreview;
|
|
9063
|
+
}
|
|
9059
9064
|
const commandHint = document.createElement('p');
|
|
9060
9065
|
commandHint.className = 'configured-agent-command-hint';
|
|
9061
9066
|
commandHint.textContent = legacySetupScript
|
|
9062
|
-
?
|
|
9067
|
+
? 'This preview shows the setup script Hub runs before the agent. Internal Hub flags are hidden; edit it only to replace this setup-script profile with a plain command.'
|
|
9063
9068
|
: 'Reference where a secret resolves at runtime (a file, a keychain, an already-set env var) — never paste a literal secret value here.';
|
|
9064
9069
|
|
|
9065
9070
|
form.appendChild(configuredAgentField('ID', id));
|
|
@@ -9102,13 +9107,14 @@ function buildConfiguredAgentForm(agent, opts) {
|
|
|
9102
9107
|
);
|
|
9103
9108
|
if (!proceed) return;
|
|
9104
9109
|
}
|
|
9110
|
+
const shouldPreserveLegacySetup = !!legacySetupScript && (!commandValue || commandValue === legacyLaunchPreview);
|
|
9105
9111
|
const body = {
|
|
9106
9112
|
id: id.value.trim(),
|
|
9107
9113
|
label: label.value.trim(),
|
|
9108
9114
|
baseHostId: baseHost.value,
|
|
9109
9115
|
description: description.value.trim(),
|
|
9110
9116
|
enabled: enabled.checked,
|
|
9111
|
-
...(commandValue ? {
|
|
9117
|
+
...(!shouldPreserveLegacySetup && commandValue ? {
|
|
9112
9118
|
command: {
|
|
9113
9119
|
runner: configuredAgentPlatformRunner(),
|
|
9114
9120
|
command: commandValue,
|
|
@@ -9142,6 +9148,19 @@ function configuredAgentPlatformRunner() {
|
|
|
9142
9148
|
return /Windows/i.test(platform) ? 'powershell' : 'bash';
|
|
9143
9149
|
}
|
|
9144
9150
|
|
|
9151
|
+
function configuredAgentSetupLaunchPreview(setupScript, baseHostId) {
|
|
9152
|
+
if (!setupScript?.command || !baseHostId) return '';
|
|
9153
|
+
const args = Array.isArray(setupScript.args) ? setupScript.args : [];
|
|
9154
|
+
const quote = (value) => {
|
|
9155
|
+
const text = String(value || '');
|
|
9156
|
+
return `"${text.replace(/"/g, '\\"')}"`;
|
|
9157
|
+
};
|
|
9158
|
+
const setupInvocation = [quote(setupScript.command), ...args.map(quote)].join(' ');
|
|
9159
|
+
return setupScript.runner === 'powershell'
|
|
9160
|
+
? `. ${setupInvocation}; ${baseHostId}`
|
|
9161
|
+
: `source ${setupInvocation}; ${baseHostId}`;
|
|
9162
|
+
}
|
|
9163
|
+
|
|
9145
9164
|
// Mirrors the server-side check in configured-agent-command.ts's
|
|
9146
9165
|
// commandContainsLiteralSecret so the user sees the warning before the
|
|
9147
9166
|
// request round-trips, not only after.
|
|
@@ -10976,10 +10995,10 @@ function tfShowArea(area) {
|
|
|
10976
10995
|
tf.area = area;
|
|
10977
10996
|
|
|
10978
10997
|
if (area === 'projects') {
|
|
10979
|
-
// Restore the saved Projects conv; discard it if it turned out to be
|
|
10998
|
+
// Restore the saved Projects conv; discard it if it turned out to be manager/company-scoped.
|
|
10980
10999
|
const savedId = state.areaActiveId.projects || null;
|
|
10981
11000
|
const savedConv = savedId && Object.values(state.conversations || {}).flat().find((c) => c.id === savedId);
|
|
10982
|
-
state.activeId = (savedConv &&
|
|
11001
|
+
state.activeId = (savedConv && convScope(savedConv) === 'project') ? savedId : null;
|
|
10983
11002
|
// Return the shared .page to the Projects workspace and reset Company/Manager hosts.
|
|
10984
11003
|
tfEnsurePageInArea('projects');
|
|
10985
11004
|
for (const el of document.querySelectorAll('.area-conv-host')) el.hidden = true;
|
|
@@ -13007,11 +13026,16 @@ function tfActiveOrgConv() {
|
|
|
13007
13026
|
}
|
|
13008
13027
|
|
|
13009
13028
|
// #594 R2: return the manager-scoped conversation to show in Manager area.
|
|
13010
|
-
// #702: manager-tab runs are those invoked from the manager area (any persona job
|
|
13011
|
-
//
|
|
13012
|
-
// manager-
|
|
13029
|
+
// #702/#1408: manager-tab runs are those invoked from the manager area (any persona job,
|
|
13030
|
+
// including manager-agreements), decided by scope alone. A pre-scope-tracking legacy
|
|
13031
|
+
// manager-agreements record with no scope field falls back to convScope()'s 'project'
|
|
13032
|
+
// default and won't be picked here; it's still reachable, just not as this tab's
|
|
13033
|
+
// single "most active" spotlight. An active manager-invoked run surfaces in the
|
|
13034
|
+
// Manager conversation host (Coach panel included).
|
|
13013
13035
|
function tfActiveMgrConv() {
|
|
13014
|
-
|
|
13036
|
+
// Issue #1408: no job-id exception — a manager-agreements run only counts here if it
|
|
13037
|
+
// was genuinely invoked from the Manager tab (convScope(c) === 'manager').
|
|
13038
|
+
const convs = Object.values(state.conversations || {}).flat().filter((c) => c && convScope(c) === 'manager');
|
|
13015
13039
|
return (
|
|
13016
13040
|
tfMostRecentMatch(convs, (c) => c.status === 'running') ||
|
|
13017
13041
|
tfMostRecentMatch(convs, (c) => c.status === 'waiting') ||
|