@integrity-labs/agt-cli 0.28.280 → 0.28.282

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-SZ4TUGED.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-7H3IYXXP.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-EXW6NBLU.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-JOYTDNXZ.js.map
@@ -38,7 +38,7 @@ import {
38
38
  requireHost,
39
39
  safeWriteJsonAtomic,
40
40
  setConfigHash
41
- } from "../chunk-JGM444JK.js";
41
+ } from "../chunk-6SRVCYKR.js";
42
42
  import {
43
43
  getProjectDir as getProjectDir2,
44
44
  getReadyTasks,
@@ -125,7 +125,7 @@ import {
125
125
  takeZombieDetection,
126
126
  transcriptActivityAgeSeconds,
127
127
  writeEgressAllowlist
128
- } from "../chunk-7USLYY43.js";
128
+ } from "../chunk-C4QMCOAN.js";
129
129
  import {
130
130
  reapOrphanChannelMcps
131
131
  } from "../chunk-XWVM4KPK.js";
@@ -186,6 +186,29 @@ function managedMcpStructureHash(entries) {
186
186
  const basis = entries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => `${e.serverId}|${Object.keys(e.headers ?? {}).sort().join(",")}`).join("\n");
187
187
  return createHash("sha256").update(basis).digest("hex").slice(0, 16);
188
188
  }
