import { g as getCleoHome, h as getCleoHome$1, X as LeaseIpcRequestEnvelopeSchema, Y as LEASE_IPC_PROTOCOL_VERSION, Z as LeaseIpcResponseEnvelopeSchema, b as getLogger, $ as DEFAULT_RESOURCE_RETRY_AFTER_MS } from './paths-2M_UxD_H.js';
import { EventEmitter } from 'node:events';
import { readFile, stat } from 'node:fs/promises';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { availableParallelism, totalmem } from 'node:os';
import { l as lockfile } from './index-IVoIz4D5.js';
import { connect } from 'node:net';
import 'node:diagnostics_channel';
import 'fs';
import 'events';
import 'util';
import 'path';
import 'assert';
import 'worker_threads';
import 'module';
import 'node:url';
import 'url';
import 'buffer';
import 'node:module';
import 'node:child_process';
import 'node:crypto';
import 'node:process';
import 'node:async_hooks';
import 'constants';
import 'stream';

//#region ../core/dist/resources/linux-backend.js
/**
* Linux ResourceMonitor backend.
*
* Reads:
*   - `/proc/pressure/memory` — global PSI (some + full)
*   - `<cgroupSlicePath>/memory.pressure` — cleo.slice scoped PSI
*   - `/proc/meminfo` — MemAvailable
*   - `<walPath>` size stat — WAL growth signal (DHQ-050 starvation class)
*   - `/proc/<pid>/smaps_rollup` — per-child RSS (SEPARATE low-frequency path)
*
* ## Read-count discipline (Amendment 1)
*
* `sample()` calls the injected reader at most:
*   - 1× for `/proc/pressure/memory`
*   - 1× for the slice `memory.pressure` (if configured)
*   - 1× for `/proc/meminfo`
*   - N× stat calls for N configured WAL paths
*
* smaps_rollup reads are on a SEPARATE `sweepChildRss()` method and are
* structurally impossible to reach from the `sample()` hot path.
*
* ## oomd threshold facts (Amendment 4)
*
* systemd-oomd on Fedora defaults to 80%/20s on user@1000.service.
* Our hold/backoff thresholds (some avg10 >10% / full avg10 >5%) sit far
* below that line, ensuring cleo always throttles before oomd kills.
*
* @module resources/linux-backend
* @task T11994
* @epic T11992
*/
/**
* Parse a single PSI line.
*
* Format: `some avg10=0.42 avg60=0.31 avg300=0.20 total=1234567`
*
* Returns `null` when the line does not match the expected format.
*/
function parsePressureLine(line) {
	const avg10Match = /avg10=([\d.]+)/.exec(line);
	const avg60Match = /avg60=([\d.]+)/.exec(line);
	const avg300Match = /avg300=([\d.]+)/.exec(line);
	const totalMatch = /total=(\d+)/.exec(line);
	if (!avg10Match?.[1] || !avg60Match?.[1] || !avg300Match?.[1] || !totalMatch?.[1]) return null;
	return {
		avg10: parseFloat(avg10Match[1]),
		avg60: parseFloat(avg60Match[1]),
		avg300: parseFloat(avg300Match[1]),
		totalUs: parseInt(totalMatch[1], 10)
	};
}
/**
* Parse a PSI file (two lines: `some ...` and `full ...`).
*
* Returns `null` when the content is unreadable / unparseable — callers
* treat this as "pressure interface absent" (degraded mode).
*/
function parsePsiFile(content) {
	const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
	let some = null;
	let full = null;
	for (const line of lines) if (line.startsWith("some ")) some = parsePressureLine(line);
	else if (line.startsWith("full ")) full = parsePressureLine(line);
	if (!some) return null;
	return {
		some,
		full
	};
}
/**
* Parse `MemAvailable` from `/proc/meminfo`.
*
* Format: `MemAvailable:   42233788 kB`
*
* Returns `null` when the field is absent (non-Linux / old kernel).
* Reuses the same parsing pattern as `packages/core/src/llm/local-model-fit.ts:269`.
*/
function parseMemAvailable(content) {
	for (const line of content.split("\n")) if (line.startsWith("MemAvailable:")) {
		const match = /MemAvailable:\s+(\d+)\s+kB/.exec(line);
		if (match?.[1]) return parseInt(match[1], 10) * 1024;
	}
	return null;
}
/**
* Parse a `/proc/<pid>/smaps_rollup` file for RSS and PSS.
*
* Relevant fields:
*   `Rss:       12345 kB`
*   `Pss:        9876 kB`
*
* Returns `null` when either field is absent.
*/
function parseSmapsRollup(content) {
	let rssKb = null;
	let pssKb = null;
	for (const line of content.split("\n")) if (line.startsWith("Rss:")) {
		const m = /Rss:\s+(\d+)\s+kB/.exec(line);
		if (m?.[1]) rssKb = parseInt(m[1], 10);
	} else if (line.startsWith("Pss:")) {
		const m = /Pss:\s+(\d+)\s+kB/.exec(line);
		if (m?.[1]) pssKb = parseInt(m[1], 10);
	}
	if (rssKb === null || pssKb === null) return null;
	return {
		rssBytes: rssKb * 1024,
		pssBytes: pssKb * 1024
	};
}
var defaultReadFile = (path, encoding) => readFile(path, encoding);
var defaultStat = async (path) => {
	try {
		return { size: (await stat(path)).size };
	} catch {
		return null;
	}
};
/**
* Linux implementation of {@link ResourceBackend}.
*
* Uses injected `readFileFn` / `statFileFn` so unit tests can:
*   1. Fake file contents (threshold-crossing, degraded mode)
*   2. Count reads to assert the bounded-read contract (Amendment 1)
*/
var LinuxResourceBackend = class {
	globalPressurePath;
	cgroupSlicePressurePath;
	walPaths;
	readFileFn;
	statFileFn;
	constructor(opts = {}) {
		this.globalPressurePath = opts.globalPressurePath ?? "/proc/pressure/memory";
		this.cgroupSlicePressurePath = opts.cgroupSlicePressurePath ?? null;
		this.walPaths = opts.walPaths ?? [];
		this.readFileFn = opts.readFileFn ?? defaultReadFile;
		this.statFileFn = opts.statFileFn ?? defaultStat;
	}
	/**
	* Take a bounded point-in-time resource sample.
	*
	* Read-count (bounded by Amendment 1):
	*   - 1 read: `/proc/pressure/memory`
	*   - 0–1 reads: slice `memory.pressure` (if configured)
	*   - 1 read: `/proc/meminfo`
	*   - N stat calls: one per WAL path in `walPaths`
	*
	* Never spawns a child process.
	*/
	async sample() {
		const sampledAtMs = Date.now();
		const globalPressure = await this._readPsi(this.globalPressurePath);
		const pressureAvailable = globalPressure !== null;
		let slicePressure = null;
		if (this.cgroupSlicePressurePath) slicePressure = await this._readPsi(this.cgroupSlicePressurePath);
		const memAvailableBytes = await this._readMemAvailable();
		const walObservations = await this._readWalObservations();
		return {
			sampledAtMs,
			pressureAvailable,
			memAvailableBytes,
			globalPressure,
			slicePressure,
			walObservations
		};
	}
	/**
	* Sweep per-child RSS via `/proc/<pid>/smaps_rollup`.
	*
	* **STRUCTURALLY SEPARATED from sample()** — smaps_rollup reads are
	* ms-scale per multi-GB process and must never appear in the hot path.
	* Call on a separate low-frequency cadence (e.g. every 60s or on
	* backoff→hold state transition).
	*
	* @param pids - PIDs to sweep. Dead/unknown PIDs are silently skipped.
	*/
	async sweepChildRss(pids) {
		const sampledAtMs = Date.now();
		const entries = [];
		for (const pid of pids) try {
			const parsed = parseSmapsRollup(await this.readFileFn(`/proc/${pid}/smaps_rollup`, "utf-8"));
			if (parsed) entries.push({
				pid,
				...parsed
			});
		} catch {}
		return {
			sampledAtMs,
			entries
		};
	}
	async _readPsi(path) {
		try {
			return parsePsiFile(await this.readFileFn(path, "utf-8"));
		} catch {
			return null;
		}
	}
	async _readMemAvailable() {
		try {
			return parseMemAvailable(await this.readFileFn("/proc/meminfo", "utf-8"));
		} catch {
			return null;
		}
	}
	async _readWalObservations() {
		const results = [];
		for (const walPath of this.walPaths) {
			const info = await this.statFileFn(walPath);
			results.push({
				walPath,
				sizeBytes: info?.size ?? null
			});
		}
		return results;
	}
};
//#endregion
//#region ../core/dist/resources/monitor.js
/**
* ResourceMonitor — PSI + MemAvailable sensing, daemon-off capable.
*
* Two operational modes:
*
* 1. **Point-in-time sample** (`ResourceMonitor.sample()`): used inside the
*    governor acquire path when the daemon is off. Single call, bounded reads.
*
* 2. **Continuous loop** (`ResourceMonitor.startContinuous()`): used by the
*    daemon/supervisor. Polls at `pollIntervalMs` and emits `ok` → `hold` →
*    `backoff` state transitions with hysteresis.
*
* ## State machine
*
*   ok  ──── pressure > holdThreshold ──────→  hold
*   hold ─── pressure < holdThreshold - hyst ─→ ok
*   hold ─── pressure > backoffThreshold ─────→ backoff
*   backoff ─ pressure < holdThreshold - hyst ─→ ok
*
* Default thresholds (far below systemd-oomd's Fedora 80%/20s kill line):
*   - hold:    some avg10 > 10% OR full avg10 > 5%
*   - backoff: some avg10 > 20% OR full avg10 > 10%
*   - hysteresis: 3 percentage points (prevents thrash on threshold edge)
*
* ## Degraded mode
*
* When the PSI interface is absent (non-Linux, locked-down container,
* unprivileged environment), the monitor falls back to availability-only
* sampling: `pressureAvailable = false`, `state = 'ok'` unless
* `memAvailableBytes` is below `headroomMb`.
*
* ## oomd facts (Amendment 4)
*
* systemd-oomd on Fedora defaults to 80%/20s on user@1000.service (NOT
* the 50%/20s figure cited in older research). Our hold/backoff thresholds
* sit far below that line — cleo always throttles before oomd kills.
*
* ## WAL growth signal (Amendment 3)
*
* The monitor accepts an optional watch on SQLite `-wal` sidecar file sizes.
* A size above `walWarnThresholdBytes` is surfaced in the sample and can
* drive governor decisions independently of memory pressure.
*
* @module resources/monitor
* @task T11994
* @epic T11992
*/
var DEFAULTS = {
	holdSomeAvg10: 10,
	backoffSomeAvg10: 20,
	holdFullAvg10: 5,
	backoffFullAvg10: 10,
	hysteresisPoints: 3,
	headroomBytes: 256 * 1024 * 1024,
	walWarnThresholdBytes: 256 * 1024 * 1024,
	pollIntervalMs: 1500
};
function resolveThresholds(cfg) {
	return {
		holdSomeAvg10: cfg.psi?.holdSomeAvg10 ?? DEFAULTS.holdSomeAvg10,
		backoffSomeAvg10: cfg.psi?.backoffSomeAvg10 ?? DEFAULTS.backoffSomeAvg10,
		holdFullAvg10: cfg.psi?.holdFullAvg10 ?? DEFAULTS.holdFullAvg10,
		backoffFullAvg10: cfg.psi?.backoffFullAvg10 ?? DEFAULTS.backoffFullAvg10,
		hysteresisPoints: cfg.hysteresisPoints ?? DEFAULTS.hysteresisPoints,
		headroomBytes: cfg.headroomBytes ?? DEFAULTS.headroomBytes,
		walWarnThresholdBytes: cfg.walWarnThresholdBytes ?? DEFAULTS.walWarnThresholdBytes,
		pollIntervalMs: cfg.pollIntervalMs ?? DEFAULTS.pollIntervalMs
	};
}
/**
* Evaluate the pressure state given a sample and resolved thresholds.
*
* In degraded mode (PSI absent), the state is `hold` only when free memory
* falls below `headroomBytes`.
*
* @internal
*/
function evaluateState(sample, thresholds, currentState) {
	if (!sample.pressureAvailable) {
		if (sample.memAvailableBytes !== null && sample.memAvailableBytes < thresholds.headroomBytes) return {
			state: "hold",
			reason: "degraded: memAvailable below headroom"
		};
		return {
			state: "ok",
			reason: "degraded: PSI unavailable, memAvailable sufficient"
		};
	}
	const somePressure = sample.globalPressure?.some ?? sample.slicePressure?.some;
	const fullPressure = sample.globalPressure?.full ?? sample.slicePressure?.full;
	const someAvg10 = somePressure?.avg10 ?? 0;
	const fullAvg10 = fullPressure?.avg10 ?? 0;
	if (someAvg10 > thresholds.backoffSomeAvg10 || fullAvg10 > thresholds.backoffFullAvg10) return {
		state: "backoff",
		reason: `some avg10=${someAvg10.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} — above backoff threshold`
	};
	if (someAvg10 > thresholds.holdSomeAvg10 || fullAvg10 > thresholds.holdFullAvg10) return {
		state: "hold",
		reason: `some avg10=${someAvg10.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} — above hold threshold`
	};
	if (currentState !== "ok") {
		const hysteresisFloor = thresholds.holdSomeAvg10 - thresholds.hysteresisPoints;
		const fullHysteresisFloor = thresholds.holdFullAvg10 - thresholds.hysteresisPoints;
		if (someAvg10 > hysteresisFloor || fullAvg10 > fullHysteresisFloor) return {
			state: currentState === "backoff" ? "hold" : currentState,
			reason: `some avg10=${someAvg10.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} — hysteresis hold (floor: some ${hysteresisFloor}, full ${fullHysteresisFloor})`
		};
	}
	return {
		state: "ok",
		reason: `some avg10=${someAvg10.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} — below thresholds`
	};
}
/**
* ResourceMonitor — PSI + MemAvailable sensing, daemon-off capable.
*
* @example Point-in-time (daemon-off governor acquire):
* ```ts
* const monitor = new ResourceMonitor();
* const sample = await monitor.sample();
* if (sample.memAvailableBytes !== null && sample.memAvailableBytes < 256 * 1024 * 1024) {
*   throw new Error('insufficient memory');
* }
* ```
*
* @example Continuous mode (daemon supervisor):
* ```ts
* const monitor = new ResourceMonitor({ pollIntervalMs: 1500 });
* monitor.on('transition', ({ from, to, reason }) => {
*   logger.warn(`pressure: ${from} → ${to} (${reason})`);
* });
* const stop = monitor.startContinuous();
* // later:
* stop();
* ```
*/
var ResourceMonitor = class extends EventEmitter {
	thresholds;
	backend;
	_state = "ok";
	_timer = null;
	_running = false;
	constructor(cfg = {}) {
		super();
		this.thresholds = resolveThresholds(cfg);
		this.backend = cfg.backend ?? new LinuxResourceBackend();
	}
	/**
	* Current pressure state (only meaningful after at least one sample).
	*/
	get state() {
		return this._state;
	}
	/**
	* `true` while the continuous loop is running.
	*/
	get running() {
		return this._running;
	}
	/**
	* Take a single point-in-time resource sample.
	*
	* Safe to call with the daemon off (no child processes, bounded reads).
	* Does NOT update `this.state` — use {@link startContinuous} for that.
	*/
	async sample() {
		return this.backend.sample();
	}
	/**
	* Start the continuous polling loop.
	*
	* Returns a `stop()` function. Calling `stop()` is idempotent.
	*
	* Emits:
	*   - `'sample'` on every poll (even if state did not change)
	*   - `'transition'` when `PressureState` changes
	*   - `'error'` if the backend throws (loop continues regardless)
	*/
	startContinuous() {
		if (this._running) return () => this._stopLoop();
		this._running = true;
		this._scheduleNext();
		return () => this._stopLoop();
	}
	/**
	* Stop the continuous polling loop. Idempotent.
	*/
	stop() {
		this._stopLoop();
	}
	_scheduleNext() {
		this._timer = setTimeout(() => {
			this._tick();
		}, this.thresholds.pollIntervalMs);
		if (typeof this._timer.unref === "function") this._timer.unref();
	}
	async _tick() {
		if (!this._running) return;
		try {
			const sample = await this.backend.sample();
			this.emit("sample", sample);
			const { state: newState, reason } = evaluateState(sample, this.thresholds, this._state);
			if (newState !== this._state) {
				const transition = {
					from: this._state,
					to: newState,
					sample,
					reason
				};
				this._state = newState;
				this.emit("transition", transition);
			}
		} catch (err) {
			this.emit("error", err instanceof Error ? err : new Error(String(err)));
		}
		if (this._running) this._scheduleNext();
	}
	_stopLoop() {
		this._running = false;
		if (this._timer !== null) {
			clearTimeout(this._timer);
			this._timer = null;
		}
	}
};

