infinicode 2.3.4 → 2.3.6

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 (45) hide show
  1. package/.opencode/plugins/home-logo-animation.tsx +5 -1
  2. package/.opencode/plugins/mesh-commands.tsx +4 -3
  3. package/.opencode/plugins/routing-mode-display.tsx +6 -2
  4. package/.opencode/tui.json +2 -1
  5. package/dist/cli.js +25 -1
  6. package/dist/commands/mesh-install.d.ts +10 -0
  7. package/dist/commands/mesh-install.js +160 -0
  8. package/dist/commands/run.js +196 -41
  9. package/dist/kernel/agents/backends/cli-backend.d.ts +37 -0
  10. package/dist/kernel/agents/backends/cli-backend.js +132 -0
  11. package/dist/kernel/agents/backends/detect.d.ts +5 -0
  12. package/dist/kernel/agents/backends/detect.js +40 -0
  13. package/dist/kernel/agents/backends/index.d.ts +11 -0
  14. package/dist/kernel/agents/backends/index.js +3 -0
  15. package/dist/kernel/agents/backends/parse-utils.d.ts +20 -0
  16. package/dist/kernel/agents/backends/parse-utils.js +72 -0
  17. package/dist/kernel/agents/backends/registry.d.ts +30 -0
  18. package/dist/kernel/agents/backends/registry.js +140 -0
  19. package/dist/kernel/agents/backends/types.d.ts +65 -0
  20. package/dist/kernel/agents/backends/types.js +1 -0
  21. package/dist/kernel/agents/index.d.ts +8 -0
  22. package/dist/kernel/agents/index.js +8 -0
  23. package/dist/kernel/federation/federation.d.ts +54 -1
  24. package/dist/kernel/federation/federation.js +180 -8
  25. package/dist/kernel/federation/types.d.ts +26 -1
  26. package/dist/kernel/free-providers.js +83 -0
  27. package/dist/kernel/gateway/index.d.ts +7 -0
  28. package/dist/kernel/gateway/index.js +7 -0
  29. package/dist/kernel/gateway/openai-gateway.d.ts +137 -0
  30. package/dist/kernel/gateway/openai-gateway.js +723 -0
  31. package/dist/kernel/index.d.ts +3 -1
  32. package/dist/kernel/index.js +5 -1
  33. package/dist/kernel/logger.d.ts +16 -3
  34. package/dist/kernel/logger.js +38 -0
  35. package/dist/kernel/mcp/mcp-server.js +47 -8
  36. package/dist/kernel/orchestrator.d.ts +4 -0
  37. package/dist/kernel/orchestrator.js +34 -0
  38. package/dist/kernel/provider-manager.d.ts +13 -0
  39. package/dist/kernel/provider-manager.js +58 -5
  40. package/dist/kernel/providers/openai-compatible-provider.d.ts +6 -0
  41. package/dist/kernel/providers/openai-compatible-provider.js +22 -3
  42. package/dist/kernel/recovery-manager.js +55 -15
  43. package/dist/kernel/router.js +15 -2
  44. package/dist/kernel/types.d.ts +12 -2
  45. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ import type { AgentBackend, BackendId, BackendRunContext, BackendRunResult, BackendTask } from './types.js';
