instar 0.8.27 → 0.9.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 (59) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +202 -71
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/commands/setup.d.ts.map +1 -1
  6. package/dist/commands/setup.js +38 -4
  7. package/dist/commands/setup.js.map +1 -1
  8. package/dist/core/AgentConnector.d.ts +76 -0
  9. package/dist/core/AgentConnector.d.ts.map +1 -0
  10. package/dist/core/AgentConnector.js +323 -0
  11. package/dist/core/AgentConnector.js.map +1 -0
  12. package/dist/core/AutoUpdater.d.ts +7 -0
  13. package/dist/core/AutoUpdater.d.ts.map +1 -1
  14. package/dist/core/AutoUpdater.js +31 -3
  15. package/dist/core/AutoUpdater.js.map +1 -1
  16. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  17. package/dist/core/PostUpdateMigrator.js +86 -5
  18. package/dist/core/PostUpdateMigrator.js.map +1 -1
  19. package/dist/core/StateWriteAuthority.d.ts +101 -0
  20. package/dist/core/StateWriteAuthority.d.ts.map +1 -0
  21. package/dist/core/StateWriteAuthority.js +167 -0
  22. package/dist/core/StateWriteAuthority.js.map +1 -0
  23. package/dist/core/types.d.ts +104 -0
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/memory/TopicMemory.d.ts +167 -0
  30. package/dist/memory/TopicMemory.d.ts.map +1 -0
  31. package/dist/memory/TopicMemory.js +494 -0
  32. package/dist/memory/TopicMemory.js.map +1 -0
  33. package/dist/memory/TopicSummarizer.d.ts +58 -0
  34. package/dist/memory/TopicSummarizer.d.ts.map +1 -0
  35. package/dist/memory/TopicSummarizer.js +140 -0
  36. package/dist/memory/TopicSummarizer.js.map +1 -0
  37. package/dist/messaging/TelegramAdapter.d.ts +35 -0
  38. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  39. package/dist/messaging/TelegramAdapter.js +136 -2
  40. package/dist/messaging/TelegramAdapter.js.map +1 -1
  41. package/dist/server/AgentServer.d.ts +2 -0
  42. package/dist/server/AgentServer.d.ts.map +1 -1
  43. package/dist/server/AgentServer.js +1 -0
  44. package/dist/server/AgentServer.js.map +1 -1
  45. package/dist/server/routes.d.ts +2 -0
  46. package/dist/server/routes.d.ts.map +1 -1
  47. package/dist/server/routes.js +340 -1
  48. package/dist/server/routes.js.map +1 -1
  49. package/dist/users/UserManager.d.ts +21 -0
  50. package/dist/users/UserManager.d.ts.map +1 -1
  51. package/dist/users/UserManager.js +32 -0
  52. package/dist/users/UserManager.js.map +1 -1
  53. package/dist/users/UserOnboarding.d.ts +116 -0
  54. package/dist/users/UserOnboarding.d.ts.map +1 -0
  55. package/dist/users/UserOnboarding.js +365 -0
  56. package/dist/users/UserOnboarding.js.map +1 -0
  57. package/package.json +2 -1
  58. package/upgrades/0.8.23.md +106 -0
  59. package/upgrades/0.9.1.md +91 -0
package/dist/cli.js CHANGED
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0CH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkkBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAupBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDzE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2CH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAolBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAsyBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDzE"}
@@ -34,6 +34,7 @@ import { TunnelManager } from '../tunnel/TunnelManager.js';
34
34
  import { PostUpdateMigrator } from '../core/PostUpdateMigrator.js';
35
35
  import { UpgradeGuideProcessor } from '../core/UpgradeGuideProcessor.js';
36
36
  import { EvolutionManager } from '../core/EvolutionManager.js';
37
+ import { TopicMemory } from '../memory/TopicMemory.js';
37
38
  import { QuotaTracker } from '../monitoring/QuotaTracker.js';
