@integrity-labs/agt-cli 0.27.113 → 0.27.115

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.
@@ -17,7 +17,7 @@ import {
17
17
  provisionStopHook,
18
18
  requireHost,
19
19
  safeWriteJsonAtomic
20
- } from "../chunk-UHG6DSOX.js";
20
+ } from "../chunk-EOMWSV4B.js";
21
21
  import {
22
22
  getProjectDir as getProjectDir2,
23
23
  getReadyTasks,
@@ -47,6 +47,7 @@ import {
47
47
  readPaneLogTail,
48
48
  resetRestartCount,
49
49
  resolveClaudeBinary,
50
+ rotateSessionForWedge,
50
51
  sanitizeMcpJson,
51
52
  sendToAgent,
52
53
  sessionTranscriptDir,
@@ -55,7 +56,7 @@ import {
55
56
  stopPersistentSession,
56
57
  takeWatchdogGiveUpCount,
57
58
  takeZombieDetection
58
- } from "../chunk-RN3OMHW7.js";
59
+ } from "../chunk-IUN5LBTQ.js";
59
60
  import {
60
61
  KANBAN_CHECK_COMMAND,
61
62
  SUPPRESS_SENTINEL,
@@ -3076,6 +3077,53 @@ function clearAgentState(agentId, codeName) {
3076
3077
  return channelCacheMutated;
3077
3078
  }
3078
3079
 
3080
+ // src/lib/wedge-detection.ts
3081
+ var DEFAULTS = {
3082
+ inboundWaitSeconds: 120,
3083
+ inboundHardWaitSeconds: 300,
3084
+ paneStaleSeconds: 120,
3085
+ minCycles: 3
3086
+ };
3087
+ function parseMode(raw) {
3088
+ const v = (raw ?? "").trim().toLowerCase();
3089
+ return v === "shadow" || v === "enforce" ? v : "off";
3090
+ }
3091
+ function parsePositiveInt(raw, fallback, floor) {
3092
+ const n = raw ? Number.parseInt(raw, 10) : NaN;
3093
+ return Number.isInteger(n) && n >= floor ? n : fallback;
3094
+ }
3095
+ function resolveWedgeConfig(env = process.env) {
3096
+ const inboundWaitSeconds = parsePositiveInt(
3097
+ env.AGT_WEDGE_INBOUND_WAIT_SECONDS,
3098
+ DEFAULTS.inboundWaitSeconds,
3099
+ 30
3100
+ );
3101
+ const inboundHardWaitSeconds = Math.max(
3102
+ inboundWaitSeconds,
3103
+ parsePositiveInt(env.AGT_WEDGE_INBOUND_HARD_WAIT_SECONDS, DEFAULTS.inboundHardWaitSeconds, 30)
3104
+ );
3105
+ return {
3106
+ mode: parseMode(env.AGT_WEDGE_RESTART_MODE),
3107
+ inboundWaitSeconds,
3108
+ inboundHardWaitSeconds,
3109
+ paneStaleSeconds: parsePositiveInt(env.AGT_WEDGE_PANE_STALE_SECONDS, DEFAULTS.paneStaleSeconds, 30),
3110
+ minCycles: parsePositiveInt(env.AGT_WEDGE_MIN_CYCLES, DEFAULTS.minCycles, 2)
3111
+ };
3112
+ }
3113
+ function isWedgeCandidateCycle(signals, config2) {
3114
+ const inboundAge = signals.pendingInboundOldestAgeSeconds;
3115
+ if (inboundAge === null) return false;
3116
+ if (inboundAge < config2.inboundWaitSeconds) return false;
3117
+ if (inboundAge >= config2.inboundHardWaitSeconds) return true;
3118
+ const paneAge = signals.paneActivityAgeSeconds;
3119
+ const paneAdvancing = paneAge !== null && paneAge < config2.paneStaleSeconds;
3120
+ return !paneAdvancing;
3121
+ }
3122
+ function decideWedgeRestart(input, config2) {
3123
+ if (!isWedgeCandidateCycle(input, config2)) return "none";
3124
+ return input.consecutiveWedgeCycles >= config2.minCycles ? "wedged" : "none";
3125
+ }
3126
+
3079
3127
  // src/lib/restart-flags.ts
3080
3128
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync8, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
3081
3129
  import { homedir as homedir3 } from "os";
@@ -3789,6 +3837,7 @@ var autoResumeInFlight = /* @__PURE__ */ new Set();
3789
3837
  var AUTO_RESUME_SELF_WINDOW_MS = 12e4;
3790
3838
  var autoResumeLoggedSkips = /* @__PURE__ */ new Map();
3791
3839
  var autoResumeStandDowns = /* @__PURE__ */ new Set();
3840
+ var killPausedCodeNames = /* @__PURE__ */ new Set();
3792
3841
  function maybeAutoResume(agent) {
3793
3842
  const codeName = agent.code_name;
3794
3843
  if (autoResumeInFlight.has(codeName)) return;
@@ -3875,6 +3924,7 @@ function cancelPendingSessionRestart(codeName) {
3875
3924
  }
3876
3925
  var RESTART_DEFER_RECHECK_MS = 6e4;
3877
3926
  var lastInboundMs = /* @__PURE__ */ new Map();
3927
+ var consecutiveWedgeCycles = /* @__PURE__ */ new Map();
3878
3928
  function noteInbound(codeName) {
3879
3929
  lastInboundMs.set(codeName, Date.now());
3880
3930
  }
@@ -4173,7 +4223,7 @@ var cachedMaintenanceWindow = null;
4173
4223
  var lastVersionCheckAt = 0;
4174
4224
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
4175
4225
  var lastResponsivenessProbeAt = 0;
4176
- var agtCliVersion = true ? "0.27.113" : "dev";
4226
+ var agtCliVersion = true ? "0.27.115" : "dev";
4177
4227
  function resolveBrewPath(execFileSync4) {
4178
4228
  try {
4179
4229
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -5366,7 +5416,7 @@ async function pollCycle() {
5366
5416
  }
5367
5417
  try {
5368
5418
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
5369
- const { collectDiagnostics } = await import("../persistent-session-E6YLH6TX.js");
5419
+ const { collectDiagnostics } = await import("../persistent-session-PIR45QFL.js");
5370
5420
  const diagCodeNames = [...agentState.persistentSessionAgents];
5371
5421
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
5372
5422
  let tailscaleHostname;
@@ -5453,12 +5503,12 @@ async function pollCycle() {
5453
5503
  const {
5454
5504
  collectResponsivenessProbes,
5455
5505
  getResponsivenessIntervalMs
5456
- } = await import("../responsiveness-probe-TDHX6JKG.js");
5506
+ } = await import("../responsiveness-probe-FLQZ7E5H.js");
5457
5507
  const probeIntervalMs = getResponsivenessIntervalMs();
5458
5508
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
5459
5509
  const probeCodeNames = [...agentState.persistentSessionAgents];
5460
5510
  if (probeCodeNames.length > 0) {
5461
- const { takeAcpxExecFailureCount, creditAcpxExecFailureCount } = await import("../persistent-session-E6YLH6TX.js");
5511
+ const { takeAcpxExecFailureCount, creditAcpxExecFailureCount } = await import("../persistent-session-PIR45QFL.js");
5462
5512
  const drainedGiveUps = /* @__PURE__ */ new Map();
5463
5513
  const drainedAcpxFailures = /* @__PURE__ */ new Map();
5464
5514
  const probes = collectResponsivenessProbes(probeCodeNames).map((p) => {
@@ -5485,6 +5535,58 @@ async function pollCycle() {
5485
5535
  } catch (err) {
5486
5536
  log(`[responsiveness-probe] collection failed: ${err.message}`);
5487
5537
  }
5538
+ try {
5539
+ const wedgeConfig = resolveWedgeConfig();
5540
+ if (wedgeConfig.mode !== "off") {
5541
+ const { collectResponsivenessProbes } = await import("../responsiveness-probe-FLQZ7E5H.js");
5542
+ const liveAgents = agentState.persistentSessionAgents;
5543
+ for (const tracked of consecutiveWedgeCycles.keys()) {
5544
+ if (!liveAgents.has(tracked)) consecutiveWedgeCycles.delete(tracked);
5545
+ }
5546
+ for (const probe of collectResponsivenessProbes([...liveAgents])) {
5547
+ const codeName = probe.code_name;
5548
+ if (!isSessionHealthy(codeName)) {
5549
+ consecutiveWedgeCycles.delete(codeName);
5550
+ continue;
5551
+ }
5552
+ const signals = {
5553
+ paneActivityAgeSeconds: probe.pane_activity_age_seconds,
5554
+ pendingInboundOldestAgeSeconds: probe.pending_inbound_oldest_age_seconds ?? null
5555
+ };
5556
+ if (!isWedgeCandidateCycle(signals, wedgeConfig)) {
5557
+ consecutiveWedgeCycles.delete(codeName);
5558
+ continue;
5559
+ }
5560
+ const streak = (consecutiveWedgeCycles.get(codeName) ?? 0) + 1;
5561
+ consecutiveWedgeCycles.set(codeName, streak);
5562
+ if (decideWedgeRestart({ ...signals, consecutiveWedgeCycles: streak }, wedgeConfig) !== "wedged") {
5563
+ continue;
5564
+ }
5565
+ const detail = `agent=${codeName} paneAge=${signals.paneActivityAgeSeconds}s inboundAge=${signals.pendingInboundOldestAgeSeconds}s cycles=${streak}`;
5566
+ if (wedgeConfig.mode === "shadow") {
5567
+ if (streak === wedgeConfig.minCycles) {
5568
+ log(`[wedge] SHADOW would force-fresh respawn ${detail}`);
5569
+ }
5570
+ continue;
5571
+ }
5572
+ try {
5573
+ const newId = rotateSessionForWedge(codeName);
5574
+ try {
5575
+ const { execSync: es } = await import("child_process");
5576
+ es(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
5577
+ } catch {
5578
+ }
5579
+ stopPersistentSessionAndForgetMcpBaseline(codeName);
5580
+ consecutiveWedgeCycles.delete(codeName);
5581
+ log(`[wedge] forced fresh respawn ${detail} \u2192 new session ${newId} (transcript preserved)`);
5582
+ } catch (err) {
5583
+ log(`[wedge] force-fresh respawn failed for ${codeName}: ${err.message}`);
5584
+ }
5585
+ }
5586
+ }
5587
+ } catch (err) {
5588
+ log(`[wedge] detection failed: ${err.message}`);
5589
+ }
5488
5590
  try {
5489
5591
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
5490
5592
  const observations = [];
@@ -5818,6 +5920,16 @@ async function processAgent(agent, agentStates) {
5818
5920
  if (previousKnownStatus !== agent.status) {
5819
5921
  log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
5820
5922
  }
5923
+ if (agent.kill_switch) {
5924
+ killPausedCodeNames.add(agent.code_name);
5925
+ if (previousKnownStatus !== agent.status) {
5926
+ log(
5927
+ `[kill-switch] '${agent.code_name}' halted by ${agent.kill_switch.scope ?? "unknown"} kill switch` + (agent.kill_switch.reason ? ` (reason: ${agent.kill_switch.reason})` : "") + ` source=${agent.kill_switch.source ?? "unknown"}`
5928
+ );
5929
+ }
5930
+ } else if (killPausedCodeNames.delete(agent.code_name)) {
5931
+ log(`[kill-switch] '${agent.code_name}' kill switch cleared while still ${agent.status} (no resume \u2014 paused for another reason)`);
5932
+ }
5821
5933
  await stopGatewayIfRunning(agent.code_name, adapter);
5822
5934
  stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
5823
5935
  try {
@@ -5826,7 +5938,7 @@ async function processAgent(agent, agentStates) {
5826
5938
  log(`Killed tmux session for paused agent '${agent.code_name}'`);
5827
5939
  } catch {
5828
5940
  }
5829
- if (agent.status === "paused") {
5941
+ if (agent.status === "paused" && !agent.kill_switch) {
5830
5942
  maybeAutoResume(agent);
5831
5943
  }
5832
5944
  agentStates.push({
@@ -5932,6 +6044,16 @@ async function processAgent(agent, agentStates) {
5932
6044
  log(`[auto-resume] Cleared auto-resume marker for '${agent.code_name}' on operator resume \u2014 credit re-armed (ENG-6088)`);
5933
6045
  }
5934
6046
  }
6047
+ if (previousStatus === "paused" && agent.status === "active" && killPausedCodeNames.delete(agent.code_name)) {
6048
+ log(`[kill-switch] '${agent.code_name}' kill switch cleared \u2014 resuming session`);
6049
+ void injectMessage(
6050
+ agent.code_name,
6051
+ "system",
6052
+ "The kill switch that halted you has been cleared. Your session is resuming.",
6053
+ { task_name: "kill-switch-resume" },
6054
+ log
6055
+ ).catch(() => false);
6056
+ }
5935
6057
  let channelCacheMutated = false;
5936
6058
  for (const key of agentState.knownChannelConfigHashes.keys()) {
5937
6059
  if (key.startsWith(`${agent.agent_id}:`)) {
@@ -9834,7 +9956,7 @@ async function processClaudePairSessions(agents) {
9834
9956
  killPairSession,
9835
9957
  pairTmuxSession,
9836
9958
  finalizeClaudePairOnboarding
9837
- } = await import("../claude-pair-runtime-KVJ4U436.js");
9959
+ } = await import("../claude-pair-runtime-UX7GWW3O.js");
9838
9960
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
9839
9961
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
9840
9962
  const killed = await killPairSession(pairTmuxSession(pairId));