@integrity-labs/agt-cli 0.28.301 → 0.28.303
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-E6L7SSE7.js → chunk-5WQATS6J.js} +68 -1
- package/dist/chunk-5WQATS6J.js.map +1 -0
- package/dist/{chunk-HRER2SCW.js → chunk-7LFB5DAW.js} +3 -3
- package/dist/{claude-pair-runtime-BEUY5L6C.js → claude-pair-runtime-FOFN74JS.js} +2 -2
- package/dist/lib/manager-worker.js +10 -10
- package/dist/mcp/origami.js +67 -0
- package/dist/mcp/slack-channel.js +111 -0
- package/dist/{persistent-session-HWVIMZAY.js → persistent-session-52DPSJGK.js} +2 -2
- package/dist/{responsiveness-probe-WFO4SRHV.js → responsiveness-probe-KXWV4FXD.js} +10 -3
- package/dist/responsiveness-probe-KXWV4FXD.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-E6L7SSE7.js.map +0 -1
- package/dist/responsiveness-probe-WFO4SRHV.js.map +0 -1
- /package/dist/{chunk-HRER2SCW.js.map → chunk-7LFB5DAW.js.map} +0 -0
- /package/dist/{claude-pair-runtime-BEUY5L6C.js.map → claude-pair-runtime-FOFN74JS.js.map} +0 -0
- /package/dist/{persistent-session-HWVIMZAY.js.map → persistent-session-52DPSJGK.js.map} +0 -0
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
resolveConnectivityProbe,
|
|
19
19
|
worseConnectivityOutcome,
|
|
20
20
|
wrapScheduledTaskPrompt
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-5WQATS6J.js";
|
|
22
22
|
import {
|
|
23
23
|
parsePsRows
|
|
24
24
|
} from "./chunk-XWVM4KPK.js";
|
|
@@ -5882,7 +5882,7 @@ function requireHost() {
|
|
|
5882
5882
|
}
|
|
5883
5883
|
|
|
5884
5884
|
// src/lib/api-client.ts
|
|
5885
|
-
var agtCliVersion = true ? "0.28.
|
|
5885
|
+
var agtCliVersion = true ? "0.28.303" : "dev";
|
|
5886
5886
|
var lastConfigHash = null;
|
|
5887
5887
|
function setConfigHash(hash) {
|
|
5888
5888
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -8181,4 +8181,4 @@ export {
|
|
|
8181
8181
|
managerInstallSystemUnitCommand,
|
|
8182
8182
|
managerUninstallSystemUnitCommand
|
|
8183
8183
|
};
|
|
8184
|
-
//# sourceMappingURL=chunk-
|
|
8184
|
+
//# sourceMappingURL=chunk-7LFB5DAW.js.map
|
|
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
|
|
|
100
100
|
return { ok: true };
|
|
101
101
|
} catch {
|
|
102
102
|
}
|
|
103
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
103
|
+
const { resolveClaudeBinary } = await import("./persistent-session-52DPSJGK.js");
|
|
104
104
|
const claudeBin = resolveClaudeBinary();
|
|
105
105
|
const pairEnv = {
|
|
106
106
|
...process.env,
|
|
@@ -373,4 +373,4 @@ export {
|
|
|
373
373
|
startClaudePair,
|
|
374
374
|
submitClaudePairCode
|
|
375
375
|
};
|
|
376
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
376
|
+
//# sourceMappingURL=claude-pair-runtime-FOFN74JS.js.map
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
requireHost,
|
|
40
40
|
safeWriteJsonAtomic,
|
|
41
41
|
setConfigHash
|
|
42
|
-
} from "../chunk-
|
|
42
|
+
} from "../chunk-7LFB5DAW.js";
|
|
43
43
|
import {
|
|
44
44
|
getProjectDir as getProjectDir2,
|
|
45
45
|
getReadyTasks,
|
|
@@ -127,7 +127,7 @@ import {
|
|
|
127
127
|
takeZombieDetection,
|
|
128
128
|
transcriptActivityAgeSeconds,
|
|
129
129
|
writeEgressAllowlist
|
|
130
|
-
} from "../chunk-
|
|
130
|
+
} from "../chunk-5WQATS6J.js";
|
|
131
131
|
import {
|
|
132
132
|
reapOrphanChannelMcps
|
|
133
133
|
} from "../chunk-XWVM4KPK.js";
|
|
@@ -6989,7 +6989,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
6989
6989
|
var lastVersionCheckAt = 0;
|
|
6990
6990
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
6991
6991
|
var lastResponsivenessProbeAt = 0;
|
|
6992
|
-
var agtCliVersion = true ? "0.28.
|
|
6992
|
+
var agtCliVersion = true ? "0.28.303" : "dev";
|
|
6993
6993
|
function resolveBrewPath(execFileSync2) {
|
|
6994
6994
|
try {
|
|
6995
6995
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -7844,7 +7844,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
7844
7844
|
if (codeNames.length === 0) return;
|
|
7845
7845
|
void (async () => {
|
|
7846
7846
|
try {
|
|
7847
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
7847
|
+
const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
|
|
7848
7848
|
await api.post("/host/heartbeat", {
|
|
7849
7849
|
host_id: hostId,
|
|
7850
7850
|
agent_diagnostics: collectDiagnostics(codeNames)
|
|
@@ -7942,7 +7942,7 @@ async function pollCycle() {
|
|
|
7942
7942
|
}
|
|
7943
7943
|
try {
|
|
7944
7944
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
7945
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
7945
|
+
const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
|
|
7946
7946
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
7947
7947
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
7948
7948
|
let tailscaleHostname;
|
|
@@ -8090,7 +8090,7 @@ async function pollCycle() {
|
|
|
8090
8090
|
const {
|
|
8091
8091
|
collectResponsivenessProbes,
|
|
8092
8092
|
getResponsivenessIntervalMs
|
|
8093
|
-
} = await import("../responsiveness-probe-
|
|
8093
|
+
} = await import("../responsiveness-probe-KXWV4FXD.js");
|
|
8094
8094
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
8095
8095
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
8096
8096
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -8122,7 +8122,7 @@ async function pollCycle() {
|
|
|
8122
8122
|
collectResponsivenessProbes,
|
|
8123
8123
|
livePendingInboundOldestAgeSeconds,
|
|
8124
8124
|
parkPendingInbound
|
|
8125
|
-
} = await import("../responsiveness-probe-
|
|
8125
|
+
} = await import("../responsiveness-probe-KXWV4FXD.js");
|
|
8126
8126
|
const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
|
|
8127
8127
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
8128
8128
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -11099,7 +11099,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
11099
11099
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
11100
11100
|
void (async () => {
|
|
11101
11101
|
try {
|
|
11102
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
11102
|
+
const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
|
|
11103
11103
|
await api.post("/host/heartbeat", {
|
|
11104
11104
|
host_id: hostId,
|
|
11105
11105
|
agent_diagnostics: collectDiagnostics([codeName])
|
|
@@ -11149,7 +11149,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
11149
11149
|
}
|
|
11150
11150
|
try {
|
|
11151
11151
|
const hostId = await getHostId();
|
|
11152
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
11152
|
+
const { collectDiagnostics } = await import("../persistent-session-52DPSJGK.js");
|
|
11153
11153
|
await api.post("/host/heartbeat", {
|
|
11154
11154
|
host_id: hostId,
|
|
11155
11155
|
agent_diagnostics: collectDiagnostics([codeName])
|
|
@@ -11554,7 +11554,7 @@ async function processClaudePairSessions(agents) {
|
|
|
11554
11554
|
killPairSession,
|
|
11555
11555
|
pairTmuxSession,
|
|
11556
11556
|
finalizeClaudePairOnboarding
|
|
11557
|
-
} = await import("../claude-pair-runtime-
|
|
11557
|
+
} = await import("../claude-pair-runtime-FOFN74JS.js");
|
|
11558
11558
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
11559
11559
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
11560
11560
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
package/dist/mcp/origami.js
CHANGED
|
@@ -39803,6 +39803,73 @@ var gdriveCommentsTriggerAdapter = {
|
|
|
39803
39803
|
};
|
|
39804
39804
|
registerTriggerSource(gdriveCommentsTriggerAdapter);
|
|
39805
39805
|
|
|
39806
|
+
// ../core/dist/triggers/adapters/video-render.js
|
|
39807
|
+
var VIDEO_RENDER_TRIGGER_PROVIDER = "video_render";
|
|
39808
|
+
function isFetcher(v) {
|
|
39809
|
+
return !!v && typeof v.fetchRenderState === "function";
|
|
39810
|
+
}
|
|
39811
|
+
function sanitizeCode(code) {
|
|
39812
|
+
if (!code)
|
|
39813
|
+
return null;
|
|
39814
|
+
const cleaned = code.trim().toLowerCase().replace(/[^a-z0-9_]/g, "").slice(0, 64);
|
|
39815
|
+
return cleaned || null;
|
|
39816
|
+
}
|
|
39817
|
+
function collectInstruction(config2) {
|
|
39818
|
+
return `Collect it now: call generate_video with ONLY {"request_id": "${config2.resume_handle}"} (do not resend the prompt - resubmitting starts a new billed render). The tool returns a short-lived download URL; download it to a file under your project directory and deliver it with your channel's upload_file tool.`;
|
|
39819
|
+
}
|
|
39820
|
+
async function poll(ctx, trigger) {
|
|
39821
|
+
const config2 = trigger.config;
|
|
39822
|
+
if (!config2.request_id || !config2.resume_handle) {
|
|
39823
|
+
throw new Error("video_render trigger config requires request_id and resume_handle");
|
|
39824
|
+
}
|
|
39825
|
+
if (!isFetcher(ctx.credentials)) {
|
|
39826
|
+
throw new Error("video_render poll requires a VideoRenderStatusFetcher via ctx.credentials");
|
|
39827
|
+
}
|
|
39828
|
+
const state = await ctx.credentials.fetchRenderState(config2.request_id);
|
|
39829
|
+
if (state.state === "pending") {
|
|
39830
|
+
return { events: [], cursor: ctx.cursor };
|
|
39831
|
+
}
|
|
39832
|
+
const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39833
|
+
const base = {
|
|
39834
|
+
provider: VIDEO_RENDER_TRIGGER_PROVIDER,
|
|
39835
|
+
occurredAt,
|
|
39836
|
+
// One event per render, ever - immutable identity, dedup-enforced.
|
|
39837
|
+
dedupKey: `vr:${config2.request_id}`,
|
|
39838
|
+
// Platform-originated content only (see module header) - renders the clean
|
|
39839
|
+
// un-fenced internal header instead of the external-content fence.
|
|
39840
|
+
sourceTrust: "internal",
|
|
39841
|
+
meaningful: true
|
|
39842
|
+
};
|
|
39843
|
+
let event;
|
|
39844
|
+
if (state.state === "done") {
|
|
39845
|
+
event = {
|
|
39846
|
+
...base,
|
|
39847
|
+
title: "Video render complete",
|
|
39848
|
+
body: `Your video render has finished. ${collectInstruction(config2)}`
|
|
39849
|
+
};
|
|
39850
|
+
} else if (state.state === "failed") {
|
|
39851
|
+
const code = sanitizeCode(state.code);
|
|
39852
|
+
event = {
|
|
39853
|
+
...base,
|
|
39854
|
+
title: "Video render failed",
|
|
39855
|
+
body: `Your video render failed provider-side${code ? ` (code: ${code})` : ""}. It was not delivered and cannot be collected. If the video is still needed, submit a new generate_video request (a fresh prompt - the old request_id is dead).`
|
|
39856
|
+
};
|
|
39857
|
+
} else {
|
|
39858
|
+
event = {
|
|
39859
|
+
...base,
|
|
39860
|
+
title: "Video render expired",
|
|
39861
|
+
body: `Your video render expired before it was collected (the provider no longer holds the output). If the video is still needed, submit a new generate_video request (a fresh prompt - the old request_id is dead).`
|
|
39862
|
+
};
|
|
39863
|
+
}
|
|
39864
|
+
return { events: [event], cursor: ctx.cursor, complete: true };
|
|
39865
|
+
}
|
|
39866
|
+
var videoRenderTriggerAdapter = {
|
|
39867
|
+
provider: VIDEO_RENDER_TRIGGER_PROVIDER,
|
|
39868
|
+
kind: "poll",
|
|
39869
|
+
poll
|
|
39870
|
+
};
|
|
39871
|
+
registerTriggerSource(videoRenderTriggerAdapter);
|
|
39872
|
+
|
|
39806
39873
|
// ../core/dist/feature-flags/registry.js
|
|
39807
39874
|
var FLAG_REGISTRY = [
|
|
39808
39875
|
{
|
|
@@ -15585,6 +15585,57 @@ async function fetchThreadTranscript(channel, threadTs, limit, deps) {
|
|
|
15585
15585
|
return { ok: true, count: allMessages.length, formatted: formatThreadMessages(allMessages, nameById) };
|
|
15586
15586
|
}
|
|
15587
15587
|
|
|
15588
|
+
// src/slack-channel-history.ts
|
|
15589
|
+
var SLACK_HISTORY_DEFAULT_LIMIT = 50;
|
|
15590
|
+
var SLACK_HISTORY_MAX_LIMIT = 200;
|
|
15591
|
+
async function fetchChannelHistory(channel, limit, deps, options = {}) {
|
|
15592
|
+
const { botToken, resolveUserName: resolveUserName2, fetchImpl = fetch } = deps;
|
|
15593
|
+
const cappedLimit = Math.min(Math.max(limit, 1), SLACK_HISTORY_MAX_LIMIT);
|
|
15594
|
+
const allMessages = [];
|
|
15595
|
+
let cursor;
|
|
15596
|
+
do {
|
|
15597
|
+
const remaining = cappedLimit - allMessages.length;
|
|
15598
|
+
if (remaining <= 0) break;
|
|
15599
|
+
const params = new URLSearchParams({
|
|
15600
|
+
channel,
|
|
15601
|
+
limit: String(Math.min(remaining, 100)),
|
|
15602
|
+
...options.oldest ? { oldest: options.oldest } : {},
|
|
15603
|
+
...options.latest ? { latest: options.latest } : {},
|
|
15604
|
+
...cursor ? { cursor } : {}
|
|
15605
|
+
});
|
|
15606
|
+
let data;
|
|
15607
|
+
try {
|
|
15608
|
+
const res = await fetchImpl(`https://slack.com/api/conversations.history?${params}`, {
|
|
15609
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
15610
|
+
});
|
|
15611
|
+
if (!res.ok) return { ok: false, error: `http_${res.status}` };
|
|
15612
|
+
data = await res.json();
|
|
15613
|
+
} catch (err) {
|
|
15614
|
+
return { ok: false, error: err instanceof Error ? err.message : "fetch_failed" };
|
|
15615
|
+
}
|
|
15616
|
+
if (!data.ok) return { ok: false, error: data.error ?? "unknown" };
|
|
15617
|
+
for (const msg of data.messages ?? []) {
|
|
15618
|
+
allMessages.push({
|
|
15619
|
+
user: msg.user ?? msg.bot_id ?? "unknown",
|
|
15620
|
+
text: msg.text ?? "",
|
|
15621
|
+
ts: msg.ts ?? ""
|
|
15622
|
+
});
|
|
15623
|
+
}
|
|
15624
|
+
cursor = data.response_metadata?.next_cursor || void 0;
|
|
15625
|
+
} while (cursor && allMessages.length < cappedLimit);
|
|
15626
|
+
allMessages.sort((a, b) => Number(a.ts) - Number(b.ts));
|
|
15627
|
+
const uniqueUserIds = [...new Set(allMessages.map((m) => m.user))];
|
|
15628
|
+
const resolved = await Promise.all(
|
|
15629
|
+
uniqueUserIds.map(async (id) => [id, await resolveUserName2(id)])
|
|
15630
|
+
);
|
|
15631
|
+
const nameById = new Map(resolved);
|
|
15632
|
+
return {
|
|
15633
|
+
ok: true,
|
|
15634
|
+
count: allMessages.length,
|
|
15635
|
+
formatted: formatThreadMessages(allMessages, nameById)
|
|
15636
|
+
};
|
|
15637
|
+
}
|
|
15638
|
+
|
|
15588
15639
|
// src/impersonation.ts
|
|
15589
15640
|
var ENV_VAR = "AGT_ACT_AS_AGENT_ID";
|
|
15590
15641
|
var OVERRIDE_ENV_VAR = "ENABLE_IMPERSONATION_CHANNELS";
|
|
@@ -20523,6 +20574,32 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
20523
20574
|
required: ["channel", "thread_ts"]
|
|
20524
20575
|
}
|
|
20525
20576
|
},
|
|
20577
|
+
{
|
|
20578
|
+
name: "slack.read_history",
|
|
20579
|
+
description: `Read recent top-level messages in a Slack channel the bot is a member of (via conversations.history). Use this to catch up on what was said in a channel \u2014 e.g. an auto-followed channel you have not been @-mentioned in, or to gather context before posting. This reads CHANNEL history; for one thread's replies use slack.read_thread instead. Optional oldest/latest bound the time window (Slack ts values). Returns messages oldest\u2192newest. The bot only sees channels it has been invited to \u2014 a "not_in_channel" error means someone must /invite the bot first.`,
|
|
20580
|
+
inputSchema: {
|
|
20581
|
+
type: "object",
|
|
20582
|
+
properties: {
|
|
20583
|
+
channel: {
|
|
20584
|
+
type: "string",
|
|
20585
|
+
description: "Slack channel ID to read history from (the bot must be a member of it)"
|
|
20586
|
+
},
|
|
20587
|
+
limit: {
|
|
20588
|
+
type: "number",
|
|
20589
|
+
description: "Max messages to return (default 50, max 200)"
|
|
20590
|
+
},
|
|
20591
|
+
oldest: {
|
|
20592
|
+
type: "string",
|
|
20593
|
+
description: 'Optional lower time bound \u2014 only messages AFTER this Slack ts (exclusive), e.g. "1783900000.000000".'
|
|
20594
|
+
},
|
|
20595
|
+
latest: {
|
|
20596
|
+
type: "string",
|
|
20597
|
+
description: "Optional upper time bound \u2014 only messages up to and including this Slack ts."
|
|
20598
|
+
}
|
|
20599
|
+
},
|
|
20600
|
+
required: ["channel"]
|
|
20601
|
+
}
|
|
20602
|
+
},
|
|
20526
20603
|
{
|
|
20527
20604
|
name: "slack.download_attachment",
|
|
20528
20605
|
description: "Download an inbound Slack attachment on demand. Use this for non-image files (PDFs, docx, csv, etc.) that arrived via a <channel> tag \u2014 look for `file_id` in the `files` meta. Requires the `channel` attribute from the same <channel> tag so the tool can verify the attachment was shared in the current conversation. Returns the local path where the file was written (inside ~/.augmented/<codeName>/slack-inbound/). Images are auto-downloaded on receipt and don't need this tool.",
|
|
@@ -21026,6 +21103,40 @@ ${result.formatted}` : "Thread is empty or not found."
|
|
|
21026
21103
|
};
|
|
21027
21104
|
}
|
|
21028
21105
|
}
|
|
21106
|
+
if (name === "slack.read_history") {
|
|
21107
|
+
const { channel, limit: rawLimit, oldest, latest } = args;
|
|
21108
|
+
const limit = Math.min(
|
|
21109
|
+
Math.max(rawLimit ?? SLACK_HISTORY_DEFAULT_LIMIT, 1),
|
|
21110
|
+
SLACK_HISTORY_MAX_LIMIT
|
|
21111
|
+
);
|
|
21112
|
+
try {
|
|
21113
|
+
const result = await fetchChannelHistory(
|
|
21114
|
+
channel,
|
|
21115
|
+
limit,
|
|
21116
|
+
{ botToken: BOT_TOKEN, resolveUserName },
|
|
21117
|
+
{ oldest, latest }
|
|
21118
|
+
);
|
|
21119
|
+
if (!result.ok) {
|
|
21120
|
+
return {
|
|
21121
|
+
content: [{ type: "text", text: `Slack error: ${result.error}` }],
|
|
21122
|
+
isError: true
|
|
21123
|
+
};
|
|
21124
|
+
}
|
|
21125
|
+
return {
|
|
21126
|
+
content: [{
|
|
21127
|
+
type: "text",
|
|
21128
|
+
text: result.count > 0 ? `Channel history (${result.count} messages, oldest\u2192newest):
|
|
21129
|
+
|
|
21130
|
+
${result.formatted}` : "No messages in range, or the bot is not a member of this channel."
|
|
21131
|
+
}]
|
|
21132
|
+
};
|
|
21133
|
+
} catch (err) {
|
|
21134
|
+
return {
|
|
21135
|
+
content: [{ type: "text", text: `Failed: ${err.message}` }],
|
|
21136
|
+
isError: true
|
|
21137
|
+
};
|
|
21138
|
+
}
|
|
21139
|
+
}
|
|
21029
21140
|
if (name === "slack.upload_file") {
|
|
21030
21141
|
const {
|
|
21031
21142
|
path,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
writeDirectChatSessionState,
|
|
37
37
|
writeEgressAllowlist,
|
|
38
38
|
writePersistentClaudeWrapper
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-5WQATS6J.js";
|
|
40
40
|
import "./chunk-XWVM4KPK.js";
|
|
41
41
|
export {
|
|
42
42
|
EGRESS_BASELINE_DOMAINS,
|
|
@@ -77,4 +77,4 @@ export {
|
|
|
77
77
|
writeEgressAllowlist,
|
|
78
78
|
writePersistentClaudeWrapper
|
|
79
79
|
};
|
|
80
|
-
//# sourceMappingURL=persistent-session-
|
|
80
|
+
//# sourceMappingURL=persistent-session-52DPSJGK.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paneLogPath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5WQATS6J.js";
|
|
4
4
|
import "./chunk-XWVM4KPK.js";
|
|
5
5
|
|
|
6
6
|
// src/lib/responsiveness-probe.ts
|
|
@@ -165,8 +165,15 @@ var KNOWN_REPLY_BINDING_KEYS = /* @__PURE__ */ new Set([
|
|
|
165
165
|
"proactive",
|
|
166
166
|
"unknown_target",
|
|
167
167
|
"legacy",
|
|
168
|
-
"unknown_inbound_id"
|
|
168
|
+
"unknown_inbound_id",
|
|
169
169
|
// reserved canary
|
|
170
|
+
// ENG-7716: the ENG-7542 DM-channel guard writes these into the same counter
|
|
171
|
+
// file. They are additional reserved keys (channel_mistarget_corrected counts
|
|
172
|
+
// every cross-channel rewrite; dm_channel_mistarget its DM subset), overlapping
|
|
173
|
+
// the main classifications the same way unknown_inbound_id does. Allowlisted so
|
|
174
|
+
// the DM->channel leak signal survives the read/reset fold and reaches CloudWatch.
|
|
175
|
+
"channel_mistarget_corrected",
|
|
176
|
+
"dm_channel_mistarget"
|
|
170
177
|
]);
|
|
171
178
|
function readAndResetSlackReplyBindingClassifications(agentHomeDir) {
|
|
172
179
|
let names;
|
|
@@ -418,4 +425,4 @@ export {
|
|
|
418
425
|
readAndResetSlackReplyBindingClassifications,
|
|
419
426
|
readAndResetSlackReplyTargetClassifications
|
|
420
427
|
};
|
|
421
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
428
|
+
//# sourceMappingURL=responsiveness-probe-KXWV4FXD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/responsiveness-probe.ts"],"sourcesContent":["/**\n * ENG-5399 — Tier 1 responsiveness probe (manager-side).\n *\n * Cheap, fast-cadence canary that catches \"agent went silent\" inside\n * minutes, well before the existing synthetic-probe cron's ~35 min\n * staleness window (`SyntheticReplyAgeSeconds`, ENG-5122).\n *\n * Mechanism: for each managed agent, read the mtime of the agent's\n * `pane.log` and report `now - mtime` as `PaneActivityAgeSeconds` via\n * a new `/host/responsiveness-probe` endpoint. `pane.log` is the\n * tmux pipe-pane sink set up by `setupPaneLog()` — any visible\n * activity (assistant turns, tool calls, in-place progress\n * heartbeats) bumps its mtime. A silent agent has a steadily\n * climbing age that lands in CloudWatch and trips a per-agent alarm.\n *\n * ENG-6017 adds a second per-agent signal on the same cadence:\n * `pending_inbound_oldest_age_seconds` — the age of the oldest marker\n * file across the agent's `*-pending-inbound/` directories (written by\n * the channel MCP servers for inbounds awaiting delivery). This is the\n * one artifact of the \"message typed but never submitted\" failure mode\n * that every other canary is blind to: in the koda incident\n * (2026-06-04) an operator Slack DM sat undelivered for 40+ minutes\n * while pane-activity stayed fresh (health checks), synthetic probes\n * were answered by the one-shot fallback, and heartbeat/session-alive\n * only reflect manager health. The field is OMITTED (not zero) when the\n * agent has no pending-inbound markers — the API treats absent as\n * \"no signal\", never as \"healthy\" (absent-vs-zero matters for\n * mixed-version fleets where old CLIs don't report it at all).\n *\n * Run from `pollCycle()` in `manager-worker.ts` on a configurable\n * interval (default 60s via `AUGMENTED_RESPONSIVENESS_INTERVAL_MS`;\n * lowered from 5 min by ENG-7550 - this post is what refreshes\n * `agents.last_pane_activity_at`, the minutely busy-sampling substrate).\n */\n\nimport { mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { paneLogPath } from './persistent-session.js';\n\nexport interface ResponsivenessProbeResult {\n code_name: string;\n pane_activity_age_seconds: number;\n /**\n * ENG-6017: age (s) of the oldest marker file across the agent's\n * `*-pending-inbound/` directories. Omitted when no markers exist —\n * absent means \"no signal\", NOT \"zero / healthy\".\n */\n pending_inbound_oldest_age_seconds?: number;\n /**\n * ENG-6327: per-cause inbound deflection counts since the last probe (read +\n * RESET from the channel servers' `<channel>-deflections.json` counter files).\n * Omitted when there were none. Keyed by DeflectionCause.\n */\n deflections?: Record<string, number>;\n /**\n * ENG-6408 / ADR-0024 Slice 1.5: shadow lane-classification counts since the\n * last probe (read + RESET from `<channel>-lane-classifications.json`). Keyed\n * by `<lane>|<expects_reply>|<source>` plus the reserved\n * `suspected_misclassification` canary. Omitted when there were none.\n */\n lane_classifications?: Record<string, number>;\n /**\n * WS0 (Slack cross-thread reply routing): SHADOW reply-target classification\n * counts since the last probe (read + RESET from\n * `<channel>-reply-target-classifications.json`). Keyed by\n * `<result>|<pending_threads_bucket>`. Omitted when there were none.\n */\n slack_reply_target_classifications?: Record<string, number>;\n /**\n * WS2 (Slack cross-thread reply routing): SHADOW reply-BINDING classification\n * counts since the last probe (read + RESET from\n * `<channel>-reply-binding-classifications.json`). Keyed by the binding\n * classification (bound / coords_corrected / ... / unknown_inbound_id).\n * Omitted when there were none.\n */\n slack_reply_binding_classifications?: Record<string, number>;\n}\n\n/**\n * ENG-6327 — per-channel deflection counter file-layout contract. The channel\n * MCP servers increment `<channel>-deflections.json` in the agent home (via\n * recordChannelDeflection in packages/mcp/ack-reaction.ts); apps/cli does not\n * depend on @integrity-labs/mcp, so the manager re-implements the read against\n * this contract — the same read-only, no-IPC posture as the `*-pending-inbound`\n * scan above. Body is a JSON object keyed by cause with integer counts.\n */\nconst DEFLECTION_COUNTER_SUFFIX = '-deflections.json';\nconst KNOWN_DEFLECTION_CAUSES = [\n 'integration_down',\n 'session_dead',\n 'wedged',\n 'busy',\n 'duplicate', // ENG-6270 — a redundant re-delivery dropped at fresh ingress\n 'replay_orphaned', // ENG-6355 — a replay-payload marker GC'd without ever being replayed\n 'unknown',\n] as const;\n\n/**\n * Read and RESET every `<channel>-deflections.json` in an agent home, returning\n * the summed counts by cause (or null when there are none). Consumes via atomic\n * rename-then-read-then-unlink so an increment racing the reset is carried into\n * the next window rather than lost. Only known causes are accumulated so a\n * corrupt file can't smuggle an unbounded dimension into CloudWatch.\n *\n * Reset-on-read is required: these are deltas-since-last-consume, so the metric\n * would over-count if they weren't cleared each cycle. The trade-off is that a\n * failed POST loses that cycle's counts (under-count) — acceptable for a\n * frequency metric and consistent with the other best-effort probe siblings;\n * unlike the in-memory give-up counter, file-based counts aren't re-credited\n * on POST failure.\n */\nexport function readAndResetChannelDeflections(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(DEFLECTION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming); // atomic; replaces any stale .consuming from a crashed cycle\n } catch {\n continue; // vanished / not a regular file — skip\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const cause of KNOWN_DEFLECTION_CAUSES) {\n const n = parsed[cause];\n if (typeof n === 'number' && Number.isFinite(n) && n > 0) {\n total[cause] = (total[cause] ?? 0) + Math.floor(n);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt — drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n/**\n * ENG-6408 / ADR-0024 Slice 1.5 - per-channel lane-classification counter\n * file-layout contract. The channel MCP servers increment\n * `<channel>-lane-classifications.json` in the agent home (via\n * recordLaneClassification in packages/mcp/inbound-lane-telemetry.ts); apps/cli\n * does not depend on @integrity-labs/mcp, so the manager re-implements the read\n * against this contract (same posture as the deflection scan above). Body is a\n * JSON object keyed by `<lane>|<expects_reply>|<source>` (plus the reserved\n * `suspected_misclassification`) with integer counts.\n */\nconst LANE_CLASSIFICATION_COUNTER_SUFFIX = '-lane-classifications.json';\nconst SUSPECTED_MISCLASSIFICATION_KEY = 'suspected_misclassification';\nconst KNOWN_LANES = new Set(['conversational', 'directive', 'liveness']);\nconst KNOWN_LANE_SOURCES = new Set(['slack', 'telegram', 'msteams', 'direct-chat']);\n\n/**\n * Only well-formed composite keys (and the misclassification canary) are\n * accumulated, so a corrupt counter file can't smuggle an unbounded dimension\n * into CloudWatch - the same bounding the deflection reader applies.\n */\nfunction isKnownLaneClassificationKey(key: string): boolean {\n if (key === SUSPECTED_MISCLASSIFICATION_KEY) return true;\n const parts = key.split('|');\n if (parts.length !== 3) return false;\n const [lane, expectsReply, source] = parts as [string, string, string];\n return (\n KNOWN_LANES.has(lane) &&\n (expectsReply === 'true' || expectsReply === 'false') &&\n KNOWN_LANE_SOURCES.has(source)\n );\n}\n\n/**\n * Read and RESET every `<channel>-lane-classifications.json` in an agent home,\n * returning the summed counts by key (or null when there are none). Mirrors\n * readAndResetChannelDeflections exactly: atomic rename-then-read-then-unlink so\n * an increment racing the reset carries into the next window, and only known\n * keys are accumulated. SHADOW telemetry - informs the Slice 2 gate, gates\n * nothing itself.\n */\nexport function readAndResetChannelLaneClassifications(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(LANE_CLASSIFICATION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming); // atomic; replaces any stale .consuming from a crashed cycle\n } catch {\n continue; // vanished / not a regular file — skip\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const [key, raw] of Object.entries(parsed)) {\n if (!isKnownLaneClassificationKey(key)) continue;\n if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {\n total[key] = (total[key] ?? 0) + Math.floor(raw);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt — drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n/**\n * WS0 (Slack cross-thread reply routing) - per-channel reply-target counter\n * file-layout contract. The channel MCP servers increment\n * `<channel>-reply-target-classifications.json` in the agent home (via\n * recordReplyTargetClassification in packages/mcp/slack-reply-target-telemetry.ts);\n * apps/cli does not depend on @integrity-labs/mcp, so the manager re-implements\n * the read against this contract (same posture as the lane-classification scan\n * above). Body is a JSON object keyed by `<result>|<pending_threads_bucket>`\n * with integer counts.\n */\nconst REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX = '-reply-target-classifications.json';\nconst KNOWN_REPLY_TARGET_RESULTS = new Set([\n 'matched_pending',\n 'matched_recent',\n 'crossed_candidate',\n 'unknown_target',\n]);\nconst KNOWN_PENDING_THREAD_BUCKETS = new Set(['0', '1', '2', '3plus']);\n\n/**\n * Only well-formed `<result>|<bucket>` keys are accumulated, so a corrupt counter\n * file can't smuggle an unbounded dimension into CloudWatch - the same bounding\n * the lane-classification / deflection readers apply.\n */\nfunction isKnownReplyTargetKey(key: string): boolean {\n const parts = key.split('|');\n if (parts.length !== 2) return false;\n const [result, bucket] = parts as [string, string];\n return KNOWN_REPLY_TARGET_RESULTS.has(result) && KNOWN_PENDING_THREAD_BUCKETS.has(bucket);\n}\n\n/**\n * Read and RESET every `<channel>-reply-target-classifications.json` in an agent\n * home, returning the summed counts by key (or null when there are none). Mirrors\n * readAndResetChannelLaneClassifications exactly: atomic\n * rename-then-read-then-unlink so an increment racing the reset carries into the\n * next window, and only known keys are accumulated. SHADOW telemetry - informs\n * the WS2 binding gate, gates nothing itself.\n */\nexport function readAndResetSlackReplyTargetClassifications(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming); // atomic; replaces any stale .consuming from a crashed cycle\n } catch {\n continue; // vanished / not a regular file - skip\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const [key, raw] of Object.entries(parsed)) {\n if (!isKnownReplyTargetKey(key)) continue;\n if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {\n total[key] = (total[key] ?? 0) + Math.floor(raw);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt - drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n// WS2 (Slack cross-thread reply routing) - per-channel reply-BINDING counter\n// file-layout contract. The slack MCP increments\n// `<channel>-reply-binding-classifications.json` (via\n// recordReplyBindingClassification in packages/mcp/slack-reply-binding-telemetry.ts);\n// apps/cli re-implements the read against this contract, same posture as the\n// reply-target scan above. Body is a JSON object keyed by the binding\n// classification (plus the reserved unknown_inbound_id canary) with integer counts.\nconst REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX = '-reply-binding-classifications.json';\nconst KNOWN_REPLY_BINDING_KEYS = new Set([\n 'bound',\n 'coords_corrected',\n 'coords_match',\n 'proactive',\n 'unknown_target',\n 'legacy',\n 'unknown_inbound_id', // reserved canary\n // ENG-7716: the ENG-7542 DM-channel guard writes these into the same counter\n // file. They are additional reserved keys (channel_mistarget_corrected counts\n // every cross-channel rewrite; dm_channel_mistarget its DM subset), overlapping\n // the main classifications the same way unknown_inbound_id does. Allowlisted so\n // the DM->channel leak signal survives the read/reset fold and reaches CloudWatch.\n 'channel_mistarget_corrected',\n 'dm_channel_mistarget',\n]);\n\n/**\n * Read and RESET every `<channel>-reply-binding-classifications.json` in an agent\n * home, returning the summed counts by classification (or null when there are\n * none). Mirrors readAndResetSlackReplyTargetClassifications; only known keys are\n * accumulated so a corrupt file can't add an unbounded CloudWatch dimension.\n * SHADOW telemetry - informs the enforce flip, gates nothing itself.\n */\nexport function readAndResetSlackReplyBindingClassifications(\n agentHomeDir: string,\n): Record<string, number> | null {\n let names: string[];\n try {\n names = readdirSync(agentHomeDir);\n } catch {\n return null;\n }\n const total: Record<string, number> = {};\n let any = false;\n for (const name of names) {\n if (!name.endsWith(REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX)) continue;\n const full = join(agentHomeDir, name);\n const consuming = `${full}.consuming`;\n try {\n renameSync(full, consuming);\n } catch {\n continue;\n }\n try {\n const parsed = JSON.parse(readFileSync(consuming, 'utf8')) as Record<string, unknown>;\n if (parsed && typeof parsed === 'object') {\n for (const [key, raw] of Object.entries(parsed)) {\n if (!KNOWN_REPLY_BINDING_KEYS.has(key)) continue;\n if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {\n total[key] = (total[key] ?? 0) + Math.floor(raw);\n any = true;\n }\n }\n }\n } catch {\n /* corrupt - drop it */\n }\n try {\n unlinkSync(consuming);\n } catch {\n /* non-fatal */\n }\n }\n return any ? total : null;\n}\n\n// ENG-7550: 60s default (was 5 min). The probe post is what refreshes\n// agents.last_pane_activity_at, so its cadence is the resolution ceiling for\n// minutely busy sampling (sample_agent_activity at p_window_seconds=60).\n// Sized before lowering: the per-agent CloudWatch alarms already run\n// Period:60/EvaluationPeriods:1 (built for 1-min datapoints), CloudWatch\n// bills per metric not per datapoint, and the 5x Lambda/API-GW invocation\n// increase is trivial at fleet scale. The reset-on-read delta counters\n// (deflections, lane/reply classifications) are cadence-agnostic - smaller\n// batches, same totals.\nconst DEFAULT_INTERVAL_MS = 60 * 1000;\n\nexport function getResponsivenessIntervalMs(): number {\n const raw = process.env.AUGMENTED_RESPONSIVENESS_INTERVAL_MS;\n if (!raw) return DEFAULT_INTERVAL_MS;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_INTERVAL_MS;\n}\n\n/**\n * ENG-6017: oldest pending-inbound marker mtime (ms epoch) for an agent,\n * or null when the agent has no markers / no pending-inbound dirs.\n *\n * The channel MCP servers (slack-channel, telegram-channel, …) write one\n * marker file per inbound into `~/.augmented/<codeName>/<channel>-pending-\n * inbound/` and clear it when the agent acknowledges the message. The\n * directory layout is the contract here — read-only, no IPC with the MCP\n * (the MCP and CLI release independently; file mtimes need no protocol).\n *\n * ENG-6072: only plain, non-hidden files count as markers. The msteams MCP\n * keeps `.markers/` and `.processed/` housekeeping SUBDIRECTORIES inside its\n * pending-inbound dir; their mtimes never advance, so statting every dirent\n * made the gauge climb forever and fired pending-inbound-stale on agents with\n * zero stranded messages (kylie ~3.4d / scout ~34h false ALARMs the moment\n * ENG-6023 activated the alarm). Dot-entries are skipped wholesale — the\n * hidden namespace is reserved for MCP bookkeeping, never for markers.\n */\nfunction oldestPendingInboundMtimeMs(agentHomeDir: string): number | null {\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null; // agent home missing — nothing to report\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n try {\n const mtimeMs = statSync(join(dir, file.name)).mtimeMs;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n } catch {\n // Marker drained between readdir and stat — that's the happy path.\n }\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160 / ENG-6319: read a marker's classification flags.\n * - `{...}` → parsed flags.\n * - `null` → vanished mid-scan (ENOENT) — drained between stat and\n * read, the happy path; callers exclude it.\n * - `'malformed'` → present but unreadable for another reason. The live\n * scan treats this as LIVE (a corrupt marker can never\n * mask a real wedge); park leaves it in place (a corrupt\n * marker must never become a dropped message).\n */\ninterface MarkerFlags {\n /** ENG-5846: dead-lettered by the channel (⏳-noticed, never drainable). */\n undeliverable: boolean;\n /**\n * ENG-6319: auto-followed participant-thread inbound the agent may\n * legitimately skip. Excluded from the wedge live-scan (a deliberate skip\n * must not read as \"failing to drain\" and force-respawn a healthy\n * session) but still parked across respawns — it may be a real\n * operator message awaiting a reply.\n */\n discretionary: boolean;\n}\n\nfunction readMarkerFlags(markerPath: string): MarkerFlags | null | 'malformed' {\n try {\n const parsed = JSON.parse(readFileSync(markerPath, 'utf8')) as {\n undeliverable?: unknown;\n discretionary?: unknown;\n };\n return {\n undeliverable: parsed?.undeliverable === true,\n discretionary: parsed?.discretionary === true,\n };\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'ENOENT' ? null : 'malformed';\n }\n}\n\n/**\n * ENG-6160: oldest *LIVE* pending-inbound marker mtime (ms epoch) for an agent,\n * or null when there is no live marker. \"Live\" excludes:\n *\n * - markers older than `sessionStartMs` — a marker written before the current\n * session started is a leftover from a PREVIOUS session and cannot mean\n * *this* session is failing to drain. This is the load-bearing exclusion:\n * without it, an orphan marker survives a fresh respawn and the wedge\n * detector re-fires forever on a healthy idle agent (the sherlock enforce\n * loop, 2026-06-08: `inboundAge=3389s` on a `● Ready.` session).\n * - markers flagged `undeliverable: true` — already dead-lettered by the channel.\n * - markers flagged `discretionary: true` (ENG-6319) — skippable auto-followed\n * participant-thread inbound; a deliberate skip must not force-respawn a\n * healthy session.\n *\n * Distinct from `oldestPendingInboundMtimeMs` (which counts ALL markers and\n * feeds the ENG-6017 `pending-inbound-stale` CloudWatch alarm — that alarm\n * *wants* to fire on a stuck inbound, so its semantics must NOT change). This\n * variant is wedge-detection-only.\n */\nexport function oldestLivePendingInboundMtimeMs(\n agentHomeDir: string,\n opts: { sessionStartMs?: number | null } = {},\n): number | null {\n const sessionStartMs = opts.sessionStartMs ?? null;\n let oldest: number | null = null;\n let entries;\n try {\n entries = readdirSync(agentHomeDir, { withFileTypes: true });\n } catch {\n return null;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(agentHomeDir, entry.name);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const full = join(dir, file.name);\n let mtimeMs: number;\n try {\n mtimeMs = statSync(full).mtimeMs;\n } catch {\n continue; // drained between readdir and stat — happy path\n }\n if (sessionStartMs !== null && mtimeMs < sessionStartMs) continue; // pre-session leftover\n const flags = readMarkerFlags(full);\n if (flags === null) continue; // vanished between stat and read — drained, exclude\n if (flags !== 'malformed' && (flags.undeliverable || flags.discretionary)) continue;\n if (oldest === null || mtimeMs < oldest) oldest = mtimeMs;\n }\n }\n return oldest;\n}\n\n/**\n * ENG-6160: age (s) of the oldest LIVE pending-inbound marker for an agent, or\n * null when none. The wedge detector uses this instead of the alarm-facing\n * `pending_inbound_oldest_age_seconds` so a stale/dead-letter marker can't\n * false-fire a respawn.\n */\nexport function livePendingInboundOldestAgeSeconds(\n codeName: string,\n sessionStartMs: number | null,\n now: Date = new Date(),\n): number | null {\n const oldest = oldestLivePendingInboundMtimeMs(dirname(paneLogPath(codeName)), { sessionStartMs });\n if (oldest === null) return null;\n return Math.max(0, Math.floor((now.getTime() - oldest) / 1000));\n}\n\n/**\n * ENG-6160: move every pending-inbound marker for an agent aside into a sibling\n * `<channel>-pending-inbound-stale/` directory (NOT silently deleted — the\n * payload pointer is preserved for forensics), returning the count moved.\n *\n * ENG-6289: no longer called on wedge respawn — the wedge path parks instead\n * (see `parkPendingInbound` above; blanket dead-letter permanently dropped the\n * user's message). Kept as the explicit \"move everything aside\" seam for tests\n * and operator emergencies. The stale dir does not end in `-pending-inbound`,\n * so neither the probe nor this scan re-counts moved markers.\n */\n/**\n * ENG-6289: park-not-drop. On a force-fresh wedge respawn, KEEP undrained\n * pending-inbound markers in their live dirs instead of dead-lettering them —\n * the fresh session's orient hook surfaces them (\"N queued messages\" + details)\n * and ENG-5969 replay (when enabled) re-pushes their payloads. Markers are NOT\n * rewritten (no counter, no mtime bump): the ENG-6160 pre-session exclusion in\n * `oldestLivePendingInboundMtimeMs` already keeps an untouched parked marker\n * out of the fresh session's wedge signal, and re-delivery stays bounded by\n * the existing machinery — the channel-side `replay_count` cap (≤3) and the\n * orphan sweep's received_at TTL. A manager-side rewrite would be the first\n * cross-process writer into marker files, where a torn read in the channel\n * sweep DELETES the marker (`unlinkSync` on parse failure) — the exact drop\n * this function exists to prevent.\n *\n * Only markers already flagged `undeliverable: true` are dead-lettered (moved\n * to `-stale`) — the channel already gave the user the ⏳ notice for those, so\n * nothing can ever drain them. Malformed markers are LEFT IN PLACE, matching\n * the live-scan philosophy above (a corrupt marker must never mask — or\n * become — a dropped message).\n *\n * The msteams dir is skipped wholesale: its top-level files are raw Bot\n * Framework activity payloads (the transport queue, not bookkeeping — real\n * markers live in the hidden `.markers/` subdir this scan never touches), and\n * the teams channel server's boot drain redelivers them to the fresh session\n * on its own. Moving them aside (what the pre-ENG-6289 dead-letter did) was\n * actively defeating the one channel with native respawn recovery.\n */\nexport interface ParkPendingInboundResult {\n parked: number;\n deadLettered: number;\n}\n\n/**\n * Move one marker into the sibling `-stale` dead-letter dir (moved, not\n * deleted — preserved for forensics). Returns true on success; best-effort —\n * a marker that vanished or can't move is left as-is.\n */\nfunction moveMarkerToStale(dir: string, deadDir: string, name: string): boolean {\n try {\n mkdirSync(deadDir, { recursive: true });\n renameSync(join(dir, name), join(deadDir, name));\n return true;\n } catch {\n return false;\n }\n}\n\nexport function parkPendingInbound(codeName: string, _now: Date = new Date()): ParkPendingInboundResult {\n const home = dirname(paneLogPath(codeName));\n const result: ParkPendingInboundResult = { parked: 0, deadLettered: 0 };\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return result;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n if (entry.name === 'msteams-pending-inbound') continue; // transport queue — boot drain owns recovery\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n const flags = readMarkerFlags(join(dir, file.name));\n if (flags === null) continue; // drained mid-scan — already gone\n // Only undeliverable markers dead-letter. Discretionary markers\n // (ENG-6319) PARK like engaged ones — they may be a real operator\n // message awaiting a reply (the live silent-loss class); malformed\n // markers park too (corrupt must never become a drop).\n if (flags !== 'malformed' && flags.undeliverable) {\n if (moveMarkerToStale(dir, deadDir, file.name)) result.deadLettered++;\n } else {\n result.parked++;\n }\n }\n }\n return result;\n}\n\nexport function deadLetterPendingInbound(codeName: string, _now: Date = new Date()): number {\n const home = dirname(paneLogPath(codeName));\n let moved = 0;\n let entries;\n try {\n entries = readdirSync(home, { withFileTypes: true });\n } catch {\n return 0;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith('-pending-inbound')) continue;\n const dir = join(home, entry.name);\n const deadDir = join(home, `${entry.name}-stale`);\n let files;\n try {\n files = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || file.name.startsWith('.')) continue;\n if (moveMarkerToStale(dir, deadDir, file.name)) moved++;\n }\n }\n return moved;\n}\n\n/**\n * Compute the pane.log age for each agent. Missing or unreadable\n * pane.log returns null — the caller should drop those entries\n * rather than fabricate a \"fresh\" or \"ancient\" value. A missing\n * file means the agent has never spawned in this manager generation,\n * which is a separate problem covered by SessionAliveAgeSeconds.\n */\nexport function collectResponsivenessProbes(\n codeNames: string[],\n now: Date = new Date(),\n): ResponsivenessProbeResult[] {\n const nowMs = now.getTime();\n const results: ResponsivenessProbeResult[] = [];\n for (const codeName of codeNames) {\n try {\n const panePath = paneLogPath(codeName);\n const mtimeMs = statSync(panePath).mtimeMs;\n const ageSeconds = Math.max(0, Math.floor((nowMs - mtimeMs) / 1000));\n const result: ResponsivenessProbeResult = {\n code_name: codeName,\n pane_activity_age_seconds: ageSeconds,\n };\n // ENG-6017: piggyback the pending-inbound drain-age scan on the same\n // cadence. Field omitted (not 0) when there are no markers.\n const oldestMarkerMs = oldestPendingInboundMtimeMs(dirname(panePath));\n if (oldestMarkerMs !== null) {\n result.pending_inbound_oldest_age_seconds = Math.max(\n 0,\n Math.floor((nowMs - oldestMarkerMs) / 1000),\n );\n }\n // ENG-6327: read + RESET the per-channel deflection counters on the same\n // cadence so each cause's frequency reaches CloudWatch (ChannelDeflections,\n // Cause dimension). Omitted when there were none this window.\n const deflections = readAndResetChannelDeflections(dirname(panePath));\n if (deflections) {\n result.deflections = deflections;\n }\n // ENG-6408 / ADR-0024 Slice 1.5: read + RESET the shadow lane-classification\n // counters on the same cadence so the classification mix (and the\n // misclassification canary) reaches CloudWatch. Omitted when none this window.\n const laneClassifications = readAndResetChannelLaneClassifications(dirname(panePath));\n if (laneClassifications) {\n result.lane_classifications = laneClassifications;\n }\n // WS0: read + RESET the shadow reply-target counters on the same cadence so\n // the cross-thread classification mix reaches CloudWatch. Omitted when none.\n const replyTargetClassifications = readAndResetSlackReplyTargetClassifications(\n dirname(panePath),\n );\n if (replyTargetClassifications) {\n result.slack_reply_target_classifications = replyTargetClassifications;\n }\n // WS2: read + RESET the shadow reply-binding counters on the same cadence\n // so the bound / coords_corrected / unknown_target mix (the enforce-flip\n // gate) reaches CloudWatch. Omitted when none this window.\n const replyBindingClassifications = readAndResetSlackReplyBindingClassifications(\n dirname(panePath),\n );\n if (replyBindingClassifications) {\n result.slack_reply_binding_classifications = replyBindingClassifications;\n }\n results.push(result);\n } catch {\n // No pane.log yet (fresh agent, never spawned) — skip. The\n // session-alive monitor already covers the \"should be running\n // but isn't\" case.\n }\n }\n return results;\n}\n"],"mappings":"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","names":[]}
|