@integrity-labs/agt-cli 0.28.1148 → 0.28.1150

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.
Files changed (25) hide show
  1. package/dist/bin/agt.js +7 -7
  2. package/dist/{chunk-JZZW47PT.js → chunk-AJ2Z3VSN.js} +2 -2
  3. package/dist/{chunk-EZUCUG2W.js → chunk-HZFXWUA2.js} +10 -10
  4. package/dist/{chunk-LAPK7BDU.js → chunk-NSX27ZTP.js} +4 -4
  5. package/dist/{chunk-2PGIZQIJ.js → chunk-OISSVMYG.js} +12 -12
  6. package/dist/{chunk-D25NK5E5.js → chunk-TLDY7AE4.js} +5 -1
  7. package/dist/{chunk-D25NK5E5.js.map → chunk-TLDY7AE4.js.map} +1 -1
  8. package/dist/{claude-code-updater-NJMCC77Q.js → claude-code-updater-JXL4CM2V.js} +3 -3
  9. package/dist/{claude-pair-runtime-VQJGKE4W.js → claude-pair-runtime-7QPXNON3.js} +74 -11
  10. package/dist/claude-pair-runtime-7QPXNON3.js.map +1 -0
  11. package/dist/lib/manager-worker.js +298 -71
  12. package/dist/lib/manager-worker.js.map +1 -1
  13. package/dist/{persistent-session-MFHKSBD5.js → persistent-session-YKRO5RLK.js} +4 -4
  14. package/dist/{responsiveness-probe-YLIBVLAR.js → responsiveness-probe-LVRBC2AA.js} +4 -4
  15. package/dist/{session-auth-dead-PUVWMX5P.js → session-auth-dead-FREXPXHN.js} +3 -3
  16. package/package.json +1 -1
  17. package/dist/claude-pair-runtime-VQJGKE4W.js.map +0 -1
  18. /package/dist/{chunk-JZZW47PT.js.map → chunk-AJ2Z3VSN.js.map} +0 -0
  19. /package/dist/{chunk-EZUCUG2W.js.map → chunk-HZFXWUA2.js.map} +0 -0
  20. /package/dist/{chunk-LAPK7BDU.js.map → chunk-NSX27ZTP.js.map} +0 -0
  21. /package/dist/{chunk-2PGIZQIJ.js.map → chunk-OISSVMYG.js.map} +0 -0
  22. /package/dist/{claude-code-updater-NJMCC77Q.js.map → claude-code-updater-JXL4CM2V.js.map} +0 -0
  23. /package/dist/{persistent-session-MFHKSBD5.js.map → persistent-session-YKRO5RLK.js.map} +0 -0
  24. /package/dist/{responsiveness-probe-YLIBVLAR.js.map → responsiveness-probe-LVRBC2AA.js.map} +0 -0
  25. /package/dist/{session-auth-dead-PUVWMX5P.js.map → session-auth-dead-FREXPXHN.js.map} +0 -0
@@ -77,11 +77,11 @@ import {
77
77
  setConfigHash,
78
78
  shellGuard,
79
79
  tripClass
80
- } from "../chunk-EZUCUG2W.js";
80
+ } from "../chunk-HZFXWUA2.js";
81
81
  import {
82
82
  CLAUDE_CODE_SECURITY_FLOOR,
83
83
  compareVersions
84
- } from "../chunk-LAPK7BDU.js";
84
+ } from "../chunk-NSX27ZTP.js";
85
85
  import {
86
86
  getProjectDir as getProjectDir2,
87
87
  getReadyTasks,
@@ -168,7 +168,7 @@ import {
168
168
  webFetchBaselineValue,
169
169
  webFetchRestartNeeded,
170
170
  writeEgressAllowlist
171
- } from "../chunk-2PGIZQIJ.js";
171
+ } from "../chunk-OISSVMYG.js";
172
172
  import {
173
173
  isAgentIdle,
174
174
  isStaleForToday,
@@ -177,7 +177,7 @@ import {
177
177
  sessionTranscriptDir,
178
178
  subagentActivityAgeSeconds,
179
179
  transcriptActivityAgeSeconds
180
- } from "../chunk-JZZW47PT.js";
180
+ } from "../chunk-AJ2Z3VSN.js";
181
181
  import {
182
182
  classifyUnanswerablePane,
183
183
  focusedConfirmDialogBlock,
@@ -190,6 +190,13 @@ import {
190
190
  parsePsRows,
191
191
  reapOrphanChannelMcps
192
192
  } from "../chunk-XWVM4KPK.js";
193
+ import {
194
+ CLAUDE_CONFIG_DIR_ENV,
195
+ claudeConfigDirFor,
196
+ claudeConfigDirIsReady,
197
+ effectiveClaudeJsonPath,
198
+ seedClaudeConfigFromLegacy
199
+ } from "../chunk-EL5MWGGH.js";
193
200
  import {
194
201
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
195
202
  AnchorSessionClient,
@@ -293,14 +300,7 @@ import {
293
300
  serializeAccountEnforcementMarker,
294
301
  stabiliseResetInstant,
295
302
  sumTranscriptUsageInWindow
296
- } from "../chunk-D25NK5E5.js";
297
- import {
298
- CLAUDE_CONFIG_DIR_ENV,
299
- claudeConfigDirFor,
300
- claudeConfigDirIsReady,
301
- effectiveClaudeJsonPath,
302
- seedClaudeConfigFromLegacy
303
- } from "../chunk-EL5MWGGH.js";
303
+ } from "../chunk-TLDY7AE4.js";
304
304
  import "../chunk-DGUM43GV.js";
305
305
 
306
306
  // src/lib/manager-worker.ts
@@ -957,6 +957,156 @@ function claudeCodeUpgradeThrottled() {
957
957
  }
958
958
  }
