instar 0.28.17 → 0.28.18

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.
@@ -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;AAs0CD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2nItE;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,CA2oItE;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"}
@@ -568,34 +568,26 @@ function wireTelegramCallbacks(telegram, sessionManager, state, quotaTracker, ac
568
568
  return sessionManager.isSessionAlive(sessionName);
569
569
  };
570
570
  // Stall verification — check if session has recent output activity
571
- // Shared activity patterns for detecting Claude Code session output
572
- const sessionActivePatterns = [
573
- /\bRead\b|\bWrite\b|\bEdit\b|\bBash\b|\bGrep\b|\bGlob\b|\bAgent\b|\bSkill\b|\bWebFetch\b|\bWebSearch\b/, // Tool names
574
- /⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/, // Spinner characters
575
- /\d+\s*tokens?/i, // Token counts
576
- /Sent \d+ chars/, // Telegram/Slack reply confirmation
577
- /Wandering|Thinking|thought for/i, // Claude thinking indicators
578
- /Searching for \d+ pattern/i, // Search activity
579
- /ctrl\+[a-z] to/i, // Interactive prompt hints
580
- /\+ .*\(thought for/, // Tool execution with thinking
581
- /mcp__/, // MCP tool calls
582
- /●|○|◉/, // Progress indicators
583
- ];
584
- /** Check if a session is actively producing output */
585
- const isSessionActiveCheck = async (sessionName) => {
571
+ telegram.onIsSessionActive = async (sessionName) => {
586
572
  const output = sessionManager.captureOutput(sessionName, 20);
587
573
  if (!output)
588
574
  return false;
589
575
  const lines = output.trim().split('\n').slice(-15);
576
+ // Look for signs of Claude Code activity in recent output
577
+ const activePatterns = [
578
+ /\bRead\b|\bWrite\b|\bEdit\b|\bBash\b|\bGrep\b|\bGlob\b/, // Tool names
579
+ /⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/, // Spinner characters
580
+ /\d+\s*tokens?/i, // Token counts
581
+ /Sent \d+ chars/, // Telegram reply confirmation
582
+ ];
590
583
  for (const line of lines) {
591
- for (const pattern of sessionActivePatterns) {
584
+ for (const pattern of activePatterns) {
592
585
  if (pattern.test(line))
593
586
  return true;
594
587
  }
595
588
  }
596
589
  return false;
597
590
  };
598
- telegram.onIsSessionActive = isSessionActiveCheck;
599
591
  // /switch-account — swap active Claude Code account
600
592
  if (accountSwitcher) {
601
593
  telegram.onSwitchAccountRequest = async (target, replyTopicId) => {
@@ -2745,31 +2737,6 @@ export async function startServer(options) {
2745
2737
  slackAdapter.onIsSessionAlive = (tmuxSession) => {
2746
2738
  return sessionManager.isSessionAlive(tmuxSession);
2747
2739
  };
2748
- slackAdapter.onIsSessionActive = async (sessionName) => {
2749
- const output = sessionManager.captureOutput(sessionName, 20);
2750
- if (!output)
2751
- return false;
2752
- const lines = output.trim().split('\n').slice(-15);
2753
- const activePatterns = [
2754
- /\bRead\b|\bWrite\b|\bEdit\b|\bBash\b|\bGrep\b|\bGlob\b|\bAgent\b|\bSkill\b|\bWebFetch\b|\bWebSearch\b/,
2755
- /⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
2756
- /\d+\s*tokens?/i,
2757
- /Sent \d+ chars/,
2758
- /Wandering|Thinking|thought for/i,
2759
- /Searching for \d+ pattern/i,
2760
- /ctrl\+[a-z] to/i,
2761
- /\+ .*\(thought for/,
2762
- /mcp__/,
2763
- /●|○|◉/,
2764
- ];
2765
- for (const line of lines) {
2766
- for (const pattern of activePatterns) {
2767
- if (pattern.test(line))
2768
- return true;
2769
- }
2770
- }
2771
- return false;
2772
- };
2773
2740
  // Wire prompt response callback — inject button presses into sessions
2774
2741
  slackAdapter.onPromptResponse = (channelId, promptId, value) => {
2775
2742
  // Look up which session is bound to this channel
@@ -3760,58 +3727,82 @@ export async function startServer(options) {
3760
3727
  const { HookEventReceiver } = await import('../monitoring/HookEventReceiver.js');
3761
3728
  const hookEventReceiver = new HookEventReceiver({ stateDir: config.stateDir });
3762
3729
  console.log(pc.green(' Hook event receiver enabled'));
3763
- // Wire PreCompact events to trigger proactive triage after compaction.
3764
- // When a session compacts, it goes idle at a prompt. If there are unanswered
3765
- // user messages (common with Telegram/Slack), Pattern 2b will reinject them.
3766
- if (triageOrchestrator && telegram) {
3767
- const _triageOrch = triageOrchestrator;
3768
- const _telegram = telegram;
3769
- hookEventReceiver.on('PreCompact', () => {
3770
- // Delay to let compaction + recovery hooks finish
3771
- setTimeout(() => {
3772
- const topicSessions = _telegram.getAllTopicSessions();
3773
- for (const [topicId, sessionName] of topicSessions) {
3774
- if (!sessionManager.isSessionAlive(sessionName))
3775
- continue;
3776
- const history = _telegram.getTopicHistory(topicId, 5);
3777
- const lastMsg = history[history.length - 1];
3778
- if (lastMsg?.fromUser) {
3779
- console.log(`[CompactionResume] PreCompact detected, topic ${topicId} has unanswered message — activating triage`);
3780
- _triageOrch.activate(topicId, sessionName, 'watchdog', lastMsg.text, Date.now()).catch(err => {
3781
- console.warn(`[CompactionResume] Triage activation failed for topic ${topicId}:`, err);
3782
- });
3730
+ // ── Compaction Resume: unified recovery for one session ──────────────
3731
+ // Robust against subsystem misconfiguration: works with or without
3732
+ // triageOrchestrator. If the orchestrator is available we prefer it
3733
+ // (richer recovery semantics); otherwise we fall back to a direct
3734
+ // tmux re-injection of the unanswered user message.
3735
+ //
3736
+ // Three independent triggers call into this:
3737
+ // 1. PreCompact hook event (Claude Code fires it — unreliable)
3738
+ // 2. SessionWatchdog 'compaction-idle' polling (default-enabled)
3739
+ // 3. POST /internal/compaction-resume (compaction-recovery.sh hook)
3740
+ const recoverCompactedSession = async (sessionName, triggerLabel) => {
3741
+ if (!sessionManager.isSessionAlive(sessionName))
3742
+ return false;
3743
+ // Telegram path
3744
+ if (telegram) {
3745
+ const topicId = telegram.getTopicForSession(sessionName);
3746
+ if (topicId) {
3747
+ const history = telegram.getTopicHistory(topicId, 5);
3748
+ const lastMsg = history[history.length - 1];
3749
+ if (lastMsg?.fromUser) {
3750
+ console.log(`[CompactionResume] (${triggerLabel}) topic ${topicId} session "${sessionName}" has unanswered message — recovering`);
3751
+ if (triageOrchestrator) {
3752
+ try {
3753
+ await triageOrchestrator.activate(topicId, sessionName, 'watchdog', lastMsg.text, Date.now());
3754
+ return true;
3755
+ }
3756
+ catch (err) {
3757
+ console.warn(`[CompactionResume] orchestrator failed, falling back to direct inject:`, err);
3758
+ }
3783
3759
  }
3784
- }
3785
- }, 10_000);
3786
- });
3787
- console.log(pc.green(' Compaction auto-resume wired to triage orchestrator (PreCompact event)'));
3788
- }
3789
- // Watchdog compaction-idle polling — fallback for when PreCompact events
3790
- // don't fire (Claude Code doesn't reliably emit them). The watchdog polls
3791
- // every 30s and detects sessions that compacted + are idle at a prompt.
3792
- if (watchdog && triageOrchestrator) {
3793
- const _triageOrch2 = triageOrchestrator;
3794
- watchdog.on('compaction-idle', (sessionName) => {
3795
- // Check Telegram topics
3796
- if (telegram) {
3797
- const topicId = telegram.getTopicForSession(sessionName);
3798
- if (topicId) {
3799
- const history = telegram.getTopicHistory(topicId, 5);
3800
- const lastMsg = history[history.length - 1];
3801
- if (lastMsg?.fromUser) {
3802
- console.log(`[CompactionResume] Watchdog detected compaction-idle, topic ${topicId} has unanswered message — activating triage`);
3803
- _triageOrch2.activate(topicId, sessionName, 'watchdog', lastMsg.text, Date.now()).catch(err => {
3804
- console.warn(`[CompactionResume] Triage activation failed for topic ${topicId}:`, err);
3805
- });
3806
- return;
3760
+ // Fallback: direct injection with topic tag so InputGuard accepts it.
3761
+ const tagged = `[telegram:${topicId}] ${lastMsg.text}`;
3762
+ const ok = sessionManager.injectMessage(sessionName, tagged);
3763
+ if (ok) {
3764
+ console.log(`[CompactionResume] (${triggerLabel}) direct re-inject OK for topic ${topicId}`);
3765
+ return true;
3807
3766
  }
3767
+ console.warn(`[CompactionResume] (${triggerLabel}) direct re-inject FAILED for topic ${topicId}`);
3808
3768
  }
3809
3769
  }
3810
- // Note: Slack compaction-resume is handled separately below (after
3811
- // slackChannelToSyntheticId is defined) to avoid TDZ reference errors.
3770
+ }
3771
+ // Slack path handled in the Slack-aware block below (needs slackChannelToSyntheticId).
3772
+ // We attempt it here lazily via a deferred handler set on globalThis to avoid TDZ.
3773
+ const slackHandler = globalThis.__instarSlackCompactionResume;
3774
+ if (slackHandler) {
3775
+ try {
3776
+ return await slackHandler(sessionName, triggerLabel);
3777
+ }
3778
+ catch { /* fall through */ }
3779
+ }
3780
+ return false;
3781
+ };
3782
+ // Trigger 1: PreCompact hook event — wired unconditionally now.
3783
+ hookEventReceiver.on('PreCompact', () => {
3784
+ // Delay to let compaction + recovery hooks finish
3785
+ setTimeout(() => {
3786
+ if (!telegram)
3787
+ return;
3788
+ const topicSessions = telegram.getAllTopicSessions();
3789
+ for (const [, sessionName] of topicSessions) {
3790
+ recoverCompactedSession(sessionName, 'PreCompact').catch(() => { });
3791
+ }
3792
+ }, 10_000);
3793
+ });
3794
+ console.log(pc.green(' Compaction auto-resume wired (PreCompact hook event)'));
3795
+ // Trigger 2: Watchdog 'compaction-idle' polling — wired unconditionally.
3796
+ if (watchdog) {
3797
+ watchdog.on('compaction-idle', (sessionName) => {
3798
+ recoverCompactedSession(sessionName, 'watchdog-poll').catch(() => { });
3812
3799
  });
3813
- console.log(pc.green(' Compaction auto-resume wired to watchdog polling (fallback)'));
3800
+ console.log(pc.green(' Compaction auto-resume wired (watchdog poll)'));
3814
3801
  }
3802
+ // Trigger 3: stash the recovery function on globalThis so the HTTP route
3803
+ // (registered later in AgentServer) and the compaction-recovery.sh hook
3804
+ // can invoke it via POST /internal/compaction-resume.
3805
+ globalThis.__instarCompactionRecover = recoverCompactedSession;
3815
3806
  // Subagent Tracker — monitors subagent lifecycle via hook events
3816
3807
  const { SubagentTracker } = await import('../monitoring/SubagentTracker.js');
3817
3808
  const subagentTracker = new SubagentTracker({ stateDir: config.stateDir });
@@ -3946,17 +3937,18 @@ export async function startServer(options) {
3946
3937
  slackChannelToSyntheticId(channelId);
3947
3938
  }
3948
3939
  }
3949
- // Slack compaction-resume wiring — needs slackChannelToSyntheticId which is now defined
3950
- if (watchdog && triageOrchestrator && _slackAdapter) {
3951
- const _triageOrch3 = triageOrchestrator;
3940
+ // Slack compaction-resume wiring — registered as a deferred handler the
3941
+ // unified recoverCompactedSession() helper above will call. Works with or
3942
+ // without triageOrchestrator (falls back to direct sessionManager inject).
3943
+ if (_slackAdapter) {
3952
3944
  const _slack = _slackAdapter;
3953
- watchdog.on('compaction-idle', (sessionName) => {
3945
+ const slackRecover = async (sessionName, triggerLabel) => {
3954
3946
  const channelId = _slack.getChannelForSession(sessionName);
3955
3947
  if (!channelId)
3956
- return;
3948
+ return false;
3957
3949
  const syntheticId = slackChannelToSyntheticId(channelId);
3958
- // Read from the Slack messages JSONL log (local, no API call)
3959
3950
  const slackLogPath = path.join(config.stateDir, 'slack-messages.jsonl');
3951
+ let lastUserMsg = null;
3960
3952
  try {
3961
3953
  const content = fs.readFileSync(slackLogPath, 'utf-8');
3962
3954
  const lines = content.trim().split('\n').slice(-10);
@@ -3964,21 +3956,37 @@ export async function startServer(options) {
3964
3956
  try {
3965
3957
  const msg = JSON.parse(lines[i]);
3966
3958
  if (msg.channelId === channelId) {
3967
- if (msg.fromUser) {
3968
- console.log(`[CompactionResume] Watchdog detected compaction-idle, Slack channel ${channelId} has unanswered message — activating triage`);
3969
- _triageOrch3.activate(syntheticId, sessionName, 'watchdog', msg.text || 'message after compaction', Date.now()).catch(err => {
3970
- console.warn(`[CompactionResume] Triage activation failed for Slack channel ${channelId}:`, err);
3971
- });
3972
- }
3973
- break; // Only check the most recent message for this channel
3959
+ if (msg.fromUser)
3960
+ lastUserMsg = msg;
3961
+ break;
3974
3962
  }
3975
3963
  }
3976
- catch { /* skip malformed line */ }
3964
+ catch { /* skip malformed */ }
3977
3965
  }
3978
3966
  }
3979
- catch { /* log file doesn't exist — no Slack messages */ }
3980
- });
3981
- console.log(pc.green(' Compaction auto-resume wired to watchdog for Slack channels'));
3967
+ catch { /* no log */ }
3968
+ if (!lastUserMsg)
3969
+ return false;
3970
+ const text = lastUserMsg.text || 'message after compaction';
3971
+ console.log(`[CompactionResume] (${triggerLabel}) Slack channel ${channelId} has unanswered message — recovering`);
3972
+ if (triageOrchestrator) {
3973
+ try {
3974
+ await triageOrchestrator.activate(syntheticId, sessionName, 'watchdog', text, Date.now());
3975
+ return true;
3976
+ }
3977
+ catch (err) {
3978
+ console.warn(`[CompactionResume] Slack orchestrator failed, falling back to direct inject:`, err);
3979
+ }
3980
+ }
3981
+ const ok = sessionManager.injectMessage(sessionName, text);
3982
+ if (ok) {
3983
+ console.log(`[CompactionResume] (${triggerLabel}) Slack direct re-inject OK for channel ${channelId}`);
3984
+ return true;
3985
+ }
3986
+ return false;
3987
+ };
3988
+ globalThis.__instarSlackCompactionResume = slackRecover;
3989
+ console.log(pc.green(' Compaction auto-resume registered (Slack channels)'));
3982
3990
  }
3983
3991
  let presenceProxy;
3984
3992
  if (sharedIntelligence && telegram) {