infinicode 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.opencode/plugins/home-logo-animation.tsx +120 -0
  2. package/.opencode/plugins/routing-mode-display.tsx +135 -0
  3. package/.opencode/themes/infinibot-gold.json +222 -0
  4. package/.opencode/tui.json +8 -0
  5. package/README.md +564 -72
  6. package/dist/ascii-video-animation.d.ts +2 -0
  7. package/dist/ascii-video-animation.js +243 -0
  8. package/dist/cli.js +199 -50
  9. package/dist/commands/console.d.ts +6 -0
  10. package/dist/commands/console.js +111 -0
  11. package/dist/commands/kernel-setup.d.ts +3 -0
  12. package/dist/commands/kernel-setup.js +303 -0
  13. package/dist/commands/mcp.d.ts +14 -0
  14. package/dist/commands/mcp.js +100 -0
  15. package/dist/commands/mission.d.ts +16 -0
  16. package/dist/commands/mission.js +301 -0
  17. package/dist/commands/models.d.ts +3 -1
  18. package/dist/commands/models.js +111 -55
  19. package/dist/commands/providers.d.ts +6 -0
  20. package/dist/commands/providers.js +95 -0
  21. package/dist/commands/run.d.ts +2 -1
  22. package/dist/commands/run.js +349 -59
  23. package/dist/commands/serve.d.ts +20 -0
  24. package/dist/commands/serve.js +132 -0
  25. package/dist/commands/setup.d.ts +1 -1
  26. package/dist/commands/setup.js +77 -44
  27. package/dist/commands/status.d.ts +2 -1
  28. package/dist/commands/status.js +46 -30
  29. package/dist/commands/workers.d.ts +5 -0
  30. package/dist/commands/workers.js +103 -0
  31. package/dist/kernel/autonomy/goal-loop.d.ts +49 -0
  32. package/dist/kernel/autonomy/goal-loop.js +113 -0
  33. package/dist/kernel/autonomy/index.d.ts +8 -0
  34. package/dist/kernel/autonomy/index.js +7 -0
  35. package/dist/kernel/browser/browser-controller.d.ts +57 -0
  36. package/dist/kernel/browser/browser-controller.js +175 -0
  37. package/dist/kernel/checkpoint-engine.d.ts +17 -0
  38. package/dist/kernel/checkpoint-engine.js +128 -0
  39. package/dist/kernel/command-system.d.ts +37 -0
  40. package/dist/kernel/command-system.js +239 -0
  41. package/dist/kernel/config-schema.d.ts +57 -0
  42. package/dist/kernel/config-schema.js +36 -0
  43. package/dist/kernel/event-bus.d.ts +19 -0
  44. package/dist/kernel/event-bus.js +65 -0
  45. package/dist/kernel/federation/auto-update.d.ts +36 -0
  46. package/dist/kernel/federation/auto-update.js +57 -0
  47. package/dist/kernel/federation/compute-router.d.ts +27 -0
  48. package/dist/kernel/federation/compute-router.js +44 -0
  49. package/dist/kernel/federation/config-sync.d.ts +33 -0
  50. package/dist/kernel/federation/config-sync.js +37 -0
  51. package/dist/kernel/federation/discovery.d.ts +11 -0
  52. package/dist/kernel/federation/discovery.js +44 -0
  53. package/dist/kernel/federation/event-bridge.d.ts +30 -0
  54. package/dist/kernel/federation/event-bridge.js +61 -0
  55. package/dist/kernel/federation/federation-plugin.d.ts +24 -0
  56. package/dist/kernel/federation/federation-plugin.js +35 -0
  57. package/dist/kernel/federation/federation.d.ts +135 -0
  58. package/dist/kernel/federation/federation.js +378 -0
  59. package/dist/kernel/federation/index.d.ts +33 -0
  60. package/dist/kernel/federation/index.js +24 -0
  61. package/dist/kernel/federation/lan-discovery.d.ts +43 -0
  62. package/dist/kernel/federation/lan-discovery.js +135 -0
  63. package/dist/kernel/federation/moltfed-adapter.d.ts +30 -0
  64. package/dist/kernel/federation/moltfed-adapter.js +52 -0
  65. package/dist/kernel/federation/moltfed-connector.d.ts +98 -0
  66. package/dist/kernel/federation/moltfed-connector.js +193 -0
  67. package/dist/kernel/federation/node-identity.d.ts +19 -0
  68. package/dist/kernel/federation/node-identity.js +86 -0
  69. package/dist/kernel/federation/peer-mesh.d.ts +46 -0
  70. package/dist/kernel/federation/peer-mesh.js +117 -0
  71. package/dist/kernel/federation/protocol.d.ts +20 -0
  72. package/dist/kernel/federation/protocol.js +61 -0
  73. package/dist/kernel/federation/role-context.d.ts +5 -0
  74. package/dist/kernel/federation/role-context.js +45 -0
  75. package/dist/kernel/federation/telemetry.d.ts +21 -0
  76. package/dist/kernel/federation/telemetry.js +138 -0
  77. package/dist/kernel/federation/transport-http.d.ts +44 -0
  78. package/dist/kernel/federation/transport-http.js +207 -0
  79. package/dist/kernel/federation/types.d.ts +212 -0
  80. package/dist/kernel/federation/types.js +3 -0
  81. package/dist/kernel/free-providers.d.ts +28 -0
  82. package/dist/kernel/free-providers.js +162 -0
  83. package/dist/kernel/frontend-scoring.d.ts +21 -0
  84. package/dist/kernel/frontend-scoring.js +125 -0
  85. package/dist/kernel/index.d.ts +63 -0
  86. package/dist/kernel/index.js +45 -0
  87. package/dist/kernel/kernel.d.ts +75 -0
  88. package/dist/kernel/kernel.js +223 -0
  89. package/dist/kernel/logger.d.ts +18 -0
  90. package/dist/kernel/logger.js +26 -0
  91. package/dist/kernel/mcp/index.d.ts +9 -0
  92. package/dist/kernel/mcp/index.js +8 -0
  93. package/dist/kernel/mcp/mcp-server.d.ts +27 -0
  94. package/dist/kernel/mcp/mcp-server.js +178 -0
  95. package/dist/kernel/mission-engine.d.ts +42 -0
  96. package/dist/kernel/mission-engine.js +160 -0
  97. package/dist/kernel/orchestrator.d.ts +51 -0
  98. package/dist/kernel/orchestrator.js +226 -0
  99. package/dist/kernel/plugin-manager.d.ts +28 -0
  100. package/dist/kernel/plugin-manager.js +76 -0
  101. package/dist/kernel/plugins/browser-plugin.d.ts +22 -0
  102. package/dist/kernel/plugins/browser-plugin.js +34 -0
  103. package/dist/kernel/plugins/dashboard-plugin.d.ts +17 -0
  104. package/dist/kernel/plugins/dashboard-plugin.js +72 -0
  105. package/dist/kernel/plugins/discord-plugin.d.ts +11 -0
  106. package/dist/kernel/plugins/discord-plugin.js +35 -0
  107. package/dist/kernel/plugins/metrics-plugin.d.ts +33 -0
  108. package/dist/kernel/plugins/metrics-plugin.js +86 -0
  109. package/dist/kernel/plugins/search-plugin.d.ts +17 -0
  110. package/dist/kernel/plugins/search-plugin.js +20 -0
  111. package/dist/kernel/plugins/slack-plugin.d.ts +11 -0
  112. package/dist/kernel/plugins/slack-plugin.js +35 -0
  113. package/dist/kernel/plugins/telegram-plugin.d.ts +20 -0
  114. package/dist/kernel/plugins/telegram-plugin.js +122 -0
  115. package/dist/kernel/policies.d.ts +33 -0
  116. package/dist/kernel/policies.js +105 -0
  117. package/dist/kernel/provider-discovery.d.ts +41 -0
  118. package/dist/kernel/provider-discovery.js +179 -0
  119. package/dist/kernel/provider-manager.d.ts +38 -0
  120. package/dist/kernel/provider-manager.js +206 -0
  121. package/dist/kernel/provider-url.d.ts +18 -0
  122. package/dist/kernel/provider-url.js +45 -0
  123. package/dist/kernel/providers/gemini-provider.d.ts +43 -0
  124. package/dist/kernel/providers/gemini-provider.js +203 -0
  125. package/dist/kernel/providers/ollama-provider.d.ts +44 -0
  126. package/dist/kernel/providers/ollama-provider.js +241 -0
  127. package/dist/kernel/providers/openai-compatible-provider.d.ts +43 -0
  128. package/dist/kernel/providers/openai-compatible-provider.js +233 -0
  129. package/dist/kernel/recovery-manager.d.ts +38 -0
  130. package/dist/kernel/recovery-manager.js +156 -0
  131. package/dist/kernel/router.d.ts +44 -0
  132. package/dist/kernel/router.js +222 -0
  133. package/dist/kernel/sample-missions.d.ts +11 -0
  134. package/dist/kernel/sample-missions.js +132 -0
  135. package/dist/kernel/scheduler.d.ts +30 -0
  136. package/dist/kernel/scheduler.js +147 -0
  137. package/dist/kernel/sdk/harness-sdk.d.ts +37 -0
  138. package/dist/kernel/sdk/harness-sdk.js +48 -0
  139. package/dist/kernel/sdk/plugin-sdk.d.ts +27 -0
  140. package/dist/kernel/sdk/plugin-sdk.js +40 -0
  141. package/dist/kernel/search/search-controller.d.ts +32 -0
  142. package/dist/kernel/search/search-controller.js +141 -0
  143. package/dist/kernel/setup.d.ts +13 -0
  144. package/dist/kernel/setup.js +59 -0
  145. package/dist/kernel/types.d.ts +479 -0
  146. package/dist/kernel/types.js +7 -0
  147. package/dist/kernel/verification-engine.d.ts +33 -0
  148. package/dist/kernel/verification-engine.js +287 -0
  149. package/dist/kernel/worker-runtime.d.ts +66 -0
  150. package/dist/kernel/worker-runtime.js +261 -0
  151. package/dist/kernel/workers/browser-worker.d.ts +6 -0
  152. package/dist/kernel/workers/browser-worker.js +92 -0
  153. package/dist/kernel/workers/builtin-workers.d.ts +20 -0
  154. package/dist/kernel/workers/builtin-workers.js +120 -0
  155. package/dist/kernel/workers/research-worker.d.ts +5 -0
  156. package/dist/kernel/workers/research-worker.js +90 -0
  157. package/dist/prompt-ascii-video-animation.d.ts +2 -0
  158. package/dist/prompt-ascii-video-animation.js +243 -0
  159. package/package.json +43 -9