959
959
 
960
+ // src/lib/claude-pair-loop.ts
961
+ var PAIR_LOOP_ACTIVE_INTERVAL_MS = 2e3;
962
+ var PAIR_LOOP_IDLE_INTERVAL_MS = 1e4;
963
+ var PAIR_LOOP_MAX_BACKOFF_MS = 12e4;
964
+ var PAIR_LOOP_ARMED_MAX_MS = 15 * 6e4;
965
+ function pairLoopArmed(opts) {
966
+ const max = opts.maxArmedMs ?? PAIR_LOOP_ARMED_MAX_MS;
967
+ const live = /* @__PURE__ */ new Set([...opts.spawnedPairIds, ...opts.pendingPairIds]);
968
+ for (const id of [...opts.firstSeenMs.keys()]) {
969
+ if (!live.has(id)) opts.firstSeenMs.delete(id);
970
+ }
971
+ let fresh = false;
972
+ for (const id of live) {
973
+ let seen = opts.firstSeenMs.get(id);
974
+ if (seen === void 0) {
975
+ seen = opts.nowMs;
976
+ opts.firstSeenMs.set(id, seen);
977
+ }
978
+ if (opts.nowMs - seen < max) fresh = true;
979
+ }
980
+ return fresh;
981
+ }
982
+ function nextPairLoopDelayMs(armed, consecutiveFailures) {
983
+ if (consecutiveFailures > 0) {
984
+ const exp = Math.min(consecutiveFailures - 1, 16);
985
+ return Math.min(PAIR_LOOP_IDLE_INTERVAL_MS * 2 ** exp, PAIR_LOOP_MAX_BACKOFF_MS);
986
+ }
987
+ return armed ? PAIR_LOOP_ACTIVE_INTERVAL_MS : PAIR_LOOP_IDLE_INTERVAL_MS;
988
+ }
989
+ var ClaudePairLoop = class {
990
+ constructor(deps) {
991
+ this.deps = deps;
992
+ this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
993
+ this.clearTimer = deps.clearTimer ?? ((h) => clearTimeout(h));
994
+ }
995
+ timer = null;
996
+ inFlight = false;
997
+ started = false;
998
+ consecutiveFailures = 0;
999
+ // Bumped by stop(): a timer callback from an earlier start() whose tick is
1000
+ // still in flight must not schedule a second timer chain after a restart.
1001
+ generation = 0;
1002
+ inFlightTick = null;
1003
+ setTimer;
1004
+ clearTimer;
1005
+ /** Arm the loop. The first tick runs after one idle interval. Idempotent. */
1006
+ start() {
1007
+ if (this.started) return;
1008
+ this.started = true;
1009
+ this.schedule(PAIR_LOOP_IDLE_INTERVAL_MS);
1010
+ }
1011
+ /**
1012
+ * Disarm the loop. No new tick starts, and a callback from this generation
1013
+ * will not reschedule. Returns a promise that settles when a tick already in
1014
+ * flight has finished (immediately if none), so shutdown can wait for it
1015
+ * before sweeping tmux sessions.
1016
+ */
1017
+ stop() {
1018
+ this.started = false;
1019
+ this.generation += 1;
1020
+ if (this.timer != null) this.clearTimer(this.timer);
1021
+ this.timer = null;
1022
+ const pending2 = this.inFlightTick;
1023
+ return pending2 ? pending2.then(() => void 0, () => void 0) : Promise.resolve();
1024
+ }
1025
+ /** True while started. A tick in flight checks this before spawning anything new. */
1026
+ get accepting() {
1027
+ return this.started;
1028
+ }
1029
+ get failures() {
1030
+ return this.consecutiveFailures;
1031
+ }
1032
+ /** True while a tick is running. Set synchronously before the tick starts. */
1033
+ get busy() {
1034
+ return this.inFlight;
1035
+ }
1036
+ /** One guarded pass. Safe to call from anywhere; never overlaps itself. */
1037
+ async runOnce() {
1038
+ if (!this.deps.canRun()) return "skipped-not-running";
1039
+ if (this.inFlight) return "skipped-in-flight";
1040
+ this.inFlight = true;
1041
+ try {
1042
+ const tick = this.deps.tick();
1043
+ this.inFlightTick = tick;
1044
+ await tick;
1045
+ if (this.consecutiveFailures > 0) {
1046
+ this.deps.log(`[claude-pair] loop recovered after ${this.consecutiveFailures} failed tick(s)`);
1047
+ }
1048
+ this.consecutiveFailures = 0;
1049
+ return "ran";
1050
+ } catch (err) {
1051
+ this.consecutiveFailures += 1;
1052
+ if (this.consecutiveFailures === 1 || this.consecutiveFailures % 10 === 0) {
1053
+ this.deps.log(
1054
+ `[claude-pair] poll failed (${this.consecutiveFailures} in a row): ${err?.message ?? String(err)}`
1055
+ );
1056
+ }
1057
+ return "failed";
1058
+ } finally {
1059
+ this.inFlight = false;
1060
+ this.inFlightTick = null;
1061
+ }
1062
+ }
1063
+ schedule(ms) {
1064
+ if (!this.started) return;
1065
+ const gen = this.generation;
1066
+ const h = this.setTimer(() => {
1067
+ this.timer = null;
1068
+ if (gen !== this.generation) return;
1069
+ void this.runOnce().finally(() => {
1070
+ if (gen !== this.generation) return;
1071
+ let armed = false;
1072
+ try {
1073
+ armed = this.deps.isArmed();
1074
+ } catch {
1075
+ armed = false;
1076
+ }
1077
+ this.schedule(nextPairLoopDelayMs(armed, this.consecutiveFailures));
1078
+ });
1079
+ }, ms);
1080
+ if (h && typeof h === "object" && typeof h.unref === "function") h.unref();
1081
+ this.timer = h;
1082
+ }
1083
+ };
1084
+ var PairRetryBackoff = class {
1085
+ state = /* @__PURE__ */ new Map();
1086
+ /** True while this pair is inside its backoff window: skip it this tick. */
1087
+ shouldSkip(pairId, nowMs) {
1088
+ const s = this.state.get(pairId);
1089
+ return !!s && nowMs < s.notBeforeMs;
1090
+ }
1091
+ recordFailure(pairId, nowMs) {
1092
+ const failures = (this.state.get(pairId)?.failures ?? 0) + 1;
1093
+ this.state.set(pairId, { failures, notBeforeMs: nowMs + nextPairLoopDelayMs(true, failures) });
1094
+ return failures;
1095
+ }
1096
+ /** A dispatch that completed clears the pair's backoff. */
1097
+ recordSuccess(pairId) {
1098
+ this.state.delete(pairId);
1099
+ }
1100
+ /** Forget pairs that are neither spawned nor pending any more. */
1101
+ prune(liveIds) {
1102
+ const live = new Set(liveIds);
1103
+ for (const id of [...this.state.keys()]) if (!live.has(id)) this.state.delete(id);
1104
+ }
1105
+ failuresFor(pairId) {
1106
+ return this.state.get(pairId)?.failures ?? 0;
1107
+ }
1108
+ };
1109
+
960
1110
  // src/lib/mcp-config-drift.ts
