@integrity-labs/agt-cli 0.28.420 → 0.28.421
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-LLIMZG67.js → chunk-B4NX2T2H.js} +22 -1
- package/dist/{chunk-LLIMZG67.js.map → chunk-B4NX2T2H.js.map} +1 -1
- package/dist/{chunk-4D4VKZPK.js → chunk-HVVEMQWZ.js} +3 -3
- package/dist/{claude-pair-runtime-EDHQS7BP.js → claude-pair-runtime-ZTZR3VCN.js} +2 -2
- package/dist/lib/manager-worker.js +10 -10
- package/dist/mcp/direct-chat-channel.js +21 -0
- package/dist/mcp/origami.js +21 -0
- package/dist/mcp/slack-channel.js +128 -1
- package/dist/mcp/telegram-channel.js +21 -0
- package/dist/{persistent-session-YNQQHPFS.js → persistent-session-2OYYXMM7.js} +2 -2
- package/dist/{responsiveness-probe-KVEPIRMG.js → responsiveness-probe-7OOA57BP.js} +12 -3
- package/dist/responsiveness-probe-7OOA57BP.js.map +1 -0
- package/package.json +1 -1
- package/dist/responsiveness-probe-KVEPIRMG.js.map +0 -1
- /package/dist/{chunk-4D4VKZPK.js.map → chunk-HVVEMQWZ.js.map} +0 -0
- /package/dist/{claude-pair-runtime-EDHQS7BP.js.map → claude-pair-runtime-ZTZR3VCN.js.map} +0 -0
- /package/dist/{persistent-session-YNQQHPFS.js.map → persistent-session-2OYYXMM7.js.map} +0 -0
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
resolveConnectivityProbe,
|
|
26
26
|
worseConnectivityOutcome,
|
|
27
27
|
wrapScheduledTaskPrompt
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-B4NX2T2H.js";
|
|
29
29
|
import {
|
|
30
30
|
parsePsRows
|
|
31
31
|
} from "./chunk-XWVM4KPK.js";
|
|
@@ -6011,7 +6011,7 @@ function requireHost() {
|
|
|
6011
6011
|
}
|
|
6012
6012
|
|
|
6013
6013
|
// src/lib/api-client.ts
|
|
6014
|
-
var agtCliVersion = true ? "0.28.
|
|
6014
|
+
var agtCliVersion = true ? "0.28.421" : "dev";
|
|
6015
6015
|
var lastConfigHash = null;
|
|
6016
6016
|
function setConfigHash(hash) {
|
|
6017
6017
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -8515,4 +8515,4 @@ export {
|
|
|
8515
8515
|
managerInstallSystemUnitCommand,
|
|
8516
8516
|
managerUninstallSystemUnitCommand
|
|
8517
8517
|
};
|
|
8518
|
-
//# sourceMappingURL=chunk-
|
|
8518
|
+
//# sourceMappingURL=chunk-HVVEMQWZ.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-2OYYXMM7.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-ZTZR3VCN.js.map
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
resolveEffectivePinRaw,
|
|
46
46
|
safeWriteJsonAtomic,
|
|
47
47
|
setConfigHash
|
|
48
|
-
} from "../chunk-
|
|
48
|
+
} from "../chunk-HVVEMQWZ.js";
|
|
49
49
|
import {
|
|
50
50
|
getProjectDir as getProjectDir2,
|
|
51
51
|
getReadyTasks,
|
|
@@ -150,7 +150,7 @@ import {
|
|
|
150
150
|
toOpencodeModel,
|
|
151
151
|
transcriptActivityAgeSeconds,
|
|
152
152
|
writeEgressAllowlist
|
|
153
|
-
} from "../chunk-
|
|
153
|
+
} from "../chunk-B4NX2T2H.js";
|
|
154
154
|
import {
|
|
155
155
|
reapOrphanChannelMcps
|
|
156
156
|
} from "../chunk-XWVM4KPK.js";
|
|
@@ -9458,7 +9458,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
9458
9458
|
var lastVersionCheckAt = 0;
|
|
9459
9459
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
9460
9460
|
var lastResponsivenessProbeAt = 0;
|
|
9461
|
-
var agtCliVersion = true ? "0.28.
|
|
9461
|
+
var agtCliVersion = true ? "0.28.421" : "dev";
|
|
9462
9462
|
function resolveBrewPath(execFileSync2) {
|
|
9463
9463
|
try {
|
|
9464
9464
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -10606,7 +10606,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
10606
10606
|
if (codeNames.length === 0) return;
|
|
10607
10607
|
void (async () => {
|
|
10608
10608
|
try {
|
|
10609
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
10609
|
+
const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
|
|
10610
10610
|
await api.post("/host/heartbeat", {
|
|
10611
10611
|
host_id: hostId,
|
|
10612
10612
|
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor)
|
|
@@ -10713,7 +10713,7 @@ async function pollCycle() {
|
|
|
10713
10713
|
}
|
|
10714
10714
|
try {
|
|
10715
10715
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
10716
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
10716
|
+
const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
|
|
10717
10717
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
10718
10718
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor) : void 0;
|
|
10719
10719
|
let tailscaleHostname;
|
|
@@ -10827,7 +10827,7 @@ async function pollCycle() {
|
|
|
10827
10827
|
collectResponsivenessProbes,
|
|
10828
10828
|
collectPanelessActivityProbes,
|
|
10829
10829
|
getResponsivenessIntervalMs
|
|
10830
|
-
} = await import("../responsiveness-probe-
|
|
10830
|
+
} = await import("../responsiveness-probe-7OOA57BP.js");
|
|
10831
10831
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
10832
10832
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
10833
10833
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -10866,7 +10866,7 @@ async function pollCycle() {
|
|
|
10866
10866
|
collectResponsivenessProbes,
|
|
10867
10867
|
livePendingInboundOldestAgeSeconds,
|
|
10868
10868
|
parkPendingInbound
|
|
10869
|
-
} = await import("../responsiveness-probe-
|
|
10869
|
+
} = await import("../responsiveness-probe-7OOA57BP.js");
|
|
10870
10870
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-HYOOQV4A.js");
|
|
10871
10871
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
10872
10872
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -14177,7 +14177,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
14177
14177
|
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}`));
|
|
14178
14178
|
void (async () => {
|
|
14179
14179
|
try {
|
|
14180
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
14180
|
+
const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
|
|
14181
14181
|
await api.post("/host/heartbeat", {
|
|
14182
14182
|
host_id: hostId,
|
|
14183
14183
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor)
|
|
@@ -14227,7 +14227,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
14227
14227
|
}
|
|
14228
14228
|
try {
|
|
14229
14229
|
const hostId = await getHostId();
|
|
14230
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
14230
|
+
const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
|
|
14231
14231
|
await api.post("/host/heartbeat", {
|
|
14232
14232
|
host_id: hostId,
|
|
14233
14233
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor)
|
|
@@ -14680,7 +14680,7 @@ async function processClaudePairSessions(agents) {
|
|
|
14680
14680
|
killPairSession,
|
|
14681
14681
|
pairTmuxSession,
|
|
14682
14682
|
finalizeClaudePairOnboarding
|
|
14683
|
-
} = await import("../claude-pair-runtime-
|
|
14683
|
+
} = await import("../claude-pair-runtime-ZTZR3VCN.js");
|
|
14684
14684
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
14685
14685
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
14686
14686
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -34662,6 +34662,27 @@ var FLAG_REGISTRY = [
|
|
|
34662
34662
|
// change (ADR-0022 sensitive-flag confirm).
|
|
34663
34663
|
sensitive: true
|
|
34664
34664
|
},
|
|
34665
|
+
{
|
|
34666
|
+
key: "slack-scheduled-channel-guard",
|
|
34667
|
+
description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
|
|
34668
|
+
flagType: "enum",
|
|
34669
|
+
allowedValues: ["off", "shadow", "enforce"],
|
|
34670
|
+
// Ships in SHADOW, not off: the counter is the whole point - the fleet metric
|
|
34671
|
+
// is currently blind to this misroute, which is why it went unnoticed. shadow
|
|
34672
|
+
// is a free local log line + counter (no model spend, no behaviour change), so
|
|
34673
|
+
// it measures the fire rate immediately. enforce turns a misroute into a
|
|
34674
|
+
// REFUSED send, which trades a confidentiality risk for an availability one -
|
|
34675
|
+
// that is the audited per-org flip, taken after reading the shadow numbers and
|
|
34676
|
+
// after ENG-8143 shrinks the stale-marker window that dominates the false
|
|
34677
|
+
// positives.
|
|
34678
|
+
defaultValue: "shadow",
|
|
34679
|
+
// Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
|
|
34680
|
+
// resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
|
|
34681
|
+
envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
|
|
34682
|
+
// enforce REFUSES a send the agent asked to make - a visible availability
|
|
34683
|
+
// change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
|
|
34684
|
+
sensitive: true
|
|
34685
|
+
},
|
|
34665
34686
|
{
|
|
34666
34687
|
key: "slack-hot-thread-guard",
|
|
34667
34688
|
description: "Server-side hot-thread guard on the slack.reply surface (ENG-7462). Prevents an agent posting a NEW top-level Slack message when it meant to reply inside the thread it is already working in - a prompt/memory rule proved insufficient (the agent had the rule and still slipped). When a reply would otherwise post to channel ROOT (no thread_ts / message_ts / inbound_id, no active kanban card) and the agent has a recent active thread in that channel (its last bot-posted thread, from the persisted trackedThreads cache, within a freshness window), the reply is redirected into that thread. proactive:true no longer implies channel root; posting at root becomes a deliberate action (the to_channel_root flag, or the thread_ts:null sentinel). off = guard never runs, replies with no coords root exactly as today (ships dark). shadow = compute + log the would-redirect but STILL post to root (measure the fire rate before acting). enforce = apply the redirect (a soft-block: redirect + inform, never a hard rejection). Read live from the heartbeat flags-cache (or the env override); enforce is a deliberate per-org flip after a shadow soak.",
|
package/dist/mcp/origami.js
CHANGED
|
@@ -40278,6 +40278,27 @@ var FLAG_REGISTRY = [
|
|
|
40278
40278
|
// change (ADR-0022 sensitive-flag confirm).
|
|
40279
40279
|
sensitive: true
|
|
40280
40280
|
},
|
|
40281
|
+
{
|
|
40282
|
+
key: "slack-scheduled-channel-guard",
|
|
40283
|
+
description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
|
|
40284
|
+
flagType: "enum",
|
|
40285
|
+
allowedValues: ["off", "shadow", "enforce"],
|
|
40286
|
+
// Ships in SHADOW, not off: the counter is the whole point - the fleet metric
|
|
40287
|
+
// is currently blind to this misroute, which is why it went unnoticed. shadow
|
|
40288
|
+
// is a free local log line + counter (no model spend, no behaviour change), so
|
|
40289
|
+
// it measures the fire rate immediately. enforce turns a misroute into a
|
|
40290
|
+
// REFUSED send, which trades a confidentiality risk for an availability one -
|
|
40291
|
+
// that is the audited per-org flip, taken after reading the shadow numbers and
|
|
40292
|
+
// after ENG-8143 shrinks the stale-marker window that dominates the false
|
|
40293
|
+
// positives.
|
|
40294
|
+
defaultValue: "shadow",
|
|
40295
|
+
// Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
|
|
40296
|
+
// resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
|
|
40297
|
+
envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
|
|
40298
|
+
// enforce REFUSES a send the agent asked to make - a visible availability
|
|
40299
|
+
// change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
|
|
40300
|
+
sensitive: true
|
|
40301
|
+
},
|
|
40281
40302
|
{
|
|
40282
40303
|
key: "slack-hot-thread-guard",
|
|
40283
40304
|
description: "Server-side hot-thread guard on the slack.reply surface (ENG-7462). Prevents an agent posting a NEW top-level Slack message when it meant to reply inside the thread it is already working in - a prompt/memory rule proved insufficient (the agent had the rule and still slipped). When a reply would otherwise post to channel ROOT (no thread_ts / message_ts / inbound_id, no active kanban card) and the agent has a recent active thread in that channel (its last bot-posted thread, from the persisted trackedThreads cache, within a freshness window), the reply is redirected into that thread. proactive:true no longer implies channel root; posting at root becomes a deliberate action (the to_channel_root flag, or the thread_ts:null sentinel). off = guard never runs, replies with no coords root exactly as today (ships dark). shadow = compute + log the would-redirect but STILL post to root (measure the fire rate before acting). enforce = apply the redirect (a soft-block: redirect + inform, never a hard rejection). Read live from the heartbeat flags-cache (or the env override); enforce is a deliberate per-org flip after a shadow soak.",
|
|
@@ -30345,6 +30345,54 @@ function resolveSlackHotThreadMode(opts) {
|
|
|
30345
30345
|
if (cached2 !== void 0) return cached2;
|
|
30346
30346
|
return "off";
|
|
30347
30347
|
}
|
|
30348
|
+
var SLACK_SCHEDULED_CHANNEL_GUARD_FLAG_KEY = "slack-scheduled-channel-guard";
|
|
30349
|
+
function parseSlackScheduledChannelGuardMode(raw) {
|
|
30350
|
+
if (raw === void 0) return void 0;
|
|
30351
|
+
const v = raw.trim().toLowerCase();
|
|
30352
|
+
if (v === "") return void 0;
|
|
30353
|
+
if (v === "off" || v === "shadow" || v === "enforce") return v;
|
|
30354
|
+
const b = envBoolean(raw);
|
|
30355
|
+
if (b === true) return "enforce";
|
|
30356
|
+
if (b === false) return "off";
|
|
30357
|
+
return void 0;
|
|
30358
|
+
}
|
|
30359
|
+
function cachedSlackScheduledChannelGuardMode(path) {
|
|
30360
|
+
try {
|
|
30361
|
+
if (!existsSync(path)) return void 0;
|
|
30362
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
30363
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
30364
|
+
const flags = parsed.flags;
|
|
30365
|
+
if (!flags || typeof flags !== "object") return void 0;
|
|
30366
|
+
const value = flags[SLACK_SCHEDULED_CHANNEL_GUARD_FLAG_KEY];
|
|
30367
|
+
if (typeof value === "string") return parseSlackScheduledChannelGuardMode(value);
|
|
30368
|
+
if (typeof value === "boolean") return value ? "enforce" : "off";
|
|
30369
|
+
return void 0;
|
|
30370
|
+
} catch {
|
|
30371
|
+
return void 0;
|
|
30372
|
+
}
|
|
30373
|
+
}
|
|
30374
|
+
var SCHEDULED_CHANNEL_GUARD_ENFORCE_READY = false;
|
|
30375
|
+
var warnedEnforceClamped = false;
|
|
30376
|
+
function clampScheduledChannelGuardMode(mode) {
|
|
30377
|
+
if (mode !== "enforce" || SCHEDULED_CHANNEL_GUARD_ENFORCE_READY) return mode;
|
|
30378
|
+
if (!warnedEnforceClamped) {
|
|
30379
|
+
warnedEnforceClamped = true;
|
|
30380
|
+
process.stderr.write(
|
|
30381
|
+
"slack-channel: slack-scheduled-channel-guard=enforce requested but CLAMPED to shadow - enforce is blocked until ENG-8142 (the Stop hook treats a refused reply as a delivered one, so a blocked send would fail silently). Detection and counters still run.\n"
|
|
30382
|
+
);
|
|
30383
|
+
}
|
|
30384
|
+
return "shadow";
|
|
30385
|
+
}
|
|
30386
|
+
function resolveSlackScheduledChannelGuardMode(opts) {
|
|
30387
|
+
const env2 = opts?.env ?? process.env;
|
|
30388
|
+
const modeOverride = parseSlackScheduledChannelGuardMode(
|
|
30389
|
+
env2["AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE"]
|
|
30390
|
+
);
|
|
30391
|
+
if (modeOverride !== void 0) return clampScheduledChannelGuardMode(modeOverride);
|
|
30392
|
+
const cached2 = cachedSlackScheduledChannelGuardMode(opts?.cachePath ?? defaultFlagsCachePath());
|
|
30393
|
+
if (cached2 !== void 0) return clampScheduledChannelGuardMode(cached2);
|
|
30394
|
+
return "shadow";
|
|
30395
|
+
}
|
|
30348
30396
|
|
|
30349
30397
|
// src/maintenance-mode.ts
|
|
30350
30398
|
var FLAG_KEY = "platform-maintenance-mode";
|
|
@@ -34197,6 +34245,27 @@ var FLAG_REGISTRY = [
|
|
|
34197
34245
|
// change (ADR-0022 sensitive-flag confirm).
|
|
34198
34246
|
sensitive: true
|
|
34199
34247
|
},
|
|
34248
|
+
{
|
|
34249
|
+
key: "slack-scheduled-channel-guard",
|
|
34250
|
+
description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
|
|
34251
|
+
flagType: "enum",
|
|
34252
|
+
allowedValues: ["off", "shadow", "enforce"],
|
|
34253
|
+
// Ships in SHADOW, not off: the counter is the whole point - the fleet metric
|
|
34254
|
+
// is currently blind to this misroute, which is why it went unnoticed. shadow
|
|
34255
|
+
// is a free local log line + counter (no model spend, no behaviour change), so
|
|
34256
|
+
// it measures the fire rate immediately. enforce turns a misroute into a
|
|
34257
|
+
// REFUSED send, which trades a confidentiality risk for an availability one -
|
|
34258
|
+
// that is the audited per-org flip, taken after reading the shadow numbers and
|
|
34259
|
+
// after ENG-8143 shrinks the stale-marker window that dominates the false
|
|
34260
|
+
// positives.
|
|
34261
|
+
defaultValue: "shadow",
|
|
34262
|
+
// Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
|
|
34263
|
+
// resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
|
|
34264
|
+
envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
|
|
34265
|
+
// enforce REFUSES a send the agent asked to make - a visible availability
|
|
34266
|
+
// change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
|
|
34267
|
+
sensitive: true
|
|
34268
|
+
},
|
|
34200
34269
|
{
|
|
34201
34270
|
key: "slack-hot-thread-guard",
|
|
34202
34271
|
description: "Server-side hot-thread guard on the slack.reply surface (ENG-7462). Prevents an agent posting a NEW top-level Slack message when it meant to reply inside the thread it is already working in - a prompt/memory rule proved insufficient (the agent had the rule and still slipped). When a reply would otherwise post to channel ROOT (no thread_ts / message_ts / inbound_id, no active kanban card) and the agent has a recent active thread in that channel (its last bot-posted thread, from the persisted trackedThreads cache, within a freshness window), the reply is redirected into that thread. proactive:true no longer implies channel root; posting at root becomes a deliberate action (the to_channel_root flag, or the thread_ts:null sentinel). off = guard never runs, replies with no coords root exactly as today (ships dark). shadow = compute + log the would-redirect but STILL post to root (measure the fire rate before acting). enforce = apply the redirect (a soft-block: redirect + inform, never a hard rejection). Read live from the heartbeat flags-cache (or the env override); enforce is a deliberate per-org flip after a shadow soak.",
|
|
@@ -37598,6 +37667,8 @@ var REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX = "-reply-binding-classification
|
|
|
37598
37667
|
var UNKNOWN_INBOUND_ID_KEY = "unknown_inbound_id";
|
|
37599
37668
|
var CHANNEL_MISTARGET_CORRECTED_KEY = "channel_mistarget_corrected";
|
|
37600
37669
|
var DM_CHANNEL_MISTARGET_KEY = "dm_channel_mistarget";
|
|
37670
|
+
var SCHEDULED_CHANNEL_OVERRIDE_SHADOW_KEY = "scheduled_channel_override_shadow";
|
|
37671
|
+
var SCHEDULED_CHANNEL_OVERRIDE_BLOCKED_KEY = "scheduled_channel_override_blocked";
|
|
37601
37672
|
var VALID_MODES = /* @__PURE__ */ new Set(["shadow", "warn", "enforce"]);
|
|
37602
37673
|
function slackReplyBindingMode() {
|
|
37603
37674
|
const raw = process.env["AGT_SLACK_REPLY_BINDING"];
|
|
@@ -37622,6 +37693,22 @@ function recordReplyBindingClassification(agentDir, channel, input) {
|
|
|
37622
37693
|
} catch {
|
|
37623
37694
|
}
|
|
37624
37695
|
}
|
|
37696
|
+
function recordScheduledChannelOverride(agentDir, channel, input) {
|
|
37697
|
+
if (!agentDir) return;
|
|
37698
|
+
const path = join13(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
|
|
37699
|
+
let counts = {};
|
|
37700
|
+
try {
|
|
37701
|
+
const parsed = JSON.parse(readFileSync13(path, "utf-8"));
|
|
37702
|
+
if (parsed && typeof parsed === "object") counts = parsed;
|
|
37703
|
+
} catch {
|
|
37704
|
+
}
|
|
37705
|
+
const key2 = input.enforced ? SCHEDULED_CHANNEL_OVERRIDE_BLOCKED_KEY : SCHEDULED_CHANNEL_OVERRIDE_SHADOW_KEY;
|
|
37706
|
+
counts[key2] = (counts[key2] ?? 0) + 1;
|
|
37707
|
+
try {
|
|
37708
|
+
writeFileSync10(path, JSON.stringify(counts), { mode: 384 });
|
|
37709
|
+
} catch {
|
|
37710
|
+
}
|
|
37711
|
+
}
|
|
37625
37712
|
function recordChannelMistarget(agentDir, channel, input) {
|
|
37626
37713
|
if (!agentDir) return;
|
|
37627
37714
|
const path = join13(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
|
|
@@ -37750,6 +37837,13 @@ function clearScheduledTurnMarker(agentDir) {
|
|
|
37750
37837
|
}
|
|
37751
37838
|
|
|
37752
37839
|
// src/slack-reply-scheduled-priority.ts
|
|
37840
|
+
function scheduledDestOverridesNamedChannel(opts) {
|
|
37841
|
+
if (opts.inboundId || opts.enforceBound) return false;
|
|
37842
|
+
if (opts.channelGuardCorrected) return false;
|
|
37843
|
+
if (!opts.scheduledTargetChannel) return false;
|
|
37844
|
+
if (!opts.namedChannel) return false;
|
|
37845
|
+
return opts.scheduledTargetChannel !== opts.namedChannel;
|
|
37846
|
+
}
|
|
37753
37847
|
function resolveSlackScheduledPriority(opts) {
|
|
37754
37848
|
const inboundWins = !!(opts.inboundId || opts.enforceBound);
|
|
37755
37849
|
const effectiveChannel = inboundWins ? opts.bindingChannel ?? opts.fallbackChannel : opts.scheduledDest ? opts.scheduledDest.channel : opts.fallbackChannel;
|
|
@@ -41567,8 +41661,41 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
41567
41661
|
);
|
|
41568
41662
|
}
|
|
41569
41663
|
const enforceBound = bindingMode === "enforce" && (binding.classification === "bound" || binding.classification === "coords_corrected");
|
|
41664
|
+
const resolvedFallbackChannel = channelGuard.corrected && channelGuard.channel ? channelGuard.channel : channel;
|
|
41570
41665
|
const scheduledMarker = readScheduledTurnMarker(SLACK_AGENT_DIR);
|
|
41571
41666
|
const scheduledDest = scheduledMarker ? resolveScheduledTurnDestination(scheduledMarker, { channel, thread_ts }) : null;
|
|
41667
|
+
const scheduledChannelGuardMode = resolveSlackScheduledChannelGuardMode();
|
|
41668
|
+
const scheduledChannelOverride = scheduledChannelGuardMode !== "off" && scheduledDestOverridesNamedChannel({
|
|
41669
|
+
inboundId: inbound_id,
|
|
41670
|
+
enforceBound,
|
|
41671
|
+
// ENG-7542 resolved a registry-VERIFIED destination - it arbitrates like
|
|
41672
|
+
// enforceBound rather than being treated as just another named channel.
|
|
41673
|
+
channelGuardCorrected: channelGuard.corrected,
|
|
41674
|
+
// The marker's OWN target, not the resolved dest: the resolver echoes the
|
|
41675
|
+
// requested channel for a target-less marker, so comparing the resolved
|
|
41676
|
+
// value compared the raw arg against the corrected one.
|
|
41677
|
+
scheduledTargetChannel: scheduledMarker?.target?.channel_id ?? null,
|
|
41678
|
+
namedChannel: resolvedFallbackChannel
|
|
41679
|
+
});
|
|
41680
|
+
if (scheduledChannelOverride) {
|
|
41681
|
+
const enforced = scheduledChannelGuardMode === "enforce";
|
|
41682
|
+
recordScheduledChannelOverride(SLACK_AGENT_DIR, "slack", { enforced });
|
|
41683
|
+
process.stderr.write(
|
|
41684
|
+
`slack-channel(${AGENT_CODE_NAME}): scheduled_dest_channel_override_${enforced ? "blocked" : "shadow"} task=${scheduledMarker?.task_id ?? "?"} named=${redactSlackId(resolvedFallbackChannel)} task_dest=${redactSlackId(scheduledMarker?.target?.channel_id)}
|
|
41685
|
+
`
|
|
41686
|
+
);
|
|
41687
|
+
if (enforced) {
|
|
41688
|
+
return {
|
|
41689
|
+
content: [
|
|
41690
|
+
{
|
|
41691
|
+
type: "text",
|
|
41692
|
+
text: "This reply was blocked: a scheduled task is active and its destination channel is not the channel you named, so this message would have gone somewhere you did not ask for. If you are answering someone, send again with the inbound_id from their <channel> tag - that always wins over the task destination. If this is the scheduled task's own output, write it on the task card (kanban_update / kanban_done) instead of sending it - the card delivers it for you. If this is deliberate outreach that has nothing to do with the running task, it can be sent once the task finishes."
|
|
41693
|
+
}
|
|
41694
|
+
],
|
|
41695
|
+
isError: true
|
|
41696
|
+
};
|
|
41697
|
+
}
|
|
41698
|
+
}
|
|
41572
41699
|
if (scheduledDest?.retargeted) {
|
|
41573
41700
|
process.stderr.write(
|
|
41574
41701
|
`slack-channel(${AGENT_CODE_NAME}): scheduled_turn_retargeted task=${scheduledMarker?.task_id ?? "?"} requested=${redactSlackId(channel)}/${redactSlackId(thread_ts)} routed=${redactSlackId(scheduledDest.channel)}/${redactSlackId(scheduledDest.thread_ts)}
|
|
@@ -41634,7 +41761,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
41634
41761
|
scheduledDest: scheduledDest ?? null,
|
|
41635
41762
|
bindingChannel: binding.channel,
|
|
41636
41763
|
bindingThreadTs: binding.threadTs,
|
|
41637
|
-
fallbackChannel:
|
|
41764
|
+
fallbackChannel: resolvedFallbackChannel,
|
|
41638
41765
|
fallbackThreadTs: channelGuard.corrected ? channelGuard.threadTs : hotThreadMode === "enforce" && hotGuard ? hotGuard.threadTs : baseThreadTs,
|
|
41639
41766
|
fallbackSettleThreadTs: channelGuard.corrected ? channelGuard.threadTs : thread_ts
|
|
41640
41767
|
});
|
|
@@ -34547,6 +34547,27 @@ var FLAG_REGISTRY = [
|
|
|
34547
34547
|
// change (ADR-0022 sensitive-flag confirm).
|
|
34548
34548
|
sensitive: true
|
|
34549
34549
|
},
|
|
34550
|
+
{
|
|
34551
|
+
key: "slack-scheduled-channel-guard",
|
|
34552
|
+
description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
|
|
34553
|
+
flagType: "enum",
|
|
34554
|
+
allowedValues: ["off", "shadow", "enforce"],
|
|
34555
|
+
// Ships in SHADOW, not off: the counter is the whole point - the fleet metric
|
|
34556
|
+
// is currently blind to this misroute, which is why it went unnoticed. shadow
|
|
34557
|
+
// is a free local log line + counter (no model spend, no behaviour change), so
|
|
34558
|
+
// it measures the fire rate immediately. enforce turns a misroute into a
|
|
34559
|
+
// REFUSED send, which trades a confidentiality risk for an availability one -
|
|
34560
|
+
// that is the audited per-org flip, taken after reading the shadow numbers and
|
|
34561
|
+
// after ENG-8143 shrinks the stale-marker window that dominates the false
|
|
34562
|
+
// positives.
|
|
34563
|
+
defaultValue: "shadow",
|
|
34564
|
+
// Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
|
|
34565
|
+
// resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
|
|
34566
|
+
envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
|
|
34567
|
+
// enforce REFUSES a send the agent asked to make - a visible availability
|
|
34568
|
+
// change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
|
|
34569
|
+
sensitive: true
|
|
34570
|
+
},
|
|
34550
34571
|
{
|
|
34551
34572
|
key: "slack-hot-thread-guard",
|
|
34552
34573
|
description: "Server-side hot-thread guard on the slack.reply surface (ENG-7462). Prevents an agent posting a NEW top-level Slack message when it meant to reply inside the thread it is already working in - a prompt/memory rule proved insufficient (the agent had the rule and still slipped). When a reply would otherwise post to channel ROOT (no thread_ts / message_ts / inbound_id, no active kanban card) and the agent has a recent active thread in that channel (its last bot-posted thread, from the persisted trackedThreads cache, within a freshness window), the reply is redirected into that thread. proactive:true no longer implies channel root; posting at root becomes a deliberate action (the to_channel_root flag, or the thread_ts:null sentinel). off = guard never runs, replies with no coords root exactly as today (ships dark). shadow = compute + log the would-redirect but STILL post to root (measure the fire rate before acting). enforce = apply the redirect (a soft-block: redirect + inform, never a hard rejection). Read live from the heartbeat flags-cache (or the env override); enforce is a deliberate per-org flip after a shadow soak.",
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
writeDirectChatSessionState,
|
|
37
37
|
writeEgressAllowlist,
|
|
38
38
|
writePersistentClaudeWrapper
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-B4NX2T2H.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-2OYYXMM7.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paneLogPath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-B4NX2T2H.js";
|
|
4
4
|
import "./chunk-XWVM4KPK.js";
|
|
5
5
|
|
|
6
6
|
// src/lib/responsiveness-probe.ts
|
|
@@ -185,7 +185,16 @@ var KNOWN_REPLY_BINDING_KEYS = /* @__PURE__ */ new Set([
|
|
|
185
185
|
"delivery_ledger_self",
|
|
186
186
|
"delivery_ledger_moved_on",
|
|
187
187
|
"delivery_ledger_clear",
|
|
188
|
-
"delivery_ledger_fallback"
|
|
188
|
+
"delivery_ledger_fallback",
|
|
189
|
+
// ENG-8137: the scheduled-turn channel guard writes these into the same counter
|
|
190
|
+
// file (recordScheduledChannelOverride). `_shadow` counts detections while the
|
|
191
|
+
// flag observes without changing routing - the false-positive signal that gates
|
|
192
|
+
// the enforce flip; `_blocked` counts sends actually refused under enforce.
|
|
193
|
+
// Allowlisted so both reach CloudWatch: without them this guard would fire
|
|
194
|
+
// fleet-wide with no observable rate, which is the blindness that let the
|
|
195
|
+
// original misroute reach customers unnoticed.
|
|
196
|
+
"scheduled_channel_override_shadow",
|
|
197
|
+
"scheduled_channel_override_blocked"
|
|
189
198
|
]);
|
|
190
199
|
function readAndResetSlackReplyBindingClassifications(agentHomeDir) {
|
|
191
200
|
let names;
|
|
@@ -462,4 +471,4 @@ export {
|
|
|
462
471
|
readAndResetSlackReplyBindingClassifications,
|
|
463
472
|
readAndResetSlackReplyTargetClassifications
|
|
464
473
|
};
|
|
465
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
474
|
+
//# sourceMappingURL=responsiveness-probe-7OOA57BP.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-8090: what actually goes on the wire to `/host/responsiveness-probe`.\n *\n * Differs from `ResponsivenessProbeResult` in two ways, both because opencode\n * agents have no `pane.log`:\n *\n * - `pane_activity_age_seconds` is OPTIONAL. An opencode agent reports\n * occupancy and turn completion instead; the API tolerates its absence and\n * simply writes no `last_pane_activity_at` for that agent.\n * - two opencode-only fields carry the split signals.\n *\n * Kept as a separate type so `collectResponsivenessProbes` — whose result the\n * WEDGE detector consumes and which must keep requiring a real pane age — is not\n * widened. See `collectPanelessActivityProbes`.\n */\nexport interface ResponsivenessProbePayloadEntry\n extends Omit<ResponsivenessProbeResult, 'pane_activity_age_seconds'> {\n pane_activity_age_seconds?: number;\n /**\n * ENG-8090: seconds since the agent was last doing work — 0 while a turn is in\n * flight. The OCCUPANCY signal, and the substrate for busy/idle sampling and\n * agent-hours billing. Omitted when the manager has observed no turn for this\n * agent: absent is \"no signal\", never \"idle\".\n *\n * Deliberately NOT a liveness signal. A serve wedged mid-turn reports 0 here.\n */\n busy_activity_age_seconds?: number;\n /**\n * ENG-8090: seconds since a turn last produced real reply text. The LIVENESS\n * signal — \"turns are completing\", not \"the process printed something\" — and\n * the only one of the two that may resolve a synthetic probe. Omitted when no\n * turn has ever succeeded in this manager generation.\n */\n turn_completion_age_seconds?: 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 'replay_exhausted', // ENG-7736 - GC'd after replay pushes that never cleared (churn-killed)\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 // ENG-7806 (fm1): the inbound-delivery-ledger verdicts the Stop hook records for\n // each recover_*_for decision (self = this inbound already delivered; moved_on =\n // a different conversation was answered after this inbound; clear = ledger says\n // recover; fallback = no ledger data, used the transcript scan). Folded into the\n // same counter-file pipe so ledger coverage reaches CloudWatch as Classification\n // dimensions on the existing SlackReplyBindingClassifications metric.\n 'delivery_ledger_self',\n 'delivery_ledger_moved_on',\n 'delivery_ledger_clear',\n 'delivery_ledger_fallback',\n // ENG-8137: the scheduled-turn channel guard writes these into the same counter\n // file (recordScheduledChannelOverride). `_shadow` counts detections while the\n // flag observes without changing routing - the false-positive signal that gates\n // the enforce flip; `_blocked` counts sends actually refused under enforce.\n // Allowlisted so both reach CloudWatch: without them this guard would fire\n // fleet-wide with no observable rate, which is the blindness that let the\n // original misroute reach customers unnoticed.\n 'scheduled_channel_override_shadow',\n 'scheduled_channel_override_blocked',\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 attachSupplementalCounters(result, dirname(panePath), nowMs);\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 // ENG-8090: an OPENCODE agent lands here on every cycle, permanently —\n // its serve writes `opencode-serve.log`, so this stat always throws. That\n // is what `collectPanelessActivityProbes` exists to cover; this function\n // stays pane-only because the wedge detector consumes its output and must\n // never act on an agent it cannot read a pane age for.\n }\n }\n return results;\n}\n\n/**\n * ENG-8090: read + RESET the per-agent counter files that ride the probe's\n * cadence, and attach the ones that produced anything.\n *\n * Extracted from the loop above because these reads were inside the same `try`\n * as the `pane.log` stat: an agent with no `pane.log` lost not just its pane age\n * but its deflection, lane-classification and reply-binding counters too. For\n * opencode that meant those telemetry streams — including the ADR-0024\n * misclassification canary — were silently empty for the whole fleet, a wider\n * blast radius than the liveness gap ENG-8090 was filed for.\n *\n * The reads are DESTRUCTIVE (read-and-reset), so exactly one collector may run\n * them per agent per cycle. That is guaranteed structurally, not by convention:\n * `collectResponsivenessProbes` handles agents whose `pane.log` stat succeeds\n * and `collectPanelessActivityProbes` handles those whose stat fails, and the\n * two sets are complements of each other.\n */\nfunction attachSupplementalCounters(\n result: ResponsivenessProbeResult | ResponsivenessProbePayloadEntry,\n agentHome: string,\n nowMs: number,\n): void {\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(agentHome);\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(agentHome);\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(agentHome);\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(agentHome);\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(agentHome);\n if (replyBindingClassifications) {\n result.slack_reply_binding_classifications = replyBindingClassifications;\n }\n}\n\n/**\n * ENG-8090: activity probes for agents that have NO readable `pane.log`.\n *\n * The opencode runtime is the whole reason this exists. Its serve is a headless\n * HTTP server, not a tmux TUI, so there is no pane whose mtime tracks agent\n * work — and `opencode-serve.log`, the obvious substitute, is the wrong signal\n * twice over: it advances for HTTP-server reasons unrelated to any turn (so an\n * idle agent would bill as permanently busy), and it kept advancing all through\n * ENG-8058's deadlock (so a wedged agent would read alive). Both consumers are\n * fed from the turn lifecycle instead, via the two `*AgeSeconds` resolvers.\n *\n * Agents whose `pane.log` IS readable are skipped here and reported by\n * `collectResponsivenessProbes` — see the destructive-read note on\n * `attachSupplementalCounters`. A Claude Code agent that has simply never\n * spawned also has no `pane.log`; it lands here, both resolvers return null for\n * it, and it emits an entry carrying only whatever counters exist. That is a\n * strict improvement on today (those counters were dropped), and it writes no\n * activity timestamp, so it cannot make an unspawned agent look alive or busy.\n */\nexport function collectPanelessActivityProbes(\n codeNames: string[],\n resolvers: {\n /** Seconds since the agent was last occupied; 0 while a turn is in flight. */\n busyActivityAgeSeconds: (codeName: string) => number | null;\n /** Seconds since a turn last produced real reply text. */\n turnCompletionAgeSeconds: (codeName: string) => number | null;\n },\n now: Date = new Date(),\n): ResponsivenessProbePayloadEntry[] {\n const nowMs = now.getTime();\n const results: ResponsivenessProbePayloadEntry[] = [];\n for (const codeName of codeNames) {\n const panePath = paneLogPath(codeName);\n try {\n statSync(panePath);\n // Has a pane.log — `collectResponsivenessProbes` owns this agent, and\n // running the destructive counter reads here too would halve its counts.\n continue;\n } catch {\n // No pane.log — this collector owns it. Fall through.\n }\n const entry: ResponsivenessProbePayloadEntry = { code_name: codeName };\n attachSupplementalCounters(entry, dirname(panePath), nowMs);\n\n const busy = resolvers.busyActivityAgeSeconds(codeName);\n if (busy !== null && Number.isFinite(busy)) {\n entry.busy_activity_age_seconds = Math.max(0, Math.floor(busy));\n }\n const turn = resolvers.turnCompletionAgeSeconds(codeName);\n if (turn !== null && Number.isFinite(turn)) {\n entry.turn_completion_age_seconds = Math.max(0, Math.floor(turn));\n }\n // Nothing but a name. Sending it would have the API iterate a payload entry\n // that can produce no metric and no write — on every cycle, for every agent\n // that has never spawned. Absent is already the API's \"no signal\", so say\n // nothing rather than say nothing at length.\n if (Object.keys(entry).length === 1) continue;\n results.push(entry);\n }\n return results;\n}\n"],"mappings":";;;;;;AAmCA,SAAS,WAAW,aAAa,cAAc,YAAY,UAAU,kBAAkB;AACvF,SAAS,SAAS,YAAY;AAsF9B,IAAM,4BAA4B;AAClC,IAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AACF;AAgBO,SAAS,+BACd,cAC+B;AAC/B,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,YAAY;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAgC,CAAC;AACvC,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,yBAAyB,EAAG;AAC/C,UAAM,OAAO,KAAK,cAAc,IAAI;AACpC,UAAM,YAAY,GAAG,IAAI;AACzB,QAAI;AACF,iBAAW,MAAM,SAAS;AAAA,IAC5B,QAAQ;AACN;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC;AACzD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,mBAAW,SAAS,yBAAyB;AAC3C,gBAAM,IAAI,OAAO,KAAK;AACtB,cAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AACxD,kBAAM,KAAK,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK,MAAM,CAAC;AACjD,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,QAAI;AACF,iBAAW,SAAS;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,MAAM,QAAQ;AACvB;AAYA,IAAM,qCAAqC;AAC3C,IAAM,kCAAkC;AACxC,IAAM,cAAc,oBAAI,IAAI,CAAC,kBAAkB,aAAa,UAAU,CAAC;AACvE,IAAM,qBAAqB,oBAAI,IAAI,CAAC,SAAS,YAAY,WAAW,aAAa,CAAC;AAOlF,SAAS,6BAA6B,KAAsB;AAC1D,MAAI,QAAQ,gCAAiC,QAAO;AACpD,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,MAAM,cAAc,MAAM,IAAI;AACrC,SACE,YAAY,IAAI,IAAI,MACnB,iBAAiB,UAAU,iBAAiB,YAC7C,mBAAmB,IAAI,MAAM;AAEjC;AAUO,SAAS,uCACd,cAC+B;AAC/B,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,YAAY;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAgC,CAAC;AACvC,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,kCAAkC,EAAG;AACxD,UAAM,OAAO,KAAK,cAAc,IAAI;AACpC,UAAM,YAAY,GAAG,IAAI;AACzB,QAAI;AACF,iBAAW,MAAM,SAAS;AAAA,IAC5B,QAAQ;AACN;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC;AACzD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,cAAI,CAAC,6BAA6B,GAAG,EAAG;AACxC,cAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AAC9D,kBAAM,GAAG,KAAK,MAAM,GAAG,KAAK,KAAK,KAAK,MAAM,GAAG;AAC/C,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,QAAI;AACF,iBAAW,SAAS;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,MAAM,QAAQ;AACvB;AAYA,IAAM,6CAA6C;AACnD,IAAM,6BAA6B,oBAAI,IAAI;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,+BAA+B,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,OAAO,CAAC;AAOrE,SAAS,sBAAsB,KAAsB;AACnD,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,QAAQ,MAAM,IAAI;AACzB,SAAO,2BAA2B,IAAI,MAAM,KAAK,6BAA6B,IAAI,MAAM;AAC1F;AAUO,SAAS,4CACd,cAC+B;AAC/B,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,YAAY;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAgC,CAAC;AACvC,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,0CAA0C,EAAG;AAChE,UAAM,OAAO,KAAK,cAAc,IAAI;AACpC,UAAM,YAAY,GAAG,IAAI;AACzB,QAAI;AACF,iBAAW,MAAM,SAAS;AAAA,IAC5B,QAAQ;AACN;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC;AACzD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,cAAI,CAAC,sBAAsB,GAAG,EAAG;AACjC,cAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AAC9D,kBAAM,GAAG,KAAK,MAAM,GAAG,KAAK,KAAK,KAAK,MAAM,GAAG;AAC/C,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,QAAI;AACF,iBAAW,SAAS;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,MAAM,QAAQ;AACvB;AASA,IAAM,8CAA8C;AACpD,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA,EACA;AACF,CAAC;AASM,SAAS,6CACd,cAC+B;AAC/B,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,YAAY;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAgC,CAAC;AACvC,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,2CAA2C,EAAG;AACjE,UAAM,OAAO,KAAK,cAAc,IAAI;AACpC,UAAM,YAAY,GAAG,IAAI;AACzB,QAAI;AACF,iBAAW,MAAM,SAAS;AAAA,IAC5B,QAAQ;AACN;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC;AACzD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,cAAI,CAAC,yBAAyB,IAAI,GAAG,EAAG;AACxC,cAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AAC9D,kBAAM,GAAG,KAAK,MAAM,GAAG,KAAK,KAAK,KAAK,MAAM,GAAG;AAC/C,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,QAAI;AACF,iBAAW,SAAS;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,MAAM,QAAQ;AACvB;AAWA,IAAM,sBAAsB,KAAK;AAE1B,SAAS,8BAAsC;AACpD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,OAAO,SAAS,KAAK,EAAE;AACtC,SAAO,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS;AAC1D;AAoBA,SAAS,4BAA4B,cAAqC;AACxE,MAAI,SAAwB;AAC5B,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,cAAc,EAAE,eAAe,KAAK,CAAC;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,SAAS,kBAAkB,EAAG;AACtE,UAAM,MAAM,KAAK,cAAc,MAAM,IAAI;AACzC,QAAI;AACJ,QAAI;AACF,cAAQ,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAClD,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,OAAO,KAAK,KAAK,KAAK,WAAW,GAAG,EAAG;AACjD,UAAI;AACF,cAAM,UAAU,SAAS,KAAK,KAAK,KAAK,IAAI,CAAC,EAAE;AAC/C,YAAI,WAAW,QAAQ,UAAU,OAAQ,UAAS;AAAA,MACpD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAyBA,SAAS,gBAAgB,YAAsD;AAC7E,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC;AAI1D,WAAO;AAAA,MACL,eAAe,QAAQ,kBAAkB;AAAA,MACzC,eAAe,QAAQ,kBAAkB;AAAA,IAC3C;AAAA,EACF,SAAS,OAAO;AACd,WAAQ,MAAgC,SAAS,WAAW,OAAO;AAAA,EACrE;AACF;AAsBO,SAAS,gCACd,cACA,OAA2C,CAAC,GAC7B;AACf,QAAM,iBAAiB,KAAK,kBAAkB;AAC9C,MAAI,SAAwB;AAC5B,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,cAAc,EAAE,eAAe,KAAK,CAAC;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,SAAS,kBAAkB,EAAG;AACtE,UAAM,MAAM,KAAK,cAAc,MAAM,IAAI;AACzC,QAAI;AACJ,QAAI;AACF,cAAQ,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAClD,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,OAAO,KAAK,KAAK,KAAK,WAAW,GAAG,EAAG;AACjD,YAAM,OAAO,KAAK,KAAK,KAAK,IAAI;AAChC,UAAI;AACJ,UAAI;AACF,kBAAU,SAAS,IAAI,EAAE;AAAA,MAC3B,QAAQ;AACN;AAAA,MACF;AACA,UAAI,mBAAmB,QAAQ,UAAU,eAAgB;AACzD,YAAM,QAAQ,gBAAgB,IAAI;AAClC,UAAI,UAAU,KAAM;AACpB,UAAI,UAAU,gBAAgB,MAAM,iBAAiB,MAAM,eAAgB;AAC3E,UAAI,WAAW,QAAQ,UAAU,OAAQ,UAAS;AAAA,IACpD;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,mCACd,UACA,gBACA,MAAY,oBAAI,KAAK,GACN;AACf,QAAM,SAAS,gCAAgC,QAAQ,YAAY,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;AACjG,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,QAAQ,IAAI,UAAU,GAAI,CAAC;AAChE;AAkDA,SAAS,kBAAkB,KAAa,SAAiB,MAAuB;AAC9E,MAAI;AACF,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACtC,eAAW,KAAK,KAAK,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC;AAC/C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,UAAkB,OAAa,oBAAI,KAAK,GAA6B;AACtG,QAAM,OAAO,QAAQ,YAAY,QAAQ,CAAC;AAC1C,QAAM,SAAmC,EAAE,QAAQ,GAAG,cAAc,EAAE;AACtE,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,EACrD,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,SAAS,kBAAkB,EAAG;AACtE,QAAI,MAAM,SAAS,0BAA2B;AAC9C,UAAM,MAAM,KAAK,MAAM,MAAM,IAAI;AACjC,UAAM,UAAU,KAAK,MAAM,GAAG,MAAM,IAAI,QAAQ;AAChD,QAAI;AACJ,QAAI;AACF,cAAQ,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAClD,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,OAAO,KAAK,KAAK,KAAK,WAAW,GAAG,EAAG;AACjD,YAAM,QAAQ,gBAAgB,KAAK,KAAK,KAAK,IAAI,CAAC;AAClD,UAAI,UAAU,KAAM;AAKpB,UAAI,UAAU,eAAe,MAAM,eAAe;AAChD,YAAI,kBAAkB,KAAK,SAAS,KAAK,IAAI,EAAG,QAAO;AAAA,MACzD,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,yBAAyB,UAAkB,OAAa,oBAAI,KAAK,GAAW;AAC1F,QAAM,OAAO,QAAQ,YAAY,QAAQ,CAAC;AAC1C,MAAI,QAAQ;AACZ,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,EACrD,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,SAAS,kBAAkB,EAAG;AACtE,UAAM,MAAM,KAAK,MAAM,MAAM,IAAI;AACjC,UAAM,UAAU,KAAK,MAAM,GAAG,MAAM,IAAI,QAAQ;AAChD,QAAI;AACJ,QAAI;AACF,cAAQ,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAClD,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,OAAO,KAAK,KAAK,KAAK,WAAW,GAAG,EAAG;AACjD,UAAI,kBAAkB,KAAK,SAAS,KAAK,IAAI,EAAG;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,4BACd,WACA,MAAY,oBAAI,KAAK,GACQ;AAC7B,QAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAM,UAAuC,CAAC;AAC9C,aAAW,YAAY,WAAW;AAChC,QAAI;AACF,YAAM,WAAW,YAAY,QAAQ;AACrC,YAAM,UAAU,SAAS,QAAQ,EAAE;AACnC,YAAM,aAAa,KAAK,IAAI,GAAG,KAAK,OAAO,QAAQ,WAAW,GAAI,CAAC;AACnE,YAAM,SAAoC;AAAA,QACxC,WAAW;AAAA,QACX,2BAA2B;AAAA,MAC7B;AACA,iCAA2B,QAAQ,QAAQ,QAAQ,GAAG,KAAK;AAC3D,cAAQ,KAAK,MAAM;AAAA,IACrB,QAAQ;AAAA,IAUR;AAAA,EACF;AACA,SAAO;AACT;AAmBA,SAAS,2BACP,QACA,WACA,OACM;AAGN,QAAM,iBAAiB,4BAA4B,SAAS;AAC5D,MAAI,mBAAmB,MAAM;AAC3B,WAAO,qCAAqC,KAAK;AAAA,MAC/C;AAAA,MACA,KAAK,OAAO,QAAQ,kBAAkB,GAAI;AAAA,IAC5C;AAAA,EACF;AAIA,QAAM,cAAc,+BAA+B,SAAS;AAC5D,MAAI,aAAa;AACf,WAAO,cAAc;AAAA,EACvB;AAIA,QAAM,sBAAsB,uCAAuC,SAAS;AAC5E,MAAI,qBAAqB;AACvB,WAAO,uBAAuB;AAAA,EAChC;AAGA,QAAM,6BAA6B,4CAA4C,SAAS;AACxF,MAAI,4BAA4B;AAC9B,WAAO,qCAAqC;AAAA,EAC9C;AAIA,QAAM,8BAA8B,6CAA6C,SAAS;AAC1F,MAAI,6BAA6B;AAC/B,WAAO,sCAAsC;AAAA,EAC/C;AACF;AAqBO,SAAS,8BACd,WACA,WAMA,MAAY,oBAAI,KAAK,GACc;AACnC,QAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAM,UAA6C,CAAC;AACpD,aAAW,YAAY,WAAW;AAChC,UAAM,WAAW,YAAY,QAAQ;AACrC,QAAI;AACF,eAAS,QAAQ;AAGjB;AAAA,IACF,QAAQ;AAAA,IAER;AACA,UAAM,QAAyC,EAAE,WAAW,SAAS;AACrE,+BAA2B,OAAO,QAAQ,QAAQ,GAAG,KAAK;AAE1D,UAAM,OAAO,UAAU,uBAAuB,QAAQ;AACtD,QAAI,SAAS,QAAQ,OAAO,SAAS,IAAI,GAAG;AAC1C,YAAM,4BAA4B,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,IAChE;AACA,UAAM,OAAO,UAAU,yBAAyB,QAAQ;AACxD,QAAI,SAAS,QAAQ,OAAO,SAAS,IAAI,GAAG;AAC1C,YAAM,8BAA8B,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,IAClE;AAKA,QAAI,OAAO,KAAK,KAAK,EAAE,WAAW,EAAG;AACrC,YAAQ,KAAK,KAAK;AAAA,EACpB;AACA,SAAO;AACT;","names":[]}
|