instar 0.25.0 → 0.25.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.
@@ -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,CAu2GtE;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,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"}
@@ -1701,17 +1701,44 @@ export async function startServer(options) {
1701
1701
  }
1702
1702
  // If handle() returned false, fall through to relay
1703
1703
  }
1704
- // Relay to Telegram if adapter is available and session has a topic binding
1704
+ // Relay to messaging platform if adapter is available and session has a binding
1705
1705
  if (classification.action === 'relay' || classification.action === 'auto-approve') {
1706
+ let relayed = false;
1707
+ // Try Telegram first
1706
1708
  if (telegram) {
1707
1709
  const topicId = telegram.getTopicForSession(prompt.sessionName);
1708
1710
  if (topicId) {
1709
1711
  try {
1710
1712
  await telegram.relayPrompt(topicId, prompt);
1711
- console.log(`[PromptGate] Relayed ${prompt.type} prompt to topic ${topicId}`);
1713
+ console.log(`[PromptGate] Relayed ${prompt.type} prompt to Telegram topic ${topicId}`);
1714
+ relayed = true;
1712
1715
  }
1713
1716
  catch (relayErr) {
1714
- console.error(`[PromptGate] Relay failed: ${relayErr instanceof Error ? relayErr.message : relayErr}`);
1717
+ console.error(`[PromptGate] Telegram relay failed: ${relayErr instanceof Error ? relayErr.message : relayErr}`);
1718
+ }
1719
+ }
1720
+ }
1721
+ // Try Slack if not already relayed via Telegram
1722
+ if (!relayed && _slackAdapter) {
1723
+ const channelId = _slackAdapter.getChannelForSession(prompt.sessionName);
1724
+ if (channelId) {
1725
+ try {
1726
+ const question = prompt.summary || 'Agent needs your input';
1727
+ const options = (prompt.options || []).map((opt, i) => ({
1728
+ label: opt.label.slice(0, 75),
1729
+ value: opt.key,
1730
+ primary: i === 0,
1731
+ }));
1732
+ if (options.length > 0) {
1733
+ await _slackAdapter.relayPrompt(channelId, prompt.id, question, options);
1734
+ }
1735
+ else {
1736
+ await _slackAdapter.sendToChannel(channelId, `⏳ *Agent needs your input:*\n${question}\n\n_Reply in this channel to respond._`);
1737
+ }
1738
+ console.log(`[PromptGate] Relayed ${prompt.type} prompt to Slack channel ${channelId}`);
1739
+ }
1740
+ catch (relayErr) {
1741
+ console.error(`[PromptGate] Slack relay failed: ${relayErr instanceof Error ? relayErr.message : relayErr}`);
1715
1742
  }
1716
1743
  }
1717
1744
  }
@@ -1803,6 +1830,29 @@ export async function startServer(options) {
1803
1830
  if (telemetryHeartbeat) {
1804
1831
  telemetryHeartbeat.start();
1805
1832
  }
1833
+ // Helper: resolve pending plan prompts when a prompt response arrives.
1834
+ // Calls the internal route endpoint to mark the plan prompt as resolved,
1835
+ // which unblocks the PreToolUse hook that's polling for the response.
1836
+ const resolvePlanPromptForSession = (sessionName, key) => {
1837
+ const port = config.port ?? 4042;
1838
+ const payload = JSON.stringify({ sessionName, key });
1839
+ const http = require('node:http');
1840
+ const req = http.request({
1841
+ hostname: '127.0.0.1',
1842
+ port,
1843
+ path: '/hooks/plan-prompt/resolve',
1844
+ method: 'POST',
1845
+ headers: {
1846
+ 'Content-Type': 'application/json',
1847
+ 'Content-Length': Buffer.byteLength(payload),
1848
+ 'Authorization': `Bearer ${config.authToken}`,
1849
+ },
1850
+ timeout: 2000,
1851
+ });
1852
+ req.on('error', () => { }); // Best-effort
1853
+ req.write(payload);
1854
+ req.end();
1855
+ };
1806
1856
  // Set up Telegram if configured
1807
1857
  // When --no-telegram is set (lifeline owns polling), create adapter in send-only mode
1808
1858
  // so the server can still relay replies via /telegram/reply/:topicId
@@ -1831,6 +1881,8 @@ export async function startServer(options) {
1831
1881
  console.warn(`[PromptGate] Skipping injection — session "${sessionName}" is no longer alive`);
1832
1882
  return false;
1833
1883
  }
1884
+ // Also resolve any pending plan prompt for this session (unblocks the hook)
1885
+ resolvePlanPromptForSession(sessionName, key);
1834
1886
  return sessionManager.sendKey(sessionName, key);
1835
1887
  };
1836
1888
  telegram.onPromptTextResponse = (sessionName, text) => {
@@ -1871,6 +1923,8 @@ export async function startServer(options) {
1871
1923
  console.warn(`[PromptGate] Skipping injection — session "${sessionName}" is no longer alive`);
1872
1924
  return false;
1873
1925
  }
1926
+ // Also resolve any pending plan prompt for this session (unblocks the hook)
1927
+ resolvePlanPromptForSession(sessionName, key);
1874
1928
  return sessionManager.sendKey(sessionName, key);
1875
1929
  };
1876
1930
  telegram.onPromptTextResponse = (sessionName, text) => {
@@ -2301,14 +2355,14 @@ export async function startServer(options) {
2301
2355
  lines.push('--- End Thread History ---');
2302
2356
  lines.push('');
2303
2357
  lines.push('CRITICAL: You MUST relay your response back to Slack after responding.');
2304
- lines.push('Use the relay script:');
2358
+ lines.push('Use the relay script (write ONLY your reply text — do NOT pipe or cat this file into the script):');
2305
2359
  lines.push('');
2306
2360
  lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${channelId}`);
2307
2361
  lines.push('Your response text here');
2308
2362
  lines.push('EOF');
2309
2363
  lines.push('');
2310
2364
  lines.push('Strip the [slack:] prefix before interpreting the message.');
2311
- lines.push('Only relay conversational text — not tool output or internal reasoning.');
2365
+ lines.push('Only relay conversational text — not tool output, file contents, or internal reasoning.');
2312
2366
  const contextData = lines.join('\n');
2313
2367
  fs.writeFileSync(ctxPath, contextData);
2314
2368
  // Transform [image:path] and [document:path] tags into explicit read instructions
@@ -2364,8 +2418,7 @@ export async function startServer(options) {
2364
2418
  sessionManager.injectMessage(existingSession, bootstrapMessage);
2365
2419
  // Track for stall detection
2366
2420
  slackAdapter.trackMessageInjection(channelId, existingSession, message.content);
2367
- // Delivery confirmation
2368
- slackAdapter.sendToChannel(channelId, '✓ Delivered').catch(() => { });
2421
+ // Delivery confirmation via reaction only (no text message — the ✅ reaction is sufficient)
2369
2422
  }
2370
2423
  catch (injectErr) {
2371
2424
  console.error(`[slack→session] Injection failed: ${injectErr instanceof Error ? injectErr.message : injectErr}`);
@@ -2491,6 +2544,21 @@ export async function startServer(options) {
2491
2544
  slackAdapter.onIsSessionAlive = (tmuxSession) => {
2492
2545
  return sessionManager.isSessionAlive(tmuxSession);
2493
2546
  };
2547
+ // Wire prompt response callback — inject button presses into sessions
2548
+ slackAdapter.onPromptResponse = (channelId, promptId, value) => {
2549
+ // Look up which session is bound to this channel
2550
+ const sessionName = slackAdapter.getSessionForChannel(channelId);
2551
+ if (!sessionName) {
2552
+ console.warn(`[slack] Prompt response for channel ${channelId} but no session bound`);
2553
+ return;
2554
+ }
2555
+ if (!sessionManager.isSessionAlive(sessionName)) {
2556
+ console.warn(`[slack] Prompt response for dead session "${sessionName}"`);
2557
+ return;
2558
+ }
2559
+ sessionManager.sendKey(sessionName, value);
2560
+ console.log(`[slack] Prompt response injected: session="${sessionName}" key="${value}"`);
2561
+ };
2494
2562
  // Standby commands will be wired after PresenceProxy is initialized (below)
2495
2563
  }
2496
2564
  catch (err) {
@@ -2574,19 +2642,34 @@ export async function startServer(options) {
2574
2642
  sessionManager.startMonitoring();
2575
2643
  // Proactive resume heartbeat: every 60s, update the topic→UUID mapping
2576
2644
  // for all active topic-linked sessions. Ensures crash recovery via --resume.
2577
- if (_topicResumeMap && telegram) {
2645
+ if (_topicResumeMap && (telegram || _slackAdapter)) {
2578
2646
  const resumeHeartbeatInterval = setInterval(() => {
2579
2647
  try {
2580
- const topicSessions = telegram.getAllTopicSessions();
2581
- // Enrich with authoritative Claude session IDs from SessionManager
2582
2648
  const enriched = new Map();
2583
- for (const [topicId, sessionName] of topicSessions) {
2584
- const sessions = sessionManager.listRunningSessions();
2585
- const session = sessions.find(s => s.tmuxSession === sessionName);
2586
- enriched.set(topicId, {
2587
- sessionName,
2588
- claudeSessionId: session?.claudeSessionId ?? undefined,
2589
- });
2649
+ // Telegram topic-session mappings
2650
+ if (telegram) {
2651
+ const topicSessions = telegram.getAllTopicSessions();
2652
+ for (const [topicId, sessionName] of topicSessions) {
2653
+ const sessions = sessionManager.listRunningSessions();
2654
+ const session = sessions.find(s => s.tmuxSession === sessionName);
2655
+ enriched.set(topicId, {
2656
+ sessionName,
2657
+ claudeSessionId: session?.claudeSessionId ?? undefined,
2658
+ });
2659
+ }
2660
+ }
2661
+ // Slack channel-session mappings (use synthetic IDs for compatibility)
2662
+ if (_slackAdapter) {
2663
+ const registry = _slackAdapter.getChannelRegistry();
2664
+ for (const [channelId, entry] of Object.entries(registry)) {
2665
+ const syntheticId = slackChannelToSyntheticId(channelId);
2666
+ const sessions = sessionManager.listRunningSessions();
2667
+ const session = sessions.find(s => s.tmuxSession === entry.sessionName);
2668
+ enriched.set(syntheticId, {
2669
+ sessionName: entry.sessionName,
2670
+ claudeSessionId: session?.claudeSessionId ?? undefined,
2671
+ });
2672
+ }
2590
2673
  }
2591
2674
  _topicResumeMap?.refreshResumeMappings(enriched);
2592
2675
  }
@@ -2601,19 +2684,21 @@ export async function startServer(options) {
2601
2684
  // Save Claude session UUID before any session kill so the topic can be
2602
2685
  // resumed later with --resume. This fires BEFORE the tmux session is
2603
2686
  // destroyed, so the UUID can still be discovered from the JSONL mtime.
2604
- if (_topicResumeMap && telegram) {
2687
+ if (_topicResumeMap) {
2605
2688
  sessionManager.on('beforeSessionKill', (session) => {
2606
2689
  try {
2607
- // Save Telegram topic resume UUID
2608
- const topicId = telegram.getTopicForSession(session.tmuxSession);
2609
- if (topicId) {
2610
- const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
2611
- if (uuid) {
2612
- _topicResumeMap.save(topicId, uuid, session.tmuxSession);
2613
- console.log(`[beforeSessionKill] Saved resume UUID ${uuid} for topic ${topicId} (session: ${session.name}, source: ${session.claudeSessionId ? 'hook' : 'mtime'})`);
2690
+ // Save Telegram topic resume UUID (if Telegram is configured)
2691
+ if (telegram) {
2692
+ const topicId = telegram.getTopicForSession(session.tmuxSession);
2693
+ if (topicId) {
2694
+ const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
2695
+ if (uuid) {
2696
+ _topicResumeMap.save(topicId, uuid, session.tmuxSession);
2697
+ console.log(`[beforeSessionKill] Saved resume UUID ${uuid} for topic ${topicId} (session: ${session.name}, source: ${session.claudeSessionId ? 'hook' : 'mtime'})`);
2698
+ }
2614
2699
  }
2615
2700
  }
2616
- // Save Slack channel resume UUID
2701
+ // Save Slack channel resume UUID (if Slack is configured)
2617
2702
  if (_slackAdapter) {
2618
2703
  const channelId = _slackAdapter.getChannelForSession(session.tmuxSession);
2619
2704
  if (channelId) {
@@ -2740,21 +2825,31 @@ export async function startServer(options) {
2740
2825
  watchdog = new SessionWatchdog(config, sessionManager, state);
2741
2826
  watchdog.intelligence = sharedIntelligence ?? null;
2742
2827
  watchdog.on('intervention', (event) => {
2828
+ const levelNames = ['Monitoring', 'Ctrl+C', 'SIGTERM', 'SIGKILL', 'Kill Session'];
2829
+ const levelName = levelNames[event.level] || `Level ${event.level}`;
2830
+ const msg = `🔧 Watchdog [${levelName}]: ${event.action}\nStuck: \`${event.stuckCommand.slice(0, 60)}\``;
2743
2831
  if (telegram) {
2744
2832
  const topicId = telegram.getTopicForSession(event.sessionName);
2745
- if (topicId) {
2746
- const levelNames = ['Monitoring', 'Ctrl+C', 'SIGTERM', 'SIGKILL', 'Kill Session'];
2747
- const levelName = levelNames[event.level] || `Level ${event.level}`;
2748
- telegram.sendToTopic(topicId, `🔧 Watchdog [${levelName}]: ${event.action}\nStuck: \`${event.stuckCommand.slice(0, 60)}\``).catch(() => { });
2749
- }
2833
+ if (topicId)
2834
+ telegram.sendToTopic(topicId, msg).catch(() => { });
2835
+ }
2836
+ if (_slackAdapter) {
2837
+ const channelId = _slackAdapter.getChannelForSession(event.sessionName);
2838
+ if (channelId)
2839
+ _slackAdapter.sendToChannel(channelId, msg).catch(() => { });
2750
2840
  }
2751
2841
  });
2752
2842
  watchdog.on('recovery', (sessionName, fromLevel) => {
2843
+ const msg = `✅ Watchdog: session recovered (was at escalation level ${fromLevel})`;
2753
2844
  if (telegram) {
2754
2845
  const topicId = telegram.getTopicForSession(sessionName);
2755
- if (topicId) {
2756
- telegram.sendToTopic(topicId, `✅ Watchdog: session recovered (was at escalation level ${fromLevel})`).catch(() => { });
2757
- }
2846
+ if (topicId)
2847
+ telegram.sendToTopic(topicId, msg).catch(() => { });
2848
+ }
2849
+ if (_slackAdapter) {
2850
+ const channelId = _slackAdapter.getChannelForSession(sessionName);
2851
+ if (channelId)
2852
+ _slackAdapter.sendToChannel(channelId, msg).catch(() => { });
2758
2853
  }
2759
2854
  });
2760
2855
  watchdog.start();
@@ -4336,23 +4431,38 @@ export async function startServer(options) {
4336
4431
  // 1. Resume entries are consumed (removed) on spawn
4337
4432
  // 2. Proactive save may not have run yet
4338
4433
  // 3. beforeSessionKill doesn't fire for bulk process exit
4339
- if (_topicResumeMap && telegram) {
4434
+ if (_topicResumeMap) {
4340
4435
  try {
4341
4436
  const runningSessions = sessionManager.listRunningSessions();
4342
- const topicSessions = telegram.getAllTopicSessions?.();
4343
- if (topicSessions) {
4344
- let saved = 0;
4345
- for (const [topicId, sessionName] of topicSessions) {
4346
- const session = runningSessions.find(s => s.tmuxSession === sessionName);
4347
- const uuid = _topicResumeMap.findUuidForSession(sessionName, session?.claudeSessionId ?? undefined);
4437
+ let saved = 0;
4438
+ // Save Telegram topic resume UUIDs
4439
+ if (telegram) {
4440
+ const topicSessions = telegram.getAllTopicSessions?.();
4441
+ if (topicSessions) {
4442
+ for (const [topicId, sessionName] of topicSessions) {
4443
+ const session = runningSessions.find(s => s.tmuxSession === sessionName);
4444
+ const uuid = _topicResumeMap.findUuidForSession(sessionName, session?.claudeSessionId ?? undefined);
4445
+ if (uuid) {
4446
+ _topicResumeMap.save(topicId, uuid, sessionName);
4447
+ saved++;
4448
+ }
4449
+ }
4450
+ }
4451
+ }
4452
+ // Save Slack channel resume UUIDs
4453
+ if (_slackAdapter) {
4454
+ const registry = _slackAdapter.getChannelRegistry();
4455
+ for (const [channelId, entry] of Object.entries(registry)) {
4456
+ const session = runningSessions.find(s => s.tmuxSession === entry.sessionName);
4457
+ const uuid = _topicResumeMap.findUuidForSession(entry.sessionName, session?.claudeSessionId ?? undefined);
4348
4458
  if (uuid) {
4349
- _topicResumeMap.save(topicId, uuid, sessionName);
4459
+ _slackAdapter.saveChannelResume(channelId, uuid, entry.sessionName);
4350
4460
  saved++;
4351
4461
  }
4352
4462
  }
4353
- if (saved > 0) {
4354
- console.log(`[shutdown] Saved ${saved} resume UUID(s) for active sessions`);
4355
- }
4463
+ }
4464
+ if (saved > 0) {
4465
+ console.log(`[shutdown] Saved ${saved} resume UUID(s) for active sessions`);
4356
4466
  }
4357
4467
  }
4358
4468
  catch (err) {