@integrity-labs/agt-cli 0.27.106 → 0.27.108

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
@@ -28,7 +28,7 @@ import {
28
28
  success,
29
29
  table,
30
30
  warn
31
- } from "../chunk-4WI2ACNG.js";
31
+ } from "../chunk-N4YEE4O7.js";
32
32
  import {
33
33
  CHANNEL_REGISTRY,
34
34
  DEPLOYMENT_TEMPLATES,
@@ -54,7 +54,7 @@ import {
54
54
  renderTemplate,
55
55
  resolveChannels,
56
56
  serializeManifestForSlackCli
57
- } from "../chunk-GKRVSTYY.js";
57
+ } from "../chunk-BP4D3ZPJ.js";
58
58
 
59
59
  // src/bin/agt.ts
60
60
  import { join as join20 } from "path";
@@ -4934,7 +4934,7 @@ import { execFileSync, execSync } from "child_process";
4934
4934
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4935
4935
  import chalk18 from "chalk";
4936
4936
  import ora16 from "ora";
4937
- var cliVersion = true ? "0.27.106" : "dev";
4937
+ var cliVersion = true ? "0.27.108" : "dev";
4938
4938
  async function fetchLatestVersion() {
4939
4939
  const host2 = getHost();
4940
4940
  if (!host2) return null;
@@ -5857,7 +5857,7 @@ function handleError(err) {
5857
5857
  }
5858
5858
 
5859
5859
  // src/bin/agt.ts
5860
- var cliVersion2 = true ? "0.27.106" : "dev";
5860
+ var cliVersion2 = true ? "0.27.108" : "dev";
5861
5861
  var program = new Command();
5862
5862
  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");
5863
5863
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -4285,4 +4285,4 @@ export {
4285
4285
  attributeTranscriptUsageByRun,
4286
4286
  KANBAN_CHECK_COMMAND
4287
4287
  };
4288
- //# sourceMappingURL=chunk-GKRVSTYY.js.map
4288
+ //# sourceMappingURL=chunk-BP4D3ZPJ.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  claudeModelAlias,
3
3
  isClaudeFastMode
4
- } from "./chunk-GKRVSTYY.js";
4
+ } from "./chunk-BP4D3ZPJ.js";
5
5
  import {
6
6
  reapOrphanChannelMcps
7
7
  } from "./chunk-XWVM4KPK.js";
@@ -1574,4 +1574,4 @@ export {
1574
1574
  stopAllSessionsAndWait,
1575
1575
  getProjectDir
1576
1576
  };
1577
- //# sourceMappingURL=chunk-SKKWVNUY.js.map
1577
+ //# sourceMappingURL=chunk-DFQ2EOHB.js.map
@@ -9,7 +9,7 @@ import {
9
9
  parseDeliveryTarget,
10
10
  registerFramework,
11
11
  wrapScheduledTaskPrompt
12
- } from "./chunk-GKRVSTYY.js";
12
+ } from "./chunk-BP4D3ZPJ.js";
13
13
 
14
14
  // ../../packages/core/dist/integrations/registry.js
