instar 0.26.11 → 0.27.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/README.md +1 -1
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +46 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +228 -36
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/slack-cli.d.ts +6 -0
- package/dist/commands/slack-cli.d.ts.map +1 -1
- package/dist/commands/slack-cli.js +99 -1
- package/dist/commands/slack-cli.js.map +1 -1
- package/dist/core/GitSync.d.ts.map +1 -1
- package/dist/core/GitSync.js +47 -5
- package/dist/core/GitSync.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +7 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +19 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +9 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/identity/IdentityManager.d.ts +70 -0
- package/dist/identity/IdentityManager.d.ts.map +1 -0
- package/dist/identity/IdentityManager.js +179 -0
- package/dist/identity/IdentityManager.js.map +1 -0
- package/dist/identity/KeyEncryption.d.ts +40 -0
- package/dist/identity/KeyEncryption.d.ts.map +1 -0
- package/dist/identity/KeyEncryption.js +93 -0
- package/dist/identity/KeyEncryption.js.map +1 -0
- package/dist/identity/KeyRevocation.d.ts +68 -0
- package/dist/identity/KeyRevocation.d.ts.map +1 -0
- package/dist/identity/KeyRevocation.js +171 -0
- package/dist/identity/KeyRevocation.js.map +1 -0
- package/dist/identity/KeyRotation.d.ts +43 -0
- package/dist/identity/KeyRotation.d.ts.map +1 -0
- package/dist/identity/KeyRotation.js +81 -0
- package/dist/identity/KeyRotation.js.map +1 -0
- package/dist/identity/Migration.d.ts +50 -0
- package/dist/identity/Migration.d.ts.map +1 -0
- package/dist/identity/Migration.js +125 -0
- package/dist/identity/Migration.js.map +1 -0
- package/dist/identity/RecoveryPhrase.d.ts +43 -0
- package/dist/identity/RecoveryPhrase.d.ts.map +1 -0
- package/dist/identity/RecoveryPhrase.js +93 -0
- package/dist/identity/RecoveryPhrase.js.map +1 -0
- package/dist/identity/index.d.ts +13 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +20 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/types.d.ts +101 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/identity/types.js +55 -0
- package/dist/identity/types.js.map +1 -0
- package/dist/knowledge/TreeTriage.d.ts.map +1 -1
- package/dist/knowledge/TreeTriage.js +9 -0
- package/dist/knowledge/TreeTriage.js.map +1 -1
- package/dist/memory/TopicMemory.d.ts +2 -0
- package/dist/memory/TopicMemory.d.ts.map +1 -1
- package/dist/memory/TopicMemory.js +55 -0
- package/dist/memory/TopicMemory.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +15 -4
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +131 -14
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/messaging/slack/SocketModeClient.d.ts +2 -0
- package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -1
- package/dist/messaging/slack/SocketModeClient.js +31 -1
- package/dist/messaging/slack/SocketModeClient.js.map +1 -1
- package/dist/moltbridge/MoltBridgeClient.d.ts +124 -0
- package/dist/moltbridge/MoltBridgeClient.d.ts.map +1 -0
- package/dist/moltbridge/MoltBridgeClient.js +321 -0
- package/dist/moltbridge/MoltBridgeClient.js.map +1 -0
- package/dist/moltbridge/ProfileCompiler.d.ts +68 -0
- package/dist/moltbridge/ProfileCompiler.d.ts.map +1 -0
- package/dist/moltbridge/ProfileCompiler.js +317 -0
- package/dist/moltbridge/ProfileCompiler.js.map +1 -0
- package/dist/moltbridge/index.d.ts +12 -0
- package/dist/moltbridge/index.d.ts.map +1 -0
- package/dist/moltbridge/index.js +11 -0
- package/dist/moltbridge/index.js.map +1 -0
- package/dist/moltbridge/routes.d.ts +21 -0
- package/dist/moltbridge/routes.d.ts.map +1 -0
- package/dist/moltbridge/routes.js +224 -0
- package/dist/moltbridge/routes.js.map +1 -0
- package/dist/moltbridge/types.d.ts +93 -0
- package/dist/moltbridge/types.d.ts.map +1 -0
- package/dist/moltbridge/types.js +20 -0
- package/dist/moltbridge/types.js.map +1 -0
- package/dist/monitoring/PresenceProxy.d.ts +3 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +80 -3
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/monitoring/SessionMonitor.d.ts +7 -0
- package/dist/monitoring/SessionMonitor.d.ts.map +1 -1
- package/dist/monitoring/SessionMonitor.js +47 -22
- package/dist/monitoring/SessionMonitor.js.map +1 -1
- package/dist/monitoring/TriageOrchestrator.d.ts.map +1 -1
- package/dist/monitoring/TriageOrchestrator.js +25 -1
- package/dist/monitoring/TriageOrchestrator.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +29 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -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 +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +21 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/AuthorizationPolicy.d.ts +110 -0
- package/dist/threadline/AuthorizationPolicy.d.ts.map +1 -0
- package/dist/threadline/AuthorizationPolicy.js +226 -0
- package/dist/threadline/AuthorizationPolicy.js.map +1 -0
- package/dist/threadline/DiscoveryWaterfall.d.ts +73 -0
- package/dist/threadline/DiscoveryWaterfall.d.ts.map +1 -0
- package/dist/threadline/DiscoveryWaterfall.js +113 -0
- package/dist/threadline/DiscoveryWaterfall.js.map +1 -0
- package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
- package/dist/threadline/ListenerSessionManager.js +2 -0
- package/dist/threadline/ListenerSessionManager.js.map +1 -1
- package/dist/threadline/MessageSecurity.d.ts +49 -0
- package/dist/threadline/MessageSecurity.d.ts.map +1 -0
- package/dist/threadline/MessageSecurity.js +93 -0
- package/dist/threadline/MessageSecurity.js.map +1 -0
- package/dist/threadline/SecureInvitation.d.ts +79 -0
- package/dist/threadline/SecureInvitation.d.ts.map +1 -0
- package/dist/threadline/SecureInvitation.js +164 -0
- package/dist/threadline/SecureInvitation.js.map +1 -0
- package/dist/threadline/TrustAuditLog.d.ts +49 -0
- package/dist/threadline/TrustAuditLog.d.ts.map +1 -0
- package/dist/threadline/TrustAuditLog.js +111 -0
- package/dist/threadline/TrustAuditLog.js.map +1 -0
- package/dist/threadline/TrustEvaluator.d.ts +69 -0
- package/dist/threadline/TrustEvaluator.d.ts.map +1 -0
- package/dist/threadline/TrustEvaluator.js +93 -0
- package/dist/threadline/TrustEvaluator.js.map +1 -0
- package/dist/threadline/UnifiedTrustWiring.d.ts +66 -0
- package/dist/threadline/UnifiedTrustWiring.d.ts.map +1 -0
- package/dist/threadline/UnifiedTrustWiring.js +192 -0
- package/dist/threadline/UnifiedTrustWiring.js.map +1 -0
- package/dist/threadline/client/IdentityManager.d.ts +27 -6
- package/dist/threadline/client/IdentityManager.d.ts.map +1 -1
- package/dist/threadline/client/IdentityManager.js +64 -18
- package/dist/threadline/client/IdentityManager.js.map +1 -1
- package/dist/threadline/relay/SybilProtection.d.ts +78 -0
- package/dist/threadline/relay/SybilProtection.d.ts.map +1 -0
- package/dist/threadline/relay/SybilProtection.js +187 -0
- package/dist/threadline/relay/SybilProtection.js.map +1 -0
- package/package.json +9 -3
- package/playbook-scripts/build-state.py +529 -0
- package/scripts/check-contract-evidence.js +103 -0
- package/scripts/pre-push-gate.js +53 -0
- package/scripts/run-contract-tests.js +75 -0
- package/src/data/builtin-manifest.json +87 -63
- package/src/templates/hooks/build-stop-hook.sh +79 -0
- package/upgrades/0.26.10.md +36 -0
- package/upgrades/0.27.1.md +101 -0
- package/upgrades/0.27.2.md +36 -0
- package/upgrades/NEXT.md +0 -27
- /package/.claude/skills/secret-setup/{SKILL.md → skill.md} +0 -0
package/dist/commands/server.js
CHANGED
|
@@ -93,6 +93,7 @@ import { createMessagingProbes } from '../monitoring/probes/MessagingProbe.js';
|
|
|
93
93
|
import { createLifelineProbes } from '../monitoring/probes/LifelineProbe.js';
|
|
94
94
|
import { createPlatformProbes } from '../monitoring/probes/PlatformProbe.js';
|
|
95
95
|
import { bootstrapThreadline } from '../threadline/ThreadlineBootstrap.js';
|
|
96
|
+
import { createUnifiedTrustSystem } from '../threadline/UnifiedTrustWiring.js';
|
|
96
97
|
import { toInjection } from '../types/pipeline.js';
|
|
97
98
|
import { UserManager } from '../users/UserManager.js';
|
|
98
99
|
import { formatUserContextForSession, hasUserContext } from '../users/UserContextBuilder.js';
|
|
@@ -2884,7 +2885,8 @@ export async function startServer(options) {
|
|
|
2884
2885
|
sessionManager.on('beforeSessionKill', (session) => {
|
|
2885
2886
|
try {
|
|
2886
2887
|
// Save Telegram topic resume UUID (if Telegram is configured)
|
|
2887
|
-
|
|
2888
|
+
// Skip for context exhaustion kills — re-saving would cause a death loop
|
|
2889
|
+
if (telegram && !contextExhaustionKills.has(session.tmuxSession)) {
|
|
2888
2890
|
const topicId = telegram.getTopicForSession(session.tmuxSession);
|
|
2889
2891
|
if (topicId) {
|
|
2890
2892
|
const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
|
|
@@ -2895,7 +2897,9 @@ export async function startServer(options) {
|
|
|
2895
2897
|
}
|
|
2896
2898
|
}
|
|
2897
2899
|
// Save Slack channel resume UUID (if Slack is configured)
|
|
2898
|
-
if
|
|
2900
|
+
// Skip if the session is being killed for context exhaustion — saving the UUID
|
|
2901
|
+
// would cause a death loop where the next respawn loads the same bloated conversation.
|
|
2902
|
+
if (_slackAdapter && !contextExhaustionKills.has(session.tmuxSession)) {
|
|
2899
2903
|
const channelId = _slackAdapter.getChannelForSession(session.tmuxSession);
|
|
2900
2904
|
if (channelId) {
|
|
2901
2905
|
const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
|
|
@@ -2905,6 +2909,9 @@ export async function startServer(options) {
|
|
|
2905
2909
|
}
|
|
2906
2910
|
}
|
|
2907
2911
|
}
|
|
2912
|
+
else if (contextExhaustionKills.has(session.tmuxSession)) {
|
|
2913
|
+
console.log(`[beforeSessionKill] Skipping Slack resume UUID save for ${session.name} — context exhaustion recovery`);
|
|
2914
|
+
}
|
|
2908
2915
|
}
|
|
2909
2916
|
catch (err) {
|
|
2910
2917
|
console.error(`[beforeSessionKill] Failed to save resume UUID:`, err);
|
|
@@ -3076,6 +3083,9 @@ export async function startServer(options) {
|
|
|
3076
3083
|
sendToTopic: async (topicId, text) => {
|
|
3077
3084
|
const slackChId = slackProxyChannelMap.get(topicId);
|
|
3078
3085
|
if (slackChId && _slackAdapter) {
|
|
3086
|
+
// Never send monitoring messages to system channels (dashboard, lifeline)
|
|
3087
|
+
if (_slackAdapter.isSystemChannel(slackChId))
|
|
3088
|
+
return;
|
|
3079
3089
|
await _slackAdapter.sendToChannel(slackChId, text);
|
|
3080
3090
|
return;
|
|
3081
3091
|
}
|
|
@@ -3152,6 +3162,8 @@ export async function startServer(options) {
|
|
|
3152
3162
|
sendToTopic: async (topicId, text) => {
|
|
3153
3163
|
const slackChId = slackProxyChannelMap.get(topicId);
|
|
3154
3164
|
if (slackChId && _slackAdapter) {
|
|
3165
|
+
if (_slackAdapter.isSystemChannel(slackChId))
|
|
3166
|
+
return;
|
|
3155
3167
|
await _slackAdapter.sendToChannel(slackChId, text);
|
|
3156
3168
|
return;
|
|
3157
3169
|
}
|
|
@@ -3245,6 +3257,9 @@ export async function startServer(options) {
|
|
|
3245
3257
|
// SessionRecovery — fast mechanical recovery (JSONL analysis, no LLM)
|
|
3246
3258
|
// Platform-aware: works with Telegram topics AND Slack channels
|
|
3247
3259
|
let sessionRecovery;
|
|
3260
|
+
// Track sessions being killed for context exhaustion — prevents beforeSessionKill
|
|
3261
|
+
// from re-saving resume UUIDs (which would cause an infinite death loop)
|
|
3262
|
+
const contextExhaustionKills = new Set();
|
|
3248
3263
|
if (telegram || _slackAdapter) {
|
|
3249
3264
|
sessionRecovery = new SessionRecovery({ enabled: true, projectDir: config.projectDir }, {
|
|
3250
3265
|
isSessionAlive: (name) => sessionManager.isSessionAlive(name),
|
|
@@ -3307,18 +3322,95 @@ export async function startServer(options) {
|
|
|
3307
3322
|
},
|
|
3308
3323
|
respawnSessionFresh: async (topicId, _sessionName, recoveryPrompt) => {
|
|
3309
3324
|
// Fresh respawn for context exhaustion — explicitly clear resume UUID
|
|
3310
|
-
// so the new session starts clean with
|
|
3325
|
+
// so the new session starts clean with history, not --resume.
|
|
3311
3326
|
if (_topicResumeMap) {
|
|
3312
3327
|
_topicResumeMap.remove(topicId);
|
|
3313
3328
|
}
|
|
3329
|
+
// Check if this is a Slack channel (synthetic negative topic IDs)
|
|
3330
|
+
let slackChId = slackProxyChannelMap.get(topicId);
|
|
3331
|
+
// Fallback: reverse-lookup from adapter channel registry if map doesn't have it
|
|
3332
|
+
if (!slackChId && _slackAdapter && topicId < 0) {
|
|
3333
|
+
const registry = _slackAdapter.getChannelRegistry();
|
|
3334
|
+
for (const channelId of Object.keys(registry)) {
|
|
3335
|
+
if (slackChannelToSyntheticId(channelId) === topicId) {
|
|
3336
|
+
slackChId = channelId;
|
|
3337
|
+
break;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
if (slackChId) {
|
|
3341
|
+
console.log(`[respawnSessionFresh] Resolved slackChId=${slackChId} via reverse lookup (map miss for topicId=${topicId})`);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
console.log(`[respawnSessionFresh] topicId=${topicId} slackChId=${slackChId || 'none'} slackAdapter=${!!_slackAdapter} session=${_sessionName} mapSize=${slackProxyChannelMap.size}`);
|
|
3345
|
+
if (slackChId && _slackAdapter) {
|
|
3346
|
+
// Kill existing session (already flagged in contextExhaustionKills via event listener)
|
|
3347
|
+
const session = sessionManager.listRunningSessions().find(s => s.tmuxSession === _sessionName);
|
|
3348
|
+
if (session)
|
|
3349
|
+
sessionManager.killSession(session.id);
|
|
3350
|
+
// Clear the channel resume so the new session starts fresh
|
|
3351
|
+
_slackAdapter.removeChannelResume(slackChId);
|
|
3352
|
+
// Spawn a fresh session with recovery context
|
|
3353
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3354
|
+
// Build a recovery bootstrap message with thread history
|
|
3355
|
+
const history = _slackAdapter.getChannelMessages(slackChId, 30);
|
|
3356
|
+
const botUserId = _slackAdapter.getBotUserId?.() ?? null;
|
|
3357
|
+
const lines = [];
|
|
3358
|
+
lines.push(`[RECOVERY] Previous session hit the context window limit. This is a FRESH restart with thread history.`);
|
|
3359
|
+
if (recoveryPrompt)
|
|
3360
|
+
lines.push(recoveryPrompt);
|
|
3361
|
+
lines.push('');
|
|
3362
|
+
lines.push(`--- Thread History (last ${history.length} messages) ---`);
|
|
3363
|
+
for (const m of history) {
|
|
3364
|
+
const date = new Date(parseFloat(m.ts) * 1000);
|
|
3365
|
+
const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
|
3366
|
+
const isBot = botUserId && m.user === botUserId;
|
|
3367
|
+
const label = isBot ? 'Agent' : m.user;
|
|
3368
|
+
lines.push(`[${time}] ${label}: ${m.text}`);
|
|
3369
|
+
}
|
|
3370
|
+
lines.push('--- End Thread History ---');
|
|
3371
|
+
lines.push('');
|
|
3372
|
+
lines.push('CRITICAL: You MUST relay your response back to Slack.');
|
|
3373
|
+
lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${slackChId}`);
|
|
3374
|
+
lines.push('Your response text here');
|
|
3375
|
+
lines.push('EOF');
|
|
3376
|
+
const tmpDir = '/tmp/instar-slack';
|
|
3377
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
3378
|
+
const ctxPath = path.join(tmpDir, `recovery-${slackChId}-${Date.now()}.txt`);
|
|
3379
|
+
const contextData = lines.join('\n');
|
|
3380
|
+
fs.writeFileSync(ctxPath, contextData);
|
|
3381
|
+
const bootstrapMessage = `[slack:${slackChId}] [RECOVERY] Context exhaustion — session restarted fresh. (IMPORTANT: Read ${ctxPath} for thread history and Slack relay instructions — you MUST relay your response back.)`;
|
|
3382
|
+
try {
|
|
3383
|
+
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage);
|
|
3384
|
+
if (newSessionName) {
|
|
3385
|
+
_slackAdapter.registerChannelSession(slackChId, newSessionName);
|
|
3386
|
+
console.log(`[slack→recovery] Fresh session "${newSessionName}" spawned for channel ${slackChId} (context exhaustion recovery)`);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
catch (err) {
|
|
3390
|
+
console.error(`[slack→recovery] Fresh session spawn failed for ${slackChId}: ${err instanceof Error ? err.message : err}`);
|
|
3391
|
+
}
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3314
3394
|
if (telegram) {
|
|
3315
3395
|
const targetSession = telegram.getSessionForTopic(topicId);
|
|
3316
|
-
if (!targetSession)
|
|
3396
|
+
if (!targetSession) {
|
|
3397
|
+
console.warn(`[respawnSessionFresh] No Telegram or Slack session found for topicId=${topicId} — recovery has no target`);
|
|
3317
3398
|
return;
|
|
3399
|
+
}
|
|
3318
3400
|
await respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, undefined, topicMemory, undefined, recoveryPrompt, { silent: true });
|
|
3319
3401
|
}
|
|
3402
|
+
else if (!slackChId) {
|
|
3403
|
+
console.warn(`[respawnSessionFresh] No platform handler for topicId=${topicId} — recovery is a no-op`);
|
|
3404
|
+
}
|
|
3320
3405
|
},
|
|
3321
3406
|
});
|
|
3407
|
+
// Track context exhaustion kills to prevent beforeSessionKill from re-saving
|
|
3408
|
+
// resume UUIDs (which would cause an infinite death loop)
|
|
3409
|
+
sessionRecovery.on('recovery:context_exhaustion', ({ sessionName }) => {
|
|
3410
|
+
contextExhaustionKills.add(sessionName);
|
|
3411
|
+
// Clean up after 60 seconds — by then the kill and respawn are done
|
|
3412
|
+
setTimeout(() => contextExhaustionKills.delete(sessionName), 60_000);
|
|
3413
|
+
});
|
|
3322
3414
|
console.log(pc.green(' Session Recovery enabled (mechanical fast-path)'));
|
|
3323
3415
|
}
|
|
3324
3416
|
// SessionMonitor — proactive session health monitoring
|
|
@@ -3336,9 +3428,12 @@ export async function startServer(options) {
|
|
|
3336
3428
|
}
|
|
3337
3429
|
}
|
|
3338
3430
|
// Slack channel sessions (using synthetic IDs)
|
|
3431
|
+
// Exclude system channels (dashboard, lifeline) — they don't have interactive sessions
|
|
3339
3432
|
if (_slackAdapter) {
|
|
3340
3433
|
const registry = _slackAdapter.getChannelRegistry();
|
|
3341
3434
|
for (const [channelId, entry] of Object.entries(registry)) {
|
|
3435
|
+
if (_slackAdapter.isSystemChannel(channelId))
|
|
3436
|
+
continue;
|
|
3342
3437
|
const syntheticId = slackChannelToSyntheticId(channelId);
|
|
3343
3438
|
sessions.set(syntheticId, entry.sessionName);
|
|
3344
3439
|
}
|
|
@@ -3367,6 +3462,8 @@ export async function startServer(options) {
|
|
|
3367
3462
|
sendToTopic: async (topicId, text) => {
|
|
3368
3463
|
const slackChId = slackProxyChannelMap.get(topicId);
|
|
3369
3464
|
if (slackChId && _slackAdapter) {
|
|
3465
|
+
if (_slackAdapter.isSystemChannel(slackChId))
|
|
3466
|
+
return;
|
|
3370
3467
|
await _slackAdapter.sendToChannel(slackChId, text);
|
|
3371
3468
|
return;
|
|
3372
3469
|
}
|
|
@@ -3582,6 +3679,32 @@ export async function startServer(options) {
|
|
|
3582
3679
|
const { HookEventReceiver } = await import('../monitoring/HookEventReceiver.js');
|
|
3583
3680
|
const hookEventReceiver = new HookEventReceiver({ stateDir: config.stateDir });
|
|
3584
3681
|
console.log(pc.green(' Hook event receiver enabled'));
|
|
3682
|
+
// Wire PreCompact events to trigger proactive triage after compaction.
|
|
3683
|
+
// When a session compacts, it goes idle at a prompt. If there are unanswered
|
|
3684
|
+
// user messages (common with Telegram/Slack), Pattern 2b will reinject them.
|
|
3685
|
+
if (triageOrchestrator && telegram) {
|
|
3686
|
+
const _triageOrch = triageOrchestrator;
|
|
3687
|
+
const _telegram = telegram;
|
|
3688
|
+
hookEventReceiver.on('PreCompact', () => {
|
|
3689
|
+
// Delay to let compaction + recovery hooks finish
|
|
3690
|
+
setTimeout(() => {
|
|
3691
|
+
const topicSessions = _telegram.getAllTopicSessions();
|
|
3692
|
+
for (const [topicId, sessionName] of topicSessions) {
|
|
3693
|
+
if (!sessionManager.isSessionAlive(sessionName))
|
|
3694
|
+
continue;
|
|
3695
|
+
const history = _telegram.getTopicHistory(topicId, 5);
|
|
3696
|
+
const lastMsg = history[history.length - 1];
|
|
3697
|
+
if (lastMsg?.fromUser) {
|
|
3698
|
+
console.log(`[CompactionResume] PreCompact detected, topic ${topicId} has unanswered message — activating triage`);
|
|
3699
|
+
_triageOrch.activate(topicId, sessionName, 'watchdog', lastMsg.text, Date.now()).catch(err => {
|
|
3700
|
+
console.warn(`[CompactionResume] Triage activation failed for topic ${topicId}:`, err);
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
}, 10_000);
|
|
3705
|
+
});
|
|
3706
|
+
console.log(pc.green(' Compaction auto-resume wired to triage orchestrator'));
|
|
3707
|
+
}
|
|
3585
3708
|
// Subagent Tracker — monitors subagent lifecycle via hook events
|
|
3586
3709
|
const { SubagentTracker } = await import('../monitoring/SubagentTracker.js');
|
|
3587
3710
|
const subagentTracker = new SubagentTracker({ stateDir: config.stateDir });
|
|
@@ -3705,38 +3828,50 @@ export async function startServer(options) {
|
|
|
3705
3828
|
const { PresenceProxy } = await import('../monitoring/PresenceProxy.js');
|
|
3706
3829
|
const { execSync: shellExecSync } = await import('child_process');
|
|
3707
3830
|
const messagesLogPath = path.join(config.stateDir, 'telegram-messages.jsonl');
|
|
3831
|
+
const slackMessagesLogPath = path.join(config.stateDir, 'slack-messages.jsonl');
|
|
3832
|
+
// Shared helper: check a messages log file for agent responses after a timestamp
|
|
3833
|
+
const checkLogForAgentResponse = (logPath, topicId, sinceIso) => {
|
|
3834
|
+
try {
|
|
3835
|
+
const content = fs.readFileSync(logPath, 'utf-8');
|
|
3836
|
+
const lines = content.trim().split('\n').slice(-50);
|
|
3837
|
+
for (const line of lines) {
|
|
3838
|
+
try {
|
|
3839
|
+
const msg = JSON.parse(line);
|
|
3840
|
+
// Slack logs use channelId (string); Telegram logs use topicId (number).
|
|
3841
|
+
// For Slack synthetic IDs (negative), match against channelId via the map.
|
|
3842
|
+
const matchesTopic = msg.topicId === topicId
|
|
3843
|
+
|| (topicId < 0 && msg.channelId && slackChannelToSyntheticId(String(msg.channelId)) === topicId);
|
|
3844
|
+
if (matchesTopic && !msg.fromUser && msg.timestamp > sinceIso) {
|
|
3845
|
+
const t = (msg.text || '').trim();
|
|
3846
|
+
const isSystem = t === '✓ Delivered' || t.startsWith('✓ Delivered')
|
|
3847
|
+
|| t.startsWith('🔄 Session restarting') || t === 'Session respawned.'
|
|
3848
|
+
|| t === 'Session terminated.' || t.startsWith('Send a new message to start')
|
|
3849
|
+
|| t.startsWith('🔭');
|
|
3850
|
+
if (!isSystem)
|
|
3851
|
+
return true;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
catch { /* skip malformed lines */ }
|
|
3855
|
+
}
|
|
3856
|
+
return false;
|
|
3857
|
+
}
|
|
3858
|
+
catch {
|
|
3859
|
+
return false;
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3708
3862
|
presenceProxy = new PresenceProxy({
|
|
3709
3863
|
stateDir: config.stateDir,
|
|
3710
3864
|
intelligence: sharedIntelligence,
|
|
3711
3865
|
agentName: config.projectName ?? 'the agent',
|
|
3712
3866
|
hasAgentRespondedSince: (topicId, sinceMs) => {
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
if (msg.topicId === topicId && !msg.fromUser && msg.timestamp > sinceIso) {
|
|
3722
|
-
// Skip proxy messages and system messages (delivery confirmations, session lifecycle)
|
|
3723
|
-
const t = (msg.text || '').trim();
|
|
3724
|
-
const isSystem = t === '✓ Delivered' || t.startsWith('✓ Delivered')
|
|
3725
|
-
|| t.startsWith('🔄 Session restarting') || t === 'Session respawned.'
|
|
3726
|
-
|| t === 'Session terminated.' || t.startsWith('Send a new message to start')
|
|
3727
|
-
|| t.startsWith('🔭');
|
|
3728
|
-
if (!isSystem) {
|
|
3729
|
-
return true;
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
}
|
|
3733
|
-
catch { /* skip malformed lines */ }
|
|
3734
|
-
}
|
|
3735
|
-
return false;
|
|
3736
|
-
}
|
|
3737
|
-
catch {
|
|
3738
|
-
return false;
|
|
3739
|
-
}
|
|
3867
|
+
const sinceIso = new Date(sinceMs).toISOString();
|
|
3868
|
+
// Check Telegram log
|
|
3869
|
+
if (checkLogForAgentResponse(messagesLogPath, topicId, sinceIso))
|
|
3870
|
+
return true;
|
|
3871
|
+
// Also check Slack log (for Slack synthetic IDs or cross-platform responses)
|
|
3872
|
+
if (checkLogForAgentResponse(slackMessagesLogPath, topicId, sinceIso))
|
|
3873
|
+
return true;
|
|
3874
|
+
return false;
|
|
3740
3875
|
},
|
|
3741
3876
|
captureSessionOutput: (name, lines) => sessionManager.captureOutput(name, lines),
|
|
3742
3877
|
getSessionForTopic: (topicId) => {
|
|
@@ -3752,6 +3887,9 @@ export async function startServer(options) {
|
|
|
3752
3887
|
// Check if this is a Slack synthetic ID (negative = Slack channel)
|
|
3753
3888
|
const slackChannelId = slackProxyChannelMap.get(topicId);
|
|
3754
3889
|
if (slackChannelId && _slackAdapter) {
|
|
3890
|
+
// Never send proxy messages to system channels (dashboard, lifeline)
|
|
3891
|
+
if (_slackAdapter.isSystemChannel(slackChannelId))
|
|
3892
|
+
return;
|
|
3755
3893
|
// Route directly to Slack channel
|
|
3756
3894
|
await _slackAdapter.sendToChannel(slackChannelId, text);
|
|
3757
3895
|
return;
|
|
@@ -3825,6 +3963,12 @@ export async function startServer(options) {
|
|
|
3825
3963
|
await triageNurse.triage(topicId, sessionName, '', Date.now(), 'manual');
|
|
3826
3964
|
}
|
|
3827
3965
|
: undefined,
|
|
3966
|
+
recoverContextExhaustion: sessionRecovery
|
|
3967
|
+
? async (topicId, sessionName) => {
|
|
3968
|
+
const result = await sessionRecovery.checkAndRecover(topicId, sessionName);
|
|
3969
|
+
return { recovered: result.recovered };
|
|
3970
|
+
}
|
|
3971
|
+
: undefined,
|
|
3828
3972
|
});
|
|
3829
3973
|
// Hook into Telegram's onMessageLogged callback (always active, unlike EventBus which requires a feature flag)
|
|
3830
3974
|
const existingCallback = telegram.onMessageLogged;
|
|
@@ -3873,8 +4017,11 @@ export async function startServer(options) {
|
|
|
3873
4017
|
_slackAdapter.clearStallTracking(String(entry.channelId));
|
|
3874
4018
|
}
|
|
3875
4019
|
// Convert Slack channelId to synthetic numeric ID for PresenceProxy
|
|
4020
|
+
// Skip system channels (dashboard, lifeline) — they don't have interactive sessions
|
|
3876
4021
|
if (!entry.channelId)
|
|
3877
4022
|
return;
|
|
4023
|
+
if (_slackAdapter.isSystemChannel(String(entry.channelId)))
|
|
4024
|
+
return;
|
|
3878
4025
|
const syntheticId = slackChannelToSyntheticId(String(entry.channelId));
|
|
3879
4026
|
presenceProxy.onMessageLogged({
|
|
3880
4027
|
messageId: typeof entry.messageId === 'number' ? entry.messageId : parseInt(String(entry.messageId), 10) || 0,
|
|
@@ -3958,9 +4105,14 @@ export async function startServer(options) {
|
|
|
3958
4105
|
const tunnelUrl = await tunnel.start();
|
|
3959
4106
|
console.log(`[SleepWake] Tunnel restarted: ${tunnelUrl}`);
|
|
3960
4107
|
// Re-broadcast dashboard URL after tunnel restart (quick tunnels get new URL)
|
|
3961
|
-
if (
|
|
4108
|
+
if (tunnelUrl) {
|
|
3962
4109
|
const tunnelType = config.tunnel?.type || 'quick';
|
|
3963
|
-
|
|
4110
|
+
if (telegram) {
|
|
4111
|
+
await telegram.broadcastDashboardUrl(tunnelUrl, tunnelType).catch(() => { });
|
|
4112
|
+
}
|
|
4113
|
+
if (_slackAdapter) {
|
|
4114
|
+
await _slackAdapter.broadcastDashboardUrl(tunnelUrl).catch(() => { });
|
|
4115
|
+
}
|
|
3964
4116
|
}
|
|
3965
4117
|
})(),
|
|
3966
4118
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Tunnel restart timed out after 15s')), 15_000)),
|
|
@@ -3972,6 +4124,21 @@ export async function startServer(options) {
|
|
|
3972
4124
|
tunnel.enableAutoReconnect();
|
|
3973
4125
|
}
|
|
3974
4126
|
}
|
|
4127
|
+
// Reconnect Slack after sleep — WebSocket connections die during sleep
|
|
4128
|
+
// and the close-handler reconnect can silently fail if the network isn't
|
|
4129
|
+
// fully up yet. Proactive reconnect after a short delay is more reliable.
|
|
4130
|
+
if (slackAdapter) {
|
|
4131
|
+
// Wait 2s for network to stabilize after wake before reconnecting
|
|
4132
|
+
setTimeout(async () => {
|
|
4133
|
+
try {
|
|
4134
|
+
await slackAdapter.reconnect();
|
|
4135
|
+
console.log('[SleepWake] Slack reconnected');
|
|
4136
|
+
}
|
|
4137
|
+
catch (err) {
|
|
4138
|
+
console.error('[SleepWake] Slack reconnect failed:', err.message);
|
|
4139
|
+
}
|
|
4140
|
+
}, 2000);
|
|
4141
|
+
}
|
|
3975
4142
|
// Notify via batcher — wake events are informational, not urgent
|
|
3976
4143
|
// Only notify for long sleeps (>5 min) — short sleeps are routine and not worth mentioning
|
|
3977
4144
|
if (event.sleepDurationSeconds > 300) {
|
|
@@ -4399,6 +4566,7 @@ export async function startServer(options) {
|
|
|
4399
4566
|
let threadlineHandshake;
|
|
4400
4567
|
let threadlineShutdown;
|
|
4401
4568
|
let threadlineRelayClient;
|
|
4569
|
+
let unifiedTrust;
|
|
4402
4570
|
try {
|
|
4403
4571
|
const threadline = await bootstrapThreadline({
|
|
4404
4572
|
agentName: config.projectName,
|
|
@@ -4413,6 +4581,19 @@ export async function startServer(options) {
|
|
|
4413
4581
|
threadlineHandshake = threadline.handshakeManager;
|
|
4414
4582
|
threadlineShutdown = threadline.shutdown;
|
|
4415
4583
|
threadlineRelayClient = threadline.relayClient;
|
|
4584
|
+
// Initialize unified trust system (three-layer model + MoltBridge)
|
|
4585
|
+
if (threadline.trustManager) {
|
|
4586
|
+
try {
|
|
4587
|
+
unifiedTrust = createUnifiedTrustSystem(threadline.trustManager, {
|
|
4588
|
+
stateDir: config.stateDir,
|
|
4589
|
+
moltbridge: config.moltbridge,
|
|
4590
|
+
});
|
|
4591
|
+
console.log(`Unified trust system initialized (identity: ${unifiedTrust.identity.get()?.displayFingerprint ?? 'none'})`);
|
|
4592
|
+
}
|
|
4593
|
+
catch (err) {
|
|
4594
|
+
console.error(`Unified trust system init failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4416
4597
|
if (threadlineRelayClient) {
|
|
4417
4598
|
// Wire relay message delivery through ThreadlineRouter (Phase 1).
|
|
4418
4599
|
// Replaces the ad-hoc handler with proper thread persistence, auto-ack,
|
|
@@ -4604,7 +4785,7 @@ export async function startServer(options) {
|
|
|
4604
4785
|
return { content: lines.join('\n'), truncated: false, elapsedMs: Date.now() - start };
|
|
4605
4786
|
}, { description: 'Feature discovery state and behavioral contract summary' });
|
|
4606
4787
|
}
|
|
4607
|
-
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, liveConfig });
|
|
4788
|
+
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 });
|
|
4608
4789
|
await server.start();
|
|
4609
4790
|
// Connect DegradationReporter downstream systems now that everything is initialized.
|
|
4610
4791
|
// Any degradation events queued during startup will drain to feedback + telegram.
|
|
@@ -4674,9 +4855,14 @@ export async function startServer(options) {
|
|
|
4674
4855
|
try {
|
|
4675
4856
|
const tunnelUrl = await tunnel.start();
|
|
4676
4857
|
console.log(pc.green(`[tunnel] Connected: ${tunnelUrl}`));
|
|
4677
|
-
if (
|
|
4858
|
+
if (tunnelUrl) {
|
|
4678
4859
|
const tunnelType = (config.tunnel?.type || 'quick');
|
|
4679
|
-
|
|
4860
|
+
if (telegram) {
|
|
4861
|
+
await telegram.broadcastDashboardUrl(tunnelUrl, tunnelType).catch(() => { });
|
|
4862
|
+
}
|
|
4863
|
+
if (_slackAdapter) {
|
|
4864
|
+
await _slackAdapter.broadcastDashboardUrl(tunnelUrl).catch(() => { });
|
|
4865
|
+
}
|
|
4680
4866
|
}
|
|
4681
4867
|
}
|
|
4682
4868
|
catch (retryErr) {
|
|
@@ -4725,6 +4911,12 @@ export async function startServer(options) {
|
|
|
4725
4911
|
telegram.config.dashboardPin = config.dashboardPin || '';
|
|
4726
4912
|
}
|
|
4727
4913
|
await telegram.broadcastDashboardUrl(dashUrl, tunnelType);
|
|
4914
|
+
// Also broadcast to Slack dashboard channel if configured
|
|
4915
|
+
if (_slackAdapter) {
|
|
4916
|
+
await _slackAdapter.broadcastDashboardUrl(dashUrl).catch((err) => {
|
|
4917
|
+
console.warn(`[server] Slack dashboard broadcast failed: ${err.message}`);
|
|
4918
|
+
});
|
|
4919
|
+
}
|
|
4728
4920
|
}
|
|
4729
4921
|
else {
|
|
4730
4922
|
console.log(pc.yellow(` Dashboard available locally at http://localhost:${config.port}/dashboard (no tunnel configured — not broadcasting to Telegram)`));
|