@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/bin/kairo-runtime.js +0 -0
  3. package/bin/kairo.js +0 -0
  4. package/package.json +1 -1
  5. package/src/cli.js +182 -8
  6. package/src/global/check-resolutions.js +31 -0
  7. package/src/global/cli-help.js +21 -2
  8. package/src/global/component-ecosystem-checks.js +2 -0
  9. package/src/global/component-integration-cli.js +29 -10
  10. package/src/global/components-resolve-cli.js +246 -0
  11. package/src/global/connection-actions.js +147 -0
  12. package/src/global/connections.js +269 -0
  13. package/src/global/control-plane/attention.js +141 -0
  14. package/src/global/control-plane/build-report.js +146 -0
  15. package/src/global/control-plane/cli.js +36 -0
  16. package/src/global/control-plane/constants.js +38 -0
  17. package/src/global/control-plane/gentle-adapters.js +183 -0
  18. package/src/global/control-plane/provider.js +69 -0
  19. package/src/global/control-plane/review-status.js +115 -0
  20. package/src/global/control-plane/sdd-status.js +49 -0
  21. package/src/global/control-plane/team.js +63 -0
  22. package/src/global/fleet-configure-plan.js +123 -0
  23. package/src/global/fleet-configure.js +303 -0
  24. package/src/global/fleet-models.js +188 -0
  25. package/src/global/fleet-set.js +219 -0
  26. package/src/global/fleet-shared.js +38 -0
  27. package/src/global/ink/cockpit-controller.js +1 -1
  28. package/src/global/ink/cockpit-models.js +4 -1
  29. package/src/global/ink/orchestrator-app.js +21 -2
  30. package/src/global/ink/ux/live-overview.js +5 -11
  31. package/src/global/ink/ux/overview-needs.js +1 -1
  32. package/src/global/integrations/engram-evidence.js +7 -2
  33. package/src/global/integrations/sdd-apply.js +17 -7
  34. package/src/global/integrations/sdd-evidence.js +22 -3
  35. package/src/global/integrations/sdd-plan.js +21 -3
  36. package/src/global/integrations/sdd-resolutions.js +73 -0
  37. package/src/global/integrations/sdd-state.js +69 -0
  38. package/src/global/integrations/sdd-verify.js +9 -4
  39. package/src/global/mcp/kairo-mcp.js +56 -5
  40. package/src/global/mcp/resolve-mcp-workspace.js +51 -0
  41. package/src/global/mcp/work-snapshot-rule.js +89 -0
  42. package/src/global/mcp/work-snapshot-tool.js +49 -0
  43. package/src/global/mcp-install.js +239 -0
  44. package/src/global/next/next-cli.js +35 -0
  45. package/src/global/next/next-report.js +145 -0
  46. package/src/global/next/project-key.js +36 -0
  47. package/src/global/next/publish-work-snapshot.js +116 -0
  48. package/src/global/next/work-enroll.js +91 -0
  49. package/src/global/next/work-snapshot.js +216 -0
  50. package/src/global/observability/fleet-activity.js +197 -0
  51. package/src/global/observability/fleet-models-catalog.js +137 -0
  52. package/src/global/observability/fleet-platforms.js +166 -0
  53. package/src/global/observability/fleet-probe.js +229 -0
  54. package/src/global/observability/gentle-probe.js +30 -2
  55. package/src/global/observability/index.js +2 -1
  56. package/src/global/paths.js +2 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Attention items and ≤2 primary actions. Gentle commands stay verbatim.
