@integrity-labs/agt-cli 0.27.13 → 0.27.15
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 +31 -12
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-GN4XPQWJ.js → chunk-F4NG4EXD.js} +37 -28
- package/dist/chunk-F4NG4EXD.js.map +1 -0
- package/dist/{chunk-YSBGIXJG.js → chunk-HT6EETEL.js} +1 -1
- package/dist/{chunk-Q4MWFZ5Y.js → chunk-LJEV2QHN.js} +9 -6
- package/dist/{chunk-Q4MWFZ5Y.js.map → chunk-LJEV2QHN.js.map} +1 -1
- package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-OBAJZDXK.js} +2 -2
- package/dist/lib/manager-worker.js +7 -7
- package/dist/mcp/slack-channel.js +245 -82
- package/dist/mcp/telegram-channel.js +174 -27
- package/dist/{persistent-session-ICYFLUAM.js → persistent-session-SBSOZG74.js} +3 -3
- package/dist/{responsiveness-probe-WZNQ2762.js → responsiveness-probe-DU4IJ2RZ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-GN4XPQWJ.js.map +0 -1
- /package/dist/{chunk-YSBGIXJG.js.map → chunk-HT6EETEL.js.map} +0 -0
- /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-OBAJZDXK.js.map} +0 -0
- /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-SBSOZG74.js.map} +0 -0
- /package/dist/{responsiveness-probe-WZNQ2762.js.map → responsiveness-probe-DU4IJ2RZ.js.map} +0 -0
|
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
|
|
|
100
100
|
return { ok: true };
|
|
101
101
|
} catch {
|
|
102
102
|
}
|
|
103
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
103
|
+
const { resolveClaudeBinary } = await import("./persistent-session-SBSOZG74.js");
|
|
104
104
|
const claudeBin = resolveClaudeBinary();
|
|
105
105
|
const pairEnv = {
|
|
106
106
|
...process.env,
|
|
@@ -373,4 +373,4 @@ export {
|
|
|
373
373
|
startClaudePair,
|
|
374
374
|
submitClaudePairCode
|
|
375
375
|
};
|
|
376
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
376
|
+
//# sourceMappingURL=claude-pair-runtime-OBAJZDXK.js.map
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
provisionOrientHook,
|
|
16
16
|
provisionStopHook,
|
|
17
17
|
requireHost
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-LJEV2QHN.js";
|
|
19
19
|
import {
|
|
20
20
|
getProjectDir as getProjectDir2,
|
|
21
21
|
getReadyTasks,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
stopAllSessionsAndWait,
|
|
47
47
|
stopPersistentSession,
|
|
48
48
|
takeZombieDetection
|
|
49
|
-
} from "../chunk-
|
|
49
|
+
} from "../chunk-F4NG4EXD.js";
|
|
50
50
|
import {
|
|
51
51
|
KANBAN_CHECK_COMMAND,
|
|
52
52
|
appendDmFooter,
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
resolveConnectivityProbe,
|
|
70
70
|
resolveDmTarget,
|
|
71
71
|
wrapScheduledTaskPrompt
|
|
72
|
-
} from "../chunk-
|
|
72
|
+
} from "../chunk-HT6EETEL.js";
|
|
73
73
|
import {
|
|
74
74
|
parsePsRows,
|
|
75
75
|
reapOrphanChannelMcps
|
|
@@ -3166,7 +3166,7 @@ var cachedFrameworkVersion = null;
|
|
|
3166
3166
|
var lastVersionCheckAt = 0;
|
|
3167
3167
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
3168
3168
|
var lastResponsivenessProbeAt = 0;
|
|
3169
|
-
var agtCliVersion = true ? "0.27.
|
|
3169
|
+
var agtCliVersion = true ? "0.27.15" : "dev";
|
|
3170
3170
|
function resolveBrewPath(execFileSync4) {
|
|
3171
3171
|
try {
|
|
3172
3172
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -4180,7 +4180,7 @@ async function pollCycle() {
|
|
|
4180
4180
|
}
|
|
4181
4181
|
try {
|
|
4182
4182
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
4183
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
4183
|
+
const { collectDiagnostics } = await import("../persistent-session-SBSOZG74.js");
|
|
4184
4184
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
4185
4185
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
4186
4186
|
let tailscaleHostname;
|
|
@@ -4248,7 +4248,7 @@ async function pollCycle() {
|
|
|
4248
4248
|
const {
|
|
4249
4249
|
collectResponsivenessProbes,
|
|
4250
4250
|
getResponsivenessIntervalMs
|
|
4251
|
-
} = await import("../responsiveness-probe-
|
|
4251
|
+
} = await import("../responsiveness-probe-DU4IJ2RZ.js");
|
|
4252
4252
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
4253
4253
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
4254
4254
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -8196,7 +8196,7 @@ async function processClaudePairSessions(agents) {
|
|
|
8196
8196
|
killPairSession,
|
|
8197
8197
|
pairTmuxSession,
|
|
8198
8198
|
finalizeClaudePairOnboarding
|
|
8199
|
-
} = await import("../claude-pair-runtime-
|
|
8199
|
+
} = await import("../claude-pair-runtime-OBAJZDXK.js");
|
|
8200
8200
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
8201
8201
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
8202
8202
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -14249,6 +14249,102 @@ function decideSenderPolicyForward(evt, policy) {
|
|
|
14249
14249
|
return { forward: true };
|
|
14250
14250
|
}
|
|
14251
14251
|
|
|
14252
|
+
// src/ack-reaction.ts
|
|
14253
|
+
import { readdirSync, readFileSync } from "fs";
|
|
14254
|
+
import { join } from "path";
|
|
14255
|
+
var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
14256
|
+
var ACK_STARTUP_GRACE_MS = 6e4;
|
|
14257
|
+
function decideAckReaction(i) {
|
|
14258
|
+
if (!i.hasTarget) return "none";
|
|
14259
|
+
if (!i.integrationReady) return "undeliverable";
|
|
14260
|
+
if (i.tmux === "dead") return "undeliverable";
|
|
14261
|
+
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
14262
|
+
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
14263
|
+
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
14264
|
+
return "undeliverable";
|
|
14265
|
+
}
|
|
14266
|
+
return "ack";
|
|
14267
|
+
}
|
|
14268
|
+
function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
14269
|
+
if (!dir) return null;
|
|
14270
|
+
let names;
|
|
14271
|
+
try {
|
|
14272
|
+
names = readdirSync(dir);
|
|
14273
|
+
} catch {
|
|
14274
|
+
return null;
|
|
14275
|
+
}
|
|
14276
|
+
let oldest = null;
|
|
14277
|
+
for (const name of names) {
|
|
14278
|
+
if (!name.endsWith(".json")) continue;
|
|
14279
|
+
let receivedAt;
|
|
14280
|
+
try {
|
|
14281
|
+
const raw = JSON.parse(readFileSync(join(dir, name), "utf-8"));
|
|
14282
|
+
receivedAt = raw.received_at;
|
|
14283
|
+
} catch {
|
|
14284
|
+
continue;
|
|
14285
|
+
}
|
|
14286
|
+
if (!receivedAt) continue;
|
|
14287
|
+
const t = Date.parse(receivedAt);
|
|
14288
|
+
if (Number.isNaN(t)) continue;
|
|
14289
|
+
const age = now - t;
|
|
14290
|
+
if (age < 0) continue;
|
|
14291
|
+
if (oldest == null || age > oldest) oldest = age;
|
|
14292
|
+
}
|
|
14293
|
+
return oldest;
|
|
14294
|
+
}
|
|
14295
|
+
|
|
14296
|
+
// src/session-probe-runtime.ts
|
|
14297
|
+
import { execFileSync } from "child_process";
|
|
14298
|
+
function agentTmuxSessionName(codeName) {
|
|
14299
|
+
return `agt-${codeName}`;
|
|
14300
|
+
}
|
|
14301
|
+
function escapePgrepRegex(value) {
|
|
14302
|
+
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
14303
|
+
}
|
|
14304
|
+
function probeClaudeProcessInTmux(tmuxSession) {
|
|
14305
|
+
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
14306
|
+
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
14307
|
+
try {
|
|
14308
|
+
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
14309
|
+
encoding: "utf-8",
|
|
14310
|
+
timeout: 3e3
|
|
14311
|
+
}).trim();
|
|
14312
|
+
return out.length > 0 ? "alive" : "dead";
|
|
14313
|
+
} catch (err) {
|
|
14314
|
+
const e = err;
|
|
14315
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
14316
|
+
return e?.status === 1 ? "dead" : "unknown";
|
|
14317
|
+
}
|
|
14318
|
+
}
|
|
14319
|
+
function probeTmuxSession(tmuxSession) {
|
|
14320
|
+
try {
|
|
14321
|
+
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
14322
|
+
stdio: "ignore",
|
|
14323
|
+
timeout: 3e3
|
|
14324
|
+
});
|
|
14325
|
+
return "alive";
|
|
14326
|
+
} catch (err) {
|
|
14327
|
+
const e = err;
|
|
14328
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
14329
|
+
return "dead";
|
|
14330
|
+
}
|
|
14331
|
+
}
|
|
14332
|
+
function probeAgentSession(codeName) {
|
|
14333
|
+
const session = agentTmuxSessionName(codeName);
|
|
14334
|
+
const tmux = probeTmuxSession(session);
|
|
14335
|
+
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
14336
|
+
return { tmux, claude };
|
|
14337
|
+
}
|
|
14338
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
14339
|
+
var SESSION_PROBE_TTL_MS = 15e3;
|
|
14340
|
+
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
14341
|
+
const cached2 = probeCache.get(codeName);
|
|
14342
|
+
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
14343
|
+
const value = probeAgentSession(codeName);
|
|
14344
|
+
probeCache.set(codeName, { at: now, value });
|
|
14345
|
+
return value;
|
|
14346
|
+
}
|
|
14347
|
+
|
|
14252
14348
|
// src/slack-loop-throttle.ts
|
|
14253
14349
|
var DEFAULT_THROTTLE = {
|
|
14254
14350
|
threshold: 3,
|
|
@@ -14813,6 +14909,64 @@ function isMode(value) {
|
|
|
14813
14909
|
return value === "thinking" || value === "working" || value === "waiting";
|
|
14814
14910
|
}
|
|
14815
14911
|
|
|
14912
|
+
// src/slack-thread-context.ts
|
|
14913
|
+
var SLACK_AUTOLOAD_THREAD_LIMIT = 30;
|
|
14914
|
+
var SLACK_AUTOLOAD_THREAD_MAX_CHARS = 4e3;
|
|
14915
|
+
function formatThreadMessages(messages, nameById) {
|
|
14916
|
+
return messages.map((m) => `[${m.ts}] ${nameById.get(m.user) ?? m.user} (<@${m.user}>): ${m.text}`).join("\n");
|
|
14917
|
+
}
|
|
14918
|
+
function capThreadContext(formatted, maxChars) {
|
|
14919
|
+
if (formatted.length <= maxChars) return formatted;
|
|
14920
|
+
const marker = "[\u2026earlier thread messages omitted \u2014 slack.read_thread for full history\u2026]\n";
|
|
14921
|
+
if (maxChars <= marker.length) return marker.slice(0, maxChars);
|
|
14922
|
+
const tailBudget = maxChars - marker.length;
|
|
14923
|
+
const tail = formatted.slice(formatted.length - tailBudget);
|
|
14924
|
+
const firstNewline = tail.indexOf("\n");
|
|
14925
|
+
const clean = firstNewline >= 0 ? tail.slice(firstNewline + 1) : tail;
|
|
14926
|
+
return `${marker}${clean}`;
|
|
14927
|
+
}
|
|
14928
|
+
async function fetchThreadTranscript(channel, threadTs, limit, deps) {
|
|
14929
|
+
const { botToken, resolveUserName: resolveUserName2, fetchImpl = fetch } = deps;
|
|
14930
|
+
const cappedLimit = Math.min(Math.max(limit, 1), 200);
|
|
14931
|
+
const allMessages = [];
|
|
14932
|
+
let cursor;
|
|
14933
|
+
do {
|
|
14934
|
+
const remaining = cappedLimit - allMessages.length;
|
|
14935
|
+
if (remaining <= 0) break;
|
|
14936
|
+
const params = new URLSearchParams({
|
|
14937
|
+
channel,
|
|
14938
|
+
ts: threadTs,
|
|
14939
|
+
limit: String(Math.min(remaining, 100)),
|
|
14940
|
+
...cursor ? { cursor } : {}
|
|
14941
|
+
});
|
|
14942
|
+
let data;
|
|
14943
|
+
try {
|
|
14944
|
+
const res = await fetchImpl(`https://slack.com/api/conversations.replies?${params}`, {
|
|
14945
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
14946
|
+
});
|
|
14947
|
+
if (!res.ok) return { ok: false, error: `http_${res.status}` };
|
|
14948
|
+
data = await res.json();
|
|
14949
|
+
} catch (err) {
|
|
14950
|
+
return { ok: false, error: err instanceof Error ? err.message : "fetch_failed" };
|
|
14951
|
+
}
|
|
14952
|
+
if (!data.ok) return { ok: false, error: data.error ?? "unknown" };
|
|
14953
|
+
for (const msg of data.messages ?? []) {
|
|
14954
|
+
allMessages.push({
|
|
14955
|
+
user: msg.user ?? msg.bot_id ?? "unknown",
|
|
14956
|
+
text: msg.text ?? "",
|
|
14957
|
+
ts: msg.ts ?? ""
|
|
14958
|
+
});
|
|
14959
|
+
}
|
|
14960
|
+
cursor = data.response_metadata?.next_cursor || void 0;
|
|
14961
|
+
} while (cursor && allMessages.length < cappedLimit);
|
|
14962
|
+
const uniqueUserIds = [...new Set(allMessages.map((m) => m.user))];
|
|
14963
|
+
const resolved = await Promise.all(
|
|
14964
|
+
uniqueUserIds.map(async (id) => [id, await resolveUserName2(id)])
|
|
14965
|
+
);
|
|
14966
|
+
const nameById = new Map(resolved);
|
|
14967
|
+
return { ok: true, count: allMessages.length, formatted: formatThreadMessages(allMessages, nameById) };
|
|
14968
|
+
}
|
|
14969
|
+
|
|
14816
14970
|
// src/impersonation.ts
|
|
14817
14971
|
var ENV_VAR = "AGT_ACT_AS_AGENT_ID";
|
|
14818
14972
|
var OVERRIDE_ENV_VAR = "ENABLE_IMPERSONATION_CHANNELS";
|
|
@@ -14958,20 +15112,20 @@ import {
|
|
|
14958
15112
|
createWriteStream,
|
|
14959
15113
|
existsSync as existsSync2,
|
|
14960
15114
|
mkdirSync as mkdirSync3,
|
|
14961
|
-
readFileSync as
|
|
14962
|
-
readdirSync,
|
|
15115
|
+
readFileSync as readFileSync4,
|
|
15116
|
+
readdirSync as readdirSync2,
|
|
14963
15117
|
renameSync as renameSync2,
|
|
14964
15118
|
statSync,
|
|
14965
15119
|
unlinkSync as unlinkSync2,
|
|
14966
15120
|
watch,
|
|
14967
15121
|
writeFileSync as writeFileSync3
|
|
14968
15122
|
} from "fs";
|
|
14969
|
-
import { basename, join as
|
|
15123
|
+
import { basename, join as join4, resolve as resolve2 } from "path";
|
|
14970
15124
|
import { homedir as homedir2 } from "os";
|
|
14971
15125
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
14972
15126
|
|
|
14973
15127
|
// src/slack-thread-store.ts
|
|
14974
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15128
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
14975
15129
|
import { dirname } from "path";
|
|
14976
15130
|
var FILE_VERSION = 1;
|
|
14977
15131
|
var DEFAULT_TTL_DAYS = 30;
|
|
@@ -14982,7 +15136,7 @@ function loadThreadStore(filePath, opts = {}) {
|
|
|
14982
15136
|
const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
|
|
14983
15137
|
let raw;
|
|
14984
15138
|
try {
|
|
14985
|
-
raw =
|
|
15139
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
14986
15140
|
} catch {
|
|
14987
15141
|
return { threads: /* @__PURE__ */ new Map(), pruned: 0 };
|
|
14988
15142
|
}
|
|
@@ -15097,9 +15251,9 @@ async function runOrRetry(fn, opts) {
|
|
|
15097
15251
|
|
|
15098
15252
|
// src/channel-attachments.ts
|
|
15099
15253
|
import { homedir } from "os";
|
|
15100
|
-
import { join, resolve, sep } from "path";
|
|
15254
|
+
import { join as join2, resolve, sep } from "path";
|
|
15101
15255
|
function resolveChannelInboundDir(codeName, channelSlug) {
|
|
15102
|
-
const base =
|
|
15256
|
+
const base = join2(homedir(), ".augmented");
|
|
15103
15257
|
const allowedSegment = /^[A-Za-z0-9_-]+$/;
|
|
15104
15258
|
if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
|
|
15105
15259
|
throw new Error(
|
|
@@ -15745,12 +15899,12 @@ function createSlackBotUserIdClient(args) {
|
|
|
15745
15899
|
import {
|
|
15746
15900
|
existsSync,
|
|
15747
15901
|
mkdirSync as mkdirSync2,
|
|
15748
|
-
readFileSync as
|
|
15902
|
+
readFileSync as readFileSync3,
|
|
15749
15903
|
renameSync,
|
|
15750
15904
|
unlinkSync,
|
|
15751
15905
|
writeFileSync as writeFileSync2
|
|
15752
15906
|
} from "fs";
|
|
15753
|
-
import { join as
|
|
15907
|
+
import { join as join3 } from "path";
|
|
15754
15908
|
function defaultIsPidAlive(pid) {
|
|
15755
15909
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
15756
15910
|
try {
|
|
@@ -15768,7 +15922,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
15768
15922
|
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
15769
15923
|
const selfPid = options.selfPid ?? process.pid;
|
|
15770
15924
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
15771
|
-
const path =
|
|
15925
|
+
const path = join3(agentDir, basename2);
|
|
15772
15926
|
const existing = readLockHolder(path);
|
|
15773
15927
|
if (existing) {
|
|
15774
15928
|
if (existing.pid === selfPid) {
|
|
@@ -15799,7 +15953,7 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
|
|
|
15799
15953
|
function readLockHolder(path) {
|
|
15800
15954
|
if (!existsSync(path)) return null;
|
|
15801
15955
|
try {
|
|
15802
|
-
const raw =
|
|
15956
|
+
const raw = readFileSync3(path, "utf8");
|
|
15803
15957
|
const parsed = JSON.parse(raw);
|
|
15804
15958
|
const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
|
|
15805
15959
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
@@ -15881,9 +16035,9 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
|
|
|
15881
16035
|
peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
|
|
15882
16036
|
peer_disabled_mode: SLACK_PEER_DISABLED_MODE
|
|
15883
16037
|
};
|
|
15884
|
-
var SLACK_AGENT_DIR = AGENT_CODE_NAME ?
|
|
15885
|
-
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ?
|
|
15886
|
-
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ?
|
|
16038
|
+
var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
16039
|
+
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join4(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
|
|
16040
|
+
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join4(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
|
|
15887
16041
|
var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
|
|
15888
16042
|
function redactSlackId(id) {
|
|
15889
16043
|
if (!id) return "<none>";
|
|
@@ -15895,7 +16049,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
|
|
|
15895
16049
|
}
|
|
15896
16050
|
function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
15897
16051
|
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
15898
|
-
return
|
|
16052
|
+
return join4(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
15899
16053
|
}
|
|
15900
16054
|
function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
15901
16055
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
@@ -15922,10 +16076,10 @@ function clearAllSlackPendingMarkersForThread(channel, threadTs) {
|
|
|
15922
16076
|
const safeThread = threadTs.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
15923
16077
|
const prefix = `${safeChan}__${safeThread}__`;
|
|
15924
16078
|
try {
|
|
15925
|
-
for (const f of
|
|
16079
|
+
for (const f of readdirSync2(SLACK_PENDING_INBOUND_DIR)) {
|
|
15926
16080
|
if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
|
|
15927
16081
|
try {
|
|
15928
|
-
unlinkSync2(
|
|
16082
|
+
unlinkSync2(join4(SLACK_PENDING_INBOUND_DIR, f));
|
|
15929
16083
|
} catch {
|
|
15930
16084
|
}
|
|
15931
16085
|
}
|
|
@@ -15946,10 +16100,10 @@ function slackNextRetryName(filename) {
|
|
|
15946
16100
|
async function processSlackRecoveryOutboxFile(filename) {
|
|
15947
16101
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
15948
16102
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
15949
|
-
const fullPath =
|
|
16103
|
+
const fullPath = join4(SLACK_RECOVERY_OUTBOX_DIR, filename);
|
|
15950
16104
|
let payload;
|
|
15951
16105
|
try {
|
|
15952
|
-
payload = JSON.parse(
|
|
16106
|
+
payload = JSON.parse(readFileSync4(fullPath, "utf-8"));
|
|
15953
16107
|
} catch (err) {
|
|
15954
16108
|
process.stderr.write(
|
|
15955
16109
|
`slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
|
|
@@ -16023,7 +16177,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
16023
16177
|
const next = slackNextRetryName(filename);
|
|
16024
16178
|
if (next) {
|
|
16025
16179
|
try {
|
|
16026
|
-
renameSync2(fullPath,
|
|
16180
|
+
renameSync2(fullPath, join4(SLACK_RECOVERY_OUTBOX_DIR, next.next));
|
|
16027
16181
|
if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
|
|
16028
16182
|
process.stderr.write(
|
|
16029
16183
|
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -16053,7 +16207,7 @@ function scanSlackRecoveryRetries() {
|
|
|
16053
16207
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
16054
16208
|
let entries;
|
|
16055
16209
|
try {
|
|
16056
|
-
entries =
|
|
16210
|
+
entries = readdirSync2(SLACK_RECOVERY_OUTBOX_DIR);
|
|
16057
16211
|
} catch {
|
|
16058
16212
|
return;
|
|
16059
16213
|
}
|
|
@@ -16062,7 +16216,7 @@ function scanSlackRecoveryRetries() {
|
|
|
16062
16216
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
16063
16217
|
let mtimeMs;
|
|
16064
16218
|
try {
|
|
16065
|
-
mtimeMs = statSync(
|
|
16219
|
+
mtimeMs = statSync(join4(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
16066
16220
|
} catch {
|
|
16067
16221
|
continue;
|
|
16068
16222
|
}
|
|
@@ -16083,7 +16237,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
16083
16237
|
return;
|
|
16084
16238
|
}
|
|
16085
16239
|
try {
|
|
16086
|
-
for (const f of
|
|
16240
|
+
for (const f of readdirSync2(SLACK_RECOVERY_OUTBOX_DIR)) {
|
|
16087
16241
|
if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
|
|
16088
16242
|
}
|
|
16089
16243
|
} catch {
|
|
@@ -16092,7 +16246,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
16092
16246
|
const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
16093
16247
|
if (event !== "rename" || !filename) return;
|
|
16094
16248
|
if (!isFirstAttemptSlackOutboxFile(filename)) return;
|
|
16095
|
-
if (existsSync2(
|
|
16249
|
+
if (existsSync2(join4(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
|
|
16096
16250
|
void processSlackRecoveryOutboxFile(filename);
|
|
16097
16251
|
}
|
|
16098
16252
|
});
|
|
@@ -16116,7 +16270,7 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
16116
16270
|
if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
|
|
16117
16271
|
let filenames;
|
|
16118
16272
|
try {
|
|
16119
|
-
filenames =
|
|
16273
|
+
filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
|
|
16120
16274
|
} catch (err) {
|
|
16121
16275
|
process.stderr.write(
|
|
16122
16276
|
`slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -16129,10 +16283,10 @@ function sweepSlackStaleMarkersOnBoot() {
|
|
|
16129
16283
|
for (const filename of filenames) {
|
|
16130
16284
|
if (!filename.endsWith(".json")) continue;
|
|
16131
16285
|
if (filename.endsWith(".tmp")) continue;
|
|
16132
|
-
const fullPath =
|
|
16286
|
+
const fullPath = join4(SLACK_PENDING_INBOUND_DIR, filename);
|
|
16133
16287
|
let marker;
|
|
16134
16288
|
try {
|
|
16135
|
-
marker = JSON.parse(
|
|
16289
|
+
marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
|
|
16136
16290
|
} catch (err) {
|
|
16137
16291
|
process.stderr.write(
|
|
16138
16292
|
`slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
|
|
@@ -16185,7 +16339,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
16185
16339
|
if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
|
|
16186
16340
|
let filenames;
|
|
16187
16341
|
try {
|
|
16188
|
-
filenames =
|
|
16342
|
+
filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
|
|
16189
16343
|
} catch {
|
|
16190
16344
|
return;
|
|
16191
16345
|
}
|
|
@@ -16197,12 +16351,12 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
16197
16351
|
if (!filename.startsWith(channelPrefix)) continue;
|
|
16198
16352
|
if (!filename.endsWith(messageSuffix)) continue;
|
|
16199
16353
|
try {
|
|
16200
|
-
unlinkSync2(
|
|
16354
|
+
unlinkSync2(join4(SLACK_PENDING_INBOUND_DIR, filename));
|
|
16201
16355
|
} catch {
|
|
16202
16356
|
}
|
|
16203
16357
|
}
|
|
16204
16358
|
}
|
|
16205
|
-
var RESTART_FLAGS_DIR =
|
|
16359
|
+
var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
|
|
16206
16360
|
function buildAugmentedSlackMetadata() {
|
|
16207
16361
|
if (!AGT_TEAM_ID) return void 0;
|
|
16208
16362
|
return {
|
|
@@ -16384,7 +16538,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16384
16538
|
if (!existsSync2(RESTART_FLAGS_DIR)) {
|
|
16385
16539
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16386
16540
|
}
|
|
16387
|
-
const flagPath =
|
|
16541
|
+
const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
16388
16542
|
const flag = {
|
|
16389
16543
|
codeName,
|
|
16390
16544
|
source: "slack",
|
|
@@ -16491,7 +16645,7 @@ async function handleRestartCommand(opts) {
|
|
|
16491
16645
|
if (!existsSync2(RESTART_FLAGS_DIR)) {
|
|
16492
16646
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16493
16647
|
}
|
|
16494
|
-
const flagPath =
|
|
16648
|
+
const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
16495
16649
|
const flag = {
|
|
16496
16650
|
codeName,
|
|
16497
16651
|
source: "slack",
|
|
@@ -16550,7 +16704,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
|
|
|
16550
16704
|
var threadPersister = null;
|
|
16551
16705
|
function resolveThreadStorePath() {
|
|
16552
16706
|
if (!AGENT_CODE_NAME) return null;
|
|
16553
|
-
return
|
|
16707
|
+
return join4(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
|
|
16554
16708
|
}
|
|
16555
16709
|
function parseTtlDays(raw) {
|
|
16556
16710
|
if (!raw) return void 0;
|
|
@@ -16585,9 +16739,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
|
|
|
16585
16739
|
var slackStderrLogStream = null;
|
|
16586
16740
|
if (AGENT_CODE_NAME) {
|
|
16587
16741
|
try {
|
|
16588
|
-
const logDir =
|
|
16742
|
+
const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
16589
16743
|
mkdirSync3(logDir, { recursive: true });
|
|
16590
|
-
slackStderrLogStream = createWriteStream(
|
|
16744
|
+
slackStderrLogStream = createWriteStream(join4(logDir, "slack-channel-stderr.log"), {
|
|
16591
16745
|
flags: "a",
|
|
16592
16746
|
mode: 384
|
|
16593
16747
|
});
|
|
@@ -16657,7 +16811,6 @@ void resolveBotUserIdOrThrow().then((id) => {
|
|
|
16657
16811
|
);
|
|
16658
16812
|
if (authFailed) slackBotUserIdClient?.reportAuthHealth(false);
|
|
16659
16813
|
});
|
|
16660
|
-
var selfIdentityInstruction = `Mentions of your own Slack bot user are directed at you, even inside auto_followed threads.`;
|
|
16661
16814
|
var mcp = new Server(
|
|
16662
16815
|
{ name: "slack", version: "0.1.0" },
|
|
16663
16816
|
{
|
|
@@ -16672,13 +16825,12 @@ var mcp = new Server(
|
|
|
16672
16825
|
// Highest-priority lines first — Claude Code truncates this string at
|
|
16673
16826
|
// 2048 chars, so anything appended late silently disappears.
|
|
16674
16827
|
"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.",
|
|
16675
|
-
|
|
16828
|
+
`Inbound: <channel ... thread_ts="..." [thread_context="..."]>. Pass channel + thread_ts to slack.reply on threads. thread_context = thread pre-loaded; ground replies ONLY in it, never another channel's.`,
|
|
16676
16829
|
"Long task (3+ tool calls or >5s)? slack_progress_start (channel + thread_ts), slack_progress_update between steps, slack_progress_complete/_fail to close. One anchor edits in place; avoids thread spam. Not for one-shots.",
|
|
16677
|
-
selfIdentityInstruction,
|
|
16678
16830
|
"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.",
|
|
16679
16831
|
"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.",
|
|
16680
|
-
'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if
|
|
16681
|
-
"Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u2705 = success. NEVER react to signal failure \u2014 users can't tell why something failed from an emoji. On failure, slack.reply with one sentence explaining what went wrong (no stack traces, no secrets).",
|
|
16832
|
+
'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).',
|
|
16833
|
+
"Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u2705 = success. NEVER react to signal failure of YOUR work \u2014 users can't tell why something failed from an emoji. On failure, slack.reply with one sentence explaining what went wrong (no stack traces, no secrets). (The \u274C you may see on an inbound is applied by the system, not you \u2014 it marks a message that arrived while the agent couldn't reply; never add \u274C yourself.)",
|
|
16682
16834
|
`When a thread message is NOT addressed to you (different @-mention, side conversation, auto_followed catch-up): SILENTLY SKIP \u2014 no reaction, no reply, no "this wasn't for me" message.`,
|
|
16683
16835
|
"To deliver a file: save under your project dir, call slack.upload_file with path + channel + thread_ts."
|
|
16684
16836
|
].join(" ")
|
|
@@ -16730,7 +16882,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16730
16882
|
},
|
|
16731
16883
|
{
|
|
16732
16884
|
name: "slack.react",
|
|
16733
|
-
description: "Add an emoji reaction to a Slack message. Use sparingly \u2014 prefer a text reply. Reaction taxonomy: \u2705 = action completed successfully. NEVER react to signal failure \u2014 users can't tell why it failed from a reaction. On failure, call slack.reply with one sentence explaining what went wrong instead. \u{1F440} (eyes) is already auto-applied on inbound; do not duplicate.",
|
|
16885
|
+
description: "Add an emoji reaction to a Slack message. Use sparingly \u2014 prefer a text reply. Reaction taxonomy: \u2705 = action completed successfully. NEVER react to signal failure of your work \u2014 users can't tell why it failed from a reaction. On failure, call slack.reply with one sentence explaining what went wrong instead. \u{1F440} (eyes) is already auto-applied on inbound; do not duplicate. \u274C (:x:) is reserved for the system to mark an inbound that arrived while the agent couldn't reply \u2014 never add \u274C yourself.",
|
|
16734
16886
|
inputSchema: {
|
|
16735
16887
|
type: "object",
|
|
16736
16888
|
properties: {
|
|
@@ -16743,7 +16895,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16743
16895
|
},
|
|
16744
16896
|
{
|
|
16745
16897
|
name: "slack.read_thread",
|
|
16746
|
-
description: "Read the full message history of a Slack thread.
|
|
16898
|
+
description: "Read the full message history of a Slack thread. Inbound thread replies already carry the surrounding thread inline as the <channel> tag's thread_context attribute \u2014 ground your reply in that first, and only call this tool when you need MORE history than thread_context shows (it is capped to recent messages) or to catch up on an auto-followed thread.",
|
|
16747
16899
|
inputSchema: {
|
|
16748
16900
|
type: "object",
|
|
16749
16901
|
properties: {
|
|
@@ -17117,46 +17269,22 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17117
17269
|
const limit = Math.min(Math.max(rawLimit ?? 50, 1), 200);
|
|
17118
17270
|
noteThreadActivity(channel, thread_ts);
|
|
17119
17271
|
try {
|
|
17120
|
-
const
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
});
|
|
17131
|
-
const res = await fetch(`https://slack.com/api/conversations.replies?${params}`, {
|
|
17132
|
-
headers: { Authorization: `Bearer ${BOT_TOKEN}` }
|
|
17133
|
-
});
|
|
17134
|
-
const data = await res.json();
|
|
17135
|
-
if (!data.ok) {
|
|
17136
|
-
return {
|
|
17137
|
-
content: [{ type: "text", text: `Slack error: ${data.error}` }],
|
|
17138
|
-
isError: true
|
|
17139
|
-
};
|
|
17140
|
-
}
|
|
17141
|
-
for (const msg of data.messages ?? []) {
|
|
17142
|
-
allMessages.push({
|
|
17143
|
-
user: msg.user ?? msg.bot_id ?? "unknown",
|
|
17144
|
-
text: msg.text ?? "",
|
|
17145
|
-
ts: msg.ts ?? ""
|
|
17146
|
-
});
|
|
17147
|
-
}
|
|
17148
|
-
cursor = data.response_metadata?.next_cursor || void 0;
|
|
17149
|
-
} while (cursor && allMessages.length < limit);
|
|
17150
|
-
const uniqueUserIds = [...new Set(allMessages.map((m) => m.user))];
|
|
17151
|
-
const resolved = await Promise.all(uniqueUserIds.map(async (id) => [id, await resolveUserName(id)]));
|
|
17152
|
-
const nameById = new Map(resolved);
|
|
17153
|
-
const formatted = allMessages.map((m) => `[${m.ts}] ${nameById.get(m.user) ?? m.user} (<@${m.user}>): ${m.text}`).join("\n");
|
|
17272
|
+
const result = await fetchThreadTranscript(channel, thread_ts, limit, {
|
|
17273
|
+
botToken: BOT_TOKEN,
|
|
17274
|
+
resolveUserName
|
|
17275
|
+
});
|
|
17276
|
+
if (!result.ok) {
|
|
17277
|
+
return {
|
|
17278
|
+
content: [{ type: "text", text: `Slack error: ${result.error}` }],
|
|
17279
|
+
isError: true
|
|
17280
|
+
};
|
|
17281
|
+
}
|
|
17154
17282
|
return {
|
|
17155
17283
|
content: [{
|
|
17156
17284
|
type: "text",
|
|
17157
|
-
text:
|
|
17285
|
+
text: result.count > 0 ? `Thread (${result.count} messages):
|
|
17158
17286
|
|
|
17159
|
-
${formatted}` : "Thread is empty or not found."
|
|
17287
|
+
${result.formatted}` : "Thread is empty or not found."
|
|
17160
17288
|
}]
|
|
17161
17289
|
};
|
|
17162
17290
|
} catch (err) {
|
|
@@ -17206,7 +17334,7 @@ ${formatted}` : "Thread is empty or not found."
|
|
|
17206
17334
|
};
|
|
17207
17335
|
}
|
|
17208
17336
|
size = stat.size;
|
|
17209
|
-
bytes =
|
|
17337
|
+
bytes = readFileSync4(resolvedPath);
|
|
17210
17338
|
} catch (err) {
|
|
17211
17339
|
return {
|
|
17212
17340
|
content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
|
|
@@ -18226,14 +18354,24 @@ async function connectSocketMode() {
|
|
|
18226
18354
|
isAutoFollowed,
|
|
18227
18355
|
botUserId
|
|
18228
18356
|
});
|
|
18229
|
-
|
|
18357
|
+
const ackProbe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
18358
|
+
const ackDecision = decideAckReaction({
|
|
18359
|
+
hasTarget: decideSlackAckReaction({ channel, ts }),
|
|
18360
|
+
integrationReady: Boolean(BOT_TOKEN),
|
|
18361
|
+
tmux: ackProbe.tmux,
|
|
18362
|
+
claude: ackProbe.claude,
|
|
18363
|
+
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
18364
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR)
|
|
18365
|
+
});
|
|
18366
|
+
if (ackDecision !== "none") {
|
|
18367
|
+
const reactionName = ackDecision === "undeliverable" ? "x" : "eyes";
|
|
18230
18368
|
fetch("https://slack.com/api/reactions.add", {
|
|
18231
18369
|
method: "POST",
|
|
18232
18370
|
headers: {
|
|
18233
18371
|
"Content-Type": "application/json",
|
|
18234
18372
|
Authorization: `Bearer ${BOT_TOKEN}`
|
|
18235
18373
|
},
|
|
18236
|
-
body: JSON.stringify({ channel, timestamp: ts, name:
|
|
18374
|
+
body: JSON.stringify({ channel, timestamp: ts, name: reactionName })
|
|
18237
18375
|
}).catch(() => {
|
|
18238
18376
|
});
|
|
18239
18377
|
}
|
|
@@ -18246,6 +18384,29 @@ async function connectSocketMode() {
|
|
|
18246
18384
|
(f) => f.kind === "image" && typeof f.path === "string"
|
|
18247
18385
|
);
|
|
18248
18386
|
const imagePath = downloadedImages.length === 1 ? downloadedImages[0].path : void 0;
|
|
18387
|
+
let threadContext;
|
|
18388
|
+
if (isThreadReply && channel && threadTs) {
|
|
18389
|
+
try {
|
|
18390
|
+
const transcript = await fetchThreadTranscript(
|
|
18391
|
+
channel,
|
|
18392
|
+
threadTs,
|
|
18393
|
+
SLACK_AUTOLOAD_THREAD_LIMIT,
|
|
18394
|
+
{ botToken: BOT_TOKEN, resolveUserName }
|
|
18395
|
+
);
|
|
18396
|
+
if (transcript.ok && transcript.count >= 2) {
|
|
18397
|
+
threadContext = capThreadContext(
|
|
18398
|
+
transcript.formatted,
|
|
18399
|
+
SLACK_AUTOLOAD_THREAD_MAX_CHARS
|
|
18400
|
+
);
|
|
18401
|
+
}
|
|
18402
|
+
} catch (err) {
|
|
18403
|
+
const msg2 = err instanceof Error ? err.message : String(err);
|
|
18404
|
+
process.stderr.write(
|
|
18405
|
+
`slack-channel(${AGENT_CODE_NAME}): thread_context fetch failed (channel=${redactSlackId(channel)} thread=${redactSlackId(threadTs)}): ${msg2}
|
|
18406
|
+
`
|
|
18407
|
+
);
|
|
18408
|
+
}
|
|
18409
|
+
}
|
|
18249
18410
|
await mcp.notification({
|
|
18250
18411
|
method: "notifications/claude/channel",
|
|
18251
18412
|
params: {
|
|
@@ -18260,7 +18421,9 @@ async function connectSocketMode() {
|
|
|
18260
18421
|
// Only set these when we actually have attachments to avoid
|
|
18261
18422
|
// bloating every notification with empty metadata.
|
|
18262
18423
|
...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
|
|
18263
|
-
...imagePath ? { image_path: imagePath } : {}
|
|
18424
|
+
...imagePath ? { image_path: imagePath } : {},
|
|
18425
|
+
// ENG-5830: the pre-loaded surrounding thread (thread replies only).
|
|
18426
|
+
...threadContext ? { thread_context: threadContext } : {}
|
|
18264
18427
|
}
|
|
18265
18428
|
}
|
|
18266
18429
|
});
|