@integrity-labs/agt-cli 0.28.18 → 0.28.19

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
@@ -33,7 +33,7 @@ import {
33
33
  success,
34
34
  table,
35
35
  warn
36
- } from "../chunk-VPMNDODG.js";
36
+ } from "../chunk-MGE73AKK.js";
37
37
  import {
38
38
  CHANNEL_REGISTRY,
39
39
  DEPLOYMENT_TEMPLATES,
@@ -4773,7 +4773,7 @@ import { execFileSync, execSync } from "child_process";
4773
4773
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4774
4774
  import chalk18 from "chalk";
4775
4775
  import ora16 from "ora";
4776
- var cliVersion = true ? "0.28.18" : "dev";
4776
+ var cliVersion = true ? "0.28.19" : "dev";
4777
4777
  async function fetchLatestVersion() {
4778
4778
  const host2 = getHost();
4779
4779
  if (!host2) return null;
@@ -5696,7 +5696,7 @@ function handleError(err) {
5696
5696
  }
5697
5697
 
5698
5698
  // src/bin/agt.ts
5699
- var cliVersion2 = true ? "0.28.18" : "dev";
5699
+ var cliVersion2 = true ? "0.28.19" : "dev";
5700
5700
  var program = new Command();
5701
5701
  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");
5702
5702
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -8275,4 +8275,4 @@ export {
8275
8275
  managerInstallSystemUnitCommand,
8276
8276
  managerUninstallSystemUnitCommand
8277
8277
  };
8278
- //# sourceMappingURL=chunk-VPMNDODG.js.map
8278
+ //# sourceMappingURL=chunk-MGE73AKK.js.map
@@ -22,7 +22,7 @@ import {
22
22
  provisionStopHook,
23
23
  requireHost,
24
24
  safeWriteJsonAtomic
25
- } from "../chunk-VPMNDODG.js";
25
+ } from "../chunk-MGE73AKK.js";
26
26
  import {
27
27
  getProjectDir as getProjectDir2,
28
28
  getReadyTasks,
@@ -434,6 +434,16 @@ function clearPresenceReaperStateForKeys(codeName, keys) {
434
434
  presenceReaperState.delete(stateKey(codeName, key));
435
435
  }
436
436
  }
437
+ function givenUpMcpServerKeys(codeName) {
438
+ const prefix = `${codeName}\0`;
439
+ const out = /* @__PURE__ */ new Set();
440
+ for (const [key, st] of presenceReaperState) {
441
+ if (key.startsWith(prefix) && st.attempts >= MAX_PRESENCE_RESTART_ATTEMPTS) {
442
+ out.add(key.slice(prefix.length));
443
+ }
444
+ }
445
+ return out;
446
+ }
437
447
  function findMissingMcpServers(args) {
438
448
  const { rows, codeName, mcpJson } = args;
439
449
  const servers = mcpJson?.mcpServers ?? {};
@@ -1099,6 +1109,69 @@ function hydrateAutoResumeMarkers(saved) {
1099
1109
  return map;
1100
1110
  }
1101
1111
 
1112
+ // src/lib/resume-reconciler.ts
1113
+ var DEFAULT_MIN_HEALTHY_CYCLES = 2;
1114
+ var DEFAULT_BACKOFF_RESET_MS2 = 864e5;
1115
+ function readResumeReconcilerConfig(env = process.env) {
1116
+ const raw = (env["AGT_RESUME_RECONCILER_ENABLED"] ?? "").trim().toLowerCase();
1117
+ const minCycles = readEnvNumber("AGT_RESUME_MIN_HEALTHY_CYCLES", DEFAULT_MIN_HEALTHY_CYCLES);
1118
+ return {
1119
+ enabled: raw === "1" || raw === "true" || raw === "yes" || raw === "on",
1120
+ // A fractional / <1 override is meaningless for a cycle count — floor to an
1121
+ // integer and demand at least 1, so "healthy" always means "observed at
1122
+ // least once". readEnvNumber already rejects NaN / <=0 to the default.
1123
+ minHealthyCycles: Math.max(1, Math.floor(minCycles)),
1124
+ backoffResetMs: readEnvNumber("AGT_AUTO_RESUME_BACKOFF_RESET_MS", DEFAULT_BACKOFF_RESET_MS2)
1125
+ };
1126
+ }
1127
+ function decideResumeReconcile(opts) {
1128
+ const { trip, marker, health, config: config2, now } = opts;
1129
+ if (!config2.enabled) return { action: "hold", reason: "disabled" };
1130
+ if (!trip) return { action: "hold", reason: "no-trip" };
1131
+ if (marker && now - marker.autoResumedAt < config2.backoffResetMs) {
1132
+ if (trip.trippedAt === marker.trippedAt) {
1133
+ return { action: "hold", reason: "not-healthy-yet" };
1134
+ }
1135
+ return { action: "latch-unstable", trippedAt: trip.trippedAt };
1136
+ }
1137
+ if (!health.dependencyStatusActive) {
1138
+ return { action: "hold", reason: "not-healthy-yet", precondition: "status" };
1139
+ }
1140
+ if (!health.mcpPresent) {
1141
+ return { action: "hold", reason: "not-healthy-yet", precondition: "mcp" };
1142
+ }
1143
+ if (health.connectivityOkCycles < config2.minHealthyCycles) {
1144
+ return {
1145
+ action: "hold",
1146
+ reason: "not-healthy-yet",
1147
+ precondition: "connectivity",
1148
+ cyclesShort: config2.minHealthyCycles - Math.max(0, health.connectivityOkCycles)
1149
+ };
1150
+ }
1151
+ return { action: "resume", healthyCycles: health.connectivityOkCycles };
1152
+ }
1153
+
1154
+ // src/lib/resume-reconciler-inputs.ts
1155
+ function deriveMcpPresent(declaredKeys, givenUpKeys) {
1156
+ if (declaredKeys === null) return false;
1157
+ for (const key of declaredKeys) {
1158
+ if (givenUpKeys.has(key)) return false;
1159
+ }
1160
+ return true;
1161
+ }
1162
+ function parseResumeHealthResponse(raw) {
1163
+ if (!raw || typeof raw !== "object") return null;
1164
+ const r = raw;
1165
+ if (typeof r.dependencyStatusActive !== "boolean") return null;
1166
+ if (typeof r.connectivityOkCycles !== "number" || !Number.isFinite(r.connectivityOkCycles)) {
1167
+ return null;
1168
+ }
1169
+ return {
1170
+ dependencyStatusActive: r.dependencyStatusActive,
1171
+ connectivityOkCycles: r.connectivityOkCycles
1172
+ };
1173
+ }
1174
+
1102
1175
  // src/lib/model-change-respawn.ts
1103
1176
  function shouldRespawnForModelChange(input) {
1104
1177
  const { previousModel, primaryModel, framework, sessionHealthy } = input;
@@ -4741,6 +4814,10 @@ var killPausedCodeNames = /* @__PURE__ */ new Set();
4741
4814
  var BACK_ONLINE_GREETING_GUIDANCE = " When you reconnect, if you tell anyone you are back, start that message with a \u{1F44B} wave emoji and do not use a \u{1F7E2} green-light emoji.";
4742
4815
  function maybeAutoResume(agent) {
4743
4816
  const codeName = agent.code_name;
4817
+ if (readResumeReconcilerConfig().enabled) {
4818
+ void maybeResumeReconcile(agent);
4819
+ return;
4820
+ }
4744
4821
  if (autoResumeInFlight.has(codeName)) return;
4745
4822
  const trip = restartBreaker.getTrip(codeName);
4746
4823
  if (trip && autoResumeStandDowns.has(`${codeName}:${trip.trippedAt}`)) return;
@@ -4792,6 +4869,92 @@ function maybeAutoResume(agent) {
4792
4869
  log(`[auto-resume] agent=${codeName} POST failed (will retry next poll): ${err.message}`);
4793
4870
  });
4794
4871
  }
4872
+ function logResumeReconcileHoldOnce(codeName, trippedAt, reason, detail) {
4873
+ const key = `${codeName}:${trippedAt}`;
4874
+ if (autoResumeLoggedSkips.get(key) !== reason) {
4875
+ autoResumeLoggedSkips.set(key, reason);
4876
+ log(`[resume-reconciler] agent=${codeName} decision=hold reason=${reason}${detail ? ` (${detail})` : ""} (ENG-6383)`);
4877
+ }
4878
+ }
4879
+ async function maybeResumeReconcile(agent) {
4880
+ const codeName = agent.code_name;
4881
+ if (autoResumeInFlight.has(codeName)) return;
4882
+ const trip = restartBreaker.getTrip(codeName);
4883
+ if (!trip) return;
4884
+ const standDownKey = `${codeName}:${trip.trippedAt}`;
4885
+ if (autoResumeStandDowns.has(standDownKey)) return;
4886
+ autoResumeInFlight.add(codeName);
4887
+ try {
4888
+ const config2 = readResumeReconcilerConfig();
4889
+ const trippedAt = trip.trippedAt;
4890
+ let serverHealth = null;
4891
+ try {
4892
+ const raw = await api.get(
4893
+ `/host/circuit-breaker/resume-health?agent_id=${encodeURIComponent(agent.agent_id)}`
4894
+ );
4895
+ serverHealth = parseResumeHealthResponse(raw);
4896
+ } catch (err) {
4897
+ logResumeReconcileHoldOnce(codeName, trippedAt, "health-fetch-failed", err.message.slice(0, 80));
4898
+ return;
4899
+ }
4900
+ if (!serverHealth) {
4901
+ logResumeReconcileHoldOnce(codeName, trippedAt, "health-unparseable");
4902
+ return;
4903
+ }
4904
+ const declaredKeys = projectMcpKeys(codeName, getProjectDir(codeName));
4905
+ const mcpPresent = deriveMcpPresent(declaredKeys, givenUpMcpServerKeys(codeName));
4906
+ const decision = decideResumeReconcile({
4907
+ trip,
4908
+ marker: autoResumeMarkers.get(codeName),
4909
+ health: { ...serverHealth, mcpPresent },
4910
+ config: config2,
4911
+ now: Date.now()
4912
+ });
4913
+ if (decision.action === "hold") {
4914
+ const reason = decision.precondition ? `not-healthy-yet:${decision.precondition}` : decision.reason;
4915
+ const detail = decision.cyclesShort ? `${decision.cyclesShort}-short` : void 0;
4916
+ logResumeReconcileHoldOnce(codeName, trippedAt, reason, detail);
4917
+ return;
4918
+ }
4919
+ if (decision.action === "latch-unstable") {
4920
+ autoResumeStandDowns.add(standDownKey);
4921
+ const key = `${codeName}:${trippedAt}`;
4922
+ if (autoResumeLoggedSkips.get(key) !== "latch-unstable") {
4923
+ autoResumeLoggedSkips.set(key, "latch-unstable");
4924
+ log(
4925
+ `[resume-reconciler] agent=${codeName} decision=latch-unstable \u2014 auto-resumed once then re-tripped within the backoff window; latching to mandatory-manual (NOT resuming; restart counter untouched; agent stays paused with its circuit-breaker alert open) (ENG-6383)`
4926
+ );
4927
+ }
4928
+ return;
4929
+ }
4930
+ log(
4931
+ `[resume-reconciler] agent=${codeName} decision=resume healthyCycles=${decision.healthyCycles} trippedAt=${new Date(trippedAt).toISOString()} (ENG-6383)`
4932
+ );
4933
+ const res = await api.post(
4934
+ "/host/circuit-breaker/auto-resume",
4935
+ { agent_id: agent.agent_id, tripped_at: new Date(trippedAt).toISOString() }
4936
+ );
4937
+ if (res.resumed) {
4938
+ autoResumeMarkers.set(codeName, { trippedAt, autoResumedAt: Date.now() });
4939
+ restartBreaker.clear(codeName);
4940
+ reportedTrips.delete(codeName);
4941
+ state6 = {
4942
+ ...state6,
4943
+ circuitBreakerTrips: restartBreaker.serialize(),
4944
+ circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
4945
+ };
4946
+ send({ type: "state-update", state: state6 });
4947
+ log(`[resume-reconciler] agent=${codeName} resumed \u2014 a re-trip within the backoff window will latch unstable (ENG-6383)`);
4948
+ } else {
4949
+ autoResumeStandDowns.add(standDownKey);
4950
+ log(`[resume-reconciler] agent=${codeName} not applied (reason=${res.reason ?? "unknown"}) \u2014 standing down for this trip (ENG-6383)`);
4951
+ }
4952
+ } catch (err) {
4953
+ log(`[resume-reconciler] agent=${codeName} reconcile failed (will retry next poll): ${err.message}`);
4954
+ } finally {
4955
+ autoResumeInFlight.delete(codeName);
4956
+ }
4957
+ }
4795
4958
  function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-reload-mcp") {
4796
4959
  const existing = pendingSessionRestarts.get(codeName);
4797
4960
  if (existing) {
@@ -5266,7 +5429,7 @@ var cachedMaintenanceWindow = null;
5266
5429
  var lastVersionCheckAt = 0;
5267
5430
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
5268
5431
  var lastResponsivenessProbeAt = 0;
5269
- var agtCliVersion = true ? "0.28.18" : "dev";
5432
+ var agtCliVersion = true ? "0.28.19" : "dev";
5270
5433
  function resolveBrewPath(execFileSync4) {
5271
5434
  try {
5272
5435
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();