@integrity-labs/agt-cli 0.27.137 → 0.27.139

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.
@@ -14318,9 +14318,9 @@ import https from "https";
14318
14318
  import { createHash, randomUUID as randomUUID2 } from "crypto";
14319
14319
  import {
14320
14320
  createWriteStream,
14321
- existsSync as existsSync3,
14321
+ existsSync as existsSync4,
14322
14322
  mkdirSync as mkdirSync4,
14323
- readFileSync as readFileSync4,
14323
+ readFileSync as readFileSync5,
14324
14324
  readdirSync as readdirSync2,
14325
14325
  renameSync as renameSync4,
14326
14326
  statSync,
@@ -14329,7 +14329,7 @@ import {
14329
14329
  writeFileSync as writeFileSync4
14330
14330
  } from "fs";
14331
14331
  import { homedir as homedir2 } from "os";
14332
- import { join as join4 } from "path";
14332
+ import { join as join5 } from "path";
14333
14333
 
14334
14334
  // src/channel-attachments.ts
14335
14335
  import { homedir } from "os";
@@ -15310,6 +15310,10 @@ var HELP = {
15310
15310
  command: "help",
15311
15311
  description: "Show available commands"
15312
15312
  };
15313
+ var STATUS = {
15314
+ command: "status",
15315
+ description: "Show this agent\u2019s model, session origin + uptime"
15316
+ };
15313
15317
  var RESTART = {
15314
15318
  command: "restart",
15315
15319
  description: "Restart this agent"
@@ -15320,11 +15324,86 @@ var INVESTIGATE = {
15320
15324
  };
15321
15325
  function buildCommandRegistrations() {
15322
15326
  return [
15323
- { scope: { type: "default" }, commands: [HELP, RESTART] },
15324
- { scope: { type: "all_private_chats" }, commands: [HELP, RESTART, INVESTIGATE] }
15327
+ { scope: { type: "default" }, commands: [HELP, STATUS, RESTART] },
15328
+ { scope: { type: "all_private_chats" }, commands: [HELP, STATUS, RESTART, INVESTIGATE] }
15325
15329
  ];
15326
15330
  }
15327
15331
 
15332
+ // src/agent-config-state.ts
15333
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
15334
+ import { join as join2 } from "path";
15335
+ var SESSION_STATE_FILENAME = "session-state.json";
15336
+ function readAgentSessionState(stateDir) {
15337
+ if (!stateDir) return null;
15338
+ const path = join2(stateDir, SESSION_STATE_FILENAME);
15339
+ if (!existsSync2(path)) return null;
15340
+ try {
15341
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
15342
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
15343
+ return parsed;
15344
+ } catch {
15345
+ return null;
15346
+ }
15347
+ }
15348
+ function describeSessionOrigin(source) {
15349
+ switch (source) {
15350
+ case "startup":
15351
+ return "fresh start";
15352
+ case "resume":
15353
+ return "resumed";
15354
+ case "clear":
15355
+ return "cleared (/clear)";
15356
+ case "compact":
15357
+ return "compacted";
15358
+ default:
15359
+ return "unknown";
15360
+ }
15361
+ }
15362
+ function formatModelLabel(model) {
15363
+ const trimmed = model?.trim();
15364
+ return trimmed && trimmed.length > 0 ? trimmed : "unknown";
15365
+ }
15366
+ function shortSessionId(id) {
15367
+ const trimmed = id?.trim();
15368
+ if (!trimmed) return "unknown";
15369
+ return trimmed.length > 8 ? trimmed.slice(0, 8) : trimmed;
15370
+ }
15371
+ function formatRelativeAge(ts, now = Date.now()) {
15372
+ if (typeof ts !== "number" || !Number.isFinite(ts) || ts <= 0) return "unknown";
15373
+ const seconds = Math.floor((now - ts) / 1e3);
15374
+ if (seconds < 0) return "unknown";
15375
+ if (seconds < 5) return "just now";
15376
+ if (seconds < 60) return `${seconds}s ago`;
15377
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
15378
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
15379
+ return `${Math.floor(seconds / 86400)}d ago`;
15380
+ }
15381
+ function formatChannels(channels) {
15382
+ if (!Array.isArray(channels) || channels.length === 0) return "unknown";
15383
+ const cleaned = channels.map((c) => String(c).trim()).filter((c) => c.length > 0);
15384
+ return cleaned.length > 0 ? cleaned.join(", ") : "unknown";
15385
+ }
15386
+ function buildAgentConfigReport(opts) {
15387
+ const { codeName, connectivityLine, state, now = Date.now() } = opts;
15388
+ const lines = [`\u{1F916} *Config for \`${codeName}\`*`, connectivityLine];
15389
+ if (!state) {
15390
+ lines.push("\u2022 *Model:* unknown \u2014 session state not recorded yet");
15391
+ lines.push("\u2022 *Session:* unknown");
15392
+ return lines.join("\n");
15393
+ }
15394
+ lines.push(`\u2022 *Model:* \`${formatModelLabel(state.model)}\``);
15395
+ const origin = describeSessionOrigin(state.source);
15396
+ const age = formatRelativeAge(state.recorded_at, now);
15397
+ const sessionLine = age === "unknown" ? `\u2022 *Session:* ${origin}` : `\u2022 *Session:* ${origin} \xB7 started ${age}`;
15398
+ lines.push(sessionLine);
15399
+ lines.push(`\u2022 *Session ID:* \`${shortSessionId(state.session_id)}\``);
15400
+ if (state.environment && state.environment.trim().length > 0) {
15401
+ lines.push(`\u2022 *Environment:* ${state.environment.trim()}`);
15402
+ }
15403
+ lines.push(`\u2022 *Channels:* ${formatChannels(state.channels)}`);
15404
+ return lines.join("\n");
15405
+ }
15406
+
15328
15407
  // src/pane-tail.ts
15329
15408
  import { execFile } from "child_process";
15330
15409
  import { promisify } from "util";
@@ -15774,14 +15853,14 @@ var TELEGRAM_EGRESS_TOOLS = /* @__PURE__ */ new Set([
15774
15853
 
15775
15854
  // src/mcp-spawn-lock.ts
15776
15855
  import {
15777
- existsSync as existsSync2,
15856
+ existsSync as existsSync3,
15778
15857
  mkdirSync as mkdirSync3,
15779
- readFileSync as readFileSync2,
15858
+ readFileSync as readFileSync3,
15780
15859
  renameSync as renameSync3,
15781
15860
  unlinkSync as unlinkSync3,
15782
15861
  writeFileSync as writeFileSync3
15783
15862
  } from "fs";
15784
- import { join as join2 } from "path";
15863
+ import { join as join3 } from "path";
15785
15864
  function defaultIsPidAlive(pid) {
15786
15865
  if (!Number.isFinite(pid) || pid <= 0) return false;
15787
15866
  try {
@@ -15799,7 +15878,7 @@ function acquireMcpSpawnLock(args) {
15799
15878
  const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
15800
15879
  const selfPid = options.selfPid ?? process.pid;
15801
15880
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
15802
- const path = join2(agentDir, basename);
15881
+ const path = join3(agentDir, basename);
15803
15882
  const existing = readLockHolder(path);
15804
15883
  if (existing) {
15805
15884
  if (existing.pid === selfPid) {
@@ -15828,9 +15907,9 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
15828
15907
  }
15829
15908
  }
15830
15909
  function readLockHolder(path) {
15831
- if (!existsSync2(path)) return null;
15910
+ if (!existsSync3(path)) return null;
15832
15911
  try {
15833
- const raw = readFileSync2(path, "utf8");
15912
+ const raw = readFileSync3(path, "utf8");
15834
15913
  const parsed = JSON.parse(raw);
15835
15914
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
15836
15915
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -15842,8 +15921,8 @@ function readLockHolder(path) {
15842
15921
  }
15843
15922
 
15844
15923
  // src/ack-reaction.ts
15845
- import { readdirSync, readFileSync as readFileSync3 } from "fs";
15846
- import { join as join3 } from "path";
15924
+ import { readdirSync, readFileSync as readFileSync4 } from "fs";
15925
+ import { join as join4 } from "path";
15847
15926
  var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
15848
15927
  var ACK_STARTUP_GRACE_MS = 6e4;
15849
15928
  var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
@@ -15901,7 +15980,7 @@ var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
15901
15980
  function readGiveUpSignalAtMs(path, now = Date.now()) {
15902
15981
  if (!path) return null;
15903
15982
  try {
15904
- const raw = JSON.parse(readFileSync3(path, "utf8"));
15983
+ const raw = JSON.parse(readFileSync4(path, "utf8"));
15905
15984
  if (typeof raw.gave_up_at !== "string") return null;
15906
15985
  const t = Date.parse(raw.gave_up_at);
15907
15986
  if (!Number.isFinite(t) || t > now) return null;
@@ -15933,7 +16012,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
15933
16012
  if (!name.endsWith(".json")) continue;
15934
16013
  let receivedAt;
15935
16014
  try {
15936
- const raw = JSON.parse(readFileSync3(join3(dir, name), "utf-8"));
16015
+ const raw = JSON.parse(readFileSync4(join4(dir, name), "utf-8"));
15937
16016
  receivedAt = raw.received_at;
15938
16017
  } catch {
15939
16018
  continue;
@@ -15981,7 +16060,7 @@ function redactId(id) {
15981
16060
  }
15982
16061
  var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
15983
16062
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
15984
- var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
16063
+ var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join5(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15985
16064
  var AGT_HOST = process.env.AGT_HOST ?? null;
15986
16065
  var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
15987
16066
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
@@ -16075,9 +16154,9 @@ if (!BOT_TOKEN) {
16075
16154
  var stderrLogStream = null;
16076
16155
  if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
16077
16156
  try {
16078
- const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
16157
+ const logDir = join5(homedir2(), ".augmented", AGENT_CODE_NAME);
16079
16158
  mkdirSync4(logDir, { recursive: true });
16080
- stderrLogStream = createWriteStream(join4(logDir, "telegram-channel-stderr.log"), {
16159
+ stderrLogStream = createWriteStream(join5(logDir, "telegram-channel-stderr.log"), {
16081
16160
  flags: "a",
16082
16161
  mode: 384
16083
16162
  });
@@ -16267,7 +16346,7 @@ function scheduleBusyAck(chatId, messageId) {
16267
16346
  let paneLogFreshAgeMs = null;
16268
16347
  if (AGENT_DIR) {
16269
16348
  try {
16270
- const paneMtimeMs = statSync(join4(AGENT_DIR, "pane.log")).mtimeMs;
16349
+ const paneMtimeMs = statSync(join5(AGENT_DIR, "pane.log")).mtimeMs;
16271
16350
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
16272
16351
  } catch {
16273
16352
  }
@@ -16289,7 +16368,7 @@ function scheduleBusyAck(chatId, messageId) {
16289
16368
  function __resetBusyAckNoticeThrottle() {
16290
16369
  lastBusyAckNoticeAt.clear();
16291
16370
  }
16292
- var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
16371
+ var RESTART_FLAGS_DIR = join5(homedir2(), ".augmented", "restart-flags");
16293
16372
  function writeTelegramRestartConfirm(reply, requesterName) {
16294
16373
  if (!RESTART_CONFIRM_FILE) return;
16295
16374
  const marker = {
@@ -16356,6 +16435,7 @@ function buildTelegramHelpMessage(codeName) {
16356
16435
  "",
16357
16436
  `_Type these in any chat where the bot is present (intercepted by the agent):_`,
16358
16437
  `\u2022 /help \u2014 show this help`,
16438
+ `\u2022 /status \u2014 this agent's model, session origin, uptime + connectivity`,
16359
16439
  `\u2022 /restart \u2014 restart this agent`,
16360
16440
  `\u2022 /investigate-${codeName} \u2014 live tail of this agent's terminal pane (DM only; team owners/admins and the agent's reports-to person)`
16361
16441
  ].join("\n");
@@ -16385,12 +16465,45 @@ async function handleHelpCommand(opts) {
16385
16465
  );
16386
16466
  }
16387
16467
  }
16468
+ function buildTelegramStatusReply() {
16469
+ const state = readAgentSessionState(TELEGRAM_AGENT_DIR);
16470
+ return buildAgentConfigReport({
16471
+ codeName: AGENT_CODE_NAME,
16472
+ connectivityLine: "\u{1F7E2} *online* \u2014 Telegram channel connected.",
16473
+ state
16474
+ });
16475
+ }
16476
+ async function handleStatusCommand(opts) {
16477
+ try {
16478
+ const resp = await telegramApiCall(
16479
+ "sendMessage",
16480
+ {
16481
+ chat_id: opts.chatId,
16482
+ text: buildTelegramStatusReply(),
16483
+ parse_mode: "Markdown",
16484
+ reply_to_message_id: Number(opts.messageId)
16485
+ },
16486
+ 1e4
16487
+ );
16488
+ if (!resp.ok) {
16489
+ process.stderr.write(
16490
+ `telegram-channel(${AGENT_CODE_NAME}): /status reply rejected by Telegram (chat ${redactId(opts.chatId)}): ${resp.description ?? "unknown"}
16491
+ `
16492
+ );
16493
+ }
16494
+ } catch (err) {
16495
+ process.stderr.write(
16496
+ `telegram-channel(${AGENT_CODE_NAME}): /status reply send failed: ${redactAugmentedPaths(err.message)}
16497
+ `
16498
+ );
16499
+ }
16500
+ }
16388
16501
  async function handleRestartCommand(opts) {
16389
16502
  try {
16390
- if (!existsSync3(RESTART_FLAGS_DIR)) {
16503
+ if (!existsSync4(RESTART_FLAGS_DIR)) {
16391
16504
  mkdirSync4(RESTART_FLAGS_DIR, { recursive: true });
16392
16505
  }
16393
- const flagPath = join4(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
16506
+ const flagPath = join5(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
16394
16507
  writeTelegramRestartConfirm(
16395
16508
  { chat_id: opts.chatId, message_id: opts.messageId },
16396
16509
  opts.requesterName
@@ -16753,6 +16866,10 @@ var HELP_SYNTAX_RE = /^\/help(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
16753
16866
  function isHelpSyntax(text) {
16754
16867
  return HELP_SYNTAX_RE.test(text);
16755
16868
  }
16869
+ var STATUS_SYNTAX_RE = /^\/status(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
16870
+ function isStatusSyntax(text) {
16871
+ return STATUS_SYNTAX_RE.test(text);
16872
+ }
16756
16873
  function isRestartSyntax(text) {
16757
16874
  return RESTART_SYNTAX_RE.test(text);
16758
16875
  }
@@ -16765,10 +16882,10 @@ async function classifyRestartCommand(text) {
16765
16882
  if (!ours) return "verification_failed";
16766
16883
  return target === ours ? "act" : "ignore";
16767
16884
  }
16768
- var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
16769
- var PENDING_INBOUND_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-pending-inbound") : null;
16770
- var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-recovery-outbox") : null;
16771
- var RESTART_CONFIRM_FILE = AGENT_DIR ? join4(AGENT_DIR, "telegram-restart-confirm.json") : null;
16885
+ var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join5(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
16886
+ var PENDING_INBOUND_DIR = AGENT_DIR ? join5(AGENT_DIR, "telegram-pending-inbound") : null;
16887
+ var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join5(AGENT_DIR, "telegram-recovery-outbox") : null;
16888
+ var RESTART_CONFIRM_FILE = AGENT_DIR ? join5(AGENT_DIR, "telegram-restart-confirm.json") : null;
16772
16889
  var TELEGRAM_PROCESS_BOOT_MS = Date.now();
16773
16890
  function safeMarkerName(chatId, messageId) {
16774
16891
  const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
@@ -16776,7 +16893,7 @@ function safeMarkerName(chatId, messageId) {
16776
16893
  }
16777
16894
  function pendingInboundPath(chatId, messageId) {
16778
16895
  if (!PENDING_INBOUND_DIR) return null;
16779
- return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
16896
+ return join5(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
16780
16897
  }
16781
16898
  function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
16782
16899
  const path = pendingInboundPath(chatId, messageId);
@@ -16805,7 +16922,7 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
16805
16922
  function clearTelegramMarkerFileWithHeal(fullPath) {
16806
16923
  let marker = null;
16807
16924
  try {
16808
- marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
16925
+ marker = JSON.parse(readFileSync5(fullPath, "utf-8"));
16809
16926
  } catch {
16810
16927
  }
16811
16928
  if (marker && decideRecoveryHeal({
@@ -16815,7 +16932,7 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
16815
16932
  notifyBackOnline(marker.chat_id);
16816
16933
  }
16817
16934
  try {
16818
- if (existsSync3(fullPath)) unlinkSync4(fullPath);
16935
+ if (existsSync4(fullPath)) unlinkSync4(fullPath);
16819
16936
  } catch {
16820
16937
  }
16821
16938
  }
@@ -16826,9 +16943,9 @@ function clearPendingInboundMarker(chatId, messageId) {
16826
16943
  }
16827
16944
  function readPendingInboundMarker(chatId, messageId) {
16828
16945
  const path = pendingInboundPath(chatId, messageId);
16829
- if (!path || !existsSync3(path)) return null;
16946
+ if (!path || !existsSync4(path)) return null;
16830
16947
  try {
16831
- return JSON.parse(readFileSync4(path, "utf-8"));
16948
+ return JSON.parse(readFileSync5(path, "utf-8"));
16832
16949
  } catch {
16833
16950
  return null;
16834
16951
  }
@@ -16848,10 +16965,10 @@ function nextRetryName(filename) {
16848
16965
  async function processRecoveryOutboxFile(filename) {
16849
16966
  if (!RECOVERY_OUTBOX_DIR) return;
16850
16967
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
16851
- const fullPath = join4(RECOVERY_OUTBOX_DIR, filename);
16968
+ const fullPath = join5(RECOVERY_OUTBOX_DIR, filename);
16852
16969
  let payload;
16853
16970
  try {
16854
- const raw = readFileSync4(fullPath, "utf-8");
16971
+ const raw = readFileSync5(fullPath, "utf-8");
16855
16972
  payload = JSON.parse(raw);
16856
16973
  } catch (err) {
16857
16974
  process.stderr.write(
@@ -16914,7 +17031,7 @@ async function processRecoveryOutboxFile(filename) {
16914
17031
  const next = nextRetryName(filename);
16915
17032
  if (next) {
16916
17033
  try {
16917
- renameSync4(fullPath, join4(RECOVERY_OUTBOX_DIR, next.next));
17034
+ renameSync4(fullPath, join5(RECOVERY_OUTBOX_DIR, next.next));
16918
17035
  if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
16919
17036
  process.stderr.write(
16920
17037
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -16954,7 +17071,7 @@ function scanRecoveryRetries() {
16954
17071
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
16955
17072
  let mtimeMs;
16956
17073
  try {
16957
- mtimeMs = statSync(join4(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
17074
+ mtimeMs = statSync(join5(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16958
17075
  } catch {
16959
17076
  continue;
16960
17077
  }
@@ -16984,7 +17101,7 @@ function startRecoveryOutboxWatcher() {
16984
17101
  const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
16985
17102
  if (event !== "rename" || !filename) return;
16986
17103
  if (!isFirstAttemptOutboxFile(filename)) return;
16987
- if (existsSync3(join4(RECOVERY_OUTBOX_DIR, filename))) {
17104
+ if (existsSync4(join5(RECOVERY_OUTBOX_DIR, filename))) {
16988
17105
  void processRecoveryOutboxFile(filename);
16989
17106
  }
16990
17107
  });
@@ -17005,7 +17122,7 @@ function trackPendingMessage(chatId, messageId, chatType, undeliverable = false,
17005
17122
  }
17006
17123
  function sweepTelegramStaleMarkers(thresholdMs) {
17007
17124
  if (!PENDING_INBOUND_DIR) return;
17008
- if (!existsSync3(PENDING_INBOUND_DIR)) return;
17125
+ if (!existsSync4(PENDING_INBOUND_DIR)) return;
17009
17126
  let filenames;
17010
17127
  try {
17011
17128
  filenames = readdirSync2(PENDING_INBOUND_DIR);
@@ -17021,10 +17138,10 @@ function sweepTelegramStaleMarkers(thresholdMs) {
17021
17138
  for (const filename of filenames) {
17022
17139
  if (!filename.endsWith(".json")) continue;
17023
17140
  if (filename.endsWith(".tmp")) continue;
17024
- const fullPath = join4(PENDING_INBOUND_DIR, filename);
17141
+ const fullPath = join5(PENDING_INBOUND_DIR, filename);
17025
17142
  let marker;
17026
17143
  try {
17027
- marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
17144
+ marker = JSON.parse(readFileSync5(fullPath, "utf-8"));
17028
17145
  } catch (err) {
17029
17146
  process.stderr.write(
17030
17147
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
@@ -17063,14 +17180,14 @@ var orphanSweepTimer = setInterval(() => {
17063
17180
  orphanSweepTimer.unref?.();
17064
17181
  var lastGiveUpHandledAtMs = null;
17065
17182
  function listPendingInboundChatIds() {
17066
- if (!PENDING_INBOUND_DIR || !existsSync3(PENDING_INBOUND_DIR)) return [];
17183
+ if (!PENDING_INBOUND_DIR || !existsSync4(PENDING_INBOUND_DIR)) return [];
17067
17184
  const chats = /* @__PURE__ */ new Set();
17068
17185
  try {
17069
17186
  for (const name of readdirSync2(PENDING_INBOUND_DIR)) {
17070
17187
  if (!name.endsWith(".json")) continue;
17071
17188
  try {
17072
17189
  const marker = JSON.parse(
17073
- readFileSync4(join4(PENDING_INBOUND_DIR, name), "utf8")
17190
+ readFileSync5(join5(PENDING_INBOUND_DIR, name), "utf8")
17074
17191
  );
17075
17192
  if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
17076
17193
  } catch {
@@ -17111,7 +17228,7 @@ async function notifyWatchdogGiveUp(chatId) {
17111
17228
  }
17112
17229
  function checkWatchdogGiveUpNotice() {
17113
17230
  if (!AGENT_DIR) return;
17114
- const signalAtMs = readGiveUpSignalAtMs(join4(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
17231
+ const signalAtMs = readGiveUpSignalAtMs(join5(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
17115
17232
  const act = decideGiveUpNotice({
17116
17233
  signalAtMs,
17117
17234
  lastHandledAtMs: lastGiveUpHandledAtMs,
@@ -17189,7 +17306,7 @@ function clearPendingMessage(chatId, messageId) {
17189
17306
  clearPendingInboundMarker(chatId, messageId);
17190
17307
  return;
17191
17308
  }
17192
- if (!PENDING_INBOUND_DIR || !existsSync3(PENDING_INBOUND_DIR)) return;
17309
+ if (!PENDING_INBOUND_DIR || !existsSync4(PENDING_INBOUND_DIR)) return;
17193
17310
  const safeChatId = chatId.replace(/[^A-Za-z0-9_-]/g, "_");
17194
17311
  const prefix = `${safeChatId}__`;
17195
17312
  let filenames;
@@ -17201,7 +17318,7 @@ function clearPendingMessage(chatId, messageId) {
17201
17318
  for (const filename of filenames) {
17202
17319
  if (!filename.startsWith(prefix)) continue;
17203
17320
  if (!filename.endsWith(".json")) continue;
17204
- clearTelegramMarkerFileWithHeal(join4(PENDING_INBOUND_DIR, filename));
17321
+ clearTelegramMarkerFileWithHeal(join5(PENDING_INBOUND_DIR, filename));
17205
17322
  }
17206
17323
  }
17207
17324
  function noteThreadActivity(chatId, messageId) {
@@ -17800,7 +17917,7 @@ await mcp.connect(new StdioServerTransport());
17800
17917
  var REPLAY_SCAN_INTERVAL_MS = 6e4;
17801
17918
  async function replayPendingTelegramMarkers() {
17802
17919
  if (!channelReplayEnabled()) return;
17803
- if (!PENDING_INBOUND_DIR || !existsSync3(PENDING_INBOUND_DIR)) return;
17920
+ if (!PENDING_INBOUND_DIR || !existsSync4(PENDING_INBOUND_DIR)) return;
17804
17921
  const probe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
17805
17922
  const sessionAlive = probe.tmux === "alive" && probe.claude === "alive";
17806
17923
  if (!sessionAlive) return;
@@ -17814,10 +17931,10 @@ async function replayPendingTelegramMarkers() {
17814
17931
  const entries = [];
17815
17932
  for (const name of filenames) {
17816
17933
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
17817
- const fullPath = join4(PENDING_INBOUND_DIR, name);
17934
+ const fullPath = join5(PENDING_INBOUND_DIR, name);
17818
17935
  let marker;
17819
17936
  try {
17820
- marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
17937
+ marker = JSON.parse(readFileSync5(fullPath, "utf-8"));
17821
17938
  } catch {
17822
17939
  continue;
17823
17940
  }
@@ -17849,7 +17966,7 @@ async function replayPendingTelegramMarkers() {
17849
17966
  continue;
17850
17967
  }
17851
17968
  try {
17852
- if (existsSync3(path)) {
17969
+ if (existsSync4(path)) {
17853
17970
  const updated = {
17854
17971
  ...marker,
17855
17972
  replay_count: (marker.replay_count ?? 0) + 1
@@ -18008,6 +18125,18 @@ async function pollLoop() {
18008
18125
  }
18009
18126
  continue;
18010
18127
  }
18128
+ if (isStatusSyntax(trimmedContent)) {
18129
+ const disposition = await classifyRestartCommand(
18130
+ trimmedContent.replace(/^\/status/, "/restart")
18131
+ );
18132
+ if (disposition === "act") {
18133
+ await handleStatusCommand({
18134
+ chatId,
18135
+ messageId: String(msg.message_id)
18136
+ });
18137
+ }
18138
+ continue;
18139
+ }
18011
18140
  if (isRestartSyntax(trimmedContent)) {
18012
18141
  const disposition = await classifyRestartCommand(trimmedContent);
18013
18142
  if (disposition === "act") {
@@ -18172,7 +18301,7 @@ async function pollLoop() {
18172
18301
  let paneLogFreshAgeMs = null;
18173
18302
  if (AGENT_DIR) {
18174
18303
  try {
18175
- const paneMtimeMs = statSync(join4(AGENT_DIR, "pane.log")).mtimeMs;
18304
+ const paneMtimeMs = statSync(join5(AGENT_DIR, "pane.log")).mtimeMs;
18176
18305
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
18177
18306
  } catch {
18178
18307
  }
@@ -25,8 +25,9 @@ import {
25
25
  takeAcpxExecFailureCount,
26
26
  takeZombieDetection,
27
27
  writePersistentClaudeWrapper
28
- } from "./chunk-YJOFVGD2.js";
29
- import "./chunk-TDMOEMDM.js";
28
+ } from "./chunk-IDDSO7Q5.js";
29
+ import "./chunk-WCXA7EEP.js";
30
+ import "./chunk-354FAVQR.js";
30
31
  import "./chunk-XWVM4KPK.js";
31
32
  export {
32
33
  SEND_KEYS_ENTER_DELAY_MS,
@@ -56,4 +57,4 @@ export {
56
57
  takeZombieDetection,
57
58
  writePersistentClaudeWrapper
58
59
  };
59
- //# sourceMappingURL=persistent-session-NSN62HZN.js.map
60
+ //# sourceMappingURL=persistent-session-SOCMTNFC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-YJOFVGD2.js";
4
- import "./chunk-TDMOEMDM.js";
3
+ } from "./chunk-IDDSO7Q5.js";
4
+ import "./chunk-WCXA7EEP.js";
5
+ import "./chunk-354FAVQR.js";
5
6
  import "./chunk-XWVM4KPK.js";
6
7
 
7
8
  // src/lib/responsiveness-probe.ts
@@ -154,4 +155,4 @@ export {
154
155
  livePendingInboundOldestAgeSeconds,
155
156
  oldestLivePendingInboundMtimeMs
156
157
  };
157
- //# sourceMappingURL=responsiveness-probe-RF5ZCTE7.js.map
158
+ //# sourceMappingURL=responsiveness-probe-USWGCI4C.js.map
@@ -1 +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: 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 * Called on a force-fresh wedge respawn: the markers belonged to the wedged\n * session that is being torn down; the fresh session cannot meaningfully\n * process a stale, out-of-context message, and leaving them on disk both keeps\n * the ENG-6017 alarm lit and (pre-ENG-6160) re-fed the wedge loop. The stale\n * dir does not end in `-pending-inbound`, so neither the probe nor this scan\n * re-counts moved markers.\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 try {\n mkdirSync(deadDir, { recursive: true });\n renameSync(join(dir, file.name), join(deadDir, file.name));\n moved++;\n } catch {\n // best-effort — a marker that vanished or can't move is left as-is\n }\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":[]}
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 * Called on a force-fresh wedge respawn: the markers belonged to the wedged\n * session that is being torn down; the fresh session cannot meaningfully\n * process a stale, out-of-context message, and leaving them on disk both keeps\n * the ENG-6017 alarm lit and (pre-ENG-6160) re-fed the wedge loop. The stale\n * dir does not end in `-pending-inbound`, so neither the probe nor this scan\n * re-counts moved markers.\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 try {\n mkdirSync(deadDir, { recursive: true });\n renameSync(join(dir, file.name), join(deadDir, file.name));\n moved++;\n } catch {\n // best-effort — a marker that vanished or can't move is left as-is\n }\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.137",
3
+ "version": "0.27.139",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {