@integrity-labs/agt-cli 0.27.14 → 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.
@@ -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,
@@ -15016,20 +15112,20 @@ import {
15016
15112
  createWriteStream,
15017
15113
  existsSync as existsSync2,
15018
15114
  mkdirSync as mkdirSync3,
15019
- readFileSync as readFileSync3,
15020
- readdirSync,
15115
+ readFileSync as readFileSync4,
15116
+ readdirSync as readdirSync2,
15021
15117
  renameSync as renameSync2,
15022
15118
  statSync,
15023
15119
  unlinkSync as unlinkSync2,
15024
15120
  watch,
15025
15121
  writeFileSync as writeFileSync3
15026
15122
  } from "fs";
15027
- import { basename, join as join3, resolve as resolve2 } from "path";
15123
+ import { basename, join as join4, resolve as resolve2 } from "path";
15028
15124
  import { homedir as homedir2 } from "os";
15029
15125
  import { createHash, randomUUID as randomUUID2 } from "crypto";
15030
15126
 
15031
15127
  // src/slack-thread-store.ts
15032
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
15128
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
15033
15129
  import { dirname } from "path";
15034
15130
  var FILE_VERSION = 1;
15035
15131
  var DEFAULT_TTL_DAYS = 30;
@@ -15040,7 +15136,7 @@ function loadThreadStore(filePath, opts = {}) {
15040
15136
  const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
15041
15137
  let raw;
15042
15138
  try {
15043
- raw = readFileSync(filePath, "utf-8");
15139
+ raw = readFileSync2(filePath, "utf-8");
15044
15140
  } catch {
15045
15141
  return { threads: /* @__PURE__ */ new Map(), pruned: 0 };
15046
15142
  }
@@ -15155,9 +15251,9 @@ async function runOrRetry(fn, opts) {
15155
15251
 
15156
15252
  // src/channel-attachments.ts
15157
15253
  import { homedir } from "os";
15158
- import { join, resolve, sep } from "path";
15254
+ import { join as join2, resolve, sep } from "path";
15159
15255
  function resolveChannelInboundDir(codeName, channelSlug) {
15160
- const base = join(homedir(), ".augmented");
15256
+ const base = join2(homedir(), ".augmented");
15161
15257
  const allowedSegment = /^[A-Za-z0-9_-]+$/;
15162
15258
  if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
15163
15259
  throw new Error(
@@ -15803,12 +15899,12 @@ function createSlackBotUserIdClient(args) {
15803
15899
  import {
15804
15900
  existsSync,
15805
15901
  mkdirSync as mkdirSync2,
15806
- readFileSync as readFileSync2,
15902
+ readFileSync as readFileSync3,
15807
15903
  renameSync,
15808
15904
  unlinkSync,
15809
15905
  writeFileSync as writeFileSync2
15810
15906
  } from "fs";
15811
- import { join as join2 } from "path";
15907
+ import { join as join3 } from "path";
15812
15908
  function defaultIsPidAlive(pid) {
15813
15909
  if (!Number.isFinite(pid) || pid <= 0) return false;
15814
15910
  try {
@@ -15826,7 +15922,7 @@ function acquireMcpSpawnLock(args) {
15826
15922
  const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
15827
15923
  const selfPid = options.selfPid ?? process.pid;
15828
15924
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
15829
- const path = join2(agentDir, basename2);
15925
+ const path = join3(agentDir, basename2);
15830
15926
  const existing = readLockHolder(path);
15831
15927
  if (existing) {
15832
15928
  if (existing.pid === selfPid) {
@@ -15857,7 +15953,7 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
15857
15953
  function readLockHolder(path) {
15858
15954
  if (!existsSync(path)) return null;
15859
15955
  try {
15860
- const raw = readFileSync2(path, "utf8");
15956
+ const raw = readFileSync3(path, "utf8");
15861
15957
  const parsed = JSON.parse(raw);
15862
15958
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
15863
15959
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -15939,9 +16035,9 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
15939
16035
  peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
15940
16036
  peer_disabled_mode: SLACK_PEER_DISABLED_MODE
15941
16037
  };
15942
- var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join3(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15943
- var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join3(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15944
- var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join3(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
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;
15945
16041
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
15946
16042
  function redactSlackId(id) {
15947
16043
  if (!id) return "<none>";
@@ -15953,7 +16049,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
15953
16049
  }
15954
16050
  function slackPendingInboundPath(channel, threadTs, messageTs) {
15955
16051
  if (!SLACK_PENDING_INBOUND_DIR) return null;
15956
- return join3(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
16052
+ return join4(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15957
16053
  }
15958
16054
  function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
15959
16055
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
@@ -15980,10 +16076,10 @@ function clearAllSlackPendingMarkersForThread(channel, threadTs) {
15980
16076
  const safeThread = threadTs.replace(/[^A-Za-z0-9_-]/g, "_");
15981
16077
  const prefix = `${safeChan}__${safeThread}__`;
15982
16078
  try {
15983
- for (const f of readdirSync(SLACK_PENDING_INBOUND_DIR)) {
16079
+ for (const f of readdirSync2(SLACK_PENDING_INBOUND_DIR)) {
15984
16080
  if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
15985
16081
  try {
15986
- unlinkSync2(join3(SLACK_PENDING_INBOUND_DIR, f));
16082
+ unlinkSync2(join4(SLACK_PENDING_INBOUND_DIR, f));
15987
16083
  } catch {
15988
16084
  }
15989
16085
  }
@@ -16004,10 +16100,10 @@ function slackNextRetryName(filename) {
16004
16100
  async function processSlackRecoveryOutboxFile(filename) {
16005
16101
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
16006
16102
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
16007
- const fullPath = join3(SLACK_RECOVERY_OUTBOX_DIR, filename);
16103
+ const fullPath = join4(SLACK_RECOVERY_OUTBOX_DIR, filename);
16008
16104
  let payload;
16009
16105
  try {
16010
- payload = JSON.parse(readFileSync3(fullPath, "utf-8"));
16106
+ payload = JSON.parse(readFileSync4(fullPath, "utf-8"));
16011
16107
  } catch (err) {
16012
16108
  process.stderr.write(
16013
16109
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
@@ -16081,7 +16177,7 @@ async function processSlackRecoveryOutboxFile(filename) {
16081
16177
  const next = slackNextRetryName(filename);
16082
16178
  if (next) {
16083
16179
  try {
16084
- renameSync2(fullPath, join3(SLACK_RECOVERY_OUTBOX_DIR, next.next));
16180
+ renameSync2(fullPath, join4(SLACK_RECOVERY_OUTBOX_DIR, next.next));
16085
16181
  if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
16086
16182
  process.stderr.write(
16087
16183
  `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -16111,7 +16207,7 @@ function scanSlackRecoveryRetries() {
16111
16207
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
16112
16208
  let entries;
16113
16209
  try {
16114
- entries = readdirSync(SLACK_RECOVERY_OUTBOX_DIR);
16210
+ entries = readdirSync2(SLACK_RECOVERY_OUTBOX_DIR);
16115
16211
  } catch {
16116
16212
  return;
16117
16213
  }
@@ -16120,7 +16216,7 @@ function scanSlackRecoveryRetries() {
16120
16216
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
16121
16217
  let mtimeMs;
16122
16218
  try {
16123
- mtimeMs = statSync(join3(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16219
+ mtimeMs = statSync(join4(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16124
16220
  } catch {
16125
16221
  continue;
16126
16222
  }
@@ -16141,7 +16237,7 @@ function startSlackRecoveryOutboxWatcher() {
16141
16237
  return;
16142
16238
  }
16143
16239
  try {
16144
- for (const f of readdirSync(SLACK_RECOVERY_OUTBOX_DIR)) {
16240
+ for (const f of readdirSync2(SLACK_RECOVERY_OUTBOX_DIR)) {
16145
16241
  if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
16146
16242
  }
16147
16243
  } catch {
@@ -16150,7 +16246,7 @@ function startSlackRecoveryOutboxWatcher() {
16150
16246
  const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
16151
16247
  if (event !== "rename" || !filename) return;
16152
16248
  if (!isFirstAttemptSlackOutboxFile(filename)) return;
16153
- if (existsSync2(join3(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
16249
+ if (existsSync2(join4(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
16154
16250
  void processSlackRecoveryOutboxFile(filename);
16155
16251
  }
16156
16252
  });
@@ -16174,7 +16270,7 @@ function sweepSlackStaleMarkersOnBoot() {
16174
16270
  if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
16175
16271
  let filenames;
16176
16272
  try {
16177
- filenames = readdirSync(SLACK_PENDING_INBOUND_DIR);
16273
+ filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
16178
16274
  } catch (err) {
16179
16275
  process.stderr.write(
16180
16276
  `slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -16187,10 +16283,10 @@ function sweepSlackStaleMarkersOnBoot() {
16187
16283
  for (const filename of filenames) {
16188
16284
  if (!filename.endsWith(".json")) continue;
16189
16285
  if (filename.endsWith(".tmp")) continue;
16190
- const fullPath = join3(SLACK_PENDING_INBOUND_DIR, filename);
16286
+ const fullPath = join4(SLACK_PENDING_INBOUND_DIR, filename);
16191
16287
  let marker;
16192
16288
  try {
16193
- marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
16289
+ marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
16194
16290
  } catch (err) {
16195
16291
  process.stderr.write(
16196
16292
  `slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
@@ -16243,7 +16339,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16243
16339
  if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
16244
16340
  let filenames;
16245
16341
  try {
16246
- filenames = readdirSync(SLACK_PENDING_INBOUND_DIR);
16342
+ filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
16247
16343
  } catch {
16248
16344
  return;
16249
16345
  }
@@ -16255,12 +16351,12 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16255
16351
  if (!filename.startsWith(channelPrefix)) continue;
16256
16352
  if (!filename.endsWith(messageSuffix)) continue;
16257
16353
  try {
16258
- unlinkSync2(join3(SLACK_PENDING_INBOUND_DIR, filename));
16354
+ unlinkSync2(join4(SLACK_PENDING_INBOUND_DIR, filename));
16259
16355
  } catch {
16260
16356
  }
16261
16357
  }
16262
16358
  }
16263
- var RESTART_FLAGS_DIR = join3(homedir2(), ".augmented", "restart-flags");
16359
+ var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
16264
16360
  function buildAugmentedSlackMetadata() {
16265
16361
  if (!AGT_TEAM_ID) return void 0;
16266
16362
  return {
@@ -16442,7 +16538,7 @@ async function handleSlashCommandEnvelope(payload) {
16442
16538
  if (!existsSync2(RESTART_FLAGS_DIR)) {
16443
16539
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16444
16540
  }
16445
- const flagPath = join3(RESTART_FLAGS_DIR, `${codeName}.flag`);
16541
+ const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
16446
16542
  const flag = {
16447
16543
  codeName,
16448
16544
  source: "slack",
@@ -16549,7 +16645,7 @@ async function handleRestartCommand(opts) {
16549
16645
  if (!existsSync2(RESTART_FLAGS_DIR)) {
16550
16646
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16551
16647
  }
16552
- const flagPath = join3(RESTART_FLAGS_DIR, `${codeName}.flag`);
16648
+ const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
16553
16649
  const flag = {
16554
16650
  codeName,
16555
16651
  source: "slack",
@@ -16608,7 +16704,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
16608
16704
  var threadPersister = null;
16609
16705
  function resolveThreadStorePath() {
16610
16706
  if (!AGENT_CODE_NAME) return null;
16611
- return join3(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16707
+ return join4(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16612
16708
  }
16613
16709
  function parseTtlDays(raw) {
16614
16710
  if (!raw) return void 0;
@@ -16643,9 +16739,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
16643
16739
  var slackStderrLogStream = null;
16644
16740
  if (AGENT_CODE_NAME) {
16645
16741
  try {
16646
- const logDir = join3(homedir2(), ".augmented", AGENT_CODE_NAME);
16742
+ const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
16647
16743
  mkdirSync3(logDir, { recursive: true });
16648
- slackStderrLogStream = createWriteStream(join3(logDir, "slack-channel-stderr.log"), {
16744
+ slackStderrLogStream = createWriteStream(join4(logDir, "slack-channel-stderr.log"), {
16649
16745
  flags: "a",
16650
16746
  mode: 384
16651
16747
  });
@@ -16734,7 +16830,7 @@ var mcp = new Server(
16734
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.",
16735
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.",
16736
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).',
16737
- "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).",
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.)",
16738
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.`,
16739
16835
  "To deliver a file: save under your project dir, call slack.upload_file with path + channel + thread_ts."
16740
16836
  ].join(" ")
@@ -16786,7 +16882,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16786
16882
  },
16787
16883
  {
16788
16884
  name: "slack.react",
16789
- 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.",
16790
16886
  inputSchema: {
16791
16887
  type: "object",
16792
16888
  properties: {
@@ -17238,7 +17334,7 @@ ${result.formatted}` : "Thread is empty or not found."
17238
17334
  };
17239
17335
  }
17240
17336
  size = stat.size;
17241
- bytes = readFileSync3(resolvedPath);
17337
+ bytes = readFileSync4(resolvedPath);
17242
17338
  } catch (err) {
17243
17339
  return {
17244
17340
  content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
@@ -18258,14 +18354,24 @@ async function connectSocketMode() {
18258
18354
  isAutoFollowed,
18259
18355
  botUserId
18260
18356
  });
18261
- if (decideSlackAckReaction({ channel, ts })) {
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";
18262
18368
  fetch("https://slack.com/api/reactions.add", {
18263
18369
  method: "POST",
18264
18370
  headers: {
18265
18371
  "Content-Type": "application/json",
18266
18372
  Authorization: `Bearer ${BOT_TOKEN}`
18267
18373
  },
18268
- body: JSON.stringify({ channel, timestamp: ts, name: "eyes" })
18374
+ body: JSON.stringify({ channel, timestamp: ts, name: reactionName })
18269
18375
  }).catch(() => {
18270
18376
  });
18271
18377
  }