infinicode 2.8.62 → 2.8.64

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.
@@ -400,6 +400,28 @@ export class Federation {
400
400
  * until the run finishes (short tasks).
401
401
  */
402
402
  async dispatch(task, need, opts) {
403
+ const self = this.deps.identity;
404
+ const requestedSelf = need?.preferNodeId === self.nodeId ||
405
+ need?.preferName?.toLowerCase() === self.displayName.toLowerCase();
406
+ if (requestedSelf) {
407
+ const localCaps = new Set(this.describeWithBackends().capabilities);
408
+ if (!task.capabilities.every(capability => localCaps.has(capability))) {
409
+ this.deps.logger.warn('[federation] local node lacks a required dispatch capability');
410
+ return null;
411
+ }
412
+ const runId = newId();
413
+ this.runs.set(runId, {
414
+ runId,
415
+ status: 'running',
416
+ description: task.description,
417
+ startedAt: Date.now(),
418
+ });
419
+ void this.runTaskAsync(runId, task);
420
+ const record = opts?.wait
421
+ ? await this.awaitRun(runId, opts.timeoutMs ?? 120_000)
422
+ : this.runs.get(runId);
423
+ return { peerId: self.nodeId, runId, record };
424
+ }
403
425
  if (!this.mesh)
404
426
  return null;
405
427
  const peers = this.mesh.connected();
@@ -56,7 +56,9 @@ export declare function readInfinicodeFederation(): {
56
56
  port?: number;
57
57
  };
58
58
  /** Build the full discovered context for this environment. */
59
- export declare function discoverContext(): Promise<DiscoveredContext>;
59
+ export declare function discoverContext(options?: {
60
+ lan?: boolean;
61
+ }): Promise<DiscoveredContext>;
60
62
  /** Generate a random token for mesh auth or scheduler enrollment. */
61
63
  export declare function generateToken(length?: number): string;
62
64
  export {};
@@ -8,6 +8,7 @@
8
8
  * - Running infinicode nodes → reuse mesh identity + token
9
9
  */
10
10
  import { execa } from 'execa';
11
+ import dgram from 'node:dgram';
11
12
  import { existsSync, readFileSync } from 'node:fs';
12
13
  import { homedir, hostname } from 'node:os';
13
14
  import { join } from 'node:path';
@@ -45,11 +46,42 @@ export async function discoverTailscale() {
45
46
  .filter(p => p.ip !== 'unknown');
46
47
  }
47
48
  /** Scan LAN UDP beacons on the default mesh discovery port. */
48
- export async function discoverLan(port = 47915, timeoutMs = 3000) {
49
- // LAN discovery currently relies on the infinicode beacon protocol.
50
- // For auto-setup we reuse Tailscale when available; LAN fallback is a TODO
51
- // once the beacon protocol is stable enough to parse here.
52
- return [];
49
+ export async function discoverLan(port = 47915, timeoutMs = 6000) {
50
+ return new Promise(resolve => {
51
+ const peers = new Map();
52
+ const socket = dgram.createSocket({ type: 'udp4', reuseAddr: true });
53
+ let settled = false;
54
+ const finish = () => {
55
+ if (settled)
56
+ return;
57
+ settled = true;
58
+ try {
59
+ socket.close();
60
+ }
61
+ catch { /* already closed */ }
62
+ resolve([...peers.values()]);
63
+ };
64
+ socket.on('message', (buffer, remote) => {
65
+ try {
66
+ const beacon = JSON.parse(buffer.toString('utf8'));
67
+ if (beacon.t !== 'ICMESH1' || !beacon.nodeId || !Number.isInteger(beacon.meshPort))
68
+ return;
69
+ peers.set(beacon.nodeId, {
70
+ name: beacon.name ?? beacon.nodeId,
71
+ ip: remote.address,
72
+ tags: beacon.tag ? [beacon.tag] : undefined,
73
+ online: true,
74
+ source: 'lan',
75
+ });
76
+ }
77
+ catch {
78
+ // Ignore unrelated UDP traffic on the discovery port.
79
+ }
80
+ });
81
+ socket.once('error', finish);
82
+ socket.bind({ port, exclusive: false });
83
+ setTimeout(finish, timeoutMs).unref();
84
+ });
53
85
  }
54
86
  /** Read a Tailscale auth key from common locations. */
55
87
  export function findTailscaleAuthKey() {
@@ -115,8 +147,12 @@ export function readInfinicodeFederation() {
115
147
  return {};
116
148
  }
117
149
  /** Build the full discovered context for this environment. */
118
- export async function discoverContext() {
119
- const peers = await discoverTailscale();
150
+ export async function discoverContext(options = {}) {
151
+ const [tailnetPeers, lanPeers] = await Promise.all([
152
+ discoverTailscale(),
153
+ options.lan ? discoverLan() : Promise.resolve([]),
154
+ ]);
155
+ const peers = [...lanPeers, ...tailnetPeers];
120
156
  // Exclude this machine from the fleet tables.
121
157
  const selfName = hostname();
122
158
  const others = peers.filter(p => p.name !== selfName && p.online);
@@ -50,8 +50,8 @@ function commandString(argv) {
50
50
  return argv.map(a => /[^a-zA-Z0-9_./:=,-]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a).join(' ');
51
51
  }
52
52
  export async function roboparkSetup(config, opts) {
53
- const ctx = await discoverContext();
54
53
  const role = opts.role ?? 'control';
54
+ const ctx = await discoverContext({ lan: role === 'robot' && !opts.tailscale });
55
55
  const token = opts.token ?? ctx.meshToken ?? generateToken();
56
56
  const port = opts.port ? parseInt(opts.port, 10) : DEFAULT_MESH_PORT;
57
57
  const name = opts.name ?? ctx.localName;
@@ -140,11 +140,37 @@ async function setupRobot(config, opts, ctx, internal) {
140
140
  }
141
141
  const network = opts.tailscale ? 'tailscale' : 'lan';
142
142
  const networkFlag = network === 'tailscale' ? '--tailscale' : '--lan';
143
- const hubUrl = opts.hub ?? (ctx.hub ? `http://${ctx.hub.ip}:${internal.port}` : undefined);
143
+ let hubUrl = opts.hub ?? (ctx.hub ? `http://${ctx.hub.ip}:${internal.port}` : undefined);
144
144
  if (!hubUrl) {
145
145
  console.log(chalk.red(' ✗ no hub discovered. Pass --hub http://HUB_IP:47913'));
146
146
  return;
147
147
  }
148
+ if (network === 'lan') {
149
+ const reachable = async (url) => {
150
+ try {
151
+ const response = await fetch(`${url.replace(/\/+$/, '')}/fed/status`, {
152
+ headers: { Authorization: `Bearer ${internal.token}` },
153
+ signal: AbortSignal.timeout(2500),
154
+ });
155
+ return response.ok;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ };
161
+ if (!await reachable(hubUrl)) {
162
+ const discovered = ctx.hub?.source === 'lan'
163
+ ? `http://${ctx.hub.ip}:${internal.port}`
164
+ : undefined;
165
+ if (!discovered || !await reachable(discovered)) {
166
+ console.log(chalk.red(` ✗ hub is unreachable from this robot: ${hubUrl}`));
167
+ console.log(chalk.dim(' verify the hub is running with --lan, then rerun setup'));
168
+ return;
169
+ }
170
+ console.log(chalk.yellow(` âš  supplied hub unreachable; using LAN-discovered hub ${discovered}`));
171
+ hubUrl = discovered;
172
+ }
173
+ }
148
174
  const fed = {
149
175
  ...(config.get('federation') ?? {}),
150
176
  enabled: true,
@@ -116,7 +116,12 @@ function stopAllUnix() {
116
116
  // against a supervised process whose command line didn't match a pattern.
117
117
  for (const port of [47913, 47921, 47922, 8080, 5000, 5057, 8000]) {
118
118
  const lsof = spawnSync('lsof', ['-t', `-i:${port}`], { encoding: 'utf8' });
119
- const pids = (lsof.stdout ?? '').split('\n').map(s => s.trim()).filter(Boolean).map(Number);
119
+ const fuser = spawnSync('fuser', ['-n', 'tcp', String(port)], { encoding: 'utf8' });
120
+ const pids = [...new Set(`${lsof.stdout ?? ''} ${fuser.stdout ?? ''}`
121
+ .split(/\s+/)
122
+ .filter(Boolean)
123
+ .map(s => Number(s.trim()))
124
+ .filter(pid => Number.isFinite(pid) && pid > 1))];
120
125
  for (const pid of pids) {
121
126
  if (pid === self || killedPids.includes(pid))
122
127
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.62",
3
+ "version": "2.8.64",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",