@kal-elsam/kairo-runtime 0.1.4 → 0.2.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 +23 -0
- package/package.json +1 -1
- package/src/cli.js +113 -4
- package/src/global/action-planner.js +230 -0
- package/src/global/agent-capabilities/create-capability-adapter.js +224 -0
- package/src/global/agent-capabilities/index.js +54 -0
- package/src/global/brand/cli.js +11 -7
- package/src/global/capability-registry.js +80 -0
- package/src/global/capability-states.js +30 -0
- package/src/global/cli-probe.js +47 -0
- package/src/global/ink/orchestrator-app.js +224 -0
- package/src/global/ink/orchestrator-state.js +107 -0
- package/src/global/ink/run-orchestrator-ink.js +29 -0
- package/src/global/intelligence/backends/custom-http.js +175 -0
- package/src/global/intelligence/backends/ollama.js +164 -0
- package/src/global/intelligence/backends/openrouter.js +198 -0
- package/src/global/intelligence/context-compiler.js +338 -0
- package/src/global/intelligence/custom-url.js +82 -0
- package/src/global/intelligence/http.js +63 -0
- package/src/global/intelligence/index.js +38 -0
- package/src/global/intelligence/orchestrate.js +189 -0
- package/src/global/intelligence/registry.js +77 -0
- package/src/global/intelligence/router.js +191 -0
- package/src/global/intelligence/types.js +99 -0
- package/src/global/intelligence-cli.js +323 -0
- package/src/global/orchestrator.js +150 -0
- package/src/global/paths.js +1 -0
- package/src/global/profile.js +297 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import cursorManaged from "../adapters/cursor.js";
|
|
2
|
+
import codexManaged from "../adapters/codex.js";
|
|
3
|
+
import opencodeManaged from "../adapters/opencode.js";
|
|
4
|
+
import claudeManaged from "../adapters/claude.js";
|
|
5
|
+
import { createAgentCapabilityAdapter } from "./create-capability-adapter.js";
|
|
6
|
+
|
|
7
|
+
const cursor = createAgentCapabilityAdapter({
|
|
8
|
+
id: "cursor",
|
|
9
|
+
label: "Cursor",
|
|
10
|
+
managedAdapter: cursorManaged,
|
|
11
|
+
executable: "cursor",
|
|
12
|
+
opaqueAuth: true
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const codex = createAgentCapabilityAdapter({
|
|
16
|
+
id: "codex",
|
|
17
|
+
label: "Codex",
|
|
18
|
+
managedAdapter: codexManaged,
|
|
19
|
+
executable: "codex",
|
|
20
|
+
runExecutable: "codex"
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const opencode = createAgentCapabilityAdapter({
|
|
24
|
+
id: "opencode",
|
|
25
|
+
label: "OpenCode",
|
|
26
|
+
managedAdapter: opencodeManaged,
|
|
27
|
+
executable: "opencode",
|
|
28
|
+
runExecutable: "opencode"
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const claude = createAgentCapabilityAdapter({
|
|
32
|
+
id: "claude",
|
|
33
|
+
label: "Claude Code",
|
|
34
|
+
managedAdapter: claudeManaged,
|
|
35
|
+
executable: "claude",
|
|
36
|
+
authArgs: ["auth", "status"],
|
|
37
|
+
runExecutable: "claude"
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const CAPABILITY_ADAPTERS = [cursor, codex, opencode, claude];
|
|
41
|
+
|
|
42
|
+
export const AGENT_CAPABILITY_IDS = CAPABILITY_ADAPTERS.map((adapter) => adapter.id);
|
|
43
|
+
|
|
44
|
+
export function listCapabilityAdapters() {
|
|
45
|
+
return [...CAPABILITY_ADAPTERS];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveCapabilityAdapter(id) {
|
|
49
|
+
const adapter = CAPABILITY_ADAPTERS.find((candidate) => candidate.id === id);
|
|
50
|
+
if (!adapter) {
|
|
51
|
+
throw new Error(`Unknown agent capability "${id}". Use ${AGENT_CAPABILITY_IDS.join(", ")}.`);
|
|
52
|
+
}
|
|
53
|
+
return adapter;
|
|
54
|
+
}
|
package/src/global/brand/cli.js
CHANGED
|
@@ -51,9 +51,9 @@ export function formatCliCommand(subcommand, cliName = PREFERRED_CLI) {
|
|
|
51
51
|
return trimmed ? `${cliName} ${trimmed}` : cliName;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function detectInvocationPackageManager() {
|
|
55
|
-
const execPath =
|
|
56
|
-
const userAgent =
|
|
54
|
+
function detectInvocationPackageManager(env = process.env) {
|
|
55
|
+
const execPath = env.npm_execpath ?? "";
|
|
56
|
+
const userAgent = env.npm_config_user_agent ?? "";
|
|
57
57
|
|
|
58
58
|
if (execPath.includes("pnpm") || userAgent.startsWith("pnpm/")) return "pnpm";
|
|
59
59
|
if (execPath.includes("yarn") || userAgent.startsWith("yarn/")) return "yarn";
|
|
@@ -61,7 +61,11 @@ function detectInvocationPackageManager() {
|
|
|
61
61
|
return "npm";
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function resolveSuggestedInvocation(
|
|
64
|
+
export function resolveSuggestedInvocation(
|
|
65
|
+
packageName = PACKAGE_NAME,
|
|
66
|
+
argv = process.argv,
|
|
67
|
+
{ env = process.env } = {}
|
|
68
|
+
) {
|
|
65
69
|
const invokedPath = argv[1] ?? PREFERRED_CLI;
|
|
66
70
|
const invokedBase = basename(invokedPath);
|
|
67
71
|
|
|
@@ -69,7 +73,7 @@ export function resolveSuggestedInvocation(packageName = PACKAGE_NAME, argv = pr
|
|
|
69
73
|
return invokedBase;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
const packageManager = detectInvocationPackageManager();
|
|
76
|
+
const packageManager = detectInvocationPackageManager(env);
|
|
73
77
|
|
|
74
78
|
switch (packageManager) {
|
|
75
79
|
case "pnpm":
|
|
@@ -85,9 +89,9 @@ export function resolveSuggestedInvocation(packageName = PACKAGE_NAME, argv = pr
|
|
|
85
89
|
|
|
86
90
|
export function formatSuggestedCliCommand(
|
|
87
91
|
subcommand,
|
|
88
|
-
{ packageName = PACKAGE_NAME, argv = process.argv, suggestedInvocation } = {}
|
|
92
|
+
{ packageName = PACKAGE_NAME, argv = process.argv, suggestedInvocation, env = process.env } = {}
|
|
89
93
|
) {
|
|
90
|
-
const invoke = suggestedInvocation ?? resolveSuggestedInvocation(packageName, argv);
|
|
94
|
+
const invoke = suggestedInvocation ?? resolveSuggestedInvocation(packageName, argv, { env });
|
|
91
95
|
const trimmed = subcommand.trim();
|
|
92
96
|
return trimmed ? `${invoke} ${trimmed}` : invoke;
|
|
93
97
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { buildAdapterContext } from "./adapter-context.js";
|
|
2
|
+
import { CAPABILITY_STATES } from "./capability-states.js";
|
|
3
|
+
import {
|
|
4
|
+
AGENT_CAPABILITY_IDS,
|
|
5
|
+
listCapabilityAdapters,
|
|
6
|
+
resolveCapabilityAdapter
|
|
7
|
+
} from "./agent-capabilities/index.js";
|
|
8
|
+
|
|
9
|
+
export async function inspectAllCapabilities({
|
|
10
|
+
homeDir,
|
|
11
|
+
workspaceRoot = process.cwd(),
|
|
12
|
+
packageName = "@kal-elsam/kairo-runtime",
|
|
13
|
+
probeOverrides = null
|
|
14
|
+
} = {}) {
|
|
15
|
+
const context = buildAdapterContext({ homeDir, workspaceRoot, packageName });
|
|
16
|
+
const adapters = listCapabilityAdapters();
|
|
17
|
+
|
|
18
|
+
return Promise.all(adapters.map(async (adapter) => {
|
|
19
|
+
const probeOptions = probeOverrides?.[adapter.id] ?? {};
|
|
20
|
+
const inspection = adapter.inspect(context, probeOptions);
|
|
21
|
+
const models = adapter.listModels?.(context, probeOptions) ?? null;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...inspection,
|
|
25
|
+
models
|
|
26
|
+
};
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function inspectCapability(agentId, context, probeOptions = {}) {
|
|
31
|
+
const adapter = resolveCapabilityAdapter(agentId);
|
|
32
|
+
const inspection = adapter.inspect(context, probeOptions);
|
|
33
|
+
const models = adapter.listModels?.(context, probeOptions) ?? null;
|
|
34
|
+
return { ...inspection, models };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function summarizeCapabilityRegistry(capabilities) {
|
|
38
|
+
const byState = {};
|
|
39
|
+
for (const state of Object.values(CAPABILITY_STATES)) {
|
|
40
|
+
byState[state] = 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const capability of capabilities) {
|
|
44
|
+
if (byState[capability.state] != null) {
|
|
45
|
+
byState[capability.state] += 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
total: capabilities.length,
|
|
51
|
+
supported: AGENT_CAPABILITY_IDS.length,
|
|
52
|
+
detected: capabilities.filter((entry) => entry.detected).length,
|
|
53
|
+
available: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.AVAILABLE).length,
|
|
54
|
+
byState
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildCapabilityDiagnostics(capabilities) {
|
|
59
|
+
const recommendations = capabilities
|
|
60
|
+
.map((entry) => entry.recommendation)
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
|
|
63
|
+
const errors = capabilities
|
|
64
|
+
.filter((entry) => entry.state === CAPABILITY_STATES.ERROR)
|
|
65
|
+
.map((entry) => ({
|
|
66
|
+
agent: entry.id,
|
|
67
|
+
message: entry.error ?? entry.recommendation ?? "Inspection failed."
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
recommendations,
|
|
72
|
+
errors,
|
|
73
|
+
hasActionableErrors: errors.length > 0
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function delegateToAgent(agentId, context, runOptions = {}) {
|
|
78
|
+
const adapter = resolveCapabilityAdapter(agentId);
|
|
79
|
+
return adapter.run(context, runOptions);
|
|
80
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const CAPABILITY_STATES = {
|
|
2
|
+
DETECTED: "detected",
|
|
3
|
+
AUTHENTICATED: "authenticated",
|
|
4
|
+
AVAILABLE: "available",
|
|
5
|
+
UNKNOWN: "unknown",
|
|
6
|
+
ERROR: "error"
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function isCapabilityState(value) {
|
|
10
|
+
return Object.values(CAPABILITY_STATES).includes(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatCapabilityState(state) {
|
|
14
|
+
switch (state) {
|
|
15
|
+
case CAPABILITY_STATES.DETECTED:
|
|
16
|
+
return "detected";
|
|
17
|
+
case CAPABILITY_STATES.AUTHENTICATED:
|
|
18
|
+
return "authenticated";
|
|
19
|
+
case CAPABILITY_STATES.AVAILABLE:
|
|
20
|
+
return "available";
|
|
21
|
+
case CAPABILITY_STATES.UNKNOWN:
|
|
22
|
+
return "unknown";
|
|
23
|
+
case CAPABILITY_STATES.ERROR:
|
|
24
|
+
return "error";
|
|
25
|
+
default: {
|
|
26
|
+
const _exhaustive = state;
|
|
27
|
+
return String(_exhaustive);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
4
|
+
|
|
5
|
+
export function isExecutableAvailable(command, { env = process.env } = {}) {
|
|
6
|
+
const result = spawnSync("which", [command], { encoding: "utf8", env });
|
|
7
|
+
return result.status === 0 && result.stdout.trim().length > 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function probeCommand(command, args = [], {
|
|
11
|
+
cwd = process.cwd(),
|
|
12
|
+
env = process.env,
|
|
13
|
+
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
14
|
+
} = {}) {
|
|
15
|
+
const result = spawnSync(command, args, {
|
|
16
|
+
cwd,
|
|
17
|
+
env,
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
timeout: timeoutMs
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
ok: result.status === 0,
|
|
24
|
+
status: result.status,
|
|
25
|
+
stdout: (result.stdout ?? "").trim(),
|
|
26
|
+
stderr: (result.stderr ?? "").trim(),
|
|
27
|
+
error: result.error?.message ?? null,
|
|
28
|
+
timedOut: result.error?.code === "ETIMEDOUT"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parseVersionFromOutput(output) {
|
|
33
|
+
if (!output) return null;
|
|
34
|
+
|
|
35
|
+
const match = output.match(/(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
|
|
36
|
+
return match?.[1] ?? null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveProbeState({ detected, cliAvailable, authReady, probeError, opaque = false }) {
|
|
40
|
+
if (probeError) return "error";
|
|
41
|
+
if (opaque) return "unknown";
|
|
42
|
+
if (!detected && !cliAvailable) return "unknown";
|
|
43
|
+
if (cliAvailable && authReady) return "available";
|
|
44
|
+
if (authReady) return "authenticated";
|
|
45
|
+
if (detected || cliAvailable) return "detected";
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
3
|
+
import { BRAND } from "../brand/index.js";
|
|
4
|
+
import { PLAN_ACTIONS, buildActionPlan, buildReadOnlyDiagnostics } from "../action-planner.js";
|
|
5
|
+
import { buildProfileJson, resolveProfile } from "../profile.js";
|
|
6
|
+
import {
|
|
7
|
+
ORCHESTRATOR_MENU,
|
|
8
|
+
ORCHESTRATOR_VIEWS,
|
|
9
|
+
formatAgentStatusLines,
|
|
10
|
+
formatIntelligenceLines,
|
|
11
|
+
formatPlanLines,
|
|
12
|
+
formatProfileLines
|
|
13
|
+
} from "./orchestrator-state.js";
|
|
14
|
+
|
|
15
|
+
const COLORS = {
|
|
16
|
+
accent: "cyan",
|
|
17
|
+
success: "green",
|
|
18
|
+
warning: "yellow",
|
|
19
|
+
danger: "red",
|
|
20
|
+
muted: "gray"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function OrchestratorApp({
|
|
24
|
+
homeDir,
|
|
25
|
+
workspaceRoot,
|
|
26
|
+
packageName,
|
|
27
|
+
packageRoot,
|
|
28
|
+
cliVersion,
|
|
29
|
+
onComplete
|
|
30
|
+
}) {
|
|
31
|
+
const { exit } = useApp();
|
|
32
|
+
const [view, setView] = useState(ORCHESTRATOR_VIEWS.HOME);
|
|
33
|
+
const [menuIndex, setMenuIndex] = useState(0);
|
|
34
|
+
const [loading, setLoading] = useState(true);
|
|
35
|
+
const [error, setError] = useState(null);
|
|
36
|
+
const [diagnostics, setDiagnostics] = useState(null);
|
|
37
|
+
const [profileJson, setProfileJson] = useState(null);
|
|
38
|
+
const [plan, setPlan] = useState(null);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
|
|
43
|
+
async function load() {
|
|
44
|
+
try {
|
|
45
|
+
const [diag, profileResolved] = await Promise.all([
|
|
46
|
+
buildReadOnlyDiagnostics({ homeDir, workspaceRoot, packageName, packageRoot, cliVersion }),
|
|
47
|
+
resolveProfile({ homeDir, workspaceRoot })
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
if (cancelled) return;
|
|
51
|
+
setDiagnostics(diag);
|
|
52
|
+
setProfileJson(buildProfileJson(profileResolved));
|
|
53
|
+
setLoading(false);
|
|
54
|
+
} catch (loadError) {
|
|
55
|
+
if (cancelled) return;
|
|
56
|
+
setError(loadError instanceof Error ? loadError.message : String(loadError));
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
load();
|
|
62
|
+
return () => {
|
|
63
|
+
cancelled = true;
|
|
64
|
+
};
|
|
65
|
+
}, [homeDir, workspaceRoot, packageName, packageRoot, cliVersion]);
|
|
66
|
+
|
|
67
|
+
const finish = (outcome) => {
|
|
68
|
+
onComplete(outcome);
|
|
69
|
+
exit();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
useInput((inputKey, key) => {
|
|
73
|
+
if (key.escape) {
|
|
74
|
+
if (view === ORCHESTRATOR_VIEWS.HOME) {
|
|
75
|
+
finish({ cancelled: true });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setView(ORCHESTRATOR_VIEWS.HOME);
|
|
79
|
+
setPlan(null);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (loading || error) return;
|
|
84
|
+
|
|
85
|
+
if (view === ORCHESTRATOR_VIEWS.CONFIRM) {
|
|
86
|
+
if (inputKey.toLowerCase() === "y") {
|
|
87
|
+
finish({ cancelled: false, action: plan?.action ?? PLAN_ACTIONS.SETUP, confirmed: true, plan });
|
|
88
|
+
}
|
|
89
|
+
if (inputKey.toLowerCase() === "n") {
|
|
90
|
+
setView(ORCHESTRATOR_VIEWS.HOME);
|
|
91
|
+
setPlan(null);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (view !== ORCHESTRATOR_VIEWS.HOME) return;
|
|
97
|
+
|
|
98
|
+
if (key.upArrow) {
|
|
99
|
+
setMenuIndex((index) => Math.max(0, index - 1));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (key.downArrow) {
|
|
104
|
+
setMenuIndex((index) => Math.min(ORCHESTRATOR_MENU.length - 1, index + 1));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!key.return) return;
|
|
109
|
+
|
|
110
|
+
const item = ORCHESTRATOR_MENU[menuIndex];
|
|
111
|
+
if (item.action === "setup") {
|
|
112
|
+
buildActionPlan({
|
|
113
|
+
action: PLAN_ACTIONS.SETUP,
|
|
114
|
+
homeDir,
|
|
115
|
+
workspaceRoot,
|
|
116
|
+
packageName,
|
|
117
|
+
options: { packageRoot, cliVersion }
|
|
118
|
+
}).then((builtPlan) => {
|
|
119
|
+
setPlan(builtPlan);
|
|
120
|
+
setView(ORCHESTRATOR_VIEWS.CONFIRM);
|
|
121
|
+
}).catch((planError) => {
|
|
122
|
+
setError(planError instanceof Error ? planError.message : String(planError));
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setView(item.view);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (loading) {
|
|
131
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
132
|
+
React.createElement(Text, { bold: true, color: COLORS.accent }, BRAND.displayName),
|
|
133
|
+
React.createElement(Text, { color: COLORS.muted }, "Loading agent capabilities…")
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (error) {
|
|
138
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
139
|
+
React.createElement(Text, { bold: true, color: COLORS.danger }, "Orchestrator error"),
|
|
140
|
+
React.createElement(Text, null, error),
|
|
141
|
+
React.createElement(Text, { dimColor: true }, "Esc to exit")
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
146
|
+
React.createElement(Text, { bold: true, color: COLORS.accent }, `${BRAND.displayName} orchestrator`),
|
|
147
|
+
React.createElement(Text, { color: COLORS.muted }, "Harness Engineering · local-first · cloud opt-in"),
|
|
148
|
+
React.createElement(Text, null, ""),
|
|
149
|
+
renderView({ view, diagnostics, profileJson, plan, menuIndex }),
|
|
150
|
+
React.createElement(Text, null, ""),
|
|
151
|
+
React.createElement(Text, { dimColor: true }, footerHint(view))
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function renderView({ view, diagnostics, profileJson, plan, menuIndex }) {
|
|
156
|
+
switch (view) {
|
|
157
|
+
case ORCHESTRATOR_VIEWS.HOME:
|
|
158
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
159
|
+
React.createElement(Text, { bold: true }, "Menu"),
|
|
160
|
+
ORCHESTRATOR_MENU.map((item, index) =>
|
|
161
|
+
React.createElement(Text, {
|
|
162
|
+
key: item.id,
|
|
163
|
+
color: index === menuIndex ? COLORS.accent : undefined,
|
|
164
|
+
bold: index === menuIndex
|
|
165
|
+
}, `${index === menuIndex ? "› " : " "}${item.label}`)
|
|
166
|
+
),
|
|
167
|
+
React.createElement(Text, null, ""),
|
|
168
|
+
React.createElement(Text, { bold: true }, "Snapshot"),
|
|
169
|
+
React.createElement(Text, null, `Agents detected: ${diagnostics.diagnostics.detected}/${diagnostics.capabilities.length}`),
|
|
170
|
+
React.createElement(Text, null, `Available: ${diagnostics.diagnostics.available}`),
|
|
171
|
+
diagnostics.intelligence && React.createElement(
|
|
172
|
+
Text,
|
|
173
|
+
null,
|
|
174
|
+
`Intelligence: local=${diagnostics.intelligence.summary.localAvailable ? "yes" : "no"} cloud=${diagnostics.intelligence.summary.cloudAuthenticated ? "yes" : "no"}`
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
case ORCHESTRATOR_VIEWS.AGENTS:
|
|
178
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
179
|
+
React.createElement(Text, { bold: true }, "Agent capabilities"),
|
|
180
|
+
formatAgentStatusLines(diagnostics.capabilities)
|
|
181
|
+
.map((line) => React.createElement(Text, { key: line }, line))
|
|
182
|
+
);
|
|
183
|
+
case ORCHESTRATOR_VIEWS.INTELLIGENCE:
|
|
184
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
185
|
+
React.createElement(Text, { bold: true }, "Intelligence backends"),
|
|
186
|
+
formatIntelligenceLines(diagnostics)
|
|
187
|
+
.map((line) => React.createElement(Text, { key: line }, line)),
|
|
188
|
+
React.createElement(Text, null, ""),
|
|
189
|
+
React.createElement(Text, { dimColor: true }, "CLI: kairo intelligence status|models|context|route|ask")
|
|
190
|
+
);
|
|
191
|
+
case ORCHESTRATOR_VIEWS.PROFILE:
|
|
192
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
193
|
+
React.createElement(Text, { bold: true }, "Profile"),
|
|
194
|
+
formatProfileLines(profileJson)
|
|
195
|
+
.map((line) => React.createElement(Text, { key: line }, line))
|
|
196
|
+
);
|
|
197
|
+
case ORCHESTRATOR_VIEWS.PLAN:
|
|
198
|
+
case ORCHESTRATOR_VIEWS.CONFIRM:
|
|
199
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
200
|
+
React.createElement(Text, { bold: true }, view === ORCHESTRATOR_VIEWS.CONFIRM ? "Confirm plan" : "Plan"),
|
|
201
|
+
plan && formatPlanLines(plan).map((line) => React.createElement(Text, { key: line }, line)),
|
|
202
|
+
view === ORCHESTRATOR_VIEWS.CONFIRM && React.createElement(Text, { color: COLORS.warning }, "Y confirm · N decline")
|
|
203
|
+
);
|
|
204
|
+
case ORCHESTRATOR_VIEWS.HELP:
|
|
205
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
206
|
+
React.createElement(Text, { bold: true }, "Help"),
|
|
207
|
+
React.createElement(Text, null, "Kairo coordinates installed agent CLIs and governs project intelligence."),
|
|
208
|
+
React.createElement(Text, null, "Local-first: Ollama when available. Cloud (OpenRouter/free) needs consent."),
|
|
209
|
+
React.createElement(Text, null, "Use: intelligence status|models|context|route|ask"),
|
|
210
|
+
React.createElement(Text, null, "Profiles: ~/.harness/profile.json and .harness/kairo.json (project wins)."),
|
|
211
|
+
React.createElement(Text, null, "Credentials are never stored by Kairo — use environment variables.")
|
|
212
|
+
);
|
|
213
|
+
default: {
|
|
214
|
+
const _exhaustive = view;
|
|
215
|
+
return React.createElement(Text, null, `Unknown view: ${_exhaustive}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function footerHint(view) {
|
|
221
|
+
if (view === ORCHESTRATOR_VIEWS.HOME) return "↑↓ navigate · Enter select · Esc quit";
|
|
222
|
+
if (view === ORCHESTRATOR_VIEWS.CONFIRM) return "Y confirm · N decline · Esc back";
|
|
223
|
+
return "Esc back to menu";
|
|
224
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
+
import { canUseSetupInk } from "./terminal.js";
|
|
3
|
+
|
|
4
|
+
export function canUseOrchestratorShell({
|
|
5
|
+
interactive = Boolean(input.isTTY && output.isTTY),
|
|
6
|
+
term = process.env.TERM ?? "",
|
|
7
|
+
columns = output.columns ?? 80,
|
|
8
|
+
forceInk = process.env.HARNESS_INK !== "0"
|
|
9
|
+
} = {}) {
|
|
10
|
+
return canUseSetupInk({ interactive, term, columns, forceInk });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ORCHESTRATOR_VIEWS = {
|
|
14
|
+
HOME: "home",
|
|
15
|
+
AGENTS: "agents",
|
|
16
|
+
PROFILE: "profile",
|
|
17
|
+
PLAN: "plan",
|
|
18
|
+
CONFIRM: "confirm",
|
|
19
|
+
HELP: "help",
|
|
20
|
+
INTELLIGENCE: "intelligence"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ORCHESTRATOR_MENU = [
|
|
24
|
+
{ id: "status", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.HOME },
|
|
25
|
+
{ id: "agents", label: "Agents", view: ORCHESTRATOR_VIEWS.AGENTS },
|
|
26
|
+
{ id: "intelligence", label: "Intelligence", view: ORCHESTRATOR_VIEWS.INTELLIGENCE },
|
|
27
|
+
{ id: "profile", label: "Profile", view: ORCHESTRATOR_VIEWS.PROFILE },
|
|
28
|
+
{ id: "plan-setup", label: "Plan setup", view: ORCHESTRATOR_VIEWS.PLAN, action: "setup" },
|
|
29
|
+
{ id: "help", label: "Help", view: ORCHESTRATOR_VIEWS.HELP }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function formatAgentStatusLines(capabilities) {
|
|
33
|
+
return capabilities.map((entry) => {
|
|
34
|
+
const auth = entry.authenticated == null ? "n/a" : (entry.authenticated ? "yes" : "no");
|
|
35
|
+
const version = entry.version ?? "unknown";
|
|
36
|
+
return `${entry.label.padEnd(14)} ${entry.state.padEnd(14)} v${version} auth=${auth}`;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatProfileLines(profileJson) {
|
|
41
|
+
const lines = [
|
|
42
|
+
`Coordinator: ${profileJson.coordinator ?? "none"}`,
|
|
43
|
+
`Default agents: ${formatAgentsLabel(profileJson.defaultAgents)}`,
|
|
44
|
+
`Apply mode: ${profileJson.applyMode}`,
|
|
45
|
+
`Preferred backend: ${profileJson.preferredBackend ?? "auto"}`,
|
|
46
|
+
`Preferred model: ${profileJson.preferredModel ?? "auto"}`,
|
|
47
|
+
`Cloud consent preference: ${profileJson.cloudConsent ? "recorded (session --cloud-consent still required)" : "no"}`,
|
|
48
|
+
`Token budget: ${profileJson.tokenBudget ?? "none"}`
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
if (profileJson.sources.global) {
|
|
52
|
+
lines.push(`Global: ${profileJson.sources.global}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (profileJson.sources.project) {
|
|
56
|
+
lines.push(`Project: ${profileJson.sources.project}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
lines.push(`Precedence: ${profileJson.sources.precedence}`);
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatIntelligenceLines(diagnostics) {
|
|
64
|
+
const intelligence = diagnostics?.intelligence;
|
|
65
|
+
if (!intelligence) {
|
|
66
|
+
return ["Intelligence layer unavailable."];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const lines = [
|
|
70
|
+
`Local available: ${intelligence.summary.localAvailable ? "yes" : "no"}`,
|
|
71
|
+
`Cloud authenticated: ${intelligence.summary.cloudAuthenticated ? "yes" : "no"}`,
|
|
72
|
+
`Routing: ${intelligence.routingPreview?.reason ?? "n/a"}`,
|
|
73
|
+
`Can invoke: ${intelligence.routingPreview?.canInvoke ? "yes" : "no"}`,
|
|
74
|
+
""
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const backend of intelligence.backends ?? []) {
|
|
78
|
+
lines.push(
|
|
79
|
+
`${backend.label.padEnd(14)} ${backend.state.padEnd(14)} models=${backend.models?.length ?? 0}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return lines;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function formatPlanLines(plan) {
|
|
87
|
+
const lines = [`Action: ${plan.action}`, ""];
|
|
88
|
+
for (const step of plan.steps) {
|
|
89
|
+
lines.push(` • ${step}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (plan.warnings.length > 0) {
|
|
93
|
+
lines.push("", "Warnings:");
|
|
94
|
+
for (const warning of plan.warnings) {
|
|
95
|
+
lines.push(` ! ${warning}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatAgentsLabel(agents) {
|
|
103
|
+
if (agents === "detected") return "detected";
|
|
104
|
+
if (agents === "all") return "all";
|
|
105
|
+
if (Array.isArray(agents)) return agents.join(", ");
|
|
106
|
+
return String(agents);
|
|
107
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
import { OrchestratorApp } from "./orchestrator-app.js";
|
|
4
|
+
|
|
5
|
+
export async function runOrchestratorInk({
|
|
6
|
+
homeDir,
|
|
7
|
+
workspaceRoot,
|
|
8
|
+
packageRoot,
|
|
9
|
+
packageName,
|
|
10
|
+
cliVersion,
|
|
11
|
+
renderImpl = render
|
|
12
|
+
}) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const { waitUntilExit } = renderImpl(
|
|
15
|
+
React.createElement(OrchestratorApp, {
|
|
16
|
+
homeDir,
|
|
17
|
+
workspaceRoot,
|
|
18
|
+
packageRoot,
|
|
19
|
+
packageName,
|
|
20
|
+
cliVersion,
|
|
21
|
+
onComplete: resolve
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
waitUntilExit().catch((error) => {
|
|
26
|
+
resolve({ cancelled: true, error });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|