@kal-elsam/kairo-runtime 0.14.0 → 0.16.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 +110 -0
- package/bin/kairo-runtime.js +0 -0
- package/bin/kairo.js +0 -0
- package/package.json +1 -1
- package/src/cli.js +182 -8
- package/src/global/check-resolutions.js +31 -0
- package/src/global/cli-help.js +21 -2
- 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/control-plane/attention.js +141 -0
- package/src/global/control-plane/build-report.js +146 -0
- package/src/global/control-plane/cli.js +36 -0
- package/src/global/control-plane/constants.js +38 -0
- package/src/global/control-plane/gentle-adapters.js +183 -0
- package/src/global/control-plane/provider.js +69 -0
- package/src/global/control-plane/review-status.js +115 -0
- package/src/global/control-plane/sdd-status.js +49 -0
- package/src/global/control-plane/team.js +63 -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 +1 -1
- package/src/global/ink/cockpit-models.js +4 -1
- package/src/global/ink/orchestrator-app.js +21 -2
- package/src/global/ink/ux/live-overview.js +5 -11
- 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/observability/gentle-probe.js +30 -2
- package/src/global/observability/index.js +2 -1
- package/src/global/paths.js +2 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared fleets for Claude / Codex / Cursor agents (public configs only).
|
|
3
|
+
*/
|
|
4
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
8
|
+
|
|
9
|
+
export function parseFrontmatterModel(raw) {
|
|
10
|
+
const text = String(raw ?? "");
|
|
11
|
+
const match = text.match(FRONTMATTER_RE);
|
|
12
|
+
if (!match) return { model: null, name: null };
|
|
13
|
+
const block = match[1];
|
|
14
|
+
const modelLine = block.match(/^model:\s*(.+)$/m);
|
|
15
|
+
const nameLine = block.match(/^name:\s*(.+)$/m);
|
|
16
|
+
const model = modelLine ? modelLine[1].trim().replace(/^["']|["']$/g, "") : null;
|
|
17
|
+
const name = nameLine ? nameLine[1].trim().replace(/^["']|["']$/g, "") : null;
|
|
18
|
+
return { model, name };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function replaceFrontmatterModel(raw, nextModel) {
|
|
22
|
+
const text = String(raw ?? "");
|
|
23
|
+
const match = text.match(FRONTMATTER_RE);
|
|
24
|
+
if (!match) {
|
|
25
|
+
throw new Error("Agent file has no YAML frontmatter to update.");
|
|
26
|
+
}
|
|
27
|
+
const block = match[1];
|
|
28
|
+
let nextBlock;
|
|
29
|
+
if (/^model:\s*.+$/m.test(block)) {
|
|
30
|
+
nextBlock = block.replace(/^model:\s*.+$/m, `model: ${nextModel}`);
|
|
31
|
+
} else {
|
|
32
|
+
nextBlock = `${block.trimEnd()}\nmodel: ${nextModel}`;
|
|
33
|
+
}
|
|
34
|
+
return text.replace(FRONTMATTER_RE, `---\n${nextBlock}\n---`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseCodexDefaultModel(tomlText) {
|
|
38
|
+
const match = String(tomlText ?? "").match(/^\s*model\s*=\s*"([^"]+)"/m);
|
|
39
|
+
return match ? match[1] : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function replaceCodexDefaultModel(tomlText, nextModel) {
|
|
43
|
+
const text = String(tomlText ?? "");
|
|
44
|
+
if (!/^\s*model\s*=\s*"[^"]*"/m.test(text)) {
|
|
45
|
+
throw new Error("Codex config.toml has no top-level model = \"...\" line.");
|
|
46
|
+
}
|
|
47
|
+
return text.replace(/^\s*model\s*=\s*"[^"]*"/m, `model = "${nextModel}"`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function minionRole(id) {
|
|
51
|
+
if (id === "sdd-apply") return "executor";
|
|
52
|
+
if (id === "sdd-explore") return "explorer";
|
|
53
|
+
if (id === "sdd-verify") return "verifier";
|
|
54
|
+
return id.startsWith("sdd-") ? "specialist" : "minion";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function listAgentFiles(dir, read = readFile, list = readdir) {
|
|
58
|
+
try {
|
|
59
|
+
const names = await list(dir);
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
if (!name.endsWith(".md")) continue;
|
|
63
|
+
const path = join(dir, name);
|
|
64
|
+
const raw = await read(path, "utf8");
|
|
65
|
+
const meta = parseFrontmatterModel(raw);
|
|
66
|
+
const id = meta.name || name.replace(/\.md$/, "");
|
|
67
|
+
if (!id.startsWith("sdd-")) continue;
|
|
68
|
+
out.push({
|
|
69
|
+
id,
|
|
70
|
+
model: meta.model,
|
|
71
|
+
modelShort: meta.model,
|
|
72
|
+
role: minionRole(id),
|
|
73
|
+
mode: "subagent",
|
|
74
|
+
path,
|
|
75
|
+
opaque: meta.model === "inherit" || meta.model == null
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function buildClaudeFleet({ homeDir, read = readFile, list = readdir } = {}) {
|
|
85
|
+
const settingsPath = join(homeDir, ".claude", "settings.json");
|
|
86
|
+
let defaultModel = null;
|
|
87
|
+
try {
|
|
88
|
+
const settings = JSON.parse(await read(settingsPath, "utf8"));
|
|
89
|
+
defaultModel = typeof settings?.model === "string" ? settings.model : null;
|
|
90
|
+
} catch {
|
|
91
|
+
defaultModel = null;
|
|
92
|
+
}
|
|
93
|
+
const minions = await listAgentFiles(join(homeDir, ".claude", "agents"), read, list);
|
|
94
|
+
return {
|
|
95
|
+
platform: "claude",
|
|
96
|
+
orchestrator: {
|
|
97
|
+
id: "default",
|
|
98
|
+
model: defaultModel,
|
|
99
|
+
modelShort: defaultModel,
|
|
100
|
+
mode: "primary",
|
|
101
|
+
opaque: false
|
|
102
|
+
},
|
|
103
|
+
minions: minions.map((m) => ({
|
|
104
|
+
...m,
|
|
105
|
+
model: m.model ?? defaultModel,
|
|
106
|
+
modelShort: m.modelShort ?? defaultModel,
|
|
107
|
+
opaque: false
|
|
108
|
+
})),
|
|
109
|
+
opaque: false,
|
|
110
|
+
writable: true,
|
|
111
|
+
note: "Declared Claude settings + agent frontmatter models.",
|
|
112
|
+
source: "claude"
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function buildCodexFleet({ homeDir, read = readFile } = {}) {
|
|
117
|
+
const configPath = join(homeDir, ".codex", "config.toml");
|
|
118
|
+
let model = null;
|
|
119
|
+
try {
|
|
120
|
+
model = parseCodexDefaultModel(await read(configPath, "utf8"));
|
|
121
|
+
} catch {
|
|
122
|
+
model = null;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
platform: "codex",
|
|
126
|
+
orchestrator: {
|
|
127
|
+
id: "default",
|
|
128
|
+
model,
|
|
129
|
+
modelShort: model,
|
|
130
|
+
mode: "primary",
|
|
131
|
+
opaque: model == null
|
|
132
|
+
},
|
|
133
|
+
minions: [],
|
|
134
|
+
opaque: model == null,
|
|
135
|
+
writable: true,
|
|
136
|
+
configPath,
|
|
137
|
+
note: "Codex default model from ~/.codex/config.toml (no parent→child live topology).",
|
|
138
|
+
source: "codex"
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function buildCursorAgentsFleet({ homeDir, read = readFile, list = readdir } = {}) {
|
|
143
|
+
const minions = await listAgentFiles(join(homeDir, ".cursor", "agents"), read, list);
|
|
144
|
+
return {
|
|
145
|
+
platform: "cursor",
|
|
146
|
+
orchestrator: {
|
|
147
|
+
id: "auto",
|
|
148
|
+
model: null,
|
|
149
|
+
modelShort: null,
|
|
150
|
+
mode: "primary",
|
|
151
|
+
opaque: true
|
|
152
|
+
},
|
|
153
|
+
minions: minions.map((m) => ({
|
|
154
|
+
id: m.id,
|
|
155
|
+
model: m.model,
|
|
156
|
+
modelShort: m.modelShort,
|
|
157
|
+
role: m.role,
|
|
158
|
+
mode: "subagent",
|
|
159
|
+
opaque: true
|
|
160
|
+
})),
|
|
161
|
+
opaque: true,
|
|
162
|
+
writable: false,
|
|
163
|
+
note: "Cursor Auto is IDE-managed. Subagents typically use model: inherit — change models in Cursor UI.",
|
|
164
|
+
source: "cursor-agents"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared fleet topology (orchestrator → minions + models) + optional live activity.
|
|
3
|
+
* Read-only probes — writes go through fleet-set.js with consent.
|
|
4
|
+
*/
|
|
5
|
+
import { access, readFile } from "node:fs/promises";
|
|
6
|
+
import { constants as fsConstants } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { resolveHomeDir } from "../paths.js";
|
|
9
|
+
import { isExecutableAvailable } from "../cli-probe.js";
|
|
10
|
+
import { buildOpenCodeActivity } from "./fleet-activity.js";
|
|
11
|
+
import {
|
|
12
|
+
buildClaudeFleet,
|
|
13
|
+
buildCodexFleet,
|
|
14
|
+
buildCursorAgentsFleet
|
|
15
|
+
} from "./fleet-platforms.js";
|
|
16
|
+
|
|
17
|
+
const VARIANT_SUFFIX_RE = /-(?:cheap|zen)$/i;
|
|
18
|
+
|
|
19
|
+
const MINION_ROLES = Object.freeze({
|
|
20
|
+
"sdd-apply": "executor",
|
|
21
|
+
"sdd-explore": "explorer",
|
|
22
|
+
"sdd-verify": "verifier",
|
|
23
|
+
"sdd-design": "designer",
|
|
24
|
+
"sdd-propose": "proposer",
|
|
25
|
+
"sdd-spec": "specifier",
|
|
26
|
+
"sdd-tasks": "planner",
|
|
27
|
+
"sdd-archive": "archiver",
|
|
28
|
+
"sdd-onboard": "onboarder",
|
|
29
|
+
"sdd-init": "initializer"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
async function pathExists(path) {
|
|
33
|
+
try {
|
|
34
|
+
await access(path, fsConstants.F_OK);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function shortModel(model) {
|
|
42
|
+
if (typeof model !== "string" || !model) return null;
|
|
43
|
+
const slash = model.lastIndexOf("/");
|
|
44
|
+
return slash >= 0 ? model.slice(slash + 1) : model;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function agentEntries(agents) {
|
|
48
|
+
if (!agents || typeof agents !== "object" || Array.isArray(agents)) return [];
|
|
49
|
+
return Object.entries(agents).map(([id, raw]) => ({
|
|
50
|
+
id: String(id),
|
|
51
|
+
mode: typeof raw?.mode === "string" ? raw.mode : null,
|
|
52
|
+
model: typeof raw?.model === "string" ? raw.model : null
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function pickOrchestrator(entries) {
|
|
57
|
+
const gentle = entries.find((e) => e.id === "gentle-orchestrator" && e.mode === "primary");
|
|
58
|
+
if (gentle) return gentle;
|
|
59
|
+
return entries.find((e) => e.mode === "primary") ?? null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isSddMinion(entry, { includeVariants = false } = {}) {
|
|
63
|
+
if (!entry?.id?.startsWith("sdd-")) return false;
|
|
64
|
+
if (entry.mode !== "subagent") return false;
|
|
65
|
+
if (!includeVariants && VARIANT_SUFFIX_RE.test(entry.id)) return false;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function minionRole(id) {
|
|
70
|
+
return MINION_ROLES[id] ?? (id.startsWith("sdd-") ? "specialist" : "minion");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseOpenCodeFleet(config, { includeVariants = false } = {}) {
|
|
74
|
+
const defaultModel = typeof config?.model === "string" ? config.model : null;
|
|
75
|
+
const entries = agentEntries(config?.agent ?? config?.agents);
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
platform: "opencode",
|
|
79
|
+
orchestrator: null,
|
|
80
|
+
minions: [],
|
|
81
|
+
opaque: false,
|
|
82
|
+
writable: true,
|
|
83
|
+
source: "opencode.json"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const orch = pickOrchestrator(entries);
|
|
88
|
+
const orchModel = orch?.model ?? defaultModel;
|
|
89
|
+
const minions = entries
|
|
90
|
+
.filter((e) => isSddMinion(e, { includeVariants }))
|
|
91
|
+
.map((e) => {
|
|
92
|
+
const model = e.model ?? defaultModel;
|
|
93
|
+
return {
|
|
94
|
+
id: e.id,
|
|
95
|
+
model,
|
|
96
|
+
modelShort: shortModel(model),
|
|
97
|
+
role: minionRole(e.id),
|
|
98
|
+
mode: e.mode
|
|
99
|
+
};
|
|
100
|
+
})
|
|
101
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
platform: "opencode",
|
|
105
|
+
orchestrator: orch
|
|
106
|
+
? {
|
|
107
|
+
id: orch.id,
|
|
108
|
+
model: orchModel,
|
|
109
|
+
modelShort: shortModel(orchModel),
|
|
110
|
+
mode: orch.mode ?? "primary",
|
|
111
|
+
opaque: false
|
|
112
|
+
}
|
|
113
|
+
: null,
|
|
114
|
+
minions,
|
|
115
|
+
opaque: false,
|
|
116
|
+
writable: true,
|
|
117
|
+
source: "opencode.json"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function readJson(path, read = readFile) {
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(await read(path, "utf8"));
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function buildFleetReport({
|
|
130
|
+
homeDir = resolveHomeDir(),
|
|
131
|
+
includeVariants = false,
|
|
132
|
+
includeActivity = true,
|
|
133
|
+
read = readFile,
|
|
134
|
+
exists = pathExists,
|
|
135
|
+
gentleAvailable = () => isExecutableAvailable("gentle-ai"),
|
|
136
|
+
buildActivity = buildOpenCodeActivity,
|
|
137
|
+
buildClaude = buildClaudeFleet,
|
|
138
|
+
buildCodex = buildCodexFleet,
|
|
139
|
+
buildCursor = buildCursorAgentsFleet
|
|
140
|
+
} = {}) {
|
|
141
|
+
const fleets = [];
|
|
142
|
+
const openCodePath = join(homeDir, ".config", "opencode", "opencode.json");
|
|
143
|
+
if (await exists(openCodePath)) {
|
|
144
|
+
const config = await readJson(openCodePath, read);
|
|
145
|
+
if (config) {
|
|
146
|
+
const fleet = parseOpenCodeFleet(config, { includeVariants });
|
|
147
|
+
fleet.configPath = openCodePath;
|
|
148
|
+
fleets.push(fleet);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (await exists(join(homeDir, ".cursor"))) {
|
|
153
|
+
fleets.push(await buildCursor({ homeDir, read }));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (await exists(join(homeDir, ".claude"))) {
|
|
157
|
+
const fleet = await buildClaude({ homeDir, read });
|
|
158
|
+
if (fleet) fleets.push(fleet);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (await exists(join(homeDir, ".codex"))) {
|
|
162
|
+
const fleet = await buildCodex({ homeDir, read });
|
|
163
|
+
if (fleet) fleets.push(fleet);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const activity = includeActivity
|
|
167
|
+
? await buildActivity({ homeDir })
|
|
168
|
+
: null;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
ok: true,
|
|
172
|
+
kind: "declared+activity",
|
|
173
|
+
note: "Declared config topology + OpenCode live activity when available.",
|
|
174
|
+
orchestratorAuthority: gentleAvailable() ? "gentle-ai" : null,
|
|
175
|
+
fleets,
|
|
176
|
+
activity,
|
|
177
|
+
generatedAt: new Date().toISOString()
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function formatFleetText(report, { verbose = false } = {}) {
|
|
182
|
+
const lines = ["Fleet floor", ""];
|
|
183
|
+
if (report.orchestratorAuthority) {
|
|
184
|
+
lines.push(`Authority · ${report.orchestratorAuthority}`);
|
|
185
|
+
lines.push("");
|
|
186
|
+
}
|
|
187
|
+
for (const fleet of report.fleets ?? []) {
|
|
188
|
+
const orch = fleet.orchestrator;
|
|
189
|
+
const modelBit = orch?.opaque
|
|
190
|
+
? "opaque · IDE-managed"
|
|
191
|
+
: (orch?.modelShort ?? orch?.model ?? "—");
|
|
192
|
+
const minionCount = (fleet.minions ?? []).length;
|
|
193
|
+
lines.push(`${fleet.platform} · ${orch?.id ?? "—"} · ${modelBit}`);
|
|
194
|
+
if (verbose) {
|
|
195
|
+
for (const m of fleet.minions ?? []) {
|
|
196
|
+
const opaque = m.opaque ? " · opaque" : "";
|
|
197
|
+
lines.push(` ${m.id} · ${m.modelShort ?? m.model ?? "—"} · ${m.role}${opaque}`);
|
|
198
|
+
}
|
|
199
|
+
if (fleet.note) lines.push(` note: ${fleet.note}`);
|
|
200
|
+
} else if (minionCount > 0) {
|
|
201
|
+
lines.push(` ${minionCount} minions · kairo fleet --verbose`);
|
|
202
|
+
}
|
|
203
|
+
lines.push("");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const act = report.activity;
|
|
207
|
+
if (act?.available) {
|
|
208
|
+
const active = (act.agents ?? []).filter((a) => a.state === "active");
|
|
209
|
+
if (active.length === 0) {
|
|
210
|
+
lines.push("Working floor · quiet (no live OpenCode sessions)");
|
|
211
|
+
lines.push("");
|
|
212
|
+
} else {
|
|
213
|
+
lines.push(`Working floor · ${active.length} live`);
|
|
214
|
+
for (const a of active) {
|
|
215
|
+
lines.push(` ● ${a.id} · ${a.modelShort ?? a.model ?? "—"}`);
|
|
216
|
+
}
|
|
217
|
+
lines.push("");
|
|
218
|
+
}
|
|
219
|
+
} else if (act && !act.available) {
|
|
220
|
+
lines.push(`Working floor · unavailable`);
|
|
221
|
+
lines.push("");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if ((report.fleets ?? []).length === 0) {
|
|
225
|
+
lines.push("No agent platforms detected.");
|
|
226
|
+
}
|
|
227
|
+
lines.push(report.note ?? "");
|
|
228
|
+
return lines.join("\n").trimEnd();
|
|
229
|
+
}
|
|
@@ -7,8 +7,15 @@ import {
|
|
|
7
7
|
import { normalizeProbeResult } from "./probe-contract.js";
|
|
8
8
|
|
|
9
9
|
export const SUPPORTED_PROTOCOL = Object.freeze({ major: 2, minor: 0 });
|
|
10
|
+
export const SUPPORTED_PROTOCOL_MINORS = Object.freeze([0, 1]);
|
|
10
11
|
export const SUPPORTED_SCHEMA = "gentle-ai.review-integration.capabilities/v2";
|
|
12
|
+
export const SUPPORTED_SCHEMA_V21 = "gentle-ai.review-integration.capabilities/v2.1";
|
|
13
|
+
export const SUPPORTED_CAPABILITY_SCHEMAS = Object.freeze([
|
|
14
|
+
SUPPORTED_SCHEMA,
|
|
15
|
+
SUPPORTED_SCHEMA_V21
|
|
16
|
+
]);
|
|
11
17
|
export const SUPPORTED_CONTRACT = "gentle-ai.review-integration/v2";
|
|
18
|
+
export const ADDITIVE_MINOR_POLICY = "optional-fields-only";
|
|
12
19
|
export const SUPPORTED_MANDATORY_FEATURES = Object.freeze([
|
|
13
20
|
"compact_v2_authority", "exact_receipt_replay", "five_delivery_gates",
|
|
14
21
|
"immutable_snapshot", "legacy_v1_target_scoped_read_only",
|
|
@@ -51,7 +58,7 @@ export function evaluateGentleCapabilities(payload) {
|
|
|
51
58
|
diagnostics: ["Capabilities payload is not an object."]
|
|
52
59
|
});
|
|
53
60
|
}
|
|
54
|
-
if (payload.schema
|
|
61
|
+
if (!SUPPORTED_CAPABILITY_SCHEMAS.includes(payload.schema)) {
|
|
55
62
|
diagnostics.push(`schema mismatch: got ${String(payload.schema)}`);
|
|
56
63
|
}
|
|
57
64
|
if (payload.contract !== SUPPORTED_CONTRACT) {
|
|
@@ -60,9 +67,30 @@ export function evaluateGentleCapabilities(payload) {
|
|
|
60
67
|
if (payload.protocol?.major !== SUPPORTED_PROTOCOL.major) {
|
|
61
68
|
diagnostics.push(`protocol.major mismatch: got ${String(payload.protocol?.major)}`);
|
|
62
69
|
}
|
|
63
|
-
if (payload.protocol?.minor
|
|
70
|
+
if (!SUPPORTED_PROTOCOL_MINORS.includes(payload.protocol?.minor)) {
|
|
64
71
|
diagnostics.push(`protocol.minor mismatch: got ${String(payload.protocol?.minor)}`);
|
|
65
72
|
}
|
|
73
|
+
const additivePolicy = payload.compatibility?.additive_minor_policy;
|
|
74
|
+
if (additivePolicy != null && additivePolicy !== ADDITIVE_MINOR_POLICY) {
|
|
75
|
+
diagnostics.push(`additive_minor_policy mismatch: got ${String(additivePolicy)}`);
|
|
76
|
+
}
|
|
77
|
+
if (typeof payload.bootstrap?.command === "string" && payload.bootstrap.command) {
|
|
78
|
+
evidence.push({
|
|
79
|
+
kind: "bootstrap",
|
|
80
|
+
command: payload.bootstrap.command,
|
|
81
|
+
required_feature: payload.bootstrap.required_feature ?? null
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const requiredFeature = payload.bootstrap?.required_feature;
|
|
85
|
+
if (typeof requiredFeature === "string" && requiredFeature) {
|
|
86
|
+
const named = [
|
|
87
|
+
...(Array.isArray(payload.features?.mandatory) ? payload.features.mandatory : []),
|
|
88
|
+
...(Array.isArray(payload.features?.optional) ? payload.features.optional : [])
|
|
89
|
+
];
|
|
90
|
+
if (!named.some((feature) => feature?.name === requiredFeature && feature.supported === true)) {
|
|
91
|
+
diagnostics.push(`bootstrap required_feature not supported: ${requiredFeature}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
66
94
|
const mandatory = payload.features?.mandatory;
|
|
67
95
|
if (!Array.isArray(mandatory)) {
|
|
68
96
|
diagnostics.push("features.mandatory must be an array.");
|
|
@@ -24,7 +24,8 @@ export {
|
|
|
24
24
|
runPassiveObservabilitySnapshot
|
|
25
25
|
} from "./passive-snapshot-flight.js";
|
|
26
26
|
export {
|
|
27
|
-
SUPPORTED_PROTOCOL, SUPPORTED_SCHEMA,
|
|
27
|
+
SUPPORTED_PROTOCOL, SUPPORTED_PROTOCOL_MINORS, SUPPORTED_SCHEMA, SUPPORTED_SCHEMA_V21,
|
|
28
|
+
SUPPORTED_CAPABILITY_SCHEMAS, SUPPORTED_CONTRACT, ADDITIVE_MINOR_POLICY,
|
|
28
29
|
SUPPORTED_MANDATORY_FEATURES, evaluateGentleCapabilities, probeGentle, createGentleProbe,
|
|
29
30
|
resolveGentleBinaryPath
|
|
30
31
|
} from "./gentle-probe.js";
|
package/src/global/paths.js
CHANGED
|
@@ -23,7 +23,8 @@ export function harnessHomePaths(homeDir) {
|
|
|
23
23
|
monitorDir: join(root, "monitor"),
|
|
24
24
|
monitorStatePath: join(root, "monitor", "state.json"),
|
|
25
25
|
coreDir: join(root, "core"),
|
|
26
|
-
backupsDir: join(root, "backups")
|
|
26
|
+
backupsDir: join(root, "backups"),
|
|
27
|
+
sessionsDir: join(root, "sessions")
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
|