instar 0.28.2 → 0.28.3

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 (44) hide show
  1. package/dashboard/index.html +1 -1
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +89 -49
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/Config.js +2 -2
  6. package/dist/core/Config.js.map +1 -1
  7. package/dist/core/SessionManager.d.ts +1 -0
  8. package/dist/core/SessionManager.d.ts.map +1 -1
  9. package/dist/core/SessionManager.js +3 -0
  10. package/dist/core/SessionManager.js.map +1 -1
  11. package/dist/lifeline/ServerSupervisor.d.ts +1 -0
  12. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
  13. package/dist/lifeline/ServerSupervisor.js +43 -10
  14. package/dist/lifeline/ServerSupervisor.js.map +1 -1
  15. package/dist/messaging/TelegramAdapter.d.ts +13 -1
  16. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  17. package/dist/messaging/TelegramAdapter.js +90 -7
  18. package/dist/messaging/TelegramAdapter.js.map +1 -1
  19. package/dist/messaging/imessage/IMessageAdapter.d.ts +4 -2
  20. package/dist/messaging/imessage/IMessageAdapter.d.ts.map +1 -1
  21. package/dist/messaging/imessage/IMessageAdapter.js +19 -5
  22. package/dist/messaging/imessage/IMessageAdapter.js.map +1 -1
  23. package/dist/messaging/slack/SlackAdapter.d.ts +6 -0
  24. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  25. package/dist/messaging/slack/SlackAdapter.js +27 -0
  26. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  27. package/dist/server/routes.d.ts.map +1 -1
  28. package/dist/server/routes.js +77 -19
  29. package/dist/server/routes.js.map +1 -1
  30. package/dist/threadline/AgentTrustManager.d.ts +15 -3
  31. package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
  32. package/dist/threadline/AgentTrustManager.js +40 -11
  33. package/dist/threadline/AgentTrustManager.js.map +1 -1
  34. package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
  35. package/dist/threadline/ThreadlineMCPServer.js +32 -10
  36. package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
  37. package/dist/threadline/client/ThreadlineClient.d.ts +9 -1
  38. package/dist/threadline/client/ThreadlineClient.d.ts.map +1 -1
  39. package/dist/threadline/client/ThreadlineClient.js +64 -10
  40. package/dist/threadline/client/ThreadlineClient.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/data/builtin-manifest.json +48 -48
  43. package/src/templates/hooks/compaction-recovery.sh +82 -3
  44. package/upgrades/0.28.3.md +35 -0
@@ -3067,7 +3067,7 @@
3067
3067
  document.getElementById('terminalView').style.display = 'flex';
3068
3068
  document.getElementById('terminalView').style.flexDirection = 'column';
3069
3069
  document.getElementById('terminalView').style.height = '100%';
3070
- document.getElementById('termSessionName').textContent = session.name;
3070
+ document.getElementById('termSessionName').textContent = session.platformName || session.name;
3071
3071
 
3072
3072
  const badge = document.getElementById('termModelBadge');
3073
3073
  if (session.model) {
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAizCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8/HtE;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;AA2PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6zCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAwhItE;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"}
@@ -493,7 +493,13 @@ async function respawnSessionForTopic(sessionManager, telegram, targetSession, t
493
493
  console.error(`[telegram→session] Failed to save resume UUID:`, err);
494
494
  }
495
495
  }
496
- const storedName = telegram.getTopicName(topicId);
496
+ let storedName = telegram.getTopicName(topicId);
497
+ // If the name is unknown, try to resolve it from Telegram before falling back
498
+ if (!storedName || /^topic-\d+$/.test(storedName)) {
499
+ const resolved = await telegram.resolveTopicName(topicId);
500
+ if (resolved)
501
+ storedName = resolved;
502
+ }
497
503
  // Use topic name, not tmux session name — tmux names include the project prefix
498
504
  // which causes cascading names like ai-guy-ai-guy-ai-guy-topic-1 on each respawn.
499
505
  const topicName = storedName || `topic-${topicId}`;
@@ -773,7 +779,7 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
773
779
  // Prevents duplicate concurrent spawns for the same topic when messages
774
780
  // arrive faster than the async spawn completes.
775
781
  const spawningTopics = new Set();