3
+ */
4
+ import {
5
+ GENTLE_DOCTOR_COMMAND,
6
+ GENTLE_INSTALL_HINT,
7
+ GENTLE_UPGRADE_LABEL,
8
+ NO_ACTIVE_WORKFLOW,
9
+ PROVIDER,
10
+ WORKFLOW_KIND
11
+ } from "./constants.js";
12
+
13
+ function gentlePrimary(workflow) {
14
+ const provider = workflow?.provider;
15
+ if (provider === PROVIDER.UPGRADE_REQUIRED) {
16
+ return {
17
+ item: {
18
+ id: "upgrade-gentle",
19
+ severity: "warning",
20
+ message: "Gentle contract is outdated. Upgrade Gentle — do not approximate workflow."
21
+ },
22
+ action: {
23
+ id: "upgrade-gentle",
24
+ label: GENTLE_UPGRADE_LABEL,
25
+ command: GENTLE_DOCTOR_COMMAND
26
+ }
27
+ };
28
+ }
29
+ if (provider === PROVIDER.UNAVAILABLE) {
30
+ return {
31
+ item: {
32
+ id: "install-gentle",
33
+ severity: "warning",
34
+ message: GENTLE_INSTALL_HINT
35
+ },
36
+ action: {
37
+ id: "install-gentle",
38
+ label: "Install Gentle",
39
+ command: null
40
+ }
41
+ };
42
+ }
43
+ if (provider === PROVIDER.INCOMPATIBLE) {
44
+ return {
45
+ item: {
46
+ id: "gentle-incompatible",
47
+ severity: "error",
48
+ message: "Gentle response is incompatible. Fail closed."
49
+ },
50
+ action: null
51
+ };
52
+ }
53
+ const command = workflow?.nextTransition?.execute?.command;
54
+ if (provider === PROVIDER.CONNECTED && typeof command === "string" && command) {
55
+ const operation = workflow.nextTransition.execute.operation;
56
+ return {
57
+ item: null,
58
+ action: {
59
+ id: "gentle-next",
60
+ label: typeof operation === "string" && operation ? operation : "Continue Gentle",
61
+ command
62
+ }
63
+ };
64
+ }
65
+ return { item: null, action: null };
66
+ }
67
+
68
+ export function buildAttention({ work, workflow, team, connections }) {
69
+ const items = [];
70
+ const primaryActions = [];
71
+ const secondaryActions = [
72
+ { id: "setup", label: "Setup", command: "kairo setup --dry-run" },
73
+ { id: "models", label: "Models", command: "kairo fleet models" },
74
+ { id: "catalog", label: "Catalog", command: "kairo fleet" },
75
+ { id: "doctor", label: "Doctor", command: "kairo doctor" }
76
+ ];
77
+
78
+ const gentle = gentlePrimary(workflow);
79
+ if (gentle.item) items.push(gentle.item);
80
+ if (gentle.action) primaryActions.push(gentle.action);
81
+
82
+ if (work?.integration?.showRepair === true) {
83
+ items.push({
84
+ id: "repair-integration",
85
+ severity: "error",
86
+ message: work.integration.detail ?? "MCP integration needs repair."
87
+ });
88
+ if (primaryActions.length < 2) {
89
+ primaryActions.push({
90
+ id: "repair",
91
+ label: "Repair",
92
+ command: "kairo mcp install --yes"
93
+ });
94
+ }
95
+ }
96
+
97
+ if (work?.integration?.state === "missing" && primaryActions.length < 2) {
98
+ items.push({
99
+ id: "connect-mcp",
100
+ severity: "warning",
101
+ message: "Kairo MCP is not registered."
102
+ });
103
+ primaryActions.push({
104
+ id: "connect",
105
+ label: "Connect Agent",
106
+ command: "kairo mcp install --yes"
107
+ });
108
+ }
109
+
110
+ for (const chip of connections ?? []) {
111
+ if (chip?.state === "error" || chip?.state === "conflict") {
112
+ items.push({
113
+ id: `conn-${chip.id}`,
114
+ severity: "warning",
115
+ message: `${chip.label ?? chip.id}: ${chip.detail ?? chip.state}`
116
+ });
117
+ }
118
+ }
119
+
120
+ if (!team?.platforms?.length) {
121
+ items.push({
122
+ id: "no-platforms",
123
+ severity: "info",
124
+ message: "No platforms detected in declared fleet topology."
125
+ });
126
+ }
127
+
128
+ if (workflow?.kind === WORKFLOW_KIND.NONE && !workflow?.active) {
129
+ items.push({
130
+ id: "no-workflow",
131
+ severity: "info",
132
+ message: NO_ACTIVE_WORKFLOW
133
+ });
134
+ }
135
+
136
+ return {
137
+ items,
138
+ primaryActions: primaryActions.slice(0, 2),
139
+ secondaryActions
140
+ };
141
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Build kairo.control-plane/v1 from next + connections/fleet + Gentle adapters.
3
+ */
4
+ import { buildNextReport } from "../next/next-report.js";
5
+ import { buildConnectionsReport } from "../connections.js";
6
+ import { resolveHomeDir } from "../paths.js";
7
+ import {
8
+ CONTROL_PLANE_SCHEMA,
9
+ NO_ACTIVE_WORKFLOW,
10
+ WORKFLOW_KIND
11
+ } from "./constants.js";
12
+ import { buildAttention } from "./attention.js";
13
+ import { loadGentleWorkflow } from "./gentle-adapters.js";
14
+ import { normalizeTeam } from "./team.js";
15
+
16
+ function sectionOk() {
17
+ return { ok: true, error: null };
18
+ }
19
+
20
+ function sectionErr(error) {
21
+ return { ok: false, error: String(error ?? "section_failed") };
22
+ }
23
+
24
+ export async function buildControlPlaneReport({
25
+ homeDir = resolveHomeDir(),
26
+ cwd = process.cwd(),
27
+ client = "cursor",
28
+ provider = "cursor",
29
+ packageRoot = null,
30
+ packageName = null,
31
+ cliVersion = null,
32
+ buildNext = buildNextReport,
33
+ buildConnections = buildConnectionsReport,
34
+ loadWorkflow = loadGentleWorkflow
35
+ } = {}) {
36
+ const diagnostics = [];
37
+ const sections = {
38
+ work: sectionOk(),
39
+ workflow: sectionOk(),
40
+ team: sectionOk(),
41
+ attention: sectionOk()
42
+ };
43
+
44
+ let work;
45
+ try {
46
+ work = await buildNext({ homeDir, cwd, provider, client });
47
+ } catch (error) {
48
+ work = {
49
+ schema: "kairo.next/v1",
50
+ ok: false,
51
+ goal: null,
52
+ progress: [],
53
+ now: null,
54
+ blockers: [],
55
+ next: null,
56
+ conversationId: null,
57
+ updatedAt: null,
58
+ integration: {
59
+ state: "broken",
60
+ provider,
61
+ client,
62
+ mcpConnected: false,
63
+ enrolled: false,
64
+ showRepair: true,
65
+ detail: error instanceof Error ? error.message : String(error)
66
+ },
67
+ diagnostics: ["work_build_failed"]
68
+ };
69
+ sections.work = sectionErr("work_build_failed");
70
+ diagnostics.push("work_build_failed");
71
+ }
72
+
73
+ let connectionsReport;
74
+ try {
75
+ connectionsReport = await buildConnections({
76
+ homeDir,
77
+ workspaceRoot: cwd,
78
+ client,
79
+ packageRoot,
80
+ packageName,
81
+ cliVersion
82
+ });
83
+ } catch (error) {
84
+ connectionsReport = {
85
+ ok: false,
86
+ connections: [],
87
+ fleets: [],
88
+ activity: null,
89
+ fleetNote: null,
90
+ orchestratorAuthority: null
91
+ };
92
+ sections.team = sectionErr("team_build_failed");
93
+ diagnostics.push("team_build_failed");
94
+ }
95
+
96
+ const team = normalizeTeam(connectionsReport);
97
+ if (sections.team.ok && team.platforms.length === 0 && connectionsReport?.ok === false) {
98
+ sections.team = sectionErr(connectionsReport.error ?? "team_empty");
99
+ }
100
+
101
+ const gentle = await loadWorkflow({ cwd });
102
+ let workflow = gentle.workflow;
103
+ if (workflow && gentle.provider && workflow.provider == null) {
104
+ workflow = { ...workflow, provider: gentle.provider };
105
+ }
106
+ if (!gentle.ok) {
107
+ sections.workflow = sectionErr(gentle.error ?? "gentle_unavailable");
108
+ diagnostics.push(gentle.error ?? "gentle_unavailable");
109
+ if (!workflow?.active && !workflow?.review) {
110
+ workflow = {
111
+ kind: WORKFLOW_KIND.NONE,
112
+ active: false,
113
+ label: NO_ACTIVE_WORKFLOW,
114
+ phase: null,
115
+ nextTransition: null,
116
+ changeName: null,
117
+ review: null,
118
+ provider: gentle.provider ?? workflow?.provider ?? null
119
+ };
120
+ }
121
+ }
122
+
123
+ const attention = buildAttention({
124
+ work,
125
+ workflow,
126
+ team,
127
+ connections: team.connections
128
+ });
129
+
130
+ const ok = sections.work.ok || sections.team.ok;
131
+
132
+ return {
133
+ schema: CONTROL_PLANE_SCHEMA,
134
+ ok,
135
+ generatedAt: new Date().toISOString(),
136
+ client,
137
+ work,
138
+ workflow,
139
+ team,
140
+ attention,
141
+ sections,
142
+ diagnostics
143
+ };
144
+ }
145
+
146
+ export { normalizeTeam };
@@ -0,0 +1,36 @@
1
+ import { resolveHomeDir } from "../paths.js";
2
+ import { printJson } from "../json-output.js";
3
+ import { commandHeader } from "../brand/index.js";
4
+ import { buildControlPlaneReport } from "./build-report.js";
5
+
6
+ export async function runControlPlaneCli(options = {}) {
7
+ const report = await buildControlPlaneReport({
8
+ homeDir: options.homeDir ?? resolveHomeDir(),
9
+ cwd: options.cwd ?? process.cwd(),
10
+ client: options.mcpClient ?? options.client ?? "cursor",
11
+ provider: options.provider ?? "cursor",
12
+ packageRoot: options.packageRoot ?? null,
13
+ packageName: options.packageName ?? null,
14
+ cliVersion: options.cliVersion ?? null
15
+ });
16
+
17
+ if (options.json) {
18
+ printJson(report);
19
+ return report;
20
+ }
21
+
22
+ console.log(commandHeader("Control plane"));
23
+ console.log(`Work · ${report.work?.integration?.state ?? "—"}`);
24
+ console.log(`Workflow · ${report.workflow?.label ?? "—"}`);
25
+ const platforms = report.team?.platforms ?? [];
26
+ console.log(`Team · ${platforms.length} platform(s)`);
27
+ for (const p of platforms) {
28
+ console.log(` ${p.platform} · ${p.honesty} · agents=${p.agents?.length ?? 0}`);
29
+ }
30
+ const primary = report.attention?.primaryActions ?? [];
31
+ if (primary.length) {
32
+ console.log("Primary");
33
+ for (const action of primary) console.log(`- ${action.label}`);
34
+ }
35
+ return report;
36
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * kairo.control-plane/v1 — atomic panel report (work + workflow + team + attention).
3
+ */
4
+ export const CONTROL_PLANE_SCHEMA = "kairo.control-plane/v1";
5
+
6
+ export const WORKFLOW_KIND = Object.freeze({
7
+ SDD: "sdd",
8
+ REVIEW: "review",
9
+ DIRECT: "direct",
10
+ DELEGATED: "delegated",
11
+ NONE: "none"
12
+ });
13
+
14
+ export const HONESTY = Object.freeze({
15
+ LIVE: "live",
16
+ DECLARED: "declared",
17
+ OPAQUE: "opaque"
18
+ });
19
+
20
+ export const NO_ACTIVE_WORKFLOW = "No active workflow";
21
+
22
+ /** Control-plane Gentle provider — distinct from observability probe states. */
23
+ export const PROVIDER = Object.freeze({
24
+ CONNECTED: "connected",
25
+ UPGRADE_REQUIRED: "upgrade_required",
26
+ UNAVAILABLE: "unavailable",
27
+ INCOMPATIBLE: "incompatible"
28
+ });
29
+
30
+ export const PROVIDER_ERROR = Object.freeze({
31
+ [PROVIDER.UPGRADE_REQUIRED]: "gentle_upgrade_required",
32
+ [PROVIDER.UNAVAILABLE]: "gentle_unavailable",
33
+ [PROVIDER.INCOMPATIBLE]: "gentle_incompatible"
34
+ });
35
+
36
+ export const GENTLE_INSTALL_HINT = "Install gentle-ai separately, then Refresh.";
37
+ export const GENTLE_UPGRADE_LABEL = "Upgrade Gentle";
38
+ export const GENTLE_DOCTOR_COMMAND = "gentle-ai doctor";
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Read-only Gentle adapters for control-plane workflow/review.
3
+ * Negotiate capabilities before any workflow fetch. Never invent authority.
4
+ */
5
+ import { spawnSync } from "node:child_process";
6
+ import { isAbsolute } from "node:path";
7
+ import { probeGentle, resolveGentleBinaryPath } from "../observability/gentle-probe.js";
8
+ import { WORKFLOW_KIND, PROVIDER } from "./constants.js";
9
+ import {
10
+ emptyGentleWorkflow,
11
+ mapGentleProviderState,
12
+ providerError
13
+ } from "./provider.js";
14
+ import {
15
+ argvFromBootstrap,
16
+ bootstrapCommandFromProbe,
17
+ mapOfficialReviewStatus
18
+ } from "./review-status.js";
19
+ import {
20
+ SDD_STATUS_ARGS,
21
+ applySddProjection,
22
+ mapOfficialSddStatus
23
+ } from "./sdd-status.js";
24
+
25
+ const DEFAULT_TIMEOUT_MS = 8_000;
26
+
27
+ export function parseStrictJson(text) {
28
+ if (typeof text !== "string" || !text.trim()) return null;
29
+ try {
30
+ const parsed = JSON.parse(text.trim());
31
+ return parsed != null && typeof parsed === "object" ? parsed : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ export function extractJsonPayload(text) {
38
+ if (typeof text !== "string" || !text.trim()) return null;
39
+ const strict = parseStrictJson(text);
40
+ if (strict) return strict;
41
+ const trimmed = text.trim();
42
+ const fenced = trimmed.match(/```json\s*([\s\S]*?)```/i);
43
+ if (fenced?.[1]) {
44
+ try {
45
+ return JSON.parse(fenced[1].trim());
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ const start = trimmed.indexOf("{");
51
+ const end = trimmed.lastIndexOf("}");
52
+ if (start >= 0 && end > start) {
53
+ try {
54
+ return JSON.parse(trimmed.slice(start, end + 1));
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function resolvedGentleBinary(probed, command) {
63
+ if (typeof command === "string" && isAbsolute(command)) return command;
64
+ const path = probed?.evidence?.find((row) => row?.kind === "binary")?.path;
65
+ return typeof path === "string" && isAbsolute(path) ? path : resolveGentleBinaryPath();
66
+ }
67
+
68
+ export function runGentleCommand(args, {
69
+ cwd = process.cwd(),
70
+ env = process.env,
71
+ timeoutMs = DEFAULT_TIMEOUT_MS,
72
+ spawn = spawnSync,
73
+ command,
74
+ strict = false
75
+ } = {}) {
76
+ try {
77
+ if (typeof command !== "string" || !isAbsolute(command)) {
78
+ return { ok: false, error: "gentle_incompatible", payload: null };
79
+ }
80
+ const result = spawn(command, args, {
81
+ cwd,
82
+ env,
83
+ encoding: "utf8",
84
+ timeout: timeoutMs,
85
+ maxBuffer: 2 * 1024 * 1024,
86
+ shell: false
87
+ });
88
+ if (result.error) {
89
+ return { ok: false, error: result.error.message || "gentle_spawn_failed", payload: null };
90
+ }
91
+ const payload = strict
92
+ ? parseStrictJson(result.stdout ?? "")
93
+ : (extractJsonPayload(result.stdout ?? "") ?? extractJsonPayload(result.stderr ?? ""));
94
+ if (!payload) {
95
+ return { ok: false, error: "gentle_parse_failed", payload: null, status: result.status };
96
+ }
97
+ if (result.status !== 0 && result.status != null) {
98
+ return { ok: false, error: "gentle_nonzero_status", payload, status: result.status };
99
+ }
100
+ return { ok: true, payload, status: result.status, error: null };
101
+ } catch (error) {
102
+ return {
103
+ ok: false,
104
+ error: error instanceof Error ? error.message : String(error),
105
+ payload: null
106
+ };
107
+ }
108
+ }
109
+
110
+ function applyOfficialReview(workflow, mapped) {
111
+ workflow.review = mapped.review;
112
+ workflow.nextTransition = mapped.nextTransition;
113
+ if (workflow.kind === WORKFLOW_KIND.NONE && mapped.nextTransition != null) {
114
+ workflow.kind = WORKFLOW_KIND.REVIEW;
115
+ workflow.active = mapped.nextTransition?.kind === "execute" || mapped.review != null;
116
+ workflow.label = "Review";
117
+ }
118
+ }
119
+
120
+ export async function loadGentleWorkflow({
121
+ cwd,
122
+ env,
123
+ timeoutMs,
124
+ spawn,
125
+ command,
126
+ probe = probeGentle,
127
+ runCommand = runGentleCommand
128
+ } = {}) {
129
+ const probed = await probe({ cwd, env });
130
+ const provider = mapGentleProviderState(probed);
131
+ if (provider !== PROVIDER.CONNECTED) {
132
+ return {
133
+ ok: false,
134
+ error: providerError(provider, probed),
135
+ provider,
136
+ workflow: emptyGentleWorkflow({ provider })
137
+ };
138
+ }
139
+
140
+ const parsed = argvFromBootstrap(bootstrapCommandFromProbe(probed), {
141
+ repo: cwd ?? process.cwd(),
142
+ binaryPath: resolvedGentleBinary(probed, command)
143
+ });
144
+ if (!parsed.ok) {
145
+ const incompatible = PROVIDER.INCOMPATIBLE;
146
+ return {
147
+ ok: false, error: "gentle_incompatible", provider: incompatible,
148
+ workflow: emptyGentleWorkflow({ provider: incompatible })
149
+ };
150
+ }
151
+
152
+ const workflow = emptyGentleWorkflow({ provider });
153
+ const sdd = runCommand([...SDD_STATUS_ARGS], {
154
+ cwd, env, timeoutMs, spawn, command: parsed.binary, strict: true
155
+ });
156
+ if (sdd.ok) {
157
+ const mappedSdd = mapOfficialSddStatus(sdd.payload);
158
+ if (mappedSdd.ok) applySddProjection(workflow, mappedSdd.projection);
159
+ }
160
+
161
+ const reviewRun = runCommand(parsed.argv, {
162
+ cwd, env, timeoutMs, spawn, command: parsed.binary, strict: true
163
+ });
164
+ if (reviewRun.ok) {
165
+ const mapped = mapOfficialReviewStatus(reviewRun.payload);
166
+ if (mapped.ok) applyOfficialReview(workflow, mapped);
167
+ }
168
+
169
+ if (!sdd.ok && workflow.kind === WORKFLOW_KIND.NONE && workflow.review == null) {
170
+ return {
171
+ ok: false,
172
+ error: sdd.error ?? "gentle_sdd_unavailable",
173
+ provider,
174
+ workflow
175
+ };
176
+ }
177
+ return {
178
+ ok: true,
179
+ error: sdd.ok ? null : (sdd.error ?? "gentle_sdd_unavailable"),
180
+ provider,
181
+ workflow
182
+ };
183
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Map Gentle observability probe → control-plane provider.
3
+ * Version is evidence only; v1 contract is upgrade, unknown schema fails closed.
4
+ */
5
+ import {
6
+ NO_ACTIVE_WORKFLOW,
7
+ PROVIDER,
8
+ PROVIDER_ERROR,
9
+ WORKFLOW_KIND
10
+ } from "./constants.js";
11
+
12
+ const LEGACY_SCHEMA = "gentle-ai.review-integration.capabilities/v1";
13
+ const LEGACY_CONTRACT = "gentle-ai.review-integration/v1";
14
+
15
+ export function emptyGentleWorkflow({ provider = null } = {}) {
16
+ return {
17
+ kind: WORKFLOW_KIND.NONE,
18
+ active: false,
19
+ label: NO_ACTIVE_WORKFLOW,
20
+ phase: null,
21
+ nextTransition: null,
22
+ changeName: null,
23
+ review: null,
24
+ sdd: null,
25
+ provider
26
+ };
27
+ }
28
+
29
+ export function isRecognizedLegacyContract(probe) {
30
+ const evidenceSchemas = (probe?.evidence ?? [])
31
+ .map((row) => row?.schema)
32
+ .filter(Boolean)
33
+ .join(" ");
34
+ const blob = `${(probe?.diagnostics ?? []).join(" ")} ${evidenceSchemas}`;
35
+ return blob.includes(LEGACY_SCHEMA)
36
+ || blob.includes(LEGACY_CONTRACT)
37
+ || /protocol\.major mismatch: got 1\b/.test(blob);
38
+ }
39
+
40
+ export function mapGentleProviderState(probe) {
41
+ if (probe == null || typeof probe !== "object" || Array.isArray(probe)) {
42
+ return PROVIDER.INCOMPATIBLE;
43
+ }
44
+ if (probe.state === "missing") return PROVIDER.UNAVAILABLE;
45
+ if (probe.state === "available" && probe.contractCompatible === true) {
46
+ return PROVIDER.CONNECTED;
47
+ }
48
+ if (probe.state === "error") {
49
+ const blob = `${probe.error ?? ""} ${(probe.diagnostics ?? []).join(" ")}`;
50
+ if (/parse|not an object|invalid capabilities/i.test(blob)) {
51
+ return PROVIDER.INCOMPATIBLE;
52
+ }
53
+ return PROVIDER.UNAVAILABLE;
54
+ }
55
+ if (probe.state === "incompatible") {
56
+ return isRecognizedLegacyContract(probe)
57
+ ? PROVIDER.UPGRADE_REQUIRED
58
+ : PROVIDER.INCOMPATIBLE;
59
+ }
60
+ return PROVIDER.INCOMPATIBLE;
61
+ }
62
+
63
+ export function providerError(provider, probe = null) {
64
+ if (provider === PROVIDER.CONNECTED) return null;
65
+ if (provider === PROVIDER.UNAVAILABLE && probe?.state === "error") {
66
+ return "gentle_capabilities_failed";
67
+ }
68
+ return PROVIDER_ERROR[provider] ?? "gentle_incompatible";
69
+ }