@integrity-labs/agt-cli 0.28.1021 → 0.28.1023
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 +5 -5
- package/dist/{chunk-X7SX25N6.js → chunk-BHKFVSIX.js} +10 -8
- package/dist/{chunk-X7SX25N6.js.map → chunk-BHKFVSIX.js.map} +1 -1
- package/dist/{chunk-SGAW3XWQ.js → chunk-NQB4AF4Q.js} +4 -4
- package/dist/{chunk-CVQDMHOP.js → chunk-RDQILPRF.js} +22 -2
- package/dist/{chunk-CVQDMHOP.js.map → chunk-RDQILPRF.js.map} +1 -1
- package/dist/{claude-pair-runtime-JQRJXNSL.js → claude-pair-runtime-C4DDKQ6N.js} +2 -2
- package/dist/lib/manager-worker.js +278 -15
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +61 -10
- package/dist/mcp/index.js +9 -7
- package/dist/mcp/origami.js +9 -7
- package/dist/mcp/slack-channel.js +35 -10
- package/dist/mcp/telegram-channel.js +35 -10
- package/dist/{persistent-session-UZBB7EOS.js → persistent-session-3NQHNU6H.js} +3 -3
- package/dist/{responsiveness-probe-KLOUFBM5.js → responsiveness-probe-ZOXVXDPT.js} +3 -3
- package/dist/{session-auth-dead-JJUKS75B.js → session-auth-dead-ZKC2HH6T.js} +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-SGAW3XWQ.js.map → chunk-NQB4AF4Q.js.map} +0 -0
- /package/dist/{claude-pair-runtime-JQRJXNSL.js.map → claude-pair-runtime-C4DDKQ6N.js.map} +0 -0
- /package/dist/{persistent-session-UZBB7EOS.js.map → persistent-session-3NQHNU6H.js.map} +0 -0
- /package/dist/{responsiveness-probe-KLOUFBM5.js.map → responsiveness-probe-ZOXVXDPT.js.map} +0 -0
- /package/dist/{session-auth-dead-JJUKS75B.js.map → session-auth-dead-ZKC2HH6T.js.map} +0 -0
|
@@ -266,7 +266,7 @@ async function spawnPairSession(session) {
|
|
|
266
266
|
return { ok: true };
|
|
267
267
|
} catch {
|
|
268
268
|
}
|
|
269
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
269
|
+
const { resolveClaudeBinary } = await import("./persistent-session-3NQHNU6H.js");
|
|
270
270
|
const claudeBin = resolveClaudeBinary();
|
|
271
271
|
const pairEnv = {
|
|
272
272
|
...process.env,
|
|
@@ -734,4 +734,4 @@ export {
|
|
|
734
734
|
startClaudePair,
|
|
735
735
|
submitClaudePairCode
|
|
736
736
|
};
|
|
737
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
737
|
+
//# sourceMappingURL=claude-pair-runtime-C4DDKQ6N.js.map
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
safeWriteJsonAtomic,
|
|
67
67
|
setConfigHash,
|
|
68
68
|
tripClass
|
|
69
|
-
} from "../chunk-
|
|
69
|
+
} from "../chunk-NQB4AF4Q.js";
|
|
70
70
|
import {
|
|
71
71
|
getProjectDir as getProjectDir2,
|
|
72
72
|
getReadyTasks,
|
|
@@ -118,6 +118,8 @@ import {
|
|
|
118
118
|
parseModelPolicy,
|
|
119
119
|
policyResolvedModel,
|
|
120
120
|
prepareForRespawn,
|
|
121
|
+
probeClaudeProcessInTmux,
|
|
122
|
+
probeTmuxSession,
|
|
121
123
|
readChannelServerEnv,
|
|
122
124
|
readDirectChatSessionState,
|
|
123
125
|
readOpencodePaneLogTail,
|
|
@@ -147,7 +149,7 @@ import {
|
|
|
147
149
|
takeZombieDetection,
|
|
148
150
|
toOpencodeModel,
|
|
149
151
|
writeEgressAllowlist
|
|
150
|
-
} from "../chunk-
|
|
152
|
+
} from "../chunk-RDQILPRF.js";
|
|
151
153
|
import {
|
|
152
154
|
ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
|
|
153
155
|
AnchorSessionClient,
|
|
@@ -252,8 +254,10 @@ import {
|
|
|
252
254
|
subagentActivityAgeSeconds,
|
|
253
255
|
sumTranscriptUsageInWindow,
|
|
254
256
|
transcriptActivityAgeSeconds
|
|
255
|
-
} from "../chunk-
|
|
256
|
-
import
|
|
257
|
+
} from "../chunk-BHKFVSIX.js";
|
|
258
|
+
import {
|
|
259
|
+
classifyUnanswerablePane
|
|
260
|
+
} from "../chunk-DHWNVVX4.js";
|
|
257
261
|
import {
|
|
258
262
|
reapOrphanChannelMcps
|
|
259
263
|
} from "../chunk-XWVM4KPK.js";
|
|
@@ -9384,6 +9388,136 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
9384
9388
|
}
|
|
9385
9389
|
}
|
|
9386
9390
|
|
|
9391
|
+
// src/lib/blocked-prompt-monitor.ts
|
|
9392
|
+
function observeBlockedPrompt(codeName, hasSession, capture) {
|
|
9393
|
+
if (!hasSession) return { state: "unknown", reason: "no-session" };
|
|
9394
|
+
let pane;
|
|
9395
|
+
try {
|
|
9396
|
+
pane = capture(codeName);
|
|
9397
|
+
} catch {
|
|
9398
|
+
return { state: "unknown", reason: "pane-unreadable" };
|
|
9399
|
+
}
|
|
9400
|
+
if (pane === null) return { state: "unknown", reason: "pane-unreadable" };
|
|
9401
|
+
const kind = classifyUnanswerablePane(pane);
|
|
9402
|
+
return kind ? { state: "blocked", kind } : { state: "clear" };
|
|
9403
|
+
}
|
|
9404
|
+
function carriedRecoveryFor(state9, pending2, codeName) {
|
|
9405
|
+
return state9 === "clear" ? pending2.get(codeName) : void 0;
|
|
9406
|
+
}
|
|
9407
|
+
function foldPendingRecovery(cost, carried) {
|
|
9408
|
+
const total = carried ? {
|
|
9409
|
+
tasksDeferred: cost.tasksDeferred + carried.tasksDeferred,
|
|
9410
|
+
messagesWithheld: cost.messagesWithheld + carried.messagesWithheld
|
|
9411
|
+
} : cost;
|
|
9412
|
+
return { total, hasCost: total.tasksDeferred > 0 || total.messagesWithheld > 0 };
|
|
9413
|
+
}
|
|
9414
|
+
function settlePendingRecovery(pending2, unreported, disposition) {
|
|
9415
|
+
const retain = disposition === "withheld";
|
|
9416
|
+
for (const [codeName, cost] of unreported) {
|
|
9417
|
+
if (retain) pending2.set(codeName, cost);
|
|
9418
|
+
else pending2.delete(codeName);
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
var BlockedPromptCostLedger = class {
|
|
9422
|
+
/**
|
|
9423
|
+
* DISTINCT ITEMS, NOT A RUNNING SUM (obi-wan `0d2a571b2181` on #6329, MAJOR).
|
|
9424
|
+
*
|
|
9425
|
+
* This held two integers and added to them on every call. Both accrual sites
|
|
9426
|
+
* run once per ~10s poll over the SAME items:
|
|
9427
|
+
*
|
|
9428
|
+
* - a scheduled-task deferral deliberately does NOT advance `nextFireAt`
|
|
9429
|
+
* (that is what makes the task fire once the prompt is answered instead
|
|
9430
|
+
* of backlogging), so `getReadyTasks` returns the same task every tick;
|
|
9431
|
+
* - a withheld direct-chat message is left PENDING for durable replay, so
|
|
9432
|
+
* `/host/direct-chat/poll` returns the same message id every tick.
|
|
9433
|
+
*
|
|
9434
|
+
* So the counters were multiplied by the number of polls the block lasted.
|
|
9435
|
+
* One un-fired task on an agent blocked for the two days the ticket describes
|
|
9436
|
+
* yields 2 days / 10s = ~17,280, and the alert renders "17280 scheduled
|
|
9437
|
+
* task(s) not fired". The existing log lines are suppressed once-per-episode
|
|
9438
|
+
* precisely BECAUSE they repeat; the counters sat outside that suppression
|
|
9439
|
+
* and inherited the repetition instead.
|
|
9440
|
+
*
|
|
9441
|
+
* The whole argument for these numbers - "4 agents blocked, 9 scheduled tasks
|
|
9442
|
+
* not fired is what makes someone act" - depends on them being counts of
|
|
9443
|
+
* DISTINCT items. A number that grows with wall-clock time is not a cost, it
|
|
9444
|
+
* is a clock, and an operator who spotted that would stop trusting the rest
|
|
9445
|
+
* of the alert.
|
|
9446
|
+
*
|
|
9447
|
+
* Sets of ids make re-noting the same item idempotent, so correctness no
|
|
9448
|
+
* longer depends on the caller noticing it is in a poll loop.
|
|
9449
|
+
*/
|
|
9450
|
+
byAgent = /* @__PURE__ */ new Map();
|
|
9451
|
+
episode(codeName) {
|
|
9452
|
+
let e = this.byAgent.get(codeName);
|
|
9453
|
+
if (!e) {
|
|
9454
|
+
e = { tasks: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Set() };
|
|
9455
|
+
this.byAgent.set(codeName, e);
|
|
9456
|
+
}
|
|
9457
|
+
return e;
|
|
9458
|
+
}
|
|
9459
|
+
/**
|
|
9460
|
+
* Record scheduled tasks this episode has declined to fire, by task id.
|
|
9461
|
+
*
|
|
9462
|
+
* Called with the whole ready set each poll; re-noting an id already in the
|
|
9463
|
+
* episode is a no-op, which is the point.
|
|
9464
|
+
*/
|
|
9465
|
+
noteTasksDeferred(codeName, taskIds) {
|
|
9466
|
+
const e = this.episode(codeName);
|
|
9467
|
+
for (const id of taskIds) e.tasks.add(id);
|
|
9468
|
+
}
|
|
9469
|
+
/** Record one withheld direct-chat message, by message id. Idempotent. */
|
|
9470
|
+
noteMessageWithheld(codeName, messageId) {
|
|
9471
|
+
this.episode(codeName).messages.add(messageId);
|
|
9472
|
+
}
|
|
9473
|
+
costOf(codeName) {
|
|
9474
|
+
const e = this.byAgent.get(codeName);
|
|
9475
|
+
return { tasksDeferred: e?.tasks.size ?? 0, messagesWithheld: e?.messages.size ?? 0 };
|
|
9476
|
+
}
|
|
9477
|
+
/**
|
|
9478
|
+
* Settle this sweep: return the cost to report, and the observation to report
|
|
9479
|
+
* it WITH.
|
|
9480
|
+
*
|
|
9481
|
+
* A `clear` ends the episode, so the running total is cleared AFTER being
|
|
9482
|
+
* read — the poll that observes recovery still reports what the episode cost.
|
|
9483
|
+
* When that total is nonzero the observation is marked `recovering`, which is
|
|
9484
|
+
* what licenses the rollup to count those counters at all: a plain clear's
|
|
9485
|
+
* cost is ignored.
|
|
9486
|
+
*
|
|
9487
|
+
* IT IS NOT AN ANTI-TAMPERING GUARD, and this docblock used to say it was
|
|
9488
|
+
* (obi-wan `579d2c059673`). Both the core module and the route retract that
|
|
9489
|
+
* claim explicitly IN THIS SAME PR - `{state:'clear', recovering:true,
|
|
9490
|
+
* tasks_deferred:N}` is a shape any caller can send, so the flag distinguishes
|
|
9491
|
+
* a recovery from a plain clear and nothing more. I wrote the retraction
|
|
9492
|
+
* twice and left the retracted sentence standing here, which is how the next
|
|
9493
|
+
* reader ends up trusting a property that was withdrawn two files away.
|
|
9494
|
+
*
|
|
9495
|
+
* Returning the observation rather than having the caller decorate it keeps
|
|
9496
|
+
* that rule in one testable place instead of inline in the poll.
|
|
9497
|
+
*/
|
|
9498
|
+
settle(codeName, observation) {
|
|
9499
|
+
const cost = this.costOf(codeName);
|
|
9500
|
+
if (observation.state !== "clear") return { observation, cost };
|
|
9501
|
+
this.byAgent.delete(codeName);
|
|
9502
|
+
const hadCost = cost.tasksDeferred > 0 || cost.messagesWithheld > 0;
|
|
9503
|
+
return { observation: hadCost ? { state: "clear", recovering: true } : observation, cost };
|
|
9504
|
+
}
|
|
9505
|
+
/**
|
|
9506
|
+
* Drop an agent's episode entirely — teardown, not recovery (CodeRabbit on
|
|
9507
|
+
* #6329). `clearAgentCaches` purges every other per-codeName map on a
|
|
9508
|
+
* handoff, decommission or unassign; without this, an agent that left DURING
|
|
9509
|
+
* a blocked episode would hand its `tasksDeferred` and `messagesWithheld` to
|
|
9510
|
+
* whatever agent next takes that code name.
|
|
9511
|
+
*/
|
|
9512
|
+
forget(codeName) {
|
|
9513
|
+
this.byAgent.delete(codeName);
|
|
9514
|
+
}
|
|
9515
|
+
/** Current running cost without touching the episode. For tests and diagnostics. */
|
|
9516
|
+
peek(codeName) {
|
|
9517
|
+
return this.costOf(codeName);
|
|
9518
|
+
}
|
|
9519
|
+
};
|
|
9520
|
+
|
|
9387
9521
|
// src/lib/occupancy-gate.ts
|
|
9388
9522
|
import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync9, readSync as readSync2, statSync as statSync11 } from "fs";
|
|
9389
9523
|
import { join as join38 } from "path";
|
|
@@ -13048,6 +13182,8 @@ function hasRevokedResiduals(state9) {
|
|
|
13048
13182
|
}
|
|
13049
13183
|
var pendingSessionRestarts = /* @__PURE__ */ new Map();
|
|
13050
13184
|
var lastRestartWasExternal = /* @__PURE__ */ new Map();
|
|
13185
|
+
var blockedPromptCost = new BlockedPromptCostLedger();
|
|
13186
|
+
var pendingRecoveryCost = /* @__PURE__ */ new Map();
|
|
13051
13187
|
var computerUseTunnels = new TunnelWatcher();
|
|
13052
13188
|
var pendingRestartVerifications = /* @__PURE__ */ new Map();
|
|
13053
13189
|
var agentCreatedAtMs = /* @__PURE__ */ new Map();
|
|
@@ -13790,7 +13926,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
|
|
|
13790
13926
|
var dayRolloverInboundHold = /* @__PURE__ */ new Map();
|
|
13791
13927
|
var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
|
|
13792
13928
|
async function channelInboundActivityAgeSecondsFor(codeName) {
|
|
13793
|
-
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-
|
|
13929
|
+
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-ZOXVXDPT.js");
|
|
13794
13930
|
const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
|
|
13795
13931
|
if (newest === null) return null;
|
|
13796
13932
|
return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
|
|
@@ -14488,6 +14624,8 @@ function resolveAgentFramework(codeName) {
|
|
|
14488
14624
|
function clearAgentCaches(agentId, codeName) {
|
|
14489
14625
|
const channelCacheMutated = clearAgentState(agentId, codeName);
|
|
14490
14626
|
agentChannelTokens.delete(codeName);
|
|
14627
|
+
blockedPromptCost.forget(codeName);
|
|
14628
|
+
pendingRecoveryCost.delete(codeName);
|
|
14491
14629
|
lastRefreshPayload.delete(agentId);
|
|
14492
14630
|
boardTokenCache.forget(agentId);
|
|
14493
14631
|
refreshFetchGate.forget(agentId);
|
|
@@ -14534,7 +14672,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
14534
14672
|
var lastVersionCheckAt = 0;
|
|
14535
14673
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
14536
14674
|
var lastResponsivenessProbeAt = 0;
|
|
14537
|
-
var agtCliVersion = true ? "0.28.
|
|
14675
|
+
var agtCliVersion = true ? "0.28.1023" : "dev";
|
|
14538
14676
|
function resolveBrewPath(execFileSync3) {
|
|
14539
14677
|
try {
|
|
14540
14678
|
const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -16169,7 +16307,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
16169
16307
|
if (codeNames.length === 0) return;
|
|
16170
16308
|
void (async () => {
|
|
16171
16309
|
try {
|
|
16172
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
16310
|
+
const { collectDiagnostics } = await import("../persistent-session-3NQHNU6H.js");
|
|
16173
16311
|
await api.post("/host/heartbeat", {
|
|
16174
16312
|
host_id: hostId,
|
|
16175
16313
|
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
|
|
@@ -16311,7 +16449,7 @@ async function pollCycleInner() {
|
|
|
16311
16449
|
}
|
|
16312
16450
|
try {
|
|
16313
16451
|
const { detectHostSecurity } = await import("../host-security-KQYYZZVW.js");
|
|
16314
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
16452
|
+
const { collectDiagnostics } = await import("../persistent-session-3NQHNU6H.js");
|
|
16315
16453
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
16316
16454
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor) : void 0;
|
|
16317
16455
|
let tailscaleHostname;
|
|
@@ -16487,7 +16625,7 @@ async function pollCycleInner() {
|
|
|
16487
16625
|
collectPanelessActivityProbes,
|
|
16488
16626
|
getResponsivenessIntervalMs,
|
|
16489
16627
|
occupancyQualificationClassifications
|
|
16490
|
-
} = await import("../responsiveness-probe-
|
|
16628
|
+
} = await import("../responsiveness-probe-ZOXVXDPT.js");
|
|
16491
16629
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
16492
16630
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
16493
16631
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -16604,7 +16742,7 @@ async function pollCycleInner() {
|
|
|
16604
16742
|
collectResponsivenessProbes,
|
|
16605
16743
|
livePendingInboundOldestAgeSeconds,
|
|
16606
16744
|
parkPendingInbound
|
|
16607
|
-
} = await import("../responsiveness-probe-
|
|
16745
|
+
} = await import("../responsiveness-probe-ZOXVXDPT.js");
|
|
16608
16746
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-M6AFZWNU.js");
|
|
16609
16747
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
16610
16748
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -16760,7 +16898,7 @@ async function pollCycleInner() {
|
|
|
16760
16898
|
}
|
|
16761
16899
|
try {
|
|
16762
16900
|
const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-VR5QGP3J.js");
|
|
16763
|
-
const { probeSessionAuth } = await import("../session-auth-dead-
|
|
16901
|
+
const { probeSessionAuth } = await import("../session-auth-dead-ZKC2HH6T.js");
|
|
16764
16902
|
const observations = [];
|
|
16765
16903
|
const pendingCacheCommits = [];
|
|
16766
16904
|
const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
|
|
@@ -17022,6 +17160,107 @@ async function pollCycleInner() {
|
|
|
17022
17160
|
}
|
|
17023
17161
|
}
|
|
17024
17162
|
}
|
|
17163
|
+
let sweepComplete = false;
|
|
17164
|
+
let omittedNoSession = 0;
|
|
17165
|
+
try {
|
|
17166
|
+
const unreportedRecovery = /* @__PURE__ */ new Map();
|
|
17167
|
+
const sweeps = [];
|
|
17168
|
+
for (const agent of agents) {
|
|
17169
|
+
if (agent.status !== "active") continue;
|
|
17170
|
+
const framework = agent.framework ?? agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK;
|
|
17171
|
+
if (framework !== "claude-code") continue;
|
|
17172
|
+
const tmuxProbe = probeTmuxSession(`agt-${agent.code_name}`);
|
|
17173
|
+
const claudeProbe = tmuxProbe === "alive" ? probeClaudeProcessInTmux(`agt-${agent.code_name}`) : "dead";
|
|
17174
|
+
if (tmuxProbe === "unknown" || claudeProbe === "unknown") {
|
|
17175
|
+
sweeps.push({
|
|
17176
|
+
agent_id: agent.agent_id,
|
|
17177
|
+
state: "unknown",
|
|
17178
|
+
unknown_reason: "pane-unreadable",
|
|
17179
|
+
tasks_deferred: 0,
|
|
17180
|
+
messages_withheld: 0
|
|
17181
|
+
});
|
|
17182
|
+
continue;
|
|
17183
|
+
}
|
|
17184
|
+
if (tmuxProbe !== "alive" || claudeProbe !== "alive") {
|
|
17185
|
+
omittedNoSession += 1;
|
|
17186
|
+
continue;
|
|
17187
|
+
}
|
|
17188
|
+
const observation = observeBlockedPrompt(agent.code_name, true, (codeName) => {
|
|
17189
|
+
try {
|
|
17190
|
+
return syncExecFile("tmux", ["capture-pane", "-t", `agt-${codeName}`, "-p"], {
|
|
17191
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
17192
|
+
timeout: 2e3
|
|
17193
|
+
}).toString();
|
|
17194
|
+
} catch {
|
|
17195
|
+
return null;
|
|
17196
|
+
}
|
|
17197
|
+
});
|
|
17198
|
+
const { observation: settled, cost } = blockedPromptCost.settle(
|
|
17199
|
+
agent.code_name,
|
|
17200
|
+
observation
|
|
17201
|
+
);
|
|
17202
|
+
const carried = carriedRecoveryFor(settled.state, pendingRecoveryCost, agent.code_name);
|
|
17203
|
+
const { total, hasCost } = foldPendingRecovery(cost, carried);
|
|
17204
|
+
const recovering = settled.state === "clear" && hasCost;
|
|
17205
|
+
if (recovering) unreportedRecovery.set(agent.code_name, total);
|
|
17206
|
+
sweeps.push({
|
|
17207
|
+
agent_id: agent.agent_id,
|
|
17208
|
+
state: settled.state,
|
|
17209
|
+
...settled.state === "blocked" ? { variant: settled.kind } : {},
|
|
17210
|
+
...settled.state === "unknown" ? { unknown_reason: settled.reason } : {},
|
|
17211
|
+
...recovering ? { recovering: true } : {},
|
|
17212
|
+
tasks_deferred: total.tasksDeferred,
|
|
17213
|
+
messages_withheld: total.messagesWithheld
|
|
17214
|
+
});
|
|
17215
|
+
}
|
|
17216
|
+
sweepComplete = omittedNoSession === 0;
|
|
17217
|
+
if (!sweepComplete) {
|
|
17218
|
+
log(
|
|
17219
|
+
`[blocked-prompt-monitor] partial sweep: ${omittedNoSession} active agent(s) had no live session, so this batch may not close an open alert (ENG-10825)`
|
|
17220
|
+
);
|
|
17221
|
+
}
|
|
17222
|
+
if (sweeps.length === 0 && omittedNoSession > 0) {
|
|
17223
|
+
log(
|
|
17224
|
+
`[blocked-prompt-monitor] NOTHING REPORTED: all ${omittedNoSession} active claude-code agent(s) had no live session this poll, so no batch was sent and this host is unassessed. A wedged tmux now reads as 'unknown' rather than 'dead', so reaching here means either a genuine host-wide teardown or no tmux server at all (ENG-10825)`
|
|
17225
|
+
);
|
|
17226
|
+
}
|
|
17227
|
+
if (sweeps.length > 0) {
|
|
17228
|
+
const disposition = await api.post(
|
|
17229
|
+
"/host/blocked-prompt-report",
|
|
17230
|
+
{
|
|
17231
|
+
agents: sweeps,
|
|
17232
|
+
sweep_complete: sweepComplete,
|
|
17233
|
+
// WHY the sweep was partial, not just that it was (obi-wan
|
|
17234
|
+
// `82a0dd7ef253`). `sweep_complete: false` tells the route it may
|
|
17235
|
+
// not close an alert; it does not tell an operator that four agents
|
|
17236
|
+
// on this host have no session and that is why their critical will
|
|
17237
|
+
// not clear. The route writes this onto the alert payload.
|
|
17238
|
+
omitted_no_session: omittedNoSession
|
|
17239
|
+
}
|
|
17240
|
+
).then((res) => {
|
|
17241
|
+
const dropped = res?.dropped_agent_ids ?? [];
|
|
17242
|
+
if (dropped.length > 0) {
|
|
17243
|
+
log(
|
|
17244
|
+
`[blocked-prompt-monitor] the route DROPPED ${dropped.length} reported agent(s): ${dropped.join(", ")} \u2014 they are not in host_agents for this host, so this sweep cannot be complete and an open alert cannot close until they stop being swept or the rows return (ENG-10825)`
|
|
17245
|
+
);
|
|
17246
|
+
}
|
|
17247
|
+
return res?.cost_disposition;
|
|
17248
|
+
}).catch((err) => {
|
|
17249
|
+
log(
|
|
17250
|
+
`[blocked-prompt-monitor] report failed (ENG-10825): ${err.message} \u2014 the sweep still ran locally; this poll's result did not reach the alert board`
|
|
17251
|
+
);
|
|
17252
|
+
return "withheld";
|
|
17253
|
+
});
|
|
17254
|
+
settlePendingRecovery(pendingRecoveryCost, unreportedRecovery, disposition);
|
|
17255
|
+
if (disposition === "withheld" && unreportedRecovery.size > 0) {
|
|
17256
|
+
log(
|
|
17257
|
+
`[blocked-prompt-monitor] holding ${unreportedRecovery.size} unreported recovery total(s) for the next poll \u2014 the route did not persist them (ENG-10825)`
|
|
17258
|
+
);
|
|
17259
|
+
}
|
|
17260
|
+
}
|
|
17261
|
+
} catch (err) {
|
|
17262
|
+
log(`[blocked-prompt-monitor] sweep threw (suppressed): ${err.message}`);
|
|
17263
|
+
}
|
|
17025
17264
|
const frameworksThisCycle = new Set(agents.map((a) => a.framework).filter(Boolean));
|
|
17026
17265
|
for (const fw of frameworksThisCycle) {
|
|
17027
17266
|
await ensureFrameworkBinary(fw);
|
|
@@ -17248,6 +17487,22 @@ async function pollCycleInner() {
|
|
|
17248
17487
|
// `resets_hint` is the modal's own reset text, passed through verbatim.
|
|
17249
17488
|
// It is the only place Claude Code gives us a DATED reset; the banner
|
|
17250
17489
|
// parser elsewhere can only reconstruct an hour (ENG-8901).
|
|
17490
|
+
// ENG-10972: refresh ONLY the liveness field, leaving gave_up_at and the
|
|
17491
|
+
// reason untouched. Merge-then-write rather than write-fresh: clobbering
|
|
17492
|
+
// the file each poll would reset gave_up_at and keep re-arming the
|
|
17493
|
+
// give-up notice for as long as the modal sat there.
|
|
17494
|
+
refreshUsageLimitLiveness: (codeName) => {
|
|
17495
|
+
const dir = getFramework("claude-code").getAgentDir(codeName);
|
|
17496
|
+
const file = join42(dir, "watchdog-give-up.json");
|
|
17497
|
+
if (!existsSync19(file)) return;
|
|
17498
|
+
try {
|
|
17499
|
+
const raw = JSON.parse(readFileSync34(file, "utf8"));
|
|
17500
|
+
if (raw["reason"] !== "usage_limit") return;
|
|
17501
|
+
raw["usage_limit_last_seen_at"] = (/* @__PURE__ */ new Date()).toISOString();
|
|
17502
|
+
atomicWriteFileSync(file, JSON.stringify(raw));
|
|
17503
|
+
} catch {
|
|
17504
|
+
}
|
|
17505
|
+
},
|
|
17251
17506
|
signalUsageLimit: (codeName, resetsHint) => {
|
|
17252
17507
|
const dir = getFramework("claude-code").getAgentDir(codeName);
|
|
17253
17508
|
if (!existsSync19(dir)) return;
|
|
@@ -17256,6 +17511,12 @@ async function pollCycleInner() {
|
|
|
17256
17511
|
JSON.stringify({
|
|
17257
17512
|
gave_up_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17258
17513
|
reason: "usage_limit",
|
|
17514
|
+
// ENG-10972: seeded here and then REFRESHED by
|
|
17515
|
+
// refreshUsageLimitLiveness on every poll while the modal is present,
|
|
17516
|
+
// so a consumer can tell a LIVE cap from a historical marker.
|
|
17517
|
+
// `gave_up_at` keeps its own meaning (decideGiveUpNotice ages against
|
|
17518
|
+
// it), so liveness gets its own field rather than overloading one.
|
|
17519
|
+
usage_limit_last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17259
17520
|
...resetsHint ? { resets_hint: resetsHint } : {}
|
|
17260
17521
|
})
|
|
17261
17522
|
);
|
|
@@ -20895,6 +21156,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash20("sha256")
|
|
|
20895
21156
|
const blockedKind = sessionBlockedOnPrompt(codeName);
|
|
20896
21157
|
if (blockedKind) {
|
|
20897
21158
|
deferReadyForBlockedPrompt = true;
|
|
21159
|
+
blockedPromptCost.noteTasksDeferred(codeName, ready.map((t) => t.taskId));
|
|
20898
21160
|
if (shouldLogBlockedPrompt(codeName, "scheduled-firing")) {
|
|
20899
21161
|
log(
|
|
20900
21162
|
`[persistent-session] Deferring ${ready.length} ready task(s) for '${codeName}' \u2014 session is blocked on an interactive prompt (${blockedKind}) and cannot run a tool call; schedule NOT advanced, will fire once the prompt is answered (further deferrals this episode are not logged) [ENG-10335]`
|
|
@@ -21164,7 +21426,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
21164
21426
|
try {
|
|
21165
21427
|
const freshId = rotateSessionForWedge(codeName);
|
|
21166
21428
|
consecutiveWedgeCycles.delete(codeName);
|
|
21167
|
-
const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-
|
|
21429
|
+
const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-ZOXVXDPT.js");
|
|
21168
21430
|
const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
|
|
21169
21431
|
const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
|
|
21170
21432
|
const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
|
|
@@ -21207,7 +21469,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
21207
21469
|
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}`));
|
|
21208
21470
|
void (async () => {
|
|
21209
21471
|
try {
|
|
21210
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
21472
|
+
const { collectDiagnostics } = await import("../persistent-session-3NQHNU6H.js");
|
|
21211
21473
|
await api.post("/host/heartbeat", {
|
|
21212
21474
|
host_id: hostId,
|
|
21213
21475
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
|
|
@@ -21258,7 +21520,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
21258
21520
|
}
|
|
21259
21521
|
try {
|
|
21260
21522
|
const hostId = await getHostId();
|
|
21261
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
21523
|
+
const { collectDiagnostics } = await import("../persistent-session-3NQHNU6H.js");
|
|
21262
21524
|
await api.post("/host/heartbeat", {
|
|
21263
21525
|
host_id: hostId,
|
|
21264
21526
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
|
|
@@ -21657,6 +21919,7 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
21657
21919
|
return;
|
|
21658
21920
|
}
|
|
21659
21921
|
const blockedOnPrompt = sessionBlockedOnPrompt(agent.codeName);
|
|
21922
|
+
if (blockedOnPrompt) blockedPromptCost.noteMessageWithheld(agent.codeName, msg.id);
|
|
21660
21923
|
if (blockedOnPrompt && shouldLogBlockedPrompt(agent.codeName, "direct-chat")) {
|
|
21661
21924
|
log(
|
|
21662
21925
|
`[direct-chat] Not delivering to '${agent.codeName}' (msg=${msg.id}) \u2014 session is blocked on an interactive prompt (${blockedOnPrompt}), so nothing in-session can pull it; leaving pending for durable replay once the prompt is answered (further messages this episode are not logged) [ENG-10335]`
|
|
@@ -21929,7 +22192,7 @@ async function processClaudePairSessions(agents) {
|
|
|
21929
22192
|
// cannot take it for itself — by the time it runs, the write it is looking
|
|
21930
22193
|
// for may already have happened.
|
|
21931
22194
|
snapshotPairConfig
|
|
21932
|
-
} = await import("../claude-pair-runtime-
|
|
22195
|
+
} = await import("../claude-pair-runtime-C4DDKQ6N.js");
|
|
21933
22196
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
21934
22197
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
21935
22198
|
const killed = await killPairSession(pairTmuxSession(pairId));
|