instar 0.26.11 → 0.27.2

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 (161) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/init.d.ts +5 -0
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +46 -1
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/server.d.ts.map +1 -1
  7. package/dist/commands/server.js +228 -36
  8. package/dist/commands/server.js.map +1 -1
  9. package/dist/commands/slack-cli.d.ts +6 -0
  10. package/dist/commands/slack-cli.d.ts.map +1 -1
  11. package/dist/commands/slack-cli.js +99 -1
  12. package/dist/commands/slack-cli.js.map +1 -1
  13. package/dist/core/GitSync.d.ts.map +1 -1
  14. package/dist/core/GitSync.js +47 -5
  15. package/dist/core/GitSync.js.map +1 -1
  16. package/dist/core/PostUpdateMigrator.d.ts +7 -1
  17. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  18. package/dist/core/PostUpdateMigrator.js +19 -1
  19. package/dist/core/PostUpdateMigrator.js.map +1 -1
  20. package/dist/core/types.d.ts +9 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/identity/IdentityManager.d.ts +70 -0
  23. package/dist/identity/IdentityManager.d.ts.map +1 -0
  24. package/dist/identity/IdentityManager.js +179 -0
  25. package/dist/identity/IdentityManager.js.map +1 -0
  26. package/dist/identity/KeyEncryption.d.ts +40 -0
  27. package/dist/identity/KeyEncryption.d.ts.map +1 -0
  28. package/dist/identity/KeyEncryption.js +93 -0
  29. package/dist/identity/KeyEncryption.js.map +1 -0
  30. package/dist/identity/KeyRevocation.d.ts +68 -0
  31. package/dist/identity/KeyRevocation.d.ts.map +1 -0
  32. package/dist/identity/KeyRevocation.js +171 -0
  33. package/dist/identity/KeyRevocation.js.map +1 -0
  34. package/dist/identity/KeyRotation.d.ts +43 -0
  35. package/dist/identity/KeyRotation.d.ts.map +1 -0
  36. package/dist/identity/KeyRotation.js +81 -0
  37. package/dist/identity/KeyRotation.js.map +1 -0
  38. package/dist/identity/Migration.d.ts +50 -0
  39. package/dist/identity/Migration.d.ts.map +1 -0
  40. package/dist/identity/Migration.js +125 -0
  41. package/dist/identity/Migration.js.map +1 -0
  42. package/dist/identity/RecoveryPhrase.d.ts +43 -0
  43. package/dist/identity/RecoveryPhrase.d.ts.map +1 -0
  44. package/dist/identity/RecoveryPhrase.js +93 -0
  45. package/dist/identity/RecoveryPhrase.js.map +1 -0
  46. package/dist/identity/index.d.ts +13 -0
  47. package/dist/identity/index.d.ts.map +1 -0
  48. package/dist/identity/index.js +20 -0
  49. package/dist/identity/index.js.map +1 -0
  50. package/dist/identity/types.d.ts +101 -0
  51. package/dist/identity/types.d.ts.map +1 -0
  52. package/dist/identity/types.js +55 -0
  53. package/dist/identity/types.js.map +1 -0
  54. package/dist/knowledge/TreeTriage.d.ts.map +1 -1
  55. package/dist/knowledge/TreeTriage.js +9 -0
  56. package/dist/knowledge/TreeTriage.js.map +1 -1
  57. package/dist/memory/TopicMemory.d.ts +2 -0
  58. package/dist/memory/TopicMemory.d.ts.map +1 -1
  59. package/dist/memory/TopicMemory.js +55 -0
  60. package/dist/memory/TopicMemory.js.map +1 -1
  61. package/dist/messaging/slack/SlackAdapter.d.ts +15 -4
  62. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  63. package/dist/messaging/slack/SlackAdapter.js +131 -14
  64. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  65. package/dist/messaging/slack/SocketModeClient.d.ts +2 -0
  66. package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -1
  67. package/dist/messaging/slack/SocketModeClient.js +31 -1
  68. package/dist/messaging/slack/SocketModeClient.js.map +1 -1
  69. package/dist/moltbridge/MoltBridgeClient.d.ts +124 -0
  70. package/dist/moltbridge/MoltBridgeClient.d.ts.map +1 -0
  71. package/dist/moltbridge/MoltBridgeClient.js +321 -0
  72. package/dist/moltbridge/MoltBridgeClient.js.map +1 -0
  73. package/dist/moltbridge/ProfileCompiler.d.ts +68 -0
  74. package/dist/moltbridge/ProfileCompiler.d.ts.map +1 -0
  75. package/dist/moltbridge/ProfileCompiler.js +317 -0
  76. package/dist/moltbridge/ProfileCompiler.js.map +1 -0
  77. package/dist/moltbridge/index.d.ts +12 -0
  78. package/dist/moltbridge/index.d.ts.map +1 -0
  79. package/dist/moltbridge/index.js +11 -0
  80. package/dist/moltbridge/index.js.map +1 -0
  81. package/dist/moltbridge/routes.d.ts +21 -0
  82. package/dist/moltbridge/routes.d.ts.map +1 -0
  83. package/dist/moltbridge/routes.js +224 -0
  84. package/dist/moltbridge/routes.js.map +1 -0
  85. package/dist/moltbridge/types.d.ts +93 -0
  86. package/dist/moltbridge/types.d.ts.map +1 -0
  87. package/dist/moltbridge/types.js +20 -0
  88. package/dist/moltbridge/types.js.map +1 -0
  89. package/dist/monitoring/PresenceProxy.d.ts +3 -0
  90. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  91. package/dist/monitoring/PresenceProxy.js +80 -3
  92. package/dist/monitoring/PresenceProxy.js.map +1 -1
  93. package/dist/monitoring/SessionMonitor.d.ts +7 -0
  94. package/dist/monitoring/SessionMonitor.d.ts.map +1 -1
  95. package/dist/monitoring/SessionMonitor.js +47 -22
  96. package/dist/monitoring/SessionMonitor.js.map +1 -1
  97. package/dist/monitoring/TriageOrchestrator.d.ts.map +1 -1
  98. package/dist/monitoring/TriageOrchestrator.js +25 -1
  99. package/dist/monitoring/TriageOrchestrator.js.map +1 -1
  100. package/dist/scaffold/templates.d.ts.map +1 -1
  101. package/dist/scaffold/templates.js +29 -1
  102. package/dist/scaffold/templates.js.map +1 -1
  103. package/dist/server/AgentServer.d.ts +1 -0
  104. package/dist/server/AgentServer.d.ts.map +1 -1
  105. package/dist/server/AgentServer.js +1 -0
  106. package/dist/server/AgentServer.js.map +1 -1
  107. package/dist/server/routes.d.ts +2 -0
  108. package/dist/server/routes.d.ts.map +1 -1
  109. package/dist/server/routes.js +21 -0
  110. package/dist/server/routes.js.map +1 -1
  111. package/dist/threadline/AuthorizationPolicy.d.ts +110 -0
  112. package/dist/threadline/AuthorizationPolicy.d.ts.map +1 -0
  113. package/dist/threadline/AuthorizationPolicy.js +226 -0
  114. package/dist/threadline/AuthorizationPolicy.js.map +1 -0
  115. package/dist/threadline/DiscoveryWaterfall.d.ts +73 -0
  116. package/dist/threadline/DiscoveryWaterfall.d.ts.map +1 -0
  117. package/dist/threadline/DiscoveryWaterfall.js +113 -0
  118. package/dist/threadline/DiscoveryWaterfall.js.map +1 -0
  119. package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
  120. package/dist/threadline/ListenerSessionManager.js +2 -0
  121. package/dist/threadline/ListenerSessionManager.js.map +1 -1
  122. package/dist/threadline/MessageSecurity.d.ts +49 -0
  123. package/dist/threadline/MessageSecurity.d.ts.map +1 -0
  124. package/dist/threadline/MessageSecurity.js +93 -0
  125. package/dist/threadline/MessageSecurity.js.map +1 -0
  126. package/dist/threadline/SecureInvitation.d.ts +79 -0
  127. package/dist/threadline/SecureInvitation.d.ts.map +1 -0
  128. package/dist/threadline/SecureInvitation.js +164 -0
  129. package/dist/threadline/SecureInvitation.js.map +1 -0
  130. package/dist/threadline/TrustAuditLog.d.ts +49 -0
  131. package/dist/threadline/TrustAuditLog.d.ts.map +1 -0
  132. package/dist/threadline/TrustAuditLog.js +111 -0
  133. package/dist/threadline/TrustAuditLog.js.map +1 -0
  134. package/dist/threadline/TrustEvaluator.d.ts +69 -0
  135. package/dist/threadline/TrustEvaluator.d.ts.map +1 -0
  136. package/dist/threadline/TrustEvaluator.js +93 -0
  137. package/dist/threadline/TrustEvaluator.js.map +1 -0
  138. package/dist/threadline/UnifiedTrustWiring.d.ts +66 -0
  139. package/dist/threadline/UnifiedTrustWiring.d.ts.map +1 -0
  140. package/dist/threadline/UnifiedTrustWiring.js +192 -0
  141. package/dist/threadline/UnifiedTrustWiring.js.map +1 -0
  142. package/dist/threadline/client/IdentityManager.d.ts +27 -6
  143. package/dist/threadline/client/IdentityManager.d.ts.map +1 -1
  144. package/dist/threadline/client/IdentityManager.js +64 -18
  145. package/dist/threadline/client/IdentityManager.js.map +1 -1
  146. package/dist/threadline/relay/SybilProtection.d.ts +78 -0
  147. package/dist/threadline/relay/SybilProtection.d.ts.map +1 -0
  148. package/dist/threadline/relay/SybilProtection.js +187 -0
  149. package/dist/threadline/relay/SybilProtection.js.map +1 -0
  150. package/package.json +9 -3
  151. package/playbook-scripts/build-state.py +529 -0
  152. package/scripts/check-contract-evidence.js +103 -0
  153. package/scripts/pre-push-gate.js +53 -0
  154. package/scripts/run-contract-tests.js +75 -0
  155. package/src/data/builtin-manifest.json +87 -63
  156. package/src/templates/hooks/build-stop-hook.sh +79 -0
  157. package/upgrades/0.26.10.md +36 -0
  158. package/upgrades/0.27.1.md +101 -0
  159. package/upgrades/0.27.2.md +36 -0
  160. package/upgrades/NEXT.md +0 -27
  161. /package/.claude/skills/secret-setup/{SKILL.md → skill.md} +0 -0