15
15
  var INTEGRATION_REGISTRY = [
@@ -7586,4 +7586,4 @@ export {
7586
7586
  managerInstallSystemUnitCommand,
7587
7587
  managerUninstallSystemUnitCommand
7588
7588
  };
7589
- //# sourceMappingURL=chunk-4WI2ACNG.js.map
7589
+ //# sourceMappingURL=chunk-N4YEE4O7.js.map
@@ -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-4O56FXQX.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-HGR5D4DJ.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-Q4FKQLMQ.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-AI37D2CT.js.map
@@ -17,7 +17,7 @@ import {
17
17
  provisionStopHook,
18
18
  requireHost,
19
19
  safeWriteJsonAtomic
20
- } from "../chunk-4WI2ACNG.js";
20
+ } from "../chunk-N4YEE4O7.js";
21
21
  import {
22
22
  getProjectDir as getProjectDir2,
23
23
  getReadyTasks,
@@ -55,7 +55,7 @@ import {
55
55
  stopPersistentSession,
56
56
  takeWatchdogGiveUpCount,
57
57
  takeZombieDetection
58
- } from "../chunk-SKKWVNUY.js";
58
+ } from "../chunk-DFQ2EOHB.js";
59
59
  import {
60
60
  KANBAN_CHECK_COMMAND,
61
61
  SUPPRESS_SENTINEL,
@@ -79,7 +79,7 @@ import {
79
79
  resolveConnectivityProbe,
80
80
  resolveDmTarget,
81
81
  wrapScheduledTaskPrompt
82
- } from "../chunk-GKRVSTYY.js";
82
+ } from "../chunk-BP4D3ZPJ.js";
83
83
  import {
84
84
  parsePsRows,
85
85
  reapOrphanChannelMcps
@@ -968,6 +968,42 @@ function formatStatusMessage(events, windowMs) {
968
968
  return `Circuit breaker tripped: ${events.length} restarts in ${windowLabel} (${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`;
969
969
  }
970
970
 
971
+ // src/lib/auto-resume.ts
972
+ var DEFAULT_QUIET_MS = 18e5;
973
+ var DEFAULT_BACKOFF_RESET_MS = 864e5;
974
+ function readAutoResumeConfig(env = process.env) {
975
+ const raw = (env["AGT_AUTO_RESUME_ENABLED"] ?? "").trim().toLowerCase();
976
+ return {
977
+ enabled: raw === "1" || raw === "true" || raw === "yes" || raw === "on",
978
+ quietMs: readEnvNumber("AGT_AUTO_RESUME_QUIET_MS", DEFAULT_QUIET_MS),
979
+ backoffResetMs: readEnvNumber("AGT_AUTO_RESUME_BACKOFF_RESET_MS", DEFAULT_BACKOFF_RESET_MS)
980
+ };
981
+ }
982
+ function decideAutoResume(opts) {
983
+ const { trip, marker, config: config2, now } = opts;
984
+ if (!config2.enabled) return { action: "skip", reason: "disabled" };
985
+ if (!trip) return { action: "skip", reason: "no-trip" };
986
+ if (marker && now - marker.autoResumedAt < config2.backoffResetMs) {
987
+ return { action: "skip", reason: "already-auto-resumed" };
988
+ }
989
+ const lastEventAt = trip.eventsAtTrip.length > 0 ? Math.max(trip.trippedAt, ...trip.eventsAtTrip.map((e) => e.at)) : trip.trippedAt;
990
+ const quietFor = now - lastEventAt;
991
+ if (quietFor < config2.quietMs) {
992
+ return { action: "skip", reason: "not-quiet-yet", remainingMs: config2.quietMs - quietFor };
993
+ }
994
+ return { action: "resume", quietMs: quietFor };
995
+ }
996
+ function hydrateAutoResumeMarkers(saved) {
997
+ const map = /* @__PURE__ */ new Map();
998
+ if (!saved) return map;
999
+ for (const [codeName, rec] of Object.entries(saved)) {
1000
+ if (rec && typeof rec.trippedAt === "number" && typeof rec.autoResumedAt === "number") {
1001
+ map.set(codeName, rec);
1002
+ }
1003
+ }
1004
+ return map;
1005
+ }
1006
+
971
1007
  // src/lib/model-change-respawn.ts
972
1008
  function shouldRespawnForModelChange(input) {
973
1009
  const { previousModel, primaryModel, framework, sessionHealthy } = input;
@@ -3730,6 +3766,64 @@ function maybeReportCurrentTrip(codeName) {
3730
3766
  log(`[circuit-breaker] Failed to report trip for '${codeName}' to API: ${err.message}`);
3731
3767
  });
3732
3768
  }
