UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,804 lines • 71.2 kB
import { u as toErrorObject } from "./error-coercion-1P2One0X.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { o as resolveUserPath } from "./home-dir-CjKH62jd.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { p as normalizeTrimmedStringList } from "./string-normalization-YMwDjufr.mjs";
import { t as CONFIG_DIR } from "./utils-DUAwk2p5.mjs";
import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs";
import { r as resolveRealpathOrAbsolute } from "./boundary-path-CAv_plX7.mjs";
import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs";
import { s as isDefaultStateDir } from "./paths-FklbMVOH.mjs";
import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs";
import { n as ok, t as err } from "./result-BQGgYouL.mjs";
import { s as getGatewayRestartDrainSignal } from "./gateway-work-admission-G8XukyHo.mjs";
import { i as getAgentWorkspaceAccess } from "./workspace-access-CCdQbCKD.mjs";
import { n as resolveWorkspaceSkillDirectories, t as normalizeWorkspaceSkillRoots } from "./workspace-skill-roots-CFHkU9qW.mjs";
import { a as markSkillsSupportingFilesChanged, f as suspendSkillsSnapshotSources, l as resetSkillsRefreshStateForTest, o as notifySkillsWatchAvailable, t as bumpSkillsSnapshotVersion, u as setSkillsChangeListenerErrorHandler } from "./refresh-state-Cazo5oir.mjs";
import { n as resolveWorkshopWatchRoots } from "./skills-root-BZ-eiihY.mjs";
import { t as areOrderedArraysEqual } from "./ordered-array-equality-CBhMoHgO.mjs";
import { c as tryRealpath, s as resolveAllowedSkillSymlinkTargetRealPaths } from "./skill-paths-5mSgftmu.mjs";
import { n as resolveWorkspaceSkillSourcePlan, r as splitSkillSourcePlan } from "./workspace-skill-sources-DHNvd3jG.mjs";
import { i as resolvePluginSkillRootsFromMetadata, r as resolvePluginSkillRoots } from "./plugin-skills-D9aqamTI.mjs";
import { t as getFileWatchCapacityCode } from "./fs-watch-errors-D0zoLJOB.mjs";
import fs from "node:fs";
import path from "node:path";
import { AsyncLocalStorage } from "node:async_hooks";
import fs$1 from "node:fs/promises";
import os from "node:os";
import { EventEmitter } from "node:events";
import chokidar from "chokidar";
//#region src/skills/runtime/refresh-watch-close.ts
const pendingWatcherCloses = /* @__PURE__ */ new Set();
function trackSkillsWatcherClose(close) {
	const closing = (async () => {
		try {
			await close();
			return ok(void 0);
		} catch (error) {
			return err(error);
		}
	})();
	pendingWatcherCloses.add(closing);
	closing.then(() => pendingWatcherCloses.delete(closing));
	return closing;
}
function teardownSkillsPathWatcher(state) {
	const watcher = state.watcher;
	watcher.add = () => watcher;
	clearTimeout(state.timer);
	return trackSkillsWatcherClose(async () => {
		const wasClosed = watcher.closed;
		let closed;
		try {
			closed = watcher.close();
		} finally {
			if (!wasClosed) watcher.on("error", () => {});
		}
		await closed;
	});
}
async function joinSkillsWatcherCloses() {
	await Promise.all(pendingWatcherCloses);
}
//#endregion
//#region src/skills/runtime/refresh-watch-path.ts
function isTrustedSymlinkSkillTarget(source, rootRealPath, targetRealPath, allowedSymlinkTargetRealPaths) {
	if (source === "openclaw-managed" || source === "agents-skills-personal") return true;
	return isPathInside(rootRealPath, targetRealPath) || allowedSymlinkTargetRealPaths.some((root) => isPathInside(root, targetRealPath));
}
function toWatchRoot(raw) {
	const normalized = raw.replaceAll("\\", "/");
	const root = path.parse(normalized).root;
	const trimmed = normalized.replace(/\/+$/, "");
	return trimmed.length < root.length ? root : trimmed;
}
function resolveSkillsWatchPath(raw) {
	if (process.platform !== "win32") return raw;
	const absolute = path.resolve(raw);
	const root = path.parse(absolute).root;
	const parts = absolute.slice(root.length).split(path.sep);
	let cursor = root;
	let index = 0;
	for (const part of parts) {
		const next = path.join(cursor, part);
		try {
			if (fs.lstatSync(next).isSymbolicLink()) break;
		} catch {
			break;
		}
		cursor = next;
		index += 1;
	}
	try {
		const resolved = fs.realpathSync.native(cursor);
		return fs.existsSync(resolved) ? path.join(resolved, ...parts.slice(index)) : raw;
	} catch {
		return raw;
	}
}
const DEFAULT_SKILLS_WATCH_IGNORED = [
	/(^|[\\/])\.git([\\/]|$)/,
	/(^|[\\/])node_modules([\\/]|$)/,
	/(^|[\\/])dist([\\/]|$)/,
	/(^|[\\/])\.venv([\\/]|$)/,
	/(^|[\\/])venv([\\/]|$)/,
	/(^|[\\/])__pycache__([\\/]|$)/,
	/(^|[\\/])\.mypy_cache([\\/]|$)/,
	/(^|[\\/])\.pytest_cache([\\/]|$)/,
	/(^|[\\/])build([\\/]|$)/,
	/(^|[\\/])\.cache([\\/]|$)/
];
function shouldIgnoreSkillsWatchPath(watchPath, stats, usePolling = false) {
	if (DEFAULT_SKILLS_WATCH_IGNORED.some((re) => re.test(watchPath))) return true;
	if (!stats || stats.isDirectory?.() || stats.isSymbolicLink?.()) return false;
	return !usePolling || !isSkillDiscoveryFileWatchPath(watchPath);
}
function isSkillDiscoveryFileWatchPath(watchPath) {
	const basename = path.posix.basename(watchPath.replaceAll("\\", "/"));
	return (basename === "SKILL.md" || basename === "source-origin.json") && !DEFAULT_SKILLS_WATCH_IGNORED.some((re) => re.test(watchPath));
}
function getRawWatchedPath(details) {
	return isRecord(details) && typeof details.watchedPath === "string" ? details.watchedPath : void 0;
}
function rawPathToString(rawPath) {
	const decoded = Buffer.isBuffer(rawPath) ? rawPath.toString() : rawPath;
	return typeof decoded === "string" ? decoded || void 0 : void 0;
}
function resolveRawSkillsWatchPath(rawPath, details) {
	if (path.isAbsolute(rawPath)) return rawPath;
	const watchedPath = getRawWatchedPath(details);
	return watchedPath ? path.join(watchedPath, rawPath) : void 0;
}
function createSkillsWatchPathFilter(root, usePolling) {
	const directorySymlinks = /* @__PURE__ */ new Set();
	const contains = (watchPath) => isPathInside(root, watchPath) || isPathInside(watchPath, root);
	const isSupportingPath = (watchPath) => isPathInside(root, watchPath) && !DEFAULT_SKILLS_WATCH_IGNORED.some((re) => re.test(watchPath));
	return {
		isSupportingPath,
		isStructuralRaw: (event, rawPath, details) => {
			const name = rawPathToString(rawPath);
			const changedPath = name ? resolveRawSkillsWatchPath(name, details) : getRawWatchedPath(details);
			if (changedPath && !isSupportingPath(changedPath)) return false;
			if (!name || !changedPath) return true;
			if (!usePolling) return event !== "change";
			return [isRecord(details) ? details.curr : void 0, isRecord(details) ? details.prev : void 0].some((stats) => !isRecord(stats) || typeof stats.isDirectory !== "function" || stats.isDirectory());
		},
		ignored: (watchPath, stats) => {
			if (shouldIgnoreSkillsWatchPath(watchPath, stats, usePolling) || !contains(watchPath)) return true;
			if (stats?.isSymbolicLink?.()) try {
				if (fs.statSync(watchPath).isDirectory()) directorySymlinks.add(toWatchRoot(watchPath));
			} catch {}
			return false;
		},
		isRelevant: (event, changedPath) => {
			const symlinkKey = toWatchRoot(changedPath);
			const directorySymlink = directorySymlinks.has(symlinkKey);
			if (event === "unlink") directorySymlinks.delete(symlinkKey);
			return (isSkillDiscoveryFileWatchPath(changedPath) || event === "addDir" || event === "unlinkDir" || directorySymlink) && !DEFAULT_SKILLS_WATCH_IGNORED.some((re) => re.test(changedPath)) && contains(changedPath);
		}
	};
}
function resolveSkillsWatcherUsePolling() {
	const envPolling = process.env.CHOKIDAR_USEPOLLING;
	if (envPolling === void 0) return process.platform === "os400" || Boolean(process.versions.bun);
	const normalized = envPolling.toLowerCase();
	return Boolean(normalized) && normalized !== "false" && normalized !== "0";
}
function makeSkillsWatchTarget(raw, depth, previousWatchRoot) {
	const watchPath = toWatchRoot(previousWatchRoot ? raw : resolveSkillsWatchPath(raw));
	let watchRoot = watchPath;
	while (!fs.existsSync(watchRoot)) {
		const parent = path.dirname(watchRoot);
		if (parent === watchRoot) break;
		watchRoot = parent;
	}
	if (previousWatchRoot && previousWatchRoot !== watchRoot && isPathInside(previousWatchRoot, watchRoot)) {
		let cursor = previousWatchRoot;
		const parts = path.relative(previousWatchRoot, watchRoot).split(path.sep).filter(Boolean);
		for (const part of ["", ...parts]) {
			cursor = path.join(cursor, part);
			try {
				if (!fs.lstatSync(cursor).isSymbolicLink()) continue;
			} catch {}
			watchRoot = path.dirname(cursor);
			while (!fs.existsSync(watchRoot) && path.dirname(watchRoot) !== watchRoot) watchRoot = path.dirname(watchRoot);
			break;
		}
	}
	return {
		path: watchPath,
		watchRoot: toWatchRoot(watchRoot),
		depth
	};
}
function resolveSkillsWatchAncestors(target, previousAncestorRoot) {
	const ancestorRoot = isPathInside(previousAncestorRoot, target.watchRoot) ? previousAncestorRoot : target.watchRoot;
	const ancestorRoots = [];
	let currentRoot = target.watchRoot;
	while (isPathInside(ancestorRoot, currentRoot)) {
		if (currentRoot !== target.path) ancestorRoots.push(currentRoot);
		const parent = toWatchRoot(path.dirname(currentRoot));
		if (parent === currentRoot) break;
		currentRoot = parent;
	}
	return {
		ancestorRoot,
		ancestorRoots
	};
}
function readBudgetedDirEntries(dir, maxEntries) {
	const entries = [];
	const limit = Math.max(0, maxEntries);
	let handle;
	try {
		handle = fs.opendirSync(dir);
		for (let scanned = 0; scanned < limit; scanned += 1) {
			const entry = handle.readSync();
			if (!entry) return {
				ok: true,
				entries,
				scannedEntryCount: scanned
			};
			entries.push(entry);
		}
		return {
			ok: true,
			entries,
			scannedEntryCount: limit
		};
	} catch {
		return {
			ok: false,
			scannedEntryCount: 0
		};
	} finally {
		handle?.closeSync();
	}
}
//#endregion
//#region src/skills/runtime/refresh-ancestor-native.ts
var SkillsNativeWatchInvalidatedError = class extends Error {};
function isNativeSkillsStructuralChange(changedPath) {
	try {
		const state = fs.lstatSync(changedPath);
		return state.isDirectory() || state.isSymbolicLink();
	} catch {
		return true;
	}
}
function readDirectoryState(watchRoot, entryParent) {
	const state = entryParent ? fs.statSync(watchRoot, { bigint: true }) : fs.lstatSync(watchRoot, { bigint: true });
	if (!state.isDirectory()) throw new SkillsNativeWatchInvalidatedError(`Skills content watch requires a directory: ${watchRoot}`);
	return state;
}
function readRootState(watchRoot, entryParent = false) {
	try {
		return readDirectoryState(watchRoot, entryParent);
	} catch {
		return;
	}
}
function sameRootIdentity(left, right) {
	return left !== void 0 && right !== void 0 && left.dev === right.dev && left.ino === right.ino;
}
function sameRootState(left, right) {
	return left !== void 0 && right !== void 0 && sameRootIdentity(left, right) && left.mode === right.mode && left.uid === right.uid && left.gid === right.gid && left.ctimeNs === right.ctimeNs;
}
var NativeSkillsAncestorWatcher = class extends EventEmitter {
	get admitted() {
		return !this.closed && !this.failed;
	}
	get admittedIdentity() {
		return this.admitted ? this.identity : void 0;
	}
	constructor(watchRoot, ignored, rearm, admission) {
		super();
		this.closed = false;
		this.failed = false;
		this.closure = createDeferredCore();
		const requireExactRoot = admission !== "ancestor";
		const entryParent = admission === "entry-parent";
		try {
			let observationRoot = watchRoot;
			for (;;) {
				let state;
				try {
					state = requireExactRoot ? readDirectoryState(observationRoot, entryParent) : fs.lstatSync(observationRoot, { bigint: true });
				} catch (error) {
					if (requireExactRoot || !hasErrnoCode(error, "ENOENT") && !hasErrnoCode(error, "ENOTDIR")) throw error;
				}
				if (state?.isDirectory()) break;
				const parent = path.dirname(observationRoot);
				if (parent === observationRoot) throw new Error(`No directory can observe Skills root: ${watchRoot}`);
				observationRoot = parent;
			}
			const snapshot = requireExactRoot ? readDirectoryState : readRootState;
			const before = snapshot(observationRoot, entryParent);
			const nativeRootPath = path.toNamespacedPath(observationRoot);
			const rootName = path.basename(observationRoot);
			this.watcher = fs.watch(observationRoot, (event, filename) => {
				if (this.closed || this.failed) return;
				const name = rawPathToString(filename);
				const explicitRootChange = !name || event === "rename" && name === nativeRootPath;
				const currentRoot = !explicitRootChange && (event === "rename" || name === rootName) ? readRootState(observationRoot, entryParent) : void 0;
				const rootChanged = explicitRootChange || event === "rename" && !sameRootIdentity(registeredIdentity, currentRoot) || name === rootName && !sameRootState(registeredRoot, currentRoot);
				const details = { watchedPath: observationRoot };
				const changedPath = rootChanged ? observationRoot : resolveRawSkillsWatchPath(name, details);
				if (!changedPath) return;
				const reconcileEntry = process.platform === "win32" && !rootChanged;
				if (reconcileEntry) this.emit("reconcile", changedPath, event !== "change" || isNativeSkillsStructuralChange(changedPath));
				if (this.closed || this.failed || ignored(changedPath)) return;
				if (!reconcileEntry) this.emit("all", event === "rename" || rootChanged ? "ancestor" : "change", changedPath);
				if (!this.closed && !this.failed) this.emit("raw", event, filename, details);
				if (!this.closed && !this.failed && (rootChanged || observationRoot !== watchRoot && readRootState(watchRoot, entryParent))) rearm();
			});
			this.watcher.on("close", () => this.closure.resolve());
			this.watcher.on("error", (error) => {
				this.failed = true;
				this.closure.resolve();
				if (this.closed) return;
				if (process.platform === "win32" && error.code === "EPERM") try {
					if (entryParent) fs.statSync(observationRoot);
					else fs.lstatSync(observationRoot);
				} catch (stateError) {
					if (hasErrnoCode(stateError, "ENOENT") || hasErrnoCode(stateError, "ENOTDIR")) {
						this.emit("all", "ancestor", observationRoot);
						if (!this.closed) rearm();
						return;
					}
				}
				this.emit("error", error);
			});
			const after = snapshot(observationRoot, entryParent);
			const registeredIdentity = sameRootIdentity(before, after) ? after : void 0;
			if (requireExactRoot && !registeredIdentity) throw new SkillsNativeWatchInvalidatedError(`Skills directory changed during native registration: ${watchRoot}`);
			this.identity = registeredIdentity;
			const registeredRoot = sameRootState(before, after) ? after : void 0;
			queueMicrotask(() => {
				if (this.closed || this.failed) return;
				if (observationRoot !== watchRoot && readRootState(watchRoot, entryParent)) {
					rearm();
					return;
				}
				this.emit("ready");
			});
		} catch (error) {
			this.failed = true;
			if (this.watcher) this.closing = trackSkillsWatcherClose(() => {
				this.watcher.close();
				return this.closure.promise;
			});
			else this.closure.resolve();
			queueMicrotask(() => {
				if (!this.closed) this.emit("error", error);
			});
		}
	}
	close() {
		this.closed = true;
		this.removeAllListeners();
		if (this.closing) return this.closing;
		this.closing = trackSkillsWatcherClose(() => {
			this.watcher?.close();
			return this.closure.promise;
		});
		return this.closing;
	}
};
function createNativeSkillsAncestorWatcher(watchRoot, ignored, rearm, admission = "ancestor") {
	return new NativeSkillsAncestorWatcher(watchRoot, ignored, rearm, admission);
}
//#endregion
//#region src/skills/runtime/refresh-content-native.ts
function sameEntry(left, right) {
	return left !== void 0 && right !== void 0 && left.kind === right.kind && left.identity.dev === right.identity.dev && left.identity.ino === right.identity.ino && (left.kind === "directory" || left.identity.ctimeNs === right.identity.ctimeNs && left.target === right.target);
}
function missing(error) {
	return hasErrnoCode(error, "ENOENT") || hasErrnoCode(error, "ENOTDIR");
}
function readSymlinkEntry(candidate, identity) {
	if (!identity.isSymbolicLink()) return;
	try {
		return {
			kind: "symlink",
			identity,
			target: fs.readlinkSync(candidate)
		};
	} catch (error) {
		if (missing(error) || hasErrnoCode(error, "EINVAL")) return;
		throw error;
	}
}
function sameDirectory(left, right) {
	return left !== void 0 && right.isDirectory() && left.dev === right.dev && left.ino === right.ino;
}
var NativeSkillsContentWatcher = class extends EventEmitter {
	constructor(root, depth, ignored) {
		super();
		this.root = root;
		this.depth = depth;
		this.ignored = ignored;
		this.closed = false;
		this.failed = false;
		this.ready = false;
		this.dirty = false;
		this.registrations = /* @__PURE__ */ new Map();
		this.entries = /* @__PURE__ */ new Map();
		this.retired = /* @__PURE__ */ new Set();
		queueMicrotask(() => this.rescan());
	}
	get directories() {
		return new Set(this.registrations.keys());
	}
	owns(directory, registration) {
		return !this.closed && this.registrations.get(directory) === registration;
	}
	retire(directory, descendants = true) {
		for (const [candidate, registration] of this.registrations) {
			if (candidate !== directory && (!descendants || !isPathInside(directory, candidate))) continue;
			this.registrations.delete(candidate);
			const closing = registration.watcher.close();
			this.retired.add(closing);
			closing.then((result) => {
				if (!result.ok) this.retirementFailure ??= result;
				this.retired.delete(closing);
			});
		}
	}
	acquire(directory, identity, entryParent) {
		const previous = this.registrations.get(directory);
		if (previous && sameDirectory(previous.watcher.admittedIdentity, identity)) return previous;
		if (previous) this.retire(directory);
		const watcher = createNativeSkillsAncestorWatcher(directory, this.ignored, () => {
			if (this.owns(directory, registration)) {
				this.retire(directory);
				this.rescan();
			}
		}, entryParent ? "entry-parent" : "directory");
		const registration = {
			watcher,
			entryParent
		};
		this.registrations.set(directory, registration);
		watcher.on("reconcile", (changedPath, structural) => {
			if (!this.owns(directory, registration) || !structural && !this.entries.has(toWatchRoot(changedPath))) return;
			this.emit("dirty");
			if (this.owns(directory, registration)) this.rescan();
		});
		watcher.on("all", (event, changedPath) => {
			if (!this.owns(directory, registration)) return;
			if (event === "ancestor" && changedPath === directory) this.emit("all", "unlinkDir", directory);
			else if (event === "change" && this.isStructuralChange(changedPath)) this.rescan();
		});
		watcher.on("raw", (event, filename, details) => {
			if (!this.owns(directory, registration)) return;
			this.emit("raw", event, filename, details);
			if (this.owns(directory, registration) && (event !== "change" || !filename)) this.rescan();
		});
		watcher.on("error", (error) => {
			if (this.owns(directory, registration)) {
				this.retire(directory);
				if (!entryParent && directory !== this.root && (missing(error) || error instanceof SkillsNativeWatchInvalidatedError)) this.rescan();
				else this.fail(error);
			}
		});
		return registration;
	}
	isStructuralChange(changedPath) {
		return this.entries.has(toWatchRoot(changedPath)) || isNativeSkillsStructuralChange(changedPath);
	}
	fail(error) {
		if (!this.closed) {
			this.failed = true;
			this.emit("error", error);
		}
	}
	readRootState() {
		try {
			return fs.lstatSync(this.root, { bigint: true });
		} catch (error) {
			if (!missing(error)) throw error;
			const parent = toWatchRoot(path.dirname(this.root));
			const registration = this.registrations.get(parent);
			const admittedIdentity = registration?.watcher.admittedIdentity;
			if (!registration?.entryParent || !this.owns(parent, registration) || !admittedIdentity) throw error;
			if (!sameDirectory(admittedIdentity, fs.statSync(parent, { bigint: true }))) throw new SkillsNativeWatchInvalidatedError(`Skills entry observation changed before root reconciliation: ${parent}`);
			return;
		}
	}
	async scan() {
		const discovered = /* @__PURE__ */ new Map();
		const announced = /* @__PURE__ */ new Set();
		const visited = /* @__PURE__ */ new Map();
		const entryParent = !this.readRootState()?.isDirectory();
		const rootDirectory = entryParent ? toWatchRoot(path.dirname(this.root)) : this.root;
		const queue = [{
			directory: rootDirectory,
			depth: 0
		}];
		for (const current of queue) {
			if (this.closed || this.dirty || current.parent && !this.owns(current.parent.directory, current.parent.registration)) return;
			const { directory, depth } = current;
			let identity;
			try {
				identity = entryParent ? fs.statSync(directory, { bigint: true }) : fs.lstatSync(directory, { bigint: true });
			} catch (error) {
				if (directory !== rootDirectory && missing(error)) continue;
				throw error;
			}
			if (!identity.isDirectory()) {
				if (entryParent) throw new Error(`Skills entry observation requires a directory: ${directory}`);
				if (directory === rootDirectory) {
					this.dirty = true;
					return;
				}
				continue;
			}
			if (this.ignored(directory, identity)) continue;
			const registration = this.acquire(directory, identity, entryParent);
			const admittedIdentity = registration.watcher.admittedIdentity;
			if (!admittedIdentity) return;
			visited.set(directory, registration);
			if (entryParent) {
				const currentRoot = this.readRootState();
				if (currentRoot?.isDirectory()) {
					this.dirty = true;
					return;
				}
				if (currentRoot?.isSymbolicLink() && !this.ignored(this.root, currentRoot)) {
					const entry = readSymlinkEntry(this.root, currentRoot);
					if (!entry) {
						this.dirty = true;
						return;
					}
					discovered.set(this.root, entry);
				}
				if (currentRoot && !sameDirectory(admittedIdentity, fs.statSync(directory, { bigint: true }))) {
					this.retire(directory);
					this.dirty = true;
					return;
				}
				continue;
			}
			const directoryEntry = {
				kind: "directory",
				identity: admittedIdentity
			};
			discovered.set(directory, directoryEntry);
			if (this.ready && !sameEntry(this.entries.get(directory), directoryEntry)) {
				announced.add(directory);
				this.emit("all", "addDir", directory);
				if (this.dirty || !this.owns(directory, registration)) return;
			}
			let children;
			try {
				children = await fs$1.readdir(directory, { withFileTypes: true });
			} catch (error) {
				if (!this.owns(directory, registration)) return;
				if (!missing(error)) throw error;
			}
			if (this.dirty || !this.owns(directory, registration)) return;
			let after;
			try {
				after = fs.lstatSync(directory, { bigint: true });
			} catch (error) {
				if (!missing(error)) throw error;
				discovered.delete(directory);
				visited.delete(directory);
				this.retire(directory);
				if (this.ready) this.emit("all", "unlinkDir", directory);
				if (directory === rootDirectory) {
					this.dirty = true;
					return;
				}
				continue;
			}
			if (!sameDirectory(admittedIdentity, after)) {
				this.retire(directory);
				this.dirty = true;
				return;
			}
			if (!children) {
				this.dirty = true;
				return;
			}
			for (const child of children) {
				const candidate = toWatchRoot(path.join(directory, child.name));
				if (this.ignored(candidate, child)) continue;
				if (child.isSymbolicLink()) try {
					const entry = readSymlinkEntry(candidate, fs.lstatSync(candidate, { bigint: true }));
					if (!entry) {
						this.dirty = true;
						return;
					}
					discovered.set(candidate, entry);
				} catch (error) {
					if (!missing(error)) throw error;
				}
				else if (child.isDirectory() && depth < this.depth) queue.push({
					directory: candidate,
					depth: depth + 1,
					parent: {
						directory,
						registration
					}
				});
			}
		}
		if (this.closed || this.dirty) return;
		for (const [directory, registration] of visited) if (!this.owns(directory, registration)) return;
		for (const directory of this.registrations.keys()) if (!visited.has(directory)) this.retire(directory, false);
		const previous = this.entries;
		this.entries = discovered;
		if (this.ready) {
			for (const [entry, fact] of previous) if (!announced.has(entry) && !sameEntry(discovered.get(entry), fact)) {
				if (fact.kind === "symlink" && discovered.get(entry)?.kind === "symlink") continue;
				this.emit("all", fact.kind === "directory" ? "unlinkDir" : "unlink", entry);
				if (this.closed) return;
			}
			for (const [entry, fact] of discovered) if (!announced.has(entry) && !sameEntry(previous.get(entry), fact)) {
				const event = fact.kind === "directory" ? "addDir" : previous.get(entry)?.kind === "symlink" ? "change" : "add";
				this.emit("all", event, entry);
				if (this.closed) return;
			}
		}
	}
	rescan() {
		if (this.closed) return;
		this.dirty = true;
		if (this.scanning) return;
		this.scanning = (async () => {
			try {
				while (this.dirty && !this.closed) {
					this.dirty = false;
					await this.scan();
				}
				if (!this.closed && !this.failed && !this.ready) {
					this.ready = true;
					this.emit("ready");
				}
			} catch (error) {
				this.fail(error);
			} finally {
				this.scanning = void 0;
			}
		})();
	}
	close() {
		if (this.closing) return this.closing;
		this.closed = true;
		this.removeAllListeners();
		for (const directory of this.registrations.keys()) this.retire(directory);
		const scanning = this.scanning;
		this.closing = trackSkillsWatcherClose(async () => {
			await scanning;
			await Promise.all(this.retired);
			const failed = this.retirementFailure;
			if (failed && !failed.ok) throw failed.error;
		});
		return this.closing;
	}
};
function createNativeSkillsContentWatcher(root, options) {
	return new NativeSkillsContentWatcher(root, options.depth, options.ignored);
}
//#endregion
//#region src/skills/runtime/refresh-watch-transport.ts
function shouldUseNativeSkillsWatcher(usePolling) {
	return !usePolling && !process.versions.bun && (process.platform === "linux" || process.platform === "win32");
}
function adaptChokidarSkillsWatcher(watcher) {
	let closing;
	return {
		get closed() {
			return watcher.closed;
		},
		get directories() {
			return new Set(Object.keys(watcher.getWatched()));
		},
		on: (event, listener) => event === "dirty" ? void 0 : watcher.on(event, listener),
		close: () => closing ??= teardownSkillsPathWatcher({ watcher })
	};
}
function createSkillsContentWatchFactory(runInContext) {
	return (target, usePolling, ignored, stabilityThreshold) => runInContext(() => shouldUseNativeSkillsWatcher(usePolling) ? createNativeSkillsContentWatcher(target.path, {
		depth: target.depth + 1,
		ignored
	}) : adaptChokidarSkillsWatcher(chokidar.watch(target.path, {
		ignoreInitial: true,
		followSymlinks: false,
		usePolling,
		depth: target.depth + 1,
		awaitWriteFinish: {
			stabilityThreshold,
			pollInterval: 100
		},
		ignored
	})));
}
//#endregion
//#region src/skills/runtime/refresh-ancestor-watch.ts
const runInWatcherContext = AsyncLocalStorage.snapshot();
const ancestorWatchers = /* @__PURE__ */ new Map();
function createAncestorWatcher(watchRoot, usePolling, subscriptions) {
	const ignored = (candidate, stats) => {
		let allIgnored = true;
		for (const current of subscriptions) if (!current.ignored(candidate, stats)) allIgnored = false;
		return allIgnored;
	};
	return runInWatcherContext(() => {
		if (shouldUseNativeSkillsWatcher(usePolling)) {
			const watcher = createNativeSkillsAncestorWatcher(watchRoot, ignored, () => {
				const current = ancestorWatchers.get(watchRoot);
				if (current?.watcher === watcher && !current.retiring) replaceAncestorWatcher(watchRoot, usePolling, current);
			});
			watcher.on("reconcile", (_changedPath, structural) => {
				const current = ancestorWatchers.get(watchRoot);
				if (!structural || current?.watcher !== watcher || current.retiring) return;
				for (const target of Array.from(subscriptions)) if (current.watcher === watcher && !watcher.closed && subscriptions.has(target)) target.reconcile();
			});
			return {
				watcher,
				close: () => watcher.close(),
				observationRoot: watchRoot
			};
		}
		const watcher = chokidar.watch(watchRoot, {
			ignoreInitial: true,
			followSymlinks: false,
			usePolling,
			depth: 0,
			ignored
		});
		let observationRoot = watchRoot;
		if (process.platform === "darwin" && !usePolling) {
			const parent = path.dirname(watchRoot);
			watcher.add(parent);
			observationRoot = parent;
		}
		return {
			watcher,
			close: () => teardownSkillsPathWatcher({ watcher }),
			observationRoot
		};
	});
}
function observeAncestorWatcher(current) {
	const { watcher, subscriptions } = current;
	const isCurrent = () => current.watcher === watcher && !current.retiring && !watcher.closed;
	const publish = (notify) => {
		for (const target of Array.from(subscriptions)) if (isCurrent() && subscriptions.has(target)) notify(target);
	};
	watcher.on("ready", () => {
		if (!isCurrent() || current.error) return;
		current.ready = true;
		publish((target) => target.ready());
	});
	watcher.on("all", (event, changedPath) => {
		publish((target) => {
			if (isPathInside(changedPath, target.path) || isPathInside(target.path, changedPath)) target.changed(event, changedPath);
		});
	});
	watcher.on("raw", (event, rawPath, details) => {
		publish((target) => target.raw(event, rawPath, details));
	});
	watcher.on("error", (error) => {
		if (!isCurrent()) return;
		current.ready = false;
		const watchError = toErrorObject(error, "Skills ancestor watcher failed");
		current.error = watchError;
		publish((target) => target.error(watchError, current.observationRoot));
	});
}
function replaceAncestorWatcher(watchRoot, usePolling, current) {
	if (current.retiring) return current.retiring;
	current.ready = false;
	const retiring = current.close();
	current.retiring = retiring;
	for (const target of Array.from(current.subscriptions)) if (current.subscriptions.has(target)) target.unavailable();
	retiring.then((result) => {
		if (ancestorWatchers.get(watchRoot) !== current) return;
		if (!result.ok) {
			current.error = toErrorObject(result.error, "Skills ancestor watcher retirement failed");
			for (const target of Array.from(current.subscriptions)) if (current.subscriptions.has(target)) target.error(current.error, current.observationRoot);
			return;
		}
		if (current.subscriptions.size === 0) {
			ancestorWatchers.delete(watchRoot);
			return;
		}
		Object.assign(current, createAncestorWatcher(watchRoot, usePolling, current.subscriptions));
		current.retiring = void 0;
		current.error = void 0;
		observeAncestorWatcher(current);
	});
	return retiring;
}
function acquireSkillsAncestorWatcher(watchRoot, usePolling, subscription) {
	let group = ancestorWatchers.get(watchRoot);
	if (!group) {
		const subscriptions = /* @__PURE__ */ new Set([subscription]);
		group = {
			...createAncestorWatcher(watchRoot, usePolling, subscriptions),
			ready: false,
			subscriptions
		};
		ancestorWatchers.set(watchRoot, group);
		observeAncestorWatcher(group);
	} else group.subscriptions.add(subscription);
	const current = group;
	const watcher = current.watcher;
	if (current.error && !current.retiring) queueMicrotask(() => {
		if (ancestorWatchers.get(watchRoot) === current && current.subscriptions.has(subscription) && current.error && !current.retiring) replaceAncestorWatcher(watchRoot, usePolling, current);
	});
	let released;
	if (current.ready || current.error) queueMicrotask(() => {
		if (ancestorWatchers.get(watchRoot) === current && current.watcher === watcher && current.subscriptions.has(subscription)) {
			if (current.ready && !watcher.closed && !current.retiring) subscription.ready();
			else if (current.error) subscription.error(current.error, current.observationRoot);
		}
	});
	return { release: () => {
		if (released) return released;
		if (current.subscriptions.delete(subscription) && current.subscriptions.size === 0) released = replaceAncestorWatcher(watchRoot, usePolling, current);
		else released = current.retiring ?? Promise.resolve(ok(void 0));
		return released;
	} };
}
function resetSkillsAncestorWatchersForTest() {
	ancestorWatchers.clear();
}
//#endregion
//#region src/skills/runtime/refresh-content-watch.ts
/** Keep native coverage while a directory rescan establishes its replacement. */
function createSkillsContentWatcher(params) {
	let closed = false;
	let published = false;
	let revision = 0;
	let active;
	let pending;
	let closing;
	let retirementFailure;
	const retiring = /* @__PURE__ */ new Set();
	const owns = (generation) => !closed && !generation.retired && params.isCurrent() && (active === generation || pending === generation);
	const retire = (generation) => {
		generation.retired = true;
		const retirement = generation.watcher.close();
		retiring.add(retirement);
		retirement.then((result) => {
			if (!result.ok) retirementFailure ??= result;
			retiring.delete(retirement);
		});
	};
	const rescan = () => {
		if (!closed && params.isCurrent() && (active.ready || active.errored) && !pending) pending = create();
	};
	const create = () => {
		const generation = {
			watcher: params.watch(),
			revision,
			ready: false,
			readyDirectories: /* @__PURE__ */ new Set(),
			errored: false,
			retired: false
		};
		const { watcher } = generation;
		watcher.on("ready", () => {
			if (!owns(generation) || generation.ready) return;
			generation.ready = true;
			generation.readyDirectories = new Set(watcher.directories);
			if (generation === active) {
				rescan();
				return;
			}
			pending = void 0;
			if (generation.revision !== revision) {
				retire(generation);
				rescan();
				return;
			}
			const previous = active;
			active = generation;
			retire(previous);
			if (previous.errored || Array.from(generation.readyDirectories).some((directory) => !previous.readyDirectories.has(directory))) {
				rescan();
				return;
			}
			const isRescan = published;
			published = true;
			params.ready(isRescan);
		});
		watcher.on("all", (event, changedPath) => {
			if (owns(generation)) params.changed(event, changedPath);
		});
		watcher.on("dirty", () => {
			if (owns(generation)) revision += 1;
		});
		watcher.on("raw", (event, rawPath, details) => {
			if (!owns(generation)) return;
			if (params.isStructuralRaw(event, rawPath, details)) revision += 1;
			params.raw(event, rawPath, details);
		});
		watcher.on("error", (error) => {
			if (!owns(generation)) return;
			generation.errored = true;
			const isRescan = generation === pending;
			if (isRescan) {
				pending = void 0;
				retire(generation);
			}
			params.error(error, isRescan);
		});
		return generation;
	};
	active = create();
	return {
		rescan,
		structureChanged() {
			revision += 1;
		},
		close() {
			if (closing) return closing;
			closed = true;
			retire(active);
			if (pending) {
				retire(pending);
				pending = void 0;
			}
			closing = trackSkillsWatcherClose(async () => {
				await Promise.all(retiring);
				if (retirementFailure && !retirementFailure.ok) throw retirementFailure.error;
			});
			return closing;
		}
	};
}
//#endregion
//#region src/skills/runtime/refresh-file-stability.ts
const RAW_SKILL_FILE_POLL_INTERVAL_MS = 100;
function readFileStabilitySnapshot(filePath) {
	try {
		const stat = fs.statSync(filePath);
		return stat.isFile() ? {
			size: stat.size,
			mtimeMs: stat.mtimeMs
		} : void 0;
	} catch {
		return;
	}
}
async function waitForStableSkillFile(filePath, stabilityMs, watcher, readRevision) {
	if (watcher.closed || stabilityMs <= 0) return;
	let previousRevision = readRevision();
	let previous = readFileStabilitySnapshot(filePath);
	if (!previous) return;
	let stableForMs = 0;
	while (stableForMs < stabilityMs) {
		const delayMs = Math.min(RAW_SKILL_FILE_POLL_INTERVAL_MS, stabilityMs - stableForMs);
		await new Promise((resolve) => {
			setTimeout(resolve, delayMs);
		});
		if (watcher.closed) return;
		const nextRevision = readRevision();
		const next = readFileStabilitySnapshot(filePath);
		if (!next) return;
		if (nextRevision === previousRevision && next.size === previous.size && next.mtimeMs === previous.mtimeMs) {
			stableForMs += delayMs;
			continue;
		}
		previous = next;
		previousRevision = nextRevision;
		stableForMs = 0;
	}
}
function createRawSkillFileScheduler({ watcher, stabilityMs, schedule, onError }) {
	const pendingRawFiles = /* @__PURE__ */ new Map();
	return (changedPath) => {
		if (watcher.closed) return;
		const pending = pendingRawFiles.get(changedPath);
		if (pending) {
			pending.revision += 1;
			return;
		}
		const current = { revision: 0 };
		pendingRawFiles.set(changedPath, current);
		(async () => {
			try {
				while (!watcher.closed) {
					let sampledRevision = current.revision;
					await waitForStableSkillFile(changedPath, stabilityMs, watcher, () => {
						sampledRevision = current.revision;
						return sampledRevision;
					}).catch((err) => onError(changedPath, err));
					if (current.revision !== sampledRevision) continue;
					schedule(changedPath);
					return;
				}
			} finally {
				pendingRawFiles.delete(changedPath);
			}
		})();
	};
}
//#endregion
//#region src/skills/runtime/refresh-remote.ts
const log$1 = createSubsystemLogger("gateway/skills");
const runInSkillsWatcherContext = AsyncLocalStorage.snapshot();
const remoteWatchers = /* @__PURE__ */ new Map();
const remoteWatchTasks = /* @__PURE__ */ new Set();
function ensureRemoteSkillsWatcher(params) {
	const { watcherKey, workspaceDir, access } = params;
	const drainSignal = getGatewayRestartDrainSignal();
	if (drainSignal.aborted) return;
	const request = {
		sourcePlan: params.sourcePlan,
		executionWorkspaceDir: params.executionWorkspaceDir
	};
	const signature = JSON.stringify(request);
	const previous = remoteWatchers.get(watcherKey);
	if (previous?.access === access && previous.signature === signature) {
		if (previous.unavailable) bumpSkillsSnapshotVersion({
			workspaceDir,
			reason: "remote-node"
		});
		return;
	}
	disposeRemoteSkillsWatcher(watcherKey);
	const state = {
		access,
		signature,
		controller: new AbortController(),
		unavailable: !access.watchSkills
	};
	remoteWatchers.set(watcherKey, state);
	bumpSkillsSnapshotVersion({
		workspaceDir,
		reason: "remote-node"
	});
	const watch = access.watchSkills;
	if (!watch) return;
	const signal = AbortSignal.any([state.controller.signal, drainSignal]);
	const isCurrent = () => remoteWatchers.get(watcherKey) === state && !signal.aborted;
	const task = runInSkillsWatcherContext(async () => {
		try {
			await watch(request, (event) => {
				if (!isCurrent()) return;
				if (event === "unavailable") state.unavailable = true;
				else if (event === "available") {
					state.unavailable = false;
					return;
				}
				bumpSkillsSnapshotVersion({
					workspaceDir,
					reason: "remote-node"
				});
			}, signal);
		} catch (error) {
			if (isCurrent()) log$1.warn(`remote skills watcher stopped (${workspaceDir}): ${String(error)}`);
		} finally {
			if (remoteWatchers.get(watcherKey) === state) {
				remoteWatchers.delete(watcherKey);
				if (!signal.aborted) bumpSkillsSnapshotVersion({
					workspaceDir,
					reason: "remote-node"
				});
			}
		}
	});
	remoteWatchTasks.add(task);
	task.finally(() => remoteWatchTasks.delete(task));
}
function disposeRemoteSkillsWatcher(watcherKey) {
	const state = remoteWatchers.get(watcherKey);
	remoteWatchers.delete(watcherKey);
	state?.controller.abort();
	return Boolean(state);
}
async function closeRemoteSkillsWatchers() {
	for (const key of remoteWatchers.keys()) disposeRemoteSkillsWatcher(key);
	await Promise.all(remoteWatchTasks);
}
//#endregion
//#region src/skills/runtime/refresh-watch-registry.ts
const pathWatchers = /* @__PURE__ */ new Map();
const workspaceWatchTargets = /* @__PURE__ */ new Map();
const workspaceWatchOwners = /* @__PURE__ */ new Map();
const workspaceWatchTargetCache = /* @__PURE__ */ new Map();
const uncertainPooledObservationRoots = /* @__PURE__ */ new Set();
const hasUncertainPooledCoverage = (root) => Array.from(uncertainPooledObservationRoots).some((lost) => isPathInside(root, lost) || isPathInside(lost, root));
function recordPooledObservationLoss(root) {
	uncertainPooledObservationRoots.add(root);
	const changes = [];
	for (const [targetPath, state] of pathWatchers) {
		if (state.closed || !state.pooledNative || !hasUncertainPooledCoverage(targetPath)) continue;
		state.verified = false;
		if (!state.unavailable) {
			state.unavailable = true;
			changes.push({
				targetPath,
				state,
				watcherKeys: state.subscribers,
				change: "unavailable"
			});
		}
	}
	publishSkillsWatchChanges(changes);
}
function unsubscribeWorkspaceFromPath(workspaceDir, watchTarget) {
	const state = pathWatchers.get(watchTarget.path);
	if (!state) return;
	state.subscribers.delete(workspaceDir);
	if (state.subscribers.size === 0) state.close().then((result) => {
		if (result.ok && state.subscribers.size === 0 && pathWatchers.get(watchTarget.path) === state) pathWatchers.delete(watchTarget.path);
	});
}
const hasUnreadySharedTargets = (watcherKey) => (workspaceWatchTargets.get(watcherKey) ?? []).some((target) => !target.executionOnly && pathWatchers.get(target.path)?.initialScan !== "ready");
function hasVerifiedCoverage(watcherKey) {
	return workspaceWatchTargets.get(watcherKey)?.every((target) => {
		const state = pathWatchers.get(target.path);
		return state && !state.closed && state.verified && !state.unavailable;
	}) ?? false;
}
function publishRecoveredCoverage() {
	for (const [watcherKey, owner] of workspaceWatchOwners) if (owner.unavailable && hasVerifiedCoverage(watcherKey)) {
		owner.unavailable = false;
		notifySkillsWatchAvailable({
			workspaceDir: owner.workspaceDir,
			sourceScope: owner.sourceScope
		});
	}
}
function publishSkillsWatchChanges(changes) {
	const affected = /* @__PURE__ */ new Map();
	for (const pending of changes) for (const watcherKey of pending.watcherKeys) {
		const owner = workspaceWatchOwners.get(watcherKey);
		const targets = workspaceWatchTargets.get(watcherKey);
		if (owner && targets) {
			const entries = affected.get(owner.workspaceDir) ?? [];
			entries.push({
				pending,
				watcherKey,
				targets
			});
			affected.set(owner.workspaceDir, entries);
		}
	}
	for (const [workspaceDir, entries] of affected) {
		const scopes = /* @__PURE__ */ new Map();
		let changedPath;
		let unavailable = false;
		for (const { pending, watcherKey, targets } of entries) {
			const owner = workspaceWatchOwners.get(watcherKey);
			const { targetPath, state, change } = pending;
			if (!owner || state.closed || pathWatchers.get(targetPath) !== state || !state.subscribers.has(watcherKey) || workspaceWatchTargets.get(watcherKey) !== targets) continue;
			if (change !== "supporting") {
				workspaceWatchTargetCache.delete(watcherKey);
				changedPath = pending.changedPath;
			}
			unavailable ||= change === "unavailable";
			owner.unavailable ||= change === "unavailable";
			const initialScan = change === "initial-scan";
			const shared = initialScan ? owner.sharedScanPending : targets.find((entry) => entry.path === targetPath)?.executionOnly !== true;
			if (initialScan && shared) for (const [key, other] of workspaceWatchOwners) other.sharedScanPending &&= other.workspaceDir !== workspaceDir || hasUnreadySharedTargets(key);
			const kind = change === "supporting" ? "supporting" : "skills";
			if (shared) scopes.set(kind, void 0);
			else if (!scopes.has(kind) || scopes.get(kind)) {
				const selected = scopes.get(kind) ?? [];
				if (!selected.some((scope) => scope.executionWorkspaceDir === owner.sourceScope.executionWorkspaceDir)) selected.push(owner.sourceScope);
				scopes.set(kind, selected);
			}
		}
		if (scopes.has("supporting")) markSkillsSupportingFilesChanged({
			workspaceDir,
			sourceScopes: scopes.get("supporting")
		});
		if (scopes.has("skills")) bumpSkillsSnapshotVersion({
			workspaceDir,
			sourceScopes: scopes.get("skills"),
			reason: unavailable ? "watch-unavailable" : "watch",
			changedPath
		});
	}
}
function flushSkillsWatchChanges(trigger) {
	if (trigger.closed) return;
	const now = performance.now();
	const changes = [];
	for (const [targetPath, state] of pathWatchers) {
		if (state.closed || state.timer === void 0 || state !== trigger && (state.pendingAt === void 0 || state.pendingAt > now)) continue;
		changes.push({
			targetPath,
			state,
			watcherKeys: state.subscribers,
			changedPath: state.pendingPath,
			change: state.pendingChange ?? "skills"
		});
		clearTimeout(state.timer);
		state.timer = void 0;
		state.pendingAt = void 0;
		state.pendingPath = void 0;
		state.pendingChange = void 0;
	}
	publishSkillsWatchChanges(changes);
}
//#endregion
//#region src/skills/runtime/refresh-source-roots.ts
function resolveSkillsWatchSourceRoots(workspaceDir, config, agentId, executionWorkspaceDir, pluginMetadataSnapshot, sourcePlan) {
	const executionRoots = executionWorkspaceDir ? resolveWorkspaceSkillDirectories(executionWorkspaceDir) : [];
	let baseRoots;
	let extraDirs;
	let pluginSkillDirs;
	let allowedSymlinkTargetRealPaths;
	if (sourcePlan) {
		baseRoots = sourcePlan.roots.filter((root) => !["extra", "bundled"].includes(root.tier)).map((root) => ({
			path: root.dir,
			source: root.source
		}));
		extraDirs = sourcePlan.roots.filter((root) => root.tier === "extra").map((root) => root.dir);
		pluginSkillDirs = sourcePlan.pluginSkillRoots.map((root) => root.dir);
		allowedSymlinkTargetRealPaths = resolveAllowedSkillSymlinkTargetRealPaths({ skills: { load: { allowSymlinkTargets: sourcePlan.allowSymlinkTargets } } });
	} else {
		baseRoots = resolveWorkspaceSkillDirectories(workspaceDir).map(({ dir, source }) => ({
			path: dir,
			source
		}));
		baseRoots.push(...resolveWorkshopWatchRoots(config, agentId));
		baseRoots.push({
			path: path.join(CONFIG_DIR, "skills"),
			source: "openclaw-managed"
		});
		if (isDefaultStateDir()) baseRoots.push({
			path: path.join(os.homedir(), ".agents", "skills"),
			source: "agents-skills-personal"
		});
		extraDirs = normalizeTrimmedStringList(config?.skills?.load?.extraDirs).map((dir) => resolveUserPath(dir));
		pluginSkillDirs = (pluginMetadataSnapshot ? resolvePluginSkillRootsFromMetadata({
			workspaceDir,
			config,
			metadataSnapshot: pluginMetadataSnapshot
		}) : resolvePluginSkillRoots({
			workspaceDir,
			config
		})).map((root) => root.dir);
		allowedSymlinkTargetRealPaths = resolveAllowedSkillSymlinkTargetRealPaths(config);
	}
	return {
		executionRoots,
		baseRoots,
		extraDirs,
		pluginSkillDirs,
		allowedSymlinkTargetRealPaths
	};
}
//#endregion
//#region src/skills/runtime/refresh-watch-targets.ts
function skillsWatchTargetsMatch(previous, next) {
	return previous.path === next.path && previous.watchRoot === next.watchRoot && previous.depth === next.depth && previous.executionOnly === next.executionOnly;
}
function compareSkillsWatchTargets(previous, next, covered) {
	const targetsUnchanged = areOrderedArraysEqual(previous, next, skillsWatchTargetsMatch);
	if (targetsUnchanged) return {
		targetsUnchanged,
		sharedTargetsChanged: false
	};
	return {
		targetsUnchanged,
		sharedTargetsChanged: next.some((target) => !target.executionOnly && !covered.some((prior) => skillsWatchTargetsMatch(prior, target))) || previous.some((prior) => !prior.executionOnly && !next.some((target) => skillsWatchTargetsMatch(prior, target)))
	};
}
const GROUPED_SKILLS_WATCH_DEPTH = 6;
const CONFIGURED_ROOT_WATCH_DEPTH = 2;
const MAX_SYMLINK_WATCH_TARGETS_PER_ROOT = 100;
const MAX_SYMLINK_WATCH_DIRECTORY_SCANS_PER_ROOT = 200;
const MAX_SYMLINK_WATCH_RAW_ENTRIES_PER_ROOT = 2e3;
function resolveSkillsWatchTargets(workspaceDir, config, agentId, executionWorkspaceDir, pluginMetadataSnapshot, sourcePlan, cached) {
	const { executionRoots, baseRoots, extraDirs, pluginSkillDirs, allowedSymlinkTargetRealPaths } = resolveSkillsWatchSourceRoots(workspaceDir, config, agentId, executionWorkspaceDir, pluginMetadataSnapshot, sourcePlan);
	const signature = JSON.stringify({
		basePaths: baseRoots.map((root) => toWatchRoot(root.path)),
		executionPaths: executionRoots.map((root) => toWatchRoot(root.dir)),
		extraDirs: extraDirs.map(toWatchRoot),
		pluginSkillDirs: pluginSkillDirs.map(toWatchRoot),
		allowSymlinkTargets: allowedSymlinkTargetRealPaths
	});
	if (cached?.signature === signature) return cached;
	const targets = /* @__PURE__ */ new Map();
	for (const root of baseRoots) addSkillSourceWatchTargets(targets, root.path, root.source, allowedSymlinkTargetRealPaths, GROUPED_SKILLS_WATCH_DEPTH);
	for (const resolved of extraDirs) addSkillSourceWatchTargets(targets, resolved, "openclaw-extra", allowedSymlinkTargetRealPaths);
	for (const dir of pluginSkillDirs) addSkillSourceWatchTargets(targets, dir, "openclaw-plugin", allowedSymlinkTargetRealPaths);
	const executionTargets = /* @__PURE__ */ new Map();
	for (const root of executionRoots) addSkillSourceWatchTargets(executionTargets, root.dir, root.source, allowedSymlinkTargetRealPaths, GROUPED_SKILLS_WATCH_DEPTH);
	for (const [key, target] of executionTargets) {
		const shared = targets.get(key);
		if (shared) shared.depth = Math.max(shared.depth, target.depth);
		else targets.set(key, {
			...target,
			executionOnly: true
		});
	}
	return {
		signature,
		targets: Array.from(targets.values()).toSorted((a, b) => a.path.localeCompare(b.path))
	};
}
function addWatchTarget(targets, raw, depth) {
	const target = makeSkillsWatchTarget(raw, depth);
	target.depth = Math.max(target.depth, targets.get(target.path)?.depth ?? 0);
	targets.set(target.path, target);
}
function addSkillRootWatchTargets(targets, root, rootDepth) {
	addWatchTarget(targets, root, rootDepth);
	const companionSkillsRoot = path.join(root, "skills");
	addWatchTarget(targets, companionSkillsRoot, GROUPED_SKILLS_WATCH_DEPTH);
	return companionSkillsRoot;
}
function addSkillSourceWatchTargets(targets, root, source, allowedSymlinkTargetRealPaths, rootDepth = path.basename(root) === "skills" ? GROUPED_SKILLS_WATCH_DEPTH : CONFIGURED_ROOT_WATCH_DEPTH) {
	const companionSkillsRoot = addSkillRootWatchTargets(targets, root, rootDepth);
	const rootRealPath = resolveRealpathOrAbsolute(root);
	addTrustedSymlinkSkillWatchTargets(targets, root, source, allowedSymlinkTargetRealPaths, rootDepth, rootRealPath, rootRealPath);
	addTrustedSymlinkSkillWatchTargets(targets, companionSkillsRoot, source, allowedSymlinkTargetRealPaths, GROUPED_SKILLS_WATCH_DEPTH, rootRealPath, resolveRealpathOrAbsolute(companionSkillsRoot));
}
function addTrustedSymlinkSkillWatchTargets(targets, root, source, allowedSymlinkTargetRealPaths, maxDepth, containmentRootRealPath, rootRealPath) {
	try {
		if (fs.lstatSync(root).isSymbolicLink() && isTrustedSymlinkSkillTarget(source, containmentRootRealPath, rootRealPath, allowedSymlinkTargetRealPaths)) addSkillRootWatchTargets(targets, rootRealPath, maxDepth);
	} catch {
		return;
	}
	const queue = [{
		dir: root,
		depth: 0
	}];
	let watched = 0;
	let directoryScans = 0;
	let rawEntries = 0;
	for (const current of queue) {
		if (watched >= MAX_SYMLINK_WATCH_TARGETS_PER_ROOT || directoryScans >= MAX_SYMLINK_WATCH_DIRECTORY_SCANS_PER_ROOT || rawEntries >= MAX_SYMLINK_WATCH_RAW_ENTRIES_PER_ROOT) break;
		const scan = readBudgetedDirEntries(current.dir, MAX_SYMLINK_WATCH_RAW_ENTRIES_PER_ROOT - rawEntries);
		directoryScans += 1;
		rawEntries += scan.scannedEntryCount;
		if (!scan.ok) continue;
		for (const entry of scan.entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
			if (watched >= MAX_SYMLINK_WATCH_TARGETS_PER_ROOT) break;
			if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
			const childPath = path.join(current.dir, entry.name);
			if (DEFAULT_SKILLS_WATCH_IGNORED.some((re) => re.test(childPath))) continue;
			if (entry.isSymbolicLink()) {
				const targetRealPath = tryRealpath(childPath);
				if (targetRealPath && isTrustedSymlinkSkillTarget(source, containmentRootRealPath, targetRealPath, allowedSymlinkTargetRealPaths)) {
					addSkillRootWatchTargets(targets, targetRealPath, GROUPED_SKILLS_WATCH_DEPTH);
					watched += 1;
				}
				continue;
			}
			if (entry.isDirectory() && current.depth < maxDepth) queue.push({
				dir: childPath,
				depth: current.depth + 1
			});
		}
	}
}
//#endregion
//#region src/skills/runtime/refresh.ts
const log = createSubsystemLogger("gateway/skills");
const watchContent = createSkillsContentWatchFactory(AsyncLocalStorage.snapshot());
const SKILLS_WATCH_DEBOUNCE_MS = 250;
const failedContentPaths = /* @__PURE__ */ new Set();
const retiringWatchers = /* @__PURE__ */ new Set();
const replacingWatchers = /* @__PURE__ */ new Set();
let watchersClosing = false;
let nativeWatchCapacityFailed = false;
const workspaceWatchLastEnsuredAt = /* @__PURE__ */ new Map();
const SKILLS_WORKSPACE_WATCH_IDLE_TTL_MS = 36e5;
const MAX_SKILLS_WORKSPACE_WATCH_STATES = 128;
setSkillsChangeListenerErrorHandler((err) => {
	log.warn(`skills change listener failed: ${String(err)}`);
});
function createSkillsPathWatcher(target, previousAncestorRoot = target.watchRoot, previous) {
	const usePolling = resolveSkillsWatcherUsePolling();
	const pooledNative = !usePolling && !shouldUseNativeSkillsWatcher(usePolling);
	const pathFilter = createSkillsWatchPathFilter(target.path, usePolling);
	const { ancestorRoot, ancestorRoots } = resolveSkillsWatchAncestors(target, previousAncestorRoot);
	const pendingAncestors = new Set(ancestorRoots);
	let contentReady = target.path !== target.watchRoot;
	let content;
	const releaseAncestors = [];
	let closing;
	const quarantined = failedContentPaths.has(target.path);
	const state = {
		closed: false,
		close: () => {
			if (closing) return closing;
			state.closed = true;
			clearTimeout(state.timer);
			const contentClose = content?.close().then((result) => {
				if (!result.ok) failedContentPaths.add(target.path);
				return result;
			});
			closing = Promise.all([...contentClose ? [contentClose] : [], ...releaseAncestors.map((release) => release())]).then((results) => results.find((result) => !result.ok) ?? ok(void 0));
			retiringWatchers.add(closing);
			closing.then(() => retiringWatchers.delete(closing));
			return closing;
		},
		watchRoot: target.watchRoot,
		ancestorRoot,
		depth: target.depth,
		initialScan: quarantined ? "error" : previous?.initialScan ?? "pending",
		unavailable: quarantined || Boolean(previous?.unavailable) || pooledNative && hasUncertainPooledCoverage(target.path),
		pooledNative,
		verified: false,
		failed: quarantined,
		recovering: Boolean(previous?.unavailable),
		replacing: false,
		subscribers: /* @__PURE__ */ new Set()
	};
	const targetChange = {
		targetPath: target.path,
		state,
		watcherKeys: state.subscribers
	};
	const isCurrent = () => !state.closed && pathWatchers.get(target.path) === state;
	const reconcileRoot = (changedPath, replaceContent = false) => {
		if (!isCurrent()) return true;
		const nextTarget = makeSkillsWatchTarget(target.path, state.depth, state.ancestorRoot);
		if (nextTarget.watchRoot === state.watchRoot && !replaceContent) return false;
		if (pooledNative && nextTarget.watchRoot !== state.watchRoot && isPathInside(nextTarget.watchRoot, state.watchRoot)) recordPooledObservationLoss(state.watchRoot);
		if (changedPath && isCurrent()) publishSkillsWatchChanges([{
			...targetChange,
			changedPath,
			change: "skills"
		}]);
		if (!isCurrent()) return true;
		for (const subscriber of state.subscribers) {
			workspaceWatchTargetCache.delete(subscriber);
			for (const entry of workspaceWatchTargets.get(subscriber) ?? []) if (entry.path === target.path) entry.watchRoot = nextTarget.watchRoot;
		}
		const subscriber = state.subscribers.values().next().value;
		if (subscriber !== void 0) subscribeWorkspaceToPath(subscriber, nextTarget, replaceContent);
		return true;
	};
	const schedule = (changedPath, change = "skills") => {
		if (!isCurrent() || change === "supporting" && state.pendingChange === "skills") return;
		state.pendingPath = changedPath ?? state.pendingPath;
		state.pendingChange = change;
		clearTimeout(state.timer);
		state.pendingAt = performance.now() + SKILLS_WATCH_DEBOUNCE_MS;
		state.timer = setTimeout(() => flushSkillsWatchChanges(state), SKILLS_WATCH_DEBOUNCE_MS);
	};
	const scheduleRawSkillFile = createRawSkillFileScheduler({
		watcher: state,
		stabilityMs: SKILLS_WATCH_DEBOUNCE_MS,
		schedule,
		onError: (changedPath, err) => {
			log.warn(`skills watcher stability check failed (${changedPath}): ${String(err)}`);
		}
	});
	const ready = (rescan = false) => {
		if (reconcileRoot() || !contentReady) return;
		const ancestorsSettled = pendingAncestors.size === 0;
		const uncertainCoverage = pooledNative && hasUncertainPooledCoverage(target.path);
		const restored = ancestorsSettled && state.unavailable;
		if (ancestorsSettled) {
			state.unavailable = uncertainCoverage;
			state.failed = false;
			state.recovering = false;
			state.verified = !uncertainCoverage;
		}
		const changes = [];
		if (ancestorsSettled && state.initialScan !== "ready") {
			state.initialScan = "ready";
			const watcherKeys = Array.from(state.subscribers).filter((watcherKey) => workspaceWatchTargets.get(watcherKey)?.every((entry) => {
				const current = pathWatchers.get(entry.path);
				return current && !current.closed && current.initialScan !== "pending";
			}));
			changes.push({
				...targetChange,
				watcherKeys,
				change: "initial-scan"
			});
		}
		if (rescan || restored) changes.push({
			...targetChange,
			change: "skills"
		});
		publishSkillsWatchChanges(changes);
		publishRecoveredCoverage();
	};
	const onChange = (event, changedPath) => {
		if (!isCurrent()) return;
		if (pooledNative && event === "unlinkDir" && pathFilter.isRelevant(event, changedPath)) {
			recordPooledObservationLoss(changedPath);
			if (!isCurrent()) return;
		}
		const ancestorChanged = event === "ancestor";
		if ((!content || ancestorChanged || event === "addDir" || event === "unlinkDir") && reconcileRoot(changedPath, ancestorChanged && Boolean(content))) return;
		const skillsRelevant = ancestorChanged || pathFilter.isRelevant(event, changedPath);
		if (skillsRelevant && (event === "addDir" || event === "unlinkDir")) {
			content?.structureChanged();
			if (event === "addDir") content?.rescan();
		}
		if (skillsRelevant || pathFilter.isSupportingPath(changedPath)) schedule(changedPath, skillsRelevant ? "skills" : "supporting");
	};
	const handleRaw = (_eventName, rawPath, details) => {
		if (!isCurrent()) return;
		const rawPathText = rawPathToString(rawPath);
		const changedPath = rawPathText ? resolveRawSkillsWatchPath(rawPathText, details) : getRawWatchedPath(details);
		if (!changedPath) return;
		if (isPathInside(changedPath, target.path) && reconcileRoot(changedPath)) return;
		if (!rawPathText) {
			if (isPathInside(target.path, changedPath)) schedule(changedPath);
			return;
		}
		if (isSkillDiscoveryFileWatchPath(changedPath) && isPathInside(target.path, changedPath)) {
			if (usePolling) return;
			scheduleRawSkillFile(changedPath);
		} else if (pathFilter.isSupportingPath(changedPath)) schedule(changedPath, "supporting");
	};
	const onRaw = (...args) => {
		if (usePolling) queueMicrotask(() => handleRaw(...args));
		else handleRaw(...args);
	};
	const onError = (err, rescan = false, ancestor, observedRoot = ancestor) => {
		if (!isCurrent()) return;
		const capacityCode = usePolling ? void 0 : getFileWatchCapacityCode(err);
		if (capacityCode) {
			if (!nativeWatchCapacityFailed) {
				nativeWatchCapacityFailed = true;
				log.warn(`skills native watcher capacity exhausted (${capacityCode}); refreshing skills during agent preparation`);
				for (const active of pathWatchers.values()) active.close();
				for (const workspaceDir of new Set(Array.from(workspaceWatchOwners.values(), (owner) => owner.workspaceDir))) bumpSkillsSnapshotVersion({
					workspaceDir,
					reason: "watch-unavailable"
				});
			}
			return;
		}
		if (pooledNative) recordPooledObservationLoss(observedRoot ?? target.path);
		log.warn(`skills watcher error (${target.path}): ${String(err)}`);
		if (state.initialScan === "pending") state.initialScan = "error";
		const changes = [];
		state.failed = true;
		state.verified = false;
		if (!state.unavailable) {
			state.unavailable = true;
			changes.push({
				...targetChange,
				change: "unavailable"
			});
		}
		publishSkillsWatchChanges(changes);
		if (rescan) schedule();
		if (ancestor && !state.recovering) reconcileRoot(void 0, true);
	};
	if (quarantined) return state;
	if (target.path === target.watchRoot) content = createSkillsContentWatcher({
		watch: () => watchContent(target, usePolling, pathFilter.ignored, SKILLS_WATCH_DEBOUNCE_MS),
		isCurrent,
		isStructuralRaw: pathFilter.isStructuralRaw,
		ready: (rescan) => {
			contentReady = true;
			ready(rescan || state.unavailable);
		},
		changed: onChange,
		raw: onRaw,
		error: (error, rescan) => {
			contentReady = false;
			onError(error, rescan);
		}
	});
	for (const root of ancestorRoots) releaseAncestors.push(acquireSkillsAncestorWatcher(root, usePolling, {
		path: target.path,
		ignored: pathFilter.ignored,
		ready: () => {
			if (!isCurrent()) return;
			if (pendingAncestors.delete(root) && pendingAncestors.size === 0 && state.recovering && content) {
				if (reconcileRoot()) return;
				contentReady = false;
				content.structureChanged();
				content.rescan();
				return;
			}
			ready();
		},
		unavailable: () => {
			if (!isCurrent()) return;
			pendingAncestors.add(root);
			state.verified = false;
			if (!state.unavailable && state.initialScan === "ready") {
				state.unavailable = true;
				publishSkillsWatchChanges([{
					...targetChange,
					change: "unavailable"
				}]);
			}
			if (content && !state.recovering) reconcileRoot(void 0, true);
		},
		changed: onChange,
		reconcile: () => {
			if (!isCurrent() || reconcileRoot(target.path)) return;
			content?.structureChanged();
			content?.rescan();
			schedule(target.path);
		},
		raw: onRaw,
		error: (error, observationRoot) => {
			pendingAncestors.add(root);
			onError(error, false, root, observationRoot);
		}
	}).release);
	return state;
}
function subscribeWorkspaceToPath(workspaceDir, watchTarget, replaceContent = false) {
	const existing = pathWatchers.get(watchTarget.path);
	if (existing) {
		existing.subscribers.add(workspaceDir);
		const reusable = !existing.closed && !existing.failed && !replaceContent && existing.watchRoot === watchTarget.watchRoot && existing.depth >= watchTarget.depth;
		existing.depth = Math.max(existing.depth, watchTarget.depth);
		if (reusable || existing.replacing) return;
		existing.replacing = true;
		existing.verified = false;
		if (!existing.unavailable) {
			existing.unavailable = true;
			publishSkillsWatchChanges([{
				targetPath: watchTarget.path,
				state: existing,
				watcherKeys: existing.subscribers,
				change: "unavailable"
			}]);
		}
		const replacement = existing.close().then((result) => {
			if (!result.ok || watchersClosing || nativeWatchCapacityFailed || pathWatchers.get(watchTarget.path) !== existing || existing.subscribers.size === 0) return;
			const target = makeSkillsWatchTarget(watchTarget.path, existing.depth, existing.ancestorRoot);
			const next = createSkillsPathWatcher(target, existing.ancestorRoot, existing);
			for (const subscriber of existing.subscribers) {
				next.subscribers.add(subscriber);
				workspaceWatchTargetCache.delete(subscriber);
				for (const entry of workspaceWatchTargets.get(subscriber) ?? []) if (entry.path === target.path) entry.watchRoot = target.watchRoot;
			}
			pathWatchers.set(target.path, next);
		});
		replacingWatchers.add(replacement);
		replacement.finally(() => replacingWatchers.delete(replacement));
		return;
	}
	const state = createSkillsPathWatcher(watchTarget);
	state.subscribers.add(workspaceDir);
	pathWatchers.set(watchTarget.path, state);
}
function disposeWorkspaceWatchState(watcherKey, watchTargets = workspaceWatchTargets.get(watcherKey) ?? []) {
	disposeRemoteSkillsWatcher(watcherKey);
	for (const watchTarget of watchTargets) unsubscribeWorkspaceFromPath(watcherKey, watchTarget);
	workspaceWatchTargets.delete(watcherKey);
	workspaceWatchOwners.delete(watcherKey);
	workspaceWatchTargetCache.delete(watcherKey);
	workspaceWatchLastEnsuredAt.delete(watcherKey);
}
function evictWorkspaceWatchStates(now) {
	const evict = (watcherKey) => {
		const owner = workspaceWatchOwners.get(watcherKey);
		disposeWorkspaceWatchState(watcherKey);
		if (!owner) return;
		const remainingOwners = Array.from(workspaceWatchOwners.values()).filter((other) => other.workspaceDir === owner.workspaceDir);
		if (remainingOwners.length === 0) suspendSkillsSnapshotSources(owner.workspaceDir, {});
		if (owner.sourceScope.executionWorkspaceDir && !remainingOwners.some((other) => other.sourceScope.executionWorkspaceDir === owner.sourceScope.executionWorkspaceDir)) suspendSkillsSnapshotSources(owner.workspaceDir, owner.sourceScope);
	};
	const cutoff = now - SKILLS_WORKSPACE_WATCH_IDLE_TTL_MS;
	for (const [watcherKey, lastEnsuredAt] of workspaceWatchLastEnsuredAt) if (lastEnsuredAt < cutoff) evict(watcherKey);
	for (const watcherKey of workspaceWatchLastEnsuredAt.keys()) {
		if (workspaceWatchLastEnsuredAt.size <= MAX_SKILLS_WORKSPACE_WATCH_STATES) break;
		evict(watcherKey);
	}
}
function ensureSkillsWatcher(params) {
	if (watchersClosing) return;
	const workspaceDir = params.workspaceDir.trim();
	if (!workspaceDir) return;
	const { executionWorkspaceDir } = normalizeWorkspaceSkillRoots({
		agentWorkspaceDir: workspaceDir,
		executionWorkspaceDir: params.executionWorkspaceDir
	});
	const watcherKey = JSON.stringify([
		workspaceDir,
		executionWorkspaceDir,
		params.agentId
	]);
	const sourceScope = { executionWorkspaceDir };
	const owner = {
		workspaceDir,
		sourceScope,
		sharedScanPending: workspaceWatchOwners.get(watcherKey)?.sharedScanPending ?? false,
		unavailable: workspaceWatchOwners.get(watcherKey)?.unavailable ?? false
	};
	workspaceWatchOwners.set(watcherKey, owner);
	const isCurrent = () => !watchersClosing && workspaceWatchOwners.get(watcherKey) === owner;
	const refreshInputs = {
		sourceScope,
		config: params.config,
		pluginMetadataSnapshot: params.pluginMetadataSnapshot
	};
	const now = Date.now();
	const watchEnabled = params.config?.skills?.load?.watch !== false;
	const previousTargets = workspaceWatchTargets.get(watcherKey) ?? [];
	if (!watchEnabled) {
		disposeWorkspaceWatchState(watcherKey, previousTargets);
		evictWorkspaceWatchStates(now);
		return;
	}
	workspaceWatchLastEnsuredAt.delete(watcherKey);
	workspaceWatchLastEnsuredAt.set(watcherKey, now);
	evictWorkspaceWatchStates(now);
	if (!isCurrent()) return;
	const access = getAgentWorkspaceAccess(workspaceDir, "loadSkills");
	let localPlan = params.sourcePlan;
	if (access?.loadSkills) {
		const { gatewayPlan, workspacePlan } = splitSkillSourcePlan(resolveWorkspaceSkillSourcePlan(workspaceDir, params));
		ensureRemoteSkillsWatcher({
			watcherKey,
			workspaceDir,
			executionWorkspaceDir,
			access,
			sourcePlan: workspacePlan
		});
		localPlan = gatewayPlan;
	} else disposeRemoteSkillsWatcher(watcherKey);
	if (!isCurrent()) return;
	if (nativeWatchCapacityFailed) {
		workspaceWatchTargetCache.delete(watcherKey);
		bumpSkillsSnapshotVersion({
			workspaceDir,
			refreshInputs,
			reason: "watch"
		});
		return;
	}
	const failedTargets = previousTargets.filter((entry) => pathWatchers.get(entry.path)?.unavailable);
	if (failedTargets.length > 0) workspaceWatchTargetCache.delete(watcherKey);
	const cachedTargets = workspaceWatchTargetCache.get(watcherKey);
	const resolvedTargets = resolveSkillsWatchTargets(workspaceDir, params.config, params.agentId, access?.loadSkills ? void 0 : executionWorkspaceDir, params.pluginMetadataSnapshot, localPlan, cachedTargets);
	if (resolvedTargets !== cachedTargets) workspaceWatchTargetCache.set(watcherKey, resolvedTargets);
	const watchTargets = resolvedTargets.targets;
	const targetChanges = compareSkillsWatchTargets(previousTargets, watchTargets, previousTargets.length ? previousTargets : Array.from(workspaceWatchOwners).flatMap(([key, other]) => other.workspaceDir === workspaceDir ? workspaceWatchTargets.get(key) ?? [] : []));
	const watcherDepthsCoverTargets = watchTargets.every((watchTarget) => (pathWatchers.get(watchTarget.path)?.depth ?? -1) >= watchTarget.depth);
	if (targetChanges.targetsUnchanged && watcherDepthsCoverTargets && failedTargets.length === 0) return;
	const nextTargetKeys = new Set(watchTargets.map((target) => target.path));
	for (const watchTarget of previousTargets) if (!nextTargetKeys.has(watchTarget.path)) unsubscribeWorkspaceFromPath(watcherKey, watchTarget);
	workspaceWatchTargets.set(watcherKey, watchTargets);
	for (const watchTarget of watchTargets) {
		subscribeWorkspaceToPath(watcherKey, watchTarget);
		if (!isCurrent()) return;
	}
	owner.sharedScanPending ||= hasUnreadySharedTargets(watcherKey);
	const joinedUnavailable = watchTargets.some((target) => pathWatchers.get(target.path)?.unavailable && !previousTargets.some((previous) => previous.path === target.path));
	const notifyUnavailable = joinedUnavailable && !owner.unavailable;
	owner.unavailable ||= joinedUnavailable;
	if (!targetChanges.targetsUnchanged || failedTargets.length > 0) bumpSkillsSnapshotVersion({
		workspaceDir,
		sourceScopes: targetChanges.sharedTargetsChanged || failedTargets.some((target) => !target.executionOnly) ? void 0 : [sourceScope],
		refreshInputs,
		reason: notifyUnavailable ? "watch-unavailable" : "watch-targets",
		changedPath: watchTargets.map((target) => target.path).join("|")
	});
}
/** Finish discovery deferred during an outage before a worker advertises coverage. */
function reconcileSkillsWatcherCoverage(params) {
	ensureSkillsWatcher(params);
	const workspaceDir = params.workspaceDir.trim();
	const { executionWorkspaceDir } = normalizeWorkspaceSkillRoots({
		agentWorkspaceDir: workspaceDir,
		executionWorkspaceDir: params.executionWorkspaceDir
	});
	const watcherKey = JSON.stringify([
		workspaceDir,
		executionWorkspaceDir,
		params.agentId
	]);
	const owner = workspaceWatchOwners.get(watcherKey);
	const covered = !watchersClosing && !nativeWatchCapacityFailed && hasVerifiedCoverage(watcherKey);
	if (owner && !covered) owner.unavailable = true;
	return covered;
}
async function closeSkillsWatchers(resetState = false) {
	watchersClosing = true;
	if (resetState) resetSkillsRefreshStateForTest();
	const active = Array.from(pathWatchers.values());
	nativeWatchCapacityFailed = false;
	pathWatchers.clear();
	workspaceWatchTargets.clear();
	workspaceWatchOwners.clear();
	workspaceWatchTargetCache.clear();
	workspaceWatchLastEnsuredAt.clear();
	for (const state of active) state.close();
	await Promise.all([
		...replacingWatchers,
		...retiringWatchers,
		joinSkillsWatcherCloses(),
		closeRemoteSkillsWatchers()
	]);
	watchersClosing = false;
	if (resetState) {
		failedContentPaths.clear();
		uncertainPooledObservationRoots.clear();
		resetSkillsAncestorWatchersForTest();
	}
}
//#endregion
export { ensureSkillsWatcher as n, reconcileSkillsWatcherCoverage as r, closeSkillsWatchers as t };