//#region ../core/dist/resources/supervisor-admit.js
/**
* Supervisor-mode resource admission client (T12001 · Epic T11992).
*
* The `supervisor`-mode counterpart of the {@link ResourceGovernor}'s local slot
* engine. Where local mode arbitrates heavy-op concurrency through cross-process
* lockfiles, supervisor mode routes the `resource_admit` / `resource_release`
* lease-ipc verbs to the Rust `cleo-supervisor` so a single in-memory counter is
* the machine-wide source of truth.
*
* A dead/absent supervisor must NEVER deadlock work: every send degrades to
* `{ unavailable }` on any transport error, and the governor falls back to the
* local slot engine. Mirrors the `llm-queue-admit.ts` transport (core cannot
* import `@cleocode/runtime` — a cycle — so it speaks the NDJSON codec directly
* through the shared `@cleocode/contracts` lease-ipc Zod schemas).
*
* @task T12001
* @epic T11992
*/
/** The supervisor IPC socket filename — MUST equal `SOCKET_NAME` in `crates/cleo-supervisor/src/paths.rs`. */
var SUPERVISOR_SOCKET_NAME = "cleo-supervisor.sock";
/** Connect timeout (ms). A slow/absent socket degrades fast — never a deadlock. */
var CONNECT_TIMEOUT_MS = 250;
/** Per-request read timeout for a result frame (ms). */
var REQUEST_TIMEOUT_MS = 2e3;
/**
* Resolve the supervisor socket path: `$CLEO_SUPERVISOR_SOCKET` overrides,
* otherwise `<cleoHome>/cleo-supervisor.sock`.
*/
function resolveSupervisorSocketPath() {
	return process.env.CLEO_SUPERVISOR_SOCKET ?? join(getCleoHome$1(), SUPERVISOR_SOCKET_NAME);
}
/**
* Admit (or defer) a heavy op of `cls` for `holderId` against `budget` through
* the supervisor's central arbiter. Resolves the `resource_admit_result` reply,
* or `{ unavailable }` when the supervisor cannot arbitrate (degrade to local).
*/
async function sendResourceAdmit(socketPath, cls, holderId, budget) {
	const result = await sendLeaseRequest(socketPath, {
		kind: "resource_admit",
		class: cls,
		holder_id: holderId,
		budget
	}, "resource_admit_result");
	if ("unavailable" in result) return result;
	if (result.kind === "resource_admit_result") return result;
	return {
		unavailable: true,
		reason: `unexpected response kind: ${result.kind}`
	};
}
/**
* Release a previously-admitted slot of `cls` for `holderId`. Resolves the
* `resource_release_result` reply, or `{ unavailable }` on transport failure
* (the slot self-heals via the arbiter's process-death reclaim / stale recovery).
*/
async function sendResourceRelease(socketPath, cls, holderId) {
	const result = await sendLeaseRequest(socketPath, {
		kind: "resource_release",
		class: cls,
		holder_id: holderId
	}, "resource_release_result");
	if ("unavailable" in result) return result;
	if (result.kind === "resource_release_result") return result;
	return {
		unavailable: true,
		reason: `unexpected response kind: ${result.kind}`
	};
}
/**
* One-shot NDJSON lease-ipc round-trip over a fresh Unix socket. Connection per
* request (the verbs are stateless); the socket is closed before resolution.
* Any transport error resolves to `{ unavailable }` — never throws, never
* deadlocks. `expectedKind` is the result `kind` the caller correlates.
*/
function sendLeaseRequest(socketPath, request, expectedKind) {
	return new Promise((resolve) => {
		const id = `ra-${process.pid}-${request.kind}`;
		const envelope = LeaseIpcRequestEnvelopeSchema.parse({
			protocol_version: LEASE_IPC_PROTOCOL_VERSION,
			id,
			direction: "request",
			request
		});
		const line = `${JSON.stringify(envelope)}\n`;
		let settled = false;
		let buffer = "";
		const socket = connect(socketPath);
		const done = (value) => {
			if (settled) return;
			settled = true;
			socket.removeAllListeners();
			socket.destroy();
			resolve(value);
		};
		const degrade = (reason) => done({
			unavailable: true,
			reason
		});
		socket.setTimeout(REQUEST_TIMEOUT_MS);
		const connectTimer = setTimeout(() => degrade("supervisor connect timeout"), CONNECT_TIMEOUT_MS);
		socket.on("connect", () => {
			clearTimeout(connectTimer);
			socket.write(line);
		});
		socket.on("timeout", () => degrade("supervisor request timeout"));
		socket.on("error", (err) => {
			clearTimeout(connectTimer);
			degrade(err.message);
		});
		socket.on("close", () => {
			if (!settled) degrade("supervisor closed the connection before responding");
		});
		socket.on("data", (chunk) => {
			buffer += chunk.toString("utf8");
			const nl = buffer.indexOf("\n");
			if (nl < 0) return;
			let json;
			try {
				json = JSON.parse(buffer.slice(0, nl));
			} catch (cause) {
				degrade(`malformed lease-ipc frame: ${cause instanceof Error ? cause.message : cause}`);
				return;
			}
			const parsed = LeaseIpcResponseEnvelopeSchema.safeParse(json);
			if (!parsed.success) {
				degrade(`off-contract lease-ipc response: ${parsed.error.message}`);
				return;
			}
			const { response } = parsed.data;
			if (response.kind === expectedKind) {
				done(response);
				return;
			}
			if (response.kind === "error") {
				degrade(`${response.code}: ${response.message}`);
				return;
			}
			degrade(`unexpected lease-ipc response kind for ${request.kind}: ${response.kind}`);
		});
	});
}
//#endregion
//#region ../core/dist/resources/governor.js
/**
* ResourceGovernor — Never-OOM class-based admission (T11999, Epic T11992).
*
* Admits resource-intensive work through priority classes whose slot budgets
* are computed at acquire time from host memory + memory-pressure (PSI). A
* denial returns a structured, retryable {@link ResourceDeferral} — never a
* silent drop, never a crash. Existing grants are NEVER revoked.
*
* Three modes (verbatim writer-lease shape, `writer-lease.ts` `resolveLeaseMode`):
* - `supervisor` — defer to the Rust `cleo-supervisor` `resource_admit` verb.
*   Demotes to `local` (log-once) until the IPC client is wired — a
*   dead/absent arbiter must never deadlock work.
* - `local` — DEFAULT, daemon-off. Per-class slot directories under
*   `getCleoHome()/locks/resource-<class>/` arbitrated by `proper-lockfile`
*   (crash-stale auto-release ⇒ genuinely cross-process without a daemon),
*   plus a point-sample of the {@link ResourceMonitor} taken INSIDE acquire.
*   Generalizes the tool-semaphore engine (`tool-semaphore.ts`).
* - `off` — pure pass-through.
*
* `interactive-cli` is NEVER gated; `full-build` is pinned to one machine-wide
* slot regardless of pressure.
*
* @task T11999
* @epic T11992
* @adr resource-governor-never-oom-architecture §3.4
*/
var _log = null;
function log() {
	if (_log === null) _log = getLogger("resource-governor");
	return _log;
}
var _cachedMode = null;
var _supervisorDegradeLogged = false;
/**
* Resolve the governor mode from `CLEO_RESOURCES_MODE`, once per process.
* Unknown / unset values resolve to `'local'` — the production-safe default
* while the supervisor daemon is disabled.
*
* @task T11999
*/
function resolveGovernorMode() {
	if (_cachedMode !== null) return _cachedMode;
	const raw = process.env.CLEO_RESOURCES_MODE;
	_cachedMode = raw === "supervisor" || raw === "local" || raw === "off" ? raw : "local";
	return _cachedMode;
}
/**
* Log (once) that `supervisor` mode degraded to the local slot engine because
* the arbiter was unreachable — a dead/absent supervisor must never deadlock
* work (T12001). Mirrors the writer-lease degrade-once behaviour.
*/
function logSupervisorDegradeOnce(reason) {
	if (_supervisorDegradeLogged) return;
	_supervisorDegradeLogged = true;
	log().info(`CLEO_RESOURCES_MODE=supervisor but the arbiter is unreachable (${reason}); degrading to local-mode admission for the process lifetime.`);
}
var MB = 1024 * 1024;
/** Extract `some avg10` (0–100) from a sample; 0 when unavailable. */
function someAvg10(sample) {
	return (sample.globalPressure?.some ?? sample.slicePressure?.some)?.avg10 ?? 0;
}
/**
* Compute the slot budget for a class given a point-sample.
*
* - `interactive-cli` → `Infinity` (never gated).
* - `full-build` → `1` machine-wide, pressure-independent.
* - `agent-session` → `clamp(1, ⌊(MemAvailable − headroom)/estRamMb⌋, cpus−2)`.
* - `test-run` / `scoped-build` → `max(1, ⌊cpus/4⌋)`, ×0.5 when `some>hold`,
*   floored to 1 when `some>floor`.
* - `llm-call` → `max(1, cpus−2)` (primarily gated by the llm-queue elsewhere).
* - `db-heavy` → `1`, deferred (→0) under `backoff`-level pressure.
* - `background-autonomous` → `1` only when pressure is `ok`, else `0`.
*
* @adr resource-governor-never-oom-architecture §3.4 (budgets)
*/
function computeClassBudget(cls, sample, opts = {}) {
	if (cls === "interactive-cli") return Number.POSITIVE_INFINITY;
	const cpus = Math.max(1, opts.cpuCount ?? availableParallelism());
	const totalBytes = opts.totalMemBytes ?? totalmem();
	const headroomBytes = (opts.headroomMb ?? 2048) * MB;
	const hold = opts.holdSomeAvg10 ?? 10;
	const floor = opts.floorSomeAvg10 ?? 25;
	const some = someAvg10(sample);
	const availBytes = sample.memAvailableBytes ?? totalBytes;
	const fullStall = sample.globalPressure?.full?.avg10 ?? sample.slicePressure?.full?.avg10 ?? 0;
	const backoff = some > floor || fullStall > 10;
	switch (cls) {
		case "full-build": return 1;
		case "agent-session": {
			const estRamBytes = (opts.agentEstRamMb ?? 4096) * MB;
			return clamp(1, Math.floor((availBytes - headroomBytes) / estRamBytes), Math.max(1, cpus - 2));
		}
		case "llm-call": return Math.max(1, cpus - 2);
		case "test-run":
		case "scoped-build": {
			const base = Math.max(1, Math.floor(cpus / 4));
			if (some > floor) return 1;
			if (some > hold) return Math.max(1, Math.floor(base / 2));
			return base;
		}
		case "db-heavy": return backoff ? 0 : 1;
		case "background-autonomous": return some > hold || fullStall > 5 ? 0 : 1;
		default: return 1;
	}
}
function clamp(lo, v, hi) {
	return Math.max(lo, Math.min(v, hi));
}
/**
* Machine-wide slot directory for a class, under
* `getCleoHome()/locks/resource-<class>/`. Shared across projects, worktrees,
* and PIDs — exactly like the tool semaphore.
*/
function governorSlotDir(cls) {
	return join(getCleoHome(), "locks", `resource-${cls}`);
}
function ensureSlotFiles(dir, count) {
	if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
	const paths = [];
	for (let i = 0; i < count; i++) {
		const p = join(dir, `slot-${i}.lock`);
		if (!existsSync(p)) writeFileSync(p, "", { flag: "a" });
		paths.push(p);
	}
	return paths;
}
var STALE_MS = 6e5;
function sleep(ms) {
	return new Promise((r) => setTimeout(r, ms));
}
function passThroughGrant(cls) {
	return {
		deferred: false,
		class: cls,
		slot: -1,
		acquiredAtMs: Date.now(),
		release: async () => {}
	};
}
function deferral(cls, reason, retryAfterMs) {
	return {
		deferred: true,
		class: cls,
		retryAfterMs,
		reason
	};
}
var _supervisorHolderSeq = 0;
/**
* A process-unique holder id for a supervisor `resource_admit`, so each acquire
* is a distinct slot (matched 1:1 by its `release`). `pid` scopes the holder to
* this process; the sequence distinguishes concurrent holds of the same class.
*/
function supervisorHolderId(cls) {
	_supervisorHolderSeq += 1;
	return `${process.pid}:${cls}:${_supervisorHolderSeq}`;
}
/**
* The Never-OOM admission gate. Stateless wrapper over the mode-resolved
* backend (local slot dirs today; supervisor IPC when wired). Construct once
* and share, or use the module-level {@link governor} singleton.
*/
var ResourceGovernor = class {
	/**
	* Acquire one slot of `cls`. Returns a {@link ResourceGrant} on success or a
	* {@link ResourceDeferral} on denial. Never throws for admission control;
	* only genuinely unexpected I/O errors propagate.
	*/
	async acquire(cls, opts = {}) {
		const mode = resolveGovernorMode();
		if (mode === "off" || cls === "interactive-cli") return passThroughGrant(cls);
		const sample = opts.sample ?? await (opts.monitor ?? new ResourceMonitor()).sample();
		const budget = computeClassBudget(cls, sample, opts);
		if (!Number.isFinite(budget)) return passThroughGrant(cls);
		if (budget <= 0) return deferral(cls, `class '${cls}' budget is 0 under current pressure (some avg10=${someAvg10(sample).toFixed(1)})`, DEFAULT_RESOURCE_RETRY_AFTER_MS);
		if (mode === "supervisor") {
			const viaSupervisor = await this.acquireViaSupervisor(cls, Math.floor(budget));
			if (viaSupervisor !== null) return viaSupervisor;
		}
		const slots = ensureSlotFiles(governorSlotDir(cls), budget);
		const blocking = opts.blocking ?? true;
		const timeoutMs = opts.timeoutMs ?? 36e5;
		const pollMs = opts.pollMs ?? 200;
		const startedAt = Date.now();
		do {
			const order = shuffledIndices(slots.length);
			for (const idx of order) {
				const path = slots[idx];
				if (!path) continue;
				try {
					const release = await lockfile.lock(path, {
						retries: 0,
						stale: STALE_MS,
						realpath: false
					});
					let released = false;
					return {
						deferred: false,
						class: cls,
						slot: idx,
						acquiredAtMs: Date.now(),
						release: async () => {
							if (released) return;
							released = true;
							try {
								await release();
							} catch {}
						}
					};
				} catch {}
			}
			if (!blocking) break;
			await sleep(pollMs);
		} while (Date.now() - startedAt < timeoutMs);
		return deferral(cls, `class '${cls}' is at capacity (${budget} slot(s)); ` + (blocking ? `timed out after ${timeoutMs}ms` : "no slot free"), Math.min(pollMs * 4, DEFAULT_RESOURCE_RETRY_AFTER_MS));
	}
	/**
	* Route an admission through the supervisor's central `resource_admit` verb.
	* Returns a grant (whose `release` calls `resource_release`) or a deferral, or
	* `null` when the supervisor is unreachable so the caller degrades to the
	* local slot engine. Never throws — a dead arbiter never deadlocks work.
	*
	* @task T12001
	*/
	async acquireViaSupervisor(cls, budget) {
		const socketPath = resolveSupervisorSocketPath();
		const holderId = supervisorHolderId(cls);
		const reply = await sendResourceAdmit(socketPath, cls, holderId, budget);
		if ("unavailable" in reply) {
			logSupervisorDegradeOnce(reply.reason);
			return null;
		}
		if (reply.disposition === "deferred") return deferral(cls, `class '${cls}' deferred by supervisor (budget ${budget})`, reply.retry_after_ms || 2e3);
		let released = false;
		return {
			deferred: false,
			class: cls,
			slot: 0,
			acquiredAtMs: Date.now(),
			release: async () => {
				if (released) return;
				released = true;
				await sendResourceRelease(socketPath, cls, holderId).catch(() => {});
			}
		};
	}
	/** Non-blocking single-pass acquire (admission semantics). */
	async tryAcquire(cls, opts = {}) {
		return this.acquire(cls, {
			...opts,
			blocking: false
		});
	}
	/**
	* Currently-grantable slot count for `cls` = budget − held. Held is the count
	* of slot files currently locked. `Infinity` for ungated classes.
	*/
	async available(cls, opts = {}) {
		if (resolveGovernorMode() === "off" || cls === "interactive-cli") return Number.POSITIVE_INFINITY;
		const budget = computeClassBudget(cls, opts.sample ?? await (opts.monitor ?? new ResourceMonitor()).sample(), opts);
		if (!Number.isFinite(budget)) return Number.POSITIVE_INFINITY;
		if (budget <= 0) return 0;
		const held = await countHeldSlots(cls, budget);
		return Math.max(0, budget - held);
	}
};
function shuffledIndices(n) {
	const order = Array.from({ length: n }, (_, i) => i);
	for (let i = order.length - 1; i > 0; i--) {
		const j = Math.floor(Math.random() * (i + 1));
		const a = order[i];
		const b = order[j];
		if (a !== void 0 && b !== void 0) {
			order[i] = b;
			order[j] = a;
		}
	}
	return order;
}
/**
* Count how many of `budget` slots are currently held, by probing each with a
* non-blocking lock. A successful probe-lock is released immediately — it never
* holds the slot, so it cannot starve a real acquirer.
*/
async function countHeldSlots(cls, budget) {
	const dir = governorSlotDir(cls);
	if (!existsSync(dir)) return 0;
	const slots = ensureSlotFiles(dir, budget);
	let held = 0;
	for (const path of slots) try {
		await (await lockfile.lock(path, {
			retries: 0,
			stale: STALE_MS,
			realpath: false
		}))();
	} catch {
		held++;
	}
	return held;
}
/** Process-wide governor singleton. */
var governor = new ResourceGovernor();

export { governor };
//# sourceMappingURL=governor2-z1gWNV99.js.map
