@kal-elsam/kairo-runtime 0.8.0 → 0.9.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/README.md +31 -11
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +4 -4
- package/src/cli.js +24 -1
- package/src/global/global-doctor.js +2 -0
- package/src/global/ink/cockpit/primitives.js +96 -31
- package/src/global/ink/cockpit-alerts.js +36 -0
- package/src/global/ink/cockpit-changes.js +59 -35
- package/src/global/ink/cockpit-control-center.js +129 -51
- package/src/global/ink/cockpit-controller.js +58 -11
- package/src/global/ink/cockpit-focus.js +4 -2
- package/src/global/ink/cockpit-models.js +89 -47
- package/src/global/ink/cockpit-palette.js +98 -0
- package/src/global/ink/cockpit-path-label.js +19 -0
- package/src/global/ink/cockpit-recovery.js +78 -15
- package/src/global/ink/cockpit-reviews.js +14 -10
- package/src/global/ink/cockpit-runs.js +13 -4
- package/src/global/ink/cockpit-settings.js +194 -0
- package/src/global/ink/cockpit-views.js +88 -57
- package/src/global/ink/orchestrator-app.js +137 -46
- package/src/global/ink/orchestrator-state.js +24 -14
- package/src/global/ink/use-orchestrator-data.js +58 -0
- package/src/global/paths.js +3 -0
- package/src/global/runtime/alerts/alert-store.js +216 -0
- package/src/global/runtime/alerts/alert-types.js +59 -0
- package/src/global/runtime/alerts/alert-validate.js +117 -0
- package/src/global/runtime/monitor/monitor-cli.js +62 -0
- package/src/global/runtime/monitor/monitor-platform.js +95 -0
- package/src/global/runtime/monitor/monitor.js +249 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { harnessHomePaths } from "../../paths.js";
|
|
4
|
+
import { detectGlobalDrift, hasRepairableDrift } from "../../drift.js";
|
|
5
|
+
import { writeAtomicJson } from "../write-atomic-json.js";
|
|
6
|
+
import { listAlerts, saveAlert } from "../alerts/alert-store.js";
|
|
7
|
+
import { ALERT_SEVERITIES, ALERT_STATES } from "../alerts/alert-types.js";
|
|
8
|
+
import { listRunRecords } from "../run-store.js";
|
|
9
|
+
import { isRunAlive } from "../run-liveness.js";
|
|
10
|
+
import { RUN_STATES, isActiveRunState } from "../run-types.js";
|
|
11
|
+
import {
|
|
12
|
+
installAutostart, notifyNewAlert, removeAutostart, resolveMonitorPlatform
|
|
13
|
+
} from "./monitor-platform.js";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
installAutostart, notifyNewAlert, removeAutostart, resolveMonitorPlatform
|
|
17
|
+
} from "./monitor-platform.js";
|
|
18
|
+
|
|
19
|
+
const SOURCE = "monitor";
|
|
20
|
+
const INTERVAL = 300;
|
|
21
|
+
|
|
22
|
+
export class MonitorStateError extends Error {
|
|
23
|
+
constructor(message, { code = "corrupt_monitor_state" } = {}) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "MonitorStateError";
|
|
26
|
+
this.code = code;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defaultMonitorState() {
|
|
31
|
+
return {
|
|
32
|
+
version: 1, enabled: false, intervalSec: INTERVAL,
|
|
33
|
+
lastTickAt: null, lastTick: null,
|
|
34
|
+
autostart: {
|
|
35
|
+
platform: null, installed: false, supported: false, configured: false, loaded: false
|
|
36
|
+
},
|
|
37
|
+
updatedAt: null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function assertMonitorState(raw) {
|
|
42
|
+
if (!raw || typeof raw !== "object" || raw.version !== 1 || typeof raw.enabled !== "boolean") {
|
|
43
|
+
throw new MonitorStateError("Monitor state schema invalid.");
|
|
44
|
+
}
|
|
45
|
+
if (!Number.isFinite(raw.intervalSec) || raw.intervalSec < 1) {
|
|
46
|
+
throw new MonitorStateError("Monitor state.intervalSec invalid.");
|
|
47
|
+
}
|
|
48
|
+
const a = raw.autostart;
|
|
49
|
+
if (!a || typeof a !== "object"
|
|
50
|
+
|| typeof a.supported !== "boolean"
|
|
51
|
+
|| typeof a.configured !== "boolean"
|
|
52
|
+
|| typeof a.loaded !== "boolean"
|
|
53
|
+
|| typeof a.installed !== "boolean") {
|
|
54
|
+
throw new MonitorStateError("Monitor state.autostart invalid.");
|
|
55
|
+
}
|
|
56
|
+
if (a.loaded && !a.configured) {
|
|
57
|
+
throw new MonitorStateError("Monitor state.autostart loaded requires configured.");
|
|
58
|
+
}
|
|
59
|
+
if (a.installed !== a.loaded) {
|
|
60
|
+
throw new MonitorStateError("Monitor state.autostart installed must equal loaded.");
|
|
61
|
+
}
|
|
62
|
+
if (!a.supported && (a.configured || a.loaded || a.installed)) {
|
|
63
|
+
throw new MonitorStateError("Monitor state.autostart unsupported with lifecycle flags.");
|
|
64
|
+
}
|
|
65
|
+
if (!raw.enabled && (a.configured || a.loaded || a.installed)) {
|
|
66
|
+
throw new MonitorStateError("Monitor disabled with active autostart lifecycle.");
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...defaultMonitorState(),
|
|
70
|
+
...raw,
|
|
71
|
+
version: 1,
|
|
72
|
+
enabled: raw.enabled,
|
|
73
|
+
intervalSec: raw.intervalSec,
|
|
74
|
+
autostart: { ...defaultMonitorState().autostart, ...a }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function readMonitorState(homeDir, { repair = false } = {}) {
|
|
79
|
+
const path = harnessHomePaths(homeDir).monitorStatePath;
|
|
80
|
+
if (!existsSync(path)) return defaultMonitorState();
|
|
81
|
+
try {
|
|
82
|
+
return assertMonitorState(JSON.parse(await readFile(path, "utf8")));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (repair) return defaultMonitorState();
|
|
85
|
+
if (error instanceof MonitorStateError) throw error;
|
|
86
|
+
throw new MonitorStateError("Monitor state unreadable.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function writeMonitorState(homeDir, patch, { repair = false } = {}) {
|
|
91
|
+
const { monitorDir, monitorStatePath } = harnessHomePaths(homeDir);
|
|
92
|
+
await mkdir(monitorDir, { recursive: true });
|
|
93
|
+
const next = {
|
|
94
|
+
...await readMonitorState(homeDir, { repair }), ...patch,
|
|
95
|
+
updatedAt: new Date().toISOString()
|
|
96
|
+
};
|
|
97
|
+
await writeAtomicJson(monitorStatePath, next);
|
|
98
|
+
return next;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function raise(homeDir, input, notifyImpl) {
|
|
102
|
+
const result = await saveAlert({ ...input, source: SOURCE }, { homeDir });
|
|
103
|
+
if (!result.deduped) {
|
|
104
|
+
await notifyImpl({ title: "Kairo", body: `${result.alert.severity} · ${result.alert.title}` });
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function autostartRecord(platform, a) {
|
|
110
|
+
return {
|
|
111
|
+
platform: platform.id, supported: a.supported,
|
|
112
|
+
configured: Boolean(a.configured), loaded: Boolean(a.loaded),
|
|
113
|
+
installed: Boolean(a.loaded), detail: a.detail ?? null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function runMonitorTick(homeDir, deps = {}) {
|
|
118
|
+
const {
|
|
119
|
+
notifyImpl = notifyNewAlert, detectDriftImpl = detectGlobalDrift,
|
|
120
|
+
listRunsImpl = listRunRecords, isRunAliveImpl = isRunAlive,
|
|
121
|
+
packageRoot = null, workspaceRoot = null
|
|
122
|
+
} = deps;
|
|
123
|
+
const raised = [];
|
|
124
|
+
await mkdir(harnessHomePaths(homeDir).monitorDir, { recursive: true });
|
|
125
|
+
try {
|
|
126
|
+
const paths = harnessHomePaths(homeDir);
|
|
127
|
+
const state = existsSync(paths.statePath)
|
|
128
|
+
? JSON.parse(await readFile(paths.statePath, "utf8")) : null;
|
|
129
|
+
if (hasRepairableDrift(await detectDriftImpl({
|
|
130
|
+
homeDir, paths, state, packageRoot, workspaceRoot, context: { homeDir }
|
|
131
|
+
}))) {
|
|
132
|
+
raised.push(await raise(homeDir, {
|
|
133
|
+
kind: "monitor.drift", title: "Managed configuration drift",
|
|
134
|
+
summary: "Managed configs drifted. Run kairo sync.", severity: ALERT_SEVERITIES.HIGH
|
|
135
|
+
}, notifyImpl));
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
raised.push(await raise(homeDir, {
|
|
139
|
+
kind: "monitor.drift", title: "Governance scan unavailable",
|
|
140
|
+
summary: "Monitor could not complete the drift scan.", severity: ALERT_SEVERITIES.MEDIUM
|
|
141
|
+
}, notifyImpl));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let runsOk = true; let dead = 0; let failed = 0;
|
|
145
|
+
try {
|
|
146
|
+
for (const run of await listRunsImpl(homeDir, { limit: 40 })) {
|
|
147
|
+
if (isActiveRunState(run.state) && !(await isRunAliveImpl(homeDir, run))) dead += 1;
|
|
148
|
+
if (run.state === RUN_STATES.FAILED) failed += 1;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
runsOk = false;
|
|
152
|
+
raised.push(await raise(homeDir, {
|
|
153
|
+
kind: "monitor.runs-unavailable", title: "Run monitoring unavailable",
|
|
154
|
+
summary: "Monitor could not inspect agent run health this tick.",
|
|
155
|
+
severity: ALERT_SEVERITIES.MEDIUM
|
|
156
|
+
}, notifyImpl));
|
|
157
|
+
}
|
|
158
|
+
if (runsOk && dead > 0) {
|
|
159
|
+
raised.push(await raise(homeDir, {
|
|
160
|
+
kind: "run.orphaned", title: "Orphaned agent run",
|
|
161
|
+
summary: `${dead} active run(s) have no live process.`, severity: ALERT_SEVERITIES.HIGH
|
|
162
|
+
}, notifyImpl));
|
|
163
|
+
}
|
|
164
|
+
if (runsOk && failed > 0) {
|
|
165
|
+
raised.push(await raise(homeDir, {
|
|
166
|
+
kind: "run.failed", title: "Agent run failed",
|
|
167
|
+
summary: `${failed} failed run(s) need attention.`, severity: ALERT_SEVERITIES.MEDIUM
|
|
168
|
+
}, notifyImpl));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const lastTick = {
|
|
172
|
+
raised: raised.length,
|
|
173
|
+
created: raised.filter((r) => !r.deduped).length,
|
|
174
|
+
deduped: raised.filter((r) => r.deduped).length,
|
|
175
|
+
complete: runsOk, runs: runsOk ? "ok" : "unavailable"
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
state: await writeMonitorState(homeDir, {
|
|
179
|
+
lastTickAt: new Date().toISOString(), lastTick
|
|
180
|
+
}),
|
|
181
|
+
raised
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function enableMonitor(homeDir, {
|
|
186
|
+
cliEntry, nodePath = process.execPath, platform = resolveMonitorPlatform(), intervalSec = INTERVAL
|
|
187
|
+
} = {}) {
|
|
188
|
+
const autostart = await installAutostart({ homeDir, platform, nodePath, cliEntry, intervalSec });
|
|
189
|
+
try {
|
|
190
|
+
return await writeMonitorState(homeDir, {
|
|
191
|
+
enabled: true, intervalSec, autostart: autostartRecord(platform, autostart)
|
|
192
|
+
}, { repair: true });
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (autostart.loaded) await removeAutostart({ platform });
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function disableMonitor(homeDir, { platform = resolveMonitorPlatform() } = {}) {
|
|
200
|
+
return writeMonitorState(homeDir, {
|
|
201
|
+
enabled: false, autostart: autostartRecord(platform, await removeAutostart({ platform }))
|
|
202
|
+
}, { repair: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function getMonitorStatus(homeDir, { platform = resolveMonitorPlatform() } = {}) {
|
|
206
|
+
try {
|
|
207
|
+
const state = await readMonitorState(homeDir);
|
|
208
|
+
let openAlerts = 0;
|
|
209
|
+
try {
|
|
210
|
+
openAlerts = (await listAlerts({ homeDir, state: ALERT_STATES.OPEN })).length;
|
|
211
|
+
} catch { openAlerts = null; }
|
|
212
|
+
return {
|
|
213
|
+
available: true, corrupt: false, enabled: state.enabled, intervalSec: state.intervalSec,
|
|
214
|
+
lastTickAt: state.lastTickAt, lastTick: state.lastTick, autostart: state.autostart,
|
|
215
|
+
platform: platform.id,
|
|
216
|
+
notify: { supported: platform.supportsNotify, backend: platform.id }, openAlerts
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error?.code !== "corrupt_monitor_state") throw error;
|
|
220
|
+
return {
|
|
221
|
+
available: false, corrupt: true, enabled: null, intervalSec: null,
|
|
222
|
+
lastTickAt: null, lastTick: null, autostart: null, platform: platform.id,
|
|
223
|
+
notify: { supported: platform.supportsNotify, backend: platform.id },
|
|
224
|
+
openAlerts: null, error: error.message
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function monitorDoctorCheck(homeDir) {
|
|
230
|
+
const s = await getMonitorStatus(homeDir);
|
|
231
|
+
if (s.corrupt || s.available === false) {
|
|
232
|
+
return {
|
|
233
|
+
name: "monitor", status: "stale", category: "monitor",
|
|
234
|
+
detail: "corrupt state — run kairo monitor disable to repair"
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (s.enabled && s.autostart?.supported && !s.autostart?.loaded) {
|
|
238
|
+
return {
|
|
239
|
+
name: "monitor", status: "stale", category: "monitor",
|
|
240
|
+
detail: "enabled but autostart not loaded"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
name: "monitor", status: "ok", category: "monitor",
|
|
245
|
+
detail: s.enabled
|
|
246
|
+
? `enabled · last ${s.lastTickAt ?? "none"} · autostart ${s.autostart?.loaded ? "loaded" : "off"}`
|
|
247
|
+
: "disabled (opt-in · kairo monitor enable)"
|
|
248
|
+
};
|
|
249
|
+
}
|