@kal-elsam/kairo-runtime 0.4.2 → 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 +22 -1
- 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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic evidence-backed control-plane proposals.
|
|
3
|
+
* Optional intelligence absence never yields a proposal.
|
|
4
|
+
* Health strings match CONTROL_PLANE_HEALTH in control-plane-snapshot.js.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const PROPOSAL_SEVERITY = Object.freeze({
|
|
8
|
+
HIGH: "high",
|
|
9
|
+
MEDIUM: "medium",
|
|
10
|
+
LOW: "low",
|
|
11
|
+
INFO: "info"
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const PROPOSAL_DESTINATION = Object.freeze({
|
|
15
|
+
CHANGES: "changes",
|
|
16
|
+
CONTROL_CENTER: "control-center",
|
|
17
|
+
IDES: "ides",
|
|
18
|
+
MODULES: "modules",
|
|
19
|
+
ACTIVITY: "activity",
|
|
20
|
+
PROFILE: "profile"
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const SEVERITY_RANK = Object.freeze({
|
|
24
|
+
high: 0,
|
|
25
|
+
medium: 1,
|
|
26
|
+
low: 2,
|
|
27
|
+
info: 3
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const ACTIONABLE = new Set(["missing", "stale", "warning", "failed"]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Derive proposals only from status, checks, diff, adapters, and policy.
|
|
34
|
+
* No proposal without evidence; deduped by id; severity then id order.
|
|
35
|
+
*/
|
|
36
|
+
export function buildControlPlaneProposals({
|
|
37
|
+
health = null,
|
|
38
|
+
status = null,
|
|
39
|
+
adapters = null,
|
|
40
|
+
policy = null,
|
|
41
|
+
diff = null
|
|
42
|
+
} = {}) {
|
|
43
|
+
const out = [];
|
|
44
|
+
|
|
45
|
+
if (health === "NOT_CONFIGURED" || status?.overall === "missing") {
|
|
46
|
+
out.push(make(
|
|
47
|
+
"setup-local",
|
|
48
|
+
PROPOSAL_SEVERITY.HIGH,
|
|
49
|
+
"Finish local setup",
|
|
50
|
+
status?.nextAction ?? "Configure the local ecosystem before applying repairs.",
|
|
51
|
+
PROPOSAL_DESTINATION.CHANGES,
|
|
52
|
+
[
|
|
53
|
+
ev("status", "status.overall", status?.overall ?? "missing"),
|
|
54
|
+
ev("health", "control-plane.health", health ?? "NOT_CONFIGURED")
|
|
55
|
+
]
|
|
56
|
+
));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const hasDiff = Boolean(diff?.hasChanges);
|
|
60
|
+
const isDrift = status?.overall === "drift" || health === "ACTION_REQUIRED";
|
|
61
|
+
// Gate on health/overall drift only. diff.hasChanges can be true from
|
|
62
|
+
// needsManagedRepair SDD integration warnings while overall stays "ok".
|
|
63
|
+
if (isDrift) {
|
|
64
|
+
const evidence = [
|
|
65
|
+
...(diff?.changes ?? []).slice(0, 8).map((change) =>
|
|
66
|
+
ev("diff", "diff.change", change.target ?? change.path ?? change.action ?? "managed-change")),
|
|
67
|
+
...(status?.checks ?? [])
|
|
68
|
+
.filter((check) => (check.status === "stale" || check.status === "missing") && !isIntel(check))
|
|
69
|
+
.slice(0, 8)
|
|
70
|
+
.map((check) => ev("check", `status.checks.${check.name}`, check.status)),
|
|
71
|
+
ev("status", "status.overall", status?.overall ?? "drift")
|
|
72
|
+
];
|
|
73
|
+
out.push(make(
|
|
74
|
+
"repair-drift",
|
|
75
|
+
PROPOSAL_SEVERITY.HIGH,
|
|
76
|
+
"Review and repair managed drift",
|
|
77
|
+
status?.nextAction
|
|
78
|
+
?? (hasDiff
|
|
79
|
+
? `${diff.changeCount ?? diff.changes?.length ?? 0} managed change(s) pending preview.`
|
|
80
|
+
: "Managed content has drifted from the expected inventory."),
|
|
81
|
+
PROPOSAL_DESTINATION.CHANGES,
|
|
82
|
+
evidence
|
|
83
|
+
));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (health === "CHECK_FAILED" || status?.overall === "failed") {
|
|
87
|
+
const failed = (status?.checks ?? [])
|
|
88
|
+
.filter((check) => (check.status === "failed" || check.status === "missing") && !isIntel(check));
|
|
89
|
+
if (failed.length === 0) {
|
|
90
|
+
out.push(make(
|
|
91
|
+
"verify-failed-overall",
|
|
92
|
+
PROPOSAL_SEVERITY.HIGH,
|
|
93
|
+
"Investigate failed governance checks",
|
|
94
|
+
status?.nextAction ?? "Doctor reported a failed overall status.",
|
|
95
|
+
PROPOSAL_DESTINATION.CONTROL_CENTER,
|
|
96
|
+
[ev("status", "status.overall", status?.overall ?? "failed")]
|
|
97
|
+
));
|
|
98
|
+
} else {
|
|
99
|
+
for (const check of failed) {
|
|
100
|
+
out.push(make(
|
|
101
|
+
`verify-check-${slug(check.name)}`,
|
|
102
|
+
PROPOSAL_SEVERITY.HIGH,
|
|
103
|
+
`Investigate check: ${check.name}`,
|
|
104
|
+
check.detail ?? `Check status is ${check.status}.`,
|
|
105
|
+
PROPOSAL_DESTINATION.CONTROL_CENTER,
|
|
106
|
+
[
|
|
107
|
+
ev("check", `status.checks.${check.name}`, check.status),
|
|
108
|
+
ev("status", "status.overall", status?.overall ?? "failed")
|
|
109
|
+
]
|
|
110
|
+
));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const matrix = adapters?.adapters ?? status?.agents ?? [];
|
|
116
|
+
for (const entry of matrix) {
|
|
117
|
+
if (!entry?.detected || entry.managed) continue;
|
|
118
|
+
out.push(make(
|
|
119
|
+
`govern-adapter-${slug(entry.id)}`,
|
|
120
|
+
PROPOSAL_SEVERITY.MEDIUM,
|
|
121
|
+
`Govern detected agent: ${entry.id}`,
|
|
122
|
+
`${entry.id} is detected locally but not managed by Kairo.`,
|
|
123
|
+
PROPOSAL_DESTINATION.IDES,
|
|
124
|
+
[
|
|
125
|
+
ev("adapter", `adapters.${entry.id}.detected`, "true"),
|
|
126
|
+
ev("adapter", `adapters.${entry.id}.managed`, "false")
|
|
127
|
+
]
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const effective = policy ?? status?.policy ?? null;
|
|
132
|
+
if (effective?.applyMode && effective.applyMode !== "confirm") {
|
|
133
|
+
out.push(make(
|
|
134
|
+
"review-policy-apply-mode",
|
|
135
|
+
PROPOSAL_SEVERITY.LOW,
|
|
136
|
+
"Review apply mode policy",
|
|
137
|
+
`Effective applyMode is "${effective.applyMode}". Confirm intentional deviation from confirm-gated applies.`,
|
|
138
|
+
PROPOSAL_DESTINATION.PROFILE,
|
|
139
|
+
[ev("policy", "policy.applyMode", String(effective.applyMode))]
|
|
140
|
+
));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const check of status?.checks ?? []) {
|
|
144
|
+
if (isIntel(check) || !ACTIONABLE.has(check.status)) continue;
|
|
145
|
+
|
|
146
|
+
if (isExternal(check)) {
|
|
147
|
+
out.push(make(
|
|
148
|
+
`external-${slug(check.name)}`,
|
|
149
|
+
check.status === "missing" || check.status === "failed"
|
|
150
|
+
? PROPOSAL_SEVERITY.MEDIUM
|
|
151
|
+
: PROPOSAL_SEVERITY.LOW,
|
|
152
|
+
`Review external integration: ${check.name}`,
|
|
153
|
+
externalDetail(check.detail ?? `Check status is ${check.status}.`),
|
|
154
|
+
PROPOSAL_DESTINATION.MODULES,
|
|
155
|
+
[
|
|
156
|
+
ev("check", `status.checks.${check.name}`, check.status),
|
|
157
|
+
ev("integration", "check.category", check.category ?? "integration")
|
|
158
|
+
]
|
|
159
|
+
));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (check.status !== "warning" && check.status !== "stale") continue;
|
|
164
|
+
const isBackup = check.category === "backups";
|
|
165
|
+
out.push(make(
|
|
166
|
+
`warning-${slug(check.name)}`,
|
|
167
|
+
isBackup ? PROPOSAL_SEVERITY.INFO : PROPOSAL_SEVERITY.LOW,
|
|
168
|
+
isBackup ? `Note: ${check.name}` : `Review note: ${check.name}`,
|
|
169
|
+
check.detail ?? `Check status is ${check.status}.`,
|
|
170
|
+
isBackup ? PROPOSAL_DESTINATION.ACTIVITY : PROPOSAL_DESTINATION.CONTROL_CENTER,
|
|
171
|
+
[ev("check", `status.checks.${check.name}`, check.status)]
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return finalize(out);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function finalize(candidates) {
|
|
179
|
+
const byId = new Map();
|
|
180
|
+
for (const item of candidates) {
|
|
181
|
+
if (!item?.id || !item.evidence?.length) continue;
|
|
182
|
+
if (!byId.has(item.id)) byId.set(item.id, item);
|
|
183
|
+
}
|
|
184
|
+
return [...byId.values()].sort((left, right) => {
|
|
185
|
+
const rank = (SEVERITY_RANK[left.severity] ?? 99) - (SEVERITY_RANK[right.severity] ?? 99);
|
|
186
|
+
return rank !== 0 ? rank : left.id.localeCompare(right.id);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function make(id, severity, title, detail, destination, evidence) {
|
|
191
|
+
return {
|
|
192
|
+
id,
|
|
193
|
+
severity,
|
|
194
|
+
title,
|
|
195
|
+
detail,
|
|
196
|
+
destination,
|
|
197
|
+
evidence: evidence.map((entry) => ({
|
|
198
|
+
type: String(entry.type),
|
|
199
|
+
source: String(entry.source),
|
|
200
|
+
ref: String(entry.ref)
|
|
201
|
+
}))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function ev(type, source, ref) {
|
|
206
|
+
return { type, source, ref: String(ref) };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function isIntel(check) {
|
|
210
|
+
return check?.category === "intelligence" || check?.name === "intelligence providers";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isExternal(check) {
|
|
214
|
+
const name = check?.name ?? "";
|
|
215
|
+
return name.startsWith("engram:") || name.startsWith("graphify:");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function externalDetail(detail) {
|
|
219
|
+
if (/runtime.?active|not runtime/i.test(detail)) return detail;
|
|
220
|
+
return `${detail} Configuration/version/freshness evidence only — not a claim of active runtime.`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function slug(value) {
|
|
224
|
+
return String(value ?? "unknown")
|
|
225
|
+
.toLowerCase()
|
|
226
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
227
|
+
.replace(/^-|-$/g, "")
|
|
228
|
+
|| "unknown";
|
|
229
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { buildAdapterMatrixReport } from "./adapter-matrix.js";
|
|
2
|
+
import { listBackupSnapshots } from "./backups.js";
|
|
3
|
+
import { buildDiffReport } from "./diff.js";
|
|
4
|
+
import { buildExplainReport } from "./explain.js";
|
|
5
|
+
import { getHistoryPath, readHistoryEvents } from "./history.js";
|
|
6
|
+
import { harnessHomePaths } from "./paths.js";
|
|
7
|
+
import { buildStatusReport } from "./status.js";
|
|
8
|
+
import { buildControlPlaneJson } from "./json-output.js";
|
|
9
|
+
import { formatCliCommand } from "./brand/cli.js";
|
|
10
|
+
import { buildRuntimeDashboardData } from "./runtime/run-cli.js";
|
|
11
|
+
import { describeBackupSnapshots } from "./rollback.js";
|
|
12
|
+
import { buildControlPlaneProposals } from "./control-plane-proposals.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Global control-plane health. Optional intelligence absence must never
|
|
16
|
+
* degrade these states by itself.
|
|
17
|
+
*/
|
|
18
|
+
export const CONTROL_PLANE_HEALTH = {
|
|
19
|
+
NOT_CONFIGURED: "NOT_CONFIGURED",
|
|
20
|
+
ACTION_REQUIRED: "ACTION_REQUIRED",
|
|
21
|
+
HEALTHY_WITH_NOTES: "HEALTHY_WITH_NOTES",
|
|
22
|
+
HEALTHY: "HEALTHY",
|
|
23
|
+
CHECK_FAILED: "CHECK_FAILED"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const CONTROL_PLANE_CTA = {
|
|
27
|
+
SETUP: "setup",
|
|
28
|
+
REPAIR: "repair",
|
|
29
|
+
VERIFY: "verify",
|
|
30
|
+
REVIEW: "review",
|
|
31
|
+
IDLE: "idle"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pure mapping from status.overall + warning counts to control-plane health.
|
|
36
|
+
* Does not inspect intelligence backends.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveControlPlaneHealth(status) {
|
|
39
|
+
if (!status || status.state == null || status.overall === "missing") {
|
|
40
|
+
return CONTROL_PLANE_HEALTH.NOT_CONFIGURED;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (status.overall === "failed") {
|
|
44
|
+
return CONTROL_PLANE_HEALTH.CHECK_FAILED;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (status.overall === "drift") {
|
|
48
|
+
return CONTROL_PLANE_HEALTH.ACTION_REQUIRED;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if ((status.counts?.warning ?? 0) > 0) {
|
|
52
|
+
return CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return CONTROL_PLANE_HEALTH.HEALTHY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prefer setup / repairs / verification before launching runs.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveControlPlaneCta({ health, status, diff } = {}) {
|
|
62
|
+
switch (health) {
|
|
63
|
+
case CONTROL_PLANE_HEALTH.NOT_CONFIGURED:
|
|
64
|
+
return {
|
|
65
|
+
kind: CONTROL_PLANE_CTA.SETUP,
|
|
66
|
+
title: "Finish local setup",
|
|
67
|
+
detail: status?.nextAction
|
|
68
|
+
?? `Run ${formatCliCommand("setup")} to configure the local ecosystem.`,
|
|
69
|
+
destination: "changes"
|
|
70
|
+
};
|
|
71
|
+
case CONTROL_PLANE_HEALTH.ACTION_REQUIRED:
|
|
72
|
+
return {
|
|
73
|
+
kind: CONTROL_PLANE_CTA.REPAIR,
|
|
74
|
+
title: "Review and repair drift",
|
|
75
|
+
detail: status?.nextAction
|
|
76
|
+
?? `Preview repairs with ${formatCliCommand("diff")}, then confirm sync.`,
|
|
77
|
+
destination: "changes"
|
|
78
|
+
};
|
|
79
|
+
case CONTROL_PLANE_HEALTH.CHECK_FAILED:
|
|
80
|
+
return {
|
|
81
|
+
kind: CONTROL_PLANE_CTA.VERIFY,
|
|
82
|
+
title: "Investigate failed checks",
|
|
83
|
+
detail: status?.nextAction
|
|
84
|
+
?? `Run ${formatCliCommand("doctor")} for details.`,
|
|
85
|
+
destination: "control-center"
|
|
86
|
+
};
|
|
87
|
+
case CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES:
|
|
88
|
+
return {
|
|
89
|
+
kind: CONTROL_PLANE_CTA.REVIEW,
|
|
90
|
+
title: "Review notes",
|
|
91
|
+
detail: diff?.hasChanges
|
|
92
|
+
? "Some notes remain after a healthy scan. Open Changes to inspect."
|
|
93
|
+
: "Ecosystem is healthy with non-blocking notes.",
|
|
94
|
+
destination: "control-center"
|
|
95
|
+
};
|
|
96
|
+
case CONTROL_PLANE_HEALTH.HEALTHY:
|
|
97
|
+
return {
|
|
98
|
+
kind: CONTROL_PLANE_CTA.IDLE,
|
|
99
|
+
title: "Ecosystem healthy",
|
|
100
|
+
detail: status?.nextAction ?? "No governance action required.",
|
|
101
|
+
destination: "control-center"
|
|
102
|
+
};
|
|
103
|
+
default: {
|
|
104
|
+
const _exhaustive = health;
|
|
105
|
+
return {
|
|
106
|
+
kind: CONTROL_PLANE_CTA.VERIFY,
|
|
107
|
+
title: "Review control plane",
|
|
108
|
+
detail: String(_exhaustive),
|
|
109
|
+
destination: "control-center"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Read-only control-plane snapshot composed from existing engines.
|
|
117
|
+
* Never writes. Optional intelligence is intentionally omitted from health.
|
|
118
|
+
*/
|
|
119
|
+
export async function buildControlPlaneSnapshot({
|
|
120
|
+
homeDir,
|
|
121
|
+
workspaceRoot = null,
|
|
122
|
+
packageName,
|
|
123
|
+
packageRoot,
|
|
124
|
+
cliVersion,
|
|
125
|
+
includeDiff = true,
|
|
126
|
+
includeExplain = false,
|
|
127
|
+
includeRuntime = true,
|
|
128
|
+
historyLimit = 20
|
|
129
|
+
} = {}) {
|
|
130
|
+
const status = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
|
|
131
|
+
const adapters = await buildAdapterMatrixReport(homeDir);
|
|
132
|
+
const history = await readHistoryEvents(homeDir, { limit: historyLimit });
|
|
133
|
+
const backupSnapshots = await describeBackupSnapshots(
|
|
134
|
+
harnessHomePaths(homeDir).backupsDir
|
|
135
|
+
).catch(async () => {
|
|
136
|
+
const names = await listBackupSnapshots(harnessHomePaths(homeDir).backupsDir);
|
|
137
|
+
return names.map((name) => ({ name, fileCount: null }));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const diff = includeDiff
|
|
141
|
+
? await buildDiffReport(homeDir, {
|
|
142
|
+
packageRoot,
|
|
143
|
+
packageName,
|
|
144
|
+
cliVersion,
|
|
145
|
+
workspaceRoot
|
|
146
|
+
})
|
|
147
|
+
: null;
|
|
148
|
+
|
|
149
|
+
const explain = includeExplain
|
|
150
|
+
? await buildExplainReport(homeDir)
|
|
151
|
+
: null;
|
|
152
|
+
|
|
153
|
+
const runtime = includeRuntime
|
|
154
|
+
? await buildRuntimeDashboardData({ homeDir, workspaceRoot, cliVersion })
|
|
155
|
+
: null;
|
|
156
|
+
|
|
157
|
+
const health = resolveControlPlaneHealth(status);
|
|
158
|
+
const cta = resolveControlPlaneCta({ health, status, diff });
|
|
159
|
+
const proposals = buildControlPlaneProposals({
|
|
160
|
+
health,
|
|
161
|
+
status,
|
|
162
|
+
adapters,
|
|
163
|
+
policy: status.policy,
|
|
164
|
+
diff
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const governedAgents = (adapters.adapters ?? []).filter((entry) => entry.managed).length;
|
|
168
|
+
const detectedAgents = (adapters.adapters ?? []).filter((entry) => entry.detected).length;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
readOnly: true,
|
|
172
|
+
scannedAt: new Date().toISOString(),
|
|
173
|
+
cliVersion,
|
|
174
|
+
packageName,
|
|
175
|
+
homeDir,
|
|
176
|
+
workspaceRoot,
|
|
177
|
+
health,
|
|
178
|
+
cta,
|
|
179
|
+
proposals,
|
|
180
|
+
coverage: {
|
|
181
|
+
detectedAgents,
|
|
182
|
+
governedAgents,
|
|
183
|
+
components: status.components?.length ?? 0,
|
|
184
|
+
activeModules: (status.components ?? [])
|
|
185
|
+
.filter((entry) => entry.status === "ok")
|
|
186
|
+
.map((entry) => entry.id)
|
|
187
|
+
},
|
|
188
|
+
status,
|
|
189
|
+
adapters,
|
|
190
|
+
policy: status.policy,
|
|
191
|
+
backups: {
|
|
192
|
+
count: status.backups,
|
|
193
|
+
snapshots: backupSnapshots
|
|
194
|
+
},
|
|
195
|
+
history: {
|
|
196
|
+
path: getHistoryPath(homeDir),
|
|
197
|
+
limit: historyLimit,
|
|
198
|
+
events: history.events,
|
|
199
|
+
warnings: history.warnings
|
|
200
|
+
},
|
|
201
|
+
diff,
|
|
202
|
+
explain,
|
|
203
|
+
runtime: runtime
|
|
204
|
+
? {
|
|
205
|
+
activeRuns: runtime.activeRuns?.length ?? 0,
|
|
206
|
+
recentRuns: runtime.recentRuns?.length ?? 0,
|
|
207
|
+
providers: runtime.providers ?? []
|
|
208
|
+
}
|
|
209
|
+
: null,
|
|
210
|
+
envelope: buildControlPlaneJson(status, { cliVersion })
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -42,7 +42,11 @@ export function countLaunchableAgents(dashboard = null) {
|
|
|
42
42
|
|
|
43
43
|
export function hasIntelligenceConfigured(diagnostics = null) {
|
|
44
44
|
const summary = diagnostics?.intelligence?.summary;
|
|
45
|
-
return Boolean(
|
|
45
|
+
return Boolean(
|
|
46
|
+
summary?.localAvailable
|
|
47
|
+
|| summary?.cloudConfigured
|
|
48
|
+
|| summary?.cloudAuthenticated
|
|
49
|
+
);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
function hasBlockingDiagnostics(diagnostics = null) {
|
|
@@ -174,17 +178,37 @@ export function resolveDashboardRecommendation({
|
|
|
174
178
|
};
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
|
|
181
|
+
const intelConfigured = Boolean(
|
|
182
|
+
intelligence?.localAvailable
|
|
183
|
+
|| intelligence?.cloudConfigured
|
|
184
|
+
|| intelligence?.cloudAuthenticated
|
|
185
|
+
);
|
|
186
|
+
if (!intelConfigured) {
|
|
178
187
|
return {
|
|
179
188
|
kind: NEXT_STEP_KINDS.ENABLE_INTELLIGENCE,
|
|
180
189
|
title: "Enable optional intelligence",
|
|
181
|
-
message: "Enable intelligence: start Ollama or set OPENROUTER_API_KEY, then retry.",
|
|
190
|
+
message: "Enable intelligence: start Ollama, install OpenCode CLI, or set OPENCODE_API_KEY / OPENROUTER_API_KEY, then retry.",
|
|
182
191
|
detail: "Agents can still run without this optional capability.",
|
|
183
192
|
targetView: RECOMMENDATION_TARGETS.DIAGNOSTICS,
|
|
184
193
|
targetAction: null
|
|
185
194
|
};
|
|
186
195
|
}
|
|
187
196
|
|
|
197
|
+
if (
|
|
198
|
+
!intelligence?.localAvailable
|
|
199
|
+
&& intelligence?.cloudConfigured
|
|
200
|
+
&& !intelligence?.cloudAuthenticated
|
|
201
|
+
) {
|
|
202
|
+
return {
|
|
203
|
+
kind: NEXT_STEP_KINDS.REVIEW,
|
|
204
|
+
title: "Inspect intelligence authentication",
|
|
205
|
+
message: `Cloud intelligence is configured but not authenticated. Run ${formatCliCommand("intelligence status")}.`,
|
|
206
|
+
detail: "Confirm API key or OpenCode auth evidence before cloud invoke.",
|
|
207
|
+
targetView: RECOMMENDATION_TARGETS.DIAGNOSTICS,
|
|
208
|
+
targetAction: null
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
188
212
|
return {
|
|
189
213
|
kind: NEXT_STEP_KINDS.REVIEW,
|
|
190
214
|
title: "Review system health",
|
package/src/global/diff.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { updateGlobalHarness } from "./global-installer.js";
|
|
5
|
+
import { needsManagedRepair } from "./governance-repair.js";
|
|
5
6
|
import { harnessHomePaths } from "./paths.js";
|
|
6
7
|
import { readGlobalState } from "./state.js";
|
|
7
8
|
import { buildStatusReport } from "./status.js";
|
|
@@ -31,11 +32,7 @@ export async function buildDiffReport(homeDir, {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const statusReport = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
|
|
34
|
-
|
|
35
|
-
|| statusReport.counts.missing > 0
|
|
36
|
-
|| statusReport.counts.stale > 0;
|
|
37
|
-
|
|
38
|
-
if (!needsRepair) {
|
|
35
|
+
if (!needsManagedRepair(statusReport)) {
|
|
39
36
|
return {
|
|
40
37
|
homeDir,
|
|
41
38
|
installed: true,
|
package/src/global/global-cli.js
CHANGED
|
@@ -404,7 +404,7 @@ export async function runGlobalDoctor(packageRoot, {
|
|
|
404
404
|
return report;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
const { checks, ok, hasDrift } = await runGlobalDoctorChecks(homeDir, { packageRoot, workspaceRoot });
|
|
407
|
+
const { checks, ok, hasDrift, componentHealth } = await runGlobalDoctorChecks(homeDir, { packageRoot, workspaceRoot });
|
|
408
408
|
|
|
409
409
|
console.log(`${BRAND.displayName} doctor (scope: agent-global)`);
|
|
410
410
|
console.log(`Home: ${homeDir}`);
|
|
@@ -416,6 +416,14 @@ export async function runGlobalDoctor(packageRoot, {
|
|
|
416
416
|
console.log(`[${label}] ${check.name}${detail}`);
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
if (componentHealth.length > 0) {
|
|
420
|
+
console.log("");
|
|
421
|
+
console.log("Components:");
|
|
422
|
+
for (const entry of componentHealth) {
|
|
423
|
+
console.log(` ${entry.id.padEnd(16)} ${entry.status}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
419
427
|
console.log("");
|
|
420
428
|
if (ok) {
|
|
421
429
|
console.log("Status: OK");
|
|
@@ -782,7 +790,14 @@ function printComponentEntry(component, { workspace = false } = {}) {
|
|
|
782
790
|
console.log("");
|
|
783
791
|
console.log(`${component.id} (${component.version}) [${defaultLabel}]`);
|
|
784
792
|
console.log(` Label: ${component.label}`);
|
|
793
|
+
console.log(` Kind: ${component.kind ?? "component"}`);
|
|
785
794
|
console.log(` Assets: ${component.assetFiles.join(", ")}`);
|
|
795
|
+
if ((component.dependencies ?? []).length > 0) {
|
|
796
|
+
console.log(` Dependencies: ${component.dependencies.join(", ")}`);
|
|
797
|
+
}
|
|
798
|
+
if ((component.healthChecks ?? []).length > 0) {
|
|
799
|
+
console.log(` Health checks: ${component.healthChecks.map((check) => check.id).join(", ")}`);
|
|
800
|
+
}
|
|
786
801
|
|
|
787
802
|
if (component.instructions) {
|
|
788
803
|
console.log(` Instructions: ${component.instructions}`);
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { listBackupSnapshots } from "./backups.js";
|
|
2
2
|
import { buildAdapterContext } from "./adapter-context.js";
|
|
3
|
+
import { buildComponentHealthEntries } from "./component-health.js";
|
|
3
4
|
import { runComponentEcosystemChecks } from "./component-ecosystem-checks.js";
|
|
4
5
|
import { detectGlobalDrift, hasRepairableDrift } from "./drift.js";
|
|
5
6
|
import { harnessHomePaths } from "./paths.js";
|
|
6
7
|
import { resolveComponent } from "./component-registry.js";
|
|
7
8
|
import { readGlobalState } from "./state.js";
|
|
8
9
|
import { formatCliCommand } from "./brand/cli.js";
|
|
10
|
+
import {
|
|
11
|
+
BACKEND_IDS,
|
|
12
|
+
inspectIntelligenceBackends
|
|
13
|
+
} from "./intelligence/index.js";
|
|
9
14
|
|
|
10
|
-
export async function runGlobalDoctorChecks(homeDir, {
|
|
15
|
+
export async function runGlobalDoctorChecks(homeDir, {
|
|
16
|
+
packageRoot,
|
|
17
|
+
workspaceRoot = null,
|
|
18
|
+
env = process.env,
|
|
19
|
+
fetchImpl = globalThis.fetch,
|
|
20
|
+
inspectBackends = inspectIntelligenceBackends
|
|
21
|
+
} = {}) {
|
|
11
22
|
const paths = harnessHomePaths(homeDir);
|
|
12
23
|
const state = await readGlobalState(paths.statePath);
|
|
13
24
|
const installedComponents = (state?.components ?? []).map((entry) => resolveComponent(entry.id, { workspaceRoot }));
|
|
@@ -26,22 +37,84 @@ export async function runGlobalDoctorChecks(homeDir, { packageRoot, workspaceRoo
|
|
|
26
37
|
checks.push(await backupsCheck(paths));
|
|
27
38
|
checks.push(...await runComponentEcosystemChecks({
|
|
28
39
|
installedComponents,
|
|
29
|
-
workspaceRoot
|
|
40
|
+
workspaceRoot,
|
|
41
|
+
homeDir
|
|
30
42
|
}));
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
checks.push(await intelligenceProvidersCheck({ env, fetchImpl, inspectBackends }));
|
|
46
|
+
|
|
33
47
|
const hasMissing = checks.some((check) => check.status === "missing");
|
|
34
48
|
const hasStale = checks.some((check) => check.status === "stale");
|
|
49
|
+
const componentHealth = buildComponentHealthEntries(state?.components ?? [], checks);
|
|
35
50
|
|
|
36
51
|
return {
|
|
37
52
|
checks,
|
|
38
53
|
ok: !hasMissing && !hasStale,
|
|
39
54
|
hasDrift: hasRepairableDrift(checks),
|
|
55
|
+
componentHealth,
|
|
40
56
|
state,
|
|
41
57
|
paths
|
|
42
58
|
};
|
|
43
59
|
}
|
|
44
60
|
|
|
61
|
+
async function intelligenceProvidersCheck({ env, fetchImpl, inspectBackends }) {
|
|
62
|
+
try {
|
|
63
|
+
const backends = await inspectBackends({ env, fetchImpl });
|
|
64
|
+
const parts = [];
|
|
65
|
+
|
|
66
|
+
if (backends.some((entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available)) {
|
|
67
|
+
parts.push("Ollama available");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const go = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE_GO);
|
|
71
|
+
const zen = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE_ZEN);
|
|
72
|
+
const runtime = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE);
|
|
73
|
+
|
|
74
|
+
if (go?.configured || go?.hasApiKey) {
|
|
75
|
+
parts.push(formatCloudProvider("OpenCode Go", go));
|
|
76
|
+
}
|
|
77
|
+
if (zen?.configured || zen?.hasApiKey) {
|
|
78
|
+
parts.push(formatCloudProvider("OpenCode Zen", zen));
|
|
79
|
+
}
|
|
80
|
+
if (runtime?.available || runtime?.detected) {
|
|
81
|
+
const providers = runtime.evidence?.authProviders?.length
|
|
82
|
+
? runtime.evidence.authProviders.join(", ")
|
|
83
|
+
: "CLI installed";
|
|
84
|
+
parts.push(`OpenCode CLI runtime (${providers}; not authenticated by Kairo)`);
|
|
85
|
+
}
|
|
86
|
+
if (backends.some((entry) => entry.id === BACKEND_IDS.OPENROUTER && entry.hasApiKey)) {
|
|
87
|
+
parts.push("OpenRouter configured (env key; entitlement unverified)");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
name: "intelligence providers",
|
|
92
|
+
status: "ok",
|
|
93
|
+
category: "intelligence",
|
|
94
|
+
detail: parts.length > 0
|
|
95
|
+
? `${parts.join("; ")}. Cloud invoke still needs --cloud-consent --yes. API keys never prove subscription or balance.`
|
|
96
|
+
: `Optional. Start Ollama, install OpenCode CLI, or set OPENCODE_API_KEY / OPENROUTER_API_KEY. See ${formatCliCommand("intelligence status")}.`
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
name: "intelligence providers",
|
|
101
|
+
status: "warning",
|
|
102
|
+
category: "intelligence",
|
|
103
|
+
detail: `Intelligence provider inspection failed (${error?.message ?? error}). Overall doctor health is unchanged.`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function formatCloudProvider(label, entry) {
|
|
109
|
+
const identity = entry.authenticated
|
|
110
|
+
? "authenticated"
|
|
111
|
+
: entry.configured || entry.hasApiKey
|
|
112
|
+
? "configured"
|
|
113
|
+
: "detected";
|
|
114
|
+
const entitlement = entry.entitlement ?? "unknown";
|
|
115
|
+
return `${label} (${identity}; entitlement=${entitlement})`;
|
|
116
|
+
}
|
|
117
|
+
|
|
45
118
|
function stateOnlyCheck(state) {
|
|
46
119
|
if (!state) {
|
|
47
120
|
return {
|