fraim-hub 2.0.287 → 2.0.289
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 +104 -24
- package/dist/src/ai-hub/server.js +64 -7
- package/dist/src/cli/doctor/checks/agent-cli-health-checks.js +21 -0
- package/dist/src/config/ai-manager-hiring.js +6 -2
- package/dist/src/config/persona-capability-bundles.js +1 -1
- package/dist/src/config/persona-hiring.js +26 -26
- package/package.json +2 -2
- package/public/ai-hub/script.js +95 -35
- package/public/first-run/script.js +10 -1
- package/public/portfolio/aida.html +13 -13
- package/public/portfolio/ashley.html +2 -2
- package/public/portfolio/auditya.html +2 -2
- package/public/portfolio/banke.html +2 -2
- package/public/portfolio/beza.html +2 -2
- package/public/portfolio/careena.html +2 -2
- package/public/portfolio/casey.html +2 -2
- package/public/portfolio/celia.html +2 -2
- package/public/portfolio/deidre.html +2 -2
- package/public/portfolio/hari.html +2 -2
- package/public/portfolio/index.html +5 -5
- package/public/portfolio/maestro.html +2 -2
- package/public/portfolio/mandy.html +2 -2
- package/public/portfolio/mona.html +2 -2
- package/public/portfolio/pam.html +2 -2
- package/public/portfolio/procella.html +2 -2
- package/public/portfolio/qasm.html +2 -2
- package/public/portfolio/ricardo.html +2 -2
- package/public/portfolio/sade.html +2 -2
- package/public/portfolio/sam.html +2 -2
- package/public/portfolio/sechar.html +2 -2
- package/public/portfolio/sreya.html +2 -2
- package/public/portfolio/swen.html +4 -4
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,24 @@ class AiHubServer {
|
|
|
6281
6332
|
messages: persistedConversation ? persistedMessagesForRun(persistedConversation) : [],
|
|
6282
6333
|
events: resumeEvents,
|
|
6283
6334
|
eventLogRefs: persistedConversation?.eventLogRefs || [],
|
|
6284
|
-
//
|
|
6285
|
-
//
|
|
6286
|
-
//
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6335
|
+
// A manager can issue new instructions after a job was marked done.
|
|
6336
|
+
// That correction stays in the same conversation/job identity and keeps
|
|
6337
|
+
// its phase history so the tracker can show the job moving back to an
|
|
6338
|
+
// earlier phase instead of pretending this is unrelated fresh work.
|
|
6339
|
+
currentPhase: persistedRun?.currentPhase || null,
|
|
6340
|
+
phaseHistory: persistedRun?.phaseHistory || [],
|
|
6341
|
+
phaseVisits: persistedRun?.phaseVisits || [],
|
|
6290
6342
|
totals: persistedRun?.totals || emptyTotals(),
|
|
6291
6343
|
lastStatusChangeAt: now,
|
|
6292
|
-
runDiscriminant:
|
|
6344
|
+
runDiscriminant: persistedRun?.runDiscriminant || undefined,
|
|
6293
6345
|
// Issue #1357: consult custom persona owner before falling back to catalog.
|
|
6294
6346
|
personaKey: getCustomPersonaForJob(projectPath, jobId) ?? getHubPersonaForJob(jobId),
|
|
6295
6347
|
continuityDecision: conversationId ? 'same_continuity' : 'new_conversation',
|
|
6348
|
+
// Issue #1373 (Defect 2): nextJobRecommendations are only ever set at
|
|
6349
|
+
// retrospective/completion (issue #848); a resumed run has not reached
|
|
6350
|
+
// retrospective, so this is always null here. Explicit for readability
|
|
6351
|
+
// and to make the invariant visible next to the Defect 1 fix above.
|
|
6352
|
+
nextJobRecommendations: null,
|
|
6296
6353
|
};
|
|
6297
6354
|
host_session_state_1.hostSessionState.applySession(run, { configuredAgentId: configuredAgent.id, baseHostId: hostId }, sessionId, { sourceRunId: run.id, status: resolvedHostSession?.status || 'suspect' });
|
|
6298
6355
|
// 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))
|
|
@@ -64,13 +64,17 @@ function buildSearchQuery(roleKey) {
|
|
|
64
64
|
const profile = getHumanManagerProfile(roleKey);
|
|
65
65
|
return `${profile.keywords.join(' OR ')} AND ${AI_MANAGER_QUERY_TERMS}`;
|
|
66
66
|
}
|
|
67
|
+
function indefiniteArticle(phrase) {
|
|
68
|
+
return /^[aeiou]/i.test(phrase.trim()) ? 'an' : 'a';
|
|
69
|
+
}
|
|
67
70
|
function buildJobDescription(roleKey) {
|
|
68
71
|
const profile = getHumanManagerProfile(roleKey);
|
|
69
72
|
const role = persona_hiring_1.PERSONA_HIRE_CATALOG[roleKey]?.role ?? 'AI Employee';
|
|
73
|
+
const article = indefiniteArticle(role);
|
|
70
74
|
return [
|
|
71
|
-
`${profile.humanTitle} — manager for
|
|
75
|
+
`${profile.humanTitle} — manager for ${article} ${role}`,
|
|
72
76
|
'',
|
|
73
|
-
`You will manage
|
|
77
|
+
`You will manage ${article} ${role} (an autonomous AI agent) and the humans around it, owning the outcomes it ships.`,
|
|
74
78
|
'',
|
|
75
79
|
"What you'll do:",
|
|
76
80
|
`- Write crisp specifications and acceptance criteria the ${role} can execute against.`,
|
|
@@ -278,7 +278,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
278
278
|
],
|
|
279
279
|
protectedAliases: ['ai-engineering', 'agent-engineering', 'ai-agents'],
|
|
280
280
|
defaultHireMode: 'job',
|
|
281
|
-
lockCopy: 'Hire
|
|
281
|
+
lockCopy: 'Hire AIDa to unlock AI agent design, MCP enablement, eval authoring, and agent evaluation work for this request.'
|
|
282
282
|
}
|
|
283
283
|
};
|
|
284
284
|
const PROTECTED_JOB_TO_PERSONA = new Map();
|
|
@@ -20,8 +20,8 @@ exports.getPersonaHireAmountCents = getPersonaHireAmountCents;
|
|
|
20
20
|
*/
|
|
21
21
|
exports.PERSONA_HIRE_CATALOG = {
|
|
22
22
|
aida: {
|
|
23
|
-
displayName: '
|
|
24
|
-
role: 'AI
|
|
23
|
+
displayName: 'AIDa',
|
|
24
|
+
role: 'AI Developer',
|
|
25
25
|
emoji: '\u{1F9E0}',
|
|
26
26
|
gradient: 'linear-gradient(135deg, #4f46e5 0%, #06b6d4 50%, #10b981 100%)',
|
|
27
27
|
blurb: 'Designs production AI agents, connects them to tools and data, and proves their behavior with evals before deployment.',
|
|
@@ -30,7 +30,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
30
30
|
},
|
|
31
31
|
swen: {
|
|
32
32
|
displayName: 'SWEn',
|
|
33
|
-
role: '
|
|
33
|
+
role: 'Software Engineer',
|
|
34
34
|
emoji: '💻',
|
|
35
35
|
gradient: 'linear-gradient(135deg, #2563eb 0%, #06b6d4 100%)',
|
|
36
36
|
blurb: 'Implements features, refactors code, and drives PR iteration to merge, reviewing design and code as a senior would.',
|
|
@@ -39,7 +39,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
39
39
|
},
|
|
40
40
|
qasm: {
|
|
41
41
|
displayName: 'QAsm',
|
|
42
|
-
role: '
|
|
42
|
+
role: 'QA Engineer',
|
|
43
43
|
emoji: '🛡️',
|
|
44
44
|
gradient: 'linear-gradient(135deg, #10b981 0%, #14b8a6 100%)',
|
|
45
45
|
blurb: 'Runs tests, assesses code and test quality, polishes UI, and drives bug bashes with evidence.',
|
|
@@ -48,7 +48,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
48
48
|
},
|
|
49
49
|
sekhar: {
|
|
50
50
|
displayName: 'SEChar',
|
|
51
|
-
role: '
|
|
51
|
+
role: 'Security Engineer',
|
|
52
52
|
emoji: '🔒',
|
|
53
53
|
gradient: 'linear-gradient(135deg, #ef4444 0%, #f43f5e 100%)',
|
|
54
54
|
blurb: 'Sets up AI-native security baselines, runs the findings command center, reviews changes for risk, and drives remediation to closure.',
|
|
@@ -57,7 +57,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
57
57
|
},
|
|
58
58
|
sreya: {
|
|
59
59
|
displayName: 'SREya',
|
|
60
|
-
role: '
|
|
60
|
+
role: 'Site Reliability Engineer',
|
|
61
61
|
emoji: '☁️',
|
|
62
62
|
gradient: 'linear-gradient(135deg, #2563eb 0%, #10b981 100%)',
|
|
63
63
|
blurb: 'Manages deployments, monitors uptime, optimizes cloud cost, and keeps infrastructure resilient and observable.',
|
|
@@ -66,7 +66,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
66
66
|
},
|
|
67
67
|
sade: {
|
|
68
68
|
displayName: 'SADE',
|
|
69
|
-
role: '
|
|
69
|
+
role: 'Salesforce Developer',
|
|
70
70
|
emoji: '☁️',
|
|
71
71
|
gradient: 'linear-gradient(135deg, #0284c7 0%, #0369a1 100%)',
|
|
72
72
|
blurb: 'Deploys Salesforce configuration from ServiceNow tickets, builds reports and dashboards, creates Flows from business requirements, audits org health, and manages users and data at scale.',
|
|
@@ -75,7 +75,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
75
75
|
},
|
|
76
76
|
pam: {
|
|
77
77
|
displayName: 'PaM',
|
|
78
|
-
role: '
|
|
78
|
+
role: 'Product Manager',
|
|
79
79
|
emoji: '📋',
|
|
80
80
|
gradient: 'linear-gradient(135deg, #8b5cf6 0%, #d946ef 100%)',
|
|
81
81
|
blurb: 'Owns specs, PRDs, technical design, issue prep, and the path from idea to shippable artifact.',
|
|
@@ -84,7 +84,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
84
84
|
},
|
|
85
85
|
huxley: {
|
|
86
86
|
displayName: 'hUXley',
|
|
87
|
-
role: '
|
|
87
|
+
role: 'UX / Brand Designer',
|
|
88
88
|
emoji: '🎨',
|
|
89
89
|
gradient: 'linear-gradient(135deg, #ec4899 0%, #f472b6 100%)',
|
|
90
90
|
blurb: 'Builds design systems, prototypes polished user-facing surfaces, and carries brand decisions into shipped product experiences.',
|
|
@@ -93,7 +93,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
93
93
|
},
|
|
94
94
|
gautam: {
|
|
95
95
|
displayName: 'GauTaM',
|
|
96
|
-
role: '
|
|
96
|
+
role: 'GTM & Marketing Manager',
|
|
97
97
|
emoji: '📣',
|
|
98
98
|
gradient: 'linear-gradient(135deg, #f97316 0%, #f59e0b 100%)',
|
|
99
99
|
blurb: 'Defines marketing strategy, ships content, runs launches, and owns the brand voice in market.',
|
|
@@ -102,7 +102,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
102
102
|
},
|
|
103
103
|
sam: {
|
|
104
104
|
displayName: 'SAM',
|
|
105
|
-
role: '
|
|
105
|
+
role: 'Sales Account Manager',
|
|
106
106
|
emoji: '📈',
|
|
107
107
|
gradient: 'linear-gradient(135deg, #059669 0%, #0d9488 100%)',
|
|
108
108
|
blurb: 'Surfaces stalled deals, computes pipeline health, and drafts account-specific re-engagement proposals for your top at-risk opportunities.',
|
|
@@ -111,7 +111,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
111
111
|
},
|
|
112
112
|
casey: {
|
|
113
113
|
displayName: 'CaSey',
|
|
114
|
-
role: '
|
|
114
|
+
role: 'Customer Success + Support',
|
|
115
115
|
emoji: '💬',
|
|
116
116
|
gradient: 'linear-gradient(135deg, #db2777 0%, #9333ea 100%)',
|
|
117
117
|
blurb: 'Scores account churn risk and expansion potential, generates prioritized action plans for CSMs, triages support cases, and routes L1 cases through automated resolution.',
|
|
@@ -120,7 +120,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
120
120
|
},
|
|
121
121
|
mona: {
|
|
122
122
|
displayName: 'MONa',
|
|
123
|
-
role: '
|
|
123
|
+
role: 'Finance Manager',
|
|
124
124
|
emoji: '💰',
|
|
125
125
|
gradient: 'linear-gradient(135deg, #10b981 0%, #f59e0b 100%)',
|
|
126
126
|
blurb: 'Models revenue, tracks unit economics, builds financial forecasts, and owns the metrics that drive growth decisions.',
|
|
@@ -129,7 +129,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
129
129
|
},
|
|
130
130
|
hari: {
|
|
131
131
|
displayName: 'HaRi',
|
|
132
|
-
role: '
|
|
132
|
+
role: 'HR Manager',
|
|
133
133
|
emoji: '👥',
|
|
134
134
|
gradient: 'linear-gradient(135deg, #0d9488 0%, #059669 100%)',
|
|
135
135
|
blurb: 'Manages onboarding, performance reviews, benefits analysis, payroll coordination, and HR business-partner advisory.',
|
|
@@ -138,7 +138,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
138
138
|
},
|
|
139
139
|
ricardo: {
|
|
140
140
|
displayName: 'RECardo',
|
|
141
|
-
role: '
|
|
141
|
+
role: 'Recruiter',
|
|
142
142
|
emoji: '🤝',
|
|
143
143
|
gradient: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
|
|
144
144
|
blurb: 'Sources candidates, writes job descriptions, screens pipelines, and manages the hiring loop end-to-end.',
|
|
@@ -147,7 +147,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
147
147
|
},
|
|
148
148
|
cela: {
|
|
149
149
|
displayName: 'CELiA',
|
|
150
|
-
role: '
|
|
150
|
+
role: 'Legal Counsel',
|
|
151
151
|
emoji: '⚖️',
|
|
152
152
|
gradient: 'linear-gradient(135deg, #475569 0%, #6366f1 100%)',
|
|
153
153
|
blurb: 'Drafts and reviews contracts, NDAs, patents, trademarks, and the SaaS legal stack.',
|
|
@@ -156,7 +156,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
156
156
|
},
|
|
157
157
|
procella: {
|
|
158
158
|
displayName: 'PROCella',
|
|
159
|
-
role: '
|
|
159
|
+
role: 'Procurement Manager',
|
|
160
160
|
emoji: '📦',
|
|
161
161
|
gradient: 'linear-gradient(135deg, #0f766e 0%, #7c3aed 100%)',
|
|
162
162
|
blurb: 'Frames procurement strategy, sources suppliers, runs RFx packages, evaluates responses, and keeps purchases acceptance-gated.',
|
|
@@ -165,7 +165,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
165
165
|
},
|
|
166
166
|
banke: {
|
|
167
167
|
displayName: 'BANKe',
|
|
168
|
-
role: '
|
|
168
|
+
role: 'Banking KYC Employee',
|
|
169
169
|
emoji: '\u{1F3E6}',
|
|
170
170
|
gradient: 'linear-gradient(135deg, #0f766e 0%, #2563eb 100%)',
|
|
171
171
|
blurb: 'Runs banking KYC cases with consent-aware evidence capture, decision receipts, and stable audit handoff artifacts.',
|
|
@@ -174,7 +174,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
174
174
|
},
|
|
175
175
|
auditya: {
|
|
176
176
|
displayName: 'AUDITya',
|
|
177
|
-
role: '
|
|
177
|
+
role: 'Banking Auditor',
|
|
178
178
|
emoji: '\u{1F50E}',
|
|
179
179
|
gradient: 'linear-gradient(135deg, #7c3aed 0%, #0f172a 100%)',
|
|
180
180
|
blurb: 'Audits AI banking work by tracing decisions back to evidence, receipts, exceptions, and reusable audit reports.',
|
|
@@ -183,7 +183,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
183
183
|
},
|
|
184
184
|
deidre: {
|
|
185
185
|
displayName: 'DEIdre',
|
|
186
|
-
role: '
|
|
186
|
+
role: 'Inclusion Leader',
|
|
187
187
|
emoji: '🌍',
|
|
188
188
|
gradient: 'linear-gradient(135deg, #9333ea 0%, #d946ef 100%)',
|
|
189
189
|
blurb: 'Audits equity gaps, designs bias-aware AI governance, builds ERG toolkits, and creates inclusion-fluency programs.',
|
|
@@ -192,7 +192,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
192
192
|
},
|
|
193
193
|
careena: {
|
|
194
194
|
displayName: 'CAREEna',
|
|
195
|
-
role: '
|
|
195
|
+
role: 'Career Coach',
|
|
196
196
|
emoji: '🎓',
|
|
197
197
|
gradient: 'linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%)',
|
|
198
198
|
blurb: 'Runs the candidate-side search loop: role sourcing, application execution, networking, interview prep, and close-stage offer strategy.',
|
|
@@ -201,7 +201,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
201
201
|
},
|
|
202
202
|
ashley: {
|
|
203
203
|
displayName: 'AshLey',
|
|
204
|
-
role: '
|
|
204
|
+
role: 'Executive Assistant',
|
|
205
205
|
emoji: '📅',
|
|
206
206
|
gradient: 'linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)',
|
|
207
207
|
blurb: 'Owns executive coordination, weekly operating reviews, and portfolio reporting across the workforce.',
|
|
@@ -210,7 +210,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
210
210
|
},
|
|
211
211
|
mandy: {
|
|
212
212
|
displayName: 'MANdy',
|
|
213
|
-
role: '
|
|
213
|
+
role: 'Manager',
|
|
214
214
|
emoji: '🎯',
|
|
215
215
|
gradient: 'linear-gradient(135deg, #7c3aed 0%, #4338ca 100%)',
|
|
216
216
|
blurb: 'Plans the job sequence, runs sub-agents in parallel, coaches them through verification loops, and hands back a synthesized DRAFT for your approval.',
|
|
@@ -219,7 +219,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
219
219
|
},
|
|
220
220
|
beza: {
|
|
221
221
|
displayName: 'BeZa',
|
|
222
|
-
role: '
|
|
222
|
+
role: 'Business Strategist',
|
|
223
223
|
emoji: '🧭',
|
|
224
224
|
gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
225
225
|
blurb: 'Turns ideas into structured business plans, validates founder-market fit, and pressure-tests strategy.',
|
|
@@ -228,7 +228,7 @@ exports.PERSONA_HIRE_CATALOG = {
|
|
|
228
228
|
},
|
|
229
229
|
maestro: {
|
|
230
230
|
displayName: 'MAESTRO',
|
|
231
|
-
role: 'Full-Brained
|
|
231
|
+
role: 'Full-Brained Employee',
|
|
232
232
|
emoji: '★',
|
|
233
233
|
gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%)',
|
|
234
234
|
blurb: 'One AI employee who can take a job from any function and ship it back with evidence. You set the direction. You sign off on what ships. Maestro does the work.',
|
|
@@ -260,7 +260,7 @@ exports.PERSONA_AVATAR_CATALOG = {
|
|
|
260
260
|
procella: { seed: 'PROCELLA-procurement', bg: 'ccfbf1', style: 'notionists' },
|
|
261
261
|
banke: { seed: 'BANKe-banking-kyc', bg: 'ccfbf1', style: 'notionists' },
|
|
262
262
|
auditya: { seed: 'AUDITya-banking-audit', bg: 'e9d5ff', style: 'notionists' },
|
|
263
|
-
aida: { seed: '
|
|
263
|
+
aida: { seed: 'AIDa-ai-developer', bg: 'c7d2fe', style: 'notionists' },
|
|
264
264
|
};
|
|
265
265
|
function buildPersonaAvatarUrl(personaKey) {
|
|
266
266
|
const avatar = exports.PERSONA_AVATAR_CATALOG[personaKey];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.289",
|
|
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.289",
|
|
214
214
|
"mongodb": "^7.0.0",
|
|
215
215
|
"node-cron": "4.2.1",
|
|
216
216
|
"node-edge-tts": "^1.2.10",
|