UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

4,069 lines • 174 kB
import { a as addTimerTimeoutGraceMs } from "./number-coercion-CkhMiRw8.mjs";
import { t as pruneMapToMaxSize } from "./map-size-BlrH_CnE.mjs";
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs";
import { t as runInDetachedAsyncContext } from "./detached-async-context-D4YGqfxh.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs";
import { y as redactToolDetail } from "./redact-BV3wTtTv.mjs";
import { t as isPlainObject } from "./plain-object-BFvGATnd.mjs";
import "./utils-CitNzLyF.mjs";
import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs";
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { n as registerListener, t as notifyListeners } from "./listeners-BogSNJ-R.mjs";
import { d as readDatabasePathIdentitySync, n as assertDatabasePathIdentity, r as assertExistingDatabaseIdentity } from "./sqlite-worker-identity-BRNRpRyc.mjs";
import { a as createSqliteLifecycleAggregateError } from "./sqlite-lifecycle-errors-DuOygjcn.mjs";
import { F as createDiagnosticToolExecutionLiveness, I as markToolExecutionLivenessDiagnosticEvent, O as freezeDiagnosticTraceContext, _ as onTrustedInternalDiagnosticEvent, c as emitTrustedDiagnosticEventWithPrivateData, l as emitTrustedSecurityEvent, s as emitTrustedDiagnosticEvent, u as emitTrustedSkillUsedDiagnosticEvent, v as onTrustedToolExecutionEvent, w as createChildDiagnosticTraceContext } from "./diagnostic-events-DT4ejwoa.mjs";
import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs";
import { n as sha256Hex } from "./node-crypto-BG4u3UnX.mjs";
import { a as createLazyRuntimeSurface, r as createLazyRuntimeModule } from "./lazy-runtime-CJrzFzqU.mjs";
import { r as isIncognitoSessionKey } from "./session-key-CmZ4o9-l.mjs";
import "./session-key-DY3MOX4N.mjs";
import { g as resolveUnsuffixedSqliteTargetFromSessionStorePath } from "./paths-Bonddxfk.mjs";
import { i as isIncognitoOpenClawAgentSqlitePath, l as resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-BbmZlEP5.mjs";
import { t as retainOpenClawAgentDatabaseReadOnly } from "./openclaw-agent-db-readonly-DAqV3tqr.mjs";
import { l as hasSqliteWorkerOutcomeUnknown } from "./sqlite-worker-contract-C51cJHzg.mjs";
import { r as createSqliteWorkerOperationAdmission } from "./sqlite-worker-operation-admission-C5lBzzTk.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-BzUE_dOh.mjs";
import { o as captureGatewayRootWorkReleaseObserver } from "./gateway-work-admission-DAV5kkgV.mjs";
import { n as runOpenClawAgentWriteAdmission, t as runOpenClawAgentWorkerWrite } from "./openclaw-agent-write-admission-CXJiE5VB.mjs";
import { t as collectSessionEntryLookupKeys } from "./store-entry-ClyG-tia.mjs";
import { n as resolveSqliteAgentId, r as resolveSqliteSessionKey } from "./session-accessor.sqlite-scope-helpers-CfDTAEsL.mjs";
import { i as isSessionStoreReadCandidateCurrent, n as captureSessionStoreCandidateIdentities, t as assertSessionStoreReadCandidate } from "./session-store-read-candidates-CXD-JYGz.mjs";
import { i as prepareSqliteTargetFromSessionStorePath } from "./session-sqlite-target-Dd3VSz82.mjs";
import { c as normalizeToolPolicyName } from "./tool-policy-shared-CNWjndK7.mjs";
import "./tool-policy-BrMCyUgN.mjs";
import { a as composeSessionSourceAssertion, l as releaseSessionSourceAuthorities, n as assertPreparedSessionSourceCurrent, s as prepareSessionSourceAuthority, t as acceptSessionSourceValidation } from "./session-source-authority-h1UO3u0I.mjs";
import { l as getActivePluginRegistry } from "./runtime-U4i1bMh7.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import "./config-VnHbY_QV.mjs";
import { n as captureOpenClawAgentDatabaseExecution } from "./openclaw-agent-execution-C2B2fucX.mjs";
import { a as createPluginToolMatcherScope, c as pluginToolMatcherCoversTool, s as normalizePluginToolMatcher } from "./hook-types-CoUXuQe1.mjs";
import { p as resolveAgentRunAbortLifecycleFields } from "./run-termination-5c-0T0hN.mjs";
import { c as getGlobalHookRunnerRegistry, n as getGlobalHookRunner } from "./hook-runner-global-Cn3xHNvt.mjs";
import { s as cloneHookIsolationValue } from "./hooks-CgZHhfMh.mjs";
import { n as resolveSkillTelemetrySource, r as resolveSkillTelemetrySourceValue } from "./source-DbWOnpPI.mjs";
import { c as withSessionHistoryWorkerDatabase, h as resolveSessionStorePathForScope } from "./session-transcript-worker-runtime-BdmdicNg.mjs";
import { r as buildSessionCreationStamp } from "./session-entry-provenance-C8CX2hws.mjs";
import { a as getCodeModeExecBeforeHookMetadata, d as normalizeCodeModeExecBeforeHookParams, f as reconcileCodeModeExecBeforeHookParams, l as isCodeModeExecToolKind, o as getCodeModeExecBeforeHookMetadataForToolKind, s as isCodeModeControlTool } from "./code-mode-control-tools-FkhKD315.mjs";
import { r as mergeSessionEntry } from "./types-BGDhC4_W.mjs";
import { n as captureSessionStoreWriteCandidates, t as captureSessionStoreReadCandidates } from "./session-store-target-inventory-C9ERDnSH.mjs";
import "./client-DZgDBpiJ.mjs";
import { t as GatewayClientRequestError } from "./request-error-DgMXHa-w.mjs";
import { i as appendTranscriptMessage } from "./session-accessor.sqlite-transcript-write-ClP59_lJ.mjs";
import { n as publishTranscriptUpdate } from "./session-accessor.sqlite-events-x56_8OQX.mjs";
import { s as isNativeSessionEntryRead } from "./session-entry-read-ordered-D14zdHVq.mjs";
import { n as applySessionEntryTargetOperation, s as loadSessionEntryReadOnly, t as applySessionEntryOperation } from "./session-accessor.sqlite-entry-DLa7_9ui.mjs";
import "./session-accessor-C3ljJ974.mjs";
import { l as withSessionEntryReadOnlyInWorker } from "./session-entry-read-runtime-BdnQjoL6.mjs";
import { t as appendExpectedSessionTranscriptTurn } from "./session-accessor.sqlite-transcript-turn-_T1DVPZi.mjs";
import { d as withGatewayToolApprovalOwner, o as getGatewayToolCallerIdentity } from "./gateway-caller-context-CU8n6n_e.mjs";
import { r as getPluginToolMeta } from "./tool-metadata-D5DsuLcG.mjs";
import { _ as getBeforeToolCallDiagnosticOptions, a as copyBeforeToolCallWrapperMetadata, b as getBeforeToolCallSourceTool, c as getToolTerminalPresentation, g as clearBeforeToolCallWrappedMarker, h as bindBeforeToolCallMetadata, p as getChannelAgentToolMeta, v as getBeforeToolCallExecutionWrappers, y as getBeforeToolCallHookContext } from "./agent-tool-metadata-DU21au_v.mjs";
import { a as attachInternalToolExecutionPreparer, n as appendToolLoopWarning } from "./internal-hooks-CSjTREUl.mjs";
import { r as markToolExecutionNotStarted } from "./tool-effect-receipt-BHVAm8DM.mjs";
import { g as registerActiveEmbeddedRunHumanInputWaitForRun } from "./run-state-D054QiMr.mjs";
import { t as isEmbeddedMode } from "./embedded-mode--Mk71zY-.mjs";
import { t as captureDiagnosticToolProgress } from "./diagnostic-run-activity-NzwvGiAW.mjs";
import { i as diagnosticHttpStatusCode, t as diagnosticErrorCategory } from "./diagnostic-error-metadata-Dt79CD6m.mjs";
import { n as resolveDiagnosticModelContentCapturePolicy, t as cloneDiagnosticContentValue } from "./diagnostic-llm-content-P2SGagoP.mjs";
import { r as resolveSkillFileHost } from "./skill-file-host-d_CtoZp0.mjs";
import { n as resolveSkillReadPath, t as isWorkspaceSkillReadPath } from "./workspace-skill-read-path-Jvbv8xxW.mjs";
import { n as recordGenericToolActionDecision, r as runWithGenericToolActionDecision, s as normalizeFileToolPathParam } from "./agent-tools.before-tool-call.decision-CKWdJok8.mjs";
import { c as resolveToolResultFailureKind, i as protectNetworkToolExecutionError, r as isTrustedToolExecutionPreflightError, s as resolveToolExecutionErrorKind, t as formatToolExecutionErrorMessage } from "./tool-result-error-CN2enaAJ.mjs";
import { t as canonicalizePath } from "./paths-C-gBEpJw.mjs";
import { t as sanitizeApprovalScope } from "./approval-scope-BVG6XIJe.mjs";
import { n as MAX_PLUGIN_APPROVAL_TIMEOUT_MS, t as DEFAULT_PLUGIN_APPROVAL_TIMEOUT_MS } from "./plugin-approvals-TfLvsTaY.mjs";
import { t as resolveCanonicalPluginApprovalRequestAllowedDecisions } from "./plugin-approval-canonical-decisions-Bthn_onr.mjs";
import { i as resolveApprovalInitiatingSurfaceState, n as describeNativePluginApprovalClientSetup } from "./exec-approval-surface-msqJ-PiT.mjs";
import { t as callGatewayTool } from "./gateway-1wftaKTM.mjs";
import { n as extractResolvedApplyPatchTargetPaths } from "./apply-patch-paths-MBgDUBMq.mjs";
import { r as getPluginSessionExtensionStateSync } from "./host-hook-state-D_zw_kUa.mjs";
import { t as buildToolMutationState } from "./tool-mutation-BTW6eoAN.mjs";
import { t as buildOutboundSessionContext } from "./session-context-CJIY-7po.mjs";
import { t as resolveSessionDeliveryTarget } from "./targets-session-BYV5YAmn.mjs";
import { a as withClientVoiceSessionResources, n as captureClientVoiceSessionSettlement, o as withClientVoiceSessionSettlement, r as captureClientVoiceSessionSettlementContext, t as assertClientVoiceSessionSettlementCurrent } from "./client-voice-session-lifecycle-D5PcLiM1.mjs";
import { a as readOwnedVoiceSessionFacts, f as VoiceTranscriptOperationRegistry, h as voiceTranscriptEventId, m as normalizeVoiceTranscriptText, o as readVoiceSessionFacts, p as buildPersistedVoiceMessage, r as operationKey, s as readVoiceSessionRecord, t as VOICE_SESSION_STALE_AFTER_MS } from "./client-voice-session-store-H3mgDR6I.mjs";
import { n as hashToolCall } from "./tool-loop-detection-E2pymHui.mjs";
import { i as copyAgentToolSourceExecutionGuard, n as captureAgentToolExecutionBudget, o as runAgentToolSourceExecutionGuard } from "./agent-tool-source-execution-guard-DVdlO49-.mjs";
import path from "node:path";
import { AsyncLocalStorage } from "node:async_hooks";
import os from "node:os";
import { isDeepStrictEqual } from "node:util";
import { createHash, randomUUID } from "node:crypto";
//#region src/plugins/hook-before-tool-call-result.ts
const PluginApprovalResolutions = {
	ALLOW_ONCE: "allow-once",
	ALLOW_ALWAYS: "allow-always",
	DENY: "deny",
	TIMEOUT: "timeout",
	CANCELLED: "cancelled"
};
//#endregion
//#region src/agents/agent-tools.before-tool-call.state.ts
/**
* Shared before_tool_call state for adjusted tool params.
* The adapter and wrapper both consult this map so later execution can use the
* normalized payload selected by hook processing.
*/
const adjustedParamsByToolCallId = /* @__PURE__ */ new Map();
const preExecutionBlockedToolCallIds = /* @__PURE__ */ new Set();
const structuredReplaySafeToolCallIds = /* @__PURE__ */ new Set();
const trackedToolCallIds = /* @__PURE__ */ new Map();
const batchAdmittedToolCallIds = /* @__PURE__ */ new Set();
function buildAdjustedParamsKey(params) {
	if (params.runId && params.runId.trim()) return `${params.runId}:${params.toolCallId}`;
	return params.toolCallId;
}
/** Consume and remove hook-adjusted params for a completed tool call. */
function consumeAdjustedParamsForToolCall(toolCallId, runId) {
	const key = buildAdjustedParamsKey({
		runId,
		toolCallId
	});
	const params = adjustedParamsByToolCallId.get(key);
	adjustedParamsByToolCallId.delete(key);
	return params;
}
/** Snapshot hook-adjusted params without consuming later outcome bookkeeping. */
function peekAdjustedParamsForToolCall(toolCallId, runId) {
	const key = buildAdjustedParamsKey({
		runId,
		toolCallId
	});
	const params = adjustedParamsByToolCallId.get(key);
	return params === void 0 ? void 0 : structuredClone(params);
}
/** Consume whether policy prevented the target tool from starting. */
function consumePreExecutionBlockedToolCall(toolCallId, runId) {
	return preExecutionBlockedToolCallIds.delete(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/** Snapshot whether policy prevented execution without stealing cleanup from the tool owner. */
function peekPreExecutionBlockedToolCall(toolCallId, runId) {
	return preExecutionBlockedToolCallIds.has(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/** Record active wrapper ownership so a racing timeout can inspect the boundary. */
function recordToolExecutionTracked(toolCallId, runId) {
	const key = buildAdjustedParamsKey({
		runId,
		toolCallId
	});
	if (!trackedToolCallIds.has(key)) trackedToolCallIds.set(key, false);
}
function recordToolExecutionStarted(toolCallId, runId) {
	trackedToolCallIds.set(buildAdjustedParamsKey({
		runId,
		toolCallId
	}), true);
}
/** Release execution-boundary evidence when the wrapped invocation settles. */
function clearTrackedToolExecution(toolCallId, runId) {
	trackedToolCallIds.delete(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/**
* Consume exact in-flight execution state. Undefined means the wrapper already
* settled or the producer does not participate in OpenClaw boundary tracking.
*/
function consumeTrackedToolExecutionStarted(toolCallId, runId) {
	const key = buildAdjustedParamsKey({
		runId,
		toolCallId
	});
	const started = trackedToolCallIds.get(key);
	trackedToolCallIds.delete(key);
	return started;
}
function recordStructuredReplaySafeToolCall(toolCallId, runId) {
	structuredReplaySafeToolCallIds.add(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
function consumeStructuredReplaySafeToolCall(toolCallId, runId) {
	return structuredReplaySafeToolCallIds.delete(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/** Mark a call whose loop policy was already admitted with its whole assistant batch. */
function recordBatchAdmittedToolCall(toolCallId, runId) {
	batchAdmittedToolCallIds.add(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/** Consume whole-batch loop admission while leaving the remaining tool policies intact. */
function consumeBatchAdmittedToolCall(toolCallId, runId) {
	return batchAdmittedToolCallIds.delete(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/** Release exact batch-admission markers for prepared calls suppressed by steering. */
function releaseBatchAdmittedToolCalls(toolCallIds, runId) {
	for (const toolCallId of toolCallIds) batchAdmittedToolCallIds.delete(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
}
/** Remove unused batch-admission markers when their embedded run ends. */
function clearBatchAdmittedToolCallsForRun(runId) {
	const prefix = `${runId}:`;
	for (const key of batchAdmittedToolCallIds) if (key.startsWith(prefix)) batchAdmittedToolCallIds.delete(key);
}
//#endregion
//#region src/skills/runtime/run-usage.ts
const MAX_TRACKED_SKILL_USAGE_RUNS = 1024;
const skillUsageByRun = /* @__PURE__ */ new Map();
/** Records the skills the foreground run demonstrably invoked or read. */
function recordRunSkillUsage(params) {
	const runId = params.runId;
	if (!runId) return;
	const usage = skillUsageByRun.get(runId) ?? /* @__PURE__ */ new Map();
	const record = {
		name: params.name,
		source: params.source,
		activation: params.activation,
		...params.skillFile ? { skillFile: params.skillFile } : {}
	};
	usage.set(`${record.source}\u0000${record.name}\u0000${record.activation}`, record);
	skillUsageByRun.set(runId, usage);
	pruneMapToMaxSize(skillUsageByRun, MAX_TRACKED_SKILL_USAGE_RUNS);
}
/** Transfers one completed run's usage receipt to its terminal side effects. */
function consumeRunSkillUsage(runId) {
	if (!runId) return [];
	const usage = skillUsageByRun.get(runId);
	skillUsageByRun.delete(runId);
	return usage ? [...usage.values()] : [];
}
//#endregion
//#region src/agents/agent-tools.before-tool-call.diagnostics.ts
/**
* Diagnostics, skill telemetry, terminal presentation, and loop outcomes for
* before_tool_call execution.
*/
const beforeToolCallLog = createSubsystemLogger("agents/tools");
function startToolExecutionLiveness(event, emitDiagnostics, signal) {
	const liveness = createDiagnosticToolExecutionLiveness(signal);
	if (emitDiagnostics) emitTrustedDiagnosticEvent(markToolExecutionLivenessDiagnosticEvent({
		type: "tool.execution.started",
		...event
	}, liveness.view));
	return liveness;
}
const log$1 = beforeToolCallLog;
const MAX_PENDING_TERMINAL_PRESENTATIONS = 1024;
const LOOP_WARNING_BUCKET_SIZE = 10;
const MAX_LOOP_WARNING_KEYS = 256;
const MAX_TERMINAL_PRESENTATION_CHARS = 2e3;
const pendingTerminalPresentationByToolCall = /* @__PURE__ */ new Map();
function prepareToolTerminalPresentation({ ctx, tool, toolParams, toolCallId, toolCallOrdinal }) {
	const toolName = tool.name;
	const observer = toolCallId ? ctx?.onToolOutcome : void 0;
	const formatter = getToolTerminalPresentation(getBeforeToolCallSourceTool(tool) ?? tool);
	if (!formatter && !observer) return;
	let project;
	if (formatter) {
		const formatterParams = observer ? structuredClone(toolParams) : toolParams;
		project = (result) => {
			try {
				const text = formatter(formatterParams, result)?.text.trim();
				return text ? truncateUtf16Safe(redactToolDetail(text), MAX_TERMINAL_PRESENTATION_CHARS) : void 0;
			} catch (err) {
				log$1.warn(`terminal tool presentation failed: tool=${toolName || "tool"} error=${String(err)}`);
				return;
			}
		};
	}
	return {
		observer,
		toolName,
		project,
		toolCallOrdinal
	};
}
function rememberPendingTerminalPresentation(prepared, runId, toolCallId) {
	if (!prepared?.observer || !toolCallId) return;
	const key = buildAdjustedParamsKey({
		runId,
		toolCallId
	});
	pendingTerminalPresentationByToolCall.set(key, prepared);
	pruneMapToMaxSize(pendingTerminalPresentationByToolCall, MAX_PENDING_TERMINAL_PRESENTATIONS);
}
/** Finalizes a trusted terminal summary after harness result middleware. */
function finalizeToolTerminalPresentation(params) {
	const key = buildAdjustedParamsKey({
		runId: params.runId,
		toolCallId: params.toolCallId
	});
	const pending = pendingTerminalPresentationByToolCall.get(key);
	pendingTerminalPresentationByToolCall.delete(key);
	const observer = pending?.observer ?? params.observer;
	if (!observer) return;
	const toolCallOrdinal = pending?.toolCallOrdinal ?? params.toolCallOrdinal;
	observer({
		toolName: pending?.toolName || params.toolName || "tool",
		argsHash: "",
		resultHash: "",
		...toolCallOrdinal !== void 0 ? { toolCallOrdinal } : {},
		terminalPresentation: params.isError ? void 0 : pending?.project?.(params.result),
		presentationOnly: true
	});
}
const loadBeforeToolCallRuntime = createLazyRuntimeSurface(() => import("./agent-tools.before-tool-call.runtime.js"), ({ beforeToolCallRuntime }) => beforeToolCallRuntime);
function unwrapErrorCause(err) {
	try {
		if (!(err instanceof Error)) return err;
		const cause = Object.getOwnPropertyDescriptor(err, "cause");
		if (cause && "value" in cause && cause.value !== void 0) return cause.value;
	} catch {
		return err;
	}
	return err;
}
function resolveToolErrorDiagnostic(err, signal, errorCategory) {
	const cause = unwrapErrorCause(err);
	const errorCode = diagnosticHttpStatusCode(cause);
	const abortFields = resolveAgentRunAbortLifecycleFields(signal);
	const terminalReason = !abortFields.aborted ? resolveToolExecutionErrorKind(cause) : abortFields.stopReason === "timeout" ? "timed_out" : "cancelled";
	return {
		errorCategory: terminalReason === "cancelled" ? "aborted" : errorCategory ?? diagnosticErrorCategory(cause),
		terminalReason,
		...errorCode ? { errorCode } : {}
	};
}
function resolveToolResultTerminalDiagnostic(result, durationMs) {
	const failureKind = resolveToolResultFailureKind(result);
	if (!failureKind) return {
		type: "tool.execution.completed",
		durationMs
	};
	if (failureKind === "blocked") return {
		type: "tool.execution.blocked",
		deniedReason: "tool_result_blocked",
		reason: "tool_result_blocked"
	};
	return {
		type: "tool.execution.error",
		durationMs,
		errorCategory: "tool_result_error",
		terminalReason: failureKind
	};
}
function resolveToolDiagnosticIdentity(tool) {
	const pluginMeta = getPluginToolMeta(tool);
	if (pluginMeta) return pluginMeta.pluginId === "bundle-mcp" ? {
		toolSource: "mcp",
		toolOwner: pluginMeta.pluginId
	} : {
		toolSource: "plugin",
		toolOwner: pluginMeta.pluginId
	};
	const channelMeta = getChannelAgentToolMeta(tool);
	if (channelMeta) return {
		toolSource: "channel",
		toolOwner: channelMeta.channelId
	};
	return { toolSource: "core" };
}
function canonicalSkillFile(value) {
	const skillFile = value?.trim();
	return skillFile && path.isAbsolute(skillFile) ? canonicalizePath(path.resolve(skillFile)) : void 0;
}
function resolvedSkillUsageMatch(params) {
	const skillFile = canonicalSkillFile(params.skill.filePath);
	return {
		skillName: params.skill.name.trim(),
		skillSource: resolveSkillTelemetrySource(params.skill),
		activation: params.activation,
		...skillFile ? { skillFile } : {}
	};
}
function findResolvedSkillUsageMatch(params) {
	const skillName = params.skillName.trim();
	const candidates = (params.snapshot?.resolvedSkills ?? []).filter((skill) => skill.name.trim() === skillName);
	const skill = candidates.find((candidate) => resolveSkillTelemetrySource(candidate) === params.skillSource) ?? (candidates.length === 1 ? candidates[0] : void 0);
	return skill ? resolvedSkillUsageMatch({
		activation: params.activation,
		skill
	}) : void 0;
}
function resolveRelativeToolPath(candidate, ctx) {
	const trimmed = candidate.trim();
	if (!trimmed) return;
	if (trimmed.startsWith("node://") || isWorkspaceSkillReadPath(trimmed)) return trimmed;
	if (trimmed === "~") return os.homedir();
	if (trimmed.startsWith("~/")) return path.resolve(os.homedir(), trimmed.slice(2));
	if (path.isAbsolute(trimmed)) return path.resolve(trimmed);
	const base = ctx?.workspaceDir ?? ctx?.cwd;
	return base ? path.resolve(base, trimmed) : void 0;
}
function readToolPathCandidate(params, ctx) {
	return isPlainObject(params) && typeof params.path === "string" ? resolveRelativeToolPath(normalizeFileToolPathParam(params.path), ctx) : void 0;
}
function findSkillInstructionMatch(snapshot, candidate) {
	const skill = snapshot.resolvedSkills?.findLast((entry) => {
		if (typeof entry.name !== "string" || !entry.name.trim()) return false;
		const filePath = typeof entry.filePath === "string" ? entry.filePath.trim() : "";
		const baseDir = typeof entry.baseDir === "string" ? entry.baseDir.trim() : "";
		if (filePath && resolveSkillReadPath(entry) === candidate) return true;
		if (resolveSkillFileHost(entry) === "workspace") return false;
		return filePath && (filePath.startsWith("node://") ? filePath === candidate : path.isAbsolute(filePath) && path.resolve(filePath) === candidate) || baseDir && path.isAbsolute(baseDir) && path.resolve(baseDir, "SKILL.md") === candidate;
	});
	return skill ? resolvedSkillUsageMatch({
		activation: "read",
		skill
	}) : void 0;
}
function findSkillUsageMatch(params) {
	const command = params.ctx?.skillCommand;
	if (command) {
		const commandToolName = normalizeToolPolicyName(command.toolName ?? params.toolName);
		if (!commandToolName || commandToolName === params.toolName) {
			const skillSource = resolveSkillTelemetrySourceValue(command.skillSource);
			const snapshotMatch = findResolvedSkillUsageMatch({
				activation: "command",
				skillName: command.skillName,
				skillSource,
				snapshot: params.ctx?.skillsSnapshot
			});
			const skillFile = canonicalSkillFile(command.skillFile) ?? snapshotMatch?.skillFile;
			return {
				skillName: command.skillName,
				skillSource,
				activation: "command",
				...skillFile ? { skillFile } : {}
			};
		}
	}
	if (params.toolName === "skills_read") {
		const name = isPlainObject(params.toolParams) ? params.toolParams.name : void 0;
		if (typeof name !== "string") return;
		const snapshot = params.ctx?.skillsSnapshot;
		const skill = (snapshot?.discoverySkills ?? snapshot?.resolvedSkills)?.find((entry) => entry.name === name.trim() && !entry.disableModelInvocation);
		if (!skill) return;
		const usage = params.ctx?.skillUsagePaths?.find((entry) => entry.skillName === skill.name && entry.readPath === skill.filePath);
		return usage ? {
			skillFile: usage.skillFile,
			skillName: usage.skillName,
			skillSource: usage.skillSource,
			activation: "read"
		} : resolvedSkillUsageMatch({
			activation: "read",
			skill
		});
	}
	if (params.toolName !== "read") return;
	const candidate = readToolPathCandidate(params.toolParams, params.ctx);
	if (!candidate) return;
	if (params.ctx?.skillsSnapshot?.resolvedSkills?.length) return findSkillInstructionMatch(params.ctx.skillsSnapshot, candidate);
	const match = params.ctx?.skillUsagePaths?.findLast((entry) => path.resolve(entry.readPath) === candidate);
	return match ? {
		skillFile: match.skillFile,
		skillName: match.skillName,
		skillSource: match.skillSource,
		activation: "read"
	} : void 0;
}
/**
* Records one demonstrated skill use: this run's usage receipt (review trigger) and the
* trusted skill.used event (skill_usage rows, unused-skill archive clock).
*/
function recordSkillUsed(params) {
	recordRunSkillUsage({
		runId: params.ctx?.runId,
		name: params.match.skillName,
		source: params.match.skillSource,
		activation: params.match.activation,
		...params.match.skillFile ? { skillFile: params.match.skillFile } : {}
	});
	const trace = params.ctx?.trace ? freezeDiagnosticTraceContext(createChildDiagnosticTraceContext(params.ctx.trace)) : void 0;
	emitTrustedSkillUsedDiagnosticEvent({
		type: "skill.used",
		...params.ctx?.runId && { runId: params.ctx.runId },
		...params.ctx?.sessionKey && { sessionKey: params.ctx.sessionKey },
		...params.ctx?.sessionId && { sessionId: params.ctx.sessionId },
		...params.ctx?.agentId && { agentId: params.ctx.agentId },
		...trace && { trace },
		skillName: params.match.skillName,
		skillSource: params.match.skillSource,
		activation: params.match.activation,
		toolName: params.toolName,
		...params.toolCallId && { toolCallId: params.toolCallId }
	}, params.match.skillFile ? { skillUsage: { skillFile: params.match.skillFile } } : void 0);
}
function emitToolBlockedSecurityEvent(params) {
	const control = params.deniedReason === "client-voice-confirmation" ? {
		policyId: "talk-client-voice-confirmation",
		controlId: "talk-client-voice-confirmation",
		family: "approval"
	} : params.deniedReason === "tool-loop" ? {
		policyId: "tool-loop-detection",
		controlId: "tool-loop-detection",
		family: "authorization"
	} : params.deniedReason === "plugin-approval" ? {
		policyId: "plugin-tool-approval",
		controlId: "plugin-tool-approval",
		family: "approval"
	} : {
		policyId: "plugin-before-tool-call",
		controlId: "before-tool-call",
		family: "approval"
	};
	emitTrustedSecurityEvent({
		category: "tool",
		action: "tool.execution.blocked",
		outcome: "denied",
		severity: "medium",
		reason: params.deniedReason,
		...params.trace ? { trace: params.trace } : {},
		actor: { kind: "agent" },
		target: {
			kind: "tool",
			name: params.toolName,
			...params.toolIdentity.toolOwner ? { owner: params.toolIdentity.toolOwner } : {}
		},
		policy: {
			id: control.policyId,
			decision: "deny",
			reason: params.deniedReason
		},
		control: {
			id: control.controlId,
			family: control.family
		},
		attributes: {
			tool_source: params.toolIdentity.toolSource,
			...params.paramsSummary ? { params_kind: params.paramsSummary.kind } : {}
		}
	});
}
function buildToolContentPrivateData(policy, args) {
	if (!policy.toolInputs && !policy.toolOutputs) return;
	const toolContent = {};
	if (policy.toolInputs) toolContent.toolInput = cloneDiagnosticContentValue(args.input);
	if (args.includeOutput && policy.toolOutputs) toolContent.toolOutput = cloneDiagnosticContentValue(args.output);
	return Object.keys(toolContent).length > 0 ? { toolContent } : void 0;
}
function summarizeToolParams(params) {
	if (params === null) return { kind: "null" };
	if (params === void 0) return { kind: "undefined" };
	if (Array.isArray(params)) return {
		kind: "array",
		length: params.length
	};
	if (typeof params === "object") return { kind: "object" };
	if (typeof params === "string") return {
		kind: "string",
		length: params.length
	};
	if (typeof params === "number") return { kind: "number" };
	if (typeof params === "boolean") return { kind: "boolean" };
	return { kind: "other" };
}
function shouldEmitLoopWarning(state, warningKey, count) {
	if (!state.toolLoopWarningBuckets) state.toolLoopWarningBuckets = /* @__PURE__ */ new Map();
	const bucket = Math.floor(count / LOOP_WARNING_BUCKET_SIZE);
	if (bucket <= (state.toolLoopWarningBuckets.get(warningKey) ?? 0)) return false;
	state.toolLoopWarningBuckets.set(warningKey, bucket);
	pruneMapToMaxSize(state.toolLoopWarningBuckets, MAX_LOOP_WARNING_KEYS);
	return true;
}
/** Reconcile loop liveness with the final post-policy arguments before execution. */
async function reconcileLoopCallExecutionParams(args) {
	if (!args.ctx?.sessionKey && !args.ctx?.sessionId || args.ctx.loopDetection?.enabled !== true) return;
	try {
		const { getDiagnosticSessionState, markDiagnosticArgumentChurnObservation, reconcileToolCallExecutionParams, resolveToolLoopWarningThreshold } = await loadBeforeToolCallRuntime();
		const churn = reconcileToolCallExecutionParams(getDiagnosticSessionState({
			sessionKey: args.ctx.sessionKey,
			sessionId: args.ctx.sessionId
		}), {
			toolName: args.toolName,
			toolParams: args.toolParams,
			toolCallId: args.toolCallId,
			runId: args.ctx.runId,
			warningThreshold: resolveToolLoopWarningThreshold()
		});
		markDiagnosticArgumentChurnObservation({
			sessionKey: args.ctx.sessionKey,
			sessionId: args.ctx.sessionId,
			runId: args.ctx.runId,
			active: churn.active
		});
	} catch (err) {
		log$1.warn(`tool loop execution-param reconciliation failed: tool=${args.toolName} error=${String(err)}`);
	}
}
async function recordLoopOutcome(args) {
	if (!args.ctx?.sessionKey && !args.ctx?.sessionId) return;
	let recordedOutcome;
	try {
		const { getArgumentChurnNoProgressStreak, getDiagnosticSessionState, markDiagnosticArgumentChurnObservation, recordToolCallOutcome } = await loadBeforeToolCallRuntime();
		const sessionState = getDiagnosticSessionState({
			sessionKey: args.ctx.sessionKey,
			sessionId: args.ctx.sessionId
		});
		const record = recordToolCallOutcome(sessionState, {
			toolName: args.toolName,
			toolParams: args.toolParams,
			toolCallId: args.toolCallId,
			result: args.result,
			error: args.error,
			...args.ctx.runId && { runId: args.ctx.runId }
		});
		const churnContinues = record !== void 0 && getArgumentChurnNoProgressStreak((sessionState.toolCallHistory ?? []).filter((call) => call.runId === record.runId), record.toolName, record.argsHash).count > 0;
		markDiagnosticArgumentChurnObservation({
			sessionKey: args.ctx.sessionKey,
			sessionId: args.ctx.sessionId,
			runId: args.ctx.runId,
			active: churnContinues,
			existingOnly: true
		});
		if (record?.resultHash && args.ctx.onToolOutcome) recordedOutcome = {
			toolName: record.toolName,
			argsHash: record.argsHash,
			resultHash: record.resultHash,
			...args.resultContentSource ? { resultContentSource: args.resultContentSource } : {},
			...args.toolCallOrdinal !== void 0 ? { toolCallOrdinal: args.toolCallOrdinal } : {},
			...args.terminalPresentation ? { terminalPresentation: args.terminalPresentation } : {}
		};
	} catch (err) {
		log$1.warn(`tool loop outcome tracking failed: tool=${args.toolName} error=${String(err)}`);
	}
	if (recordedOutcome) args.ctx.onToolOutcome?.(recordedOutcome);
}
//#endregion
//#region src/infra/embedded-plugin-approval-broker.ts
let activeBroker = null;
var EmbeddedPluginApprovalBroker = class {
	constructor() {
		this.pending = /* @__PURE__ */ new Map();
		this.listeners = /* @__PURE__ */ new Set();
	}
	subscribe(listener) {
		return registerListener(this.listeners, listener);
	}
	listPending() {
		return [...this.pending.values()].map((entry) => entry.record);
	}
	async request(params) {
		if (params.signal?.aborted) throw params.signal.reason ?? /* @__PURE__ */ new Error("approval request aborted");
		const id = `plugin:${randomUUID()}`;
		const createdAtMs = Date.now();
		const record = {
			approvalKind: "plugin",
			id,
			request: params.request,
			createdAtMs,
			expiresAtMs: createdAtMs + params.timeoutMs
		};
		const { promise: decision, resolve, reject } = createDeferredCore();
		const timer = setTimeout(() => {
			const entry = this.pending.get(id);
			if (!entry) return;
			this.pending.delete(id);
			entry.resolve(null);
			notifyListeners(this.listeners, {
				event: "plugin.approval.removed",
				payload: { id }
			});
		}, params.timeoutMs);
		timer.unref?.();
		this.pending.set(id, {
			record,
			timer,
			resolve,
			reject
		});
		const abort = () => {
			const entry = this.pending.get(id);
			if (!entry) return;
			clearTimeout(entry.timer);
			this.pending.delete(id);
			entry.reject(params.signal?.reason ?? /* @__PURE__ */ new Error("approval request aborted"));
			notifyListeners(this.listeners, {
				event: "plugin.approval.removed",
				payload: { id }
			});
		};
		params.signal?.addEventListener("abort", abort, { once: true });
		notifyListeners(this.listeners, {
			event: "plugin.approval.requested",
			payload: record
		});
		try {
			return {
				id,
				decision: await decision
			};
		} finally {
			params.signal?.removeEventListener("abort", abort);
		}
	}
	resolve(id, decision) {
		const entry = this.pending.get(id);
		if (!entry || !resolveCanonicalPluginApprovalRequestAllowedDecisions(entry.record.request).includes(decision)) return false;
		clearTimeout(entry.timer);
		this.pending.delete(id);
		entry.resolve(decision);
		notifyListeners(this.listeners, {
			event: "plugin.approval.resolved",
			payload: {
				id,
				decision,
				resolvedBy: "tui:embedded",
				ts: Date.now(),
				request: entry.record.request
			}
		});
		return true;
	}
	stop(reason = /* @__PURE__ */ new Error("embedded plugin approval broker stopped")) {
		for (const [id, entry] of this.pending) {
			clearTimeout(entry.timer);
			entry.reject(reason);
			notifyListeners(this.listeners, {
				event: "plugin.approval.removed",
				payload: { id }
			});
		}
		this.pending.clear();
		this.listeners.clear();
	}
};
function setEmbeddedPluginApprovalBroker(broker) {
	activeBroker = broker;
}
function clearEmbeddedPluginApprovalBroker(broker) {
	if (activeBroker === broker) activeBroker = null;
}
function getEmbeddedPluginApprovalBroker() {
	return activeBroker;
}
//#endregion
//#region src/agents/agent-tools.before-tool-call.approval.ts
/**
* Approval transport for before_tool_call policy decisions.
* Owns request/wait routing, embedded approval bridging, deferred approvals,
* timeout classification, and owner-provided approval outcomes.
*/
const log = createSubsystemLogger("agents/tools");
function pluginApprovalFailure(params, reason, disposition, deniedReason = "plugin-approval") {
	return {
		blocked: true,
		kind: "failure",
		disposition,
		deniedReason,
		reason,
		params
	};
}
function pluginApprovalDeniedOutcome(baseParams) {
	return pluginApprovalFailure(baseParams, [
		"Denied by user. The tool call did not run.",
		"This denial is final: the approval request is closed. Do not mention /approve or any other approval command to the user.",
		"Do not run the tool call again or ask the user to approve it again.",
		"If the user still wants the action, explain that a new tool call will trigger a fresh approval request."
	].join("\n"), "blocked");
}
function resolvePluginToolApprovalTimeoutMs(approval) {
	if (typeof approval.timeoutMs !== "number" || !Number.isFinite(approval.timeoutMs) || approval.timeoutMs <= 0) return DEFAULT_PLUGIN_APPROVAL_TIMEOUT_MS;
	return Math.min(Math.floor(approval.timeoutMs), MAX_PLUGIN_APPROVAL_TIMEOUT_MS);
}
function mergeParamsWithApprovalOverrides(originalParams, approvalParams) {
	return isPlainObject(approvalParams) ? isPlainObject(originalParams) ? {
		...originalParams,
		...approvalParams
	} : approvalParams : originalParams;
}
function notifyPluginApprovalResolution(approval, resolution) {
	const onResolution = approval.onResolution;
	if (typeof onResolution !== "function") return;
	try {
		Promise.resolve(onResolution(resolution)).catch((err) => {
			log.warn(`plugin onResolution callback failed: ${String(err)}`);
		});
	} catch (err) {
		log.warn(`plugin onResolution callback failed: ${String(err)}`);
	}
}
function resolvePermittedPluginApprovalResolution(decision, allowedDecisions) {
	if ((decision === PluginApprovalResolutions.ALLOW_ONCE || decision === PluginApprovalResolutions.ALLOW_ALWAYS || decision === PluginApprovalResolutions.DENY) && allowedDecisions.includes(decision)) return decision;
	return PluginApprovalResolutions.TIMEOUT;
}
function buildPluginApprovalFailureReason(params) {
	const turnSourceChannel = params.ctx?.turnSourceChannel;
	if (!turnSourceChannel?.trim()) return params.fallbackReason;
	const nativePluginSurface = resolveApprovalInitiatingSurfaceState({
		channel: turnSourceChannel,
		accountId: params.ctx?.turnSourceAccountId,
		cfg: params.ctx?.config,
		approvalKind: "plugin"
	});
	const setupText = describeNativePluginApprovalClientSetup({
		channel: nativePluginSurface.channel,
		channelLabel: nativePluginSurface.channelLabel,
		accountId: nativePluginSurface.accountId
	});
	if (!setupText) return params.fallbackReason;
	if (params.noRoute) return `${params.fallbackReason}\n\n${setupText}`;
	if ((nativePluginSurface.kind === "disabled" ? nativePluginSurface : resolveApprovalInitiatingSurfaceState({
		channel: turnSourceChannel,
		accountId: params.ctx?.turnSourceAccountId,
		cfg: params.ctx?.config,
		approvalKind: "exec"
	})).kind !== "disabled") return params.fallbackReason;
	return `${params.fallbackReason}\n\n${setupText}`;
}
function resolveUnavailablePluginApprovalSurfaceReason(ctx) {
	const trigger = ctx?.trigger?.trim();
	if (!trigger) return;
	const initiatingSurface = resolveApprovalInitiatingSurfaceState({
		channel: ctx?.turnSourceChannel,
		accountId: ctx?.turnSourceAccountId,
		cfg: ctx?.config,
		approvalKind: "plugin"
	});
	if (trigger !== "user") return `Plugin approval unavailable: ${trigger} runs have no approval-capable initiating surface.`;
	if (!ctx?.turnSourceChannel?.trim() && !ctx?.approvalReviewerDeviceId?.trim()) return "Plugin approval unavailable: non-interactive CLI runs have no approval-capable initiating surface.";
	if (initiatingSurface.kind === "disabled") return `Plugin approval unavailable: the ${initiatingSurface.channelLabel} initiating surface is disabled.`;
	if (initiatingSurface.kind === "unsupported") return `Plugin approval unavailable: the ${initiatingSurface.channelLabel} initiating surface does not support approvals.`;
}
/**
* A pending plugin approval is the owning run's human-input wait, like an agent
* question: stuck-session recovery must not abort the turn while the approver
* can still answer. The approval's own timeout plus the gateway grace bounds it.
*/
async function requestPluginToolApproval(params) {
	const deadlineAtMs = Date.now() + (addTimerTimeoutGraceMs(resolvePluginToolApprovalTimeoutMs(params.approval), 1e4) ?? 13e4);
	let pending = true;
	let resolved = false;
	const release = params.ctx?.runId ? registerActiveEmbeddedRunHumanInputWaitForRun(params.ctx.runId, () => pending && params.signal?.aborted !== true && Date.now() < deadlineAtMs) : void 0;
	try {
		return await requestPluginToolApprovalDecision(params, () => {
			resolved = true;
		});
	} finally {
		pending = false;
		release?.(resolved);
	}
}
async function requestPluginToolApprovalDecision(params, markHumanDecision) {
	const approval = params.approval;
	const policySubject = params.ctx?.toolOwnerPluginId ? {
		pluginKey: params.ctx.toolOwnerPluginId,
		tool: params.toolName
	} : void 0;
	const timeoutMs = resolvePluginToolApprovalTimeoutMs(approval);
	const gatewayTimeoutMs = addTimerTimeoutGraceMs(timeoutMs, 1e4) ?? 13e4;
	const allowedDecisions = resolveCanonicalPluginApprovalRequestAllowedDecisions(approval);
	const resolveDecision = (decision) => {
		const resolution = resolvePermittedPluginApprovalResolution(decision, allowedDecisions);
		notifyPluginApprovalResolution(approval, resolution);
		if (resolution === PluginApprovalResolutions.ALLOW_ONCE || resolution === PluginApprovalResolutions.ALLOW_ALWAYS || resolution === PluginApprovalResolutions.DENY) markHumanDecision();
		if (resolution === PluginApprovalResolutions.ALLOW_ONCE || resolution === PluginApprovalResolutions.ALLOW_ALWAYS) return {
			blocked: false,
			params: mergeParamsWithApprovalOverrides(params.baseParams, params.overrideParams),
			approvalResolution: resolution
		};
		return resolution === PluginApprovalResolutions.DENY ? pluginApprovalDeniedOutcome(params.baseParams) : void 0;
	};
	let gatewayApprovalPhase = "none";
	try {
		const requestIdentity = {
			toolName: params.toolName,
			toolCallId: params.toolCallId,
			...policySubject ? { policySubject } : {},
			agentId: params.ctx?.agentId,
			sessionKey: params.ctx?.sessionKey,
			turnSourceChannel: params.ctx?.turnSourceChannel,
			turnSourceTo: params.ctx?.turnSourceTo,
			turnSourceAccountId: params.ctx?.turnSourceAccountId,
			turnSourceThreadId: params.ctx?.turnSourceThreadId
		};
		const embeddedApprovalBroker = isEmbeddedMode() ? getEmbeddedPluginApprovalBroker() : null;
		if (embeddedApprovalBroker) {
			const outcome = resolveDecision((await embeddedApprovalBroker.request({
				request: {
					pluginId: approval.pluginId,
					title: approval.title,
					description: approval.description,
					...approval.scope ? { scope: sanitizeApprovalScope(approval.scope) } : {},
					severity: approval.severity,
					allowedDecisions: approval.allowedDecisions,
					...requestIdentity
				},
				timeoutMs,
				signal: params.signal
			})).decision);
			if (outcome) return outcome;
			return approval.timeoutReason ? {
				blocked: true,
				kind: "veto",
				deniedReason: "plugin-approval",
				reason: approval.timeoutReason,
				params: params.baseParams
			} : pluginApprovalFailure(params.baseParams, "Approval timed out", "timed_out");
		}
		const unavailableSurfaceReason = resolveUnavailablePluginApprovalSurfaceReason(params.ctx);
		if (unavailableSurfaceReason) {
			notifyPluginApprovalResolution(approval, PluginApprovalResolutions.CANCELLED);
			return pluginApprovalFailure(params.baseParams, unavailableSurfaceReason, "failed", "plugin-approval-unavailable");
		}
		gatewayApprovalPhase = "request";
		const requestResult = await withGatewayToolApprovalOwner(approval.pluginId, async () => await callGatewayTool("plugin.approval.request", { timeoutMs: gatewayTimeoutMs }, {
			title: approval.title,
			description: approval.description,
			...approval.scope ? { scope: approval.scope } : {},
			severity: approval.severity,
			allowedDecisions: approval.allowedDecisions,
			...requestIdentity,
			...params.ctx?.approvalReviewerDeviceId ? { approvalReviewerDeviceIds: [params.ctx.approvalReviewerDeviceId] } : {},
			timeoutMs,
			twoPhase: true
		}, {
			expectFinal: false,
			signal: params.signal
		}));
		gatewayApprovalPhase = "none";
		const id = requestResult?.id;
		if (!id) {
			notifyPluginApprovalResolution(approval, PluginApprovalResolutions.CANCELLED);
			return pluginApprovalFailure(params.baseParams, approval.description || "Plugin approval request failed", "failed");
		}
		const hasImmediateDecision = Object.hasOwn(requestResult ?? {}, "decision");
		let decision;
		if (hasImmediateDecision) {
			decision = requestResult?.decision;
			if (decision === null) {
				notifyPluginApprovalResolution(approval, PluginApprovalResolutions.CANCELLED);
				return pluginApprovalFailure(params.baseParams, buildPluginApprovalFailureReason({
					fallbackReason: "Plugin approval unavailable (no approval route)",
					ctx: params.ctx,
					noRoute: true
				}), "failed");
			}
		} else {
			gatewayApprovalPhase = "wait";
			const waitResult = await callGatewayTool("plugin.approval.waitDecision", { timeoutMs: gatewayTimeoutMs }, { id }, { signal: params.signal });
			decision = waitResult?.id === id ? waitResult.decision : void 0;
		}
		const outcome = resolveDecision(decision);
		if (outcome) return outcome;
		const fallbackTimeoutReason = approval.timeoutReason ?? "Approval timed out";
		const timeoutReason = requestResult?.deliveryRoute === "turn-source" ? buildPluginApprovalFailureReason({
			fallbackReason: fallbackTimeoutReason,
			ctx: params.ctx
		}) : fallbackTimeoutReason;
		return {
			blocked: true,
			kind: approval.timeoutReason ? "veto" : "failure",
			disposition: "timed_out",
			deniedReason: "plugin-approval",
			reason: timeoutReason,
			params: params.baseParams
		};
	} catch (err) {
		notifyPluginApprovalResolution(approval, PluginApprovalResolutions.CANCELLED);
		const signal = params.signal;
		if (signal?.aborted === true && (err === signal.reason || err instanceof Error && (err.name === "AbortError" || "cause" in err && err.cause === signal.reason))) {
			log.warn(`plugin approval wait cancelled by run abort: ${String(err)}`);
			return pluginApprovalFailure(params.baseParams, "Approval cancelled (run aborted)", resolveToolErrorDiagnostic(err, signal).terminalReason);
		}
		const invalidRequest = err instanceof GatewayClientRequestError && err.gatewayCode === "INVALID_REQUEST";
		const reason = invalidRequest && gatewayApprovalPhase === "request" ? `Plugin approval request rejected: ${formatErrorMessage(err)}` : invalidRequest && gatewayApprovalPhase === "wait" ? `Plugin approval no longer available: ${formatErrorMessage(err)}` : "Plugin approval required (gateway unavailable)";
		log.warn(`plugin approval gateway request failed; blocking tool call: ${String(err)}`);
		return pluginApprovalFailure(params.baseParams, reason, resolveToolErrorDiagnostic(err, signal).terminalReason);
	}
}
/** Resolve a deferred plugin approval request at the later execution boundary. */
async function requestDeferredPluginToolApproval(params) {
	const deferred = params.deferredApproval;
	return requestPluginToolApproval({
		approval: deferred.approval,
		toolName: deferred.toolName,
		...deferred.toolCallId ? { toolCallId: deferred.toolCallId } : {},
		...deferred.ctx ? { ctx: deferred.ctx } : {},
		signal: params.signal,
		baseParams: deferred.baseParams,
		overrideParams: deferred.overrideParams
	});
}
function cancelDeferredPluginToolApproval(deferredApproval) {
	notifyPluginApprovalResolution(deferredApproval.approval, PluginApprovalResolutions.CANCELLED);
}
async function resolveBeforeToolCallApprovalOutcome(params) {
	const approval = params.result?.requireApproval;
	if (!approval) return;
	const baseParamsSnapshot = cloneHookIsolationValue("before_tool_call", params.baseParams);
	const overrideParamsSnapshot = params.result?.params === void 0 ? void 0 : cloneHookIsolationValue("before_tool_call", params.result.params);
	if (params.approvalMode === "defer") return {
		blocked: false,
		params: cloneHookIsolationValue("before_tool_call", baseParamsSnapshot),
		deferredApproval: {
			approval,
			toolName: params.toolName,
			...params.toolCallId ? { toolCallId: params.toolCallId } : {},
			...params.ctx ? { ctx: params.ctx } : {},
			baseParams: baseParamsSnapshot,
			overrideParams: overrideParamsSnapshot
		}
	};
	if (params.approvalMode === "report") {
		notifyPluginApprovalResolution(approval, PluginApprovalResolutions.CANCELLED);
		return pluginApprovalFailure(baseParamsSnapshot, approval.description || approval.title || "Plugin approval required", "blocked");
	}
	if (params.approvalMode === "deny") {
		notifyPluginApprovalResolution(approval, PluginApprovalResolutions.DENY);
		return {
			blocked: true,
			kind: "veto",
			deniedReason: "plugin-approval",
			reason: "approval_required",
			params: baseParamsSnapshot
		};
	}
	return await requestPluginToolApproval({
		approval,
		toolName: params.toolName,
		...params.toolCallId ? { toolCallId: params.toolCallId } : {},
		...params.ctx ? { ctx: params.ctx } : {},
		signal: params.signal,
		baseParams: baseParamsSnapshot,
		overrideParams: overrideParamsSnapshot
	});
}
//#endregion
//#region src/plugins/host-tool-param-parsers.ts
/**
* Derive host-owned metadata for a tool call. Returns an empty object when no
* parser is registered for the tool, which lets callers spread the result
* unconditionally without a nullability check.
*/
async function deriveToolParams(toolName, params, options) {
	if (toolName !== "apply_patch") return {};
	const paths = await extractResolvedApplyPatchTargetPaths(params, options);
	return paths.length > 0 ? { derivedPaths: Object.freeze([...paths]) } : {};
}
//#endregion
//#region src/plugins/trusted-tool-policy.ts
/** True when the supplied or active plugin registry has trusted tool policies. */
function hasTrustedToolPolicies(registry = getActivePluginRegistry()) {
	return copyTrustedPolicyRegistrations(registry).length > 0;
}
function unreadableTrustedPolicyRegistration() {
	return {
		pluginId: "unknown-plugin",
		source: "runtime",
		get policy() {
			throw new Error("trusted policy registration is unreadable");
		}
	};
}
function copyTrustedPolicyRegistrations(registry) {
	try {
		const policies = registry?.trustedToolPolicies;
		if (!policies) return [];
		return Array.isArray(policies) ? policies.map((policy) => policy) : [unreadableTrustedPolicyRegistration()];
	} catch {
		return [unreadableTrustedPolicyRegistration()];
	}
}
function readTrustedPolicyPluginString(registration, key) {
	try {
		const value = registration[key];
		return typeof value === "string" && value.trim() ? value.trim() : void 0;
	} catch {
		return;
	}
}
function trustedPolicyDiagnosticPluginId(registration) {
	return readTrustedPolicyPluginString(registration, "pluginId") ?? "unknown-plugin";
}
function readTrustedPolicy(registration) {
	try {
		const policy = registration.policy;
		return policy && typeof policy.evaluate === "function" ? policy : void 0;
	} catch {
		return;
	}
}
function readTrustedPolicyMatcher(policy) {
	try {
		return normalizePluginToolMatcher(policy.matcher);
	} catch {
		return null;
	}
}
function getTrustedToolPolicyMatcherScope(registry = getActivePluginRegistry()) {
	return createPluginToolMatcherScope(copyTrustedPolicyRegistrations(registry).map((registration) => {
		const policy = readTrustedPolicy(registration);
		if (!policy) return;
		return readTrustedPolicyMatcher(policy) ?? void 0;
	}));
}
function readTrustedPolicyId(registration) {
	const fallback = trustedPolicyDiagnosticPluginId(registration);
	const policy = readTrustedPolicy(registration);
	if (!policy) return fallback;
	try {
		const id = policy.id;
		return typeof id === "string" && id.trim() ? id.trim() : fallback;
	} catch {
		return fallback;
	}
}
function trustedPolicyDefaultBlockReason(registration) {
	return `blocked by ${readTrustedPolicyId(registration)}`;
}
function trustedPolicyFailureResult(registration, detail) {
	return {
		block: true,
		blockReason: `${trustedPolicyDefaultBlockReason(registration)}: ${detail}`
	};
}
/** Lists trusted tool policies for status and diagnostics. */
function getTrustedToolPolicyDiagnosticEntries(registry = getActivePluginRegistry()) {
	return copyTrustedPolicyRegistrations(registry).map((registration) => {
		const entry = {
			id: readTrustedPolicyId(registration),
			pluginId: trustedPolicyDiagnosticPluginId(registration)
		};
		const pluginName = readTrustedPolicyPluginString(registration, "pluginName");
		if (pluginName) entry.pluginName = pluginName;
		return entry;
	});
}
function normalizeDerivedEventFields(value) {
	return Array.isArray(value?.derivedPaths) ? { derivedPaths: Object.freeze([...value.derivedPaths]) } : {};
}
function normalizeToolIdentity(value) {
	return {
		...value?.toolKind && { toolKind: value.toolKind },
		...value?.toolInputKind && { toolInputKind: value.toolInputKind }
	};
}
/** Runs trusted tool policies before a tool call and returns the first terminal decision. */
async function runTrustedToolPolicies(event, ctx, options) {
	const policies = copyTrustedPolicyRegistrations(options?.registry ?? getActivePluginRegistry());
	let adjustedParams = event.params;
	let hasAdjustedParams = false;
	let approval;
	const sessionExtensionStateCache = /* @__PURE__ */ new Map();
	let resolvedSessionConfig = options?.config;
	let didResolveSessionConfig = Boolean(options?.config);
	const resolveSessionConfig = () => {
		if (!didResolveSessionConfig) {
			didResolveSessionConfig = true;
			try {
				resolvedSessionConfig = getRuntimeConfig();
			} catch {
				resolvedSessionConfig = void 0;
			}
		}
		return resolvedSessionConfig;
	};
	const { derivedPaths, toolKind, toolInputKind, ...eventWithoutDerivedPaths } = event;
	const { toolKind: ctxToolKind, toolInputKind: ctxToolInputKind, ...ctxWithoutToolIdentity } = ctx;
	let currentDerivedEvent = normalizeDerivedEventFields({ derivedPaths });
	let currentEventToolIdentity = normalizeToolIdentity({
		toolKind,
		toolInputKind
	});
	let currentContextToolIdentity = normalizeToolIdentity({
		toolKind: ctxToolKind,
		toolInputKind: ctxToolInputKind
	});
	const buildEvent = (params) => ({
		...eventWithoutDerivedPaths,
		params,
		...currentEventToolIdentity,
		...currentDerivedEvent
	});
	for (const registration of policies) {
		const pluginId = readTrustedPolicyPluginString(registration, "pluginId");
		if (!pluginId) return trustedPolicyFailureResult(registration, "policy owner is unreadable");
		const policyCtx = {
			...ctxWithoutToolIdentity,
			...currentContextToolIdentity,
			getSessionExtension: (namespace) => {
				const normalizedNamespace = namespace.trim();
				if (!sessionExtensionStateCache.has(pluginId)) {
					const config = ctx.sessionKey ? resolveSessionConfig() : void 0;
					sessionExtensionStateCache.set(pluginId, config ? getPluginSessionExtensionStateSync({
						cfg: config,
						pluginId,
						sessionKey: ctx.sessionKey,
						agentId: ctx.agentId
					}) : void 0);
				}
				const pluginState = sessionExtensionStateCache.get(pluginId);
				if (!normalizedNamespace || !pluginState) return;
				return pluginState[normalizedNamespace];
			}
		};
		const policy = readTrustedPolicy(registration);
		if (!policy) return trustedPolicyFailureResult(registration, "policy is unreadable");
		const matcher = readTrustedPolicyMatcher(policy);
		if (matcher === null) return trustedPolicyFailureResult(registration, "policy matcher is unreadable");
		if (!pluginToolMatcherCoversTool(matcher, event.toolName)) continue;
		let decision;
		try {
			decision = await policy.evaluate(buildEvent(adjustedParams), policyCtx);
		} catch {
			return trustedPolicyFailureResult(registration, "policy evaluation failed");
		}
		if (!decision) continue;
		try {
			if ("allow" in decision && decision.allow === false) return {
				block: true,
				blockReason: decision.reason ?? trustedPolicyDefaultBlockReason(registration)
			};
			if ("block" in decision && decision.block === true) return {
				...decision,
				blockReason: decision.blockReason ?? trustedPolicyDefaultBlockReason(registration)
			};
			if ("params" in decision && isPlainObject(decision.params)) {
				const normalized = options?.normalizeEvent?.(buildEvent(decision.params), policyCtx);
				adjustedParams = normalized?.params ?? decision.params;
				if (normalized?.event) currentEventToolIdentity = normalizeToolIdentity(normalized.event);
				if (normalized?.ctx) currentContextToolIdentity = normalizeToolIdentity(normalized.ctx);
				else if (normalized?.event) currentContextToolIdentity = normalizeToolIdentity(normalized.event);
				hasAdjustedParams = true;
				currentDerivedEvent = normalizeDerivedEventFields(await options?.deriveEvent?.(adjustedParams));
			}
			if ("requireApproval" in decision && decision.requireApproval && !approval) approval = decision.requireApproval;
		} catch {
			ctx.abortSignal?.throwIfAborted();
			return trustedPolicyFailureResult(registration, "policy decision is unreadable");
		}
	}
	if (!hasAdjustedParams && !approval) return;
	return {
		...hasAdjustedParams ? { params: adjustedParams } : {},
		...approval ? { requireApproval: approval } : {}
	};
}
//#endregion
//#region src/talk/client-voice-confirmation-policy.ts
function stableToolFingerprint(toolName, params) {
	const normalize = (value) => {
		if (Array.isArray(value)) return value.map(normalize);
		if (!value || typeof value !== "object") return value;
		return Object.fromEntries(Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalize(entry)]));
	};
	return createHash("sha256").update(`${toolName}\0${JSON.stringify(normalize(params))}`).digest("hex");
}
function requiresHighImpactVoiceConfirmation(toolName, params) {
	const normalizedTool = toolName.trim().toLowerCase();
	if (!buildToolMutationState(normalizedTool, params).mutatingAction) return false;
	return ![
		"write",
		"edit",
		"apply_patch",
		"create_goal",
		"update_goal",
		"get_goal"
	].includes(normalizedTool);
}
//#endregion
//#region src/talk/client-voice-confirmation.ts
/** In-memory spoken confirmation binding for high-impact Talk actions. */
const CONFIRMATION_TTL_MS = 12e4;
const utteranceContextBrand = Symbol("voice-confirmation-utterance");
const confirmationScopes = /* @__PURE__ */ new Map();
const utteranceContexts = /* @__PURE__ */ new WeakMap();
function confirmationScopeKey(agentId, voiceSessionId) {
	return `${agentId}\0${voiceSessionId}`;
}
function clearPendingExpiryTimer(state) {
	if (!state.pendingExpiryTimer) return;
	clearTimeout(state.pendingExpiryTimer);
	delete state.pendingExpiryTimer;
}
function clearPendingConfirmation(state) {
	clearPendingExpiryTimer(state);
	state.pending?.changed.resolve();
	delete state.pending;
	delete state.recentUtterance;
}
function notifyPendingConfirmationChanged(pending) {
	const changed = pending.changed;
	pending.changed = createDeferredCore();
	changed.resolve();
}
function cleanupConfirmationScope(scopeKey, state) {
	if (state.pending || state.recentUtterance || state.approvedByRun.size > 0 || state.observationsByRun.size > 0) return;
	if (confirmationScopes.get(scopeKey) === state) confirmationScopes.delete(scopeKey);
}
function pruneExpiredPendingConfirmation(scopeKey, state, now) {
	if (state.pending && state.pending.expiresAt < now) clearPendingConfirmation(state);
	cleanupConfirmationScope(scopeKey, state);
}
function schedulePendingConfirmationExpiry(scopeKey, state, now) {
	const pending = state.pending;
	if (!pending) {
		clearPendingExpiryTimer(state);
		cleanupConfirmationScope(scopeKey, state);
		return;
	}
	clearPendingExpiryTimer(state);
	state.pendingExpiryTimer = setTimeout(() => {
		delete state.pendingExpiryTimer;
		if (confirmationScopes.get(scopeKey) !== state || state.pending !== pending) return;
		const current = Date.now();
		if (pending.expiresAt < current) {
			clearPendingConfirmation(state);
			cleanupConfirmationScope(scopeKey, state);
		} else schedulePendingConfirmationExpiry(scopeKey, state, current);
	}, Math.max(1, pending.expiresAt - now + 1));
	state.pendingExpiryTimer.unref?.();
}
function getPrunedConfirmationScope(scopeKey, now) {
	const state = confirmationScopes.get(scopeKey);
	if (!state) return;
	pruneExpiredPendingConfirmation(scopeKey, state, now);
	return confirmationScopes.get(scopeKey);
}
function getOrCreateConfirmationScope(scopeKey) {
	const existing = confirmationScopes.get(scopeKey);
	if (existing) return existing;
	const state = {
		approvedByRun: /* @__PURE__ */ new Map(),
		observationsByRun: /* @__PURE__ */ new Map()
	};
	confirmationScopes.set(scopeKey, state);
	return state;
}
function resolveApprovedFingerprint(scopeKey, runId, fingerprint, now, consume) {
	if (!runId) return false;
	const state = confirmationScopes.get(scopeKey);
	const approved = state?.approvedByRun.get(runId);
	const expiresAt = approved?.get(fingerprint);
	const expired = !expiresAt || expiresAt < now;
	if (expired || consume) {
		approved?.delete(fingerprint);
		if (!expired) state?.observationsByRun.get(runId)?.delete(fingerprint);
		if (approved?.size === 0) state?.approvedByRun.delete(runId);
		if (state) cleanupConfirmationScope(scopeKey, state);
	}
	return !expired;
}
/** Capture host-observed speech before any finalization or persistence can change its challenge. */
function captureClientVoiceConfirmationUtterance(params) {
	const timestamp = params.now ?? Date.now();
	const state = getPrunedConfirmationScope(confirmationScopeKey(params.agentId, params.voiceSessionId), timestamp);
	const context = Object.freeze({ [utteranceContextBrand]: true });
	utteranceContexts.set(context, {
		agentId: params.agentId,
		voiceSessionId: params.voiceSessionId,
		pending: state?.pending,
		timestamp
	});
	if (state?.pending) {
		delete state.recentUtterance;
		delete state.pending.utteranceRejected;
		state.pending.utterance = context;
		notifyPendingConfirmationChanged(state.pending);
	}
	return context;
}
/** Bind one transcript entry before queue admission; retries retain its original observation. */
function prepareClientVoiceConfirmationTranscript(params) {
	if (params.confirmation === null) return null;
	const current = getPrunedConfirmationScope(confirmationScopeKey(params.agentId, params.voiceSessionId), params.now ?? Date.now())?.pending?.utterance;
	const context = params.confirmation ?? (current && utteranceContexts.get(current)?.entryId === params.entryId ? current : captureClientVoiceConfirmationUtterance(params));
	const observed = utteranceContexts.get(context);
	if (!observed || observed.agentId !== params.agentId || observed.voiceSessionId !== params.voiceSessionId || observed.entryId !== void 0 && observed.entryId !== params.entryId) return null;
	observed.entryId = params.entryId;
	return context;
}
/** A deduplicated write can reuse its original receipt, never manufacture a new one. */
function recordClientVoiceConfirmationTranscriptAppend(params) {
	const observed = utteranceContexts.get(params.confirmation);
	if (observed?.entryId === params.entryId && params.appended) observed.persistedText = params.text;
}
/** Record persisted speech only for the challenge and utterance observed before its write. */
function noteClientVoiceConfirmationUtterance(params) {
	const scopeKey = confirmationScopeKey(params.agentId, params.voiceSessionId);
	const state = getPrunedConfirmationScope(scopeKey, params.timestamp);
	const observed = utteranceContexts.get(params.confirmation);
	if (!state?.pending || !observed || observed.agentId !== params.agentId || observed.voiceSessionId !== params.voiceSessionId) return;
	if (observed.pending === state.pending && observed.persistedText !== void 0 && REFUSAL_PATTERN.test(normalizeUtterance(observed.persistedText)) && state.pending.createdAt < observed.timestamp) {
		clearPendingConfirmation(state);
		cleanupConfirmationScope(scopeKey, state);
		return;
	}
	if (observed.pending !== state.pending || state.pending.utterance !== params.confirmation || observed.persistedText === void 0) {
		if (!state.pending.utterance || state.pending.utterance === params.confirmation) {
			state.pending.utteranceRejected = true;
			notifyPendingConfirmationChanged(state.pending);
		}
		return;
	}
	state.recentUtterance = {
		text: observed.persistedText,
		timestamp: observed.timestamp
	};
	notifyPendingConfirmationChanged(state.pending);
}
function resolveClientVoiceToolConfirmationPolicy(params, consume) {
	if (!params.agentId || !params.voiceSessionId) return { allowed: true };
	if (!requiresHighImpactVoiceConfirmation(params.toolName, params.toolParams)) return { allowed: true };
	if (params.isConfirmable && !params.isConfirmable()) return { allowed: true };
	const now = params.now ?? Date.now();
	const fingerprint = stableToolFingerprint(params.toolName, params.toolParams);
	const scopeKey = confirmationScopeKey(params.agentId, params.voiceSessionId);
	if (resolveApprovedFingerprint(scopeKey, params.runId, fingerprint, now, consume)) return { allowed: true };
	const state = getPrunedConfirmationScope(scopeKey, now) ?? getOrCreateConfirmationScope(scopeKey);
	const pending = state.pending;
	const existing = pending?.fingerprint === fingerprint ? pending : void 0;
	if (!existing) clearPendingConfirmation(state);
	const confirmation = existing ?? {
		confirmationId: randomUUID(),
		...params.runId ? { runId: params.runId } : {},
		fingerprint,
		createdAt: now,
		expiresAt: now + CONFIRMATION_TTL_MS,
		changed: createDeferredCore()
	};
	if (params.runId) confirmation.runId = params.runId;
	if (params.runId && params.toolCallId && params.runId.length <= 256 && params.toolCallId.length <= 256 && params.toolName.length <= 128) confirmation.blockedCall = {
		runId: params.runId,
		toolCallId: params.toolCallId,
		toolName: params.toolName
	};
	state.pending = confirmation;
	const observation = params.runId ? state.observationsByRun.get(params.runId) : void 0;
	if (observation) observation.set(fingerprint, confirmation.confirmationId);
	schedulePendingConfirmationExpiry(scopeKey, state, now);
	return {
		allowed: false,
		reason: `VOICE_CONFIRMATION_REQUIRED:${confirmation.confirmationId} The high-impact voice action "${params.toolName}" was not executed. ` + (observation ? "Ask the user to say \"yes\" to confirm this action or \"no\" to cancel it. A later native delegation carries the confirmation; do not add confirmationId to action tool arguments." : "Ask the user for explicit spoken confirmation, then call openclaw_agent_consult again with this confirmationId.")
	};
}
/** Check whether one exact high-impact action is approved without consuming its grant. */
function checkClientVoiceToolConfirmationPolicy(params) {
	return resolveClientVoiceToolConfirmationPolicy(params, false);
}
/** Authorize the canonical execution params and consume their one-shot grant. */
function consumeClientVoiceToolConfirmationPolicy(params) {
	return resolveClientVoiceToolConfirmationPolicy(params, true);
}
/** Read transcript readiness without granting or extending the current challenge. */
function readClientVoiceConfirmationReadiness(agentId, voiceSessionId) {
	const state = getPrunedConfirmationScope(confirmationScopeKey(agentId, voiceSessionId), Date.now());
	if (!state?.pending) return;
	return {
		confirmationId: state.pending.confirmationId,
		needsUserUtterance: !hasLaterUserUtterance(state),
		utteranceRejected: state.pending.utteranceRejected === true,
		changed: state.pending.changed.promise
	};
}
/** A newly observed user utterance cannot reuse an older final affirmation. */
function invalidateClientVoiceConfirmationUtterance(agentId, voiceSessionId) {
	const scopeKey = confirmationScopeKey(agentId, voiceSessionId);
	const state = confirmationScopes.get(scopeKey);
	if (state) {
		delete state.recentUtterance;
		if (state.pending) {
			delete state.pending.utterance;
			notifyPendingConfirmationChanged(state.pending);
		}
		cleanupConfirmationScope(scopeKey, state);
	}
}
/** Retain this run's veto outcome for speech; this observation never authorizes an action. */
function observeClientVoiceConfirmationRun(params) {
	const scopeKey = confirmationScopeKey(params.agentId, params.voiceSessionId);
	const state = getOrCreateConfirmationScope(scopeKey);
	const observation = /* @__PURE__ */ new Map();
	state.observationsByRun.set(params.runId, observation);
	return {
		readReply(options) {
			if (observation.size === 0) return;
			const pending = confirmationScopes.get(scopeKey)?.pending;
			if (pending && observation.get(pending.fingerprint) === pending.confirmationId && pending.expiresAt >= Date.now()) {
				const speech = "One pending action has not run. Say \"yes\" to confirm that action or \"no\" to cancel it.";
				return options?.includeConfirmationId ? `VOICE_CONFIRMATION_REQUIRED:${pending.confirmationId} ${speech} After spoken confirmation, call openclaw_agent_consult with this confirmationId.` : speech;
			}
			return "An action in that request was not run because its spoken confirmation is no longer current. Make a new request if you still want it.";
		},
		release() {
			const current = confirmationScopes.get(scopeKey);
			if (current?.observationsByRun.get(params.runId) === observation) {
				current.observationsByRun.delete(params.runId);
				cleanupConfirmationScope(scopeKey, current);
			}
		}
	};
}
const REFUSAL_PATTERN = /\b(no|don't|do not|cancel|stop|never mind)\b/;
function normalizeUtterance(text) {
	return text.trim().toLowerCase().replace(/[‘’ʼ]/g, "'").replace(/[,;:.!?]+/g, "").replace(/\s+/g, " ");
}
function isExplicitAffirmation(text) {
	const normalized = normalizeUtterance(text);
	if (REFUSAL_PATTERN.test(normalized)) return false;
	return /^(yes|yes do it|do it|confirm|confirmed|go ahead|proceed|send it|make the change|restart it)$/.test(normalized);
}
function hasLaterUserUtterance(state) {
	return Boolean(state.pending && state.recentUtterance && state.recentUtterance.timestamp > state.pending.createdAt);
}
function hasLaterExplicitAffirmation(state) {
	return Boolean(state.recentUtterance && hasLaterUserUtterance(state) && isExplicitAffirmation(state.recentUtterance.text));
}
/** Native delegation has no tool arguments; only the call's persisted speech can confirm it. */
function authorizeObservedClientVoiceConfirmation(params) {
	const now = params.now ?? Date.now();
	const state = getPrunedConfirmationScope(confirmationScopeKey(params.agentId, params.voiceSessionId), now);
	if (!state?.pending || !hasLaterExplicitAffirmation(state)) return;
	return authorizeClientVoiceConfirmation({
		...params,
		now,
		confirmationId: state.pending.confirmationId
	});
}
/** Bind a later affirmative utterance to one exact paused action. */
function authorizeClientVoiceConfirmation(params) {
	const now = params.now ?? Date.now();
	const state = getPrunedConfirmationScope(confirmationScopeKey(params.agentId, params.voiceSessionId), now);
	const confirmation = state?.pending;
	if (!confirmation) throw new Error("voice confirmation is missing, expired, or belongs to another action");
	if (confirmation.confirmationId !== params.confirmationId) throw new Error("a newer confirmation request supersedes this one; ask again");
	if (!hasLaterExplicitAffirmation(state)) throw new Error("explicit spoken confirmation was not found after the action request");
	return {
		agentId: params.agentId,
		voiceSessionId: params.voiceSessionId,
		confirmationId: params.confirmationId,
		fingerprint: confirmation.fingerprint,
		expiresAt: confirmation.expiresAt,
		...confirmation.blockedCall ? { retryContext: `The user's persisted spoken confirmation is bound to this previously blocked tool call: ${JSON.stringify(confirmation.blockedCall)}. Retry only that call with its unchanged arguments. Do not add confirmationId or other confirmation metadata to the action tool's arguments. This confirmation authorizes one exact action; other actions still require their own confirmation.` } : {}
	};
}
/**
* Bind a validated spoken grant to the one follow-up run and consume the
* challenge. Invalidated detached grants return false without disrupting the
* admitted run; final tool policy then blocks because no approval was created.
*/
function bindAuthorizedClientVoiceConfirmation(params) {
	const now = params.now ?? Date.now();
	const scopeKey = confirmationScopeKey(params.grant.agentId, params.grant.voiceSessionId);
	const state = confirmationScopes.get(scopeKey);
	const pending = state?.pending;
	if (!state || !pending || pending.expiresAt < now || pending.confirmationId !== params.grant.confirmationId || pending.fingerprint !== params.grant.fingerprint || pending.expiresAt !== params.grant.expiresAt || !hasLaterExplicitAffirmation(state)) return false;
	const approved = state.approvedByRun.get(params.runId) ?? /* @__PURE__ */ new Map();
	approved.set(pending.fingerprint, pending.expiresAt);
	state.approvedByRun.set(params.runId, approved);
	clearPendingConfirmation(state);
	cleanupConfirmationScope(scopeKey, state);
	return true;
}
/**
* Remove ephemeral confirmation state when the logical call closes. Approved
* grants for still-live consult runs survive: a spoken "yes" followed by hangup
* must not re-block the confirmed action its run is about to execute.
*/
function deactivateClientVoiceConfirmationSession(agentId, voiceSessionId, liveRunIds = []) {
	const scopeKey = confirmationScopeKey(agentId, voiceSessionId);
	const state = confirmationScopes.get(scopeKey);
	if (!state) return;
	clearPendingConfirmation(state);
	const live = new Set(liveRunIds);
	for (const runId of state.approvedByRun.keys()) if (!live.has(runId)) state.approvedByRun.delete(runId);
	for (const runId of state.observationsByRun.keys()) if (!live.has(runId)) state.observationsByRun.delete(runId);
	cleanupConfirmationScope(scopeKey, state);
}
/** Drop a completed run's surviving grants once its lifecycle ends. */
function releaseClientVoiceConfirmationRun(agentId, voiceSessionId, runId) {
	const scopeKey = confirmationScopeKey(agentId, voiceSessionId);
	const state = confirmationScopes.get(scopeKey);
	if (!state) return;
	state.approvedByRun.delete(runId);
	state.observationsByRun.delete(runId);
	cleanupConfirmationScope(scopeKey, state);
}
/** Test-only reset for process-global state. */
function resetClientVoiceConfirmationStateForTest() {
	for (const state of confirmationScopes.values()) clearPendingExpiryTimer(state);
	confirmationScopes.clear();
}
function snapshotClientVoiceConfirmationStateForTest() {
	const snapshot = {
		scopeOwners: confirmationScopes.size,
		pendingChallenges: 0,
		recentUtterances: 0,
		approvedRuns: 0,
		approvedGrants: 0,
		expiryOwners: 0
	};
	for (const state of confirmationScopes.values()) {
		snapshot.pendingChallenges += state.pending ? 1 : 0;
		snapshot.recentUtterances += state.recentUtterance ? 1 : 0;
		snapshot.approvedRuns += state.approvedByRun.size;
		for (const approved of state.approvedByRun.values()) snapshot.approvedGrants += approved.size;
		snapshot.expiryOwners += state.pendingExpiryTimer ? 1 : 0;
	}
	return snapshot;
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.clientVoiceConfirmationTestApi")] = {
	resetClientVoiceConfirmationStateForTest,
	snapshotClientVoiceConfirmationStateForTest
};
//#endregion
//#region src/talk/client-voice-session-source.ts
/** Voice metadata stays bound to its admitted physical store across provider and queue waits. */
function captureClientVoiceSessionSourceOptions(agentId) {
	const env = cloneEnvWithPlatformSemantics(process.env);
	env.OPENCLAW_STATE_DIR = resolveStateDir(env);
	return {
		agentId,
		env,
		path: resolveOpenClawAgentSqlitePath({
			agentId,
			env
		})
	};
}
/** Retain entry ownership independently of the durable voice metadata store. */
async function captureClientVoiceEntrySource(params, assertLifetimeCurrent) {
	const original = captureClientVoiceSessionSourceOptions(params.agentId);
	const storePath = params.storePath ?? original.path;
	const captured = captureSessionStoreWriteCandidates(storePath);
	const canonical = resolveUnsuffixedSqliteTargetFromSessionStorePath(storePath);
	const assertCurrent = () => {
		assertLifetimeCurrent();
		captured.assertCurrent();
	};
	const capture = (target) => {
		assertCurrent();
		const agentId = resolveSqliteAgentId({
			scopedAgentId: params.agentId,
			storeAgentId: target.agentId,
			storeShared: target.shared
		});
		if (!agentId || !target.agentId) throw new Error("Talk entry target has no physical owner");
		const observed = captured.resolveIdentity(target.path);
		const options = {
			agentId: target.agentId,
			path: observed.canonicalPath,
			env: original.env
		};
		return {
			options,
			execution: captureOpenClawAgentDatabaseExecution(options, {
				...observed.key.startsWith("file:") ? { expectedIdentity: {
					kind: "file",
					physicalIdentity: observed.key.slice(5),
					nativeLocation: observed.canonicalPath,
					birthtime: observed.birthtime
				} } : { expectedCreationIdentity: observed },
				requestedPath: target.path
			}),
			agentId,
			storePath,
			assertCurrent
		};
	};
	return canonical.agentId ? capture(canonical) : capture(await prepareSqliteTargetFromSessionStorePath(storePath, {
		agentId: params.agentId,
		env: original.env
	}));
}
function createClientVoiceSessionSource(options, identity) {
	const settlementContext = captureClientVoiceSessionSettlementContext(options.env);
	return {
		options,
		identity,
		settlementContext,
		assertCurrent() {
			assertClientVoiceSessionSettlementCurrent(settlementContext);
			settlementContext.admission.assertCurrent();
			assertDatabasePathIdentity(options.path, identity);
		}
	};
}
function captureClientVoiceSessionSource(agentId) {
	const options = captureClientVoiceSessionSourceOptions(agentId);
	return createClientVoiceSessionSource(options, readDatabasePathIdentitySync(options.path));
}
/** An explicit source qualifies the run's store; an omitted source retains replay custody. */
function matchesClientVoiceRunSource(owner, binding, source) {
	return owner?.binding.agentId === binding.agentId && owner.binding.voiceSessionId === binding.voiceSessionId && owner.binding.sessionKey === binding.sessionKey && (!source || owner.source.identity.key === source.identity.key && owner.source.identity.birthtime === source.identity.birthtime);
}
/** Foreign source checks retain their own readers; local predicates belong to the voice writer. */
async function prepareClientVoiceSessionSourceChecks(writer, authorities) {
	const checks = [];
	const foreign = /* @__PURE__ */ new Map();
	for (const check of authorities.flatMap((authority) => authority.checks)) {
		const source = check.predicate.source;
		if (typeof source.databaseIdentity === "string" && `file:${source.databaseIdentity}` === writer.identity.key && source.databaseBirthtime === writer.identity.birthtime) checks.push(check);
		else {
			const group = foreign.get(source.databaseIdentity) ?? [];
			group.push(check);
			foreign.set(source.databaseIdentity, group);
		}
	}
	const assertAuthoritiesCurrent = () => authorities.forEach((authority) => authority.assertCurrent());
	if (foreign.size === 0) return {
		checks,
		assertCurrent: assertAuthoritiesCurrent
	};
	const [{ retainOpenClawAgentDatabaseReadOnly }, { readOpenClawAgentDatabaseIdentity }, { readSessionSourceValidation }, { hasSqliteSessionOwnerColumns }] = await Promise.all([
		import("./openclaw-agent-db-readonly-BPrk2NDl.mjs"),
		import("./openclaw-agent-db-identity-iQ8ROdpt.mjs"),
		import("./session-source-predicate.worker-B4Tq5g8W.mjs"),
		import("./session-accessor.sqlite-owner-projection-CmEtMLwP.mjs")
	]);
	const resources = [];
	const release = () => releaseSessionSourceAuthorities(resources);
	const assertions = [];
	try {
		assertAuthoritiesCurrent();
		for (const group of foreign.values()) {
			const source = group[0].predicate.source;
			const assertPathsCurrent = () => {
				for (const { predicate } of group) if (typeof predicate.source.databaseIdentity === "string") assertExistingDatabaseIdentity(predicate.source.path, `file:${predicate.source.databaseIdentity}`, predicate.source.databaseBirthtime);
			};
			assertPathsCurrent();
			const retained = retainOpenClawAgentDatabaseReadOnly({
				...writer.options,
				agentId: source.agentId,
				path: source.path
			});
			if (!retained.found) throw new Error("Voice session source is unavailable");
			resources.push(retained.claim);
			const identity = readOpenClawAgentDatabaseIdentity(retained.database);
			if (identity.identity !== source.databaseIdentity || identity.birthtime !== source.databaseBirthtime) throw new Error("Voice session source changed its captured database owner");
			hasSqliteSessionOwnerColumns(retained.database.db);
			const predicates = group.map((check) => check.predicate);
			assertions.push(() => {
				assertPathsCurrent();
				retained.claim.assertCurrent();
				acceptSessionSourceValidation({
					checks: group,
					assertCurrent: assertAuthoritiesCurrent
				}, readSessionSourceValidation(retained.database, predicates));
			});
		}
		return {
			nativeSource: true,
			checks,
			assertCurrent: () => {
				assertAuthoritiesCurrent();
				assertions.forEach((assertCurrent) => assertCurrent());
			},
			release
		};
	} catch (error) {
		await releaseSessionSourceAuthorities(resources, [error]);
		throw error;
	}
}
//#endregion
//#region src/talk/client-voice-session-write.ts
/** Voice metadata keeps its durable agent owner, including for incognito transcripts. */
function captureClientVoiceSessionWriter(params) {
	const captured = params.physicalSource ?? captureClientVoiceSessionSource(params.agentId);
	const options = captured.options;
	captured.assertCurrent();
	const existing = captured.identity.key.startsWith("file:");
	const execution = captureOpenClawAgentDatabaseExecution(options, existing ? { expectedIdentity: {
		kind: "file",
		physicalIdentity: captured.identity.key.slice(5),
		birthtime: captured.identity.birthtime,
		nativeLocation: captured.identity.canonicalPath
	} } : { expectedCreationIdentity: captured.identity });
	const assertCurrent = () => {
		assertClientVoiceSessionSettlementCurrent(captured.settlementContext);
		execution.assertCurrent();
		if (existing) captured.assertCurrent();
		params.assertCurrent?.();
	};
	const createSource = (authority) => ({
		assertCurrent: () => {
			assertCurrent();
			authority?.assertCurrent();
		},
		createAdmission(binding) {
			return () => ({
				nativeLocations: binding.nativeLocations,
				admission: createSqliteWorkerOperationAdmission((request, grant) => {
					binding.authorize(request);
					const facts = isRecord(request.facts) ? request.facts.publication : void 0;
					if (isRecord(facts) && facts.kind === "voice-session-authority") {
						if (!authority) throw new Error("Voice session authority omitted its prepared assertion");
						if (facts.sourceValidation) acceptSessionSourceValidation(authority, facts.sourceValidation);
						if (facts.facts) {
							if (!authority.transaction) throw new Error("Voice session transaction omitted its prepared assertion");
							authority.transaction.assertCurrent(facts.facts);
						}
						authority.assertCurrent();
					}
					if (!grant()) throw new Error("Voice session write authority expired");
				}, binding.attachment)
			});
		}
	});
	async function mutate(input, publish, authority) {
		const mutation = structuredClone(input);
		const source = createSource(authority);
		return runOpenClawAgentWorkerWrite(options, async () => {
			if (mutation.kind === "create") await execution.prepare(source);
			const result = await execution.runExisting(source, async (worker) => {
				const committed = await worker.execute({
					type: "voice.session.mutate",
					input: {
						...mutation,
						...authority?.transaction ? { transactionSource: {
							source: authority.transaction.source,
							agentId: authority.transaction.agentId,
							sessionKey: authority.transaction.sessionKey
						} } : {},
						...authority?.checks.length ? { sources: authority.checks.map((check) => check.predicate) } : {}
					}
				});
				return { value: publish ? publish(committed.record, committed.entry) : committed.record };
			});
			if (!result) throw new Error("Voice session database is missing");
			return result.value;
		});
	}
	const readCommittedIdentity = () => {
		const committed = execution.fileIdentity;
		return committed && {
			key: `file:${committed.physicalIdentity}`,
			birthtime: committed.birthtime,
			canonicalPath: committed.nativeLocation
		};
	};
	return {
		options,
		get identity() {
			return readCommittedIdentity() ?? captured.identity;
		},
		settlementContext: captured.settlementContext,
		get source() {
			assertCurrent();
			const committed = readCommittedIdentity();
			if (committed) return createClientVoiceSessionSource(options, committed);
			if (existing) return captured;
			throw new Error("Voice session creation has not been acknowledged");
		},
		assertCurrent,
		get admissionExecution() {
			return execution;
		},
		adoptNativeDatabase: (database) => execution.adoptNativeDatabase(database),
		release: () => execution.release(),
		read(voiceSessionId) {
			return runOpenClawAgentWorkerWrite(options, () => execution.runExisting(createSource(), async (worker) => {
				const result = await worker.execute({
					type: "voice.session.read",
					input: { voiceSessionId }
				});
				assertCurrent();
				return result;
			}));
		},
		mutate
	};
}
/** Ensure Talk has the same canonical agent-session row that chat turns append to. */
async function ensureClientVoiceAgentSessionEntry(params) {
	const assertLifetimeCurrent = () => {
		params.assertCurrent?.();
		if (params.deadlineAt !== void 0 && Date.now() >= params.deadlineAt) throw new Error("Realtime browser session expired during startup; try again");
	};
	const reduction = () => {
		const fallbackEntry = mergeSessionEntry(void 0, {});
		return {
			operation: {
				kind: "ensure-identity",
				sessionId: fallbackEntry.sessionId,
				creation: buildSessionCreationStamp({
					via: "talk",
					actor: params.creation?.actor ?? {
						type: "human",
						source: "unknown"
					},
					sandbox: params.creation?.sandbox
				})
			},
			fallbackEntry
		};
	};
	const publication = {
		onCommitted: params.onCommitted,
		onCommittedSource: params.onCommittedSource
	};
	const complete = (created) => {
		if (!created?.sessionId) throw new Error(`agent session could not be initialized (${params.sessionKey})`);
		return created.sessionId;
	};
	if (isIncognitoSessionKey(params.sessionKey)) {
		const { operation, fallbackEntry } = reduction();
		const authority = composeSessionSourceAssertion([params.requester, params.source]);
		return complete(await applySessionEntryOperation(params, operation, {
			...publication,
			fallbackEntry,
			workerGuard: { assertCurrent: assertLifetimeCurrent },
			assertCommitAllowed: () => {
				assertLifetimeCurrent();
				authority();
			}
		}));
	}
	const resources = [];
	return withClientVoiceSessionResources(resources, async () => {
		const selected = await captureClientVoiceEntrySource(params, assertLifetimeCurrent);
		resources.push(selected.execution);
		return await runOpenClawAgentWriteAdmission(selected.options, async () => {
			const { execution, agentId } = selected;
			const requester = await prepareSessionSourceAuthority(params.requester);
			resources.push(requester);
			const borrowedRequester = Object.assign(() => params.requester?.(), { prepareSessionSource: async () => ({
				...requester,
				release: void 0
			}) });
			const source = composeSessionSourceAssertion([borrowedRequester, params.source]);
			const assertPreparationCurrent = () => {
				execution.assertCurrent();
				selected.assertCurrent();
				assertPreparedSessionSourceCurrent(requester);
			};
			const { withOpenClawAgentDatabaseAsync, withOpenClawAgentDatabaseRuntimeFromExecution } = await import("./openclaw-agent-db-l9sMJW3o.mjs");
			const apply = async (database) => {
				await execution.adoptNativeDatabase(database);
				assertPreparationCurrent();
				const identity = execution.fileIdentity;
				if (!identity) throw new Error("Talk entry preparation omitted its physical source");
				const canonicalKey = resolveSqliteSessionKey(params.sessionKey, agentId);
				const readSource = {
					agentId: execution.agentId,
					path: identity.nativeLocation,
					databaseIdentity: identity.physicalIdentity,
					databaseBirthtime: identity.birthtime
				};
				const grant = !requester.nativeSource && !requester.opaqueCommitGuard && requester.checks.length === 0 ? await params.prepareWorkerGrant?.({
					source: readSource,
					agentId,
					sessionKey: canonicalKey
				}) : void 0;
				if (grant) resources.push(grant);
				assertPreparationCurrent();
				grant?.assertLifetimeCurrent();
				const { operation, fallbackEntry } = reduction();
				return complete(await applySessionEntryTargetOperation({
					agentId,
					env: selected.options.env,
					storePath: selected.storePath,
					target: {
						canonicalKey,
						storeKeys: collectSessionEntryLookupKeys(canonicalKey)
					},
					readSource
				}, operation, {
					...publication,
					fallbackEntry,
					retainedExecution: execution,
					workerGuard: {
						source: grant?.transaction ? borrowedRequester : source,
						ensureIdentitySource: grant?.transaction,
						assertCurrent: () => {
							assertPreparationCurrent();
							grant?.assertLifetimeCurrent();
						}
					}
				}));
			};
			return requester.opaqueCommitGuard ? withOpenClawAgentDatabaseAsync(selected.options, apply, () => {
				assertPreparationCurrent();
				source();
			}) : withOpenClawAgentDatabaseRuntimeFromExecution(selected.options, execution, apply, assertPreparationCurrent);
		}, true);
	});
}
/** Prepare authority once, then mutate and publish under the existing physical-store FIFO. */
async function mutateAuthorizedClientVoiceSession(params, writer, prepareMutation, publish) {
	const resources = [];
	return withClientVoiceSessionResources(resources, async () => {
		const sourceCandidates = params.source ? captureSessionStoreReadCandidates(params.source.storePath) : [];
		const sourceIdentities = captureSessionStoreCandidateIdentities(sourceCandidates);
		const assertSourceLocatorsCurrent = () => {
			if (!sourceCandidates.every(isSessionStoreReadCandidateCurrent)) throw new Error("Voice session source changed");
		};
		return await runOpenClawAgentWriteAdmission(writer.options, async () => {
			writer.assertCurrent();
			params.assertCurrent?.();
			const requester = await prepareSessionSourceAuthority(params.requester);
			resources.push(requester);
			params.assertCurrent?.();
			requester.assertCurrent();
			const mutation = prepareMutation();
			let assertSourceIdentityCurrent;
			let authority;
			let workerGrant;
			if (params.source) {
				if (params.source.prepareWorkerGrant && !requester.nativeSource && !requester.opaqueCommitGuard && [...sourceIdentities.values()].every((identity) => !identity.key.startsWith("file:") || identity.key === writer.identity.key && identity.birthtime === writer.identity.birthtime)) {
					workerGrant = await params.source.prepareWorkerGrant();
					resources.push(workerGrant);
				}
				authority = workerGrant?.transaction ? {
					checks: [],
					assertCurrent: workerGrant.assertLifetimeCurrent,
					transaction: workerGrant.transaction
				} : await prepareSessionSourceAuthority(params.source.assertCurrent);
				if (!workerGrant?.transaction) resources.push(authority);
				const preparedSources = [...authority.checks.map(({ predicate }) => predicate.source), ...authority.transaction ? [authority.transaction.source] : []].filter((source) => [...sourceIdentities.values()].some((identity) => typeof source.databaseIdentity === "string" && identity.key === `file:${source.databaseIdentity}` && identity.birthtime === source.databaseBirthtime));
				const preparedSource = preparedSources.every((source) => source.databaseIdentity === preparedSources[0]?.databaseIdentity) ? preparedSources[0] : void 0;
				const canonical = resolveUnsuffixedSqliteTargetFromSessionStorePath(params.source.storePath);
				const sourceTarget = preparedSource ?? (canonical.agentId || authority.nativeSource && isIncognitoOpenClawAgentSqlitePath(canonical.path, writer.options) ? {
					...canonical,
					agentId: canonical.agentId ?? params.agentId
				} : await prepareSqliteTargetFromSessionStorePath(params.source.storePath, {
					agentId: params.agentId,
					env: writer.options.env
				}));
				const sourcePath = assertSessionStoreReadCandidate(sourceTarget.path, sourceCandidates);
				const sourceIdentity = sourceIdentities.get(sourcePath);
				if (!sourceTarget.agentId || !sourceIdentity) throw new Error("Voice session source changed its captured database owner");
				const assertSourceCurrent = () => {
					assertSourceLocatorsCurrent();
					assertSessionStoreReadCandidate(sourceTarget.path, sourceCandidates);
					assertDatabasePathIdentity(sourcePath, sourceIdentity);
				};
				writer.assertCurrent();
				params.assertCurrent?.();
				requester.assertCurrent();
				assertSourceCurrent();
				authority.assertCurrent();
				assertSourceCurrent();
				assertSourceIdentityCurrent = assertSourceCurrent;
				if ((sourceIdentity.key !== writer.identity.key || sourceIdentity.birthtime !== writer.identity.birthtime) && (requester.nativeSource || requester.opaqueCommitGuard || authority.nativeSource || authority.opaqueCommitGuard || authority.checks.length === 0)) {
					const native = retainOpenClawAgentDatabaseReadOnly({
						...writer.options,
						agentId: sourceTarget.agentId,
						path: sourcePath
					});
					if (!native.found) throw new Error("Voice session source is unavailable");
					resources.push(native.claim);
					assertSourceIdentityCurrent = () => {
						assertSourceCurrent();
						native.claim.assertCurrent();
					};
				}
			}
			const sources = await prepareClientVoiceSessionSourceChecks(writer, authority ? [requester, authority] : [requester]);
			resources.push(sources);
			let committed;
			if (sources.nativeSource || requester.nativeSource || requester.opaqueCommitGuard || authority?.nativeSource || authority?.opaqueCommitGuard) {
				const [{ withOpenClawAgentDatabaseAsync, withOpenClawAgentDatabaseRuntimeFromExecution }, { runOpenClawAgentWriteWithYieldingAdmission }, { hasSqliteSessionOwnerColumns }, { readSessionSourceValidation }, { readSessionPendingInputAuthorityFactsInTransaction }, kernel] = await Promise.all([
					import("./openclaw-agent-db-l9sMJW3o.mjs"),
					import("./openclaw-agent-db-transaction-Bmplz47D.mjs"),
					import("./session-accessor.sqlite-owner-projection-CmEtMLwP.mjs"),
					import("./session-source-predicate.worker-B4Tq5g8W.mjs"),
					import("./session-pending-input-authority.kernel-BZVfoqXF.mjs"),
					import("./client-voice-session-write.kernel-BcBu0bNP.mjs")
				]);
				const opaqueGuard = requester.opaqueCommitGuard || authority?.opaqueCommitGuard;
				const predicates = sources.checks.map((check) => check.predicate);
				const assertPreparationCurrent = () => {
					assertSourceLocatorsCurrent();
					assertSourceIdentityCurrent?.();
					writer.assertCurrent();
					params.assertCurrent?.();
					assertPreparedSessionSourceCurrent(requester);
				};
				const assertNativeCurrent = (transaction) => {
					assertPreparationCurrent();
					if (transaction) acceptSessionSourceValidation(sources, readSessionSourceValidation(transaction, predicates));
					sources.assertCurrent();
					if (transaction) {
						if (authority?.transaction) authority.transaction.assertCurrent(readSessionPendingInputAuthorityFactsInTransaction(transaction, authority.transaction.sessionKey, authority.transaction.agentId));
					}
					assertSourceIdentityCurrent?.();
				};
				committed = await runOpenClawAgentWriteAdmission(writer.options, async () => {
					assertDatabasePathIdentity(writer.options.path, writer.identity);
					const mutate = async (database) => {
						assertNativeCurrent();
						await writer.adoptNativeDatabase(database);
						assertNativeCurrent();
						if (predicates.length > 0 || authority?.transaction) hasSqliteSessionOwnerColumns(database.db);
						return runOpenClawAgentWriteWithYieldingAdmission((transaction) => {
							assertNativeCurrent(transaction);
							const committedRecord = kernel.mutateVoiceSessionInDatabase(transaction, mutation);
							assertNativeCurrent(transaction);
							return committedRecord;
						}, writer.options, { operationLabel: `voice.session.${mutation.kind}` });
					};
					return publish(opaqueGuard ? await withOpenClawAgentDatabaseAsync(writer.options, mutate, assertNativeCurrent) : await withOpenClawAgentDatabaseRuntimeFromExecution(writer.options, writer.admissionExecution, mutate, assertPreparationCurrent));
				}, true);
			} else committed = await writer.mutate(mutation, publish, {
				checks: sources.checks,
				transaction: authority?.transaction,
				assertCurrent: () => {
					assertSourceLocatorsCurrent();
					assertSourceIdentityCurrent?.();
					params.assertCurrent?.();
					sources.assertCurrent();
				}
			});
			if (workerGrant?.transaction && params.source?.retainWorkerGrant) {
				params.source.retainWorkerGrant(workerGrant);
				resources.splice(resources.indexOf(workerGrant), 1);
			}
			return committed;
		}, true);
	});
}
/** Create a call record or resume the same open call across transport restarts. */
async function createOrResumeClientVoiceSession(input, retainedWriter) {
	const params = { ...input };
	return withClientVoiceSessionSettlement(async () => {
		const voiceSessionId = params.voiceSessionId?.trim() || randomUUID();
		const writer = retainedWriter ?? captureClientVoiceSessionWriter({
			agentId: params.agentId,
			physicalSource: params.physicalSource
		});
		return withClientVoiceSessionResources(retainedWriter ? [] : [writer], () => mutateAuthorizedClientVoiceSession(params, writer, () => ({
			kind: "create",
			agentId: params.agentId,
			sessionKey: params.sessionKey,
			voiceSessionId,
			provider: params.provider?.trim() || void 0,
			origin: params.origin,
			transcriptCapable: params.transcriptCapable,
			now: params.now ?? Date.now()
		}), () => voiceSessionId));
	}, void 0, retainedWriter?.settlementContext ?? params.physicalSource?.settlementContext);
}
//#endregion
//#region src/talk/client-voice-mutation-digest-owner.ts
const loadMessageRuntime = createLazyRuntimeModule(() => import("./runtime-7z0Gnc9h.mjs"));
const CLIENT_VOICE_MUTATION_DIGEST_POLICY = {
	maxRetainedIntents: 64,
	maxRetainedIdentityBytes: 65536,
	maxConcurrentAttempts: 2,
	maxAttemptFailures: 3,
	attemptAbortAfterMs: 3e4,
	failureRetentionMs: 3e5
};
function formatMutationDigest(effects) {
	if (effects.length === 0) return;
	return ["Voice call changes", ...effects.slice(0, 12).map((effect) => `- ${effect.toolName}: ${effect.status === "started" ? "outcome not confirmed" : effect.status}`)].join("\n");
}
/** A confirmed send survives marker retries without re-entering the sender. */
async function deliverClientVoiceMutationDigest(record, config, signal, writer, delivery, preparedEntry, conversation) {
	if (record.digestDeliveredAt) return;
	if (!delivery.confirmed) {
		const text = formatMutationDigest(record.effects);
		if (!text) return;
		const assertCurrent = () => {
			writer.assertCurrent();
			const selected = conversation.selected;
			if (selected) assertExistingDatabaseIdentity(selected.canonicalPath, selected.key, selected.birthtime);
		};
		const send = async (entry, assertReadCurrent) => {
			if (!entry) throw new Error(`Voice mutation digest conversation not found (${record.sessionKey})`);
			const target = resolveSessionDeliveryTarget({
				entry,
				requestedChannel: "last"
			});
			if (!target.channel || target.channel === "webchat" || !target.to) return;
			const { sendDurableMessageBatchCore, durableMessageBatchMayHaveReachedRecipient } = await loadMessageRuntime();
			assertCurrent();
			assertReadCurrent();
			signal.throwIfAborted();
			const result = await sendDurableMessageBatchCore({
				cfg: config,
				channel: target.channel,
				to: target.to,
				...target.accountId ? { accountId: target.accountId } : {},
				...target.threadId != null ? { threadId: target.threadId } : {},
				payloads: [{ text }],
				durability: "required",
				requireUnknownSendReconciliation: true,
				signal,
				session: buildOutboundSessionContext({
					cfg: config,
					agentId: record.agentId,
					sessionKey: record.sessionKey,
					policySessionKey: record.sessionKey
				})
			});
			if (durableMessageBatchMayHaveReachedRecipient(result)) delivery.mayHaveReachedRecipient = true;
			if (result.status === "failed" || result.status === "partial_failed") throw result.error;
			if (result.status === "suppressed" && delivery.mayHaveReachedRecipient) throw new Error("voice mutation digest delivery outcome is uncertain");
			delivery.confirmed = {
				agentId: record.agentId,
				sessionKey: record.sessionKey,
				voiceSessionId: record.voiceSessionId,
				deliveredAt: Date.now()
			};
		};
		const target = resolveUnsuffixedSqliteTargetFromSessionStorePath(conversation.storePath);
		const captured = conversation.identities.get(writer.identity.canonicalPath);
		if (!isIncognitoSessionKey(record.sessionKey) && target.agentId === record.agentId && target.path === writer.options.path && captured?.key === writer.identity.key && captured.birthtime === writer.identity.birthtime) {
			conversation.selected ??= captured;
			await send(preparedEntry, assertCurrent);
		} else await withSessionEntryReadOnlyInWorker({
			agentId: record.agentId,
			sessionKey: record.sessionKey,
			storePath: conversation.selected?.canonicalPath ?? conversation.storePath,
			env: writer.options.env,
			projection: "list"
		}, assertCurrent, async (read, owner) => {
			if (!read.ok) throw read.error;
			if (owner.scope && !conversation.selected) {
				const selected = readDatabasePathIdentitySync(owner.scope.storePath);
				const original = conversation.identities.get(selected.canonicalPath);
				if (!original) throw new Error("Voice mutation digest conversation store changed before delivery");
				assertExistingDatabaseIdentity(selected.canonicalPath, original.key, original.birthtime);
				conversation.selected = selected;
			}
			await send(read.value, () => owner.assertCurrent());
		});
		if (!delivery.confirmed) return;
	}
	await writer.mutate({
		...delivery.confirmed,
		kind: "delivered",
		now: Date.now()
	});
}
var ClientVoiceMutationDigestOwner = class {
	constructor(options) {
		this.options = options;
		this.intents = /* @__PURE__ */ new Map();
		this.pendingKeys = /* @__PURE__ */ new Set();
		this.retryAfterActiveKeys = /* @__PURE__ */ new Set();
		this.activeAttempts = /* @__PURE__ */ new Map();
		this.retainedIdentityBytes = 0;
		this.policy = CLIENT_VOICE_MUTATION_DIGEST_POLICY;
	}
	record(params) {
		const key = operationKey(params.agentId, params.voiceSessionId);
		const existing = this.intents.get(key);
		if (existing?.retryBlocked) return;
		if (existing) {
			existing.context = this.options.updateContext?.(existing.context, params.context) ?? params.context;
			this.retry(params);
			return;
		}
		const identityBytes = Buffer.byteLength(params.agentId, "utf8") + Buffer.byteLength(params.voiceSessionId, "utf8") + 1;
		if (identityBytes > this.policy.maxRetainedIdentityBytes) {
			this.options.warn("voice mutation digest identity exceeds the retry owner byte limit");
			return;
		}
		if (this.intents.size >= this.policy.maxRetainedIntents || this.retainedIdentityBytes + identityBytes > this.policy.maxRetainedIdentityBytes) {
			this.options.warn("voice mutation digest retry owner is full");
			return;
		}
		const intent = {
			...params,
			identityBytes,
			failedAttempts: 0,
			queuedSettlement: this.options.captureAttempt?.(params.context)
		};
		this.intents.set(key, intent);
		this.retainedIdentityBytes += identityBytes;
		this.pendingKeys.add(key);
		this.pump();
	}
	retry(params) {
		const key = operationKey(params.agentId, params.voiceSessionId);
		const intent = this.intents.get(key);
		if (!intent || intent.retryBlocked) return;
		intent.queuedSettlement ??= this.options.captureAttempt?.(intent.context);
		(this.activeAttempts.has(key) ? this.retryAfterActiveKeys : this.pendingKeys).add(key);
		this.pump();
	}
	retryAgent(agentId, context) {
		try {
			for (const [key, intent] of this.intents) {
				if (intent.agentId !== agentId || intent.retryBlocked || this.options.matchesRetryContext?.(intent.context, context) === false) continue;
				intent.context = this.options.updateContext?.(intent.context, context) ?? context;
				intent.queuedSettlement ??= this.options.captureAttempt?.(intent.context);
				(this.activeAttempts.has(key) ? this.retryAfterActiveKeys : this.pendingKeys).add(key);
			}
		} finally {
			this.pump();
		}
	}
	snapshot() {
		return {
			active: this.activeAttempts.size,
			pending: this.pendingKeys.size,
			retained: this.intents.size,
			retainedIdentityBytes: this.retainedIdentityBytes
		};
	}
	clear() {
		for (const attempt of this.activeAttempts.values()) attempt.abort(/* @__PURE__ */ new Error("voice mutation digest delivery owner reset"));
		for (const intent of this.intents.values()) {
			intent.queuedSettlement?.release();
			if (intent.failureExpiry) clearTimeout(intent.failureExpiry);
		}
		this.intents.clear();
		this.pendingKeys.clear();
		this.retryAfterActiveKeys.clear();
		this.activeAttempts.clear();
		this.retainedIdentityBytes = 0;
	}
	deleteIntent(key, expected) {
		const current = this.intents.get(key);
		if (!current || expected && current !== expected) return;
		this.intents.delete(key);
		current.queuedSettlement?.release();
		this.pendingKeys.delete(key);
		this.retryAfterActiveKeys.delete(key);
		if (current.failureExpiry) clearTimeout(current.failureExpiry);
		this.retainedIdentityBytes -= current.identityBytes;
	}
	blockRetry(key, intent) {
		intent.retryBlocked = true;
		this.clearFailureState(intent);
		this.pendingKeys.delete(key);
		this.retryAfterActiveKeys.delete(key);
		intent.queuedSettlement?.release();
		delete intent.queuedSettlement;
	}
	stopAfterFailure(key, intent, reason) {
		const delivered = this.options.deliveryState?.(intent.context) === "confirmed";
		if (delivered) this.blockRetry(key, intent);
		else this.deleteIntent(key, intent);
		this.options.warn(`voice mutation digest ${delivered ? "marker retry stopped" : "dropped"} ${reason}`);
	}
	retainAfterFailure(key, intent) {
		if (intent.failureExpiry) return;
		intent.failureExpiry = setTimeout(() => {
			if (this.intents.get(key) !== intent) return;
			if (this.activeAttempts.has(key)) {
				intent.expireAfterActive = true;
				return;
			}
			this.stopAfterFailure(key, intent, `after retry retention expired (${intent.failedAttempts} failed attempts)`);
		}, this.policy.failureRetentionMs);
		intent.failureExpiry.unref?.();
	}
	clearFailureState(intent) {
		if (intent.failureExpiry) {
			clearTimeout(intent.failureExpiry);
			delete intent.failureExpiry;
		}
		delete intent.expireAfterActive;
		intent.failedAttempts = 0;
	}
	pump() {
		while (this.activeAttempts.size < this.policy.maxConcurrentAttempts && this.pendingKeys.size > 0) {
			const key = this.pendingKeys.values().next().value;
			if (!key) return;
			this.pendingKeys.delete(key);
			const intent = this.intents.get(key);
			if (!intent || this.activeAttempts.has(key)) continue;
			this.startAttempt(key, intent);
		}
	}
	startAttempt(key, intent) {
		runInDetachedAsyncContext(() => {
			const settlement = intent.queuedSettlement;
			delete intent.queuedSettlement;
			const attempt = new AbortController();
			this.activeAttempts.set(key, attempt);
			const timeout = setTimeout(() => attempt.abort(/* @__PURE__ */ new Error("voice mutation digest delivery abort requested")), this.policy.attemptAbortAfterMs);
			timeout.unref?.();
			let completion;
			try {
				const run = () => this.options.attempt({
					...intent,
					signal: attempt.signal
				});
				completion = settlement ? settlement.run(run) : run();
			} catch (error) {
				completion = Promise.reject(error instanceof Error ? error : new Error(String(error)));
			}
			completion.then((complete) => {
				if (complete) this.deleteIntent(key, intent);
				else this.clearFailureState(intent);
			}).catch((error) => {
				if (this.activeAttempts.get(key) !== attempt) return;
				if (hasSqliteWorkerOutcomeUnknown(error)) {
					this.blockRetry(key, intent);
					this.options.warn("voice mutation digest settlement is unknown; delivery was not replayed");
					return;
				}
				if (this.options.deliveryState?.(intent.context) === "uncertain") {
					this.blockRetry(key, intent);
					this.options.warn("voice mutation digest may have been delivered; delivery was not replayed");
					return;
				}
				intent.failedAttempts += 1;
				const message = error instanceof Error ? error.message : String(error);
				if (intent.failedAttempts >= this.policy.maxAttemptFailures) {
					this.stopAfterFailure(key, intent, `after ${intent.failedAttempts} failed attempts: ${message}`);
					return;
				}
				this.options.warn(message);
				this.retainAfterFailure(key, intent);
			}).finally(() => {
				settlement?.release();
				clearTimeout(timeout);
				if (this.activeAttempts.get(key) !== attempt) return;
				this.activeAttempts.delete(key);
				if (intent.expireAfterActive && this.intents.get(key) === intent) {
					this.stopAfterFailure(key, intent, `after retry retention expired (${intent.failedAttempts} failed attempts)`);
					this.pump();
					return;
				}
				if (this.retryAfterActiveKeys.delete(key) && this.intents.has(key)) this.pendingKeys.add(key);
				this.pump();
			});
		});
	}
};
function sameMutationDigestSource(previous, next) {
	return previous.source.options.path === next.source.options.path && previous.source.identity.key === next.source.identity.key && previous.source.identity.birthtime === next.source.identity.birthtime;
}
function createClientVoiceMutationDigestDeliveryOptions(readLiveConsultRuns, captureAttempt) {
	const hasLiveConsultRun = (record, source) => [...readLiveConsultRuns()].some(([runId, { binding, source: runSource }]) => binding.agentId === record.agentId && binding.voiceSessionId === record.voiceSessionId && runSource.identity.key === source.identity.key && runSource.identity.birthtime === source.identity.birthtime && (source.identity.key.startsWith("file:") || runSource.identity.canonicalPath === source.identity.canonicalPath) && (record.consultRunIds === void 0 || record.consultRunIds.includes(runId)));
	return {
		captureAttempt: (context) => {
			if (!context.conversation) {
				const storePath = resolveSessionStorePathForScope({
					agentId: context.source.options.agentId,
					env: context.source.options.env
				}, context.config);
				context.conversation = {
					storePath,
					identities: captureSessionStoreCandidateIdentities(captureSessionStoreReadCandidates(storePath))
				};
			}
			return captureAttempt(context.source.settlementContext);
		},
		matchesRetryContext: (previous, next) => sameMutationDigestSource(previous, next) && previous.source.settlementContext.admission === next.source.settlementContext.admission,
		deliveryState: ({ delivery }) => delivery?.confirmed !== void 0 ? "confirmed" : delivery?.mayHaveReachedRecipient ? "uncertain" : "unsent",
		updateContext(previous, next) {
			if (!sameMutationDigestSource(previous, next)) throw new Error("Voice mutation digest cannot change its accepted physical source");
			previous.source.assertCurrent();
			return {
				config: next.config,
				source: previous.source,
				conversation: previous.conversation,
				...previous.delivery ? { delivery: previous.delivery } : {}
			};
		},
		attempt: async ({ voiceSessionId, context, signal }) => {
			const { config, source } = context;
			source.assertCurrent();
			if (!context.delivery?.confirmed && hasLiveConsultRun({
				agentId: source.options.agentId,
				voiceSessionId
			}, source)) return false;
			const delivery = context.delivery ??= {};
			const writer = captureClientVoiceSessionWriter({
				agentId: source.options.agentId,
				physicalSource: source
			});
			return withClientVoiceSessionResources([writer], async () => {
				if (delivery.confirmed) {
					await writer.mutate({
						...delivery.confirmed,
						kind: "delivered",
						now: Date.now()
					});
					return true;
				}
				const prepared = await writer.read(voiceSessionId);
				const record = prepared?.record;
				if (!record) return true;
				if (record.status !== "closed" || hasLiveConsultRun(record, source)) return false;
				const conversation = context.conversation;
				if (!conversation) throw new Error("Voice mutation digest conversation source was not captured");
				await deliverClientVoiceMutationDigest(record, config, signal, writer, delivery, prepared?.entry, conversation);
				return true;
			});
		},
		warn: (message) => console.warn(`[talk] deferred voice mutation digest failed: ${message}`)
	};
}
//#endregion
//#region src/talk/client-voice-session-read.ts
/** Use the existing agent reader's custody and revocation lifecycle. */
async function lookupClientVoiceSessions(request, source) {
	const env = cloneEnvWithPlatformSemantics(source?.env ?? process.env);
	env.OPENCLAW_STATE_DIR = resolveStateDir(env);
	const options = {
		agentId: request.agentId,
		env,
		path: resolveOpenClawAgentSqlitePath({
			agentId: request.agentId,
			env,
			path: source?.path
		})
	};
	return withSessionHistoryWorkerDatabase(options, async (owner) => {
		const result = await owner.readVoiceSessions({
			request,
			env
		});
		owner.assertCurrent();
		return result.matches;
	});
}
/** Read the canonical agent-session id without creating state during provider startup. */
function resolveClientVoiceAgentSessionId(params) {
	return loadSessionEntryReadOnly(params)?.sessionId?.trim() || void 0;
}
/** Resolve the unique open client-owned call for legacy tool-call clients. */
async function resolveOpenClientVoiceSessionId(params, source) {
	const matches = await lookupClientVoiceSessions({
		kind: "legacy",
		...params
	}, source);
	return matches.length === 1 ? matches[0]?.voiceSessionId : void 0;
}
/** Validate ownership and open state before starting a voice-bound consult. */
function assertClientVoiceSessionOpen(params, source) {
	source?.assertCurrent();
	const record = readOwnedVoiceSessionFacts(params, source?.options);
	source?.assertCurrent();
	if (record.status !== "open") throw new Error("voice session is closed");
	return record.origin;
}
//#endregion
//#region src/talk/client-voice-session.ts
/** Durable per-agent voice-call records for Talk continuity and mutation evidence. */
const voiceSessionByRunId = /* @__PURE__ */ new Map();
const voiceSessionOperations = new VoiceTranscriptOperationRegistry();
let unsubscribeToolEffects;
let unsubscribeRunCompletion;
async function closeVoiceSessionOperationOwner(params, retainedWriter) {
	return withClientVoiceSessionSettlement(async () => {
		const writer = retainedWriter ?? captureClientVoiceSessionWriter(params);
		return withClientVoiceSessionResources(retainedWriter ? [] : [writer], async () => {
			let closed;
			await voiceSessionOperations.close(operationKey(params.agentId, params.voiceSessionId), async () => {
				closed = await closeClientVoiceSessionInternal({
					...params,
					writer
				});
			});
			if (closed === void 0 && params.staleBefore === void 0) return await closeVoiceSessionOperationOwner(params, writer);
			return closed ?? false;
		});
	}, void 0, params.physicalSource?.settlementContext ?? retainedWriter?.settlementContext);
}
async function recordClientVoiceToolEffect(event) {
	const owner = event.runId ? voiceSessionByRunId.get(event.runId) : void 0;
	if (!owner) return;
	const { binding, source } = owner;
	try {
		await runWithClientVoiceRunSettlement(owner, () => withClientVoiceSessionSettlement(async () => {
			const writer = captureClientVoiceSessionWriter({
				...binding,
				physicalSource: source
			});
			await withClientVoiceSessionResources([writer], () => writer.mutate({
				...binding,
				kind: "effect",
				event,
				now: Date.now()
			}));
		}, void 0, source.settlementContext));
	} catch (error) {
		console.warn(`[talk] voice tool effect persistence failed: ${String(error)}`);
	}
}
function runWithClientVoiceRunSettlement(owner, run) {
	const capture = () => captureClientVoiceSessionSettlement(owner.source.settlementContext);
	const settlement = owner.settlement?.run(capture) ?? capture();
	try {
		return settlement.run(run);
	} finally {
		settlement.release();
	}
}
function retireClientVoiceRun(runId, owner, retry = true) {
	if (voiceSessionByRunId.get(runId) !== owner) return;
	owner.retired = true;
	voiceSessionByRunId.delete(runId);
	owner.stopObserving?.();
	try {
		const { binding } = owner;
		releaseClientVoiceConfirmationRun(binding.agentId, binding.voiceSessionId, runId);
		if (retry) {
			const retryDigest = () => mutationDigestDeliveryOwner.retry(binding);
			if (owner.settlement) owner.settlement.run(retryDigest);
			else retryDigest();
		}
	} finally {
		owner.settlement?.release();
	}
}
function ensureToolEffectSubscription() {
	unsubscribeToolEffects ??= onTrustedToolExecutionEvent((event) => {
		recordClientVoiceToolEffect(event);
	});
	unsubscribeRunCompletion ??= onTrustedInternalDiagnosticEvent((event) => {
		if (event.type !== "run.completed") return;
		const owner = voiceSessionByRunId.get(event.runId);
		if (!owner || owner.settlement) return;
		retireClientVoiceRun(event.runId, owner);
	}, { include: ["run.completed"] });
}
/** Correlate a consult run with its open call for confirmation and mutation evidence. */
async function registerClientVoiceConsultRun(input) {
	const params = { ...input };
	const previous = voiceSessionByRunId.get(params.runId);
	const sameBinding = previous !== void 0 && matchesClientVoiceRunSource(previous, params, params.physicalSource);
	const writer = sameBinding ? runWithClientVoiceRunSettlement(previous, () => captureClientVoiceSessionWriter({
		...params,
		physicalSource: params.physicalSource ?? previous.source
	})) : captureClientVoiceSessionWriter(params);
	const observeRelease = captureGatewayRootWorkReleaseObserver();
	const capture = () => captureClientVoiceSessionSettlement(writer.settlementContext);
	let settlement;
	let registered;
	return withClientVoiceSessionResources(() => settlement && settlement !== registered?.settlement ? [settlement, writer] : [writer], async () => {
		try {
			settlement = (sameBinding ? previous.settlement?.run(capture) : void 0) ?? capture();
			const accepted = settlement;
			return await accepted.run(() => {
				const mutation = {
					kind: "consult",
					agentId: params.agentId,
					sessionKey: params.sessionKey,
					voiceSessionId: params.voiceSessionId,
					runId: params.runId,
					now: Date.now()
				};
				const publish = (record) => {
					const source = writer.source;
					const retired = sameBinding && previous.retired;
					let owner = retired ? void 0 : voiceSessionByRunId.get(params.runId);
					if (!retired && !matchesClientVoiceRunSource(owner, params, source)) {
						if (owner) retireClientVoiceRun(params.runId, owner);
						owner = {
							binding: Object.freeze({
								agentId: params.agentId,
								voiceSessionId: params.voiceSessionId,
								sessionKey: params.sessionKey
							}),
							source,
							...observeRelease ? { settlement: accepted } : {}
						};
						registered = owner;
						voiceSessionByRunId.set(params.runId, owner);
						const published = owner;
						owner.stopObserving = observeRelease?.((reason) => retireClientVoiceRun(params.runId, published, reason === "settled"));
					}
					if (record?.status === "closed" && params.config) mutationDigestDeliveryOwner.record({
						agentId: params.agentId,
						voiceSessionId: params.voiceSessionId,
						context: {
							config: params.config,
							source: owner?.source ?? (sameBinding ? previous.source : source)
						}
					});
					ensureToolEffectSubscription();
					const release = () => {
						if (owner) retireClientVoiceRun(params.runId, owner);
					};
					params.onRegistered?.(release);
					return release;
				};
				return params.requester || params.source ? mutateAuthorizedClientVoiceSession(params, writer, () => mutation, publish) : writer.mutate(mutation, publish);
			});
		} catch (error) {
			if (registered) try {
				retireClientVoiceRun(params.runId, registered, false);
			} catch (releaseError) {
				throw createSqliteLifecycleAggregateError([error, releaseError], "Voice session registration and cleanup failed", error);
			}
			throw error;
		}
	});
}
/** Return the open voice-call binding for one executing run. */
function resolveClientVoiceRunBinding(runId) {
	return runId ? voiceSessionByRunId.get(runId)?.binding : void 0;
}
/**
* Confirmation applies only when the session can observe spoken approvals:
* relay sessions (server hears utterances) or clients that report transcripts.
* Legacy clients without transcript reporting keep pre-gate behavior.
*/
function isClientVoiceSessionConfirmable(binding) {
	const record = readVoiceSessionFacts(binding.agentId, binding.voiceSessionId);
	return record?.origin === "relay" || record?.transcriptCapable === true || record?.hasUserTranscript === true;
}
function appendVoiceTranscript(params, retainedWriter) {
	const normalized = {
		...params,
		sessionTarget: { ...params.sessionTarget },
		text: normalizeVoiceTranscriptText(params.text)
	};
	if (!normalized.text) return Promise.resolve();
	const confirmation = normalized.role === "user" ? prepareClientVoiceConfirmationTranscript({
		agentId: normalized.agentId,
		voiceSessionId: normalized.voiceSessionId,
		entryId: normalized.entryId,
		confirmation: normalized.confirmation
	}) : null;
	return withClientVoiceSessionSettlement(async () => {
		const writer = retainedWriter ?? captureClientVoiceSessionWriter(normalized);
		return withClientVoiceSessionResources(retainedWriter ? [] : [writer], async () => {
			await voiceSessionOperations.run(operationKey(normalized.agentId, normalized.voiceSessionId), async () => {
				const sessionTarget = {
					...normalized.sessionTarget,
					agentId: normalized.agentId,
					env: writer.options.env
				};
				const failureKey = sha256Hex(normalized.entryId);
				const timestamp = normalized.timestamp ?? Date.now();
				const reservation = {
					agentId: normalized.agentId,
					sessionKey: normalized.sessionKey,
					voiceSessionId: normalized.voiceSessionId,
					origin: normalized.origin,
					kind: "reserve",
					failureKey,
					now: Date.now()
				};
				const appendReserved = async (record, entry, target, assertFresh, workerTranscript = false) => {
					if (!record) throw new Error("voice session not found");
					if (!entry?.sessionId) throw new Error(`agent session not found (${normalized.sessionKey})`);
					const transcriptTarget = {
						...target,
						sessionId: entry.sessionId
					};
					const messageOptions = {
						...normalized.config ? { config: normalized.config } : {},
						eventId: voiceTranscriptEventId(normalized.voiceSessionId, normalized.entryId),
						message: buildPersistedVoiceMessage({
							role: normalized.role,
							text: normalized.text,
							timestamp,
							provider: record.provider ?? "realtime"
						}),
						now: timestamp
					};
					const turn = workerTranscript ? await appendExpectedSessionTranscriptTurn(transcriptTarget, {
						config: normalized.config,
						keyFormat: "agent-qualified",
						expectedSessionId: entry.sessionId,
						selectedSessionId: entry.sessionId,
						selectedLifecycleRevision: entry.lifecycleRevision,
						sessionFile: normalized.sessionTarget.sessionKey,
						assertCurrent: assertFresh,
						messages: [messageOptions],
						voiceTranscript: {
							agentId: normalized.agentId,
							sessionKey: normalized.sessionKey,
							voiceSessionId: normalized.voiceSessionId,
							failureKey,
							role: normalized.role
						}
					}) : void 0;
					const appended = workerTranscript ? turn?.appendedMessages[0] : await appendTranscriptMessage(transcriptTarget, {
						...messageOptions,
						preparation: { source: composeSessionSourceAssertion([assertFresh]) }
					});
					if (!appended) throw new Error("agent session changed before voice transcript append");
					if (confirmation) recordClientVoiceConfirmationTranscriptAppend({
						confirmation,
						entryId: normalized.entryId,
						text: normalized.text,
						appended: appended.appended
					});
					if (appended.appended) {
						await publishTranscriptUpdate(transcriptTarget, {
							message: appended.message,
							messageId: appended.messageId
						});
						assertFresh();
					}
					const confirmed = workerTranscript ? turn?.voiceSession : await writer.mutate({
						agentId: normalized.agentId,
						sessionKey: normalized.sessionKey,
						voiceSessionId: normalized.voiceSessionId,
						kind: "confirm",
						role: normalized.role,
						failureKey,
						now: Date.now()
					});
					if (normalized.role === "user" && confirmation) {
						if (!confirmed?.hasUserTranscript) throw new Error("voice transcript confirmation was not committed");
						noteClientVoiceConfirmationUtterance({
							agentId: normalized.agentId,
							voiceSessionId: normalized.voiceSessionId,
							timestamp: Date.now(),
							confirmation
						});
					}
				};
				const nativeTranscript = isNativeSessionEntryRead(sessionTarget, normalized.agentId);
				const transcriptStore = resolveUnsuffixedSqliteTargetFromSessionStorePath(sessionTarget.storePath || resolveOpenClawAgentSqlitePath({
					agentId: normalized.agentId,
					env: writer.options.env
				}));
				if (!nativeTranscript && (transcriptStore.agentId || transcriptStore.shared) && transcriptStore.path === writer.options.path) {
					const prepared = await writer.mutate({
						...reservation,
						transcriptSessionKey: sessionTarget.sessionKey
					}, (record, entry) => ({
						record,
						entry
					}));
					await appendReserved(prepared.record, prepared.entry, {
						...sessionTarget,
						storePath: writer.options.path
					}, writer.assertCurrent, true);
				} else await withSessionEntryReadOnlyInWorker(sessionTarget, writer.assertCurrent, async (read, source) => {
					if (!read.ok) throw read.error;
					if (!read.value?.sessionId) throw new Error(`agent session not found (${normalized.sessionKey})`);
					const physicalSource = source.scope?.storePath ? readDatabasePathIdentitySync(source.scope.storePath) : void 0;
					const record = await writer.mutate(reservation);
					source.assertCurrent();
					await appendReserved(record, read.value, {
						...sessionTarget,
						...source.scope
					}, () => {
						writer.assertCurrent();
						if (physicalSource) assertExistingDatabaseIdentity(physicalSource.canonicalPath, physicalSource.key, physicalSource.birthtime);
					}, !nativeTranscript && physicalSource?.key === writer.identity.key && physicalSource.birthtime === writer.identity.birthtime);
				});
			}, { weight: normalized.text.length });
		});
	}, void 0, retainedWriter?.settlementContext);
}
/** Append one finalized client-owned transcript item idempotently. */
function appendClientVoiceTranscript(params, retainedWriter) {
	return appendVoiceTranscript({
		...params,
		origin: "client"
	}, retainedWriter);
}
/** Wait for the accepted transcript/effect prefix without closing the logical call. */
async function flushClientVoiceSessionWrites(params, retainedWriter) {
	if (retainedWriter === null) {
		await voiceSessionOperations.flush(operationKey(params.agentId, params.voiceSessionId));
		return;
	}
	const writer = retainedWriter ?? captureClientVoiceSessionWriter(params);
	await withClientVoiceSessionResources(retainedWriter ? [] : [writer], async () => {
		await voiceSessionOperations.flush(operationKey(params.agentId, params.voiceSessionId));
		await runOpenClawAgentWriteAdmission(writer.options, () => writer.assertCurrent());
	});
}
/** Append one finalized relay-owned transcript item idempotently. */
function appendRelayVoiceTranscript(params, retainedWriter) {
	return appendVoiceTranscript({
		...params,
		origin: "relay"
	}, retainedWriter);
}
const mutationDigestDeliveryOwner = new ClientVoiceMutationDigestOwner(createClientVoiceMutationDigestDeliveryOptions(() => voiceSessionByRunId, captureClientVoiceSessionSettlement));
async function closeClientVoiceSessionInternal(params) {
	const now = params.now ?? Date.now();
	const closed = await params.writer.mutate({
		kind: "close",
		agentId: params.agentId,
		sessionKey: params.sessionKey,
		voiceSessionId: params.voiceSessionId,
		transcriptFailurePolicy: params.transcriptFailurePolicy,
		expectedOrigin: params.expectedOrigin,
		staleBefore: params.staleBefore,
		now
	});
	if (!closed) return false;
	const liveRunIds = closed.consultRunIds.filter((runId) => {
		const binding = voiceSessionByRunId.get(runId)?.binding;
		return binding?.voiceSessionId === params.voiceSessionId && binding.agentId === params.agentId;
	});
	deactivateClientVoiceConfirmationSession(params.agentId, params.voiceSessionId, liveRunIds);
	mutationDigestDeliveryOwner.record({
		agentId: params.agentId,
		voiceSessionId: params.voiceSessionId,
		context: {
			config: params.config,
			source: params.writer.source
		}
	});
	return true;
}
/** Close a logical voice call after its accepted transcript prefix is durable. */
async function closeClientVoiceSession(params, retainedWriter) {
	await closeVoiceSessionOperationOwner({
		...params,
		transcriptFailurePolicy: "require-success"
	}, retainedWriter);
}
/**
* Terminally close a relay call after its bounded append retries settle.
* Relays have no payload replay owner after teardown, so unresolved hashes remain as audit state.
*/
async function closeRelayVoiceSessionRecord(params, retainedWriter) {
	await closeVoiceSessionOperationOwner({
		...params,
		transcriptFailurePolicy: "retain-and-close"
	}, retainedWriter);
}
/** Close abandoned open calls idle for the fixed six-hour recovery window. */
async function closeStaleClientVoiceSessions(params) {
	const now = params.now ?? Date.now();
	const physicalSource = captureClientVoiceSessionSource(params.agentId);
	mutationDigestDeliveryOwner.retryAgent(params.agentId, {
		config: params.config,
		source: physicalSource
	});
	const stale = await lookupClientVoiceSessions({
		kind: "stale",
		agentId: params.agentId,
		updatedBefore: now - VOICE_SESSION_STALE_AFTER_MS,
		excludeVoiceSessionId: params.excludeVoiceSessionId
	}, physicalSource.options);
	let closed = 0;
	for (const record of stale) try {
		if (await closeVoiceSessionOperationOwner({
			agentId: params.agentId,
			physicalSource,
			sessionKey: record.sessionKey,
			voiceSessionId: record.voiceSessionId,
			config: params.config,
			now,
			staleBefore: now - 216e5,
			transcriptFailurePolicy: "require-success"
		})) closed += 1;
	} catch (error) {
		params.warn?.(`failed to close stale voice session ${record.voiceSessionId}: ${error instanceof Error ? error.message : String(error)}`);
	}
	return closed;
}
const clientVoiceSessionTesting = {
	readRecord: readVoiceSessionRecord,
	digestDeliveryPolicy: CLIENT_VOICE_MUTATION_DIGEST_POLICY,
	digestDeliverySnapshot: () => mutationDigestDeliveryOwner.snapshot(),
	reset() {
		for (const [runId, owner] of voiceSessionByRunId) retireClientVoiceRun(runId, owner, false);
		voiceSessionOperations.clear();
		mutationDigestDeliveryOwner.clear();
		unsubscribeToolEffects?.();
		unsubscribeToolEffects = void 0;
		unsubscribeRunCompletion?.();
		unsubscribeRunCompletion = void 0;
	}
};
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.clientVoiceSessionTestApi")] = clientVoiceSessionTesting;
//#endregion
//#region src/agents/tool-loop-admission.ts
async function evaluateToolLoopCall(call, ctx, stateOverride) {
	if (!ctx.sessionKey || ctx.loopDetection?.enabled !== true) return;
	const toolName = normalizeToolPolicyName(call.toolName || "tool");
	const { getDiagnosticSessionState, logToolLoopAction, detectToolCallLoop } = await loadBeforeToolCallRuntime();
	const sessionState = getDiagnosticSessionState({
		sessionKey: ctx.sessionKey,
		sessionId: ctx.sessionId
	});
	const result = detectToolCallLoop(stateOverride ?? sessionState, toolName, call.params, ctx.runId ? { runId: ctx.runId } : void 0);
	if (!result.stuck) return;
	const diagnostic = {
		sessionKey: ctx.sessionKey,
		sessionId: ctx.sessionId,
		...ctx.agentId ? { agentId: ctx.agentId } : {},
		toolName,
		level: result.level,
		detector: result.detector,
		count: result.count,
		message: result.message,
		pairedToolName: result.pairedToolName
	};
	if (result.level === "critical") {
		beforeToolCallLog.error(`Blocking ${toolName} due to critical loop: ${result.message}`);
		logToolLoopAction({
			...diagnostic,
			action: "block"
		});
		return {
			kind: "critical-tool-loop",
			toolCallId: call.toolCallId ?? "",
			toolName,
			actionKey: hashToolCall(toolName, call.params),
			detector: result.detector,
			count: result.count,
			reason: result.message
		};
	}
	const baseWarningKey = result.warningKey ?? `${result.detector}:${toolName}`;
	if (shouldEmitLoopWarning(sessionState, ctx.runId ? `${ctx.runId}:${baseWarningKey}` : baseWarningKey, result.count)) {
		beforeToolCallLog.warn(`Loop warning for ${toolName}: ${result.message}`);
		logToolLoopAction({
			...diagnostic,
			action: "warn"
		});
		return {
			kind: "tool-loop-warning",
			toolCallId: call.toolCallId ?? "",
			count: result.count
		};
	}
}
async function recordToolLoopCall(call, ctx) {
	if (!ctx.sessionKey || ctx.loopDetection?.enabled !== true) return;
	const { getDiagnosticSessionState, recordToolCall } = await loadBeforeToolCallRuntime();
	recordToolCall(getDiagnosticSessionState({
		sessionKey: ctx.sessionKey,
		sessionId: ctx.sessionId
	}), normalizeToolPolicyName(call.toolName || "tool"), call.params, call.toolCallId, ctx.runId ? { runId: ctx.runId } : void 0);
}
/** Preserve the existing single-call admission path for harnesses without batch control. */
async function admitSingleToolCallLoop(call, ctx) {
	const intervention = await evaluateToolLoopCall(call, ctx);
	if (intervention?.kind !== "critical-tool-loop") await recordToolLoopCall(call, ctx);
	return intervention;
}
/**
* Admit an assistant tool batch atomically. Successful prepared calls reserve
* exact markers here; calls rejected by argument validation never reach the
* wrapper and reserve none. Agent-core commits both in assistant order at the
* final launch boundary, and a released call is dropped. A later veto still
* records only denial evidence.
*/
async function admitToolCallBatch(calls, ctx) {
	if (!ctx.sessionKey || ctx.loopDetection?.enabled !== true) return {};
	const { getDiagnosticSessionState, markDiagnosticArgumentChurnObservation, reconcileToolCallExecutionParams, recordToolCall, recordToolCallOutcome, resolveToolLoopWarningThreshold } = await loadBeforeToolCallRuntime();
	const warningThreshold = resolveToolLoopWarningThreshold();
	const sessionState = getDiagnosticSessionState({
		sessionKey: ctx.sessionKey,
		sessionId: ctx.sessionId
	});
	const projectedState = {
		...sessionState,
		toolCallHistory: [...sessionState.toolCallHistory ?? []]
	};
	const recordLoopVeto = (state, call) => {
		recordToolCall(state, normalizeToolPolicyName(call.toolCall.name || "tool"), call.args, call.toolCall.id, ctx.runId ? { runId: ctx.runId } : void 0);
		const projectedCall = state.toolCallHistory?.at(-1);
		if (projectedCall) projectedCall.outcomeKind = "tool-loop-veto";
	};
	const projectLoopVeto = (call) => {
		const scratchState = {
			...sessionState,
			toolCallHistory: []
		};
		recordLoopVeto(scratchState, call);
		const projectedCall = scratchState.toolCallHistory?.at(-1);
		if (projectedCall) projectedState.toolCallHistory?.push(projectedCall);
	};
	const warnings = [];
	for (const call of calls) {
		const intervention = await evaluateToolLoopCall({
			toolName: normalizeToolPolicyName(call.toolCall.name || "tool"),
			params: call.args,
			toolCallId: call.toolCall.id
		}, ctx, projectedState);
		if (intervention?.kind === "critical-tool-loop") {
			for (const rejectedCall of calls) if (hashToolCall(normalizeToolPolicyName(rejectedCall.toolCall.name || "tool"), rejectedCall.args) === intervention.actionKey) recordLoopVeto(sessionState, rejectedCall);
			return { intervention };
		}
		if (intervention) warnings.push(intervention);
		projectLoopVeto(call);
	}
	for (const call of calls) if (!call.validationFailure) recordBatchAdmittedToolCall(call.toolCall.id, ctx.runId);
	const admittedById = new Map(calls.map((call) => [call.toolCall.id, {
		toolName: normalizeToolPolicyName(call.toolCall.name || "tool"),
		validationFailure: call.validationFailure
	}]));
	const committedIds = /* @__PURE__ */ new Set();
	const commitReadyCall = (readyCall) => {
		const admitted = admittedById.get(readyCall.toolCallId);
		if (!admitted || committedIds.has(readyCall.toolCallId)) return;
		recordToolCall(sessionState, admitted.toolName, readyCall.args, readyCall.toolCallId, ctx.runId ? { runId: ctx.runId } : void 0);
		if (admitted.validationFailure) {
			committedIds.add(readyCall.toolCallId);
			const record = recordToolCallOutcome(sessionState, {
				toolName: admitted.toolName,
				toolParams: readyCall.args,
				toolCallId: readyCall.toolCallId,
				result: admitted.validationFailure,
				runId: ctx.runId
			});
			if (record) record.outcomeKind = "argument-validation";
			return;
		}
		const churn = reconcileToolCallExecutionParams(sessionState, {
			toolName: admitted.toolName,
			toolParams: readyCall.args,
			toolCallId: readyCall.toolCallId,
			runId: ctx.runId,
			warningThreshold
		});
		markDiagnosticArgumentChurnObservation({
			sessionKey: ctx.sessionKey,
			sessionId: ctx.sessionId,
			runId: ctx.runId,
			active: churn.active
		});
		committedIds.add(readyCall.toolCallId);
	};
	return {
		warnings,
		commitReadyCalls(readyCalls) {
			if (readyCalls.length === 1 && readyCalls[0]) {
				commitReadyCall(readyCalls[0]);
				return;
			}
			const readyById = new Map(readyCalls.map((call) => [call.toolCallId, call]));
			for (const call of calls) {
				const readyCall = readyById.get(call.toolCall.id);
				if (readyCall) commitReadyCall(readyCall);
			}
		},
		releaseSkippedCalls(toolCallIds) {
			for (const toolCallId of toolCallIds) admittedById.delete(toolCallId);
			releaseBatchAdmittedToolCalls(toolCallIds, ctx.runId);
		}
	};
}
//#endregion
//#region src/agents/agent-tools.before-tool-call.policy.ts
/**
* Ordered before_tool_call policy chain.
*
* Ordering is behavior: loop admission, owner probes, voice confirmation,
* trusted policies, approvals, normal hooks, and final owner approval must
* remain in this sequence.
*/
const BEFORE_TOOL_CALL_HOOK_FAILURE_REASON = "Tool call blocked because before_tool_call hook failed";
/** Keep receipt routing private without widening observable hook outcomes. */
function markPrivateDecision(outcome, marker) {
	Object.defineProperty(outcome, marker, { value: true });
}
function getBeforeToolCallPolicyDiagnosticState() {
	const policyRegistry = getGlobalHookRunnerRegistry() ?? void 0;
	return {
		hasBeforeToolCallHook: getGlobalHookRunner()?.hasHooks("before_tool_call") === true,
		trustedToolPolicies: getTrustedToolPolicyDiagnosticEntries(policyRegistry)
	};
}
function hasBeforeToolCallPolicy() {
	const state = getBeforeToolCallPolicyDiagnosticState();
	return state.hasBeforeToolCallHook || state.trustedToolPolicies.length > 0;
}
/** Consume voice approval only after tool-owned finalization produces execution params. */
function consumeFinalClientVoiceToolConfirmation(args) {
	if (isCodeModeExecToolKind(args.toolKind)) return { allowed: true };
	const voiceRun = resolveClientVoiceRunBinding(args.ctx?.runId);
	return consumeClientVoiceToolConfirmationPolicy({
		agentId: voiceRun?.agentId,
		voiceSessionId: voiceRun?.voiceSessionId,
		runId: args.ctx?.runId,
		toolCallId: args.toolCallId,
		toolName: normalizeToolPolicyName(args.toolName || "tool"),
		toolParams: args.params,
		...voiceRun ? { isConfirmable: () => isClientVoiceSessionConfirmable(voiceRun) } : {}
	});
}
async function runBeforeToolCallHook(args) {
	const toolName = normalizeToolPolicyName(args.toolName || "tool");
	const params = args.params;
	let loopWarning;
	const withLoopWarning = (outcome) => {
		if (!outcome.blocked && loopWarning) outcome.loopWarning = loopWarning;
		return outcome;
	};
	let releaseArgumentChurnPolicyWait;
	try {
		if (args.ctx?.sessionKey) {
			if (args.ctx.loopDetection?.enabled === true) {
				const { markDiagnosticArgumentChurnObservation } = await loadBeforeToolCallRuntime();
				const policyWaitToken = Symbol("before-tool-call-policy-wait");
				const policyWaitRef = {
					sessionKey: args.ctx.sessionKey,
					sessionId: args.ctx.sessionId,
					runId: args.ctx.runId,
					policyWaitToken
				};
				markDiagnosticArgumentChurnObservation({
					...policyWaitRef,
					policyWait: "enter"
				});
				releaseArgumentChurnPolicyWait = () => markDiagnosticArgumentChurnObservation({
					...policyWaitRef,
					policyWait: "exit"
				});
			}
			if (!(args.toolCallId !== void 0 && consumeBatchAdmittedToolCall(args.toolCallId, args.ctx.runId))) {
				const intervention = await admitSingleToolCallLoop({
					toolName,
					params,
					toolCallId: args.toolCallId
				}, args.ctx);
				if (intervention?.kind === "critical-tool-loop") {
					const outcome = {
						blocked: true,
						kind: "veto",
						deniedReason: "tool-loop",
						reason: intervention.reason,
						params
					};
					markPrivateDecision(outcome, "genericDecision");
					return outcome;
				}
				loopWarning = intervention;
			}
		}
		const hookRunner = getGlobalHookRunner();
		const hasBeforeToolCallHooks = hookRunner?.hasHooks("before_tool_call") === true;
		const policyRegistry = getGlobalHookRunnerRegistry() ?? void 0;
		const shouldRunTrustedPolicies = hasTrustedToolPolicies(policyRegistry);
		const normalizedParams = isPlainObject(params) ? params : {};
		const voiceRun = resolveClientVoiceRunBinding(args.ctx?.runId);
		const voiceConfirmation = isCodeModeExecToolKind(args.toolKind) ? { allowed: true } : checkClientVoiceToolConfirmationPolicy({
			agentId: voiceRun?.agentId,
			voiceSessionId: voiceRun?.voiceSessionId,
			runId: args.ctx?.runId,
			toolCallId: args.toolCallId,
			toolName,
			toolParams: normalizedParams,
			...voiceRun ? { isConfirmable: () => isClientVoiceSessionConfirmable(voiceRun) } : {}
		});
		if (!voiceConfirmation.allowed) return {
			blocked: true,
			kind: "veto",
			deniedReason: "client-voice-confirmation",
			reason: voiceConfirmation.reason,
			params
		};
		if (!shouldRunTrustedPolicies && !hasBeforeToolCallHooks) return withLoopWarning({
			blocked: false,
			params
		});
		const deriveOptions = args.ctx?.cwd || args.ctx?.sandbox ? {
			...args.ctx.cwd ? { cwd: args.ctx.cwd } : {},
			...args.ctx.sandbox ? { sandbox: args.ctx.sandbox } : {},
			...args.signal ? { signal: args.signal } : {}
		} : void 0;
		const derivedToolParams = await deriveToolParams(toolName, normalizedParams, deriveOptions);
		const deriveToolEventParams = async (candidateParams) => {
			const derived = await deriveToolParams(toolName, candidateParams, deriveOptions);
			return derived.derivedPaths ? { derivedPaths: derived.derivedPaths } : {};
		};
		const toolIdentity = {
			...args.toolKind && { toolKind: args.toolKind },
			...args.toolInputKind && { toolInputKind: args.toolInputKind }
		};
		const buildToolContext = (identity) => ({
			toolName,
			...identity,
			...args.ctx?.agentId && { agentId: args.ctx.agentId },
			...args.ctx?.sessionKey && { sessionKey: args.ctx.sessionKey },
			...args.ctx?.sessionId && { sessionId: args.ctx.sessionId },
			...args.ctx?.runId && { runId: args.ctx.runId },
			...args.signal ? { abortSignal: args.signal } : {},
			...args.ctx?.trace && { trace: freezeDiagnosticTraceContext(args.ctx.trace) },
			...args.toolCallId && { toolCallId: args.toolCallId },
			...args.ctx?.channelId && { channelId: args.ctx.channelId },
			...args.ctx?.requester ? { requester: args.ctx.requester } : {}
		});
		const toolContext = buildToolContext(toolIdentity);
		let trustedPolicyParams = normalizedParams;
		const trustedPolicyResult = shouldRunTrustedPolicies ? await runTrustedToolPolicies({
			toolName,
			params: normalizedParams,
			...toolIdentity,
			...args.ctx?.runId && { runId: args.ctx.runId },
			...args.toolCallId && { toolCallId: args.toolCallId },
			...derivedToolParams.derivedPaths ? { derivedPaths: derivedToolParams.derivedPaths } : {}
		}, toolContext, {
			...policyRegistry ? { registry: policyRegistry } : {},
			...args.ctx?.config ? { config: args.ctx.config } : {},
			deriveEvent: deriveToolEventParams,
			normalizeEvent(eventValue) {
				const normalizedEventParams = reconcileCodeModeExecBeforeHookParams({
					owner: { toolKind: eventValue.toolKind },
					originalParams: trustedPolicyParams,
					hookParams: trustedPolicyParams,
					adjustedParams: eventValue.params
				});
				if (!isPlainObject(normalizedEventParams)) return;
				trustedPolicyParams = normalizedEventParams;
				const normalizedEventIdentity = getCodeModeExecBeforeHookMetadataForToolKind({
					toolKind: eventValue.toolKind,
					params: normalizedEventParams
				});
				return {
					params: normalizedEventParams,
					...normalizedEventIdentity ? {
						event: normalizedEventIdentity,
						ctx: normalizedEventIdentity
					} : {}
				};
			}
		}) : void 0;
		if (trustedPolicyResult?.block) {
			const outcome = {
				blocked: true,
				kind: "veto",
				deniedReason: "plugin-before-tool-call",
				reason: trustedPolicyResult.blockReason || "Tool call blocked by trusted plugin policy",
				params
			};
			markPrivateDecision(outcome, "genericDecision");
			return outcome;
		}
		let trustedApprovalParams;
		let trustedApprovalResolution;
		let trustedApprovalGranted = false;
		if (trustedPolicyResult?.requireApproval) {
			const approvalOutcome = await resolveBeforeToolCallApprovalOutcome({
				result: trustedPolicyResult,
				approvalMode: args.approvalMode,
				toolName,
				...args.toolCallId ? { toolCallId: args.toolCallId } : {},
				...args.ctx ? { ctx: args.ctx } : {},
				signal: args.signal,
				baseParams: params
			});
			if (approvalOutcome) {
				if (approvalOutcome.blocked) return approvalOutcome;
				if (approvalOutcome.deferredApproval) return withLoopWarning(approvalOutcome);
				trustedApprovalParams = approvalOutcome.params;
				trustedApprovalResolution = approvalOutcome.approvalResolution;
				trustedApprovalGranted = true;
			}
		}
		const policyAdjustedParams = trustedApprovalParams ?? trustedPolicyResult?.params ?? params;
		const trustedApprovalBoundary = trustedApprovalGranted;
		const policyAdjustedToolIdentity = getCodeModeExecBeforeHookMetadataForToolKind({
			toolKind: args.toolKind,
			params: policyAdjustedParams
		}) ?? toolIdentity;
		const policyAdjustedToolContext = buildToolContext(policyAdjustedToolIdentity);
		const policyAdjustedDerivedToolParams = trustedPolicyResult?.params && isPlainObject(policyAdjustedParams) ? await deriveToolParams(toolName, policyAdjustedParams, deriveOptions) : derivedToolParams;
		const approvedParamsSnapshot = trustedApprovalBoundary && hasBeforeToolCallHooks ? cloneHookIsolationValue("before_tool_call", policyAdjustedParams) : void 0;
		let finalParams = approvedParamsSnapshot ?? policyAdjustedParams;
		let finalApprovalResolution = trustedApprovalResolution;
		let ownerDecisionMarked = false;
		if (hasBeforeToolCallHooks) {
			const hookParamsValue = trustedApprovalBoundary ? cloneHookIsolationValue("before_tool_call", approvedParamsSnapshot) : policyAdjustedParams;
			const hookEventParams = isPlainObject(hookParamsValue) ? hookParamsValue : {};
			const callerIdentity = getGatewayToolCallerIdentity();
			const receipt = callerIdentity?.executionIdentityToken && callerIdentity.receiptAuthority ? {
				token: callerIdentity.executionIdentityToken,
				assertAuthority: callerIdentity.receiptAuthority,
				markOwnerDecision: () => {
					ownerDecisionMarked = true;
				}
			} : void 0;
			const hookResult = await hookRunner.runBeforeToolCall({
				toolName,
				params: hookEventParams,
				...policyAdjustedToolIdentity,
				...args.ctx?.runId && { runId: args.ctx.runId },
				...args.toolCallId && { toolCallId: args.toolCallId },
				...policyAdjustedDerivedToolParams.derivedPaths ? { derivedPaths: policyAdjustedDerivedToolParams.derivedPaths } : {}
			}, policyAdjustedToolContext, receipt);
			if (hookResult?.block) return {
				blocked: true,
				kind: "veto",
				deniedReason: "plugin-before-tool-call",
				reason: hookResult.blockReason || "Tool call blocked by plugin hook",
				params: policyAdjustedParams
			};
			const rawHookCandidateParams = hookResult?.params === void 0 ? isPlainObject(hookParamsValue) ? hookEventParams : approvedParamsSnapshot : mergeParamsWithApprovalOverrides(hookEventParams, hookResult.params);
			const hookCandidateParams = hookResult?.params === void 0 ? rawHookCandidateParams : reconcileCodeModeExecBeforeHookParams({
				owner: { toolKind: args.toolKind },
				originalParams: hookEventParams,
				hookParams: hookEventParams,
				adjustedParams: rawHookCandidateParams
			});
			if (hookResult?.requireApproval) {
				const approvalOutcome = await resolveBeforeToolCallApprovalOutcome({
					result: trustedApprovalBoundary ? {
						...hookResult,
						params: void 0
					} : hookResult,
					approvalMode: args.approvalMode,
					toolName,
					...args.toolCallId ? { toolCallId: args.toolCallId } : {},
					...args.ctx ? { ctx: args.ctx } : {},
					signal: args.signal,
					baseParams: trustedApprovalBoundary ? hookCandidateParams : policyAdjustedParams
				});
				if (approvalOutcome) {
					if (approvalOutcome.blocked) return approvalOutcome;
					if (approvalOutcome.deferredApproval) return withLoopWarning(approvalOutcome);
					finalParams = approvalOutcome.params;
					finalApprovalResolution = approvalOutcome.approvalResolution ?? finalApprovalResolution;
				}
			} else if (trustedApprovalBoundary && !isDeepStrictEqual(approvedParamsSnapshot, hookCandidateParams)) return {
				blocked: true,
				kind: "failure",
				disposition: "blocked",
				deniedReason: "plugin-approval",
				reason: "Tool call parameters changed after trusted approval",
				params: approvedParamsSnapshot
			};
			if (!trustedApprovalBoundary && hookResult?.params) finalParams = reconcileCodeModeExecBeforeHookParams({
				owner: { toolKind: args.toolKind },
				originalParams: policyAdjustedParams,
				hookParams: policyAdjustedParams,
				adjustedParams: mergeParamsWithApprovalOverrides(finalParams, hookResult.params)
			});
		}
		const allowed = {
			blocked: false,
			params: finalParams
		};
		if (ownerDecisionMarked || finalApprovalResolution) markPrivateDecision(allowed, "ownerDecision");
		if (finalApprovalResolution) allowed.approvalResolution = finalApprovalResolution;
		return withLoopWarning(allowed);
	} catch (err) {
		const toolCallId = args.toolCallId ? ` toolCallId=${args.toolCallId}` : "";
		const cause = unwrapErrorCause(err);
		beforeToolCallLog.error(`before_tool_call hook failed: tool=${toolName}${toolCallId} error=${String(cause)}`);
		return {
			blocked: true,
			kind: "failure",
			deniedReason: "plugin-before-tool-call",
			disposition: resolveToolErrorDiagnostic(cause, args.signal).terminalReason,
			reason: BEFORE_TOOL_CALL_HOOK_FAILURE_REASON,
			params
		};
	} finally {
		try {
			releaseArgumentChurnPolicyWait?.();
		} catch (err) {
			beforeToolCallLog.warn(`before_tool_call policy-wait release failed: tool=${toolName} error=${String(err)}`);
		}
	}
}
//#endregion
//#region src/agents/agent-tools.execution-preparer.ts
const INTERNAL_EXECUTION_CONTROL = Symbol("openclawInternalExecutionControl");
function createControl() {
	const ready = createDeferredCore();
	const decision = createDeferredCore();
	return {
		[INTERNAL_EXECUTION_CONTROL]: true,
		ready: ready.promise,
		pause: (args) => {
			ready.resolve(args);
			return decision.promise;
		},
		launch: (start) => decision.resolve({
			launch: true,
			start
		}),
		dispose: () => decision.resolve({ launch: false })
	};
}
function readInternalExecutionControl(value) {
	return value && typeof value === "object" && value[INTERNAL_EXECUTION_CONTROL] === true ? value : void 0;
}
function createInternalExecutionPreparer(startExecution) {
	return async (params) => {
		const control = createControl();
		const execution = startExecution(params, control);
		const settled = await Promise.race([control.ready.then((args) => ({
			kind: "ready",
			args
		})), execution.then((result) => ({
			kind: "result",
			result
		}), (error) => ({
			kind: "error",
			error
		}))]);
		if (settled.kind !== "ready") return {
			kind: "immediate",
			outcome: settled.kind === "result" ? {
				kind: "result",
				result: settled.result,
				isError: false
			} : {
				kind: "error",
				error: settled.error
			},
			dispose() {}
		};
		let disposed = false;
		return {
			kind: "ready",
			args: settled.args,
			execute(start) {
				if (!disposed) control.launch(start);
				return execution;
			},
			dispose() {
				if (!disposed) {
					disposed = true;
					control.dispose();
					execution.catch(() => void 0);
				}
			}
		};
	};
}
//#endregion
//#region src/agents/agent-tools.execution-validation.ts
const executionValidators = resolveGlobalSingleton(Symbol.for("openclaw.toolExecutionValidationContext"), () => new AsyncLocalStorage());
/** Keep per-call validation inside the policy wrapper's final execution boundary. */
async function runWithToolExecutionValidation(toolCallId, validator, execute) {
	return await executionValidators.run({
		toolCallId,
		validate: validator
	}, execute);
}
/** Validate hook-adjusted arguments without leaking a validator into concurrent calls. */
async function validateToolExecutionParams(toolCallId, params) {
	const scopedValidator = executionValidators.getStore();
	if (scopedValidator?.toolCallId === toolCallId) await scopedValidator.validate(params);
}
//#endregion
//#region src/agents/plugin-runtime-refresh.ts
const refreshScope = resolveGlobalSingleton(Symbol.for("openclaw.agentPluginRuntimeRefresh"), () => new AsyncLocalStorage());
/** Captured host control survives plugin callbacks without becoming run authority. */
function captureAgentPluginRuntimeRefresh() {
	const owner = refreshScope.getStore();
	return {
		bindConsumer: (isCurrent) => {
			if (owner?.active) owner.consumer = isCurrent;
		},
		request: () => {
			if (!owner?.active || owner.consumer?.() !== true) return false;
			owner.requested = true;
			return true;
		},
		isRequested: () => owner?.active === true && owner.requested,
		isPending: () => owner?.active === true && owner.requested && owner.holds === 0,
		hold: () => {
			if (!owner?.active) return () => {};
			owner.holds += 1;
			let released = false;
			return () => {
				if (!released) {
					released = true;
					owner.holds -= 1;
				}
			};
		},
		assertActive: () => {
			if (owner && !owner.active) throw new Error("Plugin runtime changed. Continue with the refreshed tool catalog.");
		},
		assertCurrent: () => {
			if (owner && (!owner.active || owner.requested)) throw new Error("Plugin runtime changed. Continue with the refreshed tool catalog; do not repeat completed actions.");
		}
	};
}
/** One visible run owns refresh requests across all of its prepared runtime generations. */
function createAgentPluginRuntimeRefresh() {
	let owner;
	const close = () => {
		if (owner) {
			owner.active = false;
			owner.consumer = void 0;
			owner.requested = false;
		}
	};
	return {
		run: (run) => {
			close();
			owner = {
				active: true,
				holds: 0,
				requested: false
			};
			return refreshScope.run(owner, run);
		},
		close
	};
}
//#endregion
//#region src/agents/agent-tools.before-tool-call.wrapper.ts
/**
* Wrapped before_tool_call execution boundary.
* Owns tool preparation/finalization, adjusted-param replay state, terminal
* results, diagnostics around execution, and wrapper metadata.
*/
const MAX_TRACKED_ADJUSTED_PARAMS = 1024;
const INTERNAL_DISPOSED_RESULT = {
	content: [],
	details: {
		status: "skipped",
		deniedReason: "internal-dispose"
	}
};
/** Run tool-owned preparation while retaining the exact prepared object. */
async function prepareBeforeToolCallExecutionParams(params) {
	const prepare = params.tool.prepareBeforeToolCallParams;
	return prepare ? await prepare(params.params, {
		...params.toolCallId ? { toolCallId: params.toolCallId } : {},
		...params.ctx ? { hookContext: params.ctx } : {},
		...params.signal ? { signal: params.signal } : {}
	}) : params.params;
}
/** Reconcile hook rewrites and restore tool-owned state before execution. */
function finalizeBeforeToolCallExecutionParams(params) {
	const reconciledParams = reconcileCodeModeExecBeforeHookParams({
		owner: { tool: params.tool },
		originalParams: params.preparedParams,
		hookParams: params.hookParams,
		adjustedParams: params.adjustedParams
	});
	const finalize = params.tool.finalizeBeforeToolCallParams;
	if (!finalize) return reconciledParams;
	if (params.finalizerMode === "adapter") return finalize(reconciledParams, params.preparedParams);
	return finalize.call(params.tool, reconciledParams, params.preparedParams) ?? reconciledParams;
}
var BeforeToolCallFailureError = class extends Error {
	constructor(message, disposition, cause) {
		super(message, cause === void 0 ? void 0 : { cause });
		this.disposition = disposition;
		this.name = "BeforeToolCallFailureError";
	}
};
function tagBeforeToolCallFailure(error, signal, stage) {
	try {
		if (error instanceof BeforeToolCallFailureError) return error;
	} catch {}
	const message = formatToolExecutionErrorMessage(error, "before_tool_call failed");
	const disposition = resolveToolErrorDiagnostic(error, signal).terminalReason;
	const tagged = new BeforeToolCallFailureError(message, disposition, error);
	if (stage === "tool_preparation" && isTrustedToolExecutionPreflightError(error)) markToolExecutionNotStarted(tagged);
	return tagged;
}
function getBeforeToolCallFailureDisposition(error) {
	try {
		return error instanceof BeforeToolCallFailureError ? error.disposition : void 0;
	} catch {
		return;
	}
}
/** Remember hook-adjusted params for later adapter-side execution. */
function recordAdjustedParamsForToolCall(toolCallId, params, runId) {
	if (!toolCallId) return;
	let snapshot;
	try {
		snapshot = structuredClone(params);
	} catch {
		return;
	}
	adjustedParamsByToolCallId.set(buildAdjustedParamsKey({
		runId,
		toolCallId
	}), snapshot);
	pruneMapToMaxSize(adjustedParamsByToolCallId, MAX_TRACKED_ADJUSTED_PARAMS);
}
/** Record that one concrete core-owned tool call may use structured replay classification. */
function recordStructuredReplayTrustForToolCall(toolCallId, tool, runId) {
	if (!toolCallId || getPluginToolMeta(tool) || getChannelAgentToolMeta(tool)) return;
	recordStructuredReplaySafeToolCall(toolCallId, runId);
	while (structuredReplaySafeToolCallIds.size > MAX_TRACKED_ADJUSTED_PARAMS) {
		const oldest = structuredReplaySafeToolCallIds.values().next().value;
		if (!oldest) break;
		structuredReplaySafeToolCallIds.delete(oldest);
	}
}
const preExecutionBlockedToolResults = /* @__PURE__ */ new WeakSet();
function isPreExecutionBlockedToolResult(result) {
	return result !== null && typeof result === "object" && preExecutionBlockedToolResults.has(result);
}
function buildBlockedToolResult(params) {
	recordPreExecutionBlockedToolCall(params.toolCallId, params.runId);
	const result = {
		content: [{
			type: "text",
			text: params.reason
		}],
		details: {
			status: "blocked",
			deniedReason: params.deniedReason ?? "plugin-before-tool-call",
			reason: params.reason
		}
	};
	preExecutionBlockedToolResults.add(result);
	return result;
}
function wrapToolWithBeforeToolCallHook(tool, ctx, options = {}) {
	const execute = tool.execute;
	const refresh = captureAgentPluginRuntimeRefresh();
	const assertAgentPluginRuntimeCurrent = isCodeModeControlTool(tool) && tool.name === "wait" ? refresh.assertActive : refresh.assertCurrent;
	if (!execute) return tool;
	const toolName = tool.name || "tool";
	const toolOwnerPluginId = getPluginToolMeta(tool)?.pluginId;
	const admitExecution = captureAgentToolExecutionBudget();
	const diagnosticIdentity = resolveToolDiagnosticIdentity(tool);
	const hookOptions = {
		...options,
		emitDiagnostics: options.emitDiagnostics !== false
	};
	const toolContentPolicy = resolveDiagnosticModelContentCapturePolicy(ctx?.config);
	const wrappedTool = {
		...tool,
		execute: async (toolCallId, params, signal, onUpdate, ...executionArgs) => {
			assertAgentPluginRuntimeCurrent();
			const prepareControl = readInternalExecutionControl(executionArgs.at(-1));
			if (prepareControl) executionArgs.pop();
			const toolCallOrdinal = ctx?.allocateToolOutcomeOrdinal?.(toolCallId);
			const preExecutionStartedAt = Date.now();
			const normalizedToolName = normalizeToolPolicyName(toolName || "tool");
			const trace = hookOptions.emitDiagnostics && ctx?.trace ? freezeDiagnosticTraceContext(createChildDiagnosticTraceContext(ctx.trace)) : void 0;
			const buildEventBase = (toolParams) => ({
				...ctx?.runId && { runId: ctx.runId },
				...ctx?.sessionKey && { sessionKey: ctx.sessionKey },
				...ctx?.sessionId && { sessionId: ctx.sessionId },
				...ctx?.agentId && { agentId: ctx.agentId },
				...trace && { trace },
				toolName: normalizedToolName,
				...diagnosticIdentity,
				...toolCallId && { toolCallId },
				paramsSummary: summarizeToolParams(toolParams),
				mutatingAction: buildToolMutationState(normalizedToolName, toolParams).mutatingAction
			});
			const recordPreExecutionError = (error, toolParams, errorCategory) => {
				recordPreExecutionBlockedToolCall(toolCallId, ctx?.runId);
				if (!hookOptions.emitDiagnostics) return;
				emitTrustedDiagnosticEvent({
					type: "tool.execution.error",
					...buildEventBase(toolParams),
					durationMs: Date.now() - preExecutionStartedAt,
					...resolveToolErrorDiagnostic(error, signal, errorCategory)
				});
			};
			const recordPreExecutionDisposition = (toolParams, disposition, errorCategory, deniedReason) => {
				recordPreExecutionBlockedToolCall(toolCallId, ctx?.runId);
				if (!hookOptions.emitDiagnostics) return;
				const eventBase = buildEventBase(toolParams);
				if (disposition === "blocked") {
					const reason = deniedReason ?? "plugin-before-tool-call";
					emitTrustedDiagnosticEvent({
						type: "tool.execution.blocked",
						...eventBase,
						deniedReason: reason,
						reason
					});
					return;
				}
				emitTrustedDiagnosticEvent({
					type: "tool.execution.error",
					...eventBase,
					durationMs: Date.now() - preExecutionStartedAt,
					errorCategory: disposition === "cancelled" ? "aborted" : errorCategory,
					terminalReason: disposition
				});
			};
			const blockToolCall = async (blockedCall) => {
				if (blockedCall.genericDecision) recordGenericToolActionDecision(tool, toolCallId, "denied");
				const eventBase = buildEventBase(blockedCall.toolParams);
				if (hookOptions.emitDiagnostics) {
					emitTrustedDiagnosticEvent({
						type: "tool.execution.blocked",
						...eventBase,
						reason: blockedCall.reason,
						deniedReason: blockedCall.deniedReason
					});
					emitToolBlockedSecurityEvent({
						ctx,
						deniedReason: blockedCall.deniedReason,
						toolIdentity: diagnosticIdentity,
						toolName: normalizedToolName,
						trace,
						paramsSummary: eventBase.paramsSummary
					});
				}
				const blockedResult = buildBlockedToolResult({
					reason: blockedCall.reason,
					deniedReason: blockedCall.deniedReason,
					toolCallId,
					runId: ctx?.runId
				});
				await recordLoopOutcome({
					ctx,
					toolName: normalizedToolName,
					toolParams: blockedCall.toolParams,
					toolCallId,
					result: blockedResult,
					toolCallOrdinal
				});
				return blockedResult;
			};
			let preparedParams;
			try {
				preparedParams = await prepareBeforeToolCallExecutionParams({
					tool,
					params,
					toolCallId,
					ctx,
					signal
				});
			} catch (error) {
				recordPreExecutionError(error, params, "tool_preparation");
				throw tagBeforeToolCallFailure(error, signal, "tool_preparation");
			}
			const hookParams = normalizeCodeModeExecBeforeHookParams({
				tool,
				params: preparedParams
			});
			const hookMetadata = getCodeModeExecBeforeHookMetadata({
				tool,
				params: preparedParams
			});
			let outcome;
			try {
				outcome = await runBeforeToolCallHook({
					toolName,
					params: hookParams,
					...hookMetadata,
					toolCallId,
					ctx: ctx ? {
						...ctx,
						toolOwnerPluginId
					} : toolOwnerPluginId ? { toolOwnerPluginId } : void 0,
					signal,
					approvalMode: hookOptions.approvalMode
				});
			} catch (error) {
				recordPreExecutionError(error, hookParams, "before_tool_call");
				throw tagBeforeToolCallFailure(error, signal, "before_tool_call");
			}
			if (outcome.blocked) {
				if (outcome.kind !== "veto") {
					recordPreExecutionDisposition(outcome.params ?? hookParams, outcome.disposition, outcome.deniedReason === "plugin-approval" ? "plugin_approval" : "before_tool_call", outcome.deniedReason);
					throw new BeforeToolCallFailureError(outcome.reason, outcome.disposition);
				}
				return await blockToolCall({
					reason: outcome.reason,
					deniedReason: outcome.deniedReason ?? "plugin-before-tool-call",
					toolParams: outcome.params ?? hookParams,
					genericDecision: outcome.genericDecision
				});
			}
			let executeParams;
			try {
				signal?.throwIfAborted();
				executeParams = finalizeBeforeToolCallExecutionParams({
					tool,
					preparedParams,
					hookParams,
					adjustedParams: outcome.params,
					finalizerMode: "wrapped"
				});
				await validateToolExecutionParams(toolCallId, executeParams);
				await reconcileLoopCallExecutionParams({
					ctx,
					toolName: normalizedToolName,
					toolParams: executeParams,
					toolCallId
				});
			} catch (error) {
				recordPreExecutionError(error, outcome.params ?? hookParams, "tool_preparation");
				throw tagBeforeToolCallFailure(error, signal, "tool_preparation");
			}
			let onImplementationStart;
			if (prepareControl) {
				const decision = await prepareControl.pause(executeParams);
				if (!decision.launch) {
					recordGenericToolActionDecision(tool, toolCallId, "suppressed");
					return INTERNAL_DISPOSED_RESULT;
				}
				onImplementationStart = decision.start;
			}
			const voiceConfirmation = consumeFinalClientVoiceToolConfirmation({
				toolCallId,
				toolName,
				toolKind: hookMetadata?.toolKind,
				params: executeParams,
				ctx
			});
			if (!voiceConfirmation.allowed) return await blockToolCall({
				reason: voiceConfirmation.reason,
				deniedReason: "client-voice-confirmation",
				toolParams: executeParams
			});
			signal?.throwIfAborted();
			assertAgentPluginRuntimeCurrent();
			runAgentToolSourceExecutionGuard(tool);
			admitExecution?.();
			onImplementationStart?.();
			recordAdjustedParamsForToolCall(toolCallId, executeParams, ctx?.runId);
			const eventBase = buildEventBase(executeParams);
			const recordProgress = captureDiagnosticToolProgress(eventBase);
			recordToolExecutionStarted(toolCallId, ctx?.runId);
			const liveness = startToolExecutionLiveness(eventBase, hookOptions.emitDiagnostics, signal);
			const startedAt = Date.now();
			try {
				let result;
				try {
					const args = [
						toolCallId,
						executeParams,
						signal,
						onUpdate,
						...executionArgs
					];
					const invoke = () => liveness.run(() => execute(...args));
					result = outcome.ownerDecision ? await invoke() : await runWithGenericToolActionDecision(tool, toolCallId, invoke);
				} catch (error) {
					throw hookOptions.protectNetworkErrors !== false && tool.resultContentSource === "network" && getBeforeToolCallFailureDisposition(error) === void 0 ? protectNetworkToolExecutionError(error, "Tool execution failed.", signal) : error;
				}
				const durationMs = Date.now() - startedAt;
				const preparedTerminalPresentation = prepareToolTerminalPresentation({
					ctx,
					tool,
					toolParams: executeParams,
					toolCallId,
					toolCallOrdinal
				});
				await recordLoopOutcome({
					ctx,
					toolName: normalizedToolName,
					toolParams: executeParams,
					toolCallId,
					result,
					resultContentSource: tool.resultContentSource,
					toolCallOrdinal,
					terminalPresentation: preparedTerminalPresentation?.project?.(result)
				});
				if (!signal?.aborted) rememberPendingTerminalPresentation(preparedTerminalPresentation, ctx?.runId, toolCallId);
				const terminalDiagnostic = resolveToolResultTerminalDiagnostic(result, durationMs);
				if (terminalDiagnostic.type === "tool.execution.completed" && !signal?.aborted) recordProgress?.();
				const skillMatch = findSkillUsageMatch({
					toolName: normalizedToolName,
					toolParams: executeParams,
					ctx
				});
				if (skillMatch && terminalDiagnostic.type === "tool.execution.completed") recordSkillUsed({
					ctx,
					match: skillMatch,
					toolName: normalizedToolName,
					toolCallId
				});
				if (hookOptions.emitDiagnostics) emitTrustedDiagnosticEventWithPrivateData({
					...eventBase,
					...terminalDiagnostic
				}, buildToolContentPrivateData(toolContentPolicy, {
					input: executeParams,
					output: result,
					includeOutput: true
				}));
				return outcome.loopWarning ? appendToolLoopWarning(result, outcome.loopWarning) : result;
			} catch (err) {
				if (hookOptions.emitDiagnostics) emitTrustedDiagnosticEventWithPrivateData({
					type: "tool.execution.error",
					...eventBase,
					durationMs: Date.now() - startedAt,
					...resolveToolErrorDiagnostic(err, signal)
				}, buildToolContentPrivateData(toolContentPolicy, {
					input: executeParams,
					includeOutput: false
				}));
				await recordLoopOutcome({
					ctx,
					toolName: normalizedToolName,
					toolParams: executeParams,
					toolCallId,
					error: err,
					resultContentSource: isTrustedToolExecutionPreflightError(err) || signal?.aborted && err === signal.reason ? void 0 : tool.resultContentSource,
					toolCallOrdinal
				});
				throw err;
			} finally {
				liveness.close();
			}
		}
	};
	const executeWithHooks = wrappedTool.execute;
	const prepareExecution = createInternalExecutionPreparer(async (params, control) => {
		recordToolExecutionTracked(params.toolCallId, ctx?.runId);
		try {
			return await Reflect.apply(executeWithHooks, wrappedTool, [
				params.toolCallId,
				params.args,
				params.signal,
				params.onUpdate,
				...params.executionArgs ?? [],
				control
			]);
		} finally {
			clearTrackedToolExecution(params.toolCallId, ctx?.runId);
		}
	});
	attachInternalToolExecutionPreparer(wrappedTool, prepareExecution);
	wrappedTool.execute = async (toolCallId, params, signal, onUpdate, ...executionArgs) => {
		const prepared = await prepareExecution({
			toolCallId,
			args: params,
			signal,
			onUpdate,
			executionArgs
		});
		try {
			if (prepared.kind === "immediate") {
				if (prepared.outcome.kind === "error") throw prepared.outcome.error;
				return prepared.outcome.result;
			}
			return await prepared.execute();
		} finally {
			prepared.dispose();
		}
	};
	copyBeforeToolCallWrapperMetadata(tool, wrappedTool);
	bindBeforeToolCallMetadata(wrappedTool, {
		options: hookOptions,
		sourceTool: tool,
		hookContext: ctx
	});
	return wrappedTool;
}
/** Rebuild a before_tool_call wrapper while preserving the original source tool. */
function rewrapToolWithBeforeToolCallHook(tool, ctx, options = {}) {
	const preservedContext = getBeforeToolCallHookContext(tool);
	const sourceTool = getBeforeToolCallSourceTool(tool) ?? tool;
	const wrapperOptions = {
		...getBeforeToolCallDiagnosticOptions(tool),
		...options
	};
	if (sourceTool === tool) return wrapToolWithBeforeToolCallHook(tool, ctx ?? preservedContext, wrapperOptions);
	const rewrapSource = {
		...tool,
		execute: sourceTool.execute
	};
	clearBeforeToolCallWrappedMarker(rewrapSource);
	copyBeforeToolCallWrapperMetadata(tool, rewrapSource);
	copyAgentToolSourceExecutionGuard(sourceTool, rewrapSource);
	copyAgentToolSourceExecutionGuard(tool, rewrapSource);
	let rebuilt = wrapToolWithBeforeToolCallHook(rewrapSource, ctx ?? preservedContext, wrapperOptions);
	for (const wrapExecution of getBeforeToolCallExecutionWrappers(tool)) rebuilt = wrapExecution(rebuilt);
	return rebuilt;
}
function recordPreExecutionBlockedToolCall(toolCallId, runId) {
	if (!toolCallId) return;
	preExecutionBlockedToolCallIds.add(buildAdjustedParamsKey({
		runId,
		toolCallId
	}));
	while (preExecutionBlockedToolCallIds.size > MAX_TRACKED_ADJUSTED_PARAMS) {
		const oldest = preExecutionBlockedToolCallIds.values().next().value;
		if (!oldest) break;
		preExecutionBlockedToolCallIds.delete(oldest);
	}
}
//#endregion
export { clearBatchAdmittedToolCallsForRun as $, resolveOpenClientVoiceSessionId as A, captureClientVoiceConfirmationUtterance as B, closeRelayVoiceSessionRecord as C, resolveClientVoiceRunBinding as D, registerClientVoiceConsultRun as E, captureClientVoiceSessionSourceOptions as F, cancelDeferredPluginToolApproval as G, observeClientVoiceConfirmationRun as H, createClientVoiceSessionSource as I, clearEmbeddedPluginApprovalBroker as J, requestDeferredPluginToolApproval as K, authorizeClientVoiceConfirmation as L, createOrResumeClientVoiceSession as M, ensureClientVoiceAgentSessionEntry as N, assertClientVoiceSessionOpen as O, captureClientVoiceSessionSource as P, consumeRunSkillUsage as Q, authorizeObservedClientVoiceConfirmation as R, closeClientVoiceSession as S, flushClientVoiceSessionWrites as T, readClientVoiceConfirmationReadiness as U, invalidateClientVoiceConfirmationUtterance as V, getTrustedToolPolicyMatcherScope as W, finalizeToolTerminalPresentation as X, setEmbeddedPluginApprovalBroker as Y, recordSkillUsed as Z, hasBeforeToolCallPolicy as _, prepareBeforeToolCallExecutionParams as a, peekPreExecutionBlockedToolCall as at, appendClientVoiceTranscript as b, rewrapToolWithBeforeToolCallHook as c, createAgentPluginRuntimeRefresh as d, consumeAdjustedParamsForToolCall as et, runWithToolExecutionValidation as f, getBeforeToolCallPolicyDiagnosticState as g, consumeFinalClientVoiceToolConfirmation as h, isPreExecutionBlockedToolResult as i, peekAdjustedParamsForToolCall as it, captureClientVoiceSessionWriter as j, resolveClientVoiceAgentSessionId as k, wrapToolWithBeforeToolCallHook as l, readInternalExecutionControl as m, finalizeBeforeToolCallExecutionParams as n, consumeStructuredReplaySafeToolCall as nt, recordAdjustedParamsForToolCall as o, PluginApprovalResolutions as ot, createInternalExecutionPreparer as p, EmbeddedPluginApprovalBroker as q, getBeforeToolCallFailureDisposition as r, consumeTrackedToolExecutionStarted as rt, recordStructuredReplayTrustForToolCall as s, buildBlockedToolResult as t, consumePreExecutionBlockedToolCall as tt, captureAgentPluginRuntimeRefresh as u, runBeforeToolCallHook as v, closeStaleClientVoiceSessions as w, appendRelayVoiceTranscript as x, admitToolCallBatch as y, bindAuthorizedClientVoiceConfirmation as z };