@kal-elsam/kairo-runtime 0.9.0 → 0.10.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/cockpit/primitives.js +47 -35
- 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 +69 -97
- package/src/global/ink/orchestrator-app.js +32 -0
- package/src/global/ink/setup-app.js +55 -72
- package/src/global/ink/setup-state.js +16 -0
- package/src/global/ink/theme.js +27 -0
- package/src/global/ink/ux/live-activity.js +194 -0
- package/src/global/ink/ux/live-alerts.js +159 -0
- package/src/global/ink/ux/live-governance.js +195 -0
- package/src/global/ink/ux/live-orchestration.js +188 -0
- package/src/global/ink/ux/live-overview.js +125 -0
- package/src/global/ink/ux/live-settings.js +189 -0
- package/src/global/ink/ux/live-setup.js +160 -0
- package/src/global/ink/ux/live-usage.js +51 -0
- package/src/global/ink/ux/semantic.js +84 -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
package/README.md
CHANGED
|
@@ -16,8 +16,11 @@ as a technical alias).
|
|
|
16
16
|
Kairo Runtime and prints a migration warning. Prefer `@kal-elsam/kairo-runtime` and the
|
|
17
17
|
`kairo` CLI for new installs. See [`packages/harness-bridge/README.md`](packages/harness-bridge/README.md).
|
|
18
18
|
|
|
19
|
-
Terminal UX
|
|
20
|
-
|
|
19
|
+
Terminal UX is **terminal-first**: bare `kairo` opens the Ink Control Center (or Setup
|
|
20
|
+
when unconfigured). Explicit commands (`setup`, `status`, `sync`, `doctor`, `monitor`,
|
|
21
|
+
`shell`, …) keep their current behavior. Progressive disclosure keeps paths and IDs in
|
|
22
|
+
Details, list caps honest (`… N more`), and Callout reserved for status — never for
|
|
23
|
+
primary values. No web loopback / `kairo ui` in this release.
|
|
21
24
|
|
|
22
25
|
- **npm:** https://www.npmjs.com/package/@kal-elsam/kairo-runtime
|
|
23
26
|
- **repo:** https://github.com/Kal-elSam/harness
|
|
@@ -32,13 +35,11 @@ npx @kal-elsam/kairo-runtime
|
|
|
32
35
|
kairo
|
|
33
36
|
```
|
|
34
37
|
|
|
35
|
-
**First run** (no `~/.harness/state.json`):
|
|
36
|
-
|
|
38
|
+
**First run** (no `~/.harness/state.json`): semantic Setup (Detect → Agents →
|
|
39
|
+
Components → Preview → Confirm) → full-screen Control Center.
|
|
37
40
|
|
|
38
|
-
**Later runs** (state present):
|
|
39
|
-
|
|
40
|
-
governance (scan → evidence → preview → confirm → apply → re-scan → recovery).
|
|
41
|
-
Runs stay secondary. Layout adapts to terminal size:
|
|
41
|
+
**Later runs** (state present): semantic Cockpit — Overview, Governance, Activity,
|
|
42
|
+
Orchestration, Usage, Settings — plus Alerts inbox. Layout adapts to terminal size:
|
|
42
43
|
|
|
43
44
|
| Mode | Size | Layout |
|
|
44
45
|
|------|------|--------|
|
|
@@ -73,6 +74,11 @@ Settings browses a curated catalog (pinned `pi-usage-widget@0.2.1`, MIT).
|
|
|
73
74
|
Preview → confirm shows an in-session confirmation receipt (`wroteFiles: false`);
|
|
74
75
|
it does not persist or install anything.
|
|
75
76
|
|
|
77
|
+
### Usage evidence
|
|
78
|
+
|
|
79
|
+
Usage shows measured budget pairs, profile limits, and finite run `tokenUsage`
|
|
80
|
+
fields only. It never invents totals, costs, or savings.
|
|
81
|
+
|
|
76
82
|
Respects `NO_COLOR`, `HARNESS_ASCII=1`, and `HARNESS_INK=0`. Status is always labeled
|
|
77
83
|
in text, never color alone.
|
|
78
84
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kal-elsam/kairo-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Pi, Engram, and Graphify.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/Kal-elSam/harness#readme",
|
|
@@ -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.10.0");
|
|
30
30
|
assert.ok(pkg.dependencies["ansi-escapes"]);
|
|
31
31
|
|
|
32
32
|
assert.equal(resolveLayoutMode({ columns: 120, rows: 40 }), LAYOUT_MODES.WIDE);
|
|
@@ -132,7 +132,7 @@ const proposalLines = formatProposalLines([
|
|
|
132
132
|
id: "setup-local",
|
|
133
133
|
severity: "high",
|
|
134
134
|
title: "Finish local setup",
|
|
135
|
-
destination: "
|
|
135
|
+
destination: "setup",
|
|
136
136
|
evidence: [{ type: "status", source: "status.overall", ref: "missing" }]
|
|
137
137
|
}
|
|
138
138
|
], {
|
|
@@ -143,7 +143,7 @@ const proposalLines = formatProposalLines([
|
|
|
143
143
|
requestBudgetTokens: 10
|
|
144
144
|
}
|
|
145
145
|
});
|
|
146
|
-
assert.match(proposalLines.join("\n"), /\[HIGH\] Finish local setup →
|
|
146
|
+
assert.match(proposalLines.join("\n"), /\[HIGH\] Finish local setup → setup/);
|
|
147
147
|
assert.match(proposalLines.join("\n"), /Budget · stable 1\/10/);
|
|
148
148
|
assert.match(proposalLines.join("\n"), /evidence: status\.overall/);
|
|
149
149
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Manual TTY gate: Home → Setup → Overview. No writes. Esc exits. */
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { render } from "ink";
|
|
5
|
+
import { TaskFlowApp } from "../src/global/ink/ux/task-flow-app.js";
|
|
6
|
+
|
|
7
|
+
const app = render(React.createElement(TaskFlowApp, {
|
|
8
|
+
onExit: () => app.unmount()
|
|
9
|
+
}));
|
|
@@ -12,6 +12,7 @@ export const PROPOSAL_SEVERITY = Object.freeze({
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
export const PROPOSAL_DESTINATION = Object.freeze({
|
|
15
|
+
SETUP: "setup",
|
|
15
16
|
CHANGES: "changes",
|
|
16
17
|
CONTROL_CENTER: "control-center",
|
|
17
18
|
IDES: "ides",
|
|
@@ -48,7 +49,7 @@ export function buildControlPlaneProposals({
|
|
|
48
49
|
PROPOSAL_SEVERITY.HIGH,
|
|
49
50
|
"Finish local setup",
|
|
50
51
|
status?.nextAction ?? "Configure the local ecosystem before applying repairs.",
|
|
51
|
-
PROPOSAL_DESTINATION.
|
|
52
|
+
PROPOSAL_DESTINATION.SETUP,
|
|
52
53
|
[
|
|
53
54
|
ev("status", "status.overall", status?.overall ?? "missing"),
|
|
54
55
|
ev("health", "control-plane.health", health ?? "NOT_CONFIGURED")
|
|
@@ -66,7 +66,7 @@ export function resolveControlPlaneCta({ health, status, diff } = {}) {
|
|
|
66
66
|
title: "Finish local setup",
|
|
67
67
|
detail: status?.nextAction
|
|
68
68
|
?? `Run ${formatCliCommand("setup")} to configure the local ecosystem.`,
|
|
69
|
-
destination: "
|
|
69
|
+
destination: "setup"
|
|
70
70
|
};
|
|
71
71
|
case CONTROL_PLANE_HEALTH.ACTION_REQUIRED:
|
|
72
72
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { COCKPIT_COLORS, statusColor } from "../theme.js";
|
|
3
|
+
import { COCKPIT_COLORS, resolveInkColor, statusColor } from "../theme.js";
|
|
4
4
|
|
|
5
5
|
export function CockpitBadge({ label, kind = "ready", colorEnabled = true }) {
|
|
6
6
|
return React.createElement(Text, {
|
|
@@ -8,36 +8,47 @@ export function CockpitBadge({ label, kind = "ready", colorEnabled = true }) {
|
|
|
8
8
|
}, label);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function CockpitEmptyState({ title, message, hint }) {
|
|
11
|
+
export function CockpitEmptyState({ title, message, hint, colorEnabled = true }) {
|
|
12
12
|
return React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
13
|
-
title && React.createElement(Text, {
|
|
13
|
+
title && React.createElement(Text, {
|
|
14
|
+
bold: true,
|
|
15
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.secondary)
|
|
16
|
+
}, title),
|
|
14
17
|
message && React.createElement(Text, null, message),
|
|
15
|
-
hint && React.createElement(Text, {
|
|
18
|
+
hint && React.createElement(Text, {
|
|
19
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
20
|
+
}, hint)
|
|
16
21
|
);
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
export function CockpitPanel({ title, focused = false, width, children }) {
|
|
24
|
+
export function CockpitPanel({ title, focused = false, width, children, colorEnabled = true }) {
|
|
20
25
|
return React.createElement(Box, {
|
|
21
26
|
flexDirection: "column",
|
|
22
27
|
width,
|
|
23
28
|
borderStyle: "single",
|
|
24
|
-
borderColor:
|
|
29
|
+
borderColor: resolveInkColor(
|
|
30
|
+
colorEnabled,
|
|
31
|
+
focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.muted
|
|
32
|
+
),
|
|
25
33
|
paddingX: 1,
|
|
26
34
|
flexGrow: 1
|
|
27
35
|
},
|
|
28
36
|
title && React.createElement(Text, {
|
|
29
37
|
bold: true,
|
|
30
|
-
color:
|
|
38
|
+
color: resolveInkColor(
|
|
39
|
+
colorEnabled,
|
|
40
|
+
focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.secondary
|
|
41
|
+
)
|
|
31
42
|
}, title),
|
|
32
43
|
children
|
|
33
44
|
);
|
|
34
45
|
}
|
|
35
46
|
|
|
36
|
-
export function CockpitSection({ title, children }) {
|
|
47
|
+
export function CockpitSection({ title, children, colorEnabled = true }) {
|
|
37
48
|
return React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
38
49
|
title && React.createElement(Text, {
|
|
39
50
|
bold: true,
|
|
40
|
-
color: COCKPIT_COLORS.secondary
|
|
51
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.secondary)
|
|
41
52
|
}, title),
|
|
42
53
|
children
|
|
43
54
|
);
|
|
@@ -45,7 +56,7 @@ export function CockpitSection({ title, children }) {
|
|
|
45
56
|
|
|
46
57
|
export function CockpitKeyHint({ keys, label, colorEnabled = true }) {
|
|
47
58
|
return React.createElement(Text, {
|
|
48
|
-
color: colorEnabled
|
|
59
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
49
60
|
}, `${keys} ${label}`);
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -53,10 +64,10 @@ export function CockpitTopBar({ model, colorEnabled = true }) {
|
|
|
53
64
|
return React.createElement(Box, { justifyContent: "space-between", width: "100%" },
|
|
54
65
|
React.createElement(Text, {
|
|
55
66
|
bold: true,
|
|
56
|
-
color: colorEnabled
|
|
67
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.primary)
|
|
57
68
|
}, `╭─ ${model.brand} ─ ${model.status}`),
|
|
58
69
|
React.createElement(Text, {
|
|
59
|
-
color: colorEnabled
|
|
70
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
60
71
|
}, `${model.projectLabel} ─╮`)
|
|
61
72
|
);
|
|
62
73
|
}
|
|
@@ -67,11 +78,11 @@ export function CockpitNav({ model, colorEnabled = true }) {
|
|
|
67
78
|
model.items.map((item) => {
|
|
68
79
|
const suffix = item.current && !item.selected ? " (open)" : "";
|
|
69
80
|
const color = item.focused
|
|
70
|
-
? (colorEnabled
|
|
81
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.primary)
|
|
71
82
|
: item.selected
|
|
72
|
-
? (colorEnabled
|
|
83
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.secondary)
|
|
73
84
|
: item.current
|
|
74
|
-
? (colorEnabled
|
|
85
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
75
86
|
: undefined;
|
|
76
87
|
return React.createElement(Text, {
|
|
77
88
|
key: item.id,
|
|
@@ -80,7 +91,7 @@ export function CockpitNav({ model, colorEnabled = true }) {
|
|
|
80
91
|
}, `${item.marker} ${item.label}${suffix}`);
|
|
81
92
|
}),
|
|
82
93
|
model.explanation && React.createElement(Text, {
|
|
83
|
-
color: COCKPIT_COLORS.muted
|
|
94
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
84
95
|
}, model.explanation)
|
|
85
96
|
);
|
|
86
97
|
}
|
|
@@ -102,12 +113,10 @@ export function CockpitNavStrip({
|
|
|
102
113
|
const label = stripLabel(item, { compact });
|
|
103
114
|
const suffix = item.current && !item.selected ? "*" : "";
|
|
104
115
|
const color = item.focused
|
|
105
|
-
? (colorEnabled
|
|
116
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.primary)
|
|
106
117
|
: item.selected
|
|
107
|
-
? (colorEnabled
|
|
108
|
-
:
|
|
109
|
-
? (colorEnabled ? COCKPIT_COLORS.muted : undefined)
|
|
110
|
-
: (colorEnabled ? COCKPIT_COLORS.muted : undefined);
|
|
118
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.secondary)
|
|
119
|
+
: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted);
|
|
111
120
|
return React.createElement(Text, {
|
|
112
121
|
key: item.id,
|
|
113
122
|
bold: item.focused || item.selected,
|
|
@@ -120,7 +129,7 @@ export function CockpitNavStrip({
|
|
|
120
129
|
if (index > 0) {
|
|
121
130
|
joined.push(React.createElement(Text, {
|
|
122
131
|
key: `sep-${index}`,
|
|
123
|
-
color: colorEnabled
|
|
132
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
124
133
|
}, " · "));
|
|
125
134
|
}
|
|
126
135
|
joined.push(node);
|
|
@@ -130,12 +139,15 @@ export function CockpitNavStrip({
|
|
|
130
139
|
flexDirection: "column",
|
|
131
140
|
width: "100%",
|
|
132
141
|
borderStyle: "single",
|
|
133
|
-
borderColor:
|
|
142
|
+
borderColor: resolveInkColor(
|
|
143
|
+
colorEnabled,
|
|
144
|
+
focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.muted
|
|
145
|
+
),
|
|
134
146
|
paddingX: 1
|
|
135
147
|
},
|
|
136
148
|
React.createElement(Box, { flexDirection: "row", flexWrap: "wrap" }, ...joined),
|
|
137
149
|
model.explanation && React.createElement(Text, {
|
|
138
|
-
color: COCKPIT_COLORS.muted
|
|
150
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
139
151
|
}, model.explanation)
|
|
140
152
|
);
|
|
141
153
|
}
|
|
@@ -145,7 +157,9 @@ export function CockpitSystemStrip({ model, colorEnabled = true }) {
|
|
|
145
157
|
return React.createElement(Box, { flexDirection: "column" },
|
|
146
158
|
model.rows.map((row) =>
|
|
147
159
|
React.createElement(Text, { key: row.key },
|
|
148
|
-
React.createElement(Text, {
|
|
160
|
+
React.createElement(Text, {
|
|
161
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
162
|
+
}, `${row.key.padEnd(7)}`),
|
|
149
163
|
React.createElement(CockpitBadge, {
|
|
150
164
|
label: row.value,
|
|
151
165
|
kind: row.kind,
|
|
@@ -156,17 +170,14 @@ export function CockpitSystemStrip({ model, colorEnabled = true }) {
|
|
|
156
170
|
);
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
export function CockpitFooter({ model, columns = 80 }) {
|
|
173
|
+
export function CockpitFooter({ model, columns = 80, colorEnabled = true }) {
|
|
160
174
|
const width = Math.max(24, Math.min(Number(columns) || 80, 120));
|
|
161
175
|
const bar = Math.max(20, width - 2);
|
|
176
|
+
const muted = resolveInkColor(colorEnabled, COCKPIT_COLORS.muted);
|
|
162
177
|
return React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
163
|
-
React.createElement(Text, { color:
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
React.createElement(Text, { color: COCKPIT_COLORS.muted }, `│ ${model.text}`),
|
|
167
|
-
React.createElement(Text, { color: COCKPIT_COLORS.muted },
|
|
168
|
-
`╰${"─".repeat(bar)}╯`
|
|
169
|
-
)
|
|
178
|
+
React.createElement(Text, { color: muted }, `├${"─".repeat(bar)}┤`),
|
|
179
|
+
React.createElement(Text, { color: muted }, `│ ${model.text}`),
|
|
180
|
+
React.createElement(Text, { color: muted }, `╰${"─".repeat(bar)}╯`)
|
|
170
181
|
);
|
|
171
182
|
}
|
|
172
183
|
|
|
@@ -196,8 +207,9 @@ export function CockpitShell({
|
|
|
196
207
|
React.createElement(CockpitPanel, {
|
|
197
208
|
title: undefined,
|
|
198
209
|
focused: contentFocused,
|
|
199
|
-
width: "100%"
|
|
210
|
+
width: "100%",
|
|
211
|
+
colorEnabled
|
|
200
212
|
}, children),
|
|
201
|
-
React.createElement(CockpitFooter, { model: footer, columns })
|
|
213
|
+
React.createElement(CockpitFooter, { model: footer, columns, colorEnabled })
|
|
202
214
|
);
|
|
203
215
|
}
|
|
@@ -143,6 +143,6 @@ export function formatChangesActionLines({
|
|
|
143
143
|
|
|
144
144
|
export function buildChangesFooterParts(phase) {
|
|
145
145
|
if (phase === CHANGES_PHASE.PREVIEWING || phase === CHANGES_PHASE.APPLYING) return ["Working…", "Esc Back"];
|
|
146
|
-
if (phase === CHANGES_PHASE.CONFIRMING) return ["Y Apply", "N/Esc Cancel"];
|
|
147
|
-
return ["A Preview", "R Re-scan", "Esc Back"];
|
|
146
|
+
if (phase === CHANGES_PHASE.CONFIRMING) return ["Y Apply", "N/Esc Cancel", "Space"];
|
|
147
|
+
return ["A Preview", "R Re-scan", "Space", "Esc Back"];
|
|
148
148
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { CONTROL_PLANE_HEALTH } from "../control-plane-snapshot.js";
|
|
2
2
|
import { formatAlertsHeadline } from "./cockpit-alerts.js";
|
|
3
|
+
import {
|
|
4
|
+
adaptUsageModel,
|
|
5
|
+
formatMeasuredBudgets,
|
|
6
|
+
formatUsageLinesFromModel
|
|
7
|
+
} from "./cockpit-usage.js";
|
|
3
8
|
|
|
4
9
|
export function buildControlCenterModel({
|
|
5
10
|
projectName = "project",
|
|
@@ -112,73 +117,16 @@ function formatHealthLabel(kind) {
|
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
function formatTokenHeadline(budgets) {
|
|
115
|
-
|
|
116
|
-
const parts = [];
|
|
117
|
-
if (Number.isFinite(budgets.stableUsedTokens) && Number.isFinite(budgets.stableBudgetTokens)) {
|
|
118
|
-
parts.push(`stable ${budgets.stableUsedTokens}/${budgets.stableBudgetTokens}`);
|
|
119
|
-
}
|
|
120
|
-
if (Number.isFinite(budgets.requestUsedTokens) && Number.isFinite(budgets.requestBudgetTokens)) {
|
|
121
|
-
parts.push(`request ${budgets.requestUsedTokens}/${budgets.requestBudgetTokens}`);
|
|
122
|
-
}
|
|
123
|
-
return parts.length > 0 ? parts.join(" · ") : "Data unavailable";
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Usage surface: measured budgets when present, configured profile limits,
|
|
128
|
-
* and auditable run tokenUsage — never invent totals.
|
|
129
|
-
*/
|
|
130
|
-
function resolveProfileLimits(dashboard) {
|
|
131
|
-
const resolved = dashboard?.profile?.profile ?? {};
|
|
132
|
-
const configured = [];
|
|
133
|
-
if (Number.isFinite(resolved.tokenBudget)) configured.push(`token ${resolved.tokenBudget}`);
|
|
134
|
-
if (Number.isFinite(resolved.stableContextBudget)) configured.push(`stable ${resolved.stableContextBudget}`);
|
|
135
|
-
if (Number.isFinite(resolved.requestContextBudget)) configured.push(`request ${resolved.requestContextBudget}`);
|
|
136
|
-
return configured;
|
|
120
|
+
return formatMeasuredBudgets(budgets) ?? "Data unavailable";
|
|
137
121
|
}
|
|
138
122
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function formatRunUsageLine(run) {
|
|
147
|
-
const usage = run.tokenUsage;
|
|
148
|
-
const parts = [];
|
|
149
|
-
if (Number.isFinite(usage.input)) parts.push(`in ${usage.input}`);
|
|
150
|
-
if (Number.isFinite(usage.output)) parts.push(`out ${usage.output}`);
|
|
151
|
-
if (Number.isFinite(usage.total)) parts.push(`total ${usage.total}`);
|
|
152
|
-
return `${run.agentId ?? "agent"} · ${parts.join(" · ")}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function formatUsageLines({ snapshot = null, dashboard = null } = {}) {
|
|
156
|
-
const lines = ["MEASURED"];
|
|
157
|
-
const measured = formatTokenHeadline(snapshot?.budgets);
|
|
158
|
-
lines.push(measured === "Data unavailable" ? "Data unavailable" : measured);
|
|
159
|
-
|
|
160
|
-
const configured = resolveProfileLimits(dashboard);
|
|
161
|
-
lines.push("", "CONFIGURED LIMITS");
|
|
162
|
-
lines.push(configured.length > 0 ? configured.join(" · ") : "No profile token budgets configured.");
|
|
163
|
-
|
|
164
|
-
const runs = [...(dashboard?.activeRuns ?? []), ...(dashboard?.recentRuns ?? [])]
|
|
165
|
-
.filter((run) => hasFiniteUsage(run?.tokenUsage));
|
|
166
|
-
lines.push("", "RUN USAGE");
|
|
167
|
-
if (runs.length === 0) {
|
|
168
|
-
lines.push("No auditable run tokenUsage yet.");
|
|
169
|
-
} else {
|
|
170
|
-
for (const run of runs.slice(0, 3)) {
|
|
171
|
-
lines.push(formatRunUsageLine(run));
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
lines.push("", "Auditable budgets only — no invented token savings.");
|
|
176
|
-
return lines;
|
|
123
|
+
/** Usage surface via shared auditable model — never invent totals. */
|
|
124
|
+
export function formatUsageLines({
|
|
125
|
+
snapshot = null, dashboard = null, layoutMode = undefined
|
|
126
|
+
} = {}) {
|
|
127
|
+
return formatUsageLinesFromModel(adaptUsageModel({ snapshot, dashboard, layoutMode }));
|
|
177
128
|
}
|
|
178
129
|
|
|
179
130
|
export function hasAuditableUsage({ snapshot = null, dashboard = null } = {}) {
|
|
180
|
-
|
|
181
|
-
if (resolveProfileLimits(dashboard).length > 0) return true;
|
|
182
|
-
return [...(dashboard?.activeRuns ?? []), ...(dashboard?.recentRuns ?? [])]
|
|
183
|
-
.some((run) => hasFiniteUsage(run?.tokenUsage));
|
|
131
|
+
return adaptUsageModel({ snapshot, dashboard }).hasEvidence;
|
|
184
132
|
}
|
|
@@ -35,7 +35,10 @@ export function createCockpitUiState({
|
|
|
35
35
|
helpOpen = false,
|
|
36
36
|
returnView = null,
|
|
37
37
|
paletteOpen = false,
|
|
38
|
-
paletteIndex = 0
|
|
38
|
+
paletteIndex = 0,
|
|
39
|
+
overviewDetailsOpen = false,
|
|
40
|
+
governanceDetailsOpen = false,
|
|
41
|
+
activityDetailsOpen = false
|
|
39
42
|
} = {}) {
|
|
40
43
|
const regions = regionsForLayout(layoutMode);
|
|
41
44
|
const preferred = region ?? defaultRegionForView(view, layoutMode);
|
|
@@ -52,6 +55,9 @@ export function createCockpitUiState({
|
|
|
52
55
|
returnView,
|
|
53
56
|
paletteOpen,
|
|
54
57
|
paletteIndex: Math.max(0, paletteIndex),
|
|
58
|
+
overviewDetailsOpen: Boolean(overviewDetailsOpen),
|
|
59
|
+
governanceDetailsOpen: Boolean(governanceDetailsOpen),
|
|
60
|
+
activityDetailsOpen: Boolean(activityDetailsOpen),
|
|
55
61
|
shouldExit: false
|
|
56
62
|
};
|
|
57
63
|
}
|
|
@@ -145,7 +151,10 @@ export function reduceCockpitUi(state, action) {
|
|
|
145
151
|
listIndex: 0,
|
|
146
152
|
region: defaultRegionForView(item.view, state.layoutMode),
|
|
147
153
|
helpOpen: false,
|
|
148
|
-
returnView: null
|
|
154
|
+
returnView: null,
|
|
155
|
+
overviewDetailsOpen: false,
|
|
156
|
+
governanceDetailsOpen: false,
|
|
157
|
+
activityDetailsOpen: false
|
|
149
158
|
});
|
|
150
159
|
}
|
|
151
160
|
case "set-view": {
|
|
@@ -159,9 +168,21 @@ export function reduceCockpitUi(state, action) {
|
|
|
159
168
|
: clamp(action.navIndex, 0, COCKPIT_NAV.length - 1),
|
|
160
169
|
region: action.region ?? defaultRegionForView(nextView, state.layoutMode),
|
|
161
170
|
returnView: action.returnView ?? state.returnView,
|
|
162
|
-
helpOpen: nextView === ORCHESTRATOR_VIEWS.HELP
|
|
171
|
+
helpOpen: nextView === ORCHESTRATOR_VIEWS.HELP,
|
|
172
|
+
overviewDetailsOpen: false,
|
|
173
|
+
governanceDetailsOpen: false,
|
|
174
|
+
activityDetailsOpen: false
|
|
163
175
|
});
|
|
164
176
|
}
|
|
177
|
+
case "toggle-overview-details":
|
|
178
|
+
if (state.view !== ORCHESTRATOR_VIEWS.HOME) return state;
|
|
179
|
+
return { ...state, overviewDetailsOpen: !state.overviewDetailsOpen };
|
|
180
|
+
case "toggle-governance-details":
|
|
181
|
+
if (state.view !== ORCHESTRATOR_VIEWS.CHANGES) return state;
|
|
182
|
+
return { ...state, governanceDetailsOpen: !state.governanceDetailsOpen };
|
|
183
|
+
case "toggle-activity-details":
|
|
184
|
+
if (state.view !== ORCHESTRATOR_VIEWS.ACTIVITY) return state;
|
|
185
|
+
return { ...state, activityDetailsOpen: !state.activityDetailsOpen };
|
|
165
186
|
case "toggle-help":
|
|
166
187
|
if (state.helpOpen || state.view === ORCHESTRATOR_VIEWS.HELP) {
|
|
167
188
|
return goOverview(state);
|
|
@@ -170,12 +191,24 @@ export function reduceCockpitUi(state, action) {
|
|
|
170
191
|
...state,
|
|
171
192
|
helpOpen: true,
|
|
172
193
|
view: ORCHESTRATOR_VIEWS.HELP,
|
|
173
|
-
region: defaultRegionForView(ORCHESTRATOR_VIEWS.HELP, state.layoutMode)
|
|
194
|
+
region: defaultRegionForView(ORCHESTRATOR_VIEWS.HELP, state.layoutMode),
|
|
195
|
+
overviewDetailsOpen: false,
|
|
196
|
+
governanceDetailsOpen: false,
|
|
197
|
+
activityDetailsOpen: false
|
|
174
198
|
});
|
|
175
199
|
case "escape": {
|
|
176
200
|
if (state.paletteOpen) {
|
|
177
201
|
return closePalette(state);
|
|
178
202
|
}
|
|
203
|
+
if (state.view === ORCHESTRATOR_VIEWS.HOME && state.overviewDetailsOpen) {
|
|
204
|
+
return { ...state, overviewDetailsOpen: false };
|
|
205
|
+
}
|
|
206
|
+
if (state.view === ORCHESTRATOR_VIEWS.CHANGES && state.governanceDetailsOpen) {
|
|
207
|
+
return { ...state, governanceDetailsOpen: false };
|
|
208
|
+
}
|
|
209
|
+
if (state.view === ORCHESTRATOR_VIEWS.ACTIVITY && state.activityDetailsOpen) {
|
|
210
|
+
return { ...state, activityDetailsOpen: false };
|
|
211
|
+
}
|
|
179
212
|
if (state.helpOpen || state.view === ORCHESTRATOR_VIEWS.HELP) {
|
|
180
213
|
return goOverview(state);
|
|
181
214
|
}
|
|
@@ -228,7 +261,10 @@ function goOverview(state) {
|
|
|
228
261
|
navIndex: 0,
|
|
229
262
|
listIndex: 0,
|
|
230
263
|
region: defaultRegionForView(ORCHESTRATOR_VIEWS.HOME, state.layoutMode),
|
|
231
|
-
returnView: null
|
|
264
|
+
returnView: null,
|
|
265
|
+
overviewDetailsOpen: false,
|
|
266
|
+
governanceDetailsOpen: false,
|
|
267
|
+
activityDetailsOpen: false
|
|
232
268
|
});
|
|
233
269
|
}
|
|
234
270
|
|
|
@@ -14,6 +14,7 @@ export function resolveEnterNavIntent({
|
|
|
14
14
|
const isOverview = navItem.id === "overview" || navItem.view === ORCHESTRATOR_VIEWS.HOME;
|
|
15
15
|
if (isOverview) {
|
|
16
16
|
if (currentView === ORCHESTRATOR_VIEWS.HOME && ctaDestination) {
|
|
17
|
+
if (ctaDestination === "setup") return { kind: "activate-setup" };
|
|
17
18
|
return { kind: "activate-cta", destination: ctaDestination };
|
|
18
19
|
}
|
|
19
20
|
return { kind: "open-nav", view: ORCHESTRATOR_VIEWS.HOME };
|
|
@@ -243,6 +243,7 @@ export function buildFooterModel({
|
|
|
243
243
|
hasError = false,
|
|
244
244
|
changesPhase = null,
|
|
245
245
|
recoveryPhase = null,
|
|
246
|
+
recoveryHasPreview = false,
|
|
246
247
|
settingsPhase = null,
|
|
247
248
|
columns = 80
|
|
248
249
|
} = {}) {
|
|
@@ -290,7 +291,8 @@ export function buildFooterModel({
|
|
|
290
291
|
|
|
291
292
|
if (view === ORCHESTRATOR_VIEWS.ACTIVITY) {
|
|
292
293
|
return {
|
|
293
|
-
text: buildRecoveryFooterParts(recoveryPhase
|
|
294
|
+
text: buildRecoveryFooterParts(recoveryPhase, { hasPreview: recoveryHasPreview })
|
|
295
|
+
.join(` ${glyphs.bullet} `),
|
|
294
296
|
columns: footerColumns
|
|
295
297
|
};
|
|
296
298
|
}
|
|
@@ -315,16 +317,21 @@ export function buildFooterModel({
|
|
|
315
317
|
parts.push("Tab Region");
|
|
316
318
|
}
|
|
317
319
|
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
// HOME footer must stay on one line at 80 cols (frame already near 24 rows).
|
|
321
|
+
if (view === ORCHESTRATOR_VIEWS.HOME) {
|
|
322
|
+
return {
|
|
323
|
+
text: ["↑↓", "Enter", "Space", "R", "?", "/", "Esc"].join(` ${glyphs.bullet} `),
|
|
324
|
+
columns: footerColumns
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (region === COCKPIT_REGIONS.NAV
|
|
329
|
+
|| view === ORCHESTRATOR_VIEWS.IDES
|
|
322
330
|
|| view === ORCHESTRATOR_VIEWS.MODULES
|
|
323
331
|
|| view === ORCHESTRATOR_VIEWS.PROFILE
|
|
324
332
|
|| view === ORCHESTRATOR_VIEWS.PROVIDERS
|
|
325
333
|
|| view === ORCHESTRATOR_VIEWS.DIAGNOSTICS
|
|
326
334
|
|| view === ORCHESTRATOR_VIEWS.USAGE
|
|
327
|
-
|| region === COCKPIT_REGIONS.NAV
|
|
328
335
|
|| view === ORCHESTRATOR_VIEWS.RUNS
|
|
329
336
|
|| view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
|
|
330
337
|
|| view === ORCHESTRATOR_VIEWS.RECENT_RUNS
|
|
@@ -337,7 +344,7 @@ export function buildFooterModel({
|
|
|
337
344
|
|
|
338
345
|
parts.push("? Help");
|
|
339
346
|
parts.push("/ Actions");
|
|
340
|
-
parts.push(
|
|
347
|
+
parts.push("Esc Back");
|
|
341
348
|
|
|
342
349
|
return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
|
|
343
350
|
}
|
|
@@ -4,7 +4,8 @@ import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
|
4
4
|
export const PALETTE_KINDS = Object.freeze({
|
|
5
5
|
NAVIGATE: "navigate",
|
|
6
6
|
REFRESH: "refresh",
|
|
7
|
-
HELP: "help"
|
|
7
|
+
HELP: "help",
|
|
8
|
+
SETUP: "setup"
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
const DESTINATION_VIEWS = Object.freeze({
|
|
@@ -37,15 +38,25 @@ export function buildPaletteActions({
|
|
|
37
38
|
navItems = COCKPIT_NAV
|
|
38
39
|
} = {}) {
|
|
39
40
|
const actions = [];
|
|
40
|
-
|
|
41
|
-
if (recommendedView) {
|
|
41
|
+
if (ctaDestination === "setup") {
|
|
42
42
|
actions.push({
|
|
43
43
|
id: "recommended",
|
|
44
|
-
kind: PALETTE_KINDS.
|
|
45
|
-
label: ctaTitle?.trim() || "
|
|
46
|
-
view:
|
|
47
|
-
description: ctaDetail?.trim() || "
|
|
44
|
+
kind: PALETTE_KINDS.SETUP,
|
|
45
|
+
label: ctaTitle?.trim() || "Finish local setup",
|
|
46
|
+
view: null,
|
|
47
|
+
description: ctaDetail?.trim() || "Open the local setup wizard."
|
|
48
48
|
});
|
|
49
|
+
} else {
|
|
50
|
+
const recommendedView = resolvePaletteDestination(ctaDestination);
|
|
51
|
+
if (recommendedView) {
|
|
52
|
+
actions.push({
|
|
53
|
+
id: "recommended",
|
|
54
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
55
|
+
label: ctaTitle?.trim() || "Recommended next action",
|
|
56
|
+
view: recommendedView,
|
|
57
|
+
description: ctaDetail?.trim() || "Overview recommended destination."
|
|
58
|
+
});
|
|
59
|
+
}
|
|
49
60
|
}
|
|
50
61
|
for (const item of navItems) {
|
|
51
62
|
actions.push({
|
|
@@ -75,7 +75,7 @@ export function listRecoverySnapshots(snapshot) {
|
|
|
75
75
|
return snapshot?.backups?.snapshots ?? [];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function formatWhen(timestamp) {
|
|
78
|
+
export function formatWhen(timestamp) {
|
|
79
79
|
if (!timestamp) return "unknown time";
|
|
80
80
|
const raw = String(timestamp);
|
|
81
81
|
if (/^\d{4}-\d{2}-\d{2}/.test(raw)) return raw.slice(0, 16).replace("T", " ");
|
|
@@ -84,7 +84,7 @@ function formatWhen(timestamp) {
|
|
|
84
84
|
return date.toISOString().slice(0, 16).replace("T", " ");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
function formatResult(action) {
|
|
87
|
+
export function formatResult(action) {
|
|
88
88
|
const value = String(action ?? "").toLowerCase();
|
|
89
89
|
if (!value) return "done";
|
|
90
90
|
if (value === "applied" || value === "ok" || value === "success") return "ok";
|
|
@@ -93,7 +93,7 @@ function formatResult(action) {
|
|
|
93
93
|
return value;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function shortName(name) {
|
|
96
|
+
export function shortName(name) {
|
|
97
97
|
const raw = String(name ?? "").trim();
|
|
98
98
|
if (!raw) return "snapshot";
|
|
99
99
|
const parts = raw.split(/[/\\]/).filter(Boolean);
|
|
@@ -176,8 +176,11 @@ export function formatRecoveryLines({
|
|
|
176
176
|
return lines;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
export function buildRecoveryFooterParts(phase) {
|
|
179
|
+
export function buildRecoveryFooterParts(phase, { hasPreview = false } = {}) {
|
|
180
180
|
if (phase === RECOVERY_PHASE.PREVIEWING || phase === RECOVERY_PHASE.APPLYING) return ["Working…", "Esc Back"];
|
|
181
|
-
if (phase === RECOVERY_PHASE.CONFIRMING) return ["Y Restore", "N/Esc Cancel"];
|
|
182
|
-
|
|
181
|
+
if (phase === RECOVERY_PHASE.CONFIRMING) return ["Y Restore", "N/Esc Cancel", "Space"];
|
|
182
|
+
const parts = ["↑↓ Select", "Enter Preview", "R Re-scan"];
|
|
183
|
+
if (hasPreview) parts.push("Space");
|
|
184
|
+
parts.push("Esc Back");
|
|
185
|
+
return parts;
|
|
183
186
|
}
|