instar 1.3.399 → 1.3.401

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.
Files changed (41) hide show
  1. package/dashboard/index.html +48 -18
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +73 -5
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/QuotaAwareScheduler.d.ts +96 -0
  6. package/dist/core/QuotaAwareScheduler.d.ts.map +1 -0
  7. package/dist/core/QuotaAwareScheduler.js +134 -0
  8. package/dist/core/QuotaAwareScheduler.js.map +1 -0
  9. package/dist/core/SessionManager.d.ts +6 -0
  10. package/dist/core/SessionManager.d.ts.map +1 -1
  11. package/dist/core/SessionManager.js +6 -0
  12. package/dist/core/SessionManager.js.map +1 -1
  13. package/dist/core/SessionRefresh.d.ts +18 -1
  14. package/dist/core/SessionRefresh.d.ts.map +1 -1
  15. package/dist/core/SessionRefresh.js +4 -1
  16. package/dist/core/SessionRefresh.js.map +1 -1
  17. package/dist/core/frameworkSessionLaunch.d.ts +10 -0
  18. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  19. package/dist/core/frameworkSessionLaunch.js +10 -6
  20. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  21. package/dist/core/types.d.ts +18 -0
  22. package/dist/core/types.d.ts.map +1 -1
  23. package/dist/core/types.js.map +1 -1
  24. package/dist/scaffold/templates.d.ts.map +1 -1
  25. package/dist/scaffold/templates.js +1 -0
  26. package/dist/scaffold/templates.js.map +1 -1
  27. package/dist/server/AgentServer.d.ts +1 -0
  28. package/dist/server/AgentServer.d.ts.map +1 -1
  29. package/dist/server/AgentServer.js +1 -0
  30. package/dist/server/AgentServer.js.map +1 -1
  31. package/dist/server/routes.d.ts +2 -0
  32. package/dist/server/routes.d.ts.map +1 -1
  33. package/dist/server/routes.js +26 -0
  34. package/dist/server/routes.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/data/builtin-manifest.json +48 -48
  37. package/src/scaffold/templates.ts +1 -0
  38. package/upgrades/1.3.400.md +55 -0
  39. package/upgrades/1.3.401.md +30 -0
  40. package/upgrades/side-effects/dashboard-stream-phase3-ui.md +58 -0
  41. package/upgrades/side-effects/subscription-auth-scheduler.md +90 -0
@@ -212,9 +212,11 @@
212
212
  background: #3a2a1b;
213
213
  color: #fbbf24;
214
214
  }
215
- /* A session running on ANOTHER machine — visible, but its terminal can't
216
- stream here (open that machine's dashboard to interact). */
217
- .session-item.remote { opacity: 0.8; cursor: default; }
215
+ /* A session running on ANOTHER machine — now clickable; selecting it
216
+ streams its terminal here via the cross-machine relay (§2.2). The badge
217
+ already names the machine, so a slightly dimmed row is enough hint. */
218
+ .session-item.remote { opacity: 0.9; cursor: pointer; }
219
+ .session-item.remote:hover { opacity: 1; }
218
220
 
219
221
  .type-badge {
220
222
  padding: 1px 6px;
@@ -3582,6 +3584,7 @@
3582
3584
  let remoteSessions = [];
3583
3585
  let selfMachineNickname = null;
3584
3586
  let activeSession = null;
3587
+ let activeMachineId = null; // non-null when the active session streams from a peer (§2.2)
3585
3588
  let term = null;
3586
3589
  let fitAddon = null;
3587
3590
  let historyLinesLoaded = 2000; // matches initial subscribe capture
@@ -3677,6 +3680,12 @@
3677
3680
  ws.onopen = () => {
3678
3681
  reconnectAttempts = 0;
3679
3682
  updateConnectionStatus(true);
3683
+ // Resubscribe the active session after a (re)connect so a server
3684
+ // restart doesn't strand the terminal (§2.5). Remote sessions carry
3685
+ // their machineId so the resubscribe re-opens the relay.
3686
+ if (activeSession) {
3687
+ wsSend({ type: 'subscribe', session: activeSession, ...(activeMachineId ? { machineId: activeMachineId } : {}) });
3688
+ }
3680
3689
  };
3681
3690
 
3682
3691
  ws.onclose = () => {
@@ -3768,7 +3777,21 @@
3768
3777
  break;
3769
3778
 
3770
3779
  case 'error':
3771
- console.error('[ws]', msg.message);
3780
+ // Pool Dashboard Streaming (§2.4): every remote-stream failure state
3781
+ // is shown honestly in the terminal — never a frozen/black screen or
3782
+ // a silent swallow. Only surface errors for the session in view.
3783
+ if (msg.code && term && (msg.session === activeSession || !msg.session)) {
3784
+ const m = {
3785
+ 'machine-unreachable': '\r\n\x1b[31m--- That machine is unreachable. ---\x1b[0m\r\n',
3786
+ 'peer-stream-lost': '\r\n\x1b[33m--- Stream lost — reconnecting… ---\x1b[0m\r\n',
3787
+ 'input-not-allowed': '\r\n\x1b[33m[remote typing is disabled on this machine — turn it on in that machine\'s settings]\x1b[0m\r\n',
3788
+ 'session-transferred': `\r\n\x1b[33m--- This session moved to ${msg.newMachineId || 'another machine'} — reselect it to follow. ---\x1b[0m\r\n`,
3789
+ 'invalid-session': '\r\n\x1b[31m--- Invalid session name. ---\x1b[0m\r\n',
3790
+ 'session-not-found': '\r\n\x1b[31m--- Session not found on that machine. ---\x1b[0m\r\n',
3791
+ }[msg.code];
3792
+ if (m) { term.write(m); break; }
3793
+ }
3794
+ console.error('[ws]', msg.code || msg.message);
3772
3795
  break;
3773
3796
  }
3774
3797
  }
