@integrity-labs/agt-cli 0.27.9-test.12 → 0.27.9-test.14

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.
Files changed (35) hide show
  1. package/dist/bin/agt.js +4 -4
  2. package/dist/chunk-354FAVQR.js +173 -0
  3. package/dist/chunk-354FAVQR.js.map +1 -0
  4. package/dist/{chunk-MQJ5DMPT.js → chunk-7GKJZBTB.js} +111 -204
  5. package/dist/chunk-7GKJZBTB.js.map +1 -0
  6. package/dist/{chunk-HR5T2RQF.js → chunk-I3YS5WFV.js} +3 -1
  7. package/dist/chunk-I3YS5WFV.js.map +1 -0
  8. package/dist/{chunk-SXBZDUKN.js → chunk-R7NKCGWN.js} +341 -46
  9. package/dist/chunk-R7NKCGWN.js.map +1 -0
  10. package/dist/{chunk-I2OTQJH3.js → chunk-WOOYOAPG.js} +2644 -2142
  11. package/dist/chunk-WOOYOAPG.js.map +1 -0
  12. package/dist/{claude-pair-runtime-F6USL3EW.js → claude-pair-runtime-GIUCD7IG.js} +2 -2
  13. package/dist/{claude-scheduler-EM24LTGV.js → claude-scheduler-FATCLHDM.js} +2 -2
  14. package/dist/daily-session-PNQX5URX.js +27 -0
  15. package/dist/lib/manager-worker.js +1125 -149
  16. package/dist/lib/manager-worker.js.map +1 -1
  17. package/dist/mcp/augmented-admin.js +21335 -0
  18. package/dist/mcp/index.js +121 -4
  19. package/dist/mcp/slack-channel.js +845 -208
  20. package/dist/mcp/teams-channel.js +73 -29
  21. package/dist/mcp/telegram-channel.js +579 -157
  22. package/dist/{persistent-session-OL5EDYB4.js → persistent-session-35PWSTLO.js} +10 -3
  23. package/dist/persistent-session-35PWSTLO.js.map +1 -0
  24. package/dist/responsiveness-probe-MA4M2QM4.js +158 -0
  25. package/dist/responsiveness-probe-MA4M2QM4.js.map +1 -0
  26. package/package.json +3 -2
  27. package/dist/chunk-HR5T2RQF.js.map +0 -1
  28. package/dist/chunk-I2OTQJH3.js.map +0 -1
  29. package/dist/chunk-MQJ5DMPT.js.map +0 -1
  30. package/dist/chunk-SXBZDUKN.js.map +0 -1
  31. package/dist/responsiveness-probe-B6LJJRUD.js +0 -74
  32. package/dist/responsiveness-probe-B6LJJRUD.js.map +0 -1
  33. /package/dist/{claude-pair-runtime-F6USL3EW.js.map → claude-pair-runtime-GIUCD7IG.js.map} +0 -0
  34. /package/dist/{claude-scheduler-EM24LTGV.js.map → claude-scheduler-FATCLHDM.js.map} +0 -0
  35. /package/dist/{persistent-session-OL5EDYB4.js.map → daily-session-PNQX5URX.js.map} +0 -0