961
1111
  import { createHash } from "crypto";
962
1112
  function decideMcpDriftAction(currentHash, knownHash) {
@@ -14891,7 +15041,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
14891
15041
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
14892
15042
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
14893
15043
  async function channelInboundActivityAgeSecondsFor(codeName) {
14894
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-YLIBVLAR.js");
15044
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-LVRBC2AA.js");
14895
15045
  const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
14896
15046
  if (newest === null) return null;
14897
15047
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -15649,7 +15799,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
15649
15799
  var lastVersionCheckAt = 0;
15650
15800
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
15651
15801
  var lastResponsivenessProbeAt = 0;
15652
- var agtCliVersion = true ? "0.28.1148" : "dev";
15802
+ var agtCliVersion = true ? "0.28.1150" : "dev";
15653
15803
  function resolveBrewPath(execFileSync3) {
15654
15804
  try {
15655
15805
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -17424,7 +17574,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
17424
17574
  if (codeNames.length === 0) return;
17425
17575
  void (async () => {
17426
17576
  try {
17427
- const { collectDiagnostics } = await import("../persistent-session-MFHKSBD5.js");
17577
+ const { collectDiagnostics } = await import("../persistent-session-YKRO5RLK.js");
17428
17578
  await api.post("/host/heartbeat", {
17429
17579
  host_id: hostId,
17430
17580
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -17566,7 +17716,7 @@ async function pollCycleInner() {
17566
17716
  }
17567
17717
  try {
17568
17718
  const { detectHostSecurity } = await import("../host-security-KQYYZZVW.js");
17569
- const { collectDiagnostics } = await import("../persistent-session-MFHKSBD5.js");
17719
+ const { collectDiagnostics } = await import("../persistent-session-YKRO5RLK.js");
17570
17720
  const diagCodeNames = [...agentState.persistentSessionAgents];
17571
17721
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor) : void 0;
17572
17722
  let tailscaleHostname;
@@ -17766,8 +17916,13 @@ async function pollCycleInner() {
17766
17916
  setConfigHash(hbResp?.config_hash ?? null);
17767
17917
  try {
17768
17918
  const desiredClaudeCodeVersion = hbResp?.desired_claude_code_version?.trim();
17769
- const { maybeUpdateClaudeCode, shouldRunClaudeCodeUpdater } = await import("../claude-code-updater-NJMCC77Q.js");
17770
- if (desiredClaudeCodeVersion && shouldRunClaudeCodeUpdater({
17919
+ const { maybeUpdateClaudeCode, shouldRunClaudeCodeUpdater } = await import("../claude-code-updater-JXL4CM2V.js");
17920
+ claudeUpdaterInFlight = true;
17921
+ if (desiredClaudeCodeVersion && (claudePairLoop.busy || claudePairingInProgress())) {
17922
+ log(
17923
+ claudePairingInProgress() ? "[claude-code-updater] Claude re-login in progress on this host, deferring to a later cycle" : "[claude-code-updater] a claude-pair poll is mid-flight (no re-login), deferring to the next cycle"
17924
+ );
17925
+ } else if (desiredClaudeCodeVersion && shouldRunClaudeCodeUpdater({
17771
17926
  desiredVersion: desiredClaudeCodeVersion,
17772
17927
  installedVersion: cachedFrameworkVersion,
17773
17928
  windowGate: decideMaintenanceWindowGate({ window: cachedMaintenanceWindow, now: /* @__PURE__ */ new Date() })
@@ -17780,6 +17935,8 @@ async function pollCycleInner() {
17780
17935
  }
17781
17936
  } catch (err) {
17782
17937
  log(`Claude Code updater error: ${err.message}`);
17938
+ } finally {
17939
+ claudeUpdaterInFlight = false;
17783
17940
  }
17784
17941
  } catch (err) {
17785
17942
  setConfigHash(null);
@@ -17791,7 +17948,7 @@ async function pollCycleInner() {
17791
17948
  collectPanelessActivityProbes,
17792
17949
  getResponsivenessIntervalMs,
17793
17950
  occupancyQualificationClassifications
17794
- } = await import("../responsiveness-probe-YLIBVLAR.js");
17951
+ } = await import("../responsiveness-probe-LVRBC2AA.js");
17795
17952
  const probeIntervalMs = getResponsivenessIntervalMs();
17796
17953
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
17797
17954
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -17908,7 +18065,7 @@ async function pollCycleInner() {
17908
18065
  collectResponsivenessProbes,
17909
18066
  livePendingInboundOldestAgeSeconds,
17910
18067
  parkPendingInbound
17911
- } = await import("../responsiveness-probe-YLIBVLAR.js");
18068
+ } = await import("../responsiveness-probe-LVRBC2AA.js");
17912
18069
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-M6AFZWNU.js");
17913
18070
  const wedgeNow = /* @__PURE__ */ new Date();
17914
18071
  const liveAgents = agentState.persistentSessionAgents;
@@ -18194,7 +18351,7 @@ async function pollCycleInner() {
18194
18351
  }
18195
18352
  }
18196
18353
  );
18197
- const { parkPendingInbound } = await import("../responsiveness-probe-YLIBVLAR.js");
18354
+ const { parkPendingInbound } = await import("../responsiveness-probe-LVRBC2AA.js");
18198
18355
  const { parked, deadLettered } = parkPendingInbound(codeName, new Date(fatalNowMs));
18199
18356
  paneFatalStreak.delete(codeName);
18200
18357
  paneFatalRestarts.set(codeName, [...history, fatalNowMs]);
@@ -18225,7 +18382,7 @@ async function pollCycleInner() {
18225
18382
  TRANSCRIPT_MOUNT_ESCALATION_REFRESH_MS,
18226
18383
  bindSourceEscalations
18227
18384
  } = await import("../transcript-mount-health-OHE6IVJV.js");
18228
- const { transcriptBindSourcesFor, ensureTranscriptBindSources } = await import("../persistent-session-MFHKSBD5.js");
18385
+ const { transcriptBindSourcesFor, ensureTranscriptBindSources } = await import("../persistent-session-YKRO5RLK.js");
18229
18386
  const mountConfig = resolveTranscriptMountConfig();
18230
18387
  const liveAgents = agentState.persistentSessionAgents;
18231
18388
  for (const tracked of [
@@ -18390,7 +18547,7 @@ async function pollCycleInner() {
18390
18547
  }
18391
18548
  try {
18392
18549
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-VR5QGP3J.js");
18393
- const { probeSessionAuth } = await import("../session-auth-dead-PUVWMX5P.js");
18550
+ const { probeSessionAuth } = await import("../session-auth-dead-FREXPXHN.js");
18394
18551
  const observations = [];
18395
18552
  const pendingCacheCommits = [];
18396
18553
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -18460,6 +18617,10 @@ async function pollCycleInner() {
18460
18617
  }
18461
18618
  const data = await api.post("/host/agents", { host_id: hostId });
18462
18619
  const agents = data.agents ?? [];
18620
+ pairableAgentsSnapshot = agents.map((a) => ({
18621
+ agentId: a.agent_id,
18622
+ codeName: a.code_name
18623
+ }));
18463
18624
  hostFlagStore().setMirrorAgentIds(agents.map((a) => a.agent_id));
18464
18625
  if (agents.length > 0) {
18465
18626
  if (consecutiveEmptyAgentPolls > 0) {
@@ -18881,14 +19042,6 @@ async function pollCycleInner() {
18881
19042
  clearAgentCaches(prev.agentId, prev.codeName);
18882
19043
  }
18883
19044
  }
18884
- try {
18885
- await processClaudePairSessions(agents.map((a) => ({
18886
- agentId: a.agent_id,
18887
- codeName: a.code_name
18888
- })));
18889
- } catch (err) {
18890
- log(`[claude-pair] poll failed: ${err.message}`);
18891
- }
18892
19045
  if (Date.now() - lastChannelSweepAt >= CHANNEL_SWEEP_INTERVAL_MS) {
18893
19046
  lastChannelSweepAt = Date.now();
18894
19047
  const agentCodeNames = new Set(
@@ -22269,7 +22422,11 @@ async function ensurePersistentSessionUnchecked(agent, tasks, boardItems, refres
22269
22422
  const currentPolicyId = currentPolicyModel ? modelPolicy?.policyId ?? null : null;
22270
22423
  const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);
22271
22424
  const recordedPolicyModel = modelPolicyModelBySession.get(codeName);
22272
- if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
22425
+ const authTupleChanged = Boolean(recordedAuthTuple && recordedAuthTuple !== currentAuthTuple);
22426
+ if (authTupleChanged && claudePairingInProgress()) {
22427
+ log(`[${codeName}] auth tuple changed while a Claude re-login is in progress, deferring the restart to the next cycle`);
22428
+ }
22429
+ if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && !claudePairingInProgress() && isSessionHealthy(codeName)) {
22273
22430
  const authTupleDiff = diffAuthTuples(recordedAuthTuple, currentAuthTuple);
22274
22431
  log(
22275
22432
  `[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 ${authTupleDiff.summary} \u2014 restarting session`
@@ -22280,7 +22437,9 @@ async function ensurePersistentSessionUnchecked(agent, tasks, boardItems, refres
22280
22437
  previousModel: recordedPolicyModel,
22281
22438
  currentModel: currentPolicyModel
22282
22439
  });
22283
- if (policyNotice && isSessionHealthy(codeName)) {
22440
+ let restartNoticeLanded = false;
22441
+ const noticeAlreadyDelivered = restartNoticeDeliveredFor.get(codeName) === currentAuthTuple;
22442
+ if (policyNotice && isSessionHealthy(codeName) && !noticeAlreadyDelivered) {
22284
22443
  const status = await injectMessageWithStatus(
22285
22444
  codeName,
22286
22445
  "system",
@@ -22293,6 +22452,7 @@ async function ensurePersistentSessionUnchecked(agent, tasks, boardItems, refres
22293
22452
  log
22294
22453
  ).catch(() => ({ delivered: false, fallbackUsed: false }));
22295
22454
  if (status.fallbackUsed) {
22455
+ restartNoticeLanded = true;
22296
22456
  await new Promise((r) => setTimeout(r, 3e3));
22297
22457
  } else {
22298
22458
  log(
@@ -22300,18 +22460,35 @@ async function ensurePersistentSessionUnchecked(agent, tasks, boardItems, refres
22300
22460
  );
22301
22461
  }
22302
22462
  }
22303
- stopPersistentSessionAndForgetMcpBaseline(codeName, "auth-tuple-change", void 0, void 0, {
22304
- reason: authTupleDiff.summary,
22305
- detail: {
22306
- auth_tuple: {
22307
- changed: authTupleDiff.changed,
22308
- unchanged: authTupleDiff.unchanged,
22309
- components: authTupleDiff.components
22310
- }
22463
+ if (claudePairingInProgress()) {
22464
+ if (restartNoticeLanded) {
22465
+ restartNoticeDeliveredFor.set(codeName, currentAuthTuple);
22466
+ await injectMessageWithStatus(
22467
+ codeName,
22468
+ "system",
22469
+ "Update: the restart announced above is postponed for a moment while a Claude sign-in finishes on this host. Carry on; it will happen shortly, without a further notice.",
22470
+ { task_name: "model-policy-restart-postponed" },
22471
+ log
22472
+ ).catch(() => void 0);
22311
22473
  }
22312
- });
22313
- agentState.persistentSessionAgents.delete(codeName);
22314
- restartTrigger = "auth-tuple";
22474
+ log(
22475
+ `[${codeName}] Claude re-login in progress, deferring the auth-tuple restart to the next cycle` + (restartNoticeLanded || noticeAlreadyDelivered ? " (restart notice already delivered, not re-sent)" : "")
22476
+ );
22477
+ } else {
22478
+ restartNoticeDeliveredFor.delete(codeName);
22479
+ stopPersistentSessionAndForgetMcpBaseline(codeName, "auth-tuple-change", void 0, void 0, {
22480
+ reason: authTupleDiff.summary,
22481
+ detail: {
22482
+ auth_tuple: {
22483
+ changed: authTupleDiff.changed,
22484
+ unchanged: authTupleDiff.unchanged,
22485
+ components: authTupleDiff.components
22486
+ }
22487
+ }
22488
+ });
22489
+ agentState.persistentSessionAgents.delete(codeName);
22490
+ restartTrigger = "auth-tuple";
22491
+ }
22315
22492
  }
22316
22493
  const staleForToday = isStaleForToday(codeName, /* @__PURE__ */ new Date(), agentTimezone);
22317
22494
  if (!staleForToday) pendingDayRolloverReset.delete(codeName);
@@ -23030,7 +23207,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
23030
23207
  try {
23031
23208
  const freshId = rotateSessionForWedge(codeName);
23032
23209
  consecutiveWedgeCycles.delete(codeName);
23033
- const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-YLIBVLAR.js");
23210
+ const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-LVRBC2AA.js");
23034
23211
  const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
23035
23212
  const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
23036
23213
  const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
@@ -23073,7 +23250,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
23073
23250
  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}`));
23074
23251
  void (async () => {
23075
23252
  try {
23076
- const { collectDiagnostics } = await import("../persistent-session-MFHKSBD5.js");
23253
+ const { collectDiagnostics } = await import("../persistent-session-YKRO5RLK.js");
23077
23254
  await api.post("/host/heartbeat", {
23078
23255
  host_id: hostId,
23079
23256
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -23124,7 +23301,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
23124
23301
  }
23125
23302
  try {
23126
23303
  const hostId = await getHostId();
23127
- const { collectDiagnostics } = await import("../persistent-session-MFHKSBD5.js");
23304
+ const { collectDiagnostics } = await import("../persistent-session-YKRO5RLK.js");
23128
23305
  await api.post("/host/heartbeat", {
23129
23306
  host_id: hostId,
23130
23307
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -23771,6 +23948,47 @@ var boardTokenCache = new BoardTokenCache();
23771
23948
  var notifyBoardCache = /* @__PURE__ */ new Map();
23772
23949
  var notifiedWaitingHumanIds = /* @__PURE__ */ new Map();
23773
23950
  var spawnedPairIds = /* @__PURE__ */ new Set();
23951
+ var pairableAgentsSnapshot = [];
23952
+ var lastPairPendingIds = [];
23953
+ var pairFirstSeenMs = /* @__PURE__ */ new Map();
23954
+ function claudePairLoopArmed() {
23955
+ return pairLoopArmed({
23956
+ spawnedPairIds,
23957
+ pendingPairIds: lastPairPendingIds,
23958
+ firstSeenMs: pairFirstSeenMs,
23959
+ nowMs: Date.now()
23960
+ });
23961
+ }
23962
+ var pairRetryBackoff = new PairRetryBackoff();
23963
+ var restartNoticeDeliveredFor = /* @__PURE__ */ new Map();
23964
+ var claudeUpdaterInFlight = false;
23965
+ async function runClaudePairTick() {
23966
+ if (pairableAgentsSnapshot.length === 0 && spawnedPairIds.size === 0) lastPairPendingIds = [];
23967
+ if (claudeUpdaterInFlight) return;
23968
+ await processClaudePairSessions(pairableAgentsSnapshot);
23969
+ pairRetryBackoff.prune([...spawnedPairIds, ...lastPairPendingIds]);
23970
+ }
23971
+ async function _claudePairTickForTests(agents) {
23972
+ const prevRunning = running;
23973
+ running = true;
23974
+ claudePairLoop.start();
23975
+ pairableAgentsSnapshot = [...agents];
23976
+ try {
23977
+ await runClaudePairTick();
23978
+ } finally {
23979
+ void claudePairLoop.stop();
23980
+ running = prevRunning;
23981
+ }
23982
+ }
23983
+ function claudePairingInProgress() {
23984
+ return claudePairLoopArmed();
23985
+ }
23986
+ var claudePairLoop = new ClaudePairLoop({
23987
+ tick: runClaudePairTick,
23988
+ isArmed: claudePairLoopArmed,
23989
+ canRun: () => running && !!config && !restartAfterUpgrade,
23990
+ log
23991
+ });
23774
23992
  async function processClaudePairSessions(agents) {
23775
23993
  if (agents.length === 0 && spawnedPairIds.size === 0) return;
23776
23994
  const agentIds = agents.map((a) => a.agentId);
@@ -23779,6 +23997,7 @@ async function processClaudePairSessions(agents) {
23779
23997
  agent_ids: agentIds,
23780
23998
  spawned_pair_ids: Array.from(spawnedPairIds)
23781
23999
  });
24000
+ lastPairPendingIds = (pendingResp.pending ?? []).map((p) => p.pair_id);
23782
24001
  const {
23783
24002
  startClaudePair,
23784
24003
  submitClaudePairCode,
@@ -23796,8 +24015,10 @@ async function processClaudePairSessions(agents) {
23796
24015
  // ENG-10862: the pre-submit mtime baseline. Taken here because finalize
23797
24016
  // cannot take it for itself — by the time it runs, the write it is looking
23798
24017
  // for may already have happened.
23799
- snapshotPairConfig
23800
- } = await import("../claude-pair-runtime-VQJGKE4W.js");
24018
+ snapshotPairConfig,
24019
+ // ENG-11659: result -> report body + the manager.log line that records it.
24020
+ planPairStartReport
24021
+ } = await import("../claude-pair-runtime-7QPXNON3.js");
23801
24022
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
23802
24023
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
23803
24024
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -23815,7 +24036,10 @@ async function processClaudePairSessions(agents) {
23815
24036
  "timeout"
23816
24037
  ]);
23817
24038
  async function reportAndCleanup(pairId, body) {
23818
- await api.post("/host/claude-pair/result", { pair_id: pairId, ...body });
24039
+ const resp = await api.post("/host/claude-pair/result", {
24040
+ pair_id: pairId,
24041
+ ...body
24042
+ });
23819
24043
  if (typeof body.status === "string" && TERMINAL.has(body.status)) {
23820
24044
  const isOnboardingFailure = typeof body.error_code === "string" && body.error_code.startsWith("onboarding_");
23821
24045
  if (body.status === "success" || isOnboardingFailure) {
@@ -23826,8 +24050,12 @@ async function processClaudePairSessions(agents) {
23826
24050
  spawnedPairIds.delete(pairId);
23827
24051
  }
23828
24052
  }
24053
+ return resp?.no_op !== true;
23829
24054
  }
23830
24055
  for (const session of pendingResp.pending) {
24056
+ if (!running || !claudePairLoop.accepting) break;
24057
+ if (pairRetryBackoff.shouldSkip(session.pair_id, Date.now())) continue;
24058
+ let dispatchFailed = false;
23831
24059
  const codeName = codeNameByAgentId.get(session.agent_id) ?? "<unassigned>";
23832
24060
  const pairSession = pairTmuxSession(session.pair_id);
23833
24061
  try {
@@ -23847,26 +24075,13 @@ async function processClaudePairSessions(agents) {
23847
24075
  spawnedPairIds.add(session.pair_id);
23848
24076
  log(`[claude-pair] dispatching /login (pair ${session.pair_id.slice(0, 8)})`);
23849
24077
  const result = await startClaudePair({ session: pairSession });
23850
- if (result.kind === "url") {
23851
- await api.post("/host/claude-pair/result", {
23852
- pair_id: session.pair_id,
23853
- status: "awaiting_code",
23854
- url: result.url
23855
- });
23856
- } else if (result.kind === "timeout") {
23857
- await reportAndCleanup(session.pair_id, {
23858
- status: "timeout",
23859
- error_code: "url_prompt_not_seen",
23860
- error_message: "Claude Code did not print the URL prompt within the deadline"
23861
- });
23862
- } else {
23863
- const errKind = result.error.kind;
23864
- const errMessage2 = "message" in result.error ? result.error.message : void 0;
23865
- await reportAndCleanup(session.pair_id, {
23866
- status: errKind === "no-session" ? "session_missing" : "failure",
23867
- error_code: errKind,
23868
- error_message: errMessage2
23869
- });
24078
+ const report = planPairStartReport(session.pair_id, result);
24079
+ log(report.logLine);
24080
+ const accepted = await reportAndCleanup(session.pair_id, report.body);
24081
+ if (!accepted) {
24082
+ log(
24083
+ `[claude-pair] result for pair ${session.pair_id.slice(0, 8)} was a server no_op: the row was already terminal (e.g. cancelled from the console) when it landed`
24084
+ );
23870
24085
  }
23871
24086
  } else if (session.status === "code_submitted" && session.code) {
23872
24087
  log(`[claude-pair] submitting code (pair ${session.pair_id.slice(0, 8)})`);
@@ -23937,7 +24152,13 @@ async function processClaudePairSessions(agents) {
23937
24152
  }
23938
24153
  }
23939
24154
  } catch (err) {
23940
- log(`[claude-pair] dispatch failed for pair ${session.pair_id.slice(0, 8)}: ${err.message}`);
24155
+ dispatchFailed = true;
24156
+ const failures = pairRetryBackoff.recordFailure(session.pair_id, Date.now());
24157
+ log(
24158
+ `[claude-pair] dispatch failed for pair ${session.pair_id.slice(0, 8)} (${failures} in a row, retrying in ${Math.round(nextPairLoopDelayMs(true, failures) / 1e3)}s): ${err.message}`
24159
+ );
24160
+ } finally {
24161
+ if (!dispatchFailed) pairRetryBackoff.recordSuccess(session.pair_id);
23941
24162
  }
23942
24163
  }
23943
24164
  }
@@ -24624,6 +24845,7 @@ function startPolling() {
24624
24845
  void postLivenessHeartbeat();
24625
24846
  }, LIVENESS_HEARTBEAT_INTERVAL_MS);
24626
24847
  livenessTimer.unref();
24848
+ claudePairLoop.start();
24627
24849
  startPaneOccupancySampler(() => agentState.persistentSessionAgents);
24628
24850
  startPidPressureSampler(() => agentState.persistentSessionAgents);
24629
24851
  startRuntimeVersionSampler(() => agentState.persistentSessionAgents);
@@ -24790,6 +25012,7 @@ async function stopPolling(opts = {}) {
24790
25012
  clearInterval(livenessTimer);
24791
25013
  livenessTimer = null;
24792
25014
  }
25015
+ const pairLoopDrained = claudePairLoop.stop();
24793
25016
  stopPaneOccupancySampler();
24794
25017
  stopPidPressureSampler();
24795
25018
  stopRuntimeVersionSampler();
@@ -24836,6 +25059,7 @@ async function stopPolling(opts = {}) {
24836
25059
  closeSessionRunForCode(codeName, "completed", "manager shutdown");
24837
25060
  }
24838
25061
  const drainReleaseSnapshot = snapshotDrainReleaseTargets();
25062
+ await Promise.race([pairLoopDrained, new Promise((r) => setTimeout(r, 3e3).unref())]);
24839
25063
  log("Killing tmux sessions...");
24840
25064
  try {
24841
25065
  await killAllAgtTmuxSessions();
@@ -25205,6 +25429,7 @@ export {
25205
25429
  REALTIME_REBIND_STUCK_AFTER_MS,
25206
25430
  SELF_UPDATE_LATCH_MAX_HOLD_MS,
25207
25431
  UntrustworthyToolkitResponseError,
25432
+ _claudePairTickForTests,
25208
25433
  applyMemoryTombstones,
25209
25434
  applyRestartAcks,
25210
25435
  cancelKanbanNoticeOnDrain,
@@ -25213,6 +25438,8 @@ export {
25213
25438
  claudeCodeUpgradeMarkerPath,
25214
25439
  claudeCodeUpgradeThrottled,
25215
25440
  claudeManagedSettingsPath,
25441
+ claudePairLoopArmed,
25442
+ claudePairingInProgress,
25216
25443
  coverageOf,
25217
25444
  createManagedPruneLedger,
25218
25445
  createManagedToolkitReader,