3769
+ var autoResumeMarkers = /* @__PURE__ */ new Map();
3770
+ var autoResumeInFlight = /* @__PURE__ */ new Set();
3771
+ var AUTO_RESUME_SELF_WINDOW_MS = 12e4;
3772
+ var autoResumeLoggedSkips = /* @__PURE__ */ new Map();
3773
+ var autoResumeStandDowns = /* @__PURE__ */ new Set();
3774
+ function maybeAutoResume(agent) {
3775
+ const codeName = agent.code_name;
3776
+ if (autoResumeInFlight.has(codeName)) return;
3777
+ const trip = restartBreaker.getTrip(codeName);
3778
+ if (trip && autoResumeStandDowns.has(`${codeName}:${trip.trippedAt}`)) return;
3779
+ const decision = decideAutoResume({
3780
+ trip,
3781
+ marker: autoResumeMarkers.get(codeName),
3782
+ config: readAutoResumeConfig(),
3783
+ now: Date.now()
3784
+ });
3785
+ if (decision.action === "skip") {
3786
+ if ((decision.reason === "already-auto-resumed" || decision.reason === "not-quiet-yet") && trip) {
3787
+ const key = `${codeName}:${trip.trippedAt}`;
3788
+ if (autoResumeLoggedSkips.get(key) !== decision.reason) {
3789
+ autoResumeLoggedSkips.set(key, decision.reason);
3790
+ const detail = decision.reason === "not-quiet-yet" ? ` (~${Math.ceil((decision.remainingMs ?? 0) / 6e4)}min remaining)` : " \u2014 credit spent for this cycle; operator resume re-arms it";
3791
+ log(`[auto-resume] agent=${codeName} decision=skip reason=${decision.reason}${detail} (ENG-6088)`);
3792
+ }
3793
+ }
3794
+ return;
3795
+ }
3796
+ const trippedAt = trip.trippedAt;
3797
+ log(
3798
+ `[auto-resume] agent=${codeName} decision=resume quiet=${Math.round(decision.quietMs / 6e4)}min trippedAt=${new Date(trippedAt).toISOString()} (ENG-6088)`
3799
+ );
3800
+ autoResumeInFlight.add(codeName);
3801
+ api.post("/host/circuit-breaker/auto-resume", {
3802
+ agent_id: agent.agent_id,
3803
+ quiet_ms: decision.quietMs,
3804
+ tripped_at: new Date(trippedAt).toISOString()
3805
+ }).then((res) => {
3806
+ autoResumeInFlight.delete(codeName);
3807
+ if (res.resumed) {
3808
+ autoResumeMarkers.set(codeName, { trippedAt, autoResumedAt: Date.now() });
3809
+ restartBreaker.clear(codeName);
3810
+ reportedTrips.delete(codeName);
3811
+ state5 = {
3812
+ ...state5,
3813
+ circuitBreakerTrips: restartBreaker.serialize(),
3814
+ circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
3815
+ };
3816
+ send({ type: "state-update", state: state5 });
3817
+ log(`[auto-resume] agent=${codeName} resumed \u2014 re-trip within backoff window will stay paused (ENG-6088)`);
3818
+ } else {
3819
+ autoResumeStandDowns.add(`${codeName}:${trippedAt}`);
3820
+ log(`[auto-resume] agent=${codeName} not applied (reason=${res.reason ?? "unknown"}) \u2014 standing down for this trip (ENG-6088)`);
3821
+ }
3822
+ }).catch((err) => {
3823
+ autoResumeInFlight.delete(codeName);
3824
+ log(`[auto-resume] agent=${codeName} POST failed (will retry next poll): ${err.message}`);
3825
+ });
3826
+ }
3733
3827
  function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-reload-mcp") {
3734
3828
  const existing = pendingSessionRestarts.get(codeName);