2
+ /** Accumulator threaded through per-line parsing. */
3
+ export interface CliParsed {
4
+ sessionId?: string;
5
+ content?: string;
6
+ isError?: boolean;
7
+ }
8
+ export interface CliBackendSpec {
9
+ id: BackendId;
10
+ displayName: string;
11
+ /** Binary name looked up on PATH (overridable via `binEnv`). */
12
+ bin: string;
13
+ /** argv for a fresh run. `prompt` already includes any role preamble. */
14
+ runArgs(prompt: string, task: BackendTask): string[];
15
+ /** argv to continue an existing session with a new message. */
16
+ resumeArgs?(sessionId: string, message: string, task: BackendTask): string[];
17
+ /** Parse one output line (or the whole raw output, as a fallback) into `acc`. */
18
+ parseLine(line: string, acc: CliParsed): void;
19
+ /** Extra process env. */
20
+ env?: NodeJS.ProcessEnv;
21
+ /** Env var overriding the binary path (e.g. INFINICODE_CLAUDE_BIN). */
22
+ binEnv?: string;
23
+ /** Env var of extra whitespace-split args appended to every invocation. */
24
+ extraArgsEnv?: string;
25
+ }
26
+ export declare class CliAgentBackend implements AgentBackend {
27
+ private spec;
28
+ readonly id: BackendId;
29
+ readonly displayName: string;
30
+ constructor(spec: CliBackendSpec);
31
+ private resolveBin;
32
+ available(): boolean;
33
+ private extraArgs;
34
+ run(task: BackendTask, ctx: BackendRunContext): Promise<BackendRunResult>;
35
+ send(sessionId: string, message: string, ctx: BackendRunContext): Promise<BackendRunResult>;
36
+ private exec;
37
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * OpenKernel — Generic CLI agent backend
3
+ *
4
+ * Runs a third-party coding CLI (Claude Code / Codex / OpenCode) as the executor
5
+ * for a dispatched mesh task. Spawns the process, streams every stdout/stderr
6
+ * line up the mesh as a frame, and parses the CLI's output into a session id +
7
+ * final text so the run can be followed and (later) messaged again.
8
+ *
9
+ * A `CliBackendSpec` captures the per-CLI differences (binary, argv, parsing);
10
+ * everything else — spawning, streaming, cancellation — is shared here.
11
+ */
12
+ import { spawn } from 'node:child_process';
13
+ import { findExecutable } from './detect.js';
14
+ export class CliAgentBackend {
15
+ spec;
16
+ id;
17
+ displayName;
18
+ constructor(spec) {
19
+ this.spec = spec;
20
+ this.id = spec.id;
21
+ this.displayName = spec.displayName;
22
+ }
23
+ resolveBin() {
24
+ const override = this.spec.binEnv ? process.env[this.spec.binEnv] : undefined;
25
+ if (override)
26
+ return override;
27
+ return findExecutable(this.spec.bin);
28
+ }
29
+ available() {
30
+ return this.resolveBin() !== null;
31
+ }
32
+ extraArgs() {
33
+ const raw = this.spec.extraArgsEnv ? process.env[this.spec.extraArgsEnv] : undefined;
34
+ return raw ? raw.split(/\s+/).filter(Boolean) : [];
35
+ }
36
+ async run(task, ctx) {
37
+ const prompt = task.prompt && ctx.rolePreamble ? `${ctx.rolePreamble}\n\n${task.prompt}` : task.prompt;
38
+ const argv = task.sessionId && this.spec.resumeArgs
39
+ ? this.spec.resumeArgs(task.sessionId, prompt, task)
40
+ : this.spec.runArgs(prompt, task);
41
+ return this.exec([...argv, ...this.extraArgs()], ctx);
42
+ }
43
+ async send(sessionId, message, ctx) {
44
+ const msg = ctx.rolePreamble ? `${ctx.rolePreamble}\n\n${message}` : message;
45
+ const task = { prompt: msg, sessionId };
46
+ const argv = this.spec.resumeArgs
47
+ ? this.spec.resumeArgs(sessionId, msg, task)
48
+ : this.spec.runArgs(msg, task);
49
+ return this.exec([...argv, ...this.extraArgs()], ctx);
50
+ }
51
+ exec(argv, ctx) {
52
+ return new Promise(resolve => {
53
+ const bin = this.resolveBin();
54
+ if (!bin) {
55
+ resolve({ status: 'failed', error: `${this.spec.bin} not found on PATH` });
56
+ return;
57
+ }
58
+ const child = spawn(bin, argv, {
59
+ cwd: ctx.cwd ?? process.cwd(),
60
+ env: { ...process.env, ...this.spec.env },
61
+ stdio: ['ignore', 'pipe', 'pipe'],
62
+ });
63
+ const acc = {};
64
+ let stdoutBuf = '';
65
+ let rawAll = '';
66
+ let stderrTail = '';
67
+ let settled = false;
68
+ const onAbort = () => {
69
+ try {
70
+ child.kill();
71
+ }
72
+ catch { /* already gone */ }
73
+ };
74
+ ctx.signal?.addEventListener('abort', onAbort, { once: true });
75
+ const finish = (result) => {
76
+ if (settled)
77
+ return;
78
+ settled = true;
79
+ ctx.signal?.removeEventListener('abort', onAbort);
80
+ resolve(result);
81
+ };
82
+ child.stdout.setEncoding('utf8');
83
+ child.stdout.on('data', (chunk) => {
84
+ rawAll += chunk;
85
+ stdoutBuf += chunk;
86
+ let nl;
87
+ while ((nl = stdoutBuf.indexOf('\n')) >= 0) {
88
+ const line = stdoutBuf.slice(0, nl).replace(/\r$/, '');
89
+ stdoutBuf = stdoutBuf.slice(nl + 1);
90
+ if (!line.trim())
91
+ continue;
92
+ try {
93
+ this.spec.parseLine(line, acc);
94
+ }
95
+ catch { /* tolerant parsing */ }
96
+ ctx.onFrame({ kind: 'log', text: line });
97
+ }
98
+ });
99
+ child.stderr.setEncoding('utf8');
100
+ child.stderr.on('data', (chunk) => {
101
+ stderrTail = (stderrTail + chunk).slice(-2000);
102
+ const trimmed = chunk.replace(/\s+$/, '');
103
+ if (trimmed)
104
+ ctx.onFrame({ kind: 'log', text: trimmed });
105
+ });
106
+ child.on('error', err => finish({ status: 'failed', error: err.message }));
107
+ child.on('close', code => {
108
+ // Flush a trailing partial line.
109
+ if (stdoutBuf.trim()) {
110
+ try {
111
+ this.spec.parseLine(stdoutBuf.trim(), acc);
112
+ }
113
+ catch { /* ignore */ }
114
+ }
115
+ // Fallback for CLIs that pretty-print one multi-line JSON object.
116
+ if (!acc.content && !acc.sessionId && rawAll.trim()) {
117
+ try {
118
+ this.spec.parseLine(rawAll.trim(), acc);
119
+ }
120
+ catch { /* ignore */ }
121
+ }
122
+ const ok = !acc.isError && (code === 0 || !!acc.content);
123
+ finish({
124
+ status: ok ? 'completed' : 'failed',
125
+ sessionId: acc.sessionId,
126
+ content: acc.content,
127
+ error: ok ? undefined : (stderrTail.trim() || acc.content || `${this.spec.bin} exited with code ${code}`),
128
+ });
129
+ });
130
+ });
131
+ }
132
+ }
@@ -0,0 +1,5 @@
1
+ /** Absolute path to `bin` on PATH, or null if not found. */
2
+ export declare function findExecutable(bin: string): string | null;
3
+ export declare function hasExecutable(bin: string): boolean;
4
+ /** Test hook — forget cached lookups. */
5
+ export declare function clearExecutableCache(): void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * OpenKernel — Executable detection
3
+ *
4
+ * Dependency-free `which`: scan PATH (honoring PATHEXT on Windows) so a backend
5
+ * can advertise availability without spawning `--version`. Results are cached —
6
+ * a device's installed CLIs don't change mid-process.
7
+ */
8
+ import { existsSync } from 'node:fs';
9
+ import { join, delimiter } from 'node:path';
10
+ const cache = new Map();
11
+ /** Absolute path to `bin` on PATH, or null if not found. */
12
+ export function findExecutable(bin) {
13
+ const cached = cache.get(bin);
14
+ if (cached !== undefined)
15
+ return cached;
16
+ const dirs = (process.env.PATH ?? '').split(delimiter).filter(Boolean);
17
+ const exts = process.platform === 'win32'
18
+ ? ['', ...(process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)]
19
+ : [''];
20
+ for (const dir of dirs) {
21
+ for (const ext of exts) {
22
+ for (const cased of new Set([ext, ext.toLowerCase(), ext.toUpperCase()])) {
23
+ const full = join(dir, bin + cased);
24
+ if (existsSync(full)) {
25
+ cache.set(bin, full);
26
+ return full;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ cache.set(bin, null);
32
+ return null;
33
+ }
34
+ export function hasExecutable(bin) {
35
+ return findExecutable(bin) !== null;
36
+ }
37
+ /** Test hook — forget cached lookups. */
38
+ export function clearExecutableCache() {
39
+ cache.clear();
40
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * OpenKernel — Agent Backends public API
3
+ *
4
+ * Pluggable executors for dispatched mesh tasks: the native kernel plus
5
+ * third-party coding CLIs (Claude Code / Codex / OpenCode).
6
+ */
7
+ export type { AgentBackend, BackendId, BackendTask, BackendFrame, BackendRunContext, BackendRunResult, } from './types.js';
8
+ export { CliAgentBackend } from './cli-backend.js';
9
+ export type { CliBackendSpec, CliParsed } from './cli-backend.js';
10
+ export { AgentBackendRegistry, createDefaultBackendRegistry, CLAUDE_SPEC, OPENCODE_SPEC, CODEX_SPEC, } from './registry.js';
11
+ export { findExecutable, hasExecutable, clearExecutableCache } from './detect.js';
@@ -0,0 +1,3 @@
1
+ export { CliAgentBackend } from './cli-backend.js';
2
+ export { AgentBackendRegistry, createDefaultBackendRegistry, CLAUDE_SPEC, OPENCODE_SPEC, CODEX_SPEC, } from './registry.js';
3
+ export { findExecutable, hasExecutable, clearExecutableCache } from './detect.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * OpenKernel — Backend output parsing helpers
3
+ *
4
+ * Third-party CLIs emit different JSON shapes (and different versions change
5
+ * them), so parsing is deliberately defensive: try JSON, pull the common
6
+ * fields, and always fall back to raw text. Nothing here throws.
7
+ */
8
+ export declare function tryJson(line: string): Record<string, unknown> | null;
9
+ export declare function str(v: unknown): string | undefined;
10
+ /** Append a line to an accumulating text body. */
11
+ export declare function appendLine(acc: string | undefined, line: string): string;
12
+ /**
13
+ * Pull assistant text out of a wide range of JSON event shapes:
14
+ * Anthropic/Claude content arrays, plain `content` strings, and the usual
15
+ * `text`/`response`/`result`/`message` fields other CLIs use.
16
+ */
17
+ export declare function extractText(obj: Record<string, unknown>): string | undefined;
18
+ /** Common session-id keys across the CLIs, plus a bare-UUID fallback. */
19
+ export declare function extractSessionId(obj: Record<string, unknown>): string | undefined;
20
+ export declare function sessionIdFromText(line: string): string | undefined;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * OpenKernel — Backend output parsing helpers
3
+ *
4
+ * Third-party CLIs emit different JSON shapes (and different versions change
5
+ * them), so parsing is deliberately defensive: try JSON, pull the common
6
+ * fields, and always fall back to raw text. Nothing here throws.
7
+ */
8
+ export function tryJson(line) {
9
+ const s = line.trim();
10
+ if (!s || (s[0] !== '{' && s[0] !== '['))
11
+ return null;
12
+ try {
13
+ const v = JSON.parse(s);
14
+ return v && typeof v === 'object' ? v : null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ export function str(v) {
21
+ return typeof v === 'string' && v.length ? v : undefined;
22
+ }
23
+ /** Append a line to an accumulating text body. */
24
+ export function appendLine(acc, line) {
25
+ return acc ? `${acc}\n${line}` : line;
26
+ }
27
+ /**
28
+ * Pull assistant text out of a wide range of JSON event shapes:
29
+ * Anthropic/Claude content arrays, plain `content` strings, and the usual
30
+ * `text`/`response`/`result`/`message` fields other CLIs use.
31
+ */
32
+ export function extractText(obj) {
33
+ const msg = obj.message;
34
+ const content = (msg?.content ?? obj.content);
35
+ if (Array.isArray(content)) {
36
+ const parts = content
37
+ .map(c => (c && typeof c === 'object' && c.type === 'text'
38
+ ? str(c.text)
39
+ : undefined))
40
+ .filter((t) => !!t);
41
+ if (parts.length)
42
+ return parts.join('');
43
+ }
44
+ if (typeof content === 'string' && content.length)
45
+ return content;
46
+ for (const k of ['text', 'response', 'result', 'output', 'answer']) {
47
+ const t = str(obj[k]);
48
+ if (t)
49
+ return t;
50
+ }
51
+ if (msg) {
52
+ const t = str(msg.text) ?? str(msg.content);
53
+ if (t)
54
+ return t;
55
+ }
56
+ return undefined;
57
+ }
58
+ const UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
59
+ /** Common session-id keys across the CLIs, plus a bare-UUID fallback. */
60
+ export function extractSessionId(obj) {
61
+ return (str(obj.session_id) ??
62
+ str(obj.sessionID) ??
63
+ str(obj.sessionId) ??
64
+ str(obj.thread_id) ??
65
+ str(obj.conversation_id) ??
66
+ str(obj.session) ??
67
+ str(obj.id));
68
+ }
69
+ export function sessionIdFromText(line) {
70
+ const m = line.match(UUID_RE);
71
+ return m ? m[0] : undefined;
72
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * OpenKernel — Agent Backend registry + built-in CLI specs
3
+ *
4
+ * Holds the executors a node can route a task to. 'kernel' (the native path) is
5
+ * always present and handled inline by the Federation manager; the CLI backends
6
+ * are advertised only when their binary is actually installed, so the mesh's
7
+ * compute-router won't send a `claude` task to a node without Claude Code.
8
+ *
9
+ * Each CLI is invoked in headless / non-interactive mode. Flags are the stable
10
+ * public ones for each tool; a device can override the binary or append args via
11
+ * env (INFINICODE_<CLI>_BIN / INFINICODE_<CLI>_ARGS) without a code change.
12
+ */
13
+ import type { AgentBackend, BackendId } from './types.js';
14
+ import { type CliBackendSpec } from './cli-backend.js';
15
+ declare const CLAUDE_SPEC: CliBackendSpec;
16
+ declare const OPENCODE_SPEC: CliBackendSpec;
17
+ declare const CODEX_SPEC: CliBackendSpec;
18
+ export declare class AgentBackendRegistry {
19
+ private map;
20
+ register(backend: AgentBackend): this;
21
+ get(id: BackendId): AgentBackend | undefined;
22
+ /** True if this node can run `id` (the native kernel, or an installed CLI). */
23
+ has(id: BackendId): boolean;
24
+ all(): AgentBackend[];
25
+ /** Backend ids runnable here — 'kernel' plus every installed CLI. */
26
+ availableIds(): BackendId[];
27
+ }
28
+ /** Registry seeded with the built-in CLI backends (availability checked lazily). */
29
+ export declare function createDefaultBackendRegistry(): AgentBackendRegistry;
30
+ export { CLAUDE_SPEC, OPENCODE_SPEC, CODEX_SPEC };
@@ -0,0 +1,140 @@
1
+ import { CliAgentBackend } from './cli-backend.js';
2
+ import { tryJson, extractText, extractSessionId, sessionIdFromText } from './parse-utils.js';
3
+ // ── Claude Code ──────────────────────────────────────────────────────────────
4
+ // `claude -p` = headless print mode. stream-json emits JSONL: a system/init
5
+ // object carrying session_id, assistant messages, then a terminal `result`.
6
+ const CLAUDE_SPEC = {
7
+ id: 'claude',
8
+ displayName: 'Claude Code',
9
+ bin: 'claude',
10
+ binEnv: 'INFINICODE_CLAUDE_BIN',
11
+ extraArgsEnv: 'INFINICODE_CLAUDE_ARGS',
12
+ runArgs: prompt => [
13
+ '-p', prompt,
14
+ '--output-format', 'stream-json',
15
+ '--verbose',
16
+ '--dangerously-skip-permissions',
17
+ ],
18
+ resumeArgs: (sessionId, message) => [
19
+ '--resume', sessionId,
20
+ '-p', message,
21
+ '--output-format', 'stream-json',
22
+ '--verbose',
23
+ '--dangerously-skip-permissions',
24
+ ],
25
+ parseLine: (line, acc) => {
26
+ const obj = tryJson(line);
27
+ if (!obj)
28
+ return;
29
+ const sid = extractSessionId(obj);
30
+ if (sid)
31
+ acc.sessionId = sid;
32
+ if (obj.type === 'result') {
33
+ const t = extractText(obj);
34
+ if (t)
35
+ acc.content = t;
36
+ if (obj.is_error === true)
37
+ acc.isError = true;
38
+ if (typeof obj.subtype === 'string' && obj.subtype !== 'success')
39
+ acc.isError = true;
40
+ }
41
+ },
42
+ };
43
+ // ── OpenCode ─────────────────────────────────────────────────────────────────
44
+ // `opencode run "<task>" --format json` — non-interactive, resumable by session.
45
+ const OPENCODE_SPEC = {
46
+ id: 'opencode',
47
+ displayName: 'OpenCode',
48
+ bin: 'opencode',
49
+ binEnv: 'INFINICODE_OPENCODE_BIN',
50
+ extraArgsEnv: 'INFINICODE_OPENCODE_ARGS',
51
+ runArgs: (prompt, task) => [
52
+ 'run', prompt,
53
+ '--format', 'json',
54
+ ...(task.model ? ['--model', task.model] : []),
55
+ '--dangerously-skip-permissions',
56
+ ],
57
+ resumeArgs: (sessionId, message, task) => [
58
+ 'run', message,
59
+ '--format', 'json',
60
+ '--session', sessionId,
61
+ ...(task.model ? ['--model', task.model] : []),
62
+ '--dangerously-skip-permissions',
63
+ ],
64
+ parseLine: (line, acc) => {
65
+ const obj = tryJson(line);
66
+ if (!obj)
67
+ return;
68
+ const sid = extractSessionId(obj);
69
+ if (sid)
70
+ acc.sessionId ??= sid;
71
+ const t = extractText(obj);
72
+ if (t)
73
+ acc.content = t;
74
+ if (obj.error)
75
+ acc.isError = true;
76
+ },
77
+ };
78
+ // ── Codex ────────────────────────────────────────────────────────────────────
79
+ // `codex exec "<prompt>"` = non-interactive. Output shape varies by version, so
80
+ // parse defensively: JSON when present, otherwise accumulate text + sniff a UUID.
81
+ const CODEX_SPEC = {
82
+ id: 'codex',
83
+ displayName: 'Codex',
84
+ bin: 'codex',
85
+ binEnv: 'INFINICODE_CODEX_BIN',
86
+ extraArgsEnv: 'INFINICODE_CODEX_ARGS',
87
+ runArgs: prompt => ['exec', prompt],
88
+ resumeArgs: (sessionId, message) => ['exec', 'resume', sessionId, message],
89
+ parseLine: (line, acc) => {
90
+ const obj = tryJson(line);
91
+ if (obj) {
92
+ const sid = extractSessionId(obj);
93
+ if (sid)
94
+ acc.sessionId ??= sid;
95
+ const t = extractText(obj);
96
+ if (t)
97
+ acc.content = t;
98
+ if (obj.error || obj.is_error === true)
99
+ acc.isError = true;
100
+ return;
101
+ }
102
+ const sid = sessionIdFromText(line);
103
+ if (sid)
104
+ acc.sessionId ??= sid;
105
+ acc.content = acc.content ? `${acc.content}\n${line}` : line;
106
+ },
107
+ };
108
+ export class AgentBackendRegistry {
109
+ map = new Map();
110
+ register(backend) {
111
+ this.map.set(backend.id, backend);
112
+ return this;
113
+ }
114
+ get(id) {
115
+ return this.map.get(id);
116
+ }
117
+ /** True if this node can run `id` (the native kernel, or an installed CLI). */
118
+ has(id) {
119
+ if (id === 'kernel')
120
+ return true;
121
+ const b = this.map.get(id);
122
+ return !!b && b.available();
123
+ }
124
+ all() {
125
+ return [...this.map.values()];
126
+ }
127
+ /** Backend ids runnable here — 'kernel' plus every installed CLI. */
128
+ availableIds() {
129
+ const cli = this.all().filter(b => b.available()).map(b => b.id);
130
+ return ['kernel', ...cli];
131
+ }
132
+ }
133
+ /** Registry seeded with the built-in CLI backends (availability checked lazily). */
134
+ export function createDefaultBackendRegistry() {
135
+ return new AgentBackendRegistry()
136
+ .register(new CliAgentBackend(CLAUDE_SPEC))
137
+ .register(new CliAgentBackend(OPENCODE_SPEC))
138
+ .register(new CliAgentBackend(CODEX_SPEC));
139
+ }
140
+ export { CLAUDE_SPEC, OPENCODE_SPEC, CODEX_SPEC };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * OpenKernel — Agent Backend types
3
+ *
4
+ * A backend is *who actually runs a dispatched task* on a node. The mesh's
5
+ * default backend is the local kernel (`kernel.execute`), but a node can also
6
+ * run a third-party coding CLI — Claude Code, Codex, OpenCode — as the executor.
7
+ * That is what lets a task dispatched across the mesh be fulfilled by a real
8
+ * external agent, and (with a persisted session id) lets those agents be
9
+ * messaged again later.
10
+ *
11
+ * Everything here is transport-agnostic: the Federation manager owns spawning +
12
+ * streaming; a backend just knows how to drive one CLI and parse its output.
13
+ */
14
+ import type { Capability } from '../../types.js';
15
+ /** Identifier of a backend. 'kernel' is the built-in native path. */
16
+ export type BackendId = 'kernel' | 'claude' | 'codex' | 'opencode' | (string & {});
17
+ /** A unit of work handed to a backend. */
18
+ export interface BackendTask {
19
+ prompt: string;
20
+ capabilities?: Capability[];
21
+ /** Optional model hint (provider/model or a CLI-native model id). */
22
+ model?: string;
23
+ /** Resume an existing CLI session instead of starting fresh. */
24
+ sessionId?: string;
25
+ }
26
+ /** A streamed activity fragment emitted while a backend runs. */
27
+ export interface BackendFrame {
28
+ kind: 'log' | 'event' | 'output' | 'error';
29
+ text?: string;
30
+ data?: unknown;
31
+ }
32
+ /** Context passed to a backend run — where to stream, how to cancel. */
33
+ export interface BackendRunContext {
34
+ runId: string;
35
+ /** Called for each activity fragment (streamed up the mesh SSE feed). */
36
+ onFrame: (frame: BackendFrame) => void;
37
+ /** Abort the underlying process. */
38
+ signal?: AbortSignal;
39
+ /** Working directory for the spawned CLI (defaults to process.cwd()). */
40
+ cwd?: string;
41
+ /** Persistent device role, prepended to the prompt so the agent is in-character. */
42
+ rolePreamble?: string;
43
+ }
44
+ /** Terminal result of a backend run. */
45
+ export interface BackendRunResult {
46
+ status: 'completed' | 'failed';
47
+ /** The CLI's native session id — enables resume + later messaging. */
48
+ sessionId?: string;
49
+ /** Final assistant text, if captured. */
50
+ content?: string;
51
+ error?: string;
52
+ }
53
+ /** Drives one executor (a CLI or the native kernel). */
54
+ export interface AgentBackend {
55
+ id: BackendId;
56
+ displayName: string;
57
+ /** True when this backend can run here (e.g. the CLI is on PATH). Sync + cheap. */
58
+ available(): boolean;
59
+ /** Run a task to completion, streaming frames along the way. */
60
+ run(task: BackendTask, ctx: BackendRunContext): Promise<BackendRunResult>;
61
+ /** Send a follow-up message into an existing session (mailbox — slice 2). */
62
+ send?(sessionId: string, message: string, ctx: BackendRunContext): Promise<BackendRunResult>;
63
+ /** Best-effort stop of a running session. */
64
+ stop?(sessionId: string): Promise<void>;
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * OpenKernel — Agents public API
3
+ *
4
+ * How a task actually gets run on a node: the native kernel, or a third-party
5
+ * coding CLI acting as the executor (so a mesh-dispatched task can be fulfilled
6
+ * by Claude Code, Codex, or OpenCode).
7
+ */
8
+ export * from './backends/index.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * OpenKernel — Agents public API
3
+ *
4
+ * How a task actually gets run on a node: the native kernel, or a third-party
5
+ * coding CLI acting as the executor (so a mesh-dispatched task can be fulfilled
6
+ * by Claude Code, Codex, or OpenCode).
7
+ */
8
+ export * from './backends/index.js';