@@ -3845,13 +3868,12 @@
3845
3868
  el = document.createElement('div');
3846
3869
  el.className = 'session-item';
3847
3870
  el.dataset.session = sessionRowKey(session);
3848
- if (session.remote) {
3849
- // A remote session's terminal streams on ITS machine's dashboard
3850
- // clicking here can't subscribe to it, so the row is informational.
3851
- el.title = `Running on ${session.machineNickname || session.machineId || 'another machine'} — open that machine's dashboard to interact.`;
3852
- } else {
3853
- el.onclick = () => selectSession(session.tmuxSession, session);
3854
- }
3871
+ // Pool Dashboard Streaming (§2.2): remote sessions are now clickable —
3872
+ // selecting one streams it from its owning machine via the relay.
3873
+ el.title = session.remote
3874
+ ? `Running on ${session.machineNickname || session.machineId || 'another machine'} — click to stream it here.`
3875
+ : '';
3876
+ el.onclick = () => selectSession(session.tmuxSession, session);
3855
3877
  list.appendChild(el);
3856
3878
  }
3857
3879
 
@@ -3912,11 +3934,13 @@
3912
3934
 
3913
3935
  // ── Terminal ──────────────────────────────────────────────
3914
3936
  function selectSession(tmuxSession, session) {
3915
- // Unsubscribe from previous
3937
+ // Unsubscribe from previous (carry its machineId so a remote unsub routes).
3916
3938
  if (activeSession) {
3917
- wsSend({ type: 'unsubscribe', session: activeSession });
3939
+ wsSend({ type: 'unsubscribe', session: activeSession, ...(activeMachineId ? { machineId: activeMachineId } : {}) });
3918
3940
  }
3919
3941
 
3942
+ // Pool Dashboard Streaming (§2.2): a remote session streams from its owner.
3943
+ activeMachineId = session.remote ? (session.machineId || null) : null;
3920
3944
  activeSession = tmuxSession;
3921
3945
  userIsFollowing = true; // Reset scroll tracking on session switch
3922
3946
  hideResumeButton(); // Carry-over button from prior session would be misleading
@@ -3958,9 +3982,12 @@
3958
3982
  initTerminal();
3959
3983
  }
3960
3984
 
3961
- // Clear and subscribe
3985
+ // Clear and subscribe (remote → carry machineId so the server relays).
3962
3986
  term.clear();
3963
- wsSend({ type: 'subscribe', session: tmuxSession });
3987
+ if (activeMachineId) {
3988
+ term.write(`\x1b[2m[streaming from ${session.machineNickname || activeMachineId}…]\x1b[0m\r\n`);
3989
+ }
3990
+ wsSend({ type: 'subscribe', session: tmuxSession, ...(activeMachineId ? { machineId: activeMachineId } : {}) });
3964
3991
 
3965
3992
  // Update active state in sidebar
3966
3993
  renderSessionList();
@@ -4203,25 +4230,28 @@
4203
4230
  }
4204
4231
 
4205
4232
  // ── Input ────────────────────────────────────────────────
4233
+ // Remote sessions carry machineId so the server relays input to the owner
4234
+ // (which enforces its own allowRemoteInput gate; a refusal returns an
4235
+ // input-not-allowed error frame, surfaced in the terminal — never silent).
4206
4236
  function sendInput() {
4207
4237
  const input = document.getElementById('termInput');
4208
4238
  const text = input.value;
4209
4239
  if (!text || !activeSession) return;
4210
4240
 
4211
- wsSend({ type: 'input', session: activeSession, text });
4241
+ wsSend({ type: 'input', session: activeSession, text, ...(activeMachineId ? { machineId: activeMachineId } : {}) });
4212
4242
  input.value = '';
4213
4243
  input.focus();
4214
4244
  }
