@integrity-labs/agt-cli 0.28.1015 → 0.28.1017

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.
@@ -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-YP2UTQGA.js");
269
+ const { resolveClaudeBinary } = await import("./persistent-session-UZBB7EOS.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-GSPFYCG4.js.map
737
+ //# sourceMappingURL=claude-pair-runtime-JQRJXNSL.js.map
@@ -18,6 +18,7 @@ import {
18
18
  decidePin,
19
19
  defaultFlagsCachePath,
20
20
  deriveMcpServerKey,
21
+ describeBlockedPromptHold,
21
22
  describeChannelWriteFailure,
22
23
  describeStartupCompletion,
23
24
  diffEnvIntegrations,
@@ -65,7 +66,7 @@ import {
65
66
  safeWriteJsonAtomic,
66
67
  setConfigHash,
67
68
  tripClass
68
- } from "../chunk-GHHDFHKE.js";
69
+ } from "../chunk-HIDB6U3D.js";
69
70
  import {
70
71
  getProjectDir as getProjectDir2,
71
72
  getReadyTasks,
@@ -146,7 +147,7 @@ import {
146
147
  takeZombieDetection,
147
148
  toOpencodeModel,
148
149
  writeEgressAllowlist
149
- } from "../chunk-MHDNKKEO.js";
150
+ } from "../chunk-CVQDMHOP.js";
150
151
  import {
151
152
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
152
153
  AnchorSessionClient,
@@ -187,6 +188,7 @@ import {
187
188
  classifyCursorAdvance,
188
189
  classifyOutput,
189
190
  classifySlackPolicyBlock,
191
+ classifyTelegramPolicyBlock,
190
192
  classifyTranscriptRateLimit,
191
193
  coerceOnboardingState,
192
194
  cursorAdvanceCounterKey,
@@ -250,7 +252,7 @@ import {
250
252
  subagentActivityAgeSeconds,
251
253
  sumTranscriptUsageInWindow,
252
254
  transcriptActivityAgeSeconds
253
- } from "../chunk-RJ3SI5JG.js";
255
+ } from "../chunk-X7SX25N6.js";
254
256
  import "../chunk-DHWNVVX4.js";
255
257
  import {
256
258
  reapOrphanChannelMcps
@@ -305,7 +307,44 @@ var TELEGRAM_RESTART_ON_CHANGE_ENV_KEYS = [
305
307
  /** Peer kill switch. A switch whose purpose is stopping something promptly. */
306
308
  "PEER_DISABLED",
307
309
  /** Derived boolean mirror of the same switch, kept for the rollout. */
308
- "TELEGRAM_PEER_DISABLED"
310
+ "TELEGRAM_PEER_DISABLED",
311
+ // ENG-9665. These are the purest case this list exists for: they ARE the
312
+ // sender policy, so a change to them is an operator changing who may talk to
313
+ // the agent, made in the console and visible there immediately. Waiting for
314
+ // an unrelated respawn would mean the console shows a restriction the running
315
+ // session is not applying - which is the exact ENG-9802 defect, and the same
316
+ // gap this file's header notes for SLACK_SENDER_POLICY.
317
+ //
318
+ // WHICH MECHANISM ACTUALLY RESPAWNS FOR THESE - Obi Wan `1c6a60a29aa5`, and
319
+ // it is `decideSenderPolicyRestart`, NOT this list's hash.
320
+ //
321
+ // Being classified here is a statement that a change is restart-worthy. It is
322
+ // NOT, for these three, the thing that delivers the restart:
323
+ // `telegramPolicyEnvFromInputs` builds the restart basis from the CHANNEL
324
+ // CONFIG, and a sender policy is not in the channel config - it is a column on
325
+ // `agents`. So these keys can never enter that hash, and the earlier comment
326
+ // here (and commit 97868b5a's message) credited a mechanism that does not
327
+ // reach them.
328
+ //
329
+ // The restart does happen, via `decideSenderPolicyRestart`, which reads the
330
+ // policy from where it actually lives and now covers telegram because
331
+ // `senderPolicyIsEnforcedOn('telegram')` is true. Deliberately NOT plumbed
332
+ // into the basis as well: that would mean two mechanisms racing to respawn on
333
+ // one edit, and the config-shaped one would be reading a value that is not in
334
+ // the config.
335
+ //
336
+ // KNOWN CONSEQUENCE, stated rather than left to be found:
337
+ // `isTelegramPolicyRestrictive` reads the basis, so it returns false for an
338
+ // agent whose ONLY Telegram restriction is a sender policy. That feeds the
339
+ // ENG-7771 unknown-baseline branch. Correct today only because
340
+ // `decideSenderPolicyRestart` covers the same agent by a different route; if
341
+ // that ever stops being true, this is where the gap reappears.
342
+ /** The mode itself. Nothing else decides more about who is admitted. */
343
+ "TELEGRAM_SENDER_POLICY",
344
+ /** manager_only's single admitted counterparty. */
345
+ "TELEGRAM_SENDER_POLICY_PRINCIPAL_ID",
346
+ /** team_only's admitted set. An edit here adds or removes people. */
347
+ "TELEGRAM_SENDER_POLICY_TEAM_PRINCIPAL_IDS"
309
348
  ];
310
349
  function telegramPolicyEnvBasis(env) {
311
350
  const parts = [];
@@ -11241,6 +11280,37 @@ function parsePeerAgentModeEnv2(raw) {
11241
11280
  return "off";
11242
11281
  }
11243
11282
 
11283
+ // ../../packages/core/dist/channels/governance/telegram-inbound-filter.js
11284
+ function isBotSender(evt) {
11285
+ return evt.from?.is_bot === true;
11286
+ }
11287
+ function isSameTeamPeer(evt) {
11288
+ const gate = evt.peerGatePath;
11289
+ return gate === "same_team" || gate === void 0;
11290
+ }
11291
+ function decideSenderPolicyForwardTelegram(evt, policy) {
11292
+ if (policy.mode === "all")
11293
+ return { forward: true };
11294
+ const fromId = evt.from?.id;
11295
+ if (fromId === void 0)
11296
+ return { forward: false, reason: "sender_policy" };
11297
+ const fromIdStr = String(fromId);
11298
+ if (policy.mode === "manager_only") {
11299
+ return policy.principalTelegramUserId && fromIdStr === policy.principalTelegramUserId ? { forward: true } : { forward: false, reason: "sender_policy" };
11300
+ }
11301
+ if (policy.mode === "team_only") {
11302
+ const list = policy.teamPrincipalTelegramUserIds;
11303
+ if (list && list.length > 0 && list.includes(fromIdStr)) {
11304
+ return { forward: true };
11305
+ }
11306
+ return isBotSender(evt) && isSameTeamPeer(evt) ? { forward: true } : { forward: false, reason: "sender_policy" };
11307
+ }
11308
+ if (policy.mode === "team_agents_only") {
11309
+ return isBotSender(evt) && isSameTeamPeer(evt) ? { forward: true } : { forward: false, reason: "sender_policy" };
11310
+ }
11311
+ return isBotSender(evt) ? { forward: true } : { forward: false, reason: "sender_policy" };
11312
+ }
11313
+
11244
11314
  // ../../packages/core/dist/channels/governance/telegram-chat-type.js
11245
11315
  function isTelegramPrivateChat(chatType) {
11246
11316
  return chatType === "private";
@@ -11254,6 +11324,7 @@ function evaluateTelegramInboundGate(evt, deps) {
11254
11324
  const content = text.length > 0 ? { forward: true } : { forward: false, reason: "empty" };
11255
11325
  let peerLeaf;
11256
11326
  let isSelf = false;
11327
+ let peerGatePath;
11257
11328
  if (isBot) {
11258
11329
  const classifierMsg = {
11259
11330
  message_id: evt.message_id,
@@ -11271,6 +11342,13 @@ function evaluateTelegramInboundGate(evt, deps) {
11271
11342
  const c = classifyPeerMessage2(classifierMsg, deps.peerConfig, deps.self);
11272
11343
  isSelf = c.kind === "self";
11273
11344
  peerLeaf = { kind: c.kind, reason: c.kind === "drop" ? c.reason : void 0 };
11345
+ peerGatePath = evt.from ? deps.peerConfig.peers.find((pr) => pr.bot_id === evt.from.id)?.gate_path : void 0;
11346
+ }
11347
+ const policy = deps.senderPolicy;
11348
+ let senderPolicyLeaf;
11349
+ if (policy) {
11350
+ const verdict = decideSenderPolicyForwardTelegram({ ...evt, peerGatePath }, policy);
11351
+ senderPolicyLeaf = verdict.forward ? { forward: true } : { forward: false, reason: classifyTelegramPolicyBlock(policy) };
11274
11352
  }
11275
11353
  const decision = decideInboundAccess({
11276
11354
  content,
@@ -11286,7 +11364,27 @@ function evaluateTelegramInboundGate(evt, deps) {
11286
11364
  // this is `{ forward: true }`, which the composer treats identically to the
11287
11365
  // field being absent.
11288
11366
  chatType: deps.privateChatsOnly && !isTelegramPrivateChat(evt.chat.type) ? { forward: false } : { forward: true },
11289
- // No sender-policy mode system and no command dispatch on the injection gate.
11367
+ // ENG-9665: the sender-policy axis, applied to ALL senders (a peer agent is
11368
+ // gated here too, before peer governance) exactly as on Slack and Teams.
11369
+ // Absent policy means `all`, which is a no-op.
11370
+ senderPolicy: senderPolicyLeaf,
11371
+ // Injection gate: no command dispatch, and NO DECLINE IS EVER SENT on this
11372
+ // path - so `sameOrg` has nothing to decide here.
11373
+ //
11374
+ // Obi Wan `2d99e22b7e8d`: this used to compute
11375
+ // `allowedChats.size > 0 && allowedChats.has(chatId)` under a comment
11376
+ // reasoning about decline-vs-silence as if it were live. It is not. This
11377
+ // function returns `{ admit, reason }` and DISCARDS `decision.decline`, and
11378
+ // `dispatchTelegramInbound` only logs and returns 'dropped_gate'. So the
11379
+ // computed value had no observable effect for any allowlist, while the
11380
+ // comment implied an agent WITH one would decline. A reader auditing the
11381
+ // insider-decline posture would have concluded opencode Telegram delivers
11382
+ // declines when it delivers silence in every case.
11383
+ //
11384
+ // `slack-sender-gate.ts` states this honestly with a flat `sameOrg: false`,
11385
+ // so this now matches its sibling. IF a decline is ever wired on this
11386
+ // runtime, this is the line to revisit - and the argument for what to put
11387
+ // here lives in telegram-channel.ts, where declines are actually sent.
11290
11388
  sameOrg: false
11291
11389
  });
11292
11390
  if (decision.kind === "drop")
@@ -11317,6 +11415,32 @@ function buildTelegramPeerClassifierConfigFromEnv(env, opts) {
11317
11415
  peer_disabled_mode: parseTelegramPeerDisabledModeEnv(env, opts?.onWarn)
11318
11416
  };
11319
11417
  }
11418
+ function buildTelegramSenderPolicyFromEnv(env, ctx) {
11419
+ const raw = (env["TELEGRAM_SENDER_POLICY"] ?? "all").trim().toLowerCase();
11420
+ const teamId = ctx.agentTeamId;
11421
+ if (raw === "all")
11422
+ return { mode: "all" };
11423
+ if (raw === "agents_only")
11424
+ return { mode: "agents_only" };
11425
+ if (raw === "team_agents_only") {
11426
+ if (!teamId)
11427
+ throw new Error("TELEGRAM_SENDER_POLICY=team_agents_only requires AGT_TEAM_ID");
11428
+ return { mode: "team_agents_only", teamId };
11429
+ }
11430
+ if (raw === "team_only") {
11431
+ if (!teamId)
11432
+ throw new Error("TELEGRAM_SENDER_POLICY=team_only requires AGT_TEAM_ID");
11433
+ const teamPrincipalTelegramUserIds = (env["TELEGRAM_SENDER_POLICY_TEAM_PRINCIPAL_IDS"] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11434
+ return { mode: "team_only", teamId, teamPrincipalTelegramUserIds };
11435
+ }
11436
+ if (raw === "manager_only") {
11437
+ if (!teamId)
11438
+ throw new Error("TELEGRAM_SENDER_POLICY=manager_only requires AGT_TEAM_ID");
11439
+ const principalTelegramUserId = env["TELEGRAM_SENDER_POLICY_PRINCIPAL_ID"];
11440
+ return principalTelegramUserId ? { mode: "manager_only", teamId, principalTelegramUserId } : { mode: "manager_only", teamId };
11441
+ }
11442
+ throw new Error(`Invalid TELEGRAM_SENDER_POLICY=${JSON.stringify(env["TELEGRAM_SENDER_POLICY"])}`);
11443
+ }
11320
11444
 
11321
11445
  // src/lib/telegram-ingest.ts
11322
11446
  var nodeHttpsTelegramFetch = (url, init) => new Promise((resolve2, reject) => {
@@ -11375,7 +11499,7 @@ function memoryOffsetStore(initial = 0) {
11375
11499
  value = o;
11376
11500
  } };
11377
11501
  }
11378
- function resolveTelegramIngestParamsFromEnv(env) {
11502
+ function resolveTelegramIngestParamsFromEnv(env, ctx) {
11379
11503
  const botToken = env["TELEGRAM_BOT_TOKEN"];
11380
11504
  if (!botToken) return null;
11381
11505
  const ackReaction = (env["TELEGRAM_ACK_REACTION"] ?? "").trim() || "\u{1F440}";
@@ -11384,6 +11508,7 @@ function resolveTelegramIngestParamsFromEnv(env) {
11384
11508
  botToken,
11385
11509
  allowedChats: parseTelegramAllowedChatsEnv(env),
11386
11510
  peerConfig: buildTelegramPeerClassifierConfigFromEnv(env),
11511
+ senderPolicy: buildTelegramSenderPolicyFromEnv(env, ctx),
11387
11512
  ackReaction,
11388
11513
  ...skipReaction ? { skipReaction } : {}
11389
11514
  };
@@ -11441,7 +11566,12 @@ async function dispatchTelegramInbound(update, ctx) {
11441
11566
  const decision = evaluateTelegramInboundGate(msg, {
11442
11567
  allowedChats: ctx.allowedChats,
11443
11568
  peerConfig: ctx.peerConfig,
11444
- self: ctx.self
11569
+ self: ctx.self,
11570
+ // ENG-9665 / Obi Wan `526be87f0cbd`: THE LINE THAT WAS MISSING. Without it
11571
+ // `deps.senderPolicy` is undefined, which the gate treats as `all`, so the
11572
+ // registry's claim that both runtimes supply this leaf was false on this
11573
+ // path. eng-9665-opencode-sender-policy.test.ts fails if it is removed.
11574
+ senderPolicy: ctx.senderPolicy
11445
11575
  });
11446
11576
  if (!decision.admit) {
11447
11577
  ctx.log(`[telegram-ingest:${ctx.codeName}] dropped chat=${redactId(chatId)} reason=${decision.reason}`);
@@ -11548,6 +11678,7 @@ function startTelegramIngest(config2) {
11548
11678
  codeName: config2.codeName,
11549
11679
  allowedChats: config2.allowedChats,
11550
11680
  peerConfig: config2.peerConfig,
11681
+ senderPolicy: config2.senderPolicy,
11551
11682
  self: config2.self,
11552
11683
  inject: config2.inject,
11553
11684
  sendReply,
@@ -11635,6 +11766,10 @@ function fingerprint2(params) {
11635
11766
  botToken: params.botToken,
11636
11767
  allowedChats: [...params.allowedChats].sort(),
11637
11768
  peerConfig: params.peerConfig,
11769
+ // The whole policy: the mode AND the principal identities. A revocation
11770
+ // changes only the ids, so hashing the mode alone would miss exactly the
11771
+ // case `3ff390e32af6` describes.
11772
+ senderPolicy: params.senderPolicy,
11638
11773
  ackReaction: params.ackReaction ?? null,
11639
11774
  skipReaction: params.skipReaction ?? null
11640
11775
  })
@@ -11704,7 +11839,7 @@ async function ensureOpencodeTelegramIngest(opts) {
11704
11839
  stopOpencodeTelegramIngest(codeName, log2);
11705
11840
  return;
11706
11841
  }
11707
- params = resolveTelegramIngestParamsFromEnv(env);
11842
+ params = resolveTelegramIngestParamsFromEnv(env, { agentTeamId: env["AGT_TEAM_ID"] ?? null });
11708
11843
  } catch (err) {
11709
11844
  stopOpencodeTelegramIngest(codeName, log2);
11710
11845
  log2(`[telegram-ingest:${codeName}] not started: ${err instanceof Error ? err.message : String(err)}`);
@@ -11740,6 +11875,7 @@ async function ensureOpencodeTelegramIngest(opts) {
11740
11875
  botToken: params.botToken,
11741
11876
  allowedChats: params.allowedChats,
11742
11877
  peerConfig: params.peerConfig,
11878
+ senderPolicy: params.senderPolicy,
11743
11879
  self,
11744
11880
  inject: (msg) => injectOpencodeMessage(codeName, msg, { awaitReply: true }),
11745
11881
  log: log2,
@@ -13654,7 +13790,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
13654
13790
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
13655
13791
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
13656
13792
  async function channelInboundActivityAgeSecondsFor(codeName) {
13657
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-YTFWHUTQ.js");
13793
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-KLOUFBM5.js");
13658
13794
  const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
13659
13795
  if (newest === null) return null;
13660
13796
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -14173,7 +14309,7 @@ function stopPersistentSessionAndForgetMcpBaseline(codeName, breakerReason, gate
14173
14309
  const gate = restartGateFor(codeName, gateReason);
14174
14310
  if (gate !== "bypass" && gate !== "proceed") {
14175
14311
  log(`[maintenance-window] Deferring '${gateReason}' restart for '${codeName}' (${gate})`);
14176
- return;
14312
+ return false;
14177
14313
  }
14178
14314
  cancelPendingSessionRestart(codeName);
14179
14315
  stopPersistentSession(codeName, log);
@@ -14191,6 +14327,7 @@ function stopPersistentSessionAndForgetMcpBaseline(codeName, breakerReason, gate
14191
14327
  recordRestartForBreaker(codeName, breakerReason);
14192
14328
  }
14193
14329
  reportRestartEvent(codeName, breakerReason, auditDetail);
14330
+ return true;
14194
14331
  }
14195
14332
  var agentRuntimeTeardownDeps = {
14196
14333
  stopOpencodeServe: (codeName, teardownLog) => stopOpencodeSession(codeName, teardownLog),
@@ -14397,7 +14534,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
14397
14534
  var lastVersionCheckAt = 0;
14398
14535
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
14399
14536
  var lastResponsivenessProbeAt = 0;
14400
- var agtCliVersion = true ? "0.28.1015" : "dev";
14537
+ var agtCliVersion = true ? "0.28.1017" : "dev";
14401
14538
  function resolveBrewPath(execFileSync3) {
14402
14539
  try {
14403
14540
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -16032,7 +16169,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
16032
16169
  if (codeNames.length === 0) return;
16033
16170
  void (async () => {
16034
16171
  try {
16035
- const { collectDiagnostics } = await import("../persistent-session-YP2UTQGA.js");
16172
+ const { collectDiagnostics } = await import("../persistent-session-UZBB7EOS.js");
16036
16173
  await api.post("/host/heartbeat", {
16037
16174
  host_id: hostId,
16038
16175
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -16174,7 +16311,7 @@ async function pollCycleInner() {
16174
16311
  }
16175
16312
  try {
16176
16313
  const { detectHostSecurity } = await import("../host-security-KQYYZZVW.js");
16177
- const { collectDiagnostics } = await import("../persistent-session-YP2UTQGA.js");
16314
+ const { collectDiagnostics } = await import("../persistent-session-UZBB7EOS.js");
16178
16315
  const diagCodeNames = [...agentState.persistentSessionAgents];
16179
16316
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor) : void 0;
16180
16317
  let tailscaleHostname;
@@ -16350,7 +16487,7 @@ async function pollCycleInner() {
16350
16487
  collectPanelessActivityProbes,
16351
16488
  getResponsivenessIntervalMs,
16352
16489
  occupancyQualificationClassifications
16353
- } = await import("../responsiveness-probe-YTFWHUTQ.js");
16490
+ } = await import("../responsiveness-probe-KLOUFBM5.js");
16354
16491
  const probeIntervalMs = getResponsivenessIntervalMs();
16355
16492
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
16356
16493
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -16467,7 +16604,7 @@ async function pollCycleInner() {
16467
16604
  collectResponsivenessProbes,
16468
16605
  livePendingInboundOldestAgeSeconds,
16469
16606
  parkPendingInbound
16470
- } = await import("../responsiveness-probe-YTFWHUTQ.js");
16607
+ } = await import("../responsiveness-probe-KLOUFBM5.js");
16471
16608
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-M6AFZWNU.js");
16472
16609
  const wedgeNow = /* @__PURE__ */ new Date();
16473
16610
  const liveAgents = agentState.persistentSessionAgents;
@@ -16623,7 +16760,7 @@ async function pollCycleInner() {
16623
16760
  }
16624
16761
  try {
16625
16762
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-VR5QGP3J.js");
16626
- const { probeSessionAuth } = await import("../session-auth-dead-TX4YQUTJ.js");
16763
+ const { probeSessionAuth } = await import("../session-auth-dead-JJUKS75B.js");
16627
16764
  const observations = [];
16628
16765
  const pendingCacheCommits = [];
16629
16766
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -19480,6 +19617,26 @@ async function processAgent(agent, agentStates, managedToolkits) {
19480
19617
  );
19481
19618
  reapMissingMcpSessions({
19482
19619
  log,
19620
+ /**
19621
+ * ENG-10878: let the reaper see the budget it would be spending.
19622
+ *
19623
+ * `mcp-presence-reaper` is on the TIGHT crash bar with
19624
+ * `auth-tuple-change` and `model-change` - 2 in 600s, the 3rd trips.
19625
+ * NOT with `day-rollover` or `channel-set-change`, which this comment
19626
+ * used to name (obi-wan `486aa45fa3bd`): those are `'scheduled'` and
19627
+ * `'provisioning'` class and are not counted by `remainingCrashBudget`
19628
+ * at all. The single restart ENG-10878 spends on a blocked
19629
+ * pane cannot trip the breaker alone, but it can be the event that lets
19630
+ * a later one do it, and the agent is then paused on another reason's
19631
+ * account. With this wired, the reaper declines the restart when there
19632
+ * is nothing left and says so, rather than spending the last unit.
19633
+ *
19634
+ * The arithmetic lives in the breaker (`remainingCrashBudget`) rather
19635
+ * than here: `max` and the window are its business and are env-tunable,
19636
+ * so computing it at the call site would quietly stop being correct the
19637
+ * moment either moves.
19638
+ */
19639
+ restartBudgetRemaining: (codeName) => restartBreaker.remainingCrashBudget(codeName),
19483
19640
  codeName: agent.code_name,
19484
19641
  mcpJson: computerUseReaperView(mcpJsonParsed, computerUseVerdict),
19485
19642
  sessionStartedAt: sess?.startedAt ?? null,
@@ -19565,7 +19722,7 @@ async function processAgent(agent, agentStates, managedToolkits) {
19565
19722
  agent.agent_id,
19566
19723
  "mcp-presence-reaper",
19567
19724
  "blocked",
19568
- `session blocked on an interactive prompt (${ctx.kind}); declared MCP(s) [${ctx.activeKeys.join(", ")}] were never spawned, not killed \u2014 a restart re-presents the same prompt. Needs a human: tmux attach -t agt-${codeName}` + // Fingerprint, never pane text. This string is persisted on the
19725
+ `${describeBlockedPromptHold(ctx)}: tmux attach -t agt-${codeName}` + // Fingerprint, never pane text. This string is persisted on the
19569
19726
  // alert row, so it leaves the host; the pane can hold the
19570
19727
  // customer message the agent was mid-turn on. Same policy the
19571
19728
  // channel-input-watchdog follows for this very block.
@@ -21007,7 +21164,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
21007
21164
  try {
21008
21165
  const freshId = rotateSessionForWedge(codeName);
21009
21166
  consecutiveWedgeCycles.delete(codeName);
21010
- const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-YTFWHUTQ.js");
21167
+ const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-KLOUFBM5.js");
21011
21168
  const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
21012
21169
  const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
21013
21170
  const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
@@ -21050,7 +21207,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
21050
21207
  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}`));
21051
21208
  void (async () => {
21052
21209
  try {
21053
- const { collectDiagnostics } = await import("../persistent-session-YP2UTQGA.js");
21210
+ const { collectDiagnostics } = await import("../persistent-session-UZBB7EOS.js");
21054
21211
  await api.post("/host/heartbeat", {
21055
21212
  host_id: hostId,
21056
21213
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -21101,7 +21258,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
21101
21258
  }
21102
21259
  try {
21103
21260
  const hostId = await getHostId();
21104
- const { collectDiagnostics } = await import("../persistent-session-YP2UTQGA.js");
21261
+ const { collectDiagnostics } = await import("../persistent-session-UZBB7EOS.js");
21105
21262
  await api.post("/host/heartbeat", {
21106
21263
  host_id: hostId,
21107
21264
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -21772,7 +21929,7 @@ async function processClaudePairSessions(agents) {
21772
21929
  // cannot take it for itself — by the time it runs, the write it is looking
21773
21930
  // for may already have happened.
21774
21931
  snapshotPairConfig
21775
- } = await import("../claude-pair-runtime-GSPFYCG4.js");
21932
+ } = await import("../claude-pair-runtime-JQRJXNSL.js");
21776
21933
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
21777
21934
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
21778
21935
  const killed = await killPairSession(pairTmuxSession(pairId));