@@ -0,0 +1,5 @@
1
+ import type { RoleContext } from './types.js';
2
+ export declare function loadRole(storageDir?: string): RoleContext | null;
3
+ export declare function saveRole(role: Omit<RoleContext, 'updatedAt'>, storageDir?: string): RoleContext;
4
+ /** Render a role context as a system-preamble line for task prompts. */
5
+ export declare function rolePreamble(ctx: RoleContext | null): string | undefined;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * OpenKernel — Role Context
3
+ *
4
+ * A device's persistent identity + server-architecture brief. Loaded on every
5
+ * task run so a spawned agent is proactive and context-aware without being
6
+ * re-briefed ("you are RoboPanda; camera+mic on GPIO; part of the RoboPark
7
+ * mesh; report scene events to the hub"). Stored as JSON under the kernel dir.
8
+ */
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ function roleFile(storageDir) {
12
+ return join(storageDir, 'role.json');
13
+ }
14
+ export function loadRole(storageDir = join(process.cwd(), '.openkernel')) {
15
+ const file = roleFile(storageDir);
16
+ if (!existsSync(file))
17
+ return null;
18
+ try {
19
+ return JSON.parse(readFileSync(file, 'utf8'));
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export function saveRole(role, storageDir = join(process.cwd(), '.openkernel')) {
26
+ const ctx = { ...role, updatedAt: Date.now() };
27
+ try {
28
+ if (!existsSync(storageDir))
29
+ mkdirSync(storageDir, { recursive: true });
30
+ writeFileSync(roleFile(storageDir), JSON.stringify(ctx, null, 2));
31
+ }
32
+ catch {
33
+ // non-fatal
34
+ }
35
+ return ctx;
36
+ }
37
+ /** Render a role context as a system-preamble line for task prompts. */
38
+ export function rolePreamble(ctx) {
39
+ if (!ctx)
40
+ return undefined;
41
+ const parts = [`You are ${ctx.role}.`];
42
+ if (ctx.architecture)
43
+ parts.push(ctx.architecture);
44
+ return parts.join(' ');
45
+ }
@@ -0,0 +1,21 @@
1
+ import type { HardwareSnapshot, NodeIdentity, NodeStatus, PeerInfo } from './types.js';
2
+ export declare class TelemetryCollector {
3
+ private lastCpu;
4
+ private lastNet;
5
+ private cpuTimes;
6
+ private cpuPercent;
7
+ /** Per-core % from a single snapshot (approximate, no per-core delta kept). */
8
+ private perCore;
9
+ private network;
10
+ collect(): HardwareSnapshot;
11
+ }
12
+ /** A 0–100 load figure derived from a snapshot (used by the compute router). */
13
+ export declare function loadFromSnapshot(hw: HardwareSnapshot | undefined): number;
14
+ /** Build the neutral NodeStatus entry the mesh map renders. */
15
+ export declare function nodeStatusFromPeer(peer: PeerInfo): NodeStatus;
16
+ /** Local node's own status entry. */
17
+ export declare function selfNodeStatus(identity: NodeIdentity, hw: HardwareSnapshot, manifest: {
18
+ version: string;
19
+ capabilities: NodeStatus['capabilities'];
20
+ commands: string[];
21
+ }): NodeStatus;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * OpenKernel — Telemetry Collector
3
+ *
4
+ * Produces a HardwareSnapshot on the same shape InfiniBot uses, so the MOLTFED
5
+ * adapter can forward it verbatim to `hardware.snapshot` and light up InfiniBot's
6
+ * hardware panels + neural-mesh viz. Pure `node:os` — no native deps, safe on a Pi.
7
+ *
8
+ * CPU % is a delta between ticks; network is a byte-rate delta. Call collect()
9
+ * on a fixed cadence (~1 Hz) so deltas are meaningful.
10
+ */
11
+ import os from 'node:os';
12
+ export class TelemetryCollector {
13
+ lastCpu = null;
14
+ lastNet = new Map();
15
+ cpuTimes() {
16
+ let idle = 0;
17
+ let total = 0;
18
+ for (const c of os.cpus()) {
19
+ idle += c.times.idle;
20
+ total += c.times.user + c.times.nice + c.times.sys + c.times.idle + c.times.irq;
21
+ }
22
+ return { idle, total };
23
+ }
24
+ cpuPercent() {
25
+ const cur = this.cpuTimes();
26
+ if (!this.lastCpu) {
27
+ this.lastCpu = cur;
28
+ const load = os.loadavg()[0];
29
+ return Math.min(100, Math.round((load / os.cpus().length) * 100));
30
+ }
31
+ const idleDiff = cur.idle - this.lastCpu.idle;
32
+ const totalDiff = cur.total - this.lastCpu.total;
33
+ this.lastCpu = cur;
34
+ if (totalDiff <= 0)
35
+ return 0;
36
+ return Math.max(0, Math.min(100, Math.round((1 - idleDiff / totalDiff) * 100)));
37
+ }
38
+ /** Per-core % from a single snapshot (approximate, no per-core delta kept). */
39
+ perCore() {
40
+ return os.cpus().map(c => {
41
+ const t = c.times;
42
+ const total = t.user + t.nice + t.sys + t.idle + t.irq;
43
+ return total > 0 ? Math.round((1 - t.idle / total) * 100) : 0;
44
+ });
45
+ }
46
+ network() {
47
+ // node:os doesn't expose byte counters; report interface presence with
48
+ // zeroed rates by default. A platform collector can override later.
49
+ const ifaces = os.networkInterfaces();
50
+ const out = [];
51
+ for (const [name, addrs] of Object.entries(ifaces)) {
52
+ if (!addrs || addrs.every(a => a.internal))
53
+ continue;
54
+ const prev = this.lastNet.get(name);
55
+ const now = Date.now();
56
+ // Without kernel counters we can't compute a true rate; keep 0 but retain
57
+ // the hook so a native collector can populate rx/tx deltas.
58
+ out.push({ name, rxBytesPerSec: 0, txBytesPerSec: 0 });
59
+ this.lastNet.set(name, { rx: prev?.rx ?? 0, tx: prev?.tx ?? 0, at: now });
60
+ }
61
+ return { interfaces: out };
62
+ }
63
+ collect() {
64
+ const total = os.totalmem();
65
+ const free = os.freemem();
66
+ const used = total - free;
67
+ const la = os.loadavg();
68
+ return {
69
+ cpu: {
70
+ percent: this.cpuPercent(),
71
+ perCorePercent: this.perCore(),
72
+ loadAvg: [la[0], la[1], la[2]],
73
+ cores: os.cpus().length,
74
+ model: os.cpus()[0]?.model ?? 'unknown',
75
+ },
76
+ memory: {
77
+ totalBytes: total,
78
+ usedBytes: used,
79
+ freeBytes: free,
80
+ percent: total > 0 ? Math.round((used / total) * 100) : 0,
81
+ },
82
+ gpu: null, // populated by an optional platform GPU probe later
83
+ disk: { mounts: [] },
84
+ network: this.network(),
85
+ system: {
86
+ hostname: os.hostname(),
87
+ platform: os.platform(),
88
+ arch: os.arch(),
89
+ uptimeSec: Math.round(os.uptime()),
90
+ kernelRelease: os.release(),
91
+ },
92
+ processes: [],
93
+ timestamp: Date.now(),
94
+ };
95
+ }
96
+ }
97
+ /** A 0–100 load figure derived from a snapshot (used by the compute router). */
98
+ export function loadFromSnapshot(hw) {
99
+ if (!hw)
100
+ return 50; // unknown → mid
101
+ // Blend CPU% and memory% so a memory-bound Pi isn't picked for heavy work.
102
+ return Math.round(hw.cpu.percent * 0.7 + hw.memory.percent * 0.3);
103
+ }
104
+ /** Build the neutral NodeStatus entry the mesh map renders. */
105
+ export function nodeStatusFromPeer(peer) {
106
+ return {
107
+ nodeId: peer.nodeId,
108
+ displayName: peer.displayName,
109
+ role: peer.role,
110
+ platform: peer.manifest?.platform ?? 'unknown',
111
+ deviceFamily: peer.manifest?.deviceFamily,
112
+ modelIdentifier: peer.manifest?.modelIdentifier,
113
+ version: peer.manifest?.version ?? peer.version ?? '?',
114
+ capabilities: peer.manifest?.capabilities ?? [],
115
+ commands: peer.manifest?.commands ?? [],
116
+ connected: peer.connected,
117
+ connectedAtMs: peer.connected ? peer.lastSeenAt : undefined,
118
+ load: loadFromSnapshot(peer.hardware),
119
+ lastSeenAt: peer.lastSeenAt,
120
+ };
121
+ }
122
+ /** Local node's own status entry. */
123
+ export function selfNodeStatus(identity, hw, manifest) {
124
+ return {
125
+ nodeId: identity.nodeId,
126
+ displayName: identity.displayName,
127
+ role: identity.role,
128
+ platform: hw.system.platform,
129
+ modelIdentifier: hw.cpu.model,
130
+ version: manifest.version,
131
+ capabilities: manifest.capabilities,
132
+ commands: manifest.commands,
133
+ connected: true,
134
+ connectedAtMs: Date.now(),
135
+ load: loadFromSnapshot(hw),
136
+ lastSeenAt: Date.now(),
137
+ };
138
+ }
@@ -0,0 +1,44 @@
1
+ import type { Logger } from '../types.js';
2
+ import type { FederationEnvelope, NodeManifest, StreamFrame } from './types.js';
3
+ export type RpcHandler = (env: FederationEnvelope, remote: string) => Promise<FederationEnvelope | void>;
4
+ export interface MeshServerOptions {
5
+ host?: string;
6
+ port: number;
7
+ /** Optional shared token; requests must present it as Bearer auth. */
8
+ token?: string;
9
+ getManifest: () => NodeManifest;
10
+ onRpc: RpcHandler;
11
+ logger: Logger;
12
+ }
13
+ export declare class MeshServer {
14
+ private opts;
15
+ private server?;
16
+ private sse;
17
+ private seq;
18
+ constructor(opts: MeshServerOptions);
19
+ get streamCount(): number;
20
+ start(): Promise<void>;
21
+ /** Push a frame to every connected stream subscriber. */
22
+ broadcast(frame: StreamFrame): void;
23
+ private authOk;
24
+ private handle;
25
+ private openStream;
26
+ private readBody;
27
+ stop(): Promise<void>;
28
+ }
29
+ export interface MeshClientOptions {
30
+ token?: string;
31
+ timeoutMs?: number;
32
+ }
33
+ export declare class MeshClient {
34
+ private opts;
35
+ constructor(opts?: MeshClientOptions);
36
+ private headers;
37
+ fetchManifest(baseUrl: string): Promise<NodeManifest>;
38
+ rpc(baseUrl: string, env: FederationEnvelope): Promise<FederationEnvelope>;
39
+ /**
40
+ * Open an SSE stream to a peer. Returns a stop() fn. Reconnection is the
41
+ * caller's job (peer-mesh handles it on heartbeat failure).
42
+ */
43
+ openStream(baseUrl: string, onFrame: (f: StreamFrame) => void, onClose?: () => void): () => void;
44
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * OpenKernel — Federation Transport (HTTP + SSE)
3
+ *
4
+ * Dependency-free transport built on node:http (server) + global fetch (client).
5
+ *
6
+ * POST /fed/rpc → receive a FederationEnvelope, return a reply envelope
7
+ * GET /fed/stream → SSE: server pushes compact StreamFrames to a peer
8
+ * GET /fed/manifest → this node's manifest (cheap discovery probe)
9
+ *
10
+ * This directionality is deliberate: a hub OPENS the connection to a satellite
11
+ * (GET /stream to receive its activity/telemetry, POST /rpc to send tasks). The
12
+ * satellite is accept-only and never dials out — matching the secure one-way
13
+ * network model. A WebSocket/MOLTFED transport can be added behind the same
14
+ * MeshClient/MeshServer shape later for deep InfiniBot-fabric interop.
15
+ */
16
+ import { createServer } from 'node:http';
17
+ import { frameToSSE, parseSSE } from './protocol.js';
18
+ export class MeshServer {
19
+ opts;
20
+ server;
21
+ sse = new Map();
22
+ seq = 0;
23
+ constructor(opts) {
24
+ this.opts = opts;
25
+ }
26
+ get streamCount() {
27
+ return this.sse.size;
28
+ }
29
+ async start() {
30
+ this.server = createServer((req, res) => this.handle(req, res));
31
+ await new Promise((resolve, reject) => {
32
+ this.server.once('error', reject);
33
+ this.server.listen(this.opts.port, this.opts.host ?? '0.0.0.0', () => {
34
+ this.server.off('error', reject);
35
+ resolve();
36
+ });
37
+ });
38
+ this.opts.logger.info(`[federation] mesh listening on ${this.opts.host ?? '0.0.0.0'}:${this.opts.port}`);
39
+ }
40
+ /** Push a frame to every connected stream subscriber. */
41
+ broadcast(frame) {
42
+ if (this.sse.size === 0)
43
+ return;
44
+ const payload = frameToSSE(frame);
45
+ for (const client of this.sse.values()) {
46
+ try {
47
+ client.res.write(payload);
48
+ }
49
+ catch {
50
+ this.sse.delete(client.id);
51
+ }
52
+ }
53
+ }
54
+ authOk(req) {
55
+ if (!this.opts.token)
56
+ return true;
57
+ const h = req.headers['authorization'];
58
+ return typeof h === 'string' && h === `Bearer ${this.opts.token}`;
59
+ }
60
+ handle(req, res) {
61
+ const url = req.url ?? '/';
62
+ if (!this.authOk(req)) {
63
+ res.writeHead(401).end('unauthorized');
64
+ return;
65
+ }
66
+ const remote = req.socket.remoteAddress ?? 'unknown';
67
+ if (req.method === 'GET' && url.startsWith('/fed/manifest')) {
68
+ res.writeHead(200, { 'content-type': 'application/json' });
69
+ res.end(JSON.stringify(this.opts.getManifest()));
70
+ return;
71
+ }
72
+ if (req.method === 'GET' && url.startsWith('/fed/stream')) {
73
+ this.openStream(req, res);
74
+ return;
75
+ }
76
+ if (req.method === 'POST' && url.startsWith('/fed/rpc')) {
77
+ this.readBody(req)
78
+ .then(async (body) => {
79
+ let env;
80
+ try {
81
+ env = JSON.parse(body);
82
+ }
83
+ catch {
84
+ res.writeHead(400).end('bad envelope');
85
+ return;
86
+ }
87
+ const out = await this.opts.onRpc(env, remote);
88
+ res.writeHead(200, { 'content-type': 'application/json' });
89
+ res.end(JSON.stringify(out ?? { ok: true }));
90
+ })
91
+ .catch(err => {
92
+ this.opts.logger.warn(`[federation] rpc error: ${err instanceof Error ? err.message : String(err)}`);
93
+ res.writeHead(500).end('rpc failed');
94
+ });
95
+ return;
96
+ }
97
+ res.writeHead(404).end('not found');
98
+ }
99
+ openStream(req, res) {
100
+ res.writeHead(200, {
101
+ 'content-type': 'text/event-stream',
102
+ 'cache-control': 'no-cache',
103
+ connection: 'keep-alive',
104
+ });
105
+ res.write(': connected\n\n');
106
+ const id = `sse_${++this.seq}`;
107
+ this.sse.set(id, { id, res });
108
+ this.opts.logger.info(`[federation] stream subscriber ${id} (${this.sse.size} total)`);
109
+ req.on('close', () => {
110
+ this.sse.delete(id);
111
+ });
112
+ }
113
+ readBody(req) {
114
+ return new Promise((resolve, reject) => {
115
+ let data = '';
116
+ req.on('data', chunk => {
117
+ data += chunk;
118
+ if (data.length > 8_000_000)
119
+ reject(new Error('body too large'));
120
+ });
121
+ req.on('end', () => resolve(data));
122
+ req.on('error', reject);
123
+ });
124
+ }
125
+ async stop() {
126
+ for (const c of this.sse.values()) {
127
+ try {
128
+ c.res.end();
129
+ }
130
+ catch { /* ignore */ }
131
+ }
132
+ this.sse.clear();
133
+ if (this.server) {
134
+ await new Promise(resolve => this.server.close(() => resolve()));
135
+ this.server = undefined;
136
+ }
137
+ }
138
+ }
139
+ export class MeshClient {
140
+ opts;
141
+ constructor(opts = {}) {
142
+ this.opts = opts;
143
+ }
144
+ headers() {
145
+ const h = { 'content-type': 'application/json' };
146
+ if (this.opts.token)
147
+ h['authorization'] = `Bearer ${this.opts.token}`;
148
+ return h;
149
+ }
150
+ async fetchManifest(baseUrl) {
151
+ const res = await fetch(`${baseUrl}/fed/manifest`, {
152
+ headers: this.headers(),
153
+ signal: AbortSignal.timeout(this.opts.timeoutMs ?? 5000),
154
+ });
155
+ if (!res.ok)
156
+ throw new Error(`manifest ${res.status}`);
157
+ return (await res.json());
158
+ }
159
+ async rpc(baseUrl, env) {
160
+ const res = await fetch(`${baseUrl}/fed/rpc`, {
161
+ method: 'POST',
162
+ headers: this.headers(),
163
+ body: JSON.stringify(env),
164
+ signal: AbortSignal.timeout(this.opts.timeoutMs ?? 30_000),
165
+ });
166
+ if (!res.ok)
167
+ throw new Error(`rpc ${res.status}`);
168
+ return (await res.json());
169
+ }
170
+ /**
171
+ * Open an SSE stream to a peer. Returns a stop() fn. Reconnection is the
172
+ * caller's job (peer-mesh handles it on heartbeat failure).
173
+ */
174
+ openStream(baseUrl, onFrame, onClose) {
175
+ const ctrl = new AbortController();
176
+ (async () => {
177
+ try {
178
+ const res = await fetch(`${baseUrl}/fed/stream`, {
179
+ headers: this.headers(),
180
+ signal: ctrl.signal,
181
+ });
182
+ if (!res.ok || !res.body)
183
+ throw new Error(`stream ${res.status}`);
184
+ const reader = res.body.getReader();
185
+ const decoder = new TextDecoder();
186
+ let buf = '';
187
+ for (;;) {
188
+ const { done, value } = await reader.read();
189
+ if (done)
190
+ break;
191
+ buf += decoder.decode(value, { stream: true });
192
+ const { frames, rest } = parseSSE(buf);
193
+ buf = rest;
194
+ for (const f of frames)
195
+ onFrame(f);
196
+ }
197
+ }
198
+ catch {
199
+ // aborted or dropped
200
+ }
201
+ finally {
202
+ onClose?.();
203
+ }
204
+ })();
205
+ return () => ctrl.abort();
206
+ }
207
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * OpenKernel — Federation Types
3
+ *
4
+ * The mesh is provider/harness-agnostic. Everything on the wire is plain JSON:
5
+ * a small envelope carrying one of a handful of message kinds, plus a compact
6
+ * "stream frame" format tuned for high-frequency, animation-friendly rendering
7
+ * in a harness UI (short keys, coalesced telemetry).
8
+ *
9
+ * Nothing here is InfiniBot-specific — the MOLTFED adapter translates these
10
+ * neutral shapes into InfiniBot's message names so its neural-mesh map + hardware
11
+ * panels light up without any InfiniBot changes.
12
+ */
13
+ import type { Capability } from '../types.js';
14
+ /** Role of a node in the mesh. Governs connection direction + permissions. */
15
+ export type NodeRole = 'hub' | 'peer' | 'satellite' | 'relay';
16
+ /** Stable identity of a node, persisted to disk so it survives restarts. */
17
+ export interface NodeIdentity {
18
+ nodeId: string;
19
+ displayName: string;
20
+ role: NodeRole;
21
+ /** Public key (base64) — used for app-layer auth of peers. */
22
+ publicKey?: string;
23
+ createdAt: number;
24
+ }
25
+ /** What a node can do — advertised to peers + the harness map. */
26
+ export interface NodeManifest {
27
+ nodeId: string;
28
+ displayName: string;
29
+ role: NodeRole;
30
+ version: string;
31
+ platform: string;
32
+ arch: string;
33
+ /** e.g. "Raspberry Pi 5", "Windows Workstation". */
34
+ deviceFamily?: string;
35
+ modelIdentifier?: string;
36
+ /** Aggregate capabilities of this node's registered workers. */
37
+ capabilities: Capability[];
38
+ /** Worker types this node can run. */
39
+ workers: string[];
40
+ /** Slash/MCP commands this node exposes (for click-to-invoke). */
41
+ commands: string[];
42
+ /** Provider ids available on this node. */
43
+ providers: string[];
44
+ startedAt: number;
45
+ }
46
+ /** A peer known to this node. */
47
+ export interface PeerInfo {
48
+ nodeId: string;
49
+ displayName: string;
50
+ role: NodeRole;
51
+ /** Base URL we reach this peer at, e.g. http://100.x.y.z:47913 . */
52
+ url: string;
53
+ connected: boolean;
54
+ lastSeenAt: number;
55
+ latencyMs?: number;
56
+ manifest?: NodeManifest;
57
+ /** Latest telemetry snapshot (load used by the compute router). */
58
+ hardware?: HardwareSnapshot;
59
+ version?: string;
60
+ }
61
+ export interface HardwareSnapshot {
62
+ cpu: {
63
+ percent: number;
64
+ perCorePercent: number[];
65
+ loadAvg: [number, number, number];
66
+ cores: number;
67
+ model: string;
68
+ };
69
+ memory: {
70
+ totalBytes: number;
71
+ usedBytes: number;
72
+ freeBytes: number;
73
+ percent: number;
74
+ };
75
+ gpu: {
76
+ vendor: string;
77
+ utilizationPercent: number;
78
+ vramUsedBytes: number;
79
+ vramTotalBytes: number;
80
+ name: string;
81
+ } | null;
82
+ disk: {
83
+ mounts: Array<{
84
+ path: string;
85
+ totalBytes: number;
86
+ usedBytes: number;
87
+ percent: number;
88
+ }>;
89
+ };
90
+ network: {
91
+ interfaces: Array<{
92
+ name: string;
93
+ rxBytesPerSec: number;
94
+ txBytesPerSec: number;
95
+ }>;
96
+ };
97
+ system: {
98
+ hostname: string;
99
+ platform: string;
100
+ arch: string;
101
+ uptimeSec: number;
102
+ kernelRelease: string;
103
+ };
104
+ processes: Array<{
105
+ pid: number;
106
+ name: string;
107
+ cpuPercent: number;
108
+ memoryBytes: number;
109
+ }>;
110
+ timestamp: number;
111
+ }
112
+ /** Node status entry consumed by the mesh map (neutral form of nodes.status). */
113
+ export interface NodeStatus {
114
+ nodeId: string;
115
+ displayName: string;
116
+ role: NodeRole;
117
+ platform: string;
118
+ deviceFamily?: string;
119
+ modelIdentifier?: string;
120
+ version: string;
121
+ capabilities: Capability[];
122
+ commands: string[];
123
+ connected: boolean;
124
+ connectedAtMs?: number;
125
+ /** 0–100 rough load, from the latest hardware snapshot. */
126
+ load?: number;
127
+ lastSeenAt: number;
128
+ }
129
+ export type MessageKind = 'hello' | 'welcome' | 'heartbeat' | 'manifest' | 'task.dispatch' | 'task.accepted' | 'task.status' | 'task.result' | 'task.cancel' | 'command' | 'command.result' | 'config.request' | 'config.offer' | 'update.announce' | 'update.request' | 'role.get' | 'role.set' | 'event' | 'telemetry' | 'ack' | 'error';
130
+ /** A dispatched/received task run — tracked for non-blocking handoff + monitoring. */
131
+ export interface RunRecord {
132
+ runId: string;
133
+ status: 'running' | 'completed' | 'failed';
134
+ description: string;
135
+ missionId?: string;
136
+ outputs?: Array<{
137
+ status: string;
138
+ content?: string;
139
+ error?: string;
140
+ }>;
141
+ error?: string;
142
+ startedAt: number;
143
+ completedAt?: number;
144
+ }
145
+ /** Persistent per-device role + server-architecture context. */
146
+ export interface RoleContext {
147
+ /** e.g. "RoboPanda" — the identity a spawned agent adopts. */
148
+ role: string;
149
+ /** Free-text description of this device's job + the wider architecture. */
150
+ architecture?: string;
151
+ /** Extra structured context injected into every task run on this node. */
152
+ extra?: Record<string, unknown>;
153
+ updatedAt: number;
154
+ }
155
+ /** Signed, routable message envelope. */
156
+ export interface FederationEnvelope<T = unknown> {
157
+ /** Protocol version for forward-compat. */
158
+ v: number;
159
+ /** Message id (for request/response correlation). */
160
+ id: string;
161
+ kind: MessageKind;
162
+ from: string;
163
+ to?: string;
164
+ ts: number;
165
+ /** Correlation id — set on replies to the request's id. */
166
+ re?: string;
167
+ data?: T;
168
+ /** Optional bearer/token for app-layer auth. */
169
+ auth?: string;
170
+ }
171
+ /**
172
+ * A minimal, high-frequency frame for the harness to render/animate. Short keys
173
+ * keep bytes low for smooth 1–10 Hz updates. `t` is a frame type; the harness
174
+ * switches on it to drive different animations.
175
+ */
176
+ export interface StreamFrame {
177
+ /** frame type: ev=event, hw=telemetry, hb=heartbeat, nd=node status, rt=routing */
178
+ t: 'ev' | 'hw' | 'hb' | 'nd' | 'rt' | 'log';
179
+ /** origin nodeId */
180
+ n: string;
181
+ /** monotonic sequence for gap detection */
182
+ s: number;
183
+ /** epoch ms */
184
+ ts: number;
185
+ /** compact payload — shape depends on t */
186
+ d?: unknown;
187
+ }
188
+ /** A shareable slice of config a hub can push to satellites. */
189
+ export interface SharedConfig {
190
+ /** Cloud providers (id/name/baseURL/apiKey/enabled). */
191
+ cloudProviders?: Array<Record<string, unknown>>;
192
+ /** Default policy + model. */
193
+ policy?: string;
194
+ defaultModel?: string;
195
+ /** Free-form extra keys. */
196
+ extra?: Record<string, unknown>;
197
+ /** Version/etag so a satellite only re-applies when it changes. */
198
+ revision: number;
199
+ updatedAt: number;
200
+ }
201
+ export interface UpdateAnnounce {
202
+ version: string;
203
+ /** How to fetch the update (npm tag, url, or "git"). */
204
+ channel: 'npm' | 'url' | 'git';
205
+ ref?: string;
206
+ notes?: string;
207
+ /** Minimum protocol version the update requires. */
208
+ minProtocol?: number;
209
+ }
210
+ export declare const FEDERATION_PROTOCOL_VERSION = 1;
211
+ /** Default mesh ports — chosen to sit alongside InfiniBot's 47913/47914. */
212
+ export declare const DEFAULT_MESH_PORT = 47913;
@@ -0,0 +1,3 @@
1
+ export const FEDERATION_PROTOCOL_VERSION = 1;
2
+ /** Default mesh ports — chosen to sit alongside InfiniBot's 47913/47914. */
3
+ export const DEFAULT_MESH_PORT = 47913;