@integrity-labs/agt-cli 0.28.1152 → 0.28.1154
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 +3 -3
- package/dist/chunk-PG343LR6.js +718 -0
- package/dist/chunk-PG343LR6.js.map +1 -0
- package/dist/{chunk-7GV2FQWS.js → chunk-PIK27GUV.js} +2 -2
- package/dist/{claude-code-updater-54ZISKTQ.js → claude-code-updater-BQ7OJMUH.js} +2 -2
- package/dist/lib/manager-worker.js +83 -83
- package/dist/lib/manager-worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZW6MEVUD.js +0 -342
- package/dist/chunk-ZW6MEVUD.js.map +0 -1
- /package/dist/{chunk-7GV2FQWS.js.map → chunk-PIK27GUV.js.map} +0 -0
- /package/dist/{claude-code-updater-54ZISKTQ.js.map → claude-code-updater-BQ7OJMUH.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/claude-code-updater.ts","../src/lib/disk-guard.ts"],"sourcesContent":["// ENG-5672: idle-gated, server-pinned Claude Code auto-updater.\n// ENG-5798: multi-install drift handling — detect coexisting claude binaries,\n// short-circuit on native autoupdater hosts, verify against absolute\n// install path, hard-error after N consecutive verify mismatches.\n//\n// Hosts install @anthropic-ai/claude-code once at bootstrap (host-bootstrap.ts)\n// and never refresh it, so a long-lived host runs a stale Claude Code forever.\n// This module brings a host to its *desired* version, which the server pins via\n// /host/refresh (per-host override, else fleet default, else 'latest').\n//\n// Safety properties (this runs npm install -g on live prod hosts):\n// • Idle-gated — we never install while a `claude --name agt-*` agent process\n// is running on the host (a running process holds the old binary anyway;\n// replacing it under a live session is pointless and risky). Busy → defer\n// to a later cycle.\n// • No-op when already on target — a concrete target equal to the installed\n// version short-circuits; a dist-tag ('latest') is resolved to a concrete\n// version via `npm view` and compared, so we don't reinstall every cycle.\n// • Host-global throttle — at most one check per interval (default 6h), so the\n// manager's ~10-30s poll doesn't shell out to npm/ps constantly.\n// • Log-and-continue — every failure is logged and swallowed; an error here\n// must never break the manager poll cycle.\n// • Multi-install awareness (ENG-5798) — on hosts where Anthropic's native\n// autoupdater owns /usr/bin/claude, this updater short-circuits cleanly\n// instead of fighting it. On hosts where multiple binaries coexist (e.g. a\n// brew cask alongside the npm global), we log every path+version once so\n// drift is visible in manager.log without spamming every cycle.\n//\n// The *installed* version is reported separately as hosts.framework_version\n// (the claude-code framework adapter's getVersion() == `claude --version`), so\n// this module does not report it itself.\n\nimport { execFile, execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { promisify } from 'node:util';\nimport { effectiveClaudeJsonPath } from './claude-config-location.js';\nimport { parsePsRows, CLAUDE_AGENT_PROCESS_PATTERN } from './orphan-channel-mcp-reaper.js';\nimport { FORCED_UPDATE_HARD_DEADLINE_MS } from '@augmented/core';\nimport { withPackageCacheLock } from './disk-guard.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst NPM_PKG = '@anthropic-ai/claude-code';\n\n/**\n * ENG-11641 follow-up: the minimum host Claude Code version, for security.\n *\n * The npm package is a native binary with its own bundled Bun, so patching\n * system Node does nothing for it. Versions before 2.1.308 bundle a Bun whose\n * Brotli decoder predates the CVE-2026-111497 (Node2Shell) fix, and 2.1.280 /\n * 2.1.296 also ship the compromised emSign ECC Root CA - G3 in their built-in\n * trust store (eMudhra intermediate key compromise, oss-security 2026-10-20).\n * #7108 fixed the agt-runtime IMAGE; it did nothing for the host's own global\n * install, which #7108 measured at 2.1.280 on 22 of 24 prod hosts. Those hosts\n * never converge because this updater only runs inside the maintenance window\n * AND only when no agent is live, and a working fleet is never idle.\n *\n * A host below this floor therefore:\n * • has its target raised to the floor when the pin/fleet default is lower\n * (a pin below a security floor IS the vulnerability, so it is overridden\n * and logged, not honoured);\n * • bypasses the maintenance window (see manager-worker's call site);\n * • still defers while an agent is live, for at most\n * FORCED_UPDATE_HARD_DEADLINE_MS (the same deadline the \"Update CLI now\"\n * forced update uses, ENG-8449), then installs under live agents ONLY IF\n * the npm install slot is provably the binary agents run (its version is\n * readable, below the floor and equal to PATH's). Otherwise — unresolvable\n * slot, unreadable slot, slot already fixed, or slot on a different version\n * (the ENG-5798 multi-install case) — it does NOT force, logs\n * `ERROR security_floor_blocked`, and the host stays below the floor until\n * an operator fixes its install slots. That is a known, alarmed limit, not\n * a guarantee of convergence.\n *\n * Raise this when a new security floor is set; never lower it.\n */\nexport const CLAUDE_CODE_SECURITY_FLOOR = '2.1.308';\n\n/** How soon to re-check a below-floor host that deferred because it was busy. */\nconst BELOW_FLOOR_RECHECK_MS = 60_000;\n\n/** Default host-global check interval. Overridable via env for tuning. */\nconst DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h\n\n/**\n * ENG-5798: After this many consecutive verify mismatches against the same\n * target version, emit a distinct one-shot error code that the\n * synthetic-probe / CloudWatch alarm pipeline can latch onto, instead of\n * silently re-failing forever. Overridable via env for testing.\n */\nconst DEFAULT_VERIFY_MISMATCH_THRESHOLD = 5;\n\nfunction intervalMs(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_INTERVAL_MS'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INTERVAL_MS;\n}\n\nfunction verifyMismatchThreshold(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_VERIFY_THRESHOLD'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_VERIFY_MISMATCH_THRESHOLD;\n}\n\n/** Extract a version string (leading semver) from `claude --version` / npm output. */\nexport function parseVersion(output: string): string | null {\n const m = output.match(/\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*/);\n return m ? m[0] : null;\n}\n\n/** A target spec is \"concrete\" when it already names an exact version. */\nexport function isConcreteVersion(spec: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*$/.test(spec.trim());\n}\n\n/**\n * Compare two concrete versions by their numeric major.minor.patch core.\n * A prerelease (`1.2.3-beta.1`) sorts BEFORE its release (`1.2.3`), as in\n * semver. Returns null when either side does not parse, so callers can fail\n * closed rather than guess.\n */\nexport function compareVersions(a: string, b: string): number | null {\n const re = /^(\\d+)\\.(\\d+)\\.(\\d+)(-[0-9A-Za-z.-]+)?/;\n const ma = re.exec(a.trim());\n const mb = re.exec(b.trim());\n if (!ma || !mb) return null;\n for (let i = 1; i <= 3; i++) {\n const d = Number(ma[i]) - Number(mb[i]);\n if (d !== 0) return d < 0 ? -1 : 1;\n }\n const preA = ma[4] !== undefined;\n const preB = mb[4] !== undefined;\n if (preA === preB) return 0;\n return preA ? -1 : 1;\n}\n\n/**\n * Whether the manager should call the updater on this poll (ENG-11641\n * follow-up). Normally only inside the maintenance window; a host whose\n * installed Claude Code is below the security floor runs it regardless of the\n * window. No server target means \"change nothing\" (ENG-10620).\n */\nexport function shouldRunClaudeCodeUpdater(input: {\n desiredVersion: string | null | undefined;\n installedVersion: string | null | undefined;\n windowGate: 'proceed' | 'defer';\n floor?: string;\n}): boolean {\n if (!input.desiredVersion) return false;\n if (isBelowSecurityFloor(input.installedVersion, input.floor)) return true;\n return input.windowGate === 'proceed';\n}\n\n/**\n * True when `version` is a known version strictly below\n * {@link CLAUDE_CODE_SECURITY_FLOOR}. Unknown/unparseable → false: we only\n * escalate past the normal gates on a measured version, never on a guess.\n */\nexport function isBelowSecurityFloor(\n version: string | null | undefined,\n floor: string = CLAUDE_CODE_SECURITY_FLOOR,\n): boolean {\n if (!version) return false;\n const parsed = parseVersion(version);\n if (!parsed) return false;\n const cmp = compareVersions(parsed, floor);\n return cmp !== null && cmp < 0;\n}\n\n/**\n * Well-known absolute paths where a `claude` binary may live on managed hosts.\n * Probed alongside the PATH-resolved binary so we can see all install slots,\n * even ones not on the manager systemd unit's PATH.\n */\nexport const WELL_KNOWN_CLAUDE_PATHS: readonly string[] = [\n '/usr/bin/claude', // Anthropic native autoupdater (Linux deb/rpm)\n '/usr/local/bin/claude', // npm global on macOS Intel + Linux without nvm\n '/opt/homebrew/bin/claude', // Homebrew on macOS Apple Silicon\n '/home/linuxbrew/.linuxbrew/bin/claude', // Linuxbrew (used on agt-aws-* hosts)\n];\n\nexport interface ClaudeCodeUpdaterDeps {\n /** Server-pinned target: a concrete version or an npm dist-tag like 'latest'. */\n desiredVersion: string;\n /** Currently-installed version, if the caller already knows it (e.g. the\n * manager's cachedFrameworkVersion). When omitted we run `claude --version`. */\n currentVersion?: string | null;\n log: (msg: string) => void;\n /** Override \"now\" for tests. */\n now?: () => number;\n /** Run a command; injected in tests. Defaults to execFile. */\n runCommand?: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>;\n /** Raw `ps -eo pid,ppid,args` snapshot; injected in tests. */\n psSnapshot?: () => string;\n /** Sync existence probe for binary detection; injected in tests. */\n fileExists?: (path: string) => boolean;\n /** Read ~/.claude.json contents (raw); returns null when absent. Injected in tests. */\n readClaudeJson?: (path: string) => string | null;\n /** Resolve the user's home directory; injected in tests. */\n homeDir?: () => string;\n /** Resolve symlinks to a real path (throws when absent); injected in tests. */\n realPath?: (path: string) => string;\n /** Security floor override; defaults to CLAUDE_CODE_SECURITY_FLOOR. Injected in tests. */\n securityFloor?: string;\n}\n\nexport type UpdaterOutcome =\n | { action: 'throttled' }\n | { action: 'up_to_date'; version: string }\n | { action: 'deferred_busy'; target: string }\n | { action: 'skipped_native_install'; target: string }\n | { action: 'updated'; from: string | null; to: string }\n | { action: 'failed'; reason: string };\n\n// ── Host-global state (module-level; one updater per host process) ──────────\nlet lastCheckAt = 0;\n/** Per-process one-shot guard for the multi-install warn line. */\nlet multiInstallWarned = false;\n/**\n * Sticky counter for consecutive `post-install verify mismatch` against the\n * same target version. Reset on success, on target change, or after the\n * threshold error has been emitted (so we emit it exactly once per stuck run).\n */\nlet verifyMismatchState: { target: string; count: number; alarmed: boolean } | null = null;\n/**\n * When a below-floor host first deferred because it was busy. The\n * FORCED_UPDATE_HARD_DEADLINE_MS clock runs from here; cleared once the host\n * is at or above the floor. Process-local on purpose: a manager restart\n * restarts the clock, which costs at most one more deadline period.\n */\nlet belowFloorBusySince: number | null = null;\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetClaudeCodeUpdaterState(): void {\n lastCheckAt = 0;\n multiInstallWarned = false;\n verifyMismatchState = null;\n belowFloorBusySince = null;\n}\n\nfunction defaultPsSnapshot(): string {\n return execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 });\n}\n\nfunction defaultReadClaudeJson(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/** True when at least one `claude --name agt-*` agent process is live. */\nexport function hostHasLiveAgent(psOutput: string): boolean {\n return parsePsRows(psOutput).some((r) => CLAUDE_AGENT_PROCESS_PATTERN.test(r.args));\n}\n\n/**\n * ENG-5798: returns true when ~/.claude.json reports `installMethod: \"native\"`,\n * meaning Anthropic's native autoupdater owns the on-disk binary. In that case\n * the brew/npm update path is actively harmful — it installs a second binary\n * the host's PATH never resolves to, and verify reads from yet a third.\n */\nexport function readsAsNativeInstall(raw: string | null): boolean {\n if (!raw) return false;\n try {\n const parsed = JSON.parse(raw) as { installMethod?: unknown };\n return parsed.installMethod === 'native';\n } catch {\n return false;\n }\n}\n\n/**\n * ENG-10620: true when `realPath` is a binary owned by Claude Code's native\n * installer, which keeps every version under `…/claude/versions/<semver>`\n * (e.g. `~/.local/share/claude/versions/2.1.274`, symlinked from\n * `~/.local/bin/claude`). An npm global resolves into\n * `…/node_modules/@anthropic-ai/claude-code/` instead.\n */\nexport function isNativeInstallPath(realPath: string): boolean {\n return /\\/claude\\/versions\\/\\d+\\.\\d+\\.\\d+[^/]*$/.test(realPath);\n}\n\n/**\n * ENG-10620: decide native ownership from the binary this host ACTUALLY runs,\n * not from `~/.claude.json`.\n *\n * `installMethod` is written by whichever `claude` last started, not by the\n * one the manager launches. On agt-aws-1 an operator shell's PATH resolved a\n * native `~/.local/bin/claude` while agents ran the npm global at\n * `/usr/bin/claude`; that one manual run flipped `installMethod` to `native`\n * and this updater stopped converging the host to its pin for good, while the\n * binary it would have updated was never native at all.\n *\n * Resolution mirrors how agent sessions pick their binary (resolveClaudeBinary:\n * CLAUDE_PATH, then PATH). The config key remains only as a fallback for a\n * host where no binary can be resolved.\n */\nasync function resolveNativeOwnership(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n realPath: (path: string) => string,\n claudeJson: string | null,\n): Promise<{ native: boolean; binary: string | null; configSaysNative: boolean }> {\n const configSaysNative = readsAsNativeInstall(claudeJson);\n let resolved: string | null = null;\n const override = process.env['CLAUDE_PATH'];\n if (override) {\n resolved = override;\n } else {\n try {\n const { stdout } = await run('which', ['claude']);\n resolved = stdout.trim().split('\\n')[0]?.trim() || null;\n } catch {\n resolved = null;\n }\n }\n if (resolved) {\n try {\n const real = realPath(resolved);\n return { native: isNativeInstallPath(real), binary: real, configSaysNative };\n } catch {\n /* unresolvable — fall back to the config key */\n }\n }\n return { native: configSaysNative, binary: null, configSaysNative };\n}\n\n/**\n * Resolve the absolute filesystem path that `npm install -g <pkg>` will write\n * the `claude` binary to. Returns null when `npm prefix -g` fails or is empty,\n * in which case the caller should fall back to PATH-based verify.\n *\n * We deliberately use `npm prefix -g` rather than `which claude` because the\n * latter resolves through PATH, which on agt-aws-* hosts can land on\n * /usr/bin/claude (the native autoupdater slot) — exactly the misalignment\n * ENG-5798 is fixing.\n */\nasync function resolveInstallTargetPath(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n): Promise<string | null> {\n try {\n const { stdout } = await run('npm', ['prefix', '-g']);\n const prefix = stdout.trim();\n if (!prefix) return null;\n return `${prefix}/bin/claude`;\n } catch {\n return null;\n }\n}\n\n/**\n * Enumerate every `claude` binary visible to the host: PATH-resolved + the\n * well-known absolute slots + `~/.claude/local/claude`. Returns one entry per\n * unique resolved path with the version each reports (or null if it failed to\n * execute). Used purely for the one-shot drift warn line.\n */\nexport async function probeClaudeInstalls(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n fileExists: (path: string) => boolean,\n home: string,\n): Promise<Array<{ path: string; version: string | null }>> {\n const candidates = new Set<string>(WELL_KNOWN_CLAUDE_PATHS);\n candidates.add(`${home}/.claude/local/claude`);\n // PATH-resolved. `which` is portable enough across the Linux + macOS hosts\n // we target; errors here just mean \"no claude on PATH\", which is fine.\n try {\n const { stdout } = await run('which', ['claude']);\n const p = stdout.trim().split('\\n')[0]?.trim();\n if (p) candidates.add(p);\n } catch {\n /* no-op */\n }\n\n const results: Array<{ path: string; version: string | null }> = [];\n for (const p of candidates) {\n if (!fileExists(p)) continue;\n let version: string | null = null;\n try {\n const { stdout } = await run(p, ['--version']);\n version = parseVersion(stdout);\n } catch {\n /* binary exists but failed to execute — record path with null version */\n }\n results.push({ path: p, version });\n }\n return results;\n}\n\n/**\n * Bring Claude Code to the desired version if the host is idle and not already\n * on target. Safe to call every manager poll — internally throttled. Returns an\n * outcome describing what happened (for logging/observability/tests).\n */\nexport async function maybeUpdateClaudeCode(deps: ClaudeCodeUpdaterDeps): Promise<UpdaterOutcome> {\n const now = deps.now ? deps.now() : Date.now();\n const run = deps.runCommand ?? ((cmd, args) => execFileAsync(cmd, args, { maxBuffer: 16 * 1024 * 1024 }));\n const ps = deps.psSnapshot ?? defaultPsSnapshot;\n const fileExists = deps.fileExists ?? existsSync;\n const readClaudeJson = deps.readClaudeJson ?? defaultReadClaudeJson;\n const realPath = deps.realPath ?? realpathSync;\n const home = (deps.homeDir ?? homedir)();\n const { log } = deps;\n const floor = deps.securityFloor ?? CLAUDE_CODE_SECURITY_FLOOR;\n let forcedUnderLiveAgents = false;\n\n // 1. Host-global throttle.\n if (now - lastCheckAt < intervalMs()) {\n return { action: 'throttled' };\n }\n lastCheckAt = now;\n\n try {\n const spec = (deps.desiredVersion || 'latest').trim();\n\n // 2. ENG-5798 multi-install drift detection. Emit one warn line per\n // process listing every claude binary + version we can see. This is\n // independent of the install decision below — even on hosts where we\n // proceed normally, surfacing drift is the difference between a 3-day\n // silent loop and a one-line lead.\n if (!multiInstallWarned) {\n const installs = await probeClaudeInstalls(run, fileExists, home);\n if (installs.length > 1) {\n const summary = installs\n .map((i) => `${i.path}=${i.version ?? 'unknown'}`)\n .join(', ');\n log(`[claude-code-updater] multiple claude installs detected: ${summary}`);\n }\n multiInstallWarned = true;\n }\n\n // 3. ENG-5798 native-install short-circuit. When Anthropic's native\n // autoupdater owns the host binary, the brew/npm update path is\n // actively harmful (it installs a second binary the manager's PATH\n // never resolves to, then verifies against yet a third). Defer.\n // ENG-10869: the path the AGENT actually reads, not the legacy one.\n // Once this host migrates, `~/.claude.json` is ARCHIVED, so a hardcoded\n // read here returns nothing forever and `configSaysNative` is permanently\n // false — silently re-opening the ENG-5798 harm this short-circuit exists\n // to prevent (brew/npm installing a second binary on a host Anthropic's\n // native autoupdater owns). The resolver falls back to the legacy file, so\n // an unmigrated host is unaffected.\n const claudeJsonPath = effectiveClaudeJsonPath(home);\n const ownership = await resolveNativeOwnership(run, realPath, readClaudeJson(claudeJsonPath));\n if (!ownership.native && ownership.configSaysNative) {\n log(\n `[claude-code-updater] ${claudeJsonPath} says installMethod=native but the binary this host runs (${ownership.binary}) is not a native install — ignoring it and converging to the pin`,\n );\n }\n if (ownership.native) {\n // One-shot info line per process — same throttle as the warn above, but\n // we re-use the throttle window to keep this from logging every poll.\n log('[claude-code-updater] native install detected, deferring to Anthropic autoupdater');\n // Reset any sticky verify-mismatch state — it can't apply once we stop\n // attempting installs.\n verifyMismatchState = null;\n return { action: 'skipped_native_install', target: spec };\n }\n\n // 4. Resolve the target to a concrete version (read-only). A concrete spec\n // is its own target; a dist-tag is resolved via `npm view`.\n let target: string;\n if (isConcreteVersion(spec)) {\n target = spec;\n } else {\n const { stdout } = await run('npm', ['view', `${NPM_PKG}@${spec}`, 'version']);\n const resolved = parseVersion(stdout);\n if (!resolved) {\n log(`[claude-code-updater] could not resolve ${NPM_PKG}@${spec} to a version; skipping`);\n return { action: 'failed', reason: 'unresolved_target' };\n }\n target = resolved;\n }\n\n // 4b. Security floor: never converge to a version below it. A pin (or a\n // stale fleet default) under the floor would otherwise short-circuit at\n // step 6 as \"up_to_date\" and keep the host vulnerable forever.\n const targetVsFloor = compareVersions(target, floor);\n if (targetVsFloor !== null && targetVsFloor < 0) {\n log(\n `[claude-code-updater] target ${target} is below the security floor ${floor} (ENG-11641: CVE-2026-111497 + emSign root) — raising the target to the floor`,\n );\n target = floor;\n }\n\n // 5. Determine the installed version (prefer the caller's cached value).\n let current = deps.currentVersion ?? null;\n if (!current) {\n try {\n const { stdout } = await run('claude', ['--version']);\n current = parseVersion(stdout);\n } catch {\n current = null; // claude missing/broken — treat as \"needs install\".\n }\n }\n\n const belowFloor = isBelowSecurityFloor(current, floor);\n if (!belowFloor) belowFloorBusySince = null;\n\n // 6. Already on target → nothing to do.\n if (current && current === target) {\n // Successful no-op — clear sticky verify mismatch state for this target.\n if (verifyMismatchState && verifyMismatchState.target === target) {\n verifyMismatchState = null;\n }\n return { action: 'up_to_date', version: target };\n }\n\n // 7. Idle gate — never replace the binary under a live agent session.\n // Exception: a host below the security floor defers for at most\n // FORCED_UPDATE_HARD_DEADLINE_MS, then installs under live agents if the\n // npm slot is provably the running binary (see the module header; the\n // same bounded-wait shape as the forced agt-cli update, ENG-8449).\n if (hostHasLiveAgent(ps())) {\n if (!belowFloor) {\n log(`[claude-code-updater] host busy (live agent) — deferring update to ${target} to a later cycle`);\n return { action: 'deferred_busy', target };\n }\n if (belowFloorBusySince === null) belowFloorBusySince = now;\n const waitedMs = now - belowFloorBusySince;\n if (waitedMs < FORCED_UPDATE_HARD_DEADLINE_MS) {\n // Re-check in ~1 min instead of the normal 6h throttle, so the deadline\n // is actually observed when it passes.\n lastCheckAt = now - intervalMs() + BELOW_FLOOR_RECHECK_MS;\n log(\n `[claude-code-updater] host busy (live agent) and ${current} is below the security floor ${floor} — deferring update to ${target}; will force it in ${Math.ceil((FORCED_UPDATE_HARD_DEADLINE_MS - waitedMs) / 60_000)}m if the npm install slot is the binary agents run`,\n );\n return { action: 'deferred_busy', target };\n }\n // Review 97810715f303 / ENG-5798: before overriding the idle gate, check\n // the slot npm actually writes. If it is ALREADY at or above the floor,\n // the below-floor reading came from a different binary on PATH (the\n // multi-install case), so an install here cannot fix anything. Forcing it\n // would re-run `npm install -g` under live agents every cycle while the\n // PATH binary stays put. Fall back to the normal idle-gated behaviour and\n // say why.\n const slotPath = await resolveInstallTargetPath(run);\n let slotVersion: string | null = null;\n if (slotPath) {\n try {\n slotVersion = parseVersion((await run(slotPath, ['--version'])).stdout);\n } catch {\n slotVersion = null;\n }\n }\n // Review fe4300a0793c: an unresolvable slot means we cannot prove an\n // install would help either, so it gets the same fall-back.\n if (!slotPath) {\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] ERROR security_floor_blocked: deadline reached but \\`npm prefix -g\\` did not resolve — cannot prove an install would reach the binary agents run; not forcing an install under live agents`,\n );\n return { action: 'deferred_busy', target };\n }\n // Review 0a3b967ea569: only force when there is positive evidence that\n // the slot npm writes IS the binary agents run and IS below the floor:\n // its version is readable, below the floor, and equal to what PATH\n // reports. An empty/broken slot (version unreadable), a slot already at\n // or above the floor, or a slot on a different version than PATH all mean\n // an install cannot be shown to help, so fall back to the idle gate.\n const slotIsTheRunningBinary =\n slotVersion !== null && isBelowSecurityFloor(slotVersion, floor) && slotVersion === current;\n if (!slotIsTheRunningBinary) {\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] ERROR security_floor_blocked: PATH claude reports ${current} (below the security floor ${floor}) but the npm install slot ${slotPath} reports ${slotVersion ?? 'no readable version'} — cannot show the install would reach the binary agents run (multi-install, ENG-5798); not forcing an install under live agents, investigate the host's claude install slots`,\n );\n return { action: 'deferred_busy', target };\n }\n log(\n `[claude-code-updater] security floor deadline reached: ${current} < ${floor} and host still busy after ${Math.round(waitedMs / 60_000)}m — installing ${target} anyway (ENG-11641)`,\n );\n forcedUnderLiveAgents = true;\n }\n\n // 8. Install the target, then verify against the *absolute* path of the\n // binary npm just wrote — not via PATH. PATH resolution is the original\n // ENG-5798 bug: on hosts with multiple claude installs, PATH lands on a\n // different binary than the one npm overwrites, so verify never agrees.\n const installTargetPath = await resolveInstallTargetPath(run);\n log(`[claude-code-updater] updating Claude Code ${current ?? '(none)'} → ${target}`);\n // ENG-11669 (Obi Wan d623befb7109): hold the package-cache lock so the\n // disk guard's `npm cache clean` can never run under this install.\n await withPackageCacheLock(() => run('npm', ['install', '-g', `${NPM_PKG}@${target}`]), {\n label: 'the Claude Code install',\n log,\n });\n\n // Prefer the absolute install target; fall back to PATH only when\n // `npm prefix -g` failed (in which case verify is best-effort).\n const verifyTarget = installTargetPath ?? 'claude';\n let installed: string | null = null;\n try {\n const { stdout } = await run(verifyTarget, ['--version']);\n installed = parseVersion(stdout);\n } catch {\n installed = null;\n }\n\n if (installed !== target) {\n // Review fe4300a0793c: a forced install that did not verify must not keep\n // the old deadline, or every later cycle would force again immediately.\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] post-install verify mismatch: expected ${target}, got ${installed ?? 'unknown'} from ${verifyTarget}`,\n );\n\n // ENG-5798: hard-error after N consecutive mismatches against the same\n // target. Reset the counter when the target changes — a new pin is a\n // new event, not a continuation of the old stuck run.\n if (!verifyMismatchState || verifyMismatchState.target !== target) {\n verifyMismatchState = { target, count: 1, alarmed: false };\n } else {\n verifyMismatchState.count += 1;\n }\n\n const threshold = verifyMismatchThreshold();\n if (verifyMismatchState.count >= threshold && !verifyMismatchState.alarmed) {\n verifyMismatchState.alarmed = true;\n log(\n `[claude-code-updater] ERROR verify_mismatch_stuck: ${verifyMismatchState.count} consecutive verify failures for target ${target} (verify path: ${verifyTarget}) — install is not taking effect, investigate host install slots`,\n );\n return { action: 'failed', reason: 'verify_mismatch_threshold' };\n }\n return { action: 'failed', reason: 'verify_mismatch' };\n }\n\n // Successful install — clear sticky state. Also end any security-floor wait\n // episode, so a later below-floor reading starts a fresh deadline instead of\n // inheriting this one's (review 97810715f303).\n verifyMismatchState = null;\n belowFloorBusySince = null;\n log(`[claude-code-updater] updated Claude Code ${current ?? '(none)'} → ${installed}`);\n if (forcedUnderLiveAgents) {\n // Review 8f1c08ba3c77: from here `claude --version` (and so the heartbeat's\n // framework_version) reads the new version, but the live sessions that held\n // the idle gate still execute the OLD binary until they restart. Say so\n // loudly, so the host does not read as remediated when it is not. The\n // recycle of those sessions is runtime-image-recycle's job (#7134, host\n // mode: /proc/<pid>/exe reads \"(deleted)\").\n log(\n `[claude-code-updater] WARNING installed ${installed} under live agents: running sessions keep the previous ${current ?? 'unknown'} binary until they restart. framework_version now reports ${installed}, which does NOT mean the live sessions are patched`,\n );\n }\n return { action: 'updated', from: current, to: installed };\n } catch (err) {\n // Review f540008e5f47: a forced install that THREW (npm non-zero exit,\n // EACCES, registry/network error) is the commonest failed install. Like a\n // failed verify, it must not keep the old deadline.\n if (forcedUnderLiveAgents) belowFloorBusySince = null;\n // Log-and-continue: an updater failure must never break the manager tick.\n log(`[claude-code-updater] update attempt failed: ${err instanceof Error ? err.message : String(err)}`);\n return { action: 'failed', reason: 'exception' };\n }\n}\n","/**\n * ENG-11669 — keep a host's root disk from filling up, and never start a CLI\n * self-update into a full disk.\n *\n * What happened (alyve-host-1, 23 Oct 14:00:40Z): the root disk was full when\n * the manager's self-update ran `npm install -g @integrity-labs/agt-cli@…`.\n * npm removes the old package before writing the new one, the write died with\n * ENOSPC, and `/usr/bin/agt` was simply gone. The manager could not start again,\n * and six agents were dark for ~33h until a human freed disk over SSM. The\n * ENG-6698 smoke check could not help: it runs AFTER the install, and by then\n * the old binary was already deleted.\n *\n * Two defences, both here:\n * 1. `ensureDiskForSelfUpdate` - before any self-update install, make sure the\n * disk has room. If it does not, run the safe cleanup once and re-check. If\n * it still does not, refuse the install (keep the working binary) and say so.\n * 2. `maybeRunDiskGuard` - hourly, and independent of any update: when the disk\n * is low, run the same safe cleanup, so a slow fill (package caches, journal)\n * is reclaimed long before anything ENOSPCs.\n *\n * SAFE CLEANUP is deliberately narrow. Every step reclaims a CACHE or a log that\n * the system rebuilds on demand, and none of them can touch agent work:\n * - `journalctl --vacuum-size=200M` (systemd journal)\n * - `npm cache clean --force` (npm's content cache)\n * - `pnpm store prune` (unreferenced packages only, by design)\n * It never deletes files by path. In particular it never touches\n * `/root/.claude/projects` (agent transcripts, the ONLY copy of the last 7 days\n * on a host that missed its log-retention reconcile) or agent workspaces.\n *\n * Pure decision functions plus injected effects, so every branch is testable\n * without a real disk or a real `sudo`.\n */\n\nimport { readdirSync, readFileSync, statSync, statfsSync } from 'node:fs';\nimport { execFileSync } from 'node:child_process';\n\nconst GIB = 1024 * 1024 * 1024;\n\n/** Free/total bytes on a filesystem, as seen by an unprivileged writer. */\nexport interface DiskStat {\n freeBytes: number;\n totalBytes: number;\n}\n\n/**\n * The disk is LOW below one threshold: 10% of the volume, clamped to\n * [2 GiB, 5 GiB]. Small disks get the 2 GiB floor (on 8 GB, 10% would be too\n * tight to be useful); big disks get the 5 GiB cap, so a 200 GB volume with\n * 15 GiB free is NOT low (Obi Wan 97b2c0d967a7: OR-ing the two bounds made a\n * big, moderately full disk 'low' forever, vacuuming the journal every hour\n * and raising a false STILL LOW, with no ENOSPC risk behind it).\n */\nexport const DISK_LOW_FREE_RATIO = 0.1;\nexport const DISK_LOW_FREE_BYTES = 2 * GIB;\nexport const DISK_LOW_FREE_CAP_BYTES = 5 * GIB;\n\nexport function diskLowThresholdBytes(totalBytes: number): number {\n return Math.min(DISK_LOW_FREE_CAP_BYTES, Math.max(DISK_LOW_FREE_BYTES, totalBytes * DISK_LOW_FREE_RATIO));\n}\n\n/**\n * Minimum free space to START a self-update install. The agt-cli package plus\n * npm's staging is a few hundred MB; 1 GiB leaves room for the install AND for\n * the agents still writing while it runs.\n */\nexport const SELF_UPDATE_MIN_FREE_BYTES = 1 * GIB;\n\n/** The hourly guard's throttle. */\nexport const DISK_GUARD_INTERVAL_MS = 60 * 60 * 1000;\n\n/** Read free/total for `path`. `null` when the stat itself fails (reported, never guessed). */\nexport function readDiskStat(path = '/', statfs: typeof statfsSync = statfsSync): DiskStat | null {\n try {\n const s = statfs(path);\n // bavail: blocks available to a NON-root writer. The manager may run as\n // root, but agents and npm's cache writers often do not, and root's reserved\n // blocks are exactly what lets a \"full\" disk limp on; measuring without them\n // is the honest number.\n const bsize = Number(s.bsize);\n return { freeBytes: Number(s.bavail) * bsize, totalBytes: Number(s.blocks) * bsize };\n } catch {\n return null;\n }\n}\n\nexport type DiskVerdict = 'ok' | 'low' | 'unknown';\n\nexport function diskVerdict(stat: DiskStat | null): DiskVerdict {\n if (!stat || !Number.isFinite(stat.freeBytes) || !Number.isFinite(stat.totalBytes) || stat.totalBytes <= 0) {\n return 'unknown';\n }\n return stat.freeBytes < diskLowThresholdBytes(stat.totalBytes) ? 'low' : 'ok';\n}\n\n/** One cleanup step: a binary and its args. Never a shell string, never a path to delete. */\nexport interface CleanupStep {\n bin: string;\n args: string[];\n /**\n * Needs root (the system journal). The npm cache and pnpm store are the\n * MANAGER USER's own, so those run as that user, never under sudo: under\n * sudo, HOME can become /root and the command would clean root's cache\n * instead of the one that is actually growing (Obi Wan c33094e187b2).\n */\n needsRoot: boolean;\n /**\n * Touches a package-manager cache that a concurrent install reads from.\n * Skipped while any npm/pnpm install is running (Obi Wan b8ed1339eaf2):\n * wiping the cache under `npm install -g @anthropic-ai/claude-code` (the\n * Claude Code updater runs in the same poll) could fail that install after\n * npm already removed the old package, the missing-binary failure again.\n */\n packageCache: boolean;\n}\n\nexport const SAFE_CLEANUP_STEPS: readonly CleanupStep[] = [\n { bin: 'journalctl', args: ['--vacuum-size=200M'], needsRoot: true, packageCache: false },\n { bin: 'npm', args: ['cache', 'clean', '--force'], needsRoot: false, packageCache: true },\n { bin: 'pnpm', args: ['store', 'prune'], needsRoot: false, packageCache: true },\n];\n\n/**\n * Obi Wan 3c3bd567504f: on a NON-root manager the self-update runs\n * `sudo -n npm install -g`, whose cache is root's when sudo resets HOME. So a\n * non-root manager cleans BOTH: its own cache (above, no sudo) and root's\n * (`sudo -n -H`, HOME forced to root's). On a root manager they are the same\n * cache and this step is not run.\n */\nexport const NON_ROOT_EXTRA_STEPS: readonly CleanupStep[] = [\n { bin: 'npm', args: ['cache', 'clean', '--force'], needsRoot: true, packageCache: true },\n];\n\n/** Async on purpose: the guard runs inside the manager's poll loop, and a synchronous\n * `npm cache clean` would freeze heartbeats for as long as it takes. */\nexport type ExecFn = (cmd: string, args: string[], opts: { timeout: number }) => Promise<void>;\n\nexport interface CleanupStepResult {\n step: string;\n outcome: 'ran' | 'missing' | 'failed' | 'skipped';\n detail?: string;\n}\n\n/**\n * Run every safe cleanup step, best-effort and independently: one failing (or\n * not installed, e.g. pnpm on a host that never used it) never stops the rest.\n * Elevates via `sudo -n` when not root, exactly like the npm self-update path.\n */\nexport async function runSafeCleanup(deps: {\n exec: ExecFn;\n isRoot: boolean;\n timeoutMs?: number;\n /**\n * Is an npm/pnpm install running right now? true, or a check that could not\n * answer, SKIPS the package-cache steps (fail safe: a skipped clean costs\n * one hour of cache; a clean under an install can cost the binary).\n */\n packageManagerBusy?: () => Promise<boolean>;\n}): Promise<CleanupStepResult[]> {\n const timeout = deps.timeoutMs ?? CLEANUP_STEP_TIMEOUT_MS;\n const results: CleanupStepResult[] = [];\n const steps = deps.isRoot ? SAFE_CLEANUP_STEPS : [...SAFE_CLEANUP_STEPS, ...NON_ROOT_EXTRA_STEPS];\n for (const step of steps) {\n const label = `${step.needsRoot && !deps.isRoot ? 'sudo ' : ''}${step.bin} ${step.args.join(' ')}`;\n if (step.packageCache) {\n // Re-checked before EVERY package-cache step (Obi Wan d623befb7109): a\n // step can take minutes, and an install started outside this process is\n // not held off by the lock.\n const busy = deps.packageManagerBusy ? await deps.packageManagerBusy().catch(() => true) : false;\n if (busy) {\n results.push({ step: label, outcome: 'skipped', detail: 'an npm/pnpm install is running' });\n continue;\n }\n }\n const [cmd, args] =\n deps.isRoot || !step.needsRoot\n ? [step.bin, step.args]\n : ['sudo', [...(step.bin === 'journalctl' ? ['-n'] : ['-n', '-H']), step.bin, ...step.args]];\n try {\n await deps.exec(cmd, args, { timeout });\n results.push({ step: label, outcome: 'ran' });\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { status?: number; stderr?: Buffer | string };\n const stderr = e.stderr ? String(e.stderr) : '';\n // ENOENT (no such binary) as root, or sudo's \"command not found\" as non-root.\n const missing = e.code === 'ENOENT' || /command not found|not found/i.test(stderr);\n results.push({\n step: label,\n outcome: missing ? 'missing' : 'failed',\n detail: (stderr || e.message || '').trim().slice(0, 200),\n });\n }\n }\n return results;\n}\n\nfunction mb(bytes: number): string {\n return `${Math.round(bytes / (1024 * 1024))}MB`;\n}\n\nfunction summarize(results: CleanupStepResult[]): string {\n return results.map((r) => `${r.step}=${r.outcome}`).join(', ');\n}\n\nexport interface DiskGuardDeps {\n readStat: () => DiskStat | null;\n cleanup: () => Promise<CleanupStepResult[]>;\n log: (line: string) => void;\n}\n\nexport type SelfUpdateRoom =\n /**\n * `release` MUST be called once the install has finished (or failed): the\n * self-update holds the package-cache lock from its checks through its install,\n * so nothing can start a cleanup or another npm install in between\n * (Obi Wan a2c6f266bf18).\n */\n | { ok: true; freeBytes: number | null; cleaned: boolean; release: () => void }\n | { ok: false; freeBytes: number; cleaned: true }\n /** ENG-11669 (Obi Wan 84656ed7dfce): held because the package cache stayed busy; disk not measured. */\n | { ok: false; freeBytes: null; cleaned: false; reason: 'package_cache_busy' };\n\n/**\n * ENG-11669: is there room to START a self-update install? Called immediately\n * before the install, the only point at which the answer is current.\n *\n * - Enough room: go.\n * - Not enough: run the safe cleanup once, re-measure, go if it worked.\n * - Still not enough: REFUSE. The caller keeps the working binary and retries\n * on a later cycle; a refused update costs a version, an ENOSPC mid-install\n * costs the host.\n * - Unreadable stat: go, as before this guard existed. Failing closed on an\n * unreadable statfs would freeze every update on a host with an odd mount,\n * which is a worse outcome than the one this guards against; it is logged.\n */\n/**\n * ENG-11669 (Obi Wan 7c1384a483ca): hold-episode state. While a host is held,\n * the self-update check still runs every few minutes; without this every one of\n * them would re-run a cleanup that already failed and re-log the HELD line,\n * roughly 288 times a day into a disk that is already full. Same shape as the\n * ENG-10724 device-state hold's latch: one HELD line per episode, cleanup\n * retried at most once per DISK_GUARD_INTERVAL_MS, both reset the moment there\n * is room again.\n */\nlet holdEpisode: { lastCleanupMs: number } | null = null;\nlet busyEpisode: { since: number; loggedAt: number } | null = null;\nexport function resetSelfUpdateHoldEpisode(): void {\n holdEpisode = null;\n busyEpisode = null;\n forcedHoldAnnounced = false;\n forcedBusyAnnounced = false;\n}\n\n/**\n * Obi Wan 39885fe919e4: a held forced \"Update CLI now\" is re-kicked every\n * update check (~5 min) until it succeeds, each time with force=true, so the\n * forced HELD line is latched like the device-state hold's: said once per\n * forced REQUEST, re-armed by the caller when a new request arrives\n * (rearmForcedDiskHoldAnnounce, wired next to selfUpdateForcedHoldAnnounced).\n */\nlet forcedHoldAnnounced = false;\n/** Obi Wan 253abefc6771: a forced request held on a BUSY package cache is told why, once per request. */\nlet forcedBusyAnnounced = false;\nexport function rearmForcedDiskHoldAnnounce(): void {\n forcedHoldAnnounced = false;\n forcedBusyAnnounced = false;\n}\n\n/**\n * One lock around everything that reads or wipes the package caches: the safe\n * cleanup, and package installs (the Claude Code updater's `npm install -g`).\n * Obi Wan 7a21830b25c1 / d623befb7109: the hourly guard runs fire-and-forget\n * and an async install can start while it runs, so a single up-front \"is an\n * install running?\" check is not enough. Holders run strictly one at a time,\n * in arrival order. The agt self-update check waits for the lock to go idle\n * before it measures and returns; its install is synchronous (execFileSync\n * blocks the event loop), so nothing can start a cleanup under it.\n */\nlet cacheLockTail: Promise<void> = Promise.resolve();\nlet cacheLockHolders = 0;\n\n/**\n * Obi Wan 84656ed7dfce: no holder may own the lock forever. A stalled\n * `npm install -g` (a registry hang) must not park the agt self-update, the\n * host's remediation channel, behind it indefinitely. After this long the lock\n * is RELEASED (logged) even if the holder is still running; the holder itself\n * is not killed, and anything queued may then proceed. Both remaining sides\n * re-check with pgrep: the cleanup before each package-cache step, and the agt\n * self-update before it starts its install (Obi Wan 59205b9de2aa).\n */\nexport const PACKAGE_CACHE_LOCK_MAX_HOLD_MS = 10 * 60 * 1000;\n/** How long the agt self-update check waits for the lock before holding instead. */\n// Obi Wan 5e64cc74a59a / 128538f49206: the self-update's bounded wait to\n// ACQUIRE the lock. See SELF_UPDATE_LOCK_MAX_HOLD_MS for the full budget.\nexport const PACKAGE_CACHE_IDLE_MAX_WAIT_MS = 5 * 60 * 1000;\n/** Per-step timeout for the safe cleanup. */\nexport const CLEANUP_STEP_TIMEOUT_MS = 120_000;\n/**\n * The self-update holds the lock across measure -> cleanup -> install. Worst\n * case inside the hold: cleanup of 4 steps x 120s (non-root) = 8 min, plus the\n * install (npm 180s timeout) = 11 min, so a 12 min max hold.\n *\n * Against the 20 min self-update latch (Obi Wan 128538f49206), which is taken\n * BEFORE the pre-install network checks: brew update + outdated (60s + 30s)\n * = 1.5 min, then the 5 min acquire bound, then the 12 min hold, then the 15s\n * smoke check = 18.75 min worst case. Inside 20.\n */\nexport const SELF_UPDATE_LOCK_MAX_HOLD_MS = 12 * 60 * 1000;\n/**\n * The self-update's own command timeouts, shared with manager-worker.ts so the\n * latch budget test reads the values that actually run (Obi Wan 6909f9596353).\n */\nexport const SELF_UPDATE_TIMEOUTS = {\n brewUpdateMs: 60_000,\n brewOutdatedMs: 30_000,\n brewUpgradeMs: 120_000,\n npmInstallMs: 180_000,\n smokeCheckMs: 15_000,\n} as const;\n/**\n * Obi Wan 51f6036d7808: the longest the self-update defers to \"another install\n * is running\". Past it the install goes ahead (logged): a looping, hung or\n * hostile install must not hold the host's remediation channel forever. Ours is\n * still serialised by the package-cache lock; this only stops deferring to\n * processes outside it.\n */\nexport const SELF_UPDATE_BUSY_CEILING_MS = 2 * 60 * 60 * 1000;\n/** Busy holds are logged once, then re-said (as a warning) at most this often. */\nexport const BUSY_HOLD_RELOG_MS = 60 * 60 * 1000;\n\nconst INSTALL_VERBS = new Set(['install', 'i', 'ci', 'add', 'update', 'upgrade', 'up']);\nconst NOT_AN_INSTALL = new Set(['run', 'run-script', 'exec', 'x', 'dlx', 'test', 'start']);\nconst PM_BINS = /^(npm|pnpm)$/;\nconst PM_SCRIPTS = /^(npm|npm-cli\\.js|pnpm|pnpm\\.c?js)$/;\nconst basenameOf = (p: string) => p.slice(p.lastIndexOf('/') + 1);\n\n/**\n * Obi Wan 106f4019f3b9: is THIS process a package-manager install? Only when\n * the process itself is npm/pnpm (argv[0], or node running the npm/pnpm\n * script) and its first subcommand is an install verb. A shell whose command\n * line merely mentions one (`bash -c \"npm install && npm run dev\"`, alive for\n * the life of the dev server) is the shell, not an install, and does not count;\n * its npm child does, for exactly as long as that child runs.\n */\nexport function isPackageInstallArgv(argv: readonly string[]): boolean {\n let args = argv.filter((a) => a.length > 0);\n // npm overwrites its process title (\"npm install\"), which reads back as one\n // space-joined argv entry.\n if (args.length === 1 && args[0]!.includes(' ')) args = args[0]!.split(/\\s+/);\n if (args.length === 0) return false;\n let rest: string[];\n const bin = basenameOf(args[0]!);\n if (PM_BINS.test(bin)) rest = args.slice(1);\n else if (/^node(js)?$/.test(bin) && args[1] && PM_SCRIPTS.test(basenameOf(args[1]))) rest = args.slice(2);\n else return false;\n const sub = rest.find((a) => !a.startsWith('-'));\n if (!sub || NOT_AN_INSTALL.has(sub)) return false;\n if (INSTALL_VERBS.has(sub)) return true;\n // A flag value before the subcommand (`npm --prefix /x install`): look one further.\n const next = rest.slice(rest.indexOf(sub) + 1).find((a) => !a.startsWith('-'));\n return next !== undefined && INSTALL_VERBS.has(next);\n}\n\n/**\n * Host-wide: is any npm/pnpm install running that could collide with ours?\n *\n * - Linux: read /proc. A process whose ROOT FILESYSTEM is not ours (an agent's\n * container) is skipped: its npm cache and global prefix live on a different\n * filesystem, so it cannot race our cleanup or our `npm install -g` (Obi Wan\n * e43cffc301c5 / 51f6036d7808). Identity is the dev:ino of /proc/<pid>/root,\n * NOT the mount namespace (Obi Wan ee7fd03f9843): a systemd unit with\n * PrivateTmp/ProtectSystem, a snap or an SSM session can sit in its own\n * mount namespace while sharing the host's root, and those still count. A\n * root we cannot stat is counted (fail safe); a pid that vanished mid-scan is\n * skipped. Statting another uid's /proc/<pid>/root needs ptrace access: the\n * fleet's manager runs as root (agt-manager.service, User=root by default),\n * so it can. A non-root manager would count other uids' container installs\n * as busy, which costs at most SELF_UPDATE_BUSY_CEILING_MS of deferral (Obi\n * Wan 7e45e4c1a576).\n * - No /proc (macOS, Obi Wan eff7b7bd11f3): fall back to `ps -A -o args=`, the\n * space-joined argv of every process. Install argv has no spaces inside its\n * tokens in practice, so splitting on whitespace is good enough here.\n * - Neither readable: throws, which the callers read as busy. That is bounded:\n * the self-update stops deferring after SELF_UPDATE_BUSY_CEILING_MS.\n */\nexport function anyPackageInstallRunning(\n deps: {\n listPids?: () => string[];\n readCmdline?: (pid: string) => string | null;\n /** dev:ino of the process's root directory; null when it cannot be read. */\n readRootId?: (pid: string) => string | null;\n listProcessArgs?: () => string[];\n selfPid?: number;\n } = {},\n): boolean {\n let pids: string[] | null;\n try {\n pids = (deps.listPids ?? (() => readdirSync('/proc').filter((d) => /^\\d+$/.test(d))))();\n } catch (err) {\n if ((err as { code?: string }).code !== 'ENOENT') throw err;\n pids = null; // no /proc at all\n }\n if (pids === null) {\n const lines = (deps.listProcessArgs ?? defaultPsArgs)();\n return lines.some((line) => isPackageInstallArgv(line.trim().split(/\\s+/)));\n }\n const readCmdline =\n deps.readCmdline ??\n ((pid: string) => {\n try {\n return readFileSync(`/proc/${pid}/cmdline`, 'utf8');\n } catch {\n return null;\n }\n });\n const readRootId =\n deps.readRootId ??\n ((pid: string) => {\n try {\n const st = statSync(`/proc/${pid}/root/`);\n return `${st.dev}:${st.ino}`;\n } catch {\n return null;\n }\n });\n const self = String(deps.selfPid ?? process.pid);\n const selfRoot = readRootId('self');\n for (const pid of pids) {\n if (pid === self) continue;\n const raw = readCmdline(pid);\n if (!raw || !isPackageInstallArgv(raw.split('\\0'))) continue;\n const root = readRootId(pid);\n if (selfRoot && root && root !== selfRoot) continue; // another container's root filesystem\n return true;\n }\n return false;\n}\n\nfunction defaultPsArgs(): string[] {\n return execFileSync('ps', ['-A', '-o', 'args='], { encoding: 'utf8', timeout: 5_000 }).split('\\n').filter(Boolean);\n}\n\n/**\n * Acquire the package-cache lock, waiting at most `maxWaitMs` (default: no\n * limit). Returns a release function, or null when the wait bound is hit. A\n * waiter that gives up keeps its queue slot but releases it the instant it is\n * reached, so it never blocks anyone behind it. Every acquisition is\n * force-released after `maxHoldMs` (logged) so no holder owns it forever.\n */\nexport async function acquirePackageCacheLock(\n opts: { maxWaitMs?: number; maxHoldMs?: number; label?: string; log?: (line: string) => void } = {},\n): Promise<(() => void) | null> {\n const prev = cacheLockTail;\n let releaseTail!: () => void;\n cacheLockTail = new Promise<void>((r) => (releaseTail = r));\n\n let abandoned = false;\n let waitTimer: ReturnType<typeof setTimeout> | undefined;\n const acquired = await new Promise<boolean>((resolve) => {\n void prev.then(() => {\n if (abandoned) {\n releaseTail(); // reached our slot after giving up: pass it straight on\n return;\n }\n if (waitTimer) clearTimeout(waitTimer);\n resolve(true);\n });\n if (opts.maxWaitMs !== undefined) {\n waitTimer = setTimeout(() => {\n abandoned = true;\n resolve(false);\n }, opts.maxWaitMs);\n (waitTimer as { unref?: () => void }).unref?.();\n }\n });\n if (!acquired) return null;\n\n cacheLockHolders += 1;\n let released = false;\n const maxHold = opts.maxHoldMs ?? PACKAGE_CACHE_LOCK_MAX_HOLD_MS;\n const releaseOnce = () => {\n if (released) return;\n released = true;\n clearTimeout(holdTimer);\n cacheLockHolders -= 1;\n releaseTail();\n };\n const holdTimer = setTimeout(() => {\n (opts.log ?? console.warn)(\n `[disk-guard] package-cache lock held ${Math.round(maxHold / 60_000)}m by ${opts.label ?? 'a holder'} that is still running; ` +\n 'releasing it so a stalled install cannot block the self-update or cleanup forever [ENG-11669]',\n );\n releaseOnce();\n }, maxHold);\n (holdTimer as { unref?: () => void }).unref?.();\n return releaseOnce;\n}\n\nexport async function withPackageCacheLock<T>(\n fn: () => Promise<T>,\n opts: { maxHoldMs?: number; label?: string; log?: (line: string) => void } = {},\n): Promise<T> {\n const release = (await acquirePackageCacheLock(opts))!;\n try {\n return await fn();\n } finally {\n release();\n }\n}\nasync function exclusiveCleanup(cleanup: () => Promise<CleanupStepResult[]>): Promise<CleanupStepResult[]> {\n return withPackageCacheLock(cleanup, { label: 'the disk-guard cleanup' });\n}\n/** Test seam: drop all lock state (a test that leaks a holder must not wedge the next). */\nexport function resetPackageCacheLock(): void {\n cacheLockTail = Promise.resolve();\n cacheLockHolders = 0;\n}\n/**\n * Is anything holding the package-cache lock? A cleanup, a toolkit/updater\n * install, or the agt self-update between its disk check and its install.\n */\nexport function cleanupIsRunning(): boolean {\n return cacheLockHolders > 0;\n}\n\n/**\n * ENG-11669: is there room to START a self-update install? Called immediately\n * before the install, the only point at which the answer is current.\n *\n * - Enough room: go.\n * - Not enough: run the safe cleanup once, re-measure, go if it worked.\n * - Still not enough: REFUSE. The caller keeps the working binary and retries\n * on a later cycle; a refused update costs a version, an ENOSPC mid-install\n * costs the host. Within a hold episode the refusal is quiet and the cleanup\n * is not re-run more than hourly.\n * - Unreadable stat: go, as before this guard existed. Failing closed on an\n * unreadable statfs would freeze every update on a host with an odd mount,\n * which is a worse outcome than the one this guards against; it is logged.\n */\nexport async function ensureDiskForSelfUpdate(\n deps: DiskGuardDeps & {\n nowMs?: number;\n force?: boolean;\n idleWaitMs?: number;\n /**\n * Obi Wan 59205b9de2aa: any npm/pnpm install running on the host, whether or\n * not it took our lock (a force-released stalled one, or someone else's).\n * true, or a check that cannot answer, HOLDS the self-update.\n */\n packageManagerBusy?: () => Promise<boolean>;\n },\n): Promise<SelfUpdateRoom> {\n const busyHold = (why: 'lock' | 'pgrep'): SelfUpdateRoom => {\n // Obi Wan 106f4019f3b9: latched like the disk HELD line. Said once when the\n // busy episode starts, then at most hourly, as an escalation naming how long.\n const now = deps.nowMs ?? Date.now();\n let first = false;\n if (busyEpisode === null) {\n busyEpisode = { since: now, loggedAt: now };\n first = true;\n }\n // A forced \"Update CLI now\" has a human waiting on the answer: say why\n // once per request even inside a latched episode, like the disk hold does.\n const announceForced = deps.force === true && !forcedBusyAnnounced;\n if (announceForced) forcedBusyAnnounced = true;\n if (first || announceForced || now - busyEpisode.loggedAt >= BUSY_HOLD_RELOG_MS) {\n const heldMin = Math.round((now - busyEpisode.since) / 60_000);\n busyEpisode.loggedAt = now;\n deps.log(\n `[disk-guard] HELD${deps.force ? ' (forced \"Update CLI now\")' : ''} self-update: ` +\n (why === 'lock'\n ? 'a package install or cache cleanup has held the package cache for too long; '\n : 'another npm/pnpm install is running on this host; ') +\n 'not starting `npm install -g` alongside it. Retrying on a later check' +\n (first\n ? ' (further busy holds this episode are logged hourly)'\n : `. STILL held after ${heldMin}m: if no install should be running, find and stop the stuck npm/pnpm process`) +\n ' [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: null, cleaned: false, reason: 'package_cache_busy' };\n };\n const isBusyRaw = async () => (deps.packageManagerBusy ? await deps.packageManagerBusy().catch(() => true) : false);\n // Busy, unless we have already deferred for longer than the ceiling.\n const isBusy = async () => {\n if (!(await isBusyRaw())) {\n // Obi Wan 417034a6c12b: the ceiling bounds CONTINUOUS deferral, so an idle\n // reading ends the busy episode even when the update then holds on disk.\n busyEpisode = null;\n return false;\n }\n const now = deps.nowMs ?? Date.now();\n if (busyEpisode && now - busyEpisode.since >= SELF_UPDATE_BUSY_CEILING_MS) {\n deps.log(\n `[disk-guard] self-update has deferred to another npm/pnpm install for ${Math.round((now - busyEpisode.since) / 60_000)}m, ` +\n 'past the ceiling; proceeding anyway so a stuck or looping install cannot block updates forever [ENG-11669]',\n );\n return false;\n }\n return true;\n };\n\n // Obi Wan a2c6f266bf18: take the package-cache lock (bounded) and KEEP it from\n // these checks through the install. Nothing queued behind it, our own cleanup\n // included, can start between \"there is room and nothing else is installing\"\n // and the install itself. The caller releases it after the install.\n const release = await acquirePackageCacheLock({\n maxWaitMs: deps.idleWaitMs ?? PACKAGE_CACHE_IDLE_MAX_WAIT_MS,\n maxHoldMs: SELF_UPDATE_LOCK_MAX_HOLD_MS,\n label: 'the agt self-update',\n log: deps.log,\n });\n if (!release) return busyHold('lock');\n try {\n if (await isBusy()) {\n release();\n return busyHold('pgrep');\n }\n const room = await ensureDiskForSelfUpdateLocked(deps);\n if (!room.ok) {\n release();\n return room;\n }\n // Re-checked last: an install outside the lock may have started meanwhile.\n if (await isBusy()) {\n release();\n return busyHold('pgrep');\n }\n busyEpisode = null;\n return { ...room, release };\n } catch (err) {\n release();\n throw err;\n }\n}\n\n/** The disk decision, run while the caller holds the package-cache lock. */\nasync function ensureDiskForSelfUpdateLocked(\n deps: DiskGuardDeps & { nowMs?: number; force?: boolean },\n): Promise<{ ok: true; freeBytes: number | null; cleaned: boolean } | Exclude<SelfUpdateRoom, { ok: true }>> {\n const nowMs = deps.nowMs ?? Date.now();\n const before = deps.readStat();\n if (!before) {\n deps.log('[disk-guard] could not read free space on the install filesystem; proceeding with the self-update unchecked [ENG-11669]');\n return { ok: true, freeBytes: null, cleaned: false };\n }\n if (before.freeBytes >= SELF_UPDATE_MIN_FREE_BYTES) {\n if (holdEpisode) deps.log(`[disk-guard] self-update hold cleared: ${mb(before.freeBytes)} free [ENG-11669]`);\n holdEpisode = null;\n forcedHoldAnnounced = false;\n return { ok: true, freeBytes: before.freeBytes, cleaned: false };\n }\n\n // Already held this episode and cleaned recently: refuse quietly. Cleaning\n // again would just repeat a cleanup that already did not make room.\n if (holdEpisode && nowMs - holdEpisode.lastCleanupMs < DISK_GUARD_INTERVAL_MS) {\n // A forced \"Update CLI now\" is a human waiting on an answer, and human-paced,\n // so it is always said (Obi Wan b07e89e3f0a2; the device-state hold's\n // forced announce is the precedent). The scheduled checks stay quiet.\n if (deps.force && !forcedHoldAnnounced) {\n forcedHoldAnnounced = true;\n deps.log(\n `[disk-guard] HELD (forced \"Update CLI now\"): only ${mb(before.freeBytes)} free for the install ` +\n `(need ${mb(SELF_UPDATE_MIN_FREE_BYTES)}); cleanup already ran this hour and did not make room. ` +\n 'Free disk on this host; the request stays pending and applies once there is room ' +\n '(said once per request) [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: before.freeBytes, cleaned: true };\n }\n\n const firstHold = holdEpisode === null;\n if (firstHold) {\n deps.log(\n `[disk-guard] only ${mb(before.freeBytes)} free for the install (need ${mb(SELF_UPDATE_MIN_FREE_BYTES)} to self-update); ` +\n 'running safe cleanup first [ENG-11669]',\n );\n }\n // We already hold the lock: call the cleanup directly (exclusiveCleanup would\n // queue behind ourselves).\n const results = await deps.cleanup();\n const after = deps.readStat();\n const freeAfter = after?.freeBytes ?? before.freeBytes;\n if (freeAfter >= SELF_UPDATE_MIN_FREE_BYTES) {\n deps.log(`[disk-guard] cleanup freed space: ${mb(before.freeBytes)} -> ${mb(freeAfter)} (${summarize(results)}) [ENG-11669]`);\n holdEpisode = null;\n return { ok: true, freeBytes: freeAfter, cleaned: true };\n }\n holdEpisode = { lastCleanupMs: nowMs };\n if (!firstHold && deps.force && !forcedHoldAnnounced) {\n forcedHoldAnnounced = true;\n // Obi Wan e3f6b7a31ff4: a forced update that lands on the hourly retry is\n // refused here too, and must say so like the early-return branch does.\n deps.log(\n `[disk-guard] HELD (forced \"Update CLI now\"): still only ${mb(freeAfter)} free after cleanup ` +\n `(need ${mb(SELF_UPDATE_MIN_FREE_BYTES)}; ${summarize(results)}). Free disk on this host, then retry [ENG-11669]`,\n );\n }\n if (firstHold) {\n deps.log(\n `[disk-guard] HELD self-update: still only ${mb(freeAfter)} free after cleanup (${summarize(results)}). ` +\n 'Keeping the current agt binary rather than risk an install that deletes it and then fails with ENOSPC. ' +\n 'Free disk on this host (or grow the volume) and the update applies on a later check. ' +\n '(further holds this episode are not logged) [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: freeAfter, cleaned: true };\n}\n\n/**\n * ENG-11669 (Obi Wan 16f6c25922ae): the stat that bounds an install is the\n * TIGHTER of `/` and the filesystem the install writes to (the npm global\n * prefix or the brew Cellar). On EC2 hosts both are `/`; a host with the prefix\n * on its own mount must not be judged by the wrong disk. Unreadable paths are\n * skipped; all unreadable is null (caller proceeds, logged).\n */\nexport function tightestDiskStat(paths: string[], read: (p: string) => DiskStat | null = readDiskStat): DiskStat | null {\n let best: DiskStat | null = null;\n for (const p of [...new Set(paths)]) {\n const s = read(p);\n if (s && (best === null || s.freeBytes < best.freeBytes)) best = s;\n }\n return best;\n}\n\n/** Module state for the hourly guard. Exported reset for tests. */\nlet lastGuardRunMs: number | null = null;\nexport function resetDiskGuardThrottle(): void {\n lastGuardRunMs = null;\n guardBlindLogged = false;\n}\n\nexport type DiskGuardRun =\n | { ran: false; reason: 'throttled' | 'ok' | 'unknown' }\n | { ran: true; beforeBytes: number; afterBytes: number | null; stillLow: boolean };\n\n/**\n * ENG-11669: the hourly guard. Cheap when healthy (one statfs per hour); only\n * shells out when the disk is actually low. Logs once per run that cleans.\n */\nlet guardBlindLogged = false;\n\nexport async function maybeRunDiskGuard(deps: DiskGuardDeps & { nowMs: number }): Promise<DiskGuardRun> {\n if (lastGuardRunMs !== null && deps.nowMs - lastGuardRunMs < DISK_GUARD_INTERVAL_MS) {\n return { ran: false, reason: 'throttled' };\n }\n lastGuardRunMs = deps.nowMs;\n const before = deps.readStat();\n const verdict = diskVerdict(before);\n if (verdict === 'unknown' || !before) {\n // Obi Wan 21988ab455e0: a blind guard must say so, once per blind spell, or\n // \"healthy\" and \"cannot see\" read the same in manager.log.\n if (!guardBlindLogged) {\n guardBlindLogged = true;\n deps.log(\n '[disk-guard] could not read free space on /; the hourly disk guard is blind (no cleanup, no host_disk_low) ' +\n 'until it can. Said once until the reading comes back [ENG-11669]',\n );\n }\n return { ran: false, reason: 'unknown' };\n }\n if (guardBlindLogged) {\n guardBlindLogged = false;\n deps.log('[disk-guard] free space on / is readable again; the hourly disk guard is back [ENG-11669]');\n }\n if (verdict !== 'low') return { ran: false, reason: 'ok' };\n\n const results = await exclusiveCleanup(deps.cleanup);\n const after = deps.readStat();\n const stillLow = diskVerdict(after) === 'low';\n deps.log(\n `[disk-guard] / was low (${mb(before.freeBytes)} free of ${mb(before.totalBytes)}); safe cleanup ran ` +\n `(${summarize(results)}); now ${after ? mb(after.freeBytes) : 'unknown'} free.` +\n (stillLow\n ? ' STILL LOW: something other than caches is filling this disk. Investigate before it fills: ' +\n '`du -xh --max-depth=2 / | sort -rh | head`. Never delete /root/.claude/projects (agent transcripts). [ENG-11669]'\n : ' [ENG-11669]'),\n );\n return { ran: true, beforeBytes: before.freeBytes, afterBytes: after?.freeBytes ?? null, stillLow };\n}\n"],"mappings":";;;;;;;;;;;;AAgCA,SAAS,UAAU,gBAAAA,qBAAoB;AACvC,SAAS,YAAY,gBAAAC,eAAc,oBAAoB;AACvD,SAAS,eAAe;AACxB,SAAS,iBAAiB;;;ACF1B,SAAS,aAAa,cAAc,UAAU,kBAAkB;AAChE,SAAS,oBAAoB;AAE7B,IAAM,MAAM,OAAO,OAAO;AAgBnB,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB,IAAI;AAChC,IAAM,0BAA0B,IAAI;AAEpC,SAAS,sBAAsB,YAA4B;AAChE,SAAO,KAAK,IAAI,yBAAyB,KAAK,IAAI,qBAAqB,aAAa,mBAAmB,CAAC;AAC1G;AAOO,IAAM,6BAA6B,IAAI;AAGvC,IAAM,yBAAyB,KAAK,KAAK;AAGzC,SAAS,aAAa,OAAO,KAAK,SAA4B,YAA6B;AAChG,MAAI;AACF,UAAM,IAAI,OAAO,IAAI;AAKrB,UAAM,QAAQ,OAAO,EAAE,KAAK;AAC5B,WAAO,EAAE,WAAW,OAAO,EAAE,MAAM,IAAI,OAAO,YAAY,OAAO,EAAE,MAAM,IAAI,MAAM;AAAA,EACrF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,SAAS,YAAY,MAAoC;AAC9D,MAAI,CAAC,QAAQ,CAAC,OAAO,SAAS,KAAK,SAAS,KAAK,CAAC,OAAO,SAAS,KAAK,UAAU,KAAK,KAAK,cAAc,GAAG;AAC1G,WAAO;AAAA,EACT;AACA,SAAO,KAAK,YAAY,sBAAsB,KAAK,UAAU,IAAI,QAAQ;AAC3E;AAuBO,IAAM,qBAA6C;AAAA,EACxD,EAAE,KAAK,cAAc,MAAM,CAAC,oBAAoB,GAAG,WAAW,MAAM,cAAc,MAAM;AAAA,EACxF,EAAE,KAAK,OAAO,MAAM,CAAC,SAAS,SAAS,SAAS,GAAG,WAAW,OAAO,cAAc,KAAK;AAAA,EACxF,EAAE,KAAK,QAAQ,MAAM,CAAC,SAAS,OAAO,GAAG,WAAW,OAAO,cAAc,KAAK;AAChF;AASO,IAAM,uBAA+C;AAAA,EAC1D,EAAE,KAAK,OAAO,MAAM,CAAC,SAAS,SAAS,SAAS,GAAG,WAAW,MAAM,cAAc,KAAK;AACzF;AAiBA,eAAsB,eAAe,MAUJ;AAC/B,QAAM,UAAU,KAAK,aAAa;AAClC,QAAM,UAA+B,CAAC;AACtC,QAAM,QAAQ,KAAK,SAAS,qBAAqB,CAAC,GAAG,oBAAoB,GAAG,oBAAoB;AAChG,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,GAAG,KAAK,aAAa,CAAC,KAAK,SAAS,UAAU,EAAE,GAAG,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,GAAG,CAAC;AAChG,QAAI,KAAK,cAAc;AAIrB,YAAM,OAAO,KAAK,qBAAqB,MAAM,KAAK,mBAAmB,EAAE,MAAM,MAAM,IAAI,IAAI;AAC3F,UAAI,MAAM;AACR,gBAAQ,KAAK,EAAE,MAAM,OAAO,SAAS,WAAW,QAAQ,iCAAiC,CAAC;AAC1F;AAAA,MACF;AAAA,IACF;AACA,UAAM,CAAC,KAAK,IAAI,IACd,KAAK,UAAU,CAAC,KAAK,YACjB,CAAC,KAAK,KAAK,KAAK,IAAI,IACpB,CAAC,QAAQ,CAAC,GAAI,KAAK,QAAQ,eAAe,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,GAAI,KAAK,KAAK,GAAG,KAAK,IAAI,CAAC;AAC/F,QAAI;AACF,YAAM,KAAK,KAAK,KAAK,MAAM,EAAE,QAAQ,CAAC;AACtC,cAAQ,KAAK,EAAE,MAAM,OAAO,SAAS,MAAM,CAAC;AAAA,IAC9C,SAAS,KAAK;AACZ,YAAM,IAAI;AACV,YAAM,SAAS,EAAE,SAAS,OAAO,EAAE,MAAM,IAAI;AAE7C,YAAM,UAAU,EAAE,SAAS,YAAY,+BAA+B,KAAK,MAAM;AACjF,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,SAAS,UAAU,YAAY;AAAA,QAC/B,SAAS,UAAU,EAAE,WAAW,IAAI,KAAK,EAAE,MAAM,GAAG,GAAG;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,GAAG,OAAuB;AACjC,SAAO,GAAG,KAAK,MAAM,SAAS,OAAO,KAAK,CAAC;AAC7C;AAEA,SAAS,UAAU,SAAsC;AACvD,SAAO,QAAQ,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAC/D;AA0CA,IAAI,cAAgD;AACpD,IAAI,cAA0D;AAe9D,IAAI,sBAAsB;AAE1B,IAAI,sBAAsB;AACnB,SAAS,8BAAoC;AAClD,wBAAsB;AACtB,wBAAsB;AACxB;AAYA,IAAI,gBAA+B,QAAQ,QAAQ;AACnD,IAAI,mBAAmB;AAWhB,IAAM,iCAAiC,KAAK,KAAK;AAIjD,IAAM,iCAAiC,IAAI,KAAK;AAEhD,IAAM,0BAA0B;AAWhC,IAAM,+BAA+B,KAAK,KAAK;AAK/C,IAAM,uBAAuB;AAAA,EAClC,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAChB;AAQO,IAAM,8BAA8B,IAAI,KAAK,KAAK;AAElD,IAAM,qBAAqB,KAAK,KAAK;AAE5C,IAAM,gBAAgB,oBAAI,IAAI,CAAC,WAAW,KAAK,MAAM,OAAO,UAAU,WAAW,IAAI,CAAC;AACtF,IAAM,iBAAiB,oBAAI,IAAI,CAAC,OAAO,cAAc,QAAQ,KAAK,OAAO,QAAQ,OAAO,CAAC;AACzF,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,aAAa,CAAC,MAAc,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;AAUzD,SAAS,qBAAqB,MAAkC;AACrE,MAAI,OAAO,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAG1C,MAAI,KAAK,WAAW,KAAK,KAAK,CAAC,EAAG,SAAS,GAAG,EAAG,QAAO,KAAK,CAAC,EAAG,MAAM,KAAK;AAC5E,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,MAAI;AACJ,QAAM,MAAM,WAAW,KAAK,CAAC,CAAE;AAC/B,MAAI,QAAQ,KAAK,GAAG,EAAG,QAAO,KAAK,MAAM,CAAC;AAAA,WACjC,cAAc,KAAK,GAAG,KAAK,KAAK,CAAC,KAAK,WAAW,KAAK,WAAW,KAAK,CAAC,CAAC,CAAC,EAAG,QAAO,KAAK,MAAM,CAAC;AAAA,MACnG,QAAO;AACZ,QAAM,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAC/C,MAAI,CAAC,OAAO,eAAe,IAAI,GAAG,EAAG,QAAO;AAC5C,MAAI,cAAc,IAAI,GAAG,EAAG,QAAO;AAEnC,QAAM,OAAO,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAC7E,SAAO,SAAS,UAAa,cAAc,IAAI,IAAI;AACrD;AAwBO,SAAS,yBACd,OAOI,CAAC,GACI;AACT,MAAI;AACJ,MAAI;AACF,YAAQ,KAAK,aAAa,MAAM,YAAY,OAAO,EAAE,OAAO,CAAC,MAAM,QAAQ,KAAK,CAAC,CAAC,IAAI;AAAA,EACxF,SAAS,KAAK;AACZ,QAAK,IAA0B,SAAS,SAAU,OAAM;AACxD,WAAO;AAAA,EACT;AACA,MAAI,SAAS,MAAM;AACjB,UAAM,SAAS,KAAK,mBAAmB,eAAe;AACtD,WAAO,MAAM,KAAK,CAAC,SAAS,qBAAqB,KAAK,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;AAAA,EAC5E;AACA,QAAM,cACJ,KAAK,gBACJ,CAAC,QAAgB;AAChB,QAAI;AACF,aAAO,aAAa,SAAS,GAAG,YAAY,MAAM;AAAA,IACpD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF,QAAM,aACJ,KAAK,eACJ,CAAC,QAAgB;AAChB,QAAI;AACF,YAAM,KAAK,SAAS,SAAS,GAAG,QAAQ;AACxC,aAAO,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF,QAAM,OAAO,OAAO,KAAK,WAAW,QAAQ,GAAG;AAC/C,QAAM,WAAW,WAAW,MAAM;AAClC,aAAW,OAAO,MAAM;AACtB,QAAI,QAAQ,KAAM;AAClB,UAAM,MAAM,YAAY,GAAG;AAC3B,QAAI,CAAC,OAAO,CAAC,qBAAqB,IAAI,MAAM,IAAI,CAAC,EAAG;AACpD,UAAM,OAAO,WAAW,GAAG;AAC3B,QAAI,YAAY,QAAQ,SAAS,SAAU;AAC3C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,gBAA0B;AACjC,SAAO,aAAa,MAAM,CAAC,MAAM,MAAM,OAAO,GAAG,EAAE,UAAU,QAAQ,SAAS,IAAM,CAAC,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACnH;AASA,eAAsB,wBACpB,OAAiG,CAAC,GACpE;AAC9B,QAAM,OAAO;AACb,MAAI;AACJ,kBAAgB,IAAI,QAAc,CAAC,MAAO,cAAc,CAAE;AAE1D,MAAI,YAAY;AAChB,MAAI;AACJ,QAAM,WAAW,MAAM,IAAI,QAAiB,CAAC,YAAY;AACvD,SAAK,KAAK,KAAK,MAAM;AACnB,UAAI,WAAW;AACb,oBAAY;AACZ;AAAA,MACF;AACA,UAAI,UAAW,cAAa,SAAS;AACrC,cAAQ,IAAI;AAAA,IACd,CAAC;AACD,QAAI,KAAK,cAAc,QAAW;AAChC,kBAAY,WAAW,MAAM;AAC3B,oBAAY;AACZ,gBAAQ,KAAK;AAAA,MACf,GAAG,KAAK,SAAS;AACjB,MAAC,UAAqC,QAAQ;AAAA,IAChD;AAAA,EACF,CAAC;AACD,MAAI,CAAC,SAAU,QAAO;AAEtB,sBAAoB;AACpB,MAAI,WAAW;AACf,QAAM,UAAU,KAAK,aAAa;AAClC,QAAM,cAAc,MAAM;AACxB,QAAI,SAAU;AACd,eAAW;AACX,iBAAa,SAAS;AACtB,wBAAoB;AACpB,gBAAY;AAAA,EACd;AACA,QAAM,YAAY,WAAW,MAAM;AACjC,KAAC,KAAK,OAAO,QAAQ;AAAA,MACnB,wCAAwC,KAAK,MAAM,UAAU,GAAM,CAAC,QAAQ,KAAK,SAAS,UAAU;AAAA,IAEtG;AACA,gBAAY;AAAA,EACd,GAAG,OAAO;AACV,EAAC,UAAqC,QAAQ;AAC9C,SAAO;AACT;AAEA,eAAsB,qBACpB,IACA,OAA6E,CAAC,GAClE;AACZ,QAAM,UAAW,MAAM,wBAAwB,IAAI;AACnD,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,YAAQ;AAAA,EACV;AACF;AACA,eAAe,iBAAiB,SAA2E;AACzG,SAAO,qBAAqB,SAAS,EAAE,OAAO,yBAAyB,CAAC;AAC1E;AA4BA,eAAsB,wBACpB,MAWyB;AACzB,QAAM,WAAW,CAAC,QAA0C;AAG1D,UAAM,MAAM,KAAK,SAAS,KAAK,IAAI;AACnC,QAAI,QAAQ;AACZ,QAAI,gBAAgB,MAAM;AACxB,oBAAc,EAAE,OAAO,KAAK,UAAU,IAAI;AAC1C,cAAQ;AAAA,IACV;AAGA,UAAM,iBAAiB,KAAK,UAAU,QAAQ,CAAC;AAC/C,QAAI,eAAgB,uBAAsB;AAC1C,QAAI,SAAS,kBAAkB,MAAM,YAAY,YAAY,oBAAoB;AAC/E,YAAM,UAAU,KAAK,OAAO,MAAM,YAAY,SAAS,GAAM;AAC7D,kBAAY,WAAW;AACvB,WAAK;AAAA,QACH,oBAAoB,KAAK,QAAQ,+BAA+B,EAAE,oBAC/D,QAAQ,SACL,iFACA,wDACJ,2EACC,QACG,yDACA,sBAAsB,OAAO,kFACjC;AAAA,MACJ;AAAA,IACF;AACA,WAAO,EAAE,IAAI,OAAO,WAAW,MAAM,SAAS,OAAO,QAAQ,qBAAqB;AAAA,EACpF;AACA,QAAM,YAAY,YAAa,KAAK,qBAAqB,MAAM,KAAK,mBAAmB,EAAE,MAAM,MAAM,IAAI,IAAI;AAE7G,QAAM,SAAS,YAAY;AACzB,QAAI,CAAE,MAAM,UAAU,GAAI;AAGxB,oBAAc;AACd,aAAO;AAAA,IACT;AACA,UAAM,MAAM,KAAK,SAAS,KAAK,IAAI;AACnC,QAAI,eAAe,MAAM,YAAY,SAAS,6BAA6B;AACzE,WAAK;AAAA,QACH,yEAAyE,KAAK,OAAO,MAAM,YAAY,SAAS,GAAM,CAAC;AAAA,MAEzH;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAMA,QAAM,UAAU,MAAM,wBAAwB;AAAA,IAC5C,WAAW,KAAK,cAAc;AAAA,IAC9B,WAAW;AAAA,IACX,OAAO;AAAA,IACP,KAAK,KAAK;AAAA,EACZ,CAAC;AACD,MAAI,CAAC,QAAS,QAAO,SAAS,MAAM;AACpC,MAAI;AACF,QAAI,MAAM,OAAO,GAAG;AAClB,cAAQ;AACR,aAAO,SAAS,OAAO;AAAA,IACzB;AACA,UAAM,OAAO,MAAM,8BAA8B,IAAI;AACrD,QAAI,CAAC,KAAK,IAAI;AACZ,cAAQ;AACR,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,OAAO,GAAG;AAClB,cAAQ;AACR,aAAO,SAAS,OAAO;AAAA,IACzB;AACA,kBAAc;AACd,WAAO,EAAE,GAAG,MAAM,QAAQ;AAAA,EAC5B,SAAS,KAAK;AACZ,YAAQ;AACR,UAAM;AAAA,EACR;AACF;AAGA,eAAe,8BACb,MAC2G;AAC3G,QAAM,QAAQ,KAAK,SAAS,KAAK,IAAI;AACrC,QAAM,SAAS,KAAK,SAAS;AAC7B,MAAI,CAAC,QAAQ;AACX,SAAK,IAAI,yHAAyH;AAClI,WAAO,EAAE,IAAI,MAAM,WAAW,MAAM,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,OAAO,aAAa,4BAA4B;AAClD,QAAI,YAAa,MAAK,IAAI,0CAA0C,GAAG,OAAO,SAAS,CAAC,mBAAmB;AAC3G,kBAAc;AACd,0BAAsB;AACtB,WAAO,EAAE,IAAI,MAAM,WAAW,OAAO,WAAW,SAAS,MAAM;AAAA,EACjE;AAIA,MAAI,eAAe,QAAQ,YAAY,gBAAgB,wBAAwB;AAI7E,QAAI,KAAK,SAAS,CAAC,qBAAqB;AACtC,4BAAsB;AACtB,WAAK;AAAA,QACH,qDAAqD,GAAG,OAAO,SAAS,CAAC,+BAC9D,GAAG,0BAA0B,CAAC;AAAA,MAG3C;AAAA,IACF;AACA,WAAO,EAAE,IAAI,OAAO,WAAW,OAAO,WAAW,SAAS,KAAK;AAAA,EACjE;AAEA,QAAM,YAAY,gBAAgB;AAClC,MAAI,WAAW;AACb,SAAK;AAAA,MACH,qBAAqB,GAAG,OAAO,SAAS,CAAC,+BAA+B,GAAG,0BAA0B,CAAC;AAAA,IAExG;AAAA,EACF;AAGA,QAAM,UAAU,MAAM,KAAK,QAAQ;AACnC,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,YAAY,OAAO,aAAa,OAAO;AAC7C,MAAI,aAAa,4BAA4B;AAC3C,SAAK,IAAI,qCAAqC,GAAG,OAAO,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,KAAK,UAAU,OAAO,CAAC,eAAe;AAC5H,kBAAc;AACd,WAAO,EAAE,IAAI,MAAM,WAAW,WAAW,SAAS,KAAK;AAAA,EACzD;AACA,gBAAc,EAAE,eAAe,MAAM;AACrC,MAAI,CAAC,aAAa,KAAK,SAAS,CAAC,qBAAqB;AACpD,0BAAsB;AAGtB,SAAK;AAAA,MACH,2DAA2D,GAAG,SAAS,CAAC,6BAC7D,GAAG,0BAA0B,CAAC,KAAK,UAAU,OAAO,CAAC;AAAA,IAClE;AAAA,EACF;AACA,MAAI,WAAW;AACb,SAAK;AAAA,MACH,6CAA6C,GAAG,SAAS,CAAC,wBAAwB,UAAU,OAAO,CAAC;AAAA,IAItG;AAAA,EACF;AACA,SAAO,EAAE,IAAI,OAAO,WAAW,WAAW,SAAS,KAAK;AAC1D;AASO,SAAS,iBAAiB,OAAiB,OAAuC,cAA+B;AACtH,MAAI,OAAwB;AAC5B,aAAW,KAAK,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,GAAG;AACnC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,SAAS,QAAQ,EAAE,YAAY,KAAK,WAAY,QAAO;AAAA,EACnE;AACA,SAAO;AACT;AAGA,IAAI,iBAAgC;AAcpC,IAAI,mBAAmB;AAEvB,eAAsB,kBAAkB,MAAgE;AACtG,MAAI,mBAAmB,QAAQ,KAAK,QAAQ,iBAAiB,wBAAwB;AACnF,WAAO,EAAE,KAAK,OAAO,QAAQ,YAAY;AAAA,EAC3C;AACA,mBAAiB,KAAK;AACtB,QAAM,SAAS,KAAK,SAAS;AAC7B,QAAM,UAAU,YAAY,MAAM;AAClC,MAAI,YAAY,aAAa,CAAC,QAAQ;AAGpC,QAAI,CAAC,kBAAkB;AACrB,yBAAmB;AACnB,WAAK;AAAA,QACH;AAAA,MAEF;AAAA,IACF;AACA,WAAO,EAAE,KAAK,OAAO,QAAQ,UAAU;AAAA,EACzC;AACA,MAAI,kBAAkB;AACpB,uBAAmB;AACnB,SAAK,IAAI,2FAA2F;AAAA,EACtG;AACA,MAAI,YAAY,MAAO,QAAO,EAAE,KAAK,OAAO,QAAQ,KAAK;AAEzD,QAAM,UAAU,MAAM,iBAAiB,KAAK,OAAO;AACnD,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,WAAW,YAAY,KAAK,MAAM;AACxC,OAAK;AAAA,IACH,2BAA2B,GAAG,OAAO,SAAS,CAAC,YAAY,GAAG,OAAO,UAAU,CAAC,wBAC1E,UAAU,OAAO,CAAC,UAAU,QAAQ,GAAG,MAAM,SAAS,IAAI,SAAS,YACtE,WACG,gNAEA;AAAA,EACR;AACA,SAAO,EAAE,KAAK,MAAM,aAAa,OAAO,WAAW,YAAY,OAAO,aAAa,MAAM,SAAS;AACpG;;;ADjuBA,IAAM,gBAAgB,UAAU,QAAQ;AAExC,IAAM,UAAU;AAiCT,IAAM,6BAA6B;AAG1C,IAAM,yBAAyB;AAG/B,IAAM,sBAAsB,IAAI,KAAK,KAAK;AAQ1C,IAAM,oCAAoC;AAE1C,SAAS,aAAqB;AAC5B,QAAM,MAAM,OAAO,SAAS,QAAQ,IAAI,+BAA+B,KAAK,IAAI,EAAE;AAClF,SAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;AACjD;AAEA,SAAS,0BAAkC;AACzC,QAAM,MAAM,OAAO,SAAS,QAAQ,IAAI,oCAAoC,KAAK,IAAI,EAAE;AACvF,SAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;AACjD;AAGO,SAAS,aAAa,QAA+B;AAC1D,QAAM,IAAI,OAAO,MAAM,8BAA8B;AACrD,SAAO,IAAI,EAAE,CAAC,IAAI;AACpB;AAGO,SAAS,kBAAkB,MAAuB;AACvD,SAAO,iCAAiC,KAAK,KAAK,KAAK,CAAC;AAC1D;AAQO,SAAS,gBAAgB,GAAW,GAA0B;AACnE,QAAM,KAAK;AACX,QAAM,KAAK,GAAG,KAAK,EAAE,KAAK,CAAC;AAC3B,QAAMC,MAAK,GAAG,KAAK,EAAE,KAAK,CAAC;AAC3B,MAAI,CAAC,MAAM,CAACA,IAAI,QAAO;AACvB,WAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,UAAM,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI,OAAOA,IAAG,CAAC,CAAC;AACtC,QAAI,MAAM,EAAG,QAAO,IAAI,IAAI,KAAK;AAAA,EACnC;AACA,QAAM,OAAO,GAAG,CAAC,MAAM;AACvB,QAAM,OAAOA,IAAG,CAAC,MAAM;AACvB,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,OAAO,KAAK;AACrB;AAQO,SAAS,2BAA2B,OAK/B;AACV,MAAI,CAAC,MAAM,eAAgB,QAAO;AAClC,MAAI,qBAAqB,MAAM,kBAAkB,MAAM,KAAK,EAAG,QAAO;AACtE,SAAO,MAAM,eAAe;AAC9B;AAOO,SAAS,qBACd,SACA,QAAgB,4BACP;AACT,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAAS,aAAa,OAAO;AACnC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,MAAM,gBAAgB,QAAQ,KAAK;AACzC,SAAO,QAAQ,QAAQ,MAAM;AAC/B;AAOO,IAAM,0BAA6C;AAAA,EACxD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAoCA,IAAI,cAAc;AAElB,IAAI,qBAAqB;AAMzB,IAAI,sBAAkF;AAOtF,IAAI,sBAAqC;AAGlC,SAAS,+BAAqC;AACnD,gBAAc;AACd,uBAAqB;AACrB,wBAAsB;AACtB,wBAAsB;AACxB;AAEA,SAAS,oBAA4B;AACnC,SAAOC,cAAa,MAAM,CAAC,OAAO,eAAe,GAAG,EAAE,UAAU,SAAS,WAAW,KAAK,OAAO,KAAK,CAAC;AACxG;AAEA,SAAS,sBAAsB,MAA6B;AAC1D,MAAI;AACF,WAAOC,cAAa,MAAM,OAAO;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,iBAAiB,UAA2B;AAC1D,SAAO,YAAY,QAAQ,EAAE,KAAK,CAAC,MAAM,6BAA6B,KAAK,EAAE,IAAI,CAAC;AACpF;AAQO,SAAS,qBAAqB,KAA6B;AAChE,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,OAAO,kBAAkB;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,oBAAoB,UAA2B;AAC7D,SAAO,0CAA0C,KAAK,QAAQ;AAChE;AAiBA,eAAe,uBACb,KACA,UACA,YACgF;AAChF,QAAM,mBAAmB,qBAAqB,UAAU;AACxD,MAAI,WAA0B;AAC9B,QAAM,WAAW,QAAQ,IAAI,aAAa;AAC1C,MAAI,UAAU;AACZ,eAAW;AAAA,EACb,OAAO;AACL,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,SAAS,CAAC,QAAQ,CAAC;AAChD,iBAAW,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,GAAG,KAAK,KAAK;AAAA,IACrD,QAAQ;AACN,iBAAW;AAAA,IACb;AAAA,EACF;AACA,MAAI,UAAU;AACZ,QAAI;AACF,YAAM,OAAO,SAAS,QAAQ;AAC9B,aAAO,EAAE,QAAQ,oBAAoB,IAAI,GAAG,QAAQ,MAAM,iBAAiB;AAAA,IAC7E,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,kBAAkB,QAAQ,MAAM,iBAAiB;AACpE;AAYA,eAAe,yBACb,KACwB;AACxB,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,GAAG,MAAM;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,oBACpB,KACA,YACA,MAC0D;AAC1D,QAAM,aAAa,IAAI,IAAY,uBAAuB;AAC1D,aAAW,IAAI,GAAG,IAAI,uBAAuB;AAG7C,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,SAAS,CAAC,QAAQ,CAAC;AAChD,UAAM,IAAI,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,GAAG,KAAK;AAC7C,QAAI,EAAG,YAAW,IAAI,CAAC;AAAA,EACzB,QAAQ;AAAA,EAER;AAEA,QAAM,UAA2D,CAAC;AAClE,aAAW,KAAK,YAAY;AAC1B,QAAI,CAAC,WAAW,CAAC,EAAG;AACpB,QAAI,UAAyB;AAC7B,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC;AAC7C,gBAAU,aAAa,MAAM;AAAA,IAC/B,QAAQ;AAAA,IAER;AACA,YAAQ,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAOA,eAAsB,sBAAsB,MAAsD;AAChG,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,IAAI;AAC7C,QAAM,MAAM,KAAK,eAAe,CAAC,KAAK,SAAS,cAAc,KAAK,MAAM,EAAE,WAAW,KAAK,OAAO,KAAK,CAAC;AACvG,QAAM,KAAK,KAAK,cAAc;AAC9B,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,iBAAiB,KAAK,kBAAkB;AAC9C,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,QAAQ,KAAK,WAAW,SAAS;AACvC,QAAM,EAAE,IAAI,IAAI;AAChB,QAAM,QAAQ,KAAK,iBAAiB;AACpC,MAAI,wBAAwB;AAG5B,MAAI,MAAM,cAAc,WAAW,GAAG;AACpC,WAAO,EAAE,QAAQ,YAAY;AAAA,EAC/B;AACA,gBAAc;AAEd,MAAI;AACF,UAAM,QAAQ,KAAK,kBAAkB,UAAU,KAAK;AAOpD,QAAI,CAAC,oBAAoB;AACvB,YAAM,WAAW,MAAM,oBAAoB,KAAK,YAAY,IAAI;AAChE,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,UAAU,SACb,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,WAAW,SAAS,EAAE,EAChD,KAAK,IAAI;AACZ,YAAI,4DAA4D,OAAO,EAAE;AAAA,MAC3E;AACA,2BAAqB;AAAA,IACvB;AAaA,UAAM,iBAAiB,wBAAwB,IAAI;AACnD,UAAM,YAAY,MAAM,uBAAuB,KAAK,UAAU,eAAe,cAAc,CAAC;AAC5F,QAAI,CAAC,UAAU,UAAU,UAAU,kBAAkB;AACnD;AAAA,QACE,yBAAyB,cAAc,6DAA6D,UAAU,MAAM;AAAA,MACtH;AAAA,IACF;AACA,QAAI,UAAU,QAAQ;AAGpB,UAAI,mFAAmF;AAGvF,4BAAsB;AACtB,aAAO,EAAE,QAAQ,0BAA0B,QAAQ,KAAK;AAAA,IAC1D;AAIA,QAAI;AACJ,QAAI,kBAAkB,IAAI,GAAG;AAC3B,eAAS;AAAA,IACX,OAAO;AACL,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,QAAQ,GAAG,OAAO,IAAI,IAAI,IAAI,SAAS,CAAC;AAC7E,YAAM,WAAW,aAAa,MAAM;AACpC,UAAI,CAAC,UAAU;AACb,YAAI,2CAA2C,OAAO,IAAI,IAAI,yBAAyB;AACvF,eAAO,EAAE,QAAQ,UAAU,QAAQ,oBAAoB;AAAA,MACzD;AACA,eAAS;AAAA,IACX;AAKA,UAAM,gBAAgB,gBAAgB,QAAQ,KAAK;AACnD,QAAI,kBAAkB,QAAQ,gBAAgB,GAAG;AAC/C;AAAA,QACE,gCAAgC,MAAM,gCAAgC,KAAK;AAAA,MAC7E;AACA,eAAS;AAAA,IACX;AAGA,QAAI,UAAU,KAAK,kBAAkB;AACrC,QAAI,CAAC,SAAS;AACZ,UAAI;AACF,cAAM,EAAE,OAAO,IAAI,MAAM,IAAI,UAAU,CAAC,WAAW,CAAC;AACpD,kBAAU,aAAa,MAAM;AAAA,MAC/B,QAAQ;AACN,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,aAAa,qBAAqB,SAAS,KAAK;AACtD,QAAI,CAAC,WAAY,uBAAsB;AAGvC,QAAI,WAAW,YAAY,QAAQ;AAEjC,UAAI,uBAAuB,oBAAoB,WAAW,QAAQ;AAChE,8BAAsB;AAAA,MACxB;AACA,aAAO,EAAE,QAAQ,cAAc,SAAS,OAAO;AAAA,IACjD;AAOA,QAAI,iBAAiB,GAAG,CAAC,GAAG;AAC1B,UAAI,CAAC,YAAY;AACf,YAAI,2EAAsE,MAAM,mBAAmB;AACnG,eAAO,EAAE,QAAQ,iBAAiB,OAAO;AAAA,MAC3C;AACA,UAAI,wBAAwB,KAAM,uBAAsB;AACxD,YAAM,WAAW,MAAM;AACvB,UAAI,WAAW,gCAAgC;AAG7C,sBAAc,MAAM,WAAW,IAAI;AACnC;AAAA,UACE,oDAAoD,OAAO,gCAAgC,KAAK,+BAA0B,MAAM,sBAAsB,KAAK,MAAM,iCAAiC,YAAY,GAAM,CAAC;AAAA,QACvN;AACA,eAAO,EAAE,QAAQ,iBAAiB,OAAO;AAAA,MAC3C;AAQA,YAAM,WAAW,MAAM,yBAAyB,GAAG;AACnD,UAAI,cAA6B;AACjC,UAAI,UAAU;AACZ,YAAI;AACF,wBAAc,cAAc,MAAM,IAAI,UAAU,CAAC,WAAW,CAAC,GAAG,MAAM;AAAA,QACxE,QAAQ;AACN,wBAAc;AAAA,QAChB;AAAA,MACF;AAGA,UAAI,CAAC,UAAU;AACb,8BAAsB;AACtB;AAAA,UACE;AAAA,QACF;AACA,eAAO,EAAE,QAAQ,iBAAiB,OAAO;AAAA,MAC3C;AAOA,YAAM,yBACJ,gBAAgB,QAAQ,qBAAqB,aAAa,KAAK,KAAK,gBAAgB;AACtF,UAAI,CAAC,wBAAwB;AAC3B,8BAAsB;AACtB;AAAA,UACE,2EAA2E,OAAO,8BAA8B,KAAK,8BAA8B,QAAQ,YAAY,eAAe,qBAAqB;AAAA,QAC7M;AACA,eAAO,EAAE,QAAQ,iBAAiB,OAAO;AAAA,MAC3C;AACA;AAAA,QACE,0DAA0D,OAAO,MAAM,KAAK,8BAA8B,KAAK,MAAM,WAAW,GAAM,CAAC,uBAAkB,MAAM;AAAA,MACjK;AACA,8BAAwB;AAAA,IAC1B;AAMA,UAAM,oBAAoB,MAAM,yBAAyB,GAAG;AAC5D,QAAI,8CAA8C,WAAW,QAAQ,WAAM,MAAM,EAAE;AAGnF,UAAM,qBAAqB,MAAM,IAAI,OAAO,CAAC,WAAW,MAAM,GAAG,OAAO,IAAI,MAAM,EAAE,CAAC,GAAG;AAAA,MACtF,OAAO;AAAA,MACP;AAAA,IACF,CAAC;AAID,UAAM,eAAe,qBAAqB;AAC1C,QAAI,YAA2B;AAC/B,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,cAAc,CAAC,WAAW,CAAC;AACxD,kBAAY,aAAa,MAAM;AAAA,IACjC,QAAQ;AACN,kBAAY;AAAA,IACd;AAEA,QAAI,cAAc,QAAQ;AAGxB,4BAAsB;AACtB;AAAA,QACE,gEAAgE,MAAM,SAAS,aAAa,SAAS,SAAS,YAAY;AAAA,MAC5H;AAKA,UAAI,CAAC,uBAAuB,oBAAoB,WAAW,QAAQ;AACjE,8BAAsB,EAAE,QAAQ,OAAO,GAAG,SAAS,MAAM;AAAA,MAC3D,OAAO;AACL,4BAAoB,SAAS;AAAA,MAC/B;AAEA,YAAM,YAAY,wBAAwB;AAC1C,UAAI,oBAAoB,SAAS,aAAa,CAAC,oBAAoB,SAAS;AAC1E,4BAAoB,UAAU;AAC9B;AAAA,UACE,sDAAsD,oBAAoB,KAAK,2CAA2C,MAAM,kBAAkB,YAAY;AAAA,QAChK;AACA,eAAO,EAAE,QAAQ,UAAU,QAAQ,4BAA4B;AAAA,MACjE;AACA,aAAO,EAAE,QAAQ,UAAU,QAAQ,kBAAkB;AAAA,IACvD;AAKA,0BAAsB;AACtB,0BAAsB;AACtB,QAAI,6CAA6C,WAAW,QAAQ,WAAM,SAAS,EAAE;AACrF,QAAI,uBAAuB;AAOzB;AAAA,QACE,2CAA2C,SAAS,0DAA0D,WAAW,SAAS,6DAA6D,SAAS;AAAA,MAC1M;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,WAAW,MAAM,SAAS,IAAI,UAAU;AAAA,EAC3D,SAAS,KAAK;AAIZ,QAAI,sBAAuB,uBAAsB;AAEjD,QAAI,gDAAgD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACtG,WAAO,EAAE,QAAQ,UAAU,QAAQ,YAAY;AAAA,EACjD;AACF;","names":["execFileSync","readFileSync","mb","execFileSync","readFileSync"]}
|
|
@@ -8078,7 +8078,7 @@ function exchangeFailureKind(err) {
|
|
|
8078
8078
|
}
|
|
8079
8079
|
|
|
8080
8080
|
// src/lib/api-client.ts
|
|
8081
|
-
var agtCliVersion = true ? "0.28.
|
|
8081
|
+
var agtCliVersion = true ? "0.28.1154" : "dev";
|
|
8082
8082
|
var lastConfigHash = null;
|
|
8083
8083
|
function setConfigHash(hash) {
|
|
8084
8084
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -13235,4 +13235,4 @@ export {
|
|
|
13235
13235
|
managerInstallSystemUnitCommand,
|
|
13236
13236
|
managerUninstallSystemUnitCommand
|
|
13237
13237
|
};
|
|
13238
|
-
//# sourceMappingURL=chunk-
|
|
13238
|
+
//# sourceMappingURL=chunk-PIK27GUV.js.map
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
probeClaudeInstalls,
|
|
13
13
|
readsAsNativeInstall,
|
|
14
14
|
shouldRunClaudeCodeUpdater
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-PG343LR6.js";
|
|
16
16
|
import "./chunk-XWVM4KPK.js";
|
|
17
17
|
import "./chunk-EL5MWGGH.js";
|
|
18
18
|
import "./chunk-PQ6K7IGT.js";
|
|
@@ -32,4 +32,4 @@ export {
|
|
|
32
32
|
readsAsNativeInstall,
|
|
33
33
|
shouldRunClaudeCodeUpdater
|
|
34
34
|
};
|
|
35
|
-
//# sourceMappingURL=claude-code-updater-
|
|
35
|
+
//# sourceMappingURL=claude-code-updater-BQ7OJMUH.js.map
|
|
@@ -77,11 +77,20 @@ import {
|
|
|
77
77
|
setConfigHash,
|
|
78
78
|
shellGuard,
|
|
79
79
|
tripClass
|
|
80
|
-
} from "../chunk-
|
|
80
|
+
} from "../chunk-PIK27GUV.js";
|
|
81
81
|
import {
|
|
82
82
|
CLAUDE_CODE_SECURITY_FLOOR,
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
SELF_UPDATE_TIMEOUTS,
|
|
84
|
+
anyPackageInstallRunning,
|
|
85
|
+
compareVersions,
|
|
86
|
+
ensureDiskForSelfUpdate,
|
|
87
|
+
maybeRunDiskGuard,
|
|
88
|
+
readDiskStat,
|
|
89
|
+
rearmForcedDiskHoldAnnounce,
|
|
90
|
+
runSafeCleanup,
|
|
91
|
+
tightestDiskStat,
|
|
92
|
+
withPackageCacheLock
|
|
93
|
+
} from "../chunk-PG343LR6.js";
|
|
85
94
|
import {
|
|
86
95
|
getProjectDir as getProjectDir2,
|
|
87
96
|
getReadyTasks,
|
|
@@ -13674,7 +13683,6 @@ var client = null;
|
|
|
13674
13683
|
var chatChannel = null;
|
|
13675
13684
|
var driftChannel = null;
|
|
13676
13685
|
var assignChannel = null;
|
|
13677
|
-
var configChannel = null;
|
|
13678
13686
|
var kanbanChannel = null;
|
|
13679
13687
|
var integrationContextChannel = null;
|
|
13680
13688
|
var tokenRotationChannels = [];
|
|
@@ -13721,7 +13729,6 @@ function startRealtimeChat(config2) {
|
|
|
13721
13729
|
chatChannel = null;
|
|
13722
13730
|
driftChannel = null;
|
|
13723
13731
|
assignChannel = null;
|
|
13724
|
-
configChannel = null;
|
|
13725
13732
|
kanbanChannel = null;
|
|
13726
13733
|
integrationContextChannel = null;
|
|
13727
13734
|
tokenRotationChannels = [];
|
|
@@ -13809,34 +13816,6 @@ function startRealtimeAssignments(config2) {
|
|
|
13809
13816
|
});
|
|
13810
13817
|
log2(`[realtime] Subscribing to host_agents for host ${hostId}`);
|
|
13811
13818
|
}
|
|
13812
|
-
function startRealtimeConfig(config2) {
|
|
13813
|
-
const { agentIds, onConfigChange, log: log2 } = config2;
|
|
13814
|
-
if (agentIds.length === 0) return;
|
|
13815
|
-
const sb = ensureClient(config2);
|
|
13816
|
-
const filterStr = agentIds.length === 1 ? `agent_id=eq.${agentIds[0]}` : `agent_id=in.(${agentIds.join(",")})`;
|
|
13817
|
-
const lastKnown = /* @__PURE__ */ new Map();
|
|
13818
|
-
configChannel = sb.channel("config-realtime").on("postgres_changes", {
|
|
13819
|
-
event: "UPDATE",
|
|
13820
|
-
schema: "public",
|
|
13821
|
-
table: "agents",
|
|
13822
|
-
filter: filterStr
|
|
13823
|
-
}, (payload) => {
|
|
13824
|
-
const agent = payload.new;
|
|
13825
|
-
const fingerprint3 = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
|
|
13826
|
-
const prev = lastKnown.get(agent.agent_id);
|
|
13827
|
-
if (prev === fingerprint3) return;
|
|
13828
|
-
lastKnown.set(agent.agent_id, fingerprint3);
|
|
13829
|
-
log2(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);
|
|
13830
|
-
onConfigChange(agent);
|
|
13831
|
-
}).subscribe((status) => {
|
|
13832
|
-
if (status === "SUBSCRIBED") {
|
|
13833
|
-
log2("[realtime] Config channel connected");
|
|
13834
|
-
} else if (status === "CLOSED" || status === "CHANNEL_ERROR") {
|
|
13835
|
-
log2(`[realtime] Config channel: ${status}`);
|
|
13836
|
-
}
|
|
13837
|
-
});
|
|
13838
|
-
log2(`[realtime] Subscribing to agents table for ${agentIds.length} agent(s)`);
|
|
13839
|
-
}
|
|
13840
13819
|
var METRICS_WINDOW = 200;
|
|
13841
13820
|
var kanbanMetrics = {
|
|
13842
13821
|
delivered: 0,
|
|
@@ -14056,13 +14035,6 @@ function stopRealtimeChat() {
|
|
|
14056
14035
|
}
|
|
14057
14036
|
assignChannel = null;
|
|
14058
14037
|
}
|
|
14059
|
-
if (configChannel) {
|
|
14060
|
-
try {
|
|
14061
|
-
configChannel.unsubscribe();
|
|
14062
|
-
} catch {
|
|
14063
|
-
}
|
|
14064
|
-
configChannel = null;
|
|
14065
|
-
}
|
|
14066
14038
|
if (kanbanChannel) {
|
|
14067
14039
|
try {
|
|
14068
14040
|
kanbanChannel.unsubscribe();
|
|
@@ -15126,6 +15098,38 @@ async function hostShouldHaveClaudeDeviceState() {
|
|
|
15126
15098
|
}
|
|
15127
15099
|
return verdict.applies;
|
|
15128
15100
|
}
|
|
15101
|
+
async function diskGuardDeps() {
|
|
15102
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
15103
|
+
const { promisify: promisify3 } = await import("util");
|
|
15104
|
+
const execFileAsync3 = promisify3(execFile2);
|
|
15105
|
+
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
15106
|
+
const packageManagerBusy = async () => anyPackageInstallRunning();
|
|
15107
|
+
return {
|
|
15108
|
+
readStat: () => readDiskStat("/"),
|
|
15109
|
+
cleanup: () => runSafeCleanup({
|
|
15110
|
+
isRoot,
|
|
15111
|
+
// Async: a synchronous `npm cache clean` would block the poll loop
|
|
15112
|
+
// (and the heartbeat) for as long as it runs.
|
|
15113
|
+
exec: async (cmd, args, opts) => {
|
|
15114
|
+
await execFileAsync3(cmd, args, { timeout: opts.timeout });
|
|
15115
|
+
},
|
|
15116
|
+
packageManagerBusy
|
|
15117
|
+
}),
|
|
15118
|
+
packageManagerBusy,
|
|
15119
|
+
log
|
|
15120
|
+
};
|
|
15121
|
+
}
|
|
15122
|
+
async function selfUpdateDiskHold(force) {
|
|
15123
|
+
const installPaths = ["/"];
|
|
15124
|
+
try {
|
|
15125
|
+
const { realpathSync } = await import("fs");
|
|
15126
|
+
installPaths.push(dirname11(realpathSync(process.argv[1] ?? "/")));
|
|
15127
|
+
} catch {
|
|
15128
|
+
}
|
|
15129
|
+
const deps = await diskGuardDeps();
|
|
15130
|
+
const room = await ensureDiskForSelfUpdate({ ...deps, force, readStat: () => tightestDiskStat(installPaths) });
|
|
15131
|
+
return room.ok ? room.release : "failed";
|
|
15132
|
+
}
|
|
15129
15133
|
async function selfUpdateDeviceStateHold(force) {
|
|
15130
15134
|
if (!await hostShouldHaveClaudeDeviceState()) return null;
|
|
15131
15135
|
const deviceState = deviceStateRotationGate.mayRotate(SELF_UPDATE_DEVICE_STATE_HOLD_KEY, Date.now());
|
|
@@ -15301,6 +15305,7 @@ function runPendingForcedUpdate() {
|
|
|
15301
15305
|
if (forcedUpdateRequestLatch && forcedUpdateRequestLatch.requestedAt !== requestedUpdateAt) {
|
|
15302
15306
|
forcedUpdateRequestLatch = null;
|
|
15303
15307
|
selfUpdateForcedHoldAnnounced = false;
|
|
15308
|
+
rearmForcedDiskHoldAnnounce();
|
|
15304
15309
|
}
|
|
15305
15310
|
if (forcedUpdate !== "defer-idle" && forcedUpdateRequestLatch?.requestedAt === requestedUpdateAt && forcedUpdateRequestLatch.lastOutcome !== null && Date.now() - forcedUpdateRequestLatch.lastAttemptMs < UPDATE_CHECK_INTERVAL_MS) {
|
|
15306
15311
|
return false;
|
|
@@ -15350,6 +15355,7 @@ function runPendingForcedUpdate() {
|
|
|
15350
15355
|
}
|
|
15351
15356
|
forcedUpdateRequestLatch = null;
|
|
15352
15357
|
selfUpdateForcedHoldAnnounced = false;
|
|
15358
|
+
rearmForcedDiskHoldAnnounce();
|
|
15353
15359
|
log(
|
|
15354
15360
|
outcome === "updated" ? `[self-update] "Update CLI now" completed \u2014 upgrade installed; manager restart scheduled.` : `[self-update] "Update CLI now" completed \u2014 host was already on the latest in-channel build; nothing to install.`
|
|
15355
15361
|
);
|
|
@@ -15799,7 +15805,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
15799
15805
|
var lastVersionCheckAt = 0;
|
|
15800
15806
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
15801
15807
|
var lastResponsivenessProbeAt = 0;
|
|
15802
|
-
var agtCliVersion = true ? "0.28.
|
|
15808
|
+
var agtCliVersion = true ? "0.28.1154" : "dev";
|
|
15803
15809
|
function resolveBrewPath(execFileSync3) {
|
|
15804
15810
|
try {
|
|
15805
15811
|
const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -15890,7 +15896,10 @@ async function ensureToolkitCliInner(toolkitSlug) {
|
|
|
15890
15896
|
return;
|
|
15891
15897
|
}
|
|
15892
15898
|
log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
|
|
15893
|
-
await execFileAsync3("npm", ["install", "-g", pkg], { timeout: 18e4 })
|
|
15899
|
+
await withPackageCacheLock(() => execFileAsync3("npm", ["install", "-g", pkg], { timeout: 18e4 }), {
|
|
15900
|
+
label: `the ${toolkitSlug} toolkit install`,
|
|
15901
|
+
log
|
|
15902
|
+
});
|
|
15894
15903
|
} else if (resolvedInstaller === "brew") {
|
|
15895
15904
|
if (!pkg) {
|
|
15896
15905
|
log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
|
|
@@ -16341,7 +16350,10 @@ async function ensureOpencodeBinary() {
|
|
|
16341
16350
|
if (probe()) return true;
|
|
16342
16351
|
log("[framework-install] opencode binary not found - installing opencode-ai via npm...");
|
|
16343
16352
|
try {
|
|
16344
|
-
const r = await
|
|
16353
|
+
const r = await withPackageCacheLock(
|
|
16354
|
+
() => runAsync("npm", ["install", "-g", `opencode-ai@${OPENCODE_NPM_VERSION}`], { timeout: 18e4 }),
|
|
16355
|
+
{ label: "the opencode install", log }
|
|
16356
|
+
);
|
|
16345
16357
|
if (r.code !== 0) {
|
|
16346
16358
|
log(
|
|
16347
16359
|
`[framework-install] opencode install failed (exit ${r.code}): ${(r.stderr || r.stdout).trim().slice(0, 300)} - install manually: npm i -g opencode-ai@${OPENCODE_NPM_VERSION}`
|
|
@@ -16598,14 +16610,14 @@ async function checkAndUpdateCliViaBrew(force = false) {
|
|
|
16598
16610
|
}
|
|
16599
16611
|
let metadataFresh = true;
|
|
16600
16612
|
try {
|
|
16601
|
-
execFileSync3(brewPath, ["update", "--quiet"], { timeout:
|
|
16613
|
+
execFileSync3(brewPath, ["update", "--quiet"], { timeout: SELF_UPDATE_TIMEOUTS.brewUpdateMs, stdio: "pipe" });
|
|
16602
16614
|
} catch (err) {
|
|
16603
16615
|
metadataFresh = false;
|
|
16604
16616
|
log(`[self-update] brew update failed (continuing with stale cache): ${err.message}`);
|
|
16605
16617
|
}
|
|
16606
16618
|
try {
|
|
16607
16619
|
const outdated = execFileSync3(brewPath, ["outdated", "--json=v2"], {
|
|
16608
|
-
timeout:
|
|
16620
|
+
timeout: SELF_UPDATE_TIMEOUTS.brewOutdatedMs,
|
|
16609
16621
|
encoding: "utf-8"
|
|
16610
16622
|
});
|
|
16611
16623
|
const data = JSON.parse(outdated);
|
|
@@ -16636,13 +16648,22 @@ async function checkAndUpdateCliViaBrew(force = false) {
|
|
|
16636
16648
|
return "noop";
|
|
16637
16649
|
}
|
|
16638
16650
|
log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew${force ? " (forced \u2014 Update CLI now)" : ""}...`);
|
|
16651
|
+
const brewDiskHold = await selfUpdateDiskHold(force);
|
|
16652
|
+
if (typeof brewDiskHold === "string") return brewDiskHold;
|
|
16639
16653
|
const brewInstallHold = await selfUpdateDeviceStateHold(force);
|
|
16640
|
-
if (brewInstallHold)
|
|
16654
|
+
if (brewInstallHold) {
|
|
16655
|
+
brewDiskHold();
|
|
16656
|
+
return brewInstallHold;
|
|
16657
|
+
}
|
|
16641
16658
|
try {
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16659
|
+
try {
|
|
16660
|
+
execFileSync3(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
|
|
16661
|
+
timeout: SELF_UPDATE_TIMEOUTS.brewUpgradeMs,
|
|
16662
|
+
stdio: "pipe"
|
|
16663
|
+
});
|
|
16664
|
+
} finally {
|
|
16665
|
+
brewDiskHold();
|
|
16666
|
+
}
|
|
16646
16667
|
log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
|
|
16647
16668
|
stampLastSelfUpdateApplied(selfUpdateAppliedMarkerPath());
|
|
16648
16669
|
restartAfterUpgrade = true;
|
|
@@ -16826,17 +16847,24 @@ async function installAgtCliViaNpm(version, reasonLabel, force = false) {
|
|
|
16826
16847
|
"--registry=https://registry.npmjs.org"
|
|
16827
16848
|
];
|
|
16828
16849
|
const args = isRoot ? installArgs : ["-n", "npm", ...installArgs];
|
|
16850
|
+
const npmDiskHold = await selfUpdateDiskHold(force);
|
|
16851
|
+
if (typeof npmDiskHold === "string") return npmDiskHold;
|
|
16829
16852
|
const npmInstallHold = await selfUpdateDeviceStateHold(force);
|
|
16830
|
-
if (npmInstallHold)
|
|
16853
|
+
if (npmInstallHold) {
|
|
16854
|
+
npmDiskHold();
|
|
16855
|
+
return npmInstallHold;
|
|
16856
|
+
}
|
|
16831
16857
|
try {
|
|
16832
|
-
execFileSync3(cmd, args, { timeout:
|
|
16858
|
+
execFileSync3(cmd, args, { timeout: SELF_UPDATE_TIMEOUTS.npmInstallMs, stdio: "pipe" });
|
|
16833
16859
|
} catch (err) {
|
|
16834
16860
|
log(`[self-update] npm upgrade failed: ${err.message}`);
|
|
16835
16861
|
return "failed";
|
|
16862
|
+
} finally {
|
|
16863
|
+
npmDiskHold();
|
|
16836
16864
|
}
|
|
16837
16865
|
let smokeOk = false;
|
|
16838
16866
|
try {
|
|
16839
|
-
const out = execFileSync3("agt", ["--version"], { timeout:
|
|
16867
|
+
const out = execFileSync3("agt", ["--version"], { timeout: SELF_UPDATE_TIMEOUTS.smokeCheckMs, stdio: "pipe" }).toString();
|
|
16840
16868
|
smokeOk = isAgtVersionSmokeOk(out, version);
|
|
16841
16869
|
if (!smokeOk) {
|
|
16842
16870
|
log(
|
|
@@ -17693,6 +17721,7 @@ async function pollCycleInner() {
|
|
|
17693
17721
|
checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${err.message}`));
|
|
17694
17722
|
}
|
|
17695
17723
|
maybeUpgradeClaudeCode().catch((err) => log(`[claude-code-upgrade] Check failed: ${err.message}`));
|
|
17724
|
+
diskGuardDeps().then((deps) => maybeRunDiskGuard({ ...deps, nowMs: Date.now() })).catch((err) => log(`[disk-guard] check failed: ${err.message}`));
|
|
17696
17725
|
maybeProbeClaudeRuntimeAuth().catch((err) => log(`[runtime-auth-probe] Check failed: ${err.message}`));
|
|
17697
17726
|
maybePollHostUsageFromCycle().catch((err) => log(`[host-usage] Check failed: ${err.message}`));
|
|
17698
17727
|
try {
|
|
@@ -17916,7 +17945,7 @@ async function pollCycleInner() {
|
|
|
17916
17945
|
setConfigHash(hbResp?.config_hash ?? null);
|
|
17917
17946
|
try {
|
|
17918
17947
|
const desiredClaudeCodeVersion = hbResp?.desired_claude_code_version?.trim();
|
|
17919
|
-
const { maybeUpdateClaudeCode, shouldRunClaudeCodeUpdater } = await import("../claude-code-updater-
|
|
17948
|
+
const { maybeUpdateClaudeCode, shouldRunClaudeCodeUpdater } = await import("../claude-code-updater-BQ7OJMUH.js");
|
|
17920
17949
|
claudeUpdaterInFlight = true;
|
|
17921
17950
|
if (desiredClaudeCodeVersion && (claudePairLoop.busy || claudePairingInProgress())) {
|
|
17922
17951
|
log(
|
|
@@ -19212,7 +19241,6 @@ async function pollCycleInner() {
|
|
|
19212
19241
|
ensureRealtimeStarted(agentStates);
|
|
19213
19242
|
ensureRealtimeDriftStarted(agentStates);
|
|
19214
19243
|
ensureRealtimeAssignStarted(agentStates);
|
|
19215
|
-
ensureRealtimeConfigStarted(agentStates);
|
|
19216
19244
|
ensureRealtimeKanbanStarted(agentStates);
|
|
19217
19245
|
ensureRealtimeIntegrationContextStarted(agentStates);
|
|
19218
19246
|
try {
|
|
@@ -23052,7 +23080,6 @@ function holdRealtimeRebindInFlight(failures, agentIds, now, graceMs = REALTIME_
|
|
|
23052
23080
|
}
|
|
23053
23081
|
}
|
|
23054
23082
|
var realtimeAssignStarted = false;
|
|
23055
|
-
var realtimeConfigStarted = false;
|
|
23056
23083
|
var realtimeSubscribedAgentIds = /* @__PURE__ */ new Set();
|
|
23057
23084
|
function healRealtimeSocketDivergence() {
|
|
23058
23085
|
if (!shouldForcePollFallback(isRealtimeConnected(), isRealtimeSocketConnected())) return false;
|
|
@@ -23061,7 +23088,6 @@ function healRealtimeSocketDivergence() {
|
|
|
23061
23088
|
realtimeStarted = false;
|
|
23062
23089
|
realtimeDriftStarted = false;
|
|
23063
23090
|
realtimeAssignStarted = false;
|
|
23064
|
-
realtimeConfigStarted = false;
|
|
23065
23091
|
realtimeKanbanStarted = false;
|
|
23066
23092
|
subscribedIntegrationContextAgentIds = /* @__PURE__ */ new Set();
|
|
23067
23093
|
return true;
|
|
@@ -23079,7 +23105,6 @@ function ensureRealtimeStarted(agentStates) {
|
|
|
23079
23105
|
realtimeStarted = false;
|
|
23080
23106
|
realtimeDriftStarted = false;
|
|
23081
23107
|
realtimeAssignStarted = false;
|
|
23082
|
-
realtimeConfigStarted = false;
|
|
23083
23108
|
realtimeKanbanStarted = false;
|
|
23084
23109
|
subscribedIntegrationContextAgentIds = /* @__PURE__ */ new Set();
|
|
23085
23110
|
}
|
|
@@ -23119,7 +23144,6 @@ function ensureRealtimeStarted(agentStates) {
|
|
|
23119
23144
|
realtimeStarted = false;
|
|
23120
23145
|
realtimeDriftStarted = false;
|
|
23121
23146
|
realtimeAssignStarted = false;
|
|
23122
|
-
realtimeConfigStarted = false;
|
|
23123
23147
|
realtimeKanbanStarted = false;
|
|
23124
23148
|
subscribedIntegrationContextAgentIds = /* @__PURE__ */ new Set();
|
|
23125
23149
|
}
|
|
@@ -23360,30 +23384,6 @@ function ensureRealtimeAssignStarted(agentStates) {
|
|
|
23360
23384
|
log(`[realtime] Assignment subscription failed: ${err.message}`);
|
|
23361
23385
|
});
|
|
23362
23386
|
}
|
|
23363
|
-
function ensureRealtimeConfigStarted(agentStates) {
|
|
23364
|
-
if (realtimeConfigStarted) return;
|
|
23365
|
-
const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
|
|
23366
|
-
if (activeAgentIds.length === 0) return;
|
|
23367
|
-
const apiKey = process.env["AGT_API_KEY"];
|
|
23368
|
-
if (!apiKey) return;
|
|
23369
|
-
void exchangeApiKey(apiKey).then((exchange) => {
|
|
23370
|
-
if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;
|
|
23371
|
-
startRealtimeConfig({
|
|
23372
|
-
supabaseUrl: exchange.supabaseUrl,
|
|
23373
|
-
supabaseAnonKey: exchange.supabaseAnonKey,
|
|
23374
|
-
token: exchange.token,
|
|
23375
|
-
agentIds: activeAgentIds,
|
|
23376
|
-
onConfigChange: (agent) => {
|
|
23377
|
-
agentState.knownStatuses.delete(agent.agent_id);
|
|
23378
|
-
},
|
|
23379
|
-
log
|
|
23380
|
-
});
|
|
23381
|
-
realtimeConfigStarted = true;
|
|
23382
|
-
log(`[realtime] Config subscription started for ${activeAgentIds.length} agent(s)`);
|
|
23383
|
-
}).catch((err) => {
|
|
23384
|
-
log(`[realtime] Config subscription failed: ${err.message}`);
|
|
23385
|
-
});
|
|
23386
|
-
}
|
|
23387
23387
|
function ensureRealtimeKanbanStarted(agentStates) {
|
|
23388
23388
|
if (realtimeKanbanStarted) return;
|
|
23389
23389
|
const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
|