infinicode 2.3.5 → 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.
- package/.opencode/plugins/home-logo-animation.tsx +5 -1
- package/.opencode/plugins/mesh-commands.tsx +4 -3
- package/.opencode/plugins/routing-mode-display.tsx +6 -2
- package/.opencode/tui.json +2 -1
- package/dist/cli.js +25 -1
- package/dist/commands/mesh-install.d.ts +10 -0
- package/dist/commands/mesh-install.js +160 -0
- package/dist/commands/run.js +82 -22
- package/dist/kernel/agents/backends/cli-backend.d.ts +37 -0
- package/dist/kernel/agents/backends/cli-backend.js +132 -0
- package/dist/kernel/agents/backends/detect.d.ts +5 -0
- package/dist/kernel/agents/backends/detect.js +40 -0
- package/dist/kernel/agents/backends/index.d.ts +11 -0
- package/dist/kernel/agents/backends/index.js +3 -0
- package/dist/kernel/agents/backends/parse-utils.d.ts +20 -0
- package/dist/kernel/agents/backends/parse-utils.js +72 -0
- package/dist/kernel/agents/backends/registry.d.ts +30 -0
- package/dist/kernel/agents/backends/registry.js +140 -0
- package/dist/kernel/agents/backends/types.d.ts +65 -0
- package/dist/kernel/agents/backends/types.js +1 -0
- package/dist/kernel/agents/index.d.ts +8 -0
- package/dist/kernel/agents/index.js +8 -0
- package/dist/kernel/federation/federation.d.ts +54 -1
- package/dist/kernel/federation/federation.js +180 -8
- package/dist/kernel/federation/types.d.ts +26 -1
- package/dist/kernel/free-providers.js +83 -0
- package/dist/kernel/gateway/openai-gateway.d.ts +55 -3
- package/dist/kernel/gateway/openai-gateway.js +428 -49
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/index.js +3 -1
- package/dist/kernel/logger.d.ts +16 -3
- package/dist/kernel/logger.js +38 -0
- package/dist/kernel/mcp/mcp-server.js +47 -8
- package/dist/kernel/orchestrator.d.ts +4 -0
- package/dist/kernel/orchestrator.js +34 -0
- package/dist/kernel/provider-manager.d.ts +13 -0
- package/dist/kernel/provider-manager.js +58 -5
- package/dist/kernel/providers/openai-compatible-provider.d.ts +6 -0
- package/dist/kernel/providers/openai-compatible-provider.js +22 -3
- package/dist/kernel/recovery-manager.js +55 -15
- package/dist/kernel/router.js +15 -2
- package/dist/kernel/types.d.ts +12 -2
- package/package.json +1 -1
|
@@ -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,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';
|
|
@@ -16,7 +16,8 @@ import { ConfigSync } from './config-sync.js';
|
|
|
16
16
|
import { AutoUpdate } from './auto-update.js';
|
|
17
17
|
import { ComputeRouter, type ComputeNeed } from './compute-router.js';
|
|
18
18
|
import { type ManifestInputs } from './node-identity.js';
|
|
19
|
-
import { type RoleContext, type RunRecord } from './types.js';
|
|
19
|
+
import { type AgentHandle, type RoleContext, type RunRecord } from './types.js';
|
|
20
|
+
import { AgentBackendRegistry, type BackendId } from '../agents/index.js';
|
|
20
21
|
export interface DispatchTask {
|
|
21
22
|
description: string;
|
|
22
23
|
capabilities: Capability[];
|
|
@@ -26,6 +27,13 @@ export interface DispatchTask {
|
|
|
26
27
|
/** When true, the receiving node auto-plans the goal into phases (a phased
|
|
27
28
|
* build) instead of running it as one atomic task. */
|
|
28
29
|
autoPlan?: boolean;
|
|
30
|
+
/** Which executor should run this on the receiving node: 'kernel' (native,
|
|
31
|
+
* default) or an installed CLI backend ('claude' | 'codex' | 'opencode'). */
|
|
32
|
+
agent?: BackendId;
|
|
33
|
+
/** Resume a specific CLI session on the receiving node instead of a fresh run. */
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
/** Model hint passed to a CLI backend (provider/model or a CLI-native id). */
|
|
36
|
+
model?: string;
|
|
29
37
|
}
|
|
30
38
|
export interface FederationOptions {
|
|
31
39
|
version: string;
|
|
@@ -50,6 +58,9 @@ export interface FederationDeps {
|
|
|
50
58
|
describe: () => ManifestInputs;
|
|
51
59
|
logger: Logger;
|
|
52
60
|
options: FederationOptions;
|
|
61
|
+
/** Executors this node can run dispatched tasks on. Defaults to the native
|
|
62
|
+
* kernel plus any installed coding CLIs (Claude Code / Codex / OpenCode). */
|
|
63
|
+
backends?: AgentBackendRegistry;
|
|
53
64
|
}
|
|
54
65
|
export declare class Federation {
|
|
55
66
|
private deps;
|
|
@@ -70,6 +81,8 @@ export declare class Federation {
|
|
|
70
81
|
readonly configSync: ConfigSync;
|
|
71
82
|
readonly autoUpdate: AutoUpdate;
|
|
72
83
|
readonly compute: ComputeRouter;
|
|
84
|
+
/** Executors available on this node (native kernel + installed CLIs). */
|
|
85
|
+
readonly backends: AgentBackendRegistry;
|
|
73
86
|
/** Local subscribers to the merged mesh frame stream (for MCP stream_activity). */
|
|
74
87
|
private frameSubs;
|
|
75
88
|
/** Recent frames for catch-up. */
|
|
@@ -90,6 +103,13 @@ export declare class Federation {
|
|
|
90
103
|
private runTask;
|
|
91
104
|
/** Run a dispatched task in the background, updating its RunRecord + streaming. */
|
|
92
105
|
private runTaskAsync;
|
|
106
|
+
/**
|
|
107
|
+
* Execute a dispatched task with a third-party CLI backend (Claude Code /
|
|
108
|
+
* Codex / OpenCode) instead of the native kernel. The backend's activity
|
|
109
|
+
* streams up the same mesh SSE feed (log/ev frames), and its native session id
|
|
110
|
+
* is captured on the RunRecord so the run can be followed and later messaged.
|
|
111
|
+
*/
|
|
112
|
+
private runViaBackend;
|
|
93
113
|
/**
|
|
94
114
|
* Hand a task to the best-fit connected peer (compute-routed) or a named one.
|
|
95
115
|
* Non-blocking by default — returns a runId to follow. Pass wait to block
|
|
@@ -122,6 +142,30 @@ export declare class Federation {
|
|
|
122
142
|
awaitRun(runId: string, timeoutMs?: number): Promise<RunRecord>;
|
|
123
143
|
/** Run a slash-command on a specific peer. */
|
|
124
144
|
runCommandOn(nodeId: string, input: string): Promise<unknown>;
|
|
145
|
+
/**
|
|
146
|
+
* CLI agent sessions on THIS node — any run that used a CLI backend and
|
|
147
|
+
* captured a session id. Live while running, dormant (still resumable) once
|
|
148
|
+
* complete. Deduped by session, newest kept, so a session that's been messaged
|
|
149
|
+
* several times shows once.
|
|
150
|
+
*/
|
|
151
|
+
localAgents(): AgentHandle[];
|
|
152
|
+
/** All messageable CLI agent sessions across the mesh (local + peers). */
|
|
153
|
+
agents(): Promise<AgentHandle[]>;
|
|
154
|
+
/** Resolve an address (`nodeId/sessionId`, a sessionId, or a runId) to a handle. */
|
|
155
|
+
private resolveAgent;
|
|
156
|
+
/**
|
|
157
|
+
* Message a CLI agent session — wherever it lives on the mesh. Resumes that
|
|
158
|
+
* exact session with the text (so the agent keeps its context) and returns a
|
|
159
|
+
* runId to follow the reply. This is how one CLI agent talks to another.
|
|
160
|
+
*/
|
|
161
|
+
sendTo(address: string, text: string, opts?: {
|
|
162
|
+
wait?: boolean;
|
|
163
|
+
timeoutMs?: number;
|
|
164
|
+
}): Promise<{
|
|
165
|
+
runId: string;
|
|
166
|
+
nodeId: string;
|
|
167
|
+
record?: RunRecord;
|
|
168
|
+
} | null>;
|
|
125
169
|
/** Hub: publish shared config (providers/API keys/policy) to the fleet. */
|
|
126
170
|
publishConfig(cfg: Omit<SharedConfig, 'revision' | 'updatedAt'>): SharedConfig;
|
|
127
171
|
/** Satellite: pull shared config from a peer (usually the hub). */
|
|
@@ -147,6 +191,15 @@ export declare class Federation {
|
|
|
147
191
|
* new nodes that come online later are picked up too. Idempotent.
|
|
148
192
|
*/
|
|
149
193
|
startLanDiscovery(tag?: string, discoveryPort?: number): void;
|
|
194
|
+
/** Backend ids this node can execute tasks with ('kernel' + installed CLIs). */
|
|
195
|
+
availableBackends(): BackendId[];
|
|
196
|
+
/**
|
|
197
|
+
* The manifest inputs augmented with `backend:<id>` capability tags for each
|
|
198
|
+
* installed executor. This lets the compute-router match a task that asks for
|
|
199
|
+
* a specific agent (e.g. capabilities:['backend:claude']) to a node that
|
|
200
|
+
* actually has that CLI — without changing the caller's describe().
|
|
201
|
+
*/
|
|
202
|
+
private describeWithBackends;
|
|
150
203
|
/** Neutral node-status list (self + peers) — feeds the mesh map. */
|
|
151
204
|
nodeStatuses(): NodeStatus[];
|
|
152
205
|
peers(): PeerInfo[];
|