@integrity-labs/agt-cli 0.27.170 → 0.28.2

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.
@@ -14452,7 +14452,7 @@ function watchSuccessTextSlack(fileId, durationMs, reused) {
14452
14452
  }
14453
14453
 
14454
14454
  // src/ack-reaction.ts
14455
- import { readdirSync, readFileSync as readFileSync2 } from "fs";
14455
+ import { readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
14456
14456
  import { join as join2 } from "path";
14457
14457
 
14458
14458
  // src/flags-cache-read.ts
@@ -14498,19 +14498,23 @@ var ACK_STARTUP_GRACE_MS = 6e4;
14498
14498
  var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
14499
14499
  function decideAckReaction(i) {
14500
14500
  if (!i.hasTarget) return "none";
14501
- if (!i.integrationReady) return "undeliverable";
14502
- if (i.tmux === "dead") return "undeliverable";
14503
- if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
14501
+ return classifyUndeliverableCause(i) !== null ? "undeliverable" : "ack";
14502
+ }
14503
+ function classifyUndeliverableCause(i) {
14504
+ if (!i.hasTarget) return null;
14505
+ if (!i.integrationReady) return "integration_down";
14506
+ if (i.tmux === "dead") return "session_dead";
14507
+ if (!i.withinStartupGrace && i.claude === "dead") return "session_dead";
14504
14508
  const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
14505
14509
  if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
14506
14510
  const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
14507
14511
  const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
14508
14512
  if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
14509
- return "ack";
14513
+ return null;
14510
14514
  }
14511
- return "undeliverable";
14515
+ return "wedged";
14512
14516
  }
14513
- return "ack";
14517
+ return null;
14514
14518
  }
14515
14519
  function decideRecoveryHeal(i) {
14516
14520
  return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
@@ -14521,8 +14525,8 @@ var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
14521
14525
  function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
14522
14526
  return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
14523
14527
  }