776
- telegram.onTopicMessage = (msg) => {
782
+ telegram.onTopicMessage = async (msg) => {
777
783
  const topicId = msg.metadata?.messageThreadId ?? null;
778
784
  if (!topicId)
779
785
  return;
@@ -944,8 +950,16 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
944
950
  console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate`);
945
951
  return;
946
952
  }
947
- const spawnName = storedTopicName || `topic-${topicId}`;
948
953
  spawningTopics.add(topicId);
954
+ // Resolve topic name — try in-memory, then active probe, then fallback
955
+ let spawnName = storedTopicName;
956
+ if (!spawnName || /^topic-\d+$/.test(spawnName)) {
957
+ const resolved = await telegram.resolveTopicName(topicId);
958
+ if (resolved)
959
+ spawnName = resolved;
960
+ }
961
+ if (!spawnName)
962
+ spawnName = `topic-${topicId}`;
949
963
  // Use the shared spawn helper that includes topic history + user context
950
964
  spawnSessionForTopic(sessionManager, telegram, spawnName, topicId, text, topicMemory, resolvedUser ?? undefined).then((newSessionName) => {
951
965
  telegram.registerTopicSession(topicId, newSessionName, spawnName);
@@ -2023,6 +2037,10 @@ export async function startServer(options) {
2023
2037
  // Send-only mode: no polling, but sendToTopic() works for session replies
2024
2038
  telegram = new TelegramAdapter(telegramConfig.config, config.stateDir);
2025
2039
  console.log(pc.green(` Telegram send-only mode (${isStandbyTelegram ? 'standby' : 'lifeline owns polling'})`));
2040
+ // Resolve any topic names still using the fallback "topic-NNNN" pattern
2041
+ telegram.resolveUnknownTopicNames().catch(err => {
2042
+ console.warn(`[telegram] Topic name resolution failed: ${err}`);
2043
+ });
2026
2044
  // Ensure topics exist even in send-only mode (createForumTopic is a simple API call)
2027
2045
  ensureAgentAttentionTopic(telegram, state).catch(err => {
2028
2046
  console.error(`[server] Failed to ensure Agent Attention topic: ${err}`);
@@ -2487,40 +2505,49 @@ export async function startServer(options) {
2487
2505
  else if (safeSenderName) {
2488
2506
  prefix = `[slack:${channelId} from ${safeSenderName}]`;
2489
2507
  }
2490
- // Write context file for the session
2508
+ // Build context for the session — inject inline like Telegram does
2509
+ // Use async fallback to fetch from Slack API if ring buffer is empty
2491
2510
  const tmpDir = '/tmp/instar-slack';
2492
2511
  fs.mkdirSync(tmpDir, { recursive: true });
2493
- const ctxPath = path.join(tmpDir, `ctx-${channelId}-${Date.now()}.txt`);
2494
- const history = slackAdapter.getChannelMessages(channelId, 30);
2512
+ const history = await slackAdapter.getChannelMessagesWithFallback(channelId, 30);
2495
2513
  const unansweredCount = slackAdapter.getUnansweredCount(channelId);
2496
2514
  const botUserId = slackAdapter.getBotUserId?.() ?? null;
2497
- // Build human-readable thread history (matches Telegram's context format)
2498
- const lines = [];
2499
- lines.push(`--- Thread History (last ${history.length} messages) ---`);
2500
- lines.push('IMPORTANT: Read this history carefully before taking any action.');
2501
- lines.push('Your task is to continue THIS conversation, not start something new.');
2502
- lines.push(`Channel: ${channelId}`);
2503
- lines.push('');
2504
- for (const m of history) {
2505
- const date = new Date(parseFloat(m.ts) * 1000);
2506
- const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
2507
- const isBot = botUserId && m.user === botUserId;
2508
- const label = isBot ? 'Agent' : (senderName || m.user);
2509
- lines.push(`[${time}] ${label}: ${m.text}`);
2515
+ // Build human-readable thread history (matches Telegram's inline context pattern)
2516
+ const contextLines = [];
2517
+ if (history.length > 0) {
2518
+ contextLines.push('CONTINUATION — You are resuming an EXISTING Slack conversation. Read the context below before responding. Do NOT ask what was being discussed.');
2519
+ contextLines.push('');
2520
+ contextLines.push(`--- Thread History (last ${history.length} messages) ---`);
2521
+ contextLines.push('IMPORTANT: Read this history carefully before taking any action.');
2522
+ contextLines.push('Your task is to continue THIS conversation, not start something new.');
2523
+ contextLines.push(`Channel: ${channelId}`);
2524
+ contextLines.push('');
2525
+ for (const m of history) {
2526
+ const date = new Date(parseFloat(m.ts) * 1000);
2527
+ const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
2528
+ const isBot = botUserId && m.user === botUserId;
2529
+ const label = isBot ? 'Agent' : (senderName || m.user);
2530
+ contextLines.push(`[${time}] ${label}: ${m.text}`);
2531
+ }
2532
+ contextLines.push('');
2533
+ contextLines.push('--- End Thread History ---');
2534
+ }
2535
+ else {
2536
+ console.warn(`[slack→context] No history available for channel ${channelId} — session starts without thread context`);
2510
2537
  }
2511
- lines.push('');
2512
- lines.push('--- End Thread History ---');
2513
- lines.push('');
2514
- lines.push('CRITICAL: You MUST relay your response back to Slack after responding.');
2515
- lines.push('Use the relay script (write ONLY your reply text — do NOT pipe or cat this file into the script):');
2516
- lines.push('');
2517
- lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${channelId}`);
2518
- lines.push('Your response text here');
2519
- lines.push('EOF');
2520
- lines.push('');
2521
- lines.push('Strip the [slack:] prefix before interpreting the message.');
2522
- lines.push('Only relay conversational text — not tool output, file contents, or internal reasoning.');
2523
- const contextData = lines.join('\n');
2538
+ contextLines.push('');
2539
+ contextLines.push('CRITICAL: You MUST relay your response back to Slack after responding.');
2540
+ contextLines.push('Use the relay script (write ONLY your reply text — do NOT pipe or cat this file into the script):');
2541
+ contextLines.push('');
2542
+ contextLines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${channelId}`);
2543
+ contextLines.push('Your response text here');
2544
+ contextLines.push('EOF');
2545
+ contextLines.push('');
2546
+ contextLines.push('Strip the [slack:] prefix before interpreting the message.');
2547
+ contextLines.push('Only relay conversational text — not tool output, file contents, or internal reasoning.');
2548
+ const contextData = contextLines.join('\n');
2549
+ // Also write to file as backup reference
2550
+ const ctxPath = path.join(tmpDir, `ctx-${channelId}-${Date.now()}.txt`);
2524
2551
  fs.writeFileSync(ctxPath, contextData);
2525
2552
  // Transform [image:path] and [document:path] tags into explicit read instructions
2526
2553
  let transformedContent = message.content.replace(/\[image:([^\]]+)\]/g, (_, imagePath) => {
@@ -2534,14 +2561,18 @@ export async function startServer(options) {
2534
2561
  }
2535
2562
  return `[User sent a file — it has been saved to ${docPath}. Read the file to view its contents]`;
2536
2563
  });
2537
- const FILE_THRESHOLD = 500;
2564
+ // Inject context INLINE in the bootstrap message (matches Telegram pattern).
2565
+ // This ensures the agent sees thread history immediately without needing to read a file.
2538
2566
  const fullMessage = `${prefix} ${transformedContent} (IMPORTANT: Read ${ctxPath} for thread history and Slack relay instructions — you MUST relay your response back.)`;
2539
- // Long messages: write to temp file and inject reference (matches Telegram pattern)
2567
+ // Large bootstrap messages: write to file with INLINE context included
2568
+ const FILE_THRESHOLD = 500;
2540
2569
  let bootstrapMessage;
2541
2570
  if (fullMessage.length > FILE_THRESHOLD) {
2571
+ // Write full message + inline context to the file so agent gets everything in one read
2542
2572
  const msgFilePath = path.join(tmpDir, `msg-${channelId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}.txt`);
2543
- fs.writeFileSync(msgFilePath, fullMessage);
2544
- bootstrapMessage = `${prefix} [Long message saved to ${msgFilePath} — read it to see the full message]`;
2573
+ const fullContent = `${fullMessage}\n\n${contextData}`;
2574
+ fs.writeFileSync(msgFilePath, fullContent);
2575
+ bootstrapMessage = `${prefix} [Long message saved to ${msgFilePath} — read it to see the full message and thread history]`;
2545
2576
  }
2546
2577
  else {
2547
2578
  bootstrapMessage = fullMessage;
@@ -2594,7 +2625,7 @@ export async function startServer(options) {
2594
2625
  // Route: DMs go to lifeline session, channels spawn new sessions
2595
2626
  const targetSession = isDM ? 'lifeline' : undefined;
2596
2627
  try {
2597
- const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId });
2628
+ const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId, slackChannelId: channelId });
2598
2629
  if (newSessionName) {
2599
2630
  slackAdapter.registerChannelSession(channelId, newSessionName);
2600
2631
  slackAdapter.trackMessageInjection(channelId, newSessionName, message.content);
@@ -3359,23 +3390,32 @@ export async function startServer(options) {
3359
3390
  _slackAdapter.removeChannelResume(slackChId);
3360
3391
  // Spawn a fresh session with recovery context
3361
3392
  await new Promise(resolve => setTimeout(resolve, 2000));
3362
- // Build a recovery bootstrap message with thread history
3363
- const history = _slackAdapter.getChannelMessages(slackChId, 30);
3393
+ // Build a recovery bootstrap message with thread history (inline, matching Telegram pattern)
3394
+ // Use async fallback to fetch from Slack API if ring buffer is empty (race condition on restart)
3395
+ const history = await _slackAdapter.getChannelMessagesWithFallback(slackChId, 30);
3364
3396
  const botUserId = _slackAdapter.getBotUserId?.() ?? null;
3365
3397
  const lines = [];
3398
+ lines.push(`CONTINUATION — You are resuming an EXISTING Slack conversation after context exhaustion. Read the context below and pick up where you left off. Do NOT ask what was being discussed.`);
3399
+ lines.push('');
3366
3400
  lines.push(`[RECOVERY] Previous session hit the context window limit. This is a FRESH restart with thread history.`);
3367
3401
  if (recoveryPrompt)
3368
3402
  lines.push(recoveryPrompt);
3369
3403
  lines.push('');
3370
- lines.push(`--- Thread History (last ${history.length} messages) ---`);
3371
- for (const m of history) {
3372
- const date = new Date(parseFloat(m.ts) * 1000);
3373
- const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
3374
- const isBot = botUserId && m.user === botUserId;
3375
- const label = isBot ? 'Agent' : m.user;
3376
- lines.push(`[${time}] ${label}: ${m.text}`);
3404
+ if (history.length > 0) {
3405
+ lines.push(`--- Thread History (last ${history.length} messages) ---`);
3406
+ for (const m of history) {
3407
+ const date = new Date(parseFloat(m.ts) * 1000);
3408
+ const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
3409
+ const isBot = botUserId && m.user === botUserId;
3410
+ const label = isBot ? 'Agent' : m.user;
3411
+ lines.push(`[${time}] ${label}: ${m.text}`);
3412
+ }
3413
+ lines.push('--- End Thread History ---');
3414
+ }
3415
+ else {
3416
+ console.warn(`[slack→recovery] WARNING: No history available for channel ${slackChId} — recovery context is empty. Ring buffer may not be populated yet.`);
3417
+ lines.push('[WARNING: Thread history unavailable — ring buffer may not be populated. Check Slack channel for recent messages before responding.]');
3377
3418
  }
3378
- lines.push('--- End Thread History ---');
3379
3419
  lines.push('');
3380
3420
  lines.push('CRITICAL: You MUST relay your response back to Slack.');
3381
3421
  lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${slackChId}`);
@@ -3386,9 +3426,9 @@ export async function startServer(options) {
3386
3426
  const ctxPath = path.join(tmpDir, `recovery-${slackChId}-${Date.now()}.txt`);
3387
3427
  const contextData = lines.join('\n');
3388
3428
  fs.writeFileSync(ctxPath, contextData);
3389
- 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.)`;
3429
+ const bootstrapMessage = `[slack:${slackChId}] ${contextData}`;
3390
3430
  try {
3391
- const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage);
3431
+ const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, undefined, { slackChannelId: slackChId });
3392
3432
  if (newSessionName) {
3393
3433
  _slackAdapter.registerChannelSession(slackChId, newSessionName);
3394
3434
  console.log(`[slack→recovery] Fresh session "${newSessionName}" spawned for channel ${slackChId} (context exhaustion recovery)`);