UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,492 lines 61.8 kB
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { a as buildAgentMainSessionKey, b as toAgentStoreSessionKey } from "./session-key-BnWWjqNc.js";
import { d as isSecretRef, h as normalizeSecretInputString } from "./types.secrets-kC0nOetj.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import "./sessions-BdNAJTEP.js";
import { t as SessionManager } from "./session-manager-DO0bDxav.js";
import { i as resolveCliBackendConfig } from "./cli-backends-Bs_a4MW0.js";
import { t as SYSTEM_AGENT_ID } from "./agent-id-D7twbNRG.js";
import { o as prepareSystemAgentRunAdmission } from "./admitted-run-context-CHY5SdVF.js";
import { t as isSystemAgentNavigationOperation } from "./operation-types-LNFFS9G0.js";
import { a as isPersistentSystemAgentOperation, c as isInvalidConfigSetOperation, d as redactSystemAgentConfigPath, l as isSystemAgentSensitiveConfigValue, n as SYSTEM_AGENT_OPERATOR_NAVIGATION_HANDOFF, o as parseSystemAgentOperation, r as describeSystemAgentPersistentOperation } from "./operations-parse-DIiMdt6S.js";
import { t as executeSystemAgentOperation } from "./operations-BAC9TFg6.js";
import { i as resolvePendingOperatorProposal, n as hashSystemAgentOperation, r as resolveOperatorApprovalDecision, t as classifySystemAgentApprovalText } from "./operator-approval-BAOcpwRx.js";
import { n as isSystemAgentInferenceUnavailableError, t as SystemAgentInferenceUnavailableError } from "./inference-error-Dyv9IgLa.js";
import { i as loadSystemAgentOverview, t as formatSystemAgentOnboardingWelcome } from "./overview-BoFF75kz.js";
import { a as resolveSystemAgentVerifiedInferenceRoute, i as resolveSystemAgentExpectedAgentHarnessRuntimeArtifact } from "./verified-inference-D8I0G1w6.js";
import { t as approvalQuestion } from "./dialogue-DF7jf9Tr.js";
import { i as extractAgentRunText } from "./agent-run-result-JlQhspoW.js";
import { a as normalizeCliModel } from "./helpers-D0iW2O3m.js";
import { n as sanitizeWizardStepForClient, r as wizardStepAwaitsInput, t as WizardSession } from "./session-7QOyBCVr.js";
import { a as SYSTEM_AGENT_SYSTEM_PROMPT } from "./assistant-prompts-Cs9_Vt57.js";
import path from "node:path";
import fs from "node:fs/promises";
import { randomUUID } from "node:crypto";
//#region src/system-agent/agent-turn.ts
/**
* OpenClaw is a real agent: same loop, session transcript, and tool pipeline
* as regular agents — restricted to the single ring-zero `openclaw` tool.
* Embedded runtimes enforce that restriction with toolsAllow. CLI harnesses
* must explicitly support per-run native-tool selection, then receive the tool
* over a dedicated stdio MCP server that replaces the normal bundle surface.
* Turns share one persistent session so the conversation has genuine
* multi-turn memory. Inference setup must succeed before this runner is entered.
*/
const AGENT_TURN_TIMEOUT_MS = 12e4;
const SYSTEM_AGENT_TOOL_NAME = "openclaw";
function createSystemAgentSession(verifiedInference) {
	if (!verifiedInference) throw new SystemAgentInferenceUnavailableError("agent-turn");
	return {
		sessionId: `openclaw-${randomUUID()}`,
		verifiedInference,
		proposalRef: {}
	};
}
async function ensureSystemAgentDirs() {
	const base = path.join(resolveStateDir(), "openclaw");
	const workspaceDir = path.join(base, "workspace");
	await fs.mkdir(workspaceDir, { recursive: true });
	return { workspaceDir };
}
async function cleanupSystemAgentSession(session) {
	delete session.cliSession;
	delete session.sessionManager;
}
function clearSystemAgentCliSession(session) {
	delete session.cliSession;
}
function clearFailedSystemAgentSessionState(session) {
	session.proposalRef.current = void 0;
	session.proposalRef.operation = void 0;
	clearSystemAgentCliSession(session);
}
function throwSystemAgentInferenceUnavailable(params) {
	clearFailedSystemAgentSessionState(params.session);
	throw new SystemAgentInferenceUnavailableError("agent-turn", params.failures);
}
function cliRouteKey(route, backend) {
	return JSON.stringify({
		provider: route.provider,
		backendId: backend?.id ?? route.provider,
		modelLabel: route.modelLabel,
		configuredModel: route.model,
		model: backend ? normalizeCliModel(route.model, backend.config) : route.model,
		authProfileId: route.authProfileId ?? "",
		agentDir: path.resolve(route.agentDir),
		backend: backend ? {
			pluginId: backend.pluginId,
			modelProvider: backend.modelProvider,
			config: backend.config,
			bundleMcp: backend.bundleMcp,
			bundleMcpMode: backend.bundleMcpMode,
			authEpochMode: backend.authEpochMode,
			nativeToolMode: backend.nativeToolMode,
			toolAvailabilityEnforcement: backend.toolAvailabilityEnforcement,
			sideQuestionToolMode: backend.sideQuestionToolMode
		} : null
	});
}
function resolveSystemAgentCliBackend(route) {
	const backend = resolveCliBackendConfig(route.provider, route.runConfig, { agentId: SYSTEM_AGENT_ID });
	if (!backend) return null;
	const { liveSession: _liveSession, ...config } = backend.config;
	return {
		...backend,
		config
	};
}
function resolveSystemAgentCliToolAvailability(backend) {
	if (backend?.nativeToolMode === "none") return;
	if (backend?.nativeToolMode === "selectable" && (backend.toolAvailabilityEnforcement === "execution-args" && backend.resolveExecutionArgs || backend.toolAvailabilityEnforcement === "prepare-execution" && backend.prepareExecution)) return {
		native: [],
		openClaw: [SYSTEM_AGENT_TOOL_NAME]
	};
	const backendId = backend?.id ?? "unknown";
	throw new Error(`CLI backend ${backendId} cannot enforce OpenClaw's exact tool availability`);
}
/**
* CLI harnesses run the openclaw tool in a stdio MCP subprocess, so the
* in-process proposalRef/directiveRef cannot be shared with the host. Mirror
* the tool's transitions from the harness tool events instead: a denial
* registers the exact-operation hash, a mismatch voids it, an executed
* mutation consumes it, and directive actions replay the interactive handoff —
* same lifecycle as system-agent-tool.ts enforces.
*/
async function mirrorSystemAgentToolStateFromEvents(params) {
	const [{ onAgentEventForRun }, { extractToolResultText }, { resolveSystemAgentProposalTransition, resolveSystemAgentDirectiveTransition }] = await Promise.all([
		import("./agent-events-BmgMD6UA.js"),
		import("./embedded-agent-tool-results-CuhcX8mD.js"),
		import("./system-agent-tool-CgJexaQa.js")
	]);
	return onAgentEventForRun(params.runId, (evt) => {
		if (evt.runId !== params.runId || evt.stream !== "tool" || evt.data.phase !== "result") return;
		const name = typeof evt.data.name === "string" ? evt.data.name : "";
		if (name !== "openclaw" && !name.endsWith("__openclaw")) return;
		const args = typeof evt.data.args === "object" && evt.data.args !== null ? evt.data.args : {};
		const resultText = extractToolResultText(evt.data.result) ?? "";
		const transition = resolveSystemAgentProposalTransition({
			args,
			resultText
		});
		if (transition) {
			params.proposalRef.current = transition.proposal;
			params.proposalRef.operation = transition.operation;
		}
		const directive = resolveSystemAgentDirectiveTransition({
			args,
			resultText
		});
		if (directive && params.directiveRef.current?.kind !== "approved-operation") params.directiveRef.current = directive;
	});
}
/**
* Run one OpenClaw turn through the embedded agent loop. Route, runner, and
* output failures are typed so callers may try another inference path without
* mistaking the failure for deterministic setup authority.
*/
async function runSystemAgentTurnWithDeps(params, deps = {}) {
	const binding = params.session.verifiedInference;
	if (!binding) return throwSystemAgentInferenceUnavailable({ session: params.session });
	let plan;
	try {
		plan = await resolveSystemAgentVerifiedInferenceRoute(binding, deps);
	} catch (error) {
		return throwSystemAgentInferenceUnavailable({
			session: params.session,
			failures: [error]
		});
	}
	if (!plan) return throwSystemAgentInferenceUnavailable({ session: params.session });
	let expectedAgentHarnessRuntimeArtifact;
	try {
		expectedAgentHarnessRuntimeArtifact = resolveSystemAgentExpectedAgentHarnessRuntimeArtifact(binding);
	} catch (error) {
		return throwSystemAgentInferenceUnavailable({
			session: params.session,
			failures: [error]
		});
	}
	let workspaceDir;
	try {
		({workspaceDir} = await ensureSystemAgentDirs());
	} catch (error) {
		return throwSystemAgentInferenceUnavailable({
			session: params.session,
			failures: [error]
		});
	}
	const runId = `openclaw-turn-${randomUUID()}`;
	const sessionManager = params.session.sessionManager ?? SessionManager.inMemory(workspaceDir);
	params.session.sessionManager = sessionManager;
	const preparedRunAdmission = prepareSystemAgentRunAdmission(plan.runConfig, runId, SYSTEM_AGENT_ID, "system-agent.turn");
	const policySessionKey = buildAgentMainSessionKey({ agentId: SYSTEM_AGENT_ID });
	const shared = {
		sessionId: params.session.sessionId,
		sessionKey: toAgentStoreSessionKey({
			agentId: SYSTEM_AGENT_ID,
			requestKey: params.session.sessionId
		}),
		agentId: SYSTEM_AGENT_ID,
		trigger: "manual",
		sessionFile: `in-memory:${params.session.sessionId}`,
		sessionManager,
		workspaceDir,
		config: plan.runConfig,
		prompt: params.input,
		timeoutMs: AGENT_TURN_TIMEOUT_MS,
		thinkLevel: "off",
		runId,
		messageChannel: "openclaw",
		messageProvider: "openclaw",
		disableTrajectory: true
	};
	const directiveRef = {};
	const systemAgentTool = {
		agentId: plan.agentId,
		surface: params.surface,
		approvalArmed: params.approvalArmed,
		...params.operatorApprovalOnly ? { operatorApprovalOnly: true } : {},
		proposalRef: params.session.proposalRef,
		directiveRef
	};
	try {
		let result;
		if (plan.runner === "cli") {
			const backend = resolveSystemAgentCliBackend(plan);
			const cliToolAvailability = resolveSystemAgentCliToolAvailability(backend);
			const routeKey = cliRouteKey(plan, backend);
			const previousBinding = params.session.cliSession?.routeKey === routeKey ? params.session.cliSession.binding : void 0;
			if (!previousBinding) clearSystemAgentCliSession(params.session);
			const runCli = deps.runCliAgent ?? (await import("./cli-runner-msdgHSKR.js")).runCliAgent;
			const stopToolStateMirror = await mirrorSystemAgentToolStateFromEvents({
				runId,
				proposalRef: params.session.proposalRef,
				directiveRef
			});
			try {
				result = await runCli({
					...shared,
					preparedRunAdmission,
					provider: plan.provider,
					model: plan.model,
					agentDir: plan.agentDir,
					...plan.authProfileId ? { authProfileId: plan.authProfileId } : {},
					extraSystemPrompt: SYSTEM_AGENT_SYSTEM_PROMPT,
					extraSystemPromptStatic: SYSTEM_AGENT_SYSTEM_PROMPT,
					systemAgentTool,
					...cliToolAvailability ? { cliToolAvailability } : {},
					...previousBinding ? { cliSessionBinding: previousBinding } : {},
					runtimePolicySessionKey: policySessionKey,
					disableCliLiveSession: true,
					cleanupCliLiveSessionOnRunEnd: true
				});
			} finally {
				stopToolStateMirror();
			}
			const agentMeta = result.meta?.agentMeta;
			if (agentMeta?.clearCliSessionBinding || !agentMeta?.cliSessionBinding?.sessionId) clearSystemAgentCliSession(params.session);
			else if (agentMeta?.cliSessionBinding?.sessionId) params.session.cliSession = {
				routeKey,
				binding: agentMeta.cliSessionBinding
			};
		} else {
			clearSystemAgentCliSession(params.session);
			result = await (deps.runEmbeddedAgent ?? (await import("./embedded-agent-CUhbhIMN.js")).runEmbeddedAgent)({
				...shared,
				preparedRunAdmission,
				extraSystemPrompt: SYSTEM_AGENT_SYSTEM_PROMPT,
				toolsAllow: ["openclaw"],
				systemAgentTool,
				disableMessageTool: true,
				provider: plan.provider,
				model: plan.model,
				agentDir: plan.agentDir,
				agentHarnessRuntimeOverride: plan.agentHarnessRuntimeOverride,
				sandboxSessionKey: policySessionKey,
				...expectedAgentHarnessRuntimeArtifact ? { expectedAgentHarnessRuntimeArtifact } : {},
				...plan.authProfileId ? {
					authProfileId: plan.authProfileId,
					authProfileIdSource: "user"
				} : {}
			});
		}
		if (params.session.verifiedInference !== binding) throw new SystemAgentInferenceUnavailableError("agent-turn");
		if (!await resolveSystemAgentVerifiedInferenceRoute(binding, deps)) throw new SystemAgentInferenceUnavailableError("agent-turn");
		const text = extractAgentRunText(result)?.trim();
		if (!text) throw new SystemAgentInferenceUnavailableError("agent-turn");
		return {
			text,
			modelLabel: plan.modelLabel,
			...directiveRef.current ? { directive: directiveRef.current } : {}
		};
	} catch (error) {
		const failures = error instanceof SystemAgentInferenceUnavailableError ? [...error.failures] : [error];
		return throwSystemAgentInferenceUnavailable({
			session: params.session,
			failures
		});
	} finally {
		preparedRunAdmission.close();
	}
}
const runSystemAgentTurn = (params) => runSystemAgentTurnWithDeps(params);
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.systemAgentTurnTestApi")] = { runSystemAgentTurnWithDeps };
//#endregion
//#region src/system-agent/chat-turn-router.ts
const log$1 = createSubsystemLogger("system-agent/chat-engine");
function createCaptureRuntime() {
	const lines = [];
	return {
		log: (...args) => lines.push(args.join(" ")),
		error: (...args) => lines.push(args.join(" ")),
		exit: (code) => {
			throw new Error(`OpenClaw operation exited with code ${String(code)}`);
		},
		read: () => lines.join("\n").trim()
	};
}
function formatOperationError(error) {
	return `That did not go through: ${error instanceof Error ? error.message : String(error)}`;
}
function redactSensitiveCommandText(text) {
	const operation = parseSystemAgentOperation(text);
	if (isInvalidConfigSetOperation(operation)) return "config set <invalid path> <redacted secret>";
	if (operation.kind === "config-set") {
		const displayPath = redactSystemAgentConfigPath(operation.path);
		if (displayPath !== operation.path || isSystemAgentSensitiveConfigValue(operation.path, operation.value)) return `config set ${displayPath} <redacted secret>`;
	}
	if (operation.kind === "config-set-ref") return `config set-ref ${redactSystemAgentConfigPath(operation.path)} <redacted reference>`;
	return text;
}
function formatPendingOperationForAssistant(operation) {
	const description = describeSystemAgentPersistentOperation(operation);
	return operation.kind === "setup" ? `${description}. Exact setup JSON: ${JSON.stringify(operation)}. Keep the verified model unless the user explicitly asks to leave OpenClaw and reconfigure inference.` : description;
}
function preservePendingSetupModel(pending, operation) {
	if (pending?.kind !== "setup" || operation.kind !== "setup") return operation;
	const pendingModel = pending.model?.trim();
	const requestedModel = operation.model?.trim();
	const withAgentName = {
		...operation,
		...operation.agentName ? {} : pending.agentName ? { agentName: pending.agentName } : {}
	};
	if (requestedModel && requestedModel !== pendingModel) return withAgentName;
	return {
		...withAgentName,
		...requestedModel ? {} : pendingModel ? { model: pendingModel } : {}
	};
}
var ChatTurnRouter = class {
	constructor(options, dependencies, agentSession, wizard, callbacks) {
		this.options = options;
		this.dependencies = dependencies;
		this.agentSession = agentSession;
		this.wizard = wizard;
		this.callbacks = callbacks;
		this.pending = null;
		this.awaitingSetupChannel = false;
	}
	propose(operation) {
		this.clearPendingProposals();
		this.pending = this.recordCreateAgentRequester(operation);
		return describeSystemAgentPersistentOperation(this.pending);
	}
	getPendingOperatorProposal() {
		const proposal = resolvePendingOperatorProposal(this.pending, this.agentSession.proposalRef);
		if (!proposal) return null;
		const operation = this.recordCreateAgentRequester(proposal.operation);
		return operation === proposal.operation ? proposal : {
			operation,
			hash: hashSystemAgentOperation(operation)
		};
	}
	async resolveOperatorApproval(decision, proposalHash, beforePersistentApply) {
		return await resolveOperatorApprovalDecision({
			decision,
			proposalHash,
			getProposal: () => this.getPendingOperatorProposal(),
			clear: () => this.clearPendingProposals(),
			apply: async (operation) => {
				this.proposalResolution = "approved";
				return await this.applyApprovedPersistentOperation(operation, beforePersistentApply);
			},
			denied: () => {
				this.proposalResolution = "declined";
				return {
					text: "Denied. No change.",
					action: "none",
					applied: false
				};
			}
		});
	}
	clearForInferenceLoss() {
		this.clearPendingProposals();
		this.proposalResolution = void 0;
		this.awaitingSetupChannel = false;
		this.lastSensitiveChannel = void 0;
	}
	async answerWizard(result) {
		const answer = await result;
		return {
			...answer,
			text: await this.finishWizardText(answer)
		};
	}
	async resolveTurn(text, options) {
		if (this.wizard.active) {
			const result = await this.wizard.resolveReply(text);
			return {
				text: await this.finishWizardText(result),
				action: "none"
			};
		}
		const trimmed = text.trim();
		if (!trimmed) return {
			text: "Tiny claw tap: tell me what you want — setup, repair, channels, anything config.",
			action: "none"
		};
		if (/^(quit|exit)$/i.test(trimmed)) return {
			text: "OpenClaw retracts into shell. Bye.",
			action: "exit"
		};
		if (this.awaitingSetupChannel) {
			if (/^(cancel|abort|stop)$/i.test(trimmed)) {
				this.awaitingSetupChannel = false;
				return {
					text: "Channel wizard handoff cancelled.",
					action: "none"
				};
			}
			if (!/^[a-z0-9_-]+$/i.test(trimmed)) return {
				text: "Reply with one channel id, such as `slack` or `telegram`, or say `cancel`.",
				action: "none"
			};
			this.awaitingSetupChannel = false;
			return await this.runOperation({
				kind: "open-setup",
				target: "channels",
				channel: trimmed.toLowerCase()
			}, void 0);
		}
		if (this.options.operatorApprovalOnly && this.getPendingOperatorProposal()) return {
			text: "Approval pending. Human must decide in OpenClaw UI.",
			action: "none"
		};
		const typed = parseSystemAgentOperation(text);
		if (isInvalidConfigSetOperation(typed)) return {
			text: typed.message,
			action: "none"
		};
		if (typed.kind === "config-set" || typed.kind === "config-set-ref" || typed.kind === "config-get" || typed.kind === "config-schema") return await this.runOperation(typed, void 0);
		if (isSystemAgentNavigationOperation(typed)) return await this.runOperation(typed, void 0);
		const intent = this.options.operatorApprovalOnly ? "other" : await this.classifyApprovalIntent(text);
		if (this.pending) {
			if (intent === "approve") {
				await this.callbacks.requireVerifiedInference();
				return await this.applyPendingProposal(this.pending);
			}
			if (intent === "decline") {
				this.clearPendingProposals();
				this.proposalResolution = "declined";
				return {
					text: "Skipped. No barnacles on config today.",
					action: "none"
				};
			}
		}
		if (intent === "decline") {
			this.agentSession.proposalRef.current = void 0;
			this.agentSession.proposalRef.operation = void 0;
		}
		return await this.resolveAssistantTurn(text, this.options.operatorApprovalOnly ? false : intent === "approve", options?.uiContext);
	}
	async classifyApprovalIntent(text) {
		if (!(this.pending !== null || this.agentSession.proposalRef.current !== void 0)) return "other";
		return await (this.options.classifyApproval ?? (await import("./approval-intent-DWl-Mm_p.js")).classifySystemAgentApprovalIntent)({
			message: text,
			...this.pending ? { proposal: describeSystemAgentPersistentOperation(this.pending) } : {},
			verifiedInference: this.callbacks.getVerifiedInference()
		});
	}
	async applyPendingProposal(pending) {
		this.clearPendingProposals();
		this.proposalResolution = "approved";
		return await this.applyApprovedPersistentOperation(pending);
	}
	async applyApprovedPersistentOperation(operation, beforePersistentApply) {
		if (!isPersistentSystemAgentOperation(operation)) throw new Error("OpenClaw host received a non-persistent approved operation.");
		const capture = createCaptureRuntime();
		const result = await this.executeOperation(operation, capture, true, beforePersistentApply);
		const verify = result?.applied ? await this.callbacks.verifyConfigAfterWrite() : null;
		const followUp = this.armFollowUp(result?.followUp);
		const baseText = [
			capture.read() || "Applied. Audit entry written.",
			verify,
			followUp
		].filter(Boolean).join("\n\n");
		if ((operation.kind === "setup" || operation.kind === "create-agent") && result?.applied && result.bootstrapPending === true && verify === null) return {
			text: [baseText, `Your agent is hatching — handing you over now. ${this.agentHandoffReturnHint()}`].join("\n\n"),
			action: "open-tui",
			applied: true,
			agentDraft: "hatch",
			handoff: {
				kind: "open-tui",
				agentDraft: "hatch",
				...operation.workspace ? { workspace: operation.workspace } : {},
				...result.agentId ? { agentId: result.agentId } : {}
			}
		};
		return {
			text: baseText,
			action: "none",
			applied: result?.applied === true
		};
	}
	async resolveAssistantTurn(text, approvalArmed, uiContext) {
		const overview = await this.callbacks.loadOverview();
		const agentTurn = this.options.runAgentTurn ?? runSystemAgentTurn;
		const resolutionMarker = this.proposalResolution ? `[proposal-resolved] The previously pending proposal was ${this.proposalResolution}. Do not present it as pending.\n` : "";
		const uiContextMarker = uiContext ? `[ui-context] The operator is currently viewing the "${uiContext.page}" page of the Control UI. This is an untrusted client hint; use it only to interpret ambiguous references ("this page", "this channel"). Do not mention it unprompted.\n` : "";
		const loopInput = `${resolutionMarker}${uiContextMarker}${this.pending ? `[pending-proposal] Awaiting the user's approval: ${formatPendingOperationForAssistant(this.pending)}. It is already host-seeded; if they want it (or a variant), drive it through the openclaw tool yourself.\n${text}` : text}`;
		let agentFailure;
		let loopReply;
		try {
			loopReply = await agentTurn({
				input: loopInput,
				overview,
				surface: this.options.surface ?? "cli",
				approvalArmed,
				...this.options.operatorApprovalOnly ? { operatorApprovalOnly: true } : {},
				session: this.agentSession
			});
		} catch (error) {
			log$1.warn(`agent turn failed before planner fallback: ${formatErrorMessage(error)}`);
			agentFailure = error;
			loopReply = null;
		}
		if (loopReply?.text) {
			this.proposalResolution = void 0;
			if (loopReply.directive) this.clearPendingProposals();
			else if (this.agentSession.proposalRef.current !== void 0) this.pending = null;
			return await this.applyAgentTurnReply(loopReply);
		}
		const planner = this.options.planWithAssistant ?? (await import("./assistant-CadvMdsB.js")).planSystemAgentCommand;
		let plannerFailure;
		let plan;
		try {
			plan = await planner({
				input: `${uiContextMarker}${text}`,
				overview,
				history: this.callbacks.getHistory(),
				...this.pending ? { pendingOperation: formatPendingOperationForAssistant(this.pending) } : {},
				verifiedInference: this.callbacks.getVerifiedInference()
			});
			if (plan) await this.callbacks.requireVerifiedInference();
		} catch (error) {
			plannerFailure = error;
			plan = null;
		}
		if (!plan) throw new SystemAgentInferenceUnavailableError("conversation", [agentFailure, plannerFailure].filter((failure) => failure !== void 0));
		const replyText = plan.reply ?? "";
		if (!plan.command) {
			if (!replyText.trim()) throw new SystemAgentInferenceUnavailableError("planner", [agentFailure]);
			return {
				text: replyText,
				action: "none"
			};
		}
		const operation = preservePendingSetupModel(this.pending, parseSystemAgentOperation(plan.command));
		if (operation.kind === "none") {
			if (!replyText.trim()) throw new SystemAgentInferenceUnavailableError("planner", [agentFailure]);
			return {
				text: replyText,
				action: "none"
			};
		}
		const provenance = `(${plan.modelLabel ?? "model"} → \`${plan.command}\`)`;
		const executed = await this.runOperation(operation, provenance);
		return {
			...executed,
			text: [replyText, executed.text].filter(Boolean).join("\n\n")
		};
	}
	async applyAgentTurnReply(loopReply) {
		await this.callbacks.requireVerifiedInference();
		const directive = loopReply.directive;
		if (!directive) return {
			text: loopReply.text,
			action: "none"
		};
		const reply = directive.kind === "approved-operation" ? await this.applyApprovedPersistentOperation(directive.operation) : await this.runOperation(directive, void 0);
		return {
			...reply,
			text: directive.kind === "open-tui" && reply.action === "open-tui" ? loopReply.text : [loopReply.text, reply.text].filter(Boolean).join("\n\n")
		};
	}
	async runOperation(operation, provenance) {
		const recordedOperation = this.recordCreateAgentRequester(operation);
		await this.callbacks.requireVerifiedInference();
		if (this.options.operatorApprovalOnly && isSystemAgentNavigationOperation(recordedOperation)) return {
			text: SYSTEM_AGENT_OPERATOR_NAVIGATION_HANDOFF,
			action: "none"
		};
		if (recordedOperation.kind === "open-tui") {
			this.clearPendingProposals();
			return {
				text: `Opening a chat with your agent. ${this.agentHandoffReturnHint()}`,
				action: "open-tui",
				handoff: recordedOperation
			};
		}
		if (recordedOperation.kind === "model-accounts") {
			this.clearPendingProposals();
			return {
				text: this.options.surface === "gateway" ? "Opening Settings → Profile → Connected accounts. Check the Gateway, person, and Personal scope, then sign in or select a saved account. Nothing has changed yet; never paste credentials into this conversation." : "Run `openclaw models accounts list` to see your personal accounts, or `openclaw models accounts login <provider>` for protected sign-in. Check the Gateway and person shown before signing in. You can also use Settings → Profile → Connected accounts in the Control UI. Nothing has changed; never paste credentials into this conversation.",
				action: "none",
				...this.options.surface === "gateway" ? { handoff: recordedOperation } : {}
			};
		}
		if (recordedOperation.kind === "open-setup") {
			this.clearPendingProposals();
			if (this.options.surface === "gateway") return {
				text: "Open Settings to change your model or connect a channel. To change providers from a shell, run `openclaw onboard` on the machine running OpenClaw.",
				action: "none"
			};
			if (![
				"channels",
				"search",
				"gateway"
			].includes(recordedOperation.target)) return {
				text: "Setup can replace the inference route powering this session. Exit OpenClaw and run `openclaw onboard`; it saves only a route that passes a live test. Then start OpenClaw again.",
				action: "none"
			};
			let handoff = recordedOperation;
			if (handoff.target === "channels" && !handoff.channel) {
				if (!this.lastSensitiveChannel) {
					this.awaitingSetupChannel = true;
					return {
						text: "Which channel should I open in the masked terminal wizard?",
						action: "none"
					};
				}
				handoff = {
					...handoff,
					channel: this.lastSensitiveChannel
				};
				this.lastSensitiveChannel = void 0;
			}
			this.awaitingSetupChannel = false;
			return {
				text: `Opening the ${handoff.target === "channels" ? `${handoff.channel ?? "channel"} setup` : handoff.target === "search" ? "web search setup" : "Gateway setup"} wizard.`,
				action: "open-setup",
				handoff
			};
		}
		if (recordedOperation.kind === "channel-setup") return await this.startWizard(this.wizard.startChannel(recordedOperation.channel));
		if (recordedOperation.kind === "skills-setup") return await this.startWizard(this.wizard.startSkills());
		if (recordedOperation.kind === "search-setup") return await this.startWizard(this.wizard.startSearch());
		if (recordedOperation.kind === "gateway-config-setup") return await this.startWizard(this.wizard.startGateway());
		if (recordedOperation.kind === "memory-import") return await this.startWizard(this.wizard.startMemoryImport());
		if (recordedOperation.kind === "model-setup") return this.startModelSetup();
		const capture = createCaptureRuntime();
		if (isPersistentSystemAgentOperation(recordedOperation) && !this.options.yes) {
			this.clearPendingProposals();
			await executeSystemAgentOperation(recordedOperation, capture, {
				approved: false,
				operatorApprovalOnly: this.options.operatorApprovalOnly,
				deps: this.commandDeps()
			});
			this.pending = recordedOperation;
			return {
				text: [
					provenance,
					capture.read(),
					this.options.operatorApprovalOnly ? void 0 : approvalQuestion(recordedOperation)
				].filter(Boolean).join("\n\n"),
				action: "none"
			};
		}
		const result = await this.executeOperation(recordedOperation, capture, this.options.yes === true || !isPersistentSystemAgentOperation(recordedOperation));
		const verify = result?.applied ? await this.callbacks.verifyConfigAfterWrite() : null;
		const followUp = this.armFollowUp(result?.followUp);
		const reply = [
			provenance,
			capture.read(),
			verify,
			followUp
		].filter(Boolean).join("\n\n");
		if (result?.exitsInteractive === true) return {
			text: reply,
			action: "exit"
		};
		return {
			text: reply,
			action: "none"
		};
	}
	async executeOperation(operation, capture, approved, beforePersistentApply) {
		try {
			const execute = this.dependencies.executeOperation ?? executeSystemAgentOperation;
			if (approved) await this.callbacks.requirePersistentApplyInference(capture);
			return await execute(operation, capture, {
				approved,
				deps: this.commandDeps(),
				beforePersistentApply,
				onVerifiedInferenceChanged: this.callbacks.rebindVerifiedInference
			});
		} catch (error) {
			if (isSystemAgentInferenceUnavailableError(error)) throw error;
			capture.error(formatOperationError(error));
			return;
		}
	}
	async startWizard(result) {
		this.lastSensitiveChannel = void 0;
		this.clearPendingProposals();
		const resolved = await result;
		if (resolved.sensitiveChannel) this.lastSensitiveChannel = resolved.sensitiveChannel;
		return {
			text: await this.finishWizardText(resolved),
			action: "none"
		};
	}
	async finishWizardText(result) {
		const verify = result.configWritten ? await this.callbacks.verifyConfigAfterWrite() : null;
		return [result.text, verify].filter(Boolean).join("\n");
	}
	startModelSetup() {
		this.clearPendingProposals();
		return {
			text: ["Changing provider credentials would replace the inference route powering this session.", "Stop the OpenClaw host through whatever started it. Run `openclaw onboard` on the machine running OpenClaw: it stages credentials, live-tests the new route, and saves only a passing setup. Then restart the host and return to OpenClaw."].join("\n"),
			action: "none"
		};
	}
	commandDeps() {
		return {
			...this.options.deps,
			loadOverview: this.callbacks.loadOverview,
			...this.options.surface ? { setupSurface: this.options.surface } : {}
		};
	}
	agentHandoffReturnHint() {
		return this.options.surface === "gateway" ? "You can return through Settings → Ask OpenClaw." : "Use /openclaw to come back.";
	}
	clearPendingProposals() {
		this.pending = null;
		this.agentSession.proposalRef.current = void 0;
		this.agentSession.proposalRef.operation = void 0;
	}
	recordCreateAgentRequester(operation) {
		const requesterAgentId = this.options.requesterAgentId?.trim();
		if (operation.kind !== "create-agent" || !requesterAgentId || operation.requesterAgentId === requesterAgentId) return operation;
		return {
			...operation,
			requesterAgentId
		};
	}
	armFollowUp(operation) {
		return operation?.kind === "model-setup" ? ["No usable inference route is configured, so OpenClaw cannot continue.", "Run `openclaw onboard` on the machine running OpenClaw; it saves only a route that passes a live test."].join("\n") : null;
	}
};
//#endregion
//#region src/system-agent/chat-wizard-host.ts
const log = createSubsystemLogger("system-agent/chat-wizard-host");
const WIZARD_CANCEL_HINT = "Say `cancel` to stop this setup.";
let hostedRuntimePromise;
function loadHostedRuntime() {
	return hostedRuntimePromise ??= import("./hosted-setup.runtime.js");
}
function formatWizardOptions(step) {
	return (step.options ?? []).map((option, index) => {
		const hint = option.hint ? ` — ${option.hint}` : "";
		return `${index + 1}. ${option.label}${hint}`;
	});
}
function wizardStepChatQuestion(step) {
	if (!step) return;
	if (step.type === "confirm") {
		const yesRecommended = step.initialValue !== false;
		return {
			id: step.id,
			header: step.title ?? "Confirm",
			question: step.message ?? "Continue?",
			options: [{
				label: "Yes",
				reply: "yes",
				...yesRecommended ? { recommended: true } : {}
			}, {
				label: "No",
				reply: "no",
				...!yesRecommended ? { recommended: true } : {}
			}]
		};
	}
	if (step.type !== "select") return;
	const options = step.options ?? [];
	if (options.length < 2 || options.length > 4) return;
	return {
		id: step.id,
		header: step.title ?? "Choose one",
		question: step.message ?? "Choose one.",
		options: options.map((option) => {
			const mapped = { label: option.label };
			if (option.hint) mapped.description = option.hint;
			if (step.initialValue !== void 0 && option.value === step.initialValue) mapped.recommended = true;
			return mapped;
		})
	};
}
function renderWizardStep(step) {
	const lines = [];
	if (step.title) lines.push(`**${step.title}**`);
	if (step.message) lines.push(step.message);
	switch (step.type) {
		case "select":
			lines.push(...formatWizardOptions(step), "Reply with a number.");
			break;
		case "multiselect":
			lines.push(...formatWizardOptions(step), "Reply with numbers (e.g. 1,3) or `none`.");
			break;
		case "confirm":
			lines.push("Reply yes or no.");
			break;
		case "text":
			if (step.placeholder) lines.push(`(e.g. ${step.placeholder})`);
			lines.push("Type your answer.");
	}
	return lines.filter(Boolean).join("\n");
}
function parseWizardAnswer(step, text) {
	const trimmed = text.trim();
	if (step.type === "confirm") {
		const intent = classifySystemAgentApprovalText(trimmed);
		return intent === "approve" ? { value: true } : intent === "decline" ? { value: false } : null;
	}
	if (step.type === "text") return { value: trimmed };
	const options = step.options ?? [];
	const matchOption = (token) => {
		if (/^\d+$/.test(token)) {
			const index = Number(token);
			if (Number.isSafeInteger(index) && index >= 1 && index <= options.length) return options[index - 1];
		}
		const lower = token.toLowerCase();
		return options.find((option) => option.label.toLowerCase() === lower || typeof option.value === "string" && option.value.toLowerCase() === lower);
	};
	if (step.type === "select") {
		const option = matchOption(trimmed);
		return option ? { value: option.value } : null;
	}
	if (step.type === "multiselect") {
		if (/^none$/i.test(trimmed)) return { value: [] };
		const values = [];
		for (const token of trimmed.split(/[\s,]+/).filter(Boolean)) {
			const option = matchOption(token);
			if (!option) return null;
			values.push(option.value);
		}
		return { value: values };
	}
	return { value: step.type === "action" ? true : void 0 };
}
function formatStructuredWizardAnswerForHistory(step, value) {
	if (step.sensitive === true) return "<redacted secret>";
	if (step.type === "text") return [
		"string",
		"number",
		"boolean",
		"bigint"
	].includes(typeof value) ? String(value) : "<wizard answer>";
	if (step.type === "confirm") return typeof value === "boolean" ? value ? "Yes" : "No" : "<wizard answer>";
	if (step.type === "select") return step.options?.find((option) => Object.is(option.value, value))?.label ?? "<wizard answer>";
	if (step.type === "multiselect") {
		if (!Array.isArray(value)) return "<wizard answer>";
		if (value.length === 0) return "None";
		const labels = value.map((entry) => step.options?.find((option) => Object.is(option.value, entry))?.label);
		return labels.every((label) => label !== void 0) ? labels.join(", ") : "<wizard answer>";
	}
	return "Continue";
}
var SystemAgentWizardAnswerError = class extends Error {};
var ChatWizardHost = class {
	constructor(options) {
		this.options = options;
		this.bridge = null;
	}
	get active() {
		return this.bridge !== null;
	}
	get sensitiveInputPending() {
		return this.bridge?.step?.sensitive === true;
	}
	dispose() {
		this.bridge?.session.cancel();
		this.bridge = null;
	}
	decorateReply(reply) {
		const step = this.bridge?.step ?? null;
		const completedReply = reply.text && step && wizardStepAwaitsInput(step) ? {
			...reply,
			text: `${reply.text}\n${WIZARD_CANCEL_HINT}`
		} : reply;
		const question = wizardStepChatQuestion(step);
		const clientStep = step ? sanitizeWizardStepForClient(step) : null;
		return {
			...completedReply,
			...step?.sensitive === true ? { sensitive: true } : {},
			...this.bridge ? { wizardInputPending: true } : {},
			...question ? { question } : {},
			...clientStep ? { step: clientStep } : {}
		};
	}
	async answer(answer) {
		const bridge = this.bridge;
		const step = bridge?.step;
		if (!bridge || !step) throw new SystemAgentWizardAnswerError("No hosted wizard is awaiting an answer.");
		if (answer.stepId !== step.id) throw new SystemAgentWizardAnswerError("The hosted wizard answer targets a stale step.");
		const validationError = await bridge.session.answer(step.id, answer.value);
		return {
			...validationError ? {
				text: [validationError, renderWizardStep(step)].join("\n\n"),
				configWritten: false
			} : await this.pump(),
			userHistoryText: formatStructuredWizardAnswerForHistory(step, answer.value)
		};
	}
	async cancel(cancel) {
		const bridge = this.bridge;
		const step = bridge?.step;
		if (!bridge || !step) throw new SystemAgentWizardAnswerError("No hosted wizard is awaiting cancellation.");
		if (cancel.stepId !== step.id) throw new SystemAgentWizardAnswerError("The hosted wizard cancel targets a stale step.");
		if (!bridge.session.cancel()) throw new SystemAgentWizardAnswerError("The hosted wizard cannot be cancelled right now.");
		return {
			...await this.pump(),
			userHistoryText: "Cancel"
		};
	}
	async resolveReply(text) {
		const bridge = this.bridge;
		if (!bridge) return {
			text: "",
			configWritten: false
		};
		if (/^(cancel|abort|stop|quit|exit)$/i.test(text.trim())) {
			bridge.session.cancel();
			return await this.pump();
		}
		const step = bridge.step;
		if (!step) return await this.pump();
		const answer = parseWizardAnswer(step, text);
		if (!answer) return {
			text: ["I could not match that answer.", renderWizardStep(step)].join("\n"),
			configWritten: false
		};
		const validationError = await bridge.session.answer(step.id, answer.value);
		return validationError ? {
			text: [validationError, renderWizardStep(step)].join("\n\n"),
			configWritten: false
		} : await this.pump();
	}
	async startChannel(channel) {
		const run = this.options.dependencies?.runChannelSetupWizard;
		return await this.start({
			kind: "channel",
			label: channel,
			autoSelectChannel: channel,
			run: async (prompter) => run ? await run(channel, prompter, this.options.beforePersistentApply) : await (await loadHostedRuntime()).runHostedChannelSetup(channel, prompter, this.options.beforePersistentApply)
		});
	}
	async startSkills() {
		const run = this.options.dependencies?.runSkillsSetupWizard;
		return await this.start({
			kind: "skills",
			label: "skills",
			run: async (prompter) => run ? await run(prompter, this.options.beforePersistentApply) : await (await loadHostedRuntime()).runHostedSkillsSetup(prompter, this.options.beforePersistentApply)
		});
	}
	async startSearch() {
		const run = this.options.dependencies?.runSearchSetupWizard;
		return await this.start({
			kind: "search",
			label: "web search",
			run: async (prompter) => run ? await run(prompter, this.options.beforePersistentApply) : await (await loadHostedRuntime()).runHostedSearchSetup(prompter, this.options.beforePersistentApply)
		});
	}
	async startGateway() {
		const run = this.options.dependencies?.runGatewaySetupWizard;
		const result = await this.start({
			kind: "gateway",
			label: "gateway",
			run: async (prompter) => run ? await run(prompter, this.options.beforePersistentApply) : await (await loadHostedRuntime()).runHostedGatewaySetup(prompter, this.options.beforePersistentApply)
		});
		if (this.options.surface !== "gateway" || !this.bridge) return result;
		const warning = ["Before we start: changing the Gateway port, bind address, or auth credential requires a Gateway restart to apply.", "That restart may disconnect this chat, and you may need to sign in to the Control UI again with the new address or credential."].join(" ");
		return {
			...result,
			text: [warning, result.text].filter(Boolean).join("\n\n")
		};
	}
	async startMemoryImport() {
		const run = this.options.dependencies?.runMemoryImportWizard;
		const providers = [];
		return await this.start({
			kind: "memory-import",
			label: "memory import",
			memoryImportProviders: providers,
			run: async (prompter) => run ? await run(prompter, this.options.beforePersistentApply, (value) => providers.push(value)) : await (await loadHostedRuntime()).runHostedMemoryImport(prompter, this.options.beforePersistentApply, (value) => providers.push(value))
		});
	}
	async start(params) {
		const completion = {
			status: "applied",
			...params.memoryImportProviders ? { memoryImportProviders: params.memoryImportProviders } : {}
		};
		const session = new WizardSession(async (prompter) => {
			const result = await params.run(prompter);
			if (typeof result === "string") completion.status = result;
			else if (result) completion.memoryImport = result;
		});
		this.bridge = {
			session,
			step: null,
			kind: params.kind,
			label: params.label,
			completion,
			...params.autoSelectChannel ? { autoSelectChannel: params.autoSelectChannel } : {}
		};
		return await this.pump();
	}
	tryAutoSelect(step) {
		const bridge = this.bridge;
		const channel = bridge?.autoSelectChannel;
		if (!bridge || !channel || step.type !== "select" && step.type !== "multiselect") return null;
		const match = (step.options ?? []).find((option) => typeof option.value === "string" && option.value.toLowerCase() === channel);
		if (!match) return null;
		bridge.autoSelectChannel = void 0;
		return { value: step.type === "multiselect" ? [match.value] : match.value };
	}
	async pump() {
		const bridge = this.bridge;
		if (!bridge) return {
			text: "",
			configWritten: false
		};
		const result = await bridge.session.next();
		if (result.done) {
			this.bridge = null;
			const label = bridge.label;
			if (result.status === "done") {
				if (bridge.kind === "memory-import") try {
					return {
						text: await (await loadHostedRuntime()).renderMemoryImport(bridge.completion.memoryImport, this.options.dependencies?.appendAuditEntry),
						configWritten: false
					};
				} catch (error) {
					log.warn(`memory import completed without audit entry: ${formatErrorMessage(error)}`);
					return {
						text: await (await loadHostedRuntime()).renderMemoryImport(bridge.completion.memoryImport, async () => ""),
						configWritten: false
					};
				}
				if (bridge.completion.status === "kept-current") return {
					text: `${label[0]?.toUpperCase() ?? "S"}${label.slice(1)} setup kept the current configuration. Nothing was changed.`,
					configWritten: false
				};
				await this.auditSetup(bridge);
				return {
					text: (bridge.kind === "channel" ? [`Done — ${label} is configured.`, "Say `restart gateway` to apply channel changes, or `channels` to review."] : bridge.kind === "skills" ? ["Done — skills dependency setup is complete."] : bridge.kind === "search" ? ["Done — web search setup is complete.", "Restart the Gateway if the selected provider or plugin changed."] : ["Done — gateway settings saved.", "Restart the Gateway to apply them (`restart gateway`)."]).join("\n"),
					configWritten: true
				};
			}
			if (bridge.kind === "memory-import") try {
				await (await loadHostedRuntime()).auditMemoryImport(bridge.completion.memoryImportProviders ?? [], this.options.dependencies?.appendAuditEntry);
			} catch (error) {
				log.warn(`memory import completed without audit entry: ${formatErrorMessage(error)}`);
			}
			if (result.status === "cancelled") return {
				text: `${label[0]?.toUpperCase() ?? "S"}${label.slice(1)} setup cancelled. Nothing was changed beyond completed steps.`,
				configWritten: false
			};
			return {
				text: `${label[0]?.toUpperCase() ?? "S"}${label.slice(1)} setup stopped: ${result.error ?? "unknown error"}`,
				configWritten: false
			};
		}
		bridge.step = result.step ?? null;
		if (bridge.step) {
			const auto = this.tryAutoSelect(bridge.step);
			if (auto) {
				const step = bridge.step;
				bridge.step = null;
				await bridge.session.answer(step.id, auto.value);
				return await this.pump();
			}
			if (this.options.surface === "cli" && bridge.step.sensitive === true) {
				bridge.session.cancel();
				this.bridge = null;
				return {
					text: ["Sensitive input is not accepted in the OpenClaw chat because terminal input is visible.", bridge.kind === "channel" ? `Say \`open channel wizard\` and I'll hand you to the masked terminal wizard for ${bridge.label}, or run \`openclaw channels add --channel ${bridge.label}\` yourself later.` : bridge.kind === "gateway" ? "Say `open gateway wizard` and I'll hand you to the masked terminal wizard, or run `openclaw configure --section gateway` yourself later." : "Say `open search wizard` and I'll hand you to the masked terminal wizard, or run `openclaw configure --section web` yourself later."].join("\n"),
					configWritten: false,
					...bridge.kind === "channel" ? { sensitiveChannel: bridge.label } : {}
				};
			}
			if (bridge.step.type === "note" || bridge.step.type === "progress") {
				const step = bridge.step;
				bridge.step = null;
				await bridge.session.answer(step.id, void 0);
				const next = await this.pump();
				return {
					...next,
					text: [renderWizardStep(step), next.text].filter(Boolean).join("\n\n")
				};
			}
			if (bridge.step.type === "action" && bridge.step.executor !== "client") {
				const step = bridge.step;
				bridge.step = null;
				await bridge.session.answer(step.id, true);
				return await this.pump();
			}
		}
		return {
			text: bridge.step ? renderWizardStep(bridge.step) : "",
			configWritten: false
		};
	}
	async auditSetup(bridge) {
		const entry = bridge.kind === "channel" ? {
			operation: "channels.setup",
			summary: `Configured channel ${bridge.label} via chat setup`,
			details: { channel: bridge.label }
		} : bridge.kind === "skills" ? {
			operation: "skills.setup",
			summary: "Completed skills dependency setup via chat",
			details: { capability: "skills" }
		} : bridge.kind === "search" ? {
			operation: "search.setup",
			summary: "Configured web search via chat setup",
			details: { capability: "web-search" }
		} : {
			operation: "gateway.setup",
			summary: "Configured Gateway via chat setup",
			details: { capability: "gateway" }
		};
		try {
			await (this.options.dependencies?.appendAuditEntry ?? (await import("./system-agent/audit.js")).appendSystemAgentAuditEntry)(entry);
		} catch (error) {
			log.warn(`${bridge.kind} setup completed without audit entry: ${formatErrorMessage(error)}`);
		}
	}
};
//#endregion
//#region src/system-agent/post-write-verification.ts
function unavailable(reason) {
	return [`⚠ The write was applied, but post-write verification is unavailable: ${reason}.`, "Run `openclaw doctor --fix` on the machine running OpenClaw, then verify the configuration before continuing."].join("\n");
}
async function verifyConfigAfterSystemAgentWrite(resolveRepair) {
	let issuesText;
	try {
		const { readConfigFileSnapshot } = await import("./config/config.js");
		const snapshot = await readConfigFileSnapshot();
		if (!snapshot.exists) return unavailable("openclaw.json was not found");
		if (snapshot.valid) return null;
		const issues = (snapshot.issues ?? []).map((issue) => `${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
		issuesText = issues.length > 0 ? issues.join("\n") : "unknown validation failure";
	} catch {
		return unavailable("openclaw.json could not be read");
	}
	const notice = `⚠ openclaw.json failed validation after that write:\n${issuesText}`;
	let recovery;
	try {
		recovery = await resolveRepair(`[config-verify] The config file is now invalid:\n${issuesText}\nPropose one corrective command from the allowed list.`);
	} catch (error) {
		if (!isSystemAgentInferenceUnavailableError(error)) throw error;
		return `${notice}\nThe write was applied, but inference could not propose a repair. Run \`openclaw doctor --fix\` on the machine running OpenClaw, then try again.`;
	}
	return recovery.text ? `${notice}\n\n${recovery.text}` : `${notice}\nUse \`config schema <path>\` here to check the expected shape. Or, with OpenClaw stopped, run \`openclaw doctor --fix\` on the machine running it.`;
}
//#endregion
//#region src/system-agent/chat-engine.ts
/**
* One conversation with OpenClaw, independent of transport. The facade owns
* serialization, history, and the verified inference session; concept owners
* route turns and host setup wizards behind the stable public entrypoint.
*/
var SystemAgentChatEngine = class {
	constructor(options, internals = {}) {
		this.options = options;
		this.history = [];
		this.turnQueue = Promise.resolve();
		const binding = options?.verifiedInference;
		if (!binding) throw new SystemAgentInferenceUnavailableError("conversation");
		this.verifiedInference = binding;
		this.agentSession = createSystemAgentSession(binding);
		this.wizard = new ChatWizardHost({
			surface: options.surface,
			beforePersistentApply: async (runtime) => {
				await this.requirePersistentApplyInference(runtime);
			},
			dependencies: internals.wizardDependencies
		});
		this.router = new ChatTurnRouter(options, { executeOperation: internals.executeOperation }, this.agentSession, this.wizard, {
			requireVerifiedInference: async () => await this.requireVerifiedInference(),
			requirePersistentApplyInference: async (runtime) => await this.requirePersistentApplyInference(runtime),
			rebindVerifiedInference: (next) => this.rebindVerifiedInference(next),
			getVerifiedInference: () => this.verifiedInference,
			loadOverview: async () => await this.loadOverview(),
			getHistory: () => this.history,
			verifyConfigAfterWrite: async () => await this.verifyConfigAfterWrite()
		});
	}
	propose(operation) {
		return this.router.propose(operation);
	}
	getPendingOperatorProposal() {
		return this.router.getPendingOperatorProposal();
	}
	async resolveOperatorApproval(decision, proposalHash, beforePersistentApply, terminalStatus) {
		const turn = this.turnQueue.then(async () => {
			const reply = await this.router.resolveOperatorApproval(decision, proposalHash, beforePersistentApply);
			if (reply && terminalStatus && !reply.applied) reply.text = `OpenClaw change ${terminalStatus}. No change. Retry the request if it is still needed.`;
			if (reply && decision === "allow-once" && !reply.applied) reply.text += " Check the current settings and OpenClaw status before retrying.";
			if (reply?.text) this.history.push({
				role: "assistant",
				text: reply.text
			});
			return reply;
		});
		this.turnQueue = turn.catch(() => void 0);
		return await turn;
	}
	noteAssistantMessage(text) {
		this.history.push({
			role: "assistant",
			text
		});
	}
	seedHistory(turns) {
		this.history.push(...turns.map((turn) => ({
			...turn,
			text: turn.role === "user" ? redactSensitiveCommandText(turn.text) : turn.text
		})));
	}
	historyLength() {
		return this.history.length;
	}
	historySince(index) {
		return this.history.slice(index).map((turn) => ({
			role: turn.role,
			text: turn.text
		}));
	}
	async dispose() {
		this.wizard.dispose();
		await cleanupSystemAgentSession(this.agentSession);
	}
	/**
	* Project the live hosted-wizard interaction onto a rejoin reply so a
	* reconnecting client re-renders the answer controls this session still
	* awaits; a no-op when no wizard is active.
	*/
	decorateRejoinReply(reply) {
		return this.wizard.decorateReply(reply);
	}
	async handle(text, options) {
		const turn = this.turnQueue.then(async () => {
			await this.requireVerifiedInference();
			const sensitiveTurn = this.wizard.sensitiveInputPending;
			const reply = await this.router.resolveTurn(text, options);
			return this.completeTurn(reply, sensitiveTurn ? "<redacted secret>" : redactSensitiveCommandText(text));
		});
		this.turnQueue = turn.catch(() => void 0);
		return await turn;
	}
	async answerWizard(answer) {
		const turn = this.turnQueue.then(async () => {
			await this.requireVerifiedInference();
			const result = await this.router.answerWizard(this.wizard.answer(answer));
			return this.completeTurn({
				text: result.text,
				action: "none"
			}, result.userHistoryText);
		});
		this.turnQueue = turn.catch(() => void 0);
		return await turn;
	}
	async cancelWizard(cancel) {
		const turn = this.turnQueue.then(async () => {
			const result = await this.router.answerWizard(this.wizard.cancel(cancel));
			return this.completeTurn({
				text: result.text,
				action: "none"
			}, result.userHistoryText);
		});
		this.turnQueue = turn.catch(() => void 0);
		return await turn;
	}
	completeTurn(reply, userHistoryText) {
		const completed = this.wizard.decorateReply(reply);
		this.history.push({
			role: "user",
			text: userHistoryText
		});
		if (completed.text) this.history.push({
			role: "assistant",
			text: completed.text
		});
		return completed;
	}
	async loadOverview() {
		const route = await this.requireVerifiedInference();
		return {
			...await (this.options.deps?.loadOverview ?? loadSystemAgentOverview)({ agentId: route.agentId }),
			defaultModel: route.modelLabel
		};
	}
	async planGreeting(params) {
		const planner = this.options.planGreeting;
		const plan = planner ? await planner(params) : await import("./assistant-CadvMdsB.js").then(({ planSystemAgentGreetingWithConfiguredModel }) => planSystemAgentGreetingWithConfiguredModel({
			...params,
			verifiedInference: this.verifiedInference,
			deps: this.options.deps
		}));
		if (plan) await this.requireVerifiedInference();
		return plan;
	}
	async requireVerifiedInference() {
		const binding = this.verifiedInference;
		if (this.agentSession.verifiedInference !== binding) return this.throwInferenceUnavailable();
		try {
			const route = await resolveSystemAgentVerifiedInferenceRoute(binding, this.options.deps);
			if (route) return route;
		} catch (error) {
			return this.throwInferenceUnavailable([error]);
		}
		return this.throwInferenceUnavailable();
	}
	async requirePersistentApplyInference(runtime) {
		const binding = this.verifiedInference;
		if (this.agentSession.verifiedInference !== binding) return this.throwInferenceUnavailable();
		try {
			const { resolvePersistentApplyInference } = await import("./system-agent/setup-inference.js");
			const route = await resolvePersistentApplyInference({
				binding,
				runtime,
				deps: this.options.deps
			});
			if (route) return route;
		} catch (error) {
			if (isSystemAgentInferenceUnavailableError(error)) return this.throwInferenceUnavailable(error.failures, false);
			return this.throwInferenceUnavailable([error], false);
		}
		return this.throwInferenceUnavailable([], false);
	}
	rebindVerifiedInference(binding) {
		if (binding.execution.agentId !== this.verifiedInference.execution.agentId) return;
		delete this.agentSession.cliSession;
		this.verifiedInference = binding;
		this.agentSession.verifiedInference = binding;
	}
	throwInferenceUnavailable(failures = [], cancelWizard = true) {
		this.router.clearForInferenceLoss();
		delete this.agentSession.cliSession;
		if (cancelWizard) this.wizard.dispose();
		this.history.splice(0);
		throw new SystemAgentInferenceUnavailableError("conversation", failures);
	}
	async verifyConfigAfterWrite() {
		return await verifyConfigAfterSystemAgentWrite((message) => this.router.resolveAssistantTurn(message, false));
	}
};
//#endregion
//#region src/system-agent/onboarding-welcome.ts
/**
* Card-client questions for the two welcome variants. Replies are texts the
* engine already understands; the prose welcome always stands alone for
* text-only clients (macOS app, TUI).
*/
const READY_WELCOME_QUESTION = {
	id: "onboarding-next-step",
	header: "Next step",
	question: "What would you like to do first?",
	options: [
		{
			label: "Talk to my agent",
			reply: "talk to agent",
			recommended: true,
			description: "Meet your agent right here."
		},
		{
			label: "Connect WhatsApp",
			reply: "connect whatsapp"
		},
		{
			label: "Connect Telegram",
			reply: "connect telegram"
		},
		{
			label: "See all channels",
			reply: "channels"
		}
	],
	isOther: true,
	skipAction: "exit"
};
const SETUP_WELCOME_QUESTION = {
	id: "onboarding-apply-setup",
	header: "Ready when you are",
	question: "Should I set all of that up now?",
	options: [{
		label: "Yes — set it up",
		reply: "yes",
		recommended: true
	}, {
		label: "What will you change?",
		reply: "what exactly will you set up?",
		description: "Ask before anything is written."
	}],
	isOther: true
};
/**
* The basic bootstrap is conversational: the welcome message carries the plan
* and the engine holds it as the pending proposal, so a bare "yes" applies it.
* This path starts only after a live inference turn. Already-configured
* installs get the channels/handoff guide instead.
*/
/**
* "Configured" must match the app onboarding gate (wizard metadata or gateway
* auth), not just a model: a model-only config would otherwise get the
* ready-guide welcome while the gate stays locked, stranding the page.
*/
async function loadAuthoredSetupConfig(params) {
	const authoredConfig = await (async () => {
		if (!params.configExists || !params.configValid) return;
		try {
			const { readConfigFileSnapshot } = await import("./config/config.js");
			const snapshot = await readConfigFileSnapshot();
			return snapshot.sourceConfig ?? snapshot.config ?? {};
		} catch {
			return;
		}
	})();
	const auth = authoredConfig?.gateway?.auth;
	const hasAuthMode = normalizeSecretInputString(auth?.mode) !== void 0;
	const hasAuthSecret = isSecretRef(auth?.token) || normalizeSecretInputString(auth?.token) !== void 0 || isSecretRef(auth?.password) || normalizeSecretInputString(auth?.password) !== void 0;
	const hasAuthoredSetup = authoredConfig?.wizard !== void 0 && Object.keys(authoredConfig.wizard).length > 0 || hasAuthMode || hasAuthSecret;
	return {
		...authoredConfig ? { authoredConfig } : {},
		hasAuthoredSetup
	};
}
async function buildOnboardingWelcome(params) {
	const overview = await params.engine.loadOverview();
	const { authoredConfig, hasAuthoredSetup } = await loadAuthoredSetupConfig({
		configExists: overview.config.exists,
		configValid: overview.config.valid
	});
	const localSetup = params.localRecovery === true && overview.config.exists && overview.config.valid && authoredConfig !== void 0 && authoredConfig?.gateway?.mode !== "remote" ? (await import("./local-onboarding-state-COhpjJFR.js")).readLocalOnboardingStateForConfig(overview.config.path, authoredConfig) : void 0;
	const pendingSetup = localSetup?.status === "pending" ? localSetup : void 0;
	const defaultModel = overview.defaultModel?.trim();
	const requestedWorkspace = params.workspace?.trim() ? resolveUserPath(params.workspace.trim()) : void 0;
	const authoredWorkspace = authoredConfig?.agents?.defaults?.workspace?.trim() ? resolveUserPath(authoredConfig.agents.defaults.workspace.trim()) : void 0;
	if (hasAuthoredSetup && !pendingSetup && defaultModel && (!requestedWorkspace || requestedWorkspace === authoredWorkspace)) {
		const welcome = formatSystemAgentOnboardingWelcome(overview);
		params.engine.noteAssistantMessage(welcome);
		return {
			text: welcome,
			question: READY_WELCOME_QUESTION
		};
	}
	if (!defaultModel) throw new Error("OpenClaw onboarding requires working inference first. Run `openclaw onboard` on the machine running OpenClaw to configure and verify a default model.");
	const { DEFAULT_WORKSPACE } = await import("./onboard-helpers-DC4-A9ie.js");
	const workspace = resolveUserPath(pendingSetup?.workspace || requestedWorkspace || authoredWorkspace || DEFAULT_WORKSPACE);
	params.engine.propose({
		kind: "setup",
		workspace,
		...params.agentName ? { agentName: params.agentName } : {}
	});
	const welcome = [
		"## Hi, I'm OpenClaw — let's hatch your agent.",
		"",
		"No menus here: tell me what you want and I'll do the configuring. I looked around this machine:",
		"",
		`- AI: ${defaultModel} — already verified with a real reply; switching later is one sentence.`,
		`- Workspace: ${shortenHomePath(workspace)}`,
		"- Gateway: runs locally, private to this machine (token auth).",
		"",
		"Say **yes** and I'll set all of that up now.",
		"",
		"Heads up: your agent gets real access to this machine — https://docs.openclaw.ai/security",
		"Afterwards: `connect discord`, `connect slack`, `connect telegram`, `connect whatsapp` (or `channels` for the full list), then `talk to agent` to meet your agent."
	].join("\n");
	params.engine.noteAssistantMessage(welcome);
	return {
		text: welcome,
		question: SETUP_WELCOME_QUESTION
	};
}
//#endregion
export { SystemAgentChatEngine as n, SystemAgentWizardAnswerError as r, buildOnboardingWelcome as t };