@integrity-labs/agt-cli 0.27.60 → 0.27.62

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.
@@ -7163,4 +7163,4 @@ export {
7163
7163
  managerInstallSystemUnitCommand,
7164
7164
  managerUninstallSystemUnitCommand
7165
7165
  };
7166
- //# sourceMappingURL=chunk-2YSXMB7P.js.map
7166
+ //# sourceMappingURL=chunk-33OAS3V2.js.map
@@ -15,7 +15,7 @@ import {
15
15
  provisionOrientHook,
16
16
  provisionStopHook,
17
17
  requireHost
18
- } from "../chunk-2YSXMB7P.js";
18
+ } from "../chunk-33OAS3V2.js";
19
19
  import {
20
20
  getProjectDir as getProjectDir2,
21
21
  getReadyTasks,
@@ -34,6 +34,7 @@ import {
34
34
  isAgentPromptReady,
35
35
  isSessionHealthy,
36
36
  isStaleForToday,
37
+ paneLogPath,
37
38
  peekCurrentSession,
38
39
  prepareForRespawn,
39
40
  readPaneLogTail,
@@ -1177,6 +1178,109 @@ function formatCoalesceDeferLogLine(opts) {
1177
1178
  return `[self-update] coalescing \u2014 ${opts.installed} \u2192 ${opts.latest} (${opts.channelLabel}) available, deferring for ${remainingSec}s (last restart inside ${windowMin}min window). See ENG-5862. Override with AGT_SELF_UPDATE_COALESCE_MS=0 during incident response.`;
1178
1179
  }
1179
1180
 
1181
+ // src/lib/maintenance-window.ts
1182
+ var DEFAULT_WINDOW_START = "01:00";
1183
+ var DEFAULT_WINDOW_END = "02:00";
1184
+ function isMaintenanceWindowDisabled(env = process.env) {
1185
+ return env["AGT_MAINTENANCE_WINDOW_DISABLE"] === "1";
1186
+ }
1187
+ var HHMM_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
1188
+ function normalizeHhmm(value) {
1189
+ if (typeof value !== "string") return null;
1190
+ const trimmed = value.trim();
1191
+ return HHMM_RE.test(trimmed) ? trimmed : null;
1192
+ }
1193
+ function resolveWindow(input) {
1194
+ const tzRaw = typeof input.timezone === "string" ? input.timezone.trim() : "";
1195
+ return {
1196
+ start: normalizeHhmm(input.start) ?? DEFAULT_WINDOW_START,
1197
+ end: normalizeHhmm(input.end) ?? DEFAULT_WINDOW_END,
1198
+ // Blank/whitespace → UTC. An explicit 'UTC' is also UTC. (Unlike
1199
+ // getTeamTimezone we keep 'UTC' here because this is the *evaluation* tz,
1200
+ // not an inherit-or-not decision.)
1201
+ timezone: tzRaw.length > 0 ? tzRaw : "UTC"
1202
+ };
1203
+ }
1204
+ function hhmmToMinutes(hhmm) {
1205
+ const [h, m] = hhmm.split(":");
1206
+ return Number(h) * 60 + Number(m);
1207
+ }
1208
+ function localHhmm(now, timezone) {
1209
+ try {
1210
+ const parts = new Intl.DateTimeFormat("en-GB", {
1211
+ timeZone: timezone,
1212
+ hour: "2-digit",
1213
+ minute: "2-digit",
1214
+ hour12: false
1215
+ }).formatToParts(now);
1216
+ const hh = parts.find((p) => p.type === "hour")?.value ?? "00";
1217
+ const mm = parts.find((p) => p.type === "minute")?.value ?? "00";
1218
+ const h = hh === "24" ? "00" : hh;
1219
+ return `${h}:${mm}`;
1220
+ } catch {
1221
+ const h = String(now.getUTCHours()).padStart(2, "0");
1222
+ const m = String(now.getUTCMinutes()).padStart(2, "0");
1223
+ return `${h}:${m}`;
1224
+ }
1225
+ }
1226
+ function isWithinMaintenanceWindow(input, now) {
1227
+ const w = resolveWindow(input);
1228
+ const start = hhmmToMinutes(w.start);
1229
+ const end = hhmmToMinutes(w.end);
1230
+ if (start === end) return false;
1231
+ const cur = hhmmToMinutes(localHhmm(now, w.timezone));
1232
+ if (start < end) return cur >= start && cur < end;
1233
+ return cur >= start || cur < end;
1234
+ }
1235
+ function minutesUntilWindowOpen(input, now) {
1236
+ if (isWithinMaintenanceWindow(input, now)) return 0;
1237
+ const w = resolveWindow(input);
1238
+ const start = hhmmToMinutes(w.start);
1239
+ const cur = hhmmToMinutes(localHhmm(now, w.timezone));
1240
+ return cur <= start ? start - cur : 1440 - cur + start;
1241
+ }
1242
+ function formatWindowDeferLogLine(input, now) {
1243
+ const w = resolveWindow(input);
1244
+ const mins = minutesUntilWindowOpen(input, now);
1245
+ return `next maintenance window ${w.start} ${w.timezone} (~${mins} min)`;
1246
+ }
1247
+
1248
+ // src/lib/update-window-gate.ts
1249
+ function decideMaintenanceWindowGate(opts) {
1250
+ if (isMaintenanceWindowDisabled(opts.env)) return "proceed";
1251
+ if (!opts.window) return "proceed";
1252
+ return isWithinMaintenanceWindow(opts.window, opts.now) ? "proceed" : "defer";
1253
+ }
1254
+ function isUrgentUpgrade(opts) {
1255
+ if (!opts.urgentTarget) return false;
1256
+ if (opts.installed === "dev") return false;
1257
+ return opts.isNewer(opts.installed, opts.urgentTarget);
1258
+ }
1259
+ var RESTART_IDLE_THRESHOLD_SECONDS = 120;
1260
+ var RESTART_INBOUND_QUIET_SECONDS = 300;
1261
+ var GATEABLE_RESTART_REASONS = /* @__PURE__ */ new Set([
1262
+ "model-change",
1263
+ "channel-set-change",
1264
+ "sender-policy-change",
1265
+ "hot-reload-mcp",
1266
+ "mcp-presence-reaper"
1267
+ ]);
1268
+ function isGateableRestartReason(reason) {
1269
+ return reason != null && GATEABLE_RESTART_REASONS.has(reason);
1270
+ }
1271
+ function decideRestartGate(opts) {
1272
+ if (isMaintenanceWindowDisabled(opts.env)) return "proceed";
1273
+ if (opts.window && !isWithinMaintenanceWindow(opts.window, opts.now)) {
1274
+ return "defer-window";
1275
+ }
1276
+ const paneThreshold = opts.idleThresholdSeconds ?? RESTART_IDLE_THRESHOLD_SECONDS;
1277
+ const inboundThreshold = opts.inboundQuietSeconds ?? RESTART_INBOUND_QUIET_SECONDS;
1278
+ const paneBusy = opts.paneLogAgeSeconds !== null && opts.paneLogAgeSeconds < paneThreshold;
1279
+ const inboundBusy = opts.inboundAgeSeconds !== null && opts.inboundAgeSeconds < inboundThreshold;
1280
+ if (paneBusy || inboundBusy) return "defer-idle";
1281
+ return "proceed";
1282
+ }
1283
+
1180
1284
  // src/lib/claude-pid-tracker.ts
1181
1285
  import { existsSync, readFileSync as readFileSync3 } from "fs";
1182
1286
  function readPidFile(path) {
@@ -3375,6 +3479,14 @@ function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-
3375
3479
  }
3376
3480
  const timer = setTimeout(() => {
3377
3481
  pendingSessionRestarts.delete(codeName);
3482
+ const gate = restartGateFor(codeName, breakerReason);
3483
+ if (gate !== "bypass" && gate !== "proceed") {
3484
+ log(
3485
+ `[maintenance-window] Deferring '${reason}' restart for '${codeName}' (${gate}) \u2014 re-checking in ${RESTART_DEFER_RECHECK_MS / 1e3}s`
3486
+ );
3487
+ scheduleSessionRestart(codeName, RESTART_DEFER_RECHECK_MS, reason, breakerReason);
3488
+ return;
3489
+ }
3378
3490
  stopPersistentSession(codeName, log);
3379
3491
  runningMcpHashes.delete(codeName);
3380
3492
  recordRestartForBreaker(codeName, breakerReason);
@@ -3390,6 +3502,34 @@ function cancelPendingSessionRestart(codeName) {
3390
3502
  pendingSessionRestarts.delete(codeName);
3391
3503
  log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);
3392
3504
  }
3505
+ var RESTART_DEFER_RECHECK_MS = 6e4;
3506
+ var lastInboundMs = /* @__PURE__ */ new Map();
3507
+ function noteInbound(codeName) {
3508
+ lastInboundMs.set(codeName, Date.now());
3509
+ }
3510
+ function inboundAgeSecondsFor(codeName) {
3511
+ const ts = lastInboundMs.get(codeName);
3512
+ if (ts === void 0) return null;
3513
+ return Math.max(0, Math.floor((Date.now() - ts) / 1e3));
3514
+ }
3515
+ function paneLogAgeSecondsFor(codeName) {
3516
+ try {
3517
+ const mtimeMs = statSync2(paneLogPath(codeName)).mtimeMs;
3518
+ return Math.max(0, Math.floor((Date.now() - mtimeMs) / 1e3));
3519
+ } catch (err) {
3520
+ if (err?.code === "ENOENT") return null;
3521
+ return 0;
3522
+ }
3523
+ }
3524
+ function restartGateFor(codeName, breakerReason) {
3525
+ if (!isGateableRestartReason(breakerReason)) return "bypass";
3526
+ return decideRestartGate({
3527
+ window: cachedMaintenanceWindow,
3528
+ paneLogAgeSeconds: paneLogAgeSecondsFor(codeName),
3529
+ inboundAgeSeconds: inboundAgeSecondsFor(codeName),
3530
+ now: /* @__PURE__ */ new Date()
3531
+ });
3532
+ }
3393
3533
  var runningMcpHashes = /* @__PURE__ */ new Map();
3394
3534
  var runningMcpServerKeys = /* @__PURE__ */ new Map();
3395
3535
  function projectMcpHash(_codeName, projectDir) {
@@ -3468,6 +3608,11 @@ async function runAgentConnectivityProbes(agent, integrations, projectDir) {
3468
3608
  }
3469
3609
  }
3470
3610
  function stopPersistentSessionAndForgetMcpBaseline(codeName, breakerReason) {
3611
+ const gate = restartGateFor(codeName, breakerReason);
3612
+ if (gate !== "bypass" && gate !== "proceed") {
3613
+ log(`[maintenance-window] Deferring '${breakerReason}' restart for '${codeName}' (${gate})`);
3614
+ return;
3615
+ }
3471
3616
  cancelPendingSessionRestart(codeName);
3472
3617
  stopPersistentSession(codeName, log);
3473
3618
  runningMcpHashes.delete(codeName);
@@ -3595,10 +3740,11 @@ function clearAgentCaches(agentId, codeName) {
3595
3740
  if (channelCacheMutated) saveChannelHashCache2();
3596
3741
  }
3597
3742
  var cachedFrameworkVersion = null;
3743
+ var cachedMaintenanceWindow = null;
3598
3744
  var lastVersionCheckAt = 0;
3599
3745
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3600
3746
  var lastResponsivenessProbeAt = 0;
3601
- var agtCliVersion = true ? "0.27.60" : "dev";
3747
+ var agtCliVersion = true ? "0.27.62" : "dev";
3602
3748
  function resolveBrewPath(execFileSync4) {
3603
3749
  try {
3604
3750
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -3867,6 +4013,9 @@ function claudeCodeUpgradeThrottled() {
3867
4013
  async function maybeUpgradeClaudeCode() {
3868
4014
  if (claudeCodeUpgradeInFlight) return;
3869
4015
  if (claudeCodeUpgradeThrottled()) return;
4016
+ if (decideMaintenanceWindowGate({ window: cachedMaintenanceWindow, now: /* @__PURE__ */ new Date() }) === "defer") {
4017
+ return;
4018
+ }
3870
4019
  claudeCodeUpgradeInFlight = true;
3871
4020
  stampClaudeCodeUpgradeMarker();
3872
4021
  const { execFileSync: execFileSync4 } = await import("child_process");
@@ -3972,6 +4121,12 @@ async function checkAndUpdateCliViaBrew() {
3972
4121
  if (agtOutdated) {
3973
4122
  const installed = agtOutdated.installed_versions?.[0] ?? "unknown";
3974
4123
  const latest = agtOutdated.current_version ?? "unknown";
4124
+ if (decideMaintenanceWindowGate({ window: cachedMaintenanceWindow, now: /* @__PURE__ */ new Date() }) === "defer") {
4125
+ log(
4126
+ `[self-update] agt CLI ${installed} \u2192 ${latest} (brew) deferred \u2014 ` + formatWindowDeferLogLine(cachedMaintenanceWindow ?? {}, /* @__PURE__ */ new Date())
4127
+ );
4128
+ return;
4129
+ }
3975
4130
  const coalesceWindowMs = resolveCoalesceWindowMs();
3976
4131
  const coalesce = decideSelfUpdateCoalesce({
3977
4132
  windowMs: coalesceWindowMs,
@@ -4010,7 +4165,6 @@ async function checkAndUpdateCliViaBrew() {
4010
4165
  }
4011
4166
  }
4012
4167
  async function checkAndUpdateCliViaNpm() {
4013
- const { execFileSync: execFileSync4 } = await import("child_process");
4014
4168
  if (agtCliVersion === "dev") return;
4015
4169
  const channel = process.env.AGT_CLI_RELEASE_CHANNEL || "latest";
4016
4170
  let latest;
@@ -4036,6 +4190,18 @@ async function checkAndUpdateCliViaNpm() {
4036
4190
  log(`[self-update] npm registry fetch failed: ${err.message}`);
4037
4191
  return;
4038
4192
  }
4193
+ const urgentTarget = await fetchUrgentDistTagVersion();
4194
+ if (isUrgentUpgrade({
4195
+ urgentTarget,
4196
+ installed: agtCliVersion,
4197
+ isNewer: (installed, candidate) => candidate !== installed && !isOlderSemverTuple(installed, candidate)
4198
+ })) {
4199
+ log(
4200
+ `[self-update] URGENT agt CLI hotfix ${agtCliVersion} \u2192 ${urgentTarget} (overrides channel=${channel} + maintenance window). Upgrading via npm...`
4201
+ );
4202
+ await installAgtCliViaNpm(urgentTarget, "urgent");
4203
+ return;
4204
+ }
4039
4205
  let shouldUpdate;
4040
4206
  if (channel === "latest") {
4041
4207
  shouldUpdate = isNewerSemver(agtCliVersion, latest);
@@ -4056,6 +4222,12 @@ async function checkAndUpdateCliViaNpm() {
4056
4222
  }
4057
4223
  return;
4058
4224
  }
4225
+ if (decideMaintenanceWindowGate({ window: cachedMaintenanceWindow, now: /* @__PURE__ */ new Date() }) === "defer") {
4226
+ log(
4227
+ `[self-update] agt CLI ${agtCliVersion} \u2192 ${latest} (channel=${channel}) deferred \u2014 ` + formatWindowDeferLogLine(cachedMaintenanceWindow ?? {}, /* @__PURE__ */ new Date())
4228
+ );
4229
+ return;
4230
+ }
4059
4231
  const coalesceWindowMs = resolveCoalesceWindowMs();
4060
4232
  const coalesce = decideSelfUpdateCoalesce({
4061
4233
  windowMs: coalesceWindowMs,
@@ -4073,27 +4245,40 @@ async function checkAndUpdateCliViaNpm() {
4073
4245
  return;
4074
4246
  }
4075
4247
  log(`[self-update] agt CLI update available: ${agtCliVersion} \u2192 ${latest} (channel=${channel}). Upgrading via npm...`);
4248
+ await installAgtCliViaNpm(latest, `channel=${channel}`);
4249
+ }
4250
+ async function fetchUrgentDistTagVersion() {
4251
+ try {
4252
+ const res = await fetch("https://registry.npmjs.org/@integrity-labs/agt-cli/urgent", {
4253
+ signal: AbortSignal.timeout(1e4),
4254
+ headers: { Accept: "application/json" }
4255
+ });
4256
+ if (!res.ok) return null;
4257
+ const body = await res.json();
4258
+ return body.version ?? null;
4259
+ } catch {
4260
+ return null;
4261
+ }
4262
+ }
4263
+ async function installAgtCliViaNpm(version, reasonLabel) {
4264
+ const { execFileSync: execFileSync4 } = await import("child_process");
4076
4265
  const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
4077
4266
  const cmd = isRoot ? "npm" : "sudo";
4078
- const args = isRoot ? [
4079
- "install",
4080
- "-g",
4081
- `@integrity-labs/agt-cli@${latest}`,
4082
- "--registry=https://registry.npmjs.org"
4083
- ] : [
4084
- "-n",
4085
- "npm",
4267
+ const installArgs = [
4086
4268
  "install",
4087
4269
  "-g",
4088
- `@integrity-labs/agt-cli@${latest}`,
4270
+ `@integrity-labs/agt-cli@${version}`,
4089
4271
  "--registry=https://registry.npmjs.org"
4090
4272
  ];
4273
+ const args = isRoot ? installArgs : ["-n", "npm", ...installArgs];
4091
4274
  try {
4092
4275
  execFileSync4(cmd, args, { timeout: 18e4, stdio: "pipe" });
4093
- log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
4276
+ log(
4277
+ `[self-update] agt CLI upgraded to ${version} (${reasonLabel}). Scheduling manager restart so the new binary takes effect.`
4278
+ );
4094
4279
  stampLastSelfUpdateApplied(selfUpdateAppliedMarkerPath());
4095
4280
  restartAfterUpgrade = true;
4096
- pendingUpgradeVersion = latest;
4281
+ pendingUpgradeVersion = version;
4097
4282
  } catch (err) {
4098
4283
  log(`[self-update] npm upgrade failed: ${err.message}`);
4099
4284
  }
@@ -4612,7 +4797,7 @@ async function pollCycle() {
4612
4797
  log,
4613
4798
  resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,
4614
4799
  stopSession: (codeName) => {
4615
- stopPersistentSessionAndForgetMcpBaseline(codeName, "channel-set-change");
4800
+ stopPersistentSessionAndForgetMcpBaseline(codeName, "channel-restart-flag");
4616
4801
  agentState.persistentSessionAgents.delete(codeName);
4617
4802
  claudeAuthTupleBySession.delete(codeName);
4618
4803
  },
@@ -4724,13 +4909,18 @@ async function pollCycle() {
4724
4909
  // behind a fresh heartbeat.
4725
4910
  realtime_socket_connected: isRealtimeSocketConnected()
4726
4911
  });
4912
+ if (hbResp?.maintenance_window) {
4913
+ cachedMaintenanceWindow = hbResp.maintenance_window;
4914
+ }
4727
4915
  try {
4728
- const { maybeUpdateClaudeCode } = await import("../claude-code-updater-4E5T2X3Z.js");
4729
- await maybeUpdateClaudeCode({
4730
- desiredVersion: hbResp?.desired_claude_code_version ?? "latest",
4731
- currentVersion: cachedFrameworkVersion,
4732
- log
4733
- });
4916
+ if (decideMaintenanceWindowGate({ window: cachedMaintenanceWindow, now: /* @__PURE__ */ new Date() }) === "proceed") {
4917
+ const { maybeUpdateClaudeCode } = await import("../claude-code-updater-4E5T2X3Z.js");
4918
+ await maybeUpdateClaudeCode({
4919
+ desiredVersion: hbResp?.desired_claude_code_version ?? "latest",
4920
+ currentVersion: cachedFrameworkVersion,
4921
+ log
4922
+ });
4923
+ }
4734
4924
  } catch (err) {
4735
4925
  log(`Claude Code updater error: ${err.message}`);
4736
4926
  }
@@ -7779,6 +7969,7 @@ async function pollDirectChatMessages(agentStates) {
7779
7969
  }
7780
7970
  }
7781
7971
  async function processDirectChatMessage(agent, msg) {
7972
+ noteInbound(agent.codeName);
7782
7973
  const fw = agentFrameworkCache.get(agent.codeName) ?? "openclaw";
7783
7974
  log(`[direct-chat] Processing message for '${agent.codeName}' (fw=${fw}): id=${msg.id} len=${msg.content.length}`);
7784
7975
  if (isDirectChatMessageExpired(msg.created_at, Date.now(), directChatMaxAgeMs())) {