@kal-elsam/kairo-runtime 0.7.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 +43 -11
- package/global-template/components/catalog.json +4 -1
- package/global-template/components/orchestrator/extensions/pi/kairo-minion.js +604 -0
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +4 -4
- package/src/cli.js +34 -2
- package/src/global/adapters/pi.js +1 -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/execution-adapters/pi.js +12 -2
- 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
- package/src/global/runtime/orchestration/index.js +23 -0
- package/src/global/runtime/orchestration/orch-receipts.js +234 -0
- package/src/global/runtime/orchestration/orch-types.js +173 -0
- package/src/global/runtime/orchestration/orch-validate.js +63 -0
- package/src/global/runtime/run-cli.js +1 -0
- package/src/global/runtime/run-manager.js +59 -4
- package/src/global/runtime/run-strategy.js +71 -0
- package/src/global/runtime/run-supervisor.js +22 -2
- package/src/global/runtime/run-types.js +5 -1
|
@@ -26,7 +26,7 @@ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
26
26
|
const require = createRequire(import.meta.url);
|
|
27
27
|
const pkg = require(join(root, "package.json"));
|
|
28
28
|
|
|
29
|
-
assert.equal(pkg.version, "0.
|
|
29
|
+
assert.equal(pkg.version, "0.9.0");
|
|
30
30
|
assert.ok(pkg.dependencies["ansi-escapes"]);
|
|
31
31
|
|
|
32
32
|
assert.equal(resolveLayoutMode({ columns: 120, rows: 40 }), LAYOUT_MODES.WIDE);
|
|
@@ -85,7 +85,7 @@ const asciiNav = buildNavModel({
|
|
|
85
85
|
assert.equal(asciiNav.items[1].marker, ">");
|
|
86
86
|
assert.equal(asciiNav.items[0].current, true);
|
|
87
87
|
assert.equal(asciiNav.items[1].selected, true);
|
|
88
|
-
assert.match(asciiNav.explanation, /
|
|
88
|
+
assert.match(asciiNav.explanation, /Repair|Govern|Activity|Usage|Settings|Overview/i);
|
|
89
89
|
|
|
90
90
|
function applyKey(state, keyAction) {
|
|
91
91
|
const routed = routeCockpitKey(state, keyAction);
|
|
@@ -98,7 +98,7 @@ function smokeNavigation(layoutMode) {
|
|
|
98
98
|
layoutMode,
|
|
99
99
|
region: layoutMode === LAYOUT_MODES.MINIMAL ? COCKPIT_REGIONS.CONTENT : COCKPIT_REGIONS.NAV
|
|
100
100
|
});
|
|
101
|
-
const diagnosticsIndex = COCKPIT_NAV.findIndex((item) => item.id === "
|
|
101
|
+
const diagnosticsIndex = COCKPIT_NAV.findIndex((item) => item.id === "governance");
|
|
102
102
|
while (state.navIndex < diagnosticsIndex) {
|
|
103
103
|
state = applyKey(state, { type: "arrow", direction: "down" });
|
|
104
104
|
}
|
|
@@ -108,7 +108,7 @@ function smokeNavigation(layoutMode) {
|
|
|
108
108
|
|
|
109
109
|
state = applyKey(state, { type: "arrow", direction: "up" });
|
|
110
110
|
state = applyKey(state, { type: "enter" });
|
|
111
|
-
assert.equal(state.view, ORCHESTRATOR_VIEWS.
|
|
111
|
+
assert.equal(state.view, ORCHESTRATOR_VIEWS.HOME);
|
|
112
112
|
assert.equal(state.region, COCKPIT_REGIONS.NAV);
|
|
113
113
|
|
|
114
114
|
const footer = buildFooterModel({
|
package/src/cli.js
CHANGED
|
@@ -39,6 +39,8 @@ import { runOrchestratorDiagnostics, runOrchestratorShell } from "./global/orche
|
|
|
39
39
|
import { runIntelligenceCli } from "./global/intelligence-cli.js";
|
|
40
40
|
import { runGlobalRun, runGlobalRuns } from "./global/runtime/run-cli.js";
|
|
41
41
|
import { runGlobalReview, runGlobalReviews } from "./global/runtime/review/review-cli.js";
|
|
42
|
+
import { runGlobalMonitor } from "./global/runtime/monitor/monitor-cli.js";
|
|
43
|
+
import { normalizeRunStrategy } from "./global/runtime/run-strategy.js";
|
|
42
44
|
import {
|
|
43
45
|
LEGACY_PACKAGE_NAME,
|
|
44
46
|
PACKAGE_NAME,
|
|
@@ -118,6 +120,9 @@ export async function runCli(argv) {
|
|
|
118
120
|
case "reviews":
|
|
119
121
|
await runGlobalReviews(optionsWithPolicy, packageManifest);
|
|
120
122
|
return;
|
|
123
|
+
case "monitor":
|
|
124
|
+
await runGlobalMonitor(optionsWithPolicy, packageManifest, { packageRoot });
|
|
125
|
+
return;
|
|
121
126
|
case "intelligence":
|
|
122
127
|
await runIntelligenceCli(optionsWithPolicy, packageManifest);
|
|
123
128
|
return;
|
|
@@ -386,6 +391,7 @@ export function parseArgs(argv) {
|
|
|
386
391
|
agent: null,
|
|
387
392
|
task: null,
|
|
388
393
|
model: null,
|
|
394
|
+
strategy: "direct",
|
|
389
395
|
intelligenceBackend: null,
|
|
390
396
|
permissions: null,
|
|
391
397
|
captureTranscript: false,
|
|
@@ -421,6 +427,10 @@ export function parseArgs(argv) {
|
|
|
421
427
|
parseIntelligenceAction(args, options);
|
|
422
428
|
}
|
|
423
429
|
|
|
430
|
+
if (command === "monitor") {
|
|
431
|
+
parseMonitorAction(args, options);
|
|
432
|
+
}
|
|
433
|
+
|
|
424
434
|
for (let index = 0; index < args.length; index += 1) {
|
|
425
435
|
const arg = args[index];
|
|
426
436
|
|
|
@@ -499,6 +509,13 @@ export function parseArgs(argv) {
|
|
|
499
509
|
else if (arg.startsWith("--agent=")) options.agent = arg.slice("--agent=".length);
|
|
500
510
|
else if (arg === "--model") options.model = args[++index];
|
|
501
511
|
else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
|
|
512
|
+
else if (arg === "--strategy") {
|
|
513
|
+
options.strategy = normalizeRunStrategy(requireFlagValue("--strategy", args[++index]));
|
|
514
|
+
} else if (arg.startsWith("--strategy=")) {
|
|
515
|
+
options.strategy = normalizeRunStrategy(
|
|
516
|
+
requireFlagValue("--strategy", arg.slice("--strategy=".length))
|
|
517
|
+
);
|
|
518
|
+
}
|
|
502
519
|
else if (arg === "--backend") options.intelligenceBackend = args[++index] ?? "";
|
|
503
520
|
else if (arg.startsWith("--backend=")) options.intelligenceBackend = arg.slice("--backend=".length);
|
|
504
521
|
else if (arg === "--permissions") options.permissions = parsePathList(args[++index]);
|
|
@@ -669,6 +686,19 @@ function parseReviewsAction(args, options) {
|
|
|
669
686
|
}
|
|
670
687
|
}
|
|
671
688
|
|
|
689
|
+
function parseMonitorAction(args, options) {
|
|
690
|
+
const action = args[0];
|
|
691
|
+
if (!action || action.startsWith("-")) {
|
|
692
|
+
options.monitorAction = "status";
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (!new Set(["enable", "disable", "status", "tick"]).has(action)) {
|
|
696
|
+
throw new Error(`Unknown monitor action "${action}". Use enable, disable, status, or tick.`);
|
|
697
|
+
}
|
|
698
|
+
args.shift();
|
|
699
|
+
options.monitorAction = action;
|
|
700
|
+
}
|
|
701
|
+
|
|
672
702
|
function parseIntelligenceAction(args, options) {
|
|
673
703
|
const action = args[0];
|
|
674
704
|
if (!action || action.startsWith("-")) {
|
|
@@ -727,6 +757,7 @@ function normalizeCommand(command) {
|
|
|
727
757
|
if (command === "runs") return "runs";
|
|
728
758
|
if (command === "review") return "review";
|
|
729
759
|
if (command === "reviews") return "reviews";
|
|
760
|
+
if (command === "monitor") return "monitor";
|
|
730
761
|
if (command === "intelligence" || command === "intel") return "intelligence";
|
|
731
762
|
if (command === "setup") return "setup";
|
|
732
763
|
if (command === "status") return "status";
|
|
@@ -797,7 +828,7 @@ Usage:
|
|
|
797
828
|
${cli} --dry-run Setup dry-run (scriptable)
|
|
798
829
|
${cli} --version
|
|
799
830
|
${cli} shell Operations cockpit (TTY)
|
|
800
|
-
${cli} run --agent <id> --task "..." [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
|
|
831
|
+
${cli} run --agent <id> --task "..." [--strategy direct|orchestrated] [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
|
|
801
832
|
${cli} runs list [--json] [--limit <n>] [--active-only]
|
|
802
833
|
${cli} runs show <runId> [--json] [--limit <n>] [--follow]
|
|
803
834
|
${cli} runs stop <runId> [--json]
|
|
@@ -854,6 +885,7 @@ Commands:
|
|
|
854
885
|
runs List, inspect, or cancel agent runs under ~/.harness/runs/.
|
|
855
886
|
review Bounded read-only review via Codex or Pi; receipts under ~/.harness/reviews/.
|
|
856
887
|
reviews List or show prior review receipts (secret-free).
|
|
888
|
+
monitor Opt-in anomaly monitor (enable|disable|status|tick). macOS LaunchAgent; notify shell:false.
|
|
857
889
|
orchestrator Read-only capability registry diagnostics (--json supported).
|
|
858
890
|
intelligence Harness Engineering layer: backends, context packs, routing, budgets.
|
|
859
891
|
Local-first (Ollama). Cloud only with --cloud-consent.
|
|
@@ -882,7 +914,7 @@ Commands:
|
|
|
882
914
|
|
|
883
915
|
JSON output (--json on supported commands):
|
|
884
916
|
status, sync, doctor, adapters, explain, diff, history, history last,
|
|
885
|
-
policy (show/set/reset), report
|
|
917
|
+
policy (show/set/reset), report, monitor
|
|
886
918
|
Human text remains the default. See README.md for examples and field notes.
|
|
887
919
|
|
|
888
920
|
Version:
|
|
@@ -7,7 +7,7 @@ export const PI_CODING_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
|
|
|
7
7
|
|
|
8
8
|
const CUSTOM_DIR_MESSAGE =
|
|
9
9
|
`Pi config writes are unsupported when ${PI_CODING_AGENT_DIR_ENV} points away from `
|
|
10
|
-
+ `~/${PI_DEFAULT_ROOT_DIR} (out of scope for Kairo
|
|
10
|
+
+ `~/${PI_DEFAULT_ROOT_DIR} (out of scope for Kairo managed config). `
|
|
11
11
|
+ `Unset ${PI_CODING_AGENT_DIR_ENV} to manage the default directory; runtime launches remain available.`;
|
|
12
12
|
|
|
13
13
|
/** True when PI_CODING_AGENT_DIR is set to a non-default absolute/relative path. */
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
BACKEND_IDS,
|
|
12
12
|
inspectIntelligenceBackends
|
|
13
13
|
} from "./intelligence/index.js";
|
|
14
|
+
import { monitorDoctorCheck } from "./runtime/monitor/monitor.js";
|
|
14
15
|
|
|
15
16
|
export async function runGlobalDoctorChecks(homeDir, {
|
|
16
17
|
packageRoot,
|
|
@@ -43,6 +44,7 @@ export async function runGlobalDoctorChecks(homeDir, {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
checks.push(await intelligenceProvidersCheck({ env, fetchImpl, inspectBackends }));
|
|
47
|
+
checks.push(await monitorDoctorCheck(homeDir));
|
|
46
48
|
|
|
47
49
|
const hasMissing = checks.some((check) => check.status === "missing");
|
|
48
50
|
const hasStale = checks.some((check) => check.status === "stale");
|
|
@@ -33,6 +33,22 @@ export function CockpitPanel({ title, focused = false, width, children }) {
|
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export function CockpitSection({ title, children }) {
|
|
37
|
+
return React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
38
|
+
title && React.createElement(Text, {
|
|
39
|
+
bold: true,
|
|
40
|
+
color: COCKPIT_COLORS.secondary
|
|
41
|
+
}, title),
|
|
42
|
+
children
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function CockpitKeyHint({ keys, label, colorEnabled = true }) {
|
|
47
|
+
return React.createElement(Text, {
|
|
48
|
+
color: colorEnabled ? COCKPIT_COLORS.muted : undefined
|
|
49
|
+
}, `${keys} ${label}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
export function CockpitTopBar({ model, colorEnabled = true }) {
|
|
37
53
|
return React.createElement(Box, { justifyContent: "space-between", width: "100%" },
|
|
38
54
|
React.createElement(Text, {
|
|
@@ -45,6 +61,7 @@ export function CockpitTopBar({ model, colorEnabled = true }) {
|
|
|
45
61
|
);
|
|
46
62
|
}
|
|
47
63
|
|
|
64
|
+
/** Vertical nav list (legacy / tests). Prefer CockpitNavStrip in the shell. */
|
|
48
65
|
export function CockpitNav({ model, colorEnabled = true }) {
|
|
49
66
|
return React.createElement(Box, { flexDirection: "column" },
|
|
50
67
|
model.items.map((item) => {
|
|
@@ -68,6 +85,62 @@ export function CockpitNav({ model, colorEnabled = true }) {
|
|
|
68
85
|
);
|
|
69
86
|
}
|
|
70
87
|
|
|
88
|
+
function stripLabel(item, { compact }) {
|
|
89
|
+
if (!compact) return item.label;
|
|
90
|
+
return item.label.split(/[&·]/)[0].trim().split(/\s+/)[0];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Horizontal navigation strip under the top bar. */
|
|
94
|
+
export function CockpitNavStrip({
|
|
95
|
+
model,
|
|
96
|
+
colorEnabled = true,
|
|
97
|
+
layoutMode = "compact",
|
|
98
|
+
focused = false
|
|
99
|
+
}) {
|
|
100
|
+
const compact = layoutMode !== "wide";
|
|
101
|
+
const parts = model.items.map((item) => {
|
|
102
|
+
const label = stripLabel(item, { compact });
|
|
103
|
+
const suffix = item.current && !item.selected ? "*" : "";
|
|
104
|
+
const color = item.focused
|
|
105
|
+
? (colorEnabled ? COCKPIT_COLORS.primary : undefined)
|
|
106
|
+
: item.selected
|
|
107
|
+
? (colorEnabled ? COCKPIT_COLORS.secondary : undefined)
|
|
108
|
+
: item.current
|
|
109
|
+
? (colorEnabled ? COCKPIT_COLORS.muted : undefined)
|
|
110
|
+
: (colorEnabled ? COCKPIT_COLORS.muted : undefined);
|
|
111
|
+
return React.createElement(Text, {
|
|
112
|
+
key: item.id,
|
|
113
|
+
bold: item.focused || item.selected,
|
|
114
|
+
color
|
|
115
|
+
}, `${item.marker}${label}${suffix}`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const joined = [];
|
|
119
|
+
parts.forEach((node, index) => {
|
|
120
|
+
if (index > 0) {
|
|
121
|
+
joined.push(React.createElement(Text, {
|
|
122
|
+
key: `sep-${index}`,
|
|
123
|
+
color: colorEnabled ? COCKPIT_COLORS.muted : undefined
|
|
124
|
+
}, " · "));
|
|
125
|
+
}
|
|
126
|
+
joined.push(node);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return React.createElement(Box, {
|
|
130
|
+
flexDirection: "column",
|
|
131
|
+
width: "100%",
|
|
132
|
+
borderStyle: "single",
|
|
133
|
+
borderColor: focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.muted,
|
|
134
|
+
paddingX: 1
|
|
135
|
+
},
|
|
136
|
+
React.createElement(Box, { flexDirection: "row", flexWrap: "wrap" }, ...joined),
|
|
137
|
+
model.explanation && React.createElement(Text, {
|
|
138
|
+
color: COCKPIT_COLORS.muted
|
|
139
|
+
}, model.explanation)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Kept for model tests; no longer rendered in the shell. */
|
|
71
144
|
export function CockpitSystemStrip({ model, colorEnabled = true }) {
|
|
72
145
|
return React.createElement(Box, { flexDirection: "column" },
|
|
73
146
|
model.rows.map((row) =>
|
|
@@ -83,56 +156,48 @@ export function CockpitSystemStrip({ model, colorEnabled = true }) {
|
|
|
83
156
|
);
|
|
84
157
|
}
|
|
85
158
|
|
|
86
|
-
export function CockpitFooter({ model }) {
|
|
87
|
-
|
|
159
|
+
export function CockpitFooter({ model, columns = 80 }) {
|
|
160
|
+
const width = Math.max(24, Math.min(Number(columns) || 80, 120));
|
|
161
|
+
const bar = Math.max(20, width - 2);
|
|
162
|
+
return React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
88
163
|
React.createElement(Text, { color: COCKPIT_COLORS.muted },
|
|
89
|
-
`├${"─".repeat(
|
|
164
|
+
`├${"─".repeat(bar)}┤`
|
|
90
165
|
),
|
|
91
166
|
React.createElement(Text, { color: COCKPIT_COLORS.muted }, `│ ${model.text}`),
|
|
92
167
|
React.createElement(Text, { color: COCKPIT_COLORS.muted },
|
|
93
|
-
`╰${"─".repeat(
|
|
168
|
+
`╰${"─".repeat(bar)}╯`
|
|
94
169
|
)
|
|
95
170
|
);
|
|
96
171
|
}
|
|
97
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Responsive single-panel shell: TopBar → NavStrip → Main → Footer.
|
|
175
|
+
* SYSTEM side column removed (diagnostics via Enter/detail in later slices).
|
|
176
|
+
*/
|
|
98
177
|
export function CockpitShell({
|
|
99
178
|
topBar,
|
|
100
179
|
footer,
|
|
101
180
|
layoutMode,
|
|
102
181
|
nav,
|
|
103
|
-
system,
|
|
104
182
|
navFocused,
|
|
105
183
|
contentFocused,
|
|
106
|
-
systemFocused,
|
|
107
184
|
colorEnabled = true,
|
|
185
|
+
columns = 80,
|
|
108
186
|
children
|
|
109
187
|
}) {
|
|
110
|
-
const showNav = layoutMode === "wide" || layoutMode === "compact";
|
|
111
|
-
const showSystem = layoutMode === "wide";
|
|
112
|
-
|
|
113
188
|
return React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
114
189
|
React.createElement(CockpitTopBar, { model: topBar, colorEnabled }),
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
focused: contentFocused,
|
|
128
|
-
width: showSystem ? 48 : showNav ? 56 : "100%"
|
|
129
|
-
}, children),
|
|
130
|
-
showSystem && React.createElement(CockpitPanel, {
|
|
131
|
-
title: system?.title ?? "SYSTEM",
|
|
132
|
-
focused: systemFocused,
|
|
133
|
-
width: 20
|
|
134
|
-
}, React.createElement(CockpitSystemStrip, { model: system, colorEnabled }))
|
|
135
|
-
),
|
|
136
|
-
React.createElement(CockpitFooter, { model: footer })
|
|
190
|
+
nav && React.createElement(CockpitNavStrip, {
|
|
191
|
+
model: nav,
|
|
192
|
+
colorEnabled,
|
|
193
|
+
layoutMode,
|
|
194
|
+
focused: navFocused
|
|
195
|
+
}),
|
|
196
|
+
React.createElement(CockpitPanel, {
|
|
197
|
+
title: undefined,
|
|
198
|
+
focused: contentFocused,
|
|
199
|
+
width: "100%"
|
|
200
|
+
}, children),
|
|
201
|
+
React.createElement(CockpitFooter, { model: footer, columns })
|
|
137
202
|
);
|
|
138
203
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ALERT_STATES } from "../runtime/alerts/alert-types.js";
|
|
2
|
+
|
|
3
|
+
export function formatAlertsHeadline(alerts = null) {
|
|
4
|
+
if (alerts == null) return { count: null, headline: "Alert data unavailable" };
|
|
5
|
+
const count = alerts.filter((alert) => alert.state === ALERT_STATES.OPEN).length;
|
|
6
|
+
return { count, headline: count === 0 ? "None pending" : `${count} pending` };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatAlertListLines(alerts = null) {
|
|
10
|
+
if (alerts == null) return ["Alert data unavailable"];
|
|
11
|
+
const open = alerts.filter((alert) => alert.state === ALERT_STATES.OPEN);
|
|
12
|
+
if (open.length === 0) return ["No pending alerts."];
|
|
13
|
+
return open.map((alert) => {
|
|
14
|
+
const when = String(alert.createdAt ?? "").slice(0, 16).replace("T", " ");
|
|
15
|
+
return `${alert.severity} · ${alert.title} · ${when || "unknown time"}`;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatAlertDetailLines(alert) {
|
|
20
|
+
if (!alert) return ["Alert not found."];
|
|
21
|
+
return [
|
|
22
|
+
"SUMMARY",
|
|
23
|
+
`${alert.severity} · ${alert.state} · ${alert.kind}`,
|
|
24
|
+
alert.title,
|
|
25
|
+
alert.summary || "No summary.",
|
|
26
|
+
"",
|
|
27
|
+
"DETAILS",
|
|
28
|
+
`Id · ${alert.alertId}`,
|
|
29
|
+
`Source · ${alert.source ?? "n/a"} · Created · ${alert.createdAt ?? "n/a"}`
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function selectAlertFromList(alerts = null, index = 0) {
|
|
34
|
+
if (!Array.isArray(alerts)) return null;
|
|
35
|
+
return alerts.filter((alert) => alert.state === ALERT_STATES.OPEN)[index] ?? null;
|
|
36
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatConfirmPath } from "./cockpit-path-label.js";
|
|
2
2
|
|
|
3
3
|
export const CHANGES_PHASE = Object.freeze({
|
|
4
4
|
IDLE: "idle",
|
|
@@ -62,53 +62,77 @@ export function reduceChangesAction(state, action) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
function healthLabel(kind) {
|
|
66
|
+
return String(kind ?? "unknown").replaceAll("_", " ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Governance surface: status, coverage, recommended action first.
|
|
71
|
+
* Paths only when detail=true or during confirm (home-relative / distinct).
|
|
72
|
+
*/
|
|
73
|
+
export function formatChangesActionLines({
|
|
74
|
+
snapshot,
|
|
75
|
+
changesAction,
|
|
76
|
+
detail = false,
|
|
77
|
+
homeDir = null
|
|
78
|
+
} = {}) {
|
|
67
79
|
const phase = changesAction?.phase ?? CHANGES_PHASE.IDLE;
|
|
68
|
-
const
|
|
80
|
+
const coverage = snapshot?.coverage ?? {};
|
|
81
|
+
const diff = snapshot?.diff;
|
|
82
|
+
const cta = snapshot?.cta;
|
|
83
|
+
const pending = diff?.hasChanges
|
|
84
|
+
? (diff.changeCount ?? diff.changes?.length ?? 0)
|
|
85
|
+
: 0;
|
|
86
|
+
const lines = [
|
|
87
|
+
"STATUS",
|
|
88
|
+
`${healthLabel(snapshot?.health)}${pending > 0 ? ` · ${pending} pending` : " · drift clean"}`,
|
|
89
|
+
"",
|
|
90
|
+
"COVERAGE",
|
|
91
|
+
`${coverage.governedAgents ?? 0}/${coverage.detectedAgents ?? 0} agents governed · ${coverage.components ?? 0} components`,
|
|
92
|
+
"",
|
|
93
|
+
"NEXT",
|
|
94
|
+
cta?.title ?? "Review governance when ready"
|
|
95
|
+
];
|
|
96
|
+
if (cta?.detail) lines.push(cta.detail);
|
|
69
97
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
budgets: snapshot?.budgets ?? null
|
|
74
|
-
});
|
|
75
|
-
if (proposalLines[0] !== "No proposals targeting this view.") {
|
|
76
|
-
lines.push(...proposalLines, "");
|
|
98
|
+
if (changesAction?.message) lines.push("", changesAction.message);
|
|
99
|
+
if (changesAction?.error && changesAction.error !== "setup-required") {
|
|
100
|
+
lines.push(`Error: ${changesAction.error}`);
|
|
77
101
|
}
|
|
78
102
|
|
|
79
|
-
if (changesAction?.message) lines.push(changesAction.message);
|
|
80
|
-
if (changesAction?.error && changesAction.error !== "setup-required") lines.push(`Error: ${changesAction.error}`);
|
|
81
|
-
|
|
82
103
|
const preview = changesAction?.preview;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
const planned = preview?.hasChanges
|
|
105
|
+
? (preview.changes ?? [])
|
|
106
|
+
: (diff?.hasChanges && !preview ? (diff.changes ?? []) : []);
|
|
107
|
+
if (planned.length > 0) {
|
|
108
|
+
lines.push("", `${planned.length} planned change(s)`);
|
|
109
|
+
const showDetail = detail || phase === CHANGES_PHASE.CONFIRMING;
|
|
110
|
+
if (showDetail) {
|
|
111
|
+
lines.push("DETAILS");
|
|
112
|
+
const limit = detail ? 12 : 3;
|
|
113
|
+
for (const change of planned.slice(0, limit)) {
|
|
114
|
+
lines.push(
|
|
115
|
+
`${change.action ?? change.kind} · ${formatConfirmPath(change.target, homeDir)}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (planned.length > limit) lines.push(`… ${planned.length - limit} more`);
|
|
98
119
|
}
|
|
99
120
|
} else if (!diff) {
|
|
100
|
-
lines.push("Scan did not include diff yet. Press R to re-scan.");
|
|
121
|
+
lines.push("", "Scan did not include diff yet. Press R to re-scan.");
|
|
122
|
+
} else if (!diff.installed) {
|
|
123
|
+
lines.push("", diff.summary ?? "Setup required before changes can be previewed.");
|
|
124
|
+
} else if (!diff.hasChanges && !changesAction?.message) {
|
|
125
|
+
lines.push("", diff.summary ?? "No pending governance changes.");
|
|
101
126
|
}
|
|
102
127
|
|
|
103
128
|
const receipt = changesAction?.receipt;
|
|
104
129
|
if (receipt) {
|
|
105
|
-
lines.push("", `
|
|
106
|
-
if (receipt.backups?.length) lines.push(
|
|
107
|
-
if (receipt.checksBefore && receipt.checksAfter) {
|
|
130
|
+
lines.push("", `Result · ${receipt.action}${receipt.partial ? " (partial)" : ""}`);
|
|
131
|
+
if (receipt.backups?.length) lines.push(`${receipt.backups.length} backup(s) retained`);
|
|
132
|
+
if (detail && receipt.checksBefore && receipt.checksAfter) {
|
|
133
|
+
lines.push("DETAILS");
|
|
108
134
|
lines.push(`Checks · before ok=${receipt.checksBefore.ok} → after ok=${receipt.checksAfter.ok}`);
|
|
109
135
|
}
|
|
110
|
-
if (receipt.integrations) lines.push(`Integrations · ${receipt.integrations.status ?? "n/a"}`);
|
|
111
|
-
if (receipt.partial) lines.push("Partial evidence retained — success not claimed.");
|
|
112
136
|
}
|
|
113
137
|
|
|
114
138
|
if (phase === CHANGES_PHASE.IDLE || phase === CHANGES_PHASE.COMPLETED || phase === CHANGES_PHASE.FAILED) {
|