@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { COCKPIT_NAV } from "./cockpit-models.js";
|
|
2
|
+
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
3
|
+
|
|
4
|
+
export const PALETTE_KINDS = Object.freeze({
|
|
5
|
+
NAVIGATE: "navigate",
|
|
6
|
+
REFRESH: "refresh",
|
|
7
|
+
HELP: "help"
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const DESTINATION_VIEWS = Object.freeze({
|
|
11
|
+
changes: ORCHESTRATOR_VIEWS.CHANGES,
|
|
12
|
+
governance: ORCHESTRATOR_VIEWS.CHANGES,
|
|
13
|
+
"control-center": ORCHESTRATOR_VIEWS.HOME,
|
|
14
|
+
ides: ORCHESTRATOR_VIEWS.IDES,
|
|
15
|
+
modules: ORCHESTRATOR_VIEWS.MODULES,
|
|
16
|
+
activity: ORCHESTRATOR_VIEWS.ACTIVITY,
|
|
17
|
+
profile: ORCHESTRATOR_VIEWS.PROFILE,
|
|
18
|
+
runs: ORCHESTRATOR_VIEWS.RUNS,
|
|
19
|
+
orchestration: ORCHESTRATOR_VIEWS.RUNS,
|
|
20
|
+
usage: ORCHESTRATOR_VIEWS.USAGE,
|
|
21
|
+
alerts: ORCHESTRATOR_VIEWS.ALERTS
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export function resolvePaletteDestination(key) {
|
|
25
|
+
return DESTINATION_VIEWS[key] ?? null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function canOpenPalette({ loading = false, busy = false, confirming = false } = {}) {
|
|
29
|
+
return !loading && !busy && !confirming;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Optional CTA + six destinations + Refresh + Help. No write shortcuts. */
|
|
33
|
+
export function buildPaletteActions({
|
|
34
|
+
ctaDestination = null,
|
|
35
|
+
ctaTitle = null,
|
|
36
|
+
ctaDetail = null,
|
|
37
|
+
navItems = COCKPIT_NAV
|
|
38
|
+
} = {}) {
|
|
39
|
+
const actions = [];
|
|
40
|
+
const recommendedView = resolvePaletteDestination(ctaDestination);
|
|
41
|
+
if (recommendedView) {
|
|
42
|
+
actions.push({
|
|
43
|
+
id: "recommended",
|
|
44
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
45
|
+
label: ctaTitle?.trim() || "Recommended next action",
|
|
46
|
+
view: recommendedView,
|
|
47
|
+
description: ctaDetail?.trim() || "Overview recommended destination."
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
for (const item of navItems) {
|
|
51
|
+
actions.push({
|
|
52
|
+
id: item.id,
|
|
53
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
54
|
+
label: item.label,
|
|
55
|
+
view: item.view,
|
|
56
|
+
description: item.description
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
actions.push({
|
|
60
|
+
id: "alerts",
|
|
61
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
62
|
+
label: "Alerts",
|
|
63
|
+
view: ORCHESTRATOR_VIEWS.ALERTS,
|
|
64
|
+
description: "Pending alert inbox."
|
|
65
|
+
});
|
|
66
|
+
actions.push({
|
|
67
|
+
id: "refresh",
|
|
68
|
+
kind: PALETTE_KINDS.REFRESH,
|
|
69
|
+
label: "Refresh",
|
|
70
|
+
view: null,
|
|
71
|
+
description: "Reload the read-only cockpit scan."
|
|
72
|
+
});
|
|
73
|
+
actions.push({
|
|
74
|
+
id: "help",
|
|
75
|
+
kind: PALETTE_KINDS.HELP,
|
|
76
|
+
label: "Help",
|
|
77
|
+
view: ORCHESTRATOR_VIEWS.HELP,
|
|
78
|
+
description: "Keyboard reference and primary flow."
|
|
79
|
+
});
|
|
80
|
+
return actions;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildPaletteModel({ actions = buildPaletteActions(), index = 0, unicode = true } = {}) {
|
|
84
|
+
const max = Math.max(0, actions.length - 1);
|
|
85
|
+
const safeIndex = Math.min(Math.max(0, index), max);
|
|
86
|
+
const focus = unicode ? "›" : ">";
|
|
87
|
+
return {
|
|
88
|
+
title: "ACTIONS",
|
|
89
|
+
hint: "Navigate · Refresh · Help — no writes",
|
|
90
|
+
items: actions.map((action, i) => ({
|
|
91
|
+
...action,
|
|
92
|
+
marker: i === safeIndex ? focus : " ",
|
|
93
|
+
selected: i === safeIndex
|
|
94
|
+
})),
|
|
95
|
+
selected: actions[safeIndex] ?? null,
|
|
96
|
+
index: safeIndex
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distinguishable path for confirmation / detail disclosure.
|
|
3
|
+
* Prefer home-relative `~/...`; otherwise keep parent/basename.
|
|
4
|
+
*/
|
|
5
|
+
export function formatConfirmPath(target, homeDir = null) {
|
|
6
|
+
const raw = String(target ?? "").trim();
|
|
7
|
+
if (!raw) return "target";
|
|
8
|
+
const normalized = raw.replaceAll("\\", "/");
|
|
9
|
+
if (normalized === "~" || normalized.startsWith("~/")) return normalized;
|
|
10
|
+
|
|
11
|
+
const home = homeDir ? String(homeDir).replaceAll("\\", "/").replace(/\/$/, "") : "";
|
|
12
|
+
if (home && (normalized === home || normalized.startsWith(`${home}/`))) {
|
|
13
|
+
return `~${normalized.slice(home.length)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
17
|
+
if (parts.length >= 2) return parts.slice(-2).join("/");
|
|
18
|
+
return parts[0] || normalized;
|
|
19
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatConfirmPath } from "./cockpit-path-label.js";
|
|
2
|
+
|
|
1
3
|
export const RECOVERY_PHASE = Object.freeze({
|
|
2
4
|
IDLE: "idle",
|
|
3
5
|
PREVIEWING: "previewing",
|
|
@@ -73,40 +75,101 @@ export function listRecoverySnapshots(snapshot) {
|
|
|
73
75
|
return snapshot?.backups?.snapshots ?? [];
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
function formatWhen(timestamp) {
|
|
79
|
+
if (!timestamp) return "unknown time";
|
|
80
|
+
const raw = String(timestamp);
|
|
81
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(raw)) return raw.slice(0, 16).replace("T", " ");
|
|
82
|
+
const date = new Date(raw);
|
|
83
|
+
if (Number.isNaN(date.getTime())) return raw.slice(0, 16);
|
|
84
|
+
return date.toISOString().slice(0, 16).replace("T", " ");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatResult(action) {
|
|
88
|
+
const value = String(action ?? "").toLowerCase();
|
|
89
|
+
if (!value) return "done";
|
|
90
|
+
if (value === "applied" || value === "ok" || value === "success") return "ok";
|
|
91
|
+
if (value === "cancelled" || value === "canceled") return "cancelled";
|
|
92
|
+
if (value.includes("fail")) return "failed";
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function shortName(name) {
|
|
97
|
+
const raw = String(name ?? "").trim();
|
|
98
|
+
if (!raw) return "snapshot";
|
|
99
|
+
const parts = raw.split(/[/\\]/).filter(Boolean);
|
|
100
|
+
return parts[parts.length - 1] || raw;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Activity surface: what agents did, when, and result.
|
|
105
|
+
* Snapshot restore stays secondary; paths only under DETAILS.
|
|
106
|
+
*/
|
|
107
|
+
export function formatRecoveryLines({
|
|
108
|
+
snapshot,
|
|
109
|
+
recoveryAction,
|
|
110
|
+
listIndex = 0,
|
|
111
|
+
dashboard = null,
|
|
112
|
+
detail = false,
|
|
113
|
+
homeDir = null
|
|
114
|
+
} = {}) {
|
|
77
115
|
const backups = listRecoverySnapshots(snapshot);
|
|
78
116
|
const events = snapshot?.history?.events ?? [];
|
|
79
117
|
const phase = recoveryAction?.phase ?? RECOVERY_PHASE.IDLE;
|
|
80
|
-
const lines = [
|
|
81
|
-
`Activity & recovery · ${phase}`,
|
|
82
|
-
`History: ${events.length} recent event(s)`,
|
|
83
|
-
...events.slice(0, 3).map((event) => `${event.command ?? event.type ?? "event"} · ${event.action ?? ""} · ${event.timestamp ?? ""}`),
|
|
84
|
-
"",
|
|
85
|
-
`Snapshots: ${snapshot?.backups?.count ?? backups.length}`
|
|
86
|
-
];
|
|
118
|
+
const lines = ["RECENT"];
|
|
87
119
|
|
|
120
|
+
const recent = [];
|
|
121
|
+
for (const event of events.slice(0, 3)) {
|
|
122
|
+
recent.push(
|
|
123
|
+
`${formatWhen(event.timestamp)} · ${event.command ?? event.type ?? "event"} · ${formatResult(event.action)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
for (const run of (dashboard?.recentRuns ?? []).slice(0, 2)) {
|
|
127
|
+
recent.push(
|
|
128
|
+
`${formatWhen(run.updatedAt ?? run.endedAt ?? run.startedAt)} · ${run.agentId ?? "agent"} · ${formatResult(run.state)}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (recent.length === 0) lines.push("No recent agent activity.");
|
|
132
|
+
else lines.push(...recent.slice(0, 4));
|
|
133
|
+
|
|
134
|
+
lines.push("", "SNAPSHOTS");
|
|
88
135
|
if (backups.length === 0) {
|
|
89
136
|
lines.push("No global snapshots yet.");
|
|
90
137
|
} else {
|
|
91
138
|
backups.forEach((entry, index) => {
|
|
92
139
|
const mark = index === listIndex ? "›" : " ";
|
|
93
|
-
lines.push(`${mark} ${entry.name} · ${entry.fileCount ?? "?"} files`);
|
|
140
|
+
lines.push(`${mark} ${shortName(entry.name)} · ${entry.fileCount ?? "?"} files`);
|
|
94
141
|
});
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
if (recoveryAction?.message) lines.push("", recoveryAction.message);
|
|
145
|
+
|
|
98
146
|
const preview = recoveryAction?.preview;
|
|
99
147
|
if (preview) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
148
|
+
const fileCount = preview.files?.length ?? 0;
|
|
149
|
+
lines.push(`Restore preview · ${fileCount} file(s)`);
|
|
150
|
+
if (detail || phase === RECOVERY_PHASE.CONFIRMING) {
|
|
151
|
+
lines.push("DETAILS");
|
|
152
|
+
const limit = detail ? 12 : 3;
|
|
153
|
+
for (const file of (preview.files ?? []).slice(0, limit)) {
|
|
154
|
+
lines.push(formatConfirmPath(file.displayPath ?? file.path, homeDir));
|
|
155
|
+
}
|
|
156
|
+
if ((preview.files?.length ?? 0) > limit) {
|
|
157
|
+
lines.push(`… ${preview.files.length - limit} more`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
103
160
|
}
|
|
161
|
+
|
|
104
162
|
const receipt = recoveryAction?.receipt;
|
|
105
163
|
if (receipt) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
164
|
+
const restored = receipt.restored?.length ?? 0;
|
|
165
|
+
lines.push("", `Result · ${receipt.action ?? "rollback"} · ${restored} restored`);
|
|
166
|
+
if (detail && receipt.safetyBackup) {
|
|
167
|
+
lines.push("DETAILS", `Safety backup retained`);
|
|
168
|
+
} else if (receipt.safetyBackup) {
|
|
169
|
+
lines.push("Safety backup retained");
|
|
170
|
+
}
|
|
109
171
|
}
|
|
172
|
+
|
|
110
173
|
if (phase === RECOVERY_PHASE.IDLE || phase === RECOVERY_PHASE.COMPLETED || phase === RECOVERY_PHASE.FAILED) {
|
|
111
174
|
lines.push("", "Enter preview · Y restore · N/Esc cancel · R re-scan");
|
|
112
175
|
}
|
|
@@ -22,8 +22,10 @@ export function formatReviewListLines(receipts = []) {
|
|
|
22
22
|
return receipts.map((receipt) => {
|
|
23
23
|
const counts = countFindingsBySeverity(receipt.findings);
|
|
24
24
|
const findingTotal = (receipt.findings ?? []).length;
|
|
25
|
-
const created = String(receipt.createdAt ?? "").slice(0,
|
|
26
|
-
|
|
25
|
+
const created = String(receipt.createdAt ?? "").slice(0, 16).replace("T", " ");
|
|
26
|
+
const agent = String(receipt.agentId ?? "agent");
|
|
27
|
+
const state = String(receipt.state ?? "unknown");
|
|
28
|
+
return `${agent} · ${state} · ${findingTotal} findings (h${counts.high}/m${counts.medium}/l${counts.low}) · ${created || "unknown time"}`;
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -33,15 +35,17 @@ export function formatReviewDetailLines(receipt) {
|
|
|
33
35
|
const snapshot = receipt.snapshot ?? {};
|
|
34
36
|
const totals = snapshot.totals ?? {};
|
|
35
37
|
const lines = [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
`Created
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
"SUMMARY",
|
|
39
|
+
`${receipt.agentId}${receipt.model ? ` · ${receipt.model}` : ""} · ${receipt.state}`,
|
|
40
|
+
`Findings · ${(receipt.findings ?? []).length} (high ${counts.high}, medium ${counts.medium}, low ${counts.low})`,
|
|
41
|
+
`Created · ${receipt.createdAt ?? "n/a"}`,
|
|
42
|
+
"",
|
|
43
|
+
"DETAILS",
|
|
44
|
+
`Id · ${receipt.reviewId}`,
|
|
45
|
+
`Snapshot · ${snapshot.mode ?? "n/a"} · ${totals.fileCount ?? 0} files`
|
|
42
46
|
];
|
|
43
47
|
if ((receipt.warnings ?? []).length > 0) {
|
|
44
|
-
lines.push(`Warnings
|
|
48
|
+
lines.push(`Warnings · ${receipt.warnings.length}`);
|
|
45
49
|
}
|
|
46
50
|
const findings = receipt.findings ?? [];
|
|
47
51
|
if (findings.length === 0) {
|
|
@@ -56,7 +60,7 @@ export function formatReviewDetailLines(receipt) {
|
|
|
56
60
|
);
|
|
57
61
|
}
|
|
58
62
|
if (findings.length > 20) {
|
|
59
|
-
lines.push(` … ${findings.length - 20} more
|
|
63
|
+
lines.push(` … ${findings.length - 20} more`);
|
|
60
64
|
}
|
|
61
65
|
return lines;
|
|
62
66
|
}
|
|
@@ -5,19 +5,19 @@ export const RUNS_HUB_ITEMS = [
|
|
|
5
5
|
id: "active",
|
|
6
6
|
label: "Active runs",
|
|
7
7
|
view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
|
|
8
|
-
description: "Inspect and cancel supervised runs
|
|
8
|
+
description: "Inspect and cancel supervised runs in flight."
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
id: "history",
|
|
12
12
|
label: "History",
|
|
13
13
|
view: ORCHESTRATOR_VIEWS.RECENT_RUNS,
|
|
14
|
-
description: "
|
|
14
|
+
description: "Completed and failed outcomes."
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
id: "reviews",
|
|
18
18
|
label: "Reviews",
|
|
19
19
|
view: ORCHESTRATOR_VIEWS.REVIEWS,
|
|
20
|
-
description: "
|
|
20
|
+
description: "Secret-free review receipts (read-only)."
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
id: "launch",
|
|
@@ -51,6 +51,15 @@ export function resolveRunsHubItem(listIndex = 0, items = RUNS_HUB_ITEMS) {
|
|
|
51
51
|
return items[index] ?? null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** Selectable hub labels only — counts belong in the panel title/hint. */
|
|
54
55
|
export function formatRunsHubLines(items = RUNS_HUB_ITEMS) {
|
|
55
|
-
return items.map((item) =>
|
|
56
|
+
return items.map((item) => item.label);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatOrchestrationStatus({
|
|
60
|
+
active = 0,
|
|
61
|
+
recent = 0,
|
|
62
|
+
reviews = 0
|
|
63
|
+
} = {}) {
|
|
64
|
+
return `${active} active · ${recent} recent · ${reviews} reviews`;
|
|
56
65
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated Settings catalog — browse → preview → confirm (no filesystem install).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const SETTINGS_PHASE = Object.freeze({
|
|
6
|
+
BROWSE: "browse",
|
|
7
|
+
PREVIEW: "preview",
|
|
8
|
+
CONFIRMING: "confirming",
|
|
9
|
+
COMPLETED: "completed"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const CURATED_INTEGRATIONS = Object.freeze([
|
|
13
|
+
Object.freeze({
|
|
14
|
+
id: "pi-usage-widget",
|
|
15
|
+
name: "Pi usage widget",
|
|
16
|
+
version: "0.2.1",
|
|
17
|
+
license: "MIT",
|
|
18
|
+
status: "available",
|
|
19
|
+
capabilities: Object.freeze(["usage-display", "local-only"]),
|
|
20
|
+
permissions: Object.freeze(["full-local-access-on-install"]),
|
|
21
|
+
audit: "pending-security-review",
|
|
22
|
+
summary: "Local Pi package for usage visibility in the agent UI.",
|
|
23
|
+
notes: "Explicit install only. Never auto-applied by Kairo."
|
|
24
|
+
})
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export function createSettingsActionState() {
|
|
28
|
+
return {
|
|
29
|
+
phase: SETTINGS_PHASE.BROWSE,
|
|
30
|
+
selectedId: null,
|
|
31
|
+
message: null,
|
|
32
|
+
receipt: null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function listCuratedIntegrations() {
|
|
37
|
+
return [...CURATED_INTEGRATIONS];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getCuratedIntegration(id) {
|
|
41
|
+
return CURATED_INTEGRATIONS.find((entry) => entry.id === id) ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function reduceSettingsAction(state, action) {
|
|
45
|
+
switch (action.type) {
|
|
46
|
+
case "reset":
|
|
47
|
+
return createSettingsActionState();
|
|
48
|
+
case "preview": {
|
|
49
|
+
const entry = getCuratedIntegration(action.id);
|
|
50
|
+
if (!entry) {
|
|
51
|
+
return { ...state, phase: SETTINGS_PHASE.BROWSE, message: "Integration not found." };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
phase: SETTINGS_PHASE.PREVIEW,
|
|
55
|
+
selectedId: entry.id,
|
|
56
|
+
message: "Enter Confirm · Esc back — no files written yet.",
|
|
57
|
+
receipt: null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
case "confirm-prompt":
|
|
61
|
+
if (state.phase !== SETTINGS_PHASE.PREVIEW || !state.selectedId) return state;
|
|
62
|
+
return {
|
|
63
|
+
...state,
|
|
64
|
+
phase: SETTINGS_PHASE.CONFIRMING,
|
|
65
|
+
message: "Confirm explicit install intent? Y confirm · N/Esc cancel"
|
|
66
|
+
};
|
|
67
|
+
case "confirm": {
|
|
68
|
+
if (state.phase !== SETTINGS_PHASE.CONFIRMING || !state.selectedId) return state;
|
|
69
|
+
const entry = getCuratedIntegration(state.selectedId);
|
|
70
|
+
return {
|
|
71
|
+
phase: SETTINGS_PHASE.COMPLETED,
|
|
72
|
+
selectedId: state.selectedId,
|
|
73
|
+
message: "Confirmed — no auto-install. Apply via documented Pi/CLI path only.",
|
|
74
|
+
receipt: {
|
|
75
|
+
id: state.selectedId,
|
|
76
|
+
version: entry?.version ?? null,
|
|
77
|
+
confirmedAt: new Date().toISOString(),
|
|
78
|
+
wroteFiles: false
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
case "cancel":
|
|
83
|
+
return {
|
|
84
|
+
...createSettingsActionState(),
|
|
85
|
+
message: "Cancelled — no files written."
|
|
86
|
+
};
|
|
87
|
+
default:
|
|
88
|
+
return state;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function formatSettingsBrowseLines(
|
|
93
|
+
integrations = listCuratedIntegrations(),
|
|
94
|
+
listIndex = 0
|
|
95
|
+
) {
|
|
96
|
+
if (integrations.length === 0) return ["No curated integrations available."];
|
|
97
|
+
return [
|
|
98
|
+
"CURATED INTEGRATIONS",
|
|
99
|
+
...integrations.map((entry, index) => {
|
|
100
|
+
const mark = index === listIndex ? "›" : " ";
|
|
101
|
+
return `${mark} ${entry.status} · ${entry.name} · ${entry.version} · ${entry.license}`;
|
|
102
|
+
}),
|
|
103
|
+
"",
|
|
104
|
+
"POLICY",
|
|
105
|
+
"Browse → preview → confirm. Install stays explicit; never automatic.",
|
|
106
|
+
"Enter preview · Esc back"
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function formatSettingsDetailLines(entry, settingsAction = null) {
|
|
111
|
+
if (!entry) return ["Integration not found."];
|
|
112
|
+
const phase = settingsAction?.phase ?? SETTINGS_PHASE.BROWSE;
|
|
113
|
+
|
|
114
|
+
// Completed: receipt first so compact TTY (80×24) shows wroteFiles above the fold.
|
|
115
|
+
if (phase === SETTINGS_PHASE.COMPLETED && settingsAction?.receipt) {
|
|
116
|
+
const receipt = settingsAction.receipt;
|
|
117
|
+
return [
|
|
118
|
+
"RESULT",
|
|
119
|
+
settingsAction.message ?? "Confirmed — no auto-install.",
|
|
120
|
+
"RECEIPT",
|
|
121
|
+
`Id · ${receipt.id} · wroteFiles · ${receipt.wroteFiles}`,
|
|
122
|
+
`Confirmed · ${receipt.confirmedAt}`,
|
|
123
|
+
`${entry.name} · ${entry.version} · ${entry.license}`,
|
|
124
|
+
"Esc back · install stays explicit"
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const lines = [
|
|
129
|
+
"INTEGRATION",
|
|
130
|
+
`${entry.name} · ${entry.version}`,
|
|
131
|
+
`Status · ${entry.status} · License · ${entry.license}`,
|
|
132
|
+
`Audit · ${entry.audit}`,
|
|
133
|
+
"",
|
|
134
|
+
"CAPABILITIES",
|
|
135
|
+
entry.capabilities.join(" · ") || "none",
|
|
136
|
+
"",
|
|
137
|
+
"PERMISSIONS",
|
|
138
|
+
entry.permissions.join(" · ") || "none",
|
|
139
|
+
"",
|
|
140
|
+
"SUMMARY",
|
|
141
|
+
entry.summary,
|
|
142
|
+
entry.notes
|
|
143
|
+
];
|
|
144
|
+
if (phase === SETTINGS_PHASE.PREVIEW || phase === SETTINGS_PHASE.CONFIRMING) {
|
|
145
|
+
lines.push("", "PREVIEW", "No filesystem changes. Confirm only records explicit intent.");
|
|
146
|
+
}
|
|
147
|
+
if (settingsAction?.message) lines.push("", settingsAction.message);
|
|
148
|
+
return lines;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function formatSettingsLines({
|
|
152
|
+
listIndex = 0,
|
|
153
|
+
settingsAction = null,
|
|
154
|
+
snapshot = null,
|
|
155
|
+
diagnostics = null
|
|
156
|
+
} = {}) {
|
|
157
|
+
const integrations = listCuratedIntegrations();
|
|
158
|
+
const phase = settingsAction?.phase ?? SETTINGS_PHASE.BROWSE;
|
|
159
|
+
if (phase !== SETTINGS_PHASE.BROWSE && settingsAction?.selectedId) {
|
|
160
|
+
return formatSettingsDetailLines(
|
|
161
|
+
getCuratedIntegration(settingsAction.selectedId),
|
|
162
|
+
settingsAction
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const policy = snapshot?.policy;
|
|
167
|
+
const sources = diagnostics?.profile?.sources;
|
|
168
|
+
const sourceLabel = sources?.global || sources?.project
|
|
169
|
+
? [sources.global ? "global" : null, sources.project ? "project" : null].filter(Boolean).join(", ")
|
|
170
|
+
: "none";
|
|
171
|
+
return [
|
|
172
|
+
...formatSettingsBrowseLines(integrations, listIndex),
|
|
173
|
+
"",
|
|
174
|
+
"PROFILE & POLICY",
|
|
175
|
+
`Policy · ${policy?.profile ?? "none"} · apply ${policy?.applyMode ?? "n/a"}`,
|
|
176
|
+
`Preflight · ${policy?.preflight ?? "n/a"} · sources · ${sourceLabel}`,
|
|
177
|
+
"",
|
|
178
|
+
`Selected · ${integrations[listIndex]?.id ?? "none"}`
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function buildSettingsFooterParts(phase = SETTINGS_PHASE.BROWSE) {
|
|
183
|
+
switch (phase) {
|
|
184
|
+
case SETTINGS_PHASE.PREVIEW:
|
|
185
|
+
return ["Enter Confirm", "Esc Back"];
|
|
186
|
+
case SETTINGS_PHASE.CONFIRMING:
|
|
187
|
+
return ["Y Confirm", "N/Esc Cancel"];
|
|
188
|
+
case SETTINGS_PHASE.COMPLETED:
|
|
189
|
+
return ["Esc Back", "/ Actions"];
|
|
190
|
+
case SETTINGS_PHASE.BROWSE:
|
|
191
|
+
default:
|
|
192
|
+
return ["↑↓ Select", "Enter Preview", "Esc Nav", "/ Actions"];
|
|
193
|
+
}
|
|
194
|
+
}
|