4215
4245
 
4216
4246
  function sendKey(key) {
4217
4247
  if (!activeSession) return;
4218
- wsSend({ type: 'key', session: activeSession, key });
4248
+ wsSend({ type: 'key', session: activeSession, key, ...(activeMachineId ? { machineId: activeMachineId } : {}) });
4219
4249
  }
4220
4250
 
4221
4251
  function sendSpecial(text) {
4222
4252
  if (!activeSession) return;
4223
4253
  // Send literal text without Enter
4224
- wsSend({ type: 'key', session: activeSession, key: text });
4254
+ wsSend({ type: 'key', session: activeSession, key: text, ...(activeMachineId ? { machineId: activeMachineId } : {}) });
4225
4255
  }
4226
4256
 
4227
4257
  // ── New Session Modal ────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQ3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA6JtD,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA84BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,GAC/F,IAAI,CAyUN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAy5TtE;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;AAaH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQ3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA6JtD,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA05BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,GAC/F,IAAI,CAyUN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAk9TtE;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"}
@@ -378,7 +378,14 @@ let _slackAdapter = null;
378
378
  // Null until startServer constructs it; the Telegram /restart handler falls
379
379
  // back to the inline kill+respawn path when null (e.g. early in boot).
380
380
  let _sessionRefresh = null;
381
- async function spawnSessionForTopic(sessionManager, telegram, sessionName, topicId, latestMessage, topicMemory, userProfile, precomputedContext) {
381
+ // Subscription & Auth Standard P1.3 quota-aware account-swap scheduler. Null
382
+ // until wired (requires SessionRefresh + the subscription pool).
383
+ let _quotaAwareScheduler = null;
384
+ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topicId, latestMessage, topicMemory, userProfile, precomputedContext,
385
+ /** Subscription & Auth Standard P1.3 (additive): launch under this account's
386
+ * config home + record its id (the quota-aware account-swap mechanism).
387
+ * Omitted = unchanged behaviour. */
388
+ accountSwap) {
382
389
  const msg = latestMessage || 'Session started — send a message to continue.';
383
390
  // If memory is elevated/critical and we have the reaper, try to free memory
384
391
  // by cleaning orphans before spawning. Interactive sessions are NEVER blocked
@@ -619,6 +626,10 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
619
626
  framework,
620
627
  ...(codexLocalProvider ? { codexLocalProvider } : {}),
621
628
  ...(codexLocalModelOverride ? { defaultModel: codexLocalModelOverride } : {}),
629
+ // Subscription & Auth Standard P1.3 (additive): account-swap — launch under
630
+ // this account's config home + record its id. Unset = unchanged.
631
+ ...(accountSwap?.configHome ? { configHome: accountSwap.configHome } : {}),
632
+ ...(accountSwap?.accountId ? { subscriptionAccountId: accountSwap.accountId } : {}),
622
633
  });
623
634
  // Clear the resume entry after successful spawn to prevent stale reuse
