@kal-elsam/kairo-runtime 0.9.0 → 0.11.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 +14 -8
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +3 -3
- package/scripts/ux-prototype-tty.mjs +9 -0
- package/src/global/control-plane-proposals.js +2 -1
- package/src/global/control-plane-snapshot.js +1 -1
- package/src/global/ink/brand/wordmark.js +50 -0
- package/src/global/ink/cockpit/primitives.js +72 -50
- package/src/global/ink/cockpit-changes.js +2 -2
- package/src/global/ink/cockpit-control-center.js +12 -64
- package/src/global/ink/cockpit-controller.js +41 -5
- package/src/global/ink/cockpit-enter.js +1 -0
- package/src/global/ink/cockpit-models.js +14 -7
- package/src/global/ink/cockpit-palette.js +18 -7
- package/src/global/ink/cockpit-recovery.js +9 -6
- package/src/global/ink/cockpit-usage.js +111 -0
- package/src/global/ink/cockpit-views.js +70 -97
- package/src/global/ink/orchestrator-app.js +44 -4
- package/src/global/ink/setup-app.js +55 -72
- package/src/global/ink/setup-state.js +16 -0
- package/src/global/ink/theme.js +40 -8
- package/src/global/ink/ux/live-activity.js +191 -0
- package/src/global/ink/ux/live-alerts.js +156 -0
- package/src/global/ink/ux/live-governance.js +191 -0
- package/src/global/ink/ux/live-orchestration.js +185 -0
- package/src/global/ink/ux/live-overview.js +175 -0
- package/src/global/ink/ux/live-settings.js +186 -0
- package/src/global/ink/ux/live-setup.js +160 -0
- package/src/global/ink/ux/live-usage.js +49 -0
- package/src/global/ink/ux/semantic.js +101 -0
- package/src/global/ink/ux/task-flow-app.js +85 -0
- package/src/global/ink/ux/task-flow.js +173 -0
- package/src/global/orchestrator.js +37 -20
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/** Live semantic Governance. Ownership: Callout=status · Confirm/primary=action · footer=keys. */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { formatConfirmPath } from "../cockpit-path-label.js";
|
|
5
|
+
import { CHANGES_PHASE } from "../cockpit-changes.js";
|
|
6
|
+
import { LAYOUT_MODES } from "../layout.js";
|
|
7
|
+
import { ActionList, Callout, Confirm, Details, ViewTitle } from "./semantic.js";
|
|
8
|
+
import { mapHealthTone } from "./live-overview.js";
|
|
9
|
+
|
|
10
|
+
function healthLabel(kind) {
|
|
11
|
+
return String(kind ?? "unknown").replaceAll("_", " ");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function plannedChanges(snapshot, changesAction) {
|
|
15
|
+
const preview = changesAction?.preview;
|
|
16
|
+
const diff = snapshot?.diff;
|
|
17
|
+
if (preview?.hasChanges) return preview.changes ?? [];
|
|
18
|
+
if (diff?.hasChanges && !preview) return diff.changes ?? [];
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function phaseTone(phase, healthKind) {
|
|
23
|
+
if (phase === CHANGES_PHASE.FAILED) return "danger";
|
|
24
|
+
if (phase === CHANGES_PHASE.COMPLETED) return "ready";
|
|
25
|
+
if (phase === CHANGES_PHASE.CONFIRMING || phase === CHANGES_PHASE.PREVIEWING || phase === CHANGES_PHASE.APPLYING) {
|
|
26
|
+
return "warn";
|
|
27
|
+
}
|
|
28
|
+
return mapHealthTone(healthKind);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function phaseTitle(phase, snapshot, changesAction) {
|
|
32
|
+
if (phase === CHANGES_PHASE.PREVIEWING) return "Previewing";
|
|
33
|
+
if (phase === CHANGES_PHASE.CONFIRMING) return "Confirm apply";
|
|
34
|
+
if (phase === CHANGES_PHASE.APPLYING) return "Applying";
|
|
35
|
+
if (phase === CHANGES_PHASE.COMPLETED) return "Apply complete";
|
|
36
|
+
if (phase === CHANGES_PHASE.FAILED) {
|
|
37
|
+
return changesAction?.error === "setup-required" ? "Setup required" : "Governance failed";
|
|
38
|
+
}
|
|
39
|
+
const pending = snapshot?.diff?.hasChanges
|
|
40
|
+
? (snapshot.diff.changeCount ?? snapshot.diff.changes?.length ?? 0)
|
|
41
|
+
: 0;
|
|
42
|
+
return `${healthLabel(snapshot?.health)}${pending > 0 ? ` · ${pending} pending` : " · drift clean"}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Compact/minimal: 3 paths; wide: 12. */
|
|
46
|
+
export function detailsPathLimit(layoutMode = LAYOUT_MODES.COMPACT) {
|
|
47
|
+
return layoutMode === LAYOUT_MODES.WIDE ? 12 : 3;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function calloutBody(phase, changesAction) {
|
|
51
|
+
if (phase !== CHANGES_PHASE.FAILED) return "";
|
|
52
|
+
if (changesAction?.error === "setup-required") return "Not configured — open Overview and run setup.";
|
|
53
|
+
if (changesAction?.error) return `Error · ${changesAction.error}`;
|
|
54
|
+
return changesAction?.message ?? "";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildDetailsLines(planned, showPaths, changesAction, homeDir, pathLimit) {
|
|
58
|
+
if (!showPaths) return [];
|
|
59
|
+
if (planned.length > 0) {
|
|
60
|
+
const lines = planned.slice(0, pathLimit).map((c) =>
|
|
61
|
+
`${c.action ?? c.kind} · ${formatConfirmPath(c.target, homeDir)}`
|
|
62
|
+
);
|
|
63
|
+
if (planned.length > pathLimit) lines.push(`… ${planned.length - pathLimit} more`);
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
66
|
+
const receipt = changesAction?.receipt;
|
|
67
|
+
if (receipt?.checksBefore && receipt?.checksAfter) {
|
|
68
|
+
return [`Checks · before ok=${receipt.checksBefore.ok} → after ok=${receipt.checksAfter.ok}`];
|
|
69
|
+
}
|
|
70
|
+
return ["No path evidence on this scan."];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Pure adapter: paths only in detailsLines (confirming or Details open). */
|
|
74
|
+
export function adaptGovernanceModel({
|
|
75
|
+
snapshot = null,
|
|
76
|
+
changesAction = null,
|
|
77
|
+
homeDir = null,
|
|
78
|
+
detailsOpen = false,
|
|
79
|
+
layoutMode = LAYOUT_MODES.COMPACT
|
|
80
|
+
} = {}) {
|
|
81
|
+
const phase = changesAction?.phase ?? CHANGES_PHASE.IDLE;
|
|
82
|
+
const coverage = snapshot?.coverage ?? {};
|
|
83
|
+
const cta = snapshot?.cta;
|
|
84
|
+
const planned = plannedChanges(snapshot, changesAction);
|
|
85
|
+
const showPaths = detailsOpen || phase === CHANGES_PHASE.CONFIRMING;
|
|
86
|
+
const confirming = phase === CHANGES_PHASE.CONFIRMING;
|
|
87
|
+
const working = phase === CHANGES_PHASE.PREVIEWING || phase === CHANGES_PHASE.APPLYING;
|
|
88
|
+
const actionable = phase === CHANGES_PHASE.IDLE
|
|
89
|
+
|| phase === CHANGES_PHASE.COMPLETED
|
|
90
|
+
|| phase === CHANGES_PHASE.FAILED;
|
|
91
|
+
|
|
92
|
+
const metrics = [
|
|
93
|
+
{
|
|
94
|
+
id: "coverage",
|
|
95
|
+
label: `Coverage · ${coverage.governedAgents ?? 0}/${coverage.detectedAgents ?? 0} agents · ${coverage.components ?? 0} components`
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "planned",
|
|
99
|
+
label: planned.length > 0
|
|
100
|
+
? `Planned · ${planned.length} change(s)`
|
|
101
|
+
: (snapshot?.diff?.summary ?? "No pending governance changes.")
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
if (changesAction?.receipt) {
|
|
105
|
+
const r = changesAction.receipt;
|
|
106
|
+
metrics.push({
|
|
107
|
+
id: "receipt",
|
|
108
|
+
label: `Result · ${r.action}${r.partial ? " (partial)" : ""}${r.backups?.length ? ` · ${r.backups.length} backup(s)` : ""}`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
title: "Governance",
|
|
114
|
+
phase,
|
|
115
|
+
callout: {
|
|
116
|
+
tone: phaseTone(phase, snapshot?.health),
|
|
117
|
+
title: phaseTitle(phase, snapshot, changesAction),
|
|
118
|
+
body: calloutBody(phase, changesAction)
|
|
119
|
+
},
|
|
120
|
+
primary: confirming || working
|
|
121
|
+
? null
|
|
122
|
+
: {
|
|
123
|
+
label: cta?.title ?? "Review governance when ready",
|
|
124
|
+
detail: actionable ? (cta?.detail ?? null) : null
|
|
125
|
+
},
|
|
126
|
+
confirm: confirming
|
|
127
|
+
? {
|
|
128
|
+
summary: planned.length > 0
|
|
129
|
+
? `Apply ${planned.length} planned change(s).`
|
|
130
|
+
: "Apply confirmed governance preview.",
|
|
131
|
+
primaryLabel: "Apply"
|
|
132
|
+
}
|
|
133
|
+
: null,
|
|
134
|
+
metrics,
|
|
135
|
+
details: buildDetailsLines(planned, showPaths, changesAction, homeDir, detailsPathLimit(layoutMode)),
|
|
136
|
+
detailsOpen: showPaths
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function SemanticGovernancePanel({
|
|
141
|
+
snapshot = null,
|
|
142
|
+
changesAction = null,
|
|
143
|
+
homeDir = null,
|
|
144
|
+
detailsOpen = false,
|
|
145
|
+
layoutMode = LAYOUT_MODES.COMPACT,
|
|
146
|
+
colorEnabled = true,
|
|
147
|
+
unicode = true
|
|
148
|
+
}) {
|
|
149
|
+
const view = adaptGovernanceModel({
|
|
150
|
+
snapshot, changesAction, homeDir, detailsOpen, layoutMode
|
|
151
|
+
});
|
|
152
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
153
|
+
React.createElement(ViewTitle, { colorEnabled }, view.title),
|
|
154
|
+
React.createElement(Callout, {
|
|
155
|
+
tone: view.callout.tone,
|
|
156
|
+
title: view.callout.title,
|
|
157
|
+
body: view.callout.body || undefined,
|
|
158
|
+
colorEnabled,
|
|
159
|
+
compact: true
|
|
160
|
+
}),
|
|
161
|
+
view.confirm
|
|
162
|
+
? React.createElement(Confirm, {
|
|
163
|
+
summary: view.confirm.summary,
|
|
164
|
+
primaryLabel: view.confirm.primaryLabel,
|
|
165
|
+
focused: false,
|
|
166
|
+
colorEnabled,
|
|
167
|
+
mark: " "
|
|
168
|
+
})
|
|
169
|
+
: view.primary
|
|
170
|
+
? React.createElement(Box, { flexDirection: "column" },
|
|
171
|
+
React.createElement(Text, { bold: true }, ` ${view.primary.label}`),
|
|
172
|
+
view.primary.detail ? React.createElement(Text, null, view.primary.detail) : null
|
|
173
|
+
)
|
|
174
|
+
: null,
|
|
175
|
+
React.createElement(ActionList, {
|
|
176
|
+
items: view.metrics,
|
|
177
|
+
selectedIndex: -1,
|
|
178
|
+
focused: false,
|
|
179
|
+
colorEnabled,
|
|
180
|
+
unicode
|
|
181
|
+
}),
|
|
182
|
+
React.createElement(Details, {
|
|
183
|
+
open: view.detailsOpen,
|
|
184
|
+
summary: "Details",
|
|
185
|
+
lines: view.details.length > 0 ? view.details : ["No path evidence on this scan."],
|
|
186
|
+
colorEnabled,
|
|
187
|
+
focused: false,
|
|
188
|
+
mark: " "
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live semantic Orchestration lists (Hub / Active / History / Reviews).
|
|
3
|
+
* ActionList owns the only focus mark; windowSlice keeps full domain navigable.
|
|
4
|
+
* Enter destinations unchanged — adapter exposes focused identity for listIndex.
|
|
5
|
+
*/
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
import { LAYOUT_MODES } from "../layout.js";
|
|
9
|
+
import { ORCHESTRATOR_VIEWS, formatRunLines } from "../orchestrator-state.js";
|
|
10
|
+
import {
|
|
11
|
+
RUNS_HUB_ITEMS, formatOrchestrationStatus, formatRunsHubLines
|
|
12
|
+
} from "../cockpit-runs.js";
|
|
13
|
+
import { formatReviewListLines } from "../cockpit-reviews.js";
|
|
14
|
+
import { ActionList, Callout, ViewTitle } from "./semantic.js";
|
|
15
|
+
import { windowSlice } from "./live-activity.js";
|
|
16
|
+
|
|
17
|
+
export function orchestrationListLimit(layoutMode = LAYOUT_MODES.COMPACT) {
|
|
18
|
+
return layoutMode === LAYOUT_MODES.WIDE ? 8 : 3;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function emptyPack(label) {
|
|
22
|
+
return {
|
|
23
|
+
items: [{ id: "empty", label }],
|
|
24
|
+
selectedIndex: -1,
|
|
25
|
+
focusedId: null,
|
|
26
|
+
total: 0,
|
|
27
|
+
start: 0,
|
|
28
|
+
isEmpty: true
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function windowDomain(domain, listIndex, limit, toItem, emptyLabel) {
|
|
33
|
+
const windowed = windowSlice(domain, listIndex, limit);
|
|
34
|
+
if (windowed.items.length === 0) return emptyPack(emptyLabel);
|
|
35
|
+
const safe = Math.min(Math.max(0, listIndex), domain.length - 1);
|
|
36
|
+
return {
|
|
37
|
+
items: windowed.items.map((entry, i) => toItem(entry, windowed.start + i)),
|
|
38
|
+
selectedIndex: windowed.selectedIndex,
|
|
39
|
+
focusedId: toItem(domain[safe], safe).id,
|
|
40
|
+
total: domain.length,
|
|
41
|
+
start: windowed.start,
|
|
42
|
+
isEmpty: false
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hubPack(listIndex) {
|
|
47
|
+
const labels = formatRunsHubLines(RUNS_HUB_ITEMS);
|
|
48
|
+
const items = RUNS_HUB_ITEMS.map((item, i) => ({
|
|
49
|
+
id: item.id,
|
|
50
|
+
label: labels[i] ?? item.label
|
|
51
|
+
}));
|
|
52
|
+
const safe = Math.min(Math.max(0, listIndex), items.length - 1);
|
|
53
|
+
return {
|
|
54
|
+
items,
|
|
55
|
+
selectedIndex: safe,
|
|
56
|
+
focusedId: items[safe]?.id ?? null,
|
|
57
|
+
total: items.length,
|
|
58
|
+
start: 0,
|
|
59
|
+
isEmpty: false
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function runPack(runs, listIndex, limit, emptyLabel) {
|
|
64
|
+
return windowDomain(runs, listIndex, limit, (run, absoluteIndex) => ({
|
|
65
|
+
id: run.runId ?? `run-${absoluteIndex}`,
|
|
66
|
+
label: formatRunLines([run], { readable: true })[0]
|
|
67
|
+
}), emptyLabel);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function reviewPack(reviews, listIndex, limit) {
|
|
71
|
+
const empty = "No review receipts yet. Run kairo review --agent codex|pi.";
|
|
72
|
+
return windowDomain(reviews, listIndex, limit, (receipt, absoluteIndex) => ({
|
|
73
|
+
id: receipt.reviewId ?? `review-${absoluteIndex}`,
|
|
74
|
+
label: formatReviewListLines([receipt])[0]
|
|
75
|
+
}), empty);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Pure adapter: Hub · Active · History · Reviews. */
|
|
79
|
+
export function adaptOrchestrationModel({
|
|
80
|
+
view = ORCHESTRATOR_VIEWS.RUNS,
|
|
81
|
+
dashboard = null,
|
|
82
|
+
reviews = [],
|
|
83
|
+
listIndex = 0,
|
|
84
|
+
layoutMode = LAYOUT_MODES.COMPACT
|
|
85
|
+
} = {}) {
|
|
86
|
+
const limit = orchestrationListLimit(layoutMode);
|
|
87
|
+
const active = dashboard?.activeRuns ?? [];
|
|
88
|
+
const recent = dashboard?.recentRuns ?? [];
|
|
89
|
+
const reviewList = Array.isArray(reviews) ? reviews : [];
|
|
90
|
+
const status = formatOrchestrationStatus({
|
|
91
|
+
active: active.length, recent: recent.length, reviews: reviewList.length
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let title = "Orchestration";
|
|
95
|
+
let callout = { tone: active.length > 0 ? "warn" : "info", title: status, body: "" };
|
|
96
|
+
let list = hubPack(listIndex);
|
|
97
|
+
|
|
98
|
+
if (view === ORCHESTRATOR_VIEWS.RUNS) {
|
|
99
|
+
callout = {
|
|
100
|
+
tone: active.length > 0 ? "warn" : "info",
|
|
101
|
+
title: status,
|
|
102
|
+
body: "Choose Active runs, History, Reviews, or New run."
|
|
103
|
+
};
|
|
104
|
+
list = hubPack(listIndex);
|
|
105
|
+
} else if (view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS) {
|
|
106
|
+
title = "Active runs";
|
|
107
|
+
list = runPack(
|
|
108
|
+
active, listIndex, limit,
|
|
109
|
+
"No runs executing. Governance first — launch only after setup/repairs."
|
|
110
|
+
);
|
|
111
|
+
callout = {
|
|
112
|
+
tone: list.isEmpty ? "info" : "warn",
|
|
113
|
+
title: list.isEmpty ? "None active" : `${list.total} active`,
|
|
114
|
+
body: list.isEmpty
|
|
115
|
+
? "Esc back to Orchestration"
|
|
116
|
+
: "Enter opens detail"
|
|
117
|
+
};
|
|
118
|
+
} else if (view === ORCHESTRATOR_VIEWS.RECENT_RUNS) {
|
|
119
|
+
title = "Run history";
|
|
120
|
+
list = runPack(recent, listIndex, limit, "No completed runs yet.");
|
|
121
|
+
callout = {
|
|
122
|
+
tone: "info",
|
|
123
|
+
title: list.isEmpty ? "None completed" : `${list.total} completed`,
|
|
124
|
+
body: list.isEmpty
|
|
125
|
+
? "Esc back to Orchestration"
|
|
126
|
+
: "Enter opens detail"
|
|
127
|
+
};
|
|
128
|
+
} else if (view === ORCHESTRATOR_VIEWS.REVIEWS) {
|
|
129
|
+
title = "Reviews";
|
|
130
|
+
list = reviewPack(reviewList, listIndex, limit);
|
|
131
|
+
callout = {
|
|
132
|
+
tone: "info",
|
|
133
|
+
title: list.isEmpty ? "None yet" : `${list.total} receipts`,
|
|
134
|
+
body: list.isEmpty
|
|
135
|
+
? "Launch via kairo review --agent codex|pi."
|
|
136
|
+
: "Read-only · Enter opens detail"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
title,
|
|
142
|
+
view,
|
|
143
|
+
callout,
|
|
144
|
+
items: list.items,
|
|
145
|
+
selectedIndex: list.selectedIndex,
|
|
146
|
+
focusedId: list.focusedId,
|
|
147
|
+
total: list.total,
|
|
148
|
+
start: list.start,
|
|
149
|
+
isEmpty: list.isEmpty,
|
|
150
|
+
listLimit: limit
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function SemanticOrchestrationPanel({
|
|
155
|
+
view = ORCHESTRATOR_VIEWS.RUNS,
|
|
156
|
+
dashboard = null,
|
|
157
|
+
reviews = [],
|
|
158
|
+
listIndex = 0,
|
|
159
|
+
layoutMode = LAYOUT_MODES.COMPACT,
|
|
160
|
+
contentFocused = false,
|
|
161
|
+
colorEnabled = true,
|
|
162
|
+
unicode = true
|
|
163
|
+
}) {
|
|
164
|
+
const model = adaptOrchestrationModel({
|
|
165
|
+
view, dashboard, reviews, listIndex, layoutMode
|
|
166
|
+
});
|
|
167
|
+
const listFocused = contentFocused && !model.isEmpty;
|
|
168
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
169
|
+
React.createElement(ViewTitle, { colorEnabled }, model.title),
|
|
170
|
+
React.createElement(Callout, {
|
|
171
|
+
tone: model.callout.tone,
|
|
172
|
+
title: model.callout.title,
|
|
173
|
+
body: model.callout.body || undefined,
|
|
174
|
+
colorEnabled,
|
|
175
|
+
compact: true
|
|
176
|
+
}),
|
|
177
|
+
React.createElement(ActionList, {
|
|
178
|
+
items: model.items,
|
|
179
|
+
selectedIndex: model.selectedIndex,
|
|
180
|
+
focused: listFocused,
|
|
181
|
+
colorEnabled,
|
|
182
|
+
unicode
|
|
183
|
+
})
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live semantic Overview for Cockpit HOME — product cover.
|
|
3
|
+
* Nav owns the sole focus mark — this panel never renders `>`.
|
|
4
|
+
* ASCII wordmark only here (wide/compact); minimal is textual.
|
|
5
|
+
*/
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
import { CONTROL_PLANE_HEALTH } from "../../control-plane-snapshot.js";
|
|
9
|
+
import { LAYOUT_MODES } from "../layout.js";
|
|
10
|
+
import { COCKPIT_COLORS } from "../theme.js";
|
|
11
|
+
import {
|
|
12
|
+
overviewBrandTitle,
|
|
13
|
+
shouldShowWordmark,
|
|
14
|
+
wordmarkLines
|
|
15
|
+
} from "../brand/wordmark.js";
|
|
16
|
+
import { ActionList, Callout, Details } from "./semantic.js";
|
|
17
|
+
|
|
18
|
+
const DESTINATION_LABELS = {
|
|
19
|
+
setup: "Setup",
|
|
20
|
+
changes: "Governance",
|
|
21
|
+
ides: "Agents",
|
|
22
|
+
runs: "Orchestration",
|
|
23
|
+
"control-center": "Overview",
|
|
24
|
+
activity: "Activity",
|
|
25
|
+
usage: "Usage",
|
|
26
|
+
profile: "Settings"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function mapHealthTone(kind) {
|
|
30
|
+
switch (kind) {
|
|
31
|
+
case CONTROL_PLANE_HEALTH.CHECK_FAILED:
|
|
32
|
+
return "danger";
|
|
33
|
+
case CONTROL_PLANE_HEALTH.ACTION_REQUIRED:
|
|
34
|
+
case CONTROL_PLANE_HEALTH.NOT_CONFIGURED:
|
|
35
|
+
case CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES:
|
|
36
|
+
return "warn";
|
|
37
|
+
case CONTROL_PLANE_HEALTH.HEALTHY:
|
|
38
|
+
return "ready";
|
|
39
|
+
default:
|
|
40
|
+
return "warn";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function humanizeDestination(destination) {
|
|
45
|
+
if (!destination) return null;
|
|
46
|
+
return DESTINATION_LABELS[destination] ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Safe Details lines only — never invent paths/IDs; honest empty when none. */
|
|
50
|
+
export function buildOverviewDetails(model = {}) {
|
|
51
|
+
const lines = [];
|
|
52
|
+
const next = model.nextAction ?? model.cta ?? {};
|
|
53
|
+
const dest = humanizeDestination(next.destination);
|
|
54
|
+
if (dest) lines.push(`Next destination · ${dest}`);
|
|
55
|
+
if (typeof model.alerts?.count === "number") {
|
|
56
|
+
lines.push(`Open alerts · ${model.alerts.count}`);
|
|
57
|
+
}
|
|
58
|
+
if (lines.length === 0) return ["No extra evidence beyond the metrics above."];
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pure adapter: buildControlCenterModel → semantic overview props.
|
|
64
|
+
* Callout / CTA / metrics never include paths or IDs.
|
|
65
|
+
*/
|
|
66
|
+
export function adaptControlCenterToOverview(model = {}) {
|
|
67
|
+
const status = model.status ?? model.health ?? {};
|
|
68
|
+
const next = model.nextAction ?? model.cta ?? {};
|
|
69
|
+
return {
|
|
70
|
+
title: model.title ?? "Overview",
|
|
71
|
+
callout: {
|
|
72
|
+
tone: mapHealthTone(status.kind),
|
|
73
|
+
title: status.label ?? "Unknown",
|
|
74
|
+
body: status.summaryLine ?? ""
|
|
75
|
+
},
|
|
76
|
+
primary: {
|
|
77
|
+
label: next.actionTitle ?? "Review control plane",
|
|
78
|
+
detail: next.actionDetail || null,
|
|
79
|
+
hint: next.enterHint ?? null
|
|
80
|
+
},
|
|
81
|
+
metrics: [
|
|
82
|
+
{ id: "activity", label: `Activity · ${model.activity?.headline ?? "Idle"}` },
|
|
83
|
+
{ id: "alerts", label: `Alerts · ${model.alerts?.headline ?? "Alert data unavailable"}` },
|
|
84
|
+
{ id: "tokens", label: `Tokens · ${model.tokens?.headline ?? "Data unavailable"}` }
|
|
85
|
+
],
|
|
86
|
+
details: buildOverviewDetails(model)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderWordmark({ layoutMode, colorEnabled = true, unicode = true }) {
|
|
91
|
+
const lines = wordmarkLines(layoutMode, { unicode });
|
|
92
|
+
if (lines.length === 0) return null;
|
|
93
|
+
return React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
94
|
+
...lines.map((line, i) => React.createElement(Text, {
|
|
95
|
+
key: `wm-${i}`,
|
|
96
|
+
bold: i === 0,
|
|
97
|
+
color: colorEnabled ? COCKPIT_COLORS.brand : undefined
|
|
98
|
+
}, line))
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderCallout(view, colorEnabled) {
|
|
103
|
+
return React.createElement(Callout, {
|
|
104
|
+
tone: view.callout.tone,
|
|
105
|
+
title: view.callout.title,
|
|
106
|
+
body: view.callout.body,
|
|
107
|
+
colorEnabled,
|
|
108
|
+
compact: true
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function SemanticOverviewPanel({
|
|
113
|
+
model,
|
|
114
|
+
detailsOpen = false,
|
|
115
|
+
colorEnabled = true,
|
|
116
|
+
unicode = true,
|
|
117
|
+
layoutMode = LAYOUT_MODES.COMPACT
|
|
118
|
+
}) {
|
|
119
|
+
const view = adaptControlCenterToOverview(model);
|
|
120
|
+
const showArt = shouldShowWordmark(layoutMode);
|
|
121
|
+
const brandTitle = overviewBrandTitle(layoutMode);
|
|
122
|
+
const isWide = layoutMode === LAYOUT_MODES.WIDE;
|
|
123
|
+
const mark = renderWordmark({ layoutMode, colorEnabled, unicode });
|
|
124
|
+
const status = renderCallout(view, colorEnabled);
|
|
125
|
+
|
|
126
|
+
const hero = showArt
|
|
127
|
+
? (isWide
|
|
128
|
+
? React.createElement(Box, { flexDirection: "row", marginBottom: 1 },
|
|
129
|
+
React.createElement(Box, { marginRight: 2 }, mark),
|
|
130
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, status)
|
|
131
|
+
)
|
|
132
|
+
: React.createElement(Box, { flexDirection: "column" }, mark, status))
|
|
133
|
+
: React.createElement(Box, { flexDirection: "column" },
|
|
134
|
+
React.createElement(Text, {
|
|
135
|
+
bold: true,
|
|
136
|
+
color: colorEnabled ? COCKPIT_COLORS.brand : undefined
|
|
137
|
+
}, brandTitle),
|
|
138
|
+
status
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
142
|
+
hero,
|
|
143
|
+
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
144
|
+
React.createElement(Text, {
|
|
145
|
+
bold: true,
|
|
146
|
+
color: colorEnabled ? COCKPIT_COLORS.interactive : undefined
|
|
147
|
+
}, view.primary.label),
|
|
148
|
+
view.primary.detail
|
|
149
|
+
? React.createElement(Text, null, view.primary.detail)
|
|
150
|
+
: null,
|
|
151
|
+
view.primary.hint
|
|
152
|
+
? React.createElement(Text, {
|
|
153
|
+
color: colorEnabled ? COCKPIT_COLORS.muted : undefined
|
|
154
|
+
}, view.primary.hint)
|
|
155
|
+
: null
|
|
156
|
+
),
|
|
157
|
+
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
158
|
+
React.createElement(ActionList, {
|
|
159
|
+
items: view.metrics,
|
|
160
|
+
selectedIndex: -1,
|
|
161
|
+
focused: false,
|
|
162
|
+
colorEnabled,
|
|
163
|
+
unicode
|
|
164
|
+
})
|
|
165
|
+
),
|
|
166
|
+
React.createElement(Details, {
|
|
167
|
+
open: detailsOpen,
|
|
168
|
+
summary: "Details",
|
|
169
|
+
lines: view.details,
|
|
170
|
+
colorEnabled,
|
|
171
|
+
focused: false,
|
|
172
|
+
mark: " "
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|