instar 1.3.445 → 1.3.447
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 +79 -22
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +14 -4
- package/dist/core/PostUpdateMigrator.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 +8 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +54 -2
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +88 -6
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/messaging/slack/types.d.ts +29 -0
- package/dist/messaging/slack/types.d.ts.map +1 -1
- package/dist/messaging/slack/types.js.map +1 -1
- package/dist/permissions/RelationshipAnomalyScorer.d.ts +123 -0
- package/dist/permissions/RelationshipAnomalyScorer.d.ts.map +1 -0
- package/dist/permissions/RelationshipAnomalyScorer.js +249 -0
- package/dist/permissions/RelationshipAnomalyScorer.js.map +1 -0
- package/dist/permissions/RelationshipBehaviorStore.d.ts +90 -0
- package/dist/permissions/RelationshipBehaviorStore.d.ts.map +1 -0
- package/dist/permissions/RelationshipBehaviorStore.js +156 -0
- package/dist/permissions/RelationshipBehaviorStore.js.map +1 -0
- package/dist/permissions/SlackPermissionObserver.d.ts +24 -0
- package/dist/permissions/SlackPermissionObserver.d.ts.map +1 -1
- package/dist/permissions/SlackPermissionObserver.js +35 -0
- package/dist/permissions/SlackPermissionObserver.js.map +1 -1
- package/dist/permissions/index.d.ts +2 -0
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/index.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +28 -2
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/data/state-coherence-registry.json +12 -0
- package/src/templates/scripts/slack-reply.sh +26 -1
- package/upgrades/1.3.446.md +24 -0
- package/upgrades/1.3.447.md +27 -0
- package/upgrades/side-effects/slack-relationship-stepup.md +107 -0
- package/upgrades/side-effects/slack-thread-session.md +48 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA05BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA0UN;AA2lBD,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;AAaH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA05BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA0UN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA20UtE;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
|
@@ -4255,7 +4255,7 @@ export async function startServer(options) {
|
|
|
4255
4255
|
const slackCfg = slackConfig.config;
|
|
4256
4256
|
const pgCfg = slackCfg.permissionGate;
|
|
4257
4257
|
if (pgCfg && (pgCfg.observeOnly || pgCfg.enforce)) {
|
|
4258
|
-
const { SlackPermissionObserver, SlackPrincipalResolver, SlackPermissionGate, PermissionDecisionLedger, RolePolicy, HeuristicIntentClassifier, LlmIntentClassifier, HeuristicAnomalyScorer, MandateBackedGrantStore, } = await import('../permissions/index.js');
|
|
4258
|
+
const { SlackPermissionObserver, SlackPrincipalResolver, SlackPermissionGate, PermissionDecisionLedger, RolePolicy, HeuristicIntentClassifier, LlmIntentClassifier, HeuristicAnomalyScorer, RelationshipBehaviorStore, RelationshipAnomalyScorer, MandateBackedGrantStore, } = await import('../permissions/index.js');
|
|
4259
4259
|
// Own UserManager instance for verified-principal resolution (the
|
|
4260
4260
|
// Telegram-block userManager is out of scope here). Reads users.json.
|
|
4261
4261
|
const slackUserManager = new UserManager(config.stateDir, config.users);
|
|
@@ -4314,6 +4314,24 @@ export async function startServer(options) {
|
|
|
4314
4314
|
else {
|
|
4315
4315
|
intentClassifier = new HeuristicIntentClassifier();
|
|
4316
4316
|
}
|
|
4317
|
+
// ── Pillar 3 relationship-aware anomaly second factor (DARK by default) ──
|
|
4318
|
+
// With `relationshipAnomaly.enabled`, wire the durable behavioral baseline
|
|
4319
|
+
// store + the RelationshipAnomalyScorer. The baseline grows from observed
|
|
4320
|
+
// traffic (recorded SHAPE only, never content) via the observer. With the
|
|
4321
|
+
// flag OFF, we keep the placeholder HeuristicAnomalyScorer (urgency-only)
|
|
4322
|
+
// exactly as before — nothing changes.
|
|
4323
|
+
const raCfg = pgCfg.relationshipAnomaly;
|
|
4324
|
+
let anomalyScorer = new HeuristicAnomalyScorer();
|
|
4325
|
+
let behaviorStore;
|
|
4326
|
+
if (raCfg?.enabled && config.stateDir) {
|
|
4327
|
+
behaviorStore = new RelationshipBehaviorStore(config.stateDir);
|
|
4328
|
+
anomalyScorer = new RelationshipAnomalyScorer(behaviorStore, {
|
|
4329
|
+
useLlmStyleCheck: raCfg.useLlmStyleCheck === true,
|
|
4330
|
+
// Fail-closed LLM style check uses the shared internal provider when present.
|
|
4331
|
+
intelligence: sharedIntelligence ?? undefined,
|
|
4332
|
+
});
|
|
4333
|
+
console.log(`[slack] relationship-aware anomaly scorer attached (observe-only baseline; LLM style check ${raCfg.useLlmStyleCheck ? 'ON' : 'off'})`);
|
|
4334
|
+
}
|
|
4317
4335
|
const observer = new SlackPermissionObserver({
|
|
4318
4336
|
resolver: new SlackPrincipalResolver({
|
|
4319
4337
|
resolveFromSlackUserId: (id) => slackUserManager.resolveFromSlackUserId(id),
|
|
@@ -4321,11 +4339,13 @@ export async function startServer(options) {
|
|
|
4321
4339
|
gate: new SlackPermissionGate({
|
|
4322
4340
|
rolePolicy: new RolePolicy(),
|
|
4323
4341
|
classifier: intentClassifier,
|
|
4324
|
-
anomalyScorer
|
|
4342
|
+
anomalyScorer,
|
|
4325
4343
|
grants: grantStore,
|
|
4344
|
+
...(raCfg?.stepUpThreshold !== undefined ? { stepUpThreshold: raCfg.stepUpThreshold } : {}),
|
|
4326
4345
|
}),
|
|
4327
4346
|
ledger: new PermissionDecisionLedger(config.stateDir),
|
|
4328
4347
|
enforce: pgCfg.enforce === true,
|
|
4348
|
+
behaviorStore,
|
|
4329
4349
|
});
|
|
4330
4350
|
slackAdapter.setPermissionObserver(observer);
|
|
4331
4351
|
console.log(`[slack] permission gate attached (${pgCfg.enforce ? 'ENFORCE' : 'observe-only'})`);
|
|
@@ -4373,6 +4393,20 @@ export async function startServer(options) {
|
|
|
4373
4393
|
const channelId = message.channel.identifier;
|
|
4374
4394
|
const isDM = message.metadata?.isDM;
|
|
4375
4395
|
const senderName = message.metadata?.senderName || 'User';
|
|
4396
|
+
// ── Thread → session routing (§5.3) ──────────────────────────────────
|
|
4397
|
+
// The Slack channelId is ALWAYS the address we talk to the Slack API with
|
|
4398
|
+
// (replies, reactions, history). The session REGISTRY + resume map are
|
|
4399
|
+
// keyed on a routing key that, when thread routing is opted in for this
|
|
4400
|
+
// channel and the message is a reply inside a thread, becomes
|
|
4401
|
+
// `<channelId>:<thread_ts>` — giving that thread its own isolated session,
|
|
4402
|
+
// mirroring Telegram topic→session. Default (no opt-in / no thread_ts):
|
|
4403
|
+
// routingKey === channelId, byte-for-byte today's behavior.
|
|
4404
|
+
const messageTs = message.metadata?.ts;
|
|
4405
|
+
const threadTs = message.metadata?.threadTs;
|
|
4406
|
+
const routingKey = slackAdapter.resolveRoutingKey(channelId, threadTs, messageTs);
|
|
4407
|
+
const isThreadSession = slackAdapter.isThreadRoutingKey(routingKey);
|
|
4408
|
+
// The thread_ts to thread replies under (only when this is a thread session).
|
|
4409
|
+
const replyThreadTs = isThreadSession ? threadTs : undefined;
|
|
4376
4410
|
// Sentinel intercept — classify message for emergency stop/pause
|
|
4377
4411
|
if (sentinel) {
|
|
4378
4412
|
try {
|
|
@@ -4390,7 +4424,7 @@ export async function startServer(options) {
|
|
|
4390
4424
|
return;
|
|
4391
4425
|
}
|
|
4392
4426
|
else if (classification.category === 'pause') {
|
|
4393
|
-
const existingSession = slackAdapter.getSessionForChannel(
|
|
4427
|
+
const existingSession = slackAdapter.getSessionForChannel(routingKey);
|
|
4394
4428
|
if (existingSession) {
|
|
4395
4429
|
sessionManager.sendKey(existingSession, 'Escape');
|
|
4396
4430
|
slackAdapter.sendToChannel(channelId, '⏸️ Session paused.').catch(() => { });
|
|
@@ -4447,9 +4481,16 @@ export async function startServer(options) {
|
|
|
4447
4481
|
contextLines.push('CRITICAL: You MUST relay your response back to Slack after responding.');
|
|
4448
4482
|
contextLines.push('Use the relay script (write ONLY your reply text — do NOT pipe or cat this file into the script):');
|
|
4449
4483
|
contextLines.push('');
|
|
4450
|
-
|
|
4484
|
+
// Thread session: pass the thread_ts as the 2nd arg so the reply lands IN
|
|
4485
|
+
// the thread (not the channel root). Channel session: channelId only.
|
|
4486
|
+
const replyTarget = replyThreadTs ? `${channelId} ${replyThreadTs}` : `${channelId}`;
|
|
4487
|
+
contextLines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${replyTarget}`);
|
|
4451
4488
|
contextLines.push('Your response text here');
|
|
4452
4489
|
contextLines.push('EOF');
|
|
4490
|
+
if (replyThreadTs) {
|
|
4491
|
+
contextLines.push('');
|
|
4492
|
+
contextLines.push('(This is a THREAD conversation — keep your reply in this thread by passing the thread id shown above as the 2nd argument.)');
|
|
4493
|
+
}
|
|
4453
4494
|
contextLines.push('');
|
|
4454
4495
|
contextLines.push('Strip the [slack:] prefix before interpreting the message.');
|
|
4455
4496
|
contextLines.push('Only relay conversational text — not tool output, file contents, or internal reasoning.');
|
|
@@ -4485,8 +4526,8 @@ export async function startServer(options) {
|
|
|
4485
4526
|
else {
|
|
4486
4527
|
bootstrapMessage = fullMessage;
|
|
4487
4528
|
}
|
|
4488
|
-
// Check for existing session bound to this channel
|
|
4489
|
-
const existingSession = slackAdapter.getSessionForChannel(
|
|
4529
|
+
// Check for existing session bound to this channel/thread (routing key)
|
|
4530
|
+
const existingSession = slackAdapter.getSessionForChannel(routingKey);
|
|
4490
4531
|
if (existingSession) {
|
|
4491
4532
|
// Try to inject into existing session via tmux
|
|
4492
4533
|
const sessions = sessionManager.listRunningSessions();
|
|
@@ -4524,20 +4565,26 @@ export async function startServer(options) {
|
|
|
4524
4565
|
}
|
|
4525
4566
|
console.log(`[slack→session] Session "${existingSession}" died, respawning...`);
|
|
4526
4567
|
}
|
|
4527
|
-
// Check resume map for session continuity
|
|
4528
|
-
|
|
4568
|
+
// Check resume map for session continuity (keyed on routing key, so a
|
|
4569
|
+
// thread resumes its OWN session and not the channel-root one).
|
|
4570
|
+
const resumeInfo = slackAdapter.getChannelResume(routingKey);
|
|
4529
4571
|
const resumeSessionId = resumeInfo?.uuid ?? undefined;
|
|
4530
4572
|
if (resumeInfo) {
|
|
4531
|
-
slackAdapter.removeChannelResume(
|
|
4573
|
+
slackAdapter.removeChannelResume(routingKey);
|
|
4532
4574
|
}
|
|
4533
|
-
// Route: DMs go to lifeline session, channels spawn new sessions
|
|
4534
|
-
|
|
4575
|
+
// Route: DMs go to lifeline session, channels/threads spawn new sessions.
|
|
4576
|
+
// A thread session NEVER folds into the DM lifeline — it is its own
|
|
4577
|
+
// isolated work session (DMs don't carry thread_ts anyway).
|
|
4578
|
+
const targetSession = (isDM && !isThreadSession) ? 'lifeline' : undefined;
|
|
4535
4579
|
try {
|
|
4536
|
-
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId, slackChannelId: channelId });
|
|
4580
|
+
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId, slackChannelId: channelId, slackThreadTs: replyThreadTs });
|
|
4537
4581
|
if (newSessionName) {
|
|
4538
|
-
|
|
4582
|
+
// Register on the routing key (channelId for a channel session,
|
|
4583
|
+
// `<channelId>:<thread_ts>` for a thread session). channelName carries
|
|
4584
|
+
// a thread hint so the registry stays human-readable.
|
|
4585
|
+
slackAdapter.registerChannelSession(routingKey, newSessionName, isThreadSession ? `${channelId} (thread ${replyThreadTs})` : undefined);
|
|
4539
4586
|
slackAdapter.trackMessageInjection(channelId, newSessionName, message.content);
|
|
4540
|
-
console.log(`[slack→session] ${resumeSessionId ? 'Resumed' : 'Spawned'} "${newSessionName}" for channel ${channelId}`);
|
|
4587
|
+
console.log(`[slack→session] ${resumeSessionId ? 'Resumed' : 'Spawned'} "${newSessionName}" for ${isThreadSession ? `thread ${routingKey}` : `channel ${channelId}`}`);
|
|
4541
4588
|
}
|
|
4542
4589
|
}
|
|
4543
4590
|
catch (err) {
|
|
@@ -5550,17 +5597,24 @@ export async function startServer(options) {
|
|
|
5550
5597
|
}
|
|
5551
5598
|
console.log(`[respawnSessionFresh] topicId=${topicId} slackChId=${slackChId || 'none'} slackAdapter=${!!_slackAdapter} session=${_sessionName} mapSize=${slackProxyChannelMap.size}`);
|
|
5552
5599
|
if (slackChId && _slackAdapter) {
|
|
5600
|
+
// Thread→session (§5.3): slackChId may be a routing key
|
|
5601
|
+
// (`<channelId>:<thread_ts>`). The registry + resume map are keyed on
|
|
5602
|
+
// the routing key (slackChId); the Slack API + reply instruction need
|
|
5603
|
+
// the RAW channel id, and a thread reply needs the embedded thread_ts.
|
|
5604
|
+
const parsedTarget = _slackAdapter.parseRoutingKey(slackChId);
|
|
5605
|
+
const slackApiChannel = parsedTarget.channelId;
|
|
5606
|
+
const slackReplyThread = parsedTarget.threadTs;
|
|
5553
5607
|
// Kill existing session (already flagged in contextExhaustionKills via event listener)
|
|
5554
5608
|
const session = sessionManager.listRunningSessions().find(s => s.tmuxSession === _sessionName);
|
|
5555
5609
|
if (session)
|
|
5556
5610
|
sessionManager.killSession(session.id);
|
|
5557
|
-
// Clear the channel resume so the new session starts fresh
|
|
5611
|
+
// Clear the channel/thread resume so the new session starts fresh
|
|
5558
5612
|
_slackAdapter.removeChannelResume(slackChId);
|
|
5559
5613
|
// Spawn a fresh session with recovery context
|
|
5560
5614
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
5561
5615
|
// Build a recovery bootstrap message with thread history (inline, matching Telegram pattern)
|
|
5562
5616
|
// Use async fallback to fetch from Slack API if ring buffer is empty (race condition on restart)
|
|
5563
|
-
const history = await _slackAdapter.getChannelMessagesWithFallback(
|
|
5617
|
+
const history = await _slackAdapter.getChannelMessagesWithFallback(slackApiChannel, 30);
|
|
5564
5618
|
const botUserId = _slackAdapter.getBotUserId?.() ?? null;
|
|
5565
5619
|
const lines = [];
|
|
5566
5620
|
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.`);
|
|
@@ -5581,25 +5635,28 @@ export async function startServer(options) {
|
|
|
5581
5635
|
lines.push('--- End Thread History ---');
|
|
5582
5636
|
}
|
|
5583
5637
|
else {
|
|
5584
|
-
console.warn(`[slack→recovery] WARNING: No history available for channel ${
|
|
5638
|
+
console.warn(`[slack→recovery] WARNING: No history available for channel ${slackApiChannel} — recovery context is empty. Ring buffer may not be populated yet.`);
|
|
5585
5639
|
lines.push('[WARNING: Thread history unavailable — ring buffer may not be populated. Check Slack channel for recent messages before responding.]');
|
|
5586
5640
|
}
|
|
5587
5641
|
lines.push('');
|
|
5588
5642
|
lines.push('CRITICAL: You MUST relay your response back to Slack.');
|
|
5589
|
-
|
|
5643
|
+
// Thread session: include the thread_ts so the recovered reply threads.
|
|
5644
|
+
const recoveryReplyTarget = slackReplyThread ? `${slackApiChannel} ${slackReplyThread}` : `${slackApiChannel}`;
|
|
5645
|
+
lines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${recoveryReplyTarget}`);
|
|
5590
5646
|
lines.push('Your response text here');
|
|
5591
5647
|
lines.push('EOF');
|
|
5592
5648
|
const tmpDir = '/tmp/instar-slack';
|
|
5593
5649
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
5594
|
-
const ctxPath = path.join(tmpDir, `recovery-${
|
|
5650
|
+
const ctxPath = path.join(tmpDir, `recovery-${slackApiChannel}-${Date.now()}.txt`);
|
|
5595
5651
|
const contextData = lines.join('\n');
|
|
5596
5652
|
fs.writeFileSync(ctxPath, contextData);
|
|
5597
|
-
const bootstrapMessage = `[slack:${
|
|
5653
|
+
const bootstrapMessage = `[slack:${slackApiChannel}] ${contextData}`;
|
|
5598
5654
|
try {
|
|
5599
|
-
|
|
5655
|
+
// Spawn with the RAW channel (+ thread_ts) but register on the routing key.
|
|
5656
|
+
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, undefined, { slackChannelId: slackApiChannel, slackThreadTs: slackReplyThread });
|
|
5600
5657
|
if (newSessionName) {
|
|
5601
5658
|
_slackAdapter.registerChannelSession(slackChId, newSessionName);
|
|
5602
|
-
console.log(`[slack→recovery] Fresh session "${newSessionName}" spawned for
|
|
5659
|
+
console.log(`[slack→recovery] Fresh session "${newSessionName}" spawned for ${slackReplyThread ? `thread ${slackChId}` : `channel ${slackApiChannel}`} (context exhaustion recovery)`);
|
|
5603
5660
|
}
|
|
5604
5661
|
}
|
|
5605
5662
|
catch (err) {
|