@integrity-labs/agt-cli 0.27.26 → 0.27.28

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.
@@ -15,7 +15,7 @@ import {
15
15
  provisionOrientHook,
16
16
  provisionStopHook,
17
17
  requireHost
18
- } from "../chunk-S53Y2M2Q.js";
18
+ } from "../chunk-2ZTHGYH7.js";
19
19
  import {
20
20
  getProjectDir as getProjectDir2,
21
21
  getReadyTasks,
@@ -3208,7 +3208,7 @@ var cachedFrameworkVersion = null;
3208
3208
  var lastVersionCheckAt = 0;
3209
3209
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3210
3210
  var lastResponsivenessProbeAt = 0;
3211
- var agtCliVersion = true ? "0.27.26" : "dev";
3211
+ var agtCliVersion = true ? "0.27.28" : "dev";
3212
3212
  function resolveBrewPath(execFileSync4) {
3213
3213
  try {
3214
3214
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -14316,6 +14316,7 @@ import { readdirSync, readFileSync } from "fs";
14316
14316
  import { join } from "path";
14317
14317
  var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
14318
14318
  var ACK_STARTUP_GRACE_MS = 6e4;
14319
+ var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
14319
14320
  function decideAckReaction(i) {
14320
14321
  if (!i.hasTarget) return "none";
14321
14322
  if (!i.integrationReady) return "undeliverable";
@@ -14323,6 +14324,11 @@ function decideAckReaction(i) {
14323
14324
  if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
14324
14325
  const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
14325
14326
  if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
14327
+ const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
14328
+ const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
14329
+ if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
14330
+ return "ack";
14331
+ }
14326
14332
  return "undeliverable";
14327
14333
  }
14328
14334
  return "ack";
@@ -14617,6 +14623,69 @@ var SLACK_EGRESS_TOOLS = /* @__PURE__ */ new Set([
14617
14623
  "slack.upload_file"
14618
14624
  ]);
14619
14625
 
14626
+ // src/slack-pending-inbound-cleanup.ts
14627
+ import { existsSync, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
14628
+ import { join as join2 } from "path";
14629
+ function sanitizeMarkerSegment(value) {
14630
+ return value.replace(/[^A-Za-z0-9_-]/g, "_");
14631
+ }
14632
+ var defaultClearMarkerFile = (fullPath) => {
14633
+ try {
14634
+ if (existsSync(fullPath)) unlinkSync(fullPath);
14635
+ } catch {
14636
+ }
14637
+ };
14638
+ function clearAllSlackPendingMarkersForThread(dir, channel, threadTs, clear = defaultClearMarkerFile) {
14639
+ if (!dir) return 0;
14640
+ const prefix = `${sanitizeMarkerSegment(channel)}__${sanitizeMarkerSegment(threadTs)}__`;
14641
+ let cleared = 0;
14642
+ try {
14643
+ for (const f of readdirSync2(dir)) {
14644
+ if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
14645
+ clear(join2(dir, f));
14646
+ cleared += 1;
14647
+ }
14648
+ } catch {
14649
+ }
14650
+ return cleared;
14651
+ }
14652
+ function clearSlackPendingMarkerByMessageTs(dir, channel, messageTs, clear = defaultClearMarkerFile) {
14653
+ if (!dir) return 0;
14654
+ const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
14655
+ const messageSuffix = `__${sanitizeMarkerSegment(messageTs)}.json`;
14656
+ let cleared = 0;
14657
+ try {
14658
+ for (const f of readdirSync2(dir)) {
14659
+ if (!f.startsWith(channelPrefix) || !f.endsWith(messageSuffix)) continue;
14660
+ clear(join2(dir, f));
14661
+ cleared += 1;
14662
+ }
14663
+ } catch {
14664
+ }
14665
+ return cleared;
14666
+ }
14667
+ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultClearMarkerFile) {
14668
+ if (!dir) return null;
14669
+ const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
14670
+ try {
14671
+ const entries = readdirSync2(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
14672
+ const full = join2(dir, f);
14673
+ let mtime = 0;
14674
+ try {
14675
+ mtime = statSync(full).mtimeMs;
14676
+ } catch {
14677
+ }
14678
+ return { name: f, full, mtime };
14679
+ }).filter((e) => e.mtime > 0).sort((a, b) => a.mtime - b.mtime);
14680
+ const oldest = entries[0];
14681
+ if (!oldest) return null;
14682
+ clear(oldest.full);
14683
+ return oldest.name;
14684
+ } catch {
14685
+ return null;
14686
+ }
14687
+ }
14688
+
14620
14689
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
14621
14690
  import process2 from "process";
14622
14691
 
@@ -14713,17 +14782,17 @@ var StdioServerTransport = class {
14713
14782
  import {
14714
14783
  chmodSync,
14715
14784
  createWriteStream,
14716
- existsSync as existsSync2,
14785
+ existsSync as existsSync3,
14717
14786
  mkdirSync as mkdirSync3,
14718
14787
  readFileSync as readFileSync4,
14719
- readdirSync as readdirSync2,
14788
+ readdirSync as readdirSync3,
14720
14789
  renameSync as renameSync2,
14721
- statSync,
14722
- unlinkSync as unlinkSync2,
14790
+ statSync as statSync2,
14791
+ unlinkSync as unlinkSync3,
14723
14792
  watch,
14724
14793
  writeFileSync as writeFileSync3
14725
14794
  } from "fs";
14726
- import { basename, join as join4, resolve as resolve2 } from "path";
14795
+ import { basename, join as join5, resolve as resolve2 } from "path";
14727
14796
  import { homedir as homedir2 } from "os";
14728
14797
  import { createHash, randomUUID } from "crypto";
14729
14798
 
@@ -14854,9 +14923,9 @@ async function runOrRetry(fn, opts) {
14854
14923
 
14855
14924
  // src/channel-attachments.ts
14856
14925
  import { homedir } from "os";
14857
- import { join as join2, resolve, sep } from "path";
14926
+ import { join as join3, resolve, sep } from "path";
14858
14927
  function resolveChannelInboundDir(codeName, channelSlug) {
14859
- const base = join2(homedir(), ".augmented");
14928
+ const base = join3(homedir(), ".augmented");
14860
14929
  const allowedSegment = /^[A-Za-z0-9_-]+$/;
14861
14930
  if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
14862
14931
  throw new Error(
@@ -15500,14 +15569,14 @@ function createSlackBotUserIdClient(args) {
15500
15569
 
15501
15570
  // src/mcp-spawn-lock.ts
15502
15571
  import {
15503
- existsSync,
15572
+ existsSync as existsSync2,
15504
15573
  mkdirSync as mkdirSync2,
15505
15574
  readFileSync as readFileSync3,
15506
15575
  renameSync,
15507
- unlinkSync,
15576
+ unlinkSync as unlinkSync2,
15508
15577
  writeFileSync as writeFileSync2
15509
15578
  } from "fs";
15510
- import { join as join3 } from "path";
15579
+ import { join as join4 } from "path";
15511
15580
  function defaultIsPidAlive(pid) {
15512
15581
  if (!Number.isFinite(pid) || pid <= 0) return false;
15513
15582
  try {
@@ -15525,7 +15594,7 @@ function acquireMcpSpawnLock(args) {
15525
15594
  const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
15526
15595
  const selfPid = options.selfPid ?? process.pid;
15527
15596
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
15528
- const path = join3(agentDir, basename2);
15597
+ const path = join4(agentDir, basename2);
15529
15598
  const existing = readLockHolder(path);
15530
15599
  if (existing) {
15531
15600
  if (existing.pid === selfPid) {
@@ -15549,12 +15618,12 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
15549
15618
  if (!existing) return;
15550
15619
  if (existing.pid !== selfPid) return;
15551
15620
  try {
15552
- unlinkSync(lockPath);
15621
+ unlinkSync2(lockPath);
15553
15622
  } catch {
15554
15623
  }
15555
15624
  }
15556
15625
  function readLockHolder(path) {
15557
- if (!existsSync(path)) return null;
15626
+ if (!existsSync2(path)) return null;
15558
15627
  try {
15559
15628
  const raw = readFileSync3(path, "utf8");
15560
15629
  const parsed = JSON.parse(raw);
@@ -15710,9 +15779,9 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
15710
15779
  peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
15711
15780
  peer_disabled_mode: SLACK_PEER_DISABLED_MODE
15712
15781
  };
15713
- var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15714
- var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join4(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15715
- var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join4(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
15782
+ var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join5(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15783
+ var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join5(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15784
+ var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join5(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
15716
15785
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
15717
15786
  function redactSlackId(id) {
15718
15787
  if (!id) return "<none>";
@@ -15724,7 +15793,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
15724
15793
  }
15725
15794
  function slackPendingInboundPath(channel, threadTs, messageTs) {
15726
15795
  if (!SLACK_PENDING_INBOUND_DIR) return null;
15727
- return join4(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15796
+ return join5(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15728
15797
  }
15729
15798
  function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false) {
15730
15799
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
@@ -15780,22 +15849,32 @@ function clearSlackMarkerFileWithHeal(fullPath) {
15780
15849
  healSlackUndeliverable(marker.channel, marker.message_ts);
15781
15850
  }
15782
15851
  try {
15783
- if (existsSync2(fullPath)) unlinkSync2(fullPath);
15852
+ if (existsSync3(fullPath)) unlinkSync3(fullPath);
15784
15853
  } catch {
15785
15854
  }
15786
15855
  }
15787
- function clearAllSlackPendingMarkersForThread(channel, threadTs) {
15788
- if (!SLACK_PENDING_INBOUND_DIR) return;
15789
- const safeChan = channel.replace(/[^A-Za-z0-9_-]/g, "_");
15790
- const safeThread = threadTs.replace(/[^A-Za-z0-9_-]/g, "_");
15791
- const prefix = `${safeChan}__${safeThread}__`;
15792
- try {
15793
- for (const f of readdirSync2(SLACK_PENDING_INBOUND_DIR)) {
15794
- if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
15795
- clearSlackMarkerFileWithHeal(join4(SLACK_PENDING_INBOUND_DIR, f));
15796
- }
15797
- } catch {
15798
- }
15856
+ function clearAllSlackPendingMarkersForThread2(channel, threadTs) {
15857
+ clearAllSlackPendingMarkersForThread(
15858
+ SLACK_PENDING_INBOUND_DIR,
15859
+ channel,
15860
+ threadTs,
15861
+ clearSlackMarkerFileWithHeal
15862
+ );
15863
+ }
15864
+ function clearSlackPendingMarkerByMessageTs2(channel, messageTs) {
15865
+ clearSlackPendingMarkerByMessageTs(
15866
+ SLACK_PENDING_INBOUND_DIR,
15867
+ channel,
15868
+ messageTs,
15869
+ clearSlackMarkerFileWithHeal
15870
+ );
15871
+ }
15872
+ function clearOldestSlackPendingMarkerInChannel2(channel) {
15873
+ clearOldestSlackPendingMarkerInChannel(
15874
+ SLACK_PENDING_INBOUND_DIR,
15875
+ channel,
15876
+ clearSlackMarkerFileWithHeal
15877
+ );
15799
15878
  }
15800
15879
  function slackNextRetryName(filename) {
15801
15880
  const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
@@ -15811,7 +15890,7 @@ function slackNextRetryName(filename) {
15811
15890
  async function processSlackRecoveryOutboxFile(filename) {
15812
15891
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
15813
15892
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
15814
- const fullPath = join4(SLACK_RECOVERY_OUTBOX_DIR, filename);
15893
+ const fullPath = join5(SLACK_RECOVERY_OUTBOX_DIR, filename);
15815
15894
  let payload;
15816
15895
  try {
15817
15896
  payload = JSON.parse(readFileSync4(fullPath, "utf-8"));
@@ -15880,7 +15959,7 @@ async function processSlackRecoveryOutboxFile(filename) {
15880
15959
  }
15881
15960
  if (sendSucceeded) {
15882
15961
  try {
15883
- unlinkSync2(fullPath);
15962
+ unlinkSync3(fullPath);
15884
15963
  } catch {
15885
15964
  }
15886
15965
  return;
@@ -15888,7 +15967,7 @@ async function processSlackRecoveryOutboxFile(filename) {
15888
15967
  const next = slackNextRetryName(filename);
15889
15968
  if (next) {
15890
15969
  try {
15891
- renameSync2(fullPath, join4(SLACK_RECOVERY_OUTBOX_DIR, next.next));
15970
+ renameSync2(fullPath, join5(SLACK_RECOVERY_OUTBOX_DIR, next.next));
15892
15971
  if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
15893
15972
  process.stderr.write(
15894
15973
  `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -15918,7 +15997,7 @@ function scanSlackRecoveryRetries() {
15918
15997
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
15919
15998
  let entries;
15920
15999
  try {
15921
- entries = readdirSync2(SLACK_RECOVERY_OUTBOX_DIR);
16000
+ entries = readdirSync3(SLACK_RECOVERY_OUTBOX_DIR);
15922
16001
  } catch {
15923
16002
  return;
15924
16003
  }
@@ -15927,7 +16006,7 @@ function scanSlackRecoveryRetries() {
15927
16006
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
15928
16007
  let mtimeMs;
15929
16008
  try {
15930
- mtimeMs = statSync(join4(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16009
+ mtimeMs = statSync2(join5(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
15931
16010
  } catch {
15932
16011
  continue;
15933
16012
  }
@@ -15948,7 +16027,7 @@ function startSlackRecoveryOutboxWatcher() {
15948
16027
  return;
15949
16028
  }
15950
16029
  try {
15951
- for (const f of readdirSync2(SLACK_RECOVERY_OUTBOX_DIR)) {
16030
+ for (const f of readdirSync3(SLACK_RECOVERY_OUTBOX_DIR)) {
15952
16031
  if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
15953
16032
  }
15954
16033
  } catch {
@@ -15957,7 +16036,7 @@ function startSlackRecoveryOutboxWatcher() {
15957
16036
  const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
15958
16037
  if (event !== "rename" || !filename) return;
15959
16038
  if (!isFirstAttemptSlackOutboxFile(filename)) return;
15960
- if (existsSync2(join4(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
16039
+ if (existsSync3(join5(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
15961
16040
  void processSlackRecoveryOutboxFile(filename);
15962
16041
  }
15963
16042
  });
@@ -15978,10 +16057,10 @@ function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false
15978
16057
  }
15979
16058
  function sweepSlackStaleMarkersOnBoot() {
15980
16059
  if (!SLACK_PENDING_INBOUND_DIR) return;
15981
- if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
16060
+ if (!existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
15982
16061
  let filenames;
15983
16062
  try {
15984
- filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
16063
+ filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
15985
16064
  } catch (err) {
15986
16065
  process.stderr.write(
15987
16066
  `slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -15994,7 +16073,7 @@ function sweepSlackStaleMarkersOnBoot() {
15994
16073
  for (const filename of filenames) {
15995
16074
  if (!filename.endsWith(".json")) continue;
15996
16075
  if (filename.endsWith(".tmp")) continue;
15997
- const fullPath = join4(SLACK_PENDING_INBOUND_DIR, filename);
16076
+ const fullPath = join5(SLACK_PENDING_INBOUND_DIR, filename);
15998
16077
  let marker;
15999
16078
  try {
16000
16079
  marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
@@ -16004,7 +16083,7 @@ function sweepSlackStaleMarkersOnBoot() {
16004
16083
  `
16005
16084
  );
16006
16085
  try {
16007
- unlinkSync2(fullPath);
16086
+ unlinkSync3(fullPath);
16008
16087
  } catch {
16009
16088
  }
16010
16089
  cleared++;
@@ -16013,7 +16092,7 @@ function sweepSlackStaleMarkersOnBoot() {
16013
16092
  const { channel, thread_ts, message_ts, received_at } = marker;
16014
16093
  if (!channel || !thread_ts || !message_ts || !received_at) {
16015
16094
  try {
16016
- unlinkSync2(fullPath);
16095
+ unlinkSync3(fullPath);
16017
16096
  } catch {
16018
16097
  }
16019
16098
  cleared++;
@@ -16022,7 +16101,7 @@ function sweepSlackStaleMarkersOnBoot() {
16022
16101
  const receivedAtMs = Date.parse(received_at);
16023
16102
  if (!Number.isFinite(receivedAtMs) || receivedAtMs > now || now - receivedAtMs > STALE_MARKER_MS) {
16024
16103
  try {
16025
- unlinkSync2(fullPath);
16104
+ unlinkSync3(fullPath);
16026
16105
  } catch {
16027
16106
  }
16028
16107
  cleared++;
@@ -16037,7 +16116,7 @@ function sweepSlackStaleMarkersOnBoot() {
16037
16116
  }
16038
16117
  sweepSlackStaleMarkersOnBoot();
16039
16118
  function clearPendingMessage(channel, threadTs) {
16040
- clearAllSlackPendingMarkersForThread(channel, threadTs);
16119
+ clearAllSlackPendingMarkersForThread2(channel, threadTs);
16041
16120
  }
16042
16121
  function noteThreadActivity(channel, threadTs) {
16043
16122
  if (!channel || !threadTs) return;
@@ -16047,10 +16126,10 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16047
16126
  if (!channel || !messageTs) return;
16048
16127
  clearPendingMessage(channel, messageTs);
16049
16128
  if (!SLACK_PENDING_INBOUND_DIR) return;
16050
- if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
16129
+ if (!existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
16051
16130
  let filenames;
16052
16131
  try {
16053
- filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
16132
+ filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
16054
16133
  } catch {
16055
16134
  return;
16056
16135
  }
@@ -16061,10 +16140,10 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16061
16140
  for (const filename of filenames) {
16062
16141
  if (!filename.startsWith(channelPrefix)) continue;
16063
16142
  if (!filename.endsWith(messageSuffix)) continue;
16064
- clearSlackMarkerFileWithHeal(join4(SLACK_PENDING_INBOUND_DIR, filename));
16143
+ clearSlackMarkerFileWithHeal(join5(SLACK_PENDING_INBOUND_DIR, filename));
16065
16144
  }
16066
16145
  }
16067
- var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
16146
+ var RESTART_FLAGS_DIR = join5(homedir2(), ".augmented", "restart-flags");
16068
16147
  function buildAugmentedSlackMetadata() {
16069
16148
  if (!AGT_TEAM_ID) return void 0;
16070
16149
  return {
@@ -16243,10 +16322,10 @@ async function handleSlashCommandEnvelope(payload) {
16243
16322
  return;
16244
16323
  }
16245
16324
  try {
16246
- if (!existsSync2(RESTART_FLAGS_DIR)) {
16325
+ if (!existsSync3(RESTART_FLAGS_DIR)) {
16247
16326
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16248
16327
  }
16249
- const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
16328
+ const flagPath = join5(RESTART_FLAGS_DIR, `${codeName}.flag`);
16250
16329
  const flag = {
16251
16330
  codeName,
16252
16331
  source: "slack",
@@ -16350,10 +16429,10 @@ async function handleHelpCommand(opts) {
16350
16429
  async function handleRestartCommand(opts) {
16351
16430
  const codeName = AGENT_CODE_NAME ?? "unknown";
16352
16431
  try {
16353
- if (!existsSync2(RESTART_FLAGS_DIR)) {
16432
+ if (!existsSync3(RESTART_FLAGS_DIR)) {
16354
16433
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16355
16434
  }
16356
- const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
16435
+ const flagPath = join5(RESTART_FLAGS_DIR, `${codeName}.flag`);
16357
16436
  const flag = {
16358
16437
  codeName,
16359
16438
  source: "slack",
@@ -16412,7 +16491,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
16412
16491
  var threadPersister = null;
16413
16492
  function resolveThreadStorePath() {
16414
16493
  if (!AGENT_CODE_NAME) return null;
16415
- return join4(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16494
+ return join5(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16416
16495
  }
16417
16496
  function parseTtlDays(raw) {
16418
16497
  if (!raw) return void 0;
@@ -16447,9 +16526,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
16447
16526
  var slackStderrLogStream = null;
16448
16527
  if (AGENT_CODE_NAME) {
16449
16528
  try {
16450
- const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
16529
+ const logDir = join5(homedir2(), ".augmented", AGENT_CODE_NAME);
16451
16530
  mkdirSync3(logDir, { recursive: true });
16452
- slackStderrLogStream = createWriteStream(join4(logDir, "slack-channel-stderr.log"), {
16531
+ slackStderrLogStream = createWriteStream(join5(logDir, "slack-channel-stderr.log"), {
16453
16532
  flags: "a",
16454
16533
  mode: 384
16455
16534
  });
@@ -16533,7 +16612,7 @@ var mcp = new Server(
16533
16612
  // Highest-priority lines first — Claude Code truncates this string at
16534
16613
  // 2048 chars, so anything appended late silently disappears.
16535
16614
  "CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply. Text in your session WITHOUT a slack.reply call never reaches the user \u2014 the message dies inside the agent process.",
16536
- `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.`,
16615
+ `Inbound: <channel ... thread_ts="..." message_ts="..." [thread_context="..."]>. Pass channel + message_ts to slack.reply (and thread_ts on threads). thread_context = thread pre-loaded; ground replies ONLY in it, never another channel's.`,
16537
16616
  "Inbound attachments: <channel> `files` is a JSON-serialised array \u2014 JSON.parse it. If an entry has `path`, the image is already downloaded \u2014 Read it directly, do NOT call slack.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, csv): pass file_id + channel verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Don't surface internal file-handling errors that don't affect the answer.",
16538
16617
  "Address users by user_name, never by raw user ID. In multi-participant threads the CURRENT speaker is the one on the latest <channel> tag.",
16539
16618
  'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if useful, OR if your own bot user is @-mentioned (counts even in auto_followed).',
@@ -16558,7 +16637,18 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16558
16637
  text: { type: "string", description: "The message to send" },
16559
16638
  thread_ts: {
16560
16639
  type: "string",
16561
- description: "Thread timestamp for threaded replies (from the thread_ts attribute)"
16640
+ description: "Thread timestamp for threaded replies (from the thread_ts attribute). Omit for a top-level reply (e.g. a fresh DM)."
16641
+ },
16642
+ // ENG-5861: explicit message_ts so the cleanup gate can match the
16643
+ // exact pending-inbound marker — necessary because top-level DM
16644
+ // replies legitimately have no thread_ts and the marker filename
16645
+ // is `<channel>__<thread_ts>__<message_ts>.json`. Without this
16646
+ // the marker sat undrained, eventually tripping ENG-5832\'s
16647
+ // "wedged" threshold and firing a false-positive red ✗ on the
16648
+ // next inbound.
16649
+ message_ts: {
16650
+ type: "string",
16651
+ description: "The message_ts of the specific inbound this reply addresses (from the message_ts attribute on the <channel> tag). Used by the pending-inbound cleanup gate so the marker is reliably cleared even for top-level DMs that have no thread_ts."
16562
16652
  }
16563
16653
  },
16564
16654
  required: ["channel", "text"]
@@ -16796,7 +16886,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
16796
16886
  return buildImpersonationRefusal(name);
16797
16887
  }
16798
16888
  if (name === "slack.reply") {
16799
- const { channel, text, thread_ts } = args;
16889
+ const { channel, text, thread_ts, message_ts } = args;
16800
16890
  if (channel && thread_ts) {
16801
16891
  const killed = await isThreadKilled({
16802
16892
  channelType: "slack",
@@ -16855,8 +16945,15 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
16855
16945
  isError: true
16856
16946
  };
16857
16947
  }
16858
- if (channel && thread_ts) {
16859
- clearPendingMessage(channel, thread_ts);
16948
+ if (channel) {
16949
+ if (message_ts) {
16950
+ clearSlackPendingMarkerByMessageTs2(channel, message_ts);
16951
+ if (thread_ts) clearPendingMessage(channel, thread_ts);
16952
+ } else if (thread_ts) {
16953
+ clearPendingMessage(channel, thread_ts);
16954
+ } else {
16955
+ clearOldestSlackPendingMarkerInChannel2(channel);
16956
+ }
16860
16957
  }
16861
16958
  try {
16862
16959
  const res = await fetch("https://slack.com/api/chat.postMessage", {
@@ -17008,7 +17105,7 @@ ${result.formatted}` : "Thread is empty or not found."
17008
17105
  let bytes;
17009
17106
  let size;
17010
17107
  try {
17011
- const stat = statSync(resolvedPath);
17108
+ const stat = statSync2(resolvedPath);
17012
17109
  if (!stat.isFile()) {
17013
17110
  return {
17014
17111
  content: [{ type: "text", text: `Upload refused: ${resolvedPath} is not a regular file.` }],
@@ -18056,13 +18153,22 @@ async function connectSocketMode() {
18056
18153
  botUserId
18057
18154
  });
18058
18155
  const ackProbe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
18156
+ let paneLogFreshAgeMs = null;
18157
+ if (SLACK_AGENT_DIR) {
18158
+ try {
18159
+ const paneMtimeMs = statSync2(join5(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
18160
+ paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
18161
+ } catch {
18162
+ }
18163
+ }
18059
18164
  const ackDecision = decideAckReaction({
18060
18165
  hasTarget: decideSlackAckReaction({ channel, ts }),
18061
18166
  integrationReady: Boolean(BOT_TOKEN),
18062
18167
  tmux: ackProbe.tmux,
18063
18168
  claude: ackProbe.claude,
18064
18169
  withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
18065
- oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR)
18170
+ oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR),
18171
+ paneLogFreshAgeMs
18066
18172
  });
18067
18173
  if (ackDecision !== "none") {
18068
18174
  const reactionName = ackDecision === "undeliverable" ? "x" : "eyes";
@@ -18117,6 +18223,13 @@ async function connectSocketMode() {
18117
18223
  user_name: userName,
18118
18224
  channel,
18119
18225
  thread_ts: threadTs,
18226
+ // ENG-5861: explicit message_ts so the agent can pass it back
18227
+ // to slack.reply for reliable pending-inbound cleanup. For
18228
+ // top-level messages threadTs === ts; for threaded replies
18229
+ // threadTs is the thread root and message_ts is the specific
18230
+ // reply being addressed — different keys, both needed by the
18231
+ // marker-cleanup gate.
18232
+ message_ts: ts,
18120
18233
  event_type: evt.type,
18121
18234
  ...isAutoFollowed ? { auto_followed: "true" } : {},
18122
18235
  // Only set these when we actually have attachments to avoid
@@ -15506,6 +15506,7 @@ import { readdirSync, readFileSync as readFileSync2 } from "fs";
15506
15506
  import { join as join3 } from "path";
15507
15507
  var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
15508
15508
  var ACK_STARTUP_GRACE_MS = 6e4;
15509
+ var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
15509
15510
  function decideAckReaction(i) {
15510
15511
  if (!i.hasTarget) return "none";
15511
15512
  if (!i.integrationReady) return "undeliverable";
@@ -15513,6 +15514,11 @@ function decideAckReaction(i) {
15513
15514
  if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
15514
15515
  const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
15515
15516
  if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
15517
+ const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
15518
+ const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
15519
+ if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
15520
+ return "ack";
15521
+ }
15516
15522
  return "undeliverable";
15517
15523
  }
15518
15524
  return "ack";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.27.26",
3
+ "version": "0.27.28",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {