infinicode 2.2.0 → 2.2.1

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/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import { consoleRepl, consoleRun } from './commands/console.js';
15
15
  import { serve } from './commands/serve.js';
16
16
  import { mcpCommand } from './commands/mcp.js';
17
17
  import { DEFAULT_CONFIG } from './kernel/config-schema.js';
18
- const VERSION = '2.2.0';
18
+ const VERSION = '2.2.1';
19
19
  const config = new Conf({
20
20
  projectName: 'infinicode',
21
21
  defaults: DEFAULT_CONFIG,
@@ -61,6 +61,8 @@ export declare class Federation {
61
61
  private lan?;
62
62
  /** nodeIds we're currently dialing via LAN discovery (dedup guard). */
63
63
  private lanConnecting;
64
+ /** nodeIds we're currently dialing back after an inbound heartbeat/hello. */
65
+ private inboundConnecting;
64
66
  private configPullTimer?;
65
67
  readonly configSync: ConfigSync;
66
68
  readonly autoUpdate: AutoUpdate;
@@ -115,6 +117,14 @@ export declare class Federation {
115
117
  /** Satellite: pull shared config from a peer (usually the hub). */
116
118
  pullConfig(nodeId: string): Promise<boolean>;
117
119
  connect(url: string): Promise<PeerInfo | null>;
120
+ /**
121
+ * Register an inbound peer that dialed US (learned from its heartbeat/hello),
122
+ * so dispatch works regardless of who connected first. The hub dials the
123
+ * sender back (→ full manifest + stream), making a satellite that only
124
+ * `--seed`s the hub visible + dispatchable. Gated: a satellite never dials
125
+ * back (accept-only, never dials home).
126
+ */
127
+ private learnInboundPeer;
118
128
  /** Satellite: pull shared config from a freshly-connected hub/relay. */
119
129
  private maybeAutoPull;
120
130
  /** Satellite: re-pull from the first connected hub/relay (periodic refresh). */
@@ -24,6 +24,8 @@ export class Federation {
24
24
  lan;
25
25
  /** nodeIds we're currently dialing via LAN discovery (dedup guard). */
26
26
  lanConnecting = new Set();
27
+ /** nodeIds we're currently dialing back after an inbound heartbeat/hello. */
28
+ inboundConnecting = new Set();
27
29
  configPullTimer;
28
30
  configSync;
29
31
  autoUpdate;
@@ -68,6 +70,7 @@ export class Federation {
68
70
  logger,
69
71
  heartbeatMs: options.heartbeatMs,
70
72
  getLoad: () => loadFromSnapshot(this.lastHw),
73
+ selfPort: port,
71
74
  onFrame: (peerId, f) => this.ingestFrame(peerId, f),
72
75
  onPeerChange: () => this.emitLocal('nd', frame('nd', identity.nodeId, this.nodeStatuses())),
73
76
  });
@@ -95,12 +98,14 @@ export class Federation {
95
98
  logger.info(`[federation] node ${identity.displayName} (${identity.role}) online on :${port}`);
96
99
  }
97
100
  // ── Inbound RPC ────────────────────────────────────────────────────────────
98
- async onRpc(env, _remote) {
101
+ async onRpc(env, remote) {
99
102
  const self = this.deps.identity.nodeId;
100
103
  switch (env.kind) {
101
104
  case 'heartbeat':
105
+ this.learnInboundPeer(env, remote);
102
106
  return reply(env, 'ack', self, { load: loadFromSnapshot(this.lastHw) });
103
107
  case 'hello':
108
+ this.learnInboundPeer(env, remote);
104
109
  return reply(env, 'welcome', self, buildManifest(this.deps.identity, this.deps.describe()));
105
110
  case 'task.dispatch': {
106
111
  // Non-blocking handoff: register the run, ack with a runId immediately,
@@ -287,6 +292,34 @@ export class Federation {
287
292
  await this.maybeAutoPull(peer);
288
293
  return peer;
289
294
  }
295
+ /**
296
+ * Register an inbound peer that dialed US (learned from its heartbeat/hello),
297
+ * so dispatch works regardless of who connected first. The hub dials the
298
+ * sender back (→ full manifest + stream), making a satellite that only
299
+ * `--seed`s the hub visible + dispatchable. Gated: a satellite never dials
300
+ * back (accept-only, never dials home).
301
+ */
302
+ learnInboundPeer(env, remote) {
303
+ if (this.deps.identity.role === 'satellite')
304
+ return; // accept-only
305
+ const from = env.from;
306
+ if (!from || from === this.deps.identity.nodeId)
307
+ return;
308
+ if (this.mesh?.get(from))
309
+ return; // already tracked
310
+ if (this.inboundConnecting.has(from))
311
+ return; // dial in flight
312
+ const port = env.data?.port;
313
+ const ip = normalizeIp(remote);
314
+ if (!port || !ip)
315
+ return;
316
+ const url = `http://${ip}:${port}`;
317
+ this.inboundConnecting.add(from);
318
+ this.deps.logger.info(`[federation] learned inbound peer ${from} → dialing back ${url}`);
319
+ void this.connect(url)
320
+ .catch(() => { })
321
+ .finally(() => this.inboundConnecting.delete(from));
322
+ }
290
323
  /** Satellite: pull shared config from a freshly-connected hub/relay. */
291
324
  async maybeAutoPull(peer) {
292
325
  if (!this.deps.options.autoPullConfig)
@@ -403,3 +436,16 @@ export class Federation {
403
436
  await this.server?.stop();
404
437
  }
405
438
  }
439
+ /** Strip IPv6-mapped prefixes so an inbound remoteAddress becomes a diallable IPv4 host. */
440
+ function normalizeIp(remote) {
441
+ if (!remote)
442
+ return null;
443
+ let ip = remote.trim();
444
+ if (ip === '::1')
445
+ return '127.0.0.1';
446
+ if (ip.startsWith('::ffff:'))
447
+ ip = ip.slice(7);
448
+ if (ip.includes(':'))
449
+ return null; // raw IPv6 — skip (use --lan/--tailscale to dial proactively)
450
+ return ip;
451
+ }
@@ -23,6 +23,8 @@ export interface PeerMeshOptions {
23
23
  onPeerChange?: () => void;
24
24
  /** Current local load 0–100, sent with heartbeats. */
25
25
  getLoad: () => number;
26
+ /** Our own mesh port — advertised in heartbeats so hubs can dial us back. */
27
+ selfPort?: number;
26
28
  }
27
29
  export declare class PeerMesh {
28
30
  private opts;
@@ -34,6 +34,11 @@ export class PeerMesh {
34
34
  };
35
35
  this.peers.set(peer.nodeId, peer);
36
36
  this.openStream(peer);
37
+ // Immediate heartbeat so the peer learns us (nodeId + port) right away —
38
+ // lets a hub register + dial us back without waiting a full heartbeat cycle.
39
+ void this.opts.client
40
+ .rpc(url, envelope('heartbeat', this.opts.self.nodeId, { load: this.opts.getLoad(), port: this.opts.selfPort }, { to: peer.nodeId }))
41
+ .catch(() => { });
37
42
  this.opts.logger.info(`[federation] connected to ${peer.displayName} (${peer.nodeId}) @ ${url}`);
38
43
  this.opts.onPeerChange?.();
39
44
  return peer;
@@ -67,7 +72,7 @@ export class PeerMesh {
67
72
  await Promise.all(this.list().map(async (peer) => {
68
73
  const t0 = Date.now();
69
74
  try {
70
- await this.opts.client.rpc(peer.url, envelope('heartbeat', this.opts.self.nodeId, { load: this.opts.getLoad() }, { to: peer.nodeId }));
75
+ await this.opts.client.rpc(peer.url, envelope('heartbeat', this.opts.self.nodeId, { load: this.opts.getLoad(), port: this.opts.selfPort }, { to: peer.nodeId }));
71
76
  peer.latencyMs = Date.now() - t0;
72
77
  peer.lastSeenAt = Date.now();
73
78
  if (!peer.connected) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
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",