38
39
  import { AccountSwitcher } from '../monitoring/AccountSwitcher.js';
39
40
  import { QuotaNotifier } from '../monitoring/QuotaNotifier.js';
@@ -59,55 +60,81 @@ function isAutostartInstalled(projectName) {
59
60
  return false;
60
61
  }
61
62
  /**
62
- * Respawn a session for a topic, including thread history in the bootstrap.
63
- * This prevents "thread drift" where respawned sessions lose context.
63
+ * Spawn a session for a topic with full conversational context.
64
+ * Shared by both auto-spawn (new topic) and respawn (dead session) paths.
65
+ *
66
+ * Context loading priority (when TopicMemory is available):
67
+ * 1. Rolling conversation summary (captures full history)
68
+ * 2. Recent messages (last 30 — the immediate context)
69
+ * 3. Search instructions (so agent can query deeper history)
70
+ *
71
+ * Fallback: JSONL-based last 20 messages (when TopicMemory unavailable).
72
+ *
73
+ * Returns the new tmux session name.
64
74
  */
65
- async function respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, latestMessage) {
66
- console.log(`[telegram→session] Session "${targetSession}" needs respawn for topic ${topicId}`);
67
- const msg = latestMessage || 'Session respawned — send a message to continue.';
68
- // Fetch thread history for context
69
- let historyLines = [];
70
- try {
71
- const history = telegram.getTopicHistory(topicId, 20);
72
- if (history.length > 0) {
73
- historyLines.push(`--- Thread History (last ${history.length} messages) ---`);
74
- historyLines.push(`IMPORTANT: Read this history carefully before taking any action.`);
75
- historyLines.push(`Your task is to continue THIS conversation, not start something new.`);
76
- historyLines.push(``);
77
- for (const m of history) {
78
- const sender = m.fromUser ? 'User' : 'Agent';
79
- const ts = m.timestamp ? new Date(m.timestamp).toISOString().slice(11, 19) : '??:??';
80
- const text = (m.text || '').slice(0, 300);
81
- historyLines.push(`[${ts}] ${sender}: ${text}`);
82
- }
83
- historyLines.push(``);
84
- historyLines.push(`--- End Thread History ---`);
75
+ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topicId, latestMessage, topicMemory) {
76
+ const msg = latestMessage || 'Session started send a message to continue.';
77
+ let contextContent = '';
78
+ // Prefer TopicMemory (SQLite-backed, with summaries) over raw JSONL scan
79
+ if (topicMemory) {
80
+ try {
81
+ contextContent = topicMemory.formatContextForSession(topicId, 30);
82
+ }
83
+ catch (err) {
84
+ console.error(`[telegram→session] TopicMemory context failed, falling back to JSONL:`, err);
85
85
  }
86
86
  }
87
- catch (err) {
88
- console.error(`[telegram→session] Failed to fetch thread history:`, err);
87
+ // Fallback to JSONL-based history
88
+ if (!contextContent) {
89
+ try {
90
+ const history = telegram.getTopicHistory(topicId, 20);
91
+ if (history.length > 0) {
92
+ const lines = [];
93
+ lines.push(`--- Thread History (last ${history.length} messages) ---`);
94
+ lines.push(`IMPORTANT: Read this history carefully before taking any action.`);
95
+ lines.push(`Your task is to continue THIS conversation, not start something new.`);
96
+ lines.push(``);
97
+ for (const m of history) {
98
+ const sender = m.fromUser ? 'User' : 'Agent';
99
+ const ts = m.timestamp ? new Date(m.timestamp).toISOString().slice(11, 19) : '??:??';
100
+ const text = (m.text || '').slice(0, 300);
101
+ lines.push(`[${ts}] ${sender}: ${text}`);
102
+ }
103
+ lines.push(``);
104
+ lines.push(`--- End Thread History ---`);
105
+ contextContent = lines.join('\n');
106
+ }
107
+ }
108
+ catch (err) {
109
+ console.error(`[telegram→session] Failed to fetch thread history:`, err);
110
+ }
89
111
  }
90
- // Single-line bootstrap to avoid tmux send-keys newline issues.
91
- // Thread history and context go into temp files for Claude to read.
112
+ // Write context to temp file for Claude to read
92
113
  const tmpDir = '/tmp/instar-telegram';
93
114
  fs.mkdirSync(tmpDir, { recursive: true });
94
115
  let bootstrapMessage;
95
- const relayNote = `You MUST relay your response via: cat <<'EOF' | .claude/scripts/telegram-reply.sh ${topicId}\nYour response\nEOF`;
96
- if (historyLines.length > 0) {
97
- const historyContent = historyLines.join('\n');
116
+ if (contextContent) {
98
117
  const filepath = path.join(tmpDir, `history-${topicId}-${Date.now()}-${process.pid}.txt`);
99
- fs.writeFileSync(filepath, historyContent);
100
- // Single-line: user message + file reference for history + relay instructions
101
- bootstrapMessage = `[telegram:${topicId}] ${msg} (Session respawned. Thread history at ${filepath} — read it for context before responding. ${relayNote})`;
118
+ fs.writeFileSync(filepath, contextContent);
119
+ bootstrapMessage = `[telegram:${topicId}] ${msg} (Thread history and context at ${filepath} — read it for context before responding.)`;
102
120
  }
103
121
  else {
104
- bootstrapMessage = `[telegram:${topicId}] ${msg} (${relayNote})`;
122
+ bootstrapMessage = `[telegram:${topicId}] ${msg}`;
105
123
  }
124
+ const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, sessionName, { telegramTopicId: topicId });
125
+ return newSessionName;
126
+ }
127
+ /**
128
+ * Respawn a session for a topic, including thread history in the bootstrap.
129
+ * This prevents "thread drift" where respawned sessions lose context.
130
+ */
131
+ async function respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, latestMessage, topicMemory) {
132
+ console.log(`[telegram→session] Session "${targetSession}" needs respawn for topic ${topicId}`);
106
133
  const storedName = telegram.getTopicName(topicId);
107
134
  // Use topic name, not tmux session name — tmux names include the project prefix
108
135
  // which causes cascading names like ai-guy-ai-guy-ai-guy-topic-1 on each respawn.
109
136
  const topicName = storedName || `topic-${topicId}`;
110
- const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, topicName, { telegramTopicId: topicId });
137
+ const newSessionName = await spawnSessionForTopic(sessionManager, telegram, topicName, topicId, latestMessage, topicMemory);
111
138
  telegram.registerTopicSession(topicId, newSessionName);
112
139
  await telegram.sendToTopic(topicId, `Session respawned.`);
113
140
  console.log(`[telegram→session] Respawned "${newSessionName}" for topic ${topicId}`);
@@ -116,7 +143,7 @@ async function respawnSessionForTopic(sessionManager, telegram, targetSession, t
116
143
  * Wire up Telegram session management callbacks.
117
144
  * These enable /interrupt, /restart, /sessions commands and stall detection.
118
145
  */
119
- function wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, accountSwitcher, claudePath) {
146
+ function wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, accountSwitcher, claudePath, topicMemory) {
120
147
  // /interrupt — send Escape key to a tmux session
121
148
  telegram.onInterruptSession = async (sessionName) => {
122
149
  try {
@@ -137,7 +164,7 @@ function wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, ac
137
164
  }
138
165
  catch { /* may already be dead */ }
139
166
  // Respawn with thread history
140
- await respawnSessionForTopic(sessionManager, telegram, sessionName, topicId);
167
+ await respawnSessionForTopic(sessionManager, telegram, sessionName, topicId, undefined, topicMemory);
141
168
  };
142
169
  // /sessions — list running sessions
143
170
  telegram.onListSessions = () => {
@@ -332,7 +359,7 @@ function wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, ac
332
359
  * Wire up Telegram message routing: topic messages → Claude sessions.
333
360
  * This is the core handler that makes Telegram topics work like sessions.
334
361
  */
335
- function wireTelegramRouting(telegram, sessionManager, quotaTracker) {
362
+ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory) {
336
363
  telegram.onTopicMessage = (msg) => {
337
364
  const topicId = msg.metadata?.messageThreadId ?? null;
338
365
  if (!topicId)
@@ -390,37 +417,19 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker) {
390
417
  catch { /* classification failed — fall through to respawn */ }
391
418
  if (!isQuotaDeath) {
392
419
  telegram.sendToTopic(topicId, `🔄 Session restarting — message queued.`).catch(() => { });
393
- respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, text).catch(err => {
420
+ respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, text, topicMemory).catch(err => {
394
421
  console.error(`[telegram→session] Respawn failed:`, err);
395
422
  });
396
423
  }
397
424
  }
398
425
  }
399
426
  else {
400
- // No session mapped — auto-spawn one
401
- console.log(`[telegram→session] No session for topic ${topicId}, auto-spawning...`);
427
+ // No session mapped — auto-spawn with topic history (same as respawn path).
428
+ // Without history, the agent has no conversational context and gives blind answers.
429
+ console.log(`[telegram→session] No session for topic ${topicId}, auto-spawning with history...`);
402
430
  const storedName = telegram.getTopicName(topicId) || `topic-${topicId}`;
403
- // Write relay instructions to a temp file and reference it in the bootstrap message.
404
- // The session needs to know HOW to respond back to Telegram.
405
- const contextLines = [
406
- `This session was auto-created for Telegram topic ${topicId}.`,
407
- ``,
408
- `CRITICAL: You MUST relay your response back to Telegram after responding.`,
409
- `Use the relay script:`,
410
- ``,
411
- `cat <<'EOF' | .claude/scripts/telegram-reply.sh ${topicId}`,
412
- `Your response text here`,
413
- `EOF`,
414
- ``,
415
- `Strip the [telegram:${topicId}] prefix before interpreting the message.`,
416
- `Only relay conversational text — not tool output or internal reasoning.`,
417
- ];
418
- const tmpDir = '/tmp/instar-telegram';
419
- fs.mkdirSync(tmpDir, { recursive: true });
420
- const ctxPath = path.join(tmpDir, `ctx-${topicId}-${Date.now()}.txt`);
421
- fs.writeFileSync(ctxPath, contextLines.join('\n'));
422
- const bootstrapMessage = `[telegram:${topicId}] ${text} (IMPORTANT: Read ${ctxPath} for Telegram relay instructions — you MUST relay your response back.)`;
423
- sessionManager.spawnInteractiveSession(bootstrapMessage, storedName, { telegramTopicId: topicId }).then((newSessionName) => {
431
+ // Use the shared spawn helper that includes topic history
432
+ spawnSessionForTopic(sessionManager, telegram, storedName, topicId, text, topicMemory).then((newSessionName) => {
424
433
  telegram.registerTopicSession(topicId, newSessionName);
425
434
  telegram.sendToTopic(topicId, `Session starting up — reading your message now. One moment.`).catch(() => { });
426
435
  console.log(`[telegram→session] Auto-spawned "${newSessionName}" for topic ${topicId}`);
@@ -739,6 +748,7 @@ export async function startServer(options) {
739
748
  // When --no-telegram is set (lifeline owns polling), create adapter in send-only mode
740
749
  // so the server can still relay replies via /telegram/reply/:topicId
741
750
  let telegram;
751
+ let topicMemory;
742
752
  const telegramConfig = config.messaging.find(m => m.type === 'telegram' && m.enabled);
743
753
  const skipTelegram = options.telegram === false; // --no-telegram sets telegram: false
744
754
  // Standby machines use send-only Telegram — they don't poll for messages
@@ -777,9 +787,86 @@ export async function startServer(options) {
777
787
  }, 10 * 60 * 1000);
778
788
  console.log(pc.green(' Quota notifications enabled'));
779
789
  }
790
+ // Initialize TopicMemory — SQLite-backed conversational memory.
791
+ // Topic history is the primary context for every session.
792
+ topicMemory = new TopicMemory(config.stateDir);
793
+ try {
794
+ await topicMemory.open();
795
+ // Import existing messages from JSONL (idempotent — only inserts new ones)
796
+ const jsonlPath = path.join(config.stateDir, 'telegram-messages.jsonl');
797
+ if (fs.existsSync(jsonlPath)) {
798
+ const imported = topicMemory.importFromJsonl(jsonlPath);
799
+ if (imported > 0) {
800
+ console.log(pc.green(` TopicMemory: imported ${imported} messages from JSONL`));
801
+ }
802
+ }
803
+ const tmStats = topicMemory.stats();
804
+ console.log(pc.green(` TopicMemory: ${tmStats.totalMessages} messages, ${tmStats.totalTopics} topics, ${tmStats.topicsWithSummaries} summaries`));
805
+ // Wire dual-write: every message logged to JSONL also goes to SQLite
806
+ const tm = topicMemory; // Capture for closure (TypeScript narrowing)
807
+ telegram.onMessageLogged = (entry) => {
808
+ if (entry.topicId != null && tm) {
809
+ tm.insertMessage({
810
+ messageId: entry.messageId,
811
+ topicId: entry.topicId,
812
+ text: entry.text,
813
+ fromUser: entry.fromUser,
814
+ timestamp: entry.timestamp,
815
+ sessionName: entry.sessionName,
816
+ });
817
+ }
818
+ };
819
+ }
820
+ catch (err) {
821
+ console.error(` TopicMemory init failed (non-critical): ${err instanceof Error ? err.message : err}`);
822
+ }
780
823
  // Wire up topic → session routing and session management callbacks
781
- wireTelegramRouting(telegram, sessionManager, quotaTracker);
782
- wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, accountSwitcher, config.sessions.claudePath);
824
+ wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory);
825
+ wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, accountSwitcher, config.sessions.claudePath, topicMemory);
826
+ // Wire up unknown-user handling (Multi-User Setup Wizard Phase 4.5)
827
+ telegram.onGetRegistrationPolicy = () => ({
828
+ policy: config.userRegistrationPolicy ?? 'admin-only',
829
+ contactHint: config.registrationContactHint,
830
+ agentName: config.projectName,
831
+ });
832
+ telegram.onNotifyAdminJoinRequest = async (request) => {
833
+ const { JoinRequestManager } = await import('../users/UserOnboarding.js');
834
+ const joinManager = new JoinRequestManager(config.stateDir);
835
+ const joinRequest = joinManager.createRequest(request.name, request.telegramUserId, null);
836
+ // Notify admin via Lifeline topic (the always-available admin channel)
837
+ const lifelineTopicId = telegram.getLifelineTopicId();
838
+ if (lifelineTopicId) {
839
+ const userLabel = request.username ? `@${request.username}` : request.name;
840
+ await telegram.sendToTopic(lifelineTopicId, `\ud83d\udc64 **Join Request** from ${userLabel} (ID: ${request.telegramUserId})\n\n` +
841
+ `To approve: \`/approve ${joinRequest.approvalCode}\`\n` +
842
+ `To deny: \`/deny ${joinRequest.approvalCode}\``).catch(() => { });
843
+ }
844
+ };
845
+ telegram.onStartMiniOnboarding = async (telegramUserId, firstName, username) => {
846
+ const { buildUserProfile, buildCondensedConsentDisclosure } = await import('../users/UserOnboarding.js');
847
+ // Send consent disclosure first
848
+ const consentText = buildCondensedConsentDisclosure(config.projectName);
849
+ await telegram.sendToTopic(1, consentText).catch(() => { }); // General topic
850
+ // Build a basic profile (consent will be confirmed via follow-up reply)
851
+ const profile = buildUserProfile({
852
+ name: firstName,
853
+ telegramUserId,
854
+ });
855
+ // Add to users via UserManager
856
+ const { UserManager } = await import('../users/UserManager.js');
857
+ const userManager = new UserManager(config.stateDir, config.users);
858
+ userManager.upsertUser(profile);
859
+ // Add to authorized user IDs so future messages are accepted
860
+ const telegramConfig = config.messaging?.find(m => m.type === 'telegram');
861
+ if (telegramConfig?.config) {
862
+ const authIds = telegramConfig.config.authorizedUserIds ?? [];
863
+ if (!authIds.includes(telegramUserId)) {
864
+ authIds.push(telegramUserId);
865
+ telegramConfig.config.authorizedUserIds = authIds;
866
+ }
867
+ }
868
+ console.log(`[telegram] Mini-onboarding complete for ${firstName} (${telegramUserId})`);
869
+ };
783
870
  console.log(pc.green(' Telegram message routing active'));
784
871
  if (scheduler) {
785
872
  scheduler.setMessenger(telegram);
@@ -801,6 +888,30 @@ export async function startServer(options) {
801
888
  scheduler.notifyJobComplete(session.id, session.tmuxSession);
802
889
  });
803
890
  }
891
+ // Auto-summarize topics on session completion.
892
+ // When a Telegram-linked session ends, check if its topic needs a summary update.
893
+ // Uses Haiku for cost efficiency — summaries don't need deep reasoning.
894
+ if (topicMemory && telegram) {
895
+ const { TopicSummarizer } = await import('../memory/TopicSummarizer.js');
896
+ const { ClaudeCliIntelligenceProvider } = await import('../core/ClaudeCliIntelligenceProvider.js');
897
+ const summaryIntelligence = new ClaudeCliIntelligenceProvider(config.sessions.claudePath);
898
+ const summarizer = new TopicSummarizer(summaryIntelligence, topicMemory);
899
+ sessionManager.on('sessionComplete', (session) => {
900
+ // Find the topic linked to this session
901
+ const sessionTopicId = telegram.getTopicForSession(session.tmuxSession);
902
+ if (!sessionTopicId)
903
+ return;
904
+ // Check if this topic needs a summary update (async, fire-and-forget)
905
+ summarizer.summarize(sessionTopicId).then((result) => {
906
+ if (result) {
907
+ console.log(`[TopicSummarizer] Updated summary for topic ${sessionTopicId}: ${result.messagesProcessed} messages processed in ${result.durationMs}ms`);
908
+ }
909
+ }).catch((err) => {
910
+ console.error(`[TopicSummarizer] Failed for topic ${sessionTopicId}: ${err instanceof Error ? err.message : err}`);
911
+ });
912
+ });
913
+ console.log(pc.green(' Topic auto-summarization enabled (on session end)'));
914
+ }
804
915
  // Session Watchdog — auto-remediation for stuck commands
805
916
  let watchdog;
806
917
  if (config.monitoring.watchdog?.enabled) {
@@ -981,7 +1092,7 @@ export async function startServer(options) {
981
1092
  }
982
1093
  });
983
1094
  sleepWakeDetector.start();
984
- const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, publisher, viewer, tunnel, evolution, watchdog, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem });
1095
+ const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, publisher, viewer, tunnel, evolution, watchdog, topicMemory, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem });
985
1096
  await server.start();
