@kal-elsam/kairo-runtime 0.11.0 → 0.12.0
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/global-template/components/agent-skills/LICENSE +21 -0
- package/global-template/components/agent-skills/PROVENANCE.md +26 -0
- package/global-template/components/agent-skills/skills/context-engineering/SKILL.md +289 -0
- package/global-template/components/agent-skills/skills/frontend-ui-engineering/SKILL.md +328 -0
- package/global-template/components/agent-skills/skills/observability-and-instrumentation/SKILL.md +203 -0
- package/global-template/components/agent-skills/skills/performance-optimization/SKILL.md +396 -0
- package/global-template/components/agent-skills/skills/source-driven-development/SKILL.md +194 -0
- package/global-template/components/catalog.json +29 -0
- package/package.json +5 -2
- package/src/cli.js +136 -8
- package/src/global/component-builders.js +3 -1
- package/src/global/components/agent-skills.js +27 -0
- package/src/global/ink/cockpit-control-center.js +104 -4
- package/src/global/ink/cockpit-scan.js +20 -2
- package/src/global/ink/ecosystem-updates-display.js +37 -0
- package/src/global/ink/launch-input.js +32 -1
- package/src/global/ink/orchestrator-app.js +2 -1
- package/src/global/ink/orchestrator-state.js +17 -2
- package/src/global/ink/system-resources-display.js +109 -0
- package/src/global/ink/use-orchestrator-data.js +40 -4
- package/src/global/ink/ux/live-overview.js +11 -1
- package/src/global/mcp/kairo-mcp.js +230 -0
- package/src/global/observability/build-companion-snapshot.js +281 -0
- package/src/global/observability/build-observability-snapshot.js +24 -0
- package/src/global/observability/ecosystem-updates.js +224 -0
- package/src/global/observability/gentle-bundle-export.js +71 -0
- package/src/global/observability/gentle-bundle-import.js +122 -0
- package/src/global/observability/gentle-probe.js +155 -0
- package/src/global/observability/graphify-ops.js +133 -0
- package/src/global/observability/graphify-parse-cache.js +90 -0
- package/src/global/observability/graphify-probe.js +185 -0
- package/src/global/observability/hermes-activity.js +163 -0
- package/src/global/observability/hermes-probe.js +171 -0
- package/src/global/observability/index.js +85 -0
- package/src/global/observability/passive-snapshot-flight.js +93 -0
- package/src/global/observability/probe-contract.js +38 -0
- package/src/global/observability/probe-registry.js +30 -0
- package/src/global/observability/resource-advisor.js +71 -0
- package/src/global/observability/system-resources.js +171 -0
- package/src/global/runtime/alerts/alert-cli.js +31 -0
- package/src/global/runtime/alerts/alert-store.js +29 -6
- package/src/global/runtime/alerts/alert-validate.js +25 -1
- package/src/global/runtime/alerts/controlled-alert-actions.js +56 -0
- package/src/global/runtime/execution-adapters/claude.js +2 -1
- package/src/global/runtime/execution-adapters/codex.js +2 -1
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +3 -14
- package/src/global/runtime/execution-adapters/cursor.js +2 -1
- package/src/global/runtime/execution-adapters/opencode.js +2 -1
- package/src/global/runtime/execution-adapters/pi.js +2 -1
- package/src/global/runtime/review/index.js +1 -1
- package/src/global/runtime/review/review-cli.js +113 -3
- package/src/global/runtime/review/review-git.js +142 -11
- package/src/global/runtime/review/review-patch.js +2 -0
- package/src/global/runtime/review/review-receipts.js +12 -7
- package/src/global/runtime/review/review-runner.js +2 -2
- package/src/global/runtime/review/review-types.js +8 -5
- package/src/global/runtime/review/review-validate.js +5 -1
- package/src/global/runtime/run-cli.js +2 -0
- package/src/global/runtime/run-manager.js +39 -18
- package/src/global/runtime/run-permissions.js +231 -0
- package/src/global/runtime/run-profile.js +2 -0
- package/src/global/runtime/run-supervisor.js +77 -37
- package/src/global/runtime/run-types.js +2 -0
- package/src/global/updates-cli.js +41 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { resolveHomeDir } from "../paths.js";
|
|
5
|
+
import { buildControlPlaneSnapshot } from "../control-plane-snapshot.js";
|
|
6
|
+
import { listRunRecords } from "../runtime/run-store.js";
|
|
7
|
+
import { listAlerts } from "../runtime/alerts/alert-store.js";
|
|
8
|
+
import { listReviewReceipts } from "../runtime/review/review-receipts.js";
|
|
9
|
+
import { buildCompanionSnapshot } from "../observability/build-companion-snapshot.js";
|
|
10
|
+
import { probeGentle } from "../observability/gentle-probe.js";
|
|
11
|
+
import { runGraphifyOp } from "../observability/graphify-ops.js";
|
|
12
|
+
import { resolveGitHeadSha } from "../observability/graphify-probe.js";
|
|
13
|
+
import { runPassiveObservabilitySnapshot } from "../observability/passive-snapshot-flight.js";
|
|
14
|
+
import { inspectEngramIntegration } from "../integrations/engram-evidence.js";
|
|
15
|
+
|
|
16
|
+
export const KAIRO_MCP_TOOLS = Object.freeze([
|
|
17
|
+
"kairo_status", "kairo_runs", "kairo_alerts", "kairo_gentle_status",
|
|
18
|
+
"kairo_graph_query", "kairo_graph_path", "kairo_context_summary"
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const empty = z.object({});
|
|
22
|
+
export const mcpSchemas = Object.freeze({
|
|
23
|
+
empty,
|
|
24
|
+
runs: z.object({ limit: z.number().int().min(1).max(20).default(20), activeOnly: z.boolean().optional() }),
|
|
25
|
+
alerts: z.object({
|
|
26
|
+
limit: z.number().int().min(1).max(50).default(50),
|
|
27
|
+
state: z.enum(["open", "resolved", "dismissed"]).optional()
|
|
28
|
+
}),
|
|
29
|
+
graphQuery: z.object({
|
|
30
|
+
graph: z.string().min(1), question: z.string().min(1),
|
|
31
|
+
budget: z.number().int().min(1).max(8000).default(2000)
|
|
32
|
+
}),
|
|
33
|
+
graphPath: z.object({ graph: z.string().min(1), from: z.string().min(1), to: z.string().min(1) })
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const CODE_RE = /^(?:[a-z][a-z0-9_]{0,48}|status=\d+)$/;
|
|
37
|
+
export const pubCodes = (xs = []) => xs.map(String).filter((d) => CODE_RE.test(d));
|
|
38
|
+
|
|
39
|
+
export function mcpResult({ ok, code, data = null, diagnostics = [], isError = false }) {
|
|
40
|
+
const structuredContent = { ok, code, data, diagnostics: pubCodes(diagnostics) };
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
43
|
+
structuredContent, ...(isError ? { isError: true } : {})
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const pubRun = (r) => ({
|
|
48
|
+
runId: r?.runId ?? null, state: r?.state ?? null, agentId: r?.agentId ?? null,
|
|
49
|
+
startedAt: r?.startedAt ?? null, updatedAt: r?.updatedAt ?? null,
|
|
50
|
+
endedAt: r?.endedAt ?? r?.completedAt ?? null
|
|
51
|
+
});
|
|
52
|
+
const pubAlert = (a) => ({
|
|
53
|
+
alertId: a?.alertId ?? null, state: a?.state ?? null, kind: a?.kind ?? null,
|
|
54
|
+
severity: a?.severity ?? null, title: a?.title ?? null, createdAt: a?.createdAt ?? null
|
|
55
|
+
});
|
|
56
|
+
export const pubCoverage = (c) => (c == null ? null : {
|
|
57
|
+
detectedAgents: Number(c.detectedAgents) || 0, governedAgents: Number(c.governedAgents) || 0,
|
|
58
|
+
components: Number(c.components) || 0,
|
|
59
|
+
activeModules: Array.isArray(c.activeModules) ? c.activeModules.map(String) : []
|
|
60
|
+
});
|
|
61
|
+
export const pubGentle = (p) => ({
|
|
62
|
+
state: p?.state ?? "missing", error: null,
|
|
63
|
+
diagnostics: (p?.state === "error") ? ["provider_error"] : pubCodes(p?.diagnostics)
|
|
64
|
+
});
|
|
65
|
+
export const pubSignals = (s) => ({
|
|
66
|
+
gentle: { state: s?.gentle?.state ?? "missing", error: null, diagnostics: [] },
|
|
67
|
+
graphify: {
|
|
68
|
+
state: s?.graphify?.state ?? "missing", error: null, diagnostics: [],
|
|
69
|
+
graphStatus: s?.graphify?.graphStatus ?? null
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
export const pubEngram = (e) => ({ status: e?.status ?? "missing", binary: null, error: null });
|
|
73
|
+
const pubNext = (n) => (n == null ? null : { kind: n.kind ?? null, displayOnly: true, secondary: true });
|
|
74
|
+
const pubCompanion = (c) => ({
|
|
75
|
+
ok: Boolean(c?.ok), signals: pubSignals(c?.signals), engram: pubEngram(c?.engram),
|
|
76
|
+
nextSafeAction: pubNext(c?.nextSafeAction), alertsCount: c?.alertsCount ?? null,
|
|
77
|
+
linksCount: c?.links?.length ?? 0
|
|
78
|
+
});
|
|
79
|
+
const pubLinks = (links) => (links ?? []).map((l) => ({
|
|
80
|
+
kind: "soft", displayOnly: true, agentId: l.agentId ?? null,
|
|
81
|
+
reviewId: l.reviewId ?? null, runId: l.runId ?? null, deltaMs: l.deltaMs ?? null
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
function graphEnvelope(result) {
|
|
85
|
+
return mcpResult({
|
|
86
|
+
ok: Boolean(result.ok), code: result.ok ? "ok" : (result.code ?? "graphify_error"),
|
|
87
|
+
data: {
|
|
88
|
+
op: result.op ?? null, text: result.ok ? (result.text ?? null) : null, truncated: Boolean(result.truncated),
|
|
89
|
+
graphPath: result.graphPath ?? null, graphStatus: result.graphStatus ?? null
|
|
90
|
+
},
|
|
91
|
+
diagnostics: result.diagnostics ?? [], isError: !result.ok
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createToolHandlers(deps = {}) {
|
|
96
|
+
const homeDir = deps.homeDir ?? resolveHomeDir();
|
|
97
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
98
|
+
const listRuns = deps.listRuns ?? ((o) => listRunRecords(homeDir, o));
|
|
99
|
+
const listAlertRows = deps.listAlerts ?? ((o) => listAlerts({ homeDir, ...o }));
|
|
100
|
+
const listReviews = deps.listReviews ?? (() => listReviewReceipts({ homeDir, limit: 20 }));
|
|
101
|
+
const buildStatus = deps.buildStatus ?? (() => buildControlPlaneSnapshot({
|
|
102
|
+
homeDir, workspaceRoot: cwd, packageRoot: deps.packageRoot, packageName: deps.packageName,
|
|
103
|
+
cliVersion: deps.version, includeDiff: false, includeExplain: false, includeRuntime: true
|
|
104
|
+
}));
|
|
105
|
+
const resolveHead = deps.resolveHead ?? ((dir) => resolveGitHeadSha(dir));
|
|
106
|
+
/** Fresh HEAD per request/snapshot — never cache across MCP tool calls. */
|
|
107
|
+
const requestHead = () => resolveHead(cwd);
|
|
108
|
+
const buildObs = deps.buildObservability
|
|
109
|
+
?? ((ctx) => runPassiveObservabilitySnapshot(ctx, { force: Boolean(ctx?.force) }));
|
|
110
|
+
const buildCompanion = deps.buildCompanion ?? ((ctx) => buildCompanionSnapshot({
|
|
111
|
+
...ctx,
|
|
112
|
+
inspectEngram: deps.inspectEngram ?? ((c) => inspectEngramIntegration(c)),
|
|
113
|
+
loadAlerts: async () => listAlertRows({ limit: 50 }),
|
|
114
|
+
loadReviews: async () => listReviews(),
|
|
115
|
+
buildObservability: buildObs,
|
|
116
|
+
ensureRegistered: deps.ensureRegistered,
|
|
117
|
+
observabilityContext: { cwd, homeDir, workspaceRoot: cwd, headSha: requestHead() }
|
|
118
|
+
}));
|
|
119
|
+
const gentleProbe = deps.probeGentle ?? ((ctx) => probeGentle(ctx));
|
|
120
|
+
const graphOp = deps.runGraphifyOp ?? runGraphifyOp;
|
|
121
|
+
const gOpts = () => ({
|
|
122
|
+
cwd, workspaceRoot: cwd, headSha: requestHead(), whichCommand: deps.whichCommand,
|
|
123
|
+
probeCommand: deps.probeCommand, containPath: deps.containPath,
|
|
124
|
+
inspectGraph: deps.inspectGraph, resolveHead
|
|
125
|
+
});
|
|
126
|
+
const soft = (code, data) => mcpResult({
|
|
127
|
+
ok: true, code, data,
|
|
128
|
+
diagnostics: code === "degraded" ? [`${Object.keys(data)[0]}_unavailable`] : []
|
|
129
|
+
});
|
|
130
|
+
const graphFail = () => mcpResult({
|
|
131
|
+
ok: false, code: "provider_error", data: null, diagnostics: ["provider_error"], isError: true
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
async kairo_status() {
|
|
136
|
+
try {
|
|
137
|
+
const snap = await buildStatus();
|
|
138
|
+
const runs = await listRuns({ limit: 20, activeOnly: false }).catch(() => []);
|
|
139
|
+
const companion = await buildCompanion({ controlPlaneHealth: snap?.health ?? null, runs });
|
|
140
|
+
return mcpResult({
|
|
141
|
+
ok: true, code: "ok",
|
|
142
|
+
data: {
|
|
143
|
+
health: snap?.health ?? null, coverage: pubCoverage(snap?.coverage),
|
|
144
|
+
cta: snap?.cta ? { kind: snap.cta.kind ?? null } : null,
|
|
145
|
+
companion: pubCompanion(companion)
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
} catch { return soft("degraded", { health: null, coverage: null, companion: null }); }
|
|
149
|
+
},
|
|
150
|
+
async kairo_runs({ limit = 20, activeOnly = false } = {}) {
|
|
151
|
+
try {
|
|
152
|
+
return mcpResult({ ok: true, code: "ok", data: { runs: (await listRuns({ limit, activeOnly: Boolean(activeOnly) })).map(pubRun) } });
|
|
153
|
+
} catch { return soft("degraded", { runs: [] }); }
|
|
154
|
+
},
|
|
155
|
+
async kairo_alerts({ limit = 50, state } = {}) {
|
|
156
|
+
try {
|
|
157
|
+
return mcpResult({ ok: true, code: "ok", data: { alerts: (await listAlertRows({ limit, state: state ?? null })).map(pubAlert) } });
|
|
158
|
+
} catch { return soft("degraded", { alerts: [] }); }
|
|
159
|
+
},
|
|
160
|
+
async kairo_gentle_status() {
|
|
161
|
+
try {
|
|
162
|
+
const probe = pubGentle(await gentleProbe({ cwd, homeDir, workspaceRoot: cwd }));
|
|
163
|
+
return mcpResult({
|
|
164
|
+
ok: true, code: probe.state === "available" ? "ok" : "degraded", data: probe,
|
|
165
|
+
diagnostics: probe.state === "missing" ? ["gentle_missing"] : []
|
|
166
|
+
});
|
|
167
|
+
} catch {
|
|
168
|
+
return mcpResult({
|
|
169
|
+
ok: true, code: "degraded", data: { state: "missing", error: null, diagnostics: [] },
|
|
170
|
+
diagnostics: ["gentle_unavailable"]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
async kairo_graph_query({ graph, question, budget = 2000 }) {
|
|
175
|
+
try {
|
|
176
|
+
return graphEnvelope(await graphOp({
|
|
177
|
+
op: "query", args: [question], graphPath: graph, budget, ...gOpts()
|
|
178
|
+
}));
|
|
179
|
+
} catch { return graphFail(); }
|
|
180
|
+
},
|
|
181
|
+
async kairo_graph_path({ graph, from, to }) {
|
|
182
|
+
try {
|
|
183
|
+
return graphEnvelope(await graphOp({
|
|
184
|
+
op: "path", args: [from, to], graphPath: graph, ...gOpts()
|
|
185
|
+
}));
|
|
186
|
+
} catch { return graphFail(); }
|
|
187
|
+
},
|
|
188
|
+
async kairo_context_summary() {
|
|
189
|
+
try {
|
|
190
|
+
const companion = await buildCompanion({ runs: await listRuns({ limit: 20, activeOnly: false }) });
|
|
191
|
+
return mcpResult({
|
|
192
|
+
ok: true, code: companion?.ok === false ? "degraded" : "ok",
|
|
193
|
+
data: {
|
|
194
|
+
signals: pubSignals(companion?.signals), engram: pubEngram(companion?.engram),
|
|
195
|
+
links: pubLinks(companion?.links), alertsCount: companion?.alertsCount ?? null,
|
|
196
|
+
nextSafeAction: pubNext(companion?.nextSafeAction)
|
|
197
|
+
},
|
|
198
|
+
diagnostics: companion?.ok === false ? ["companion_degraded"] : []
|
|
199
|
+
});
|
|
200
|
+
} catch {
|
|
201
|
+
return soft("degraded", { signals: null, engram: null, links: [], alertsCount: null, nextSafeAction: null });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function registerKairoMcpTools(registerTool, deps = {}) {
|
|
208
|
+
const h = createToolHandlers(deps);
|
|
209
|
+
for (const [name, description, inputSchema] of [
|
|
210
|
+
["kairo_status", "Read-only control-plane + companion summary", empty],
|
|
211
|
+
["kairo_runs", "Read-only run list", mcpSchemas.runs],
|
|
212
|
+
["kairo_alerts", "Read-only alert list", mcpSchemas.alerts],
|
|
213
|
+
["kairo_gentle_status", "Gentle probe / companion gentle signal", empty],
|
|
214
|
+
["kairo_graph_query", "Read-only Graphify query", mcpSchemas.graphQuery],
|
|
215
|
+
["kairo_graph_path", "Read-only Graphify path", mcpSchemas.graphPath],
|
|
216
|
+
["kairo_context_summary", "Companion + soft links + alerts count", empty]
|
|
217
|
+
]) registerTool(name, { description, inputSchema }, h[name]);
|
|
218
|
+
return h;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function createKairoMcpServer(deps = {}) {
|
|
222
|
+
const Server = deps.McpServer ?? McpServer;
|
|
223
|
+
const server = new Server({ name: "kairo", version: deps.version ?? "0.11.0" });
|
|
224
|
+
registerKairoMcpTools(deps.registerTool ?? ((n, c, h) => server.registerTool(n, c, h)), deps);
|
|
225
|
+
return server;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function runKairoMcp(deps = {}) {
|
|
229
|
+
return (deps.serveStdio ?? serveStdio)(() => createKairoMcpServer(deps));
|
|
230
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { CONTROL_PLANE_HEALTH } from "../control-plane-snapshot.js";
|
|
2
|
+
import { buildObservabilitySnapshot as defaultObs } from "./build-observability-snapshot.js";
|
|
3
|
+
import { createGentleProbe } from "./gentle-probe.js";
|
|
4
|
+
import { createGraphifyProbe } from "./graphify-probe.js";
|
|
5
|
+
import { createHermesProbe } from "./hermes-probe.js";
|
|
6
|
+
import { loadHermesActivity as defaultHermesActivity } from "./hermes-activity.js";
|
|
7
|
+
import { loadSystemResources as defaultSystemResources } from "./system-resources.js";
|
|
8
|
+
import { recommendSystemResources } from "./resource-advisor.js";
|
|
9
|
+
import { loadEcosystemUpdates as defaultEcosystemUpdates } from "./ecosystem-updates.js";
|
|
10
|
+
import { getObservabilityProbe, registerObservabilityProbe } from "./probe-registry.js";
|
|
11
|
+
|
|
12
|
+
export const SOFT_LINK_WINDOW_MS = 60 * 60 * 1000;
|
|
13
|
+
const RUN_TS = ["updatedAt", "endedAt", "startedAt"];
|
|
14
|
+
const REVIEW_TS = ["updatedAt", "createdAt", "capturedAt", "startedAt"];
|
|
15
|
+
const GOV_PRIMARY = new Set([
|
|
16
|
+
CONTROL_PLANE_HEALTH.NOT_CONFIGURED,
|
|
17
|
+
CONTROL_PLANE_HEALTH.ACTION_REQUIRED,
|
|
18
|
+
CONTROL_PLANE_HEALTH.CHECK_FAILED
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function defaultEnsure() {
|
|
22
|
+
if (!getObservabilityProbe("gentle")) registerObservabilityProbe(createGentleProbe());
|
|
23
|
+
if (!getObservabilityProbe("graphify")) registerObservabilityProbe(createGraphifyProbe());
|
|
24
|
+
if (!getObservabilityProbe("hermes")) registerObservabilityProbe(createHermesProbe());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function emptyHermesActivity() {
|
|
28
|
+
return {
|
|
29
|
+
state: "error", error: "error", diagnostics: [], baseUrl: null, sessions: [],
|
|
30
|
+
aggregates: { returnedCount: 0, activeCount: 0, endedCount: 0, hasMore: false, lastActiveAt: null }
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function summarizeHermesActivity(a) {
|
|
34
|
+
if (a == null || typeof a !== "object") return emptyHermesActivity();
|
|
35
|
+
const empty = emptyHermesActivity();
|
|
36
|
+
return {
|
|
37
|
+
state: a.state ?? "error", error: a.error ?? null, baseUrl: a.baseUrl ?? null,
|
|
38
|
+
diagnostics: Array.isArray(a.diagnostics) ? a.diagnostics.map(String) : [],
|
|
39
|
+
sessions: Array.isArray(a.sessions) ? a.sessions : [], aggregates: a.aggregates ?? empty.aggregates
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function emptyEcosystemUpdates() {
|
|
44
|
+
return {
|
|
45
|
+
state: "error", checkedAt: null, cacheHit: false, diagnostics: [],
|
|
46
|
+
tools: {
|
|
47
|
+
kairo: { id: "kairo", state: "error", updateAvailable: false },
|
|
48
|
+
hermes: { id: "hermes", state: "error", updateAvailable: false },
|
|
49
|
+
gentle: { id: "gentle", state: "error", updateAvailable: false },
|
|
50
|
+
skills: { id: "skills", state: "error", updateAvailable: false }
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function summarizeEcosystemUpdates(raw) {
|
|
55
|
+
if (raw == null || typeof raw !== "object") return emptyEcosystemUpdates();
|
|
56
|
+
return {
|
|
57
|
+
state: raw.state ?? "error",
|
|
58
|
+
checkedAt: raw.checkedAt ?? null,
|
|
59
|
+
cacheHit: raw.cacheHit === true,
|
|
60
|
+
diagnostics: Array.isArray(raw.diagnostics) ? raw.diagnostics.map(String) : [],
|
|
61
|
+
tools: raw.tools ?? emptyEcosystemUpdates().tools
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function emptySystemResources() {
|
|
66
|
+
return {
|
|
67
|
+
state: "error", sampledAt: null, diagnostics: [],
|
|
68
|
+
memory: null, swap: null, disk: null,
|
|
69
|
+
processes: { totalCount: 0, zombieCount: 0, tracked: [] },
|
|
70
|
+
thermal: { state: "unavailable" }, ssdWear: { state: "unavailable" }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function summarizeSystemResources(raw) {
|
|
74
|
+
if (raw == null || typeof raw !== "object") return emptySystemResources();
|
|
75
|
+
const empty = emptySystemResources();
|
|
76
|
+
return {
|
|
77
|
+
state: raw.state ?? "error",
|
|
78
|
+
sampledAt: raw.sampledAt ?? null,
|
|
79
|
+
diagnostics: Array.isArray(raw.diagnostics) ? raw.diagnostics.map(String) : [],
|
|
80
|
+
memory: raw.memory ?? null, swap: raw.swap ?? null, disk: raw.disk ?? null,
|
|
81
|
+
processes: raw.processes ?? empty.processes,
|
|
82
|
+
thermal: raw.thermal ?? { state: "unavailable" },
|
|
83
|
+
ssdWear: raw.ssdWear ?? { state: "unavailable" }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function parseCompanionTimestamp(value) {
|
|
88
|
+
if (value == null || value === "") return null;
|
|
89
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
90
|
+
const ms = Date.parse(String(value));
|
|
91
|
+
return Number.isFinite(ms) ? ms : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function firstTs(obj, keys) {
|
|
95
|
+
for (const key of keys) {
|
|
96
|
+
const ms = parseCompanionTimestamp(obj?.[key]);
|
|
97
|
+
if (ms != null) return ms;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const resolveRunTimestamp = (run) => firstTs(run, RUN_TS);
|
|
103
|
+
export const resolveReviewTimestamp = (review) => firstTs(review, REVIEW_TS);
|
|
104
|
+
|
|
105
|
+
/** Soft/display-only — never authority evidence. */
|
|
106
|
+
export function softLinkReviewToRun(review, runs = []) {
|
|
107
|
+
const agentId = review?.agentId;
|
|
108
|
+
const reviewAt = resolveReviewTimestamp(review);
|
|
109
|
+
if (typeof agentId !== "string" || !agentId || reviewAt == null) return null;
|
|
110
|
+
const eligible = [];
|
|
111
|
+
for (const run of runs) {
|
|
112
|
+
if (run?.agentId !== agentId) continue;
|
|
113
|
+
const runAt = resolveRunTimestamp(run);
|
|
114
|
+
if (runAt == null || !(reviewAt > runAt)) continue;
|
|
115
|
+
const deltaMs = reviewAt - runAt;
|
|
116
|
+
if (deltaMs > SOFT_LINK_WINDOW_MS) continue;
|
|
117
|
+
const runId = String(run.runId ?? "");
|
|
118
|
+
if (!runId) continue;
|
|
119
|
+
eligible.push({ runId, runAt, deltaMs });
|
|
120
|
+
}
|
|
121
|
+
if (!eligible.length) return null;
|
|
122
|
+
eligible.sort((a, b) => (b.runAt - a.runAt) || a.runId.localeCompare(b.runId));
|
|
123
|
+
const best = eligible[0];
|
|
124
|
+
return {
|
|
125
|
+
kind: "soft", displayOnly: true, agentId,
|
|
126
|
+
reviewId: String(review.reviewId ?? ""), runId: best.runId,
|
|
127
|
+
deltaMs: best.deltaMs, runAt: best.runAt, reviewAt
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function summarizeCompanionProbes(probes = []) {
|
|
132
|
+
const byId = Object.fromEntries((probes ?? []).map((p) => [p.id, p]));
|
|
133
|
+
const gentle = byId.gentle ?? { state: "missing", diagnostics: [], error: null };
|
|
134
|
+
const graphify = byId.graphify ?? { state: "missing", diagnostics: [], error: null };
|
|
135
|
+
const graph = (graphify.evidence ?? []).find((e) => e?.kind === "graph") ?? null;
|
|
136
|
+
return {
|
|
137
|
+
gentle: { state: gentle.state, error: gentle.error ?? null, diagnostics: gentle.diagnostics ?? [] },
|
|
138
|
+
graphify: {
|
|
139
|
+
state: graphify.state, error: graphify.error ?? null,
|
|
140
|
+
diagnostics: graphify.diagnostics ?? [], graphStatus: graph?.status ?? null
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rankNext({ controlPlaneHealth, signals, engram }) {
|
|
146
|
+
const gentleState = signals?.gentle?.state;
|
|
147
|
+
const graphState = signals?.graphify?.state;
|
|
148
|
+
const graphStatus = signals?.graphify?.graphStatus;
|
|
149
|
+
const engramStatus = engram?.status ?? "missing";
|
|
150
|
+
let kind = "idle";
|
|
151
|
+
let title = "Companion signals quiet";
|
|
152
|
+
let detail = "No companion follow-up required.";
|
|
153
|
+
if (gentleState === "error" || graphState === "error" || graphStatus === "error"
|
|
154
|
+
|| engramStatus === "error" || engramStatus === "conflict" || engramStatus === "unsupported") {
|
|
155
|
+
kind = "investigate";
|
|
156
|
+
title = "Investigate companion diagnostics";
|
|
157
|
+
detail = "Optional tools reported a hard failure; inspect Gentle/Graphify/Engram read-only.";
|
|
158
|
+
} else if (gentleState === "missing" || graphState === "missing" || engramStatus === "missing") {
|
|
159
|
+
kind = "missing";
|
|
160
|
+
title = "Optional companion tool not detected";
|
|
161
|
+
detail = "Governance may still be healthy; missing tools are diagnostic only.";
|
|
162
|
+
} else if (graphStatus === "stale") {
|
|
163
|
+
kind = "inform";
|
|
164
|
+
title = "Graphify graph looks stale";
|
|
165
|
+
detail = "Informational only — stale does not block read-only ops.";
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
kind, title, detail, secondary: true,
|
|
169
|
+
governancePrimary: GOV_PRIMARY.has(controlPlaneHealth), displayOnly: true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function emptyCompanion(error = null) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false, error: error == null ? null : String(error),
|
|
176
|
+
generatedAt: new Date().toISOString(),
|
|
177
|
+
signals: {
|
|
178
|
+
gentle: { state: "error", error: null, diagnostics: [] },
|
|
179
|
+
graphify: { state: "error", error: null, diagnostics: [], graphStatus: null },
|
|
180
|
+
hermes: { activity: emptyHermesActivity() },
|
|
181
|
+
system: { resources: emptySystemResources(), advice: { recommendations: [], deepScan: false } },
|
|
182
|
+
ecosystem: { updates: emptyEcosystemUpdates() }
|
|
183
|
+
},
|
|
184
|
+
engram: { status: "error", binary: null },
|
|
185
|
+
links: [], alertsCount: null,
|
|
186
|
+
nextSafeAction: {
|
|
187
|
+
kind: "investigate", title: "Investigate companion diagnostics",
|
|
188
|
+
detail: String(error ?? "companion snapshot failed"),
|
|
189
|
+
secondary: true, governancePrimary: false, displayOnly: true
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Fail-soft companion snapshot for Cockpit overlay — never throws. */
|
|
195
|
+
export async function buildCompanionSnapshot({
|
|
196
|
+
controlPlaneHealth = null, runs = [], reviews = null, alerts = null,
|
|
197
|
+
buildObservability = defaultObs, inspectEngram = null,
|
|
198
|
+
ensureRegistered = defaultEnsure, loadReviews = null, loadAlerts = null,
|
|
199
|
+
loadHermesActivity = defaultHermesActivity,
|
|
200
|
+
loadSystemResources = defaultSystemResources,
|
|
201
|
+
loadEcosystemUpdates = defaultEcosystemUpdates,
|
|
202
|
+
resourceDeepScan = false,
|
|
203
|
+
observabilityContext = {}
|
|
204
|
+
} = {}) {
|
|
205
|
+
try {
|
|
206
|
+
ensureRegistered();
|
|
207
|
+
let obs;
|
|
208
|
+
try { obs = await buildObservability(observabilityContext); }
|
|
209
|
+
catch (err) { return emptyCompanion(err?.message ?? err); }
|
|
210
|
+
|
|
211
|
+
let engram = { status: "missing", binary: null };
|
|
212
|
+
if (typeof inspectEngram === "function") {
|
|
213
|
+
try { engram = inspectEngram(observabilityContext) ?? engram; }
|
|
214
|
+
catch (err) { engram = { status: "error", binary: null, error: err?.message ?? String(err) }; }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let hermesActivity = emptyHermesActivity();
|
|
218
|
+
try {
|
|
219
|
+
hermesActivity = summarizeHermesActivity(
|
|
220
|
+
await loadHermesActivity({ ...(observabilityContext ?? {}) })
|
|
221
|
+
);
|
|
222
|
+
} catch {
|
|
223
|
+
hermesActivity = emptyHermesActivity();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let systemResources = emptySystemResources();
|
|
227
|
+
try {
|
|
228
|
+
systemResources = summarizeSystemResources(
|
|
229
|
+
await loadSystemResources({ ...(observabilityContext ?? {}) })
|
|
230
|
+
);
|
|
231
|
+
} catch {
|
|
232
|
+
systemResources = emptySystemResources();
|
|
233
|
+
}
|
|
234
|
+
const systemAdvice = recommendSystemResources(systemResources, {
|
|
235
|
+
deepScan: resourceDeepScan === true
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
let ecosystemUpdates = emptyEcosystemUpdates();
|
|
239
|
+
try {
|
|
240
|
+
ecosystemUpdates = summarizeEcosystemUpdates(
|
|
241
|
+
await loadEcosystemUpdates({
|
|
242
|
+
...(observabilityContext ?? {}),
|
|
243
|
+
packageName: observabilityContext?.packageName,
|
|
244
|
+
installedVersion: observabilityContext?.installedVersion ?? observabilityContext?.cliVersion,
|
|
245
|
+
homeDir: observabilityContext?.homeDir
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
} catch {
|
|
249
|
+
ecosystemUpdates = emptyEcosystemUpdates();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const reviewList = Array.isArray(reviews)
|
|
253
|
+
? reviews
|
|
254
|
+
: (typeof loadReviews === "function" ? await loadReviews() : []);
|
|
255
|
+
const alertList = Array.isArray(alerts)
|
|
256
|
+
? alerts
|
|
257
|
+
: (typeof loadAlerts === "function" ? await loadAlerts() : null);
|
|
258
|
+
const signals = {
|
|
259
|
+
...summarizeCompanionProbes(obs?.probes ?? []),
|
|
260
|
+
hermes: { activity: hermesActivity },
|
|
261
|
+
system: { resources: systemResources, advice: systemAdvice },
|
|
262
|
+
ecosystem: { updates: ecosystemUpdates }
|
|
263
|
+
};
|
|
264
|
+
const links = [];
|
|
265
|
+
for (const review of reviewList ?? []) {
|
|
266
|
+
const link = softLinkReviewToRun(review, runs);
|
|
267
|
+
if (link) links.push(link);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
ok: true, error: null, generatedAt: new Date().toISOString(), signals,
|
|
271
|
+
engram: { status: engram.status ?? "missing", binary: engram.binary?.path ?? null, error: engram.error ?? null },
|
|
272
|
+
links,
|
|
273
|
+
alertsCount: Array.isArray(alertList)
|
|
274
|
+
? alertList.filter((a) => a?.state === "open" || a?.state === "pending").length
|
|
275
|
+
: null,
|
|
276
|
+
nextSafeAction: rankNext({ controlPlaneHealth, signals, engram })
|
|
277
|
+
};
|
|
278
|
+
} catch (err) {
|
|
279
|
+
return emptyCompanion(err?.message ?? err);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { normalizeProbeResult } from "./probe-contract.js";
|
|
2
|
+
import { listObservabilityProbes } from "./probe-registry.js";
|
|
3
|
+
|
|
4
|
+
/** Parallel soft snapshot — never throws for individual probe failures. */
|
|
5
|
+
export async function buildObservabilitySnapshot(context = {}) {
|
|
6
|
+
const registered = listObservabilityProbes();
|
|
7
|
+
const settled = await Promise.allSettled(
|
|
8
|
+
registered.map(async (probe) => normalizeProbeResult(await probe.probe(context), probe.id))
|
|
9
|
+
);
|
|
10
|
+
const probes = settled.map((entry, i) => {
|
|
11
|
+
const id = registered[i].id;
|
|
12
|
+
if (entry.status === "fulfilled") return entry.value;
|
|
13
|
+
return normalizeProbeResult({
|
|
14
|
+
id,
|
|
15
|
+
state: "error",
|
|
16
|
+
error: entry.reason?.message ?? String(entry.reason ?? "probe failed")
|
|
17
|
+
}, id);
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
generatedAt: new Date().toISOString(),
|
|
21
|
+
probes,
|
|
22
|
+
byId: Object.fromEntries(probes.map((p) => [p.id, p]))
|
|
23
|
+
};
|
|
24
|
+
}
|