UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

2,653 lines 102 kB
import { a as normalizeLowercaseStringOrEmpty, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { o as isRecord } from "./record-coerce-DHZ4bFlT.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { t as createLazyImportLoader } from "./lazy-promise-BONnzNfb.js";
import { _ as uniqueStrings, l as normalizeStringEntries, v as uniqueValues } from "./string-normalization-WNUDCpXX.js";
import "./fs-safe-aqmM_n6V.js";
import { a as root, s as PATH_ALIAS_POLICIES } from "./secure-temp-dir-XAWcZnE2.js";
import { r as openRootFile } from "./root-file-jRMCpJW4.js";
import "./boundary-file-read-CBe_wA_B.js";
import "./agent-scope-MrLta7Pq.js";
import { c as parseAgentSessionKey } from "./session-key-utils-Bx3apsJ3.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId, r as resolveAgentConfig } from "./agent-scope-config-CgCYpZfK.js";
import { a as logWarn } from "./logger-lqqYRtFw.js";
import { t as bindAbortRelay } from "./fetch-timeout-CDaw0X8V.js";
import { n as assertSandboxPath } from "./sandbox-paths-DAj1Euvz.js";
import { i as toRelativeSandboxPath, n as resolveWorkspaceRoot, r as resolvePathFromInput } from "./workspace-dir-DzA-cRQQ.js";
import { a as resolveGatewayMessageChannel } from "./message-channel-normalize-BLLf0ubu.js";
import "./message-channel-BiOeMu0l.js";
import { P as createCodingTools, V as createReadTool } from "./sessions-BTpdzjUa.js";
import { c as EXEC_TOOL_DISPLAY_SUMMARY, l as PROCESS_TOOL_DISPLAY_SUMMARY } from "./tool-catalog-DLkumOki.js";
import { a as collectExplicitDenylist, c as hasRestrictiveAllowPolicy, g as resolveToolProfilePolicy, h as normalizeToolName, i as collectExplicitAllowlist, l as mergeAlsoAllowPolicy, p as expandToolGroups, u as replaceWithEffectiveToolAllowlist } from "./tool-policy-CpBMaMTY.js";
import { t as isToolAllowedByPolicies } from "./tool-policy-match-Dj5a1jle.js";
import { _ as SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants-CsuK1VR-.js";
import { o as resolveSubagentCapabilityStore, t as isSubagentEnvelopeSession } from "./subagent-capabilities-Cva3gXJL.js";
import { i as resolveInheritedToolPolicyForSession, n as resolveEffectiveToolPolicy, o as resolveSubagentToolPolicyForSession, r as resolveGroupToolPolicy } from "./agent-tools.policy-dtD_g0G1.js";
import { t as resolveSenderToolPolicy } from "./sender-tool-policy-B7LKaG9D.js";
import { t as HEARTBEAT_RESPONSE_TOOL_NAME } from "./heartbeat-tool-response-D6-_RNEN.js";
import { C as wrapToolWorkspaceRootGuardWithOptions, S as wrapToolWorkspaceRootGuard, _ as createOpenClawReadTool, b as createSandboxedWriteTool, g as createHostWorkspaceWriteTool, h as createHostWorkspaceEditTool, m as resolveOpenClawPluginToolsForOptions, t as createOpenClawTools, v as createSandboxedEditTool, x as wrapToolMemoryFlushAppendOnlyWrite, y as createSandboxedReadTool } from "./openclaw-tools-C0nKaVVY.js";
import { s as shouldSuppressManagedWebSearchTool } from "./codex-native-web-search-core-B2S2BXKl.js";
import { t as normalizeToolParameterSchema } from "./agent-tools-parameter-schema-CO-BQPfo.js";
import { i as getPluginToolMeta, n as copyPluginToolMeta } from "./tools-oBw0X8xm.js";
import { O as listChannelAgentTools, T as copyChannelAgentToolMeta, _ as wrapToolWithBeforeToolCallHook, a as copyBeforeToolCallHookMarker, u as isToolWrappedWithBeforeToolCallHook } from "./agent-tools.before-tool-call-BkVrW03y.js";
import { B as resolveExecPolicyForMode } from "./exec-approvals-C6M6SGY3.js";
import { l as jsonResult, n as ToolInputError, r as asToolParamsRecord } from "./common-C4yy9V-D.js";
import { r as resolveImageSanitizationLimits } from "./image-sanitization-CxLP0YN-.js";
import { t as resolveExecCommandHighlighting } from "./exec-command-highlighting-CWuIAGVg.js";
import { n as resolveEventSessionRoutingPolicy } from "./event-session-routing-BJ_2911I.js";
import { i as resolveMergedSafeBinProfileFixtures } from "./exec-safe-bin-runtime-policy-DKOVy8lE.js";
import { n as describeProcessTool, t as describeExecTool } from "./bash-tools.descriptions-B3ho0AFu.js";
import { n as processSchema, t as execSchema } from "./bash-tools.schemas-C-ZM00W3.js";
import "./codex-native-web-search-CT-UH7H_.js";
import { t as resolveToolLoopDetectionConfig } from "./tool-loop-detection-config-LHDaKYjp.js";
import { l as resolveToolFsConfig, o as createToolFsPolicy } from "./local-roots-BqdtViGS.js";
import { x as resolveReadOnlyWorkspaceSkillMounts } from "./docker-Z5I59fn0.js";
import { n as buildDefaultToolPolicyPipelineSteps, t as applyToolPolicyPipeline } from "./tool-policy-pipeline-DwR3jF4p.js";
import fs from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
import os from "node:os";
import { spawn } from "node:child_process";
import { Type } from "typebox";
//#region src/agents/agent-tools.abort.ts
/**
* Abort-signal wrapping for agent tools.
* Combines per-call cancellation with run-level aborts while preserving plugin,
* channel, and before_tool_call metadata on wrapped tools.
*/
function throwAbortError() {
	const err = /* @__PURE__ */ new Error("Aborted");
	err.name = "AbortError";
	throw err;
}
/**
* Checks if an object is a valid AbortSignal using structural typing.
* This is more reliable than `instanceof` across different realms (VM, iframe, etc.)
* where the AbortSignal constructor may differ.
*/
function isAbortSignal(obj) {
	return obj instanceof AbortSignal;
}
function combineAbortSignals(a, b) {
	if (!a && !b) return;
	if (a && !b) return a;
	if (b && !a) return b;
	if (a?.aborted) return a;
	if (b?.aborted) return b;
	if (typeof AbortSignal.any === "function" && isAbortSignal(a) && isAbortSignal(b)) return AbortSignal.any([a, b]);
	const controller = new AbortController();
	const onAbort = bindAbortRelay(controller);
	a?.addEventListener("abort", onAbort, { once: true });
	b?.addEventListener("abort", onAbort, { once: true });
	return controller.signal;
}
/** Wrap a tool so every execute call observes the supplied run abort signal. */
function wrapToolWithAbortSignal(tool, abortSignal) {
	if (!abortSignal) return tool;
	const execute = tool.execute;
	if (!execute) return tool;
	const wrappedTool = {
		...tool,
		execute: async (toolCallId, params, signal, onUpdate) => {
			const combined = combineAbortSignals(signal, abortSignal);
			if (combined?.aborted) throwAbortError();
			return await execute(toolCallId, params, combined, onUpdate);
		}
	};
	copyPluginToolMeta(tool, wrappedTool);
	copyChannelAgentToolMeta(tool, wrappedTool);
	copyBeforeToolCallHookMarker(tool, wrappedTool);
	return wrappedTool;
}
//#endregion
//#region src/agents/agent-tools.deferred-followup.ts
/** Return tools with exec/process descriptions adjusted for cron availability. */
function applyDeferredFollowupToolDescriptions(tools, params) {
	const hasCronTool = tools.some((tool) => tool.name === "cron");
	return tools.map((tool) => {
		if (tool.name === "exec") return {
			...tool,
			description: describeExecTool({
				agentId: params?.agentId,
				hasCronTool
			})
		};
		if (tool.name === "process") return {
			...tool,
			description: describeProcessTool({ hasCronTool })
		};
		return tool;
	});
}
//#endregion
//#region src/agents/agent-tools.message-provider-policy.ts
/**
* Message-provider tool filtering.
* Channels can restrict tool names after runtime assembly when the active
* transport cannot safely render or execute a class of tools.
*/
const TOOL_DENY_BY_MESSAGE_PROVIDER = {
	"discord-voice": ["tts"],
	voice: ["tts"]
};
const TOOL_ALLOW_BY_MESSAGE_PROVIDER = { node: [
	"canvas",
	"image",
	"pdf",
	"tts",
	"web_fetch",
	"web_search"
] };
/** Filters tool names by the active message-provider allow/deny policy. */
function filterToolNamesByMessageProvider(toolNames, messageProvider) {
	const normalizedProvider = normalizeOptionalLowercaseString(messageProvider);
	if (!normalizedProvider) return [...toolNames];
	const allowedTools = TOOL_ALLOW_BY_MESSAGE_PROVIDER[normalizedProvider];
	if (allowedTools && allowedTools.length > 0) {
		const allowedSet = new Set(allowedTools);
		return toolNames.filter((toolName) => allowedSet.has(toolName));
	}
	const deniedTools = TOOL_DENY_BY_MESSAGE_PROVIDER[normalizedProvider];
	if (!deniedTools || deniedTools.length === 0) return [...toolNames];
	const deniedSet = new Set(deniedTools);
	return toolNames.filter((toolName) => !deniedSet.has(toolName));
}
/** Applies message-provider filtering while preserving duplicate tool entries. */
function filterToolsByMessageProvider(tools, messageProvider) {
	const filteredToolNames = filterToolNamesByMessageProvider(tools.map((tool) => tool.name), messageProvider);
	const remainingCounts = /* @__PURE__ */ new Map();
	for (const toolName of filteredToolNames) remainingCounts.set(toolName, (remainingCounts.get(toolName) ?? 0) + 1);
	return tools.filter((tool) => {
		const remaining = remainingCounts.get(tool.name) ?? 0;
		if (remaining <= 0) return false;
		remainingCounts.set(tool.name, remaining - 1);
		return true;
	});
}
//#endregion
//#region src/agents/agent-tools.schema.ts
/**
* Tool schema normalization wrappers.
* Applies provider-compatible parameter schema cleanup while preserving plugin
* and channel metadata on normalized tools.
*/
function isObjectSchemaWithNoRequiredParams(schema) {
	if (!schema || typeof schema !== "object" || Array.isArray(schema)) return false;
	const record = schema;
	const type = record.type;
	if (!(type === "object" || Array.isArray(type) && type.some((entry) => entry === "object"))) return false;
	return !schemaHasRequiredParams(record);
}
function schemaHasRequiredParams(schema) {
	if (Array.isArray(schema.required) && schema.required.length > 0) return true;
	for (const key of [
		"allOf",
		"anyOf",
		"oneOf"
	]) {
		const variants = schema[key];
		if (!Array.isArray(variants)) continue;
		if (variants.some((variant) => variant !== null && typeof variant === "object" && !Array.isArray(variant) && schemaHasRequiredParams(variant))) return true;
	}
	return false;
}
function addEmptyObjectArgumentPreparation(tool, parameters) {
	if (!isObjectSchemaWithNoRequiredParams(parameters)) return tool;
	return {
		...tool,
		prepareArguments: (args) => {
			const prepared = tool.prepareArguments ? tool.prepareArguments(args) : args;
			return prepared === null || prepared === void 0 ? {} : prepared;
		}
	};
}
/** Normalize a tool's parameter schema for the selected provider/model. */
function normalizeToolParameters(tool, options) {
	function preserveToolMeta(target) {
		copyPluginToolMeta(tool, target);
		copyChannelAgentToolMeta(tool, target);
		return target;
	}
	const schema = tool.parameters && typeof tool.parameters === "object" ? tool.parameters : void 0;
	if (!schema) return tool;
	const parameters = normalizeToolParameterSchema(schema, options);
	return preserveToolMeta({
		...tool,
		...addEmptyObjectArgumentPreparation(tool, parameters),
		parameters
	});
}
//#endregion
//#region src/agents/apply-patch-update.ts
/**
* Update-hunk application for the apply_patch parser.
* Locates expected old lines with tolerant matching, applies chunks in order,
* and returns normalized file contents with a trailing newline.
*/
async function defaultReadFile(filePath) {
	return fs$1.readFile(filePath, "utf8");
}
/** Apply parsed update chunks to one file and return the new file contents. */
async function applyUpdateHunk(filePath, chunks, options) {
	const originalLines = (await (options?.readFile ?? defaultReadFile)(filePath).catch((err) => {
		throw new Error(`Failed to read file to update ${filePath}: ${formatErrorMessage(err)}`);
	})).split("\n");
	if (originalLines.length > 0 && originalLines[originalLines.length - 1] === "") originalLines.pop();
	let newLines = applyReplacements(originalLines, computeReplacements(originalLines, filePath, chunks));
	if (newLines.length === 0 || newLines[newLines.length - 1] !== "") newLines = [...newLines, ""];
	return newLines.join("\n");
}
function computeReplacements(originalLines, filePath, chunks) {
	const replacements = [];
	let lineIndex = 0;
	for (const chunk of chunks) {
		if (chunk.changeContext) {
			const ctxIndex = seekSequence(originalLines, [chunk.changeContext], lineIndex, false);
			if (ctxIndex === null) throw new Error(`Failed to find context '${chunk.changeContext}' in ${filePath}`);
			lineIndex = ctxIndex + 1;
		}
		if (chunk.oldLines.length === 0) {
			const insertionIndex = chunk.changeContext && !chunk.isEndOfFile ? lineIndex : originalLines.length > 0 && originalLines[originalLines.length - 1] === "" ? originalLines.length - 1 : originalLines.length;
			replacements.push([
				insertionIndex,
				0,
				chunk.newLines
			]);
			lineIndex = insertionIndex;
			continue;
		}
		let pattern = chunk.oldLines;
		let newSlice = chunk.newLines;
		let found = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile);
		if (found === null && pattern[pattern.length - 1] === "") {
			pattern = pattern.slice(0, -1);
			if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "") newSlice = newSlice.slice(0, -1);
			found = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile);
		}
		if (found === null) throw new Error(`Failed to find expected lines in ${filePath}:\n${chunk.oldLines.join("\n")}`);
		replacements.push([
			found,
			pattern.length,
			newSlice
		]);
		lineIndex = found + pattern.length;
	}
	replacements.sort((a, b) => a[0] - b[0]);
	return replacements;
}
function applyReplacements(lines, replacements) {
	const result = [...lines];
	for (const [startIndex, oldLen, newLines] of [...replacements].toReversed()) {
		for (let i = 0; i < oldLen; i += 1) if (startIndex < result.length) result.splice(startIndex, 1);
		for (let i = 0; i < newLines.length; i += 1) result.splice(startIndex + i, 0, newLines[i]);
	}
	return result;
}
function seekSequence(lines, pattern, start, eof) {
	if (pattern.length === 0) return start;
	if (pattern.length > lines.length) return null;
	const maxStart = lines.length - pattern.length;
	const searchStart = eof && lines.length >= pattern.length ? maxStart : start;
	if (searchStart > maxStart) return null;
	for (let i = searchStart; i <= maxStart; i += 1) if (linesMatch(lines, pattern, i, (value) => value)) return i;
	for (let i = searchStart; i <= maxStart; i += 1) if (linesMatch(lines, pattern, i, (value) => value.trimEnd())) return i;
	for (let i = searchStart; i <= maxStart; i += 1) if (linesMatch(lines, pattern, i, (value) => value.trim())) return i;
	for (let i = searchStart; i <= maxStart; i += 1) if (linesMatch(lines, pattern, i, (value) => normalizePunctuation(value.trim()))) return i;
	return null;
}
function linesMatch(lines, pattern, start, normalize) {
	for (let idx = 0; idx < pattern.length; idx += 1) if (normalize(lines[start + idx]) !== normalize(pattern[idx])) return false;
	return true;
}
function normalizePunctuation(value) {
	return Array.from(value).map((char) => {
		switch (char) {
			case "‐":
			case "‑":
			case "‒":
			case "–":
			case "—":
			case "―":
			case "−": return "-";
			case "‘":
			case "’":
			case "‚":
			case "‛": return "'";
			case "“":
			case "”":
			case "„":
			case "‟": return "\"";
			case "\xA0":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ":
			case " ": return " ";
			default: return char;
		}
	}).join("");
}
//#endregion
//#region src/agents/apply-patch.ts
/**
* Runtime apply_patch tool and parser.
* Parses OpenAI-style patch envelopes and applies add/update/delete/move hunks
* through guarded host or sandbox filesystem operations.
*/
const BEGIN_PATCH_MARKER = "*** Begin Patch";
const END_PATCH_MARKER = "*** End Patch";
const ADD_FILE_MARKER = "*** Add File: ";
const DELETE_FILE_MARKER = "*** Delete File: ";
const UPDATE_FILE_MARKER = "*** Update File: ";
const MOVE_TO_MARKER = "*** Move to: ";
const EOF_MARKER = "*** End of File";
const CHANGE_CONTEXT_MARKER = "@@ ";
const EMPTY_CHANGE_CONTEXT_MARKER = "@@";
const applyPatchSchema = Type.Object({ input: Type.String({ description: "Patch content using the *** Begin Patch/End Patch format." }) });
/** Create the agent tool wrapper for applying patch-envelope input. */
function createApplyPatchTool(options = {}) {
	const cwd = options.cwd ?? process.cwd();
	const sandbox = options.sandbox;
	const workspaceOnly = options.workspaceOnly !== false;
	return {
		name: "apply_patch",
		label: "apply_patch",
		description: "Apply a patch to one or more files using the apply_patch format. The input should include *** Begin Patch and *** End Patch markers.",
		parameters: applyPatchSchema,
		execute: async (_toolCallId, args, signal) => {
			const params = args;
			const input = typeof params.input === "string" ? params.input : "";
			if (!input.trim()) throw new Error("Provide a patch input.");
			if (signal?.aborted) {
				const err = /* @__PURE__ */ new Error("Aborted");
				err.name = "AbortError";
				throw err;
			}
			const result = await applyPatch(input, {
				cwd,
				sandbox,
				workspaceOnly,
				signal
			});
			return {
				content: [{
					type: "text",
					text: result.text
				}],
				details: { summary: result.summary }
			};
		}
	};
}
/** Parse and apply a patch envelope to the configured filesystem target. */
async function applyPatch(input, options) {
	const parsed = parsePatchText(input);
	if (parsed.hunks.length === 0) throw new Error("No files were modified.");
	const summary = {
		added: [],
		modified: [],
		deleted: []
	};
	const seen = {
		added: /* @__PURE__ */ new Set(),
		modified: /* @__PURE__ */ new Set(),
		deleted: /* @__PURE__ */ new Set()
	};
	const fileOps = resolvePatchFileOps(options);
	for (const hunk of parsed.hunks) {
		if (options.signal?.aborted) {
			const err = /* @__PURE__ */ new Error("Aborted");
			err.name = "AbortError";
			throw err;
		}
		if (hunk.kind === "add") {
			const target = await resolvePatchPath(hunk.path, options);
			await assertPatchParentPath(hunk.path, options);
			await ensureDir(target.resolved, fileOps);
			await fileOps.writeFile(target.resolved, hunk.contents);
			recordSummary(summary, seen, "added", target.display);
			continue;
		}
		if (hunk.kind === "delete") {
			const target = await resolvePatchPath(hunk.path, options, PATH_ALIAS_POLICIES.unlinkTarget);
			await fileOps.remove(target.resolved);
			recordSummary(summary, seen, "deleted", target.display);
			continue;
		}
		const target = await resolvePatchPath(hunk.path, options);
		const applied = await applyUpdateHunk(target.resolved, hunk.chunks, { readFile: (pathLocal) => fileOps.readFile(pathLocal) });
		if (hunk.movePath) {
			const moveTarget = await resolvePatchPath(hunk.movePath, options);
			await assertPatchParentPath(hunk.movePath, options);
			await ensureDir(moveTarget.resolved, fileOps);
			const moveResolvesToSource = path.resolve(moveTarget.resolved) === path.resolve(target.resolved);
			await fileOps.writeFile(moveResolvesToSource ? target.resolved : moveTarget.resolved, applied);
			if (!moveResolvesToSource) await fileOps.remove(target.resolved);
			recordSummary(summary, seen, "modified", moveResolvesToSource ? target.display : moveTarget.display);
		} else {
			await fileOps.writeFile(target.resolved, applied);
			recordSummary(summary, seen, "modified", target.display);
		}
	}
	return {
		summary,
		text: formatSummary(summary)
	};
}
function recordSummary(summary, seen, bucket, value) {
	if (seen[bucket].has(value)) return;
	seen[bucket].add(value);
	summary[bucket].push(value);
}
function formatSummary(summary) {
	const lines = ["Success. Updated the following files:"];
	for (const file of summary.added) lines.push(`A ${file}`);
	for (const file of summary.modified) lines.push(`M ${file}`);
	for (const file of summary.deleted) lines.push(`D ${file}`);
	return lines.join("\n");
}
function resolvePatchFileOps(options) {
	if (options.sandbox) {
		const { root, bridge } = options.sandbox;
		return {
			readFile: async (filePath) => {
				return (await bridge.readFile({
					filePath,
					cwd: root
				})).toString("utf8");
			},
			writeFile: (filePath, content) => bridge.writeFile({
				filePath,
				cwd: root,
				data: content
			}),
			remove: (filePath) => bridge.remove({
				filePath,
				cwd: root,
				force: false
			}),
			mkdirp: (dir) => bridge.mkdirp({
				filePath: dir,
				cwd: root
			})
		};
	}
	const workspaceOnly = options.workspaceOnly !== false;
	const rootPromise = workspaceOnly ? root(options.cwd) : void 0;
	return {
		readFile: async (filePath) => {
			if (!workspaceOnly) return await fs$1.readFile(filePath, "utf8");
			const opened = await openRootFile({
				absolutePath: filePath,
				rootPath: options.cwd,
				boundaryLabel: "workspace root"
			});
			assertBoundaryRead(opened, filePath);
			try {
				return fs.readFileSync(opened.fd, "utf8");
			} finally {
				fs.closeSync(opened.fd);
			}
		},
		writeFile: async (filePath, content) => {
			if (!workspaceOnly) {
				await fs$1.writeFile(filePath, content, "utf8");
				return;
			}
			const relative = toRelativeSandboxPath(options.cwd, filePath);
			await (await rootPromise)?.write(relative, content, { encoding: "utf8" });
		},
		remove: async (filePath) => {
			if (!workspaceOnly) {
				await fs$1.rm(filePath);
				return;
			}
			const relative = toRelativeSandboxPath(options.cwd, filePath);
			await (await rootPromise)?.remove(relative);
		},
		mkdirp: async (dir) => {
			if (!workspaceOnly) {
				await fs$1.mkdir(dir, { recursive: true });
				return;
			}
			const relative = toRelativeSandboxPath(options.cwd, dir, { allowRoot: true });
			const root = await rootPromise;
			if (!root) return;
			if (relative === "" || relative === ".") {
				await root.ensureRoot();
				return;
			}
			await root.mkdir(relative);
		}
	};
}
async function ensureDir(filePath, ops) {
	const parent = path.dirname(filePath);
	if (!parent || parent === ".") return;
	await ops.mkdirp(parent);
}
async function assertPatchParentPath(filePath, options) {
	if (options.workspaceOnly === false || options.sandbox) return;
	const parent = path.dirname(filePath);
	if (!parent || parent === ".") return;
	await assertSandboxPath({
		filePath: parent,
		cwd: options.cwd,
		root: options.cwd
	});
	await assertNoExistingParentAliases({
		parentPath: resolvePathFromInput(parent, options.cwd),
		rootPath: options.cwd
	});
}
async function assertNoExistingParentAliases(params) {
	const rootPath = path.resolve(params.rootPath);
	const parentPath = path.resolve(params.parentPath);
	const relative = path.relative(rootPath, parentPath);
	if (!relative || relative === "" || relativePathEscapesRoot(relative)) return;
	let current = rootPath;
	for (const segment of relative.split(path.sep)) {
		if (!segment) continue;
		current = path.join(current, segment);
		const stat = await fs$1.lstat(current).catch((error) => {
			if (error.code === "ENOENT") return null;
			throw error;
		});
		if (!stat) return;
		if (stat.isSymbolicLink()) throw new Error(`Path alias under sandbox root: ${path.relative(rootPath, current)}`);
	}
}
async function resolvePatchPath(filePath, options, aliasPolicy = PATH_ALIAS_POLICIES.strict) {
	if (options.sandbox) {
		const resolved = options.sandbox.bridge.resolvePath({
			filePath,
			cwd: options.cwd
		});
		if (options.workspaceOnly !== false && resolved.hostPath) await assertSandboxPath({
			filePath: resolved.hostPath,
			cwd: options.cwd,
			root: options.cwd,
			allowFinalSymlinkForUnlink: aliasPolicy.allowFinalSymlinkForUnlink,
			allowFinalHardlinkForUnlink: aliasPolicy.allowFinalHardlinkForUnlink
		});
		return {
			resolved: resolved.hostPath ?? resolved.containerPath,
			display: resolved.relativePath || resolved.containerPath
		};
	}
	const resolved = options.workspaceOnly !== false ? (await assertSandboxPath({
		filePath,
		cwd: options.cwd,
		root: options.cwd,
		allowFinalSymlinkForUnlink: aliasPolicy.allowFinalSymlinkForUnlink,
		allowFinalHardlinkForUnlink: aliasPolicy.allowFinalHardlinkForUnlink
	})).resolved : resolvePathFromInput(filePath, options.cwd);
	return {
		resolved,
		display: toDisplayPath(resolved, options.cwd)
	};
}
function assertBoundaryRead(opened, targetPath) {
	if (opened.ok) return;
	const reason = opened.reason === "validation" ? "unsafe path" : "path not found";
	throw new Error(`Failed boundary read for ${targetPath} (${reason})`);
}
function toDisplayPath(resolved, cwd) {
	const relative = path.relative(cwd, resolved);
	if (!relative || relative === "") return path.basename(resolved);
	if (relativePathEscapesRoot(relative)) return resolved;
	return relative;
}
function relativePathEscapesRoot(relativePath) {
	return relativePath === ".." || relativePath.startsWith("../") || relativePath.startsWith("..\\") || path.isAbsolute(relativePath);
}
function parsePatchText(input) {
	const trimmed = input.trim();
	if (!trimmed) throw new Error("Invalid patch: input is empty.");
	const validated = checkPatchBoundariesLenient(trimmed.split(/\r?\n/));
	const hunks = [];
	const lastLineIndex = validated.length - 1;
	let remaining = validated.slice(1, lastLineIndex);
	let lineNumber = 2;
	while (remaining.length > 0) {
		const { hunk, consumed } = parseOneHunk(remaining, lineNumber);
		hunks.push(hunk);
		lineNumber += consumed;
		remaining = remaining.slice(consumed);
	}
	return {
		hunks,
		patch: validated.join("\n")
	};
}
function checkPatchBoundariesLenient(lines) {
	const strictError = checkPatchBoundariesStrict(lines);
	if (!strictError) return lines;
	if (lines.length < 4) throw new Error(strictError);
	const first = lines[0];
	const last = lines.at(-1);
	if (last && (first === "<<EOF" || first === "<<'EOF'" || first === "<<\"EOF\"") && last.endsWith("EOF")) {
		const inner = lines.slice(1, -1);
		const innerError = checkPatchBoundariesStrict(inner);
		if (!innerError) return inner;
		throw new Error(innerError);
	}
	throw new Error(strictError);
}
function checkPatchBoundariesStrict(lines) {
	const firstLine = lines[0]?.trim();
	const lastLine = lines[lines.length - 1]?.trim();
	if (firstLine === BEGIN_PATCH_MARKER && lastLine === END_PATCH_MARKER) return null;
	if (firstLine !== BEGIN_PATCH_MARKER) return "The first line of the patch must be '*** Begin Patch'";
	return "The last line of the patch must be '*** End Patch'";
}
function parseOneHunk(lines, lineNumber) {
	if (lines.length === 0) throw new Error(`Invalid patch hunk at line ${lineNumber}: empty hunk`);
	const firstLine = lines[0].trim();
	if (firstLine.startsWith(ADD_FILE_MARKER)) {
		const targetPath = firstLine.slice(14);
		let contents = "";
		let consumed = 1;
		for (const addLine of lines.slice(1)) if (addLine.startsWith("+")) {
			contents += `${addLine.slice(1)}\n`;
			consumed += 1;
		} else break;
		return {
			hunk: {
				kind: "add",
				path: targetPath,
				contents
			},
			consumed
		};
	}
	if (firstLine.startsWith(DELETE_FILE_MARKER)) return {
		hunk: {
			kind: "delete",
			path: firstLine.slice(17)
		},
		consumed: 1
	};
	if (firstLine.startsWith(UPDATE_FILE_MARKER)) {
		const targetPath = firstLine.slice(17);
		let remaining = lines.slice(1);
		let consumed = 1;
		let movePath;
		const moveCandidate = remaining[0]?.trim();
		if (moveCandidate?.startsWith(MOVE_TO_MARKER)) {
			movePath = moveCandidate.slice(13);
			remaining = remaining.slice(1);
			consumed += 1;
		}
		const chunks = [];
		while (remaining.length > 0) {
			if (remaining[0].trim() === "") {
				remaining = remaining.slice(1);
				consumed += 1;
				continue;
			}
			if (remaining[0].startsWith("***")) break;
			const { chunk, consumed: chunkLines } = parseUpdateFileChunk(remaining, lineNumber + consumed, chunks.length === 0);
			chunks.push(chunk);
			remaining = remaining.slice(chunkLines);
			consumed += chunkLines;
		}
		if (chunks.length === 0) throw new Error(`Invalid patch hunk at line ${lineNumber}: Update file hunk for path '${targetPath}' is empty`);
		return {
			hunk: {
				kind: "update",
				path: targetPath,
				movePath,
				chunks
			},
			consumed
		};
	}
	throw new Error(`Invalid patch hunk at line ${lineNumber}: '${lines[0]}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`);
}
function parseUpdateFileChunk(lines, lineNumber, allowMissingContext) {
	if (lines.length === 0) throw new Error(`Invalid patch hunk at line ${lineNumber}: Update hunk does not contain any lines`);
	let changeContext;
	let startIndex = 0;
	if (lines[0] === EMPTY_CHANGE_CONTEXT_MARKER) startIndex = 1;
	else if (lines[0].startsWith(CHANGE_CONTEXT_MARKER)) {
		changeContext = lines[0].slice(3);
		startIndex = 1;
	} else if (!allowMissingContext) throw new Error(`Invalid patch hunk at line ${lineNumber}: Expected update hunk to start with a @@ context marker, got: '${lines[0]}'`);
	if (startIndex >= lines.length) throw new Error(`Invalid patch hunk at line ${lineNumber + 1}: Update hunk does not contain any lines`);
	const chunk = {
		changeContext,
		oldLines: [],
		newLines: [],
		isEndOfFile: false
	};
	let parsedLines = 0;
	for (const line of lines.slice(startIndex)) {
		if (line === EOF_MARKER) {
			if (parsedLines === 0) throw new Error(`Invalid patch hunk at line ${lineNumber + 1}: Update hunk does not contain any lines`);
			chunk.isEndOfFile = true;
			parsedLines += 1;
			break;
		}
		const marker = line[0];
		if (!marker) {
			chunk.oldLines.push("");
			chunk.newLines.push("");
			parsedLines += 1;
			continue;
		}
		if (marker === " ") {
			const content = line.slice(1);
			chunk.oldLines.push(content);
			chunk.newLines.push(content);
			parsedLines += 1;
			continue;
		}
		if (marker === "+") {
			chunk.newLines.push(line.slice(1));
			parsedLines += 1;
			continue;
		}
		if (marker === "-") {
			chunk.oldLines.push(line.slice(1));
			parsedLines += 1;
			continue;
		}
		if (parsedLines === 0) throw new Error(`Invalid patch hunk at line ${lineNumber + 1}: Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`);
		break;
	}
	return {
		chunk,
		consumed: parsedLines + startIndex
	};
}
//#endregion
//#region src/agents/local-model-lean.ts
const LOCAL_MODEL_LEAN_DENY_TOOL_NAMES = new Set([
	"browser",
	"cron",
	"message"
]);
const LOCAL_MODEL_LEAN_TOOL_SEARCH_DEFAULTS = {
	enabled: true,
	mode: "tools",
	searchDefaultLimit: 5,
	maxSearchLimit: 10
};
function resolvePreservedLocalModelLeanToolNames(names) {
	if (!names) return /* @__PURE__ */ new Set();
	return new Set(expandToolGroups([...names]).map(normalizeToolName).filter((name) => name && name !== "*"));
}
/** Resolves tool names that must survive local-model lean filtering. */
function resolveLocalModelLeanPreserveToolNames(params) {
	const names = [...params?.toolNames ?? []];
	if (params?.forceMessageTool || params?.sourceReplyDeliveryMode === "message_tool_only") names.push("message");
	return [...new Set(names)];
}
function resolveLocalModelLeanAgentId(params) {
	const explicitAgentId = typeof params.agentId === "string" && params.agentId.trim() ? normalizeAgentId(params.agentId) : void 0;
	if (explicitAgentId) return explicitAgentId;
	const parsedSessionAgentId = parseAgentSessionKey(params.sessionKey)?.agentId;
	if (parsedSessionAgentId) return normalizeAgentId(parsedSessionAgentId);
	return params.config ? resolveDefaultAgentId(params.config) : void 0;
}
/** Returns true when local-model lean mode is enabled for the selected agent. */
function isLocalModelLeanEnabled(params) {
	const normalizedAgentId = resolveLocalModelLeanAgentId(params);
	return (params.config && normalizedAgentId ? resolveAgentConfig(params.config, normalizedAgentId)?.experimental ?? params.config.agents?.defaults?.experimental : params.config?.agents?.defaults?.experimental)?.localModelLean ?? false;
}
/** Filters tools for local-model lean mode while preserving required delivery tools. */
function filterLocalModelLeanTools(params) {
	if (!isLocalModelLeanEnabled(params)) return params.tools;
	const preservedToolNames = resolvePreservedLocalModelLeanToolNames(params.preserveToolNames);
	return params.tools.filter((tool) => {
		const normalizedName = normalizeToolName(tool.name);
		return preservedToolNames.has(normalizedName) || !LOCAL_MODEL_LEAN_DENY_TOOL_NAMES.has(normalizedName);
	});
}
function applyLocalModelLeanToolSearchDefaults(params) {
	if (!params.config || !isLocalModelLeanEnabled(params)) return params.config;
	if (params.config.tools?.toolSearch !== void 0) return params.config;
	return {
		...params.config,
		tools: {
			...params.config.tools,
			toolSearch: LOCAL_MODEL_LEAN_TOOL_SEARCH_DEFAULTS
		}
	};
}
//#endregion
//#region src/agents/tool-search.ts
/**
* Tool Search catalog compaction.
*
* Presents large OpenClaw/MCP/client tool inventories through search, describe, call, and optional code-mode tools.
*/
const TOOL_SEARCH_CODE_MODE_TOOL_NAME = "tool_search_code";
const TOOL_SEARCH_RAW_TOOL_NAME = "tool_search";
const TOOL_DESCRIBE_RAW_TOOL_NAME = "tool_describe";
const TOOL_CALL_RAW_TOOL_NAME = "tool_call";
const TOOL_SEARCH_CONTROL_TOOL_NAMES = new Set([
	TOOL_SEARCH_CODE_MODE_TOOL_NAME,
	TOOL_SEARCH_RAW_TOOL_NAME,
	TOOL_DESCRIBE_RAW_TOOL_NAME,
	TOOL_CALL_RAW_TOOL_NAME
]);
const DEFAULT_CODE_TIMEOUT_MS = 1e4;
const DEFAULT_SEARCH_LIMIT = 8;
const DEFAULT_MAX_SEARCH_LIMIT = 20;
const MAX_REUSABLE_CATALOG_SNAPSHOTS = 256;
const TOOL_SEARCH_CODE_MODE_CHILD_SOURCE = String.raw`
import vm from "node:vm";

let activeController;

function send(message) {
  if (typeof process.send === "function" && process.connected) {
    process.send(message);
  }
}

function sendAndFlush(message) {
  return new Promise((resolve) => {
    if (typeof process.send !== "function" || !process.connected) {
      resolve();
      return;
    }
    try {
      process.send(message, () => resolve());
    } catch {
      resolve();
    }
  });
}

function toJsonSafe(value) {
  if (value === undefined) {
    return null;
  }
  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    if (value instanceof Error) {
      return value.message;
    }
    if (value === null) {
      return null;
    }
    switch (typeof value) {
      case "string":
        return value;
      case "number":
      case "boolean":
      case "bigint":
      case "symbol":
      case "function":
        return String(value);
      default:
        return Object.prototype.toString.call(value);
    }
  }
}

function formatLogItem(value) {
  if (typeof value === "string") {
    return value;
  }
  const safe = toJsonSafe(value);
  return typeof safe === "string" ? safe : JSON.stringify(safe);
}

function bridgeResultPayload(message) {
  if (!message.ok) {
    return typeof message.error === "string" ? message.error : "tool bridge failed";
  }
  const json = JSON.stringify(toJsonSafe(message.value));
  return typeof json === "string" ? json : "null";
}

function settleBridge(message) {
  if (!activeController) {
    return;
  }
  const id = typeof message?.id === "string" ? message.id : "";
  try {
    activeController.settleBridge(id, Boolean(message.ok), bridgeResultPayload(message));
  } catch (error) {
    send({
      type: "result",
      ok: false,
      error: error instanceof Error ? error.message : String(error),
    });
  }
}

function buildModelScriptSource(code) {
  return "(async (openclaw, console) => {\n" + code + "\n})(openclaw, console)";
}

function buildControllerSource() {
  // The controller returns promise-like bridge handles. The model code can await
  // them naturally, while the parent process serializes real tool calls.
  return (
    '"use strict";\n' +
    "(() => {\n" +
    "const pending = new Map();\n" +
    "const bridgeMessages = [];\n" +
    "const logs = [];\n" +
    "let idleWaiters = [];\n" +
    "let nextBridgeId = 1;\n" +
    toJsonSafe.toString() +
    "\n" +
    formatLogItem.toString() +
    "\n" +
    "function notifyBridgeIdle() {\n" +
    "  if (pending.size !== 0 || bridgeMessages.length !== 0) return;\n" +
    "  const waiters = idleWaiters;\n" +
    "  idleWaiters = [];\n" +
    "  for (const resolve of waiters) resolve();\n" +
    "}\n" +
    "function isBridgeIdle() {\n" +
    "  return pending.size === 0 && bridgeMessages.length === 0;\n" +
    "}\n" +
    "function waitForBridgeIdle() {\n" +
    "  if (isBridgeIdle()) return Promise.resolve();\n" +
    "  return new Promise((resolve) => idleWaiters.push(resolve));\n" +
    "}\n" +
    "function bridge(method, args) {\n" +
    "  let promise;\n" +
    "  const start = () => {\n" +
    "    if (!promise) {\n" +
    "      const id = String(nextBridgeId++);\n" +
    "      promise = new Promise((resolve, reject) => {\n" +
    "        pending.set(id, { resolve, reject });\n" +
    "        bridgeMessages.push({ id, method, args: toJsonSafe(args) });\n" +
    "      });\n" +
    "    }\n" +
    "    return promise;\n" +
    "  };\n" +
    "  return Object.freeze({\n" +
    "    then: (resolve, reject) => start().then(resolve, reject),\n" +
    "    catch: (reject) => start().catch(reject),\n" +
    "    finally: (onFinally) => start().finally(onFinally),\n" +
    "  });\n" +
    "}\n" +
    "const console = Object.freeze({\n" +
    "  log: (...items) => logs.push(items.map(formatLogItem)),\n" +
    "  warn: (...items) => logs.push(items.map(formatLogItem)),\n" +
    "  error: (...items) => logs.push(items.map(formatLogItem)),\n" +
    "});\n" +
    "const openclaw = Object.freeze({\n" +
    "  tools: Object.freeze({\n" +
    "    search: (query, options) => bridge('search', [query, options]),\n" +
    "    describe: (id) => bridge('describe', [id]),\n" +
    "    call: (id, input) => bridge('call', [id, input]),\n" +
    "  }),\n" +
    "});\n" +
    "return Object.freeze({\n" +
    "  openclaw,\n" +
    "  console,\n" +
    "  isBridgeIdle,\n" +
    "  waitForBridgeIdle,\n" +
    "  takeLogs: () => logs.splice(0),\n" +
    "  takeBridgeMessages: () => bridgeMessages.splice(0),\n" +
    "  settleBridge: (id, ok, payload) => {\n" +
    "    const waiter = pending.get(String(id));\n" +
    "    if (!waiter) return;\n" +
    "    pending.delete(String(id));\n" +
    "    if (ok) {\n" +
    "      waiter.resolve(JSON.parse(String(payload)));\n" +
    "    } else {\n" +
    "      waiter.reject(new Error(String(payload)));\n" +
    "    }\n" +
    "    Promise.resolve().then(notifyBridgeIdle);\n" +
    "  },\n" +
    "});\n" +
    "})()"
  );
}

function pumpController(controller) {
  for (const items of controller.takeLogs()) {
    send({ type: "log", items });
  }
  for (const message of controller.takeBridgeMessages()) {
    send({ type: "bridge", id: message.id, method: message.method, args: message.args });
  }
}

async function runModelCode(code, timeoutMs) {
  const sandbox = Object.create(null);
  const context = vm.createContext(sandbox, {
    name: "tool_search_code",
    codeGeneration: { strings: false, wasm: false },
  });
  const controllerScript = new vm.Script(buildControllerSource(), {
    filename: "tool_search_code:controller.js",
  });
  const controller = controllerScript.runInContext(context, {
    timeout: Math.max(1, Math.min(Number(timeoutMs) || 1, 2147483647)),
    breakOnSigint: false,
  });
  Object.defineProperties(sandbox, {
    console: { value: controller.console, enumerable: true },
    openclaw: { value: controller.openclaw, enumerable: true },
  });
  activeController = controller;
  const pumpTimer = setInterval(() => pumpController(controller), 1);
  try {
    const modelScript = new vm.Script(buildModelScriptSource(code), {
      filename: "tool_search_code:model.js",
    });
    const result = await Promise.resolve(
      modelScript.runInContext(context, {
        timeout: Math.max(1, Math.min(Number(timeoutMs) || 1, 2147483647)),
        breakOnSigint: false,
      }),
    ).then(
      (value) => ({ ok: true, value: toJsonSafe(value) }),
      (error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }),
    );
    do {
      pumpController(controller);
      await controller.waitForBridgeIdle();
      pumpController(controller);
    } while (!controller.isBridgeIdle());
    pumpController(controller);
    await sendAndFlush(
      result.ok
        ? { type: "result", ok: true, value: result.value }
        : { type: "result", ok: false, error: result.error },
    );
  } finally {
    clearInterval(pumpTimer);
    activeController = undefined;
  }
}

process.on("message", (message) => {
  if (message?.type === "bridge-result") {
    settleBridge(message);
    return;
  }
  if (message?.type !== "run") {
    return;
  }
  const code = typeof message.code === "string" ? message.code : "";
  runModelCode(code, message.timeoutMs).catch((error) => {
    return sendAndFlush({
      type: "result",
      ok: false,
      error: error instanceof Error ? error.message : String(error),
    });
  }).finally(() => {
    setTimeout(() => process.exit(0), 100);
  });
});
`;
const SESSION_CATALOGS_KEY = Symbol.for("openclaw.toolSearch.sessionCatalogs");
const globalToolSearchState = globalThis;
const sessionCatalogs = globalToolSearchState[SESSION_CATALOGS_KEY] ?? (globalToolSearchState[SESSION_CATALOGS_KEY] = /* @__PURE__ */ new Map());
const reusableCatalogSnapshots = /* @__PURE__ */ new Map();
const catalogFingerprints = /* @__PURE__ */ new WeakMap();
const catalogToolIdentities = /* @__PURE__ */ new WeakMap();
let nextCatalogToolIdentity = 1;
function readToolSearchConfig(config) {
	const toolSearch = (isRecord(config?.tools) ? config.tools : void 0)?.toolSearch;
	if (toolSearch === true) return { enabled: true };
	if (toolSearch === false) return { enabled: false };
	return isRecord(toolSearch) ? toolSearch : {};
}
function readBoolean(value, fallback) {
	return typeof value === "boolean" ? value : fallback;
}
function readInteger(value, fallback) {
	return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
}
function isToolSearchCodeModeSupported() {
	return process.allowedNodeEnvironmentFlags.has("--permission");
}
function resolveMinCodeTimeoutMs() {
	return 1e3;
}
function resolveToolSearchConfig(config) {
	const raw = readToolSearchConfig(config);
	const rawMode = typeof raw.mode === "string" ? raw.mode : "code";
	const requestedMode = rawMode === "tools" || rawMode === "code" ? rawMode : "code";
	const mode = requestedMode === "code" && !isToolSearchCodeModeSupported() ? "tools" : requestedMode;
	const configured = Object.keys(raw).some((key) => key !== "enabled");
	const maxSearchLimit = Math.max(1, Math.min(50, readInteger(raw.maxSearchLimit, DEFAULT_MAX_SEARCH_LIMIT)));
	return {
		enabled: readBoolean(raw.enabled, configured),
		mode,
		codeTimeoutMs: Math.max(resolveMinCodeTimeoutMs(), Math.min(6e4, readInteger(raw.codeTimeoutMs, DEFAULT_CODE_TIMEOUT_MS))),
		searchDefaultLimit: Math.max(1, Math.min(maxSearchLimit, readInteger(raw.searchDefaultLimit, DEFAULT_SEARCH_LIMIT))),
		maxSearchLimit
	};
}
function sessionCatalogKeys(input) {
	const runId = input.runId?.trim();
	if (runId) return [`run:${runId}`];
	const keys = [];
	if (input.sessionId?.trim()) keys.push(`session:${input.sessionId.trim()}`);
	if (input.sessionKey?.trim()) keys.push(`key:${input.sessionKey.trim()}`);
	if (input.agentId?.trim()) keys.push(`agent:${input.agentId.trim()}`);
	return uniqueStrings(keys);
}
function sessionCatalogKey(input) {
	return sessionCatalogKeys(input)[0];
}
function reusableCatalogKey(input) {
	return sessionCatalogKey({
		sessionId: input.sessionId,
		sessionKey: input.sessionKey,
		agentId: input.agentId
	});
}
function stableJsonFingerprint(value, seen = /* @__PURE__ */ new WeakSet()) {
	if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
	if (seen.has(value)) return "\"[Circular]\"";
	seen.add(value);
	if (Array.isArray(value)) return `[${value.map((item) => stableJsonFingerprint(item, seen)).join(",")}]`;
	const record = value;
	return `{${Object.keys(record).toSorted().map((key) => `${JSON.stringify(key)}:${stableJsonFingerprint(record[key], seen)}`).join(",")}}`;
}
function catalogToolIdentity(tool) {
	const existing = catalogToolIdentities.get(tool);
	if (existing !== void 0) return existing;
	const next = nextCatalogToolIdentity;
	nextCatalogToolIdentity += 1;
	catalogToolIdentities.set(tool, next);
	return next;
}
function catalogEntriesFingerprint(entries) {
	return entries.map((entry) => [
		entry.id,
		entry.source,
		entry.sourceName ?? "",
		stableJsonFingerprint(entry.mcp),
		entry.name,
		entry.label ?? "",
		entry.description,
		stableJsonFingerprint(entry.parameters),
		String(catalogToolIdentity(entry.tool))
	].map((part) => JSON.stringify(part)).join(":")).toSorted().join("\n");
}
function restoreToolSearchCatalog(params) {
	const keys = sessionCatalogKeys(params);
	if (keys.length === 0 && !params.catalogRef) return;
	const next = {
		entries: params.entries,
		searchCount: 0,
		describeCount: 0,
		callCount: 0
	};
	if (params.catalogRef) params.catalogRef.current = next;
	catalogFingerprints.set(next, params.fingerprint);
	for (const key of keys) sessionCatalogs.set(key, next);
	return next;
}
function bindToolSearchCatalog(params) {
	if (params.catalogRef) params.catalogRef.current = params.catalog;
	for (const key of sessionCatalogKeys(params)) sessionCatalogs.set(key, params.catalog);
}
function rememberReusableCatalog(key, catalog) {
	if (!key) return;
	const fingerprint = catalogFingerprints.get(catalog);
	if (!fingerprint) return;
	if (reusableCatalogSnapshots.has(key)) reusableCatalogSnapshots.delete(key);
	reusableCatalogSnapshots.set(key, {
		entries: catalog.entries,
		fingerprint
	});
	while (reusableCatalogSnapshots.size > MAX_REUSABLE_CATALOG_SNAPSHOTS) {
		const oldestKey = reusableCatalogSnapshots.keys().next().value;
		if (!oldestKey) break;
		reusableCatalogSnapshots.delete(oldestKey);
	}
}
function classifyTool(tool) {
	const meta = getPluginToolMeta(tool);
	const pluginId = meta?.pluginId?.trim();
	if (pluginId === "bundle-mcp") {
		const mcp = meta?.mcp;
		return {
			source: "mcp",
			sourceName: pluginId,
			...mcp ? { mcp } : {}
		};
	}
	if (pluginId) return {
		source: "openclaw",
		sourceName: pluginId
	};
	return {
		source: "openclaw",
		sourceName: "core"
	};
}
function makeCatalogId(tool, source, sourceName) {
	return `${source}:${sourceName?.trim() || "core"}:${tool.name}`;
}
function wrapCatalogTool(tool, hookContext) {
	if (!hookContext || isToolWrappedWithBeforeToolCallHook(tool)) return tool;
	return wrapToolWithBeforeToolCallHook(tool, hookContext);
}
function toCatalogEntry(tool, sourceOverride, hookContext) {
	const classified = classifyTool(tool);
	const source = sourceOverride ?? classified.source;
	const sourceName = sourceOverride === "client" ? "client" : classified.sourceName;
	const catalogTool = source === "client" ? tool : wrapCatalogTool(tool, hookContext);
	return {
		id: makeCatalogId(tool, source, sourceName),
		source,
		sourceName,
		...source === "mcp" && classified.mcp ? { mcp: classified.mcp } : {},
		name: tool.name,
		label: tool.label,
		description: tool.description ?? "",
		parameters: tool.parameters,
		tool: catalogTool
	};
}
function shouldCatalogTool(tool) {
	if (TOOL_SEARCH_CONTROL_TOOL_NAMES.has(tool.name)) return false;
	return true;
}
function shouldExposeControlTool(name, mode) {
	if (name === "tool_search_code") return mode === "code";
	if (name === "tool_search" || name === "tool_describe" || name === "tool_call") return mode === "tools";
	return false;
}
function readMessageToolResultId(message) {
	const record = message;
	const role = typeof record.role === "string" ? record.role : "";
	const canUseDirectId = role === "toolResult" || role === "tool";
	const direct = record.toolCallId ?? record.toolUseId ?? record.tool_use_id;
	if (canUseDirectId && typeof direct === "string" && direct.trim()) return direct;
	const content = record.content;
	if (!Array.isArray(content)) return;
	for (const block of content) {
		if (!isRecord(block)) continue;
		if (block.type !== "toolResult") continue;
		const nested = block.toolCallId ?? block.toolUseId ?? block.tool_use_id ?? block.id;
		if (typeof nested === "string" && nested.trim()) return nested;
	}
}
function textFromToolSearchProjectionResult(result, isError) {
	if (isRecord(result)) {
		const detailError = (isRecord(result.details) ? result.details : void 0)?.error;
		if (typeof detailError === "string" && detailError.trim()) return detailError;
		const content = result.content;
		if (Array.isArray(content)) {
			const text = content.map((item) => isRecord(item) && typeof item.text === "string" ? item.text : "").filter(Boolean).join("\n");
			if (text.trim()) return text;
		}
	}
	const safe = toJsonSafe(result);
	if (typeof safe === "string") return safe;
	const encoded = JSON.stringify(safe);
	if (typeof encoded === "string") return encoded;
	return isError ? "Tool Search target tool failed." : "Tool Search target tool completed.";
}
function buildToolSearchTargetTranscriptMessages(projection) {
	const input = toJsonSafe(projection.input);
	const timestamp = projection.timestamp ?? Date.now();
	const resultRecord = isRecord(projection.result) ? projection.result : void 0;
	const resultContent = Array.isArray(resultRecord?.content) && resultRecord.content.length > 0 ? toJsonSafe(resultRecord.content) : [{
		type: "text",
		text: textFromToolSearchProjectionResult(projection.result, projection.isError === true)
	}];
	return [{
		role: "assistant",
		content: [{
			type: "toolCall",
			id: projection.toolCallId,
			name: projection.toolName,
			arguments: input,
			input
		}],
		stopReason: "toolUse",
		timestamp
	}, {
		role: "toolResult",
		toolCallId: projection.toolCallId,
		toolName: projection.toolName,
		isError: projection.isError === true,
		content: resultContent,
		timestamp
	}];
}
function projectToolSearchTargetTranscriptMessages(messages, projections) {
	if (projections.length === 0) return messages;
	const byParent = /* @__PURE__ */ new Map();
	const unmatched = [];
	for (const projection of projections) {
		const parent = projection.parentToolCallId?.trim();
		if (!parent) {
			unmatched.push(projection);
			continue;
		}
		const group = byParent.get(parent) ?? [];
		group.push(projection);
		byParent.set(parent, group);
	}
	const inserted = /* @__PURE__ */ new Set();
	const projected = [];
	for (const message of messages) {
		projected.push(message);
		const toolResultId = readMessageToolResultId(message);
		const group = toolResultId ? byParent.get(toolResultId) : void 0;
		if (!group) continue;
		for (const projection of group) {
			projected.push(...buildToolSearchTargetTranscriptMessages(projection));
			inserted.add(projection);
		}
	}
	for (const projection of [...unmatched, ...projections]) {
		if (inserted.has(projection)) continue;
		projected.push(...buildToolSearchTargetTranscriptMessages(projection));
		inserted.add(projection);
	}
	return projected;
}
/** Create an explicit catalog holder for callers that cannot rely on session keys. */
function createToolSearchCatalogRef() {
	return {};
}
/** Replace visible tools with Tool Search controls and register hidden catalog entries. */
function applyToolSearchCatalog(params) {
	const config = resolveToolSearchConfig(params.config);
	return applyToolCatalogCompaction({
		...params,
		enabled: config.enabled,
		isVisibleControlTool: (tool) => TOOL_SEARCH_CONTROL_TOOL_NAMES.has(tool.name) && shouldExposeControlTool(tool.name, config.mode)
	});
}
/** Move client-provided tools into an existing Tool Search catalog. */
function addClientToolsToToolSearchCatalog(params) {
	return addClientToolsToToolCatalog({
		...params,
		enabled: resolveToolSearchConfig(params.config).enabled
	});
}
/** Register catalog entries under run/session keys and optional direct refs. */
function registerToolSearchCatalog(params) {
	const keys = sessionCatalogKeys(params);
	const primaryKey = keys[0];
	if (!primaryKey && !params.catalogRef) return;
	const prior = params.append ? params.catalogRef?.current ?? (primaryKey ? sessionCatalogs.get(primaryKey) : void 0) : void 0;
	const byId = /* @__PURE__ */ new Map();
	for (const entry of prior?.entries ?? []) byId.set(entry.id, entry);
	for (const entry of params.entries) {
		byId.set(entry.id, entry);
		byId.set(entry.name, entry);
	}
	const next = {
		entries: uniqueValues(byId.values()).toSorted((a, b) => a.id.localeCompare(b.id)),
		searchCount: prior?.searchCount ?? 0,
		describeCount: prior?.describeCount ?? 0,
		callCount: prior?.callCount ?? 0
	};
	catalogFingerprints.set(next, catalogEntriesFingerprint(next.entries));
	if (params.catalogRef) params.catalogRef.current = next;
	for (const key of keys) sessionCatalogs.set(key, next);
	return next;
}
/** Clear Tool Search catalog state for a run/session/ref. */
function clearToolSearchCatalog(params) {
	if (params.catalogRef) params.catalogRef.current = void 0;
	for (const key of sessionCatalogKeys(params)) sessionCatalogs.delete(key);
	if (!params.runId?.trim()) {
		const snapshotKey = reusableCatalogKey(params);
		if (snapshotKey) reusableCatalogSnapshots.delete(snapshotKey);
	}
}
function resolveCatalog(ctx) {
	if (ctx.catalogRef?.current) return ctx.catalogRef.current;
	const keys = sessionCatalogKeys({
		sessionId: ctx.sessionId,
		sessionKey: ctx.sessionKey,
		agentId: ctx.agentId,
		runId: ctx.runId
	});
	for (const key of keys) {
		const catalog = sessionCatalogs.get(key);
		if (catalog) return catalog;
	}
	if (ctx.runId?.trim()) throw new ToolInputError("Tool Search catalog is unavailable for this run.");
	const uniqueCatalogs = uniqueValues(sessionCatalogs.values());
	if (uniqueCatalogs.length === 1) {
		const catalog = uniqueCatalogs[0];
		if (catalog) return catalog;
	}
	throw new ToolInputError("Tool Search catalog is unavailable for this run.");
}
function compactEntry(entry) {
	return {
		id: entry.id,
		source: entry.source,
		sourceName: entry.sourceName,
		...entry.mcp ? { mcp: entry.mcp } : {},
		name: entry.name,
		label: entry.label,
		description: entry.description
	};
}
function describeEntry(entry) {
	return {
		...compactEntry(entry),
		parameters: entry.parameters ?? {}
	};
}
function tokenize(input) {
	return normalizeStringEntries(input.toLowerCase().split(/[^a-z0-9_./:-]+/u));
}
function scoreEntry(entry, terms) {
	if (terms.length === 0) return 1;
	const name = entry.name.toLowerCase();
	const id = entry.id.toLowerCase();
	const label = (entry.label ?? "").toLowerCase();
	const description = entry.description.toLowerCase();
	let score = 0;
	for (const term of terms) {
		if (name === term || id === term) score += 20;
		if (name.includes(term)) score += 8;
		if (id.includes(term)) score += 6;
		if (label.includes(term)) score += 4;
		if (description.includes(term)) score += 2;
	}
	return score;
}
function visibleCatalogEntries(catalog, options) {
	return options?.includeMcp === false ? catalog.entries.filter((entry) => entry.source !== "mcp") : catalog.entries;
}
function findEntry(catalog, id, options) {
	const needle = id.trim();
	const entry = visibleCatalogEntries(catalog, options).find((candidate) => candidate.id === needle || candidate.name === needle);
	if (!entry) throw new ToolInputError(`Unknown tool id: ${needle}`);
	return entry;
}
function findEntryByExactId(catalog, id) {
	const needle = id.trim();
	const entry = catalog.entries.find((candidate) => candidate.id === needle);
	if (!entry) throw new ToolInputError(`Unknown tool id: ${needle}`);
	return entry;
}
function readId(args) {
	const params = asToolParamsRecord(args);
	const value = params.id ?? params.toolId ?? params.name;
	if (typeof value !== "string" || !value.trim()) throw new ToolInputError("id must be a non-empty string.");
	return value.trim();
}
function readLimit(value, config) {
	if (value === void 0) return config.searchDefaultLimit;
	if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new ToolInputError("limit must be a positive integer.");
	return Math.min(value, config.maxSearchLimit);
}
function readSearchArgs(args, config) {
	const params = asToolParamsRecord(args);
	const query = params.query;
	if (typeof query !== "string") throw new ToolInputError("query must be a string.");
	const options = isRecord(params.options) ? params.options : void 0;
	return {
		query,
		limit: readLimit(params.limit ?? options?.limit, config)
	};
}
function readCallArgs(args) {
	const params = asToolParamsRecord(args);
	return {
		id: readId(params),
		input: params.args ?? params.input ?? {}
	};
}
function getTelemetry(catalog) {
	const sources = {
		openclaw: 0,
		mcp: 0,
		client: 0
	};
	for (const entry of catalog.entries) sources[entry.source] += 1;
	return {
		catalogSize: catalog.entries.length,
		sources,
		searchCount: catalog.searchCount,
		describeCount: catalog.describeCount,
		callCount: catalog.callCount
	};
}
function sanitizeToolCallIdPart(value) {
	return value.trim().replace(/[^A-Za-z0-9_.:-]+/g, "_").slice(0, 120) || "call";
}
var ToolSearchRuntime = class {
	constructor(ctx, config) {
		this.ctx = ctx;
		this.config = config;
		this.callSequence = 0;
		this.search = async (query, options) => {
			const catalog = resolveCatalog(this.ctx);
			catalog.searchCount += 1;
			const limit = readLimit(options?.limit, this.config);
			const terms = tokenize(query);
			return visibleCatalogEntries(catalog, options).map((entry) => ({
				entry,
				score: scoreEntry(entry, terms)
			})).filter((hit) => hit.score > 0).toSorted((a, b) => b.score - a.score || a.entry.id.localeCompare(b.entry.id)).slice(0, limit).map((hit) => compactEntry(hit.entry));
		};
		this.all = (options) => {
			return visibleCatalogEntries(resolveCatalog(this.ctx), options).map((entry) => compactEntry(entry));
		};
		this.namespaceEntries = () => {
			return resolveCatalog(this.ctx).entries.map((entry) => Object.assign(compactEntry(entry), { parameters: entry.parameters ?? {} }));
		};
		this.describe = async (id, options) => {
			const catalog = resolveCatalog(this.ctx);
			catalog.describeCount += 1;
			return describeEntry(findEntry(catalog, id, options));
		};
		this.call = async (id, input, options) => {
			const catalog = resolveCatalog(this.ctx);
			const entry = findEntry(catalog, id);
			return await this.callEntry(catalog, entry, input, options);
		};
		this.callExactId = async (id, input, options) => {
			const catalog = resolveCatalog(this.ctx);
			const entry = findEntryByExactId(catalog, id);
			return await this.callEntry(catalog, entry, input, options);
		};
		this.callEntry = async (catalog, entry, input, options) => {
			catalog.callCount += 1;
			const toolCallId = `tool_search_code:${sanitizeToolCallIdPart(options?.parentToolCallId ?? "direct")}:${entry.name}:${++this.callSequence}`;
			const result = await (this.ctx.executeTool ?? (async (params) => await params.tool.execute(params.toolCallId, params.input, params.signal, params.onUpdate, void 0)))({
				tool: entry.tool,
				toolName: entry.name,
				toolCallId,
				parentToolCallId: options?.parentToolCallId,
				input: input ?? {},
				signal: options?.signal ?? this.ctx.abortSignal,
				onUpdate: options?.onUpdate
			});
			return {
				tool: compactEntry(entry),
				result
			};
		};
	}
	telemetry() {
		return getTelemetry(resolveCatalog(this.ctx));
	}
};
/** Compact a native tool list into visible control tools plus hidden catalog entries. */
function applyToolCatalogCompaction(params) {
	if (!params.enabled) return {
		tools: params.tools,
		compacted: false,
		catalogToolCount: 0,
		catalogRegistered: false,
		catalogReused: false
	};
	const hasControlTool = params.tools.some((tool) => params.isVisibleControlTool(tool));
	const key = sessionCatalogKey(params);
	if (!hasControlTool || !key && !params.catalogRef) return {
		tools: params.tools.filter((tool) => !TOOL_SEARCH_CONTROL_TOOL_NAMES.has(tool.name)),
		compacted: false,
		catalogToolCount: 0,
		catalogRegistered: false,
		catalogReused: false
	};
	const visible = [];
	const catalog = [];
	const shouldCatalog = params.shouldCatalogTool ?? shouldCatalogTool;
	for (const tool of params.tools) {
		if (params.isVisibleControlTool(tool)) {
			visible.push(tool);
			continue;
		}
		if (TOOL_SEARCH_CONTROL_TOOL_NAMES.has(tool.name)) continue;
		if (shouldCatalog(tool)) {
			catalog.push(toCatalogEntry(tool, void 0, params.toolHookContext));
			continue;
		}
		visible.push(tool);
	}
	const incomingFingerprint = catalogEntriesFingerprint(catalog);
	const existingCatalog = params.catalogRef?.current ?? (key ? sessionCatalogs.get(key) : void 0);
	if (existingCatalog && catalogFingerprints.get(existingCatalog) === incomingFingerprint) {
		bindToolSearchCatalog({
			sessionId: params.sessionId,
			sessionKey: params.sessionKey,
			agentId: params.agentId,
			runId: params.runId,
			catalogRef: params.catalogRef,
			catalog: existingCatalog
		});
		return {
			tools: visible,
			compacted: catalog.length > 0,
			catalogToolCount: catalog.length,
			catalogRegistered: true,
			catalogReused: true
		};
	}
	const reusableKey = reusableCatalogKey(params);
	const reusableSnapshot = reusableKey ? reusableCatalogSnapshots.get(reusableKey) : void 0;
	if (reusableSnapshot?.fingerprint === incomingFingerprint) {
		restoreToolSearchCatalog({
			sessionId: params.sessionId,
			sessionKey: params.sessionKey,
			agentId: params.agentId,
			runId: params.runId,
			catalogRef: params.catalogRef,
			entries: reusableSnapshot.entries,
			fingerprint: reusableSnapshot.fingerprint
		});
		if (reusableKey) {
			reusableCatalogSnapshots.delete(reusableKey);
			reusableCatalogSnapshots.set(reusableKey, reusableSnapshot);
		}
		return {
			tools: visible,
			compacted: catalog.length > 0,
			catalogToolCount: catalog.length,
			catalogRegistered: true,
			catalogReused: true
		};
	}
	const registered = registerToolSearchCatalog({
		sessionId: params.sessionId,
		sessionKey: params.sessionKey,
		agentId: params.agentId,
		runId: params.runId,
		catalogRef: params.catalogRef,
		entries: catalog,
		append: false
	});
	if (registered) rememberReusableCatalog(reusableKey, registered);
	return {
		tools: visible,
		compacted: catalog.length > 0,
		catalogToolCount: catalog.length,
		catalogRegistered: true,
		catalogReused: false
	};
}
/** Append client-side tool definitions to an already registered catalog. */
function addClientToolsToToolCatalog(params) {
	const key = sessionCatalogKey(params);
	if (!params.enabled || !key && !params.catalogRef) return {
		tools: params.tools,
		compacted: false,
		catalogToolCount: 0
	};
	if (!(params.catalogRef?.current ?? (key ? sessionCatalogs.get(key) : void 0))) return {
		tools: params.tools,
		compacted: false,
		catalogToolCount: 0
	};
	registerToolSearchCatalog({
		sessionId: params.sessionId,
		sessionKey: params.sessionKey,
		agentId: params.agentId,
		runId: params.runId,
		catalogRef: params.catalogRef,
		entries: params.tools.map((tool) => toCatalogEntry(tool, "client")),
		append: true
	});
	return {
		tools: [],
		compacted: params.tools.length > 0,
		catalogToolCount: params.tools.length
	};
}
function toJsonSafe(value) {
	if (value === void 0) return null;
	try {
		const serialized = JSON.stringify(value);
		return serialized === void 0 ? null : JSON.parse(serialized);
	} catch {
		if (value instanceof Error) return value.message;
		if (value === null) return null;
		switch (typeof value) {
			case "string": return value;
			case "number":
			case "boolean":
			case "bigint":
			case "symbol":
			case "function": return String(value);
			default: return Object.prototype.toString.call(value);
		}
	}
}
async function runCodeMode(params) {
	const runtime = new ToolSearchRuntime(params.ctx, params.config);
	const logs = [];
	return {
		ok: true,
		value: toJsonSafe(await runCodeModeChild({
			code: params.code,
			config: params.config,
			logs,
			parentToolCallId: params.toolCallId,
			runtime,
			signal: params.signal,
			onUpdate: params.onUpdate
		})),
		logs,
		telemetry: runtime.telemetry()
	};
}
function buildCodeModeChildArgs() {
	if (!process.allowedNodeEnvironmentFlags.has("--permission")) throw new ToolInputError("tool_search_code requires a Node runtime with --permission support.");
	return [
		"--permission",
		"--input-type=module",
		"--eval",
		TOOL_SEARCH_CODE_MODE_CHILD_SOURCE
	];
}
function isCodeModeBridgeMethod(value) {
	return value === "search" || value === "describe" || value === "call";
}
async function runCodeModeBridgeRequest(runtime, method, args, options) {
	const values = Array.isArray(args) ? args : [];
	switch (method) {
		case "search": {
			const query = values[0];
			if (typeof query !== "string") throw new ToolInputError("search query must be a string.");
			const optionsLocal = isRecord(values[1]) ? values[1] : void 0;
			return await runtime.search(query, { limit: typeof optionsLocal?.limit === "number" ? optionsLocal.limit : void 0 });
		}
		case "describe": {
			const id = values[0];
			if (typeof id !== "string") throw new ToolInputError("describe id must be a string.");
			return await runtime.describe(id);
		}
		case "call": {
			const id = values[0];
			if (typeof id !== "string") throw new ToolInputError("call id must be a string.");
			return await runtime.call(id, values[1] ?? {}, options);
		}
	}
	throw new ToolInputError("Unsupported tool_search_code bridge method.");
}
function runCodeModeChild(params) {
	return new Promise((resolve, reject) => {
		const child = spawn(process.execPath, buildCodeModeChildArgs(), {
			cwd: os.tmpdir(),
			env: {},
			stdio: [
				"ignore",
				"pipe",
				"pipe",
				"ipc"
			]
		});
		const stderr = [];
		let settled = false;
		let timedOut = false;
		let exitRejectionTimer;
		const bridgeAbortController = new AbortController();
		const settle = (callback) => {
			if (settled) return;
			settled = true;
			if (timer) clearTimeout(timer);
			if (exitRejectionTimer) clearTimeout(exitRejectionTimer);
			params.signal?.removeEventListener("abort", abortFromParent);
			child.kill();
			callback();
		};
		const abortFromParent = () => {
			bridgeAbortController.abort(params.signal?.reason);
			child.kill("SIGKILL");
			settle(() => reject(/* @__PURE__ */ new Error("tool_search_code aborted")));
		};
		const timer = setTimeout(() => {
			timedOut = true;
			bridgeAbortController.abort(/* @__PURE__ */ new Error("tool_search_code timed out"));
			child.kill("SIGKILL");
			settle(() => reject(/* @__PURE__ */ new Error("tool_search_code timed out")));
		}, params.config.codeTimeoutMs);
		params.signal?.addEventListener("abort", abortFromParent, { once: true });
		if (params.signal?.aborted) {
			abortFromParent();
			return;
		}
		child.stderr?.setEncoding("utf8");
		child.stderr?.on("data", (chunk) => {
			stderr.push(chunk);
		});
		child.on("error", (error) => {
			settle(() => reject(error));
		});
		child.on("exit", (code, signal) => {
			if (settled) return;
			const rejectOnExit = () => {
				const suffix = stderr.join("").trim();
				const detail = suffix ? `: ${suffix.slice(0, 500)}` : "";
				settle(() => reject(/* @__PURE__ */ new Error(timedOut ? "tool_search_code timed out" : `tool_search_code child exited with ${signal ?? code}${detail}`)));
			};
			if (code === 0 && signal === null) {
				exitRejectionTimer = setTimeout(rejectOnExit, 250);
				return;
			}
			rejectOnExit();
		});
		child.on("message", (message) => {
			if (settled) return;
			if (!isRecord(message) || typeof message.type !== "string") return;
			if (message.type === "log") {
				const items = Array.isArray(message.items) ? message.items : [];
				params.logs.push(items.map((item) => String(item)).join(" "));
				return;
			}
			if (message.type === "result") {
				if (message.ok) settle(() => resolve(message.value));
				else settle(() => reject(new Error(typeof message.error === "string" ? message.error : "code failed")));
				return;
			}
			if (message.type !== "bridge") return;
			const id = typeof message.id === "string" ? message.id : "";
			const method = isCodeModeBridgeMethod(message.method) ? message.method : void 0;
			if (!id || !method) return;
			runCodeModeBridgeRequest(params.runtime, method, message.args, {
				parentToolCallId: params.parentToolCallId,
				signal: bridgeAbortController.signal,
				onUpdate: params.onUpdate
			}).then((value) => {
				if (settled || !child.connected) return;
				const response = {
					type: "bridge-result",
					id,
					ok: true,
					value: toJsonSafe(value)
				};
				child.send(response, () => void 0);
			}).catch((error) => {
				if (settled || !child.connected) return;
				const response = {
					type: "bridge-result",
					id,
					ok: false,
					error: error instanceof Error ? error.message : String(error)
				};
				child.send(response, () => void 0);
			});
		});
		child.send({
			type: "run",
			code: params.code,
			timeoutMs: params.config.codeTimeoutMs
		});
	});
}
function readCode(args) {
	const code = asToolParamsRecord(args).code;
	if (typeof code !== "string" || !code.trim()) throw new ToolInputError("code must be a non-empty string.");
	return code;
}
/** Create Tool Search control tools for the current run/session context. */
function createToolSearchTools(ctx) {
	const config = resolveToolSearchConfig(ctx.runtimeConfig ?? ctx.config);
	const runtime = new ToolSearchRuntime(ctx, config);
	return [
		{
			name: TOOL_SEARCH_CODE_MODE_TOOL_NAME,
			label: "Tool Search Code",
			description: "Run JavaScript in an isolated Node subprocess with openclaw.tools.search, openclaw.tools.describe, and openclaw.tools.call for large tool catalogs.",
			parameters: Type.Object({ code: Type.String({ description: "JavaScript body for an async function. Use return to return the final value. The openclaw.tools bridge is available." }) }),
			execute: async (toolCallId, args, signal, onUpdate) => jsonResult(await runCodeMode({
				toolCallId,
				ctx,
				code: readCode(args),
				config,
				signal,
				onUpdate
			}))
		},
		{
			name: TOOL_SEARCH_RAW_TOOL_NAME,
			label: "Tool Search",
			description: "Search the effective Tool Search catalog.",
			parameters: Type.Object({
				query: Type.String({ description: "Search query." }),
				limit: Type.Optional(Type.Number({ description: "Maximum number of results." }))
			}),
			execute: async (_toolCallId, args) => {
				const search = readSearchArgs(args, config);
				return jsonResult(await runtime.search(search.query, { limit: search.limit }));
			}
		},
		{
			name: TOOL_DESCRIBE_RAW_TOOL_NAME,
			label: "Tool Describe",
			description: "Load the full schema and metadata for one search result.",
			parameters: Type.Object({ id: Type.String({ description: "Tool search result id or tool name." }) }),
			execute: async (_toolCallId, args) => jsonResult(await runtime.describe(readId(args)))
		},
		{
			name: TOOL_CALL_RAW_TOOL_NAME,
			label: "Tool Call",
			description: "Call a selected Tool Search catalog entry through OpenClaw.",
			parameters: Type.Object({
				id: Type.String({ description: "Tool search result id or tool name." }),
				args: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Tool input." }))
			}),
			execute: async (_toolCallId, args, signal, onUpdate) => {
				const call = readCallArgs(args);
				return jsonResult(await runtime.call(call.id, call.input, {
					parentToolCallId: _toolCallId,
					signal,
					onUpdate
				}));
			}
		}
	];
}
//#endregion
//#region src/agents/agent-tools.ts
/**
* Builds the effective OpenClaw agent tool surface.
* Assembles core, shell, channel, OpenClaw, plugin, and Tool Search tools, then
* applies sandbox, profile, provider, sender, group, and sub-agent policy.
*/
const MEMORY_FLUSH_ALLOWED_TOOL_NAMES = new Set(["read", "write"]);
function readOnlySandboxReadMounts(sandbox) {
	if (!sandbox) return;
	const mounts = [];
	if (sandbox.workspaceAccess === "ro" && sandbox.agentWorkspaceDir !== sandbox.workspaceDir) mounts.push({
		containerRoot: SANDBOX_AGENT_WORKSPACE_MOUNT,
		hostRoot: sandbox.agentWorkspaceDir
	});
	if (sandbox.workspaceAccess === "rw") mounts.push(...resolveReadOnlyWorkspaceSkillMounts({
		workspaceDir: sandbox.workspaceDir,
		agentWorkspaceDir: sandbox.agentWorkspaceDir,
		skillsWorkspaceDir: sandbox.skillsWorkspaceDir,
		workdir: sandbox.containerWorkdir,
		workspaceAccess: sandbox.workspaceAccess
	}).map((mount) => ({
		containerRoot: mount.containerPath,
		hostRoot: mount.hostPath
	})));
	return mounts.length > 0 ? mounts : void 0;
}
function resolveSkillReadRoots(skillsSnapshot) {
	const roots = /* @__PURE__ */ new Set();
	for (const skill of skillsSnapshot?.resolvedSkills ?? []) {
		const baseDir = typeof skill.baseDir === "string" ? skill.baseDir.trim() : "";
		const filePath = typeof skill.filePath === "string" ? skill.filePath.trim() : "";
		const root = baseDir || (filePath ? path.dirname(filePath) : "");
		if (!root || !path.isAbsolute(root)) continue;
		roots.add(path.resolve(root));
	}
	if (roots.size === 0) return;
	return Array.from(roots);
}
const bashToolsModuleLoader = createLazyImportLoader(() => import("./bash-tools-Cu5Jr4Pk.js"));
function loadBashToolsModule() {
	return bashToolsModuleLoader.load();
}
function createLazyExecTool(defaults) {
	let loadedTool;
	const loadTool = async () => {
		if (!loadedTool) {
			const { createExecTool } = await loadBashToolsModule();
			loadedTool = createExecTool(defaults);
		}
		return loadedTool;
	};
	return {
		name: "exec",
		label: "exec",
		displaySummary: EXEC_TOOL_DISPLAY_SUMMARY,
		get description() {
			return describeExecTool({
				agentId: defaults?.agentId,
				hasCronTool: defaults?.hasCronTool === true
			});
		},
		parameters: execSchema,
		prepareBeforeToolCallParams: async (...args) => (await loadTool()).prepareBeforeToolCallParams?.(...args) ?? args[0],
		finalizeBeforeToolCallParams: (params, preparedParams) => loadedTool?.finalizeBeforeToolCallParams?.(params, preparedParams) ?? params,
		execute: async (...args) => (await loadTool()).execute(...args)
	};
}
function createLazyProcessTool(defaults) {
	let loadedTool;
	const loadTool = async () => {
		if (!loadedTool) {
			const { createProcessTool } = await loadBashToolsModule();
			loadedTool = createProcessTool(defaults);
		}
		return loadedTool;
	};
	return {
		name: "process",
		label: "process",
		displaySummary: PROCESS_TOOL_DISPLAY_SUMMARY,
		description: describeProcessTool({ hasCronTool: defaults?.hasCronTool === true }),
		parameters: processSchema,
		execute: async (...args) => (await loadTool()).execute(...args)
	};
}
/** Resolve the process-tool isolation key for exec/process session state. */
function resolveProcessToolScopeKey(params) {
	const explicitScopeKey = params.scopeKey?.trim();
	if (explicitScopeKey) return explicitScopeKey;
	const sessionKey = params.sessionKey?.trim();
	if (sessionKey) return sessionKey;
	const sessionId = params.sessionId?.trim();
	if (sessionId) return sessionId;
	const agentId = params.agentId?.trim();
	return agentId ? `agent:${agentId}` : void 0;
}
function applyModelProviderToolPolicy(toolsInput, params) {
	let tools = toolsInput;
	tools = filterLocalModelLeanTools({
		tools,
		config: params?.config,
		agentId: params?.agentId,
		sessionKey: params?.sessionKey,
		preserveToolNames: params?.localModelLeanPreserveToolNames ?? params?.runtimeToolAllowlist
	});
	if (params?.suppressManagedWebSearch !== false && shouldSuppressManagedWebSearchTool({
		config: params?.config,
		modelProvider: params?.modelProvider,
		modelApi: params?.modelApi,
		modelId: params?.modelId,
		agentId: params?.agentId,
		sessionKey: params?.sessionKey,
		agentDir: params?.agentDir
	})) return tools.filter((tool) => tool.name !== "web_search");
	return tools;
}
function isApplyPatchAllowedForModel(params) {
	const allowModels = Array.isArray(params.allowModels) ? params.allowModels : [];
	if (allowModels.length === 0) return true;
	const modelId = params.modelId?.trim();
	if (!modelId) return false;
	const normalizedModelId = normalizeLowercaseStringOrEmpty(modelId);
	const provider = normalizeOptionalLowercaseString(params.modelProvider);
	const normalizedFull = provider && !normalizedModelId.includes("/") ? `${provider}/${normalizedModelId}` : normalizedModelId;
	return allowModels.some((entry) => {
		const normalized = normalizeOptionalLowercaseString(entry);
		if (!normalized) return false;
		return normalized === normalizedModelId || normalized === normalizedFull;
	});
}
function hasLegacyExecPolicy(exec) {
	return exec?.security !== void 0 || exec?.ask !== void 0;
}
function applyExecPolicyLayer(base, layer) {
	if (!layer) return base;
	if (layer.mode) return {
		mode: layer.mode,
		...resolveExecPolicyForMode(layer.mode)
	};
	if (hasLegacyExecPolicy(layer)) return {
		security: layer.security ?? base.security,
		ask: layer.ask ?? base.ask
	};
	return base;
}
function resolveExecConfig(params) {
	const cfg = params.cfg;
	const globalExec = cfg?.tools?.exec;
	const agentExec = cfg && params.agentId ? resolveAgentConfig(cfg, params.agentId)?.tools?.exec : void 0;
	const layeredPolicy = applyExecPolicyLayer(applyExecPolicyLayer({}, globalExec), agentExec);
	return {
		host: agentExec?.host ?? globalExec?.host,
		mode: layeredPolicy.mode,
		security: layeredPolicy.security,
		ask: layeredPolicy.ask,
		node: agentExec?.node ?? globalExec?.node,
		pathPrepend: agentExec?.pathPrepend ?? globalExec?.pathPrepend,
		safeBins: agentExec?.safeBins ?? globalExec?.safeBins,
		strictInlineEval: agentExec?.strictInlineEval ?? globalExec?.strictInlineEval,
		commandHighlighting: resolveExecCommandHighlighting({
			config: cfg,
			agentId: params.agentId
		}),
		safeBinTrustedDirs: agentExec?.safeBinTrustedDirs ?? globalExec?.safeBinTrustedDirs,
		safeBinProfiles: resolveMergedSafeBinProfileFixtures({
			global: globalExec,
			local: agentExec
		}),
		reviewer: agentExec?.reviewer ?? globalExec?.reviewer,
		backgroundMs: agentExec?.backgroundMs ?? globalExec?.backgroundMs,
		timeoutSec: agentExec?.timeoutSec ?? globalExec?.timeoutSec,
		approvalRunningNoticeMs: agentExec?.approvalRunningNoticeMs ?? globalExec?.approvalRunningNoticeMs,
		cleanupMs: agentExec?.cleanupMs ?? globalExec?.cleanupMs,
		notifyOnExit: agentExec?.notifyOnExit ?? globalExec?.notifyOnExit,
		notifyOnExitEmptySuccess: agentExec?.notifyOnExitEmptySuccess ?? globalExec?.notifyOnExitEmptySuccess,
		applyPatch: agentExec?.applyPatch ?? globalExec?.applyPatch
	};
}
/** Build the runtime tool list for one agent run. */
function createOpenClawCodingTools(options) {
	const execToolName = "exec";
	const sandbox = options?.sandbox?.enabled ? options.sandbox : void 0;
	const isMemoryFlushRun = options?.trigger === "memory";
	if (isMemoryFlushRun && !options?.memoryFlushWritePath) throw new Error("memoryFlushWritePath required for memory-triggered tool runs");
	const memoryFlushWritePath = isMemoryFlushRun ? options.memoryFlushWritePath : void 0;
	const cronSelfRemoveOnlyJobId = options?.trigger === "cron" && options.jobId?.trim() ? options.jobId.trim() : void 0;
	const { agentId, globalPolicy, globalProviderPolicy, agentPolicy, agentProviderPolicy, profile, providerProfile, profileAlsoAllow, providerProfileAlsoAllow } = resolveEffectiveToolPolicy({
		config: options?.config,
		sessionKey: options?.sessionKey,
		agentId: options?.agentId,
		modelProvider: options?.modelProvider,
		modelId: options?.modelId
	});
	const sandboxToolPolicy = sandbox?.tools;
	const groupPolicy = resolveGroupToolPolicy({
		config: options?.config,
		sessionKey: options?.sessionKey,
		spawnedBy: options?.spawnedBy,
		messageProvider: options?.messageProvider,
		groupId: options?.groupId,
		groupChannel: options?.groupChannel,
		groupSpace: options?.groupSpace,
		accountId: options?.agentAccountId,
		senderId: options?.senderId,
		senderName: options?.senderName,
		senderUsername: options?.senderUsername,
		senderE164: options?.senderE164
	});
	const senderPolicy = resolveSenderToolPolicy({
		config: options?.config,
		agentId,
		messageProvider: options?.messageProvider,
		senderId: options?.senderId,
		senderName: options?.senderName,
		senderUsername: options?.senderUsername,
		senderE164: options?.senderE164
	});
	const profilePolicy = resolveToolProfilePolicy(profile);
	const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
	const enableHeartbeatTool = options?.enableHeartbeatTool === true || options?.trigger === "heartbeat" && options?.config?.messages?.visibleReplies === "message_tool";
	const forceHeartbeatTool = options?.forceHeartbeatTool === true || enableHeartbeatTool;
	const toolSearchConfig = resolveToolSearchConfig(options?.config);
	const toolSearchControlsEnabled = options?.includeToolSearchControls === true && toolSearchConfig.enabled;
	const toolSearchControlAllowlist = toolSearchControlsEnabled ? [
		TOOL_SEARCH_CODE_MODE_TOOL_NAME,
		TOOL_SEARCH_RAW_TOOL_NAME,
		TOOL_DESCRIBE_RAW_TOOL_NAME,
		TOOL_CALL_RAW_TOOL_NAME
	] : [];
	const mergeToolSearchControlAllowlist = (policy) => mergeAlsoAllowPolicy(policy, toolSearchControlAllowlist);
	const runtimeToolAllowlistIncludesMessage = expandToolGroups(options?.runtimeToolAllowlist ?? []).some((toolName) => {
		const normalized = normalizeToolName(toolName);
		return normalized === "*" || normalized === "message";
	});
	const localModelLeanPreserveToolNames = resolveLocalModelLeanPreserveToolNames({
		toolNames: options?.runtimeToolAllowlist,
		forceMessageTool: options?.forceMessageTool,
		sourceReplyDeliveryMode: options?.sourceReplyDeliveryMode
	});
	const runtimeProfileAlsoAllow = [
		...options?.forceMessageTool || options?.sourceReplyDeliveryMode === "message_tool_only" ? ["message"] : [],
		...runtimeToolAllowlistIncludesMessage ? ["message"] : [],
		...forceHeartbeatTool ? [HEARTBEAT_RESPONSE_TOOL_NAME] : [],
		...toolSearchControlAllowlist
	];
	const profilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(profilePolicy, [...profileAlsoAllow ?? [], ...runtimeProfileAlsoAllow]);
	const providerProfilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(providerProfilePolicy, [...providerProfileAlsoAllow ?? [], ...runtimeProfileAlsoAllow]);
	const scopeKey = resolveProcessToolScopeKey({
		scopeKey: options?.exec?.scopeKey,
		sessionKey: options?.sessionKey,
		sessionId: options?.sessionId,
		agentId
	});
	const subagentStore = resolveSubagentCapabilityStore(options?.sessionKey, { cfg: options?.config });
	const subagentPolicy = options?.sessionKey && isSubagentEnvelopeSession(options.sessionKey, {
		cfg: options.config,
		store: subagentStore
	}) ? resolveSubagentToolPolicyForSession(options.config, options.sessionKey, { store: subagentStore }) : void 0;
	const inheritedToolPolicy = resolveInheritedToolPolicyForSession(options?.config, options?.sessionKey, { store: subagentStore });
	const globalPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(globalPolicy);
	const globalProviderPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(globalProviderPolicy);
	const agentPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(agentPolicy);
	const agentProviderPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(agentProviderPolicy);
	const groupPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(groupPolicy);
	const senderPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(senderPolicy);
	const sandboxToolPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(sandboxToolPolicy);
	const subagentPolicyWithToolSearchControls = mergeToolSearchControlAllowlist(subagentPolicy);
	const allowBackground = isToolAllowedByPolicies("process", [
		profilePolicyWithAlsoAllow,
		providerProfilePolicyWithAlsoAllow,
		globalPolicyWithToolSearchControls,
		globalProviderPolicyWithToolSearchControls,
		agentPolicyWithToolSearchControls,
		agentProviderPolicyWithToolSearchControls,
		groupPolicyWithToolSearchControls,
		senderPolicyWithToolSearchControls,
		sandboxToolPolicyWithToolSearchControls,
		subagentPolicyWithToolSearchControls,
		inheritedToolPolicy
	]);
	options?.recordToolPrepStage?.("tool-policy");
	const execConfig = resolveExecConfig({
		cfg: options?.config,
		agentId
	});
	const fsConfig = resolveToolFsConfig({
		cfg: options?.config,
		agentId
	});
	const fsPolicy = createToolFsPolicy({ workspaceOnly: isMemoryFlushRun || fsConfig.workspaceOnly });
	const sandboxRoot = sandbox?.workspaceDir;
	const sandboxFsBridge = sandbox?.fsBridge;
	const allowWorkspaceWrites = sandbox?.workspaceAccess !== "ro";
	const workspaceRoot = resolveWorkspaceRoot(options?.workspaceDir);
	const runtimeRoot = resolveWorkspaceRoot(options?.cwd ?? options?.workspaceDir);
	const codingRoot = sandboxRoot ?? runtimeRoot;
	const memoryFlushWriteRoot = sandboxRoot ?? workspaceRoot;
	const includeCoreTools = options?.includeCoreTools !== false;
	const toolConstructionPlan = options?.toolConstructionPlan ?? {
		includeBaseCodingTools: includeCoreTools,
		includeShellTools: includeCoreTools,
		includeChannelTools: includeCoreTools,
		includeOpenClawTools: includeCoreTools,
		includePluginTools: true
	};
	const includeBaseCodingTools = includeCoreTools && toolConstructionPlan.includeBaseCodingTools;
	const includeShellTools = includeCoreTools && toolConstructionPlan.includeShellTools;
	const includeOpenClawTools = includeCoreTools && toolConstructionPlan.includeOpenClawTools;
	const includeChannelTools = toolConstructionPlan.includeChannelTools;
	const includePluginTools = toolConstructionPlan.includePluginTools;
	const workspaceOnly = fsPolicy.workspaceOnly;
	const skillReadRoots = sandboxRoot ? void 0 : resolveSkillReadRoots(options?.skillsSnapshot);
	const applyPatchConfig = execConfig.applyPatch;
	const applyPatchWorkspaceOnly = workspaceOnly || applyPatchConfig?.workspaceOnly !== false;
	const applyPatchEnabled = applyPatchConfig?.enabled !== false && isApplyPatchAllowedForModel({
		modelProvider: options?.modelProvider,
		modelId: options?.modelId,
		allowModels: applyPatchConfig?.allowModels
	});
	if (sandboxRoot && !sandboxFsBridge) throw new Error("Sandbox filesystem bridge is unavailable.");
	const imageSanitization = resolveImageSanitizationLimits(options?.config);
	options?.recordToolPrepStage?.("workspace-policy");
	const base = [];
	if (includeBaseCodingTools) for (const tool of createCodingTools(codingRoot)) {
		if (tool.name === "read") {
			if (sandboxRoot) {
				const sandboxed = createSandboxedReadTool({
					root: sandboxRoot,
					bridge: sandboxFsBridge,
					modelContextWindowTokens: options?.modelContextWindowTokens,
					imageSanitization
				});
				base.push(workspaceOnly ? wrapToolWorkspaceRootGuardWithOptions(sandboxed, sandboxRoot, {
					additionalContainerMounts: readOnlySandboxReadMounts(sandbox),
					containerWorkdir: sandbox.containerWorkdir
				}) : sandboxed);
				continue;
			}
			const wrapped = createOpenClawReadTool(createReadTool(codingRoot), {
				modelContextWindowTokens: options?.modelContextWindowTokens,
				imageSanitization
			});
			base.push(workspaceOnly ? wrapToolWorkspaceRootGuardWithOptions(wrapped, codingRoot, { additionalRoots: skillReadRoots }) : wrapped);
			continue;
		}
		if (tool.name === "bash" || tool.name === execToolName) continue;
		if (tool.name === "write") {
			if (sandboxRoot) continue;
			const wrapped = createHostWorkspaceWriteTool(codingRoot, { workspaceOnly });
			base.push(workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, codingRoot) : wrapped);
			continue;
		}
		if (tool.name === "edit") {
			if (sandboxRoot) continue;
			const wrapped = createHostWorkspaceEditTool(codingRoot, { workspaceOnly });
			base.push(workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, codingRoot) : wrapped);
			continue;
		}
		base.push(tool);
	}
	options?.recordToolPrepStage?.("base-coding-tools");
	const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {};
	const effectiveExecPolicy = applyExecPolicyLayer(execConfig, options?.exec);
	const execTool = includeShellTools ? createLazyExecTool({
		...execDefaults,
		host: options?.exec?.host ?? execConfig.host,
		mode: effectiveExecPolicy.mode,
		security: effectiveExecPolicy.security,
		ask: effectiveExecPolicy.ask,
		config: options?.exec?.config ?? options?.config,
		reviewer: options?.exec?.reviewer ?? execConfig.reviewer,
		trigger: options?.trigger,
		node: options?.exec?.node ?? execConfig.node,
		pathPrepend: options?.exec?.pathPrepend ?? execConfig.pathPrepend,
		safeBins: options?.exec?.safeBins ?? execConfig.safeBins,
		strictInlineEval: options?.exec?.strictInlineEval ?? execConfig.strictInlineEval,
		commandHighlighting: options?.exec?.commandHighlighting ?? execConfig.commandHighlighting,
		safeBinTrustedDirs: options?.exec?.safeBinTrustedDirs ?? execConfig.safeBinTrustedDirs,
		safeBinProfiles: options?.exec?.safeBinProfiles ?? execConfig.safeBinProfiles,
		agentId,
		cwd: codingRoot,
		allowBackground,
		scopeKey,
		sessionKey: options?.sessionKey,
		sessionId: options?.sessionId,
		sessionStore: options?.config?.session?.store,
		mainKey: options?.config?.session?.mainKey,
		sessionScope: options?.config?.session?.scope,
		eventRouting: resolveEventSessionRoutingPolicy({
			cfg: options?.config,
			sessionKey: options?.sessionKey,
			channel: options?.messageProvider,
			accountId: options?.agentAccountId
		}),
		messageProvider: options?.messageProvider,
		currentChannelId: options?.currentChannelId,
		currentThreadTs: options?.currentThreadTs,
		accountId: options?.agentAccountId,
		backgroundMs: options?.exec?.backgroundMs ?? execConfig.backgroundMs,
		timeoutSec: options?.exec?.timeoutSec ?? execConfig.timeoutSec,
		approvalRunningNoticeMs: options?.exec?.approvalRunningNoticeMs ?? execConfig.approvalRunningNoticeMs,
		notifyOnExit: options?.exec?.notifyOnExit ?? execConfig.notifyOnExit,
		notifyOnExitEmptySuccess: options?.exec?.notifyOnExitEmptySuccess ?? execConfig.notifyOnExitEmptySuccess,
		sandbox: sandbox ? {
			containerName: sandbox.containerName,
			workspaceDir: sandbox.workspaceDir,
			containerWorkdir: sandbox.containerWorkdir,
			env: sandbox.backend?.env ?? sandbox.docker.env,
			buildExecSpec: sandbox.backend?.buildExecSpec.bind(sandbox.backend),
			finalizeExec: sandbox.backend?.finalizeExec?.bind(sandbox.backend)
		} : void 0
	}) : null;
	const processTool = includeShellTools ? createLazyProcessTool({
		cleanupMs: cleanupMsOverride ?? execConfig.cleanupMs,
		scopeKey
	}) : null;
	const applyPatchTool = !includeShellTools || !applyPatchEnabled || sandboxRoot && !allowWorkspaceWrites ? null : createApplyPatchTool({
		cwd: codingRoot,
		sandbox: sandboxRoot && allowWorkspaceWrites ? {
			root: sandboxRoot,
			bridge: sandboxFsBridge
		} : void 0,
		workspaceOnly: applyPatchWorkspaceOnly
	});
	options?.recordToolPrepStage?.("shell-tools");
	const pluginToolAllowlist = collectExplicitAllowlist([
		profilePolicy,
		providerProfilePolicy,
		globalPolicy,
		globalProviderPolicy,
		agentPolicy,
		agentProviderPolicy,
		groupPolicy,
		senderPolicy,
		sandboxToolPolicy,
		subagentPolicy,
		inheritedToolPolicy,
		options?.runtimeToolAllowlist ? { allow: options.runtimeToolAllowlist } : void 0
	]);
	const pluginToolDenylist = collectExplicitDenylist([
		profilePolicy,
		providerProfilePolicy,
		globalPolicy,
		globalProviderPolicy,
		agentPolicy,
		agentProviderPolicy,
		groupPolicy,
		senderPolicy,
		sandboxToolPolicy,
		subagentPolicy,
		inheritedToolPolicy
	]);
	const inheritedToolDenylist = [...pluginToolDenylist];
	const inheritedToolAllowlist = [];
	const shouldInheritEffectiveToolAllowlist = [
		profilePolicy,
		providerProfilePolicy,
		globalPolicy,
		globalProviderPolicy,
		agentPolicy,
		agentProviderPolicy,
		groupPolicy,
		senderPolicy,
		sandboxToolPolicy,
		subagentPolicy,
		inheritedToolPolicy,
		options?.runtimeToolAllowlist ? { allow: options.runtimeToolAllowlist } : void 0
	].some(hasRestrictiveAllowPolicy);
	const pluginToolsOnly = includeOpenClawTools || !includePluginTools ? [] : resolveOpenClawPluginToolsForOptions({
		options: {
			agentSessionKey: options?.sessionKey,
			agentChannel: resolveGatewayMessageChannel(options?.messageProvider),
			agentAccountId: options?.agentAccountId,
			agentTo: options?.messageTo,
			agentThreadId: options?.messageThreadId,
			agentDir: options?.agentDir,
			workspaceDir: workspaceRoot,
			config: options?.config,
			fsPolicy,
			requesterSenderId: options?.senderId,
			sessionId: options?.sessionId,
			sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl,
			allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true,
			sandboxed: Boolean(sandbox),
			pluginToolAllowlist,
			pluginToolDenylist,
			currentChannelId: options?.currentChannelId,
			currentThreadTs: options?.currentThreadTs,
			currentMessageId: options?.currentMessageId,
			modelProvider: options?.modelProvider,
			modelId: options?.modelId,
			modelHasVision: options?.modelHasVision,
			requireExplicitMessageTarget: options?.requireExplicitMessageTarget,
			disableMessageTool: options?.disableMessageTool,
			requesterAgentIdOverride: agentId,
			allowGatewaySubagentBinding: options?.allowGatewaySubagentBinding,
			authProfileStore: options?.authProfileStore
		},
		resolvedConfig: options?.config
	});
	const toolSearchTools = toolSearchControlsEnabled ? createToolSearchTools({
		config: options?.config,
		runtimeConfig: options?.config,
		agentId,
		sessionKey: options?.sessionKey,
		sessionId: options?.sessionId,
		runId: options?.runId,
		catalogRef: options?.toolSearchCatalogRef,
		abortSignal: options?.abortSignal,
		executeTool: options?.toolSearchCatalogExecutor
	}) : [];
	const tools = [
		...base,
		...includeBaseCodingTools && sandboxRoot ? allowWorkspaceWrites ? [workspaceOnly ? wrapToolWorkspaceRootGuardWithOptions(createSandboxedEditTool({
			root: sandboxRoot,
			bridge: sandboxFsBridge
		}), sandboxRoot, { containerWorkdir: sandbox.containerWorkdir }) : createSandboxedEditTool({
			root: sandboxRoot,
			bridge: sandboxFsBridge
		}), workspaceOnly ? wrapToolWorkspaceRootGuardWithOptions(createSandboxedWriteTool({
			root: sandboxRoot,
			bridge: sandboxFsBridge
		}), sandboxRoot, { containerWorkdir: sandbox.containerWorkdir }) : createSandboxedWriteTool({
			root: sandboxRoot,
			bridge: sandboxFsBridge
		})] : [] : [],
		...includeShellTools && applyPatchTool ? [applyPatchTool] : [],
		...execTool ? [execTool] : [],
		...processTool ? [processTool] : [],
		...includeChannelTools ? listChannelAgentTools({ cfg: options?.config }) : [],
		...includeOpenClawTools ? createOpenClawTools({
			sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl,
			allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true,
			agentSessionKey: options?.sessionKey,
			runId: options?.runId,
			runSessionKey: options?.runSessionKey,
			agentChannel: resolveGatewayMessageChannel(options?.messageProvider),
			agentAccountId: options?.agentAccountId,
			agentTo: options?.messageTo,
			agentThreadId: options?.messageThreadId,
			agentGroupId: options?.groupId ?? null,
			agentGroupChannel: options?.groupChannel ?? null,
			agentGroupSpace: options?.groupSpace ?? null,
			agentMemberRoleIds: options?.memberRoleIds,
			agentDir: options?.agentDir,
			sandboxRoot,
			sandboxContainerWorkdir: sandbox?.containerWorkdir,
			sandboxFsBridge,
			fsPolicy,
			workspaceDir: workspaceRoot,
			spawnWorkspaceDir: options?.spawnWorkspaceDir ? resolveWorkspaceRoot(options.spawnWorkspaceDir) : void 0,
			sandboxed: Boolean(sandbox),
			config: options?.config,
			pluginToolAllowlist,
			pluginToolDenylist,
			currentChannelId: options?.currentChannelId,
			currentThreadTs: options?.currentThreadTs,
			currentMessageId: options?.currentMessageId,
			currentInboundAudio: options?.currentInboundAudio,
			modelProvider: options?.modelProvider,
			modelId: options?.modelId,
			replyToMode: options?.replyToMode,
			hasRepliedRef: options?.hasRepliedRef,
			modelHasVision: options?.modelHasVision,
			requireExplicitMessageTarget: options?.requireExplicitMessageTarget,
			sourceReplyDeliveryMode: options?.sourceReplyDeliveryMode,
			inboundEventKind: options?.inboundEventKind,
			disableMessageTool: options?.disableMessageTool,
			enableHeartbeatTool,
			disablePluginTools: !includePluginTools,
			wrapBeforeToolCallHook: false,
			...cronSelfRemoveOnlyJobId ? { cronSelfRemoveOnlyJobId } : {},
			requesterAgentIdOverride: agentId,
			requesterSenderId: options?.senderId,
			senderIsOwner: options?.senderIsOwner,
			authProfileStore: options?.authProfileStore,
			sessionId: options?.sessionId,
			inheritedToolAllowlist,
			inheritedToolDenylist,
			onYield: options?.onYield,
			allowGatewaySubagentBinding: options?.allowGatewaySubagentBinding,
			recordToolPrepStage: options?.recordToolPrepStage
		}) : pluginToolsOnly,
		...toolSearchTools
	];
	options?.recordToolPrepStage?.("openclaw-tools");
	const toolsForMemoryFlush = isMemoryFlushRun && memoryFlushWritePath ? [] : tools;
	if (isMemoryFlushRun && memoryFlushWritePath) for (const tool of tools) {
		if (!MEMORY_FLUSH_ALLOWED_TOOL_NAMES.has(tool.name)) continue;
		if (tool.name === "write") {
			toolsForMemoryFlush.push(wrapToolMemoryFlushAppendOnlyWrite(tool, {
				root: memoryFlushWriteRoot,
				relativePath: memoryFlushWritePath,
				containerWorkdir: sandbox?.containerWorkdir,
				sandbox: sandboxRoot && sandboxFsBridge ? {
					root: sandboxRoot,
					bridge: sandboxFsBridge
				} : void 0
			}));
			continue;
		}
		toolsForMemoryFlush.push(tool);
	}
	const unavailableCoreToolReason = isMemoryFlushRun && memoryFlushWritePath ? "memory-triggered compaction runs expose only read and append-only write" : void 0;
	const toolsForMessageProvider = filterToolsByMessageProvider(toolsForMemoryFlush, options?.messageProvider);
	options?.recordToolPrepStage?.("message-provider-policy");
	const toolsForModelProvider = applyModelProviderToolPolicy(toolsForMessageProvider, {
		config: options?.config,
		modelProvider: options?.modelProvider,
		modelApi: options?.modelApi,
		modelId: options?.modelId,
		agentId: options?.agentId,
		sessionKey: options?.sessionKey,
		agentDir: options?.agentDir,
		modelCompat: options?.modelCompat,
		suppressManagedWebSearch: options?.suppressManagedWebSearch,
		runtimeToolAllowlist: options?.runtimeToolAllowlist,
		localModelLeanPreserveToolNames
	});
	options?.recordToolPrepStage?.("model-provider-policy");
	const subagentFiltered = applyToolPolicyPipeline({
		tools: toolsForModelProvider,
		toolMeta: (tool) => getPluginToolMeta(tool),
		warn: logWarn,
		steps: [
			...buildDefaultToolPolicyPipelineSteps({
				profilePolicy: profilePolicyWithAlsoAllow,
				profile,
				profileUnavailableCoreWarningAllowlist: profilePolicy?.allow,
				providerProfilePolicy: providerProfilePolicyWithAlsoAllow,
				providerProfile,
				providerProfileUnavailableCoreWarningAllowlist: providerProfilePolicy?.allow,
				globalPolicy: globalPolicyWithToolSearchControls,
				globalProviderPolicy: globalProviderPolicyWithToolSearchControls,
				agentPolicy: agentPolicyWithToolSearchControls,
				agentProviderPolicy: agentProviderPolicyWithToolSearchControls,
				groupPolicy: groupPolicyWithToolSearchControls,
				senderPolicy: senderPolicyWithToolSearchControls,
				agentId,
				unavailableCoreToolReason
			}),
			{
				policy: sandboxToolPolicyWithToolSearchControls,
				label: "sandbox tools.allow",
				unavailableCoreToolReason
			},
			{
				policy: subagentPolicyWithToolSearchControls,
				label: "subagent tools.allow",
				unavailableCoreToolReason
			},
			{
				policy: inheritedToolPolicy,
				label: "inherited tools",
				unavailableCoreToolReason
			}
		],
		auditLogLevel: options?.toolPolicyAuditLogLevel
	});
	if (shouldInheritEffectiveToolAllowlist) replaceWithEffectiveToolAllowlist(inheritedToolAllowlist, subagentFiltered);
	options?.recordToolPrepStage?.("authorization-policy");
	const normalized = subagentFiltered.map((tool) => normalizeToolParameters(tool, {
		modelProvider: options?.modelProvider,
		modelId: options?.modelId,
		modelCompat: options?.modelCompat
	}));
	options?.recordToolPrepStage?.("schema-normalization");
	const withHooks = normalized.map((tool) => wrapToolWithBeforeToolCallHook(tool, {
		agentId,
		...options?.config ? { config: options.config } : {},
		cwd: codingRoot,
		workspaceDir: workspaceRoot,
		...options?.skillsSnapshot ? { skillsSnapshot: options.skillsSnapshot } : {},
		...sandboxRoot && allowWorkspaceWrites ? { sandbox: {
			root: sandboxRoot,
			bridge: sandboxFsBridge
		} } : {},
		sessionKey: options?.sessionKey,
		sessionId: options?.sessionId,
		runId: options?.runId,
		channelId: options?.hookChannelId ?? options?.currentChannelId,
		...options?.trace ? { trace: options.trace } : {},
		loopDetection: resolveToolLoopDetectionConfig({
			cfg: options?.config,
			agentId
		}),
		onToolOutcome: options?.onToolOutcome
	}, { emitDiagnostics: options?.emitBeforeToolCallDiagnostics }));
	options?.recordToolPrepStage?.("tool-hooks");
	const withAbort = options?.abortSignal ? withHooks.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal)) : withHooks;
	options?.recordToolPrepStage?.("abort-wrappers");
	const withDeferredFollowupDescriptions = applyDeferredFollowupToolDescriptions(withAbort, { agentId });
	options?.recordToolPrepStage?.("deferred-followup-descriptions");
	return withDeferredFollowupDescriptions;
}
//#endregion
export { filterLocalModelLeanTools as _, TOOL_SEARCH_CODE_MODE_TOOL_NAME as a, addClientToolsToToolCatalog as c, applyToolSearchCatalog as d, clearToolSearchCatalog as f, applyLocalModelLeanToolSearchDefaults as g, resolveToolSearchConfig as h, TOOL_DESCRIBE_RAW_TOOL_NAME as i, addClientToolsToToolSearchCatalog as l, projectToolSearchTargetTranscriptMessages as m, resolveProcessToolScopeKey as n, TOOL_SEARCH_RAW_TOOL_NAME as o, createToolSearchCatalogRef as p, TOOL_CALL_RAW_TOOL_NAME as r, ToolSearchRuntime as s, createOpenClawCodingTools as t, applyToolCatalogCompaction as u, isLocalModelLeanEnabled as v, resolveLocalModelLeanPreserveToolNames as y };