@@ -14211,6 +14211,10 @@ function decideSlackEngagement(input) {
14211
14211
  function decideSlackAckReaction(input) {
14212
14212
  return Boolean(input.channel && input.ts);
14213
14213
  }
14214
+ function isRestartSenderAllowed(allowedUsers, userId) {
14215
+ if (allowedUsers.size === 0) return true;
14216
+ return userId != null && allowedUsers.has(userId);
14217
+ }
14214
14218
  function extractAugmentedSlackLabel(evt) {
14215
14219
  if (evt.metadata?.event_type !== "augmented_agent_message") return null;
14216
14220
  return {
@@ -14301,6 +14305,45 @@ function readDeclineCooldownMs(envVarName, defaultSeconds = 1800) {
14301
14305
  return parsed * 1e3;
14302
14306
  }
14303
14307
 
14308
+ // src/inbound-access.ts
14309
+ function decideInboundAccess(input) {
14310
+ if (!input.content.forward) {
14311
+ return { kind: "drop", reason: `content:${input.content.reason}` };
14312
+ }
14313
+ if (input.isSelf) {
14314
+ return { kind: "drop", reason: "self" };
14315
+ }
14316
+ if (input.orgBoundary && !input.orgBoundary.forward) {
14317
+ return { kind: "drop", reason: "org_boundary" };
14318
+ }
14319
+ if (input.senderPolicy && !input.senderPolicy.forward) {
14320
+ const reason = input.senderPolicy.reason;
14321
+ const declineWorthy = input.sameOrg && reason !== "internal_only";
14322
+ return {
14323
+ kind: "drop",
14324
+ reason: `sender_policy:${reason}`,
14325
+ decline: declineWorthy ? politeDeclineCopy(reason) : void 0
14326
+ };
14327
+ }
14328
+ if (input.command && !input.isBot) {
14329
+ return {
14330
+ kind: "command",
14331
+ command: input.command.command,
14332
+ authorized: input.command.authorized
14333
+ };
14334
+ }
14335
+ if (input.isBot) {
14336
+ const peer = input.peer;
14337
+ if (!peer || peer.kind === "self") {
14338
+ return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
14339
+ }
14340
+ if (peer.kind === "drop") {
14341
+ return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
14342
+ }
14343
+ }
14344
+ return { kind: "admit" };
14345
+ }
14346
+
14304
14347
  // src/ack-reaction.ts
14305
14348
  import { readdirSync, readFileSync } from "fs";
14306
14349
  import { join } from "path";
@@ -14335,6 +14378,29 @@ function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDEL
14335
14378
  function undeliverableNoticeText() {
14336
14379
  return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
14337
14380
  }
14381
+ var BUSY_ACK_THRESHOLD_MS = 9e4;
14382
+ var BUSY_ACK_NOTICE_THROTTLE_MS = 10 * 60 * 1e3;
14383
+ function channelBusyAckEnabled() {
14384
+ const v = process.env.AGT_CHANNEL_BUSY_ACK_ENABLED;
14385
+ return v === "1" || v === "true";
14386
+ }
14387
+ function channelBusyAckThresholdMs() {
14388
+ const raw = parseInt(process.env.AGT_CHANNEL_BUSY_ACK_THRESHOLD_MS ?? "", 10);
14389
+ return Number.isFinite(raw) && raw > 0 ? raw : BUSY_ACK_THRESHOLD_MS;
14390
+ }
14391
+ function decideBusyAck(i) {
14392
+ if (!i.hasTarget) return false;
14393
+ if (!i.stillPending) return false;
14394
+ if (!Number.isFinite(i.pendingAgeMs)) return false;
14395
+ const threshold = i.thresholdMs ?? channelBusyAckThresholdMs();
14396
+ if (i.pendingAgeMs < threshold) return false;
14397
+ if (!i.sessionAlive) return false;
14398
+ const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
14399
+ return i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
14400
+ }
14401
+ function busyAckNoticeText() {
14402
+ return "\u{1F6E0}\uFE0F I'm in the middle of something right now \u2014 I'll follow up on this as soon as I'm free.";
14403
+ }
14338
14404
  var GIVE_UP_SIGNAL_FILENAME = "watchdog-give-up.json";
14339
14405
  var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
14340
14406
  function readGiveUpSignalAtMs(path, now = Date.now()) {
@@ -14454,6 +14520,81 @@ function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = D
14454
14520
  return value;
14455
14521
  }
14456
14522
 
14523
+ // src/agent-config-state.ts
14524
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
14525
+ import { join as join2 } from "path";
14526
+ var SESSION_STATE_FILENAME = "session-state.json";
14527
+ function readAgentSessionState(stateDir) {
14528
+ if (!stateDir) return null;
14529
+ const path = join2(stateDir, SESSION_STATE_FILENAME);
14530
+ if (!existsSync(path)) return null;
14531
+ try {
14532
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
14533
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
14534
+ return parsed;
14535
+ } catch {
14536
+ return null;
14537
+ }
14538
+ }
14539
+ function describeSessionOrigin(source) {
14540
+ switch (source) {
14541
+ case "startup":
14542
+ return "fresh start";
14543
+ case "resume":
14544
+ return "resumed";
14545
+ case "clear":
14546
+ return "cleared (/clear)";
14547
+ case "compact":
14548
+ return "compacted";
14549
+ default:
14550
+ return "unknown";
14551
+ }
14552
+ }
14553
+ function formatModelLabel(model) {
14554
+ const trimmed = model?.trim();
14555
+ return trimmed && trimmed.length > 0 ? trimmed : "unknown";
14556
+ }
14557
+ function shortSessionId(id) {
14558
+ const trimmed = id?.trim();
14559
+ if (!trimmed) return "unknown";
14560
+ return trimmed.length > 8 ? trimmed.slice(0, 8) : trimmed;
14561
+ }
14562
+ function formatRelativeAge(ts, now = Date.now()) {
14563
+ if (typeof ts !== "number" || !Number.isFinite(ts) || ts <= 0) return "unknown";
14564
+ const seconds = Math.floor((now - ts) / 1e3);
14565
+ if (seconds < 0) return "unknown";
14566
+ if (seconds < 5) return "just now";
14567
+ if (seconds < 60) return `${seconds}s ago`;
14568
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
14569
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
14570
+ return `${Math.floor(seconds / 86400)}d ago`;
14571
+ }
14572
+ function formatChannels(channels) {
14573
+ if (!Array.isArray(channels) || channels.length === 0) return "unknown";
14574
+ const cleaned = channels.map((c) => String(c).trim()).filter((c) => c.length > 0);
14575
+ return cleaned.length > 0 ? cleaned.join(", ") : "unknown";
14576
+ }
14577
+ function buildAgentConfigReport(opts) {
14578
+ const { codeName, connectivityLine, state, now = Date.now() } = opts;
14579
+ const lines = [`\u{1F916} *Config for \`${codeName}\`*`, connectivityLine];
14580
+ if (!state) {
14581
+ lines.push("\u2022 *Model:* unknown \u2014 session state not recorded yet");
14582
+ lines.push("\u2022 *Session:* unknown");
14583
+ return lines.join("\n");
14584
+ }
14585
+ lines.push(`\u2022 *Model:* \`${formatModelLabel(state.model)}\``);
14586
+ const origin = describeSessionOrigin(state.source);
14587
+ const age = formatRelativeAge(state.recorded_at, now);
14588
+ const sessionLine = age === "unknown" ? `\u2022 *Session:* ${origin}` : `\u2022 *Session:* ${origin} \xB7 started ${age}`;
14589
+ lines.push(sessionLine);
14590
+ lines.push(`\u2022 *Session ID:* \`${shortSessionId(state.session_id)}\``);
14591
+ if (state.environment && state.environment.trim().length > 0) {
14592
+ lines.push(`\u2022 *Environment:* ${state.environment.trim()}`);
14593
+ }
14594
+ lines.push(`\u2022 *Channels:* ${formatChannels(state.channels)}`);
14595
+ return lines.join("\n");
14596
+ }
14597
+
14457
14598
  // src/pane-tail.ts
14458
14599
  import { execFile } from "child_process";
14459
14600
  import { promisify } from "util";
@@ -14461,9 +14602,9 @@ import { open, stat } from "fs/promises";
14461
14602
 
14462
14603
  // src/channel-attachments.ts
14463
14604
  import { homedir } from "os";
14464
- import { join as join2, resolve, sep } from "path";
14605
+ import { join as join3, resolve, sep } from "path";
14465
14606
  function resolveChannelInboundDir(codeName, channelSlug) {
14466
- const base = join2(homedir(), ".augmented");
14607
+ const base = join3(homedir(), ".augmented");
14467
14608
  const allowedSegment = /^[A-Za-z0-9_-]+$/;
14468
14609
  if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
14469
14610
  throw new Error(
@@ -14889,14 +15030,14 @@ var SLACK_EGRESS_TOOLS = /* @__PURE__ */ new Set([
14889
15030
  ]);
14890
15031
 
14891
15032
  // src/slack-pending-inbound-cleanup.ts
14892
- import { existsSync, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
14893
- import { join as join3 } from "path";
15033
+ import { existsSync as existsSync2, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
15034
+ import { join as join4 } from "path";
14894
15035
  function sanitizeMarkerSegment(value) {
14895
15036
  return value.replace(/[^A-Za-z0-9_-]/g, "_");
14896
15037
  }
14897
15038
  var defaultClearMarkerFile = (fullPath) => {
14898
15039
  try {
14899
- if (existsSync(fullPath)) unlinkSync(fullPath);
15040
+ if (existsSync2(fullPath)) unlinkSync(fullPath);
14900
15041
  } catch {
14901
15042
  }
14902
15043
  };
@@ -14907,7 +15048,7 @@ function clearAllSlackPendingMarkersForThread(dir, channel, threadTs, clear = de
14907
15048
  try {
14908
15049
  for (const f of readdirSync2(dir)) {
14909
15050
  if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
14910
- clear(join3(dir, f));
15051
+ clear(join4(dir, f));
14911
15052
  cleared += 1;
14912
15053
  }
14913
15054
  } catch {
@@ -14922,7 +15063,7 @@ function clearSlackPendingMarkerByMessageTs(dir, channel, messageTs, clear = def
14922
15063
  try {
14923
15064
  for (const f of readdirSync2(dir)) {
14924
15065
  if (!f.startsWith(channelPrefix) || !f.endsWith(messageSuffix)) continue;
14925
- clear(join3(dir, f));
15066
+ clear(join4(dir, f));
14926
15067
  cleared += 1;
14927
15068
  }
14928
15069
  } catch {
@@ -14934,7 +15075,7 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
14934
15075
  const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
14935
15076
  try {
14936
15077
  const entries = readdirSync2(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
14937
- const full = join3(dir, f);
15078
+ const full = join4(dir, f);
14938
15079
  let mtime = 0;
14939
15080
  try {
14940
15081
  mtime = statSync(full).mtimeMs;
@@ -14951,6 +15092,47 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
14951
15092
  }
14952
15093
  }
14953
15094
 
15095
+ // src/restart-confirm.ts
15096
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, renameSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
15097
+ import { dirname } from "path";
15098
+ import { randomUUID } from "crypto";
15099
+ var RESTART_CONFIRM_MAX_AGE_MS = 10 * 60 * 1e3;
15100
+ function shouldPostRestartConfirm(marker, processBootMs, nowMs) {
15101
+ if (!marker) return false;
15102
+ const requestedAt = marker.requested_at;
15103
+ if (typeof requestedAt !== "number" || !Number.isFinite(requestedAt)) return false;
15104
+ if (requestedAt >= processBootMs) return false;
15105
+ if (nowMs - requestedAt > RESTART_CONFIRM_MAX_AGE_MS) return false;
15106
+ return true;
15107
+ }
15108
+ function buildBackOnlineText(name) {
15109
+ const trimmed = typeof name === "string" ? name.trim() : "";
15110
+ return trimmed ? `Hey ${trimmed}, I'm back online after my restart. \u{1F7E2}` : `I'm back online after my restart. \u{1F7E2}`;
15111
+ }
15112
+ function writeRestartConfirmMarker(filePath, marker) {
15113
+ const dir = dirname(filePath);
15114
+ if (!existsSync3(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
15115
+ const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
15116
+ writeFileSync(tmpPath, JSON.stringify(marker) + "\n", { encoding: "utf8", mode: 384 });
15117
+ renameSync(tmpPath, filePath);
15118
+ }
15119
+ function readRestartConfirmMarker(filePath) {
15120
+ try {
15121
+ if (!existsSync3(filePath)) return null;
15122
+ const parsed = JSON.parse(readFileSync3(filePath, "utf8"));
15123
+ if (!parsed || typeof parsed !== "object") return null;
15124
+ return parsed;
15125
+ } catch {
15126
+ return null;
15127
+ }
15128
+ }
15129
+ function clearRestartConfirmMarker(filePath) {
15130
+ try {
15131
+ if (existsSync3(filePath)) unlinkSync2(filePath);
15132
+ } catch {
15133
+ }
15134
+ }
15135
+
14954
15136
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
14955
15137
  import process2 from "process";
14956
15138
 
@@ -15047,23 +15229,23 @@ var StdioServerTransport = class {
15047
15229
  import {
15048
15230
  chmodSync,
15049
15231
  createWriteStream,
15050
- existsSync as existsSync3,
15051
- mkdirSync as mkdirSync3,
15052
- readFileSync as readFileSync4,
15232
+ existsSync as existsSync6,
15233
+ mkdirSync as mkdirSync5,
15234
+ readFileSync as readFileSync7,
15053
15235
  readdirSync as readdirSync3,
15054
- renameSync as renameSync2,
15236
+ renameSync as renameSync3,
15055
15237
  statSync as statSync2,
15056
- unlinkSync as unlinkSync3,
15238
+ unlinkSync as unlinkSync4,
15057
15239
  watch,
15058
- writeFileSync as writeFileSync3
15240
+ writeFileSync as writeFileSync5
15059
15241
  } from "fs";
15060
- import { basename, join as join5, resolve as resolve2 } from "path";
15242
+ import { basename, join as join6, resolve as resolve2 } from "path";
15061
15243
  import { homedir as homedir2 } from "os";
15062
- import { createHash, randomUUID } from "crypto";
15244
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
15063
15245
 
15064
15246
  // src/slack-thread-store.ts
15065
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
15066
- import { dirname } from "path";
15247
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
15248
+ import { dirname as dirname2 } from "path";
15067
15249
  var FILE_VERSION = 1;
15068
15250
  var DEFAULT_TTL_DAYS = 30;
15069
15251
  var DEFAULT_MIN_INTERVAL_MS = 1e3;
@@ -15073,7 +15255,7 @@ function loadThreadStore(filePath, opts = {}) {
15073
15255
  const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
15074
15256
  let raw;
15075
15257
  try {
15076
- raw = readFileSync2(filePath, "utf-8");
15258
+ raw = readFileSync4(filePath, "utf-8");
15077
15259
  } catch {
15078
15260
  return { threads: /* @__PURE__ */ new Map(), pruned: 0 };
15079
15261
  }
@@ -15117,8 +15299,8 @@ function createThreadPersister(opts) {
15117
15299
  let pendingSnapshot = null;
15118
15300
  const writeNow = (snap) => {
15119
15301
  try {
15120
- mkdirSync(dirname(opts.filePath), { recursive: true });
15121
- writeFileSync(opts.filePath, serializeThreadStore(snap), "utf-8");
15302
+ mkdirSync2(dirname2(opts.filePath), { recursive: true });
15303
+ writeFileSync2(opts.filePath, serializeThreadStore(snap), "utf-8");
15122
15304
  lastWriteAt = Date.now();
15123
15305
  } catch (err) {
15124
15306
  opts.onError?.(
@@ -15186,6 +15368,72 @@ async function runOrRetry(fn, opts) {
15186
15368
  }
15187
15369
  }
15188
15370
 
15371
+ // src/slack-bot-photo.ts
15372
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
15373
+ import { dirname as dirname3 } from "path";
15374
+ async function applyBotPhoto(opts) {
15375
+ const fetchImpl = opts.fetchImpl ?? fetch;
15376
+ const log = opts.log ?? ((m) => {
15377
+ process.stderr.write(m);
15378
+ });
15379
+ const { token, avatarUrl, markerPath } = opts;
15380
+ if (markerPath && existsSync4(markerPath)) {
15381
+ try {
15382
+ if (readFileSync5(markerPath, "utf-8").trim() === avatarUrl) {
15383
+ return { status: "skipped-unchanged" };
15384
+ }
15385
+ } catch {
15386
+ }
15387
+ }
15388
+ try {
15389
+ const imgRes = await fetchImpl(avatarUrl, { signal: AbortSignal.timeout(1e4) });
15390
+ if (!imgRes.ok) {
15391
+ log(`slack-channel: avatar download failed (HTTP ${imgRes.status}) \u2014 skipping bot photo
15392
+ `);
15393
+ return { status: "download-failed", httpStatus: imgRes.status };
15394
+ }
15395
+ const bytes = new Uint8Array(await imgRes.arrayBuffer());
15396
+ const form = new FormData();
15397
+ form.append("image", new Blob([bytes], { type: "image/jpeg" }), "avatar.jpg");
15398
+ const res = await fetchImpl("https://slack.com/api/users.setPhoto", {
15399
+ method: "POST",
15400
+ headers: { Authorization: `Bearer ${token}` },
15401
+ body: form,
15402
+ signal: AbortSignal.timeout(15e3)
15403
+ });
15404
+ const data = await res.json();
15405
+ if (!data.ok) {
15406
+ const code = data.error ?? "unknown";
15407
+ if (code === "missing_scope") {
15408
+ if (!opts.warned || !opts.warned.has(code)) {
15409
+ opts.warned?.add(code);
15410
+ log(
15411
+ `slack-channel: users.setPhoto failed (missing_scope) \u2014 bot avatar disabled until users.profile:write is granted (re-OAuth required)
15412
+ `
15413
+ );
15414
+ }
15415
+ } else {
15416
+ log(`slack-channel: users.setPhoto failed (${code}) \u2014 bot avatar not updated
15417
+ `);
15418
+ }
15419
+ return { status: "slack-error", error: code };
15420
+ }
15421
+ if (markerPath) {
15422
+ try {
15423
+ mkdirSync3(dirname3(markerPath), { recursive: true, mode: 448 });
15424
+ writeFileSync3(markerPath, avatarUrl, { mode: 384 });
15425
+ } catch {
15426
+ }
15427
+ }
15428
+ log("slack-channel: bot profile photo updated from agent avatar\n");
15429
+ return { status: "applied" };
15430
+ } catch (err) {
15431
+ log(`slack-channel: applyBotPhotoOnFirstConnect threw: ${err.message}
15432
+ `);
15433
+ return { status: "threw", message: err.message };
15434
+ }
15435
+ }
15436
+
15189
15437
  // src/slack-inbound-files.ts
15190
15438
  function classifySlackFile(file) {
15191
15439
  if (!file || typeof file.id !== "string" || !file.id) return null;
@@ -15200,6 +15448,178 @@ function buildSafeInboundPath2(codeName, fileId, mimetype) {
15200
15448
  return buildSafeInboundPath(resolveSlackInboundDir(codeName), fileId, mimetype);
15201
15449
  }
15202
15450
 
15451
+ // src/slack-rich-text.ts
15452
+ var MAX_DEPTH = 12;
15453
+ function isRecord(v) {
15454
+ return typeof v === "object" && v !== null && !Array.isArray(v);
15455
+ }
15456
+ function asString(v) {
15457
+ return typeof v === "string" ? v : "";
15458
+ }
15459
+ function asArray(v) {
15460
+ return Array.isArray(v) ? v : [];
15461
+ }
15462
+ function isTableElement(el) {
15463
+ return isRecord(el) && asString(el.type).includes("table");
15464
+ }
15465
+ function renderEmoji(el) {
15466
+ const unicode = asString(el.unicode);
15467
+ if (unicode) {
15468
+ try {
15469
+ const cps = unicode.split("-").map((h) => parseInt(h, 16));
15470
+ if (cps.every((n) => Number.isFinite(n) && n >= 0 && n <= 1114111)) {
15471
+ return String.fromCodePoint(...cps);
15472
+ }
15473
+ } catch {
15474
+ }
15475
+ }
15476
+ const name = asString(el.name);
15477
+ return name ? `:${name}:` : "";
15478
+ }
15479
+ function renderLeaf(el) {
15480
+ if (!isRecord(el)) return "";
15481
+ switch (asString(el.type)) {
15482
+ case "text":
15483
+ return asString(el.text);
15484
+ case "link": {
15485
+ const label = asString(el.text);
15486
+ const url = asString(el.url);
15487
+ if (label && url && label !== url) return `${label} (${url})`;
15488
+ return label || url;
15489
+ }
15490
+ case "emoji":
15491
+ return renderEmoji(el);
15492
+ case "user":
15493
+ return `<@${asString(el.user_id)}>`;
15494
+ case "usergroup":
15495
+ return `<!subteam^${asString(el.usergroup_id)}>`;
15496
+ case "channel":
15497
+ return `<#${asString(el.channel_id)}>`;
15498
+ case "broadcast":
15499
+ return `@${asString(el.range) || "channel"}`;
15500
+ case "date":
15501
+ return asString(el.fallback) || asString(el.timestamp);
15502
+ default:
15503
+ if (Array.isArray(el.elements)) return renderInline(el.elements);
15504
+ return asString(el.text);
15505
+ }
15506
+ }
15507
+ function renderInline(elements) {
15508
+ return elements.map(renderLeaf).join("");
15509
+ }
15510
+ function deepText(node, depth) {
15511
+ if (depth > MAX_DEPTH) return "";
15512
+ if (typeof node === "string") return node;
15513
+ if (Array.isArray(node)) return node.map((n) => deepText(n, depth + 1)).join("");
15514
+ if (!isRecord(node)) return "";
15515
+ const type = asString(node.type);
15516
+ if (type && type !== "rich_text" && !type.startsWith("rich_text_") && !type.includes("table")) {
15517
+ return renderLeaf(node);
15518
+ }
15519
+ if (Array.isArray(node.elements)) return node.elements.map((n) => deepText(n, depth + 1)).join("");
15520
+ if (Array.isArray(node.rows)) return node.rows.map((n) => deepText(n, depth + 1)).join(" ");
15521
+ return asString(node.text);
15522
+ }
15523
+ function renderCell(cell) {
15524
+ if (!isRecord(cell)) return deepText(cell, 0).replace(/\s*\n\s*/g, " ").trim();
15525
+ const inner = Array.isArray(cell.elements) ? renderSection(cell, 0) : deepText(cell, 0);
15526
+ return inner.replace(/\s*\n\s*/g, " ").trim();
15527
+ }
15528
+ function renderTable(el) {
15529
+ const rawRows = asArray(el.elements).length ? asArray(el.elements) : asArray(el.rows);
15530
+ const rows = [];
15531
+ for (const row of rawRows) {
15532
+ if (!isRecord(row) && !Array.isArray(row)) continue;
15533
+ const rawCells = Array.isArray(row) ? row : asArray(row.elements).length ? asArray(row.elements) : asArray(row.cells);
15534
+ if (rawCells.length === 0) continue;
15535
+ rows.push(rawCells.map(renderCell));
15536
+ }
15537
+ if (rows.length === 0) {
15538
+ const fallback = deepText(el, 0).replace(/[ \t]+\n/g, "\n").trim();
15539
+ return fallback;
15540
+ }
15541
+ const colCount = rows.reduce((max, r) => Math.max(max, r.length), 0);
15542
+ const pad = (r) => {
15543
+ const cells = r.slice();
15544
+ while (cells.length < colCount) cells.push("");
15545
+ return cells.map((c) => c.replace(/\|/g, "\\|"));
15546
+ };
15547
+ const lines = [];
15548
+ const header = rows[0] ?? [];
15549
+ const body = rows.slice(1);
15550
+ lines.push(`| ${pad(header).join(" | ")} |`);
15551
+ lines.push(`| ${Array.from({ length: colCount }, () => "---").join(" | ")} |`);
15552
+ for (const r of body) lines.push(`| ${pad(r).join(" | ")} |`);
15553
+ return lines.join("\n");
15554
+ }
15555
+ function renderSection(section, depth) {
15556
+ if (depth > MAX_DEPTH || !isRecord(section)) return "";
15557
+ const type = asString(section.type);
15558
+ if (isTableElement(section)) return renderTable(section);
15559
+ switch (type) {
15560
+ case "rich_text_section":
15561
+ return renderInline(asArray(section.elements));
15562
+ case "rich_text_preformatted": {
15563
+ const code = renderInline(asArray(section.elements));
15564
+ return code ? `\`\`\`
15565
+ ${code}
15566
+ \`\`\`` : "";
15567
+ }
15568
+ case "rich_text_quote": {
15569
+ const quoted = renderInline(asArray(section.elements));
15570
+ return quoted ? quoted.split("\n").map((l) => `> ${l}`).join("\n") : "";
15571
+ }
15572
+ case "rich_text_list": {
15573
+ const ordered = asString(section.style) === "ordered";
15574
+ const items = asArray(section.elements).map((item, i) => {
15575
+ const body = renderSection(item, depth + 1) || renderInline(asArray(item?.elements));
15576
+ const marker = ordered ? `${i + 1}.` : "-";
15577
+ return `${marker} ${body}`.trimEnd();
15578
+ });
15579
+ return items.join("\n");
15580
+ }
15581
+ default:
15582
+ if (Array.isArray(section.elements)) return renderInline(asArray(section.elements));
15583
+ return deepText(section, depth + 1);
15584
+ }
15585
+ }
15586
+ function renderSlackBlocks(blocks) {
15587
+ let hadTable = false;
15588
+ const out = [];
15589
+ try {
15590
+ for (const block of asArray(blocks)) {
15591
+ if (!isRecord(block) || asString(block.type) !== "rich_text") continue;
15592
+ for (const section of asArray(block.elements)) {
15593
+ if (isTableElement(section)) hadTable = true;
15594
+ const rendered = renderSection(section, 0);
15595
+ if (rendered.trim().length > 0) out.push(rendered);
15596
+ }
15597
+ }
15598
+ } catch {
15599
+ }
15600
+ const text = out.join("\n\n").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
15601
+ return { text, hadTable };
15602
+ }
15603
+ function mergeInboundText(text, blocks) {
15604
+ const { text: rendered, hadTable } = renderSlackBlocks(blocks);
15605
+ if (!hadTable) return { content: text, recoveredTable: false };
15606
+ const content = rendered.length > 0 ? rendered : text;
15607
+ return { content, recoveredTable: rendered.length > 0 && content !== text };
15608
+ }
15609
+
15610
+ // src/slack-allowlist-source.ts
15611
+ function parseAllowedUsersCsv(raw) {
15612
+ return new Set(
15613
+ (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0)
15614
+ );
15615
+ }
15616
+ function extractAllowedUsersFromMcpJson(jsonText) {
15617
+ const parsed = JSON.parse(jsonText);
15618
+ const slackServer = parsed.mcpServers?.["slack"];
15619
+ if (!slackServer) return null;
15620
+ return parseAllowedUsersCsv(slackServer.env?.["SLACK_ALLOWED_USERS"]);
15621
+ }
15622
+
15203
15623
  // src/slack-response-mode.ts
15204
15624
  var MODES = [
15205
15625
  "mention_only",
@@ -15752,14 +16172,14 @@ function createSlackBotUserIdClient(args) {
15752
16172
 
15753
16173
  // src/mcp-spawn-lock.ts
15754
16174
  import {
15755
- existsSync as existsSync2,
15756
- mkdirSync as mkdirSync2,
15757
- readFileSync as readFileSync3,
15758
- renameSync,
15759
- unlinkSync as unlinkSync2,
15760
- writeFileSync as writeFileSync2
16175
+ existsSync as existsSync5,
16176
+ mkdirSync as mkdirSync4,
16177
+ readFileSync as readFileSync6,
16178
+ renameSync as renameSync2,
16179
+ unlinkSync as unlinkSync3,
16180
+ writeFileSync as writeFileSync4
15761
16181
  } from "fs";
15762
- import { join as join4 } from "path";
16182
+ import { join as join5 } from "path";
15763
16183
  function defaultIsPidAlive(pid) {
15764
16184
  if (!Number.isFinite(pid) || pid <= 0) return false;
15765
16185
  try {
@@ -15777,7 +16197,7 @@ function acquireMcpSpawnLock(args) {
15777
16197
  const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
15778
16198
  const selfPid = options.selfPid ?? process.pid;
15779
16199
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
15780
- const path = join4(agentDir, basename2);
16200
+ const path = join5(agentDir, basename2);
15781
16201
  const existing = readLockHolder(path);
15782
16202
  if (existing) {
15783
16203
  if (existing.pid === selfPid) {
@@ -15787,11 +16207,11 @@ function acquireMcpSpawnLock(args) {
15787
16207
  return { kind: "blocked", path, holder: existing };
15788
16208
  }
15789
16209
  }
15790
- mkdirSync2(agentDir, { recursive: true, mode: 448 });
16210
+ mkdirSync4(agentDir, { recursive: true, mode: 448 });
15791
16211
  const tmpPath = `${path}.${selfPid}.tmp`;
15792
16212
  const payload = { pid: selfPid, started_at: now() };
15793
- writeFileSync2(tmpPath, JSON.stringify(payload), { mode: 384 });
15794
- renameSync(tmpPath, path);
16213
+ writeFileSync4(tmpPath, JSON.stringify(payload), { mode: 384 });
16214
+ renameSync2(tmpPath, path);
15795
16215
  return { kind: "acquired", path };
15796
16216
  }
15797
16217
  function releaseMcpSpawnLock(lockPath, opts = {}) {
@@ -15801,14 +16221,14 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
15801
16221
  if (!existing) return;
15802
16222
  if (existing.pid !== selfPid) return;
15803
16223
  try {
15804
- unlinkSync2(lockPath);
16224
+ unlinkSync3(lockPath);
15805
16225
  } catch {
15806
16226
  }
15807
16227
  }
15808
16228
  function readLockHolder(path) {
15809
- if (!existsSync2(path)) return null;
16229
+ if (!existsSync5(path)) return null;
15810
16230
  try {
15811
- const raw = readFileSync3(path, "utf8");
16231
+ const raw = readFileSync6(path, "utf8");
15812
16232
  const parsed = JSON.parse(raw);
15813
16233
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
15814
16234
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -15823,6 +16243,7 @@ function readLockHolder(path) {
15823
16243
  var BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
15824
16244
  var APP_TOKEN = process.env.SLACK_APP_TOKEN;
15825
16245
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? null;
16246
+ var AGENT_AVATAR_URL = process.env.SLACK_AGENT_AVATAR_URL?.trim() || null;
15826
16247
  var SLACK_COMMAND_MAX_LENGTH = 32;
15827
16248
  function agentSlashCommand(base) {
15828
16249
  const codeName = AGENT_CODE_NAME?.trim().toLowerCase();
@@ -15937,9 +16358,7 @@ async function maybeSendSenderPolicyDecline(args) {
15937
16358
  var BLOCK_KIT_ENABLED = process.env.SLACK_BLOCK_KIT_ENABLED === "true";
15938
16359
  var BLOCK_KIT_ASK_USER_ENABLED = process.env.SLACK_BLOCK_KIT_ASK_USER_ENABLED === "true";
15939
16360
  var BLOCK_KIT_DISABLED = process.env.SLACK_BLOCK_KIT_DISABLED === "true";
15940
- var ALLOWED_USERS = new Set(
15941
- (process.env.SLACK_ALLOWED_USERS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
15942
- );
16361
+ var ALLOWED_USERS = parseAllowedUsersCsv(process.env.SLACK_ALLOWED_USERS);
15943
16362
  var THREAD_AUTO_FOLLOW = process.env.SLACK_THREAD_AUTO_FOLLOW ?? "off";
15944
16363
  var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
15945
16364
  var SLACK_PEER_DISABLED_MODE = (() => {
@@ -15985,10 +16404,34 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
15985
16404
  peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
15986
16405
  peer_disabled_mode: SLACK_PEER_DISABLED_MODE
15987
16406
  };
15988
- var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join5(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15989
- var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join5(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15990
- var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join5(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
16407
+ var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join6(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
16408
+ var SLACK_MCP_CONFIG_PATH = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "project", ".mcp.json") : null;
16409
+ var liveAllowedUsersCache = null;
16410
+ function readLiveAllowedUsers() {
16411
+ if (!SLACK_MCP_CONFIG_PATH) return null;
16412
+ try {
16413
+ const mtimeMs = statSync2(SLACK_MCP_CONFIG_PATH).mtimeMs;
16414
+ if (liveAllowedUsersCache && liveAllowedUsersCache.mtimeMs === mtimeMs) {
16415
+ return liveAllowedUsersCache.value;
16416
+ }
16417
+ const value = extractAllowedUsersFromMcpJson(
16418
+ readFileSync7(SLACK_MCP_CONFIG_PATH, "utf-8")
16419
+ );
16420
+ if (value === null) return null;
16421
+ liveAllowedUsersCache = { mtimeMs, value };
16422
+ return value;
16423
+ } catch {
16424
+ return null;
16425
+ }
16426
+ }
16427
+ function getEffectiveAllowedUsers() {
16428
+ return readLiveAllowedUsers() ?? ALLOWED_USERS;
16429
+ }
16430
+ var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
16431
+ var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
16432
+ var SLACK_RESTART_CONFIRM_FILE = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-restart-confirm.json") : null;
15991
16433
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
16434
+ var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-avatar-applied") : null;
15992
16435
  function redactSlackId(id) {
15993
16436
  if (!id) return "<none>";
15994
16437
  return createHash("sha256").update(id).digest("hex").slice(0, 8);
@@ -15999,7 +16442,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
15999
16442
  }
16000
16443
  function slackPendingInboundPath(channel, threadTs, messageTs) {
16001
16444
  if (!SLACK_PENDING_INBOUND_DIR) return null;
16002
- return join5(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
16445
+ return join6(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
16003
16446
  }
16004
16447
  function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false) {
16005
16448
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
@@ -16013,8 +16456,8 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
16013
16456
  ...undeliverable ? { undeliverable: true } : {}
16014
16457
  };
16015
16458
  try {
16016
- mkdirSync3(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
16017
- writeFileSync3(path, JSON.stringify(marker), { mode: 384 });
16459
+ mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
16460
+ writeFileSync5(path, JSON.stringify(marker), { mode: 384 });
16018
16461
  } catch (err) {
16019
16462
  process.stderr.write(
16020
16463
  `slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
@@ -16022,6 +16465,15 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
16022
16465
  );
16023
16466
  }
16024
16467
  }
16468
+ function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
16469
+ const path = slackPendingInboundPath(channel, threadTs, messageTs);
16470
+ if (!path || !existsSync6(path)) return null;
16471
+ try {
16472
+ return JSON.parse(readFileSync7(path, "utf-8"));
16473
+ } catch {
16474
+ return null;
16475
+ }
16476
+ }
16025
16477
  function healSlackUndeliverable(channel, messageTs) {
16026
16478
  if (!BOT_TOKEN || !channel || !messageTs) return;
16027
16479
  const headers = {
@@ -16072,10 +16524,71 @@ function postUndeliverableNotice(channel, threadTs, isThreadReply) {
16072
16524
  function __resetSlackUndeliverableNoticeThrottle() {
16073
16525
  lastSlackUndeliverableNoticeAt.clear();
16074
16526
  }
16527
+ var lastSlackBusyAckNoticeAt = /* @__PURE__ */ new Map();
16528
+ function postBusyAckNotice(channel, threadTs, isThreadReply) {
16529
+ if (!BOT_TOKEN || !channel) return;
16530
+ const now = Date.now();
16531
+ const conversationKey = isThreadReply && threadTs ? `${channel}:${threadTs}` : channel;
16532
+ if (!shouldPostUndeliverableNotice(
16533
+ lastSlackBusyAckNoticeAt.get(conversationKey),
16534
+ now,
16535
+ BUSY_ACK_NOTICE_THROTTLE_MS
16536
+ )) {
16537
+ return;
16538
+ }
16539
+ lastSlackBusyAckNoticeAt.set(conversationKey, now);
16540
+ fetch("https://slack.com/api/chat.postMessage", {
16541
+ method: "POST",
16542
+ headers: {
16543
+ "Content-Type": "application/json",
16544
+ Authorization: `Bearer ${BOT_TOKEN}`
16545
+ },
16546
+ body: JSON.stringify({
16547
+ channel,
16548
+ text: busyAckNoticeText(),
16549
+ ...threadTs ? { thread_ts: threadTs } : {}
16550
+ }),
16551
+ // Bound the request so a hung Slack call can't dangle to the platform
16552
+ // timeout (matches the other bounded REST calls in this file). 10s mirrors
16553
+ // Telegram's busy-ack send; fire-and-forget, an abort is swallowed below.
16554
+ signal: AbortSignal.timeout(1e4)
16555
+ }).catch(() => {
16556
+ });
16557
+ }
16558
+ function scheduleBusyAck(channel, threadTs, messageTs, isThreadReply) {
16559
+ if (!channelBusyAckEnabled()) return;
16560
+ const thresholdMs = channelBusyAckThresholdMs();
16561
+ const timer = setTimeout(() => {
16562
+ const probe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
16563
+ let paneLogFreshAgeMs = null;
16564
+ if (SLACK_AGENT_DIR) {
16565
+ try {
16566
+ const paneMtimeMs = statSync2(join6(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
16567
+ paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
16568
+ } catch {
16569
+ }
16570
+ }
16571
+ const marker = readSlackPendingInboundMarker(channel, threadTs, messageTs);
16572
+ if (!marker) return;
16573
+ const post = decideBusyAck({
16574
+ hasTarget: Boolean(channel && messageTs),
16575
+ stillPending: true,
16576
+ pendingAgeMs: Math.max(0, Date.now() - Date.parse(marker.received_at)),
16577
+ sessionAlive: probe.tmux === "alive" && probe.claude === "alive",
16578
+ paneLogFreshAgeMs,
16579
+ thresholdMs
16580
+ });
16581
+ if (post) postBusyAckNotice(channel, threadTs, isThreadReply);
16582
+ }, thresholdMs);
16583
+ timer.unref();
16584
+ }
16585
+ function __resetSlackBusyAckNoticeThrottle() {
16586
+ lastSlackBusyAckNoticeAt.clear();
16587
+ }
16075
16588
  function clearSlackMarkerFileWithHeal(fullPath) {
16076
16589
  let marker = null;
16077
16590
  try {
16078
- marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
16591
+ marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
16079
16592
  } catch {
16080
16593
  }
16081
16594
  if (marker && decideRecoveryHeal({
@@ -16085,7 +16598,7 @@ function clearSlackMarkerFileWithHeal(fullPath) {
16085
16598
  healSlackUndeliverable(marker.channel, marker.message_ts);
16086
16599
  }
16087
16600
  try {
16088
- if (existsSync3(fullPath)) unlinkSync3(fullPath);
16601
+ if (existsSync6(fullPath)) unlinkSync4(fullPath);
16089
16602
  } catch {
16090
16603
  }
16091
16604
  }
@@ -16126,17 +16639,17 @@ function slackNextRetryName(filename) {
16126
16639
  async function processSlackRecoveryOutboxFile(filename) {
16127
16640
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
16128
16641
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
16129
- const fullPath = join5(SLACK_RECOVERY_OUTBOX_DIR, filename);
16642
+ const fullPath = join6(SLACK_RECOVERY_OUTBOX_DIR, filename);
16130
16643
  let payload;
16131
16644
  try {
16132
- payload = JSON.parse(readFileSync4(fullPath, "utf-8"));
16645
+ payload = JSON.parse(readFileSync7(fullPath, "utf-8"));
16133
16646
  } catch (err) {
16134
16647
  process.stderr.write(
16135
16648
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
16136
16649
  `
16137
16650
  );
16138
16651
  try {
16139
- renameSync2(fullPath, `${fullPath}.parse-error.poison`);
16652
+ renameSync3(fullPath, `${fullPath}.parse-error.poison`);
16140
16653
  } catch {
16141
16654
  }
16142
16655
  return;
@@ -16147,7 +16660,7 @@ async function processSlackRecoveryOutboxFile(filename) {
16147
16660
  `
16148
16661
  );
16149
16662
  try {
16150
- renameSync2(fullPath, `${fullPath}.malformed.poison`);
16663
+ renameSync3(fullPath, `${fullPath}.malformed.poison`);
16151
16664
  } catch {
16152
16665
  }
16153
16666
  return;
@@ -16195,7 +16708,7 @@ async function processSlackRecoveryOutboxFile(filename) {
16195
16708
  }
16196
16709
  if (sendSucceeded) {
16197
16710
  try {
16198
- unlinkSync3(fullPath);
16711
+ unlinkSync4(fullPath);
16199
16712
  } catch {
16200
16713
  }
16201
16714
  return;
@@ -16203,7 +16716,7 @@ async function processSlackRecoveryOutboxFile(filename) {
16203
16716
  const next = slackNextRetryName(filename);
16204
16717
  if (next) {
16205
16718
  try {
16206
- renameSync2(fullPath, join5(SLACK_RECOVERY_OUTBOX_DIR, next.next));
16719
+ renameSync3(fullPath, join6(SLACK_RECOVERY_OUTBOX_DIR, next.next));
16207
16720
  if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
16208
16721
  process.stderr.write(
16209
16722
  `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -16242,7 +16755,7 @@ function scanSlackRecoveryRetries() {
16242
16755
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
16243
16756
  let mtimeMs;
16244
16757
  try {
16245
- mtimeMs = statSync2(join5(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16758
+ mtimeMs = statSync2(join6(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16246
16759
  } catch {
16247
16760
  continue;
16248
16761
  }
@@ -16254,7 +16767,7 @@ function scanSlackRecoveryRetries() {
16254
16767
  function startSlackRecoveryOutboxWatcher() {
16255
16768
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
16256
16769
  try {
16257
- mkdirSync3(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
16770
+ mkdirSync5(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
16258
16771
  } catch (err) {
16259
16772
  process.stderr.write(
16260
16773
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox mkdir failed: ${err.message}
@@ -16272,7 +16785,7 @@ function startSlackRecoveryOutboxWatcher() {
16272
16785
  const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
16273
16786
  if (event !== "rename" || !filename) return;
16274
16787
  if (!isFirstAttemptSlackOutboxFile(filename)) return;
16275
- if (existsSync3(join5(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
16788
+ if (existsSync6(join6(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
16276
16789
  void processSlackRecoveryOutboxFile(filename);
16277
16790
  }
16278
16791
  });
@@ -16293,7 +16806,7 @@ function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false
16293
16806
  }
16294
16807
  function sweepSlackStaleMarkers(thresholdMs) {
16295
16808
  if (!SLACK_PENDING_INBOUND_DIR) return;
16296
- if (!existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
16809
+ if (!existsSync6(SLACK_PENDING_INBOUND_DIR)) return;
16297
16810
  let filenames;
16298
16811
  try {
16299
16812
  filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
@@ -16309,17 +16822,17 @@ function sweepSlackStaleMarkers(thresholdMs) {
16309
16822
  for (const filename of filenames) {
16310
16823
  if (!filename.endsWith(".json")) continue;
16311
16824
  if (filename.endsWith(".tmp")) continue;
16312
- const fullPath = join5(SLACK_PENDING_INBOUND_DIR, filename);
16825
+ const fullPath = join6(SLACK_PENDING_INBOUND_DIR, filename);
16313
16826
  let marker;
16314
16827
  try {
16315
- marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
16828
+ marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
16316
16829
  } catch (err) {
16317
16830
  process.stderr.write(
16318
16831
  `slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
16319
16832
  `
16320
16833
  );
16321
16834
  try {
16322
- unlinkSync3(fullPath);
16835
+ unlinkSync4(fullPath);
16323
16836
  } catch {
16324
16837
  }
16325
16838
  cleared++;
@@ -16328,7 +16841,7 @@ function sweepSlackStaleMarkers(thresholdMs) {
16328
16841
  const { channel, thread_ts, message_ts, received_at } = marker;
16329
16842
  if (!channel || !thread_ts || !message_ts || isPendingMarkerStale(received_at, now, thresholdMs)) {
16330
16843
  try {
16331
- unlinkSync3(fullPath);
16844
+ unlinkSync4(fullPath);
16332
16845
  } catch {
16333
16846
  }
16334
16847
  cleared++;
@@ -16351,14 +16864,14 @@ var slackOrphanSweepTimer = setInterval(() => {
16351
16864
  slackOrphanSweepTimer.unref?.();
16352
16865
  var lastSlackGiveUpHandledAtMs = null;
16353
16866
  function listPendingSlackConversations() {
16354
- if (!SLACK_PENDING_INBOUND_DIR || !existsSync3(SLACK_PENDING_INBOUND_DIR)) return [];
16867
+ if (!SLACK_PENDING_INBOUND_DIR || !existsSync6(SLACK_PENDING_INBOUND_DIR)) return [];
16355
16868
  const byKey = /* @__PURE__ */ new Map();
16356
16869
  try {
16357
16870
  for (const name of readdirSync3(SLACK_PENDING_INBOUND_DIR)) {
16358
16871
  if (!name.endsWith(".json")) continue;
16359
16872
  try {
16360
16873
  const marker = JSON.parse(
16361
- readFileSync4(join5(SLACK_PENDING_INBOUND_DIR, name), "utf8")
16874
+ readFileSync7(join6(SLACK_PENDING_INBOUND_DIR, name), "utf8")
16362
16875
  );
16363
16876
  if (typeof marker.channel !== "string" || !marker.channel) continue;
16364
16877
  if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
@@ -16408,7 +16921,7 @@ function postSlackWatchdogGiveUpNotice(channel, threadTs, isThreadReply) {
16408
16921
  }
16409
16922
  function checkSlackWatchdogGiveUpNotice() {
16410
16923
  if (!SLACK_AGENT_DIR) return;
16411
- const signalAtMs = readGiveUpSignalAtMs(join5(SLACK_AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
16924
+ const signalAtMs = readGiveUpSignalAtMs(join6(SLACK_AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
16412
16925
  const act = decideGiveUpNotice({
16413
16926
  signalAtMs,
16414
16927
  lastHandledAtMs: lastSlackGiveUpHandledAtMs,
@@ -16435,7 +16948,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
16435
16948
  strandedInboundNoticeInFlight = true;
16436
16949
  let hadFailure = false;
16437
16950
  try {
16438
- if (!SLACK_PENDING_INBOUND_DIR || !existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
16951
+ if (!SLACK_PENDING_INBOUND_DIR || !existsSync6(SLACK_PENDING_INBOUND_DIR)) return;
16439
16952
  let filenames;
16440
16953
  try {
16441
16954
  filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
@@ -16448,10 +16961,10 @@ async function notifyStrandedInboundsOnFirstConnect() {
16448
16961
  let notified = 0;
16449
16962
  for (const filename of filenames) {
16450
16963
  if (!filename.endsWith(".json")) continue;
16451
- const fullPath = join5(SLACK_PENDING_INBOUND_DIR, filename);
16964
+ const fullPath = join6(SLACK_PENDING_INBOUND_DIR, filename);
16452
16965
  let marker;
16453
16966
  try {
16454
- marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
16967
+ marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
16455
16968
  } catch {
16456
16969
  continue;
16457
16970
  }
@@ -16464,7 +16977,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
16464
16977
  const key2 = `${channel}__${thread_ts}`;
16465
16978
  if (seen.has(key2)) {
16466
16979
  try {
16467
- unlinkSync3(fullPath);
16980
+ unlinkSync4(fullPath);
16468
16981
  } catch {
16469
16982
  }
16470
16983
  continue;
@@ -16478,7 +16991,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
16478
16991
  if (res.ok) {
16479
16992
  notified += 1;
16480
16993
  try {
16481
- unlinkSync3(fullPath);
16994
+ unlinkSync4(fullPath);
16482
16995
  } catch {
16483
16996
  }
16484
16997
  } else {
@@ -16500,6 +17013,73 @@ async function notifyStrandedInboundsOnFirstConnect() {
16500
17013
  strandedInboundNoticeInFlight = false;
16501
17014
  }
16502
17015
  }
17016
+ var restartConfirmNoticeFired = false;
17017
+ var restartConfirmNoticeInFlight = false;
17018
+ var firstConnectNoticesGated = false;
17019
+ var suppressStrandedForBoot = false;
17020
+ async function notifyRestartCompleteOnFirstConnect() {
17021
+ if (restartConfirmNoticeFired || restartConfirmNoticeInFlight) return;
17022
+ if (!SLACK_RESTART_CONFIRM_FILE) return;
17023
+ restartConfirmNoticeInFlight = true;
17024
+ let hadFailure = false;
17025
+ try {
17026
+ const marker = readRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
17027
+ if (!shouldPostRestartConfirm(marker, SLACK_PROCESS_BOOT_MS, Date.now())) {
17028
+ if (marker) clearRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
17029
+ return;
17030
+ }
17031
+ const channel = marker.reply["channel"];
17032
+ if (channel == null) {
17033
+ clearRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
17034
+ return;
17035
+ }
17036
+ const threadTs = marker.reply["thread_ts"];
17037
+ const res = await postSlackMessage({
17038
+ channel: String(channel),
17039
+ ...threadTs != null ? { thread_ts: String(threadTs) } : {},
17040
+ text: buildBackOnlineText(marker.requester_name)
17041
+ });
17042
+ if (res.ok) {
17043
+ clearRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
17044
+ process.stderr.write(
17045
+ `slack-channel(${AGENT_CODE_NAME}): posted back-online confirmation after restart
17046
+ `
17047
+ );
17048
+ } else {
17049
+ hadFailure = true;
17050
+ process.stderr.write(
17051
+ `slack-channel(${AGENT_CODE_NAME}): back-online confirmation failed: ${res.error}
17052
+ `
17053
+ );
17054
+ }
17055
+ } catch (err) {
17056
+ hadFailure = true;
17057
+ process.stderr.write(
17058
+ `slack-channel(${AGENT_CODE_NAME}): back-online confirmation threw: ${redactAugmentedPaths2(err.message)}
17059
+ `
17060
+ );
17061
+ } finally {
17062
+ if (!hadFailure) restartConfirmNoticeFired = true;
17063
+ restartConfirmNoticeInFlight = false;
17064
+ }
17065
+ }
17066
+ function writeSlackRestartConfirm(reply, requesterName) {
17067
+ if (!SLACK_RESTART_CONFIRM_FILE) return;
17068
+ const marker = {
17069
+ source: "slack",
17070
+ requested_at: Date.now(),
17071
+ ...requesterName ? { requester_name: requesterName } : {},
17072
+ reply
17073
+ };
17074
+ try {
17075
+ writeRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE, marker);
17076
+ } catch (err) {
17077
+ process.stderr.write(
17078
+ `slack-channel(${AGENT_CODE_NAME}): restart-confirm marker write failed: ${redactAugmentedPaths2(err.message)}
17079
+ `
17080
+ );
17081
+ }
17082
+ }
16503
17083
  function clearPendingMessage(channel, threadTs) {
16504
17084
  clearAllSlackPendingMarkersForThread2(channel, threadTs);
16505
17085
  }
@@ -16511,7 +17091,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16511
17091
  if (!channel || !messageTs) return;
16512
17092
  clearPendingMessage(channel, messageTs);
16513
17093
  if (!SLACK_PENDING_INBOUND_DIR) return;
16514
- if (!existsSync3(SLACK_PENDING_INBOUND_DIR)) return;
17094
+ if (!existsSync6(SLACK_PENDING_INBOUND_DIR)) return;
16515
17095
  let filenames;
16516
17096
  try {
16517
17097
  filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
@@ -16525,10 +17105,10 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16525
17105
  for (const filename of filenames) {
16526
17106
  if (!filename.startsWith(channelPrefix)) continue;
16527
17107
  if (!filename.endsWith(messageSuffix)) continue;
16528
- clearSlackMarkerFileWithHeal(join5(SLACK_PENDING_INBOUND_DIR, filename));
17108
+ clearSlackMarkerFileWithHeal(join6(SLACK_PENDING_INBOUND_DIR, filename));
16529
17109
  }
16530
17110
  }
16531
- var RESTART_FLAGS_DIR = join5(homedir2(), ".augmented", "restart-flags");
17111
+ var RESTART_FLAGS_DIR = join6(homedir2(), ".augmented", "restart-flags");
16532
17112
  function buildAugmentedSlackMetadata() {
16533
17113
  if (!AGT_TEAM_ID) return void 0;
16534
17114
  return {
@@ -16579,9 +17159,9 @@ function buildSlackHelpMessage(codeName) {
16579
17159
  `\u{1F916} *Available commands for \`${codeName}\`*`,
16580
17160
  "",
16581
17161
  "All commands are real Slack slash commands (autocomplete via `/`). For backward compatibility, `/help` and the restart command can also be typed as plain messages in any channel where the bot is present:",
16582
- "\u2022 `/help` \u2014 show this help",
17162
+ `\u2022 \`${agentSlashCommand("/help")}\` (or type \`/help\`) \u2014 show this help`,
16583
17163
  `\u2022 \`${agentSlashCommand("/restart")}\` \u2014 restart this agent`,
16584
- `\u2022 \`${agentSlashCommand("/agent-status")}\` \u2014 report whether this agent is online + last activity`,
17164
+ `\u2022 \`${agentSlashCommand("/status")}\` \u2014 this agent's model, session origin, uptime + connectivity`,
16585
17165
  "\u2022 `/kill` \u2014 silence all agents in this thread for 6h (use as a thread reply)",
16586
17166
  "\u2022 `/unkill` \u2014 clear a kill (use as a thread reply)",
16587
17167
  `\u2022 \`${agentSlashCommand("/investigate")}\` \u2014 live tail of this agent's terminal pane (DM only, allowlisted users; works while the channel process is alive \u2014 a wedged host still needs SSM diagnostics)`
@@ -16642,6 +17222,19 @@ function clearBotStatusBestEffort() {
16642
17222
  }).catch(() => {
16643
17223
  });
16644
17224
  }
17225
+ var botPhotoApplyFired = false;
17226
+ var setBotPhotoWarnedErrors = /* @__PURE__ */ new Set();
17227
+ async function applyBotPhotoOnFirstConnect() {
17228
+ if (botPhotoApplyFired) return;
17229
+ if (!BOT_TOKEN || !AGENT_AVATAR_URL) return;
17230
+ botPhotoApplyFired = true;
17231
+ await applyBotPhoto({
17232
+ token: BOT_TOKEN,
17233
+ avatarUrl: AGENT_AVATAR_URL,
17234
+ markerPath: SLACK_AVATAR_MARKER_PATH,
17235
+ warned: setBotPhotoWarnedErrors
17236
+ });
17237
+ }
16645
17238
  function formatLastActivity() {
16646
17239
  if (lastActivityAt == null || lastActivityKind == null) return "no activity yet";
16647
17240
  const seconds = Math.max(0, Math.floor((Date.now() - lastActivityAt) / 1e3));
@@ -16653,7 +17246,9 @@ function buildAgentStatusReply(codeName) {
16653
17246
  const connected = currentWs != null && !isShuttingDown;
16654
17247
  const dot = connected ? "\u{1F7E2}" : "\u{1F534}";
16655
17248
  const state = connected ? "online" : "offline";
16656
- return `${dot} \`${codeName}\` is *${state}* \u2014 Socket Mode ${connected ? "connected" : "disconnected"}. Last activity: ${formatLastActivity()}.`;
17249
+ const connectivityLine = `${dot} *${state}* \u2014 Socket Mode ${connected ? "connected" : "disconnected"}. Last activity: ${formatLastActivity()}.`;
17250
+ const sessionState = readAgentSessionState(SLACK_AGENT_DIR);
17251
+ return buildAgentConfigReport({ codeName, connectivityLine, state: sessionState });
16657
17252
  }
16658
17253
  async function postEphemeralViaResponseUrl(responseUrl, text, logTag) {
16659
17254
  try {
@@ -16775,7 +17370,7 @@ async function handleDebugSlashCommand(payload, responseUrl) {
16775
17370
  const verdict = evaluateDebugGate({
16776
17371
  channelId: payload.channel_id,
16777
17372
  userId: payload.user_id,
16778
- allowedUsers: ALLOWED_USERS
17373
+ allowedUsers: getEffectiveAllowedUsers()
16779
17374
  });
16780
17375
  const investigateCmd = agentSlashCommand("/investigate");
16781
17376
  if (!verdict.ok) {
@@ -16868,16 +17463,17 @@ async function handleSlashCommandEnvelope(payload) {
16868
17463
  const responseUrl = payload.response_url;
16869
17464
  const codeName = AGENT_CODE_NAME ?? "unknown";
16870
17465
  if (!command || !responseUrl) return;
16871
- if (matchesAgentCommand(command, "/agent-status")) {
17466
+ if (matchesAgentCommand(command, "/status") || matchesAgentCommand(command, "/agent-status")) {
16872
17467
  await postEphemeralViaResponseUrl(responseUrl, buildAgentStatusReply(codeName), codeName);
16873
17468
  return;
16874
17469
  }
16875
- if (command === "/help") {
17470
+ if (matchesAgentCommand(command, "/help")) {
16876
17471
  await postEphemeralViaResponseUrl(responseUrl, buildSlackHelpMessage(codeName), codeName);
16877
17472
  return;
16878
17473
  }
16879
17474
  if (matchesAgentCommand(command, "/restart")) {
16880
- if (ALLOWED_USERS.size > 0 && (!payload.user_id || !ALLOWED_USERS.has(payload.user_id))) {
17475
+ const restartAllowedUsers = getEffectiveAllowedUsers();
17476
+ if (restartAllowedUsers.size > 0 && (!payload.user_id || !restartAllowedUsers.has(payload.user_id))) {
16881
17477
  process.stderr.write(
16882
17478
  `slack-channel(${codeName}): /restart slash-command denied \u2014 user not in SLACK_ALLOWED_USERS
16883
17479
  `
@@ -16898,10 +17494,17 @@ async function handleSlashCommandEnvelope(payload) {
16898
17494
  return;
16899
17495
  }
16900
17496
  try {
16901
- if (!existsSync3(RESTART_FLAGS_DIR)) {
16902
- mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
17497
+ if (!existsSync6(RESTART_FLAGS_DIR)) {
17498
+ mkdirSync5(RESTART_FLAGS_DIR, { recursive: true });
16903
17499
  }
16904
- const flagPath = join5(RESTART_FLAGS_DIR, `${codeName}.flag`);
17500
+ const flagPath = join6(RESTART_FLAGS_DIR, `${codeName}.flag`);
17501
+ writeSlackRestartConfirm(
17502
+ {
17503
+ channel: payload.channel_id,
17504
+ ...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
17505
+ },
17506
+ payload.user_name
17507
+ );
16905
17508
  const flag = {
16906
17509
  codeName,
16907
17510
  source: "slack",
@@ -16911,9 +17514,9 @@ async function handleSlashCommandEnvelope(payload) {
16911
17514
  ...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
16912
17515
  }
16913
17516
  };
16914
- const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
16915
- writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16916
- renameSync2(tmpPath, flagPath);
17517
+ const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
17518
+ writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17519
+ renameSync3(tmpPath, flagPath);
16917
17520
  process.stderr.write(
16918
17521
  `slack-channel(${codeName}): /restart slash-command queued from channel ${hashChannelId(payload.channel_id)}
16919
17522
  `
@@ -17009,10 +17612,17 @@ async function handleHelpCommand(opts) {
17009
17612
  async function handleRestartCommand(opts) {
17010
17613
  const codeName = AGENT_CODE_NAME ?? "unknown";
17011
17614
  try {
17012
- if (!existsSync3(RESTART_FLAGS_DIR)) {
17013
- mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
17615
+ if (!existsSync6(RESTART_FLAGS_DIR)) {
17616
+ mkdirSync5(RESTART_FLAGS_DIR, { recursive: true });
17014
17617
  }
17015
- const flagPath = join5(RESTART_FLAGS_DIR, `${codeName}.flag`);
17618
+ const flagPath = join6(RESTART_FLAGS_DIR, `${codeName}.flag`);
17619
+ writeSlackRestartConfirm(
17620
+ {
17621
+ channel: opts.channel,
17622
+ ...opts.threadTs ? { thread_ts: opts.threadTs } : {}
17623
+ },
17624
+ opts.requesterName
17625
+ );
17016
17626
  const flag = {
17017
17627
  codeName,
17018
17628
  source: "slack",
@@ -17023,9 +17633,9 @@ async function handleRestartCommand(opts) {
17023
17633
  message_ts: opts.ts
17024
17634
  }
17025
17635
  };
17026
- const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
17027
- writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17028
- renameSync2(tmpPath, flagPath);
17636
+ const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
17637
+ writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
17638
+ renameSync3(tmpPath, flagPath);
17029
17639
  process.stderr.write(
17030
17640
  `slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
17031
17641
  `
@@ -17071,7 +17681,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
17071
17681
  var threadPersister = null;
17072
17682
  function resolveThreadStorePath() {
17073
17683
  if (!AGENT_CODE_NAME) return null;
17074
- return join5(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
17684
+ return join6(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
17075
17685
  }
17076
17686
  function parseTtlDays(raw) {
17077
17687
  if (!raw) return void 0;
@@ -17106,9 +17716,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
17106
17716
  var slackStderrLogStream = null;
17107
17717
  if (AGENT_CODE_NAME) {
17108
17718
  try {
17109
- const logDir = join5(homedir2(), ".augmented", AGENT_CODE_NAME);
17110
- mkdirSync3(logDir, { recursive: true });
17111
- slackStderrLogStream = createWriteStream(join5(logDir, "slack-channel-stderr.log"), {
17719
+ const logDir = join6(homedir2(), ".augmented", AGENT_CODE_NAME);
17720
+ mkdirSync5(logDir, { recursive: true });
17721
+ slackStderrLogStream = createWriteStream(join6(logDir, "slack-channel-stderr.log"), {
17112
17722
  flags: "a",
17113
17723
  mode: 384
17114
17724
  });
@@ -17682,7 +18292,7 @@ ${result.formatted}` : "Thread is empty or not found."
17682
18292
  };
17683
18293
  }
17684
18294
  size = stat2.size;
17685
- bytes = readFileSync4(resolvedPath);
18295
+ bytes = readFileSync7(resolvedPath);
17686
18296
  } catch (err) {
17687
18297
  return {
17688
18298
  content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
@@ -18320,8 +18930,8 @@ async function downloadSlackFile(fileId, codeName) {
18320
18930
  if (!isPathInside(savedPath, dir)) {
18321
18931
  throw new Error(`refusing to write ${savedPath} outside ${dir}`);
18322
18932
  }
18323
- mkdirSync3(dir, { recursive: true });
18324
- writeFileSync3(savedPath, bytes, { mode: 384 });
18933
+ mkdirSync5(dir, { recursive: true });
18934
+ writeFileSync5(savedPath, bytes, { mode: 384 });
18325
18935
  try {
18326
18936
  chmodSync(savedPath, 384);
18327
18937
  } catch {
@@ -18432,7 +19042,19 @@ async function connectSocketMode() {
18432
19042
  process.stderr.write("slack-channel: Socket Mode connected\n");
18433
19043
  recordActivity("connect");
18434
19044
  void setBotStatus(":large_green_circle:", "Active");
18435
- void notifyStrandedInboundsOnFirstConnect();
19045
+ if (!firstConnectNoticesGated) {
19046
+ firstConnectNoticesGated = true;
19047
+ suppressStrandedForBoot = SLACK_RESTART_CONFIRM_FILE != null && shouldPostRestartConfirm(
19048
+ readRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE),
19049
+ SLACK_PROCESS_BOOT_MS,
19050
+ Date.now()
19051
+ );
19052
+ }
19053
+ if (!suppressStrandedForBoot) {
19054
+ void notifyStrandedInboundsOnFirstConnect();
19055
+ }
19056
+ void notifyRestartCompleteOnFirstConnect();
19057
+ void applyBotPhotoOnFirstConnect();
18436
19058
  };
18437
19059
  ws.onmessage = async (event) => {
18438
19060
  try {
@@ -18486,130 +19108,134 @@ async function connectSocketMode() {
18486
19108
  const evt = msg.payload.event;
18487
19109
  if (evt.user === botUserId) return;
18488
19110
  if (evt.type !== "app_mention" && evt.type !== "message") return;
19111
+ const isBot = Boolean(evt.bot_id);
18489
19112
  const filterDecision = decideSlackMessageForward(evt);
18490
- if (!filterDecision.forward) {
18491
- process.stderr.write(`slack-channel: dropped message event (reason=${filterDecision.reason}, subtype=${evt.subtype ?? "none"}, ts=${evt.ts ?? "n/a"})
18492
- `);
18493
- return;
18494
- }
18495
19113
  const senderPolicyDecision = decideSenderPolicyForward(evt, SLACK_SENDER_POLICY);
18496
- if (!senderPolicyDecision.forward) {
18497
- const subReason = classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
18498
- process.stderr.write(`slack-channel: dropped message event (reason=sender_policy, sub=${subReason}, mode=${SLACK_SENDER_POLICY.mode}, ts=${evt.ts ?? "n/a"})
18499
- `);
18500
- await maybeSendSenderPolicyDecline({
18501
- channel: evt.channel,
18502
- senderId: evt.user,
18503
- // CR on PR #1623: top-level posts (DMs, channel root messages)
18504
- // arrive with no `thread_ts` and MUST decline inline, not in a
18505
- // new thread off the inbound message. The `?? evt.ts` fallback
18506
- // forced every root message into a thread reply — exactly the
18507
- // case the helper's "omit thread_ts when undefined" branch was
18508
- // meant to handle. Pass through as-is: in-thread for thread
18509
- // replies (thread_ts present), inline for everything else.
18510
- threadTs: evt.thread_ts,
18511
- subReason
18512
- }).catch((err) => {
18513
- process.stderr.write(
18514
- `slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
18515
- `
18516
- );
18517
- });
18518
- return;
18519
- }
18520
- recordActivity("inbound");
18521
- const isDirectMessage = evt.channel?.startsWith("D");
18522
- const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
18523
- const trackTs = evt.thread_ts ?? evt.ts ?? "";
18524
- const threadKey = buildThreadKey(evt.channel, trackTs);
19114
+ const policyBlockReason = senderPolicyDecision.forward ? void 0 : classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
19115
+ const peerClassification = isBot ? classifyPeerMessage(
19116
+ {
19117
+ text: evt.text,
19118
+ channel: evt.channel ?? "",
19119
+ channel_type: evt.channel_type,
19120
+ user: evt.user,
19121
+ bot_id: evt.bot_id,
19122
+ thread_ts: evt.thread_ts,
19123
+ parent_user_id: evt.parent_user_id
19124
+ },
19125
+ SLACK_PEER_CLASSIFIER_CONFIG,
19126
+ { bot_user_id: botUserId }
19127
+ ) : void 0;
18525
19128
  const rawText = evt.text ?? "";
18526
19129
  const strippedText = rawText.replace(/^\s*<@[^>]+>\s*/, "").trim();
18527
19130
  const restartSuffixed = agentSlashCommand("/restart");
19131
+ const helpSuffixed = agentSlashCommand("/help");
18528
19132
  const isRestartCommand = strippedText === "/restart" || strippedText.startsWith("/restart ") || strippedText === restartSuffixed || strippedText.startsWith(`${restartSuffixed} `);
18529
- const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ");
18530
- if (isHelpCommand) {
18531
- await handleHelpCommand({
18532
- channel: evt.channel ?? "",
18533
- threadTs: evt.thread_ts,
18534
- ts: evt.ts ?? ""
18535
- });
19133
+ const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ") || strippedText === helpSuffixed || strippedText.startsWith(`${helpSuffixed} `);
19134
+ const command = isBot ? void 0 : isHelpCommand ? { command: "help", authorized: true } : isRestartCommand ? { command: "restart", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : void 0;
19135
+ const slackHomeTeamId = process.env.SLACK_HOME_TEAM_ID;
19136
+ const sameOrg = !!slackHomeTeamId && evt.team === slackHomeTeamId;
19137
+ const access = decideInboundAccess({
19138
+ content: filterDecision.forward ? { forward: true } : { forward: false, reason: filterDecision.reason },
19139
+ // The agent's own user-echo was already skipped above; peer 'self'
19140
+ // covers the bot-id self case inside the composer.
19141
+ isSelf: false,
19142
+ isBot,
19143
+ peer: peerClassification ? {
19144
+ kind: peerClassification.kind,
19145
+ reason: peerClassification.kind === "drop" ? peerClassification.reason : void 0
19146
+ } : void 0,
19147
+ senderPolicy: senderPolicyDecision.forward ? { forward: true } : { forward: false, reason: policyBlockReason },
19148
+ command,
19149
+ sameOrg
19150
+ });
19151
+ const passedIdentity = access.kind !== "drop" || access.reason.startsWith("peer:");
19152
+ if (passedIdentity) recordActivity("inbound");
19153
+ if (access.kind === "drop") {
19154
+ const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
19155
+ process.stderr.write(
19156
+ `slack-channel: inbound drop reason=${access.reason} channel=${channelHash} ts=${redactSlackId(evt.ts)}
19157
+ `
19158
+ );
19159
+ if (access.decline !== void 0 && policyBlockReason) {
19160
+ await maybeSendSenderPolicyDecline({
19161
+ channel: evt.channel,
19162
+ senderId: evt.user,
19163
+ threadTs: evt.thread_ts,
19164
+ subReason: policyBlockReason
19165
+ }).catch((err) => {
19166
+ process.stderr.write(
19167
+ `slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
19168
+ `
19169
+ );
19170
+ });
19171
+ }
19172
+ if (access.reason === "peer:cross_team_grant_missing") {
19173
+ crossTeamPeerAuditClient?.emit({
19174
+ event_type: "slack.peer.drop.cross_team_grant_missing",
19175
+ peer_agent_id: null,
19176
+ gate_path: null,
19177
+ grant_id: null,
19178
+ chat_id: evt.channel ?? null,
19179
+ message_id: evt.ts ?? null
19180
+ });
19181
+ }
18536
19182
  return;
18537
19183
  }
18538
- if (isRestartCommand) {
18539
- const senderAllowed = ALLOWED_USERS.size === 0 || evt.user != null && ALLOWED_USERS.has(evt.user);
18540
- if (!senderAllowed) {
18541
- await denyUnauthorizedRestart({
19184
+ if (access.kind === "command") {
19185
+ if (access.command === "help") {
19186
+ await handleHelpCommand({
18542
19187
  channel: evt.channel ?? "",
18543
- threadTs: evt.thread_ts
19188
+ threadTs: evt.thread_ts,
19189
+ ts: evt.ts ?? ""
18544
19190
  });
18545
19191
  return;
18546
19192
  }
18547
- await handleRestartCommand({
18548
- channel: evt.channel ?? "",
18549
- // Only carry thread_ts when the originating message was already
18550
- // threaded; a top-level command acks in-channel rather than
18551
- // synthesising a brand-new thread (CodeRabbit feedback).
18552
- threadTs: evt.thread_ts,
18553
- ts: evt.ts ?? ""
18554
- });
18555
- return;
18556
- }
18557
- if (ALLOWED_USERS.size > 0 && evt.user && !ALLOWED_USERS.has(evt.user)) return;
18558
- if (evt.bot_id) {
18559
- const peerMsg = {
18560
- text: evt.text,
18561
- channel: evt.channel ?? "",
18562
- channel_type: evt.channel_type,
18563
- user: evt.user,
18564
- bot_id: evt.bot_id,
18565
- thread_ts: evt.thread_ts,
18566
- parent_user_id: evt.parent_user_id
18567
- };
18568
- const decision = classifyPeerMessage(
18569
- peerMsg,
18570
- SLACK_PEER_CLASSIFIER_CONFIG,
18571
- { bot_user_id: botUserId }
18572
- );
18573
- if (decision.kind === "self") return;
18574
- if (decision.kind === "drop") {
18575
- const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
18576
- process.stderr.write(
18577
- `slack-channel: peer drop reason=${decision.reason} channel=${channelHash}
18578
- `
18579
- );
18580
- if (decision.reason === "cross_team_grant_missing") {
18581
- crossTeamPeerAuditClient?.emit({
18582
- event_type: "slack.peer.drop.cross_team_grant_missing",
18583
- peer_agent_id: null,
18584
- gate_path: null,
18585
- grant_id: null,
18586
- chat_id: evt.channel ?? null,
18587
- message_id: evt.ts ?? null
19193
+ if (access.command === "restart") {
19194
+ if (!access.authorized) {
19195
+ await denyUnauthorizedRestart({
19196
+ channel: evt.channel ?? "",
19197
+ threadTs: evt.thread_ts
18588
19198
  });
19199
+ return;
18589
19200
  }
19201
+ const resolvedName = await resolveUserName(evt.user);
19202
+ const requesterName = resolvedName && resolvedName !== evt.user ? resolvedName : void 0;
19203
+ await handleRestartCommand({
19204
+ channel: evt.channel ?? "",
19205
+ // Only carry thread_ts when the originating message was already
19206
+ // threaded; a top-level command acks in-channel.
19207
+ threadTs: evt.thread_ts,
19208
+ ts: evt.ts ?? "",
19209
+ requesterName
19210
+ });
18590
19211
  return;
18591
19212
  }
18592
- if (decision.kind === "peer-ingress") {
18593
- const peerGate = decision.peer.gate_path;
18594
- if (peerGate === "intra_org_unrestricted") {
18595
- crossTeamPeerAuditClient?.emit({
18596
- event_type: "slack.peer.ingress.cross_team",
18597
- peer_agent_id: decision.peer.agent_id || null,
18598
- gate_path: peerGate,
18599
- grant_id: null,
18600
- chat_id: evt.channel ?? null,
18601
- message_id: evt.ts ?? null
18602
- });
18603
- } else if (typeof peerGate === "string" && peerGate.startsWith("grant:")) {
18604
- crossTeamPeerAuditClient?.emit({
18605
- event_type: "slack.peer.ingress.cross_team",
18606
- peer_agent_id: decision.peer.agent_id || null,
18607
- gate_path: peerGate,
18608
- grant_id: peerGate.slice("grant:".length),
18609
- chat_id: evt.channel ?? null,
18610
- message_id: evt.ts ?? null
18611
- });
18612
- }
19213
+ return;
19214
+ }
19215
+ const isDirectMessage = evt.channel?.startsWith("D");
19216
+ const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
19217
+ const trackTs = evt.thread_ts ?? evt.ts ?? "";
19218
+ const threadKey = buildThreadKey(evt.channel, trackTs);
19219
+ if (isBot && peerClassification?.kind === "peer-ingress") {
19220
+ const peerGate = peerClassification.peer.gate_path;
19221
+ if (peerGate === "intra_org_unrestricted") {
19222
+ crossTeamPeerAuditClient?.emit({
19223
+ event_type: "slack.peer.ingress.cross_team",
19224
+ peer_agent_id: peerClassification.peer.agent_id || null,
19225
+ gate_path: peerGate,
19226
+ grant_id: null,
19227
+ chat_id: evt.channel ?? null,
19228
+ message_id: evt.ts ?? null
19229
+ });
19230
+ } else if (typeof peerGate === "string" && peerGate.startsWith("grant:")) {
19231
+ crossTeamPeerAuditClient?.emit({
19232
+ event_type: "slack.peer.ingress.cross_team",
19233
+ peer_agent_id: peerClassification.peer.agent_id || null,
19234
+ gate_path: peerGate,
19235
+ grant_id: peerGate.slice("grant:".length),
19236
+ chat_id: evt.channel ?? null,
19237
+ message_id: evt.ts ?? null
19238
+ });
18613
19239
  }
18614
19240
  }
18615
19241
  if (evt.type === "app_mention") {
@@ -18645,7 +19271,7 @@ async function connectSocketMode() {
18645
19271
  let paneLogFreshAgeMs = null;
18646
19272
  if (SLACK_AGENT_DIR) {
18647
19273
  try {
18648
- const paneMtimeMs = statSync2(join5(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
19274
+ const paneMtimeMs = statSync2(join6(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
18649
19275
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
18650
19276
  } catch {
18651
19277
  }
@@ -18676,6 +19302,9 @@ async function connectSocketMode() {
18676
19302
  }
18677
19303
  if (channel && ts && shouldEngage) {
18678
19304
  trackPendingMessage(channel, threadTs, ts, ackDecision === "undeliverable");
19305
+ if (ackDecision === "ack") {
19306
+ scheduleBusyAck(channel, threadTs, ts, isThreadReply);
19307
+ }
18679
19308
  }
18680
19309
  const userName = await resolveUserName(user);
18681
19310
  const fileMeta = await buildInboundFileMeta(evt.files, AGENT_CODE_NAME, channel);
@@ -18706,10 +19335,17 @@ async function connectSocketMode() {
18706
19335
  );
18707
19336
  }
18708
19337
  }
19338
+ const { content: inboundContent, recoveredTable } = mergeInboundText(text, evt.blocks);
19339
+ if (recoveredTable) {
19340
+ process.stderr.write(
19341
+ `slack-channel(${AGENT_CODE_NAME}): recovered rich_text table from blocks (channel=${redactSlackId(channel)} ts=${redactSlackId(ts)})
19342
+ `
19343
+ );
19344
+ }
18709
19345
  await mcp.notification({
18710
19346
  method: "notifications/claude/channel",
18711
19347
  params: {
18712
- content: text,
19348
+ content: inboundContent,
18713
19349
  meta: {
18714
19350
  user,
18715
19351
  user_name: userName,
@@ -18843,6 +19479,7 @@ if (THREAD_STORE_PATH) {
18843
19479
  }
18844
19480
  connectSocketModeSafely();
18845
19481
  export {
19482
+ __resetSlackBusyAckNoticeThrottle,
18846
19483
  __resetSlackGiveUpNoticeStateForTests,
18847
19484
  __resetSlackUndeliverableNoticeThrottle
18848
19485
  };