@kal-elsam/kairo-runtime 0.4.3 → 0.5.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 +124 -17
- package/global-template/components/catalog.json +63 -6
- package/global-template/components/sdd-core/personas/teaching.md +42 -0
- package/global-template/components/sdd-core/skills/sdd-apply/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-apply/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-archive/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-archive/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-design/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-design/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-explore/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-explore/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-init/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-init/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-propose/SKILL.md +35 -0
- package/global-template/components/sdd-core/skills/sdd-propose/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-spec/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-spec/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-tasks/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-tasks/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-verify/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-verify/references/contract.md +4 -0
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +29 -8
- package/scripts/smoke-test.sh +9 -0
- package/src/cli.js +54 -5
- package/src/global/action-planner.js +17 -4
- package/src/global/component-authoring.js +1 -2
- package/src/global/component-ecosystem-checks.js +74 -15
- package/src/global/component-health.js +56 -0
- package/src/global/component-integration-cli.js +302 -0
- package/src/global/component-manifest.js +214 -0
- package/src/global/component-registry.js +19 -2
- package/src/global/component-resolve.js +51 -0
- package/src/global/components/sdd-core.js +6 -0
- package/src/global/control-plane-proposals.js +229 -0
- package/src/global/control-plane-snapshot.js +212 -0
- package/src/global/dashboard-guidance.js +27 -3
- package/src/global/diff.js +2 -5
- package/src/global/global-cli.js +16 -1
- package/src/global/global-doctor.js +75 -2
- package/src/global/global-installer.js +43 -6
- package/src/global/governance-actions.js +212 -0
- package/src/global/governance-repair.js +13 -0
- package/src/global/ink/cockpit-changes.js +124 -0
- package/src/global/ink/cockpit-control-center.js +106 -0
- package/src/global/ink/cockpit-controller.js +17 -0
- package/src/global/ink/cockpit-enter.js +27 -0
- package/src/global/ink/cockpit-focus.js +7 -1
- package/src/global/ink/cockpit-models.js +98 -46
- package/src/global/ink/cockpit-proposals.js +82 -0
- package/src/global/ink/cockpit-recovery.js +120 -0
- package/src/global/ink/cockpit-runs.js +47 -0
- package/src/global/ink/cockpit-scan.js +60 -0
- package/src/global/ink/cockpit-views.js +144 -74
- package/src/global/ink/orchestrator-app.js +156 -42
- package/src/global/ink/orchestrator-state.js +6 -0
- package/src/global/ink/use-orchestrator-data.js +239 -17
- package/src/global/integrations/engram-apply.js +105 -0
- package/src/global/integrations/engram-evidence.js +218 -0
- package/src/global/integrations/engram-exec.js +107 -0
- package/src/global/integrations/engram-plan.js +60 -0
- package/src/global/integrations/engram-provider.js +22 -0
- package/src/global/integrations/engram-receipts.js +116 -0
- package/src/global/integrations/engram-rollback.js +131 -0
- package/src/global/integrations/index.js +12 -0
- package/src/global/integrations/provider-contract.js +83 -0
- package/src/global/integrations/provider-registry.js +47 -0
- package/src/global/integrations/sdd-apply.js +190 -0
- package/src/global/integrations/sdd-destinations.js +95 -0
- package/src/global/integrations/sdd-evidence.js +89 -0
- package/src/global/integrations/sdd-fs-guard.js +183 -0
- package/src/global/integrations/sdd-lifecycle.js +84 -0
- package/src/global/integrations/sdd-persona.js +71 -0
- package/src/global/integrations/sdd-plan.js +87 -0
- package/src/global/integrations/sdd-provider.js +21 -0
- package/src/global/integrations/sdd-receipts.js +44 -0
- package/src/global/integrations/sdd-rollback.js +269 -0
- package/src/global/integrations/sdd-skill-files.js +61 -0
- package/src/global/integrations/sdd-state.js +175 -0
- package/src/global/integrations/sdd-verify.js +122 -0
- package/src/global/intelligence/backends/opencode-catalog.js +212 -0
- package/src/global/intelligence/backends/opencode-cli.js +247 -0
- package/src/global/intelligence/backends/opencode-evidence.js +129 -0
- package/src/global/intelligence/backends/opencode-http.js +152 -0
- package/src/global/intelligence/backends/opencode-invoke.js +83 -0
- package/src/global/intelligence/backends/opencode-providers.js +67 -0
- package/src/global/intelligence/backends/opencode-runtime.js +242 -0
- package/src/global/intelligence/context-compiler.js +95 -50
- package/src/global/intelligence/index.js +38 -1
- package/src/global/intelligence/orchestrate.js +4 -2
- package/src/global/intelligence/registry.js +62 -6
- package/src/global/intelligence/router.js +188 -70
- package/src/global/intelligence/transport-registry.js +147 -0
- package/src/global/intelligence/types.js +21 -2
- package/src/global/intelligence-cli.js +48 -17
- package/src/global/intelligence-session.js +37 -0
- package/src/global/json-output.js +1 -0
- package/src/global/load-component-catalog.js +10 -28
- package/src/global/load-workspace-component-catalog.js +22 -63
- package/src/global/state-migration.js +21 -1
- package/src/global/state.js +13 -8
- package/src/global/status.js +4 -17
- package/src/global/sync.js +2 -5
|
@@ -19,6 +19,11 @@ import {
|
|
|
19
19
|
normalizeGlobalState
|
|
20
20
|
} from "./state-migration.js";
|
|
21
21
|
import { formatCliCommand } from "./brand/cli.js";
|
|
22
|
+
import {
|
|
23
|
+
aggregateSessionRefreshRequired,
|
|
24
|
+
applySddLifecycle,
|
|
25
|
+
nextSddState
|
|
26
|
+
} from "./integrations/sdd-lifecycle.js";
|
|
22
27
|
|
|
23
28
|
export async function installGlobalHarness({
|
|
24
29
|
packageRoot,
|
|
@@ -32,6 +37,7 @@ export async function installGlobalHarness({
|
|
|
32
37
|
dryRun = false
|
|
33
38
|
}) {
|
|
34
39
|
const paths = harnessHomePaths(homeDir);
|
|
40
|
+
const priorState = await readGlobalState(paths.statePath);
|
|
35
41
|
const timestamp = backupTimestamp();
|
|
36
42
|
const targetComponents = resolveTargetComponents({ components, noDefaultComponents, workspaceRoot });
|
|
37
43
|
const context = buildAdapterContext({
|
|
@@ -58,7 +64,9 @@ export async function installGlobalHarness({
|
|
|
58
64
|
configsRepaired: [],
|
|
59
65
|
configsUnchanged: [],
|
|
60
66
|
backups: [],
|
|
61
|
-
driftDetected: false
|
|
67
|
+
driftDetected: false,
|
|
68
|
+
integrations: {},
|
|
69
|
+
sessionRefreshRequired: false
|
|
62
70
|
};
|
|
63
71
|
|
|
64
72
|
const componentFiles = await installComponentAssets({
|
|
@@ -72,6 +80,9 @@ export async function installGlobalHarness({
|
|
|
72
80
|
result.assetsRepaired.push(...Object.keys(componentFiles));
|
|
73
81
|
|
|
74
82
|
await applyAdapterPlans({ context, targetAdapters, result });
|
|
83
|
+
await attachSddLifecycle({
|
|
84
|
+
result, homeDir, packageRoot, targetAdapters, targetComponents, dryRun, priorState
|
|
85
|
+
});
|
|
75
86
|
|
|
76
87
|
if (!dryRun) {
|
|
77
88
|
await persistGlobalState({
|
|
@@ -82,7 +93,8 @@ export async function installGlobalHarness({
|
|
|
82
93
|
targetComponents,
|
|
83
94
|
homeDir,
|
|
84
95
|
componentFiles,
|
|
85
|
-
backups: result.backups
|
|
96
|
+
backups: result.backups,
|
|
97
|
+
sdd: nextSddState(priorState?.sdd, result.integrations.sdd)
|
|
86
98
|
});
|
|
87
99
|
}
|
|
88
100
|
|
|
@@ -144,7 +156,9 @@ export async function syncGlobalHarness({
|
|
|
144
156
|
configsUnchanged: [],
|
|
145
157
|
backups: [],
|
|
146
158
|
driftDetected: hasRepairableDrift(driftChecks),
|
|
147
|
-
repairs: driftChecks.filter((check) => check.status === "missing" || check.status === "stale")
|
|
159
|
+
repairs: driftChecks.filter((check) => check.status === "missing" || check.status === "stale"),
|
|
160
|
+
integrations: {},
|
|
161
|
+
sessionRefreshRequired: false
|
|
148
162
|
};
|
|
149
163
|
|
|
150
164
|
const { coreFiles, repaired, unchanged } = await repairComponentAssets({
|
|
@@ -184,6 +198,10 @@ export async function syncGlobalHarness({
|
|
|
184
198
|
if (applied.action !== "unchanged") result.configsRepaired.push(applied.configFile);
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
await attachSddLifecycle({
|
|
202
|
+
result, homeDir, packageRoot, targetAdapters, targetComponents, dryRun, priorState: state
|
|
203
|
+
});
|
|
204
|
+
|
|
187
205
|
if (!dryRun) {
|
|
188
206
|
await persistGlobalState({
|
|
189
207
|
paths,
|
|
@@ -194,7 +212,8 @@ export async function syncGlobalHarness({
|
|
|
194
212
|
homeDir,
|
|
195
213
|
componentFiles: coreFiles,
|
|
196
214
|
backups: [...(state.backups ?? []), ...result.backups],
|
|
197
|
-
installedAt: state.installedAt
|
|
215
|
+
installedAt: state.installedAt,
|
|
216
|
+
sdd: nextSddState(state?.sdd, result.integrations.sdd)
|
|
198
217
|
});
|
|
199
218
|
}
|
|
200
219
|
|
|
@@ -244,6 +263,22 @@ export async function uninstallGlobalHarness({ homeDir, dryRun = false }) {
|
|
|
244
263
|
return result;
|
|
245
264
|
}
|
|
246
265
|
|
|
266
|
+
async function attachSddLifecycle({
|
|
267
|
+
result, homeDir, packageRoot, targetAdapters, targetComponents, dryRun, priorState
|
|
268
|
+
}) {
|
|
269
|
+
const sdd = await applySddLifecycle({
|
|
270
|
+
homeDir,
|
|
271
|
+
packageRoot,
|
|
272
|
+
agentIds: targetAdapters.map((adapter) => adapter.id),
|
|
273
|
+
componentIds: targetComponents.map((component) => component.id),
|
|
274
|
+
dryRun,
|
|
275
|
+
priorSdd: priorState?.sdd ?? null,
|
|
276
|
+
yes: true
|
|
277
|
+
});
|
|
278
|
+
result.integrations.sdd = sdd;
|
|
279
|
+
result.sessionRefreshRequired = aggregateSessionRefreshRequired(result, sdd);
|
|
280
|
+
}
|
|
281
|
+
|
|
247
282
|
async function applyAdapterPlans({ context, targetAdapters, result }) {
|
|
248
283
|
for (const adapter of targetAdapters) {
|
|
249
284
|
const plan = adapter.plan(context);
|
|
@@ -266,7 +301,8 @@ async function persistGlobalState({
|
|
|
266
301
|
homeDir,
|
|
267
302
|
componentFiles,
|
|
268
303
|
backups,
|
|
269
|
-
installedAt
|
|
304
|
+
installedAt,
|
|
305
|
+
sdd
|
|
270
306
|
}) {
|
|
271
307
|
const existingState = installedAt ? { installedAt } : await readGlobalState(paths.statePath);
|
|
272
308
|
const adapterEntries = targetAdapters.map((adapter) => buildAdapterStateEntry(adapter, homeDir));
|
|
@@ -279,6 +315,7 @@ async function persistGlobalState({
|
|
|
279
315
|
components: componentEntries,
|
|
280
316
|
coreFiles: componentFiles,
|
|
281
317
|
backups,
|
|
282
|
-
installedAt: existingState?.installedAt
|
|
318
|
+
installedAt: existingState?.installedAt,
|
|
319
|
+
sdd
|
|
283
320
|
}));
|
|
284
321
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { buildChangesFromPlan } from "./diff.js";
|
|
3
|
+
import { buildCheckSnapshot, recordRollbackHistory, recordSyncHistory } from "./history.js";
|
|
4
|
+
import { harnessHomePaths } from "./paths.js";
|
|
5
|
+
import { applyRollback, previewRollback } from "./rollback.js";
|
|
6
|
+
import { buildStatusReport } from "./status.js";
|
|
7
|
+
import { runHarnessSync } from "./sync.js";
|
|
8
|
+
import { needsManagedRepair } from "./governance-repair.js";
|
|
9
|
+
|
|
10
|
+
function stableJson(value) {
|
|
11
|
+
if (value == null || typeof value !== "object") return JSON.stringify(value);
|
|
12
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
13
|
+
const keys = Object.keys(value).sort();
|
|
14
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function fingerprintGovernancePreview(payload) {
|
|
18
|
+
return createHash("sha256").update(stableJson(payload)).digest("hex");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function summarizeIntegrations(result) {
|
|
22
|
+
const sdd = result?.integrations?.sdd ?? null;
|
|
23
|
+
if (!sdd) return null;
|
|
24
|
+
return {
|
|
25
|
+
status: sdd.status ?? null,
|
|
26
|
+
partial: Boolean(sdd.partial),
|
|
27
|
+
conflicts: sdd.conflicts ?? [],
|
|
28
|
+
receiptId: sdd.receipt?.id ?? null,
|
|
29
|
+
sessionRefreshRequired: Boolean(result?.sessionRefreshRequired ?? sdd.sessionRefreshRequired)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildSyncPreviewBody({ action, report, result }) {
|
|
34
|
+
const changes = result ? buildChangesFromPlan(result) : [];
|
|
35
|
+
const integrations = summarizeIntegrations(result);
|
|
36
|
+
const hasIntegrationWork = Boolean(
|
|
37
|
+
integrations
|
|
38
|
+
&& integrations.status
|
|
39
|
+
&& integrations.status !== "skipped"
|
|
40
|
+
&& integrations.status !== "noop"
|
|
41
|
+
);
|
|
42
|
+
return {
|
|
43
|
+
kind: "sync",
|
|
44
|
+
action,
|
|
45
|
+
setupRequired: action === "setup-required",
|
|
46
|
+
hasChanges: changes.length > 0 || hasIntegrationWork || needsManagedRepair(report),
|
|
47
|
+
changes,
|
|
48
|
+
integrations,
|
|
49
|
+
checksBefore: buildCheckSnapshot(report?.counts),
|
|
50
|
+
overall: report?.overall ?? null
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function captureSyncPreview(options) {
|
|
55
|
+
const outcome = await runHarnessSync({
|
|
56
|
+
...options,
|
|
57
|
+
dryRun: true,
|
|
58
|
+
yes: false,
|
|
59
|
+
confirm: false,
|
|
60
|
+
json: true,
|
|
61
|
+
interactive: false,
|
|
62
|
+
preflight: false
|
|
63
|
+
});
|
|
64
|
+
const body = buildSyncPreviewBody({
|
|
65
|
+
action: outcome.action,
|
|
66
|
+
report: outcome.report,
|
|
67
|
+
result: outcome.result
|
|
68
|
+
});
|
|
69
|
+
const fingerprint = fingerprintGovernancePreview(body);
|
|
70
|
+
return { ...body, fingerprint, wrote: false };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function previewGovernanceSync(options) {
|
|
74
|
+
return captureSyncPreview(options);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function applyGovernanceSync({
|
|
78
|
+
preview,
|
|
79
|
+
packageRoot,
|
|
80
|
+
packageName,
|
|
81
|
+
cliVersion,
|
|
82
|
+
homeDir,
|
|
83
|
+
workspaceRoot = null
|
|
84
|
+
}) {
|
|
85
|
+
if (!preview?.fingerprint) {
|
|
86
|
+
return { ok: false, reason: "missing-preview", wrote: false, receipt: null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const paths = harnessHomePaths(homeDir);
|
|
90
|
+
const checksBefore = preview.checksBefore
|
|
91
|
+
?? buildCheckSnapshot((await buildStatusReport(homeDir, { packageRoot, workspaceRoot }))?.counts);
|
|
92
|
+
|
|
93
|
+
const fresh = await captureSyncPreview({
|
|
94
|
+
packageRoot, packageName, cliVersion, homeDir, workspaceRoot
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (fresh.setupRequired) {
|
|
98
|
+
return { ok: false, reason: "setup-required", wrote: false, receipt: null, preview: fresh };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (fresh.fingerprint !== preview.fingerprint) {
|
|
102
|
+
return { ok: false, reason: "stale-preview", wrote: false, receipt: null, preview: fresh };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!fresh.hasChanges) {
|
|
106
|
+
return {
|
|
107
|
+
ok: true,
|
|
108
|
+
reason: "noop",
|
|
109
|
+
wrote: false,
|
|
110
|
+
receipt: {
|
|
111
|
+
action: "noop",
|
|
112
|
+
backups: [],
|
|
113
|
+
checksBefore,
|
|
114
|
+
checksAfter: fresh.checksBefore,
|
|
115
|
+
integrations: fresh.integrations
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const outcome = await runHarnessSync({
|
|
121
|
+
packageRoot,
|
|
122
|
+
packageName,
|
|
123
|
+
cliVersion,
|
|
124
|
+
homeDir,
|
|
125
|
+
workspaceRoot,
|
|
126
|
+
dryRun: false,
|
|
127
|
+
yes: true,
|
|
128
|
+
confirm: false,
|
|
129
|
+
json: true,
|
|
130
|
+
interactive: false,
|
|
131
|
+
preflight: false
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await recordSyncHistory(homeDir, {
|
|
135
|
+
cliVersion,
|
|
136
|
+
options: { dryRun: false, yes: true, json: true, interactive: false, preflight: false },
|
|
137
|
+
outcome,
|
|
138
|
+
checksBefore,
|
|
139
|
+
packageRoot,
|
|
140
|
+
workspaceRoot
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const afterReport = outcome.report
|
|
144
|
+
?? await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
|
|
145
|
+
const receipt = {
|
|
146
|
+
action: outcome.action,
|
|
147
|
+
backups: outcome.result?.backups ?? [],
|
|
148
|
+
checksBefore,
|
|
149
|
+
checksAfter: buildCheckSnapshot(afterReport?.counts),
|
|
150
|
+
integrations: summarizeIntegrations(outcome.result),
|
|
151
|
+
sessionRefreshRequired: Boolean(outcome.result?.sessionRefreshRequired),
|
|
152
|
+
historyPath: paths.historyPath
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
ok: outcome.action === "repaired" || outcome.action === "noop",
|
|
157
|
+
reason: outcome.action,
|
|
158
|
+
wrote: Boolean(outcome.wrote),
|
|
159
|
+
receipt,
|
|
160
|
+
partial: Boolean(outcome.result?.integrations?.sdd?.partial)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function buildRollbackPreviewBody(preview) {
|
|
165
|
+
return {
|
|
166
|
+
kind: "rollback",
|
|
167
|
+
snapshot: preview.snapshot,
|
|
168
|
+
noop: Boolean(preview.noop),
|
|
169
|
+
files: (preview.plans ?? []).map((plan) => ({
|
|
170
|
+
backupName: plan.backupName,
|
|
171
|
+
displayPath: plan.displayPath
|
|
172
|
+
}))
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function previewGovernanceRollback({ homeDir, snapshot }) {
|
|
177
|
+
const raw = await previewRollback({ homeDir, snapshot });
|
|
178
|
+
const body = buildRollbackPreviewBody(raw);
|
|
179
|
+
return { ...body, fingerprint: fingerprintGovernancePreview(body), wrote: false };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function applyGovernanceRollback({
|
|
183
|
+
preview,
|
|
184
|
+
homeDir,
|
|
185
|
+
cliVersion
|
|
186
|
+
}) {
|
|
187
|
+
if (!preview?.fingerprint || !preview?.snapshot) {
|
|
188
|
+
return { ok: false, reason: "missing-preview", wrote: false, receipt: null };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const fresh = await previewGovernanceRollback({ homeDir, snapshot: preview.snapshot });
|
|
192
|
+
if (fresh.fingerprint !== preview.fingerprint) {
|
|
193
|
+
return { ok: false, reason: "stale-preview", wrote: false, receipt: null, preview: fresh };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await applyRollback({ homeDir, snapshot: preview.snapshot });
|
|
197
|
+
await recordRollbackHistory(homeDir, { cliVersion, snapshot: preview.snapshot, result });
|
|
198
|
+
|
|
199
|
+
const afterReport = await buildStatusReport(homeDir, {});
|
|
200
|
+
return {
|
|
201
|
+
ok: true,
|
|
202
|
+
reason: result.noop ? "noop" : "applied",
|
|
203
|
+
wrote: !result.noop,
|
|
204
|
+
receipt: {
|
|
205
|
+
action: result.noop ? "noop" : "rollback",
|
|
206
|
+
snapshot: preview.snapshot,
|
|
207
|
+
restored: result.restored ?? [],
|
|
208
|
+
safetyBackup: result.safetyBackup ?? null,
|
|
209
|
+
checksAfter: buildCheckSnapshot(afterReport?.counts)
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Shared “repair needed” rule for sync, diff/snapshot, and cockpit actions. */
|
|
2
|
+
export function needsManagedRepair(statusReport) {
|
|
3
|
+
if (!statusReport) return false;
|
|
4
|
+
const needsSddRepair = (statusReport.checks ?? []).some((check) =>
|
|
5
|
+
check.componentId === "sdd-core"
|
|
6
|
+
&& check.category === "integration"
|
|
7
|
+
&& check.status === "warning"
|
|
8
|
+
);
|
|
9
|
+
return statusReport.overall === "drift"
|
|
10
|
+
|| (statusReport.counts?.missing ?? 0) > 0
|
|
11
|
+
|| (statusReport.counts?.stale ?? 0) > 0
|
|
12
|
+
|| needsSddRepair;
|
|
13
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { formatProposalLines, proposalLimitForLayout } from "./cockpit-proposals.js";
|
|
2
|
+
|
|
3
|
+
export const CHANGES_PHASE = Object.freeze({
|
|
4
|
+
IDLE: "idle",
|
|
5
|
+
PREVIEWING: "previewing",
|
|
6
|
+
CONFIRMING: "confirming",
|
|
7
|
+
APPLYING: "applying",
|
|
8
|
+
COMPLETED: "completed",
|
|
9
|
+
FAILED: "failed"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function createChangesActionState() {
|
|
13
|
+
return { phase: CHANGES_PHASE.IDLE, preview: null, receipt: null, error: null, message: null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function reduceChangesAction(state, action) {
|
|
17
|
+
switch (action.type) {
|
|
18
|
+
case "reset":
|
|
19
|
+
return createChangesActionState();
|
|
20
|
+
case "preview-start":
|
|
21
|
+
return { ...createChangesActionState(), phase: CHANGES_PHASE.PREVIEWING, message: "Building exact preview…" };
|
|
22
|
+
case "preview-ready":
|
|
23
|
+
if (action.preview?.setupRequired) {
|
|
24
|
+
return {
|
|
25
|
+
...createChangesActionState(),
|
|
26
|
+
phase: CHANGES_PHASE.FAILED,
|
|
27
|
+
error: "setup-required",
|
|
28
|
+
message: "Not configured — open Control center and run setup. Changes never installs silently."
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!action.preview?.hasChanges) {
|
|
32
|
+
return {
|
|
33
|
+
...createChangesActionState(),
|
|
34
|
+
phase: CHANGES_PHASE.IDLE,
|
|
35
|
+
preview: action.preview,
|
|
36
|
+
message: "No pending governance changes."
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
phase: CHANGES_PHASE.CONFIRMING,
|
|
41
|
+
preview: action.preview,
|
|
42
|
+
receipt: null,
|
|
43
|
+
error: null,
|
|
44
|
+
message: "Confirm apply? Y apply · N/Esc cancel"
|
|
45
|
+
};
|
|
46
|
+
case "preview-failed":
|
|
47
|
+
return { ...state, phase: CHANGES_PHASE.FAILED, error: action.error ?? "preview-failed", message: action.message ?? "Preview failed." };
|
|
48
|
+
case "apply-start":
|
|
49
|
+
return { ...state, phase: CHANGES_PHASE.APPLYING, message: "Applying confirmed repairs…" };
|
|
50
|
+
case "apply-done":
|
|
51
|
+
return {
|
|
52
|
+
phase: action.ok ? CHANGES_PHASE.COMPLETED : CHANGES_PHASE.FAILED,
|
|
53
|
+
preview: action.preview ?? state.preview,
|
|
54
|
+
receipt: action.receipt ?? null,
|
|
55
|
+
error: action.ok ? null : (action.reason ?? "apply-failed"),
|
|
56
|
+
message: action.message ?? (action.ok ? "Apply complete." : "Apply failed.")
|
|
57
|
+
};
|
|
58
|
+
case "cancel":
|
|
59
|
+
return { ...createChangesActionState(), message: "Cancelled — no files written." };
|
|
60
|
+
default:
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatChangesActionLines({ snapshot, changesAction, layoutMode = "compact" }) {
|
|
66
|
+
const diff = snapshot?.diff;
|
|
67
|
+
const phase = changesAction?.phase ?? CHANGES_PHASE.IDLE;
|
|
68
|
+
const lines = [`Changes · ${phase}`];
|
|
69
|
+
|
|
70
|
+
const proposalLines = formatProposalLines(snapshot?.proposals ?? [], {
|
|
71
|
+
limit: proposalLimitForLayout(layoutMode),
|
|
72
|
+
destinationFilter: "changes",
|
|
73
|
+
budgets: snapshot?.budgets ?? null
|
|
74
|
+
});
|
|
75
|
+
if (proposalLines[0] !== "No proposals targeting this view.") {
|
|
76
|
+
lines.push(...proposalLines, "");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (changesAction?.message) lines.push(changesAction.message);
|
|
80
|
+
if (changesAction?.error && changesAction.error !== "setup-required") lines.push(`Error: ${changesAction.error}`);
|
|
81
|
+
|
|
82
|
+
const preview = changesAction?.preview;
|
|
83
|
+
if (preview?.hasChanges) {
|
|
84
|
+
lines.push(`${preview.changes?.length ?? 0} planned change(s)`);
|
|
85
|
+
for (const change of preview.changes ?? []) {
|
|
86
|
+
lines.push(`${change.action ?? change.kind} · ${change.target} · ${change.status ?? "planned"}`);
|
|
87
|
+
}
|
|
88
|
+
if (preview.integrations?.status && preview.integrations.status !== "skipped") {
|
|
89
|
+
lines.push(`SDD lifecycle · ${preview.integrations.status}${preview.integrations.partial ? " (partial)" : ""}`);
|
|
90
|
+
}
|
|
91
|
+
if (preview.fingerprint) lines.push(`Fingerprint · ${preview.fingerprint.slice(0, 12)}…`);
|
|
92
|
+
} else if (diff && !preview) {
|
|
93
|
+
if (!diff.installed) lines.push(diff.summary ?? "Setup required before changes can be previewed.");
|
|
94
|
+
else if (!diff.hasChanges) lines.push(diff.summary ?? "No pending governance changes.");
|
|
95
|
+
else {
|
|
96
|
+
lines.push(diff.summary ?? "Pending changes");
|
|
97
|
+
for (const change of diff.changes ?? []) lines.push(`${change.action ?? change.kind} · ${change.target} · ${change.status}`);
|
|
98
|
+
}
|
|
99
|
+
} else if (!diff) {
|
|
100
|
+
lines.push("Scan did not include diff yet. Press R to re-scan.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const receipt = changesAction?.receipt;
|
|
104
|
+
if (receipt) {
|
|
105
|
+
lines.push("", `Receipt · ${receipt.action}`);
|
|
106
|
+
if (receipt.backups?.length) lines.push(`Backups · ${receipt.backups.length}`);
|
|
107
|
+
if (receipt.checksBefore && receipt.checksAfter) {
|
|
108
|
+
lines.push(`Checks · before ok=${receipt.checksBefore.ok} → after ok=${receipt.checksAfter.ok}`);
|
|
109
|
+
}
|
|
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
|
+
}
|
|
113
|
+
|
|
114
|
+
if (phase === CHANGES_PHASE.IDLE || phase === CHANGES_PHASE.COMPLETED || phase === CHANGES_PHASE.FAILED) {
|
|
115
|
+
lines.push("", "A preview · Y confirm apply · N/Esc cancel · R re-scan");
|
|
116
|
+
}
|
|
117
|
+
return lines;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function buildChangesFooterParts(phase) {
|
|
121
|
+
if (phase === CHANGES_PHASE.PREVIEWING || phase === CHANGES_PHASE.APPLYING) return ["Working…", "Esc Back"];
|
|
122
|
+
if (phase === CHANGES_PHASE.CONFIRMING) return ["Y Apply", "N/Esc Cancel"];
|
|
123
|
+
return ["A Preview", "R Re-scan", "Esc Back"];
|
|
124
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { CONTROL_PLANE_HEALTH } from "../control-plane-snapshot.js";
|
|
2
|
+
import { formatProposalLines, proposalLimitForLayout } from "./cockpit-proposals.js";
|
|
3
|
+
|
|
4
|
+
export function buildControlCenterModel({
|
|
5
|
+
projectName = "project",
|
|
6
|
+
snapshot = null,
|
|
7
|
+
layoutMode = "compact"
|
|
8
|
+
} = {}) {
|
|
9
|
+
if (!snapshot) {
|
|
10
|
+
return {
|
|
11
|
+
title: `CONTROL CENTER — ${projectName}`,
|
|
12
|
+
purpose: "Kairo aligns installed IDEs and agents with this project's architecture, rules, tools, and workflows.",
|
|
13
|
+
health: {
|
|
14
|
+
kind: CONTROL_PLANE_HEALTH.CHECK_FAILED,
|
|
15
|
+
label: "CHECK FAILED",
|
|
16
|
+
summaryLine: "Control-plane scan not available yet."
|
|
17
|
+
},
|
|
18
|
+
coverageLines: [],
|
|
19
|
+
cta: {
|
|
20
|
+
title: "NEXT",
|
|
21
|
+
actionTitle: "Retry scan",
|
|
22
|
+
actionDetail: "Press R to reload the read-only governance scan.",
|
|
23
|
+
enterHint: "R Retry",
|
|
24
|
+
kind: "verify",
|
|
25
|
+
destination: null
|
|
26
|
+
},
|
|
27
|
+
notes: [],
|
|
28
|
+
proposalLines: ["No evidence-backed proposals."],
|
|
29
|
+
includeEmbeddedStatus: layoutMode !== "wide"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const healthLabel = formatHealthLabel(snapshot.health);
|
|
34
|
+
const coverage = snapshot.coverage ?? {};
|
|
35
|
+
const policy = snapshot.policy;
|
|
36
|
+
const warnings = snapshot.status?.counts?.warning ?? 0;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
title: `CONTROL CENTER — ${projectName}`,
|
|
40
|
+
purpose: "Kairo aligns installed IDEs and agents with this project's architecture, rules, tools, and workflows.",
|
|
41
|
+
health: {
|
|
42
|
+
kind: snapshot.health,
|
|
43
|
+
label: healthLabel,
|
|
44
|
+
summaryLine: [
|
|
45
|
+
`${coverage.governedAgents ?? 0}/${coverage.detectedAgents ?? 0} agents governed`,
|
|
46
|
+
`${coverage.components ?? 0} modules`,
|
|
47
|
+
`${snapshot.backups?.count ?? 0} backups`
|
|
48
|
+
].join(" · ")
|
|
49
|
+
},
|
|
50
|
+
coverageLines: [
|
|
51
|
+
`Active modules: ${(coverage.activeModules ?? []).join(", ") || "none"}`,
|
|
52
|
+
`Policy: ${policy?.profile ?? "none"} · applyMode ${policy?.applyMode ?? "n/a"}`,
|
|
53
|
+
warnings > 0
|
|
54
|
+
? `Notes: ${warnings} non-blocking check warning(s)`
|
|
55
|
+
: "Notes: none",
|
|
56
|
+
snapshot.diff?.hasChanges
|
|
57
|
+
? `Drift: ${snapshot.diff.changeCount ?? snapshot.diff.changes?.length ?? 0} change(s) pending review`
|
|
58
|
+
: "Drift: clean"
|
|
59
|
+
],
|
|
60
|
+
cta: {
|
|
61
|
+
title: "NEXT",
|
|
62
|
+
actionTitle: snapshot.cta?.title ?? "Review control plane",
|
|
63
|
+
actionDetail: snapshot.cta?.detail ?? "",
|
|
64
|
+
enterHint: "Enter again →",
|
|
65
|
+
kind: snapshot.cta?.kind ?? null,
|
|
66
|
+
destination: snapshot.cta?.destination ?? null
|
|
67
|
+
},
|
|
68
|
+
notes: buildNotes(snapshot),
|
|
69
|
+
proposalLines: formatProposalLines(snapshot.proposals ?? [], {
|
|
70
|
+
limit: proposalLimitForLayout(layoutMode),
|
|
71
|
+
budgets: snapshot.budgets ?? null
|
|
72
|
+
}),
|
|
73
|
+
includeEmbeddedStatus: layoutMode !== "wide",
|
|
74
|
+
runsSecondaryHint: "Runs remain available as a secondary capability after setup and repairs."
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatHealthLabel(kind) {
|
|
79
|
+
switch (kind) {
|
|
80
|
+
case CONTROL_PLANE_HEALTH.NOT_CONFIGURED:
|
|
81
|
+
return "NOT CONFIGURED";
|
|
82
|
+
case CONTROL_PLANE_HEALTH.ACTION_REQUIRED:
|
|
83
|
+
return "ACTION REQUIRED";
|
|
84
|
+
case CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES:
|
|
85
|
+
return "HEALTHY WITH NOTES";
|
|
86
|
+
case CONTROL_PLANE_HEALTH.HEALTHY:
|
|
87
|
+
return "HEALTHY";
|
|
88
|
+
case CONTROL_PLANE_HEALTH.CHECK_FAILED:
|
|
89
|
+
return "CHECK FAILED";
|
|
90
|
+
default:
|
|
91
|
+
return String(kind ?? "UNKNOWN");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildNotes(snapshot) {
|
|
96
|
+
const notes = [];
|
|
97
|
+
for (const check of snapshot.status?.checks ?? []) {
|
|
98
|
+
if (check.status === "warning") {
|
|
99
|
+
notes.push(`${check.name}: ${check.detail ?? check.status}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (notes.length === 0 && snapshot.health === CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES) {
|
|
103
|
+
notes.push("Non-blocking notes are present. Open IDEs & models or Harness modules for detail.");
|
|
104
|
+
}
|
|
105
|
+
return notes.slice(0, 6);
|
|
106
|
+
}
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
regionsForLayout,
|
|
13
13
|
routeCockpitKey
|
|
14
14
|
} from "./cockpit-focus.js";
|
|
15
|
+
import { isRunsChildView } from "./cockpit-runs.js";
|
|
16
|
+
import { navIndexForView } from "./cockpit-models.js";
|
|
15
17
|
|
|
16
18
|
export {
|
|
17
19
|
canTabBetweenRegions,
|
|
@@ -142,6 +144,9 @@ export function reduceCockpitUi(state, action) {
|
|
|
142
144
|
returnView: null
|
|
143
145
|
};
|
|
144
146
|
}
|
|
147
|
+
if (isRunsChildView(state.view)) {
|
|
148
|
+
return goRunsHub(state);
|
|
149
|
+
}
|
|
145
150
|
if (state.view !== ORCHESTRATOR_VIEWS.HOME) {
|
|
146
151
|
return goOverview(state);
|
|
147
152
|
}
|
|
@@ -169,3 +174,15 @@ function goOverview(state) {
|
|
|
169
174
|
returnView: null
|
|
170
175
|
};
|
|
171
176
|
}
|
|
177
|
+
|
|
178
|
+
function goRunsHub(state) {
|
|
179
|
+
return {
|
|
180
|
+
...state,
|
|
181
|
+
helpOpen: false,
|
|
182
|
+
view: ORCHESTRATOR_VIEWS.RUNS,
|
|
183
|
+
navIndex: navIndexForView(ORCHESTRATOR_VIEWS.RUNS),
|
|
184
|
+
listIndex: 0,
|
|
185
|
+
region: defaultRegionForView(ORCHESTRATOR_VIEWS.RUNS, state.layoutMode),
|
|
186
|
+
returnView: null
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Separate nav open from Control center CTA activation.
|
|
5
|
+
* Enter opens Control center first; Enter again while there activates the CTA.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveEnterNavIntent({
|
|
8
|
+
currentView,
|
|
9
|
+
navItem,
|
|
10
|
+
ctaDestination = null
|
|
11
|
+
} = {}) {
|
|
12
|
+
if (!navItem) return { kind: "noop" };
|
|
13
|
+
|
|
14
|
+
const isOverview = navItem.id === "overview" || navItem.view === ORCHESTRATOR_VIEWS.HOME;
|
|
15
|
+
if (isOverview) {
|
|
16
|
+
if (currentView === ORCHESTRATOR_VIEWS.HOME && ctaDestination) {
|
|
17
|
+
return { kind: "activate-cta", destination: ctaDestination };
|
|
18
|
+
}
|
|
19
|
+
return { kind: "open-nav", view: ORCHESTRATOR_VIEWS.HOME };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (navItem.action === "launch") {
|
|
23
|
+
return { kind: "launch" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { kind: "open-nav", view: navItem.view };
|
|
27
|
+
}
|
|
@@ -4,16 +4,22 @@ import { LAYOUT_MODES } from "./layout.js";
|
|
|
4
4
|
|
|
5
5
|
const NAV_FOCUSED_VIEWS = new Set([
|
|
6
6
|
ORCHESTRATOR_VIEWS.HOME,
|
|
7
|
+
ORCHESTRATOR_VIEWS.IDES,
|
|
8
|
+
ORCHESTRATOR_VIEWS.MODULES,
|
|
9
|
+
ORCHESTRATOR_VIEWS.CHANGES,
|
|
10
|
+
ORCHESTRATOR_VIEWS.PROFILE,
|
|
7
11
|
ORCHESTRATOR_VIEWS.PROVIDERS,
|
|
8
12
|
ORCHESTRATOR_VIEWS.DIAGNOSTICS,
|
|
9
13
|
ORCHESTRATOR_VIEWS.HELP
|
|
10
14
|
]);
|
|
11
15
|
|
|
12
16
|
const CONTENT_INTERACTIVE_VIEWS = new Set([
|
|
17
|
+
ORCHESTRATOR_VIEWS.RUNS,
|
|
13
18
|
ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
|
|
14
19
|
ORCHESTRATOR_VIEWS.RECENT_RUNS,
|
|
15
20
|
ORCHESTRATOR_VIEWS.RUN_DETAIL,
|
|
16
|
-
ORCHESTRATOR_VIEWS.LAUNCH
|
|
21
|
+
ORCHESTRATOR_VIEWS.LAUNCH,
|
|
22
|
+
ORCHESTRATOR_VIEWS.ACTIVITY
|
|
17
23
|
]);
|
|
18
24
|
|
|
19
25
|
export function isNavFocusedView(view) {
|