3735
3829
  if (existing) {
@@ -4039,7 +4133,7 @@ var cachedMaintenanceWindow = null;
4039
4133
  var lastVersionCheckAt = 0;
4040
4134
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
4041
4135
  var lastResponsivenessProbeAt = 0;
4042
- var agtCliVersion = true ? "0.27.106" : "dev";
4136
+ var agtCliVersion = true ? "0.27.108" : "dev";
4043
4137
  function resolveBrewPath(execFileSync4) {
4044
4138
  try {
4045
4139
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -5232,7 +5326,7 @@ async function pollCycle() {
5232
5326
  }
5233
5327
  try {
5234
5328
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
5235
- const { collectDiagnostics } = await import("../persistent-session-4O56FXQX.js");
5329
+ const { collectDiagnostics } = await import("../persistent-session-HGR5D4DJ.js");
5236
5330
  const diagCodeNames = [...agentState.persistentSessionAgents];
5237
5331
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
5238
5332
  let tailscaleHostname;
@@ -5319,12 +5413,12 @@ async function pollCycle() {
5319
5413
  const {
5320
5414
  collectResponsivenessProbes,
5321
5415
  getResponsivenessIntervalMs
5322
- } = await import("../responsiveness-probe-I4XS2KCA.js");
5416
+ } = await import("../responsiveness-probe-FS376EVY.js");
5323
5417
  const probeIntervalMs = getResponsivenessIntervalMs();
5324
5418
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
5325
5419
  const probeCodeNames = [...agentState.persistentSessionAgents];
5326
5420
  if (probeCodeNames.length > 0) {
5327
- const { takeAcpxExecFailureCount, creditAcpxExecFailureCount } = await import("../persistent-session-4O56FXQX.js");
5421
+ const { takeAcpxExecFailureCount, creditAcpxExecFailureCount } = await import("../persistent-session-HGR5D4DJ.js");
5328
5422
  const drainedGiveUps = /* @__PURE__ */ new Map();
5329
5423
  const drainedAcpxFailures = /* @__PURE__ */ new Map();
5330
5424
  const probes = collectResponsivenessProbes(probeCodeNames).map((p) => {
@@ -5615,7 +5709,9 @@ async function pollCycle() {
5615
5709
  // ENG-5441: serialise trip state on every poll so manager restarts
5616
5710
  // never silently clear a tripped breaker. Cheap — only tripped
5617
5711
  // entries are stored, and the typical state is zero entries.
5618
- circuitBreakerTrips: restartBreaker.serialize()
5712
+ circuitBreakerTrips: restartBreaker.serialize(),
5713
+ // ENG-6088: spent auto-resume credits survive manager restarts too.
5714
+ circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
5619
5715
  };
5620
5716
  if (consecutivePollFailures > 0) {
5621
5717
  log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);
@@ -5690,6 +5786,9 @@ async function processAgent(agent, agentStates) {
5690
5786
  log(`Killed tmux session for paused agent '${agent.code_name}'`);
5691
5787
  } catch {
5692
5788
  }
5789
+ if (agent.status === "paused") {
5790
+ maybeAutoResume(agent);
5791
+ }
5693
5792
  agentStates.push({
5694
5793
  agentId: agent.agent_id,
5695
5794
  codeName: agent.code_name,
@@ -5781,6 +5880,18 @@ async function processAgent(agent, agentStates) {
5781
5880
  reportedTrips.delete(agent.code_name);
5782
5881
  log(`[circuit-breaker] Cleared trip for '${agent.code_name}' on operator resume (paused \u2192 active)`);
5783
5882
  }
5883
+ if (previousStatus === "paused" && agent.status === "active") {
5884
+ const marker = autoResumeMarkers.get(agent.code_name);
5885
+ const isSelfResume = marker !== void 0 && Date.now() - marker.autoResumedAt < AUTO_RESUME_SELF_WINDOW_MS;
5886
+ if (!isSelfResume && autoResumeMarkers.delete(agent.code_name)) {
5887
+ state5 = {
5888
+ ...state5,
5889
+ circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
5890
+ };
5891
+ send({ type: "state-update", state: state5 });
5892
+ log(`[auto-resume] Cleared auto-resume marker for '${agent.code_name}' on operator resume \u2014 credit re-armed (ENG-6088)`);
5893
+ }
5894
+ }
5784
5895
  let channelCacheMutated = false;
5785
5896
  for (const key of agentState.knownChannelConfigHashes.keys()) {
5786
5897
  if (key.startsWith(`${agent.agent_id}:`)) {
@@ -7645,6 +7756,17 @@ async function deliverScheduledCardResult(codeName, agentId, cardId) {
7645
7756
  markScheduledCardDeliveryComplete(cardId);
7646
7757
  return "terminal";
7647
7758
  }
7759
+ if (card.suppress_delivery === true) {
7760
+ log(`[scheduled-kanban] Suppressing delivery (structured flag) for card ${cardId} (task '${task.name}') on '${codeName}' \u2014 result recorded on the card only`);
7761
+ if (task.deliveryMode === "announce" && task.deliveryTo) {
7762
+ await reportDeliveryStatus(agentId, task.taskId, {
7763
+ status: "skipped",
7764
+ error_code: "NO_CONTENT"
7765
+ });
7766
+ }
7767
+ markScheduledCardDeliveryComplete(cardId);
7768
+ return "delivered";
7769
+ }
7648
7770
  await processClaudeTaskResult(codeName, agentId, task.templateId, card.result ?? "", {
7649
7771
  mode: task.deliveryMode,
7650
7772
  channel: task.deliveryChannel,
@@ -7679,7 +7801,7 @@ var SCHEDULED_CARD_DELIVERY_CONTRACT = `
7679
7801
  ---
7680
7802
  [delivery contract \u2014 system]
7681
7803
  The result you write on this card IS the message that will be delivered to the user. Delivery happens automatically from the card \u2014 never send, post, or message it yourself.
7682
- If \u2014 and ONLY if \u2014 the task above contains explicit opt-out wording the user typed ("DO NOT notify me unless \u2026", "only if X", "stay silent unless \u2026") and that condition is NOT met this run, write exactly ${SUPPRESS_SENTINEL} \u2014 bare, no backticks, no other text \u2014 ALONE as the card result and mark the card done. That is the ONLY way to honor the user's do-not-notify instruction; writing "nothing urgent" / "all quiet" instead would still be DELIVERED as a message. If the task asks for a digest/report WITHOUT opt-out wording, a zero-item report is a valid deliverable \u2014 deliver it.`;
7804
+ If \u2014 and ONLY if \u2014 the task above contains explicit opt-out wording the user typed ("DO NOT notify me unless \u2026", "only if X", "stay silent unless \u2026") and that condition is NOT met this run, call kanban_done with suppress_delivery: true \u2014 your result is still recorded on the card but will NOT be messaged to the user. That is how you honor the user's do-not-notify instruction; a "nothing urgent" / "all quiet" result WITHOUT the flag would still be DELIVERED as a message. (Legacy fallback: writing exactly ${SUPPRESS_SENTINEL} alone as the result also suppresses.) If the task asks for a digest/report WITHOUT opt-out wording, a zero-item report is a valid deliverable \u2014 deliver it.`;
7683
7805
  async function routeScheduledTaskViaKanban(codeName, agentId, task, prompt) {
7684
7806
  const { run_id, kanban_item_id } = await startRun({
7685
7807
  agent_id: agentId,
@@ -7700,7 +7822,7 @@ async function routeScheduledTaskViaKanban(codeName, agentId, task, prompt) {
7700
7822
  return false;
7701
7823
  }
7702
7824
  const nudge = `You have a new scheduled task on your kanban board: id=${kanban_item_id} title=${JSON.stringify(task.name)}. Call kanban_move("${kanban_item_id}", "in_progress"), do the work described on the card, then write the finished result onto the card and mark it done. Do NOT send, post, or message the result to anyone yourself \u2014 the result you write on the card IS the message that will be delivered to the user; delivery happens automatically from the card.
7703
- If \u2014 and ONLY if \u2014 the task description contains explicit opt-out wording the user typed (e.g. "DO NOT notify me unless urgent", "only if X", "stay silent unless \u2026") and that condition is NOT met this run, write exactly ${SUPPRESS_SENTINEL} \u2014 bare, no backticks, no other text, no "nothing urgent", no notes \u2014 ALONE as the card result and mark the card done. That is the ONLY way to honor the user's do-not-notify instruction: a "nothing urgent" status would still be DELIVERED as a message. A report with zero items is only a valid deliverable when the task asks for a digest WITHOUT opt-out wording \u2014 when in doubt, deliver.
7825
+ If \u2014 and ONLY if \u2014 the task description contains explicit opt-out wording the user typed (e.g. "DO NOT notify me unless urgent", "only if X", "stay silent unless \u2026") and that condition is NOT met this run, call kanban_done with suppress_delivery: true \u2014 your result stays on the card but will NOT be messaged to the user. That is how you honor the user's do-not-notify instruction: a "nothing urgent" result without the flag would still be DELIVERED as a message. A report with zero items is only a valid deliverable when the task asks for a digest WITHOUT opt-out wording \u2014 when in doubt, deliver.
7704
7826
  ` + formatRunMarker(run_id);
7705
7827
  let injectStatus;
7706
7828
  try {
@@ -9668,7 +9790,7 @@ async function processClaudePairSessions(agents) {
9668
9790
  killPairSession,
9669
9791
  pairTmuxSession,
9670
9792
  finalizeClaudePairOnboarding
9671
- } = await import("../claude-pair-runtime-Q4FKQLMQ.js");
9793
+ } = await import("../claude-pair-runtime-AI37D2CT.js");
9672
9794
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
9673
9795
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
9674
9796
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -10294,6 +10416,11 @@ function startManager(opts) {
10294
10416
  const n = Object.keys(parsed.circuitBreakerTrips).length;
10295
10417
  if (n > 0) log(`[startup] rehydrated ${n} circuit-breaker trip(s) \u2014 agents will remain paused until manually resumed`);
10296
10418
  }
10419
+ if (parsed.circuitBreakerAutoResumes && typeof parsed.circuitBreakerAutoResumes === "object") {
10420
+ autoResumeMarkers = hydrateAutoResumeMarkers(parsed.circuitBreakerAutoResumes);
10421
+ const n = autoResumeMarkers.size;
10422
+ if (n > 0) log(`[startup] rehydrated ${n} auto-resume marker(s) (ENG-6088)`);
10423
+ }
10297
10424
  }
10298
10425
  } catch (err) {
10299
10426
  log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);