instar 1.3.103 → 1.3.104

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;AAmSH,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,CAu3OtE;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;AAmSH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAwvDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAo7OtE;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"}
@@ -308,6 +308,10 @@ let _topicResumeMap = null;
308
308
  // is byte-identical to today's always-local dispatch. Set once in startServer().
309
309
  let _sessionRouter = null;
310
310
  let _sessionPoolStage = () => 'dark';
311
+ /** Multi-Machine Session Pool §L4: per-topic placement pin store ("move this to <nickname>"). */
312
+ let _topicPinStore = null;
313
+ /** Recognize + apply a "move/run this on <nickname>" relocation on inbound; returns handled=true when the message WAS a relocation command (so it must not also be dispatched). */
314
+ let _tryNicknameRelocation = null;
311
315
  /** Per-topic framework override (claude-code | codex-cli). Populated from
312
316
  * `config.topicFrameworks` at server boot. Boot-immutable; runtime
313
317
  * mutations go through `_topicFrameworksStore` instead so they persist
@@ -1173,12 +1177,29 @@ getHubDeps) {
1173
1177
  // entirely → byte-identical to single-machine dispatch. Any error falls back
1174
1178
  // to the local path below (fail-safe). The live-transfer behavior is gated by
1175
1179
  // the staged rollout (StageAdvancer); shadow records ownership but stays local.
1180
+ //
1181
+ // §L4 transfer-by-nickname: FIRST, intercept an explicit "move/run this on
1182
+ // <nickname>" relocation command. If recognized, it sets the topic's
1183
+ // placement pin (+ releases local ownership) and is fully handled here — the
1184
+ // command message itself is NOT dispatched to a session. Subsequent messages
1185
+ // for the topic then re-place onto the pinned machine via route() below.
1186
+ if (_tryNicknameRelocation && _sessionPoolStage() !== 'dark') {
1187
+ try {
1188
+ const relo = await _tryNicknameRelocation(topicId, text);
1189
+ if (relo.handled)
1190
+ return;
1191
+ }
1192
+ catch (err) {
1193
+ console.warn(`[session-pool] nickname relocation error for topic ${topicId}: ${err instanceof Error ? err.message : String(err)}`);
1194
+ }
1195
+ }
1176
1196
  if (_sessionRouter && _sessionPoolStage() !== 'dark') {
1177
1197
  try {
1178
1198
  const outcome = await _sessionRouter.route({
1179
1199
  sessionKey: String(topicId),
1180
1200
  messageId: String(msg.id),
1181
1201
  payload: text,
1202
+ topicMetadata: _topicPinStore?.asTopicMetadata(String(topicId)),
1182
1203
  });
1183
1204
  if (outcome.action === 'forwarded' || outcome.action === 'duplicate') {
1184
1205
  console.log(`[session-pool] topic ${topicId} handled by owner ${outcome.owner ?? '?'} (${outcome.action}) — not dispatching locally`);
@@ -8507,6 +8528,66 @@ export async function startServer(options) {
8507
8528
  }
8508
8529
  };
8509
8530
  console.log(pc.green(' SessionRouter wired (L4) — inert until rollout stage advances past dark'));
8531
+ // ── L4 transfer-by-nickname activation: the "move/run this on <nickname>"
8532
+ // trigger. The recognizer + planner are pure units; this wires them to the
8533
+ // pin store + ownership so a recognized command pins the topic to the named
8534
+ // machine and (if we currently own it) releases it, so the topic's next
8535
+ // routed message re-places onto the pinned machine via the already-wired
8536
+ // placeAndClaim → spawnOnMachine → owner-side onAccepted resume path.
8537
+ // Inert unless the rollout stage is past 'dark' (gated at the call site).
8538
+ const nickMod = await import('../core/NicknameCommand.js');
8539
+ const transferMod = await import('../core/TransferByNickname.js');
8540
+ const pinMod = await import('../core/TopicPlacementPinStore.js');
8541
+ _topicPinStore = new pinMod.TopicPlacementPinStore({
8542
+ filePath: path.join(config.stateDir, 'session-pool', 'topic-pins.json'),
8543
+ });
8544
+ _tryNicknameRelocation = async (topicId, text) => {
8545
+ const sessionKey = String(topicId);
8546
+ const caps = machinePoolRegistry?.getCapacities() ?? [];
8547
+ const nickToMachine = new Map();
8548
+ for (const c of caps)
8549
+ if (c.nickname)
8550
+ nickToMachine.set(c.nickname.toLowerCase(), c.machineId);
8551
+ const knownNicknames = caps.map((c) => c.nickname).filter((n) => !!n);
8552
+ const cmd = nickMod.recognizeNicknameCommand(text, knownNicknames);
8553
+ if (!cmd)
8554
+ return { handled: false };
8555
+ const plan = transferMod.planTransferByNickname(cmd, {
8556
+ resolveNickname: (n) => nickToMachine.get(n.toLowerCase()) ?? null,
8557
+ validNicknames: () => knownNicknames,
8558
+ isOnline: (m) => machinePoolRegistry?.getCapacity(m)?.online ?? false,
8559
+ currentOwnerOf: (sk) => ownReg.ownerOf(sk),
8560
+ isMidReply: () => false, // best-effort; the pin takes effect on the next routed message
8561
+ lastPlacementUpdateAt: (sk) => _topicPinStore?.lastUpdatedAtMs(sk) ?? null,
8562
+ now: () => Date.now(),
8563
+ }, sessionKey);
8564
+ if (plan.action === 'transfer' || plan.action === 'noop') {
8565
+ const target = plan.targetMachine;
8566
+ _topicPinStore.set(sessionKey, target, plan.setPin ?? true);
8567
+ // If THIS machine actively owns the topic, release so the next message re-places to the pin.
8568
+ try {
8569
+ if (ownReg.ownerOf(sessionKey) === meshSelfId) {
8570
+ ownReg.cas({ type: 'release', machineId: meshSelfId }, { sessionKey, sender: meshSelfId, nonce: `${meshSelfId}:rel:${sessionKey}:${Math.round(performance.now())}` });
8571
+ }
8572
+ }
8573
+ catch { /* best-effort; route() re-places regardless once the owner is cleared */ }
8574
+ await telegram?.sendToTopic(topicId, plan.action === 'noop'
8575
+ ? `This conversation is already pinned to ${cmd.nickname} — it'll keep running there.`
8576
+ : `Moving this conversation to ${cmd.nickname} — it'll pick up there on your next message.`).catch(() => { });
8577
+ console.log(pc.green(` [session-pool] topic ${topicId} pinned to ${target} (${plan.action}) via "${cmd.matchedVerb}"`));
8578
+ return { handled: true };
8579
+ }
8580
+ if (plan.action === 'confirm-required') {
8581
+ await telegram?.sendToTopic(topicId, plan.confirmationPrompt ?? `That machine isn't reachable right now — say "yes, move it" to confirm.`).catch(() => { });
8582
+ return { handled: true };
8583
+ }
8584
+ // reject
8585
+ const validList = (plan.validNicknames ?? []).join(', ');
8586
+ await telegram?.sendToTopic(topicId, plan.rejectReason === 'unknown-machine-nickname'
8587
+ ? `I don't know a machine called "${plan.detail}". I can move this to: ${validList || '(no other machines available)'}.`
8588
+ : `I can't move this right now (${plan.rejectReason}).`).catch(() => { });
8589
+ return { handled: true };
8590
+ };
8510
8591
  }
8511
8592
  catch (err) {
8512
8593
  console.log(pc.dim(` [session-router] not wired: ${err instanceof Error ? err.message : String(err)}`));