instar 0.23.15 → 0.23.16
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/dashboard/index.html +23 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +70 -5
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +11 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +69 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +18 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +82 -2
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/TopicResumeMap.d.ts +14 -2
- package/dist/core/TopicResumeMap.d.ts.map +1 -1
- package/dist/core/TopicResumeMap.js +36 -41
- package/dist/core/TopicResumeMap.js.map +1 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/data/http-hook-templates.js +10 -10
- package/dist/data/http-hook-templates.js.map +1 -1
- package/dist/memory/TopicMemory.js +3 -3
- package/dist/memory/TopicMemory.js.map +1 -1
- package/dist/monitoring/SessionRecovery.d.ts +110 -0
- package/dist/monitoring/SessionRecovery.d.ts.map +1 -0
- package/dist/monitoring/SessionRecovery.js +432 -0
- package/dist/monitoring/SessionRecovery.js.map +1 -0
- package/dist/monitoring/crash-detector.d.ts +50 -0
- package/dist/monitoring/crash-detector.d.ts.map +1 -0
- package/dist/monitoring/crash-detector.js +224 -0
- package/dist/monitoring/crash-detector.js.map +1 -0
- package/dist/monitoring/jsonl-truncator.d.ts +44 -0
- package/dist/monitoring/jsonl-truncator.d.ts.map +1 -0
- package/dist/monitoring/jsonl-truncator.js +224 -0
- package/dist/monitoring/jsonl-truncator.js.map +1 -0
- package/dist/monitoring/stall-detector.d.ts +34 -0
- package/dist/monitoring/stall-detector.d.ts.map +1 -0
- package/dist/monitoring/stall-detector.js +151 -0
- package/dist/monitoring/stall-detector.js.map +1 -0
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +16 -2
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/WebSocketManager.d.ts +2 -0
- package/dist/server/WebSocketManager.d.ts.map +1 -1
- package/dist/server/WebSocketManager.js +24 -3
- package/dist/server/WebSocketManager.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +52 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +2 -1
- package/scripts/pre-push-gate.js +149 -0
- package/src/data/builtin-manifest.json +63 -63
- package/src/data/http-hook-templates.ts +10 -10
- package/src/templates/hooks/telegram-topic-context.sh +4 -4
- package/upgrades/0.23.10.md +19 -0
- package/upgrades/0.23.11.md +21 -0
- package/upgrades/0.23.15.md +14 -1
- package/upgrades/0.23.16.md +25 -0
package/dashboard/index.html
CHANGED
|
@@ -1881,6 +1881,9 @@
|
|
|
1881
1881
|
let activeSession = null;
|
|
1882
1882
|
let term = null;
|
|
1883
1883
|
let fitAddon = null;
|
|
1884
|
+
let historyLinesLoaded = 2000; // matches initial subscribe capture
|
|
1885
|
+
let historyLoading = false;
|
|
1886
|
+
let historyExhausted = false; // true when we've loaded all available history
|
|
1884
1887
|
|
|
1885
1888
|
// ── Auth ─────────────────────────────────────────────────
|
|
1886
1889
|
function authenticate() {
|
|
@@ -2004,6 +2007,26 @@
|
|
|
2004
2007
|
}
|
|
2005
2008
|
break;
|
|
2006
2009
|
|
|
2010
|
+
case 'history':
|
|
2011
|
+
if (msg.session === activeSession && term) {
|
|
2012
|
+
const prevLineCount = historyLinesLoaded;
|
|
2013
|
+
historyLinesLoaded = msg.lines;
|
|
2014
|
+
// Check if we got more content than before
|
|
2015
|
+
const newLineCount = (msg.data || '').split('\n').length;
|
|
2016
|
+
const oldLineCount = prevLineCount;
|
|
2017
|
+
if (newLineCount <= oldLineCount + 10) {
|
|
2018
|
+
// No meaningful new content — we've hit the buffer limit
|
|
2019
|
+
historyExhausted = true;
|
|
2020
|
+
}
|
|
2021
|
+
// Rewrite terminal with expanded history
|
|
2022
|
+
term.clear();
|
|
2023
|
+
term.write(msg.data);
|
|
2024
|
+
// Don't auto-scroll — user is reading history
|
|
2025
|
+
historyLoading = false;
|
|
2026
|
+
hideHistorySpinner();
|
|
2027
|
+
}
|
|
2028
|
+
break;
|
|
2029
|
+
|
|
2007
2030
|
case 'session_ended':
|
|
2008
2031
|
if (msg.session === activeSession) {
|
|
2009
2032
|
// Session ended — show in terminal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwPH,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;AAwPH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgkCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAy5EtE;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
|
@@ -283,7 +283,7 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
|
|
|
283
283
|
let usedFallback = false;
|
|
284
284
|
if (topicMemory?.isReady()) {
|
|
285
285
|
try {
|
|
286
|
-
contextContent = topicMemory.formatContextForSession(topicId,
|
|
286
|
+
contextContent = topicMemory.formatContextForSession(topicId, 50);
|
|
287
287
|
}
|
|
288
288
|
catch (err) {
|
|
289
289
|
// @silent-fallback-ok — TopicMemory format, JSONL fallback
|
|
@@ -294,12 +294,16 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
|
|
|
294
294
|
if (!contextContent) {
|
|
295
295
|
usedFallback = true;
|
|
296
296
|
try {
|
|
297
|
-
const history = telegram.getTopicHistory(topicId,
|
|
297
|
+
const history = telegram.getTopicHistory(topicId, 50);
|
|
298
298
|
if (history.length > 0) {
|
|
299
299
|
const lines = [];
|
|
300
300
|
lines.push(`--- Thread History (last ${history.length} messages) ---`);
|
|
301
301
|
lines.push(`IMPORTANT: Read this history carefully before taking any action.`);
|
|
302
302
|
lines.push(`Your task is to continue THIS conversation, not start something new.`);
|
|
303
|
+
const topicName = telegram.getTopicName?.(topicId);
|
|
304
|
+
if (topicName) {
|
|
305
|
+
lines.push(`Topic: ${topicName}`);
|
|
306
|
+
}
|
|
303
307
|
lines.push(``);
|
|
304
308
|
for (const m of history) {
|
|
305
309
|
// Use actual sender name if available (multi-user topics), fall back to generic
|
|
@@ -307,7 +311,7 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
|
|
|
307
311
|
? (m.senderName || 'User')
|
|
308
312
|
: 'Agent';
|
|
309
313
|
const ts = m.timestamp ? new Date(m.timestamp).toISOString().slice(11, 19) : '??:??';
|
|
310
|
-
const text = (m.text || '').slice(0,
|
|
314
|
+
const text = (m.text || '').slice(0, 2000);
|
|
311
315
|
lines.push(`[${ts}] ${sender}: ${text}`);
|
|
312
316
|
}
|
|
313
317
|
lines.push(``);
|
|
@@ -434,13 +438,30 @@ async function spawnSessionForTopic(sessionManager, telegram, sessionName, topic
|
|
|
434
438
|
if (resumeSessionId) {
|
|
435
439
|
_topicResumeMap?.remove(topicId);
|
|
436
440
|
}
|
|
441
|
+
// Proactive UUID save — schedule an immediate discovery attempt after spawn.
|
|
442
|
+
// The JSONL file appears ~3-5 seconds after Claude Code starts.
|
|
443
|
+
// This is belt-and-suspenders alongside the 60s heartbeat and beforeSessionKill.
|
|
444
|
+
if (_topicResumeMap && !resumeSessionId) {
|
|
445
|
+
setTimeout(() => {
|
|
446
|
+
try {
|
|
447
|
+
const uuid = _topicResumeMap.findClaudeSessionUuid();
|
|
448
|
+
if (uuid) {
|
|
449
|
+
_topicResumeMap.save(topicId, uuid, newSessionName);
|
|
450
|
+
console.log(`[spawnSessionForTopic] Proactive UUID save: ${uuid} for topic ${topicId}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
console.error(`[spawnSessionForTopic] Proactive UUID save failed:`, err);
|
|
455
|
+
}
|
|
456
|
+
}, 8000);
|
|
457
|
+
}
|
|
437
458
|
return newSessionName;
|
|
438
459
|
}
|
|
439
460
|
/**
|
|
440
461
|
* Respawn a session for a topic, including thread history in the bootstrap.
|
|
441
462
|
* This prevents "thread drift" where respawned sessions lose context.
|
|
442
463
|
*/
|
|
443
|
-
async function respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, latestMessage, topicMemory, userProfile) {
|
|
464
|
+
async function respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, latestMessage, topicMemory, userProfile, recoveryPrompt) {
|
|
444
465
|
console.log(`[telegram→session] Session "${targetSession}" needs respawn for topic ${topicId}`);
|
|
445
466
|
// Save the old session's Claude UUID before respawning so --resume can reattach context
|
|
446
467
|
if (_topicResumeMap) {
|
|
@@ -459,7 +480,12 @@ async function respawnSessionForTopic(sessionManager, telegram, targetSession, t
|
|
|
459
480
|
// Use topic name, not tmux session name — tmux names include the project prefix
|
|
460
481
|
// which causes cascading names like ai-guy-ai-guy-ai-guy-topic-1 on each respawn.
|
|
461
482
|
const topicName = storedName || `topic-${topicId}`;
|
|
462
|
-
|
|
483
|
+
// If this is a recovery respawn, prepend the recovery context to the message
|
|
484
|
+
// so the session knows what happened and can avoid repeating the failure.
|
|
485
|
+
const effectiveMessage = recoveryPrompt
|
|
486
|
+
? `${recoveryPrompt}\n\n${latestMessage || 'Session recovered — continue where you left off.'}`
|
|
487
|
+
: latestMessage;
|
|
488
|
+
const newSessionName = await spawnSessionForTopic(sessionManager, telegram, topicName, topicId, effectiveMessage, topicMemory, userProfile);
|
|
463
489
|
telegram.registerTopicSession(topicId, newSessionName, topicName);
|
|
464
490
|
await telegram.sendToTopic(topicId, `Session respawned.`);
|
|
465
491
|
console.log(`[telegram→session] Respawned "${newSessionName}" for topic ${topicId}`);
|
|
@@ -2013,6 +2039,26 @@ export async function startServer(options) {
|
|
|
2013
2039
|
resumeHeartbeatInterval.unref();
|
|
2014
2040
|
console.log(pc.green(' Resume heartbeat: active (60s interval)'));
|
|
2015
2041
|
}
|
|
2042
|
+
// Save Claude session UUID before any session kill so the topic can be
|
|
2043
|
+
// resumed later with --resume. This fires BEFORE the tmux session is
|
|
2044
|
+
// destroyed, so the UUID can still be discovered from the JSONL mtime.
|
|
2045
|
+
if (_topicResumeMap && telegram) {
|
|
2046
|
+
sessionManager.on('beforeSessionKill', (session) => {
|
|
2047
|
+
try {
|
|
2048
|
+
const topicId = telegram.getTopicForSession(session.tmuxSession);
|
|
2049
|
+
if (!topicId)
|
|
2050
|
+
return;
|
|
2051
|
+
const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession);
|
|
2052
|
+
if (uuid) {
|
|
2053
|
+
_topicResumeMap.save(topicId, uuid, session.tmuxSession);
|
|
2054
|
+
console.log(`[beforeSessionKill] Saved resume UUID ${uuid} for topic ${topicId} (session: ${session.name})`);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
catch (err) {
|
|
2058
|
+
console.error(`[beforeSessionKill] Failed to save resume UUID:`, err);
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2016
2062
|
if (scheduler) {
|
|
2017
2063
|
sessionManager.on('sessionComplete', (session) => {
|
|
2018
2064
|
scheduler.processQueue();
|
|
@@ -2450,6 +2496,25 @@ export async function startServer(options) {
|
|
|
2450
2496
|
const { SubagentTracker } = await import('../monitoring/SubagentTracker.js');
|
|
2451
2497
|
const subagentTracker = new SubagentTracker({ stateDir: config.stateDir });
|
|
2452
2498
|
console.log(pc.green(' Subagent tracker enabled'));
|
|
2499
|
+
// Wire subagent awareness into zombie cleanup — prevents killing sessions
|
|
2500
|
+
// that are idle at the prompt but waiting for subagent results.
|
|
2501
|
+
const MAX_SUBAGENT_WAIT_MS = 60 * 60_000; // 60 minutes — stale subagent safety cap
|
|
2502
|
+
sessionManager.setSubagentChecker((session) => {
|
|
2503
|
+
if (!session.claudeSessionId)
|
|
2504
|
+
return false;
|
|
2505
|
+
const active = subagentTracker.getActiveSubagents(session.claudeSessionId);
|
|
2506
|
+
if (active.length === 0)
|
|
2507
|
+
return false;
|
|
2508
|
+
// Safety cap: if ALL active subagents have been running > 60 minutes,
|
|
2509
|
+
// treat them as stale (likely missed a SubagentStop event) and allow the kill.
|
|
2510
|
+
const now = Date.now();
|
|
2511
|
+
const allStale = active.every(a => now - new Date(a.startedAt).getTime() > MAX_SUBAGENT_WAIT_MS);
|
|
2512
|
+
if (allStale) {
|
|
2513
|
+
console.warn(`[SessionManager] Session "${session.name}" has ${active.length} stale subagent(s) (>60m). Allowing zombie kill.`);
|
|
2514
|
+
return false;
|
|
2515
|
+
}
|
|
2516
|
+
return true;
|
|
2517
|
+
});
|
|
2453
2518
|
// Worktree Monitor — detects orphaned worktrees after sessions complete
|
|
2454
2519
|
const { WorktreeMonitor } = await import('../monitoring/WorktreeMonitor.js');
|
|
2455
2520
|
const worktreeMonitor = new WorktreeMonitor({
|