@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.
- package/dist/bin/agt.js +4 -4
- package/dist/chunk-354FAVQR.js +173 -0
- package/dist/chunk-354FAVQR.js.map +1 -0
- package/dist/{chunk-YJOFVGD2.js → chunk-IDDSO7Q5.js} +52 -199
- package/dist/chunk-IDDSO7Q5.js.map +1 -0
- package/dist/{chunk-UIRCFCED.js → chunk-PSH4UXVY.js} +2 -2
- package/dist/{chunk-TDMOEMDM.js → chunk-WCXA7EEP.js} +26 -9
- package/dist/chunk-WCXA7EEP.js.map +1 -0
- package/dist/{claude-pair-runtime-ZOHU673W.js → claude-pair-runtime-ZLYTTNUY.js} +2 -2
- package/dist/daily-session-PNQX5URX.js +27 -0
- package/dist/lib/manager-worker.js +50 -20
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/slack-channel.js +146 -69
- package/dist/mcp/telegram-channel.js +178 -49
- package/dist/{persistent-session-NSN62HZN.js → persistent-session-SOCMTNFC.js} +4 -3
- package/dist/persistent-session-SOCMTNFC.js.map +1 -0
- package/dist/{responsiveness-probe-RF5ZCTE7.js → responsiveness-probe-USWGCI4C.js} +4 -3
- package/dist/{responsiveness-probe-RF5ZCTE7.js.map → responsiveness-probe-USWGCI4C.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-TDMOEMDM.js.map +0 -1
- package/dist/chunk-YJOFVGD2.js.map +0 -1
- /package/dist/{chunk-UIRCFCED.js.map → chunk-PSH4UXVY.js.map} +0 -0
- /package/dist/{claude-pair-runtime-ZOHU673W.js.map → claude-pair-runtime-ZLYTTNUY.js.map} +0 -0
- /package/dist/{persistent-session-NSN62HZN.js.map → daily-session-PNQX5URX.js.map} +0 -0
|
@@ -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
|
|
14321
|
+
existsSync as existsSync4,
|
|
14322
14322
|
mkdirSync as mkdirSync4,
|
|
14323
|
-
readFileSync as
|
|
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
|
|
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
|
|
15856
|
+
existsSync as existsSync3,
|
|
15778
15857
|
mkdirSync as mkdirSync3,
|
|
15779
|
-
readFileSync as
|
|
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
|
|
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 =
|
|
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 (!
|
|
15910
|
+
if (!existsSync3(path)) return null;
|
|
15832
15911
|
try {
|
|
15833
|
-
const raw =
|
|
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
|
|
15846
|
-
import { join as
|
|
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(
|
|
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(
|
|
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" ?
|
|
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 =
|
|
16157
|
+
const logDir = join5(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
16079
16158
|
mkdirSync4(logDir, { recursive: true });
|
|
16080
|
-
stderrLogStream = createWriteStream(
|
|
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(
|
|
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 =
|
|
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 (!
|
|
16503
|
+
if (!existsSync4(RESTART_FLAGS_DIR)) {
|
|
16391
16504
|
mkdirSync4(RESTART_FLAGS_DIR, { recursive: true });
|
|
16392
16505
|
}
|
|
16393
|
-
const flagPath =
|
|
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" ?
|
|
16769
|
-
var PENDING_INBOUND_DIR = AGENT_DIR ?
|
|
16770
|
-
var RECOVERY_OUTBOX_DIR = AGENT_DIR ?
|
|
16771
|
-
var RESTART_CONFIRM_FILE = AGENT_DIR ?
|
|
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
|
|
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(
|
|
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 (
|
|
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 || !
|
|
16946
|
+
if (!path || !existsSync4(path)) return null;
|
|
16830
16947
|
try {
|
|
16831
|
-
return JSON.parse(
|
|
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 =
|
|
16968
|
+
const fullPath = join5(RECOVERY_OUTBOX_DIR, filename);
|
|
16852
16969
|
let payload;
|
|
16853
16970
|
try {
|
|
16854
|
-
const raw =
|
|
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,
|
|
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(
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
17141
|
+
const fullPath = join5(PENDING_INBOUND_DIR, filename);
|
|
17025
17142
|
let marker;
|
|
17026
17143
|
try {
|
|
17027
|
-
marker = JSON.parse(
|
|
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 || !
|
|
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
|
-
|
|
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(
|
|
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 || !
|
|
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(
|
|
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 || !
|
|
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 =
|
|
17934
|
+
const fullPath = join5(PENDING_INBOUND_DIR, name);
|
|
17818
17935
|
let marker;
|
|
17819
17936
|
try {
|
|
17820
|
-
marker = JSON.parse(
|
|
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 (
|
|
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(
|
|
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-
|
|
29
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
4
|
-
import "./chunk-
|
|
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-
|
|
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":[]}
|