instar 0.25.2 → 0.25.4

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;AA0PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsnCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAy9GtE;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;AA0PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsnCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAslHtE;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"}
@@ -2515,10 +2515,15 @@ export async function startServer(options) {
2515
2515
  clearTimeout(timer);
2516
2516
  }
2517
2517
  };
2518
- // Start stall detection timer
2518
+ // Wire intelligence provider for LLM-gated stall confirmation
2519
+ if (sharedIntelligence) {
2520
+ slackAdapter.intelligence = sharedIntelligence;
2521
+ }
2522
+ // Start stall detection timer (with promise tracking)
2519
2523
  const stallTimeout = slackConfig.config.stallTimeoutMinutes ?? 5;
2524
+ const promiseTimeout = slackConfig.config.promiseTimeoutMinutes ?? 10;
2520
2525
  if (stallTimeout > 0) {
2521
- slackAdapter.startStallDetection(stallTimeout * 60 * 1000);
2526
+ slackAdapter.startStallDetection(stallTimeout * 60 * 1000, promiseTimeout * 60 * 1000);
2522
2527
  }
2523
2528
  // Wire session management callbacks for slash commands
2524
2529
  slackAdapter.onInterruptSession = async (sessionName) => {
@@ -2559,7 +2564,7 @@ export async function startServer(options) {
2559
2564
  sessionManager.sendKey(sessionName, value);
2560
2565
  console.log(`[slack] Prompt response injected: session="${sessionName}" key="${value}"`);
2561
2566
  };
2562
- // Standby commands will be wired after PresenceProxy is initialized (below)
2567
+ // Standby commands and triage status will be wired after PresenceProxy/TriageOrchestrator (below)
2563
2568
  }
