@integrity-labs/agt-cli 0.28.57 → 0.28.58

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