instar 1.3.245 → 1.3.247

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 (67) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +147 -8
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +13 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  8. package/dist/core/PostUpdateMigrator.js +15 -1
  9. package/dist/core/PostUpdateMigrator.js.map +1 -1
  10. package/dist/core/RelocationNicknameSet.d.ts +42 -0
  11. package/dist/core/RelocationNicknameSet.d.ts.map +1 -0
  12. package/dist/core/RelocationNicknameSet.js +47 -0
  13. package/dist/core/RelocationNicknameSet.js.map +1 -0
  14. package/dist/core/SessionManager.d.ts +3 -0
  15. package/dist/core/SessionManager.d.ts.map +1 -1
  16. package/dist/core/SessionManager.js +3 -0
  17. package/dist/core/SessionManager.js.map +1 -1
  18. package/dist/core/TopicPlacementDescription.d.ts +46 -0
  19. package/dist/core/TopicPlacementDescription.d.ts.map +1 -0
  20. package/dist/core/TopicPlacementDescription.js +32 -0
  21. package/dist/core/TopicPlacementDescription.js.map +1 -0
  22. package/dist/core/frameworkInjectionProcesses.d.ts +41 -0
  23. package/dist/core/frameworkInjectionProcesses.d.ts.map +1 -0
  24. package/dist/core/frameworkInjectionProcesses.js +53 -0
  25. package/dist/core/frameworkInjectionProcesses.js.map +1 -0
  26. package/dist/core/frameworkSessionLaunch.d.ts +9 -0
  27. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  28. package/dist/core/frameworkSessionLaunch.js +7 -0
  29. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  30. package/dist/core/types.d.ts +22 -0
  31. package/dist/core/types.d.ts.map +1 -1
  32. package/dist/core/types.js.map +1 -1
  33. package/dist/messaging/SpawnRequestManager.d.ts +13 -0
  34. package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
  35. package/dist/messaging/SpawnRequestManager.js +3 -0
  36. package/dist/messaging/SpawnRequestManager.js.map +1 -1
  37. package/dist/messaging/types.d.ts.map +1 -1
  38. package/dist/messaging/types.js +8 -1
  39. package/dist/messaging/types.js.map +1 -1
  40. package/dist/scaffold/templates.d.ts.map +1 -1
  41. package/dist/scaffold/templates.js +3 -1
  42. package/dist/scaffold/templates.js.map +1 -1
  43. package/dist/server/AgentServer.d.ts +6 -0
  44. package/dist/server/AgentServer.d.ts.map +1 -1
  45. package/dist/server/AgentServer.js +3 -0
  46. package/dist/server/AgentServer.js.map +1 -1
  47. package/dist/server/routes.d.ts +11 -0
  48. package/dist/server/routes.d.ts.map +1 -1
  49. package/dist/server/routes.js +163 -0
  50. package/dist/server/routes.js.map +1 -1
  51. package/dist/threadline/ThreadlineRouter.d.ts +55 -1
  52. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
  53. package/dist/threadline/ThreadlineRouter.js +246 -6
  54. package/dist/threadline/ThreadlineRouter.js.map +1 -1
  55. package/dist/threadline/WarmSessionPool.d.ts +22 -0
  56. package/dist/threadline/WarmSessionPool.d.ts.map +1 -1
  57. package/dist/threadline/WarmSessionPool.js +38 -0
  58. package/dist/threadline/WarmSessionPool.js.map +1 -1
  59. package/package.json +1 -1
  60. package/scripts/instar-dev-precommit.js +42 -0
  61. package/src/data/builtin-manifest.json +65 -65
  62. package/src/scaffold/templates.ts +3 -1
  63. package/upgrades/1.3.246.md +97 -0
  64. package/upgrades/1.3.247.md +48 -0
  65. package/upgrades/side-effects/mm-placement-transfer-robustness.md +45 -0
  66. package/upgrades/side-effects/threadline-warm-session-a2a.md +146 -0
  67. package/upgrades/threadline-warm-session-a2a.eli16.md +63 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0SH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgxDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyqQtE;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;AA2SH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgxDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmzQtE;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"}
@@ -122,7 +122,8 @@ import { pickupDroppedMessages } from '../messaging/DropPickup.js';
122
122
  import { pickupGitSyncMessages } from '../messaging/GitSyncTransport.js';
123
123
  import { DeliveryRetryManager } from '../messaging/DeliveryRetryManager.js';
124
124
  import { SpawnRequestManager } from '../messaging/SpawnRequestManager.js';
125
- import { ThreadlineRouter } from '../threadline/ThreadlineRouter.js';
125
+ import { ThreadlineRouter, trustMeetsFloor } from '../threadline/ThreadlineRouter.js';
126
+ import { WarmSessionPool } from '../threadline/WarmSessionPool.js';
126
127
  import { resolveThreadlineMcpEntry } from '../threadline/mcpEntry.js';
127
128
  import { ThreadResumeMap } from '../threadline/ThreadResumeMap.js';
128
129
  import { ConversationStore } from '../threadline/ConversationStore.js';
@@ -7116,6 +7117,42 @@ export async function startServer(options) {
7116
7117
  ?? 5,
7117
7118
  getActiveSessions: () => sessionManager.listRunningSessions(),
7118
7119
  spawnSession: async (prompt, opts) => {
7120
+ // Warm-session A2A (Arch Y): when the SpawnRequest is flagged interactive,
7121
+ // launch a PERSISTENT (keep-alive) interactive worker instead of the
7122
+ // headless one-shot `claude -p`. The grounded prompt is delivered as the
7123
+ // worker's FIRST turn (after-ready inject), and the worker stays alive so
7124
+ // follow-ups inject into the same session. The MCP/permission flag set
7125
+ // matches the `-p` path: claude interactive keeps the PROJECT MCP (where
7126
+ // threadline_send lives) under --dangerously-skip-permissions, and codex
7127
+ // interactive launches with --dangerously-bypass-approvals-and-sandbox +
7128
+ // the per-agent threadline MCP (the bypass the `-p` codexAllowMcpTools
7129
+ // selects) — so threadline_send is available either way.
7130
+ if (opts?.interactive) {
7131
+ const warmName = `msg-warm-${Date.now()}`;
7132
+ // Framework-GENERAL: the warm worker runs in the LOCAL agent's
7133
+ // framework (claude-code / codex-cli / gemini-cli), NOT hardcoded
7134
+ // Claude. spawnInteractiveSession + frameworkSessionLaunch compose the
7135
+ // right argv + MCP/permission flags per framework (claude keeps PROJECT
7136
+ // MCP under --dangerously-skip-permissions; codex launches under
7137
+ // --dangerously-bypass-approvals-and-sandbox with its per-agent
7138
+ // threadline MCP) so threadline_send works regardless of framework.
7139
+ // A2A reply threads have no topic, so the agent default framework
7140
+ // applies (mirrors resolveTopicFramework's own fallback).
7141
+ const tmuxSession = await sessionManager.spawnInteractiveSession(prompt, warmName, {
7142
+ framework: _defaultFramework,
7143
+ // Deterministic conversation id for lossless eviction→resume (#746).
7144
+ // frameworkSessionLaunch maps this per framework: claude-code pins
7145
+ // `--session-id`; codex/gemini ignore it (they resume by their own
7146
+ // mechanism). NOT a Claude assumption — it's the abstraction's job.
7147
+ sessionId: opts?.sessionId,
7148
+ });
7149
+ // spawnInteractiveSession returns the tmux name; resolve the instar
7150
+ // session id from the running set so the warm callback returns the
7151
+ // slice-2 {sessionId, tmuxSession} shape spawnWarmThread expects.
7152
+ const running = sessionManager.listRunningSessions();
7153
+ const rec = running.find(s => s.tmuxSession === tmuxSession);
7154
+ return { sessionId: rec?.id ?? tmuxSession, tmuxSession };
7155
+ }
7119
7156
  const session = await sessionManager.spawnSession({
7120
7157
  name: `msg-spawn-${Date.now()}`,
7121
7158
  prompt,
@@ -7222,9 +7259,66 @@ export async function startServer(options) {
7222
7259
  else {
7223
7260
  console.log('[spawn-manager] drain loop disabled by config.threadline.spawn.drainEnabled=false');
7224
7261
  }
7262
+ // Warm-Session A2A (Arch Y, dark-ship) — keep each A2A reply thread's session
7263
+ // alive between messages so follow-ups inject into the live worker instead of
7264
+ // cold-spawning. Resolved via the developmentAgent gate: `enabled ?? !!dev` →
7265
+ // LIVE on Echo, DARK on the fleet. The pool is null when disabled, so the
7266
+ // ThreadlineRouter falls back to the proven cold-spawn path byte-for-byte.
7267
+ const warmCfg = config.threadline?.warmSessionA2A;
7268
+ const warmEnabled = warmCfg?.enabled ?? !!config.developmentAgent;
7269
+ const warmSessionPool = warmEnabled
7270
+ ? new WarmSessionPool({
7271
+ globalCap: warmCfg?.globalCap ?? 3,
7272
+ perPeerCap: warmCfg?.perPeerCap ?? 1,
7273
+ ttlMs: warmCfg?.ttlMs ?? 600000,
7274
+ })
7275
+ : null;
7276
+ const warmTrustFloor = warmCfg?.trustFloor ?? 'verified';
7277
+ // Server-owned kill primitive: resolve the tmux NAME → instar session id →
7278
+ // killSession (sessions are filed by id, not tmux name). Used for cap-eviction
7279
+ // on admit and the periodic reap tick. Never throws to the caller.
7280
+ const killWarmSessionByName = (sessionName) => {
7281
+ try {
7282
+ const rec = sessionManager.listRunningSessions().find(s => s.tmuxSession === sessionName);
7283
+ if (rec)
7284
+ sessionManager.killSession(rec.id);
7285
+ else
7286
+ console.log(`[warm-session] kill requested for ${sessionName} but no running session matched (already gone?)`);
7287
+ }
7288
+ catch (err) {
7289
+ console.warn(`[warm-session] kill ${sessionName} failed: ${err instanceof Error ? err.message : String(err)}`);
7290
+ }
7291
+ };
7292
+ if (warmEnabled) {
7293
+ console.log(`[warm-session] A2A keep-alive ENABLED (globalCap=${warmCfg?.globalCap ?? 3}, perPeerCap=${warmCfg?.perPeerCap ?? 1}, ttlMs=${warmCfg?.ttlMs ?? 600000}, trustFloor=${warmTrustFloor})`);
7294
+ }
7225
7295
  // Threadline Router — handles threaded cross-agent conversations via relay
7226
7296
  const threadlineRouter = new ThreadlineRouter(messageRouter, spawnManager, threadResumeMap, messageStore, { localAgent: config.projectName, localMachine: os.hostname() }, null, // autonomyGate
7227
- messageDelivery);
7297
+ messageDelivery, // PR-4: live-session injection path
7298
+ undefined, // onLedgerEvent (wired below via registerLedgerEmitters if present)
7299
+ undefined, // nowFn
7300
+ warmSessionPool, // Warm-Session A2A (null when disabled)
7301
+ warmEnabled, warmTrustFloor, warmEnabled ? killWarmSessionByName : null);
7302
+ // Warm-Session A2A reap tick — kill warm sessions idle past their TTL so a
7303
+ // flood can't pin processes. Gated on warmEnabled; .unref() so it never holds
7304
+ // the process open; cleared on shutdown. Eviction is lossless (the next
7305
+ // message resumes via #746).
7306
+ let warmReapTimer = null;
7307
+ if (warmEnabled && warmSessionPool) {
7308
+ warmReapTimer = setInterval(() => {
7309
+ try {
7310
+ const expired = warmSessionPool.reapExpired();
7311
+ for (const rec of expired) {
7312
+ console.log(`[warm-session] reaping idle warm session ${rec.sessionName} (thread ${rec.threadId})`);
7313
+ killWarmSessionByName(rec.sessionName);
7314
+ }
7315
+ }
7316
+ catch (err) {
7317
+ console.warn(`[warm-session] reap tick failed: ${err instanceof Error ? err.message : String(err)}`);
7318
+ }
7319
+ }, 60_000);
7320
+ warmReapTimer.unref();
7321
+ }
7228
7322
  sessionManager.on('sessionComplete', (session) => {
7229
7323
  const sessionName = session.tmuxSession || session.name;
7230
7324
  if (!sessionName)
@@ -7784,11 +7878,19 @@ export async function startServer(options) {
7784
7878
  transport: { protocol: 'relay', origin: { agent: senderFingerprint, machine: 'relay' }, nonce: `${crypto.randomUUID()}:${new Date().toISOString()}`, timestamp: new Date().toISOString() },
7785
7879
  delivery: { status: 'delivered', attempts: 1, lastAttempt: new Date().toISOString() },
7786
7880
  };
7881
+ // Warm-Session A2A (Arch Y, dark-ship): mark this inbound as eligible for
7882
+ // a keep-alive interactive worker when the feature is on, it's NOT a
7883
+ // topic-bound reply (those go to TopicLinkageHandler), and the peer meets
7884
+ // the trust floor. Trust compare via the explicit ordering array
7885
+ // (trustMeetsFloor), NEVER string `>=`. When false (or feature off), the
7886
+ // router takes the proven cold-spawn path byte-for-byte.
7887
+ const preferWarmSession = warmEnabled && !isTopicBoundReply && trustMeetsFloor(trustLevel, warmTrustFloor);
7787
7888
  const relayContext = {
7788
7889
  trust: { kind: 'plaintext-tofu', senderFingerprint },
7789
7890
  senderFingerprint,
7790
7891
  senderName,
7791
7892
  trustLevel,
7893
+ preferWarmSession,
7792
7894
  };
7793
7895
  let result = await threadlineRouter.handleInboundMessage(envelope, relayContext);
7794
7896
  // Fallback for threadId-less messages
@@ -9246,17 +9348,50 @@ export async function startServer(options) {
9246
9348
  const nickMod = await import('../core/NicknameCommand.js');
9247
9349
  const transferMod = await import('../core/TransferByNickname.js');
9248
9350
  const pinMod = await import('../core/TopicPlacementPinStore.js');
9351
+ const relocSetMod = await import('../core/RelocationNicknameSet.js');
9352
+ const nickAssignMod = await import('../core/NicknameAssigner.js');
9249
9353
  _topicPinStore = new pinMod.TopicPlacementPinStore({
9250
9354
  filePath: path.join(config.stateDir, 'session-pool', 'topic-pins.json'),
9251
9355
  });
9356
+ // Authoritative resolver for THIS machine's OWN nickname. Guarantees a topic
9357
+ // can always be moved BACK to the machine currently handling it, even when the
9358
+ // capacities view omits the self nickname (the back-transfer bug: the lifeline
9359
+ // forwards inbound to the holder, so the relocation check runs on the very
9360
+ // machine the user is moving back to). Priority: capacities self-entry →
9361
+ // identity registry self-entry → deterministic derive (reconstructs the
9362
+ // auto-assigned nickname; a manual rename is persisted on the identity entry).
9363
+ const resolveSelfNickname = (caps) => {
9364
+ const fromCaps = caps.find((c) => c.machineId === meshSelfId)?.nickname;
9365
+ if (fromCaps)
9366
+ return fromCaps;
9367
+ try {
9368
+ const selfEntry = meshIdMgr
9369
+ .getActiveMachines()
9370
+ .find((m) => m.machineId === meshSelfId)?.entry;
9371
+ if (selfEntry?.nickname)
9372
+ return selfEntry.nickname;
9373
+ const id = meshIdMgr.hasIdentity()
9374
+ ? meshIdMgr.loadIdentity()
9375
+ : null;
9376
+ const derived = nickAssignMod.deriveBaseNickname(id?.name, selfEntry?.hardware?.platform);
9377
+ if (derived)
9378
+ return derived;
9379
+ }
9380
+ catch {
9381
+ /* @silent-fallback-ok — best-effort self-nickname resolution; on any failure we
9382
+ return undefined and the recognizer simply falls back to the capacities-derived
9383
+ set (the pre-existing behavior), so this can only ever ADD a nickname, never break. */
9384
+ }
9385
+ return undefined;
9386
+ };
9252
9387
  _tryNicknameRelocation = async (topicId, text) => {
9253
9388
  const sessionKey = String(topicId);
9254
9389
  const caps = machinePoolRegistry?.getCapacities() ?? [];
9255
- const nickToMachine = new Map();
9256
- for (const c of caps)
9257
- if (c.nickname)
9258
- nickToMachine.set(c.nickname.toLowerCase(), c.machineId);
9259
- const knownNicknames = caps.map((c) => c.nickname).filter((n) => !!n);
9390
+ const { knownNicknames, nickToMachine } = relocSetMod.buildRelocationNicknameSet({
9391
+ capacities: caps,
9392
+ selfMachineId: meshSelfId,
9393
+ selfNickname: resolveSelfNickname(caps),
9394
+ });
9260
9395
  const cmd = nickMod.recognizeNicknameCommand(text, knownNicknames);
9261
9396
  if (!cmd)
9262
9397
  return { handled: false };
@@ -9372,7 +9507,7 @@ export async function startServer(options) {
9372
9507
  catch (err) {
9373
9508
  console.log(pc.dim(` [session-pool] rollout gate not wired: ${err instanceof Error ? err.message : String(err)}`));
9374
9509
  }
9375
- 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, 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, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, sessionOwnershipRegistry, 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 });
9510
+ 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, 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, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? 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 });
9376
9511
  // Boot-recovery (tunnel-failure-resilience spec Part 6): if the agent
9377
9512
  // died mid-relay-episode, the persisted tunnel.json carries
9378
9513
  // rotationPending=true. Rotate the dashboard PIN + authToken BEFORE
@@ -9738,6 +9873,10 @@ export async function startServer(options) {
9738
9873
  await notificationBatcher.flushAll(); // Drain pending notifications before exit
9739
9874
  notificationBatcher.stop();
9740
9875
  retryManager.stop();
9876
+ if (warmReapTimer) {
9877
+ clearInterval(warmReapTimer);
9878
+ warmReapTimer = null;
9879
+ } // Warm-Session A2A reap tick
9741
9880
  spawnManager.dispose(); // §4.4: stop drain loop + clear DRR state
9742
9881
  summarySentinel.stop();
9743
9882
  memoryMonitor.stop();