@integrity-labs/agt-cli 0.28.57 → 0.28.59

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.
@@ -14637,8 +14637,11 @@ function orphanSweepIntervalMs() {
14637
14637
  }
14638
14638
  var MAX_MARKER_REPLAYS = 3;
14639
14639
  function channelReplayEnabled() {
14640
- const v = process.env.AGT_CHANNEL_REPLAY_ENABLED;
14641
- return v === "1" || v === "true";
14640
+ return resolveHostBooleanFlag({
14641
+ key: "channel-replay",
14642
+ envVar: "AGT_CHANNEL_REPLAY_ENABLED",
14643
+ defaultValue: false
14644
+ });
14642
14645
  }
14643
14646
  function shouldReplayMarker(i) {
14644
14647
  if (!i.enabled) return false;
@@ -15448,15 +15451,15 @@ import {
15448
15451
  createWriteStream,
15449
15452
  existsSync as existsSync7,
15450
15453
  ftruncateSync,
15451
- mkdirSync as mkdirSync5,
15454
+ mkdirSync as mkdirSync6,
15452
15455
  openSync,
15453
- readFileSync as readFileSync8,
15456
+ readFileSync as readFileSync9,
15454
15457
  readdirSync as readdirSync3,
15455
15458
  renameSync as renameSync3,
15456
15459
  statSync as statSync2,
15457
- unlinkSync as unlinkSync4,
15460
+ unlinkSync as unlinkSync5,
15458
15461
  watch,
15459
- writeFileSync as writeFileSync6,
15462
+ writeFileSync as writeFileSync7,
15460
15463
  writeSync
15461
15464
  } from "fs";
15462
15465
  import { basename, join as join7, resolve as resolve2 } from "path";
@@ -15575,6 +15578,156 @@ function isThreadEntry(value) {
15575
15578
  return (v.involvement === "started" || v.involvement === "mentioned") && typeof v.last_seen_at === "string";
15576
15579
  }
15577
15580
 
15581
+ // src/dm-restart-notice.ts
15582
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
15583
+ import { dirname as dirname3 } from "path";
15584
+ var RECENT_DM_VERSION = 1;
15585
+ var DEFAULT_RECENT_DM_TTL_MS = 30 * 60 * 1e3;
15586
+ var DEFAULT_DM_NOTICE_WINDOW_MS = 5 * 60 * 1e3;
15587
+ var DEFAULT_MIN_INTERVAL_MS2 = 1e3;
15588
+ function recordDmActivity(map, dmKey, now = /* @__PURE__ */ new Date()) {
15589
+ if (!dmKey) return;
15590
+ map.set(dmKey, { last_activity_at: now.toISOString() });
15591
+ }
15592
+ function recentlyActiveDmKeys(map, now = /* @__PURE__ */ new Date(), windowMs = DEFAULT_DM_NOTICE_WINDOW_MS) {
15593
+ const cutoff = now.getTime() - windowMs;
15594
+ return [...map.entries()].map(([key2, e]) => ({ key: key2, ts: Date.parse(e.last_activity_at) })).filter(({ ts }) => Number.isFinite(ts) && ts >= cutoff).sort((a, b) => b.ts - a.ts).map(({ key: key2 }) => key2);
15595
+ }
15596
+ function serializeRecentDms(map) {
15597
+ const out = { version: RECENT_DM_VERSION, dms: {} };
15598
+ for (const [key2, e] of map) out.dms[key2] = e;
15599
+ return JSON.stringify(out, null, 2);
15600
+ }
15601
+ function loadRecentDms(filePath, opts = {}) {
15602
+ const now = opts.now ?? /* @__PURE__ */ new Date();
15603
+ const ttlMs = opts.ttlMs ?? DEFAULT_RECENT_DM_TTL_MS;
15604
+ let raw;
15605
+ try {
15606
+ raw = readFileSync6(filePath, "utf-8");
15607
+ } catch (err) {
15608
+ const code = err.code;
15609
+ if (code !== "ENOENT") {
15610
+ opts.onError?.(`dm-restart-notice: failed to read recent-DM file: ${err.message}`);
15611
+ }
15612
+ return /* @__PURE__ */ new Map();
15613
+ }
15614
+ let parsed;
15615
+ try {
15616
+ parsed = JSON.parse(raw);
15617
+ } catch (err) {
15618
+ opts.onError?.(`dm-restart-notice: malformed recent-DM JSON (${err.message}) \u2014 ignoring`);
15619
+ return /* @__PURE__ */ new Map();
15620
+ }
15621
+ if (typeof parsed !== "object" || parsed === null || // CodeRabbit: reject unknown versions so a future/legacy on-disk format can't
15622
+ // be read with this version's (possibly incompatible) semantics.
15623
+ parsed.version !== RECENT_DM_VERSION || typeof parsed.dms !== "object" || parsed.dms === null) {
15624
+ opts.onError?.("dm-restart-notice: unexpected recent-DM shape/version \u2014 ignoring");
15625
+ return /* @__PURE__ */ new Map();
15626
+ }
15627
+ const map = /* @__PURE__ */ new Map();
15628
+ const cutoff = now.getTime() - ttlMs;
15629
+ for (const [key2, val] of Object.entries(parsed.dms)) {
15630
+ const at = val?.last_activity_at;
15631
+ if (typeof at !== "string") continue;
15632
+ const ts = Date.parse(at);
15633
+ if (!Number.isFinite(ts) || ts < cutoff) continue;
15634
+ map.set(key2, { last_activity_at: at });
15635
+ }
15636
+ return map;
15637
+ }
15638
+ function createRecentDmPersister(opts) {
15639
+ const interval = opts.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
15640
+ let lastWriteAt = 0;
15641
+ let pendingTimer = null;
15642
+ let pendingSnapshot = null;
15643
+ const writeNow = (snap) => {
15644
+ try {
15645
+ mkdirSync3(dirname3(opts.filePath), { recursive: true });
15646
+ writeFileSync4(opts.filePath, serializeRecentDms(snap), "utf-8");
15647
+ lastWriteAt = Date.now();
15648
+ } catch (err) {
15649
+ opts.onError?.(`dm-restart-notice: failed to persist recent-DMs: ${err.message}`);
15650
+ }
15651
+ };
15652
+ return {
15653
+ schedule(map) {
15654
+ pendingSnapshot = new Map(map);
15655
+ const sinceLast = Date.now() - lastWriteAt;
15656
+ if (sinceLast >= interval) {
15657
+ const snap = pendingSnapshot;
15658
+ pendingSnapshot = null;
15659
+ writeNow(snap);
15660
+ return;
15661
+ }
15662
+ if (pendingTimer) return;
15663
+ pendingTimer = setTimeout(() => {
15664
+ pendingTimer = null;
15665
+ const snap = pendingSnapshot;
15666
+ pendingSnapshot = null;
15667
+ if (snap) writeNow(snap);
15668
+ }, interval - sinceLast);
15669
+ pendingTimer.unref?.();
15670
+ },
15671
+ flush(map) {
15672
+ if (pendingTimer) {
15673
+ clearTimeout(pendingTimer);
15674
+ pendingTimer = null;
15675
+ pendingSnapshot = null;
15676
+ }
15677
+ writeNow(new Map(map));
15678
+ },
15679
+ dispose() {
15680
+ if (pendingTimer) clearTimeout(pendingTimer);
15681
+ pendingTimer = null;
15682
+ pendingSnapshot = null;
15683
+ }
15684
+ };
15685
+ }
15686
+ var CHANNEL_ADD_RESTART_VERSION = 1;
15687
+ var CHANNEL_ADD_RESTART_MAX_AGE_MS = 15 * 60 * 1e3;
15688
+ function readChannelAddRestartMarker(filePath) {
15689
+ let raw;
15690
+ try {
15691
+ raw = readFileSync6(filePath, "utf-8");
15692
+ } catch {
15693
+ return null;
15694
+ }
15695
+ try {
15696
+ const parsed = JSON.parse(raw);
15697
+ if (typeof parsed !== "object" || parsed === null || parsed.version !== CHANNEL_ADD_RESTART_VERSION || typeof parsed.at !== "string") {
15698
+ return null;
15699
+ }
15700
+ const rawAdded = parsed.added;
15701
+ const added = Array.isArray(rawAdded) ? rawAdded.filter((x) => typeof x === "string") : [];
15702
+ return { version: CHANNEL_ADD_RESTART_VERSION, at: parsed.at, added };
15703
+ } catch {
15704
+ return null;
15705
+ }
15706
+ }
15707
+ function shouldPostChannelAddNotice(marker, bootMs, maxAgeMs = CHANNEL_ADD_RESTART_MAX_AGE_MS) {
15708
+ if (!marker) return false;
15709
+ const at = Date.parse(marker.at);
15710
+ if (!Number.isFinite(at)) return false;
15711
+ const age = bootMs - at;
15712
+ return age <= maxAgeMs && age >= -6e4;
15713
+ }
15714
+ function clearChannelAddRestartMarker(filePath) {
15715
+ try {
15716
+ unlinkSync3(filePath);
15717
+ } catch {
15718
+ }
15719
+ }
15720
+ function buildChannelAddNoticeText(added) {
15721
+ const list = added.filter(Boolean);
15722
+ if (list.length === 1) {
15723
+ return `Quick heads-up \u2014 I briefly restarted to bring a new channel (${list[0]}) online. I'm back now; please re-send anything I might have missed.`;
15724
+ }
15725
+ if (list.length > 1) {
15726
+ return `Quick heads-up \u2014 I briefly restarted to bring new channels (${list.join(", ")}) online. I'm back now; please re-send anything I might have missed.`;
15727
+ }
15728
+ return `Quick heads-up \u2014 I briefly restarted to update my configuration. I'm back now; please re-send anything I might have missed.`;
15729
+ }
15730
+
15578
15731
  // src/safe-async.ts
15579
15732
  async function runOrRetry(fn, opts) {
15580
15733
  try {
@@ -15593,8 +15746,8 @@ async function runOrRetry(fn, opts) {
15593
15746
  }
15594
15747
 
15595
15748
  // src/slack-bot-photo.ts
15596
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
15597
- import { dirname as dirname3 } from "path";
15749
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
15750
+ import { dirname as dirname4 } from "path";
15598
15751
  async function applyBotPhoto(opts) {
15599
15752
  const fetchImpl = opts.fetchImpl ?? fetch;
15600
15753
  const log = opts.log ?? ((m) => {
@@ -15603,7 +15756,7 @@ async function applyBotPhoto(opts) {
15603
15756
  const { token, avatarUrl, markerPath } = opts;
15604
15757
  if (markerPath && existsSync5(markerPath)) {
15605
15758
  try {
15606
- if (readFileSync6(markerPath, "utf-8").trim() === avatarUrl) {
15759
+ if (readFileSync7(markerPath, "utf-8").trim() === avatarUrl) {
15607
15760
  return { status: "skipped-unchanged" };
15608
15761
  }
15609
15762
  } catch {
@@ -15644,8 +15797,8 @@ async function applyBotPhoto(opts) {
15644
15797
  }
15645
15798
  if (markerPath) {
15646
15799
  try {
15647
- mkdirSync3(dirname3(markerPath), { recursive: true, mode: 448 });
15648
- writeFileSync4(markerPath, avatarUrl, { mode: 384 });
15800
+ mkdirSync4(dirname4(markerPath), { recursive: true, mode: 448 });
15801
+ writeFileSync5(markerPath, avatarUrl, { mode: 384 });
15649
15802
  } catch {
15650
15803
  }
15651
15804
  }
@@ -16428,11 +16581,11 @@ function createSlackBotUserIdClient(args) {
16428
16581
  // src/mcp-spawn-lock.ts
16429
16582
  import {
16430
16583
  existsSync as existsSync6,
16431
- mkdirSync as mkdirSync4,
16432
- readFileSync as readFileSync7,
16584
+ mkdirSync as mkdirSync5,
16585
+ readFileSync as readFileSync8,
16433
16586
  renameSync as renameSync2,
16434
- unlinkSync as unlinkSync3,
16435
- writeFileSync as writeFileSync5
16587
+ unlinkSync as unlinkSync4,
16588
+ writeFileSync as writeFileSync6
16436
16589
  } from "fs";
16437
16590
  import { join as join6 } from "path";
16438
16591
  function defaultIsPidAlive(pid) {
@@ -16462,10 +16615,10 @@ function acquireMcpSpawnLock(args) {
16462
16615
  return { kind: "blocked", path, holder: existing };
16463
16616
  }
16464
16617
  }
16465
- mkdirSync4(agentDir, { recursive: true, mode: 448 });
16618
+ mkdirSync5(agentDir, { recursive: true, mode: 448 });
16466
16619
  const tmpPath = `${path}.${selfPid}.tmp`;
16467
16620
  const payload = { pid: selfPid, started_at: now() };
16468
- writeFileSync5(tmpPath, JSON.stringify(payload), { mode: 384 });
16621
+ writeFileSync6(tmpPath, JSON.stringify(payload), { mode: 384 });
16469
16622
  renameSync2(tmpPath, path);
16470
16623
  return { kind: "acquired", path };
16471
16624
  }
@@ -16476,14 +16629,14 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
16476
16629
  if (!existing) return;
16477
16630
  if (existing.pid !== selfPid) return;
16478
16631
  try {
16479
- unlinkSync3(lockPath);
16632
+ unlinkSync4(lockPath);
16480
16633
  } catch {
16481
16634
  }
16482
16635
  }
16483
16636
  function readLockHolder(path) {
16484
16637
  if (!existsSync6(path)) return null;
16485
16638
  try {
16486
- const raw = readFileSync7(path, "utf8");
16639
+ const raw = readFileSync8(path, "utf8");
16487
16640
  const parsed = JSON.parse(raw);
16488
16641
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
16489
16642
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -16672,7 +16825,7 @@ function readLiveAllowedUsers() {
16672
16825
  return liveAllowedUsersCache.value;
16673
16826
  }
16674
16827
  const value = extractAllowedUsersFromMcpJson(
16675
- readFileSync8(SLACK_MCP_CONFIG_PATH, "utf-8")
16828
+ readFileSync9(SLACK_MCP_CONFIG_PATH, "utf-8")
16676
16829
  );
16677
16830
  if (value === null) return null;
16678
16831
  liveAllowedUsersCache = { mtimeMs, value };
@@ -16687,6 +16840,8 @@ function getEffectiveAllowedUsers() {
16687
16840
  var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join7(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
16688
16841
  var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join7(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
16689
16842
  var SLACK_RESTART_CONFIRM_FILE = SLACK_AGENT_DIR ? join7(SLACK_AGENT_DIR, "slack-restart-confirm.json") : null;
16843
+ var SLACK_RECENT_DMS_FILE = SLACK_AGENT_DIR ? join7(SLACK_AGENT_DIR, "slack-recent-dms.json") : null;
16844
+ var SLACK_CHANNEL_ADD_RESTART_FILE = SLACK_AGENT_DIR ? join7(SLACK_AGENT_DIR, "slack-channel-add-restart.json") : null;
16690
16845
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
16691
16846
  var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ? join7(SLACK_AGENT_DIR, "slack-avatar-applied") : null;
16692
16847
  function redactSlackId(id) {
@@ -16717,8 +16872,8 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
16717
16872
  ...payload ? { payload } : {}
16718
16873
  };
16719
16874
  try {
16720
- mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
16721
- writeFileSync6(path, JSON.stringify(marker), { mode: 384 });
16875
+ mkdirSync6(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
16876
+ writeFileSync7(path, JSON.stringify(marker), { mode: 384 });
16722
16877
  } catch (err) {
16723
16878
  process.stderr.write(
16724
16879
  `slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
@@ -16748,7 +16903,7 @@ function attachSlackReplayPayload(channel, threadTs, messageTs, payload) {
16748
16903
  if (!path) return;
16749
16904
  let marker;
16750
16905
  try {
16751
- marker = JSON.parse(readFileSync8(path, "utf-8"));
16906
+ marker = JSON.parse(readFileSync9(path, "utf-8"));
16752
16907
  } catch {
16753
16908
  return;
16754
16909
  }
@@ -16759,7 +16914,7 @@ function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
16759
16914
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
16760
16915
  if (!path || !existsSync7(path)) return null;
16761
16916
  try {
16762
- return JSON.parse(readFileSync8(path, "utf-8"));
16917
+ return JSON.parse(readFileSync9(path, "utf-8"));
16763
16918
  } catch {
16764
16919
  return null;
16765
16920
  }
@@ -16887,7 +17042,7 @@ function __resetSlackBusyAckNoticeThrottle() {
16887
17042
  function clearSlackMarkerFileWithHeal(fullPath) {
16888
17043
  let marker = null;
16889
17044
  try {
16890
- marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
17045
+ marker = JSON.parse(readFileSync9(fullPath, "utf-8"));
16891
17046
  } catch {
16892
17047
  }
16893
17048
  if (marker && decideRecoveryHeal({
@@ -16897,7 +17052,7 @@ function clearSlackMarkerFileWithHeal(fullPath) {
16897
17052
  healSlackUndeliverable(marker.channel, marker.message_ts);
16898
17053
  }
16899
17054
  try {
16900
- if (existsSync7(fullPath)) unlinkSync4(fullPath);
17055
+ if (existsSync7(fullPath)) unlinkSync5(fullPath);
16901
17056
  } catch {
16902
17057
  }
16903
17058
  }
@@ -16941,7 +17096,7 @@ async function processSlackRecoveryOutboxFile(filename) {
16941
17096
  const fullPath = join7(SLACK_RECOVERY_OUTBOX_DIR, filename);
16942
17097
  let payload;
16943
17098
  try {
16944
- payload = JSON.parse(readFileSync8(fullPath, "utf-8"));
17099
+ payload = JSON.parse(readFileSync9(fullPath, "utf-8"));
16945
17100
  } catch (err) {
16946
17101
  process.stderr.write(
16947
17102
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
@@ -17009,7 +17164,7 @@ ${payload.text}`;
17009
17164
  }
17010
17165
  if (sendSucceeded) {
17011
17166
  try {
17012
- unlinkSync4(fullPath);
17167
+ unlinkSync5(fullPath);
17013
17168
  } catch {
17014
17169
  }
17015
17170
  return;
@@ -17068,7 +17223,7 @@ function scanSlackRecoveryRetries() {
17068
17223
  function startSlackRecoveryOutboxWatcher() {
17069
17224
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
17070
17225
  try {
17071
- mkdirSync5(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
17226
+ mkdirSync6(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
17072
17227
  } catch (err) {
17073
17228
  process.stderr.write(
17074
17229
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox mkdir failed: ${err.message}
@@ -17127,14 +17282,14 @@ function sweepSlackStaleMarkers(thresholdMs) {
17127
17282
  const fullPath = join7(SLACK_PENDING_INBOUND_DIR, filename);
17128
17283
  let marker;
17129
17284
  try {
17130
- marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
17285
+ marker = JSON.parse(readFileSync9(fullPath, "utf-8"));
17131
17286
  } catch (err) {
17132
17287
  process.stderr.write(
17133
17288
  `slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
17134
17289
  `
17135
17290
  );
17136
17291
  try {
17137
- unlinkSync4(fullPath);
17292
+ unlinkSync5(fullPath);
17138
17293
  } catch {
17139
17294
  }
17140
17295
  cleared++;
@@ -17149,7 +17304,7 @@ function sweepSlackStaleMarkers(thresholdMs) {
17149
17304
  recordChannelDeflection(SLACK_AGENT_DIR, "slack", "replay_orphaned");
17150
17305
  }
17151
17306
  try {
17152
- unlinkSync4(fullPath);
17307
+ unlinkSync5(fullPath);
17153
17308
  } catch {
17154
17309
  }
17155
17310
  cleared++;
@@ -17179,7 +17334,7 @@ function listPendingSlackConversations() {
17179
17334
  if (!name.endsWith(".json")) continue;
17180
17335
  try {
17181
17336
  const marker = JSON.parse(
17182
- readFileSync8(join7(SLACK_PENDING_INBOUND_DIR, name), "utf8")
17337
+ readFileSync9(join7(SLACK_PENDING_INBOUND_DIR, name), "utf8")
17183
17338
  );
17184
17339
  if (typeof marker.channel !== "string" || !marker.channel) continue;
17185
17340
  if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
@@ -17273,7 +17428,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
17273
17428
  const fullPath = join7(SLACK_PENDING_INBOUND_DIR, filename);
17274
17429
  let marker;
17275
17430
  try {
17276
- marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
17431
+ marker = JSON.parse(readFileSync9(fullPath, "utf-8"));
17277
17432
  } catch {
17278
17433
  continue;
17279
17434
  }
@@ -17286,7 +17441,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
17286
17441
  const key2 = `${channel}__${thread_ts}`;
17287
17442
  if (seen.has(key2)) {
17288
17443
  try {
17289
- unlinkSync4(fullPath);
17444
+ unlinkSync5(fullPath);
17290
17445
  } catch {
17291
17446
  }
17292
17447
  continue;
@@ -17300,7 +17455,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
17300
17455
  if (res.ok) {
17301
17456
  notified += 1;
17302
17457
  try {
17303
- unlinkSync4(fullPath);
17458
+ unlinkSync5(fullPath);
17304
17459
  } catch {
17305
17460
  }
17306
17461
  } else {
@@ -17372,6 +17527,49 @@ async function notifyRestartCompleteOnFirstConnect() {
17372
17527
  restartConfirmNoticeInFlight = false;
17373
17528
  }
17374
17529
  }
17530
+ var dmChannelAddNoticeFired = false;
17531
+ var dmChannelAddNoticeInFlight = false;
17532
+ async function notifyDmUsersOnChannelAddRestart() {
17533
+ if (dmChannelAddNoticeFired || dmChannelAddNoticeInFlight) return;
17534
+ if (!SLACK_CHANNEL_ADD_RESTART_FILE) return;
17535
+ dmChannelAddNoticeInFlight = true;
17536
+ try {
17537
+ const marker = readChannelAddRestartMarker(SLACK_CHANNEL_ADD_RESTART_FILE);
17538
+ if (!shouldPostChannelAddNotice(marker, SLACK_PROCESS_BOOT_MS)) {
17539
+ if (marker) clearChannelAddRestartMarker(SLACK_CHANNEL_ADD_RESTART_FILE);
17540
+ return;
17541
+ }
17542
+ const dmChannels = recentlyActiveDmKeys(recentDms);
17543
+ const text = buildChannelAddNoticeText(marker.added);
17544
+ let notified = 0;
17545
+ for (const channel of dmChannels) {
17546
+ const res = await postSlackMessage({ channel, text });
17547
+ if (res.ok) {
17548
+ notified += 1;
17549
+ } else {
17550
+ process.stderr.write(
17551
+ `slack-channel(${AGENT_CODE_NAME}): channel-add DM notice failed channel=${redactSlackId(channel)} error=${res.error}
17552
+ `
17553
+ );
17554
+ }
17555
+ }
17556
+ clearChannelAddRestartMarker(SLACK_CHANNEL_ADD_RESTART_FILE);
17557
+ if (notified > 0) {
17558
+ process.stderr.write(
17559
+ `slack-channel(${AGENT_CODE_NAME}): notified ${notified} recently-active DM(s) after channel-add restart
17560
+ `
17561
+ );
17562
+ }
17563
+ } catch (err) {
17564
+ process.stderr.write(
17565
+ `slack-channel(${AGENT_CODE_NAME}): channel-add DM notice threw: ${redactAugmentedPaths2(err.message)}
17566
+ `
17567
+ );
17568
+ } finally {
17569
+ dmChannelAddNoticeFired = true;
17570
+ dmChannelAddNoticeInFlight = false;
17571
+ }
17572
+ }
17375
17573
  function writeSlackRestartConfirm(reply, requesterName) {
17376
17574
  if (!SLACK_RESTART_CONFIRM_FILE) return;
17377
17575
  const marker = {
@@ -17806,7 +18004,7 @@ async function handleSlashCommandEnvelope(payload) {
17806
18004
  }
17807
18005
  try {
17808
18006
  if (!existsSync7(RESTART_FLAGS_DIR)) {
17809
- mkdirSync5(RESTART_FLAGS_DIR, { recursive: true });
18007
+ mkdirSync6(RESTART_FLAGS_DIR, { recursive: true });
17810
18008
  }
17811
18009
  const flagPath = join7(RESTART_FLAGS_DIR, `${codeName}.flag`);
17812
18010
  writeSlackRestartConfirm(
@@ -17826,7 +18024,7 @@ async function handleSlashCommandEnvelope(payload) {
17826
18024
  }
17827
18025
  };
17828
18026
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
17829
- writeFileSync6(tmpPath, JSON.stringify(flag) + "\n", "utf8");
18027
+ writeFileSync7(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17830
18028
  renameSync3(tmpPath, flagPath);
17831
18029
  process.stderr.write(
17832
18030
  `slack-channel(${codeName}): /restart slash-command queued from channel ${hashChannelId(payload.channel_id)}
@@ -17968,7 +18166,7 @@ async function handleRestartCommand(opts) {
17968
18166
  const codeName = AGENT_CODE_NAME ?? "unknown";
17969
18167
  try {
17970
18168
  if (!existsSync7(RESTART_FLAGS_DIR)) {
17971
- mkdirSync5(RESTART_FLAGS_DIR, { recursive: true });
18169
+ mkdirSync6(RESTART_FLAGS_DIR, { recursive: true });
17972
18170
  }
17973
18171
  const flagPath = join7(RESTART_FLAGS_DIR, `${codeName}.flag`);
17974
18172
  writeSlackRestartConfirm(
@@ -17989,7 +18187,7 @@ async function handleRestartCommand(opts) {
17989
18187
  }
17990
18188
  };
17991
18189
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
17992
- writeFileSync6(tmpPath, JSON.stringify(flag) + "\n", "utf8");
18190
+ writeFileSync7(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17993
18191
  renameSync3(tmpPath, flagPath);
17994
18192
  process.stderr.write(
17995
18193
  `slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
@@ -18085,6 +18283,8 @@ async function denyUnauthorizedWatch(opts) {
18085
18283
  ...opts.threadTs ? { thread_ts: opts.threadTs } : {}
18086
18284
  });
18087
18285
  }
18286
+ var recentDms = /* @__PURE__ */ new Map();
18287
+ var recentDmPersister = null;
18088
18288
  var trackedThreads = /* @__PURE__ */ new Map();
18089
18289
  var THREAD_STORE_PATH = resolveThreadStorePath();
18090
18290
  var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAYS);
@@ -18131,7 +18331,7 @@ var slackStderrLogStream = null;
18131
18331
  if (AGENT_CODE_NAME) {
18132
18332
  try {
18133
18333
  const logDir = join7(homedir3(), ".augmented", AGENT_CODE_NAME);
18134
- mkdirSync5(logDir, { recursive: true });
18334
+ mkdirSync6(logDir, { recursive: true });
18135
18335
  slackStderrLogStream = createWriteStream(join7(logDir, "slack-channel-stderr.log"), {
18136
18336
  flags: "a",
18137
18337
  mode: 384
@@ -18712,7 +18912,7 @@ ${result.formatted}` : "Thread is empty or not found."
18712
18912
  };
18713
18913
  }
18714
18914
  size = stat2.size;
18715
- bytes = readFileSync8(resolvedPath);
18915
+ bytes = readFileSync9(resolvedPath);
18716
18916
  } catch (err) {
18717
18917
  return {
18718
18918
  content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
@@ -19350,8 +19550,8 @@ async function downloadSlackFile(fileId, codeName) {
19350
19550
  if (!isPathInside(savedPath, dir)) {
19351
19551
  throw new Error(`refusing to write ${savedPath} outside ${dir}`);
19352
19552
  }
19353
- mkdirSync5(dir, { recursive: true });
19354
- writeFileSync6(savedPath, bytes, { mode: 384 });
19553
+ mkdirSync6(dir, { recursive: true });
19554
+ writeFileSync7(savedPath, bytes, { mode: 384 });
19355
19555
  try {
19356
19556
  chmodSync(savedPath, 384);
19357
19557
  } catch {
@@ -19430,7 +19630,7 @@ async function replayPendingSlackMarkers() {
19430
19630
  const fullPath = join7(SLACK_PENDING_INBOUND_DIR, name);
19431
19631
  let marker;
19432
19632
  try {
19433
- marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
19633
+ marker = JSON.parse(readFileSync9(fullPath, "utf-8"));
19434
19634
  } catch {
19435
19635
  continue;
19436
19636
  }
@@ -19557,6 +19757,7 @@ async function connectSocketMode() {
19557
19757
  void notifyStrandedInboundsOnFirstConnect();
19558
19758
  }
19559
19759
  void notifyRestartCompleteOnFirstConnect();
19760
+ void notifyDmUsersOnChannelAddRestart();
19560
19761
  void applyBotPhotoOnFirstConnect();
19561
19762
  };
19562
19763
  ws.onmessage = async (event) => {
@@ -19754,6 +19955,14 @@ async function connectSocketMode() {
19754
19955
  const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
19755
19956
  const trackTs = evt.thread_ts ?? evt.ts ?? "";
19756
19957
  const threadKey = buildThreadKey(evt.channel, trackTs);
19958
+ if (isDirectMessage && !isBot && evt.channel && evt.user && evt.user !== botUserId) {
19959
+ recordDmActivity(recentDms, evt.channel);
19960
+ if (isShuttingDown) {
19961
+ recentDmPersister?.flush(recentDms);
19962
+ } else {
19963
+ recentDmPersister?.schedule(recentDms);
19964
+ }
19965
+ }
19757
19966
  if (isBot && peerClassification?.kind === "peer-ingress") {
19758
19967
  const peerGate = peerClassification.peer.gate_path;
19759
19968
  if (peerGate === "intra_org_unrestricted") {
@@ -19989,6 +20198,14 @@ function shutdown(reason, exitCode = 0) {
19989
20198
  threadPersister?.dispose();
19990
20199
  } catch {
19991
20200
  }
20201
+ try {
20202
+ recentDmPersister?.flush(recentDms);
20203
+ } catch {
20204
+ }
20205
+ try {
20206
+ recentDmPersister?.dispose();
20207
+ } catch {
20208
+ }
19992
20209
  process.stderr.write(`slack-channel: ${reason} \u2014 closing Socket Mode and exiting
19993
20210
  `);
19994
20211
  try {
@@ -20032,7 +20249,12 @@ process.on("unhandledRejection", (reason) => {
20032
20249
  }
20033
20250
  if (THREAD_STORE_PATH) {
20034
20251
  const threadStoreLabel = "slack-tracked-threads.json";
20035
- const redact = (msg) => msg.replaceAll(THREAD_STORE_PATH, threadStoreLabel);
20252
+ const recentDmsLabel = "slack-recent-dms.json";
20253
+ const redact = (msg) => {
20254
+ let out = msg.replaceAll(THREAD_STORE_PATH, threadStoreLabel);
20255
+ if (SLACK_RECENT_DMS_FILE) out = out.replaceAll(SLACK_RECENT_DMS_FILE, recentDmsLabel);
20256
+ return out;
20257
+ };
20036
20258
  const { threads, pruned } = loadThreadStore(THREAD_STORE_PATH, {
20037
20259
  ttlDays: THREAD_STORE_TTL_DAYS,
20038
20260
  onError: (msg) => process.stderr.write(`${redact(msg)}
@@ -20048,6 +20270,18 @@ if (THREAD_STORE_PATH) {
20048
20270
  `slack-channel: hydrated ${trackedThreads.size} tracked thread(s)${pruned > 0 ? ` (pruned ${pruned} stale)` : ""} from ${threadStoreLabel}
20049
20271
  `
20050
20272
  );
20273
+ if (SLACK_RECENT_DMS_FILE) {
20274
+ const loadedDms = loadRecentDms(SLACK_RECENT_DMS_FILE, {
20275
+ onError: (msg) => process.stderr.write(`${redact(msg)}
20276
+ `)
20277
+ });
20278
+ for (const [key2, entry] of loadedDms) recentDms.set(key2, entry);
20279
+ recentDmPersister = createRecentDmPersister({
20280
+ filePath: SLACK_RECENT_DMS_FILE,
20281
+ onError: (msg) => process.stderr.write(`${redact(msg)}
20282
+ `)
20283
+ });
20284
+ }
20051
20285
  } else {
20052
20286
  process.stderr.write(
20053
20287
  "slack-channel: AGT_AGENT_CODE_NAME not set \u2014 running without thread-follow persistence\n"