instar 0.26.3 → 0.26.5

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 (102) hide show
  1. package/README.md +61 -7
  2. package/dashboard/index.html +55 -0
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +2 -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 +188 -3
  8. package/dist/commands/server.js.map +1 -1
  9. package/dist/commands/slack-cli.d.ts.map +1 -1
  10. package/dist/commands/slack-cli.js +6 -0
  11. package/dist/commands/slack-cli.js.map +1 -1
  12. package/dist/core/CapabilityMapper.d.ts.map +1 -1
  13. package/dist/core/CapabilityMapper.js +2 -0
  14. package/dist/core/CapabilityMapper.js.map +1 -1
  15. package/dist/core/EvolutionManager.d.ts +17 -0
  16. package/dist/core/EvolutionManager.d.ts.map +1 -1
  17. package/dist/core/EvolutionManager.js +64 -0
  18. package/dist/core/EvolutionManager.js.map +1 -1
  19. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  20. package/dist/core/PostUpdateMigrator.js +37 -0
  21. package/dist/core/PostUpdateMigrator.js.map +1 -1
  22. package/dist/core/SessionManager.d.ts +31 -0
  23. package/dist/core/SessionManager.d.ts.map +1 -1
  24. package/dist/core/SessionManager.js +169 -16
  25. package/dist/core/SessionManager.js.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/messaging/imessage/IMessageAdapter.d.ts +128 -0
  31. package/dist/messaging/imessage/IMessageAdapter.d.ts.map +1 -0
  32. package/dist/messaging/imessage/IMessageAdapter.js +541 -0
  33. package/dist/messaging/imessage/IMessageAdapter.js.map +1 -0
  34. package/dist/messaging/imessage/NativeBackend.d.ts +82 -0
  35. package/dist/messaging/imessage/NativeBackend.d.ts.map +1 -0
  36. package/dist/messaging/imessage/NativeBackend.js +353 -0
  37. package/dist/messaging/imessage/NativeBackend.js.map +1 -0
  38. package/dist/messaging/imessage/OutboundAuditLog.d.ts +49 -0
  39. package/dist/messaging/imessage/OutboundAuditLog.d.ts.map +1 -0
  40. package/dist/messaging/imessage/OutboundAuditLog.js +58 -0
  41. package/dist/messaging/imessage/OutboundAuditLog.js.map +1 -0
  42. package/dist/messaging/imessage/OutboundRateLimiter.d.ts +54 -0
  43. package/dist/messaging/imessage/OutboundRateLimiter.d.ts.map +1 -0
  44. package/dist/messaging/imessage/OutboundRateLimiter.js +111 -0
  45. package/dist/messaging/imessage/OutboundRateLimiter.js.map +1 -0
  46. package/dist/messaging/imessage/index.d.ts +10 -0
  47. package/dist/messaging/imessage/index.d.ts.map +1 -0
  48. package/dist/messaging/imessage/index.js +13 -0
  49. package/dist/messaging/imessage/index.js.map +1 -0
  50. package/dist/messaging/imessage/normalize-phone.d.ts +26 -0
  51. package/dist/messaging/imessage/normalize-phone.d.ts.map +1 -0
  52. package/dist/messaging/imessage/normalize-phone.js +55 -0
  53. package/dist/messaging/imessage/normalize-phone.js.map +1 -0
  54. package/dist/messaging/imessage/types.d.ts +95 -0
  55. package/dist/messaging/imessage/types.d.ts.map +1 -0
  56. package/dist/messaging/imessage/types.js +5 -0
  57. package/dist/messaging/imessage/types.js.map +1 -0
  58. package/dist/messaging/shared/MessagingEventBus.d.ts +5 -0
  59. package/dist/messaging/shared/MessagingEventBus.d.ts.map +1 -1
  60. package/dist/messaging/shared/MessagingEventBus.js.map +1 -1
  61. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  62. package/dist/messaging/slack/SlackAdapter.js +65 -1
  63. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  64. package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -1
  65. package/dist/messaging/slack/SocketModeClient.js +6 -0
  66. package/dist/messaging/slack/SocketModeClient.js.map +1 -1
  67. package/dist/monitoring/PresenceProxy.d.ts +6 -0
  68. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  69. package/dist/monitoring/PresenceProxy.js +46 -0
  70. package/dist/monitoring/PresenceProxy.js.map +1 -1
  71. package/dist/monitoring/QuotaExhaustionDetector.d.ts +15 -0
  72. package/dist/monitoring/QuotaExhaustionDetector.d.ts.map +1 -1
  73. package/dist/monitoring/QuotaExhaustionDetector.js +26 -3
  74. package/dist/monitoring/QuotaExhaustionDetector.js.map +1 -1
  75. package/dist/monitoring/SessionMonitor.d.ts.map +1 -1
  76. package/dist/monitoring/SessionMonitor.js +5 -2
  77. package/dist/monitoring/SessionMonitor.js.map +1 -1
  78. package/dist/monitoring/SessionRecovery.d.ts +16 -1
  79. package/dist/monitoring/SessionRecovery.d.ts.map +1 -1
  80. package/dist/monitoring/SessionRecovery.js +71 -8
  81. package/dist/monitoring/SessionRecovery.js.map +1 -1
  82. package/dist/scaffold/templates.d.ts +1 -1
  83. package/dist/scaffold/templates.d.ts.map +1 -1
  84. package/dist/scaffold/templates.js +32 -1
  85. package/dist/scaffold/templates.js.map +1 -1
  86. package/dist/scheduler/JobScheduler.js +1 -1
  87. package/dist/scheduler/JobScheduler.js.map +1 -1
  88. package/dist/server/AgentServer.d.ts +1 -0
  89. package/dist/server/AgentServer.d.ts.map +1 -1
  90. package/dist/server/AgentServer.js +1 -0
  91. package/dist/server/AgentServer.js.map +1 -1
  92. package/dist/server/routes.d.ts +1 -0
  93. package/dist/server/routes.d.ts.map +1 -1
  94. package/dist/server/routes.js +169 -0
  95. package/dist/server/routes.js.map +1 -1
  96. package/package.json +1 -1
  97. package/src/data/builtin-manifest.json +83 -67
  98. package/src/templates/hooks/intercept-imsg-send.js +68 -0
  99. package/src/templates/hooks/settings-template.json +11 -0
  100. package/src/templates/scripts/imessage-reply.sh +130 -0
  101. package/upgrades/0.26.4.md +17 -0
  102. package/upgrades/0.26.5.md +15 -0
@@ -865,8 +865,9 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
865
865
  telegram.trackMessageInjection(topicId, targetSession, text);
866
866
  }
867
867
  else {
868
- // Session died — check if it's a quota death before respawning
868
+ // Session died — classify death cause before deciding how to respawn
869
869
  let isQuotaDeath = false;
870
+ let isContextExhausted = false;
870
871
  try {
871
872
  const output = sessionManager.captureOutput(targetSession, 100);
872
873
  if (output) {
@@ -877,10 +878,36 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
877
878
  telegram.sendToTopic(topicId, `🔴 Session died — quota limit reached.\n${classification.detail}\n\n` +
878
879
  `Use /switch-account to switch, /login to add an account, or reply again to force restart.`).catch(() => { });
879
880
  }
881
+ else if (classification.cause === 'context_exhausted') {
882
+ isContextExhausted = true;
883
+ telegram.sendToTopic(topicId, `🔄 Conversation got too long — starting a fresh session with your recent history.`).catch(() => { });
884
+ }
880
885
  }
881
886
  }
882
887
  catch { /* classification failed — fall through to respawn */ }
883
- if (!isQuotaDeath) {
888
+ if (isContextExhausted) {
889
+ // Context exhaustion: respawn FRESH (no --resume) — the old conversation
890
+ // is too large to continue. The respawn path will load telegram thread
891
+ // history as context, giving the new session continuity.
892
+ if (spawningTopics.has(topicId)) {
893
+ console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate respawn`);
894
+ return;
895
+ }
896
+ spawningTopics.add(topicId);
897
+ // Remove the resume UUID so respawnSessionForTopic doesn't try --resume
898
+ if (_topicResumeMap) {
899
+ _topicResumeMap.remove(topicId);
900
+ }
901
+ respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, text, topicMemory, resolvedUser ?? undefined)
902
+ .catch(err => {
903
+ console.error(`[telegram→session] Context exhaustion respawn failed:`, err);
904
+ telegram.sendToTopic(topicId, `❌ Fresh session restart failed. Try sending your message again.`).catch(() => { });
905
+ })
906
+ .finally(() => {
907
+ spawningTopics.delete(topicId);
908
+ });
909
+ }
910
+ else if (!isQuotaDeath) {
884
911
  // Guard: skip respawn if one is already in progress for this topic.
885
912
  // Prevents the infinite respawn loop: dead session + rapid messages → each
886
913
  // message triggers a new respawn → multiple concurrent spawns → chaos.
@@ -990,6 +1017,115 @@ function wireWhatsAppRouting(whatsapp, sessionManager) {
990
1017
  }
991
1018
  });
992
1019
  }
1020
+ /**
1021
+ * Wire iMessage message routing — mirrors Telegram's session patterns exactly.
1022
+ *
1023
+ * Routes incoming iMessages to Claude Code sessions using the same flow as Telegram:
1024
+ * 1. Existing alive session → injectIMessageMessage (with pendingInjections tracking)
1025
+ * 2. Existing dead session → respawn via spawnInteractiveSession(bootstrapMessage)
1026
+ * 3. No session → auto-spawn via spawnInteractiveSession(bootstrapMessage)
1027
+ *
1028
+ * Key design: the bootstrap message (with inline context) is passed as the
1029
+ * initialMessage to spawnInteractiveSession, which handles wait-for-ready and
1030
+ * injection internally — the same code path that Telegram uses successfully.
1031
+ */
1032
+ function wireIMessageRouting(imessage, sessionManager) {
1033
+ const spawningSenders = new Set();
1034
+ // Wire session alive check for stall detection
1035
+ imessage.setIsSessionAlive((sessionName) => sessionManager.isSessionAlive(sessionName));
1036
+ const replyScript = '.claude/scripts/imessage-reply.sh';
1037
+ /**
1038
+ * Build a bootstrap message for spawning an iMessage session.
1039
+ * Follows Telegram's spawnSessionForTopic pattern: context is INLINE in the
1040
+ * bootstrap message, not a file reference. For long messages, writes to a temp
1041
+ * file with a strong read instruction (matching Telegram's BOOTSTRAP_FILE_THRESHOLD).
1042
+ */
1043
+ function buildBootstrapMessage(sender, text, senderName) {
1044
+ // Get conversation history from chat.db (includes both user AND agent messages)
1045
+ const conversationContext = imessage.getConversationContext(sender, 30);
1046
+ const parts = [];
1047
+ if (conversationContext) {
1048
+ parts.push(`CONTINUATION — You are resuming an EXISTING conversation via iMessage.`, `Read the context below before responding.`, ``, conversationContext, ``, `IMPORTANT: Your response MUST acknowledge and continue the conversation above. Do NOT introduce yourself or ask "how can I help" — the user has been talking to you. Pick up where the conversation left off.`, ``);
1049
+ }
1050
+ // Sanitize sender name to prevent injection via chat.db display_name
1051
+ const safeSenderName = senderName ? senderName.replace(/[\[\]`$\\]/g, '') : undefined;
1052
+ // iMessage relay instructions
1053
+ parts.push(`--- iMessage SESSION (${sender}) ---`, `This is a PERSISTENT conversational session via iMessage.`, `MANDATORY: After EVERY response, relay your message back to the user:`, ` cat <<'EOF' | ${replyScript} "${sender}"`, ` Your response text here`, ` EOF`, ``, `CRITICAL: After replying, STAY AT THE PROMPT and wait for follow-up messages.`, `Do NOT exit. More messages will be injected as [imessage:${sender}] prefixed text.`, `Strip the [imessage:...] prefix before interpreting messages.`, `Only relay conversational text — not tool output or internal reasoning.`, `--- END iMessage SESSION ---`, ``, `The user's latest message:`, `[imessage:${sender}${safeSenderName ? ` from ${safeSenderName}` : ''}] ${text}`);
1054
+ let bootstrapMessage = parts.join('\n');
1055
+ // Large bootstrap messages: write to temp file with strong read instruction
1056
+ // (matches Telegram's BOOTSTRAP_FILE_THRESHOLD pattern)
1057
+ const BOOTSTRAP_FILE_THRESHOLD = 500;
1058
+ if (bootstrapMessage.length > BOOTSTRAP_FILE_THRESHOLD) {
1059
+ const tmpDir = '/tmp/instar-imessage';
1060
+ fs.mkdirSync(tmpDir, { recursive: true, mode: 0o700 });
1061
+ // Clean up old temp files (>1 hour) to prevent unbounded accumulation
1062
+ try {
1063
+ const cutoff = Date.now() - 3_600_000;
1064
+ for (const f of fs.readdirSync(tmpDir)) {
1065
+ const fp = path.join(tmpDir, f);
1066
+ try {
1067
+ if (fs.statSync(fp).mtimeMs < cutoff)
1068
+ fs.unlinkSync(fp);
1069
+ }
1070
+ catch { /* ignore */ }
1071
+ }
1072
+ }
1073
+ catch { /* non-critical */ }
1074
+ const senderSlug = sender.replace(/[^a-zA-Z0-9]/g, '').slice(-8);
1075
+ const filepath = path.join(tmpDir, `bootstrap-${senderSlug}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`);
1076
+ fs.writeFileSync(filepath, bootstrapMessage, { mode: 0o600 });
1077
+ console.log(`[imessage→session] Bootstrap too large (${bootstrapMessage.length} chars), wrote to ${filepath}`);
1078
+ bootstrapMessage = `[IMPORTANT: Read ${filepath} — it contains your full session context, conversation history, and the user's latest message. You MUST read this file before responding.]`;
1079
+ }
1080
+ return bootstrapMessage;
1081
+ }
1082
+ imessage.onMessage(async (msg) => {
1083
+ const sender = msg.channel?.identifier;
1084
+ if (!sender)
1085
+ return;
1086
+ const text = msg.content;
1087
+ const senderName = msg.metadata?.senderName ?? undefined;
1088
+ const senderNorm = sender.toLowerCase();
1089
+ // Skip empty messages (reactions, read receipts, lookback artifacts)
1090
+ if (!text || !text.trim())
1091
+ return;
1092
+ // Check for existing session
1093
+ const targetSession = imessage.getSessionForSender(sender);
1094
+ // Guard: skip if spawn already in progress for this sender
1095
+ if (spawningSenders.has(senderNorm)) {
1096
+ console.log(`[imessage→session] Spawn already in progress for ${senderNorm} — skipping`);
1097
+ return;
1098
+ }
1099
+ if (targetSession && sessionManager.isSessionAlive(targetSession)) {
1100
+ // Session alive — inject directly (same as Telegram's injectTelegramMessage path)
1101
+ console.log(`[imessage→session] Injecting into ${targetSession}: "${text.slice(0, 80)}"`);
1102
+ sessionManager.injectIMessageMessage(targetSession, sender, text, senderName);
1103
+ imessage.trackMessageInjection(sender, targetSession, text);
1104
+ }
1105
+ else {
1106
+ // Session dead or missing — spawn with full context (same as Telegram's spawnSessionForTopic)
1107
+ spawningSenders.add(senderNorm);
1108
+ // Use a hash of the full sender to avoid collisions (slice(-6) collides easily)
1109
+ const crypto = await import('node:crypto');
1110
+ const senderHash = crypto.createHash('sha1').update(sender.toLowerCase()).digest('hex').slice(0, 8);
1111
+ const sessionName = `im-${senderHash}`;
1112
+ const bootstrapMessage = buildBootstrapMessage(sender, text, senderName);
1113
+ // Pass bootstrap as initialMessage — spawnInteractiveSession handles
1114
+ // wait-for-ready and injection internally (same code path as Telegram)
1115
+ sessionManager.spawnInteractiveSession(bootstrapMessage, sessionName)
1116
+ .then((newSession) => {
1117
+ imessage.registerSession(sender, newSession);
1118
+ console.log(`[imessage→session] Spawned "${newSession}" for ${imessage.constructor.maskIdentifier?.(sender) || sender}`);
1119
+ })
1120
+ .catch((err) => {
1121
+ console.error(`[imessage→session] Spawn failed:`, err);
1122
+ })
1123
+ .finally(() => {
1124
+ spawningSenders.delete(senderNorm);
1125
+ });
1126
+ }
1127
+ });
1128
+ }
993
1129
  /**
994
1130
  * Ensure the Agent Attention topic exists — the agent's direct line to the user.
995
1131
  * Created once on first server start, persisted in state.
@@ -2590,6 +2726,39 @@ export async function startServer(options) {
2590
2726
  });
2591
2727
  }
2592
2728
  }
2729
+ // ── iMessage adapter initialization ────────────────────────────────
2730
+ let imessageAdapter;
2731
+ const imessageConfig = config.messaging?.find(m => m.type === 'imessage' && m.enabled);
2732
+ if (imessageConfig) {
2733
+ try {
2734
+ const { IMessageAdapter } = await import('../messaging/imessage/index.js');
2735
+ imessageAdapter = new IMessageAdapter(imessageConfig.config, config.stateDir);
2736
+ // Wire session routing (following Telegram/WhatsApp pattern)
2737
+ wireIMessageRouting(imessageAdapter, sessionManager);
2738
+ // Set agent name for mention-based triggering
2739
+ const imAgentName = imessageConfig.config?.agentName || config.projectName;
2740
+ if (imAgentName)
2741
+ imessageAdapter.setAgentName(imAgentName);
2742
+ await imessageAdapter.start();
2743
+ const triggerInfo = imessageAdapter.getTriggerMode() === 'mention'
2744
+ ? `trigger: @${imAgentName}`
2745
+ : 'trigger: all messages';
2746
+ console.log(pc.green(` iMessage adapter: connected (${triggerInfo})`));
2747
+ console.log(pc.green(' iMessage message routing: wired'));
2748
+ }
2749
+ catch (err) {
2750
+ const reason = err instanceof Error ? err.message : String(err);
2751
+ console.error(pc.red(` iMessage init failed: ${reason}`));
2752
+ imessageAdapter = undefined;
2753
+ degradationReporter.report({
2754
+ feature: 'iMessage',
2755
+ primary: 'iMessage messaging adapter',
2756
+ fallback: 'Other messaging channels',
2757
+ reason: `iMessage init failed: ${reason}`,
2758
+ impact: 'iMessage messaging unavailable.',
2759
+ });
2760
+ }
2761
+ }
2593
2762
  // Initialize SemanticMemory — the knowledge graph that unifies all memory systems.
2594
2763
  // Uses the same better-sqlite3 as TopicMemory; shares the rebuild path.
2595
2764
  let semanticMemory;
@@ -3122,6 +3291,22 @@ export async function startServer(options) {
3122
3291
  if (telegram)
3123
3292
  await telegram.sendToTopic(topicId, message);
3124
3293
  },
3294
+ captureSessionOutput: (name, lines) => {
3295
+ return sessionManager.captureOutput(name, lines);
3296
+ },
3297
+ respawnSessionFresh: async (topicId, _sessionName, recoveryPrompt) => {
3298
+ // Fresh respawn for context exhaustion — explicitly clear resume UUID
3299
+ // so the new session starts clean with telegram history, not --resume.
3300
+ if (_topicResumeMap) {
3301
+ _topicResumeMap.remove(topicId);
3302
+ }
3303
+ if (telegram) {
3304
+ const targetSession = telegram.getSessionForTopic(topicId);
3305
+ if (!targetSession)
3306
+ return;
3307
+ await respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, undefined, topicMemory, undefined, recoveryPrompt, { silent: true });
3308
+ }
3309
+ },
3125
3310
  });
3126
3311
  console.log(pc.green(' Session Recovery enabled (mechanical fast-path)'));
3127
3312
  }
@@ -4408,7 +4593,7 @@ export async function startServer(options) {
4408
4593
  return { content: lines.join('\n'), truncated: false, elapsedMs: Date.now() - start };
4409
4594
  }, { description: 'Feature discovery state and behavioral contract summary' });
4410
4595
  }
4411
- 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, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, listenerManager: listenerManager ?? undefined, responseReviewGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, liveConfig });
4596
+ 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 });
4412
4597
  await server.start();
4413
4598
  // Connect DegradationReporter downstream systems now that everything is initialized.
4414
4599
  // Any degradation events queued during startup will drain to feedback + telegram.