@integrity-labs/agt-cli 0.27.166 → 0.27.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +3 -3
- package/dist/{chunk-DMZQM4DY.js → chunk-ZQGQVROS.js} +1 -1
- package/dist/lib/manager-worker.js +4 -4
- package/dist/mcp/slack-channel.js +32 -13
- package/dist/mcp/telegram-channel.js +102 -76
- package/dist/{responsiveness-probe-AL3O7SYZ.js → responsiveness-probe-7NDEHXXZ.js} +13 -10
- package/dist/responsiveness-probe-7NDEHXXZ.js.map +1 -0
- package/package.json +1 -1
- package/dist/responsiveness-probe-AL3O7SYZ.js.map +0 -1
- /package/dist/{chunk-DMZQM4DY.js.map → chunk-ZQGQVROS.js.map} +0 -0
package/dist/bin/agt.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
success,
|
|
34
34
|
table,
|
|
35
35
|
warn
|
|
36
|
-
} from "../chunk-
|
|
36
|
+
} from "../chunk-ZQGQVROS.js";
|
|
37
37
|
import {
|
|
38
38
|
CHANNEL_REGISTRY,
|
|
39
39
|
DEPLOYMENT_TEMPLATES,
|
|
@@ -5019,7 +5019,7 @@ import { execFileSync, execSync } from "child_process";
|
|
|
5019
5019
|
import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
|
|
5020
5020
|
import chalk18 from "chalk";
|
|
5021
5021
|
import ora16 from "ora";
|
|
5022
|
-
var cliVersion = true ? "0.27.
|
|
5022
|
+
var cliVersion = true ? "0.27.168" : "dev";
|
|
5023
5023
|
async function fetchLatestVersion() {
|
|
5024
5024
|
const host2 = getHost();
|
|
5025
5025
|
if (!host2) return null;
|
|
@@ -5942,7 +5942,7 @@ function handleError(err) {
|
|
|
5942
5942
|
}
|
|
5943
5943
|
|
|
5944
5944
|
// src/bin/agt.ts
|
|
5945
|
-
var cliVersion2 = true ? "0.27.
|
|
5945
|
+
var cliVersion2 = true ? "0.27.168" : "dev";
|
|
5946
5946
|
var program = new Command();
|
|
5947
5947
|
program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
|
|
5948
5948
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
provisionStopHook,
|
|
23
23
|
requireHost,
|
|
24
24
|
safeWriteJsonAtomic
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-ZQGQVROS.js";
|
|
26
26
|
import {
|
|
27
27
|
getProjectDir as getProjectDir2,
|
|
28
28
|
getReadyTasks,
|
|
@@ -5041,7 +5041,7 @@ var cachedMaintenanceWindow = null;
|
|
|
5041
5041
|
var lastVersionCheckAt = 0;
|
|
5042
5042
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
5043
5043
|
var lastResponsivenessProbeAt = 0;
|
|
5044
|
-
var agtCliVersion = true ? "0.27.
|
|
5044
|
+
var agtCliVersion = true ? "0.27.168" : "dev";
|
|
5045
5045
|
function resolveBrewPath(execFileSync4) {
|
|
5046
5046
|
try {
|
|
5047
5047
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -6338,7 +6338,7 @@ async function pollCycle() {
|
|
|
6338
6338
|
const {
|
|
6339
6339
|
collectResponsivenessProbes,
|
|
6340
6340
|
getResponsivenessIntervalMs
|
|
6341
|
-
} = await import("../responsiveness-probe-
|
|
6341
|
+
} = await import("../responsiveness-probe-7NDEHXXZ.js");
|
|
6342
6342
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
6343
6343
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
6344
6344
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -6377,7 +6377,7 @@ async function pollCycle() {
|
|
|
6377
6377
|
collectResponsivenessProbes,
|
|
6378
6378
|
livePendingInboundOldestAgeSeconds,
|
|
6379
6379
|
parkPendingInbound
|
|
6380
|
-
} = await import("../responsiveness-probe-
|
|
6380
|
+
} = await import("../responsiveness-probe-7NDEHXXZ.js");
|
|
6381
6381
|
const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
|
|
6382
6382
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
6383
6383
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -14208,6 +14208,9 @@ function decideSlackEngagement(input) {
|
|
|
14208
14208
|
const isExplicitMention = input.type === "app_mention" || !!input.botUserId && input.text.includes(`<@${input.botUserId}>`);
|
|
14209
14209
|
return !(input.isAutoFollowed && !isExplicitMention);
|
|
14210
14210
|
}
|
|
14211
|
+
function decideMarkerArm(input) {
|
|
14212
|
+
return input.shouldEngage || input.isAutoFollowed && input.botPostedInThread;
|
|
14213
|
+
}
|
|
14211
14214
|
function decideSlackAckReaction(input) {
|
|
14212
14215
|
return Boolean(input.channel && input.ts);
|
|
14213
14216
|
}
|
|
@@ -14585,6 +14588,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
|
14585
14588
|
let receivedAt;
|
|
14586
14589
|
try {
|
|
14587
14590
|
const raw = JSON.parse(readFileSync2(join2(dir, name), "utf-8"));
|
|
14591
|
+
if (raw.discretionary === true) continue;
|
|
14588
14592
|
receivedAt = raw.received_at;
|
|
14589
14593
|
} catch {
|
|
14590
14594
|
continue;
|
|
@@ -15439,7 +15443,11 @@ function loadThreadStore(filePath, opts = {}) {
|
|
|
15439
15443
|
pruned++;
|
|
15440
15444
|
continue;
|
|
15441
15445
|
}
|
|
15442
|
-
threads.set(key2, {
|
|
15446
|
+
threads.set(key2, {
|
|
15447
|
+
involvement: entry.involvement,
|
|
15448
|
+
last_seen_at: entry.last_seen_at,
|
|
15449
|
+
...entry.bot_posted === true ? { bot_posted: true } : {}
|
|
15450
|
+
});
|
|
15443
15451
|
}
|
|
15444
15452
|
return { threads, pruned };
|
|
15445
15453
|
}
|
|
@@ -16600,7 +16608,7 @@ function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
|
16600
16608
|
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
16601
16609
|
return join7(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
16602
16610
|
}
|
|
16603
|
-
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false) {
|
|
16611
|
+
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
|
|
16604
16612
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
16605
16613
|
if (!path || !SLACK_PENDING_INBOUND_DIR) return;
|
|
16606
16614
|
const marker = {
|
|
@@ -16608,8 +16616,9 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
16608
16616
|
thread_ts: threadTs,
|
|
16609
16617
|
message_ts: messageTs,
|
|
16610
16618
|
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16611
|
-
// Only persist the
|
|
16612
|
-
...undeliverable ? { undeliverable: true } : {}
|
|
16619
|
+
// Only persist the flags when set — keeps healthy-path markers byte-identical.
|
|
16620
|
+
...undeliverable ? { undeliverable: true } : {},
|
|
16621
|
+
...discretionary ? { discretionary: true } : {}
|
|
16613
16622
|
};
|
|
16614
16623
|
try {
|
|
16615
16624
|
mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
@@ -16957,8 +16966,9 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
16957
16966
|
}
|
|
16958
16967
|
startSlackRecoveryOutboxWatcher();
|
|
16959
16968
|
var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
16960
|
-
|
|
16961
|
-
|
|
16969
|
+
var DISCRETIONARY_MARKER_MS = 10 * 60 * 1e3;
|
|
16970
|
+
function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
|
|
16971
|
+
writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable, discretionary);
|
|
16962
16972
|
}
|
|
16963
16973
|
function sweepSlackStaleMarkers(thresholdMs) {
|
|
16964
16974
|
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
@@ -16995,7 +17005,8 @@ function sweepSlackStaleMarkers(thresholdMs) {
|
|
|
16995
17005
|
continue;
|
|
16996
17006
|
}
|
|
16997
17007
|
const { channel, thread_ts, message_ts, received_at } = marker;
|
|
16998
|
-
|
|
17008
|
+
const effectiveThresholdMs = marker.discretionary ? Math.min(thresholdMs, DISCRETIONARY_MARKER_MS) : thresholdMs;
|
|
17009
|
+
if (!channel || !thread_ts || !message_ts || isPendingMarkerStale(received_at, now, effectiveThresholdMs)) {
|
|
16999
17010
|
try {
|
|
17000
17011
|
unlinkSync4(fullPath);
|
|
17001
17012
|
} catch {
|
|
@@ -17031,6 +17042,7 @@ function listPendingSlackConversations() {
|
|
|
17031
17042
|
);
|
|
17032
17043
|
if (typeof marker.channel !== "string" || !marker.channel) continue;
|
|
17033
17044
|
if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
|
|
17045
|
+
if (marker.discretionary === true) continue;
|
|
17034
17046
|
const isThreadReply = typeof marker.message_ts === "string" && marker.message_ts !== marker.thread_ts;
|
|
17035
17047
|
const key2 = isThreadReply ? `${marker.channel}:${marker.thread_ts}` : marker.channel;
|
|
17036
17048
|
if (!byKey.has(key2)) {
|
|
@@ -17910,7 +17922,11 @@ function rememberThread(channel, threadTs, involvement, opts = {}) {
|
|
|
17910
17922
|
const existing = trackedThreads.get(key2);
|
|
17911
17923
|
const next = {
|
|
17912
17924
|
involvement: opts.override || !existing ? involvement : existing.involvement,
|
|
17913
|
-
last_seen_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
17925
|
+
last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17926
|
+
// ENG-6319: sticky — once the bot has posted in a thread it stays a
|
|
17927
|
+
// conversational participant for marker-arming (persisted only when true
|
|
17928
|
+
// so pre-flag entries and never-posted threads stay byte-identical).
|
|
17929
|
+
...opts.botPosted || existing?.bot_posted ? { bot_posted: true } : {}
|
|
17914
17930
|
};
|
|
17915
17931
|
trackedThreads.set(key2, next);
|
|
17916
17932
|
if (isShuttingDown) {
|
|
@@ -18378,7 +18394,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
18378
18394
|
recordActivity("reply");
|
|
18379
18395
|
if (THREAD_AUTO_FOLLOW !== "off") {
|
|
18380
18396
|
const trackTs = effectiveThreadTs ?? data.ts ?? void 0;
|
|
18381
|
-
rememberThread(channel, trackTs, thread_ts ? "mentioned" : "started");
|
|
18397
|
+
rememberThread(channel, trackTs, thread_ts ? "mentioned" : "started", { botPosted: true });
|
|
18382
18398
|
}
|
|
18383
18399
|
if (interactiveHostAvailable() && data.ts) {
|
|
18384
18400
|
const cfg = {
|
|
@@ -19502,6 +19518,9 @@ async function connectSocketMode() {
|
|
|
19502
19518
|
isAutoFollowed,
|
|
19503
19519
|
botUserId
|
|
19504
19520
|
});
|
|
19521
|
+
const participantEntry = threadKey ? trackedThreads.get(threadKey) : void 0;
|
|
19522
|
+
const botPostedInThread = participantEntry?.bot_posted === true || participantEntry?.involvement === "started";
|
|
19523
|
+
const armMarker = decideMarkerArm({ shouldEngage, isAutoFollowed, botPostedInThread });
|
|
19505
19524
|
const ackProbe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
19506
19525
|
let paneLogFreshAgeMs = null;
|
|
19507
19526
|
if (SLACK_AGENT_DIR) {
|
|
@@ -19531,13 +19550,13 @@ async function connectSocketMode() {
|
|
|
19531
19550
|
body: JSON.stringify({ channel, timestamp: ts, name: reactionName })
|
|
19532
19551
|
}).catch(() => {
|
|
19533
19552
|
});
|
|
19534
|
-
if (ackDecision === "undeliverable" && channel &&
|
|
19553
|
+
if (ackDecision === "undeliverable" && channel && armMarker) {
|
|
19535
19554
|
postUndeliverableNotice(channel, threadTs, isThreadReply);
|
|
19536
19555
|
}
|
|
19537
19556
|
}
|
|
19538
|
-
if (channel && ts &&
|
|
19539
|
-
trackPendingMessage(channel, threadTs, ts, ackDecision === "undeliverable");
|
|
19540
|
-
if (ackDecision === "ack") {
|
|
19557
|
+
if (channel && ts && armMarker) {
|
|
19558
|
+
trackPendingMessage(channel, threadTs, ts, ackDecision === "undeliverable", !shouldEngage);
|
|
19559
|
+
if (ackDecision === "ack" && shouldEngage) {
|
|
19541
19560
|
scheduleBusyAck(channel, threadTs, ts, isThreadReply);
|
|
19542
19561
|
}
|
|
19543
19562
|
}
|
|
@@ -14320,16 +14320,16 @@ import {
|
|
|
14320
14320
|
createWriteStream,
|
|
14321
14321
|
existsSync as existsSync5,
|
|
14322
14322
|
mkdirSync as mkdirSync4,
|
|
14323
|
-
readFileSync as
|
|
14324
|
-
readdirSync as
|
|
14323
|
+
readFileSync as readFileSync7,
|
|
14324
|
+
readdirSync as readdirSync3,
|
|
14325
14325
|
renameSync as renameSync4,
|
|
14326
|
-
statSync,
|
|
14326
|
+
statSync as statSync2,
|
|
14327
14327
|
unlinkSync as unlinkSync4,
|
|
14328
14328
|
watch,
|
|
14329
14329
|
writeFileSync as writeFileSync4
|
|
14330
14330
|
} from "fs";
|
|
14331
14331
|
import { homedir as homedir3 } from "os";
|
|
14332
|
-
import { join as
|
|
14332
|
+
import { join as join7 } from "path";
|
|
14333
14333
|
|
|
14334
14334
|
// src/channel-attachments.ts
|
|
14335
14335
|
import { homedir } from "os";
|
|
@@ -16000,16 +16000,53 @@ var TELEGRAM_EGRESS_TOOLS = /* @__PURE__ */ new Set([
|
|
|
16000
16000
|
"channel_request_input"
|
|
16001
16001
|
]);
|
|
16002
16002
|
|
|
16003
|
+
// src/telegram-pending-inbound-cleanup.ts
|
|
16004
|
+
import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
16005
|
+
import { join as join3 } from "path";
|
|
16006
|
+
function markerArrivalMs(fullPath) {
|
|
16007
|
+
try {
|
|
16008
|
+
const received = JSON.parse(readFileSync3(fullPath, "utf-8")).received_at;
|
|
16009
|
+
const parsed = received ? Date.parse(received) : Number.NaN;
|
|
16010
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
16011
|
+
} catch {
|
|
16012
|
+
}
|
|
16013
|
+
try {
|
|
16014
|
+
return statSync(fullPath).mtimeMs;
|
|
16015
|
+
} catch {
|
|
16016
|
+
return Number.POSITIVE_INFINITY;
|
|
16017
|
+
}
|
|
16018
|
+
}
|
|
16019
|
+
function clearAllTelegramPendingMarkersForChat(pendingDir, chatId, clearMarkerFile, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
16020
|
+
const prefix = `${chatId.replace(/[^A-Za-z0-9_-]/g, "_")}__`;
|
|
16021
|
+
const bounded = Number.isFinite(cutoffMs);
|
|
16022
|
+
let filenames;
|
|
16023
|
+
try {
|
|
16024
|
+
filenames = readdirSync(pendingDir);
|
|
16025
|
+
} catch {
|
|
16026
|
+
return 0;
|
|
16027
|
+
}
|
|
16028
|
+
let cleared = 0;
|
|
16029
|
+
for (const filename of filenames) {
|
|
16030
|
+
if (!filename.startsWith(prefix)) continue;
|
|
16031
|
+
if (!filename.endsWith(".json")) continue;
|
|
16032
|
+
const fullPath = join3(pendingDir, filename);
|
|
16033
|
+
if (bounded && markerArrivalMs(fullPath) > cutoffMs) continue;
|
|
16034
|
+
clearMarkerFile(fullPath);
|
|
16035
|
+
cleared++;
|
|
16036
|
+
}
|
|
16037
|
+
return cleared;
|
|
16038
|
+
}
|
|
16039
|
+
|
|
16003
16040
|
// src/mcp-spawn-lock.ts
|
|
16004
16041
|
import {
|
|
16005
16042
|
existsSync as existsSync3,
|
|
16006
16043
|
mkdirSync as mkdirSync3,
|
|
16007
|
-
readFileSync as
|
|
16044
|
+
readFileSync as readFileSync4,
|
|
16008
16045
|
renameSync as renameSync3,
|
|
16009
16046
|
unlinkSync as unlinkSync3,
|
|
16010
16047
|
writeFileSync as writeFileSync3
|
|
16011
16048
|
} from "fs";
|
|
16012
|
-
import { join as
|
|
16049
|
+
import { join as join4 } from "path";
|
|
16013
16050
|
function defaultIsPidAlive(pid) {
|
|
16014
16051
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
16015
16052
|
try {
|
|
@@ -16027,7 +16064,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
16027
16064
|
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
16028
16065
|
const selfPid = options.selfPid ?? process.pid;
|
|
16029
16066
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
16030
|
-
const path =
|
|
16067
|
+
const path = join4(agentDir, basename);
|
|
16031
16068
|
const existing = readLockHolder(path);
|
|
16032
16069
|
if (existing) {
|
|
16033
16070
|
if (existing.pid === selfPid) {
|
|
@@ -16058,7 +16095,7 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
|
|
|
16058
16095
|
function readLockHolder(path) {
|
|
16059
16096
|
if (!existsSync3(path)) return null;
|
|
16060
16097
|
try {
|
|
16061
|
-
const raw =
|
|
16098
|
+
const raw = readFileSync4(path, "utf8");
|
|
16062
16099
|
const parsed = JSON.parse(raw);
|
|
16063
16100
|
const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
|
|
16064
16101
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
@@ -16070,15 +16107,15 @@ function readLockHolder(path) {
|
|
|
16070
16107
|
}
|
|
16071
16108
|
|
|
16072
16109
|
// src/ack-reaction.ts
|
|
16073
|
-
import { readdirSync, readFileSync as
|
|
16074
|
-
import { join as
|
|
16110
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
16111
|
+
import { join as join6 } from "path";
|
|
16075
16112
|
|
|
16076
16113
|
// src/flags-cache-read.ts
|
|
16077
|
-
import { existsSync as existsSync4, readFileSync as
|
|
16114
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
16078
16115
|
import { homedir as homedir2 } from "os";
|
|
16079
|
-
import { join as
|
|
16116
|
+
import { join as join5 } from "path";
|
|
16080
16117
|
function defaultFlagsCachePath() {
|
|
16081
|
-
return
|
|
16118
|
+
return join5(homedir2(), ".augmented", "flags-cache.json");
|
|
16082
16119
|
}
|
|
16083
16120
|
function envBoolean(raw) {
|
|
16084
16121
|
if (raw === void 0) return void 0;
|
|
@@ -16091,7 +16128,7 @@ function envBoolean(raw) {
|
|
|
16091
16128
|
function cachedBoolean(key2, path) {
|
|
16092
16129
|
try {
|
|
16093
16130
|
if (!existsSync4(path)) return void 0;
|
|
16094
|
-
const parsed = JSON.parse(
|
|
16131
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
16095
16132
|
if (!parsed || typeof parsed !== "object") return void 0;
|
|
16096
16133
|
const flags = parsed.flags;
|
|
16097
16134
|
if (!flags || typeof flags !== "object") return void 0;
|
|
@@ -16171,7 +16208,7 @@ var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
|
16171
16208
|
function readGiveUpSignalAtMs(path, now = Date.now()) {
|
|
16172
16209
|
if (!path) return null;
|
|
16173
16210
|
try {
|
|
16174
|
-
const raw = JSON.parse(
|
|
16211
|
+
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
16175
16212
|
if (typeof raw.gave_up_at !== "string") return null;
|
|
16176
16213
|
const t = Date.parse(raw.gave_up_at);
|
|
16177
16214
|
if (!Number.isFinite(t) || t > now) return null;
|
|
@@ -16194,7 +16231,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
|
16194
16231
|
if (!dir) return null;
|
|
16195
16232
|
let names;
|
|
16196
16233
|
try {
|
|
16197
|
-
names =
|
|
16234
|
+
names = readdirSync2(dir);
|
|
16198
16235
|
} catch {
|
|
16199
16236
|
return null;
|
|
16200
16237
|
}
|
|
@@ -16203,7 +16240,8 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
|
16203
16240
|
if (!name.endsWith(".json")) continue;
|
|
16204
16241
|
let receivedAt;
|
|
16205
16242
|
try {
|
|
16206
|
-
const raw = JSON.parse(
|
|
16243
|
+
const raw = JSON.parse(readFileSync6(join6(dir, name), "utf-8"));
|
|
16244
|
+
if (raw.discretionary === true) continue;
|
|
16207
16245
|
receivedAt = raw.received_at;
|
|
16208
16246
|
} catch {
|
|
16209
16247
|
continue;
|
|
@@ -16251,7 +16289,7 @@ function redactId(id) {
|
|
|
16251
16289
|
}
|
|
16252
16290
|
var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
16253
16291
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
|
|
16254
|
-
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
16292
|
+
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join7(homedir3(), ".augmented", AGENT_CODE_NAME) : null;
|
|
16255
16293
|
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
16256
16294
|
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
16257
16295
|
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
@@ -16345,9 +16383,9 @@ if (!BOT_TOKEN) {
|
|
|
16345
16383
|
var stderrLogStream = null;
|
|
16346
16384
|
if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
|
|
16347
16385
|
try {
|
|
16348
|
-
const logDir =
|
|
16386
|
+
const logDir = join7(homedir3(), ".augmented", AGENT_CODE_NAME);
|
|
16349
16387
|
mkdirSync4(logDir, { recursive: true });
|
|
16350
|
-
stderrLogStream = createWriteStream(
|
|
16388
|
+
stderrLogStream = createWriteStream(join7(logDir, "telegram-channel-stderr.log"), {
|
|
16351
16389
|
flags: "a",
|
|
16352
16390
|
mode: 384
|
|
16353
16391
|
});
|
|
@@ -16537,7 +16575,7 @@ function scheduleBusyAck(chatId, messageId) {
|
|
|
16537
16575
|
let paneLogFreshAgeMs = null;
|
|
16538
16576
|
if (AGENT_DIR) {
|
|
16539
16577
|
try {
|
|
16540
|
-
const paneMtimeMs =
|
|
16578
|
+
const paneMtimeMs = statSync2(join7(AGENT_DIR, "pane.log")).mtimeMs;
|
|
16541
16579
|
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
16542
16580
|
} catch {
|
|
16543
16581
|
}
|
|
@@ -16559,7 +16597,7 @@ function scheduleBusyAck(chatId, messageId) {
|
|
|
16559
16597
|
function __resetBusyAckNoticeThrottle() {
|
|
16560
16598
|
lastBusyAckNoticeAt.clear();
|
|
16561
16599
|
}
|
|
16562
|
-
var RESTART_FLAGS_DIR =
|
|
16600
|
+
var RESTART_FLAGS_DIR = join7(homedir3(), ".augmented", "restart-flags");
|
|
16563
16601
|
function writeTelegramRestartConfirm(reply, requesterName) {
|
|
16564
16602
|
if (!RESTART_CONFIRM_FILE) return;
|
|
16565
16603
|
const marker = {
|
|
@@ -16752,7 +16790,7 @@ async function handleRestartCommand(opts) {
|
|
|
16752
16790
|
if (!existsSync5(RESTART_FLAGS_DIR)) {
|
|
16753
16791
|
mkdirSync4(RESTART_FLAGS_DIR, { recursive: true });
|
|
16754
16792
|
}
|
|
16755
|
-
const flagPath =
|
|
16793
|
+
const flagPath = join7(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
|
|
16756
16794
|
writeTelegramRestartConfirm(
|
|
16757
16795
|
{ chat_id: opts.chatId, message_id: opts.messageId },
|
|
16758
16796
|
opts.requesterName
|
|
@@ -17135,10 +17173,10 @@ async function classifyRestartCommand(text) {
|
|
|
17135
17173
|
if (!ours) return "verification_failed";
|
|
17136
17174
|
return target === ours ? "act" : "ignore";
|
|
17137
17175
|
}
|
|
17138
|
-
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
17139
|
-
var PENDING_INBOUND_DIR = AGENT_DIR ?
|
|
17140
|
-
var RECOVERY_OUTBOX_DIR = AGENT_DIR ?
|
|
17141
|
-
var RESTART_CONFIRM_FILE = AGENT_DIR ?
|
|
17176
|
+
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join7(homedir3(), ".augmented", AGENT_CODE_NAME) : null;
|
|
17177
|
+
var PENDING_INBOUND_DIR = AGENT_DIR ? join7(AGENT_DIR, "telegram-pending-inbound") : null;
|
|
17178
|
+
var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join7(AGENT_DIR, "telegram-recovery-outbox") : null;
|
|
17179
|
+
var RESTART_CONFIRM_FILE = AGENT_DIR ? join7(AGENT_DIR, "telegram-restart-confirm.json") : null;
|
|
17142
17180
|
var TELEGRAM_PROCESS_BOOT_MS = Date.now();
|
|
17143
17181
|
function safeMarkerName(chatId, messageId) {
|
|
17144
17182
|
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -17146,7 +17184,7 @@ function safeMarkerName(chatId, messageId) {
|
|
|
17146
17184
|
}
|
|
17147
17185
|
function pendingInboundPath(chatId, messageId) {
|
|
17148
17186
|
if (!PENDING_INBOUND_DIR) return null;
|
|
17149
|
-
return
|
|
17187
|
+
return join7(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
17150
17188
|
}
|
|
17151
17189
|
function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
|
|
17152
17190
|
const path = pendingInboundPath(chatId, messageId);
|
|
@@ -17175,7 +17213,7 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
|
|
|
17175
17213
|
function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
17176
17214
|
let marker = null;
|
|
17177
17215
|
try {
|
|
17178
|
-
marker = JSON.parse(
|
|
17216
|
+
marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
17179
17217
|
} catch {
|
|
17180
17218
|
}
|
|
17181
17219
|
if (marker && decideRecoveryHeal({
|
|
@@ -17189,16 +17227,11 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
|
17189
17227
|
} catch {
|
|
17190
17228
|
}
|
|
17191
17229
|
}
|
|
17192
|
-
function clearPendingInboundMarker(chatId, messageId) {
|
|
17193
|
-
const path = pendingInboundPath(chatId, messageId);
|
|
17194
|
-
if (!path) return;
|
|
17195
|
-
clearTelegramMarkerFileWithHeal(path);
|
|
17196
|
-
}
|
|
17197
17230
|
function readPendingInboundMarker(chatId, messageId) {
|
|
17198
17231
|
const path = pendingInboundPath(chatId, messageId);
|
|
17199
17232
|
if (!path || !existsSync5(path)) return null;
|
|
17200
17233
|
try {
|
|
17201
|
-
return JSON.parse(
|
|
17234
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
17202
17235
|
} catch {
|
|
17203
17236
|
return null;
|
|
17204
17237
|
}
|
|
@@ -17218,10 +17251,10 @@ function nextRetryName(filename) {
|
|
|
17218
17251
|
async function processRecoveryOutboxFile(filename) {
|
|
17219
17252
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
17220
17253
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
17221
|
-
const fullPath =
|
|
17254
|
+
const fullPath = join7(RECOVERY_OUTBOX_DIR, filename);
|
|
17222
17255
|
let payload;
|
|
17223
17256
|
try {
|
|
17224
|
-
const raw =
|
|
17257
|
+
const raw = readFileSync7(fullPath, "utf-8");
|
|
17225
17258
|
payload = JSON.parse(raw);
|
|
17226
17259
|
} catch (err) {
|
|
17227
17260
|
process.stderr.write(
|
|
@@ -17253,6 +17286,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
17253
17286
|
};
|
|
17254
17287
|
if (payload.message_id) body.reply_to_message_id = Number(payload.message_id);
|
|
17255
17288
|
let sendSucceeded = false;
|
|
17289
|
+
const recoveryDrainCutoffMs = Date.now();
|
|
17256
17290
|
try {
|
|
17257
17291
|
const resp = await telegramApiCall("sendMessage", body, 15e3);
|
|
17258
17292
|
if (resp.ok) {
|
|
@@ -17261,7 +17295,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
17261
17295
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery sent (chat=${redactId(payload.chat_id)} msg=${redactId(payload.message_id ?? "")})
|
|
17262
17296
|
`
|
|
17263
17297
|
);
|
|
17264
|
-
|
|
17298
|
+
clearPendingMessage(payload.chat_id, recoveryDrainCutoffMs);
|
|
17265
17299
|
} else {
|
|
17266
17300
|
process.stderr.write(
|
|
17267
17301
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery failed (chat=${redactId(payload.chat_id)}): ${resp.description ?? "unknown"}
|
|
@@ -17284,7 +17318,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
17284
17318
|
const next = nextRetryName(filename);
|
|
17285
17319
|
if (next) {
|
|
17286
17320
|
try {
|
|
17287
|
-
renameSync4(fullPath,
|
|
17321
|
+
renameSync4(fullPath, join7(RECOVERY_OUTBOX_DIR, next.next));
|
|
17288
17322
|
if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
17289
17323
|
process.stderr.write(
|
|
17290
17324
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -17315,7 +17349,7 @@ function scanRecoveryRetries() {
|
|
|
17315
17349
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
17316
17350
|
let entries;
|
|
17317
17351
|
try {
|
|
17318
|
-
entries =
|
|
17352
|
+
entries = readdirSync3(RECOVERY_OUTBOX_DIR);
|
|
17319
17353
|
} catch {
|
|
17320
17354
|
return;
|
|
17321
17355
|
}
|
|
@@ -17324,7 +17358,7 @@ function scanRecoveryRetries() {
|
|
|
17324
17358
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
17325
17359
|
let mtimeMs;
|
|
17326
17360
|
try {
|
|
17327
|
-
mtimeMs =
|
|
17361
|
+
mtimeMs = statSync2(join7(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
17328
17362
|
} catch {
|
|
17329
17363
|
continue;
|
|
17330
17364
|
}
|
|
@@ -17345,7 +17379,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
17345
17379
|
return;
|
|
17346
17380
|
}
|
|
17347
17381
|
try {
|
|
17348
|
-
for (const f of
|
|
17382
|
+
for (const f of readdirSync3(RECOVERY_OUTBOX_DIR)) {
|
|
17349
17383
|
if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
|
|
17350
17384
|
}
|
|
17351
17385
|
} catch {
|
|
@@ -17354,7 +17388,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
17354
17388
|
const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
17355
17389
|
if (event !== "rename" || !filename) return;
|
|
17356
17390
|
if (!isFirstAttemptOutboxFile(filename)) return;
|
|
17357
|
-
if (existsSync5(
|
|
17391
|
+
if (existsSync5(join7(RECOVERY_OUTBOX_DIR, filename))) {
|
|
17358
17392
|
void processRecoveryOutboxFile(filename);
|
|
17359
17393
|
}
|
|
17360
17394
|
});
|
|
@@ -17378,7 +17412,7 @@ function sweepTelegramStaleMarkers(thresholdMs) {
|
|
|
17378
17412
|
if (!existsSync5(PENDING_INBOUND_DIR)) return;
|
|
17379
17413
|
let filenames;
|
|
17380
17414
|
try {
|
|
17381
|
-
filenames =
|
|
17415
|
+
filenames = readdirSync3(PENDING_INBOUND_DIR);
|
|
17382
17416
|
} catch (err) {
|
|
17383
17417
|
process.stderr.write(
|
|
17384
17418
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -17391,10 +17425,10 @@ function sweepTelegramStaleMarkers(thresholdMs) {
|
|
|
17391
17425
|
for (const filename of filenames) {
|
|
17392
17426
|
if (!filename.endsWith(".json")) continue;
|
|
17393
17427
|
if (filename.endsWith(".tmp")) continue;
|
|
17394
|
-
const fullPath =
|
|
17428
|
+
const fullPath = join7(PENDING_INBOUND_DIR, filename);
|
|
17395
17429
|
let marker;
|
|
17396
17430
|
try {
|
|
17397
|
-
marker = JSON.parse(
|
|
17431
|
+
marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
17398
17432
|
} catch (err) {
|
|
17399
17433
|
process.stderr.write(
|
|
17400
17434
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
|
|
@@ -17436,11 +17470,11 @@ function listPendingInboundChatIds() {
|
|
|
17436
17470
|
if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return [];
|
|
17437
17471
|
const chats = /* @__PURE__ */ new Set();
|
|
17438
17472
|
try {
|
|
17439
|
-
for (const name of
|
|
17473
|
+
for (const name of readdirSync3(PENDING_INBOUND_DIR)) {
|
|
17440
17474
|
if (!name.endsWith(".json")) continue;
|
|
17441
17475
|
try {
|
|
17442
17476
|
const marker = JSON.parse(
|
|
17443
|
-
|
|
17477
|
+
readFileSync7(join7(PENDING_INBOUND_DIR, name), "utf8")
|
|
17444
17478
|
);
|
|
17445
17479
|
if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
|
|
17446
17480
|
} catch {
|
|
@@ -17481,7 +17515,7 @@ async function notifyWatchdogGiveUp(chatId) {
|
|
|
17481
17515
|
}
|
|
17482
17516
|
function checkWatchdogGiveUpNotice() {
|
|
17483
17517
|
if (!AGENT_DIR) return;
|
|
17484
|
-
const signalAtMs = readGiveUpSignalAtMs(
|
|
17518
|
+
const signalAtMs = readGiveUpSignalAtMs(join7(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
|
|
17485
17519
|
const act = decideGiveUpNotice({
|
|
17486
17520
|
signalAtMs,
|
|
17487
17521
|
lastHandledAtMs: lastGiveUpHandledAtMs,
|
|
@@ -17531,6 +17565,7 @@ async function deliverQueuedReply(p) {
|
|
|
17531
17565
|
}
|
|
17532
17566
|
const body = { chat_id: p.chatId, text: p.text };
|
|
17533
17567
|
if (p.replyToMessageId) body.reply_to_message_id = Number(p.replyToMessageId);
|
|
17568
|
+
const drainCutoffMs = Date.now();
|
|
17534
17569
|
const data = await telegramApiCall("sendMessage", body, 15e3);
|
|
17535
17570
|
if (!data.ok) {
|
|
17536
17571
|
process.stderr.write(
|
|
@@ -17541,7 +17576,7 @@ async function deliverQueuedReply(p) {
|
|
|
17541
17576
|
}
|
|
17542
17577
|
recordReply(p.chatId, "", Date.now(), p.throttleCfg);
|
|
17543
17578
|
if (p.isReplyTool) {
|
|
17544
|
-
clearPendingMessage(p.chatId,
|
|
17579
|
+
clearPendingMessage(p.chatId, drainCutoffMs);
|
|
17545
17580
|
}
|
|
17546
17581
|
process.stderr.write(
|
|
17547
17582
|
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_delivered chat=${redactId(p.chatId)}
|
|
@@ -17554,29 +17589,18 @@ async function deliverQueuedReply(p) {
|
|
|
17554
17589
|
);
|
|
17555
17590
|
}
|
|
17556
17591
|
}
|
|
17557
|
-
function clearPendingMessage(chatId,
|
|
17558
|
-
if (messageId) {
|
|
17559
|
-
clearPendingInboundMarker(chatId, messageId);
|
|
17560
|
-
return;
|
|
17561
|
-
}
|
|
17592
|
+
function clearPendingMessage(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
17562
17593
|
if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return;
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
return;
|
|
17570
|
-
}
|
|
17571
|
-
for (const filename of filenames) {
|
|
17572
|
-
if (!filename.startsWith(prefix)) continue;
|
|
17573
|
-
if (!filename.endsWith(".json")) continue;
|
|
17574
|
-
clearTelegramMarkerFileWithHeal(join6(PENDING_INBOUND_DIR, filename));
|
|
17575
|
-
}
|
|
17594
|
+
clearAllTelegramPendingMarkersForChat(
|
|
17595
|
+
PENDING_INBOUND_DIR,
|
|
17596
|
+
chatId,
|
|
17597
|
+
clearTelegramMarkerFileWithHeal,
|
|
17598
|
+
cutoffMs
|
|
17599
|
+
);
|
|
17576
17600
|
}
|
|
17577
|
-
function noteThreadActivity(chatId,
|
|
17601
|
+
function noteThreadActivity(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
17578
17602
|
if (!chatId) return;
|
|
17579
|
-
clearPendingMessage(chatId,
|
|
17603
|
+
clearPendingMessage(chatId, cutoffMs);
|
|
17580
17604
|
}
|
|
17581
17605
|
var mcp = new Server(
|
|
17582
17606
|
{ name: "telegram", version: "0.1.0" },
|
|
@@ -17840,6 +17864,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17840
17864
|
try {
|
|
17841
17865
|
const body = { chat_id, text };
|
|
17842
17866
|
if (reply_to_message_id) body.reply_to_message_id = Number(reply_to_message_id);
|
|
17867
|
+
const drainCutoffMs = Date.now();
|
|
17843
17868
|
const data = await telegramApiCall("sendMessage", body, 15e3);
|
|
17844
17869
|
if (!data.ok) {
|
|
17845
17870
|
return {
|
|
@@ -17849,7 +17874,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17849
17874
|
}
|
|
17850
17875
|
recordReply(chat_id, "", tgThrottleNow, tgThrottleCfg);
|
|
17851
17876
|
if (name === "telegram.reply") {
|
|
17852
|
-
clearPendingMessage(chat_id,
|
|
17877
|
+
clearPendingMessage(chat_id, drainCutoffMs);
|
|
17853
17878
|
}
|
|
17854
17879
|
return { content: [{ type: "text", text: "sent" }] };
|
|
17855
17880
|
} catch (err) {
|
|
@@ -17867,7 +17892,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17867
17892
|
isError: true
|
|
17868
17893
|
};
|
|
17869
17894
|
}
|
|
17870
|
-
|
|
17895
|
+
const drainCutoffMs = Date.now();
|
|
17871
17896
|
try {
|
|
17872
17897
|
const data = await telegramApiCall(
|
|
17873
17898
|
"setMessageReaction",
|
|
@@ -17884,6 +17909,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17884
17909
|
isError: true
|
|
17885
17910
|
};
|
|
17886
17911
|
}
|
|
17912
|
+
noteThreadActivity(chat_id, drainCutoffMs);
|
|
17887
17913
|
return { content: [{ type: "text", text: emoji2 ? "reacted" : "cleared" }] };
|
|
17888
17914
|
} catch (err) {
|
|
17889
17915
|
return {
|
|
@@ -18176,7 +18202,7 @@ async function replayPendingTelegramMarkers() {
|
|
|
18176
18202
|
if (!sessionAlive) return;
|
|
18177
18203
|
let filenames;
|
|
18178
18204
|
try {
|
|
18179
|
-
filenames =
|
|
18205
|
+
filenames = readdirSync3(PENDING_INBOUND_DIR);
|
|
18180
18206
|
} catch {
|
|
18181
18207
|
return;
|
|
18182
18208
|
}
|
|
@@ -18184,10 +18210,10 @@ async function replayPendingTelegramMarkers() {
|
|
|
18184
18210
|
const entries = [];
|
|
18185
18211
|
for (const name of filenames) {
|
|
18186
18212
|
if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
|
|
18187
|
-
const fullPath =
|
|
18213
|
+
const fullPath = join7(PENDING_INBOUND_DIR, name);
|
|
18188
18214
|
let marker;
|
|
18189
18215
|
try {
|
|
18190
|
-
marker = JSON.parse(
|
|
18216
|
+
marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
18191
18217
|
} catch {
|
|
18192
18218
|
continue;
|
|
18193
18219
|
}
|
|
@@ -18595,7 +18621,7 @@ async function pollLoop() {
|
|
|
18595
18621
|
let paneLogFreshAgeMs = null;
|
|
18596
18622
|
if (AGENT_DIR) {
|
|
18597
18623
|
try {
|
|
18598
|
-
const paneMtimeMs =
|
|
18624
|
+
const paneMtimeMs = statSync2(join7(AGENT_DIR, "pane.log")).mtimeMs;
|
|
18599
18625
|
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
18600
18626
|
} catch {
|
|
18601
18627
|
}
|
|
@@ -42,12 +42,15 @@ function oldestPendingInboundMtimeMs(agentHomeDir) {
|
|
|
42
42
|
}
|
|
43
43
|
return oldest;
|
|
44
44
|
}
|
|
45
|
-
function
|
|
45
|
+
function readMarkerFlags(markerPath) {
|
|
46
46
|
try {
|
|
47
47
|
const parsed = JSON.parse(readFileSync(markerPath, "utf8"));
|
|
48
|
-
return
|
|
48
|
+
return {
|
|
49
|
+
undeliverable: parsed?.undeliverable === true,
|
|
50
|
+
discretionary: parsed?.discretionary === true
|
|
51
|
+
};
|
|
49
52
|
} catch (error) {
|
|
50
|
-
return error.code === "ENOENT" ? null :
|
|
53
|
+
return error.code === "ENOENT" ? null : "malformed";
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
function oldestLivePendingInboundMtimeMs(agentHomeDir, opts = {}) {
|
|
@@ -78,9 +81,9 @@ function oldestLivePendingInboundMtimeMs(agentHomeDir, opts = {}) {
|
|
|
78
81
|
continue;
|
|
79
82
|
}
|
|
80
83
|
if (sessionStartMs !== null && mtimeMs < sessionStartMs) continue;
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
if (undeliverable) continue;
|
|
84
|
+
const flags = readMarkerFlags(full);
|
|
85
|
+
if (flags === null) continue;
|
|
86
|
+
if (flags !== "malformed" && (flags.undeliverable || flags.discretionary)) continue;
|
|
84
87
|
if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;
|
|
85
88
|
}
|
|
86
89
|
}
|
|
@@ -122,9 +125,9 @@ function parkPendingInbound(codeName, _now = /* @__PURE__ */ new Date()) {
|
|
|
122
125
|
}
|
|
123
126
|
for (const file of files) {
|
|
124
127
|
if (!file.isFile() || file.name.startsWith(".")) continue;
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
if (undeliverable) {
|
|
128
|
+
const flags = readMarkerFlags(join(dir, file.name));
|
|
129
|
+
if (flags === null) continue;
|
|
130
|
+
if (flags !== "malformed" && flags.undeliverable) {
|
|
128
131
|
if (moveMarkerToStale(dir, deadDir, file.name)) result.deadLettered++;
|
|
129
132
|
} else {
|
|
130
133
|
result.parked++;
|
|
@@ -192,4 +195,4 @@ export {
|
|
|
192
195
|
oldestLivePendingInboundMtimeMs,
|
|
193
196
|
parkPendingInbound
|
|
194
197
|
};
|
|
195
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
198
|
+
//# sourceMappingURL=responsiveness-probe-7NDEHXXZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/responsiveness-probe.ts"],"sourcesContent":["/**\n * ENG-5399 — Tier 1 responsiveness probe (manager-side).\n *\n * Cheap, fast-cadence canary that catches \"agent went silent\" inside\n * minutes, well before the existing synthetic-probe cron's ~35 min\n * staleness window (`SyntheticReplyAgeSeconds`, ENG-5122).\n *\n * Mechanism: for each managed agent, read the mtime of the agent's\n * `pane.log` and report `now - mtime` as `PaneActivityAgeSeconds` via\n * a new `/host/responsiveness-probe` endpoint. `pane.log` is the\n * tmux pipe-pane sink set up by `setupPaneLog()` — any visible\n * activity (assistant turns, tool calls, in-place progress\n * heartbeats) bumps its mtime. A silent agent has a steadily\n * climbing age that lands in CloudWatch and trips a per-agent alarm.\n *\n * ENG-6017 adds a second per-agent signal on the same cadence:\n * `pending_inbound_oldest_age_seconds` — the age of the oldest marker\n * file across the agent's `*-pending-inbound/` directories (written by\n * the channel MCP servers for inbounds awaiting delivery). This is the\n * one artifact of the \"message typed but never submitted\" failure mode\n * that every other canary is blind to: in the koda incident\n * (2026-06-04) an operator Slack DM sat undelivered for 40+ minutes\n * while pane-activity stayed fresh (health checks), synthetic probes\n * were answered by the one-shot fallback, and heartbeat/session-alive\n * only reflect manager health. The field is OMITTED (not zero) when the\n * agent has no pending-inbound markers — the API treats absent as\n * \"no signal\", never as \"healthy\" (absent-vs-zero matters for\n * mixed-version fleets where old CLIs don't report it at all).\n *\n * Run from `pollCycle()` in `manager-worker.ts` on a configurable\n * interval (default 5 min via `AUGMENTED_RESPONSIVENESS_INTERVAL_MS`).\n */\n\nimport { mkdirSync, readdirSync, readFileSync, renameSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { paneLogPath } from './persistent-session.js';\n\nexport interface ResponsivenessProbeResult {\n code_name: string;\n pane_activity_age_seconds: number;\n /**\n * ENG-6017: age (s) of the oldest marker file across the agent's\n * `*-pending-inbound/` directories. Omitted when no markers exist —\n * absent means \"no signal\", NOT \"zero / healthy\".\n */\n pending_inbound_oldest_age_seconds?: number;\n}\n\nconst DEFAULT_INTERVAL_MS = 5 * 60 * 1000;\n\nexport function getResponsivenessIntervalMs(): number {\n const raw = process.env.AUGMENTED_RESPONSIVENESS_INTERVAL_MS;\n if (!raw) return DEFAULT_INTERVAL_MS;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_INTERVAL_MS;\n}\n\n/**\n * ENG-6017: oldest pending-inbound marker mtime (ms epoch) for an agent,\n * or null when the agent has no markers / no pending-inbound dirs.\n *\n * The channel MCP servers (slack-channel, telegram-channel, …) write one\n * marker file per inbound into `~/.augmented/<codeName>/<channel>-pending-\n * inbound/` and clear it when the agent acknowledges the message. The\n * directory layout is the contract here — read-only, no IPC with the MCP\n * (the MCP and CLI release independently; file mtimes need no protocol).\n *\n * ENG-6072: only plain, non-hidden files count as markers. The msteams MCP\n * keeps `.markers/` and `.processed/` housekeeping SUBDIRECTORIES inside its\n * pending-inbound dir; their mtimes never advance, so statting every dirent\n * made the gauge climb forever and fired pending-inbound-stale on agents with\n * zero stranded messages (kylie ~3.4d / scout ~34h false ALARMs the moment\n * ENG-6023 activated the alarm). Dot-entries are skipped wholesale — the\n * hidden namespace is reserved for MCP bookkeeping, never for markers.\n */\nfunction oldestPendingInboundMtimeMs(agentHomeDir: string): number | null {\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null; // agent home missing — nothing to report\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n try {\n const mtimeMs = statSync(join(dir, file.name)).mtimeMs;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n } catch {\n // Marker drained between readdir and stat — that's the happy path.\n }\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160 / ENG-6319: read a marker's classification flags.\n * - `{...}` → parsed flags.\n * - `null` → vanished mid-scan (ENOENT) — drained between stat and\n * read, the happy path; callers exclude it.\n * - `'malformed'` → present but unreadable for another reason. The live\n * scan treats this as LIVE (a corrupt marker can never\n * mask a real wedge); park leaves it in place (a corrupt\n * marker must never become a dropped message).\n */\ninterface MarkerFlags {\n /** ENG-5846: dead-lettered by the channel (⏳-noticed, never drainable). */\n undeliverable: boolean;\n /**\n * ENG-6319: auto-followed participant-thread inbound the agent may\n * legitimately skip. Excluded from the wedge live-scan (a deliberate skip\n * must not read as \"failing to drain\" and force-respawn a healthy\n * session) but still parked across respawns — it may be a real\n * operator message awaiting a reply.\n */\n discretionary: boolean;\n}\n\nfunction readMarkerFlags(markerPath: string): MarkerFlags | null | 'malformed' {\n try {\n const parsed = JSON.parse(readFileSync(markerPath, 'utf8')) as {\n undeliverable?: unknown;\n discretionary?: unknown;\n };\n return {\n undeliverable: parsed?.undeliverable === true,\n discretionary: parsed?.discretionary === true,\n };\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'ENOENT' ? null : 'malformed';\n }\n}\n\n/**\n * ENG-6160: oldest *LIVE* pending-inbound marker mtime (ms epoch) for an agent,\n * or null when there is no live marker. \"Live\" excludes:\n *\n * - markers older than `sessionStartMs` — a marker written before the current\n * session started is a leftover from a PREVIOUS session and cannot mean\n * *this* session is failing to drain. This is the load-bearing exclusion:\n * without it, an orphan marker survives a fresh respawn and the wedge\n * detector re-fires forever on a healthy idle agent (the sherlock enforce\n * loop, 2026-06-08: `inboundAge=3389s` on a `● Ready.` session).\n * - markers flagged `undeliverable: true` — already dead-lettered by the channel.\n * - markers flagged `discretionary: true` (ENG-6319) — skippable auto-followed\n * participant-thread inbound; a deliberate skip must not force-respawn a\n * healthy session.\n *\n * Distinct from `oldestPendingInboundMtimeMs` (which counts ALL markers and\n * feeds the ENG-6017 `pending-inbound-stale` CloudWatch alarm — that alarm\n * *wants* to fire on a stuck inbound, so its semantics must NOT change). This\n * variant is wedge-detection-only.\n */\nexport function oldestLivePendingInboundMtimeMs(\n agentHomeDir: string,\n opts: { sessionStartMs?: number | null } = {},\n): number | null {\n const sessionStartMs = opts.sessionStartMs ?? null;\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const full = join(dir, file.name);\n let mtimeMs: number;\n try {\n mtimeMs = statSync(full).mtimeMs;\n } catch {\n continue; // drained between readdir and stat — happy path\n }\n if (sessionStartMs !== null && mtimeMs < sessionStartMs) continue; // pre-session leftover\n const flags = readMarkerFlags(full);\n if (flags === null) continue; // vanished between stat and read — drained, exclude\n if (flags !== 'malformed' && (flags.undeliverable || flags.discretionary)) continue;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160: age (s) of the oldest LIVE pending-inbound marker for an agent, or\n * null when none. The wedge detector uses this instead of the alarm-facing\n * `pending_inbound_oldest_age_seconds` so a stale/dead-letter marker can't\n * false-fire a respawn.\n */\nexport function livePendingInboundOldestAgeSeconds(\n codeName: string,\n sessionStartMs: number | null,\n now: Date = new Date(),\n): number | null {\n const oldest = oldestLivePendingInboundMtimeMs(dirname(paneLogPath(codeName)), { sessionStartMs });\n if (oldest === null) return null;\n return Math.max(0, Math.floor((now.getTime() - oldest) / 1000));\n}\n\n/**\n * ENG-6160: move every pending-inbound marker for an agent aside into a sibling\n * `<channel>-pending-inbound-stale/` directory (NOT silently deleted — the\n * payload pointer is preserved for forensics), returning the count moved.\n *\n * ENG-6289: no longer called on wedge respawn — the wedge path parks instead\n * (see `parkPendingInbound` above; blanket dead-letter permanently dropped the\n * user's message). Kept as the explicit \"move everything aside\" seam for tests\n * and operator emergencies. The stale dir does not end in `-pending-inbound`,\n * so neither the probe nor this scan re-counts moved markers.\n */\n/**\n * ENG-6289: park-not-drop. On a force-fresh wedge respawn, KEEP undrained\n * pending-inbound markers in their live dirs instead of dead-lettering them —\n * the fresh session's orient hook surfaces them (\"N queued messages\" + details)\n * and ENG-5969 replay (when enabled) re-pushes their payloads. Markers are NOT\n * rewritten (no counter, no mtime bump): the ENG-6160 pre-session exclusion in\n * `oldestLivePendingInboundMtimeMs` already keeps an untouched parked marker\n * out of the fresh session's wedge signal, and re-delivery stays bounded by\n * the existing machinery — the channel-side `replay_count` cap (≤3) and the\n * orphan sweep's received_at TTL. A manager-side rewrite would be the first\n * cross-process writer into marker files, where a torn read in the channel\n * sweep DELETES the marker (`unlinkSync` on parse failure) — the exact drop\n * this function exists to prevent.\n *\n * Only markers already flagged `undeliverable: true` are dead-lettered (moved\n * to `-stale`) — the channel already gave the user the ⏳ notice for those, so\n * nothing can ever drain them. Malformed markers are LEFT IN PLACE, matching\n * the live-scan philosophy above (a corrupt marker must never mask — or\n * become — a dropped message).\n *\n * The msteams dir is skipped wholesale: its top-level files are raw Bot\n * Framework activity payloads (the transport queue, not bookkeeping — real\n * markers live in the hidden `.markers/` subdir this scan never touches), and\n * the teams channel server's boot drain redelivers them to the fresh session\n * on its own. Moving them aside (what the pre-ENG-6289 dead-letter did) was\n * actively defeating the one channel with native respawn recovery.\n */\nexport interface ParkPendingInboundResult {\n parked: number;\n deadLettered: number;\n}\n\n/**\n * Move one marker into the sibling `-stale` dead-letter dir (moved, not\n * deleted — preserved for forensics). Returns true on success; best-effort —\n * a marker that vanished or can't move is left as-is.\n */\nfunction moveMarkerToStale(dir: string, deadDir: string, name: string): boolean {\n try {\n mkdirSync(deadDir, { recursive: true });\n renameSync(join(dir, name), join(deadDir, name));\n return true;\n } catch {\n return false;\n }\n}\n\nexport function parkPendingInbound(codeName: string, _now: Date = new Date()): ParkPendingInboundResult {\n const home = dirname(paneLogPath(codeName));\n const result: ParkPendingInboundResult = { parked: 0, deadLettered: 0 };\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return result;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n if (entry.name === 'msteams-pending-inbound') continue; // transport queue — boot drain owns recovery\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const flags = readMarkerFlags(join(dir, file.name));\n if (flags === null) continue; // drained mid-scan — already gone\n // Only undeliverable markers dead-letter. Discretionary markers\n // (ENG-6319) PARK like engaged ones — they may be a real operator\n // message awaiting a reply (the live silent-loss class); malformed\n // markers park too (corrupt must never become a drop).\n if (flags !== 'malformed' && flags.undeliverable) {\n if (moveMarkerToStale(dir, deadDir, file.name)) result.deadLettered++;\n } else {\n result.parked++;\n }\n }\n }\n return result;\n}\n\nexport function deadLetterPendingInbound(codeName: string, _now: Date = new Date()): number {\n const home = dirname(paneLogPath(codeName));\n let moved = 0;\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return 0;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n if (moveMarkerToStale(dir, deadDir, file.name)) moved++;\n }\n }\n return moved;\n}\n\n/**\n * Compute the pane.log age for each agent. Missing or unreadable\n * pane.log returns null — the caller should drop those entries\n * rather than fabricate a \"fresh\" or \"ancient\" value. A missing\n * file means the agent has never spawned in this manager generation,\n * which is a separate problem covered by SessionAliveAgeSeconds.\n */\nexport function collectResponsivenessProbes(\n codeNames: string[],\n now: Date = new Date(),\n): ResponsivenessProbeResult[] {\n const nowMs = now.getTime();\n const results: ResponsivenessProbeResult[] = [];\n for (const codeName of codeNames) {\n try {\n const panePath = paneLogPath(codeName);\n const mtimeMs = statSync(panePath).mtimeMs;\n const ageSeconds = Math.max(0, Math.floor((nowMs - mtimeMs) / 1000));\n const result: ResponsivenessProbeResult = {\n code_name: codeName,\n pane_activity_age_seconds: ageSeconds,\n };\n // ENG-6017: piggyback the pending-inbound drain-age scan on the same\n // cadence. Field omitted (not 0) when there are no markers.\n const oldestMarkerMs = oldestPendingInboundMtimeMs(dirname(panePath));\n if (oldestMarkerMs !== null) {\n result.pending_inbound_oldest_age_seconds = Math.max(\n 0,\n Math.floor((nowMs - oldestMarkerMs) / 1000),\n );\n }\n results.push(result);\n } catch {\n // No pane.log yet (fresh agent, never spawned) — skip. The\n // session-alive monitor already covers the \"should be running\n // but isn't\" case.\n }\n }\n return results;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/responsiveness-probe.ts"],"sourcesContent":["/**\n * ENG-5399 — Tier 1 responsiveness probe (manager-side).\n *\n * Cheap, fast-cadence canary that catches \"agent went silent\" inside\n * minutes, well before the existing synthetic-probe cron's ~35 min\n * staleness window (`SyntheticReplyAgeSeconds`, ENG-5122).\n *\n * Mechanism: for each managed agent, read the mtime of the agent's\n * `pane.log` and report `now - mtime` as `PaneActivityAgeSeconds` via\n * a new `/host/responsiveness-probe` endpoint. `pane.log` is the\n * tmux pipe-pane sink set up by `setupPaneLog()` — any visible\n * activity (assistant turns, tool calls, in-place progress\n * heartbeats) bumps its mtime. A silent agent has a steadily\n * climbing age that lands in CloudWatch and trips a per-agent alarm.\n *\n * ENG-6017 adds a second per-agent signal on the same cadence:\n * `pending_inbound_oldest_age_seconds` — the age of the oldest marker\n * file across the agent's `*-pending-inbound/` directories (written by\n * the channel MCP servers for inbounds awaiting delivery). This is the\n * one artifact of the \"message typed but never submitted\" failure mode\n * that every other canary is blind to: in the koda incident\n * (2026-06-04) an operator Slack DM sat undelivered for 40+ minutes\n * while pane-activity stayed fresh (health checks), synthetic probes\n * were answered by the one-shot fallback, and heartbeat/session-alive\n * only reflect manager health. The field is OMITTED (not zero) when the\n * agent has no pending-inbound markers — the API treats absent as\n * \"no signal\", never as \"healthy\" (absent-vs-zero matters for\n * mixed-version fleets where old CLIs don't report it at all).\n *\n * Run from `pollCycle()` in `manager-worker.ts` on a configurable\n * interval (default 5 min via `AUGMENTED_RESPONSIVENESS_INTERVAL_MS`).\n */\n\nimport { mkdirSync, readdirSync, readFileSync, renameSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { paneLogPath } from './persistent-session.js';\n\nexport interface ResponsivenessProbeResult {\n code_name: string;\n pane_activity_age_seconds: number;\n /**\n * ENG-6017: age (s) of the oldest marker file across the agent's\n * `*-pending-inbound/` directories. Omitted when no markers exist —\n * absent means \"no signal\", NOT \"zero / healthy\".\n */\n pending_inbound_oldest_age_seconds?: number;\n}\n\nconst DEFAULT_INTERVAL_MS = 5 * 60 * 1000;\n\nexport function getResponsivenessIntervalMs(): number {\n const raw = process.env.AUGMENTED_RESPONSIVENESS_INTERVAL_MS;\n if (!raw) return DEFAULT_INTERVAL_MS;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_INTERVAL_MS;\n}\n\n/**\n * ENG-6017: oldest pending-inbound marker mtime (ms epoch) for an agent,\n * or null when the agent has no markers / no pending-inbound dirs.\n *\n * The channel MCP servers (slack-channel, telegram-channel, …) write one\n * marker file per inbound into `~/.augmented/<codeName>/<channel>-pending-\n * inbound/` and clear it when the agent acknowledges the message. The\n * directory layout is the contract here — read-only, no IPC with the MCP\n * (the MCP and CLI release independently; file mtimes need no protocol).\n *\n * ENG-6072: only plain, non-hidden files count as markers. The msteams MCP\n * keeps `.markers/` and `.processed/` housekeeping SUBDIRECTORIES inside its\n * pending-inbound dir; their mtimes never advance, so statting every dirent\n * made the gauge climb forever and fired pending-inbound-stale on agents with\n * zero stranded messages (kylie ~3.4d / scout ~34h false ALARMs the moment\n * ENG-6023 activated the alarm). Dot-entries are skipped wholesale — the\n * hidden namespace is reserved for MCP bookkeeping, never for markers.\n */\nfunction oldestPendingInboundMtimeMs(agentHomeDir: string): number | null {\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null; // agent home missing — nothing to report\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n try {\n const mtimeMs = statSync(join(dir, file.name)).mtimeMs;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n } catch {\n // Marker drained between readdir and stat — that's the happy path.\n }\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160: classify a marker file for the LIVE-inbound scan.\n * - `true` → flagged `\"undeliverable\": true` (dead-letter, exclude).\n * - `null` → vanished mid-scan (ENOENT) — drained between stat and read, the\n * happy path; exclude it rather than count an already-gone file.\n * - `false` → still present but malformed / unreadable for another reason —\n * treated as LIVE so a corrupt marker can never mask a real wedge.\n */\nfunction isUndeliverableMarker(markerPath: string): boolean | null {\n try {\n const parsed = JSON.parse(readFileSync(markerPath, 'utf8')) as { undeliverable?: unknown };\n return parsed?.undeliverable === true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'ENOENT' ? null : false;\n }\n}\n\n/**\n * ENG-6160: oldest *LIVE* pending-inbound marker mtime (ms epoch) for an agent,\n * or null when there is no live marker. \"Live\" excludes:\n *\n * - markers older than `sessionStartMs` — a marker written before the current\n * session started is a leftover from a PREVIOUS session and cannot mean\n * *this* session is failing to drain. This is the load-bearing exclusion:\n * without it, an orphan marker survives a fresh respawn and the wedge\n * detector re-fires forever on a healthy idle agent (the sherlock enforce\n * loop, 2026-06-08: `inboundAge=3389s` on a `● Ready.` session).\n * - markers flagged `undeliverable: true` — already dead-lettered by the channel.\n *\n * Distinct from `oldestPendingInboundMtimeMs` (which counts ALL markers and\n * feeds the ENG-6017 `pending-inbound-stale` CloudWatch alarm — that alarm\n * *wants* to fire on a stuck inbound, so its semantics must NOT change). This\n * variant is wedge-detection-only.\n */\nexport function oldestLivePendingInboundMtimeMs(\n agentHomeDir: string,\n opts: { sessionStartMs?: number | null } = {},\n): number | null {\n const sessionStartMs = opts.sessionStartMs ?? null;\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const full = join(dir, file.name);\n let mtimeMs: number;\n try {\n mtimeMs = statSync(full).mtimeMs;\n } catch {\n continue; // drained between readdir and stat — happy path\n }\n if (sessionStartMs !== null && mtimeMs < sessionStartMs) continue; // pre-session leftover\n const undeliverable = isUndeliverableMarker(full);\n if (undeliverable === null) continue; // vanished between stat and read — drained, exclude\n if (undeliverable) continue; // already dead-lettered\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160: age (s) of the oldest LIVE pending-inbound marker for an agent, or\n * null when none. The wedge detector uses this instead of the alarm-facing\n * `pending_inbound_oldest_age_seconds` so a stale/dead-letter marker can't\n * false-fire a respawn.\n */\nexport function livePendingInboundOldestAgeSeconds(\n codeName: string,\n sessionStartMs: number | null,\n now: Date = new Date(),\n): number | null {\n const oldest = oldestLivePendingInboundMtimeMs(dirname(paneLogPath(codeName)), { sessionStartMs });\n if (oldest === null) return null;\n return Math.max(0, Math.floor((now.getTime() - oldest) / 1000));\n}\n\n/**\n * ENG-6160: move every pending-inbound marker for an agent aside into a sibling\n * `<channel>-pending-inbound-stale/` directory (NOT silently deleted — the\n * payload pointer is preserved for forensics), returning the count moved.\n *\n * ENG-6289: no longer called on wedge respawn — the wedge path parks instead\n * (see `parkPendingInbound` above; blanket dead-letter permanently dropped the\n * user's message). Kept as the explicit \"move everything aside\" seam for tests\n * and operator emergencies. The stale dir does not end in `-pending-inbound`,\n * so neither the probe nor this scan re-counts moved markers.\n */\n/**\n * ENG-6289: park-not-drop. On a force-fresh wedge respawn, KEEP undrained\n * pending-inbound markers in their live dirs instead of dead-lettering them —\n * the fresh session's orient hook surfaces them (\"N queued messages\" + details)\n * and ENG-5969 replay (when enabled) re-pushes their payloads. Markers are NOT\n * rewritten (no counter, no mtime bump): the ENG-6160 pre-session exclusion in\n * `oldestLivePendingInboundMtimeMs` already keeps an untouched parked marker\n * out of the fresh session's wedge signal, and re-delivery stays bounded by\n * the existing machinery — the channel-side `replay_count` cap (≤3) and the\n * orphan sweep's received_at TTL. A manager-side rewrite would be the first\n * cross-process writer into marker files, where a torn read in the channel\n * sweep DELETES the marker (`unlinkSync` on parse failure) — the exact drop\n * this function exists to prevent.\n *\n * Only markers already flagged `undeliverable: true` are dead-lettered (moved\n * to `-stale`) — the channel already gave the user the ⏳ notice for those, so\n * nothing can ever drain them. Malformed markers are LEFT IN PLACE, matching\n * the live-scan philosophy above (a corrupt marker must never mask — or\n * become — a dropped message).\n *\n * The msteams dir is skipped wholesale: its top-level files are raw Bot\n * Framework activity payloads (the transport queue, not bookkeeping — real\n * markers live in the hidden `.markers/` subdir this scan never touches), and\n * the teams channel server's boot drain redelivers them to the fresh session\n * on its own. Moving them aside (what the pre-ENG-6289 dead-letter did) was\n * actively defeating the one channel with native respawn recovery.\n */\nexport interface ParkPendingInboundResult {\n parked: number;\n deadLettered: number;\n}\n\n/**\n * Move one marker into the sibling `-stale` dead-letter dir (moved, not\n * deleted — preserved for forensics). Returns true on success; best-effort —\n * a marker that vanished or can't move is left as-is.\n */\nfunction moveMarkerToStale(dir: string, deadDir: string, name: string): boolean {\n try {\n mkdirSync(deadDir, { recursive: true });\n renameSync(join(dir, name), join(deadDir, name));\n return true;\n } catch {\n return false;\n }\n}\n\nexport function parkPendingInbound(codeName: string, _now: Date = new Date()): ParkPendingInboundResult {\n const home = dirname(paneLogPath(codeName));\n const result: ParkPendingInboundResult = { parked: 0, deadLettered: 0 };\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return result;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n if (entry.name === 'msteams-pending-inbound') continue; // transport queue — boot drain owns recovery\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const undeliverable = isUndeliverableMarker(join(dir, file.name));\n if (undeliverable === null) continue; // drained mid-scan — already gone\n if (undeliverable) {\n if (moveMarkerToStale(dir, deadDir, file.name)) result.deadLettered++;\n } else {\n result.parked++;\n }\n }\n }\n return result;\n}\n\nexport function deadLetterPendingInbound(codeName: string, _now: Date = new Date()): number {\n const home = dirname(paneLogPath(codeName));\n let moved = 0;\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return 0;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n if (moveMarkerToStale(dir, deadDir, file.name)) moved++;\n }\n }\n return moved;\n}\n\n/**\n * Compute the pane.log age for each agent. Missing or unreadable\n * pane.log returns null — the caller should drop those entries\n * rather than fabricate a \"fresh\" or \"ancient\" value. A missing\n * file means the agent has never spawned in this manager generation,\n * which is a separate problem covered by SessionAliveAgeSeconds.\n */\nexport function collectResponsivenessProbes(\n codeNames: string[],\n now: Date = new Date(),\n): ResponsivenessProbeResult[] {\n const nowMs = now.getTime();\n const results: ResponsivenessProbeResult[] = [];\n for (const codeName of codeNames) {\n try {\n const panePath = paneLogPath(codeName);\n const mtimeMs = statSync(panePath).mtimeMs;\n const ageSeconds = Math.max(0, Math.floor((nowMs - mtimeMs) / 1000));\n const result: ResponsivenessProbeResult = {\n code_name: codeName,\n pane_activity_age_seconds: ageSeconds,\n };\n // ENG-6017: piggyback the pending-inbound drain-age scan on the same\n // cadence. Field omitted (not 0) when there are no markers.\n const oldestMarkerMs = oldestPendingInboundMtimeMs(dirname(panePath));\n if (oldestMarkerMs !== null) {\n result.pending_inbound_oldest_age_seconds = Math.max(\n 0,\n Math.floor((nowMs - oldestMarkerMs) / 1000),\n );\n }\n results.push(result);\n } catch {\n // No pane.log yet (fresh agent, never spawned) — skip. The\n // session-alive monitor already covers the \"should be running\n // but isn't\" case.\n }\n }\n return results;\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|