instar 1.3.92 → 1.3.93

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.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkSH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAksDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAqtOtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkSH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAquDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8yOtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -298,6 +298,15 @@ let _fixDeps = null;
298
298
  // Module-level reference for session resume mapping.
299
299
  // Set once in startServer() and used by spawnSessionForTopic/respawnSessionForTopic.
300
300
  let _topicResumeMap = null;
301
+ // ── Multi-Machine Session Pool (§L4) activation refs ──────────────────────
302
+ // The SessionRouter is constructed in startServer()'s mesh block, but the inbound
303
+ // dispatch handler that consults it is defined ABOVE startServer — so the router is
304
+ // shared via this module-level ref (the same pattern as _orphanReaper/_topicResumeMap).
305
+ // `_sessionPoolStage()` reads the live rollout stage; the inbound interception only
306
+ // routes through the pool when it returns a non-'dark' stage, so DARK (the default)
307
+ // is byte-identical to today's always-local dispatch. Set once in startServer().
308
+ let _sessionRouter = null;
309
+ let _sessionPoolStage = () => 'dark';
301
310
  /** Per-topic framework override (claude-code | codex-cli). Populated from
302
311
  * `config.topicFrameworks` at server boot. Boot-immutable; runtime
303
312
  * mutations go through `_topicFrameworksStore` instead so they persist
@@ -1156,6 +1165,31 @@ getHubDeps) {
1156
1165
  const pipeline = messageToPipeline(msg, storedTopicName);
1157
1166
  // Route message to corresponding session
1158
1167
  const targetSession = telegram.getSessionForTopic(topicId);
1168
+ // ── Multi-Machine Session Pool (§L4): route through the pool when active ──
1169
+ // When the rollout stage is past 'dark', consult the SessionRouter: it may
1170
+ // forward this topic's message to the machine that OWNS the session (over the
1171
+ // mesh) instead of handling it locally. DARK (the default) skips this block
1172
+ // entirely → byte-identical to single-machine dispatch. Any error falls back
1173
+ // to the local path below (fail-safe). The live-transfer behavior is gated by
1174
+ // the staged rollout (StageAdvancer); shadow records ownership but stays local.
1175
+ if (_sessionRouter && _sessionPoolStage() !== 'dark') {
1176
+ try {
1177
+ const outcome = await _sessionRouter.route({
1178
+ sessionKey: String(topicId),
1179
+ messageId: String(msg.id),
1180
+ payload: text,
1181
+ });
1182
+ if (outcome.action === 'forwarded' || outcome.action === 'duplicate') {
1183
+ console.log(`[session-pool] topic ${topicId} handled by owner ${outcome.owner ?? '?'} (${outcome.action}) — not dispatching locally`);
1184
+ return;
1185
+ }
1186
+ // 'handled-locally' / 'spawned'(self) / 'owner-dead-replaced' / 'queued' /
1187
+ // 'placement-blocked' → fall through to the existing local dispatch below.
1188
+ }
1189
+ catch (err) {
1190
+ console.warn(`[session-pool] route error for topic ${topicId} — falling back to local dispatch: ${err instanceof Error ? err.message : String(err)}`);
1191
+ }
1192
+ }
1159
1193
  if (targetSession) {
1160
1194
  // Session is mapped — check if it's alive, inject or respawn
1161
1195
  if (sessionManager.isSessionAlive(targetSession)) {
@@ -8283,6 +8317,32 @@ export async function startServer(options) {
8283
8317
  deliverSeenFallback.add(messageId);
8284
8318
  return true;
8285
8319
  },
8320
+ // Owner-side bridge (§L4 handoff): a forwarded message landed → spawn/resume
8321
+ // the local session for the topic so the conversation continues on THIS machine.
8322
+ // Only fires for a FIRST-seen forwarded deliverMessage (the ledger dedupes
8323
+ // redeliveries), and a deliverMessage only arrives from a router peer — but we
8324
+ // double-gate on stage!=='dark' to be safe. Fire-and-forget: the durable receipt
8325
+ // is already recorded + ACKed before this runs.
8326
+ onAccepted: (cmd) => {
8327
+ if (_sessionPoolStage() === 'dark' || !telegram)
8328
+ return;
8329
+ const tg = telegram;
8330
+ const topicId = Number(cmd.session);
8331
+ if (!Number.isFinite(topicId))
8332
+ return;
8333
+ const text = typeof cmd.payload === 'string'
8334
+ ? cmd.payload
8335
+ : (cmd.payload && typeof cmd.payload === 'object' && 'text' in cmd.payload)
8336
+ ? String(cmd.payload.text)
8337
+ : undefined;
8338
+ const sessionName = tg.getSessionForTopic(topicId) ?? `topic-${topicId}`;
8339
+ void spawnSessionForTopic(sessionManager, tg, sessionName, topicId, text, undefined, undefined)
8340
+ .then((name) => {
8341
+ tg.registerTopicSession(topicId, name, sessionName);
8342
+ console.log(pc.green(` [session-pool] owner-side resume for forwarded topic ${topicId} → ${name}`));
8343
+ })
8344
+ .catch((err) => console.warn(` [session-pool] owner-side resume failed for topic ${topicId}: ${err instanceof Error ? err.message : String(err)}`));
8345
+ },
8286
8346
  });
8287
8347
  meshRpcDispatcher = new meshMod.MeshRpcDispatcher({
8288
8348
  verify: {
@@ -8328,6 +8388,76 @@ export async function startServer(options) {
8328
8388
  },
8329
8389
  logger: (m) => console.log(pc.dim(` ${m}`)),
8330
8390
  });
8391
+ // ── L4 SessionRouter (activation transport) — shared via _sessionRouter ──
8392
+ // Constructed with the real registry/ownership/placement + the outbound
8393
+ // MeshRpcClient so it can reach peers. INERT until the rollout stage advances
8394
+ // past 'dark': the inbound dispatch interception only consults it when
8395
+ // _sessionPoolStage() !== 'dark'. Single-machine → placement keeps everything local.
8396
+ try {
8397
+ const routerMod = await import('../core/SessionRouter.js');
8398
+ const placeMod = await import('../core/PlacementExecutor.js');
8399
+ const clientMod = await import('../core/MeshRpcClient.js');
8400
+ let routerNonce = 0;
8401
+ const meshClient = new clientMod.MeshRpcClient({
8402
+ selfMachineId: meshSelfId,
8403
+ sign: (c) => idMod.sign(c, localSigningKeyPem),
8404
+ nonce: () => `${meshSelfId}:r:${Date.now()}:${++routerNonce}`,
8405
+ now: () => Date.now(),
8406
+ });
8407
+ const peerUrl = (machineId) => meshIdMgr.getActiveMachines().find((m) => m.machineId === machineId)?.entry.lastKnownUrl ?? null;
8408
+ _sessionRouter = new routerMod.SessionRouter({
8409
+ selfMachineId: meshSelfId,
8410
+ placement: new placeMod.PlacementExecutor(),
8411
+ machineRegistry: () => machinePoolRegistry?.getCapacities() ?? [],
8412
+ resolveOwnership: (sk) => {
8413
+ const r = ownReg.read(sk);
8414
+ if (!r)
8415
+ return { owner: null, epoch: 0, status: null };
8416
+ const status = r.status === 'released' ? null : r.status;
8417
+ return { owner: ownReg.ownerOf(sk), epoch: r.ownershipEpoch, status, target: ownReg.placementTargetOf(sk) ?? undefined };
8418
+ },
8419
+ isMachineAlive: (m) => m === meshSelfId || (machinePoolRegistry?.getCapacity(m)?.online ?? false),
8420
+ casClaimOwnership: (sk, machineId) => {
8421
+ const r = ownReg.cas({ type: 'place', machineId }, { sessionKey: sk, sender: meshSelfId, nonce: `${meshSelfId}:c:${++routerNonce}` });
8422
+ return { ok: r.ok, epoch: ownReg.read(sk)?.ownershipEpoch ?? 0 };
8423
+ },
8424
+ deliverMessage: async (target, env) => {
8425
+ const url = peerUrl(target);
8426
+ if (!url)
8427
+ throw new Error(`no peer url for ${target}`);
8428
+ const res = await meshClient.send({ machineId: target, url }, { type: 'deliverMessage', session: env.sessionKey, messageId: env.messageId, payload: env.payload, ownershipEpoch: env.ownershipEpoch }, env.ownershipEpoch);
8429
+ if (res.ok && res.result && typeof res.result === 'object' && 'accepted' in res.result) {
8430
+ return res.result;
8431
+ }
8432
+ throw new Error(`deliverMessage rejected: ${res.status} ${res.reason ?? ''}`);
8433
+ },
8434
+ spawnOnMachine: async (machineId, msg) => {
8435
+ const url = peerUrl(machineId);
8436
+ if (!url)
8437
+ throw new Error(`no peer url for ${machineId}`);
8438
+ await meshClient.send({ machineId, url }, { type: 'deliverMessage', session: msg.sessionKey, messageId: msg.messageId, payload: msg.payload, ownershipEpoch: 0 }, 0);
8439
+ },
8440
+ handleLocally: async () => { },
8441
+ queueMessage: () => { },
8442
+ raiseAttention: (title, body) => console.log(pc.dim(` [session-router] attention: ${title} — ${body}`)),
8443
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
8444
+ log: (line) => console.log(pc.dim(` [session-router] ${line}`)),
8445
+ });
8446
+ _sessionPoolStage = () => {
8447
+ try {
8448
+ const fallback = (config.multiMachine?.sessionPool ?? {});
8449
+ const live = liveConfig.get('multiMachine.sessionPool', fallback);
8450
+ return live?.enabled && live?.stage ? String(live.stage) : 'dark';
8451
+ }
8452
+ catch {
8453
+ return 'dark';
8454
+ }
8455
+ };
8456
+ console.log(pc.green(' SessionRouter wired (L4) — inert until rollout stage advances past dark'));
8457
+ }
8458
+ catch (err) {
8459
+ console.log(pc.dim(` [session-router] not wired: ${err instanceof Error ? err.message : String(err)}`));
8460
+ }
8331
8461
  }
8332
8462
  }
8333
8463
  catch (err) {