@integrity-labs/agt-cli 0.27.25 → 0.27.27
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/bin/agt.js +3 -3
- package/dist/{chunk-GI73VOGA.js → chunk-ZYFZYWPV.js} +1 -1
- package/dist/lib/manager-worker.js +2 -2
- package/dist/mcp/slack-channel.js +297 -63
- package/dist/mcp/telegram-channel.js +6 -0
- package/package.json +1 -1
- /package/dist/{chunk-GI73VOGA.js.map → chunk-ZYFZYWPV.js.map} +0 -0
package/dist/bin/agt.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
success,
|
|
28
28
|
table,
|
|
29
29
|
warn
|
|
30
|
-
} from "../chunk-
|
|
30
|
+
} from "../chunk-ZYFZYWPV.js";
|
|
31
31
|
import {
|
|
32
32
|
CHANNEL_REGISTRY,
|
|
33
33
|
DEPLOYMENT_TEMPLATES,
|
|
@@ -4643,7 +4643,7 @@ import { execFileSync, execSync } from "child_process";
|
|
|
4643
4643
|
import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
|
|
4644
4644
|
import chalk18 from "chalk";
|
|
4645
4645
|
import ora16 from "ora";
|
|
4646
|
-
var cliVersion = true ? "0.27.
|
|
4646
|
+
var cliVersion = true ? "0.27.27" : "dev";
|
|
4647
4647
|
async function fetchLatestVersion() {
|
|
4648
4648
|
const host2 = getHost();
|
|
4649
4649
|
if (!host2) return null;
|
|
@@ -5175,7 +5175,7 @@ function handleError(err) {
|
|
|
5175
5175
|
}
|
|
5176
5176
|
|
|
5177
5177
|
// src/bin/agt.ts
|
|
5178
|
-
var cliVersion2 = true ? "0.27.
|
|
5178
|
+
var cliVersion2 = true ? "0.27.27" : "dev";
|
|
5179
5179
|
var program = new Command();
|
|
5180
5180
|
program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
|
|
5181
5181
|
program.hook("preAction", (thisCommand) => {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
provisionOrientHook,
|
|
16
16
|
provisionStopHook,
|
|
17
17
|
requireHost
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-ZYFZYWPV.js";
|
|
19
19
|
import {
|
|
20
20
|
getProjectDir as getProjectDir2,
|
|
21
21
|
getReadyTasks,
|
|
@@ -3208,7 +3208,7 @@ var cachedFrameworkVersion = null;
|
|
|
3208
3208
|
var lastVersionCheckAt = 0;
|
|
3209
3209
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
3210
3210
|
var lastResponsivenessProbeAt = 0;
|
|
3211
|
-
var agtCliVersion = true ? "0.27.
|
|
3211
|
+
var agtCliVersion = true ? "0.27.27" : "dev";
|
|
3212
3212
|
function resolveBrewPath(execFileSync4) {
|
|
3213
3213
|
try {
|
|
3214
3214
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -14261,11 +14261,62 @@ function decideSenderPolicyForward(evt, policy) {
|
|
|
14261
14261
|
return decideModeForward(evt, policy);
|
|
14262
14262
|
}
|
|
14263
14263
|
|
|
14264
|
+
// src/sender-policy-decline.ts
|
|
14265
|
+
function classifySlackPolicyBlock(evt, policy) {
|
|
14266
|
+
if (policy.internalOnly && (!policy.homeTeamId || evt.team !== policy.homeTeamId)) {
|
|
14267
|
+
return "internal_only";
|
|
14268
|
+
}
|
|
14269
|
+
switch (policy.mode) {
|
|
14270
|
+
case "manager_only":
|
|
14271
|
+
return "mode_manager_only";
|
|
14272
|
+
case "team_agents_only":
|
|
14273
|
+
return "mode_team_agents_only";
|
|
14274
|
+
case "agents_only":
|
|
14275
|
+
return "mode_agents_only";
|
|
14276
|
+
case "all":
|
|
14277
|
+
return "internal_only";
|
|
14278
|
+
}
|
|
14279
|
+
}
|
|
14280
|
+
function politeDeclineCopy(reason) {
|
|
14281
|
+
switch (reason) {
|
|
14282
|
+
case "internal_only":
|
|
14283
|
+
return "Sorry, I can only respond to people inside our organisation. This conversation appears to be from outside.";
|
|
14284
|
+
case "mode_manager_only":
|
|
14285
|
+
return "Sorry, I only respond to my manager in this channel.";
|
|
14286
|
+
case "mode_team_agents_only":
|
|
14287
|
+
return "Sorry, I only respond to agents on my team in this channel.";
|
|
14288
|
+
case "mode_agents_only":
|
|
14289
|
+
return "Sorry, I only respond to other agents in this channel.";
|
|
14290
|
+
}
|
|
14291
|
+
}
|
|
14292
|
+
function decideDeclineReply(input) {
|
|
14293
|
+
const last = input.cache.get(input.key);
|
|
14294
|
+
if (last !== void 0) {
|
|
14295
|
+
const elapsed = input.now - last;
|
|
14296
|
+
if (elapsed < input.cooldownMs) {
|
|
14297
|
+
return { reply: false, remainingMs: input.cooldownMs - elapsed };
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
14300
|
+
input.cache.set(input.key, input.now);
|
|
14301
|
+
return { reply: true };
|
|
14302
|
+
}
|
|
14303
|
+
function declineCacheKey(channelId, senderId, reason) {
|
|
14304
|
+
return `${channelId}|${senderId}|${reason}`;
|
|
14305
|
+
}
|
|
14306
|
+
function readDeclineCooldownMs(envVarName, defaultSeconds = 1800) {
|
|
14307
|
+
const raw = process.env[envVarName];
|
|
14308
|
+
if (!raw) return defaultSeconds * 1e3;
|
|
14309
|
+
const parsed = Number(raw);
|
|
14310
|
+
if (!Number.isFinite(parsed) || parsed < 0) return defaultSeconds * 1e3;
|
|
14311
|
+
return parsed * 1e3;
|
|
14312
|
+
}
|
|
14313
|
+
|
|
14264
14314
|
// src/ack-reaction.ts
|
|
14265
14315
|
import { readdirSync, readFileSync } from "fs";
|
|
14266
14316
|
import { join } from "path";
|
|
14267
14317
|
var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
14268
14318
|
var ACK_STARTUP_GRACE_MS = 6e4;
|
|
14319
|
+
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
14269
14320
|
function decideAckReaction(i) {
|
|
14270
14321
|
if (!i.hasTarget) return "none";
|
|
14271
14322
|
if (!i.integrationReady) return "undeliverable";
|
|
@@ -14273,6 +14324,11 @@ function decideAckReaction(i) {
|
|
|
14273
14324
|
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
14274
14325
|
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
14275
14326
|
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
14327
|
+
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
14328
|
+
const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
14329
|
+
if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
|
|
14330
|
+
return "ack";
|
|
14331
|
+
}
|
|
14276
14332
|
return "undeliverable";
|
|
14277
14333
|
}
|
|
14278
14334
|
return "ack";
|
|
@@ -14567,6 +14623,69 @@ var SLACK_EGRESS_TOOLS = /* @__PURE__ */ new Set([
|
|
|
14567
14623
|
"slack.upload_file"
|
|
14568
14624
|
]);
|
|
14569
14625
|
|
|
14626
|
+
// src/slack-pending-inbound-cleanup.ts
|
|
14627
|
+
import { existsSync, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
|
|
14628
|
+
import { join as join2 } from "path";
|
|
14629
|
+
function sanitizeMarkerSegment(value) {
|
|
14630
|
+
return value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14631
|
+
}
|
|
14632
|
+
var defaultClearMarkerFile = (fullPath) => {
|
|
14633
|
+
try {
|
|
14634
|
+
if (existsSync(fullPath)) unlinkSync(fullPath);
|
|
14635
|
+
} catch {
|
|
14636
|
+
}
|
|
14637
|
+
};
|
|
14638
|
+
function clearAllSlackPendingMarkersForThread(dir, channel, threadTs, clear = defaultClearMarkerFile) {
|
|
14639
|
+
if (!dir) return 0;
|
|
14640
|
+
const prefix = `${sanitizeMarkerSegment(channel)}__${sanitizeMarkerSegment(threadTs)}__`;
|
|
14641
|
+
let cleared = 0;
|
|
14642
|
+
try {
|
|
14643
|
+
for (const f of readdirSync2(dir)) {
|
|
14644
|
+
if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
|
|
14645
|
+
clear(join2(dir, f));
|
|
14646
|
+
cleared += 1;
|
|
14647
|
+
}
|
|
14648
|
+
} catch {
|
|
14649
|
+
}
|
|
14650
|
+
return cleared;
|
|
14651
|
+
}
|
|
14652
|
+
function clearSlackPendingMarkerByMessageTs(dir, channel, messageTs, clear = defaultClearMarkerFile) {
|
|
14653
|
+
if (!dir) return 0;
|
|
14654
|
+
const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
|
|
14655
|
+
const messageSuffix = `__${sanitizeMarkerSegment(messageTs)}.json`;
|
|
14656
|
+
let cleared = 0;
|
|
14657
|
+
try {
|
|
14658
|
+
for (const f of readdirSync2(dir)) {
|
|
14659
|
+
if (!f.startsWith(channelPrefix) || !f.endsWith(messageSuffix)) continue;
|
|
14660
|
+
clear(join2(dir, f));
|
|
14661
|
+
cleared += 1;
|
|
14662
|
+
}
|
|
14663
|
+
} catch {
|
|
14664
|
+
}
|
|
14665
|
+
return cleared;
|
|
14666
|
+
}
|
|
14667
|
+
function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultClearMarkerFile) {
|
|
14668
|
+
if (!dir) return null;
|
|
14669
|
+
const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
|
|
14670
|
+
try {
|
|
14671
|
+
const entries = readdirSync2(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
|
|
14672
|
+
const full = join2(dir, f);
|
|
14673
|
+
let mtime = 0;
|
|
14674
|
+
try {
|
|
14675
|
+
mtime = statSync(full).mtimeMs;
|
|
14676
|
+
} catch {
|
|
14677
|
+
}
|
|
14678
|
+
return { name: f, full, mtime };
|
|
14679
|
+
}).filter((e) => e.mtime > 0).sort((a, b) => a.mtime - b.mtime);
|
|
14680
|
+
const oldest = entries[0];
|
|
14681
|
+
if (!oldest) return null;
|
|
14682
|
+
clear(oldest.full);
|
|
14683
|
+
return oldest.name;
|
|
14684
|
+
} catch {
|
|
14685
|
+
return null;
|
|
14686
|
+
}
|
|
14687
|
+
}
|
|
14688
|
+
|
|
14570
14689
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
14571
14690
|
import process2 from "process";
|
|
14572
14691
|
|
|
@@ -14663,17 +14782,17 @@ var StdioServerTransport = class {
|
|
|
14663
14782
|
import {
|
|
14664
14783
|
chmodSync,
|
|
14665
14784
|
createWriteStream,
|
|
14666
|
-
existsSync as
|
|
14785
|
+
existsSync as existsSync3,
|
|
14667
14786
|
mkdirSync as mkdirSync3,
|
|
14668
14787
|
readFileSync as readFileSync4,
|
|
14669
|
-
readdirSync as
|
|
14788
|
+
readdirSync as readdirSync3,
|
|
14670
14789
|
renameSync as renameSync2,
|
|
14671
|
-
statSync,
|
|
14672
|
-
unlinkSync as
|
|
14790
|
+
statSync as statSync2,
|
|
14791
|
+
unlinkSync as unlinkSync3,
|
|
14673
14792
|
watch,
|
|
14674
14793
|
writeFileSync as writeFileSync3
|
|
14675
14794
|
} from "fs";
|
|
14676
|
-
import { basename, join as
|
|
14795
|
+
import { basename, join as join5, resolve as resolve2 } from "path";
|
|
14677
14796
|
import { homedir as homedir2 } from "os";
|
|
14678
14797
|
import { createHash, randomUUID } from "crypto";
|
|
14679
14798
|
|
|
@@ -14804,9 +14923,9 @@ async function runOrRetry(fn, opts) {
|
|
|
14804
14923
|
|
|
14805
14924
|
// src/channel-attachments.ts
|
|
14806
14925
|
import { homedir } from "os";
|
|
14807
|
-
import { join as
|
|
14926
|
+
import { join as join3, resolve, sep } from "path";
|
|
14808
14927
|
function resolveChannelInboundDir(codeName, channelSlug) {
|
|
14809
|
-
const base =
|
|
14928
|
+
const base = join3(homedir(), ".augmented");
|
|
14810
14929
|
const allowedSegment = /^[A-Za-z0-9_-]+$/;
|
|
14811
14930
|
if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
|
|
14812
14931
|
throw new Error(
|
|
@@ -15450,14 +15569,14 @@ function createSlackBotUserIdClient(args) {
|
|
|
15450
15569
|
|
|
15451
15570
|
// src/mcp-spawn-lock.ts
|
|
15452
15571
|
import {
|
|
15453
|
-
existsSync,
|
|
15572
|
+
existsSync as existsSync2,
|
|
15454
15573
|
mkdirSync as mkdirSync2,
|
|
15455
15574
|
readFileSync as readFileSync3,
|
|
15456
15575
|
renameSync,
|
|
15457
|
-
unlinkSync,
|
|
15576
|
+
unlinkSync as unlinkSync2,
|
|
15458
15577
|
writeFileSync as writeFileSync2
|
|
15459
15578
|
} from "fs";
|
|
15460
|
-
import { join as
|
|
15579
|
+
import { join as join4 } from "path";
|
|
15461
15580
|
function defaultIsPidAlive(pid) {
|
|
15462
15581
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
15463
15582
|
try {
|
|
@@ -15475,7 +15594,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
15475
15594
|
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
15476
15595
|
const selfPid = options.selfPid ?? process.pid;
|
|
15477
15596
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
15478
|
-
const path =
|
|
15597
|
+
const path = join4(agentDir, basename2);
|
|
15479
15598
|
const existing = readLockHolder(path);
|
|
15480
15599
|
if (existing) {
|
|
15481
15600
|
if (existing.pid === selfPid) {
|
|
@@ -15499,12 +15618,12 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
|
|
|
15499
15618
|
if (!existing) return;
|
|
15500
15619
|
if (existing.pid !== selfPid) return;
|
|
15501
15620
|
try {
|
|
15502
|
-
|
|
15621
|
+
unlinkSync2(lockPath);
|
|
15503
15622
|
} catch {
|
|
15504
15623
|
}
|
|
15505
15624
|
}
|
|
15506
15625
|
function readLockHolder(path) {
|
|
15507
|
-
if (!
|
|
15626
|
+
if (!existsSync2(path)) return null;
|
|
15508
15627
|
try {
|
|
15509
15628
|
const raw = readFileSync3(path, "utf8");
|
|
15510
15629
|
const parsed = JSON.parse(raw);
|
|
@@ -15557,6 +15676,58 @@ var SLACK_SENDER_POLICY = (() => {
|
|
|
15557
15676
|
}
|
|
15558
15677
|
throw new Error(`Invalid SLACK_SENDER_POLICY=${JSON.stringify(process.env.SLACK_SENDER_POLICY)}`);
|
|
15559
15678
|
})();
|
|
15679
|
+
var SLACK_POLICY_DECLINE_CACHE = /* @__PURE__ */ new Map();
|
|
15680
|
+
var SLACK_POLICY_DECLINE_COOLDOWN_MS = readDeclineCooldownMs(
|
|
15681
|
+
"SLACK_SENDER_POLICY_REPLY_COOLDOWN_SEC"
|
|
15682
|
+
);
|
|
15683
|
+
async function maybeSendSenderPolicyDecline(args) {
|
|
15684
|
+
if (!BOT_TOKEN) return;
|
|
15685
|
+
if (!args.channel || !args.senderId) return;
|
|
15686
|
+
const key2 = declineCacheKey(args.channel, args.senderId, args.subReason);
|
|
15687
|
+
const decision = decideDeclineReply({
|
|
15688
|
+
cache: SLACK_POLICY_DECLINE_CACHE,
|
|
15689
|
+
key: key2,
|
|
15690
|
+
cooldownMs: SLACK_POLICY_DECLINE_COOLDOWN_MS,
|
|
15691
|
+
now: Date.now()
|
|
15692
|
+
});
|
|
15693
|
+
if (!decision.reply) {
|
|
15694
|
+
process.stderr.write(
|
|
15695
|
+
`slack-channel(${AGENT_CODE_NAME}): decline suppressed by cooldown (channel=${redactSlackId(args.channel)}, sender=${redactSlackId(args.senderId)}, reason=${args.subReason}, remaining=${decision.remainingMs}ms)
|
|
15696
|
+
`
|
|
15697
|
+
);
|
|
15698
|
+
return;
|
|
15699
|
+
}
|
|
15700
|
+
const text = politeDeclineCopy(args.subReason);
|
|
15701
|
+
const controller = new AbortController();
|
|
15702
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
15703
|
+
try {
|
|
15704
|
+
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
15705
|
+
method: "POST",
|
|
15706
|
+
signal: controller.signal,
|
|
15707
|
+
headers: {
|
|
15708
|
+
"Content-Type": "application/json",
|
|
15709
|
+
Authorization: `Bearer ${BOT_TOKEN}`
|
|
15710
|
+
},
|
|
15711
|
+
body: JSON.stringify({
|
|
15712
|
+
channel: args.channel,
|
|
15713
|
+
text,
|
|
15714
|
+
// Reply in-thread if the inbound was a thread message — keeps
|
|
15715
|
+
// the decline next to the message that caused it. For top-level
|
|
15716
|
+
// posts (DMs / channel root) omit thread_ts and post inline.
|
|
15717
|
+
...args.threadTs ? { thread_ts: args.threadTs } : {}
|
|
15718
|
+
})
|
|
15719
|
+
});
|
|
15720
|
+
const data = await res.json();
|
|
15721
|
+
if (!data.ok) {
|
|
15722
|
+
process.stderr.write(
|
|
15723
|
+
`slack-channel(${AGENT_CODE_NAME}): decline post failed: ${data.error ?? "unknown"}
|
|
15724
|
+
`
|
|
15725
|
+
);
|
|
15726
|
+
}
|
|
15727
|
+
} finally {
|
|
15728
|
+
clearTimeout(timeoutId);
|
|
15729
|
+
}
|
|
15730
|
+
}
|
|
15560
15731
|
var BLOCK_KIT_ENABLED = process.env.SLACK_BLOCK_KIT_ENABLED === "true";
|
|
15561
15732
|
var BLOCK_KIT_ASK_USER_ENABLED = process.env.SLACK_BLOCK_KIT_ASK_USER_ENABLED === "true";
|
|
15562
15733
|
var BLOCK_KIT_DISABLED = process.env.SLACK_BLOCK_KIT_DISABLED === "true";
|
|
@@ -15608,9 +15779,9 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
|
|
|
15608
15779
|
peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
|
|
15609
15780
|
peer_disabled_mode: SLACK_PEER_DISABLED_MODE
|
|
15610
15781
|
};
|
|
15611
|
-
var SLACK_AGENT_DIR = AGENT_CODE_NAME ?
|
|
15612
|
-
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ?
|
|
15613
|
-
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ?
|
|
15782
|
+
var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join5(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15783
|
+
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join5(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
|
|
15784
|
+
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join5(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
|
|
15614
15785
|
var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
|
|
15615
15786
|
function redactSlackId(id) {
|
|
15616
15787
|
if (!id) return "<none>";
|
|
@@ -15622,7 +15793,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
|
|
|
15622
15793
|
}
|
|
15623
15794
|
function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
15624
15795
|
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
15625
|
-
return
|
|
15796
|
+
return join5(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
15626
15797
|
}
|
|
15627
15798
|
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false) {
|
|
15628
15799
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
@@ -15678,22 +15849,32 @@ function clearSlackMarkerFileWithHeal(fullPath) {
|
|
|
15678
15849
|
healSlackUndeliverable(marker.channel, marker.message_ts);
|
|
15679
15850
|
}
|
|
15680
15851
|
try {
|
|
15681
|
-
if (
|
|
15852
|
+
if (existsSync3(fullPath)) unlinkSync3(fullPath);
|
|
15682
15853
|
} catch {
|
|
15683
15854
|
}
|
|
15684
15855
|
}
|
|
15685
|
-
function
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15856
|
+
function clearAllSlackPendingMarkersForThread2(channel, threadTs) {
|
|
15857
|
+
clearAllSlackPendingMarkersForThread(
|
|
15858
|
+
SLACK_PENDING_INBOUND_DIR,
|
|
15859
|
+
channel,
|
|
15860
|
+
threadTs,
|
|
15861
|
+
clearSlackMarkerFileWithHeal
|
|
15862
|
+
);
|
|
15863
|
+
}
|
|
15864
|
+
function clearSlackPendingMarkerByMessageTs2(channel, messageTs) {
|
|
15865
|
+
clearSlackPendingMarkerByMessageTs(
|
|
15866
|
+
SLACK_PENDING_INBOUND_DIR,
|
|
15867
|
+
channel,
|
|
15868
|
+
messageTs,
|
|
15869
|
+
clearSlackMarkerFileWithHeal
|
|
15870
|
+
);
|
|
15871
|
+
}
|
|
15872
|
+
function clearOldestSlackPendingMarkerInChannel2(channel) {
|
|
15873
|
+
clearOldestSlackPendingMarkerInChannel(
|
|
15874
|
+
SLACK_PENDING_INBOUND_DIR,
|
|
15875
|
+
channel,
|
|
15876
|
+
clearSlackMarkerFileWithHeal
|
|
15877
|
+
);
|
|
15697
15878
|
}
|
|
15698
15879
|
function slackNextRetryName(filename) {
|
|
15699
15880
|
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
@@ -15709,7 +15890,7 @@ function slackNextRetryName(filename) {
|
|
|
15709
15890
|
async function processSlackRecoveryOutboxFile(filename) {
|
|
15710
15891
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
15711
15892
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
15712
|
-
const fullPath =
|
|
15893
|
+
const fullPath = join5(SLACK_RECOVERY_OUTBOX_DIR, filename);
|
|
15713
15894
|
let payload;
|
|
15714
15895
|
try {
|
|
15715
15896
|
payload = JSON.parse(readFileSync4(fullPath, "utf-8"));
|
|
@@ -15778,7 +15959,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
15778
15959
|
}
|
|
15779
15960
|
if (sendSucceeded) {
|
|
15780
15961
|
try {
|
|
15781
|
-
|
|
15962
|
+
unlinkSync3(fullPath);
|
|
15782
15963
|
} catch {
|
|
15783
15964
|
}
|
|
15784
15965
|
return;
|
|
@@ -15786,7 +15967,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
15786
15967
|
const next = slackNextRetryName(filename);
|
|
15787
15968
|
if (next) {
|
|
15788
15969
|
try {
|
|
15789
|
-
renameSync2(fullPath,
|
|
15970
|
+
renameSync2(fullPath, join5(SLACK_RECOVERY_OUTBOX_DIR, next.next));
|
|
15790
15971
|
if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
|
|
15791
15972
|
process.stderr.write(
|
|
15792
15973
|
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -15816,7 +15997,7 @@ function scanSlackRecoveryRetries() {
|
|
|
15816
15997
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
15817
15998
|
let entries;
|
|
15818
15999
|
try {
|
|
15819
|
-
entries =
|
|
16000
|
+
entries = readdirSync3(SLACK_RECOVERY_OUTBOX_DIR);
|
|
15820
16001
|
} catch {
|
|
15821
16002
|
return;
|
|
15822
16003
|
}
|
|
@@ -15825,7 +16006,7 @@ function scanSlackRecoveryRetries() {
|
|
|
15825
16006
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
15826
16007
|
let mtimeMs;
|
|
15827
16008
|
try {
|
|
15828
|
-
mtimeMs =
|
|
16009
|
+
mtimeMs = statSync2(join5(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
15829
16010
|
} catch {
|
|
15830
16011
|
continue;
|
|
15831
16012
|
}
|
|
@@ -15846,7 +16027,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
15846
16027
|
return;
|
|
15847
16028
|
}
|
|
15848
16029
|
try {
|
|
15849
|
-
for (const f of
|
|
16030
|
+
for (const f of readdirSync3(SLACK_RECOVERY_OUTBOX_DIR)) {
|
|
15850
16031
|
if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
|
|
15851
16032
|
}
|
|
15852
16033
|
} catch {
|
|
@@ -15855,7 +16036,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
15855
16036
|
const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
15856
16037
|
if (event !== "rename" || !filename) return;
|
|
15857
16038
|
if (!isFirstAttemptSlackOutboxFile(filename)) return;
|
|
15858
|
-
if (
|
|
16039
|
+
if (existsSync3(join5(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
|
|
15859
16040
|
void processSlackRecoveryOutboxFile(filename);
|
|
15860
16041
|
}
|
|
15861
16042
|
});
|
|
@@ -15876,10 +16057,10 @@ function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false
|
|
|
15876
16057
|
}
|
|
15877
16058
|
function sweepSlackStaleMarkersOnBoot() {
|
|
15878
16059
|
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
15879
|
-
if (!
|
|
16060
|
+
if (!existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
|
|
15880
16061
|
let filenames;
|
|
15881
16062
|
try {
|
|
15882
|
-
filenames =
|
|
16063
|
+
filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
|
|
15883
16064
|
} catch (err) {
|
|
15884
16065
|
process.stderr.write(
|
|
15885
16066
|
`slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -15892,7 +16073,7 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
15892
16073
|
for (const filename of filenames) {
|
|
15893
16074
|
if (!filename.endsWith(".json")) continue;
|
|
15894
16075
|
if (filename.endsWith(".tmp")) continue;
|
|
15895
|
-
const fullPath =
|
|
16076
|
+
const fullPath = join5(SLACK_PENDING_INBOUND_DIR, filename);
|
|
15896
16077
|
let marker;
|
|
15897
16078
|
try {
|
|
15898
16079
|
marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
|
|
@@ -15902,7 +16083,7 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
15902
16083
|
`
|
|
15903
16084
|
);
|
|
15904
16085
|
try {
|
|
15905
|
-
|
|
16086
|
+
unlinkSync3(fullPath);
|
|
15906
16087
|
} catch {
|
|
15907
16088
|
}
|
|
15908
16089
|
cleared++;
|
|
@@ -15911,7 +16092,7 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
15911
16092
|
const { channel, thread_ts, message_ts, received_at } = marker;
|
|
15912
16093
|
if (!channel || !thread_ts || !message_ts || !received_at) {
|
|
15913
16094
|
try {
|
|
15914
|
-
|
|
16095
|
+
unlinkSync3(fullPath);
|
|
15915
16096
|
} catch {
|
|
15916
16097
|
}
|
|
15917
16098
|
cleared++;
|
|
@@ -15920,7 +16101,7 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
15920
16101
|
const receivedAtMs = Date.parse(received_at);
|
|
15921
16102
|
if (!Number.isFinite(receivedAtMs) || receivedAtMs > now || now - receivedAtMs > STALE_MARKER_MS) {
|
|
15922
16103
|
try {
|
|
15923
|
-
|
|
16104
|
+
unlinkSync3(fullPath);
|
|
15924
16105
|
} catch {
|
|
15925
16106
|
}
|
|
15926
16107
|
cleared++;
|
|
@@ -15935,7 +16116,7 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
15935
16116
|
}
|
|
15936
16117
|
sweepSlackStaleMarkersOnBoot();
|
|
15937
16118
|
function clearPendingMessage(channel, threadTs) {
|
|
15938
|
-
|
|
16119
|
+
clearAllSlackPendingMarkersForThread2(channel, threadTs);
|
|
15939
16120
|
}
|
|
15940
16121
|
function noteThreadActivity(channel, threadTs) {
|
|
15941
16122
|
if (!channel || !threadTs) return;
|
|
@@ -15945,10 +16126,10 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
15945
16126
|
if (!channel || !messageTs) return;
|
|
15946
16127
|
clearPendingMessage(channel, messageTs);
|
|
15947
16128
|
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
15948
|
-
if (!
|
|
16129
|
+
if (!existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
|
|
15949
16130
|
let filenames;
|
|
15950
16131
|
try {
|
|
15951
|
-
filenames =
|
|
16132
|
+
filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
|
|
15952
16133
|
} catch {
|
|
15953
16134
|
return;
|
|
15954
16135
|
}
|
|
@@ -15959,10 +16140,10 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
15959
16140
|
for (const filename of filenames) {
|
|
15960
16141
|
if (!filename.startsWith(channelPrefix)) continue;
|
|
15961
16142
|
if (!filename.endsWith(messageSuffix)) continue;
|
|
15962
|
-
clearSlackMarkerFileWithHeal(
|
|
16143
|
+
clearSlackMarkerFileWithHeal(join5(SLACK_PENDING_INBOUND_DIR, filename));
|
|
15963
16144
|
}
|
|
15964
16145
|
}
|
|
15965
|
-
var RESTART_FLAGS_DIR =
|
|
16146
|
+
var RESTART_FLAGS_DIR = join5(homedir2(), ".augmented", "restart-flags");
|
|
15966
16147
|
function buildAugmentedSlackMetadata() {
|
|
15967
16148
|
if (!AGT_TEAM_ID) return void 0;
|
|
15968
16149
|
return {
|
|
@@ -16141,10 +16322,10 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16141
16322
|
return;
|
|
16142
16323
|
}
|
|
16143
16324
|
try {
|
|
16144
|
-
if (!
|
|
16325
|
+
if (!existsSync3(RESTART_FLAGS_DIR)) {
|
|
16145
16326
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16146
16327
|
}
|
|
16147
|
-
const flagPath =
|
|
16328
|
+
const flagPath = join5(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
16148
16329
|
const flag = {
|
|
16149
16330
|
codeName,
|
|
16150
16331
|
source: "slack",
|
|
@@ -16248,10 +16429,10 @@ async function handleHelpCommand(opts) {
|
|
|
16248
16429
|
async function handleRestartCommand(opts) {
|
|
16249
16430
|
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
16250
16431
|
try {
|
|
16251
|
-
if (!
|
|
16432
|
+
if (!existsSync3(RESTART_FLAGS_DIR)) {
|
|
16252
16433
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16253
16434
|
}
|
|
16254
|
-
const flagPath =
|
|
16435
|
+
const flagPath = join5(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
16255
16436
|
const flag = {
|
|
16256
16437
|
codeName,
|
|
16257
16438
|
source: "slack",
|
|
@@ -16310,7 +16491,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
|
|
|
16310
16491
|
var threadPersister = null;
|
|
16311
16492
|
function resolveThreadStorePath() {
|
|
16312
16493
|
if (!AGENT_CODE_NAME) return null;
|
|
16313
|
-
return
|
|
16494
|
+
return join5(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
|
|
16314
16495
|
}
|
|
16315
16496
|
function parseTtlDays(raw) {
|
|
16316
16497
|
if (!raw) return void 0;
|
|
@@ -16345,9 +16526,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
|
|
|
16345
16526
|
var slackStderrLogStream = null;
|
|
16346
16527
|
if (AGENT_CODE_NAME) {
|
|
16347
16528
|
try {
|
|
16348
|
-
const logDir =
|
|
16529
|
+
const logDir = join5(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
16349
16530
|
mkdirSync3(logDir, { recursive: true });
|
|
16350
|
-
slackStderrLogStream = createWriteStream(
|
|
16531
|
+
slackStderrLogStream = createWriteStream(join5(logDir, "slack-channel-stderr.log"), {
|
|
16351
16532
|
flags: "a",
|
|
16352
16533
|
mode: 384
|
|
16353
16534
|
});
|
|
@@ -16431,7 +16612,7 @@ var mcp = new Server(
|
|
|
16431
16612
|
// Highest-priority lines first — Claude Code truncates this string at
|
|
16432
16613
|
// 2048 chars, so anything appended late silently disappears.
|
|
16433
16614
|
"CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply. Text in your session WITHOUT a slack.reply call never reaches the user \u2014 the message dies inside the agent process.",
|
|
16434
|
-
`Inbound: <channel ... thread_ts="..." [thread_context="..."]>. Pass channel +
|
|
16615
|
+
`Inbound: <channel ... thread_ts="..." message_ts="..." [thread_context="..."]>. Pass channel + message_ts to slack.reply (and thread_ts on threads). thread_context = thread pre-loaded; ground replies ONLY in it, never another channel's.`,
|
|
16435
16616
|
"Inbound attachments: <channel> `files` is a JSON-serialised array \u2014 JSON.parse it. If an entry has `path`, the image is already downloaded \u2014 Read it directly, do NOT call slack.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, csv): pass file_id + channel verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Don't surface internal file-handling errors that don't affect the answer.",
|
|
16436
16617
|
"Address users by user_name, never by raw user ID. In multi-participant threads the CURRENT speaker is the one on the latest <channel> tag.",
|
|
16437
16618
|
'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if useful, OR if your own bot user is @-mentioned (counts even in auto_followed).',
|
|
@@ -16456,7 +16637,18 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16456
16637
|
text: { type: "string", description: "The message to send" },
|
|
16457
16638
|
thread_ts: {
|
|
16458
16639
|
type: "string",
|
|
16459
|
-
description: "Thread timestamp for threaded replies (from the thread_ts attribute)"
|
|
16640
|
+
description: "Thread timestamp for threaded replies (from the thread_ts attribute). Omit for a top-level reply (e.g. a fresh DM)."
|
|
16641
|
+
},
|
|
16642
|
+
// ENG-5861: explicit message_ts so the cleanup gate can match the
|
|
16643
|
+
// exact pending-inbound marker — necessary because top-level DM
|
|
16644
|
+
// replies legitimately have no thread_ts and the marker filename
|
|
16645
|
+
// is `<channel>__<thread_ts>__<message_ts>.json`. Without this
|
|
16646
|
+
// the marker sat undrained, eventually tripping ENG-5832\'s
|
|
16647
|
+
// "wedged" threshold and firing a false-positive red ✗ on the
|
|
16648
|
+
// next inbound.
|
|
16649
|
+
message_ts: {
|
|
16650
|
+
type: "string",
|
|
16651
|
+
description: "The message_ts of the specific inbound this reply addresses (from the message_ts attribute on the <channel> tag). Used by the pending-inbound cleanup gate so the marker is reliably cleared even for top-level DMs that have no thread_ts."
|
|
16460
16652
|
}
|
|
16461
16653
|
},
|
|
16462
16654
|
required: ["channel", "text"]
|
|
@@ -16694,7 +16886,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16694
16886
|
return buildImpersonationRefusal(name);
|
|
16695
16887
|
}
|
|
16696
16888
|
if (name === "slack.reply") {
|
|
16697
|
-
const { channel, text, thread_ts } = args;
|
|
16889
|
+
const { channel, text, thread_ts, message_ts } = args;
|
|
16698
16890
|
if (channel && thread_ts) {
|
|
16699
16891
|
const killed = await isThreadKilled({
|
|
16700
16892
|
channelType: "slack",
|
|
@@ -16753,8 +16945,15 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16753
16945
|
isError: true
|
|
16754
16946
|
};
|
|
16755
16947
|
}
|
|
16756
|
-
if (channel
|
|
16757
|
-
|
|
16948
|
+
if (channel) {
|
|
16949
|
+
if (message_ts) {
|
|
16950
|
+
clearSlackPendingMarkerByMessageTs2(channel, message_ts);
|
|
16951
|
+
if (thread_ts) clearPendingMessage(channel, thread_ts);
|
|
16952
|
+
} else if (thread_ts) {
|
|
16953
|
+
clearPendingMessage(channel, thread_ts);
|
|
16954
|
+
} else {
|
|
16955
|
+
clearOldestSlackPendingMarkerInChannel2(channel);
|
|
16956
|
+
}
|
|
16758
16957
|
}
|
|
16759
16958
|
try {
|
|
16760
16959
|
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
@@ -16906,7 +17105,7 @@ ${result.formatted}` : "Thread is empty or not found."
|
|
|
16906
17105
|
let bytes;
|
|
16907
17106
|
let size;
|
|
16908
17107
|
try {
|
|
16909
|
-
const stat =
|
|
17108
|
+
const stat = statSync2(resolvedPath);
|
|
16910
17109
|
if (!stat.isFile()) {
|
|
16911
17110
|
return {
|
|
16912
17111
|
content: [{ type: "text", text: `Upload refused: ${resolvedPath} is not a regular file.` }],
|
|
@@ -17807,8 +18006,27 @@ async function connectSocketMode() {
|
|
|
17807
18006
|
}
|
|
17808
18007
|
const senderPolicyDecision = decideSenderPolicyForward(evt, SLACK_SENDER_POLICY);
|
|
17809
18008
|
if (!senderPolicyDecision.forward) {
|
|
17810
|
-
|
|
18009
|
+
const subReason = classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
|
|
18010
|
+
process.stderr.write(`slack-channel: dropped message event (reason=sender_policy, sub=${subReason}, mode=${SLACK_SENDER_POLICY.mode}, ts=${evt.ts ?? "n/a"})
|
|
17811
18011
|
`);
|
|
18012
|
+
await maybeSendSenderPolicyDecline({
|
|
18013
|
+
channel: evt.channel,
|
|
18014
|
+
senderId: evt.user,
|
|
18015
|
+
// CR on PR #1623: top-level posts (DMs, channel root messages)
|
|
18016
|
+
// arrive with no `thread_ts` and MUST decline inline, not in a
|
|
18017
|
+
// new thread off the inbound message. The `?? evt.ts` fallback
|
|
18018
|
+
// forced every root message into a thread reply — exactly the
|
|
18019
|
+
// case the helper's "omit thread_ts when undefined" branch was
|
|
18020
|
+
// meant to handle. Pass through as-is: in-thread for thread
|
|
18021
|
+
// replies (thread_ts present), inline for everything else.
|
|
18022
|
+
threadTs: evt.thread_ts,
|
|
18023
|
+
subReason
|
|
18024
|
+
}).catch((err) => {
|
|
18025
|
+
process.stderr.write(
|
|
18026
|
+
`slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
|
|
18027
|
+
`
|
|
18028
|
+
);
|
|
18029
|
+
});
|
|
17812
18030
|
return;
|
|
17813
18031
|
}
|
|
17814
18032
|
recordActivity("inbound");
|
|
@@ -17935,13 +18153,22 @@ async function connectSocketMode() {
|
|
|
17935
18153
|
botUserId
|
|
17936
18154
|
});
|
|
17937
18155
|
const ackProbe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
18156
|
+
let paneLogFreshAgeMs = null;
|
|
18157
|
+
if (SLACK_AGENT_DIR) {
|
|
18158
|
+
try {
|
|
18159
|
+
const paneMtimeMs = statSync2(join5(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
|
|
18160
|
+
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
18161
|
+
} catch {
|
|
18162
|
+
}
|
|
18163
|
+
}
|
|
17938
18164
|
const ackDecision = decideAckReaction({
|
|
17939
18165
|
hasTarget: decideSlackAckReaction({ channel, ts }),
|
|
17940
18166
|
integrationReady: Boolean(BOT_TOKEN),
|
|
17941
18167
|
tmux: ackProbe.tmux,
|
|
17942
18168
|
claude: ackProbe.claude,
|
|
17943
18169
|
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
17944
|
-
oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR)
|
|
18170
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR),
|
|
18171
|
+
paneLogFreshAgeMs
|
|
17945
18172
|
});
|
|
17946
18173
|
if (ackDecision !== "none") {
|
|
17947
18174
|
const reactionName = ackDecision === "undeliverable" ? "x" : "eyes";
|
|
@@ -17996,6 +18223,13 @@ async function connectSocketMode() {
|
|
|
17996
18223
|
user_name: userName,
|
|
17997
18224
|
channel,
|
|
17998
18225
|
thread_ts: threadTs,
|
|
18226
|
+
// ENG-5861: explicit message_ts so the agent can pass it back
|
|
18227
|
+
// to slack.reply for reliable pending-inbound cleanup. For
|
|
18228
|
+
// top-level messages threadTs === ts; for threaded replies
|
|
18229
|
+
// threadTs is the thread root and message_ts is the specific
|
|
18230
|
+
// reply being addressed — different keys, both needed by the
|
|
18231
|
+
// marker-cleanup gate.
|
|
18232
|
+
message_ts: ts,
|
|
17999
18233
|
event_type: evt.type,
|
|
18000
18234
|
...isAutoFollowed ? { auto_followed: "true" } : {},
|
|
18001
18235
|
// Only set these when we actually have attachments to avoid
|
|
@@ -15506,6 +15506,7 @@ import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
|
15506
15506
|
import { join as join3 } from "path";
|
|
15507
15507
|
var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
15508
15508
|
var ACK_STARTUP_GRACE_MS = 6e4;
|
|
15509
|
+
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
15509
15510
|
function decideAckReaction(i) {
|
|
15510
15511
|
if (!i.hasTarget) return "none";
|
|
15511
15512
|
if (!i.integrationReady) return "undeliverable";
|
|
@@ -15513,6 +15514,11 @@ function decideAckReaction(i) {
|
|
|
15513
15514
|
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
15514
15515
|
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
15515
15516
|
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
15517
|
+
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
15518
|
+
const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
15519
|
+
if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
|
|
15520
|
+
return "ack";
|
|
15521
|
+
}
|
|
15516
15522
|
return "undeliverable";
|
|
15517
15523
|
}
|
|
15518
15524
|
return "ack";
|
package/package.json
CHANGED
|
File without changes
|