@integrity-labs/agt-cli 0.28.272 → 0.28.274

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.
@@ -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-HOILVE3Q.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-SDDSZN5E.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-KOEEK5LD.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-Z726QSAR.js.map
@@ -38,7 +38,7 @@ import {
38
38
  requireHost,
39
39
  safeWriteJsonAtomic,
40
40
  setConfigHash
41
- } from "../chunk-5HCINBKO.js";
41
+ } from "../chunk-73NA6BKT.js";
42
42
  import {
43
43
  getProjectDir as getProjectDir2,
44
44
  getReadyTasks,
@@ -52,6 +52,7 @@ import {
52
52
  FLAGS_SCHEMA_VERSION,
53
53
  FLAG_REGISTRY,
54
54
  KANBAN_CHECK_COMMAND,
55
+ KANBAN_NUDGE_ACTIONABLE_STATUSES,
55
56
  MAX_AVATAR_ENV_URL_BYTES,
56
57
  SUPPRESS_SENTINEL,
57
58
  StreamEncoder,
@@ -124,7 +125,7 @@ import {
124
125
  takeZombieDetection,
125
126
  transcriptActivityAgeSeconds,
126
127
  writeEgressAllowlist
127
- } from "../chunk-5TL5THQE.js";
128
+ } from "../chunk-THMHJITM.js";
128
129
  import {
129
130
  reapOrphanChannelMcps
130
131
  } from "../chunk-XWVM4KPK.js";
@@ -610,8 +611,27 @@ function isQuarantineFile(value) {
610
611
  function reaperRestartBreakerReason(activeKeys) {
611
612
  return activeKeys.length >= 2 ? "mcp-presence-reaper" : void 0;
612
613
  }
614
+ var PROVISIONING_RELOAD_REASONS = /* @__PURE__ */ new Set([
615
+ "hot-reload-mcp",
616
+ "managed-mcp-churn",
617
+ "bind-remediation"
618
+ ]);
619
+ function isProvisioningReloadReason(reason) {
620
+ return PROVISIONING_RELOAD_REASONS.has(reason);
621
+ }
622
+ function countByClass(events) {
623
+ let crash = 0;
624
+ let provisioning = 0;
625
+ for (const e of events) {
626
+ if (isProvisioningReloadReason(e.reason)) provisioning += 1;
627
+ else crash += 1;
628
+ }
629
+ return { crash, provisioning };
630
+ }
613
631
  var DEFAULT_MAX = 2;
614
632
  var DEFAULT_WINDOW_MS = 6e5;
633
+ var DEFAULT_PROVISIONING_MAX = 5;
634
+ var DEFAULT_PROVISIONING_WINDOW_MS = 18e5;
615
635
  function readEnvNumber(name, fallback) {
616
636
  const raw = process.env[name];
617
637
  if (!raw) return fallback;
@@ -621,12 +641,18 @@ function readEnvNumber(name, fallback) {
621
641
  var RestartBreaker = class {
622
642
  max;
623
643
  windowMs;
644
+ provisioningMax;
645
+ provisioningWindowMs;
646
+ /** Longest window either tally needs — how long the events log must retain. */
647
+ retentionMs;
624
648
  now;
625
649
  events = /* @__PURE__ */ new Map();
626
650
  trips = /* @__PURE__ */ new Map();
627
651
  constructor(opts = {}) {
628
652
  this.max = opts.max ?? readEnvNumber("AGT_RESTART_BREAKER_MAX", DEFAULT_MAX);
629
653
  this.windowMs = opts.windowMs ?? readEnvNumber("AGT_RESTART_BREAKER_WINDOW_MS", DEFAULT_WINDOW_MS);
654
+ this.provisioningMax = opts.provisioningMax ?? readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_MAX", DEFAULT_PROVISIONING_MAX);
655
+ this.provisioningWindowMs = opts.provisioningWindowMs ?? readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_WINDOW_MS", DEFAULT_PROVISIONING_WINDOW_MS);
630
656
  this.now = opts.now ?? Date.now;
631
657
  if (!Number.isFinite(this.max) || this.max < 1) {
632
658
  throw new Error("restart-breaker max must be a finite number >= 1");
@@ -634,6 +660,13 @@ var RestartBreaker = class {
634
660
  if (!Number.isFinite(this.windowMs) || this.windowMs < 1e3) {
635
661
  throw new Error("restart-breaker windowMs must be a finite number >= 1000");
636
662
  }
663
+ if (!Number.isFinite(this.provisioningMax) || this.provisioningMax < 1) {
664
+ throw new Error("restart-breaker provisioningMax must be a finite number >= 1");
665
+ }
666
+ if (!Number.isFinite(this.provisioningWindowMs) || this.provisioningWindowMs < 1e3) {
667
+ throw new Error("restart-breaker provisioningWindowMs must be a finite number >= 1000");
668
+ }
669
+ this.retentionMs = Math.max(this.windowMs, this.provisioningWindowMs);
637
670
  }
638
671
  /** True if this agent's breaker is currently tripped (manager must skip spawn). */
639
672
  isTripped(codeName) {
@@ -654,24 +687,54 @@ var RestartBreaker = class {
654
687
  record(codeName, reason) {
655
688
  const existing = this.trips.get(codeName);
656
689
  if (existing) {
657
- return { tripped: false, trip: existing, windowCount: existing.eventsAtTrip.length };
690
+ return {
691
+ tripped: false,
692
+ trip: existing,
693
+ windowCount: existing.eventsAtTrip.length,
694
+ classCounts: countByClass(existing.eventsAtTrip)
695
+ };
658
696
  }
659
697
  const at = this.now();
660
- const cutoff = at - this.windowMs;
661
- const prior = (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff);
698
+ const retentionCutoff = at - this.retentionMs;
699
+ const prior = (this.events.get(codeName) ?? []).filter((e) => e.at >= retentionCutoff);
662
700
  prior.push({ reason, at });
663
701
  this.events.set(codeName, prior);
664
- if (prior.length > this.max) {
702
+ const crashCount = prior.filter(
703
+ (e) => e.at >= at - this.windowMs && !isProvisioningReloadReason(e.reason)
704
+ ).length;
705
+ const provEvents = prior.filter(
706
+ (e) => e.at >= at - this.provisioningWindowMs && isProvisioningReloadReason(e.reason)
707
+ );
708
+ const windowCount = prior.filter((e) => e.at >= at - this.windowMs).length;
709
+ const classCounts = {
710
+ crash: crashCount,
711
+ provisioning: provEvents.length
712
+ };
713
+ let trippedClass;
714
+ let tripEvents;
715
+ let tripWindowMs = this.windowMs;
716
+ if (crashCount > this.max) {
717
+ trippedClass = "crash";
718
+ tripEvents = prior.filter(
719
+ (e) => e.at >= at - this.windowMs && !isProvisioningReloadReason(e.reason)
720
+ );
721
+ tripWindowMs = this.windowMs;
722
+ } else if (provEvents.length > this.provisioningMax) {
723
+ trippedClass = "provisioning";
724
+ tripEvents = provEvents;
725
+ tripWindowMs = this.provisioningWindowMs;
726
+ }
727
+ if (trippedClass && tripEvents) {
665
728
  const trip = {
666
729
  trippedAt: at,
667
- eventsAtTrip: [...prior],
668
- statusMessage: formatStatusMessage(prior, this.windowMs)
730
+ eventsAtTrip: [...tripEvents],
731
+ statusMessage: formatStatusMessage(tripEvents, tripWindowMs, trippedClass)
669
732
  };
670
733
  this.trips.set(codeName, trip);
671
734
  this.events.delete(codeName);
672
- return { tripped: true, trip, windowCount: prior.length };
735
+ return { tripped: true, trip, windowCount, classCounts, trippedClass };
673
736
  }
674
- return { tripped: false, windowCount: prior.length };
737
+ return { tripped: false, windowCount, classCounts };
675
738
  }
676
739
  /** Operator-initiated reset: drops the trip + the events log for this agent. */
677
740
  clear(codeName) {
@@ -702,13 +765,14 @@ var RestartBreaker = class {
702
765
  return (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff).length;
703
766
  }
704
767
  };
705
- function formatStatusMessage(events, windowMs) {
768
+ function formatStatusMessage(events, windowMs, klass = "crash") {
706
769
  const last = events[events.length - 1];
707
770
  const windowLabel = windowMs < 6e4 ? `${Math.round(windowMs / 1e3)}s` : `${(windowMs / 6e4).toFixed(1).replace(/\.0$/, "")}min`;
708
771
  const reasonCounts = /* @__PURE__ */ new Map();
709
772
  for (const e of events) reasonCounts.set(e.reason, (reasonCounts.get(e.reason) ?? 0) + 1);
710
773
  const breakdown = Array.from(reasonCounts.entries()).map(([r, n]) => `${r}=${n}`).join(", ");
711
- return `Circuit breaker tripped: ${events.length} restarts in ${windowLabel} (${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`;
774
+ const classLabel = klass === "provisioning" ? "provisioning-reload " : "";
775
+ return `Circuit breaker tripped: ${events.length} ${classLabel}restarts in ${windowLabel} (${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`;
712
776
  }
713
777
 
714
778
  // src/lib/mcp-flap-dampener.ts
@@ -3745,7 +3809,7 @@ function isKanbanHybridDryRun() {
3745
3809
  const v = process.env["AGT_KANBAN_HYBRID_DRY_RUN"];
3746
3810
  return v === "1" || v?.toLowerCase() === "true";
3747
3811
  }
3748
- var HYBRID_ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress"]);
3812
+ var HYBRID_ACTIONABLE_STATUSES = new Set(KANBAN_NUDGE_ACTIONABLE_STATUSES);
3749
3813
  function isHybridActionable(item) {
3750
3814
  return HYBRID_ACTIONABLE_STATUSES.has(item.status) && item.source_type !== "scheduled_task";
3751
3815
  }
@@ -4016,6 +4080,21 @@ async function enqueueKanbanNotice(opts) {
4016
4080
  return false;
4017
4081
  }
4018
4082
  }
4083
+ async function cancelKanbanNotice(agentId) {
4084
+ try {
4085
+ const res = await api.post(
4086
+ "/host/kanban/notify/cancel",
4087
+ { agent_id: agentId }
4088
+ );
4089
+ if (res.ok !== true) return null;
4090
+ return typeof res.cancelled === "number" ? res.cancelled : 0;
4091
+ } catch (err) {
4092
+ const errText = err instanceof Error ? err.message : String(err);
4093
+ const errId = createHash7("sha256").update(errText).digest("hex").slice(0, 12);
4094
+ log(`[kanban] notice cancel failed for agent_id=${agentId} error_id=${errId}`);
4095
+ return null;
4096
+ }
4097
+ }
4019
4098
 
4020
4099
  // src/lib/manager/channels/state.ts
4021
4100
  var agentChannelTokens = /* @__PURE__ */ new Map();
@@ -5937,6 +6016,7 @@ var pendingSessionRestarts = /* @__PURE__ */ new Map();
5937
6016
  var pendingRestartVerifications = /* @__PURE__ */ new Map();
5938
6017
  var restartBreaker = new RestartBreaker();
5939
6018
  var reportedTrips = /* @__PURE__ */ new Map();
6019
+ var RESTART_BREAKER_PROVISIONING_MAX = readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_MAX", 5);
5940
6020
  var mcpFlapDampener = new McpFlapDampener();
5941
6021
  var FLAP_BREAKER_COOLDOWN_MS = readEnvNumber2(
5942
6022
  "AGT_MCP_FLAP_BREAKER_COOLDOWN_MS",
@@ -5976,7 +6056,14 @@ function recordRestartForBreaker(codeName, reason) {
5976
6056
  return;
5977
6057
  }
5978
6058
  const result = restartBreaker.record(codeName, reason);
5979
- if (!result.tripped || !result.trip) return;
6059
+ if (!result.tripped || !result.trip) {
6060
+ if (isProvisioningReloadReason(reason) && result.classCounts.provisioning >= RESTART_BREAKER_PROVISIONING_MAX) {
6061
+ log(
6062
+ `[restart-breaker] agent=${codeName} provisioning-reload churn at the trip bar: ${result.classCounts.provisioning} provisioning restarts in window (reason=${reason}); one more will auto-pause the agent \u2014 a freshly-added integration is likely churning (ENG-7560)`
6063
+ );
6064
+ }
6065
+ return;
6066
+ }
5980
6067
  const trip = result.trip;
5981
6068
  log(
5982
6069
  `[persistent-session-decision] agent=${codeName} decision=circuit-tripped detail=${JSON.stringify(trip.statusMessage)} (ENG-5441)`
@@ -6701,7 +6788,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
6701
6788
  var lastVersionCheckAt = 0;
6702
6789
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
6703
6790
  var lastResponsivenessProbeAt = 0;
6704
- var agtCliVersion = true ? "0.28.272" : "dev";
6791
+ var agtCliVersion = true ? "0.28.274" : "dev";
6705
6792
  function resolveBrewPath(execFileSync2) {
6706
6793
  try {
6707
6794
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -7551,7 +7638,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
7551
7638
  if (codeNames.length === 0) return;
7552
7639
  void (async () => {
7553
7640
  try {
7554
- const { collectDiagnostics } = await import("../persistent-session-HOILVE3Q.js");
7641
+ const { collectDiagnostics } = await import("../persistent-session-SDDSZN5E.js");
7555
7642
  await api.post("/host/heartbeat", {
7556
7643
  host_id: hostId,
7557
7644
  agent_diagnostics: collectDiagnostics(codeNames)
@@ -7649,7 +7736,7 @@ async function pollCycle() {
7649
7736
  }
7650
7737
  try {
7651
7738
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
7652
- const { collectDiagnostics } = await import("../persistent-session-HOILVE3Q.js");
7739
+ const { collectDiagnostics } = await import("../persistent-session-SDDSZN5E.js");
7653
7740
  const diagCodeNames = [...agentState.persistentSessionAgents];
7654
7741
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
7655
7742
  let tailscaleHostname;
@@ -7797,7 +7884,7 @@ async function pollCycle() {
7797
7884
  const {
7798
7885
  collectResponsivenessProbes,
7799
7886
  getResponsivenessIntervalMs
7800
- } = await import("../responsiveness-probe-3OIDWR3T.js");
7887
+ } = await import("../responsiveness-probe-ODKT4WUR.js");
7801
7888
  const probeIntervalMs = getResponsivenessIntervalMs();
7802
7889
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
7803
7890
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -7829,7 +7916,7 @@ async function pollCycle() {
7829
7916
  collectResponsivenessProbes,
7830
7917
  livePendingInboundOldestAgeSeconds,
7831
7918
  parkPendingInbound
7832
- } = await import("../responsiveness-probe-3OIDWR3T.js");
7919
+ } = await import("../responsiveness-probe-ODKT4WUR.js");
7833
7920
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
7834
7921
  const wedgeNow = /* @__PURE__ */ new Date();
7835
7922
  const liveAgents = agentState.persistentSessionAgents;
@@ -9744,6 +9831,7 @@ async function processAgent(agent, agentStates) {
9744
9831
  const wantsHybridInject = isKanbanHybridEnabled();
9745
9832
  if (agentFw === "claude-code" && sessionMode === "persistent") {
9746
9833
  let hybridBoard = hasBoardTemplates ? boardItems : void 0;
9834
+ let hybridBoardStale = false;
9747
9835
  if (!hybridBoard) {
9748
9836
  try {
9749
9837
  const boardData = await api.post(
@@ -9754,6 +9842,7 @@ async function processAgent(agent, agentStates) {
9754
9842
  kanbanBoardCache.set(agent.code_name, hybridBoard);
9755
9843
  } catch {
9756
9844
  hybridBoard = kanbanBoardCache.get(agent.code_name) ?? [];
9845
+ hybridBoardStale = true;
9757
9846
  }
9758
9847
  }
9759
9848
  void reconcileScheduledRuns(agent.code_name, agent.agent_id, hybridBoard);
@@ -9769,11 +9858,24 @@ async function processAgent(agent, agentStates) {
9769
9858
  const cardStates = wedgeRestartsByCard.get(agent.code_name);
9770
9859
  const actionable = hybridBoard.filter(isHybridActionable);
9771
9860
  const { allowed, suppressed } = cardStates ? partitionActionableByPoison(actionable, cardStates, poisonCfg) : { allowed: actionable, suppressed: [] };
9772
- if (allowed.length > 0) {
9861
+ if (hybridBoardStale) {
9862
+ if (allowed.length > 0) {
9863
+ log(
9864
+ `[manager-worker] kanban inject skipped for '${agent.code_name}' - board fetch failed this tick (stale cache)`
9865
+ );
9866
+ }
9867
+ } else if (allowed.length > 0) {
9773
9868
  void maybeInjectKanbanCheck(agent.code_name, agent.agent_id, allowed.length);
9774
- } else {
9775
- const reason = suppressed.length > 0 ? "kanban inject suppressed \u2014 suspected poison card(s) (ENG-6171)" : "kanban board drained";
9776
- closeInjectedRunIfOpen(agent.code_name, "completed", reason);
9869
+ } else if (suppressed.length > 0) {
9870
+ closeInjectedRunIfOpen(
9871
+ agent.code_name,
9872
+ "completed",
9873
+ "kanban inject suppressed \u2014 suspected poison card(s) (ENG-6171)"
9874
+ );
9875
+ } else if (openInjectedRunByCode.has(agent.code_name)) {
9876
+ const openRunId = openInjectedRunByCode.get(agent.code_name);
9877
+ openInjectedRunByCode.delete(agent.code_name);
9878
+ void cancelKanbanNoticeOnDrain(agent.code_name, agent.agent_id, openRunId);
9777
9879
  }
9778
9880
  }
9779
9881
  }
@@ -10700,7 +10802,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
10700
10802
  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}`));
10701
10803
  void (async () => {
10702
10804
  try {
10703
- const { collectDiagnostics } = await import("../persistent-session-HOILVE3Q.js");
10805
+ const { collectDiagnostics } = await import("../persistent-session-SDDSZN5E.js");
10704
10806
  await api.post("/host/heartbeat", {
10705
10807
  host_id: hostId,
10706
10808
  agent_diagnostics: collectDiagnostics([codeName])
@@ -10750,7 +10852,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
10750
10852
  }
10751
10853
  try {
10752
10854
  const hostId = await getHostId();
10753
- const { collectDiagnostics } = await import("../persistent-session-HOILVE3Q.js");
10855
+ const { collectDiagnostics } = await import("../persistent-session-SDDSZN5E.js");
10754
10856
  await api.post("/host/heartbeat", {
10755
10857
  host_id: hostId,
10756
10858
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11031,6 +11133,19 @@ function closeInjectedRunIfOpen(codeName, outcome, outcomeMessage) {
11031
11133
  openInjectedRunByCode.delete(codeName);
11032
11134
  void finishRun(runId, outcome, { outcomeMessage });
11033
11135
  }
11136
+ async function cancelKanbanNoticeOnDrain(codeName, agentId, openRunId) {
11137
+ const cancelled = await cancelKanbanNotice(agentId);
11138
+ if (cancelled != null && cancelled > 0) {
11139
+ log(
11140
+ `[manager-worker] cancelled ${cancelled} pending kanban nudge(s) for '${codeName}' - board drained before delivery`
11141
+ );
11142
+ void finishRun(openRunId, "cancelled", {
11143
+ outcomeMessage: "kanban nudge cancelled - board drained before delivery"
11144
+ });
11145
+ } else {
11146
+ void finishRun(openRunId, "completed", { outcomeMessage: "kanban board drained" });
11147
+ }
11148
+ }
11034
11149
  var KANBAN_HYBRID_DEBOUNCE_MS = (() => {
11035
11150
  const raw = process.env["AGT_KANBAN_HYBRID_DEBOUNCE_SECONDS"];
11036
11151
  const parsed = raw ? parseInt(raw, 10) : NaN;
@@ -11141,7 +11256,7 @@ async function processClaudePairSessions(agents) {
11141
11256
  killPairSession,
11142
11257
  pairTmuxSession,
11143
11258
  finalizeClaudePairOnboarding
11144
- } = await import("../claude-pair-runtime-KOEEK5LD.js");
11259
+ } = await import("../claude-pair-runtime-Z726QSAR.js");
11145
11260
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
11146
11261
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
11147
11262
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -12027,6 +12142,7 @@ export {
12027
12142
  BACK_ONLINE_GREETING_GUIDANCE,
12028
12143
  DAY_ROLLOVER_FORCE_GRACE_MIN,
12029
12144
  applyRestartAcks,
12145
+ cancelKanbanNoticeOnDrain,
12030
12146
  claudeCodeUpgradeMarkerPath,
12031
12147
  claudeCodeUpgradeThrottled,
12032
12148
  claudeManagedSettingsPath,