@kal-elsam/kairo-runtime 0.13.1 → 0.15.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/CHANGELOG.md +107 -0
- package/README.md +12 -10
- package/bin/kairo-runtime.js +0 -0
- package/bin/kairo.js +0 -0
- package/package.json +5 -1
- package/scripts/cockpit-smoke.mjs +14 -10
- package/src/cli.js +182 -11
- package/src/global/check-resolutions.js +31 -0
- package/src/global/cli-help.js +23 -4
- package/src/global/component-ecosystem-checks.js +2 -0
- package/src/global/component-integration-cli.js +29 -10
- package/src/global/components-resolve-cli.js +246 -0
- package/src/global/connection-actions.js +147 -0
- package/src/global/connections.js +269 -0
- package/src/global/fleet-configure-plan.js +123 -0
- package/src/global/fleet-configure.js +303 -0
- package/src/global/fleet-models.js +188 -0
- package/src/global/fleet-set.js +219 -0
- package/src/global/fleet-shared.js +38 -0
- package/src/global/ink/cockpit-controller.js +2 -4
- package/src/global/ink/cockpit-enter.js +3 -1
- package/src/global/ink/cockpit-focus.js +7 -1
- package/src/global/ink/cockpit-models.js +33 -21
- package/src/global/ink/cockpit-palette.js +8 -3
- package/src/global/ink/cockpit-views.js +9 -2
- package/src/global/ink/orchestrator-app.js +42 -4
- package/src/global/ink/ux/live-overview.js +53 -26
- package/src/global/ink/ux/overview-actions.js +80 -0
- package/src/global/ink/ux/overview-needs.js +1 -1
- package/src/global/integrations/engram-evidence.js +7 -2
- package/src/global/integrations/sdd-apply.js +17 -7
- package/src/global/integrations/sdd-evidence.js +22 -3
- package/src/global/integrations/sdd-plan.js +21 -3
- package/src/global/integrations/sdd-resolutions.js +73 -0
- package/src/global/integrations/sdd-state.js +69 -0
- package/src/global/integrations/sdd-verify.js +9 -4
- package/src/global/mcp/kairo-mcp.js +56 -5
- package/src/global/mcp/resolve-mcp-workspace.js +51 -0
- package/src/global/mcp/work-snapshot-rule.js +89 -0
- package/src/global/mcp/work-snapshot-tool.js +49 -0
- package/src/global/mcp-install.js +239 -0
- package/src/global/next/next-cli.js +35 -0
- package/src/global/next/next-report.js +145 -0
- package/src/global/next/project-key.js +36 -0
- package/src/global/next/publish-work-snapshot.js +116 -0
- package/src/global/next/work-enroll.js +91 -0
- package/src/global/next/work-snapshot.js +216 -0
- package/src/global/observability/fleet-activity.js +197 -0
- package/src/global/observability/fleet-models-catalog.js +137 -0
- package/src/global/observability/fleet-platforms.js +166 -0
- package/src/global/observability/fleet-probe.js +229 -0
- package/src/global/paths.js +2 -1
- package/src/global/self-update.js +216 -0
|
@@ -9,11 +9,13 @@ import { formatCliCommand } from "./brand/cli.js";
|
|
|
9
9
|
import { requireIntegrationProvider } from "./integrations/provider-registry.js";
|
|
10
10
|
import { ensureIntegrationProvidersRegistered } from "./integrations/index.js";
|
|
11
11
|
import { ENGRAM_INTEGRATION_STATUS } from "./integrations/engram-evidence.js";
|
|
12
|
-
import { SDD_HEALTH } from "./integrations/sdd-evidence.js";
|
|
12
|
+
import { SDD_HEALTH, SDD_FILE_OUTCOMES } from "./integrations/sdd-evidence.js";
|
|
13
13
|
import {
|
|
14
|
-
hasSuccessfulSddRollbackMutations, recordSddMaterialization, reconcileSddStateAfterRollback
|
|
14
|
+
hasSuccessfulSddRollbackMutations, recordSddMaterialization, reconcileSddStateAfterRollback,
|
|
15
|
+
adoptedHashesFromState, clearSddAdoptionsForPaths
|
|
15
16
|
} from "./integrations/sdd-state.js";
|
|
16
17
|
import { loadSddReceipt } from "./integrations/sdd-receipts.js";
|
|
18
|
+
import { buildSddSkillResolutions } from "./integrations/sdd-resolutions.js";
|
|
17
19
|
|
|
18
20
|
const DEFAULT_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
19
21
|
|
|
@@ -169,14 +171,24 @@ async function buildProviderContext(options, { homeDir, componentId }) {
|
|
|
169
171
|
packageRoot: options.packageRoot ?? DEFAULT_PACKAGE_ROOT,
|
|
170
172
|
persona: options.persona ?? state?.sdd?.persona ?? "off",
|
|
171
173
|
personaAgentIds: state?.sdd?.personaAgentIds ?? [],
|
|
172
|
-
trackedFiles
|
|
174
|
+
trackedFiles,
|
|
175
|
+
adoptedFiles: adoptedHashesFromState(state?.sdd),
|
|
176
|
+
overwriteConflicts: Boolean(options.overwriteConflicts)
|
|
173
177
|
};
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
async function persistSddReceiptState(homeDir, receipt) {
|
|
177
181
|
const paths = harnessHomePaths(homeDir);
|
|
178
182
|
const state = (await readGlobalState(paths.statePath)) ?? {};
|
|
179
|
-
|
|
183
|
+
let next = recordSddMaterialization(state, { receipt });
|
|
184
|
+
const appliedPaths = (receipt.files ?? [])
|
|
185
|
+
.filter((file) => file.outcome === SDD_FILE_OUTCOMES.APPLIED)
|
|
186
|
+
.map((file) => file.destinationPath)
|
|
187
|
+
.filter(Boolean);
|
|
188
|
+
if (appliedPaths.length) {
|
|
189
|
+
next = clearSddAdoptionsForPaths(next, appliedPaths);
|
|
190
|
+
}
|
|
191
|
+
await writeGlobalState(paths.statePath, next);
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
async function reconcileSddRollbackState(homeDir, result) {
|
|
@@ -230,13 +242,19 @@ export function buildEngramIntegrationChecks(inspection) {
|
|
|
230
242
|
export function buildSddIntegrationChecks(verification) {
|
|
231
243
|
const summary = verification.summary ?? {};
|
|
232
244
|
const persona = verification.persona ?? {};
|
|
245
|
+
const skillsHealthy = verification.status === SDD_HEALTH.CONFIGURED
|
|
246
|
+
|| verification.status === SDD_HEALTH.ADOPTED;
|
|
247
|
+
const skillsCheck = {
|
|
248
|
+
name: "sdd-core:skills",
|
|
249
|
+
status: skillsHealthy ? "ok" : "warning",
|
|
250
|
+
category: "integration",
|
|
251
|
+
componentId: "sdd-core",
|
|
252
|
+
detail: `SDD skills ${verification.status}: configured=${summary.configured ?? 0}, adopted=${summary.adopted ?? 0}, missing=${summary.missing ?? 0}, drifted=${summary.drifted ?? 0}, conflict=${summary.conflict ?? 0} (disk presence ≠ runtime active).`
|
|
253
|
+
};
|
|
254
|
+
const resolutions = buildSddSkillResolutions(verification);
|
|
255
|
+
if (resolutions.length) skillsCheck.resolutions = resolutions;
|
|
233
256
|
return [
|
|
234
|
-
|
|
235
|
-
name: "sdd-core:skills",
|
|
236
|
-
status: verification.status === SDD_HEALTH.CONFIGURED ? "ok" : "warning",
|
|
237
|
-
category: "integration", componentId: "sdd-core",
|
|
238
|
-
detail: `SDD skills ${verification.status}: configured=${summary.configured ?? 0}, missing=${summary.missing ?? 0}, drifted=${summary.drifted ?? 0}, conflict=${summary.conflict ?? 0} (disk presence ≠ runtime active).`
|
|
239
|
-
},
|
|
257
|
+
skillsCheck,
|
|
240
258
|
{
|
|
241
259
|
name: "sdd-core:persona",
|
|
242
260
|
status: persona.status === "configured" || persona.status === "off" ? "ok" : "warning",
|
|
@@ -283,6 +301,7 @@ function printSddConfigureHuman(result) {
|
|
|
283
301
|
if (result.dryRun) return void console.log("Dry-run only — no skills materialized.");
|
|
284
302
|
if (result.cancelled) return void console.log("Cancelled.");
|
|
285
303
|
if (result.blocked) return void console.log(`Blocked: ${result.reason ?? "conflicts present"}`);
|
|
304
|
+
if (result.overwriteConflicts) console.log("Mode: overwrite-conflicts (backups before replace).");
|
|
286
305
|
if (result.receipt) {
|
|
287
306
|
console.log(`Receipt: ${result.receipt.id}${result.receipt.partial ? " (partial)" : ""}`);
|
|
288
307
|
if (result.sessionRefreshRequired) {
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolveHomeDir, harnessHomePaths } from "./paths.js";
|
|
5
|
+
import { readGlobalState, writeGlobalState } from "./state.js";
|
|
6
|
+
import { printJson } from "./json-output.js";
|
|
7
|
+
import { formatCliCommand } from "./brand/cli.js";
|
|
8
|
+
import {
|
|
9
|
+
assertExplicitApplyConsent,
|
|
10
|
+
promptApplyConfirmation,
|
|
11
|
+
shouldPromptApplyConfirmation
|
|
12
|
+
} from "./apply-confirmation.js";
|
|
13
|
+
import { ensureIntegrationProvidersRegistered } from "./integrations/index.js";
|
|
14
|
+
import { requireIntegrationProvider } from "./integrations/provider-registry.js";
|
|
15
|
+
import { SDD_HEALTH } from "./integrations/sdd-evidence.js";
|
|
16
|
+
import {
|
|
17
|
+
adoptedHashesFromState,
|
|
18
|
+
recordSddAdoptions
|
|
19
|
+
} from "./integrations/sdd-state.js";
|
|
20
|
+
import { resolveCanonicalSddSkillFile } from "./integrations/sdd-destinations.js";
|
|
21
|
+
import { assertComponentInstalled } from "./component-integration-cli.js";
|
|
22
|
+
|
|
23
|
+
const DEFAULT_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
24
|
+
|
|
25
|
+
/** Minimal unified-style diff without external deps. */
|
|
26
|
+
export function createUnifiedDiff(fromLabel, toLabel, fromText, toText) {
|
|
27
|
+
const fromLines = String(fromText ?? "").split("\n");
|
|
28
|
+
const toLines = String(toText ?? "").split("\n");
|
|
29
|
+
const lines = [`--- ${fromLabel}`, `+++ ${toLabel}`];
|
|
30
|
+
const max = Math.max(fromLines.length, toLines.length);
|
|
31
|
+
let identical = true;
|
|
32
|
+
for (let i = 0; i < max; i += 1) {
|
|
33
|
+
const a = fromLines[i];
|
|
34
|
+
const b = toLines[i];
|
|
35
|
+
if (a === b) {
|
|
36
|
+
if (a !== undefined) lines.push(` ${a}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
identical = false;
|
|
40
|
+
if (a !== undefined) lines.push(`-${a}`);
|
|
41
|
+
if (b !== undefined) lines.push(`+${b}`);
|
|
42
|
+
}
|
|
43
|
+
if (identical) lines.push(" (no textual differences)");
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function buildSddContext(options) {
|
|
48
|
+
const homeDir = resolveHomeDir();
|
|
49
|
+
await assertComponentInstalled("sdd-core", { homeDir });
|
|
50
|
+
ensureIntegrationProvidersRegistered();
|
|
51
|
+
const provider = requireIntegrationProvider("sdd-core");
|
|
52
|
+
const state = await readGlobalState(harnessHomePaths(homeDir).statePath);
|
|
53
|
+
const trackedFiles = Object.fromEntries(
|
|
54
|
+
(state?.sdd?.files ?? []).map((file) => [file.destinationPath, file.hash])
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
homeDir,
|
|
58
|
+
provider,
|
|
59
|
+
state,
|
|
60
|
+
packageRoot: options.packageRoot ?? DEFAULT_PACKAGE_ROOT,
|
|
61
|
+
requestedAgentIds: options.adapters ?? null,
|
|
62
|
+
detectedAgentIds: (state?.adapters ?? []).map((entry) => entry.id),
|
|
63
|
+
trackedFiles,
|
|
64
|
+
adoptedFiles: adoptedHashesFromState(state?.sdd),
|
|
65
|
+
personaAgentIds: state?.sdd?.personaAgentIds ?? []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Adopt conflict disk hashes into state — no file writes. */
|
|
70
|
+
export async function runComponentsAdopt(options = {}) {
|
|
71
|
+
if (options.componentId !== "sdd-core") {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`components adopt supports sdd-core only (got "${options.componentId}").`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const dryRun = Boolean(options.dryRun);
|
|
78
|
+
const yes = Boolean(options.yes);
|
|
79
|
+
const json = Boolean(options.json);
|
|
80
|
+
assertExplicitApplyConsent({
|
|
81
|
+
applying: !dryRun,
|
|
82
|
+
dryRun,
|
|
83
|
+
json,
|
|
84
|
+
yes,
|
|
85
|
+
interactive: null,
|
|
86
|
+
command: "components adopt sdd-core"
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const ctx = await buildSddContext(options);
|
|
90
|
+
const verification = await ctx.provider.verify({
|
|
91
|
+
homeDir: ctx.homeDir,
|
|
92
|
+
packageRoot: ctx.packageRoot,
|
|
93
|
+
requestedAgentIds: ctx.requestedAgentIds,
|
|
94
|
+
detectedAgentIds: ctx.detectedAgentIds,
|
|
95
|
+
trackedFiles: ctx.trackedFiles,
|
|
96
|
+
adoptedFiles: ctx.adoptedFiles,
|
|
97
|
+
personaAgentIds: ctx.personaAgentIds
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const conflicts = (verification.findings ?? []).filter(
|
|
101
|
+
(finding) => finding.status === SDD_HEALTH.CONFLICT && finding.diskHash
|
|
102
|
+
);
|
|
103
|
+
const adoptions = conflicts.map((finding) => ({
|
|
104
|
+
destinationPath: finding.destinationPath,
|
|
105
|
+
hash: finding.diskHash,
|
|
106
|
+
skillId: finding.skillId,
|
|
107
|
+
agentIds: finding.agentIds ?? [],
|
|
108
|
+
relativePath: finding.relativePath ?? "SKILL.md",
|
|
109
|
+
reason: "Adopted pre-existing disk bytes via components adopt."
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
const result = {
|
|
113
|
+
provider: "sdd-core",
|
|
114
|
+
componentId: "sdd-core",
|
|
115
|
+
dryRun,
|
|
116
|
+
ok: true,
|
|
117
|
+
adopted: adoptions.length,
|
|
118
|
+
adoptions,
|
|
119
|
+
summary: verification.summary
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (dryRun) {
|
|
123
|
+
if (json) { printJson(result); return result; }
|
|
124
|
+
console.log(formatCliCommand("components adopt sdd-core"));
|
|
125
|
+
console.log(`Plan: adopt=${adoptions.length} conflict files (no writes).`);
|
|
126
|
+
for (const entry of adoptions) {
|
|
127
|
+
console.log(` adopt ${entry.skillId} → ${entry.destinationPath}`);
|
|
128
|
+
}
|
|
129
|
+
console.log("Dry-run only — no state updated.");
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (shouldPromptApplyConfirmation({ applying: true, dryRun, json, confirm: yes, interactive: null })) {
|
|
134
|
+
const accepted = await promptApplyConfirmation({
|
|
135
|
+
command: "components adopt sdd-core",
|
|
136
|
+
question: `Adopt ${adoptions.length} conflicting SDD skill file(s) as-is into Kairo state? [Y/n]: `
|
|
137
|
+
});
|
|
138
|
+
if (!accepted) {
|
|
139
|
+
result.cancelled = true;
|
|
140
|
+
result.ok = false;
|
|
141
|
+
if (json) printJson(result);
|
|
142
|
+
else console.log("Cancelled.");
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (adoptions.length) {
|
|
148
|
+
await writeGlobalState(
|
|
149
|
+
harnessHomePaths(ctx.homeDir).statePath,
|
|
150
|
+
recordSddAdoptions(ctx.state ?? {}, { adoptions })
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
result.applied = true;
|
|
155
|
+
if (json) { printJson(result); return result; }
|
|
156
|
+
console.log(formatCliCommand("components adopt sdd-core"));
|
|
157
|
+
console.log(`Adopted ${adoptions.length} file(s) into Kairo state (disk unchanged).`);
|
|
158
|
+
for (const entry of adoptions) {
|
|
159
|
+
console.log(` adopted ${entry.skillId} → ${entry.destinationPath}`);
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Read-only unified diff of conflict/drifted findings vs canonical. */
|
|
165
|
+
export async function runComponentsDiff(options = {}) {
|
|
166
|
+
if (options.componentId !== "sdd-core") {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`components diff supports sdd-core only (got "${options.componentId}").`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const ctx = await buildSddContext(options);
|
|
173
|
+
const verification = await ctx.provider.verify({
|
|
174
|
+
homeDir: ctx.homeDir,
|
|
175
|
+
packageRoot: ctx.packageRoot,
|
|
176
|
+
requestedAgentIds: ctx.requestedAgentIds,
|
|
177
|
+
detectedAgentIds: ctx.detectedAgentIds,
|
|
178
|
+
trackedFiles: ctx.trackedFiles,
|
|
179
|
+
adoptedFiles: ctx.adoptedFiles,
|
|
180
|
+
personaAgentIds: ctx.personaAgentIds
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const interesting = (verification.findings ?? []).filter(
|
|
184
|
+
(finding) => finding.status === SDD_HEALTH.CONFLICT || finding.status === SDD_HEALTH.DRIFTED
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const diffs = [];
|
|
188
|
+
for (const finding of interesting) {
|
|
189
|
+
let canonicalText = "";
|
|
190
|
+
let diskText = "";
|
|
191
|
+
try {
|
|
192
|
+
const canonicalPath = resolveCanonicalSddSkillFile(
|
|
193
|
+
finding.skillId,
|
|
194
|
+
finding.relativePath ?? "SKILL.md",
|
|
195
|
+
ctx.packageRoot
|
|
196
|
+
);
|
|
197
|
+
canonicalText = await readFile(canonicalPath, "utf8");
|
|
198
|
+
} catch (error) {
|
|
199
|
+
canonicalText = `/* failed to read canonical: ${error.message} */\n`;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
diskText = await readFile(finding.destinationPath, "utf8");
|
|
203
|
+
} catch (error) {
|
|
204
|
+
diskText = `/* failed to read disk: ${error.message} */\n`;
|
|
205
|
+
}
|
|
206
|
+
diffs.push({
|
|
207
|
+
skillId: finding.skillId,
|
|
208
|
+
status: finding.status,
|
|
209
|
+
destinationPath: finding.destinationPath,
|
|
210
|
+
agentIds: finding.agentIds ?? [],
|
|
211
|
+
unified: createUnifiedDiff(
|
|
212
|
+
`canonical/${finding.skillId}/${finding.relativePath ?? "SKILL.md"}`,
|
|
213
|
+
finding.destinationPath,
|
|
214
|
+
canonicalText,
|
|
215
|
+
diskText
|
|
216
|
+
)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = {
|
|
221
|
+
provider: "sdd-core",
|
|
222
|
+
componentId: "sdd-core",
|
|
223
|
+
ok: true,
|
|
224
|
+
count: diffs.length,
|
|
225
|
+
diffs,
|
|
226
|
+
summary: verification.summary
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (options.json) {
|
|
230
|
+
printJson(result);
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log(formatCliCommand("components diff sdd-core"));
|
|
235
|
+
console.log(`Findings: ${diffs.length} conflict/drifted file(s).`);
|
|
236
|
+
if (diffs.length === 0) {
|
|
237
|
+
console.log("No conflicting or drifted skill files.");
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
for (const entry of diffs) {
|
|
241
|
+
console.log("");
|
|
242
|
+
console.log(`## ${entry.status} ${entry.skillId} (${(entry.agentIds ?? []).join(",")})`);
|
|
243
|
+
console.log(entry.unified);
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommended button actions per connection chip.
|
|
3
|
+
* Optional tools never block governance; Kairo does not brew-install externals.
|
|
4
|
+
*/
|
|
5
|
+
import { formatCliCommand } from "./brand/cli.js";
|
|
6
|
+
|
|
7
|
+
function action(id, label, command, kind = "run") {
|
|
8
|
+
return { id, label, command, kind };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @returns {{ optional: boolean, actions: Array<{id,label,command,kind}> }} */
|
|
12
|
+
export function actionsForConnection(connection) {
|
|
13
|
+
if (!connection || typeof connection !== "object") {
|
|
14
|
+
return { optional: true, actions: [] };
|
|
15
|
+
}
|
|
16
|
+
const id = connection.id;
|
|
17
|
+
const state = connection.state ?? "unknown";
|
|
18
|
+
|
|
19
|
+
switch (id) {
|
|
20
|
+
case "gentle":
|
|
21
|
+
return {
|
|
22
|
+
optional: true,
|
|
23
|
+
actions: state === "available"
|
|
24
|
+
? [action("refresh", "Refresh", null, "refresh")]
|
|
25
|
+
: [
|
|
26
|
+
action("guide-gentle", "How to install", null, "guide"),
|
|
27
|
+
action("refresh", "Refresh after install", null, "refresh")
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
case "hermes":
|
|
31
|
+
return {
|
|
32
|
+
optional: true,
|
|
33
|
+
actions: state === "available"
|
|
34
|
+
? [action("refresh", "Refresh", null, "refresh")]
|
|
35
|
+
: state === "unavailable" || state === "error"
|
|
36
|
+
? [
|
|
37
|
+
action("guide-hermes-api", "Enable API tip", null, "guide"),
|
|
38
|
+
action(
|
|
39
|
+
"start-hermes",
|
|
40
|
+
"Start Hermes gateway",
|
|
41
|
+
"hermes gateway run",
|
|
42
|
+
"configure"
|
|
43
|
+
),
|
|
44
|
+
action("refresh", "Refresh", null, "refresh")
|
|
45
|
+
]
|
|
46
|
+
: [
|
|
47
|
+
action("guide-hermes", "How to install", null, "guide"),
|
|
48
|
+
action("refresh", "Refresh after install", null, "refresh")
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
case "engram":
|
|
52
|
+
return {
|
|
53
|
+
optional: true,
|
|
54
|
+
actions: state === "configured"
|
|
55
|
+
? [action("refresh", "Refresh", null, "refresh")]
|
|
56
|
+
: state === "missing"
|
|
57
|
+
? [
|
|
58
|
+
action("guide-engram", "How to install", null, "guide"),
|
|
59
|
+
action("refresh", "Refresh after install", null, "refresh")
|
|
60
|
+
]
|
|
61
|
+
: [
|
|
62
|
+
action(
|
|
63
|
+
"configure-engram",
|
|
64
|
+
"Configure Engram",
|
|
65
|
+
formatCliCommand("components configure engram-memory --dry-run"),
|
|
66
|
+
"configure"
|
|
67
|
+
),
|
|
68
|
+
action("refresh", "Refresh", null, "refresh")
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
case "graphify": {
|
|
72
|
+
const wantsUpdate = state === "stale"
|
|
73
|
+
|| /graphify update/i.test(connection.detail ?? "")
|
|
74
|
+
|| /No graphify-out/i.test(connection.detail ?? "");
|
|
75
|
+
const wantsInstall = state === "missing"
|
|
76
|
+
|| /Install graphify/i.test(connection.detail ?? "");
|
|
77
|
+
return {
|
|
78
|
+
optional: true,
|
|
79
|
+
actions: wantsInstall
|
|
80
|
+
? [
|
|
81
|
+
action("guide-graphify", "How to install", null, "guide"),
|
|
82
|
+
action("refresh", "Refresh after install", null, "refresh")
|
|
83
|
+
]
|
|
84
|
+
: wantsUpdate
|
|
85
|
+
? [
|
|
86
|
+
action("update-graph", "Update graph", "graphify update .", "configure"),
|
|
87
|
+
action("refresh", "Refresh", null, "refresh")
|
|
88
|
+
]
|
|
89
|
+
: [action("refresh", "Refresh", null, "refresh")]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
case "agent":
|
|
93
|
+
return {
|
|
94
|
+
optional: false,
|
|
95
|
+
actions: state === "connected"
|
|
96
|
+
? [action("refresh", "Refresh", null, "refresh")]
|
|
97
|
+
: [
|
|
98
|
+
action("connect-agent", "Connect Agent", formatCliCommand("mcp install"), "configure"),
|
|
99
|
+
action("refresh", "Refresh", null, "refresh")
|
|
100
|
+
]
|
|
101
|
+
};
|
|
102
|
+
default:
|
|
103
|
+
return { optional: true, actions: [] };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function enrichConnection(connection) {
|
|
108
|
+
const { optional, actions } = actionsForConnection(connection);
|
|
109
|
+
return {
|
|
110
|
+
...connection,
|
|
111
|
+
optional,
|
|
112
|
+
actions
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Top-level panel buttons that are not chip-specific. */
|
|
117
|
+
export function buildSetupActions({ needsAttention = false, agentConnected = false } = {}) {
|
|
118
|
+
const actions = [];
|
|
119
|
+
actions.push({
|
|
120
|
+
id: "setup",
|
|
121
|
+
label: "Setup",
|
|
122
|
+
command: formatCliCommand("setup"),
|
|
123
|
+
primary: !needsAttention && !agentConnected,
|
|
124
|
+
detail: "Detect agents you use (Cursor, Codex, Claude, …) and prepare only those."
|
|
125
|
+
});
|
|
126
|
+
if (needsAttention) {
|
|
127
|
+
actions.push({
|
|
128
|
+
id: "repair",
|
|
129
|
+
label: "Repair",
|
|
130
|
+
command: formatCliCommand("sync"),
|
|
131
|
+
primary: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!agentConnected) {
|
|
135
|
+
actions.push({
|
|
136
|
+
id: "connect-agent",
|
|
137
|
+
label: "Connect Agent",
|
|
138
|
+
command: formatCliCommand("mcp install"),
|
|
139
|
+
primary: !needsAttention
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
actions.push(
|
|
143
|
+
{ id: "doctor", label: "Doctor", command: formatCliCommand("doctor"), primary: false },
|
|
144
|
+
{ id: "refresh", label: "Refresh", command: null, primary: false }
|
|
145
|
+
);
|
|
146
|
+
return actions;
|
|
147
|
+
}
|