instar 1.3.879 → 1.3.880
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/init.d.ts.map +1 -1
- package/dist/commands/init.js +21 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +55 -15
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +33 -18
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SlackReplyRelayInstaller.d.ts +22 -0
- package/dist/core/SlackReplyRelayInstaller.d.ts.map +1 -0
- package/dist/core/SlackReplyRelayInstaller.js +109 -0
- package/dist/core/SlackReplyRelayInstaller.js.map +1 -0
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +11 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +49 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +66 -66
- package/src/templates/hooks/compaction-recovery.sh +1 -3
- package/src/templates/hooks/slack-channel-context.sh +1 -1
- package/src/templates/scripts/slack-reply.sh +53 -11
- package/upgrades/1.3.880.md +23 -0
- package/upgrades/side-effects/slack-session-reply-relay.md +94 -0
- package/upgrades/slack-session-reply-relay.eli16.md +9 -0
package/dist/commands/server.js
CHANGED
|
@@ -18,6 +18,24 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
18
18
|
// (A.5) wake-handler amplifier guard — the post-wake tmux re-validation is bounded
|
|
19
19
|
// async (9s + SIGKILL) instead of a sync execFileSync that can wedge the event loop.
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
+
function packagedSlackReplyRelay() {
|
|
22
|
+
for (const candidate of [
|
|
23
|
+
path.resolve(__dirname, '..', 'templates', 'scripts', 'slack-reply.sh'),
|
|
24
|
+
path.resolve(__dirname, '..', '..', 'src', 'templates', 'scripts', 'slack-reply.sh'),
|
|
25
|
+
]) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.readFileSync(candidate, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
catch { /* try packaged/dev alternative */ }
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function localSlackRelayReadiness(stateDir) {
|
|
34
|
+
const template = packagedSlackReplyRelay();
|
|
35
|
+
if (!template)
|
|
36
|
+
return { ready: false, reason: 'packaged Slack reply relay template is unavailable' };
|
|
37
|
+
return slackReplyRelayReadiness(stateDir, template);
|
|
38
|
+
}
|
|
21
39
|
import { loadConfig, ensureStateDir, detectTmuxPath, detectGeminiPath } from '../core/Config.js';
|
|
22
40
|
import { handleProcessLevelError } from '../core/uncaughtExceptionPolicy.js';
|
|
23
41
|
import { planInboundLossNotices } from '../core/inboundLossRouting.js';
|
|
@@ -33,6 +51,7 @@ import { resolveAntiThrashKnobs, readingValidity } from '../core/SwapAntiThrash.
|
|
|
33
51
|
import { shouldReleaseOnComplete, planClaimOnSpawn, ownershipNonce } from '../core/ownershipFollowsLiveWork.js';
|
|
34
52
|
import { PrHandLease } from '../core/PrHandLease.js';
|
|
35
53
|
import { ConversationRegistry } from '../core/ConversationRegistry.js';
|
|
54
|
+
import { slackReplyRelayReadiness } from '../core/SlackReplyRelayInstaller.js';
|
|
36
55
|
import { claimSuspensionExcludesPin } from '../core/TopicClaimAnnotationStore.js';
|
|
37
56
|
import { parseProfileTrigger, platformMessageIdFrom } from '../core/topicProfileIngress.js';
|
|
38
57
|
import { classifyProfileIntent, toProfilePatch, } from '../core/ProfileIntentClassifier.js';
|
|
@@ -7614,7 +7633,6 @@ export async function startServer(options) {
|
|
|
7614
7633
|
// CONSUMES it rather than re-resolving.
|
|
7615
7634
|
admissionVerdict) => {
|
|
7616
7635
|
const channelId = message.channel.identifier;
|
|
7617
|
-
const isDM = message.metadata?.isDM;
|
|
7618
7636
|
const senderName = message.metadata?.senderName || 'User';
|
|
7619
7637
|
// ── Thread → session routing (§5.3) ──────────────────────────────────
|
|
7620
7638
|
// The Slack channelId is ALWAYS the address we talk to the Slack API with
|
|
@@ -7692,15 +7710,14 @@ export async function startServer(options) {
|
|
|
7692
7710
|
contextLines.push('CRITICAL: You MUST relay your response back to Slack after responding.');
|
|
7693
7711
|
contextLines.push('Use the relay script (write ONLY your reply text — do NOT pipe or cat this file into the script):');
|
|
7694
7712
|
contextLines.push('');
|
|
7695
|
-
//
|
|
7696
|
-
//
|
|
7697
|
-
|
|
7698
|
-
contextLines.push(`cat <<'EOF' | .claude/scripts/slack-reply.sh ${replyTarget}`);
|
|
7713
|
+
// The bind-token-scoped conversation id is the destination authority;
|
|
7714
|
+
// never interpolate a caller-chosen channel/thread into the command.
|
|
7715
|
+
contextLines.push("cat <<'EOF' | .instar/scripts/slack-reply.sh");
|
|
7699
7716
|
contextLines.push('Your response text here');
|
|
7700
7717
|
contextLines.push('EOF');
|
|
7701
7718
|
if (replyThreadTs) {
|
|
7702
7719
|
contextLines.push('');
|
|
7703
|
-
contextLines.push('(This is a THREAD conversation —
|
|
7720
|
+
contextLines.push('(This is a THREAD conversation — the session binding keeps the reply in this exact thread.)');
|
|
7704
7721
|
}
|
|
7705
7722
|
contextLines.push('');
|
|
7706
7723
|
contextLines.push('Strip the [slack:] prefix before interpreting the message.');
|
|
@@ -7790,10 +7807,22 @@ export async function startServer(options) {
|
|
|
7790
7807
|
if (resumeInfo) {
|
|
7791
7808
|
slackAdapter.removeChannelResume(routingKey);
|
|
7792
7809
|
}
|
|
7793
|
-
//
|
|
7794
|
-
//
|
|
7795
|
-
//
|
|
7796
|
-
|
|
7810
|
+
// Readiness gates actual spawn only. Existing live sessions keep
|
|
7811
|
+
// receiving inbound work even if a later filesystem drift is found;
|
|
7812
|
+
// their next reply reports the helper failure rather than dropping
|
|
7813
|
+
// the user's message before injection.
|
|
7814
|
+
const relayReadiness = localSlackRelayReadiness(config.stateDir);
|
|
7815
|
+
if (conversationId === null || !relayReadiness.ready) {
|
|
7816
|
+
const reason = conversationId === null
|
|
7817
|
+
? 'conversation identity unavailable'
|
|
7818
|
+
: (!relayReadiness.ready ? relayReadiness.reason : 'unknown readiness failure');
|
|
7819
|
+
console.error(`[slack→session] spawn refused: slack-relay-not-ready (${reason})`);
|
|
7820
|
+
return;
|
|
7821
|
+
}
|
|
7822
|
+
// Slack DMs are isolated 1:1 bound sessions too. A shared lifeline
|
|
7823
|
+
// intentionally has no single INSTAR_CONVERSATION_ID, so routing DMs
|
|
7824
|
+
// there would make a destination-free reply impossible to authorize.
|
|
7825
|
+
const targetSession = undefined;
|
|
7797
7826
|
// ── SpawnAdmission (ownership-gated-spawn §3.1) — the Slack inbound
|
|
7798
7827
|
// spawn callsite. Refusal floor: durable-queue custody where live;
|
|
7799
7828
|
// Slack has no telegram-topic notice surface, so the ladder records
|
|
@@ -8020,10 +8049,16 @@ export async function startServer(options) {
|
|
|
8020
8049
|
slackAdapter.onStallDetected = (channelId, sessionName, messageText, injectedAt) => {
|
|
8021
8050
|
const slackAttentionChannel = state.get('slack-attention-channel');
|
|
8022
8051
|
if (slackAttentionChannel) {
|
|
8023
|
-
slackAdapter.sendToChannel(slackAttentionChannel, `⚠️ Stall detected in session "${sessionName}" (channel ${channelId}). Message may not have been answered: "${messageText.slice(0, 100)}"`).catch(
|
|
8052
|
+
slackAdapter.sendToChannel(slackAttentionChannel, `⚠️ Stall detected in session "${sessionName}" (channel ${channelId}). Message may not have been answered: "${messageText.slice(0, 100)}"`).catch(err => {
|
|
8053
|
+
// @silent-fallback-ok best-effort advisory; failure is surfaced in server logs
|
|
8054
|
+
console.warn(`[slack] Failed to send stall notice to attention channel: ${err instanceof Error ? err.message : String(err)}`);
|
|
8055
|
+
});
|
|
8024
8056
|
}
|
|
8025
8057
|
// Also notify in the originating channel
|
|
8026
|
-
slackAdapter.sendToChannel(channelId, `⚠️ The session appears to have stalled. Use \`!restart\` to restart or \`!interrupt\` to nudge it.`).catch(
|
|
8058
|
+
slackAdapter.sendToChannel(channelId, `⚠️ The session appears to have stalled. Use \`!restart\` to restart or \`!interrupt\` to nudge it.`).catch(err => {
|
|
8059
|
+
// @silent-fallback-ok best-effort advisory; failure is surfaced in server logs
|
|
8060
|
+
console.warn(`[slack] Failed to send stall notice to originating channel: ${err instanceof Error ? err.message : String(err)}`);
|
|
8061
|
+
});
|
|
8027
8062
|
};
|
|
8028
8063
|
// Wire voice transcription (reuses Telegram's provider resolution: Groq → OpenAI)
|
|
8029
8064
|
slackAdapter.transcribeVoice = async (filePath) => {
|
|
@@ -10317,6 +10352,11 @@ export async function startServer(options) {
|
|
|
10317
10352
|
const parsedTarget = _slackAdapter.parseRoutingKey(slackChId);
|
|
10318
10353
|
const slackApiChannel = parsedTarget.channelId;
|
|
10319
10354
|
const slackReplyThread = parsedTarget.threadTs;
|
|
10355
|
+
const relayReadiness = localSlackRelayReadiness(config.stateDir);
|
|
10356
|
+
if (!relayReadiness.ready) {
|
|
10357
|
+
console.error(`[slack→recovery] spawn refused: slack-relay-not-ready (${relayReadiness.reason})`);
|
|
10358
|
+
return;
|
|
10359
|
+
}
|
|
10320
10360
|
// Kill existing session (already flagged in contextExhaustionKills via event listener)
|
|
10321
10361
|
const session = sessionManager.listRunningSessions().find(s => s.tmuxSession === _sessionName);
|
|
10322
10362
|
if (session)
|
|
@@ -10353,9 +10393,9 @@ export async function startServer(options) {
|
|
|
10353
10393
|
}
|
|
10354
10394
|
lines.push('');
|
|
10355
10395
|
lines.push('CRITICAL: You MUST relay your response back to Slack.');
|
|
10356
|
-
//
|
|
10357
|
-
|
|
10358
|
-
lines.push(
|
|
10396
|
+
// The recovered session receives a freshly minted bind token;
|
|
10397
|
+
// destination selection stays server-side and thread-exact.
|
|
10398
|
+
lines.push("cat <<'EOF' | .instar/scripts/slack-reply.sh");
|
|
10359
10399
|
lines.push('Your response text here');
|
|
10360
10400
|
lines.push('EOF');
|
|
10361
10401
|
const tmpDir = '/tmp/instar-slack';
|