@integrity-labs/agt-cli 0.28.215 → 0.28.217

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.
package/dist/bin/agt.js CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  success,
39
39
  table,
40
40
  warn
41
- } from "../chunk-4QCJLG3Q.js";
41
+ } from "../chunk-U6RV5FVC.js";
42
42
  import {
43
43
  CHANNEL_REGISTRY,
44
44
  DEFAULT_FRAMEWORK,
@@ -4826,7 +4826,7 @@ import { execFileSync, execSync } from "child_process";
4826
4826
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4827
4827
  import chalk18 from "chalk";
4828
4828
  import ora16 from "ora";
4829
- var cliVersion = true ? "0.28.215" : "dev";
4829
+ var cliVersion = true ? "0.28.217" : "dev";
4830
4830
  async function fetchLatestVersion() {
4831
4831
  const host2 = getHost();
4832
4832
  if (!host2) return null;
@@ -5840,7 +5840,7 @@ function handleError(err) {
5840
5840
  }
5841
5841
 
5842
5842
  // src/bin/agt.ts
5843
- var cliVersion2 = true ? "0.28.215" : "dev";
5843
+ var cliVersion2 = true ? "0.28.217" : "dev";
5844
5844
  var program = new Command();
5845
5845
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
5846
5846
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -5529,7 +5529,7 @@ function requireHost() {
5529
5529
  }
5530
5530
 
5531
5531
  // src/lib/api-client.ts
5532
- var agtCliVersion = true ? "0.28.215" : "dev";
5532
+ var agtCliVersion = true ? "0.28.217" : "dev";
5533
5533
  var lastConfigHash = null;
5534
5534
  function setConfigHash(hash) {
5535
5535
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -7581,4 +7581,4 @@ export {
7581
7581
  managerInstallSystemUnitCommand,
7582
7582
  managerUninstallSystemUnitCommand
7583
7583
  };
7584
- //# sourceMappingURL=chunk-4QCJLG3Q.js.map
7584
+ //# sourceMappingURL=chunk-U6RV5FVC.js.map
@@ -36,7 +36,7 @@ import {
36
36
  requireHost,
37
37
  safeWriteJsonAtomic,
38
38
  setConfigHash
39
- } from "../chunk-4QCJLG3Q.js";
39
+ } from "../chunk-U6RV5FVC.js";
40
40
  import {
41
41
  getProjectDir as getProjectDir2,
42
42
  getReadyTasks,
@@ -685,6 +685,11 @@ var McpFlapDampener = class {
685
685
  now;
686
686
  history = /* @__PURE__ */ new Map();
687
687
  flapping = /* @__PURE__ */ new Set();
688
+ // ENG-7239: timestamp of the most recent poll on which each (agent, channel)
689
+ // was flapping. Drives `flappedWithin` so the caller can exempt a managed-MCP
690
+ // churn restart from the ENG-5441 breaker for a short cool-off after a flap;
691
+ // see that method for the Kylie/dig (2026-06-29) rationale.
692
+ lastFlapAt = /* @__PURE__ */ new Map();
688
693
  constructor(opts = {}) {
689
694
  this.windowMs = opts.windowMs ?? readEnvNumber2("AGT_MCP_FLAP_WINDOW_MS", DEFAULT_WINDOW_MS2);
690
695
  this.minTransitions = opts.minTransitions ?? readEnvNumber2("AGT_MCP_FLAP_MIN_TRANSITIONS", DEFAULT_MIN_TRANSITIONS);
@@ -742,9 +747,11 @@ var McpFlapDampener = class {
742
747
  this.flapping.add(k);
743
748
  onset = true;
744
749
  }
750
+ const nowFlapping = this.flapping.has(k);
751
+ if (nowFlapping) this.lastFlapAt.set(k, at);
745
752
  const reported = tier === "slow" ? slow : fast;
746
753
  return {
747
- flapping: this.flapping.has(k),
754
+ flapping: nowFlapping,
748
755
  distinctStates: reported.distinctStates,
749
756
  transitions: reported.transitions,
750
757
  windowCount: prior.length,
@@ -776,6 +783,27 @@ var McpFlapDampener = class {
776
783
  isFlapping(codeName, channel) {
777
784
  return this.flapping.has(this.key(codeName, channel));
778
785
  }
786
+ /**
787
+ * ENG-7239: true if (codeName, channel) was flapping within the last
788
+ * `withinMs`. Lets the caller exempt a managed-MCP-churn session restart from
789
+ * the ENG-5441 circuit breaker during (and for a short cool-off after) a
790
+ * flap episode.
791
+ *
792
+ * Why a cool-off and not just `isFlapping`: the dampener recovers eagerly, on
793
+ * the first stable poll (ENG-6512), so during an ACTIVE oscillation a single
794
+ * transient poll where the desired set momentarily repeats clears `flapping`
795
+ * for that instant. If the very next poll flips again, that flip's restart
796
+ * would (pre-fix) be treated as a genuine change and counted toward the
797
+ * breaker; three such flips in 10 min auto-paused Kylie (org dig) on
798
+ * 2026-06-29 and left her unreachable on every channel. Keying the breaker
799
+ * exemption off "flapped within the cool-off" instead of the exact-instant
800
+ * `isFlapping` closes that race. Mirrors ENG-5547's single-dead-MCP reaper
801
+ * exemption ("restart, but don't count toward the agent-wide breaker").
802
+ */
803
+ flappedWithin(codeName, channel, withinMs) {
804
+ const at = this.lastFlapAt.get(this.key(codeName, channel));
805
+ return at !== void 0 && this.now() - at <= withinMs;
806
+ }
779
807
  /** Drop all state for an agent (called on deprovision / agent removal). */
780
808
  forget(codeName) {
781
809
  const prefix = `${codeName}\0`;
@@ -785,10 +813,29 @@ var McpFlapDampener = class {
785
813
  for (const k of [...this.flapping]) {
786
814
  if (k.startsWith(prefix)) this.flapping.delete(k);
787
815
  }
816
+ for (const k of [...this.lastFlapAt.keys()]) {
817
+ if (k.startsWith(prefix)) this.lastFlapAt.delete(k);
818
+ }
788
819
  }
789
820
  };
790
821
  var FLAP_CHANNEL_INTEGRATIONS = "integrations";
791
822
  var FLAP_CHANNEL_MANAGED_MCP = "managed-mcp";
823
+ var DEFAULT_FLAP_BREAKER_COOLDOWN_MS = 6e5;
824
+ var MANAGED_MCP_SERVER_PREFIXES = [
825
+ "composio_",
826
+ "one_",
827
+ "pipedream_",
828
+ "nango_",
829
+ "paragon_",
830
+ "composio-",
831
+ "one-",
832
+ "pipedream-",
833
+ "nango-",
834
+ "paragon-"
835
+ ];
836
+ function isManagedMcpServerKey(key) {
837
+ return MANAGED_MCP_SERVER_PREFIXES.some((p) => key.startsWith(p));
838
+ }
792
839
 
793
840
  // src/lib/auto-resume.ts
794
841
  var DEFAULT_QUIET_MS = 18e5;
@@ -5365,6 +5412,9 @@ var lastChannelSweepAt = 0;
5365
5412
  var config = null;
5366
5413
  var running = false;
5367
5414
  var pollTimer = null;
5415
+ var livenessTimer = null;
5416
+ var livenessInFlight = false;
5417
+ var LIVENESS_HEARTBEAT_INTERVAL_MS = 3e4;
5368
5418
  var PANE_TAIL_PREVIEW_LINES = 5;
5369
5419
  function extractCharterTelegramPeers(rawContent, gateContext) {
5370
5420
  if (!rawContent || rawContent.length === 0) return [];
@@ -5469,6 +5519,10 @@ var pendingRestartVerifications = /* @__PURE__ */ new Map();
5469
5519
  var restartBreaker = new RestartBreaker();
5470
5520
  var reportedTrips = /* @__PURE__ */ new Map();
5471
5521
  var mcpFlapDampener = new McpFlapDampener();
5522
+ var FLAP_BREAKER_COOLDOWN_MS = readEnvNumber2(
5523
+ "AGT_MCP_FLAP_BREAKER_COOLDOWN_MS",
5524
+ DEFAULT_FLAP_BREAKER_COOLDOWN_MS
5525
+ );
5472
5526
  function recordConfigChurnEvent(agentId, codeName, channel, signature) {
5473
5527
  api.post("/host/mcp-config-churn", {
5474
5528
  agent_id: agentId,
@@ -5496,6 +5550,12 @@ async function reportReaperEvent(agentId, reaper, action, reason) {
5496
5550
  }
5497
5551
  }
5498
5552
  function recordRestartForBreaker(codeName, reason) {
5553
+ if (reason === "managed-mcp-churn" && mcpFlapDampener.flappedWithin(codeName, FLAP_CHANNEL_MANAGED_MCP, FLAP_BREAKER_COOLDOWN_MS)) {
5554
+ log(
5555
+ `[mcp-flap-dampener] '${codeName}' managed-MCP churn restart NOT counted toward circuit breaker, managed-MCP set flapped within ${Math.round(FLAP_BREAKER_COOLDOWN_MS / 1e3)}s (ENG-7239)`
5556
+ );
5557
+ return;
5558
+ }
5499
5559
  const result = restartBreaker.record(codeName, reason);
5500
5560
  if (!result.tripped || !result.trip) return;
5501
5561
  const trip = result.trip;
@@ -5752,7 +5812,7 @@ function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-
5752
5812
  runningMcpHashes.delete(codeName);
5753
5813
  recordRestartForBreaker(codeName, breakerReason);
5754
5814
  log(`[hot-reload] Session stopped for '${codeName}' \u2014 will respawn with ${reason}`);
5755
- if (breakerReason === "hot-reload-mcp" || breakerReason === "bind-remediation" || breakerReason === "channel-set-change") {
5815
+ if (breakerReason === "hot-reload-mcp" || breakerReason === "managed-mcp-churn" || breakerReason === "bind-remediation" || breakerReason === "channel-set-change") {
5756
5816
  const prior = pendingRestartVerifications.get(codeName);
5757
5817
  pendingRestartVerifications.set(codeName, {
5758
5818
  firedAt: Date.now(),
@@ -6064,7 +6124,8 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
6064
6124
  );
6065
6125
  }
6066
6126
  if (decision.restart && !quarantineOnlyDrift && !flapSuppressed) {
6067
- scheduleSessionRestart(codeName, 0, ".mcp.json content change (ENG-4897)");
6127
+ const driftBreakerReason = decision.addedOrRemoved.length > 0 && decision.addedOrRemoved.every(isManagedMcpServerKey) ? "managed-mcp-churn" : "hot-reload-mcp";
6128
+ scheduleSessionRestart(codeName, 0, ".mcp.json content change (ENG-4897)", driftBreakerReason);
6068
6129
  runningMcpHashes.delete(codeName);
6069
6130
  runningMcpServerKeys.delete(codeName);
6070
6131
  } else if (flapSuppressed) {
@@ -6163,7 +6224,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
6163
6224
  var lastVersionCheckAt = 0;
6164
6225
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
6165
6226
  var lastResponsivenessProbeAt = 0;
6166
- var agtCliVersion = true ? "0.28.215" : "dev";
6227
+ var agtCliVersion = true ? "0.28.217" : "dev";
6167
6228
  function resolveBrewPath(execFileSync2) {
6168
6229
  try {
6169
6230
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -8750,20 +8811,8 @@ async function processAgent(agent, agentStates) {
8750
8811
  const { readFileSync: readFileSync15 } = await import("fs");
8751
8812
  const mcpConfig = JSON.parse(readFileSync15(mcpPath, "utf-8"));
8752
8813
  if (mcpConfig.mcpServers) {
8753
- const managedPrefixes = [
8754
- "composio_",
8755
- "one_",
8756
- "pipedream_",
8757
- "nango_",
8758
- "paragon_",
8759
- "composio-",
8760
- "one-",
8761
- "pipedream-",
8762
- "nango-",
8763
- "paragon-"
8764
- ];
8765
8814
  for (const key of Object.keys(mcpConfig.mcpServers)) {
8766
- if (managedPrefixes.some((p) => key.startsWith(p)) && !expectedServerIds.has(key)) {
8815
+ if (isManagedMcpServerKey(key) && !expectedServerIds.has(key)) {
8767
8816
  frameworkAdapter.removeMcpServer(agent.code_name, key);
8768
8817
  log(`[managed-toolkit] Removed stale MCP server '${key}' for '${agent.code_name}'`);
8769
8818
  }
@@ -10729,9 +10778,27 @@ async function driveArtifactStreamingInner() {
10729
10778
  if (!liveIds.has(id)) artifactScanners.delete(id);
10730
10779
  }
10731
10780
  }
10781
+ async function postLivenessHeartbeat() {
10782
+ if (!running || !config || restartAfterUpgrade) return;
10783
+ if (livenessInFlight) return;
10784
+ livenessInFlight = true;
10785
+ try {
10786
+ const hostId = await getHostId();
10787
+ if (!hostId) return;
10788
+ await api.post("/host/heartbeat", { host_id: hostId, agt_version: agtCliVersion });
10789
+ } catch (err) {
10790
+ log(`[liveness] fast heartbeat failed: ${err.message}`);
10791
+ } finally {
10792
+ livenessInFlight = false;
10793
+ }
10794
+ }
10732
10795
  function startPolling() {
10733
10796
  if (!config || running) return;
10734
10797
  running = true;
10798
+ livenessTimer = setInterval(() => {
10799
+ void postLivenessHeartbeat();
10800
+ }, LIVENESS_HEARTBEAT_INTERVAL_MS);
10801
+ livenessTimer.unref();
10735
10802
  void startCaffeinate();
10736
10803
  loadChannelHashCache2();
10737
10804
  hostFlagStore().init();
@@ -10819,6 +10886,10 @@ async function stopPolling(opts = {}) {
10819
10886
  clearTimeout(pollTimer);
10820
10887
  pollTimer = null;
10821
10888
  }
10889
+ if (livenessTimer) {
10890
+ clearInterval(livenessTimer);
10891
+ livenessTimer = null;
10892
+ }
10822
10893
  const shutdownTimer = setTimeout(() => {
10823
10894
  log("Shutdown timeout exceeded (15s), forcing exit");
10824
10895
  process.exit(opts.forcedExitCode ?? 1);