infinicode 2.8.63 → 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(
|
|
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 =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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);
|
package/dist/robopark/setup.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
@@ -119,8 +119,9 @@ function stopAllUnix() {
|
|
|
119
119
|
const fuser = spawnSync('fuser', ['-n', 'tcp', String(port)], { encoding: 'utf8' });
|
|
120
120
|
const pids = [...new Set(`${lsof.stdout ?? ''} ${fuser.stdout ?? ''}`
|
|
121
121
|
.split(/\s+/)
|
|
122
|
+
.filter(Boolean)
|
|
122
123
|
.map(s => Number(s.trim()))
|
|
123
|
-
.filter(Number.isFinite))];
|
|
124
|
+
.filter(pid => Number.isFinite(pid) && pid > 1))];
|
|
124
125
|
for (const pid of pids) {
|
|
125
126
|
if (pid === self || killedPids.includes(pid))
|
|
126
127
|
continue;
|
package/package.json
CHANGED