189
+ function managedMcpStructureHashFromFile(mcpConfig, isManagedKey) {
190
+ if (!mcpConfig || typeof mcpConfig !== "object") return null;
191
+ const servers = mcpConfig.mcpServers;
192
+ if (!servers || typeof servers !== "object") return managedMcpStructureHash([]);
193
+ const entries = [];
194
+ for (const [key, value] of Object.entries(servers)) {
195
+ if (!isManagedKey(key)) continue;
196
+ const headers = value && typeof value === "object" && value.headers && typeof value.headers === "object" ? value.headers : void 0;
197
+ entries.push({ serverId: key, headers });
198
+ }
199
+ return managedMcpStructureHash(entries);
200
+ }
201
+ function decideManagedMcpRestart(input) {
202
+ const { prevStructureHash, structureHash, launchStructureHash, sessionHealthy, flapping } = input;
203
+ if (prevStructureHash === void 0) return { kind: "skip-first-convergence" };
204
+ if (structureHash === prevStructureHash) return { kind: "skip-no-change" };
205
+ if (!sessionHealthy) return { kind: "skip-unhealthy" };
206
+ if (flapping) return { kind: "skip-flapping" };
207
+ if (launchStructureHash != null && launchStructureHash === structureHash) {
208
+ return { kind: "suppress-already-live" };
209
+ }
210
+ return { kind: "restart" };
211
+ }
189
212
  function channelSecretValueHash(envEntries, channelSecretKeys) {
190
213
  if (!envEntries) return null;
191
214
  const basis = channelSecretKeys.slice().sort().filter((k) => Object.prototype.hasOwnProperty.call(envEntries, k)).map((k) => `${k}=${envEntries[k]}`).join("\n");
@@ -626,7 +649,10 @@ function reaperRestartBreakerReason(activeKeys) {
626
649
  var PROVISIONING_RELOAD_REASONS = /* @__PURE__ */ new Set([
627
650
  "hot-reload-mcp",
628
651
  "managed-mcp-churn",
629
- "bind-remediation"
652
+ "bind-remediation",
653
+ // ENG-7576: the dashboard integration-add stop itself - the first restart of
654
+ // the very burst this class exists for, previously uncounted entirely.
655
+ "integration-change"
630
656
  ]);
631
657
  function isProvisioningReloadReason(reason) {
632
658
  return PROVISIONING_RELOAD_REASONS.has(reason);
@@ -6286,7 +6312,15 @@ async function maybeResumeReconcile(agent) {
6286
6312
  }
6287
6313
  }
6288
6314
  function restartReasonBindsNewMcp(breakerReason) {
6289
- return breakerReason === "hot-reload-mcp" || breakerReason === "managed-mcp-churn" || breakerReason === "bind-remediation" || breakerReason === "channel-set-change";
6315
+ return breakerReason === "hot-reload-mcp" || breakerReason === "managed-mcp-churn" || breakerReason === "bind-remediation" || breakerReason === "channel-set-change" || // ENG-7576: the dashboard integration-add stop (the poll's restart-consumption
6316
+ // loop) is the archetypal "stop to rebind a changed MCP set" - the docstring
6317
+ // above always described it, but the path passed no breakerReason so it never
6318
+ // hit this predicate. Including it arms the ENG-6174 respawn verification for
6319
+ // the ONE real respawn of an integration add.
6320
+ breakerReason === "integration-change";
6321
+ }
6322
+ function dashboardRestartBreakerReason(restartReason) {
6323
+ return restartReason === "integration-change" ? "integration-change" : void 0;
6290
6324
  }
6291
6325
  function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-reload-mcp", beforeStop) {
6292
6326
  const existing = pendingSessionRestarts.get(codeName);
@@ -6332,6 +6366,7 @@ function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-
6332
6366
  stopPersistentSession(codeName, log);
6333
6367
  markRestartStopped(codeName, log);
6334
6368
  runningMcpHashes.delete(codeName);
6369
+ sessionLaunchManagedStructure.delete(codeName);
6335
6370
  recordRestartForBreaker(codeName, breakerReason);
6336
6371
  log(`[hot-reload] Session stopped for '${codeName}' \u2014 will respawn with ${reason}`);
6337
6372
  const bindsNewMcp = restartReasonBindsNewMcp(breakerReason);
@@ -6510,6 +6545,7 @@ function isHostBusyForForcedUpdate() {
6510
6545
  var runningMcpHashes = /* @__PURE__ */ new Map();
6511
6546
  var runningMcpServerKeys = /* @__PURE__ */ new Map();
6512
6547
  var runningChannelSecretHashes = /* @__PURE__ */ new Map();
6548
+ var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
6513
6549
  function projectMcpHash(_codeName, projectDir) {
6514
6550
  try {
6515
6551
  const raw = readFileSync15(join17(projectDir, ".mcp.json"), "utf-8");
@@ -6529,6 +6565,28 @@ function projectMcpKeys(_codeName, projectDir) {
6529
6565
  return null;
6530
6566
  }
6531
6567
  }
6568
+ function seedSessionLaunchBaselines(codeName, projectDir) {
6569
+ const hash = projectMcpHash(codeName, projectDir);
6570
+ const keys = projectMcpKeys(codeName, projectDir);
6571
+ if (hash) runningMcpHashes.set(codeName, hash);
6572
+ else runningMcpHashes.delete(codeName);
6573
+ if (keys) runningMcpServerKeys.set(codeName, keys);
6574
+ else runningMcpServerKeys.delete(codeName);
6575
+ let launchStructure = null;
6576
+ try {
6577
+ const raw = readFileSync15(join17(projectDir, ".mcp.json"), "utf-8");
6578
+ launchStructure = managedMcpStructureHashFromFile(
6579
+ JSON.parse(raw),
6580
+ isManagedMcpServerKey
6581
+ );
6582
+ } catch {
6583
+ }
6584
+ if (launchStructure !== null) {
6585
+ sessionLaunchManagedStructure.set(codeName, launchStructure);
6586
+ } else {
6587
+ sessionLaunchManagedStructure.delete(codeName);
6588
+ }
6589
+ }
6532
6590
  async function runAgentConnectivityProbes(agent, integrations, projectDir) {
6533
6591
  if (integrations.length === 0) return;
6534
6592
  const probeEnv = buildProbeEnv(projectDir);
@@ -6654,12 +6712,13 @@ function stopPersistentSessionAndForgetMcpBaseline(codeName, breakerReason, gate
6654
6712
  runningMcpHashes.delete(codeName);
6655
6713
  runningMcpServerKeys.delete(codeName);
6656
6714
  runningChannelSecretHashes.delete(codeName);
6715
+ sessionLaunchManagedStructure.delete(codeName);
6657
6716
  closeInjectedRunIfOpen(codeName, "cancelled", `session stopped (${breakerReason ?? "deprovision"})`);
6658
6717
  closeScheduledRunsForCode(codeName, "cancelled", `session stopped (${breakerReason ?? "deprovision"})`);
6659
6718
  if (breakerReason) {
6660
6719
  recordRestartForBreaker(codeName, breakerReason);
6661
6720
  }
6662
- if (breakerReason && breakerReason !== "agent-requested" && breakerReason !== "channel-restart-flag") {
6721
+ if (breakerReason && breakerReason !== "agent-requested" && breakerReason !== "channel-restart-flag" && breakerReason !== "integration-change") {
6663
6722
  const restartAgentId = agentState.codeNameToAgentId.get(codeName);
6664
6723
  if (restartAgentId) {
6665
6724
  void api.post("/host/restart-event", {
@@ -6827,7 +6886,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
6827
6886
  var lastVersionCheckAt = 0;
6828
6887
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
6829
6888
  var lastResponsivenessProbeAt = 0;
6830
- var agtCliVersion = true ? "0.28.280" : "dev";
6889
+ var agtCliVersion = true ? "0.28.282" : "dev";
6831
6890
  function resolveBrewPath(execFileSync2) {
6832
6891
  try {
6833
6892
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -7677,7 +7736,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
7677
7736
  if (codeNames.length === 0) return;
7678
7737
  void (async () => {
7679
7738
  try {
7680
- const { collectDiagnostics } = await import("../persistent-session-SZ4TUGED.js");
7739
+ const { collectDiagnostics } = await import("../persistent-session-7H3IYXXP.js");
7681
7740
  await api.post("/host/heartbeat", {
7682
7741
  host_id: hostId,
7683
7742
  agent_diagnostics: collectDiagnostics(codeNames)
@@ -7775,7 +7834,7 @@ async function pollCycle() {
7775
7834
  }
7776
7835
  try {
7777
7836
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
7778
- const { collectDiagnostics } = await import("../persistent-session-SZ4TUGED.js");
7837
+ const { collectDiagnostics } = await import("../persistent-session-7H3IYXXP.js");
7779
7838
  const diagCodeNames = [...agentState.persistentSessionAgents];
7780
7839
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
7781
7840
  let tailscaleHostname;
@@ -7923,7 +7982,7 @@ async function pollCycle() {
7923
7982
  const {
7924
7983
  collectResponsivenessProbes,
7925
7984
  getResponsivenessIntervalMs
7926
- } = await import("../responsiveness-probe-MKFZAUV5.js");
7985
+ } = await import("../responsiveness-probe-PFJM4QMQ.js");
7927
7986
  const probeIntervalMs = getResponsivenessIntervalMs();
7928
7987
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
7929
7988
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -7955,7 +8014,7 @@ async function pollCycle() {
7955
8014
  collectResponsivenessProbes,
7956
8015
  livePendingInboundOldestAgeSeconds,
7957
8016
  parkPendingInbound
7958
- } = await import("../responsiveness-probe-MKFZAUV5.js");
8017
+ } = await import("../responsiveness-probe-PFJM4QMQ.js");
7959
8018
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
7960
8019
  const wedgeNow = /* @__PURE__ */ new Date();
7961
8020
  const liveAgents = agentState.persistentSessionAgents;
@@ -8174,13 +8233,31 @@ async function pollCycle() {
8174
8233
  const lastProcessed = prev?.lastRestartProcessedAt ?? null;
8175
8234
  const alreadyServiced = lastProcessed != null && Date.parse(lastProcessed) >= Date.parse(requested);
8176
8235
  if (!alreadyServiced) {
8177
- log(`[restart] Dashboard requested restart for '${agent.code_name}' at ${requested}`);
8236
+ const restartReason = agent.restart_reason ?? null;
8237
+ log(
8238
+ `[restart] Dashboard requested restart for '${agent.code_name}' at ${requested} (reason=${restartReason ?? "manual"})`
8239
+ );
8178
8240
  try {
8179
8241
  const { execSync: es } = await import("child_process");
8180
8242
  es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: "ignore" });
8181
8243
  } catch {
8182
8244
  }
8183
- stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
8245
+ const breakerReason = dashboardRestartBreakerReason(restartReason);
8246
+ stopPersistentSessionAndForgetMcpBaseline(agent.code_name, breakerReason);
8247
+ if (breakerReason && restartReasonBindsNewMcp(breakerReason)) {
8248
+ noteRestartRequested(agent.code_name, requested);
8249
+ beginRestartTiming(
8250
+ agent.code_name,
8251
+ { reason: "integration-change (dashboard restart)", delivered: null, injectDelayMs: null },
8252
+ log
8253
+ );
8254
+ markRestartStopped(agent.code_name, log);
8255
+ const prior = pendingRestartVerifications.get(agent.code_name);
8256
+ pendingRestartVerifications.set(agent.code_name, {
8257
+ firedAt: Date.now(),
8258
+ attempts: prior?.attempts ?? 0
8259
+ });
8260
+ }
8184
8261
  restartAcks.set(agent.agent_id, requested);
8185
8262
  }
8186
8263
  restartClears.push({ agentId: agent.agent_id, requestedAt: requested });
@@ -9579,12 +9656,19 @@ async function processAgent(agent, agentStates) {
9579
9656
  }
9580
9657
  agentState.knownManagedMcpHashes.set(agent.agent_id, mcpHash);
9581
9658
  agentState.knownManagedMcpStructure.set(agent.agent_id, structureHash);
9582
- if (prevStructureHash !== void 0 && structureHash !== prevStructureHash && fwForMcp === "claude-code" && isSessionHealthy(agent.code_name) && // ENG-6123: hold the restart + inject-notice while the managed-MCP
9583
- // membership is oscillating. The .mcp.json above was still written
9584
- // (the file converges to the live set); we only withhold the
9585
- // opus-facing notice + the session reload — the two per-flip burns.
9586
- // When the set settles, the next structural change restarts once.
9587
- !mcpFlap.flapping) {
9659
+ const mcpRestartDecision = fwForMcp === "claude-code" && prevStructureHash !== void 0 && structureHash !== prevStructureHash ? decideManagedMcpRestart({
9660
+ prevStructureHash,
9661
+ structureHash,
9662
+ launchStructureHash: sessionLaunchManagedStructure.get(agent.code_name),
9663
+ sessionHealthy: isSessionHealthy(agent.code_name),
9664
+ flapping: mcpFlap.flapping
9665
+ }) : null;
9666
+ if (mcpRestartDecision?.kind === "suppress-already-live") {
9667
+ log(
9668
+ `[hot-reload] Restart suppressed for '${agent.code_name}': the managed MCP structure change is already live in the running session (launched with it) - no respawn needed (ENG-7576)`
9669
+ );
9670
+ }
9671
+ if (mcpRestartDecision?.kind === "restart") {
9588
9672
  const mcpNames = agentToolkits.map((tk) => tk.toolkit_name).join(", ") || "none (all removed)";
9589
9673
  log(`[hot-reload] MCP servers changed for '${agent.code_name}': ${mcpNames} \u2014 restarting session`);
9590
9674
  const restartNotice = agentToolkits.length > 0 ? `New MCP tool servers have been configured: ${mcpNames}. Note: MCP servers require a session restart to connect. Your manager will restart your session shortly.` : "Managed MCP tool servers were removed. Your manager will restart your session shortly so the session drops those tools.";
@@ -10590,6 +10674,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
10590
10674
  });
10591
10675
  agentState.persistentSessionAgents.add(codeName);
10592
10676
  claudeAuthTupleBySession.set(codeName, currentAuthTuple);
10677
+ seedSessionLaunchBaselines(codeName, projectDir);
10593
10678
  return {
10594
10679
  decision: "spawn",
10595
10680
  spawnAttempted: true,
@@ -10845,7 +10930,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
10845
10930
  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}`));
10846
10931
  void (async () => {
10847
10932
  try {
10848
- const { collectDiagnostics } = await import("../persistent-session-SZ4TUGED.js");
10933
+ const { collectDiagnostics } = await import("../persistent-session-7H3IYXXP.js");
10849
10934
  await api.post("/host/heartbeat", {
10850
10935
  host_id: hostId,
10851
10936
  agent_diagnostics: collectDiagnostics([codeName])
@@ -10895,7 +10980,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
10895
10980
  }
10896
10981
  try {
10897
10982
  const hostId = await getHostId();
10898
- const { collectDiagnostics } = await import("../persistent-session-SZ4TUGED.js");
10983
+ const { collectDiagnostics } = await import("../persistent-session-7H3IYXXP.js");
10899
10984
  await api.post("/host/heartbeat", {
10900
10985
  host_id: hostId,
10901
10986
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11299,7 +11384,7 @@ async function processClaudePairSessions(agents) {
11299
11384
  killPairSession,
11300
11385
  pairTmuxSession,
11301
11386
  finalizeClaudePairOnboarding
11302
- } = await import("../claude-pair-runtime-EXW6NBLU.js");
11387
+ } = await import("../claude-pair-runtime-JOYTDNXZ.js");
11303
11388
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
11304
11389
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
11305
11390
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -12200,6 +12285,7 @@ export {
12200
12285
  claudeCodeUpgradeMarkerPath,
12201
12286
  claudeCodeUpgradeThrottled,
12202
12287
  claudeManagedSettingsPath,
12288
+ dashboardRestartBreakerReason,
12203
12289
  ensureClaudeManagedSettings,
12204
12290
  extractCharterSlackPeers,
12205
12291
  extractCharterTelegramPeers,