@integrity-labs/agt-cli 0.27.166 → 0.27.168

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.
package/dist/bin/agt.js CHANGED
@@ -33,7 +33,7 @@ import {
33
33
  success,
34
34
  table,
35
35
  warn
36
- } from "../chunk-DMZQM4DY.js";
36
+ } from "../chunk-ZQGQVROS.js";
37
37
  import {
38
38
  CHANNEL_REGISTRY,
39
39
  DEPLOYMENT_TEMPLATES,
@@ -5019,7 +5019,7 @@ import { execFileSync, execSync } from "child_process";
5019
5019
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
5020
5020
  import chalk18 from "chalk";
5021
5021
  import ora16 from "ora";
5022
- var cliVersion = true ? "0.27.166" : "dev";
5022
+ var cliVersion = true ? "0.27.168" : "dev";
5023
5023
  async function fetchLatestVersion() {
5024
5024
  const host2 = getHost();
5025
5025
  if (!host2) return null;
@@ -5942,7 +5942,7 @@ function handleError(err) {
5942
5942
  }
5943
5943
 
5944
5944
  // src/bin/agt.ts
5945
- var cliVersion2 = true ? "0.27.166" : "dev";
5945
+ var cliVersion2 = true ? "0.27.168" : "dev";
5946
5946
  var program = new Command();
5947
5947
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
5948
5948
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -8222,4 +8222,4 @@ export {
8222
8222
  managerInstallSystemUnitCommand,
8223
8223
  managerUninstallSystemUnitCommand
8224
8224
  };
8225
- //# sourceMappingURL=chunk-DMZQM4DY.js.map
8225
+ //# sourceMappingURL=chunk-ZQGQVROS.js.map
@@ -22,7 +22,7 @@ import {
22
22
  provisionStopHook,
23
23
  requireHost,
24
24
  safeWriteJsonAtomic
25
- } from "../chunk-DMZQM4DY.js";
25
+ } from "../chunk-ZQGQVROS.js";
26
26
  import {
27
27
  getProjectDir as getProjectDir2,
28
28
  getReadyTasks,
@@ -5041,7 +5041,7 @@ var cachedMaintenanceWindow = null;
5041
5041
  var lastVersionCheckAt = 0;
5042
5042
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
5043
5043
  var lastResponsivenessProbeAt = 0;
5044
- var agtCliVersion = true ? "0.27.166" : "dev";
5044
+ var agtCliVersion = true ? "0.27.168" : "dev";
5045
5045
  function resolveBrewPath(execFileSync4) {
5046
5046
  try {
5047
5047
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -6338,7 +6338,7 @@ async function pollCycle() {
6338
6338
  const {
6339
6339
  collectResponsivenessProbes,
6340
6340
  getResponsivenessIntervalMs
6341
- } = await import("../responsiveness-probe-AL3O7SYZ.js");
6341
+ } = await import("../responsiveness-probe-7NDEHXXZ.js");
6342
6342
  const probeIntervalMs = getResponsivenessIntervalMs();
6343
6343
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
6344
6344
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -6377,7 +6377,7 @@ async function pollCycle() {
6377
6377
  collectResponsivenessProbes,
6378
6378
  livePendingInboundOldestAgeSeconds,
6379
6379
  parkPendingInbound
6380
- } = await import("../responsiveness-probe-AL3O7SYZ.js");
6380
+ } = await import("../responsiveness-probe-7NDEHXXZ.js");
6381
6381
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
6382
6382
  const wedgeNow = /* @__PURE__ */ new Date();
6383
6383
  const liveAgents = agentState.persistentSessionAgents;
@@ -14208,6 +14208,9 @@ function decideSlackEngagement(input) {
14208
14208
  const isExplicitMention = input.type === "app_mention" || !!input.botUserId && input.text.includes(`<@${input.botUserId}>`);
14209
14209
  return !(input.isAutoFollowed && !isExplicitMention);
14210
14210
  }
14211
+ function decideMarkerArm(input) {
14212
+ return input.shouldEngage || input.isAutoFollowed && input.botPostedInThread;
14213
+ }
14211
14214
  function decideSlackAckReaction(input) {
14212
14215
  return Boolean(input.channel && input.ts);
14213
14216
  }
@@ -14585,6 +14588,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
14585
14588
  let receivedAt;
14586
14589
  try {
14587
14590
  const raw = JSON.parse(readFileSync2(join2(dir, name), "utf-8"));
14591
+ if (raw.discretionary === true) continue;
14588
14592
  receivedAt = raw.received_at;
14589
14593
  } catch {
14590
14594
  continue;
@@ -15439,7 +15443,11 @@ function loadThreadStore(filePath, opts = {}) {
15439
15443
  pruned++;
15440
15444
  continue;
15441
15445
  }
15442
- threads.set(key2, { involvement: entry.involvement, last_seen_at: entry.last_seen_at });
15446
+ threads.set(key2, {
15447
+ involvement: entry.involvement,
15448
+ last_seen_at: entry.last_seen_at,
15449
+ ...entry.bot_posted === true ? { bot_posted: true } : {}
15450
+ });
15443
15451
  }
15444
15452
  return { threads, pruned };
15445
15453
  }
@@ -16600,7 +16608,7 @@ function slackPendingInboundPath(channel, threadTs, messageTs) {
16600
16608
  if (!SLACK_PENDING_INBOUND_DIR) return null;
16601
16609
  return join7(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
16602
16610
  }
16603
- function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false) {
16611
+ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
16604
16612
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
16605
16613
  if (!path || !SLACK_PENDING_INBOUND_DIR) return;
16606
16614
  const marker = {
@@ -16608,8 +16616,9 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
16608
16616
  thread_ts: threadTs,
16609
16617
  message_ts: messageTs,
16610
16618
  received_at: (/* @__PURE__ */ new Date()).toISOString(),
16611
- // Only persist the flag when set — keeps healthy-path markers byte-identical.
16612
- ...undeliverable ? { undeliverable: true } : {}
16619
+ // Only persist the flags when set — keeps healthy-path markers byte-identical.
16620
+ ...undeliverable ? { undeliverable: true } : {},
16621
+ ...discretionary ? { discretionary: true } : {}
16613
16622
  };
16614
16623
  try {
16615
16624
  mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
@@ -16957,8 +16966,9 @@ function startSlackRecoveryOutboxWatcher() {
16957
16966
  }
16958
16967
  startSlackRecoveryOutboxWatcher();
16959
16968
  var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
16960
- function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false) {
16961
- writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable);
16969
+ var DISCRETIONARY_MARKER_MS = 10 * 60 * 1e3;
16970
+ function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
16971
+ writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable, discretionary);
16962
16972
  }
16963
16973
  function sweepSlackStaleMarkers(thresholdMs) {
16964
16974
  if (!SLACK_PENDING_INBOUND_DIR) return;
@@ -16995,7 +17005,8 @@ function sweepSlackStaleMarkers(thresholdMs) {
16995
17005
  continue;
16996
17006
  }
16997
17007
  const { channel, thread_ts, message_ts, received_at } = marker;
16998
- if (!channel || !thread_ts || !message_ts || isPendingMarkerStale(received_at, now, thresholdMs)) {
17008
+ const effectiveThresholdMs = marker.discretionary ? Math.min(thresholdMs, DISCRETIONARY_MARKER_MS) : thresholdMs;
17009
+ if (!channel || !thread_ts || !message_ts || isPendingMarkerStale(received_at, now, effectiveThresholdMs)) {
16999
17010
  try {
17000
17011
  unlinkSync4(fullPath);
17001
17012
  } catch {
@@ -17031,6 +17042,7 @@ function listPendingSlackConversations() {
17031
17042
  );
17032
17043
  if (typeof marker.channel !== "string" || !marker.channel) continue;
17033
17044
  if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
17045
+ if (marker.discretionary === true) continue;
17034
17046
  const isThreadReply = typeof marker.message_ts === "string" && marker.message_ts !== marker.thread_ts;
17035
17047
  const key2 = isThreadReply ? `${marker.channel}:${marker.thread_ts}` : marker.channel;
17036
17048
  if (!byKey.has(key2)) {
@@ -17910,7 +17922,11 @@ function rememberThread(channel, threadTs, involvement, opts = {}) {
17910
17922
  const existing = trackedThreads.get(key2);
17911
17923
  const next = {
17912
17924
  involvement: opts.override || !existing ? involvement : existing.involvement,
17913
- last_seen_at: (/* @__PURE__ */ new Date()).toISOString()
17925
+ last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
17926
+ // ENG-6319: sticky — once the bot has posted in a thread it stays a
17927
+ // conversational participant for marker-arming (persisted only when true
17928
+ // so pre-flag entries and never-posted threads stay byte-identical).
17929
+ ...opts.botPosted || existing?.bot_posted ? { bot_posted: true } : {}
17914
17930
  };
17915
17931
  trackedThreads.set(key2, next);
17916
17932
  if (isShuttingDown) {
@@ -18378,7 +18394,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
18378
18394
  recordActivity("reply");
18379
18395
  if (THREAD_AUTO_FOLLOW !== "off") {
18380
18396
  const trackTs = effectiveThreadTs ?? data.ts ?? void 0;
18381
- rememberThread(channel, trackTs, thread_ts ? "mentioned" : "started");
18397
+ rememberThread(channel, trackTs, thread_ts ? "mentioned" : "started", { botPosted: true });
18382
18398
  }
18383
18399
  if (interactiveHostAvailable() && data.ts) {
18384
18400
  const cfg = {
@@ -19502,6 +19518,9 @@ async function connectSocketMode() {
19502
19518
  isAutoFollowed,
19503
19519
  botUserId
19504
19520
  });
19521
+ const participantEntry = threadKey ? trackedThreads.get(threadKey) : void 0;
19522
+ const botPostedInThread = participantEntry?.bot_posted === true || participantEntry?.involvement === "started";
19523
+ const armMarker = decideMarkerArm({ shouldEngage, isAutoFollowed, botPostedInThread });
19505
19524
  const ackProbe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
19506
19525
  let paneLogFreshAgeMs = null;
19507
19526
  if (SLACK_AGENT_DIR) {
@@ -19531,13 +19550,13 @@ async function connectSocketMode() {
19531
19550
  body: JSON.stringify({ channel, timestamp: ts, name: reactionName })
19532
19551
  }).catch(() => {
19533
19552
  });
19534
- if (ackDecision === "undeliverable" && channel && shouldEngage) {
19553
+ if (ackDecision === "undeliverable" && channel && armMarker) {
19535
19554
  postUndeliverableNotice(channel, threadTs, isThreadReply);
19536
19555
  }
19537
19556
  }
19538
- if (channel && ts && shouldEngage) {
19539
- trackPendingMessage(channel, threadTs, ts, ackDecision === "undeliverable");
19540
- if (ackDecision === "ack") {
19557
+ if (channel && ts && armMarker) {
19558
+ trackPendingMessage(channel, threadTs, ts, ackDecision === "undeliverable", !shouldEngage);
19559
+ if (ackDecision === "ack" && shouldEngage) {
19541
19560
  scheduleBusyAck(channel, threadTs, ts, isThreadReply);
19542
19561
  }
19543
19562
  }
@@ -14320,16 +14320,16 @@ import {
14320
14320
  createWriteStream,
14321
14321
  existsSync as existsSync5,
14322
14322
  mkdirSync as mkdirSync4,
14323
- readFileSync as readFileSync6,
14324
- readdirSync as readdirSync2,
14323
+ readFileSync as readFileSync7,
14324
+ readdirSync as readdirSync3,
14325
14325
  renameSync as renameSync4,
14326
- statSync,
14326
+ statSync as statSync2,
14327
14327
  unlinkSync as unlinkSync4,
14328
14328
  watch,
14329
14329
  writeFileSync as writeFileSync4
14330
14330
  } from "fs";
14331
14331
  import { homedir as homedir3 } from "os";
14332
- import { join as join6 } from "path";
14332
+ import { join as join7 } from "path";
14333
14333
 
14334
14334
  // src/channel-attachments.ts
14335
14335
  import { homedir } from "os";
@@ -16000,16 +16000,53 @@ var TELEGRAM_EGRESS_TOOLS = /* @__PURE__ */ new Set([
16000
16000
  "channel_request_input"
16001
16001
  ]);
16002
16002
 
16003
+ // src/telegram-pending-inbound-cleanup.ts
16004
+ import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
16005
+ import { join as join3 } from "path";
16006
+ function markerArrivalMs(fullPath) {
16007
+ try {
16008
+ const received = JSON.parse(readFileSync3(fullPath, "utf-8")).received_at;
16009
+ const parsed = received ? Date.parse(received) : Number.NaN;
16010
+ if (Number.isFinite(parsed)) return parsed;
16011
+ } catch {
16012
+ }
16013
+ try {
16014
+ return statSync(fullPath).mtimeMs;
16015
+ } catch {
16016
+ return Number.POSITIVE_INFINITY;
16017
+ }
16018
+ }
16019
+ function clearAllTelegramPendingMarkersForChat(pendingDir, chatId, clearMarkerFile, cutoffMs = Number.POSITIVE_INFINITY) {
16020
+ const prefix = `${chatId.replace(/[^A-Za-z0-9_-]/g, "_")}__`;
16021
+ const bounded = Number.isFinite(cutoffMs);
16022
+ let filenames;
16023
+ try {
16024
+ filenames = readdirSync(pendingDir);
16025
+ } catch {
16026
+ return 0;
16027
+ }
16028
+ let cleared = 0;
16029
+ for (const filename of filenames) {
16030
+ if (!filename.startsWith(prefix)) continue;
16031
+ if (!filename.endsWith(".json")) continue;
16032
+ const fullPath = join3(pendingDir, filename);
16033
+ if (bounded && markerArrivalMs(fullPath) > cutoffMs) continue;
16034
+ clearMarkerFile(fullPath);
16035
+ cleared++;
16036
+ }
16037
+ return cleared;
16038
+ }
16039
+
16003
16040
  // src/mcp-spawn-lock.ts
16004
16041
  import {
16005
16042
  existsSync as existsSync3,
16006
16043
  mkdirSync as mkdirSync3,
16007
- readFileSync as readFileSync3,
16044
+ readFileSync as readFileSync4,
16008
16045
  renameSync as renameSync3,
16009
16046
  unlinkSync as unlinkSync3,
16010
16047
  writeFileSync as writeFileSync3
16011
16048
  } from "fs";
16012
- import { join as join3 } from "path";
16049
+ import { join as join4 } from "path";
16013
16050
  function defaultIsPidAlive(pid) {
16014
16051
  if (!Number.isFinite(pid) || pid <= 0) return false;
16015
16052
  try {
@@ -16027,7 +16064,7 @@ function acquireMcpSpawnLock(args) {
16027
16064
  const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
16028
16065
  const selfPid = options.selfPid ?? process.pid;
16029
16066
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
16030
- const path = join3(agentDir, basename);
16067
+ const path = join4(agentDir, basename);
16031
16068
  const existing = readLockHolder(path);
16032
16069
  if (existing) {
16033
16070
  if (existing.pid === selfPid) {
@@ -16058,7 +16095,7 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
16058
16095
  function readLockHolder(path) {
16059
16096
  if (!existsSync3(path)) return null;
16060
16097
  try {
16061
- const raw = readFileSync3(path, "utf8");
16098
+ const raw = readFileSync4(path, "utf8");
16062
16099
  const parsed = JSON.parse(raw);
16063
16100
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
16064
16101
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -16070,15 +16107,15 @@ function readLockHolder(path) {
16070
16107
  }
16071
16108
 
16072
16109
  // src/ack-reaction.ts
16073
- import { readdirSync, readFileSync as readFileSync5 } from "fs";
16074
- import { join as join5 } from "path";
16110
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
16111
+ import { join as join6 } from "path";
16075
16112
 
16076
16113
  // src/flags-cache-read.ts
16077
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
16114
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
16078
16115
  import { homedir as homedir2 } from "os";
16079
- import { join as join4 } from "path";
16116
+ import { join as join5 } from "path";
16080
16117
  function defaultFlagsCachePath() {
16081
- return join4(homedir2(), ".augmented", "flags-cache.json");
16118
+ return join5(homedir2(), ".augmented", "flags-cache.json");
16082
16119
  }
16083
16120
  function envBoolean(raw) {
16084
16121
  if (raw === void 0) return void 0;
@@ -16091,7 +16128,7 @@ function envBoolean(raw) {
16091
16128
  function cachedBoolean(key2, path) {
16092
16129
  try {
16093
16130
  if (!existsSync4(path)) return void 0;
16094
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
16131
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
16095
16132
  if (!parsed || typeof parsed !== "object") return void 0;
16096
16133
  const flags = parsed.flags;
16097
16134
  if (!flags || typeof flags !== "object") return void 0;
@@ -16171,7 +16208,7 @@ var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
16171
16208
  function readGiveUpSignalAtMs(path, now = Date.now()) {
16172
16209
  if (!path) return null;
16173
16210
  try {
16174
- const raw = JSON.parse(readFileSync5(path, "utf8"));
16211
+ const raw = JSON.parse(readFileSync6(path, "utf8"));
16175
16212
  if (typeof raw.gave_up_at !== "string") return null;
16176
16213
  const t = Date.parse(raw.gave_up_at);
16177
16214
  if (!Number.isFinite(t) || t > now) return null;
@@ -16194,7 +16231,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
16194
16231
  if (!dir) return null;
16195
16232
  let names;
16196
16233
  try {
16197
- names = readdirSync(dir);
16234
+ names = readdirSync2(dir);
16198
16235
  } catch {
16199
16236
  return null;
16200
16237
  }
@@ -16203,7 +16240,8 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
16203
16240
  if (!name.endsWith(".json")) continue;
16204
16241
  let receivedAt;
16205
16242
  try {
16206
- const raw = JSON.parse(readFileSync5(join5(dir, name), "utf-8"));
16243
+ const raw = JSON.parse(readFileSync6(join6(dir, name), "utf-8"));
16244
+ if (raw.discretionary === true) continue;
16207
16245
  receivedAt = raw.received_at;
16208
16246
  } catch {
16209
16247
  continue;
@@ -16251,7 +16289,7 @@ function redactId(id) {
16251
16289
  }
16252
16290
  var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
16253
16291
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
16254
- var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join6(homedir3(), ".augmented", AGENT_CODE_NAME) : null;
16292
+ var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join7(homedir3(), ".augmented", AGENT_CODE_NAME) : null;
16255
16293
  var AGT_HOST = process.env.AGT_HOST ?? null;
16256
16294
  var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
16257
16295
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
@@ -16345,9 +16383,9 @@ if (!BOT_TOKEN) {
16345
16383
  var stderrLogStream = null;
16346
16384
  if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
16347
16385
  try {
16348
- const logDir = join6(homedir3(), ".augmented", AGENT_CODE_NAME);
16386
+ const logDir = join7(homedir3(), ".augmented", AGENT_CODE_NAME);
16349
16387
  mkdirSync4(logDir, { recursive: true });
16350
- stderrLogStream = createWriteStream(join6(logDir, "telegram-channel-stderr.log"), {
16388
+ stderrLogStream = createWriteStream(join7(logDir, "telegram-channel-stderr.log"), {
16351
16389
  flags: "a",
16352
16390
  mode: 384
16353
16391
  });
@@ -16537,7 +16575,7 @@ function scheduleBusyAck(chatId, messageId) {
16537
16575
  let paneLogFreshAgeMs = null;
16538
16576
  if (AGENT_DIR) {
16539
16577
  try {
16540
- const paneMtimeMs = statSync(join6(AGENT_DIR, "pane.log")).mtimeMs;
16578
+ const paneMtimeMs = statSync2(join7(AGENT_DIR, "pane.log")).mtimeMs;
16541
16579
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
16542
16580
  } catch {
16543
16581
  }
@@ -16559,7 +16597,7 @@ function scheduleBusyAck(chatId, messageId) {
16559
16597
  function __resetBusyAckNoticeThrottle() {
16560
16598
  lastBusyAckNoticeAt.clear();
16561
16599
  }
16562
- var RESTART_FLAGS_DIR = join6(homedir3(), ".augmented", "restart-flags");
16600
+ var RESTART_FLAGS_DIR = join7(homedir3(), ".augmented", "restart-flags");
16563
16601
  function writeTelegramRestartConfirm(reply, requesterName) {
16564
16602
  if (!RESTART_CONFIRM_FILE) return;
16565
16603
  const marker = {
@@ -16752,7 +16790,7 @@ async function handleRestartCommand(opts) {
16752
16790
  if (!existsSync5(RESTART_FLAGS_DIR)) {
16753
16791
  mkdirSync4(RESTART_FLAGS_DIR, { recursive: true });
16754
16792
  }
16755
- const flagPath = join6(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
16793
+ const flagPath = join7(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
16756
16794
  writeTelegramRestartConfirm(
16757
16795
  { chat_id: opts.chatId, message_id: opts.messageId },
16758
16796
  opts.requesterName
@@ -17135,10 +17173,10 @@ async function classifyRestartCommand(text) {
17135
17173
  if (!ours) return "verification_failed";
17136
17174
  return target === ours ? "act" : "ignore";
17137
17175
  }
17138
- var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join6(homedir3(), ".augmented", AGENT_CODE_NAME) : null;
17139
- var PENDING_INBOUND_DIR = AGENT_DIR ? join6(AGENT_DIR, "telegram-pending-inbound") : null;
17140
- var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join6(AGENT_DIR, "telegram-recovery-outbox") : null;
17141
- var RESTART_CONFIRM_FILE = AGENT_DIR ? join6(AGENT_DIR, "telegram-restart-confirm.json") : null;
17176
+ var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join7(homedir3(), ".augmented", AGENT_CODE_NAME) : null;
17177
+ var PENDING_INBOUND_DIR = AGENT_DIR ? join7(AGENT_DIR, "telegram-pending-inbound") : null;
17178
+ var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join7(AGENT_DIR, "telegram-recovery-outbox") : null;
17179
+ var RESTART_CONFIRM_FILE = AGENT_DIR ? join7(AGENT_DIR, "telegram-restart-confirm.json") : null;
17142
17180
  var TELEGRAM_PROCESS_BOOT_MS = Date.now();
17143
17181
  function safeMarkerName(chatId, messageId) {
17144
17182
  const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
@@ -17146,7 +17184,7 @@ function safeMarkerName(chatId, messageId) {
17146
17184
  }
17147
17185
  function pendingInboundPath(chatId, messageId) {
17148
17186
  if (!PENDING_INBOUND_DIR) return null;
17149
- return join6(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
17187
+ return join7(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
17150
17188
  }
17151
17189
  function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
17152
17190
  const path = pendingInboundPath(chatId, messageId);
@@ -17175,7 +17213,7 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
17175
17213
  function clearTelegramMarkerFileWithHeal(fullPath) {
17176
17214
  let marker = null;
17177
17215
  try {
17178
- marker = JSON.parse(readFileSync6(fullPath, "utf-8"));
17216
+ marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
17179
17217
  } catch {
17180
17218
  }
17181
17219
  if (marker && decideRecoveryHeal({
@@ -17189,16 +17227,11 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
17189
17227
  } catch {
17190
17228
  }
17191
17229
  }
17192
- function clearPendingInboundMarker(chatId, messageId) {
17193
- const path = pendingInboundPath(chatId, messageId);
17194
- if (!path) return;
17195
- clearTelegramMarkerFileWithHeal(path);
17196
- }
17197
17230
  function readPendingInboundMarker(chatId, messageId) {
17198
17231
  const path = pendingInboundPath(chatId, messageId);
17199
17232
  if (!path || !existsSync5(path)) return null;
17200
17233
  try {
17201
- return JSON.parse(readFileSync6(path, "utf-8"));
17234
+ return JSON.parse(readFileSync7(path, "utf-8"));
17202
17235
  } catch {
17203
17236
  return null;
17204
17237
  }
@@ -17218,10 +17251,10 @@ function nextRetryName(filename) {
17218
17251
  async function processRecoveryOutboxFile(filename) {
17219
17252
  if (!RECOVERY_OUTBOX_DIR) return;
17220
17253
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
17221
- const fullPath = join6(RECOVERY_OUTBOX_DIR, filename);
17254
+ const fullPath = join7(RECOVERY_OUTBOX_DIR, filename);
17222
17255
  let payload;
17223
17256
  try {
17224
- const raw = readFileSync6(fullPath, "utf-8");
17257
+ const raw = readFileSync7(fullPath, "utf-8");
17225
17258
  payload = JSON.parse(raw);
17226
17259
  } catch (err) {
17227
17260
  process.stderr.write(
@@ -17253,6 +17286,7 @@ async function processRecoveryOutboxFile(filename) {
17253
17286
  };
17254
17287
  if (payload.message_id) body.reply_to_message_id = Number(payload.message_id);
17255
17288
  let sendSucceeded = false;
17289
+ const recoveryDrainCutoffMs = Date.now();
17256
17290
  try {
17257
17291
  const resp = await telegramApiCall("sendMessage", body, 15e3);
17258
17292
  if (resp.ok) {
@@ -17261,7 +17295,7 @@ async function processRecoveryOutboxFile(filename) {
17261
17295
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery sent (chat=${redactId(payload.chat_id)} msg=${redactId(payload.message_id ?? "")})
17262
17296
  `
17263
17297
  );
17264
- if (payload.message_id) clearPendingMessage(payload.chat_id, payload.message_id);
17298
+ clearPendingMessage(payload.chat_id, recoveryDrainCutoffMs);
17265
17299
  } else {
17266
17300
  process.stderr.write(
17267
17301
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery failed (chat=${redactId(payload.chat_id)}): ${resp.description ?? "unknown"}
@@ -17284,7 +17318,7 @@ async function processRecoveryOutboxFile(filename) {
17284
17318
  const next = nextRetryName(filename);
17285
17319
  if (next) {
17286
17320
  try {
17287
- renameSync4(fullPath, join6(RECOVERY_OUTBOX_DIR, next.next));
17321
+ renameSync4(fullPath, join7(RECOVERY_OUTBOX_DIR, next.next));
17288
17322
  if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
17289
17323
  process.stderr.write(
17290
17324
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -17315,7 +17349,7 @@ function scanRecoveryRetries() {
17315
17349
  if (!RECOVERY_OUTBOX_DIR) return;
17316
17350
  let entries;
17317
17351
  try {
17318
- entries = readdirSync2(RECOVERY_OUTBOX_DIR);
17352
+ entries = readdirSync3(RECOVERY_OUTBOX_DIR);
17319
17353
  } catch {
17320
17354
  return;
17321
17355
  }
@@ -17324,7 +17358,7 @@ function scanRecoveryRetries() {
17324
17358
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
17325
17359
  let mtimeMs;
17326
17360
  try {
17327
- mtimeMs = statSync(join6(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
17361
+ mtimeMs = statSync2(join7(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
17328
17362
  } catch {
17329
17363
  continue;
17330
17364
  }
@@ -17345,7 +17379,7 @@ function startRecoveryOutboxWatcher() {
17345
17379
  return;
17346
17380
  }
17347
17381
  try {
17348
- for (const f of readdirSync2(RECOVERY_OUTBOX_DIR)) {
17382
+ for (const f of readdirSync3(RECOVERY_OUTBOX_DIR)) {
17349
17383
  if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
17350
17384
  }
17351
17385
  } catch {
@@ -17354,7 +17388,7 @@ function startRecoveryOutboxWatcher() {
17354
17388
  const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
17355
17389
  if (event !== "rename" || !filename) return;
17356
17390
  if (!isFirstAttemptOutboxFile(filename)) return;
17357
- if (existsSync5(join6(RECOVERY_OUTBOX_DIR, filename))) {
17391
+ if (existsSync5(join7(RECOVERY_OUTBOX_DIR, filename))) {
17358
17392
  void processRecoveryOutboxFile(filename);
17359
17393
  }
17360
17394
  });
@@ -17378,7 +17412,7 @@ function sweepTelegramStaleMarkers(thresholdMs) {
17378
17412
  if (!existsSync5(PENDING_INBOUND_DIR)) return;
17379
17413
  let filenames;
17380
17414
  try {
17381
- filenames = readdirSync2(PENDING_INBOUND_DIR);
17415
+ filenames = readdirSync3(PENDING_INBOUND_DIR);
17382
17416
  } catch (err) {
17383
17417
  process.stderr.write(
17384
17418
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -17391,10 +17425,10 @@ function sweepTelegramStaleMarkers(thresholdMs) {
17391
17425
  for (const filename of filenames) {
17392
17426
  if (!filename.endsWith(".json")) continue;
17393
17427
  if (filename.endsWith(".tmp")) continue;
17394
- const fullPath = join6(PENDING_INBOUND_DIR, filename);
17428
+ const fullPath = join7(PENDING_INBOUND_DIR, filename);
17395
17429
  let marker;
17396
17430
  try {
17397
- marker = JSON.parse(readFileSync6(fullPath, "utf-8"));
17431
+ marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
17398
17432
  } catch (err) {
17399
17433
  process.stderr.write(
17400
17434
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
@@ -17436,11 +17470,11 @@ function listPendingInboundChatIds() {
17436
17470
  if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return [];
17437
17471
  const chats = /* @__PURE__ */ new Set();
17438
17472
  try {
17439
- for (const name of readdirSync2(PENDING_INBOUND_DIR)) {
17473
+ for (const name of readdirSync3(PENDING_INBOUND_DIR)) {
17440
17474
  if (!name.endsWith(".json")) continue;
17441
17475
  try {
17442
17476
  const marker = JSON.parse(
17443
- readFileSync6(join6(PENDING_INBOUND_DIR, name), "utf8")
17477
+ readFileSync7(join7(PENDING_INBOUND_DIR, name), "utf8")
17444
17478
  );
17445
17479
  if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
17446
17480
  } catch {
@@ -17481,7 +17515,7 @@ async function notifyWatchdogGiveUp(chatId) {
17481
17515
  }
17482
17516
  function checkWatchdogGiveUpNotice() {
17483
17517
  if (!AGENT_DIR) return;
17484
- const signalAtMs = readGiveUpSignalAtMs(join6(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
17518
+ const signalAtMs = readGiveUpSignalAtMs(join7(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
17485
17519
  const act = decideGiveUpNotice({
17486
17520
  signalAtMs,
17487
17521
  lastHandledAtMs: lastGiveUpHandledAtMs,
@@ -17531,6 +17565,7 @@ async function deliverQueuedReply(p) {
17531
17565
  }
17532
17566
  const body = { chat_id: p.chatId, text: p.text };
17533
17567
  if (p.replyToMessageId) body.reply_to_message_id = Number(p.replyToMessageId);
17568
+ const drainCutoffMs = Date.now();
17534
17569
  const data = await telegramApiCall("sendMessage", body, 15e3);
17535
17570
  if (!data.ok) {
17536
17571
  process.stderr.write(
@@ -17541,7 +17576,7 @@ async function deliverQueuedReply(p) {
17541
17576
  }
17542
17577
  recordReply(p.chatId, "", Date.now(), p.throttleCfg);
17543
17578
  if (p.isReplyTool) {
17544
- clearPendingMessage(p.chatId, p.replyToMessageId);
17579
+ clearPendingMessage(p.chatId, drainCutoffMs);
17545
17580
  }
17546
17581
  process.stderr.write(
17547
17582
  `telegram-channel(${AGENT_CODE_NAME}): reply_queue_delivered chat=${redactId(p.chatId)}
@@ -17554,29 +17589,18 @@ async function deliverQueuedReply(p) {
17554
17589
  );
17555
17590
  }
17556
17591
  }
17557
- function clearPendingMessage(chatId, messageId) {
17558
- if (messageId) {
17559
- clearPendingInboundMarker(chatId, messageId);
17560
- return;
17561
- }
17592
+ function clearPendingMessage(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
17562
17593
  if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return;
17563
- const safeChatId = chatId.replace(/[^A-Za-z0-9_-]/g, "_");
17564
- const prefix = `${safeChatId}__`;
17565
- let filenames;
17566
- try {
17567
- filenames = readdirSync2(PENDING_INBOUND_DIR);
17568
- } catch {
17569
- return;
17570
- }
17571
- for (const filename of filenames) {
17572
- if (!filename.startsWith(prefix)) continue;
17573
- if (!filename.endsWith(".json")) continue;
17574
- clearTelegramMarkerFileWithHeal(join6(PENDING_INBOUND_DIR, filename));
17575
- }
17594
+ clearAllTelegramPendingMarkersForChat(
17595
+ PENDING_INBOUND_DIR,
17596
+ chatId,
17597
+ clearTelegramMarkerFileWithHeal,
17598
+ cutoffMs
17599
+ );
17576
17600
  }
17577
- function noteThreadActivity(chatId, messageId) {
17601
+ function noteThreadActivity(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
17578
17602
  if (!chatId) return;
17579
- clearPendingMessage(chatId, messageId);
17603
+ clearPendingMessage(chatId, cutoffMs);
17580
17604
  }
17581
17605
  var mcp = new Server(
17582
17606
  { name: "telegram", version: "0.1.0" },
@@ -17840,6 +17864,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
17840
17864
  try {
17841
17865
  const body = { chat_id, text };
17842
17866
  if (reply_to_message_id) body.reply_to_message_id = Number(reply_to_message_id);
17867
+ const drainCutoffMs = Date.now();
17843
17868
  const data = await telegramApiCall("sendMessage", body, 15e3);
17844
17869
  if (!data.ok) {
17845
17870
  return {
@@ -17849,7 +17874,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
17849
17874
  }
17850
17875
  recordReply(chat_id, "", tgThrottleNow, tgThrottleCfg);
17851
17876
  if (name === "telegram.reply") {
17852
- clearPendingMessage(chat_id, reply_to_message_id);
17877
+ clearPendingMessage(chat_id, drainCutoffMs);
17853
17878
  }
17854
17879
  return { content: [{ type: "text", text: "sent" }] };
17855
17880
  } catch (err) {
@@ -17867,7 +17892,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
17867
17892
  isError: true
17868
17893
  };
17869
17894
  }
17870
- noteThreadActivity(chat_id, message_id);
17895
+ const drainCutoffMs = Date.now();
17871
17896
  try {
17872
17897
  const data = await telegramApiCall(
17873
17898
  "setMessageReaction",
@@ -17884,6 +17909,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
17884
17909
  isError: true
17885
17910
  };
17886
17911
  }
17912
+ noteThreadActivity(chat_id, drainCutoffMs);
17887
17913
  return { content: [{ type: "text", text: emoji2 ? "reacted" : "cleared" }] };
17888
17914
  } catch (err) {
17889
17915
  return {
@@ -18176,7 +18202,7 @@ async function replayPendingTelegramMarkers() {
18176
18202
  if (!sessionAlive) return;
18177
18203
  let filenames;
18178
18204
  try {
18179
- filenames = readdirSync2(PENDING_INBOUND_DIR);
18205
+ filenames = readdirSync3(PENDING_INBOUND_DIR);
18180
18206
  } catch {
18181
18207
  return;
18182
18208
  }
@@ -18184,10 +18210,10 @@ async function replayPendingTelegramMarkers() {
18184
18210
  const entries = [];
18185
18211
  for (const name of filenames) {
18186
18212
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
18187
- const fullPath = join6(PENDING_INBOUND_DIR, name);
18213
+ const fullPath = join7(PENDING_INBOUND_DIR, name);
18188
18214
  let marker;
18189
18215
  try {
18190
- marker = JSON.parse(readFileSync6(fullPath, "utf-8"));
18216
+ marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
18191
18217
  } catch {
18192
18218
  continue;
18193
18219
  }
@@ -18595,7 +18621,7 @@ async function pollLoop() {
18595
18621
  let paneLogFreshAgeMs = null;
18596
18622
  if (AGENT_DIR) {
18597
18623
  try {
18598
- const paneMtimeMs = statSync(join6(AGENT_DIR, "pane.log")).mtimeMs;
18624
+ const paneMtimeMs = statSync2(join7(AGENT_DIR, "pane.log")).mtimeMs;
18599
18625
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
18600
18626
  } catch {
18601
18627
  }
@@ -42,12 +42,15 @@ function oldestPendingInboundMtimeMs(agentHomeDir) {
42
42
  }
43
43
  return oldest;
44
44
  }
45
- function isUndeliverableMarker(markerPath) {
45
+ function readMarkerFlags(markerPath) {
46
46
  try {
47
47
  const parsed = JSON.parse(readFileSync(markerPath, "utf8"));
48
- return parsed?.undeliverable === true;
48
+ return {
49
+ undeliverable: parsed?.undeliverable === true,
50
+ discretionary: parsed?.discretionary === true
51
+ };
49
52
  } catch (error) {
50
- return error.code === "ENOENT" ? null : false;
53
+ return error.code === "ENOENT" ? null : "malformed";
51
54
  }
52
55
  }
53
56
  function oldestLivePendingInboundMtimeMs(agentHomeDir, opts = {}) {
@@ -78,9 +81,9 @@ function oldestLivePendingInboundMtimeMs(agentHomeDir, opts = {}) {
78
81
  continue;
79
82
  }
80
83
  if (sessionStartMs !== null && mtimeMs < sessionStartMs) continue;
81
- const undeliverable = isUndeliverableMarker(full);
82
- if (undeliverable === null) continue;
83
- if (undeliverable) continue;
84
+ const flags = readMarkerFlags(full);
85
+ if (flags === null) continue;
86
+ if (flags !== "malformed" && (flags.undeliverable || flags.discretionary)) continue;
84
87
  if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;
85
88
  }
86
89
  }
@@ -122,9 +125,9 @@ function parkPendingInbound(codeName, _now = /* @__PURE__ */ new Date()) {
122
125
  }
123
126
  for (const file of files) {
124
127
  if (!file.isFile() || file.name.startsWith(".")) continue;
125
- const undeliverable = isUndeliverableMarker(join(dir, file.name));
126
- if (undeliverable === null) continue;
127
- if (undeliverable) {
128
+ const flags = readMarkerFlags(join(dir, file.name));
129
+ if (flags === null) continue;
130
+ if (flags !== "malformed" && flags.undeliverable) {
128
131
  if (moveMarkerToStale(dir, deadDir, file.name)) result.deadLettered++;
129
132
  } else {
130
133
  result.parked++;
@@ -192,4 +195,4 @@ export {
192
195
  oldestLivePendingInboundMtimeMs,
193
196
  parkPendingInbound
194
197
  };
195
- //# sourceMappingURL=responsiveness-probe-AL3O7SYZ.js.map
198
+ //# sourceMappingURL=responsiveness-probe-7NDEHXXZ.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 5 min via `AUGMENTED_RESPONSIVENESS_INTERVAL_MS`).\n */\n\nimport { mkdirSync, readdirSync, readFileSync, renameSync, statSync } 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\nconst DEFAULT_INTERVAL_MS = 5 * 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 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.27.166",
3
+ "version": "0.27.168",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1 +0,0 @@
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 5 min via `AUGMENTED_RESPONSIVENESS_INTERVAL_MS`).\n */\n\nimport { mkdirSync, readdirSync, readFileSync, renameSync, statSync } 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\nconst DEFAULT_INTERVAL_MS = 5 * 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: classify a marker file for the LIVE-inbound scan.\n * - `true` → flagged `\"undeliverable\": true` (dead-letter, exclude).\n * - `null` → vanished mid-scan (ENOENT) — drained between stat and read, the\n * happy path; exclude it rather than count an already-gone file.\n * - `false` → still present but malformed / unreadable for another reason —\n * treated as LIVE so a corrupt marker can never mask a real wedge.\n */\nfunction isUndeliverableMarker(markerPath: string): boolean | null {\n try {\n const parsed = JSON.parse(readFileSync(markerPath, 'utf8')) as { undeliverable?: unknown };\n return parsed?.undeliverable === true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'ENOENT' ? null : false;\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 *\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 undeliverable = isUndeliverableMarker(full);\n if (undeliverable === null) continue; // vanished between stat and read — drained, exclude\n if (undeliverable) continue; // already dead-lettered\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 undeliverable = isUndeliverableMarker(join(dir, file.name));\n if (undeliverable === null) continue; // drained mid-scan — already gone\n if (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 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":[]}