@@ -93,6 +93,7 @@ import { createMessagingProbes } from '../monitoring/probes/MessagingProbe.js';
93
93
  import { createLifelineProbes } from '../monitoring/probes/LifelineProbe.js';
94
94
  import { createPlatformProbes } from '../monitoring/probes/PlatformProbe.js';
95
95
  import { bootstrapThreadline } from '../threadline/ThreadlineBootstrap.js';
96
+ import { createUnifiedTrustSystem } from '../threadline/UnifiedTrustWiring.js';
96
97
  import { toInjection } from '../types/pipeline.js';
97
98
  import { UserManager } from '../users/UserManager.js';
98
99
  import { formatUserContextForSession, hasUserContext } from '../users/UserContextBuilder.js';
@@ -2884,7 +2885,8 @@ export async function startServer(options) {
2884
2885
  sessionManager.on('beforeSessionKill', (session) => {
2885
2886
  try {
2886
2887
  // Save Telegram topic resume UUID (if Telegram is configured)
2887
- if (telegram) {
2888
+ // Skip for context exhaustion kills — re-saving would cause a death loop
2889
+ if (telegram && !contextExhaustionKills.has(session.tmuxSession)) {
2888
2890
  const topicId = telegram.getTopicForSession(session.tmuxSession);
2889
2891
  if (topicId) {
2890
2892
  const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
@@ -2895,7 +2897,9 @@ export async function startServer(options) {
2895
2897
  }
2896
2898
  }
2897
2899
  // Save Slack channel resume UUID (if Slack is configured)
2898
- if (_slackAdapter) {
2900
+ // Skip if the session is being killed for context exhaustion — saving the UUID
2901
+ // would cause a death loop where the next respawn loads the same bloated conversation.
2902
+ if (_slackAdapter && !contextExhaustionKills.has(session.tmuxSession)) {
2899
2903
  const channelId = _slackAdapter.getChannelForSession(session.tmuxSession);
2900
2904
  if (channelId) {
2901
2905
  const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
@@ -2905,6 +2909,9 @@ export async function startServer(options) {
2905
2909
  }
2906
2910
  }
2907
2911
  }
2912
+ else if (contextExhaustionKills.has(session.tmuxSession)) {
2913
+ console.log(`[beforeSessionKill] Skipping Slack resume UUID save for ${session.name} — context exhaustion recovery`);
2914
+ }
2908
2915
  }
2909
2916
  catch (err) {
2910
2917
  console.error(`[beforeSessionKill] Failed to save resume UUID:`, err);
@@ -3076,6 +3083,9 @@ export async function startServer(options) {
3076
3083
  sendToTopic: async (topicId, text) => {
3077
3084
  const slackChId = slackProxyChannelMap.get(topicId);
3078
3085
  if (slackChId && _slackAdapter) {
3086
+ // Never send monitoring messages to system channels (dashboard, lifeline)
3087
+ if (_slackAdapter.isSystemChannel(slackChId))
3088
+ return;
3079
3089
  await _slackAdapter.sendToChannel(slackChId, text);
3080
3090
  return;
3081
3091
  }
@@ -3152,6 +3162,8 @@ export async function startServer(options) {
3152
3162
  sendToTopic: async (topicId, text) => {
3153
3163
  const slackChId = slackProxyChannelMap.get(topicId);
3154
3164
  if (slackChId && _slackAdapter) {
3165
+ if (_slackAdapter.isSystemChannel(slackChId))
3166
+ return;
3155
3167
  await _slackAdapter.sendToChannel(slackChId, text);
3156
3168
  return;
3157
3169
  }
@@ -3245,6 +3257,9 @@ export async function startServer(options) {
3245
3257
  // SessionRecovery — fast mechanical recovery (JSONL analysis, no LLM)
3246
3258
  // Platform-aware: works with Telegram topics AND Slack channels
3247
3259
  let sessionRecovery;
3260
+ // Track sessions being killed for context exhaustion — prevents beforeSessionKill
3261
+ // from re-saving resume UUIDs (which would cause an infinite death loop)
3262
+ const contextExhaustionKills = new Set();
3248
3263
  if (telegram || _slackAdapter) {
3249
3264
  sessionRecovery = new SessionRecovery({ enabled: true, projectDir: config.projectDir }, {
3250
3265
  isSessionAlive: (name) => sessionManager.isSessionAlive(name),
@@ -3307,18 +3322,95 @@ export async function startServer(options) {
3307
3322
  },
3308
3323
  respawnSessionFresh: async (topicId, _sessionName, recoveryPrompt) => {
3309
3324
  // Fresh respawn for context exhaustion — explicitly clear resume UUID
3310
- // so the new session starts clean with telegram history, not --resume.
3325
+ // so the new session starts clean with history, not --resume.
3311
3326
  if (_topicResumeMap) {
3312
3327
  _topicResumeMap.remove(topicId);
3313
3328
  }
3329
+ // Check if this is a Slack channel (synthetic negative topic IDs)
3330
+ let slackChId = slackProxyChannelMap.get(topicId);
3331
+ // Fallback: reverse-lookup from adapter channel registry if map doesn't have it
3332
+ if (!slackChId && _slackAdapter && topicId < 0) {
3333
+ const registry = _slackAdapter.getChannelRegistry();
3334
+ for (const channelId of Object.keys(registry)) {
3335
+ if (slackChannelToSyntheticId(channelId) === topicId) {
3336
+ slackChId = channelId;
3337
+ break;
3338
+ }
3339
+ }
3340
+ if (slackChId) {
3341
+ console.log(`[respawnSessionFresh] Resolved slackChId=${slackChId} via reverse lookup (map miss for topicId=${topicId})`);
3342
+ }
3343
+ }
3344
+ console.log(`[respawnSessionFresh] topicId=${topicId} slackChId=${slackChId || 'none'} slackAdapter=${!!_slackAdapter} session=${_sessionName} mapSize=${slackProxyChannelMap.size}`);
3345
+ if (slackChId && _slackAdapter) {
3346
+ // Kill existing session (already flagged in contextExhaustionKills via event listener)
3347
+ const session = sessionManager.listRunningSessions().find(s => s.tmuxSession === _sessionName);
3348
+ if (session)
3349
+ sessionManager.killSession(session.id);
3350
+ // Clear the channel resume so the new session starts fresh
3351
+ _slackAdapter.removeChannelResume(slackChId);
3352
+ // Spawn a fresh session with recovery context
3353
+ await new Promise(resolve => setTimeout(resolve, 2000));
3354
+ // Build a recovery bootstrap message with thread history
3355
+ const history = _slackAdapter.getChannelMessages(slackChId, 30);
3356
+ const botUserId = _slackAdapter.getBotUserId?.() ?? null;
3357
+ const lines = [];
3358
+ lines.push(`[RECOVERY] Previous session hit the context window limit. This is a FRESH restart with thread history.`);
3359
+ if (recoveryPrompt)
3360
+ lines.push(recoveryPrompt);
3361
+ lines.push('');
3362
+ lines.push(`--- Thread History (last ${history.length} messages) ---`);
3363
+ for (const m of history) {
3364
+ const date = new Date(parseFloat(m.ts) * 1000);
3365
+ const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
3366
+ const isBot = botUserId && m.user === botUserId;
3367
+ const label = isBot ? 'Agent' : m.user;
3368
+ lines.push(`[${time}] ${label}: ${m.text}`);
3369
+ }
3370
+ lines.push('--- End Thread History ---');
3371
+ lines.push('');
3372
+ lines.push('CRITICAL: You MUST relay your response back to Slack.');
3373
+ lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${slackChId}`);
3374
+ lines.push('Your response text here');
3375
+ lines.push('EOF');
3376
+ const tmpDir = '/tmp/instar-slack';
3377
+ fs.mkdirSync(tmpDir, { recursive: true });
3378
+ const ctxPath = path.join(tmpDir, `recovery-${slackChId}-${Date.now()}.txt`);
3379
+ const contextData = lines.join('\n');
3380
+ fs.writeFileSync(ctxPath, contextData);
3381
+ const bootstrapMessage = `[slack:${slackChId}] [RECOVERY] Context exhaustion — session restarted fresh. (IMPORTANT: Read ${ctxPath} for thread history and Slack relay instructions — you MUST relay your response back.)`;
3382
+ try {
3383
+ const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage);
3384
+ if (newSessionName) {
3385
+ _slackAdapter.registerChannelSession(slackChId, newSessionName);
3386
+ console.log(`[slack→recovery] Fresh session "${newSessionName}" spawned for channel ${slackChId} (context exhaustion recovery)`);
3387
+ }
3388
+ }
3389
+ catch (err) {
3390
+ console.error(`[slack→recovery] Fresh session spawn failed for ${slackChId}: ${err instanceof Error ? err.message : err}`);
3391
+ }
3392
+ return;
3393
+ }
3314
3394
  if (telegram) {
3315
3395
  const targetSession = telegram.getSessionForTopic(topicId);
3316
- if (!targetSession)
3396
+ if (!targetSession) {
3397
+ console.warn(`[respawnSessionFresh] No Telegram or Slack session found for topicId=${topicId} — recovery has no target`);
3317
3398
  return;
3399
+ }
3318
3400
  await respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, undefined, topicMemory, undefined, recoveryPrompt, { silent: true });
3319
3401
  }
3402
+ else if (!slackChId) {
3403
+ console.warn(`[respawnSessionFresh] No platform handler for topicId=${topicId} — recovery is a no-op`);
3404
+ }
3320
3405
  },
3321
3406
  });
3407
+ // Track context exhaustion kills to prevent beforeSessionKill from re-saving
3408
+ // resume UUIDs (which would cause an infinite death loop)
3409
+ sessionRecovery.on('recovery:context_exhaustion', ({ sessionName }) => {
3410
+ contextExhaustionKills.add(sessionName);
3411
+ // Clean up after 60 seconds — by then the kill and respawn are done
3412
+ setTimeout(() => contextExhaustionKills.delete(sessionName), 60_000);
3413
+ });
3322
3414
  console.log(pc.green(' Session Recovery enabled (mechanical fast-path)'));
3323
3415
  }
3324
3416
  // SessionMonitor — proactive session health monitoring
@@ -3336,9 +3428,12 @@ export async function startServer(options) {
3336
3428
  }
3337
3429
  }
3338
3430
  // Slack channel sessions (using synthetic IDs)
3431
+ // Exclude system channels (dashboard, lifeline) — they don't have interactive sessions
3339
3432
  if (_slackAdapter) {
3340
3433
  const registry = _slackAdapter.getChannelRegistry();
3341
3434
  for (const [channelId, entry] of Object.entries(registry)) {
3435
+ if (_slackAdapter.isSystemChannel(channelId))
3436
+ continue;
3342
3437
  const syntheticId = slackChannelToSyntheticId(channelId);
3343
3438
  sessions.set(syntheticId, entry.sessionName);
3344
3439
  }
@@ -3367,6 +3462,8 @@ export async function startServer(options) {
3367
3462
  sendToTopic: async (topicId, text) => {
3368
3463
  const slackChId = slackProxyChannelMap.get(topicId);
3369
3464
  if (slackChId && _slackAdapter) {
3465
+ if (_slackAdapter.isSystemChannel(slackChId))
3466
+ return;
3370
3467
  await _slackAdapter.sendToChannel(slackChId, text);
3371
3468
  return;
3372
3469
  }
@@ -3582,6 +3679,32 @@ export async function startServer(options) {
3582
3679
  const { HookEventReceiver } = await import('../monitoring/HookEventReceiver.js');
3583
3680
  const hookEventReceiver = new HookEventReceiver({ stateDir: config.stateDir });
3584
3681
  console.log(pc.green(' Hook event receiver enabled'));
3682
+ // Wire PreCompact events to trigger proactive triage after compaction.
3683
+ // When a session compacts, it goes idle at a prompt. If there are unanswered
3684
+ // user messages (common with Telegram/Slack), Pattern 2b will reinject them.
3685
+ if (triageOrchestrator && telegram) {
3686
+ const _triageOrch = triageOrchestrator;
3687
+ const _telegram = telegram;
3688
+ hookEventReceiver.on('PreCompact', () => {
3689
+ // Delay to let compaction + recovery hooks finish
3690
+ setTimeout(() => {
3691
+ const topicSessions = _telegram.getAllTopicSessions();
3692
+ for (const [topicId, sessionName] of topicSessions) {
3693
+ if (!sessionManager.isSessionAlive(sessionName))
3694
+ continue;
3695
+ const history = _telegram.getTopicHistory(topicId, 5);
3696
+ const lastMsg = history[history.length - 1];
3697
+ if (lastMsg?.fromUser) {
3698
+ console.log(`[CompactionResume] PreCompact detected, topic ${topicId} has unanswered message — activating triage`);
3699
+ _triageOrch.activate(topicId, sessionName, 'watchdog', lastMsg.text, Date.now()).catch(err => {
3700
+ console.warn(`[CompactionResume] Triage activation failed for topic ${topicId}:`, err);
3701
+ });
3702
+ }
3703
+ }
3704
+ }, 10_000);
3705
+ });
3706
+ console.log(pc.green(' Compaction auto-resume wired to triage orchestrator'));
3707
+ }
3585
3708
  // Subagent Tracker — monitors subagent lifecycle via hook events
3586
3709
  const { SubagentTracker } = await import('../monitoring/SubagentTracker.js');
3587
3710
  const subagentTracker = new SubagentTracker({ stateDir: config.stateDir });
@@ -3705,38 +3828,50 @@ export async function startServer(options) {
3705
3828
  const { PresenceProxy } = await import('../monitoring/PresenceProxy.js');
3706
3829
  const { execSync: shellExecSync } = await import('child_process');
3707
3830
  const messagesLogPath = path.join(config.stateDir, 'telegram-messages.jsonl');
3831
+ const slackMessagesLogPath = path.join(config.stateDir, 'slack-messages.jsonl');
3832
+ // Shared helper: check a messages log file for agent responses after a timestamp
3833
+ const checkLogForAgentResponse = (logPath, topicId, sinceIso) => {
3834
+ try {
3835
+ const content = fs.readFileSync(logPath, 'utf-8');
3836
+ const lines = content.trim().split('\n').slice(-50);
3837
+ for (const line of lines) {
3838
+ try {
3839
+ const msg = JSON.parse(line);
3840
+ // Slack logs use channelId (string); Telegram logs use topicId (number).
3841
+ // For Slack synthetic IDs (negative), match against channelId via the map.
3842
+ const matchesTopic = msg.topicId === topicId
3843
+ || (topicId < 0 && msg.channelId && slackChannelToSyntheticId(String(msg.channelId)) === topicId);
3844
+ if (matchesTopic && !msg.fromUser && msg.timestamp > sinceIso) {
3845
+ const t = (msg.text || '').trim();
3846
+ const isSystem = t === '✓ Delivered' || t.startsWith('✓ Delivered')
3847
+ || t.startsWith('🔄 Session restarting') || t === 'Session respawned.'
3848
+ || t === 'Session terminated.' || t.startsWith('Send a new message to start')
3849
+ || t.startsWith('🔭');
3850
+ if (!isSystem)
3851
+ return true;
3852
+ }
3853
+ }
3854
+ catch { /* skip malformed lines */ }
3855
+ }
3856
+ return false;
3857
+ }
3858
+ catch {
3859
+ return false;
3860
+ }
3861
+ };
3708
3862
  presenceProxy = new PresenceProxy({
3709
3863
  stateDir: config.stateDir,
3710
3864
  intelligence: sharedIntelligence,
3711
3865
  agentName: config.projectName ?? 'the agent',
3712
3866
  hasAgentRespondedSince: (topicId, sinceMs) => {
3713
- // Read last ~50 lines of the messages log and check for agent responses
3714
- try {
3715
- const content = fs.readFileSync(messagesLogPath, 'utf-8');
3716
- const lines = content.trim().split('\n').slice(-50);
3717
- const sinceIso = new Date(sinceMs).toISOString();
3718
- for (const line of lines) {
3719
- try {
3720
- const msg = JSON.parse(line);
3721
- if (msg.topicId === topicId && !msg.fromUser && msg.timestamp > sinceIso) {
3722
- // Skip proxy messages and system messages (delivery confirmations, session lifecycle)
3723
- const t = (msg.text || '').trim();
3724
- const isSystem = t === '✓ Delivered' || t.startsWith('✓ Delivered')
3725
- || t.startsWith('🔄 Session restarting') || t === 'Session respawned.'
3726
- || t === 'Session terminated.' || t.startsWith('Send a new message to start')
3727
- || t.startsWith('🔭');
3728
- if (!isSystem) {
3729
- return true;
3730
- }
3731
- }
3732
- }
3733
- catch { /* skip malformed lines */ }
3734
- }
3735
- return false;
3736
- }
3737
- catch {
3738
- return false;
3739
- }
3867
+ const sinceIso = new Date(sinceMs).toISOString();
3868
+ // Check Telegram log
3869
+ if (checkLogForAgentResponse(messagesLogPath, topicId, sinceIso))
3870
+ return true;
3871
+ // Also check Slack log (for Slack synthetic IDs or cross-platform responses)
3872
+ if (checkLogForAgentResponse(slackMessagesLogPath, topicId, sinceIso))
3873
+ return true;
3874
+ return false;
3740
3875
  },
3741
3876
  captureSessionOutput: (name, lines) => sessionManager.captureOutput(name, lines),
3742
3877
  getSessionForTopic: (topicId) => {
@@ -3752,6 +3887,9 @@ export async function startServer(options) {
3752
3887
  // Check if this is a Slack synthetic ID (negative = Slack channel)
3753
3888
  const slackChannelId = slackProxyChannelMap.get(topicId);
3754
3889
  if (slackChannelId && _slackAdapter) {
3890
+ // Never send proxy messages to system channels (dashboard, lifeline)
3891
+ if (_slackAdapter.isSystemChannel(slackChannelId))
3892
+ return;
3755
3893
  // Route directly to Slack channel
3756
3894
  await _slackAdapter.sendToChannel(slackChannelId, text);
3757
3895
  return;
@@ -3825,6 +3963,12 @@ export async function startServer(options) {
3825
3963
  await triageNurse.triage(topicId, sessionName, '', Date.now(), 'manual');
3826
3964
  }
3827
3965
  : undefined,
3966
+ recoverContextExhaustion: sessionRecovery
3967
+ ? async (topicId, sessionName) => {
3968
+ const result = await sessionRecovery.checkAndRecover(topicId, sessionName);
3969
+ return { recovered: result.recovered };
3970
+ }
3971
+ : undefined,
3828
3972
  });
3829
3973
  // Hook into Telegram's onMessageLogged callback (always active, unlike EventBus which requires a feature flag)
3830
3974
  const existingCallback = telegram.onMessageLogged;
@@ -3873,8 +4017,11 @@ export async function startServer(options) {
3873
4017
  _slackAdapter.clearStallTracking(String(entry.channelId));
3874
4018
  }
3875
4019
  // Convert Slack channelId to synthetic numeric ID for PresenceProxy
4020
+ // Skip system channels (dashboard, lifeline) — they don't have interactive sessions
3876
4021
  if (!entry.channelId)
3877
4022
  return;
4023
+ if (_slackAdapter.isSystemChannel(String(entry.channelId)))
4024
+ return;
3878
4025
  const syntheticId = slackChannelToSyntheticId(String(entry.channelId));
3879
4026
  presenceProxy.onMessageLogged({
3880
4027
  messageId: typeof entry.messageId === 'number' ? entry.messageId : parseInt(String(entry.messageId), 10) || 0,
@@ -3958,9 +4105,14 @@ export async function startServer(options) {
3958
4105
  const tunnelUrl = await tunnel.start();
3959
4106
  console.log(`[SleepWake] Tunnel restarted: ${tunnelUrl}`);
3960
4107
  // Re-broadcast dashboard URL after tunnel restart (quick tunnels get new URL)
3961
- if (telegram && tunnelUrl) {
4108
+ if (tunnelUrl) {
3962
4109
  const tunnelType = config.tunnel?.type || 'quick';
3963
- await telegram.broadcastDashboardUrl(tunnelUrl, tunnelType).catch(() => { });
4110
+ if (telegram) {
4111
+ await telegram.broadcastDashboardUrl(tunnelUrl, tunnelType).catch(() => { });
4112
+ }
4113
+ if (_slackAdapter) {
4114
+ await _slackAdapter.broadcastDashboardUrl(tunnelUrl).catch(() => { });
4115
+ }
3964
4116
  }
3965
4117
  })(),
3966
4118
  new Promise((_, reject) => setTimeout(() => reject(new Error('Tunnel restart timed out after 15s')), 15_000)),
@@ -3972,6 +4124,21 @@ export async function startServer(options) {
3972
4124
  tunnel.enableAutoReconnect();
3973
4125
  }
3974
4126
  }
4127
+ // Reconnect Slack after sleep — WebSocket connections die during sleep
4128
+ // and the close-handler reconnect can silently fail if the network isn't
4129
+ // fully up yet. Proactive reconnect after a short delay is more reliable.
4130
+ if (slackAdapter) {
4131
+ // Wait 2s for network to stabilize after wake before reconnecting
4132
+ setTimeout(async () => {
4133
+ try {
4134
+ await slackAdapter.reconnect();
4135
+ console.log('[SleepWake] Slack reconnected');
4136
+ }
4137
+ catch (err) {
4138
+ console.error('[SleepWake] Slack reconnect failed:', err.message);
4139
+ }
4140
+ }, 2000);
4141
+ }
3975
4142
  // Notify via batcher — wake events are informational, not urgent
3976
4143
  // Only notify for long sleeps (>5 min) — short sleeps are routine and not worth mentioning
3977
4144
  if (event.sleepDurationSeconds > 300) {
@@ -4399,6 +4566,7 @@ export async function startServer(options) {
4399
4566
  let threadlineHandshake;
4400
4567
  let threadlineShutdown;
4401
4568
  let threadlineRelayClient;
4569
+ let unifiedTrust;
4402
4570
  try {
4403
4571
  const threadline = await bootstrapThreadline({
4404
4572
  agentName: config.projectName,
@@ -4413,6 +4581,19 @@ export async function startServer(options) {
4413
4581
  threadlineHandshake = threadline.handshakeManager;
4414
4582
  threadlineShutdown = threadline.shutdown;
4415
4583
  threadlineRelayClient = threadline.relayClient;
4584
+ // Initialize unified trust system (three-layer model + MoltBridge)
4585
+ if (threadline.trustManager) {
4586
+ try {
4587
+ unifiedTrust = createUnifiedTrustSystem(threadline.trustManager, {
4588
+ stateDir: config.stateDir,
4589
+ moltbridge: config.moltbridge,
4590
+ });
4591
+ console.log(`Unified trust system initialized (identity: ${unifiedTrust.identity.get()?.displayFingerprint ?? 'none'})`);
4592
+ }
4593
+ catch (err) {
4594
+ console.error(`Unified trust system init failed (non-fatal): ${err instanceof Error ? err.message : err}`);
4595
+ }
4596
+ }
4416
4597
  if (threadlineRelayClient) {
4417
4598
  // Wire relay message delivery through ThreadlineRouter (Phase 1).
4418
4599
  // Replaces the ad-hoc handler with proper thread persistence, auto-ack,
@@ -4604,7 +4785,7 @@ export async function startServer(options) {
4604
4785
  return { content: lines.join('\n'), truncated: false, elapsedMs: Date.now() - start };
4605
4786
  }, { description: 'Feature discovery state and behavioral contract summary' });
4606
4787
  }
4607
- 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, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, listenerManager: listenerManager ?? undefined, responseReviewGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, liveConfig });
4788
+ 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, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, listenerManager: listenerManager ?? undefined, responseReviewGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig });
4608
4789
  await server.start();
4609
4790
  // Connect DegradationReporter downstream systems now that everything is initialized.
4610
4791
  // Any degradation events queued during startup will drain to feedback + telegram.
@@ -4674,9 +4855,14 @@ export async function startServer(options) {
4674
4855
  try {
4675
4856
  const tunnelUrl = await tunnel.start();
4676
4857
  console.log(pc.green(`[tunnel] Connected: ${tunnelUrl}`));
4677
- if (telegram && tunnelUrl) {
4858
+ if (tunnelUrl) {
4678
4859
  const tunnelType = (config.tunnel?.type || 'quick');
4679
- await telegram.broadcastDashboardUrl(tunnelUrl, tunnelType).catch(() => { });
4860
+ if (telegram) {
4861
+ await telegram.broadcastDashboardUrl(tunnelUrl, tunnelType).catch(() => { });
4862
+ }
4863
+ if (_slackAdapter) {
4864
+ await _slackAdapter.broadcastDashboardUrl(tunnelUrl).catch(() => { });
4865
+ }
4680
4866
  }
4681
4867
  }
4682
4868
  catch (retryErr) {
@@ -4725,6 +4911,12 @@ export async function startServer(options) {
4725
4911
  telegram.config.dashboardPin = config.dashboardPin || '';
4726
4912
  }
4727
4913
  await telegram.broadcastDashboardUrl(dashUrl, tunnelType);
4914
+ // Also broadcast to Slack dashboard channel if configured
4915
+ if (_slackAdapter) {
4916
+ await _slackAdapter.broadcastDashboardUrl(dashUrl).catch((err) => {
4917
+ console.warn(`[server] Slack dashboard broadcast failed: ${err.message}`);
4918
+ });
4919
+ }
4728
4920
  }
4729
4921
  else {
4730
4922
  console.log(pc.yellow(` Dashboard available locally at http://localhost:${config.port}/dashboard (no tunnel configured — not broadcasting to Telegram)`));