624
635
  if (resumeSessionId) {
@@ -696,7 +707,7 @@ async function respawnSessionForTopic(sessionManager, telegram, targetSession, t
696
707
  const effectiveMessage = recoveryPrompt
697
708
  ? `${recoveryPrompt}\n\n${latestMessage || 'Session recovered — continue where you left off.'}`
698
709
  : latestMessage;
699
- const newSessionName = await spawnSessionForTopic(sessionManager, telegram, topicName, topicId, effectiveMessage, topicMemory, userProfile);
710
+ const newSessionName = await spawnSessionForTopic(sessionManager, telegram, topicName, topicId, effectiveMessage, topicMemory, userProfile, undefined, (options?.configHome || options?.accountId) ? { configHome: options?.configHome, accountId: options?.accountId } : undefined);
700
711
  telegram.registerTopicSession(topicId, newSessionName, topicName);
701
712
  if (!options?.silent) {
702
713
  await telegram.sendToTopic(topicId, `Session respawned.`);
@@ -9261,15 +9272,72 @@ export async function startServer(options) {
9261
9272
  state,
9262
9273
  telegram: telegramRef,
9263
9274
  topicResumeMap: _topicResumeMap,
9264
- respawner: async (sessionName, topicId, followUpPrompt) => {
9275
+ respawner: async (sessionName, topicId, followUpPrompt, accountSwap) => {
9265
9276
  // killSession (called inside SessionRefresh) has already fired
9266
9277
  // beforeSessionKill (UUID persisted) and destroyed the tmux
9267
9278
  // session. respawnSessionForTopic spawns the new tmux running
9268
9279
  // `claude --resume <uuid>` and registers the topic mapping.
9269
- await respawnSessionForTopic(sessionManager, telegramRef, sessionName, topicId, followUpPrompt, topicMemory);
9280
+ // P1.3: accountSwap (when present) re-launches the resume under a
9281
+ // different account's config home — the --resume uuid is account-
9282
+ // agnostic, so the conversation is preserved across the swap.
9283
+ await respawnSessionForTopic(sessionManager, telegramRef, sessionName, topicId, followUpPrompt, topicMemory, undefined, undefined, accountSwap ? { configHome: accountSwap.configHome, accountId: accountSwap.accountId } : undefined);
9270
9284
  return telegramRef.getSessionForTopic(topicId) ?? sessionName;
9271
9285
  },
9272
9286
  });
9287
+ // ── QuotaAwareScheduler (Subscription & Auth Standard P1.3) ──
9288
+ // Selects the optimal account + enforces the continuity guarantee: on
9289
+ // quota pressure it resumes the session under another account (via the
9290
+ // SessionRefresh account-swap path), never letting it die. Auto-trigger
9291
+ // off RateLimitSentinel ships DARK behind a config flag (default off);
9292
+ // the manual route + selection logic are always available.
9293
+ const { QuotaAwareScheduler } = await import('../core/QuotaAwareScheduler.js');
9294
+ _quotaAwareScheduler = new QuotaAwareScheduler({
9295
+ listAccounts: () => subscriptionPool.list(),
9296
+ softThresholdPct: config.subscriptionPool?.swapSoftThresholdPct,
9297
+ refreshFn: async (o) => {
9298
+ if (!_sessionRefresh)
9299
+ return false;
9300
+ const r = await _sessionRefresh.refreshSession({
9301
+ sessionName: o.sessionName,
9302
+ reason: o.reason,
9303
+ configHome: o.configHome,
9304
+ accountId: o.accountId,
9305
+ });
9306
+ return r.ok;
9307
+ },
9308
+ onNoAlternate: (sessionName, exhaustedAccountId) => {
9309
+ void telegramRef.createAttentionItem({
9310
+ id: `subpool-no-alternate-${exhaustedAccountId}`,
9311
+ title: 'Subscription pool — no alternate account to swap to',
9312
+ summary: `Session "${sessionName}" hit account "${exhaustedAccountId}"'s quota and there's no other eligible account in the pool. The session falls back to the existing rate-limit back-off; consider enrolling another account.`,
9313
+ category: 'subscription-pool',
9314
+ priority: 'HIGH',
9315
+ sourceContext: 'subscription-pool:no-alternate',
9316
+ }).catch(() => { });
9317
+ },
9318
+ logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
9319
+ });
9320
+ // DARK auto-trigger: only when explicitly enabled does a rate-limit
9321
+ // escalation drive an account swap. Default off — opt-in per Justin's
9322
+ // tier-2 sign-off (auto-swapping live sessions is real authority).
9323
+ if (config.subscriptionPool?.autoSwapOnRateLimit) {
9324
+ rateLimitSentinel.on('rate-limit:escalated', (rlState) => {
9325
+ const sessionName = rlState?.sessionName;
9326
+ if (!sessionName)
9327
+ return;
9328
+ // Resolve which account this session is running under; only pool-managed
9329
+ // sessions are swap-eligible (others fall back to the existing back-off).
9330
+ const accountId = state.listSessions({ status: 'running' })
9331
+ .find(s => s.tmuxSession === sessionName)?.subscriptionAccountId;
9332
+ if (!accountId)
9333
+ return;
9334
+ void _quotaAwareScheduler?.onQuotaPressure({
9335
+ sessionName,
9336
+ exhaustedAccountId: accountId,
9337
+ nowMs: Date.now(),
9338
+ });
9339
+ });
9340
+ }
9273
9341
  }
9274
9342
  // ── SessionReaper (SESSION-REAPER-SPEC) ──────────────────────────────
9275
9343
  // Pressure-aware reaper of idle-but-alive sessions. Ships OFF + dry-run by
@@ -11214,7 +11282,7 @@ export async function startServer(options) {
11214
11282
  catch (err) {
11215
11283
  console.log(pc.dim(` [session-pool] rollout gate not wired: ${err instanceof Error ? err.message : String(err)}`));
11216
11284
  }
11217
- const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
11285
+ const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
11218
11286
  // Resolve the late-bound topic-operator getter (increment 2e): routing was
11219
11287
  // wired before the server existed; from here on inbound binds use the
11220
11288
  // server's own store instance.