14524
- function undeliverableNoticeText() {
14525
- return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
14528
+ function undeliverableNoticeText(replayEnabled = channelReplayEnabled()) {
14529
+ return replayEnabled ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
14526
14530
  }
14527
14531
  var BUSY_ACK_THRESHOLD_MS = 9e4;
14528
14532
  var BUSY_ACK_NOTICE_THROTTLE_MS = 10 * 60 * 1e3;
@@ -14617,6 +14621,37 @@ var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
14617
14621
  function orphanSweepIntervalMs() {
14618
14622
  return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
14619
14623
  }
14624
+ var MAX_MARKER_REPLAYS = 3;
14625
+ function channelReplayEnabled() {
14626
+ const v = process.env.AGT_CHANNEL_REPLAY_ENABLED;
14627
+ return v === "1" || v === "true";
14628
+ }
14629
+ function shouldReplayMarker(i) {
14630
+ if (!i.enabled) return false;
14631
+ if (!i.hasPayload) return false;
14632
+ if (!i.sessionAlive) return false;
14633
+ if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
14634
+ return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
14635
+ }
14636
+ var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
14637
+ function deflectionCounterPath(agentDir, channel) {
14638
+ return join2(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
14639
+ }
14640
+ function recordChannelDeflection(agentDir, channel, cause) {
14641
+ if (!agentDir) return;
14642
+ const path = deflectionCounterPath(agentDir, channel);
14643
+ let counts = {};
14644
+ try {
14645
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
14646
+ if (parsed && typeof parsed === "object") counts = parsed;
14647
+ } catch {
14648
+ }
14649
+ counts[cause] = (counts[cause] ?? 0) + 1;
14650
+ try {
14651
+ writeFileSync(path, JSON.stringify(counts), { mode: 384 });
14652
+ } catch {
14653
+ }
14654
+ }
14620
14655
 
14621
14656
  // src/session-probe-runtime.ts
14622
14657
  import { execFileSync } from "child_process";
@@ -15253,7 +15288,7 @@ function resolveReplyThreadTs(input) {
15253
15288
  }
15254
15289
 
15255
15290
  // src/restart-confirm.ts
15256
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
15291
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
15257
15292
  import { dirname } from "path";
15258
15293
  import { randomUUID } from "crypto";
15259
15294
  var RESTART_CONFIRM_MAX_AGE_MS = 10 * 60 * 1e3;
@@ -15273,7 +15308,7 @@ function writeRestartConfirmMarker(filePath, marker) {
15273
15308
  const dir = dirname(filePath);
15274
15309
  if (!existsSync4(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
15275
15310
  const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
15276
- writeFileSync(tmpPath, JSON.stringify(marker) + "\n", { encoding: "utf8", mode: 384 });
15311
+ writeFileSync2(tmpPath, JSON.stringify(marker) + "\n", { encoding: "utf8", mode: 384 });
15277
15312
  renameSync(tmpPath, filePath);
15278
15313
  }
15279
15314
  function readRestartConfirmMarker(filePath) {
@@ -15388,23 +15423,27 @@ var StdioServerTransport = class {
15388
15423
  // src/slack-channel.ts
15389
15424
  import {
15390
15425
  chmodSync,
15426
+ closeSync,
15391
15427
  createWriteStream,
15392
15428
  existsSync as existsSync7,
15429
+ ftruncateSync,
15393
15430
  mkdirSync as mkdirSync5,
15431
+ openSync,
15394
15432
  readFileSync as readFileSync8,
15395
15433
  readdirSync as readdirSync3,
15396
15434
  renameSync as renameSync3,
15397
15435
  statSync as statSync2,
15398
15436
  unlinkSync as unlinkSync4,
15399
15437
  watch,
15400
- writeFileSync as writeFileSync5
15438
+ writeFileSync as writeFileSync6,
15439
+ writeSync
15401
15440
  } from "fs";
15402
15441
  import { basename, join as join7, resolve as resolve2 } from "path";
15403
15442
  import { homedir as homedir3 } from "os";
15404
15443
  import { createHash, randomUUID as randomUUID2 } from "crypto";
15405
15444
 
15406
15445
  // src/slack-thread-store.ts
15407
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
15446
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
15408
15447
  import { dirname as dirname2 } from "path";
15409
15448
  var FILE_VERSION = 1;
15410
15449
  var DEFAULT_TTL_DAYS = 30;
@@ -15464,7 +15503,7 @@ function createThreadPersister(opts) {
15464
15503
  const writeNow = (snap) => {
15465
15504
  try {
15466
15505
  mkdirSync2(dirname2(opts.filePath), { recursive: true });
15467
- writeFileSync2(opts.filePath, serializeThreadStore(snap), "utf-8");
15506
+ writeFileSync3(opts.filePath, serializeThreadStore(snap), "utf-8");
15468
15507
  lastWriteAt = Date.now();
15469
15508
  } catch (err) {
15470
15509
  opts.onError?.(
@@ -15533,7 +15572,7 @@ async function runOrRetry(fn, opts) {
15533
15572
  }
15534
15573
 
15535
15574
  // src/slack-bot-photo.ts
15536
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
15575
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
15537
15576
  import { dirname as dirname3 } from "path";
15538
15577
  async function applyBotPhoto(opts) {
15539
15578
  const fetchImpl = opts.fetchImpl ?? fetch;
@@ -15585,7 +15624,7 @@ async function applyBotPhoto(opts) {
15585
15624
  if (markerPath) {
15586
15625
  try {
15587
15626
  mkdirSync3(dirname3(markerPath), { recursive: true, mode: 448 });
15588
- writeFileSync3(markerPath, avatarUrl, { mode: 384 });
15627
+ writeFileSync4(markerPath, avatarUrl, { mode: 384 });
15589
15628
  } catch {
15590
15629
  }
15591
15630
  }
@@ -16341,7 +16380,7 @@ import {
16341
16380
  readFileSync as readFileSync7,
16342
16381
  renameSync as renameSync2,
16343
16382
  unlinkSync as unlinkSync3,
16344
- writeFileSync as writeFileSync4
16383
+ writeFileSync as writeFileSync5
16345
16384
  } from "fs";
16346
16385
  import { join as join6 } from "path";
16347
16386
  function defaultIsPidAlive(pid) {
@@ -16374,7 +16413,7 @@ function acquireMcpSpawnLock(args) {
16374
16413
  mkdirSync4(agentDir, { recursive: true, mode: 448 });
16375
16414
  const tmpPath = `${path}.${selfPid}.tmp`;
16376
16415
  const payload = { pid: selfPid, started_at: now() };
16377
- writeFileSync4(tmpPath, JSON.stringify(payload), { mode: 384 });
16416
+ writeFileSync5(tmpPath, JSON.stringify(payload), { mode: 384 });
16378
16417
  renameSync2(tmpPath, path);
16379
16418
  return { kind: "acquired", path };
16380
16419
  }
@@ -16608,7 +16647,7 @@ function slackPendingInboundPath(channel, threadTs, messageTs) {
16608
16647
  if (!SLACK_PENDING_INBOUND_DIR) return null;
16609
16648
  return join7(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
16610
16649
  }
16611
- function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
16650
+ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false, payload) {
16612
16651
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
16613
16652
  if (!path || !SLACK_PENDING_INBOUND_DIR) return;
16614
16653
  const marker = {
@@ -16618,11 +16657,14 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
16618
16657
  received_at: (/* @__PURE__ */ new Date()).toISOString(),
16619
16658
  // Only persist the flags when set — keeps healthy-path markers byte-identical.
16620
16659
  ...undeliverable ? { undeliverable: true } : {},
16621
- ...discretionary ? { discretionary: true } : {}
16660
+ ...discretionary ? { discretionary: true } : {},
16661
+ // ENG-5969/ENG-6327: carry the replay payload only when provided (the
16662
+ // durable-replay path). Absent ⇒ the marker can't be replayed.
16663
+ ...payload ? { payload } : {}
16622
16664
  };
16623
16665
  try {
16624
16666
  mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
16625
- writeFileSync5(path, JSON.stringify(marker), { mode: 384 });
16667
+ writeFileSync6(path, JSON.stringify(marker), { mode: 384 });
16626
16668
  } catch (err) {
16627
16669
  process.stderr.write(
16628
16670
  `slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
@@ -16630,6 +16672,35 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
16630
16672
  );
16631
16673
  }
16632
16674
  }
16675
+ function rewriteSlackMarkerInPlace(path, marker) {
16676
+ let fd;
16677
+ try {
16678
+ fd = openSync(path, "r+");
16679
+ const buf = Buffer.from(JSON.stringify(marker));
16680
+ ftruncateSync(fd, 0);
16681
+ writeSync(fd, buf, 0, buf.length, 0);
16682
+ } catch {
16683
+ } finally {
16684
+ if (fd !== void 0) {
16685
+ try {
16686
+ closeSync(fd);
16687
+ } catch {
16688
+ }
16689
+ }
16690
+ }
16691
+ }
16692
+ function attachSlackReplayPayload(channel, threadTs, messageTs, payload) {
16693
+ const path = slackPendingInboundPath(channel, threadTs, messageTs);
16694
+ if (!path) return;
16695
+ let marker;
16696
+ try {
16697
+ marker = JSON.parse(readFileSync8(path, "utf-8"));
16698
+ } catch {
16699
+ return;
16700
+ }
16701
+ marker.payload = payload;
16702
+ rewriteSlackMarkerInPlace(path, marker);
16703
+ }
16633
16704
  function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
16634
16705
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
16635
16706
  if (!path || !existsSync7(path)) return null;
@@ -16743,7 +16814,14 @@ function scheduleBusyAck(channel, threadTs, messageTs, isThreadReply) {
16743
16814
  paneLogFreshAgeMs,
16744
16815
  thresholdMs
16745
16816
  });
16746
- if (post) postBusyAckNotice(channel, threadTs, isThreadReply);
16817
+ if (post) {
16818
+ recordChannelDeflection(SLACK_AGENT_DIR, "slack", "busy");
16819
+ process.stderr.write(
16820
+ `slack-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=busy channel=slack ts=${redactSlackId(messageTs)}
16821
+ `
16822
+ );
16823
+ postBusyAckNotice(channel, threadTs, isThreadReply);
16824
+ }
16747
16825
  }, thresholdMs);
16748
16826
  timer.unref();
16749
16827
  }
@@ -16967,8 +17045,8 @@ function startSlackRecoveryOutboxWatcher() {
16967
17045
  startSlackRecoveryOutboxWatcher();
16968
17046
  var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
16969
17047
  var DISCRETIONARY_MARKER_MS = 10 * 60 * 1e3;
16970
- function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
16971
- writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable, discretionary);
17048
+ function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false, discretionary = false, payload) {
17049
+ writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable, discretionary, payload);
16972
17050
  }
16973
17051
  function sweepSlackStaleMarkers(thresholdMs) {
16974
17052
  if (!SLACK_PENDING_INBOUND_DIR) return;
@@ -17684,7 +17762,7 @@ async function handleSlashCommandEnvelope(payload) {
17684
17762
  }
17685
17763
  };
17686
17764
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
17687
- writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17765
+ writeFileSync6(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17688
17766
  renameSync3(tmpPath, flagPath);
17689
17767
  process.stderr.write(
17690
17768
  `slack-channel(${codeName}): /restart slash-command queued from channel ${hashChannelId(payload.channel_id)}
@@ -17803,7 +17881,7 @@ async function handleRestartCommand(opts) {
17803
17881
  }
17804
17882
  };
17805
17883
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
17806
- writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17884
+ writeFileSync6(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17807
17885
  renameSync3(tmpPath, flagPath);
17808
17886
  process.stderr.write(
17809
17887
  `slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
@@ -19164,7 +19242,7 @@ async function downloadSlackFile(fileId, codeName) {
19164
19242
  throw new Error(`refusing to write ${savedPath} outside ${dir}`);
19165
19243
  }
19166
19244
  mkdirSync5(dir, { recursive: true });
19167
- writeFileSync5(savedPath, bytes, { mode: 384 });
19245
+ writeFileSync6(savedPath, bytes, { mode: 384 });
19168
19246
  try {
19169
19247
  chmodSync(savedPath, 384);
19170
19248
  } catch {
@@ -19216,6 +19294,77 @@ async function buildInboundFileMeta(rawFiles, codeName, channel) {
19216
19294
  return out;
19217
19295
  }
19218
19296
  await mcp.connect(new StdioServerTransport());
19297
+ var SLACK_REPLAY_SCAN_INTERVAL_MS = 6e4;
19298
+ async function replayPendingSlackMarkers() {
19299
+ if (!channelReplayEnabled()) return;
19300
+ if (!SLACK_PENDING_INBOUND_DIR || !existsSync7(SLACK_PENDING_INBOUND_DIR)) return;
19301
+ const probe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
19302
+ const sessionAlive = probe.tmux === "alive" && probe.claude === "alive";
19303
+ if (!sessionAlive) return;
19304
+ let filenames;
19305
+ try {
19306
+ filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
19307
+ } catch {
19308
+ return;
19309
+ }
19310
+ const now = Date.now();
19311
+ const entries = [];
19312
+ for (const name of filenames) {
19313
+ if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
19314
+ const fullPath = join7(SLACK_PENDING_INBOUND_DIR, name);
19315
+ let marker;
19316
+ try {
19317
+ marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
19318
+ } catch {
19319
+ continue;
19320
+ }
19321
+ const t = Date.parse(marker.received_at ?? "");
19322
+ const ageMs = Number.isFinite(t) ? now - t : 0;
19323
+ entries.push({ path: fullPath, marker, ageMs });
19324
+ }
19325
+ entries.sort((a, b) => b.ageMs - a.ageMs);
19326
+ for (const { path, marker, ageMs } of entries) {
19327
+ if (!shouldReplayMarker({
19328
+ enabled: true,
19329
+ hasPayload: Boolean(marker.payload),
19330
+ sessionAlive,
19331
+ markerAgeMs: ageMs,
19332
+ replayCount: marker.replay_count ?? 0
19333
+ })) {
19334
+ continue;
19335
+ }
19336
+ const payload = marker.payload;
19337
+ const replayParams = {
19338
+ ...payload,
19339
+ meta: { ...payload.meta, replayed: "true" }
19340
+ };
19341
+ try {
19342
+ await mcp.notification({
19343
+ method: "notifications/claude/channel",
19344
+ params: replayParams
19345
+ });
19346
+ } catch (err) {
19347
+ process.stderr.write(
19348
+ `slack-channel(${AGENT_CODE_NAME}): replay push failed: ${err.message}
19349
+ `
19350
+ );
19351
+ continue;
19352
+ }
19353
+ rewriteSlackMarkerInPlace(path, {
19354
+ ...marker,
19355
+ replay_count: (marker.replay_count ?? 0) + 1
19356
+ });
19357
+ }
19358
+ }
19359
+ var slackReplayScanInFlight = false;
19360
+ var slackReplayTimer = setInterval(() => {
19361
+ if (slackReplayScanInFlight) return;
19362
+ slackReplayScanInFlight = true;
19363
+ void replayPendingSlackMarkers().finally(() => {
19364
+ slackReplayScanInFlight = false;
19365
+ });
19366
+ }, SLACK_REPLAY_SCAN_INTERVAL_MS);
19367
+ slackReplayTimer.unref?.();
19219
19368
  var userNameCache = /* @__PURE__ */ new Map();
19220
19369
  async function resolveUserName(userId) {
19221
19370
  if (!userId || userId === "unknown") return userId ?? "unknown";
@@ -19530,7 +19679,7 @@ async function connectSocketMode() {
19530
19679
  } catch {
19531
19680
  }
19532
19681
  }
19533
- const ackDecision = decideAckReaction({
19682
+ const ackInputs = {
19534
19683
  hasTarget: decideSlackAckReaction({ channel, ts }),
19535
19684
  integrationReady: Boolean(BOT_TOKEN),
19536
19685
  tmux: ackProbe.tmux,
@@ -19538,7 +19687,16 @@ async function connectSocketMode() {
19538
19687
  withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
19539
19688
  oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR),
19540
19689
  paneLogFreshAgeMs
19541
- });
19690
+ };
19691
+ const ackDecision = decideAckReaction(ackInputs);
19692
+ if (ackDecision === "undeliverable") {
19693
+ const cause = classifyUndeliverableCause(ackInputs) ?? "unknown";
19694
+ recordChannelDeflection(SLACK_AGENT_DIR, "slack", cause);
19695
+ process.stderr.write(
19696
+ `slack-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=${cause} channel=slack ts=${redactSlackId(ts)}
19697
+ `
19698
+ );
19699
+ }
19542
19700
  if (ackDecision !== "none") {
19543
19701
  const reactionName = ackDecision === "undeliverable" ? SLACK_UNDELIVERABLE_REACTION : "eyes";
19544
19702
  fetch("https://slack.com/api/reactions.add", {
@@ -19596,32 +19754,36 @@ async function connectSocketMode() {
19596
19754
  `
19597
19755
  );
19598
19756
  }
19757
+ const replayPayload = {
19758
+ content: inboundContent,
19759
+ meta: {
19760
+ user,
19761
+ user_name: userName,
19762
+ channel,
19763
+ thread_ts: threadTs,
19764
+ // ENG-5861: explicit message_ts so the agent can pass it back
19765
+ // to slack.reply for reliable pending-inbound cleanup. For
19766
+ // top-level messages threadTs === ts; for threaded replies
19767
+ // threadTs is the thread root and message_ts is the specific
19768
+ // reply being addressed — different keys, both needed by the
19769
+ // marker-cleanup gate.
19770
+ message_ts: ts,
19771
+ event_type: evt.type,
19772
+ ...isAutoFollowed ? { auto_followed: "true" } : {},
19773
+ // Only set these when we actually have attachments to avoid
19774
+ // bloating every notification with empty metadata.
19775
+ ...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
19776
+ ...imagePath ? { image_path: imagePath } : {},
19777
+ // ENG-5830: the pre-loaded surrounding thread (thread replies only).
19778
+ ...threadContext ? { thread_context: threadContext } : {}
19779
+ }
19780
+ };
19781
+ if (channel && ts && armMarker) {
19782
+ attachSlackReplayPayload(channel, threadTs, ts, replayPayload);
19783
+ }
19599
19784
  await mcp.notification({
19600
19785
  method: "notifications/claude/channel",
19601
- params: {
19602
- content: inboundContent,
19603
- meta: {
19604
- user,
19605
- user_name: userName,
19606
- channel,
19607
- thread_ts: threadTs,
19608
- // ENG-5861: explicit message_ts so the agent can pass it back
19609
- // to slack.reply for reliable pending-inbound cleanup. For
19610
- // top-level messages threadTs === ts; for threaded replies
19611
- // threadTs is the thread root and message_ts is the specific
19612
- // reply being addressed — different keys, both needed by the
19613
- // marker-cleanup gate.
19614
- message_ts: ts,
19615
- event_type: evt.type,
19616
- ...isAutoFollowed ? { auto_followed: "true" } : {},
19617
- // Only set these when we actually have attachments to avoid
19618
- // bloating every notification with empty metadata.
19619
- ...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
19620
- ...imagePath ? { image_path: imagePath } : {},
19621
- // ENG-5830: the pre-loaded surrounding thread (thread replies only).
19622
- ...threadContext ? { thread_context: threadContext } : {}
19623
- }
19624
- }
19786
+ params: replayPayload
19625
19787
  });
19626
19788
  if (channel) {
19627
19789
  conversationIngestClient?.ingest({
@@ -14317,16 +14317,20 @@ var StdioServerTransport = class {
14317
14317
  import https from "https";
14318
14318
  import { createHash, randomUUID as randomUUID2 } from "crypto";
14319
14319
  import {
14320
+ closeSync,
14320
14321
  createWriteStream,
14321
14322
  existsSync as existsSync5,
14323
+ ftruncateSync,
14322
14324
  mkdirSync as mkdirSync4,
14325
+ openSync,
14323
14326
  readFileSync as readFileSync7,
14324
14327
  readdirSync as readdirSync3,
14325
14328
  renameSync as renameSync4,
14326
14329
  statSync as statSync2,
14327
14330
  unlinkSync as unlinkSync4,
14328
14331
  watch,
14329
- writeFileSync as writeFileSync4
14332
+ writeFileSync as writeFileSync5,
14333
+ writeSync
14330
14334
  } from "fs";
14331
14335
  import { homedir as homedir3 } from "os";
14332
14336
  import { join as join7 } from "path";
@@ -16107,7 +16111,7 @@ function readLockHolder(path) {
16107
16111
  }
16108
16112
 
16109
16113
  // src/ack-reaction.ts
16110
- import { readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
16114
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
16111
16115
  import { join as join6 } from "path";
16112
16116
 
16113
16117
  // src/flags-cache-read.ts
@@ -16153,19 +16157,23 @@ var ACK_STARTUP_GRACE_MS = 6e4;
16153
16157
  var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
16154
16158
  function decideAckReaction(i) {
16155
16159
  if (!i.hasTarget) return "none";
16156
- if (!i.integrationReady) return "undeliverable";
16157
- if (i.tmux === "dead") return "undeliverable";
16158
- if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
16160
+ return classifyUndeliverableCause(i) !== null ? "undeliverable" : "ack";
16161
+ }
16162
+ function classifyUndeliverableCause(i) {
16163
+ if (!i.hasTarget) return null;
16164
+ if (!i.integrationReady) return "integration_down";
16165
+ if (i.tmux === "dead") return "session_dead";
16166
+ if (!i.withinStartupGrace && i.claude === "dead") return "session_dead";
16159
16167
  const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
16160
16168
  if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
16161
16169
  const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
16162
16170
  const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
16163
16171
  if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
16164
- return "ack";
16172
+ return null;
16165
16173
  }
16166
- return "undeliverable";
16174
+ return "wedged";
16167
16175
  }
16168
- return "ack";
16176
+ return null;
16169
16177
  }
16170
16178
  function decideRecoveryHeal(i) {
16171
16179
  return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
@@ -16174,8 +16182,8 @@ var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
16174
16182
  function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
16175
16183
  return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
16176
16184
  }
16177
- function undeliverableNoticeText() {
16178
- return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
16185
+ function undeliverableNoticeText(replayEnabled = channelReplayEnabled()) {
16186
+ return replayEnabled ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
16179
16187
  }
16180
16188
  var BUSY_ACK_THRESHOLD_MS = 9e4;
16181
16189
  var BUSY_ACK_NOTICE_THROTTLE_MS = 10 * 60 * 1e3;
@@ -16282,6 +16290,25 @@ function shouldReplayMarker(i) {
16282
16290
  if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
16283
16291
  return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
16284
16292
  }
16293
+ var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
16294
+ function deflectionCounterPath(agentDir, channel) {
16295
+ return join6(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
16296
+ }
16297
+ function recordChannelDeflection(agentDir, channel, cause) {
16298
+ if (!agentDir) return;
16299
+ const path = deflectionCounterPath(agentDir, channel);
16300
+ let counts = {};
16301
+ try {
16302
+ const parsed = JSON.parse(readFileSync6(path, "utf-8"));
16303
+ if (parsed && typeof parsed === "object") counts = parsed;
16304
+ } catch {
16305
+ }
16306
+ counts[cause] = (counts[cause] ?? 0) + 1;
16307
+ try {
16308
+ writeFileSync4(path, JSON.stringify(counts), { mode: 384 });
16309
+ } catch {
16310
+ }
16311
+ }
16285
16312
 
16286
16313
  // src/telegram-channel.ts
16287
16314
  function redactId(id) {
@@ -16590,7 +16617,14 @@ function scheduleBusyAck(chatId, messageId) {
16590
16617
  paneLogFreshAgeMs,
16591
16618
  thresholdMs
16592
16619
  });
16593
- if (post) postBusyAckNotice(chatId, messageId);
16620
+ if (post) {
16621
+ recordChannelDeflection(AGENT_DIR, "telegram", "busy");
16622
+ process.stderr.write(
16623
+ `telegram-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=busy channel=telegram chat=${redactId(chatId)}
16624
+ `
16625
+ );
16626
+ postBusyAckNotice(chatId, messageId);
16627
+ }
16594
16628
  }, thresholdMs);
16595
16629
  timer.unref();
16596
16630
  }
@@ -16802,7 +16836,7 @@ async function handleRestartCommand(opts) {
16802
16836
  reply: { chat_id: opts.chatId, message_id: opts.messageId }
16803
16837
  };
16804
16838
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
16805
- writeFileSync4(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16839
+ writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16806
16840
  renameSync4(tmpPath, flagPath);
16807
16841
  process.stderr.write(
16808
16842
  `telegram-channel(${AGENT_CODE_NAME}): /restart queued from chat ${redactId(opts.chatId)}
@@ -17202,7 +17236,7 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
17202
17236
  };
17203
17237
  try {
17204
17238
  mkdirSync4(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
17205
- writeFileSync4(path, JSON.stringify(marker), { mode: 384 });
17239
+ writeFileSync5(path, JSON.stringify(marker), { mode: 384 });
17206
17240
  } catch (err) {
17207
17241
  process.stderr.write(
17208
17242
  `telegram-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
@@ -17210,6 +17244,23 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
17210
17244
  );
17211
17245
  }
17212
17246
  }
17247
+ function rewriteTelegramMarkerInPlace(path, marker) {
17248
+ let fd;
17249
+ try {
17250
+ fd = openSync(path, "r+");
17251
+ const buf = Buffer.from(JSON.stringify(marker));
17252
+ ftruncateSync(fd, 0);
17253
+ writeSync(fd, buf, 0, buf.length, 0);
17254
+ } catch {
17255
+ } finally {
17256
+ if (fd !== void 0) {
17257
+ try {
17258
+ closeSync(fd);
17259
+ } catch {
17260
+ }
17261
+ }
17262
+ }
17263
+ }
17213
17264
  function clearTelegramMarkerFileWithHeal(fullPath) {
17214
17265
  let marker = null;
17215
17266
  try {
@@ -18232,10 +18283,15 @@ async function replayPendingTelegramMarkers() {
18232
18283
  })) {
18233
18284
  continue;
18234
18285
  }
18286
+ const replayPayload = marker.payload;
18287
+ const replayParams = {
18288
+ ...replayPayload,
18289
+ meta: { ...replayPayload.meta, replayed: "true" }
18290
+ };
18235
18291
  try {
18236
18292
  await mcp.notification({
18237
18293
  method: "notifications/claude/channel",
18238
- params: marker.payload
18294
+ params: replayParams
18239
18295
  });
18240
18296
  } catch (err) {
18241
18297
  process.stderr.write(
@@ -18244,20 +18300,19 @@ async function replayPendingTelegramMarkers() {
18244
18300
  );
18245
18301
  continue;
18246
18302
  }
18247
- try {
18248
- if (existsSync5(path)) {
18249
- const updated = {
18250
- ...marker,
18251
- replay_count: (marker.replay_count ?? 0) + 1
18252
- };
18253
- writeFileSync4(path, JSON.stringify(updated), { mode: 384 });
18254
- }
18255
- } catch {
18256
- }
18303
+ rewriteTelegramMarkerInPlace(path, {
18304
+ ...marker,
18305
+ replay_count: (marker.replay_count ?? 0) + 1
18306
+ });
18257
18307
  }
18258
18308
  }
18309
+ var telegramReplayScanInFlight = false;
18259
18310
  var replayTimer = setInterval(() => {
18260
- void replayPendingTelegramMarkers();
18311
+ if (telegramReplayScanInFlight) return;
18312
+ telegramReplayScanInFlight = true;
18313
+ void replayPendingTelegramMarkers().finally(() => {
18314
+ telegramReplayScanInFlight = false;
18315
+ });
18261
18316
  }, REPLAY_SCAN_INTERVAL_MS);
18262
18317
  replayTimer.unref?.();
18263
18318
  var LONG_POLL_SECONDS = 50;
@@ -18626,7 +18681,7 @@ async function pollLoop() {
18626
18681
  } catch {
18627
18682
  }
18628
18683
  }
18629
- const ackDecision = decideAckReaction({
18684
+ const ackInputs = {
18630
18685
  hasTarget: Boolean(chatId && messageId),
18631
18686
  integrationReady: Boolean(BOT_TOKEN),
18632
18687
  tmux: ackProbe.tmux,
@@ -18634,7 +18689,16 @@ async function pollLoop() {
18634
18689
  withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
18635
18690
  oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR),
18636
18691
  paneLogFreshAgeMs
18637
- });
18692
+ };
18693
+ const ackDecision = decideAckReaction(ackInputs);
18694
+ if (ackDecision === "undeliverable") {
18695
+ const cause = classifyUndeliverableCause(ackInputs) ?? "unknown";
18696
+ recordChannelDeflection(AGENT_DIR, "telegram", cause);
18697
+ process.stderr.write(
18698
+ `telegram-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=${cause} channel=telegram chat=${redactId(chatId)}
18699
+ `
18700
+ );
18701
+ }
18638
18702
  if (ackDecision === "ack") {
18639
18703
  void setMessageReaction(chatId, messageId, ACK_EMOJI);
18640
18704
  } else if (ackDecision === "undeliverable") {