instar 0.23.15 → 0.23.16

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 (58) hide show
  1. package/dashboard/index.html +23 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +70 -5
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/PostUpdateMigrator.d.ts +11 -0
  6. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  7. package/dist/core/PostUpdateMigrator.js +69 -0
  8. package/dist/core/PostUpdateMigrator.js.map +1 -1
  9. package/dist/core/SessionManager.d.ts +18 -0
  10. package/dist/core/SessionManager.d.ts.map +1 -1
  11. package/dist/core/SessionManager.js +82 -2
  12. package/dist/core/SessionManager.js.map +1 -1
  13. package/dist/core/TopicResumeMap.d.ts +14 -2
  14. package/dist/core/TopicResumeMap.d.ts.map +1 -1
  15. package/dist/core/TopicResumeMap.js +36 -41
  16. package/dist/core/TopicResumeMap.js.map +1 -1
  17. package/dist/core/types.d.ts +2 -0
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js.map +1 -1
  20. package/dist/data/http-hook-templates.js +10 -10
  21. package/dist/data/http-hook-templates.js.map +1 -1
  22. package/dist/memory/TopicMemory.js +3 -3
  23. package/dist/memory/TopicMemory.js.map +1 -1
  24. package/dist/monitoring/SessionRecovery.d.ts +110 -0
  25. package/dist/monitoring/SessionRecovery.d.ts.map +1 -0
  26. package/dist/monitoring/SessionRecovery.js +432 -0
  27. package/dist/monitoring/SessionRecovery.js.map +1 -0
  28. package/dist/monitoring/crash-detector.d.ts +50 -0
  29. package/dist/monitoring/crash-detector.d.ts.map +1 -0
  30. package/dist/monitoring/crash-detector.js +224 -0
  31. package/dist/monitoring/crash-detector.js.map +1 -0
  32. package/dist/monitoring/jsonl-truncator.d.ts +44 -0
  33. package/dist/monitoring/jsonl-truncator.d.ts.map +1 -0
  34. package/dist/monitoring/jsonl-truncator.js +224 -0
  35. package/dist/monitoring/jsonl-truncator.js.map +1 -0
  36. package/dist/monitoring/stall-detector.d.ts +34 -0
  37. package/dist/monitoring/stall-detector.d.ts.map +1 -0
  38. package/dist/monitoring/stall-detector.js +151 -0
  39. package/dist/monitoring/stall-detector.js.map +1 -0
  40. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  41. package/dist/scheduler/JobScheduler.js +16 -2
  42. package/dist/scheduler/JobScheduler.js.map +1 -1
  43. package/dist/server/WebSocketManager.d.ts +2 -0
  44. package/dist/server/WebSocketManager.d.ts.map +1 -1
  45. package/dist/server/WebSocketManager.js +24 -3
  46. package/dist/server/WebSocketManager.js.map +1 -1
  47. package/dist/server/routes.d.ts.map +1 -1
  48. package/dist/server/routes.js +52 -4
  49. package/dist/server/routes.js.map +1 -1
  50. package/package.json +2 -1
  51. package/scripts/pre-push-gate.js +149 -0
  52. package/src/data/builtin-manifest.json +63 -63
  53. package/src/data/http-hook-templates.ts +10 -10
  54. package/src/templates/hooks/telegram-topic-context.sh +4 -4
  55. package/upgrades/0.23.10.md +19 -0
  56. package/upgrades/0.23.11.md +21 -0
  57. package/upgrades/0.23.15.md +14 -1
  58. package/upgrades/0.23.16.md +25 -0
@@ -1881,6 +1881,9 @@
1881
1881
  let activeSession = null;
1882
1882
  let term = null;
1883
1883
  let fitAddon = null;
1884
+ let historyLinesLoaded = 2000; // matches initial subscribe capture
1885
+ let historyLoading = false;
1886
+ let historyExhausted = false; // true when we've loaded all available history
1884
1887
 
1885
1888
  // ── Auth ─────────────────────────────────────────────────
1886
1889
  function authenticate() {
@@ -2004,6 +2007,26 @@
2004
2007
  }
2005
2008
  break;
2006
2009
 
