@integrity-labs/agt-cli 0.28.304 → 0.28.306

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-52DPSJGK.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-WYNJBCVB.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-FOFN74JS.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-6MRJ6ZJV.js.map
@@ -39,7 +39,7 @@ import {
39
39
  requireHost,
40
40
  safeWriteJsonAtomic,
41
41
  setConfigHash
42
- } from "../chunk-OA5EO3U4.js";
42
+ } from "../chunk-2EHOMRDS.js";
43
43
  import {
44
44
  getProjectDir as getProjectDir2,
45
45
  getReadyTasks,
@@ -127,7 +127,7 @@ import {
127
127
  takeZombieDetection,
128
128
  transcriptActivityAgeSeconds,
129
129
  writeEgressAllowlist
130
- } from "../chunk-5WQATS6J.js";
130
+ } from "../chunk-A546IARM.js";
131
131
  import {
132
132
  reapOrphanChannelMcps
133
133
  } from "../chunk-XWVM4KPK.js";
@@ -6997,7 +6997,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
6997
6997
  var lastVersionCheckAt = 0;
6998
6998
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
6999
6999
  var lastResponsivenessProbeAt = 0;
7000
- var agtCliVersion = true ? "0.28.304" : "dev";
7000
+ var agtCliVersion = true ? "0.28.306" : "dev";
7001
7001
  function resolveBrewPath(execFileSync2) {
7002
7002
  try {
7003
7003
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -7852,7 +7852,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
7852
7852
  if (codeNames.length === 0) return;
7853
7853
  void (async () => {
7854
7854
  try {
7855
- const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
7855
+ const { collectDiagnostics } = await import("../persistent-session-WYNJBCVB.js");
7856
7856
  await api.post("/host/heartbeat", {
7857
7857
  host_id: hostId,
7858
7858
  agent_diagnostics: collectDiagnostics(codeNames)
@@ -7950,7 +7950,7 @@ async function pollCycle() {
7950
7950
  }
7951
7951
  try {
7952
7952
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
7953
- const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
7953
+ const { collectDiagnostics } = await import("../persistent-session-WYNJBCVB.js");
7954
7954
  const diagCodeNames = [...agentState.persistentSessionAgents];
7955
7955
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
7956
7956
  let tailscaleHostname;
@@ -8098,7 +8098,7 @@ async function pollCycle() {
8098
8098
  const {
8099
8099
  collectResponsivenessProbes,
8100
8100
  getResponsivenessIntervalMs
8101
- } = await import("../responsiveness-probe-KXWV4FXD.js");
8101
+ } = await import("../responsiveness-probe-EOB7FSY2.js");
8102
8102
  const probeIntervalMs = getResponsivenessIntervalMs();
8103
8103
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
8104
8104
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -8130,7 +8130,7 @@ async function pollCycle() {
8130
8130
  collectResponsivenessProbes,
8131
8131
  livePendingInboundOldestAgeSeconds,
8132
8132
  parkPendingInbound
8133
- } = await import("../responsiveness-probe-KXWV4FXD.js");
8133
+ } = await import("../responsiveness-probe-EOB7FSY2.js");
8134
8134
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
8135
8135
  const wedgeNow = /* @__PURE__ */ new Date();
8136
8136
  const liveAgents = agentState.persistentSessionAgents;
@@ -11113,7 +11113,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
11113
11113
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
11114
11114
  void (async () => {
11115
11115
  try {
11116
- const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
11116
+ const { collectDiagnostics } = await import("../persistent-session-WYNJBCVB.js");
11117
11117
  await api.post("/host/heartbeat", {
11118
11118
  host_id: hostId,
11119
11119
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11163,7 +11163,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
11163
11163
  }
11164
11164
  try {
11165
11165
  const hostId = await getHostId();
11166
- const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
11166
+ const { collectDiagnostics } = await import("../persistent-session-WYNJBCVB.js");
11167
11167
  await api.post("/host/heartbeat", {
11168
11168
  host_id: hostId,
11169
11169
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11587,7 +11587,7 @@ async function processClaudePairSessions(agents) {
11587
11587
  killPairSession,
11588
11588
  pairTmuxSession,
11589
11589
  finalizeClaudePairOnboarding
11590
- } = await import("../claude-pair-runtime-FOFN74JS.js");
11590
+ } = await import("../claude-pair-runtime-6MRJ6ZJV.js");
11591
11591
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
11592
11592
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
11593
11593
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -40400,9 +40400,9 @@ var FLAG_REGISTRY = [
40400
40400
  },
40401
40401
  {
40402
40402
  key: "notify-dispatch",
40403
- description: "Membership-based channel-notify dispatch (ENG-7682 / notify Slice 1). off = today's behaviour: a non-@mention top-level channel message is dropped by the mention_only engagement gate (SLACK_CHANNEL_RESPONSE_MODE). membership = the agent's Slack MCP ADMITS non-@mention channel messages in channels its bot is a member of (the Socket Mode websocket already receives them), waking the agent in-thread via the SAME existing wake path used for @mentions/DMs. Only the mention_only engagement gate is relaxed - the echo/dedup, bot_id/self, and peer-classifier safety leaves still apply, so this never opens a bot-to-bot wake loop. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_NOTIFY_DISPATCH spawn env (operator/canary override, ADR-0022). Enum leaves room for a later Slice 2 'filter' value (per-channel filters). Ships dark (default off).",
40403
+ description: "Membership-based channel-notify dispatch (ENG-7682 / notify Slice 1). off = today's behaviour: a non-@mention top-level channel message is dropped by the mention_only engagement gate (SLACK_CHANNEL_RESPONSE_MODE). membership = the agent's Slack MCP ADMITS non-@mention channel messages in channels its bot is a member of (the Socket Mode websocket already receives them), waking the agent in-thread via the SAME existing wake path used for @mentions/DMs. Only the mention_only engagement gate is relaxed - the echo/dedup, bot_id/self, and peer-classifier safety leaves still apply, so this never opens a bot-to-bot wake loop. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_NOTIFY_DISPATCH spawn env (operator/canary override, ADR-0022). filter = like membership (opt-out) but honours a per-channel mute list: a member-channel message wakes the agent UNLESS the user muted that channel via the /notify slash command. The manager materializes the muted set into AGT_NOTIFY_MUTED_CHANNELS from agent_notify_channel_prefs (ENG-7682 Slice 2). Ships dark (default off).",
40404
40404
  flagType: "enum",
40405
- allowedValues: ["off", "membership"],
40405
+ allowedValues: ["off", "membership", "filter"],
40406
40406
  // Declared safe value is `off`: the mention_only gate stays in force, byte-
40407
40407
  // identical to today. membership is the deliberate, per-agent flip that
40408
40408
  // widens what wakes the agent.
@@ -6997,8 +6997,10 @@ __export(slack_block_kit_runtime_exports, {
6997
6997
  decodeActionId: () => decodeActionId,
6998
6998
  encodeActionId: () => encodeActionId,
6999
6999
  generateOptionToken: () => generateOptionToken,
7000
+ getMutedNotifyChannels: () => getMutedNotifyChannels,
7000
7001
  recordSlackDelivery: () => recordSlackDelivery,
7001
7002
  resolveInteractive: () => resolveInteractive,
7003
+ setNotifyChannelPref: () => setNotifyChannelPref,
7002
7004
  submitInteractive: () => submitInteractive,
7003
7005
  updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
7004
7006
  validateAskUserOptions: () => validateAskUserOptions,
@@ -7210,6 +7212,33 @@ async function recordSlackDelivery(cfg, input) {
7210
7212
  throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
7211
7213
  }
7212
7214
  }
7215
+ async function setNotifyChannelPref(cfg, input) {
7216
+ try {
7217
+ const res = await apiCall(cfg, "POST", "/host/notify-prefs", {
7218
+ agent_id: cfg.agentId,
7219
+ channel_id: input.channelId,
7220
+ wake: input.wake,
7221
+ updated_by: input.userId ?? null
7222
+ });
7223
+ return res.ok;
7224
+ } catch {
7225
+ return false;
7226
+ }
7227
+ }
7228
+ async function getMutedNotifyChannels(cfg) {
7229
+ try {
7230
+ const res = await apiCall(
7231
+ cfg,
7232
+ "GET",
7233
+ `/host/notify-prefs?agent_id=${encodeURIComponent(cfg.agentId)}`
7234
+ );
7235
+ if (!res.ok) return [];
7236
+ const body = await res.json().catch(() => ({}));
7237
+ return Array.isArray(body.muted_channels) ? body.muted_channels.filter((x) => typeof x === "string") : [];
7238
+ } catch {
7239
+ return [];
7240
+ }
7241
+ }
7213
7242
  var SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
7214
7243
  var init_slack_block_kit_runtime = __esm({
7215
7244
  "src/slack-block-kit-runtime.ts"() {
@@ -14933,13 +14962,17 @@ function isPendingMarkerStale(receivedAt, nowMs, thresholdMs) {
14933
14962
  }
14934
14963
  function channelOrphanMarkerMs() {
14935
14964
  const raw = parseInt(process.env.AGT_CHANNEL_ORPHAN_MARKER_MS ?? "", 10);
14936
- return Number.isFinite(raw) && raw > 0 ? raw : 30 * 6e4;
14965
+ return Number.isFinite(raw) && raw > 0 ? Math.max(raw, 12e4) : 30 * 6e4;
14937
14966
  }
14938
14967
  var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
14939
14968
  function orphanSweepIntervalMs() {
14940
14969
  return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
14941
14970
  }
14942
14971
  var MAX_MARKER_REPLAYS = 3;
14972
+ var PANE_FRESH_DEFER_MAX_MS = 10 * 60 * 1e3;
14973
+ function paneFreshDeferMaxMs() {
14974
+ return Math.max(6e4, Math.min(PANE_FRESH_DEFER_MAX_MS, Math.floor(channelOrphanMarkerMs() / 2)));
14975
+ }
14943
14976
  function channelReplayEnabled() {
14944
14977
  return resolveHostBooleanFlag({
14945
14978
  key: "channel-replay",
@@ -14952,10 +14985,16 @@ function shouldReplayMarker(i) {
14952
14985
  if (!i.hasPayload) return false;
14953
14986
  if (i.discretionary) return false;
14954
14987
  if (!i.sessionAlive) return false;
14955
- if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
14988
+ const paneFreshDeferMax = i.paneFreshDeferMaxMs ?? paneFreshDeferMaxMs();
14989
+ const minAge = i.minAgeMs ?? Math.min(REPLY_WEDGED_THRESHOLD_MS, Math.floor(paneFreshDeferMax / 2));
14990
+ if (i.markerAgeMs < minAge) return false;
14991
+ if (i.replayCount >= (i.maxReplays ?? MAX_MARKER_REPLAYS)) return false;
14956
14992
  const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
14957
- if (i.paneFreshAgeMs != null && i.paneFreshAgeMs <= paneFreshThreshold) return false;
14958
- return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
14993
+ const paneFresh = i.paneFreshAgeMs != null && i.paneFreshAgeMs <= paneFreshThreshold;
14994
+ if (paneFresh) {
14995
+ return i.markerAgeMs >= paneFreshDeferMax * (i.replayCount + 1);
14996
+ }
14997
+ return true;
14959
14998
  }
14960
14999
  function isMarkerGenuinelyAged(receivedAt, nowMs, thresholdMs) {
14961
15000
  const t = Date.parse(receivedAt ?? "");
@@ -17430,12 +17469,32 @@ function channelMessageShouldRespond(text, mode) {
17430
17469
  }
17431
17470
  function parseNotifyDispatchMode(raw) {
17432
17471
  if (!raw) return "off";
17433
- return raw.trim().toLowerCase() === "membership" ? "membership" : "off";
17472
+ const v = raw.trim().toLowerCase();
17473
+ if (v === "membership") return "membership";
17474
+ if (v === "filter") return "filter";
17475
+ return "off";
17476
+ }
17477
+ function parseMutedChannels(raw) {
17478
+ if (!raw) return /* @__PURE__ */ new Set();
17479
+ return new Set(
17480
+ raw.split(",").map((s) => s.trim()).filter(Boolean)
17481
+ );
17434
17482
  }
17435
- function channelMessageShouldWake(text, responseMode, notifyMode) {
17483
+ function channelMessageShouldWake(text, responseMode, notifyMode, channelId, mutedChannels) {
17436
17484
  if (notifyMode === "membership") return true;
17485
+ if (notifyMode === "filter") {
17486
+ if (channelId && mutedChannels && mutedChannels.has(channelId)) return false;
17487
+ return true;
17488
+ }
17437
17489
  return channelMessageShouldRespond(text, responseMode);
17438
17490
  }
17491
+ function parseNotifyCommand(text, currentChannelId) {
17492
+ const arg = (text ?? "").trim().toLowerCase().split(/\s+/)[0] ?? "";
17493
+ if (arg === "off" || arg === "mute") return { action: "mute", channelId: currentChannelId };
17494
+ if (arg === "on" || arg === "unmute") return { action: "unmute", channelId: currentChannelId };
17495
+ if (arg === "" || arg === "status") return { action: "status", channelId: currentChannelId };
17496
+ return { action: "help", channelId: currentChannelId };
17497
+ }
17439
17498
 
17440
17499
  // src/slack-list-channels.ts
17441
17500
  function normaliseForMatch(s) {
@@ -18293,6 +18352,7 @@ var SLACK_SKIP_REACTION = (process.env.SLACK_SKIP_REACTION ?? "").trim();
18293
18352
  var SLACK_ACK_REACTION = (process.env.SLACK_ACK_REACTION ?? "").trim() || "eyes";
18294
18353
  var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
18295
18354
  var NOTIFY_DISPATCH_MODE = parseNotifyDispatchMode(process.env.AGT_NOTIFY_DISPATCH);
18355
+ var MUTED_CHANNELS = parseMutedChannels(process.env.AGT_NOTIFY_MUTED_CHANNELS);
18296
18356
  var SLACK_PEER_DISABLED_MODE = (() => {
18297
18357
  const raw = (process.env.PEER_DISABLED ?? "").trim().toLowerCase();
18298
18358
  if (raw === "" || raw === "off") return "off";
@@ -19057,8 +19117,12 @@ function sweepSlackStaleMarkers(thresholdMs) {
19057
19117
  const structurallyBroken = !channel || !thread_ts || !message_ts;
19058
19118
  const timeStale = isPendingMarkerStale(received_at, now, effectiveThresholdMs);
19059
19119
  if (structurallyBroken || timeStale) {
19060
- if (!structurallyBroken && isMarkerGenuinelyAged(received_at, now, effectiveThresholdMs) && marker.payload && !marker.replay_count) {
19061
- recordChannelDeflection(SLACK_AGENT_DIR, "slack", "replay_orphaned");
19120
+ if (!structurallyBroken && isMarkerGenuinelyAged(received_at, now, effectiveThresholdMs) && marker.payload && !marker.discretionary) {
19121
+ recordChannelDeflection(
19122
+ SLACK_AGENT_DIR,
19123
+ "slack",
19124
+ marker.replay_count ? "replay_exhausted" : "replay_orphaned"
19125
+ );
19062
19126
  }
19063
19127
  try {
19064
19128
  unlinkSync6(fullPath);
@@ -19769,6 +19833,61 @@ ${renderSlackStatusSections(merged, {
19769
19833
  })}`;
19770
19834
  await postEphemeralViaResponseUrl(responseUrl, finalReport, codeName, { replaceOriginal: true });
19771
19835
  }
19836
+ async function handleNotifyCommand(payload, codeName) {
19837
+ const responseUrl = payload.response_url;
19838
+ if (!responseUrl) return;
19839
+ const channelId = payload.channel_id ?? "";
19840
+ if (!channelId) {
19841
+ await postEphemeralViaResponseUrl(responseUrl, "Run `/notify` inside a channel.", codeName);
19842
+ return;
19843
+ }
19844
+ const notifyAllowed = getEffectivePingAllowedUsers();
19845
+ if (notifyAllowed.size === 0 || !payload.user_id || !notifyAllowed.has(payload.user_id)) {
19846
+ process.stderr.write(`slack-channel(${codeName}): /notify denied - user not in the allowlist
19847
+ `);
19848
+ await postEphemeralViaResponseUrl(
19849
+ responseUrl,
19850
+ `\u{1F6AB} \`/notify\` is limited to \`${codeName}\`'s team and the person it reports to. If that's you, link your Slack in your Augmented Team contact preferences and wait for the next refresh.`,
19851
+ codeName
19852
+ );
19853
+ return;
19854
+ }
19855
+ const inactiveNote = NOTIFY_DISPATCH_MODE === "filter" ? "" : " _(note: I\u2019m not in per-channel filter mode right now, so this is saved but won\u2019t take effect until an operator enables it.)_";
19856
+ const { action } = parseNotifyCommand(payload.text, channelId);
19857
+ if (action === "help") {
19858
+ await postEphemeralViaResponseUrl(
19859
+ responseUrl,
19860
+ "`/notify off` \u2014 stop waking me on this channel\u2019s messages. `/notify on` \u2014 resume. `/notify status` \u2014 show the current setting. (An @-mention always reaches me.)",
19861
+ codeName
19862
+ );
19863
+ return;
19864
+ }
19865
+ if (action === "status") {
19866
+ const muted = MUTED_CHANNELS.has(channelId);
19867
+ await postEphemeralViaResponseUrl(
19868
+ responseUrl,
19869
+ (muted ? ":mute: This channel is *muted* \u2014 I won\u2019t wake on its messages (an @-mention still reaches me). `/notify on` to resume." : ":bell: This channel is *active* \u2014 I wake on its messages. `/notify off` to mute.") + inactiveNote,
19870
+ codeName
19871
+ );
19872
+ return;
19873
+ }
19874
+ const mute = action === "mute";
19875
+ if (mute) MUTED_CHANNELS.add(channelId);
19876
+ else MUTED_CHANNELS.delete(channelId);
19877
+ const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
19878
+ const cfg = statusRuntimeConfig();
19879
+ const persisted = cfg ? await runtime.setNotifyChannelPref(cfg, {
19880
+ channelId,
19881
+ wake: !mute,
19882
+ userId: payload.user_id ?? null
19883
+ }) : false;
19884
+ const durabilityNote = persisted ? "" : " (couldn\u2019t save it durably just now \u2014 it\u2019ll reset if I restart; re-run to retry)";
19885
+ await postEphemeralViaResponseUrl(
19886
+ responseUrl,
19887
+ (mute ? `:mute: Muted this channel \u2014 I\u2019ll stop waking on its messages (an @-mention still reaches me).${durabilityNote}` : `:bell: Un-muted this channel \u2014 I\u2019ll wake on its messages again.${durabilityNote}`) + inactiveNote,
19888
+ codeName
19889
+ );
19890
+ }
19772
19891
  async function postEphemeralViaResponseUrl(responseUrl, text, logTag, opts = {}) {
19773
19892
  try {
19774
19893
  const res = await fetch(responseUrl, {
@@ -20040,6 +20159,10 @@ async function handleSlashCommandEnvelope(payload) {
20040
20159
  await handleStatusCommand(responseUrl, codeName);
20041
20160
  return;
20042
20161
  }
20162
+ if (matchesAgentCommand(command, "/notify")) {
20163
+ await handleNotifyCommand(payload, codeName);
20164
+ return;
20165
+ }
20043
20166
  if (matchesAgentCommand(command, "/help")) {
20044
20167
  await postEphemeralViaResponseUrl(responseUrl, buildSlackHelpMessage(codeName), codeName);
20045
20168
  return;
@@ -21962,6 +22085,7 @@ async function replayPendingSlackMarkers() {
21962
22085
  } catch {
21963
22086
  }
21964
22087
  }
22088
+ const paneFreshDeferMax = paneFreshDeferMaxMs();
21965
22089
  const entries = [];
21966
22090
  for (const name of filenames) {
21967
22091
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
@@ -21988,7 +22112,11 @@ async function replayPendingSlackMarkers() {
21988
22112
  // ENG-6366: auto-followed un-mentioned thread inbound the agent may
21989
22113
  // legitimately silently skip — never re-deliver it (it never clears,
21990
22114
  // so it would otherwise re-push MAX_MARKER_REPLAYS times under replay).
21991
- discretionary: marker.discretionary
22115
+ discretionary: marker.discretionary,
22116
+ // ENG-7736: bound the pane-fresh defer (clamped under the orphan
22117
+ // window) so a perpetually-fresh pane (respawn churn) can't hold off
22118
+ // replay until the orphan sweep GCs the marker - the Sherlock loss.
22119
+ paneFreshDeferMaxMs: paneFreshDeferMax
21992
22120
  })) {
21993
22121
  continue;
21994
22122
  }
@@ -22047,7 +22175,28 @@ async function resolveUserName(userId) {
22047
22175
  var currentWs = null;
22048
22176
  var isShuttingDown = false;
22049
22177
  var acquiredLockPath = null;
22178
+ async function seedMutedChannelsFromApi() {
22179
+ try {
22180
+ const cfg = statusRuntimeConfig();
22181
+ if (!cfg) return;
22182
+ const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
22183
+ const muted = await runtime.getMutedNotifyChannels(cfg);
22184
+ for (const ch of muted) MUTED_CHANNELS.add(ch);
22185
+ if (muted.length > 0) {
22186
+ process.stderr.write(
22187
+ `slack-channel: seeded ${muted.length} muted channel(s) from notify-prefs
22188
+ `
22189
+ );
22190
+ }
22191
+ } catch (err) {
22192
+ process.stderr.write(
22193
+ `slack-channel: notify-prefs seed failed: ${err.message}
22194
+ `
22195
+ );
22196
+ }
22197
+ }
22050
22198
  function connectSocketModeSafely() {
22199
+ if (NOTIFY_DISPATCH_MODE === "filter") void seedMutedChannelsFromApi();
22051
22200
  void runOrRetry(connectSocketMode, {
22052
22201
  retryDelayMs: 1e4,
22053
22202
  onError: (err) => process.stderr.write(
@@ -22439,7 +22588,13 @@ async function connectSocketMode() {
22439
22588
  return;
22440
22589
  }
22441
22590
  } else {
22442
- if (!channelMessageShouldWake(evt.text ?? "", CHANNEL_RESPONSE_MODE, NOTIFY_DISPATCH_MODE))
22591
+ if (!channelMessageShouldWake(
22592
+ evt.text ?? "",
22593
+ CHANNEL_RESPONSE_MODE,
22594
+ NOTIFY_DISPATCH_MODE,
22595
+ evt.channel,
22596
+ MUTED_CHANNELS
22597
+ ))
22443
22598
  return;
22444
22599
  }
22445
22600
  }
@@ -6997,8 +6997,10 @@ __export(slack_block_kit_runtime_exports, {
6997
6997
  decodeActionId: () => decodeActionId,
6998
6998
  encodeActionId: () => encodeActionId,
6999
6999
  generateOptionToken: () => generateOptionToken,
7000
+ getMutedNotifyChannels: () => getMutedNotifyChannels,
7000
7001
  recordSlackDelivery: () => recordSlackDelivery,
7001
7002
  resolveInteractive: () => resolveInteractive,
7003
+ setNotifyChannelPref: () => setNotifyChannelPref,
7002
7004
  submitInteractive: () => submitInteractive,
7003
7005
  updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
7004
7006
  validateAskUserOptions: () => validateAskUserOptions,
@@ -7210,6 +7212,33 @@ async function recordSlackDelivery(cfg, input) {
7210
7212
  throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
7211
7213
  }
7212
7214
  }
7215
+ async function setNotifyChannelPref(cfg, input) {
7216
+ try {
7217
+ const res = await apiCall(cfg, "POST", "/host/notify-prefs", {
7218
+ agent_id: cfg.agentId,
7219
+ channel_id: input.channelId,
7220
+ wake: input.wake,
7221
+ updated_by: input.userId ?? null
7222
+ });
7223
+ return res.ok;
7224
+ } catch {
7225
+ return false;
7226
+ }
7227
+ }
7228
+ async function getMutedNotifyChannels(cfg) {
7229
+ try {
7230
+ const res = await apiCall(
7231
+ cfg,
7232
+ "GET",
7233
+ `/host/notify-prefs?agent_id=${encodeURIComponent(cfg.agentId)}`
7234
+ );
7235
+ if (!res.ok) return [];
7236
+ const body = await res.json().catch(() => ({}));
7237
+ return Array.isArray(body.muted_channels) ? body.muted_channels.filter((x) => typeof x === "string") : [];
7238
+ } catch {
7239
+ return [];
7240
+ }
7241
+ }
7213
7242
  var SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
7214
7243
  var init_slack_block_kit_runtime = __esm({
7215
7244
  "src/slack-block-kit-runtime.ts"() {
@@ -17341,13 +17370,17 @@ function isPendingMarkerStale(receivedAt, nowMs, thresholdMs) {
17341
17370
  }
17342
17371
  function channelOrphanMarkerMs() {
17343
17372
  const raw = parseInt(process.env.AGT_CHANNEL_ORPHAN_MARKER_MS ?? "", 10);
17344
- return Number.isFinite(raw) && raw > 0 ? raw : 30 * 6e4;
17373
+ return Number.isFinite(raw) && raw > 0 ? Math.max(raw, 12e4) : 30 * 6e4;
17345
17374
  }
17346
17375
  var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
17347
17376
  function orphanSweepIntervalMs() {
17348
17377
  return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
17349
17378
  }
17350
17379
  var MAX_MARKER_REPLAYS = 3;
17380
+ var PANE_FRESH_DEFER_MAX_MS = 10 * 60 * 1e3;
17381
+ function paneFreshDeferMaxMs() {
17382
+ return Math.max(6e4, Math.min(PANE_FRESH_DEFER_MAX_MS, Math.floor(channelOrphanMarkerMs() / 2)));
17383
+ }
17351
17384
  function channelReplayEnabled() {
17352
17385
  return resolveHostBooleanFlag({
17353
17386
  key: "channel-replay",
@@ -17360,10 +17393,16 @@ function shouldReplayMarker(i) {
17360
17393
  if (!i.hasPayload) return false;
17361
17394
  if (i.discretionary) return false;
17362
17395
  if (!i.sessionAlive) return false;
17363
- if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
17396
+ const paneFreshDeferMax = i.paneFreshDeferMaxMs ?? paneFreshDeferMaxMs();
17397
+ const minAge = i.minAgeMs ?? Math.min(REPLY_WEDGED_THRESHOLD_MS, Math.floor(paneFreshDeferMax / 2));
17398
+ if (i.markerAgeMs < minAge) return false;
17399
+ if (i.replayCount >= (i.maxReplays ?? MAX_MARKER_REPLAYS)) return false;
17364
17400
  const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
17365
- if (i.paneFreshAgeMs != null && i.paneFreshAgeMs <= paneFreshThreshold) return false;
17366
- return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
17401
+ const paneFresh = i.paneFreshAgeMs != null && i.paneFreshAgeMs <= paneFreshThreshold;
17402
+ if (paneFresh) {
17403
+ return i.markerAgeMs >= paneFreshDeferMax * (i.replayCount + 1);
17404
+ }
17405
+ return true;
17367
17406
  }
17368
17407
  function isMarkerGenuinelyAged(receivedAt, nowMs, thresholdMs) {
17369
17408
  const t = Date.parse(receivedAt ?? "");
@@ -19172,8 +19211,12 @@ function sweepTelegramStaleMarkers(thresholdMs) {
19172
19211
  const structurallyBroken = !chat_id || !message_id;
19173
19212
  const timeStale = isPendingMarkerStale(received_at, now, thresholdMs);
19174
19213
  if (structurallyBroken || timeStale) {
19175
- if (!structurallyBroken && isMarkerGenuinelyAged(received_at, now, thresholdMs) && marker.payload && !marker.replay_count) {
19176
- recordChannelDeflection(AGENT_DIR, "telegram", "replay_orphaned");
19214
+ if (!structurallyBroken && isMarkerGenuinelyAged(received_at, now, thresholdMs) && marker.payload) {
19215
+ recordChannelDeflection(
19216
+ AGENT_DIR,
19217
+ "telegram",
19218
+ marker.replay_count ? "replay_exhausted" : "replay_orphaned"
19219
+ );
19177
19220
  }
19178
19221
  try {
19179
19222
  unlinkSync6(fullPath);
@@ -20318,6 +20361,7 @@ async function replayPendingTelegramMarkers() {
20318
20361
  } catch {
20319
20362
  }
20320
20363
  }
20364
+ const paneFreshDeferMax = paneFreshDeferMaxMs();
20321
20365
  const entries = [];
20322
20366
  for (const name of filenames) {
20323
20367
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
@@ -20340,7 +20384,10 @@ async function replayPendingTelegramMarkers() {
20340
20384
  sessionAlive,
20341
20385
  markerAgeMs: ageMs,
20342
20386
  replayCount: marker.replay_count ?? 0,
20343
- paneFreshAgeMs
20387
+ paneFreshAgeMs,
20388
+ // ENG-7736: bound the pane-fresh defer (clamped under the orphan
20389
+ // window) so a perpetually-fresh pane can't hold off replay until GC.
20390
+ paneFreshDeferMaxMs: paneFreshDeferMax
20344
20391
  })) {
20345
20392
  continue;
20346
20393
  }
@@ -36,7 +36,7 @@ import {
36
36
  writeDirectChatSessionState,
37
37
  writeEgressAllowlist,
38
38
  writePersistentClaudeWrapper
39
- } from "./chunk-5WQATS6J.js";
39
+ } from "./chunk-A546IARM.js";
40
40
  import "./chunk-XWVM4KPK.js";
41
41
  export {
42
42
  EGRESS_BASELINE_DOMAINS,
@@ -77,4 +77,4 @@ export {
77
77
  writeEgressAllowlist,
78
78
  writePersistentClaudeWrapper
79
79
  };
80
- //# sourceMappingURL=persistent-session-52DPSJGK.js.map
80
+ //# sourceMappingURL=persistent-session-WYNJBCVB.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-5WQATS6J.js";
3
+ } from "./chunk-A546IARM.js";
4
4
  import "./chunk-XWVM4KPK.js";
5
5
 
6
6
  // src/lib/responsiveness-probe.ts
@@ -16,6 +16,8 @@ var KNOWN_DEFLECTION_CAUSES = [
16
16
  // ENG-6270 — a redundant re-delivery dropped at fresh ingress
17
17
  "replay_orphaned",
18
18
  // ENG-6355 — a replay-payload marker GC'd without ever being replayed
19
+ "replay_exhausted",
20
+ // ENG-7736 - GC'd after replay pushes that never cleared (churn-killed)
19
21
  "unknown"
20
22
  ];
21
23
  function readAndResetChannelDeflections(agentHomeDir) {
@@ -425,4 +427,4 @@ export {
425
427
  readAndResetSlackReplyBindingClassifications,
426
428
  readAndResetSlackReplyTargetClassifications
427
429
  };
428
- //# sourceMappingURL=responsiveness-probe-KXWV4FXD.js.map
430
+ //# sourceMappingURL=responsiveness-probe-EOB7FSY2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/responsiveness-probe.ts"],"sourcesContent":["/**\n * ENG-5399 — Tier 1 responsiveness probe (manager-side).\n *\n * Cheap, fast-cadence canary that catches \"agent went silent\" inside\n * minutes, well before the existing synthetic-probe cron's ~35 min\n * staleness window (`SyntheticReplyAgeSeconds`, ENG-5122).\n *\n * Mechanism: for each managed agent, read the mtime of the agent's\n * `pane.log` and report `now - mtime` as `PaneActivityAgeSeconds` via\n * a new `/host/responsiveness-probe` endpoint. `pane.log` is the\n * tmux pipe-pane sink set up by `setupPaneLog()` — any visible\n * activity (assistant turns, tool calls, in-place progress\n * heartbeats) bumps its mtime. A silent agent has a steadily\n * climbing age that lands in CloudWatch and trips a per-agent alarm.\n *\n * ENG-6017 adds a second per-agent signal on the same cadence:\n * `pending_inbound_oldest_age_seconds` — the age of the oldest marker\n * file across the agent's `*-pending-inbound/` directories (written by\n * the channel MCP servers for inbounds awaiting delivery). This is the\n * one artifact of the \"message typed but never submitted\" failure mode\n * that every other canary is blind to: in the koda incident\n * (2026-06-04) an operator Slack DM sat undelivered for 40+ minutes\n * while pane-activity stayed fresh (health checks), synthetic probes\n * were answered by the one-shot fallback, and heartbeat/session-alive\n * only reflect manager health. The field is OMITTED (not zero) when the\n * agent has no pending-inbound markers — the API treats absent as\n * \"no signal\", never as \"healthy\" (absent-vs-zero matters for\n * mixed-version fleets where old CLIs don't report it at all).\n *\n * Run from `pollCycle()` in `manager-worker.ts` on a configurable\n * interval (default 60s via `AUGMENTED_RESPONSIVENESS_INTERVAL_MS`;\n * lowered from 5 min by ENG-7550 - this post is what refreshes\n * `agents.last_pane_activity_at`, the minutely busy-sampling substrate).\n */\n\nimport { mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { paneLogPath } from './persistent-session.js';\n\nexport interface ResponsivenessProbeResult {\n code_name: string;\n pane_activity_age_seconds: number;\n /**\n * ENG-6017: age (s) of the oldest marker file across the agent's\n * `*-pending-inbound/` directories. Omitted when no markers exist —\n * absent means \"no signal\", NOT \"zero / healthy\".\n */\n pending_inbound_oldest_age_seconds?: number;\n /**\n * ENG-6327: per-cause inbound deflection counts since the last probe (read +\n * RESET from the channel servers' `<channel>-deflections.json` counter files).\n * Omitted when there were none. Keyed by DeflectionCause.\n */\n deflections?: Record<string, number>;\n /**\n * ENG-6408 / ADR-0024 Slice 1.5: shadow lane-classification counts since the\n * last probe (read + RESET from `<channel>-lane-classifications.json`). Keyed\n * by `<lane>|<expects_reply>|<source>` plus the reserved\n * `suspected_misclassification` canary. Omitted when there were none.\n */\n lane_classifications?: Record<string, number>;\n /**\n * WS0 (Slack cross-thread reply routing): SHADOW reply-target classification\n * counts since the last probe (read + RESET from\n * `<channel>-reply-target-classifications.json`). Keyed by\n * `<result>|<pending_threads_bucket>`. Omitted when there were none.\n */\n slack_reply_target_classifications?: Record<string, number>;\n /**\n * WS2 (Slack cross-thread reply routing): SHADOW reply-BINDING classification\n * counts since the last probe (read + RESET from\n * `<channel>-reply-binding-classifications.json`). Keyed by the binding\n * classification (bound / coords_corrected / ... / unknown_inbound_id).\n * Omitted when there were none.\n */\n slack_reply_binding_classifications?: Record<string, number>;\n}\n\n/**\n * ENG-6327 — per-channel deflection counter file-layout contract. The channel\n * MCP servers increment `<channel>-deflections.json` in the agent home (via\n * recordChannelDeflection in packages/mcp/ack-reaction.ts); apps/cli does not\n * depend on @integrity-labs/mcp, so the manager re-implements the read against\n * this contract — the same read-only, no-IPC posture as the `*-pending-inbound`\n * scan above. Body is a JSON object keyed by cause with integer counts.\n */\nconst DEFLECTION_COUNTER_SUFFIX = '-deflections.json';\nconst KNOWN_DEFLECTION_CAUSES = [\n 'integration_down',\n 'session_dead',\n 'wedged',\n 'busy',\n 'duplicate', // ENG-6270 — a redundant re-delivery dropped at fresh ingress\n 'replay_orphaned', // ENG-6355 — a replay-payload marker GC'd without ever being replayed\n 'replay_exhausted', // ENG-7736 - GC'd after replay pushes that never cleared (churn-killed)\n 'unknown',\n] as const;\n\n/**\n * Read and RESET every `<channel>-deflections.json` in an agent home, returning\n * the summed counts by cause (or null when there are none). Consumes via atomic\n * rename-then-read-then-unlink so an increment racing the reset is carried into\n * the next window rather than lost. Only known causes are accumulated so a\n * corrupt file can't smuggle an unbounded dimension into CloudWatch.\n *\n * Reset-on-read is required: these are deltas-since-last-consume, so the metric\n * would over-count if they weren't cleared each cycle. The trade-off is that a\n * failed POST loses that cycle's counts (under-count) — acceptable for a\n * frequency metric and consistent with the other best-effort probe siblings;\n * unlike the in-memory give-up counter, file-based counts aren't re-credited\n * on POST failure.\n */\nexport function readAndResetChannelDeflections(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(DEFLECTION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming); // atomic; replaces any stale .consuming from a crashed cycle\n } catch {\n continue; // vanished / not a regular file — skip\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const cause of KNOWN_DEFLECTION_CAUSES) {\n const n = parsed[cause];\n if (typeof n === 'number' && Number.isFinite(n) && n > 0) {\n total[cause] = (total[cause] ?? 0) + Math.floor(n);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt — drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n/**\n * ENG-6408 / ADR-0024 Slice 1.5 - per-channel lane-classification counter\n * file-layout contract. The channel MCP servers increment\n * `<channel>-lane-classifications.json` in the agent home (via\n * recordLaneClassification in packages/mcp/inbound-lane-telemetry.ts); apps/cli\n * does not depend on @integrity-labs/mcp, so the manager re-implements the read\n * against this contract (same posture as the deflection scan above). Body is a\n * JSON object keyed by `<lane>|<expects_reply>|<source>` (plus the reserved\n * `suspected_misclassification`) with integer counts.\n */\nconst LANE_CLASSIFICATION_COUNTER_SUFFIX = '-lane-classifications.json';\nconst SUSPECTED_MISCLASSIFICATION_KEY = 'suspected_misclassification';\nconst KNOWN_LANES = new Set(['conversational', 'directive', 'liveness']);\nconst KNOWN_LANE_SOURCES = new Set(['slack', 'telegram', 'msteams', 'direct-chat']);\n\n/**\n * Only well-formed composite keys (and the misclassification canary) are\n * accumulated, so a corrupt counter file can't smuggle an unbounded dimension\n * into CloudWatch - the same bounding the deflection reader applies.\n */\nfunction isKnownLaneClassificationKey(key: string): boolean {\n if (key === SUSPECTED_MISCLASSIFICATION_KEY) return true;\n const parts = key.split('|');\n if (parts.length !== 3) return false;\n const [lane, expectsReply, source] = parts as [string, string, string];\n return (\n KNOWN_LANES.has(lane) &&\n (expectsReply === 'true' || expectsReply === 'false') &&\n KNOWN_LANE_SOURCES.has(source)\n );\n}\n\n/**\n * Read and RESET every `<channel>-lane-classifications.json` in an agent home,\n * returning the summed counts by key (or null when there are none). Mirrors\n * readAndResetChannelDeflections exactly: atomic rename-then-read-then-unlink so\n * an increment racing the reset carries into the next window, and only known\n * keys are accumulated. SHADOW telemetry - informs the Slice 2 gate, gates\n * nothing itself.\n */\nexport function readAndResetChannelLaneClassifications(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(LANE_CLASSIFICATION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming); // atomic; replaces any stale .consuming from a crashed cycle\n } catch {\n continue; // vanished / not a regular file — skip\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const [key, raw] of Object.entries(parsed)) {\n if (!isKnownLaneClassificationKey(key)) continue;\n if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {\n total[key] = (total[key] ?? 0) + Math.floor(raw);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt — drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n/**\n * WS0 (Slack cross-thread reply routing) - per-channel reply-target counter\n * file-layout contract. The channel MCP servers increment\n * `<channel>-reply-target-classifications.json` in the agent home (via\n * recordReplyTargetClassification in packages/mcp/slack-reply-target-telemetry.ts);\n * apps/cli does not depend on @integrity-labs/mcp, so the manager re-implements\n * the read against this contract (same posture as the lane-classification scan\n * above). Body is a JSON object keyed by `<result>|<pending_threads_bucket>`\n * with integer counts.\n */\nconst REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX = '-reply-target-classifications.json';\nconst KNOWN_REPLY_TARGET_RESULTS = new Set([\n 'matched_pending',\n 'matched_recent',\n 'crossed_candidate',\n 'unknown_target',\n]);\nconst KNOWN_PENDING_THREAD_BUCKETS = new Set(['0', '1', '2', '3plus']);\n\n/**\n * Only well-formed `<result>|<bucket>` keys are accumulated, so a corrupt counter\n * file can't smuggle an unbounded dimension into CloudWatch - the same bounding\n * the lane-classification / deflection readers apply.\n */\nfunction isKnownReplyTargetKey(key: string): boolean {\n const parts = key.split('|');\n if (parts.length !== 2) return false;\n const [result, bucket] = parts as [string, string];\n return KNOWN_REPLY_TARGET_RESULTS.has(result) && KNOWN_PENDING_THREAD_BUCKETS.has(bucket);\n}\n\n/**\n * Read and RESET every `<channel>-reply-target-classifications.json` in an agent\n * home, returning the summed counts by key (or null when there are none). Mirrors\n * readAndResetChannelLaneClassifications exactly: atomic\n * rename-then-read-then-unlink so an increment racing the reset carries into the\n * next window, and only known keys are accumulated. SHADOW telemetry - informs\n * the WS2 binding gate, gates nothing itself.\n */\nexport function readAndResetSlackReplyTargetClassifications(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming); // atomic; replaces any stale .consuming from a crashed cycle\n } catch {\n continue; // vanished / not a regular file - skip\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const [key, raw] of Object.entries(parsed)) {\n if (!isKnownReplyTargetKey(key)) continue;\n if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {\n total[key] = (total[key] ?? 0) + Math.floor(raw);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt - drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n// WS2 (Slack cross-thread reply routing) - per-channel reply-BINDING counter\n// file-layout contract. The slack MCP increments\n// `<channel>-reply-binding-classifications.json` (via\n// recordReplyBindingClassification in packages/mcp/slack-reply-binding-telemetry.ts);\n// apps/cli re-implements the read against this contract, same posture as the\n// reply-target scan above. Body is a JSON object keyed by the binding\n// classification (plus the reserved unknown_inbound_id canary) with integer counts.\nconst REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX = '-reply-binding-classifications.json';\nconst KNOWN_REPLY_BINDING_KEYS = new Set([\n 'bound',\n 'coords_corrected',\n 'coords_match',\n 'proactive',\n 'unknown_target',\n 'legacy',\n 'unknown_inbound_id', // reserved canary\n // ENG-7716: the ENG-7542 DM-channel guard writes these into the same counter\n // file. They are additional reserved keys (channel_mistarget_corrected counts\n // every cross-channel rewrite; dm_channel_mistarget its DM subset), overlapping\n // the main classifications the same way unknown_inbound_id does. Allowlisted so\n // the DM->channel leak signal survives the read/reset fold and reaches CloudWatch.\n 'channel_mistarget_corrected',\n 'dm_channel_mistarget',\n]);\n\n/**\n * Read and RESET every `<channel>-reply-binding-classifications.json` in an agent\n * home, returning the summed counts by classification (or null when there are\n * none). Mirrors readAndResetSlackReplyTargetClassifications; only known keys are\n * accumulated so a corrupt file can't add an unbounded CloudWatch dimension.\n * SHADOW telemetry - informs the enforce flip, gates nothing itself.\n */\nexport function readAndResetSlackReplyBindingClassifications(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming);\n } catch {\n continue;\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const [key, raw] of Object.entries(parsed)) {\n if (!KNOWN_REPLY_BINDING_KEYS.has(key)) continue;\n if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {\n total[key] = (total[key] ?? 0) + Math.floor(raw);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt - drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n// ENG-7550: 60s default (was 5 min). The probe post is what refreshes\n// agents.last_pane_activity_at, so its cadence is the resolution ceiling for\n// minutely busy sampling (sample_agent_activity at p_window_seconds=60).\n// Sized before lowering: the per-agent CloudWatch alarms already run\n// Period:60/EvaluationPeriods:1 (built for 1-min datapoints), CloudWatch\n// bills per metric not per datapoint, and the 5x Lambda/API-GW invocation\n// increase is trivial at fleet scale. The reset-on-read delta counters\n// (deflections, lane/reply classifications) are cadence-agnostic - smaller\n// batches, same totals.\nconst DEFAULT_INTERVAL_MS = 60 * 1000;\n\nexport function getResponsivenessIntervalMs(): number {\n const raw = process.env.AUGMENTED_RESPONSIVENESS_INTERVAL_MS;\n if (!raw) return DEFAULT_INTERVAL_MS;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_INTERVAL_MS;\n}\n\n/**\n * ENG-6017: oldest pending-inbound marker mtime (ms epoch) for an agent,\n * or null when the agent has no markers / no pending-inbound dirs.\n *\n * The channel MCP servers (slack-channel, telegram-channel, …) write one\n * marker file per inbound into `~/.augmented/<codeName>/<channel>-pending-\n * inbound/` and clear it when the agent acknowledges the message. The\n * directory layout is the contract here — read-only, no IPC with the MCP\n * (the MCP and CLI release independently; file mtimes need no protocol).\n *\n * ENG-6072: only plain, non-hidden files count as markers. The msteams MCP\n * keeps `.markers/` and `.processed/` housekeeping SUBDIRECTORIES inside its\n * pending-inbound dir; their mtimes never advance, so statting every dirent\n * made the gauge climb forever and fired pending-inbound-stale on agents with\n * zero stranded messages (kylie ~3.4d / scout ~34h false ALARMs the moment\n * ENG-6023 activated the alarm). Dot-entries are skipped wholesale — the\n * hidden namespace is reserved for MCP bookkeeping, never for markers.\n */\nfunction oldestPendingInboundMtimeMs(agentHomeDir: string): number | null {\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null; // agent home missing — nothing to report\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n try {\n const mtimeMs = statSync(join(dir, file.name)).mtimeMs;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n } catch {\n // Marker drained between readdir and stat — that's the happy path.\n }\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160 / ENG-6319: read a marker's classification flags.\n * - `{...}` → parsed flags.\n * - `null` → vanished mid-scan (ENOENT) — drained between stat and\n * read, the happy path; callers exclude it.\n * - `'malformed'` → present but unreadable for another reason. The live\n * scan treats this as LIVE (a corrupt marker can never\n * mask a real wedge); park leaves it in place (a corrupt\n * marker must never become a dropped message).\n */\ninterface MarkerFlags {\n /** ENG-5846: dead-lettered by the channel (⏳-noticed, never drainable). */\n undeliverable: boolean;\n /**\n * ENG-6319: auto-followed participant-thread inbound the agent may\n * legitimately skip. Excluded from the wedge live-scan (a deliberate skip\n * must not read as \"failing to drain\" and force-respawn a healthy\n * session) but still parked across respawns — it may be a real\n * operator message awaiting a reply.\n */\n discretionary: boolean;\n}\n\nfunction readMarkerFlags(markerPath: string): MarkerFlags | null | 'malformed' {\n try {\n const parsed = JSON.parse(readFileSync(markerPath, 'utf8')) as {\n undeliverable?: unknown;\n discretionary?: unknown;\n };\n return {\n undeliverable: parsed?.undeliverable === true,\n discretionary: parsed?.discretionary === true,\n };\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'ENOENT' ? null : 'malformed';\n }\n}\n\n/**\n * ENG-6160: oldest *LIVE* pending-inbound marker mtime (ms epoch) for an agent,\n * or null when there is no live marker. \"Live\" excludes:\n *\n * - markers older than `sessionStartMs` — a marker written before the current\n * session started is a leftover from a PREVIOUS session and cannot mean\n * *this* session is failing to drain. This is the load-bearing exclusion:\n * without it, an orphan marker survives a fresh respawn and the wedge\n * detector re-fires forever on a healthy idle agent (the sherlock enforce\n * loop, 2026-06-08: `inboundAge=3389s` on a `● Ready.` session).\n * - markers flagged `undeliverable: true` — already dead-lettered by the channel.\n * - markers flagged `discretionary: true` (ENG-6319) — skippable auto-followed\n * participant-thread inbound; a deliberate skip must not force-respawn a\n * healthy session.\n *\n * Distinct from `oldestPendingInboundMtimeMs` (which counts ALL markers and\n * feeds the ENG-6017 `pending-inbound-stale` CloudWatch alarm — that alarm\n * *wants* to fire on a stuck inbound, so its semantics must NOT change). This\n * variant is wedge-detection-only.\n */\nexport function oldestLivePendingInboundMtimeMs(\n agentHomeDir: string,\n opts: { sessionStartMs?: number | null } = {},\n): number | null {\n const sessionStartMs = opts.sessionStartMs ?? null;\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const full = join(dir, file.name);\n let mtimeMs: number;\n try {\n mtimeMs = statSync(full).mtimeMs;\n } catch {\n continue; // drained between readdir and stat — happy path\n }\n if (sessionStartMs !== null && mtimeMs < sessionStartMs) continue; // pre-session leftover\n const flags = readMarkerFlags(full);\n if (flags === null) continue; // vanished between stat and read — drained, exclude\n if (flags !== 'malformed' && (flags.undeliverable || flags.discretionary)) continue;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160: age (s) of the oldest LIVE pending-inbound marker for an agent, or\n * null when none. The wedge detector uses this instead of the alarm-facing\n * `pending_inbound_oldest_age_seconds` so a stale/dead-letter marker can't\n * false-fire a respawn.\n */\nexport function livePendingInboundOldestAgeSeconds(\n codeName: string,\n sessionStartMs: number | null,\n now: Date = new Date(),\n): number | null {\n const oldest = oldestLivePendingInboundMtimeMs(dirname(paneLogPath(codeName)), { sessionStartMs });\n if (oldest === null) return null;\n return Math.max(0, Math.floor((now.getTime() - oldest) / 1000));\n}\n\n/**\n * ENG-6160: move every pending-inbound marker for an agent aside into a sibling\n * `<channel>-pending-inbound-stale/` directory (NOT silently deleted — the\n * payload pointer is preserved for forensics), returning the count moved.\n *\n * ENG-6289: no longer called on wedge respawn — the wedge path parks instead\n * (see `parkPendingInbound` above; blanket dead-letter permanently dropped the\n * user's message). Kept as the explicit \"move everything aside\" seam for tests\n * and operator emergencies. The stale dir does not end in `-pending-inbound`,\n * so neither the probe nor this scan re-counts moved markers.\n */\n/**\n * ENG-6289: park-not-drop. On a force-fresh wedge respawn, KEEP undrained\n * pending-inbound markers in their live dirs instead of dead-lettering them —\n * the fresh session's orient hook surfaces them (\"N queued messages\" + details)\n * and ENG-5969 replay (when enabled) re-pushes their payloads. Markers are NOT\n * rewritten (no counter, no mtime bump): the ENG-6160 pre-session exclusion in\n * `oldestLivePendingInboundMtimeMs` already keeps an untouched parked marker\n * out of the fresh session's wedge signal, and re-delivery stays bounded by\n * the existing machinery — the channel-side `replay_count` cap (≤3) and the\n * orphan sweep's received_at TTL. A manager-side rewrite would be the first\n * cross-process writer into marker files, where a torn read in the channel\n * sweep DELETES the marker (`unlinkSync` on parse failure) — the exact drop\n * this function exists to prevent.\n *\n * Only markers already flagged `undeliverable: true` are dead-lettered (moved\n * to `-stale`) — the channel already gave the user the ⏳ notice for those, so\n * nothing can ever drain them. Malformed markers are LEFT IN PLACE, matching\n * the live-scan philosophy above (a corrupt marker must never mask — or\n * become — a dropped message).\n *\n * The msteams dir is skipped wholesale: its top-level files are raw Bot\n * Framework activity payloads (the transport queue, not bookkeeping — real\n * markers live in the hidden `.markers/` subdir this scan never touches), and\n * the teams channel server's boot drain redelivers them to the fresh session\n * on its own. Moving them aside (what the pre-ENG-6289 dead-letter did) was\n * actively defeating the one channel with native respawn recovery.\n */\nexport interface ParkPendingInboundResult {\n parked: number;\n deadLettered: number;\n}\n\n/**\n * Move one marker into the sibling `-stale` dead-letter dir (moved, not\n * deleted — preserved for forensics). Returns true on success; best-effort —\n * a marker that vanished or can't move is left as-is.\n */\nfunction moveMarkerToStale(dir: string, deadDir: string, name: string): boolean {\n try {\n mkdirSync(deadDir, { recursive: true });\n renameSync(join(dir, name), join(deadDir, name));\n return true;\n } catch {\n return false;\n }\n}\n\nexport function parkPendingInbound(codeName: string, _now: Date = new Date()): ParkPendingInboundResult {\n const home = dirname(paneLogPath(codeName));\n const result: ParkPendingInboundResult = { parked: 0, deadLettered: 0 };\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return result;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n if (entry.name === 'msteams-pending-inbound') continue; // transport queue — boot drain owns recovery\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const flags = readMarkerFlags(join(dir, file.name));\n if (flags === null) continue; // drained mid-scan — already gone\n // Only undeliverable markers dead-letter. Discretionary markers\n // (ENG-6319) PARK like engaged ones — they may be a real operator\n // message awaiting a reply (the live silent-loss class); malformed\n // markers park too (corrupt must never become a drop).\n if (flags !== 'malformed' && flags.undeliverable) {\n if (moveMarkerToStale(dir, deadDir, file.name)) result.deadLettered++;\n } else {\n result.parked++;\n }\n }\n }\n return result;\n}\n\nexport function deadLetterPendingInbound(codeName: string, _now: Date = new Date()): number {\n const home = dirname(paneLogPath(codeName));\n let moved = 0;\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return 0;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n if (moveMarkerToStale(dir, deadDir, file.name)) moved++;\n }\n }\n return moved;\n}\n\n/**\n * Compute the pane.log age for each agent. Missing or unreadable\n * pane.log returns null — the caller should drop those entries\n * rather than fabricate a \"fresh\" or \"ancient\" value. A missing\n * file means the agent has never spawned in this manager generation,\n * which is a separate problem covered by SessionAliveAgeSeconds.\n */\nexport function collectResponsivenessProbes(\n codeNames: string[],\n now: Date = new Date(),\n): ResponsivenessProbeResult[] {\n const nowMs = now.getTime();\n const results: ResponsivenessProbeResult[] = [];\n for (const codeName of codeNames) {\n try {\n const panePath = paneLogPath(codeName);\n const mtimeMs = statSync(panePath).mtimeMs;\n const ageSeconds = Math.max(0, Math.floor((nowMs - mtimeMs) / 1000));\n const result: ResponsivenessProbeResult = {\n code_name: codeName,\n pane_activity_age_seconds: ageSeconds,\n };\n // ENG-6017: piggyback the pending-inbound drain-age scan on the same\n // cadence. Field omitted (not 0) when there are no markers.\n const oldestMarkerMs = oldestPendingInboundMtimeMs(dirname(panePath));\n if (oldestMarkerMs !== null) {\n result.pending_inbound_oldest_age_seconds = Math.max(\n 0,\n Math.floor((nowMs - oldestMarkerMs) / 1000),\n );\n }\n // ENG-6327: read + RESET the per-channel deflection counters on the same\n // cadence so each cause's frequency reaches CloudWatch (ChannelDeflections,\n // Cause dimension). Omitted when there were none this window.\n const deflections = readAndResetChannelDeflections(dirname(panePath));\n if (deflections) {\n result.deflections = deflections;\n }\n // ENG-6408 / ADR-0024 Slice 1.5: read + RESET the shadow lane-classification\n // counters on the same cadence so the classification mix (and the\n // misclassification canary) reaches CloudWatch. Omitted when none this window.\n const laneClassifications = readAndResetChannelLaneClassifications(dirname(panePath));\n if (laneClassifications) {\n result.lane_classifications = laneClassifications;\n }\n // WS0: read + RESET the shadow reply-target counters on the same cadence so\n // the cross-thread classification mix reaches CloudWatch. Omitted when none.\n const replyTargetClassifications = readAndResetSlackReplyTargetClassifications(\n dirname(panePath),\n );\n if (replyTargetClassifications) {\n result.slack_reply_target_classifications = replyTargetClassifications;\n }\n // WS2: read + RESET the shadow reply-binding counters on the same cadence\n // so the bound / coords_corrected / unknown_target mix (the enforce-flip\n // gate) reaches CloudWatch. Omitted when none this window.\n const replyBindingClassifications = readAndResetSlackReplyBindingClassifications(\n dirname(panePath),\n );\n if (replyBindingClassifications) {\n result.slack_reply_binding_classifications = replyBindingClassifications;\n }\n results.push(result);\n } catch {\n // No pane.log yet (fresh agent, never spawned) — skip. The\n // session-alive monitor already covers the \"should be running\n // but isn't\" case.\n }\n }\n return results;\n}\n"],"mappings":"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","names":[]}