infinicode 1.0.0 → 2.0.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/.opencode/plugins/home-logo-animation.tsx +120 -0
- package/.opencode/plugins/routing-mode-display.tsx +135 -0
- package/.opencode/themes/infinibot-gold.json +222 -0
- package/.opencode/tui.json +8 -0
- package/README.md +527 -75
- package/dist/ascii-video-animation.d.ts +2 -0
- package/dist/ascii-video-animation.js +243 -0
- package/dist/cli.js +195 -50
- package/dist/commands/console.d.ts +6 -0
- package/dist/commands/console.js +111 -0
- package/dist/commands/kernel-setup.d.ts +3 -0
- package/dist/commands/kernel-setup.js +303 -0
- package/dist/commands/mcp.d.ts +12 -0
- package/dist/commands/mcp.js +96 -0
- package/dist/commands/mission.d.ts +16 -0
- package/dist/commands/mission.js +301 -0
- package/dist/commands/models.d.ts +3 -1
- package/dist/commands/models.js +111 -55
- package/dist/commands/providers.d.ts +6 -0
- package/dist/commands/providers.js +95 -0
- package/dist/commands/run.d.ts +2 -1
- package/dist/commands/run.js +349 -59
- package/dist/commands/serve.d.ts +18 -0
- package/dist/commands/serve.js +127 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +77 -44
- package/dist/commands/status.d.ts +2 -1
- package/dist/commands/status.js +46 -30
- package/dist/commands/workers.d.ts +5 -0
- package/dist/commands/workers.js +103 -0
- package/dist/kernel/autonomy/goal-loop.d.ts +49 -0
- package/dist/kernel/autonomy/goal-loop.js +113 -0
- package/dist/kernel/autonomy/index.d.ts +8 -0
- package/dist/kernel/autonomy/index.js +7 -0
- package/dist/kernel/browser/browser-controller.d.ts +57 -0
- package/dist/kernel/browser/browser-controller.js +175 -0
- package/dist/kernel/checkpoint-engine.d.ts +17 -0
- package/dist/kernel/checkpoint-engine.js +128 -0
- package/dist/kernel/command-system.d.ts +37 -0
- package/dist/kernel/command-system.js +239 -0
- package/dist/kernel/config-schema.d.ts +53 -0
- package/dist/kernel/config-schema.js +36 -0
- package/dist/kernel/event-bus.d.ts +19 -0
- package/dist/kernel/event-bus.js +65 -0
- package/dist/kernel/federation/auto-update.d.ts +36 -0
- package/dist/kernel/federation/auto-update.js +57 -0
- package/dist/kernel/federation/compute-router.d.ts +27 -0
- package/dist/kernel/federation/compute-router.js +44 -0
- package/dist/kernel/federation/config-sync.d.ts +33 -0
- package/dist/kernel/federation/config-sync.js +37 -0
- package/dist/kernel/federation/discovery.d.ts +11 -0
- package/dist/kernel/federation/discovery.js +44 -0
- package/dist/kernel/federation/event-bridge.d.ts +30 -0
- package/dist/kernel/federation/event-bridge.js +61 -0
- package/dist/kernel/federation/federation-plugin.d.ts +24 -0
- package/dist/kernel/federation/federation-plugin.js +35 -0
- package/dist/kernel/federation/federation.d.ts +126 -0
- package/dist/kernel/federation/federation.js +335 -0
- package/dist/kernel/federation/index.d.ts +31 -0
- package/dist/kernel/federation/index.js +23 -0
- package/dist/kernel/federation/moltfed-adapter.d.ts +30 -0
- package/dist/kernel/federation/moltfed-adapter.js +52 -0
- package/dist/kernel/federation/moltfed-connector.d.ts +98 -0
- package/dist/kernel/federation/moltfed-connector.js +193 -0
- package/dist/kernel/federation/node-identity.d.ts +19 -0
- package/dist/kernel/federation/node-identity.js +86 -0
- package/dist/kernel/federation/peer-mesh.d.ts +46 -0
- package/dist/kernel/federation/peer-mesh.js +117 -0
- package/dist/kernel/federation/protocol.d.ts +20 -0
- package/dist/kernel/federation/protocol.js +61 -0
- package/dist/kernel/federation/role-context.d.ts +5 -0
- package/dist/kernel/federation/role-context.js +45 -0
- package/dist/kernel/federation/telemetry.d.ts +21 -0
- package/dist/kernel/federation/telemetry.js +138 -0
- package/dist/kernel/federation/transport-http.d.ts +44 -0
- package/dist/kernel/federation/transport-http.js +207 -0
- package/dist/kernel/federation/types.d.ts +212 -0
- package/dist/kernel/federation/types.js +3 -0
- package/dist/kernel/free-providers.d.ts +28 -0
- package/dist/kernel/free-providers.js +162 -0
- package/dist/kernel/frontend-scoring.d.ts +21 -0
- package/dist/kernel/frontend-scoring.js +125 -0
- package/dist/kernel/index.d.ts +63 -0
- package/dist/kernel/index.js +45 -0
- package/dist/kernel/kernel.d.ts +75 -0
- package/dist/kernel/kernel.js +223 -0
- package/dist/kernel/logger.d.ts +18 -0
- package/dist/kernel/logger.js +26 -0
- package/dist/kernel/mcp/index.d.ts +9 -0
- package/dist/kernel/mcp/index.js +8 -0
- package/dist/kernel/mcp/mcp-server.d.ts +27 -0
- package/dist/kernel/mcp/mcp-server.js +178 -0
- package/dist/kernel/mission-engine.d.ts +42 -0
- package/dist/kernel/mission-engine.js +160 -0
- package/dist/kernel/orchestrator.d.ts +51 -0
- package/dist/kernel/orchestrator.js +226 -0
- package/dist/kernel/plugin-manager.d.ts +28 -0
- package/dist/kernel/plugin-manager.js +76 -0
- package/dist/kernel/plugins/browser-plugin.d.ts +22 -0
- package/dist/kernel/plugins/browser-plugin.js +34 -0
- package/dist/kernel/plugins/dashboard-plugin.d.ts +17 -0
- package/dist/kernel/plugins/dashboard-plugin.js +72 -0
- package/dist/kernel/plugins/discord-plugin.d.ts +11 -0
- package/dist/kernel/plugins/discord-plugin.js +35 -0
- package/dist/kernel/plugins/metrics-plugin.d.ts +33 -0
- package/dist/kernel/plugins/metrics-plugin.js +86 -0
- package/dist/kernel/plugins/search-plugin.d.ts +17 -0
- package/dist/kernel/plugins/search-plugin.js +20 -0
- package/dist/kernel/plugins/slack-plugin.d.ts +11 -0
- package/dist/kernel/plugins/slack-plugin.js +35 -0
- package/dist/kernel/plugins/telegram-plugin.d.ts +20 -0
- package/dist/kernel/plugins/telegram-plugin.js +122 -0
- package/dist/kernel/policies.d.ts +33 -0
- package/dist/kernel/policies.js +105 -0
- package/dist/kernel/provider-discovery.d.ts +41 -0
- package/dist/kernel/provider-discovery.js +179 -0
- package/dist/kernel/provider-manager.d.ts +38 -0
- package/dist/kernel/provider-manager.js +206 -0
- package/dist/kernel/provider-url.d.ts +18 -0
- package/dist/kernel/provider-url.js +45 -0
- package/dist/kernel/providers/gemini-provider.d.ts +43 -0
- package/dist/kernel/providers/gemini-provider.js +203 -0
- package/dist/kernel/providers/ollama-provider.d.ts +44 -0
- package/dist/kernel/providers/ollama-provider.js +241 -0
- package/dist/kernel/providers/openai-compatible-provider.d.ts +43 -0
- package/dist/kernel/providers/openai-compatible-provider.js +233 -0
- package/dist/kernel/recovery-manager.d.ts +38 -0
- package/dist/kernel/recovery-manager.js +156 -0
- package/dist/kernel/router.d.ts +44 -0
- package/dist/kernel/router.js +222 -0
- package/dist/kernel/sample-missions.d.ts +11 -0
- package/dist/kernel/sample-missions.js +132 -0
- package/dist/kernel/scheduler.d.ts +30 -0
- package/dist/kernel/scheduler.js +147 -0
- package/dist/kernel/sdk/harness-sdk.d.ts +37 -0
- package/dist/kernel/sdk/harness-sdk.js +48 -0
- package/dist/kernel/sdk/plugin-sdk.d.ts +27 -0
- package/dist/kernel/sdk/plugin-sdk.js +40 -0
- package/dist/kernel/search/search-controller.d.ts +32 -0
- package/dist/kernel/search/search-controller.js +141 -0
- package/dist/kernel/setup.d.ts +13 -0
- package/dist/kernel/setup.js +59 -0
- package/dist/kernel/types.d.ts +479 -0
- package/dist/kernel/types.js +7 -0
- package/dist/kernel/verification-engine.d.ts +33 -0
- package/dist/kernel/verification-engine.js +287 -0
- package/dist/kernel/worker-runtime.d.ts +66 -0
- package/dist/kernel/worker-runtime.js +261 -0
- package/dist/kernel/workers/browser-worker.d.ts +6 -0
- package/dist/kernel/workers/browser-worker.js +92 -0
- package/dist/kernel/workers/builtin-workers.d.ts +20 -0
- package/dist/kernel/workers/builtin-workers.js +120 -0
- package/dist/kernel/workers/research-worker.d.ts +5 -0
- package/dist/kernel/workers/research-worker.js +90 -0
- package/dist/prompt-ascii-video-animation.d.ts +2 -0
- package/dist/prompt-ascii-video-animation.js +243 -0
- package/package.json +43 -9
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { MeshServer, MeshClient } from './transport-http.js';
|
|
2
|
+
import { PeerMesh } from './peer-mesh.js';
|
|
3
|
+
import { EventBridge } from './event-bridge.js';
|
|
4
|
+
import { ConfigSync } from './config-sync.js';
|
|
5
|
+
import { AutoUpdate } from './auto-update.js';
|
|
6
|
+
import { ComputeRouter } from './compute-router.js';
|
|
7
|
+
import { TelemetryCollector, loadFromSnapshot, selfNodeStatus, nodeStatusFromPeer } from './telemetry.js';
|
|
8
|
+
import { buildManifest } from './node-identity.js';
|
|
9
|
+
import { envelope, reply, frame, newId } from './protocol.js';
|
|
10
|
+
import { loadRole, saveRole, rolePreamble } from './role-context.js';
|
|
11
|
+
import { discoverTailscalePeers } from './discovery.js';
|
|
12
|
+
import { DEFAULT_MESH_PORT } from './types.js';
|
|
13
|
+
export class Federation {
|
|
14
|
+
deps;
|
|
15
|
+
server;
|
|
16
|
+
client;
|
|
17
|
+
mesh;
|
|
18
|
+
bridge;
|
|
19
|
+
telemetry = new TelemetryCollector();
|
|
20
|
+
telemetryTimer;
|
|
21
|
+
lastHw;
|
|
22
|
+
unsub;
|
|
23
|
+
configSync;
|
|
24
|
+
autoUpdate;
|
|
25
|
+
compute = new ComputeRouter();
|
|
26
|
+
/** Local subscribers to the merged mesh frame stream (for MCP stream_activity). */
|
|
27
|
+
frameSubs = new Set();
|
|
28
|
+
/** Recent frames for catch-up. */
|
|
29
|
+
frameHistory = [];
|
|
30
|
+
/** Runs received + executing on THIS node (async handoff). */
|
|
31
|
+
runs = new Map();
|
|
32
|
+
/** Runs WE dispatched → which peer holds them (for follow_task). */
|
|
33
|
+
dispatchedRuns = new Map();
|
|
34
|
+
constructor(deps) {
|
|
35
|
+
this.deps = deps;
|
|
36
|
+
this.client = new MeshClient({ token: deps.options.token });
|
|
37
|
+
this.configSync = new ConfigSync({ logger: deps.logger, apply: deps.options.applyConfig });
|
|
38
|
+
this.autoUpdate = new AutoUpdate({
|
|
39
|
+
version: deps.options.version,
|
|
40
|
+
logger: deps.logger,
|
|
41
|
+
enabled: deps.options.autoUpdate,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
get identity() {
|
|
45
|
+
return this.deps.identity;
|
|
46
|
+
}
|
|
47
|
+
async start() {
|
|
48
|
+
const { logger, options, identity } = this.deps;
|
|
49
|
+
const port = options.port ?? DEFAULT_MESH_PORT;
|
|
50
|
+
this.server = new MeshServer({
|
|
51
|
+
port,
|
|
52
|
+
host: options.host,
|
|
53
|
+
token: options.token,
|
|
54
|
+
logger,
|
|
55
|
+
getManifest: () => buildManifest(identity, this.deps.describe()),
|
|
56
|
+
onRpc: (env, remote) => this.onRpc(env, remote),
|
|
57
|
+
});
|
|
58
|
+
await this.server.start();
|
|
59
|
+
this.mesh = new PeerMesh({
|
|
60
|
+
self: identity,
|
|
61
|
+
client: this.client,
|
|
62
|
+
logger,
|
|
63
|
+
heartbeatMs: options.heartbeatMs,
|
|
64
|
+
getLoad: () => loadFromSnapshot(this.lastHw),
|
|
65
|
+
onFrame: (peerId, f) => this.ingestFrame(peerId, f),
|
|
66
|
+
onPeerChange: () => this.emitLocal('nd', frame('nd', identity.nodeId, this.nodeStatuses())),
|
|
67
|
+
});
|
|
68
|
+
this.mesh.start();
|
|
69
|
+
// Bridge local kernel events → mesh stream (coalesced, animation-friendly).
|
|
70
|
+
this.bridge = new EventBridge({
|
|
71
|
+
nodeId: identity.nodeId,
|
|
72
|
+
emit: f => this.server?.broadcast(f),
|
|
73
|
+
});
|
|
74
|
+
this.unsub = this.deps.kernel.subscribeAll(e => this.bridge?.handle(e));
|
|
75
|
+
// Telemetry heartbeat → stream a HardwareSnapshot on a fixed cadence.
|
|
76
|
+
const telMs = options.telemetryMs ?? 1000;
|
|
77
|
+
this.telemetryTimer = setInterval(() => {
|
|
78
|
+
this.lastHw = this.telemetry.collect();
|
|
79
|
+
this.server?.broadcast(frame('hw', identity.nodeId, this.lastHw));
|
|
80
|
+
}, telMs);
|
|
81
|
+
// Connect to seed peers.
|
|
82
|
+
for (const url of options.seeds ?? [])
|
|
83
|
+
void this.mesh.connect(url);
|
|
84
|
+
logger.info(`[federation] node ${identity.displayName} (${identity.role}) online on :${port}`);
|
|
85
|
+
}
|
|
86
|
+
// ── Inbound RPC ────────────────────────────────────────────────────────────
|
|
87
|
+
async onRpc(env, _remote) {
|
|
88
|
+
const self = this.deps.identity.nodeId;
|
|
89
|
+
switch (env.kind) {
|
|
90
|
+
case 'heartbeat':
|
|
91
|
+
return reply(env, 'ack', self, { load: loadFromSnapshot(this.lastHw) });
|
|
92
|
+
case 'hello':
|
|
93
|
+
return reply(env, 'welcome', self, buildManifest(this.deps.identity, this.deps.describe()));
|
|
94
|
+
case 'task.dispatch': {
|
|
95
|
+
// Non-blocking handoff: register the run, ack with a runId immediately,
|
|
96
|
+
// execute in the background, and let the caller follow it.
|
|
97
|
+
const task = env.data;
|
|
98
|
+
const runId = newId();
|
|
99
|
+
this.runs.set(runId, { runId, status: 'running', description: task.description, startedAt: Date.now() });
|
|
100
|
+
void this.runTaskAsync(runId, task);
|
|
101
|
+
return reply(env, 'task.accepted', self, { runId });
|
|
102
|
+
}
|
|
103
|
+
case 'task.status': {
|
|
104
|
+
const runId = env.data.runId;
|
|
105
|
+
const rec = this.runs.get(runId);
|
|
106
|
+
return reply(env, 'task.result', self, rec ?? { runId, status: 'failed', description: '', error: 'unknown run', startedAt: 0 });
|
|
107
|
+
}
|
|
108
|
+
case 'command': {
|
|
109
|
+
const input = env.data?.input ?? '';
|
|
110
|
+
const result = await this.deps.kernel.command(input);
|
|
111
|
+
return reply(env, 'command.result', self, result);
|
|
112
|
+
}
|
|
113
|
+
case 'config.request':
|
|
114
|
+
return reply(env, 'config.offer', self, this.configSync.offer());
|
|
115
|
+
case 'config.offer':
|
|
116
|
+
await this.configSync.receive(env.data);
|
|
117
|
+
return reply(env, 'ack', self);
|
|
118
|
+
case 'update.announce': {
|
|
119
|
+
const peer = this.mesh?.get(env.from);
|
|
120
|
+
await this.autoUpdate.onAnnounce(env.data, peer?.role ?? 'peer');
|
|
121
|
+
return reply(env, 'ack', self);
|
|
122
|
+
}
|
|
123
|
+
case 'role.get':
|
|
124
|
+
return reply(env, 'ack', self, this.getRole());
|
|
125
|
+
case 'role.set': {
|
|
126
|
+
const r = env.data;
|
|
127
|
+
const saved = saveRole(r);
|
|
128
|
+
return reply(env, 'ack', self, saved);
|
|
129
|
+
}
|
|
130
|
+
default:
|
|
131
|
+
return reply(env, 'ack', self);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ── Role context ───────────────────────────────────────────────────────────
|
|
135
|
+
getRole() {
|
|
136
|
+
return loadRole();
|
|
137
|
+
}
|
|
138
|
+
setRole(role) {
|
|
139
|
+
return saveRole(role);
|
|
140
|
+
}
|
|
141
|
+
async getRoleOf(nodeId) {
|
|
142
|
+
const peer = this.mesh?.get(nodeId);
|
|
143
|
+
if (!peer)
|
|
144
|
+
throw new Error(`unknown peer: ${nodeId}`);
|
|
145
|
+
const res = await this.client.rpc(peer.url, envelope('role.get', this.deps.identity.nodeId, {}, { to: nodeId }));
|
|
146
|
+
return res.data ?? null;
|
|
147
|
+
}
|
|
148
|
+
async setRoleOf(nodeId, role) {
|
|
149
|
+
const peer = this.mesh?.get(nodeId);
|
|
150
|
+
if (!peer)
|
|
151
|
+
throw new Error(`unknown peer: ${nodeId}`);
|
|
152
|
+
const res = await this.client.rpc(peer.url, envelope('role.set', this.deps.identity.nodeId, role, { to: nodeId }));
|
|
153
|
+
return res.data;
|
|
154
|
+
}
|
|
155
|
+
/** Execute a dispatched task on THIS node via the local kernel. */
|
|
156
|
+
async runTask(task) {
|
|
157
|
+
// Inject this device's persistent role so the spawned agent is context-aware.
|
|
158
|
+
const localRole = loadRole();
|
|
159
|
+
const preamble = rolePreamble(localRole);
|
|
160
|
+
const prompt = preamble ? `${preamble}\n\n${task.prompt}` : task.prompt;
|
|
161
|
+
const mission = await this.deps.kernel.execute({
|
|
162
|
+
description: task.description,
|
|
163
|
+
goal: task.description,
|
|
164
|
+
tasks: [{
|
|
165
|
+
description: task.description,
|
|
166
|
+
capabilities: task.capabilities.length ? task.capabilities : ['reasoning'],
|
|
167
|
+
input: { prompt, context: { role: task.role ?? localRole?.role, architecture: localRole?.architecture, ...task.context } },
|
|
168
|
+
}],
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
missionId: mission.id,
|
|
172
|
+
status: mission.status,
|
|
173
|
+
outputs: mission.tasks.map(t => ({ status: t.status, content: t.output?.content, error: t.error })),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** Run a dispatched task in the background, updating its RunRecord + streaming. */
|
|
177
|
+
async runTaskAsync(runId, task) {
|
|
178
|
+
const rec = this.runs.get(runId);
|
|
179
|
+
if (!rec)
|
|
180
|
+
return;
|
|
181
|
+
const nodeId = this.deps.identity.nodeId;
|
|
182
|
+
this.server?.broadcast(frame('ev', nodeId, { k: 'RUN_STARTED', run: runId, desc: task.description }));
|
|
183
|
+
try {
|
|
184
|
+
const result = await this.runTask(task);
|
|
185
|
+
rec.status = result.status === 'COMPLETED' ? 'completed' : 'failed';
|
|
186
|
+
rec.missionId = result.missionId;
|
|
187
|
+
rec.outputs = result.outputs;
|
|
188
|
+
rec.completedAt = Date.now();
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
rec.status = 'failed';
|
|
192
|
+
rec.error = err instanceof Error ? err.message : String(err);
|
|
193
|
+
rec.completedAt = Date.now();
|
|
194
|
+
}
|
|
195
|
+
this.server?.broadcast(frame('ev', nodeId, { k: 'RUN_COMPLETED', run: runId, status: rec.status }));
|
|
196
|
+
}
|
|
197
|
+
// ── Outbound operations ──────────────────────────────────────────────────
|
|
198
|
+
/**
|
|
199
|
+
* Hand a task to the best-fit connected peer (compute-routed) or a named one.
|
|
200
|
+
* Non-blocking by default — returns a runId to follow. Pass wait to block
|
|
201
|
+
* until the run finishes (short tasks).
|
|
202
|
+
*/
|
|
203
|
+
async dispatch(task, need, opts) {
|
|
204
|
+
if (!this.mesh)
|
|
205
|
+
return null;
|
|
206
|
+
const peers = this.mesh.connected();
|
|
207
|
+
const target = this.compute.best(peers, {
|
|
208
|
+
capabilities: task.capabilities,
|
|
209
|
+
preferNodeId: need?.preferNodeId,
|
|
210
|
+
preferName: need?.preferName,
|
|
211
|
+
});
|
|
212
|
+
if (!target) {
|
|
213
|
+
this.deps.logger.warn('[federation] no capable peer for dispatch');
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const res = await this.client.rpc(target.url, envelope('task.dispatch', this.deps.identity.nodeId, task, { to: target.nodeId }));
|
|
217
|
+
const runId = res.data.runId;
|
|
218
|
+
this.dispatchedRuns.set(runId, target.nodeId);
|
|
219
|
+
let record = { runId, status: 'running', description: task.description, startedAt: Date.now() };
|
|
220
|
+
if (opts?.wait) {
|
|
221
|
+
record = await this.awaitRun(runId, opts.timeoutMs ?? 120_000);
|
|
222
|
+
}
|
|
223
|
+
return { peerId: target.nodeId, runId, record };
|
|
224
|
+
}
|
|
225
|
+
/** Query a dispatched run's current record (from whichever peer holds it). */
|
|
226
|
+
async followRun(runId) {
|
|
227
|
+
const local = this.runs.get(runId);
|
|
228
|
+
if (local)
|
|
229
|
+
return { record: local, frames: this.recentFrames(50) };
|
|
230
|
+
const peerId = this.dispatchedRuns.get(runId);
|
|
231
|
+
const peer = peerId ? this.mesh?.get(peerId) : undefined;
|
|
232
|
+
if (!peer)
|
|
233
|
+
return null;
|
|
234
|
+
const res = await this.client.rpc(peer.url, envelope('task.status', this.deps.identity.nodeId, { runId }, { to: peer.nodeId }));
|
|
235
|
+
return { record: res.data, frames: this.recentFrames(50) };
|
|
236
|
+
}
|
|
237
|
+
/** Poll a run until it finishes or the timeout elapses. */
|
|
238
|
+
async awaitRun(runId, timeoutMs = 120_000) {
|
|
239
|
+
const start = Date.now();
|
|
240
|
+
for (;;) {
|
|
241
|
+
const followed = await this.followRun(runId);
|
|
242
|
+
const rec = followed?.record;
|
|
243
|
+
if (rec && rec.status !== 'running')
|
|
244
|
+
return rec;
|
|
245
|
+
if (Date.now() - start > timeoutMs) {
|
|
246
|
+
return rec ?? { runId, status: 'failed', description: '', error: 'follow timeout', startedAt: start };
|
|
247
|
+
}
|
|
248
|
+
await new Promise(r => setTimeout(r, 500));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Run a slash-command on a specific peer. */
|
|
252
|
+
async runCommandOn(nodeId, input) {
|
|
253
|
+
const peer = this.mesh?.get(nodeId);
|
|
254
|
+
if (!peer)
|
|
255
|
+
throw new Error(`unknown peer: ${nodeId}`);
|
|
256
|
+
const res = await this.client.rpc(peer.url, envelope('command', this.deps.identity.nodeId, { input }, { to: nodeId }));
|
|
257
|
+
return res.data;
|
|
258
|
+
}
|
|
259
|
+
/** Hub: publish shared config (providers/API keys/policy) to the fleet. */
|
|
260
|
+
publishConfig(cfg) {
|
|
261
|
+
return this.configSync.publish(cfg);
|
|
262
|
+
}
|
|
263
|
+
/** Satellite: pull shared config from a peer (usually the hub). */
|
|
264
|
+
async pullConfig(nodeId) {
|
|
265
|
+
const peer = this.mesh?.get(nodeId);
|
|
266
|
+
if (!peer)
|
|
267
|
+
return false;
|
|
268
|
+
const res = await this.client.rpc(peer.url, envelope('config.request', this.deps.identity.nodeId, {}, { to: nodeId }));
|
|
269
|
+
if (res.kind === 'config.offer')
|
|
270
|
+
return this.configSync.receive(res.data);
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
async connect(url) {
|
|
274
|
+
return this.mesh?.connect(url) ?? null;
|
|
275
|
+
}
|
|
276
|
+
/** Auto-discover peers over Tailscale and connect to each (skips self/known). */
|
|
277
|
+
async discoverAndConnect(tag) {
|
|
278
|
+
const port = this.deps.options.port ?? DEFAULT_MESH_PORT;
|
|
279
|
+
const urls = await discoverTailscalePeers({ port, tag, logger: this.deps.logger });
|
|
280
|
+
let connected = 0;
|
|
281
|
+
for (const url of urls) {
|
|
282
|
+
const peer = await this.mesh?.connect(url);
|
|
283
|
+
if (peer && peer.nodeId !== this.deps.identity.nodeId)
|
|
284
|
+
connected++;
|
|
285
|
+
}
|
|
286
|
+
return connected;
|
|
287
|
+
}
|
|
288
|
+
// ── Views ────────────────────────────────────────────────────────────────
|
|
289
|
+
/** Neutral node-status list (self + peers) — feeds the mesh map. */
|
|
290
|
+
nodeStatuses() {
|
|
291
|
+
const d = this.deps.describe();
|
|
292
|
+
const self = selfNodeStatus(this.deps.identity, this.lastHw ?? this.telemetry.collect(), {
|
|
293
|
+
version: d.version,
|
|
294
|
+
capabilities: d.capabilities,
|
|
295
|
+
commands: d.commands,
|
|
296
|
+
});
|
|
297
|
+
const peers = (this.mesh?.list() ?? []).map(nodeStatusFromPeer);
|
|
298
|
+
return [self, ...peers];
|
|
299
|
+
}
|
|
300
|
+
peers() {
|
|
301
|
+
return this.mesh?.list() ?? [];
|
|
302
|
+
}
|
|
303
|
+
/** Latest local hardware snapshot (for the MOLTFED connector / panels). */
|
|
304
|
+
latestHardware() {
|
|
305
|
+
return this.lastHw;
|
|
306
|
+
}
|
|
307
|
+
/** Subscribe to the merged mesh frame stream (local + peer frames). */
|
|
308
|
+
onFrames(sub) {
|
|
309
|
+
this.frameSubs.add(sub);
|
|
310
|
+
return () => this.frameSubs.delete(sub);
|
|
311
|
+
}
|
|
312
|
+
recentFrames(n = 100) {
|
|
313
|
+
return this.frameHistory.slice(-n);
|
|
314
|
+
}
|
|
315
|
+
ingestFrame(peerId, f) {
|
|
316
|
+
this.frameHistory.push({ peerId, f });
|
|
317
|
+
if (this.frameHistory.length > 500)
|
|
318
|
+
this.frameHistory.shift();
|
|
319
|
+
for (const s of this.frameSubs)
|
|
320
|
+
s(peerId, f);
|
|
321
|
+
}
|
|
322
|
+
emitLocal(_t, f) {
|
|
323
|
+
// local status changes are also streamed to connected harnesses
|
|
324
|
+
this.server?.broadcast(f);
|
|
325
|
+
this.ingestFrame(this.deps.identity.nodeId, f);
|
|
326
|
+
}
|
|
327
|
+
async stop() {
|
|
328
|
+
this.unsub?.();
|
|
329
|
+
this.bridge?.stop();
|
|
330
|
+
if (this.telemetryTimer)
|
|
331
|
+
clearInterval(this.telemetryTimer);
|
|
332
|
+
this.mesh?.stop();
|
|
333
|
+
await this.server?.stop();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — Federation public API
|
|
3
|
+
*
|
|
4
|
+
* Neutral, transport-agnostic device mesh: peer discovery + heartbeats, remote
|
|
5
|
+
* task dispatch, live activity/telemetry streaming, API/config sharing, auto
|
|
6
|
+
* update, and a MOLTFED adapter for zero-change InfiniBot interop.
|
|
7
|
+
*/
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export { Federation } from './federation.js';
|
|
10
|
+
export type { FederationOptions, FederationDeps, DispatchTask } from './federation.js';
|
|
11
|
+
export { createFederationPlugin } from './federation-plugin.js';
|
|
12
|
+
export type { FederationPlugin, FederationPluginOptions } from './federation-plugin.js';
|
|
13
|
+
export { loadOrCreateIdentity, buildManifest, totalMemoryBytes } from './node-identity.js';
|
|
14
|
+
export type { IdentityOptions, ManifestInputs } from './node-identity.js';
|
|
15
|
+
export { TelemetryCollector, loadFromSnapshot, nodeStatusFromPeer, selfNodeStatus } from './telemetry.js';
|
|
16
|
+
export { MeshServer, MeshClient } from './transport-http.js';
|
|
17
|
+
export type { MeshServerOptions, MeshClientOptions, RpcHandler } from './transport-http.js';
|
|
18
|
+
export { PeerMesh } from './peer-mesh.js';
|
|
19
|
+
export type { PeerMeshOptions } from './peer-mesh.js';
|
|
20
|
+
export { EventBridge } from './event-bridge.js';
|
|
21
|
+
export { ConfigSync } from './config-sync.js';
|
|
22
|
+
export { AutoUpdate, compareVersions } from './auto-update.js';
|
|
23
|
+
export { ComputeRouter } from './compute-router.js';
|
|
24
|
+
export type { ComputeCandidate, ComputeNeed } from './compute-router.js';
|
|
25
|
+
export { loadRole, saveRole, rolePreamble } from './role-context.js';
|
|
26
|
+
export { discoverTailscalePeers, tailscaleAvailable } from './discovery.js';
|
|
27
|
+
export type { DiscoverOptions } from './discovery.js';
|
|
28
|
+
export { envelope, reply, frame, frameToSSE, parseSSE, newId } from './protocol.js';
|
|
29
|
+
export * as moltfed from './moltfed-adapter.js';
|
|
30
|
+
export { MoltfedConnector } from './moltfed-connector.js';
|
|
31
|
+
export type { MoltfedConnectorOptions, WsLike, WsFactory, DeviceAuth } from './moltfed-connector.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — Federation public API
|
|
3
|
+
*
|
|
4
|
+
* Neutral, transport-agnostic device mesh: peer discovery + heartbeats, remote
|
|
5
|
+
* task dispatch, live activity/telemetry streaming, API/config sharing, auto
|
|
6
|
+
* update, and a MOLTFED adapter for zero-change InfiniBot interop.
|
|
7
|
+
*/
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export { Federation } from './federation.js';
|
|
10
|
+
export { createFederationPlugin } from './federation-plugin.js';
|
|
11
|
+
export { loadOrCreateIdentity, buildManifest, totalMemoryBytes } from './node-identity.js';
|
|
12
|
+
export { TelemetryCollector, loadFromSnapshot, nodeStatusFromPeer, selfNodeStatus } from './telemetry.js';
|
|
13
|
+
export { MeshServer, MeshClient } from './transport-http.js';
|
|
14
|
+
export { PeerMesh } from './peer-mesh.js';
|
|
15
|
+
export { EventBridge } from './event-bridge.js';
|
|
16
|
+
export { ConfigSync } from './config-sync.js';
|
|
17
|
+
export { AutoUpdate, compareVersions } from './auto-update.js';
|
|
18
|
+
export { ComputeRouter } from './compute-router.js';
|
|
19
|
+
export { loadRole, saveRole, rolePreamble } from './role-context.js';
|
|
20
|
+
export { discoverTailscalePeers, tailscaleAvailable } from './discovery.js';
|
|
21
|
+
export { envelope, reply, frame, frameToSSE, parseSSE, newId } from './protocol.js';
|
|
22
|
+
export * as moltfed from './moltfed-adapter.js';
|
|
23
|
+
export { MoltfedConnector } from './moltfed-connector.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — MOLTFED Adapter (InfiniBot interop)
|
|
3
|
+
*
|
|
4
|
+
* Translates infinicode's NEUTRAL federation shapes into the message shapes
|
|
5
|
+
* InfiniBot already consumes, so an infinicode node lights up InfiniBot's
|
|
6
|
+
* neural-mesh map + hardware panels with ZERO InfiniBot changes:
|
|
7
|
+
*
|
|
8
|
+
* NodeStatus[] → nodes.status { ts, nodes:[…] }
|
|
9
|
+
* HardwareSnapshot → hardware.snapshot (same shape — passthrough)
|
|
10
|
+
* StreamFrame (ev) → agent broadcast event (activity panel / map animation)
|
|
11
|
+
*
|
|
12
|
+
* This module only builds payloads. The actual WebSocket connector that logs
|
|
13
|
+
* into an InfiniBot gateway and ships these is added in the integration step;
|
|
14
|
+
* keeping the mapping here means that connector is a thin transport shim.
|
|
15
|
+
*/
|
|
16
|
+
import type { HardwareSnapshot, NodeStatus, StreamFrame } from './types.js';
|
|
17
|
+
/** InfiniBot `nodes.status` / `node.list` response shape. */
|
|
18
|
+
export declare function toNodesStatus(statuses: NodeStatus[]): {
|
|
19
|
+
ts: number;
|
|
20
|
+
nodes: Array<Record<string, unknown>>;
|
|
21
|
+
};
|
|
22
|
+
/** InfiniBot `hardware.snapshot` payload (shape already matches). */
|
|
23
|
+
export declare function toHardwareSnapshot(nodeId: string, hw: HardwareSnapshot): Record<string, unknown>;
|
|
24
|
+
/** Map a compact activity/routing frame to an InfiniBot-style `agent` event. */
|
|
25
|
+
export declare function toAgentEvent(f: StreamFrame): Record<string, unknown> | null;
|
|
26
|
+
/** Build the full set of InfiniBot-bound messages for a periodic push. */
|
|
27
|
+
export declare function buildMoltfedBatch(statuses: NodeStatus[], selfId: string, hw?: HardwareSnapshot): Array<{
|
|
28
|
+
method: string;
|
|
29
|
+
params: unknown;
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** InfiniBot `nodes.status` / `node.list` response shape. */
|
|
2
|
+
export function toNodesStatus(statuses) {
|
|
3
|
+
return {
|
|
4
|
+
ts: Date.now(),
|
|
5
|
+
nodes: statuses.map(s => ({
|
|
6
|
+
nodeId: s.nodeId,
|
|
7
|
+
displayName: s.displayName,
|
|
8
|
+
platform: s.platform,
|
|
9
|
+
deviceFamily: s.deviceFamily,
|
|
10
|
+
modelIdentifier: s.modelIdentifier,
|
|
11
|
+
version: s.version,
|
|
12
|
+
caps: s.capabilities,
|
|
13
|
+
commands: s.commands,
|
|
14
|
+
connected: s.connected,
|
|
15
|
+
connectedAtMs: s.connectedAtMs,
|
|
16
|
+
paired: true,
|
|
17
|
+
// extra infinicode fields are ignored by InfiniBot but useful to richer UIs
|
|
18
|
+
role: s.role,
|
|
19
|
+
load: s.load,
|
|
20
|
+
})),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** InfiniBot `hardware.snapshot` payload (shape already matches). */
|
|
24
|
+
export function toHardwareSnapshot(nodeId, hw) {
|
|
25
|
+
return { nodeId, snapshot: hw };
|
|
26
|
+
}
|
|
27
|
+
/** Map a compact activity/routing frame to an InfiniBot-style `agent` event. */
|
|
28
|
+
export function toAgentEvent(f) {
|
|
29
|
+
if (f.t !== 'ev' && f.t !== 'rt')
|
|
30
|
+
return null;
|
|
31
|
+
const d = (f.d ?? {});
|
|
32
|
+
return {
|
|
33
|
+
source: 'infinicode',
|
|
34
|
+
nodeId: f.n,
|
|
35
|
+
seq: f.s,
|
|
36
|
+
ts: f.ts,
|
|
37
|
+
kind: d.k,
|
|
38
|
+
missionId: d.m,
|
|
39
|
+
taskId: d.task,
|
|
40
|
+
workerId: d.w,
|
|
41
|
+
data: d.d,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Build the full set of InfiniBot-bound messages for a periodic push. */
|
|
45
|
+
export function buildMoltfedBatch(statuses, selfId, hw) {
|
|
46
|
+
const out = [
|
|
47
|
+
{ method: 'nodes.status', params: toNodesStatus(statuses) },
|
|
48
|
+
];
|
|
49
|
+
if (hw)
|
|
50
|
+
out.push({ method: 'hardware.snapshot', params: toHardwareSnapshot(selfId, hw) });
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — MOLTFED / InfiniBot Gateway Connector
|
|
3
|
+
*
|
|
4
|
+
* Logs an infinicode node into an InfiniBot gateway over WebSocket and streams
|
|
5
|
+
* it onto InfiniBot's neural-mesh map + hardware panels — WITHOUT editing
|
|
6
|
+
* InfiniBot. It speaks InfiniBot's gateway protocol (observed in InfiniBot
|
|
7
|
+
* src/mcp/server.ts):
|
|
8
|
+
*
|
|
9
|
+
* 1. open ws://<host>:18789
|
|
10
|
+
* 2. optionally receive event `connect.challenge` → nonce
|
|
11
|
+
* 3. send { type:'req', id, method:'connect', params:{ minProtocol, client,
|
|
12
|
+
* auth, role, scopes, device } }
|
|
13
|
+
* 4. receive `hello-ok` → connected
|
|
14
|
+
* 5. push request frames: { type:'req', id, method, params }
|
|
15
|
+
*
|
|
16
|
+
* Auth is PLUGGABLE (token/password by default; a device-signer can be supplied
|
|
17
|
+
* for gateways that require signed device identity). Push method names are
|
|
18
|
+
* CONFIGURABLE so this adapts to a gateway's exact method registry without code
|
|
19
|
+
* changes. The WebSocket impl is injectable for testing.
|
|
20
|
+
*
|
|
21
|
+
* NOTE: verify end-to-end against a live InfiniBot gateway; the frame shapes
|
|
22
|
+
* here mirror the observed protocol but a given gateway version may differ.
|
|
23
|
+
*/
|
|
24
|
+
import type { Logger } from '../types.js';
|
|
25
|
+
import type { NodeStatus, HardwareSnapshot, StreamFrame } from './types.js';
|
|
26
|
+
/** Minimal browser-style WebSocket surface (Node global WebSocket / ws both fit). */
|
|
27
|
+
export interface WsLike {
|
|
28
|
+
send(data: string): void;
|
|
29
|
+
close(): void;
|
|
30
|
+
onopen: ((ev?: unknown) => void) | null;
|
|
31
|
+
onmessage: ((ev: {
|
|
32
|
+
data: unknown;
|
|
33
|
+
}) => void) | null;
|
|
34
|
+
onclose: ((ev?: unknown) => void) | null;
|
|
35
|
+
onerror: ((ev?: unknown) => void) | null;
|
|
36
|
+
}
|
|
37
|
+
export type WsFactory = (url: string) => WsLike;
|
|
38
|
+
export interface DeviceAuth {
|
|
39
|
+
id: string;
|
|
40
|
+
publicKey: string;
|
|
41
|
+
/** Sign the connect payload (nonce-bound). Return a signature string. */
|
|
42
|
+
sign: (payload: unknown, nonce: string | null, signedAt: number) => string;
|
|
43
|
+
}
|
|
44
|
+
export interface MoltfedConnectorOptions {
|
|
45
|
+
gatewayUrl: string;
|
|
46
|
+
token?: string;
|
|
47
|
+
password?: string;
|
|
48
|
+
client?: {
|
|
49
|
+
id: string;
|
|
50
|
+
version: string;
|
|
51
|
+
platform: string;
|
|
52
|
+
mode: string;
|
|
53
|
+
};
|
|
54
|
+
role?: string;
|
|
55
|
+
scopes?: string[];
|
|
56
|
+
device?: DeviceAuth;
|
|
57
|
+
minProtocol?: number;
|
|
58
|
+
maxProtocol?: number;
|
|
59
|
+
pushIntervalMs?: number;
|
|
60
|
+
logger: Logger;
|
|
61
|
+
/** Data sources — usually bound to a Federation instance. */
|
|
62
|
+
getNodeStatuses: () => NodeStatus[];
|
|
63
|
+
getHardware: () => HardwareSnapshot | undefined;
|
|
64
|
+
subscribeFrames: (cb: (peerId: string, f: StreamFrame) => void) => () => void;
|
|
65
|
+
/** Gateway method names (defaults chosen to match observed InfiniBot usage). */
|
|
66
|
+
methodNodes?: string;
|
|
67
|
+
methodHardware?: string;
|
|
68
|
+
methodAgent?: string;
|
|
69
|
+
/** Injectable WS constructor for tests; defaults to global WebSocket. */
|
|
70
|
+
wsFactory?: WsFactory;
|
|
71
|
+
}
|
|
72
|
+
export declare class MoltfedConnector {
|
|
73
|
+
private opts;
|
|
74
|
+
private ws;
|
|
75
|
+
private connected;
|
|
76
|
+
private nonce;
|
|
77
|
+
private connectId;
|
|
78
|
+
private connectSent;
|
|
79
|
+
private pushTimer?;
|
|
80
|
+
private connectTimer?;
|
|
81
|
+
private unsubFrames?;
|
|
82
|
+
private stopped;
|
|
83
|
+
private reconnectAttempts;
|
|
84
|
+
constructor(opts: MoltfedConnectorOptions);
|
|
85
|
+
get isConnected(): boolean;
|
|
86
|
+
start(): void;
|
|
87
|
+
private open;
|
|
88
|
+
private onMessage;
|
|
89
|
+
private buildConnectFrame;
|
|
90
|
+
private sendConnect;
|
|
91
|
+
private onConnected;
|
|
92
|
+
private pushSnapshot;
|
|
93
|
+
/** Send a request frame (fire-and-forget; gateway acks via res). */
|
|
94
|
+
request(method: string, params: unknown): void;
|
|
95
|
+
private scheduleReconnect;
|
|
96
|
+
private stopPush;
|
|
97
|
+
stop(): void;
|
|
98
|
+
}
|