986
1097
  // Start tunnel AFTER server is listening
987
1098
  if (tunnel) {
@@ -1045,15 +1156,17 @@ export async function startServer(options) {
1045
1156
  await sessionManager.spawnSession({
1046
1157
  name: 'upgrade-notify',
1047
1158
  prompt: [
1048
- 'IMPORTANT: You are a SHORT-LIVED session with ONE specific task. Do NOT search for files or explore the codebase. Everything you need is in this prompt.',
1159
+ 'IMPORTANT: You are a SHORT-LIVED session with a SPECIFIC task. Do NOT search for files or explore the codebase. Everything you need is in this prompt.',
1049
1160
  '',
1050
- 'You have been updated to a new Instar version. Read the upgrade guide below, then:',
1161
+ 'You have been updated to a new Instar version. Read the upgrade guide below, then do ALL THREE steps:',
1051
1162
  '',
1052
- '1. Compose a brief, personalized message (3-8 sentences) for your user about the new features.',
1163
+ '## Step 1: Notify your user',
1164
+ '',
1165
+ 'Compose a brief, personalized message (3-8 sentences) for your user about the new features.',
1053
1166
  ' RULES:',
1054
1167
  ' - Write like you\'re texting a friend — warm, conversational, no jargon',
1055
1168
  ' - This should NOT look like a changelog or release notes',
1056
- ' - Lead with the biggest USER-VISIBLE feature (usually the dashboard if this is the first time)',
1169
+ ' - Lead with the biggest USER-VISIBLE feature',
1057
1170
  ' - Include CONCRETE details — actual URLs, PINs, things they can click/use right now',
1058
1171
  ' - NEVER mention "bearer tokens", "auth tokens", version numbers in headers, or internal implementation details',
1059
1172
  ' - Focus on what matters to THEM, not internal plumbing',
@@ -1064,14 +1177,32 @@ export async function startServer(options) {
1064
1177
  dashboardPin ? ` - Dashboard PIN: ${dashboardPin}` : ' - No dashboard PIN set',
1065
1178
  ` - Current version: ${getInstalledVersion()}`,
1066
1179
  '',
1067
- `2. Send the message via Telegram:`,
1180
+ `Send the message via Telegram:`,
1068
1181
  hasReplyScript && notifyTopicId
1069
1182
  ? ` Run: cat <<'MSGEOF' | bash ${replyScript} ${notifyTopicId}\nYOUR_MESSAGE_HERE\nMSGEOF`
1070
1183
  : ` Use the telegram-reply script in .instar/scripts/ to send to the updates topic.`,
1071
1184
  '',
1072
- '3. Run: instar upgrade-ack',
1185
+ '## Step 2: Update your memory with new capabilities',
1186
+ '',
1187
+ 'Read the upgrade guide\'s "Summary of New Capabilities" section and add the relevant information to your MEMORY.md file (.instar/MEMORY.md).',
1188
+ 'This ensures you KNOW about these capabilities in every future session — not just this one.',
1189
+ '',
1190
+ 'Add a section like:',
1191
+ '```',
1192
+ '## Capabilities Added in vX.Y.Z',
1193
+ '- Brief description of each capability',
1194
+ '- How to use it (API endpoints, commands, automatic behaviors)',
1195
+ '- Any behavioral changes you should be aware of',
1196
+ '```',
1197
+ '',
1198
+ 'Keep it concise — focus on WHAT you can now do and HOW to do it, not the implementation details.',
1199
+ 'If there are existing capability notes in MEMORY.md, update or merge rather than duplicate.',
1200
+ '',
1201
+ '## Step 3: Acknowledge',
1202
+ '',
1203
+ 'Run: instar upgrade-ack',
1073
1204
  '',
1074
- 'That is ALL. Do not do anything else. Do not search for files. Do not read config files. Just compose, send, ack.',
1205
+ 'Do all three steps, then exit. Do not search for files or read config files beyond MEMORY.md.',
1075
1206
  '',
1076
1207
  '--- UPGRADE GUIDE ---',
1077
1208
  guideContent,