instar 0.25.1 → 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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +155 -45
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +30 -8
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +2 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +3 -1
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +41 -3
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/messaging/slack/types.d.ts +1 -0
- package/dist/messaging/slack/types.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.d.ts +5 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +10 -2
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +59 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/templates/scripts/slack-reply.sh +10 -0
- package/upgrades/0.25.2.md +23 -0
|
@@ -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,
|
|
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"}
|
package/dist/commands/server.js
CHANGED
|
@@ -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
|
|
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]
|
|
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
|
-
|
|
2584
|
-
|
|
2585
|
-
const
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
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
|
|
2687
|
+
if (_topicResumeMap) {
|
|
2605
2688
|
sessionManager.on('beforeSessionKill', (session) => {
|
|
2606
2689
|
try {
|
|
2607
|
-
// Save Telegram topic resume UUID
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
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
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
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,
|
|
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
|
|
4434
|
+
if (_topicResumeMap) {
|
|
4340
4435
|
try {
|
|
4341
4436
|
const runningSessions = sessionManager.listRunningSessions();
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
const
|
|
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
|
-
|
|
4459
|
+
_slackAdapter.saveChannelResume(channelId, uuid, entry.sessionName);
|
|
4350
4460
|
saved++;
|
|
4351
4461
|
}
|
|
4352
4462
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
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) {
|