instar 0.28.1 → 0.28.3
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/README.md +1 -1
- package/dashboard/index.html +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +131 -52
- 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/lifeline/ServerSupervisor.d.ts +1 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +74 -10
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +8 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +115 -6
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/messaging/SpawnRequestManager.d.ts +16 -0
- package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
- package/dist/messaging/SpawnRequestManager.js +63 -5
- package/dist/messaging/SpawnRequestManager.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/server/AgentServer.d.ts +5 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +162 -34
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/AgentTrustManager.d.ts +17 -3
- package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
- package/dist/threadline/AgentTrustManager.js +44 -13
- package/dist/threadline/AgentTrustManager.js.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.js +32 -10
- package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
- package/dist/threadline/client/ThreadlineClient.d.ts +9 -1
- package/dist/threadline/client/ThreadlineClient.d.ts.map +1 -1
- package/dist/threadline/client/ThreadlineClient.js +64 -10
- package/dist/threadline/client/ThreadlineClient.js.map +1 -1
- package/dist/threadline/mcp-stdio-entry.js +3 -2
- package/dist/threadline/mcp-stdio-entry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +50 -50
- package/src/templates/hooks/compaction-recovery.sh +82 -3
- package/upgrades/0.28.2.md +29 -0
- package/upgrades/0.28.3.md +35 -0
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ Instar solves the six dimensions of agent coherence:
|
|
|
113
113
|
| **Intent Alignment** | Decision journaling, drift detection, organizational constraints | [→](https://instar.sh/features/intent/) |
|
|
114
114
|
| **Multi-Machine** | Ed25519/X25519 crypto identity, encrypted sync, automatic failover | [→](https://instar.sh/features/multi-machine/) |
|
|
115
115
|
| **Serendipity Protocol** | Sub-agents capture out-of-scope discoveries without breaking focus. HMAC-signed, secret-scanned | [→](https://instar.sh/features/serendipity/) |
|
|
116
|
-
| **Threadline Protocol** | Agent-to-agent conversations with canonical identity, three-layer trust model, authorization policy, Ed25519 invitations, Sybil protection, MoltBridge network discovery, rich agent profiles (auto-compiled from agent data with human review gate), discovery waterfall, message security, tamper-proof audit logging,
|
|
116
|
+
| **Threadline Protocol** | Agent-to-agent conversations with canonical identity, three-layer trust model, authorization policy, Ed25519 invitations, Sybil protection, MoltBridge network discovery, rich agent profiles (auto-compiled from agent data with human review gate), discovery waterfall, message security, tamper-proof audit logging, framework-agnostic interop, and persistent listener daemon (always-on relay connection, pipe-mode sessions, sub-30s cross-machine failover). 2,324 tests across 104 test files | [→](https://instar.sh/features/threadline/) |
|
|
117
117
|
| **Self-Healing** | LLM-powered stall detection, session recovery, promise tracking | [→](https://instar.sh/features/self-healing/) |
|
|
118
118
|
| **AutoUpdater** | Built-in update engine. Checks npm, auto-applies, self-restarts | [→](https://instar.sh/features/autoupdater/) |
|
|
119
119
|
| **Build Pipeline** | `/build` skill with worktree isolation, 6-phase pipeline, quality gates, stop-hook enforcement | |
|
package/dashboard/index.html
CHANGED
|
@@ -3067,7 +3067,7 @@
|
|
|
3067
3067
|
document.getElementById('terminalView').style.display = 'flex';
|
|
3068
3068
|
document.getElementById('terminalView').style.flexDirection = 'column';
|
|
3069
3069
|
document.getElementById('terminalView').style.height = '100%';
|
|
3070
|
-
document.getElementById('termSessionName').textContent = session.name;
|
|
3070
|
+
document.getElementById('termSessionName').textContent = session.platformName || session.name;
|
|
3071
3071
|
|
|
3072
3072
|
const badge = document.getElementById('termModelBadge');
|
|
3073
3073
|
if (session.model) {
|
|
@@ -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 ---');
|
|
2510
2534
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2535
|
+
else {
|
|
2536
|
+
console.warn(`[slack→context] No history available for channel ${channelId} — session starts without thread context`);
|
|
2537
|
+
}
|
|
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)`);
|
|
@@ -4631,6 +4671,8 @@ export async function startServer(options) {
|
|
|
4631
4671
|
let threadlineShutdown;
|
|
4632
4672
|
let threadlineRelayClient;
|
|
4633
4673
|
let unifiedTrust;
|
|
4674
|
+
/** Shared reply waiters for threadline waitForReply support */
|
|
4675
|
+
const threadlineReplyWaiters = new Map();
|
|
4634
4676
|
try {
|
|
4635
4677
|
const threadline = await bootstrapThreadline({
|
|
4636
4678
|
agentName: config.projectName,
|
|
@@ -4711,6 +4753,37 @@ export async function startServer(options) {
|
|
|
4711
4753
|
else {
|
|
4712
4754
|
textContent = JSON.stringify(msg.content);
|
|
4713
4755
|
}
|
|
4756
|
+
// Check if this message resolves a pending waitForReply request.
|
|
4757
|
+
// Skip auto-ack messages (they're from us, not a real reply).
|
|
4758
|
+
// Try both fingerprint and agent name as keys — the waiter is keyed by
|
|
4759
|
+
// agent name (from relay-send), but gate-passed provides the fingerprint.
|
|
4760
|
+
const isAutoAck = textContent.startsWith('Message received.') || textContent.startsWith('Message received,');
|
|
4761
|
+
let waiter = threadlineReplyWaiters.get(senderFingerprint);
|
|
4762
|
+
if (!waiter) {
|
|
4763
|
+
// Resolve fingerprint → agent name via known-agents cache
|
|
4764
|
+
const resolvedName = (() => {
|
|
4765
|
+
try {
|
|
4766
|
+
const kaPath = path.join(config.stateDir, 'threadline', 'known-agents.json');
|
|
4767
|
+
const kaData = JSON.parse(fs.readFileSync(kaPath, 'utf-8'));
|
|
4768
|
+
const agents = kaData.agents ?? kaData;
|
|
4769
|
+
if (Array.isArray(agents)) {
|
|
4770
|
+
// Check publicKey field for fingerprint match
|
|
4771
|
+
const match = agents.find((a) => a.publicKey === senderFingerprint || a.publicKey?.startsWith(senderFingerprint));
|
|
4772
|
+
return match?.name ?? null;
|
|
4773
|
+
}
|
|
4774
|
+
return null;
|
|
4775
|
+
}
|
|
4776
|
+
catch {
|
|
4777
|
+
return null;
|
|
4778
|
+
}
|
|
4779
|
+
})();
|
|
4780
|
+
if (resolvedName)
|
|
4781
|
+
waiter = threadlineReplyWaiters.get(resolvedName);
|
|
4782
|
+
}
|
|
4783
|
+
if (waiter && !isAutoAck) {
|
|
4784
|
+
waiter.resolve(textContent);
|
|
4785
|
+
// Don't return — still process the message normally for routing
|
|
4786
|
+
}
|
|
4714
4787
|
// Auto-ack (post-trust-verification, never ack status messages)
|
|
4715
4788
|
const msgType = typeof msg.content === 'object' && msg.content !== null ? msg.content.type : undefined;
|
|
4716
4789
|
if (trustLevel !== 'untrusted' && msgType !== 'status' && config.threadline?.autoAck !== false && !isAckRateLimited(senderFingerprint)) {
|
|
@@ -4881,7 +4954,7 @@ export async function startServer(options) {
|
|
|
4881
4954
|
return { content: lines.join('\n'), truncated: false, elapsedMs: Date.now() - start };
|
|
4882
4955
|
}, { description: 'Feature discovery state and behavioral contract summary' });
|
|
4883
4956
|
}
|
|
4884
|
-
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, listenerManager: listenerManager ?? undefined, responseReviewGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig });
|
|
4957
|
+
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig });
|
|
4885
4958
|
await server.start();
|
|
4886
4959
|
// Connect DegradationReporter downstream systems now that everything is initialized.
|
|
4887
4960
|
// Any degradation events queued during startup will drain to feedback + telegram.
|
|
@@ -5169,8 +5242,14 @@ export async function startServer(options) {
|
|
|
5169
5242
|
await threadlineShutdown();
|
|
5170
5243
|
wakeSocketServer?.stop();
|
|
5171
5244
|
pipeSpawner?.killAll();
|
|
5172
|
-
|
|
5173
|
-
|
|
5245
|
+
try {
|
|
5246
|
+
stopHeartbeat?.();
|
|
5247
|
+
}
|
|
5248
|
+
catch { /* non-critical during shutdown */ }
|
|
5249
|
+
try {
|
|
5250
|
+
unregisterAgent(config.projectDir);
|
|
5251
|
+
}
|
|
5252
|
+
catch { /* ELOCKED is non-critical during shutdown */ }
|
|
5174
5253
|
scheduler?.stop();
|
|
5175
5254
|
if (telegram)
|
|
5176
5255
|
await telegram.stop();
|