instar 0.28.2 → 0.28.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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +89 -49
- package/dist/commands/server.js.map +1 -1
- package/dist/core/Config.js +2 -2
- package/dist/core/Config.js.map +1 -1
- package/dist/core/SessionManager.d.ts +1 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +3 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/ServerSupervisor.d.ts +1 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +43 -10
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +13 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +90 -7
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/imessage/IMessageAdapter.d.ts +4 -2
- package/dist/messaging/imessage/IMessageAdapter.d.ts.map +1 -1
- package/dist/messaging/imessage/IMessageAdapter.js +19 -5
- package/dist/messaging/imessage/IMessageAdapter.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +6 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +27 -0
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/monitoring/QuotaTracker.js +4 -4
- package/dist/monitoring/QuotaTracker.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +12 -2
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +77 -19
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +49 -49
- package/src/templates/hooks/compaction-recovery.sh +82 -3
- package/upgrades/0.28.4.md +22 -0
|
@@ -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;
|
|
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,CAwhItE;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
|
@@ -493,7 +493,13 @@ async function respawnSessionForTopic(sessionManager, telegram, targetSession, t
|
|
|
493
493
|
console.error(`[telegram→session] Failed to save resume UUID:`, err);
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
|
-
|
|
496
|
+
let storedName = telegram.getTopicName(topicId);
|
|
497
|
+
// If the name is unknown, try to resolve it from Telegram before falling back
|
|
498
|
+
if (!storedName || /^topic-\d+$/.test(storedName)) {
|
|
499
|
+
const resolved = await telegram.resolveTopicName(topicId);
|
|
500
|
+
if (resolved)
|
|
501
|
+
storedName = resolved;
|
|
502
|
+
}
|
|
497
503
|
// Use topic name, not tmux session name — tmux names include the project prefix
|
|
498
504
|
// which causes cascading names like ai-guy-ai-guy-ai-guy-topic-1 on each respawn.
|
|
499
505
|
const topicName = storedName || `topic-${topicId}`;
|
|
@@ -773,7 +779,7 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
|
|
|
773
779
|
// Prevents duplicate concurrent spawns for the same topic when messages
|
|
774
780
|
// arrive faster than the async spawn completes.
|
|
775
781
|
const spawningTopics = new Set();
|
|
776
|
-
telegram.onTopicMessage = (msg) => {
|
|
782
|
+
telegram.onTopicMessage = async (msg) => {
|
|
777
783
|
const topicId = msg.metadata?.messageThreadId ?? null;
|
|
778
784
|
if (!topicId)
|
|
779
785
|
return;
|
|
@@ -944,8 +950,16 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
|
|
|
944
950
|
console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate`);
|
|
945
951
|
return;
|
|
946
952
|
}
|
|
947
|
-
const spawnName = storedTopicName || `topic-${topicId}`;
|
|
948
953
|
spawningTopics.add(topicId);
|
|
954
|
+
// Resolve topic name — try in-memory, then active probe, then fallback
|
|
955
|
+
let spawnName = storedTopicName;
|
|
956
|
+
if (!spawnName || /^topic-\d+$/.test(spawnName)) {
|
|
957
|
+
const resolved = await telegram.resolveTopicName(topicId);
|
|
958
|
+
if (resolved)
|
|
959
|
+
spawnName = resolved;
|
|
960
|
+
}
|
|
961
|
+
if (!spawnName)
|
|
962
|
+
spawnName = `topic-${topicId}`;
|
|
949
963
|
// Use the shared spawn helper that includes topic history + user context
|
|
950
964
|
spawnSessionForTopic(sessionManager, telegram, spawnName, topicId, text, topicMemory, resolvedUser ?? undefined).then((newSessionName) => {
|
|
951
965
|
telegram.registerTopicSession(topicId, newSessionName, spawnName);
|
|
@@ -2023,6 +2037,10 @@ export async function startServer(options) {
|
|
|
2023
2037
|
// Send-only mode: no polling, but sendToTopic() works for session replies
|
|
2024
2038
|
telegram = new TelegramAdapter(telegramConfig.config, config.stateDir);
|
|
2025
2039
|
console.log(pc.green(` Telegram send-only mode (${isStandbyTelegram ? 'standby' : 'lifeline owns polling'})`));
|
|
2040
|
+
// Resolve any topic names still using the fallback "topic-NNNN" pattern
|
|
2041
|
+
telegram.resolveUnknownTopicNames().catch(err => {
|
|
2042
|
+
console.warn(`[telegram] Topic name resolution failed: ${err}`);
|
|
2043
|
+
});
|
|
2026
2044
|
// Ensure topics exist even in send-only mode (createForumTopic is a simple API call)
|
|
2027
2045
|
ensureAgentAttentionTopic(telegram, state).catch(err => {
|
|
2028
2046
|
console.error(`[server] Failed to ensure Agent Attention topic: ${err}`);
|
|
@@ -2487,40 +2505,49 @@ export async function startServer(options) {
|
|
|
2487
2505
|
else if (safeSenderName) {
|
|
2488
2506
|
prefix = `[slack:${channelId} from ${safeSenderName}]`;
|
|
2489
2507
|
}
|
|
2490
|
-
//
|
|
2508
|
+
// Build context for the session — inject inline like Telegram does
|
|
2509
|
+
// Use async fallback to fetch from Slack API if ring buffer is empty
|
|
2491
2510
|
const tmpDir = '/tmp/instar-slack';
|
|
2492
2511
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
2493
|
-
const
|
|
2494
|
-
const history = slackAdapter.getChannelMessages(channelId, 30);
|
|
2512
|
+
const history = await slackAdapter.getChannelMessagesWithFallback(channelId, 30);
|
|
2495
2513
|
const unansweredCount = slackAdapter.getUnansweredCount(channelId);
|
|
2496
2514
|
const botUserId = slackAdapter.getBotUserId?.() ?? null;
|
|
2497
|
-
// Build human-readable thread history (matches Telegram's context
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
const
|
|
2508
|
-
|
|
2509
|
-
|
|
2515
|
+
// Build human-readable thread history (matches Telegram's inline context pattern)
|
|
2516
|
+
const contextLines = [];
|
|
2517
|
+
if (history.length > 0) {
|
|
2518
|
+
contextLines.push('CONTINUATION — You are resuming an EXISTING Slack conversation. Read the context below before responding. Do NOT ask what was being discussed.');
|
|
2519
|
+
contextLines.push('');
|
|
2520
|
+
contextLines.push(`--- Thread History (last ${history.length} messages) ---`);
|
|
2521
|
+
contextLines.push('IMPORTANT: Read this history carefully before taking any action.');
|
|
2522
|
+
contextLines.push('Your task is to continue THIS conversation, not start something new.');
|
|
2523
|
+
contextLines.push(`Channel: ${channelId}`);
|
|
2524
|
+
contextLines.push('');
|
|
2525
|
+
for (const m of history) {
|
|
2526
|
+
const date = new Date(parseFloat(m.ts) * 1000);
|
|
2527
|
+
const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
|
2528
|
+
const isBot = botUserId && m.user === botUserId;
|
|
2529
|
+
const label = isBot ? 'Agent' : (senderName || m.user);
|
|
2530
|
+
contextLines.push(`[${time}] ${label}: ${m.text}`);
|
|
2531
|
+
}
|
|
2532
|
+
contextLines.push('');
|
|
2533
|
+
contextLines.push('--- End Thread History ---');
|
|
2534
|
+
}
|
|
2535
|
+
else {
|
|
2536
|
+
console.warn(`[slack→context] No history available for channel ${channelId} — session starts without thread context`);
|
|
2510
2537
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
const
|
|
2538
|
+
contextLines.push('');
|
|
2539
|
+
contextLines.push('CRITICAL: You MUST relay your response back to Slack after responding.');
|
|
2540
|
+
contextLines.push('Use the relay script (write ONLY your reply text — do NOT pipe or cat this file into the script):');
|
|
2541
|
+
contextLines.push('');
|
|
2542
|
+
contextLines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${channelId}`);
|
|
2543
|
+
contextLines.push('Your response text here');
|
|
2544
|
+
contextLines.push('EOF');
|
|
2545
|
+
contextLines.push('');
|
|
2546
|
+
contextLines.push('Strip the [slack:] prefix before interpreting the message.');
|
|
2547
|
+
contextLines.push('Only relay conversational text — not tool output, file contents, or internal reasoning.');
|
|
2548
|
+
const contextData = contextLines.join('\n');
|
|
2549
|
+
// Also write to file as backup reference
|
|
2550
|
+
const ctxPath = path.join(tmpDir, `ctx-${channelId}-${Date.now()}.txt`);
|
|
2524
2551
|
fs.writeFileSync(ctxPath, contextData);
|
|
2525
2552
|
// Transform [image:path] and [document:path] tags into explicit read instructions
|
|
2526
2553
|
let transformedContent = message.content.replace(/\[image:([^\]]+)\]/g, (_, imagePath) => {
|
|
@@ -2534,14 +2561,18 @@ export async function startServer(options) {
|
|
|
2534
2561
|
}
|
|
2535
2562
|
return `[User sent a file — it has been saved to ${docPath}. Read the file to view its contents]`;
|
|
2536
2563
|
});
|
|
2537
|
-
|
|
2564
|
+
// Inject context INLINE in the bootstrap message (matches Telegram pattern).
|
|
2565
|
+
// This ensures the agent sees thread history immediately without needing to read a file.
|
|
2538
2566
|
const fullMessage = `${prefix} ${transformedContent} (IMPORTANT: Read ${ctxPath} for thread history and Slack relay instructions — you MUST relay your response back.)`;
|
|
2539
|
-
//
|
|
2567
|
+
// Large bootstrap messages: write to file with INLINE context included
|
|
2568
|
+
const FILE_THRESHOLD = 500;
|
|
2540
2569
|
let bootstrapMessage;
|
|
2541
2570
|
if (fullMessage.length > FILE_THRESHOLD) {
|
|
2571
|
+
// Write full message + inline context to the file so agent gets everything in one read
|
|
2542
2572
|
const msgFilePath = path.join(tmpDir, `msg-${channelId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}.txt`);
|
|
2543
|
-
|
|
2544
|
-
|
|
2573
|
+
const fullContent = `${fullMessage}\n\n${contextData}`;
|
|
2574
|
+
fs.writeFileSync(msgFilePath, fullContent);
|
|
2575
|
+
bootstrapMessage = `${prefix} [Long message saved to ${msgFilePath} — read it to see the full message and thread history]`;
|
|
2545
2576
|
}
|
|
2546
2577
|
else {
|
|
2547
2578
|
bootstrapMessage = fullMessage;
|
|
@@ -2594,7 +2625,7 @@ export async function startServer(options) {
|
|
|
2594
2625
|
// Route: DMs go to lifeline session, channels spawn new sessions
|
|
2595
2626
|
const targetSession = isDM ? 'lifeline' : undefined;
|
|
2596
2627
|
try {
|
|
2597
|
-
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId });
|
|
2628
|
+
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId, slackChannelId: channelId });
|
|
2598
2629
|
if (newSessionName) {
|
|
2599
2630
|
slackAdapter.registerChannelSession(channelId, newSessionName);
|
|
2600
2631
|
slackAdapter.trackMessageInjection(channelId, newSessionName, message.content);
|
|
@@ -3359,23 +3390,32 @@ export async function startServer(options) {
|
|
|
3359
3390
|
_slackAdapter.removeChannelResume(slackChId);
|
|
3360
3391
|
// Spawn a fresh session with recovery context
|
|
3361
3392
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3362
|
-
// Build a recovery bootstrap message with thread history
|
|
3363
|
-
|
|
3393
|
+
// Build a recovery bootstrap message with thread history (inline, matching Telegram pattern)
|
|
3394
|
+
// Use async fallback to fetch from Slack API if ring buffer is empty (race condition on restart)
|
|
3395
|
+
const history = await _slackAdapter.getChannelMessagesWithFallback(slackChId, 30);
|
|
3364
3396
|
const botUserId = _slackAdapter.getBotUserId?.() ?? null;
|
|
3365
3397
|
const lines = [];
|
|
3398
|
+
lines.push(`CONTINUATION — You are resuming an EXISTING Slack conversation after context exhaustion. Read the context below and pick up where you left off. Do NOT ask what was being discussed.`);
|
|
3399
|
+
lines.push('');
|
|
3366
3400
|
lines.push(`[RECOVERY] Previous session hit the context window limit. This is a FRESH restart with thread history.`);
|
|
3367
3401
|
if (recoveryPrompt)
|
|
3368
3402
|
lines.push(recoveryPrompt);
|
|
3369
3403
|
lines.push('');
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3404
|
+
if (history.length > 0) {
|
|
3405
|
+
lines.push(`--- Thread History (last ${history.length} messages) ---`);
|
|
3406
|
+
for (const m of history) {
|
|
3407
|
+
const date = new Date(parseFloat(m.ts) * 1000);
|
|
3408
|
+
const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
|
3409
|
+
const isBot = botUserId && m.user === botUserId;
|
|
3410
|
+
const label = isBot ? 'Agent' : m.user;
|
|
3411
|
+
lines.push(`[${time}] ${label}: ${m.text}`);
|
|
3412
|
+
}
|
|
3413
|
+
lines.push('--- End Thread History ---');
|
|
3414
|
+
}
|
|
3415
|
+
else {
|
|
3416
|
+
console.warn(`[slack→recovery] WARNING: No history available for channel ${slackChId} — recovery context is empty. Ring buffer may not be populated yet.`);
|
|
3417
|
+
lines.push('[WARNING: Thread history unavailable — ring buffer may not be populated. Check Slack channel for recent messages before responding.]');
|
|
3377
3418
|
}
|
|
3378
|
-
lines.push('--- End Thread History ---');
|
|
3379
3419
|
lines.push('');
|
|
3380
3420
|
lines.push('CRITICAL: You MUST relay your response back to Slack.');
|
|
3381
3421
|
lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${slackChId}`);
|
|
@@ -3386,9 +3426,9 @@ export async function startServer(options) {
|
|
|
3386
3426
|
const ctxPath = path.join(tmpDir, `recovery-${slackChId}-${Date.now()}.txt`);
|
|
3387
3427
|
const contextData = lines.join('\n');
|
|
3388
3428
|
fs.writeFileSync(ctxPath, contextData);
|
|
3389
|
-
const bootstrapMessage = `[slack:${slackChId}]
|
|
3429
|
+
const bootstrapMessage = `[slack:${slackChId}] ${contextData}`;
|
|
3390
3430
|
try {
|
|
3391
|
-
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage);
|
|
3431
|
+
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, undefined, { slackChannelId: slackChId });
|
|
3392
3432
|
if (newSessionName) {
|
|
3393
3433
|
_slackAdapter.registerChannelSession(slackChId, newSessionName);
|
|
3394
3434
|
console.log(`[slack→recovery] Fresh session "${newSessionName}" spawned for channel ${slackChId} (context exhaustion recovery)`);
|