2010
+ case 'history':
2011
+ if (msg.session === activeSession && term) {
2012
+ const prevLineCount = historyLinesLoaded;
2013
+ historyLinesLoaded = msg.lines;
2014
+ // Check if we got more content than before
2015
+ const newLineCount = (msg.data || '').split('\n').length;
2016
+ const oldLineCount = prevLineCount;
2017
+ if (newLineCount <= oldLineCount + 10) {
2018
+ // No meaningful new content — we've hit the buffer limit
2019
+ historyExhausted = true;
2020
+ }
2021
+ // Rewrite terminal with expanded history
2022
+ term.clear();
2023
+ term.write(msg.data);
2024
+ // Don't auto-scroll — user is reading history
2025
+ historyLoading = false;
2026
+ hideHistorySpinner();
2027
+ }
2028
+ break;
2029
+
2007
2030
  case 'session_ended':
2008
2031
  if (msg.session === activeSession) {
2009
2032
  // Session ended — show in terminal
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwPH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAoiCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAk3EtE;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;AAwPH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgkCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAy5EtE;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"}
@@ -283,7 +283,7 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
283
283
  let usedFallback = false;
284
284
  if (topicMemory?.isReady()) {
285
285
  try {
286
- contextContent = topicMemory.formatContextForSession(topicId, 30);
286
+ contextContent = topicMemory.formatContextForSession(topicId, 50);
287
287
  }
288
288
  catch (err) {
289
289
  // @silent-fallback-ok — TopicMemory format, JSONL fallback
@@ -294,12 +294,16 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
294
294
  if (!contextContent) {
295
295
  usedFallback = true;
296
296
  try {
297
- const history = telegram.getTopicHistory(topicId, 20);
297
+ const history = telegram.getTopicHistory(topicId, 50);
298
298
  if (history.length > 0) {
299
299
  const lines = [];
300
300
  lines.push(`--- Thread History (last ${history.length} messages) ---`);
301
301
  lines.push(`IMPORTANT: Read this history carefully before taking any action.`);
302
302
  lines.push(`Your task is to continue THIS conversation, not start something new.`);
303
+ const topicName = telegram.getTopicName?.(topicId);
304
+ if (topicName) {
305
+ lines.push(`Topic: ${topicName}`);
306
+ }
303
307
  lines.push(``);
304
308
  for (const m of history) {
305
309
  // Use actual sender name if available (multi-user topics), fall back to generic
@@ -307,7 +311,7 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
307
311
  ? (m.senderName || 'User')
308
312
  : 'Agent';
309
313
  const ts = m.timestamp ? new Date(m.timestamp).toISOString().slice(11, 19) : '??:??';
310
- const text = (m.text || '').slice(0, 300);
314
+ const text = (m.text || '').slice(0, 2000);
311
315
  lines.push(`[${ts}] ${sender}: ${text}`);
312
316
  }
313
317
  lines.push(``);
@@ -434,13 +438,30 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
434
438
  if (resumeSessionId) {
435
439
  _topicResumeMap?.remove(topicId);
436
440
  }
441
+ // Proactive UUID save — schedule an immediate discovery attempt after spawn.
442
+ // The JSONL file appears ~3-5 seconds after Claude Code starts.
443
+ // This is belt-and-suspenders alongside the 60s heartbeat and beforeSessionKill.
444
+ if (_topicResumeMap && !resumeSessionId) {
445
+ setTimeout(() => {
446
+ try {
447
+ const uuid = _topicResumeMap.findClaudeSessionUuid();
448
+ if (uuid) {
449
+ _topicResumeMap.save(topicId, uuid, newSessionName);
450
+ console.log(`[spawnSessionForTopic] Proactive UUID save: ${uuid} for topic ${topicId}`);
451
+ }
452
+ }
453
+ catch (err) {
454
+ console.error(`[spawnSessionForTopic] Proactive UUID save failed:`, err);
455
+ }
456
+ }, 8000);
457
+ }
437
458
  return newSessionName;
438
459
  }
439
460
  /**
440
461
  * Respawn a session for a topic, including thread history in the bootstrap.
441
462
  * This prevents "thread drift" where respawned sessions lose context.
442
463
  */
443
- async function respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, latestMessage, topicMemory, userProfile) {
464
+ async function respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, latestMessage, topicMemory, userProfile, recoveryPrompt) {
444
465
  console.log(`[telegram→session] Session "${targetSession}" needs respawn for topic ${topicId}`);
445
466
  // Save the old session's Claude UUID before respawning so --resume can reattach context
446
467
  if (_topicResumeMap) {
@@ -459,7 +480,12 @@ async function respawnSessionForTopic(sessionManager, telegram, targetSession, t
459
480
  // Use topic name, not tmux session name — tmux names include the project prefix
460
481
  // which causes cascading names like ai-guy-ai-guy-ai-guy-topic-1 on each respawn.
461
482
  const topicName = storedName || `topic-${topicId}`;
462
- const newSessionName = await spawnSessionForTopic(sessionManager, telegram, topicName, topicId, latestMessage, topicMemory, userProfile);
483
+ // If this is a recovery respawn, prepend the recovery context to the message
484
+ // so the session knows what happened and can avoid repeating the failure.
485
+ const effectiveMessage = recoveryPrompt
486
+ ? `${recoveryPrompt}\n\n${latestMessage || 'Session recovered — continue where you left off.'}`
487
+ : latestMessage;
488
+ const newSessionName = await spawnSessionForTopic(sessionManager, telegram, topicName, topicId, effectiveMessage, topicMemory, userProfile);
463
489
  telegram.registerTopicSession(topicId, newSessionName, topicName);
464
490
  await telegram.sendToTopic(topicId, `Session respawned.`);
465
491
  console.log(`[telegram→session] Respawned "${newSessionName}" for topic ${topicId}`);
@@ -2013,6 +2039,26 @@ export async function startServer(options) {
2013
2039
  resumeHeartbeatInterval.unref();
2014
2040
  console.log(pc.green(' Resume heartbeat: active (60s interval)'));
2015
2041
  }
2042
+ // Save Claude session UUID before any session kill so the topic can be
2043
+ // resumed later with --resume. This fires BEFORE the tmux session is
2044
+ // destroyed, so the UUID can still be discovered from the JSONL mtime.
2045
+ if (_topicResumeMap && telegram) {
2046
+ sessionManager.on('beforeSessionKill', (session) => {
2047
+ try {
2048
+ const topicId = telegram.getTopicForSession(session.tmuxSession);
2049
+ if (!topicId)
2050
+ return;
2051
+ const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession);
2052
+ if (uuid) {
2053
+ _topicResumeMap.save(topicId, uuid, session.tmuxSession);
2054
+ console.log(`[beforeSessionKill] Saved resume UUID ${uuid} for topic ${topicId} (session: ${session.name})`);
2055
+ }
2056
+ }
2057
+ catch (err) {
2058
+ console.error(`[beforeSessionKill] Failed to save resume UUID:`, err);
2059
+ }
2060
+ });
2061
+ }
2016
2062
  if (scheduler) {
2017
2063
  sessionManager.on('sessionComplete', (session) => {
2018
2064
  scheduler.processQueue();
@@ -2450,6 +2496,25 @@ export async function startServer(options) {
2450
2496
  const { SubagentTracker } = await import('../monitoring/SubagentTracker.js');
2451
2497
  const subagentTracker = new SubagentTracker({ stateDir: config.stateDir });
2452
2498
  console.log(pc.green(' Subagent tracker enabled'));
2499
+ // Wire subagent awareness into zombie cleanup — prevents killing sessions
2500
+ // that are idle at the prompt but waiting for subagent results.
2501
+ const MAX_SUBAGENT_WAIT_MS = 60 * 60_000; // 60 minutes — stale subagent safety cap
2502
+ sessionManager.setSubagentChecker((session) => {
2503
+ if (!session.claudeSessionId)
2504
+ return false;
2505
+ const active = subagentTracker.getActiveSubagents(session.claudeSessionId);
2506
+ if (active.length === 0)
2507
+ return false;
2508
+ // Safety cap: if ALL active subagents have been running > 60 minutes,
2509
+ // treat them as stale (likely missed a SubagentStop event) and allow the kill.
2510
+ const now = Date.now();
2511
+ const allStale = active.every(a => now - new Date(a.startedAt).getTime() > MAX_SUBAGENT_WAIT_MS);
2512
+ if (allStale) {
2513
+ console.warn(`[SessionManager] Session "${session.name}" has ${active.length} stale subagent(s) (>60m). Allowing zombie kill.`);
2514
+ return false;
2515
+ }
2516
+ return true;
2517
+ });
2453
2518
  // Worktree Monitor — detects orphaned worktrees after sessions complete
2454
2519
  const { WorktreeMonitor } = await import('../monitoring/WorktreeMonitor.js');
2455
2520
  const worktreeMonitor = new WorktreeMonitor({