2564
2569
  catch (err) {
2565
2570
  const reason = err instanceof Error ? err.message : String(err);
@@ -2856,32 +2861,63 @@ export async function startServer(options) {
2856
2861
  console.log(pc.green(' Session Watchdog enabled'));
2857
2862
  }
2858
2863
  // StallTriageNurse — LLM-powered session recovery (uses shared intelligence)
2864
+ // Platform-aware: works with Telegram topics AND Slack channels
2859
2865
  let triageNurse;
2860
- if (config.monitoring.triage?.enabled && telegram) {
2866
+ if (config.monitoring.triage?.enabled && (telegram || _slackAdapter)) {
2861
2867
  triageNurse = new StallTriageNurse({
2862
2868
  captureSessionOutput: (name, lines) => sessionManager.captureOutput(name, lines),
2863
2869
  isSessionAlive: (name) => sessionManager.isSessionAlive(name),
2864
2870
  sendKey: (name, key) => sessionManager.sendKey(name, key),
2865
2871
  sendInput: (name, text) => sessionManager.sendInput(name, text),
2866
2872
  getTopicHistory: (topicId, limit) => {
2867
- const entries = telegram.getTopicHistory(topicId, limit);
2868
- return entries.map(e => ({
2869
- text: e.text,
2870
- fromUser: e.fromUser,
2871
- timestamp: e.timestamp,
2872
- }));
2873
+ // Check if this is a Slack synthetic ID
2874
+ const slackChId = slackProxyChannelMap.get(topicId);
2875
+ if (slackChId && _slackAdapter) {
2876
+ const msgs = _slackAdapter.getChannelMessages(slackChId, limit);
2877
+ return msgs.map(m => ({ text: m.text, fromUser: true, timestamp: new Date(parseFloat(m.ts) * 1000).toISOString() }));
2878
+ }
2879
+ if (telegram) {
2880
+ const entries = telegram.getTopicHistory(topicId, limit);
2881
+ return entries.map(e => ({ text: e.text, fromUser: e.fromUser, timestamp: e.timestamp }));
2882
+ }
2883
+ return [];
2884
+ },
2885
+ sendToTopic: async (topicId, text) => {
2886
+ const slackChId = slackProxyChannelMap.get(topicId);
2887
+ if (slackChId && _slackAdapter) {
2888
+ await _slackAdapter.sendToChannel(slackChId, text);
2889
+ return;
2890
+ }
2891
+ if (telegram)
2892
+ await telegram.sendToTopic(topicId, text);
2893
+ },
2894
+ respawnSession: (name, topicId, options) => {
2895
+ if (telegram) {
2896
+ return respawnSessionForTopic(sessionManager, telegram, name, topicId, undefined, topicMemory, undefined, undefined, options);
2897
+ }
2898
+ // Slack respawn: kill and let next message trigger fresh session
2899
+ const stuckSession = sessionManager.listRunningSessions().find(s => s.tmuxSession === name);
2900
+ if (stuckSession)
2901
+ sessionManager.killSession(stuckSession.id);
2902
+ return Promise.resolve();
2903
+ },
2904
+ clearStallForTopic: (topicId) => {
2905
+ const slackChId = slackProxyChannelMap.get(topicId);
2906
+ if (slackChId && _slackAdapter) {
2907
+ _slackAdapter.clearStallTracking(slackChId);
2908
+ return;
2909
+ }
2910
+ if (telegram)
2911
+ telegram.clearStallTracking(topicId);
2873
2912
  },
2874
- sendToTopic: (topicId, text) => telegram.sendToTopic(topicId, text),
2875
- respawnSession: (name, topicId, options) => respawnSessionForTopic(sessionManager, telegram, name, topicId, undefined, topicMemory, undefined, undefined, options),
2876
- clearStallForTopic: (topicId) => telegram.clearStallTracking(topicId),
2877
2913
  }, {
2878
2914
  config: config.monitoring.triage,
2879
2915
  state,
2880
2916
  intelligence: sharedIntelligence,
2881
2917
  });
2882
- // Wire nurse into TelegramAdapter stall detection
2918
+ // Wire nurse into stall detection — both Telegram and Slack
2883
2919
  // Note: presenceProxy may be set later — use late-binding check
2884
- telegram.onStallDetected = async (topicId, sessionName, messageText, injectedAt) => {
2920
+ const stallTriageHandler = async (topicId, sessionName, messageText, injectedAt) => {
2885
2921
  // If PresenceProxy Tier 3 is actively handling this topic, defer to it
2886
2922
  if (presenceProxy) {
2887
2923
  const proxyState = presenceProxy.getState(topicId);
@@ -2896,27 +2932,58 @@ export async function startServer(options) {
2896
2932
  const result = await triageNurse.triage(topicId, sessionName, messageText, injectedAt, 'telegram_stall');
2897
2933
  return { resolved: result.resolved };
2898
2934
  };
2935
+ if (telegram) {
2936
+ telegram.onStallDetected = stallTriageHandler;
2937
+ }
2899
2938
  console.log(pc.green(' Stall Triage Nurse enabled'));
2900
2939
  }
2901
2940
  // TriageOrchestrator — next-gen session recovery with scoped Claude Code sessions
2941
+ // Platform-aware: works with Telegram topics AND Slack channels
2902
2942
  let triageOrchestrator;
2903
- if (config.monitoring.triageOrchestrator?.enabled && telegram) {
2943
+ if (config.monitoring.triageOrchestrator?.enabled && (telegram || _slackAdapter)) {
2904
2944
  triageOrchestrator = new TriageOrchestrator({
2905
2945
  captureSessionOutput: (name, lines) => sessionManager.captureOutput(name, lines),
2906
2946
  isSessionAlive: (name) => sessionManager.isSessionAlive(name),
2907
2947
  sendKey: (name, key) => sessionManager.sendKey(name, key),
2908
2948
  sendInput: (name, text) => sessionManager.sendInput(name, text),
2909
2949
  getTopicHistory: (topicId, limit) => {
2910
- const entries = telegram.getTopicHistory(topicId, limit);
2911
- return entries.map(e => ({
2912
- text: e.text,
2913
- fromUser: e.fromUser,
2914
- timestamp: e.timestamp,
2915
- }));
2950
+ const slackChId = slackProxyChannelMap.get(topicId);
2951
+ if (slackChId && _slackAdapter) {
2952
+ const msgs = _slackAdapter.getChannelMessages(slackChId, limit);
2953
+ return msgs.map(m => ({ text: m.text, fromUser: true, timestamp: new Date(parseFloat(m.ts) * 1000).toISOString() }));
2954
+ }
2955
+ if (telegram) {
2956
+ const entries = telegram.getTopicHistory(topicId, limit);
2957
+ return entries.map(e => ({ text: e.text, fromUser: e.fromUser, timestamp: e.timestamp }));
2958
+ }
2959
+ return [];
2960
+ },
2961
+ sendToTopic: async (topicId, text) => {
2962
+ const slackChId = slackProxyChannelMap.get(topicId);
2963
+ if (slackChId && _slackAdapter) {
2964
+ await _slackAdapter.sendToChannel(slackChId, text);
2965
+ return;
2966
+ }
2967
+ if (telegram)
2968
+ await telegram.sendToTopic(topicId, text);
2969
+ },
2970
+ respawnSession: (name, topicId, options) => {
2971
+ if (telegram)
2972
+ return respawnSessionForTopic(sessionManager, telegram, name, topicId, undefined, topicMemory, undefined, undefined, options);
2973
+ const stuckSession = sessionManager.listRunningSessions().find(s => s.tmuxSession === name);
2974
+ if (stuckSession)
2975
+ sessionManager.killSession(stuckSession.id);
2976
+ return Promise.resolve();
2977
+ },
2978
+ clearStallForTopic: (topicId) => {
2979
+ const slackChId = slackProxyChannelMap.get(topicId);
2980
+ if (slackChId && _slackAdapter) {
2981
+ _slackAdapter.clearStallTracking(slackChId);
2982
+ return;
2983
+ }
2984
+ if (telegram)
2985
+ telegram.clearStallTracking(topicId);
2916
2986
  },
2917
- sendToTopic: (topicId, text) => telegram.sendToTopic(topicId, text),
2918
- respawnSession: (name, topicId, options) => respawnSessionForTopic(sessionManager, telegram, name, topicId, undefined, topicMemory, undefined, undefined, options),
2919
- clearStallForTopic: (topicId) => telegram.clearStallTracking(topicId),
2920
2987
  spawnTriageSession: (name, options) => sessionManager.spawnTriageSession(name, options),
2921
2988
  getTriageSessionUuid: (sessionName) => {
2922
2989
  return _topicResumeMap?.findUuidForSession(sessionName) ?? undefined;
@@ -2957,33 +3024,37 @@ export async function startServer(options) {
2957
3024
  state,
2958
3025
  });
2959
3026
  // TriageOrchestrator takes over stall detection from StallTriageNurse
2960
- telegram.onStallDetected = async (topicId, sessionName, messageText, injectedAt) => {
3027
+ const triageStallHandler = async (topicId, sessionName, messageText, injectedAt) => {
2961
3028
  const result = await triageOrchestrator.activate(topicId, sessionName, 'stall_detector', messageText, injectedAt);
2962
3029
  return { resolved: result.resolved };
2963
3030
  };
2964
- // Cancel triage when stall tracking clears (session responded)
2965
- const origClearStall = telegram.clearStallTracking.bind(telegram);
2966
- telegram.clearStallTracking = (topicId) => {
2967
- origClearStall(topicId);
2968
- triageOrchestrator.onTargetSessionResponded(topicId);
2969
- };
2970
- // Wire /triage command
2971
- telegram.onGetTriageStatus = (topicId) => {
2972
- const ts = triageOrchestrator.getTriageState(topicId);
2973
- if (!ts)
2974
- return null;
2975
- return {
2976
- active: true,
2977
- classification: ts.classification,
2978
- checkCount: ts.checkCount,
2979
- lastCheck: new Date(ts.lastCheckAt).toISOString(),
3031
+ if (telegram) {
3032
+ telegram.onStallDetected = triageStallHandler;
3033
+ // Cancel triage when stall tracking clears (session responded)
3034
+ const origClearStall = telegram.clearStallTracking.bind(telegram);
3035
+ telegram.clearStallTracking = (topicId) => {
3036
+ origClearStall(topicId);
3037
+ triageOrchestrator.onTargetSessionResponded(topicId);
2980
3038
  };
2981
- };
3039
+ // Wire /triage command
3040
+ telegram.onGetTriageStatus = (topicId) => {
3041
+ const ts = triageOrchestrator.getTriageState(topicId);
3042
+ if (!ts)
3043
+ return null;
3044
+ return {
3045
+ active: true,
3046
+ classification: ts.classification,
3047
+ checkCount: ts.checkCount,
3048
+ lastCheck: new Date(ts.lastCheckAt).toISOString(),
3049
+ };
3050
+ };
3051
+ }
2982
3052
  console.log(pc.green(' Triage Orchestrator enabled (replaces Stall Triage Nurse for stall detection)'));
2983
3053
  }
2984
3054
  // SessionRecovery — fast mechanical recovery (JSONL analysis, no LLM)
3055
+ // Platform-aware: works with Telegram topics AND Slack channels
2985
3056
  let sessionRecovery;
2986
- if (telegram) {
3057
+ if (telegram || _slackAdapter) {
2987
3058
  sessionRecovery = new SessionRecovery({ enabled: true, projectDir: config.projectDir }, {
2988
3059
  isSessionAlive: (name) => sessionManager.isSessionAlive(name),
2989
3060
  getPanePid: (name) => {
@@ -2999,6 +3070,13 @@ export async function startServer(options) {
2999
3070
  }
3000
3071
  },
3001
3072
  killSession: (name) => {
3073
+ // Route through SessionManager to fire beforeSessionKill hook
3074
+ const session = sessionManager.listRunningSessions().find(s => s.tmuxSession === name);
3075
+ if (session) {
3076
+ sessionManager.killSession(session.id);
3077
+ return;
3078
+ }
3079
+ // Fallback: direct tmux kill for untracked sessions
3002
3080
  try {
3003
3081
  const tmux = detectTmuxPath();
3004
3082
  if (!tmux)
@@ -3008,32 +3086,86 @@ export async function startServer(options) {
3008
3086
  catch { /* may already be dead */ }
3009
3087
  },
3010
3088
  respawnSession: async (topicId, _sessionName, recoveryPrompt) => {
3011
- const targetSession = telegram.getSessionForTopic(topicId);
3012
- if (!targetSession)
3089
+ // Check Slack first (synthetic IDs are negative)
3090
+ const slackChId = slackProxyChannelMap.get(topicId);
3091
+ if (slackChId && _slackAdapter) {
3092
+ // Slack respawn: kill existing, next message triggers fresh session
3093
+ const session = sessionManager.listRunningSessions().find(s => s.tmuxSession === _sessionName);
3094
+ if (session)
3095
+ sessionManager.killSession(session.id);
3013
3096
  return;
3014
- await respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, undefined, topicMemory, undefined, recoveryPrompt, { silent: true });
3097
+ }
3098
+ if (telegram) {
3099
+ const targetSession = telegram.getSessionForTopic(topicId);
3100
+ if (!targetSession)
3101
+ return;
3102
+ await respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, undefined, topicMemory, undefined, recoveryPrompt, { silent: true });
3103
+ }
3104
+ },
3105
+ sendToTopic: async (topicId, message) => {
3106
+ const slackChId = slackProxyChannelMap.get(topicId);
3107
+ if (slackChId && _slackAdapter) {
3108
+ await _slackAdapter.sendToChannel(slackChId, message);
3109
+ return;
3110
+ }
3111
+ if (telegram)
3112
+ await telegram.sendToTopic(topicId, message);
3015
3113
  },
3016
- sendToTopic: async (topicId, message) => { await telegram.sendToTopic(topicId, message); },
3017
3114
  });
3018
3115
  console.log(pc.green(' Session Recovery enabled (mechanical fast-path)'));
3019
3116
  }
3020
3117
  // SessionMonitor — proactive session health monitoring
3118
+ // Platform-aware: monitors both Telegram and Slack sessions
3021
3119
  let sessionMonitor;
3022
- if (telegram) {
3120
+ if (telegram || _slackAdapter) {
3023
3121
  sessionMonitor = new SessionMonitor({
3024
- getActiveTopicSessions: () => telegram.getActiveTopicSessions(),
3122
+ getActiveTopicSessions: () => {
3123
+ const sessions = new Map();
3124
+ // Telegram topic sessions
3125
+ if (telegram && telegram.getActiveTopicSessions) {
3126
+ const telegramSessions = telegram.getActiveTopicSessions();
3127
+ for (const [topicId, sessionName] of telegramSessions) {
3128
+ sessions.set(topicId, sessionName);
3129
+ }
3130
+ }
3131
+ // Slack channel sessions (using synthetic IDs)
3132
+ if (_slackAdapter) {
3133
+ const registry = _slackAdapter.getChannelRegistry();
3134
+ for (const [channelId, entry] of Object.entries(registry)) {
3135
+ const syntheticId = slackChannelToSyntheticId(channelId);
3136
+ sessions.set(syntheticId, entry.sessionName);
3137
+ }
3138
+ }
3139
+ return sessions;
3140
+ },
3025
3141
  captureSessionOutput: (name, lines) => sessionManager.captureOutput(name, lines),
3026
3142
  isSessionAlive: (name) => sessionManager.isSessionAlive(name),
3027
3143
  getTopicHistory: (topicId, limit) => {
3028
- const history = telegram.getMessageLog?.();
3029
- if (!history)
3030
- return [];
3031
- return history
3032
- .filter((m) => m.topicId === topicId)
3033
- .slice(-limit)
3034
- .map((m) => ({ text: m.text, fromUser: m.fromUser, timestamp: m.timestamp }));
3144
+ const slackChId = slackProxyChannelMap.get(topicId);
3145
+ if (slackChId && _slackAdapter) {
3146
+ const msgs = _slackAdapter.getChannelMessages(slackChId, limit);
3147
+ return msgs.map(m => ({ text: m.text, fromUser: true, timestamp: new Date(parseFloat(m.ts) * 1000).toISOString() }));
3148
+ }
3149
+ if (telegram) {
3150
+ const history = telegram.getMessageLog?.();
3151
+ if (!history)
3152
+ return [];
3153
+ return history
3154
+ .filter((m) => m.topicId === topicId)
3155
+ .slice(-limit)
3156
+ .map((m) => ({ text: m.text, fromUser: m.fromUser, timestamp: m.timestamp }));
3157
+ }
3158
+ return [];
3159
+ },
3160
+ sendToTopic: async (topicId, text) => {
3161
+ const slackChId = slackProxyChannelMap.get(topicId);
3162
+ if (slackChId && _slackAdapter) {
3163
+ await _slackAdapter.sendToChannel(slackChId, text);
3164
+ return;
3165
+ }
3166
+ if (telegram)
3167
+ await telegram.sendToTopic(topicId, text);
3035
3168
  },
3036
- sendToTopic: (topicId, text) => telegram.sendToTopic(topicId, text),
3037
3169
  triggerTriage: triageOrchestrator
3038
3170
  ? async (topicId, sessionName, reason) => {
3039
3171
  const result = await triageOrchestrator.activate(topicId, sessionName, 'watchdog', reason, Date.now());
@@ -3535,6 +3667,21 @@ export async function startServer(options) {
3535
3667
  const syntheticId = slackChannelToSyntheticId(channelId);
3536
3668
  return presenceProxy.handleCommand(syntheticId, command, parseInt(userId, 10) || 0);
3537
3669
  };
3670
+ // Wire triage status for Slack !triage command
3671
+ if (triageOrchestrator) {
3672
+ _slackAdapter.onGetTriageStatus = (channelId) => {
3673
+ const syntheticId = slackChannelToSyntheticId(channelId);
3674
+ const ts = triageOrchestrator.getTriageState(syntheticId);
3675
+ if (!ts)
3676
+ return null;
3677
+ return {
3678
+ active: true,
3679
+ classification: ts.classification,
3680
+ checkCount: ts.checkCount,
3681
+ lastCheck: new Date(ts.lastCheckAt).toISOString(),
3682
+ };
3683
+ };
3684
+ }
3538
3685
  }
3539
3686
  console.log(pc.green(' Presence Proxy enabled (🔭 [Standby])'));
3540
3687
  }