openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
429 lines (428 loc) • 21.6 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import { s as readConfigFileSnapshot, u as readConfigFileSnapshotWithPluginMetadata } from "./io.runtime-B9iJRs3w.js";
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { S as toAgentEntriesRecord, i as listAgentEntries } from "./agent-scope-config-DcbEhP0R.js";
import { _ as resolveGatewayPort } from "./paths-D2sRr1a_.js";
import "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { i as formatExternalSupervisorActionRequired } from "./gateway-supervision-D7p37rG2.js";
import { t as applyMergePatch } from "./merge-patch-DCMj2JVh.js";
import "./agent-scope-DbtJyKUL.js";
import { h as validateConfigObjectWithPlugins } from "./io.types-BUCjdS5v.js";
import { h as resolveConfigSnapshotHash } from "./io.read-helpers-ZKp-UiGx.js";
import { n as enablePluginInConfig } from "./enable-3OwjFbBV.js";
import { r as hasResolvedRosterBeforeMigrations } from "./agent-roster-provenance-TGrT9xta.js";
import "./config-Cs0XXL3x.js";
import { t as resolveGatewayStartupTiming } from "./gateway-startup-timing-D9NqKiRl.js";
import { r as isReservedSystemAgentId } from "./agent-id-D7twbNRG.js";
import { i as sameDefaultInferenceRoute, t as projectDefaultInferenceRoute } from "./inference-route-fT4JRSHY.js";
import { l as resolveSystemAgentOnboardingTarget } from "./onboard-agent-target-CNKxVAZK.js";
import { t as applySystemAgentModelSelection } from "./setup-model-selection-C6YbZMxz.js";
import { isDeepStrictEqual } from "node:util";
//#region src/system-agent/setup-config-snapshot.ts
function requireValidSystemAgentSetupSnapshot(snapshot) {
if (snapshot.exists && !snapshot.valid) {
const issue = snapshot.issues?.[0];
const detail = issue ? ` (${issue.path ? `${issue.path}: ` : ""}${issue.message})` : "";
throw new Error(`OpenClaw config ${shortenHomePath(snapshot.path)} is invalid${detail}. Fix it before running setup.`);
}
const sourceConfig = snapshot.exists ? snapshot.sourceConfig ?? snapshot.config : {};
const runtimeConfig = snapshot.exists ? snapshot.runtimeConfig ?? snapshot.config : {};
const reservedAgent = listAgentEntries(runtimeConfig).find((entry) => isReservedSystemAgentId(entry.id));
if (reservedAgent) throw new Error(`Agent id "${normalizeAgentId(reservedAgent.id)}" is reserved for the system agent. Rename that configured agent, then retry setup.`);
return {
sourceConfig,
runtimeConfig
};
}
//#endregion
//#region src/system-agent/setup-inference-route-guard.ts
function withoutAgentIdentity(projection) {
const agent = isRecord(projection.agent) ? {
...projection.agent,
id: "<agent>"
} : projection.agent;
return {
...projection,
route: projection.route ? {
...projection.route,
agentId: "<agent>",
agentDir: "<agent-dir>"
} : null,
defaultSelection: { explicitIds: [] },
...agent ? { agent } : {}
};
}
function sameSetupInferenceRoute(left, right, ignoreAgentIdentity) {
return ignoreAgentIdentity ? isDeepStrictEqual(withoutAgentIdentity(left), withoutAgentIdentity(right)) : sameDefaultInferenceRoute(left, right);
}
function sameSetupConfiguredRoute(left, right, ignoreAgentIdentity) {
if (!ignoreAgentIdentity) return isDeepStrictEqual(left, right);
const normalize = (route) => route ? {
...route,
agentId: "<agent>",
agentDir: "<agent-dir>"
} : null;
return isDeepStrictEqual(normalize(left), normalize(right));
}
function assertSetupTarget(params) {
const agentId = params.resolveDefaultAgentId(params.config);
if (params.expectedAgentId && agentId !== params.expectedAgentId) throw new Error("The default agent changed while AI access was being tested. Try setup again.");
if (params.expectedAgentDir && params.resolveAgentDir(params.config, agentId) !== params.expectedAgentDir) throw new Error("The agent credential location changed while AI access was being tested. Try setup again.");
if (params.expectedModelRef) {
const current = params.resolveDefaultModelForAgent({
cfg: params.config,
agentId
});
if (`${current.provider}/${current.model}` !== params.expectedModelRef) throw new Error("The default model changed while AI access was being tested. Try setup again.");
}
}
//#endregion
//#region src/system-agent/setup-apply.ts
/** Prompter for quickstart-only flows: notes go to the log, prompts fail loud. */
function createQuickstartNotePrompter(runtime) {
const unexpected = (kind) => {
throw new Error(`openclaw setup hit an interactive ${kind} prompt; quickstart must not ask`);
};
return {
intro: async () => {},
outro: async () => {},
note: async (message, title) => {
runtime.log(title ? `${title}: ${message}` : message);
},
select: async (params) => {
if (params.initialValue !== void 0) return params.initialValue;
return unexpected("select");
},
multiselect: async () => unexpected("multiselect"),
text: async () => unexpected("text"),
confirm: async (params) => params.initialValue ?? true,
progress: (label) => {
runtime.log(label);
return {
update: (message) => runtime.log(message),
stop: (message) => {
if (message) runtime.log(message);
}
};
}
};
}
function applySecurityAcknowledgement(config) {
if (config.wizard?.securityAcknowledgedAt) return config;
return {
...config,
wizard: {
...config.wizard,
securityAcknowledgedAt: (/* @__PURE__ */ new Date()).toISOString()
}
};
}
async function applySystemAgentSetup(params, hooks) {
const { workspace, model, agentRuntimeId, authProfileId, expectedAgentId, expectedAgentDir, expectedModelRef, expectedConfigHash, configPatch, finalizeConfig, enablePluginId, refreshPluginRegistry, assertCommitPreconditions, surface, runtime } = params;
const hasExpectedConfigHash = Object.hasOwn(params, "expectedConfigHash");
const beforePersistentApply = hooks?.beforePersistentApply;
const [{ readSetupConfigFileSnapshot, resolveQuickstartGatewayDefaults }, onboardHelpers, { applyLocalSetupWorkspaceConfig, resolveOnboardingWorkspaceConflict }, { transformConfigWithPendingPluginInstalls }] = await Promise.all([
import("./setup.shared-mTYEiwX9.js"),
import("./onboard-helpers-DC4-A9ie.js"),
import("./onboard-config-DgCoy_f_.js"),
import("./install-record-commit-DVoT_zjZ.js")
]);
let snapshot = await readSetupConfigFileSnapshot();
let snapshotConfig = requireValidSystemAgentSetupSnapshot(snapshot);
const configHashBefore = resolveConfigSnapshotHash(snapshot);
const startedWithoutAuthoredRoster = !hasResolvedRosterBeforeMigrations(snapshot);
const onboardingSourceConfig = snapshot.sourceConfigBeforeMigrations ?? snapshotConfig.sourceConfig;
const initialWorkspaceConflict = resolveOnboardingWorkspaceConflict(onboardingSourceConfig, workspace);
const setupWorkspace = initialWorkspaceConflict && !params.allowWorkspaceChange ? initialWorkspaceConflict.currentWorkspaceDir : workspace;
let verifiedRoute = params.expectedInferenceRoute;
let guardedExpectedAgentId = expectedAgentId;
let guardedExpectedAgentDir = expectedAgentDir;
let sessionMigrationWarnings = [];
if (hasExpectedConfigHash && resolveConfigSnapshotHash(snapshot) !== expectedConfigHash) throw new Error("OpenClaw config changed while AI access was being tested. Try setup again.");
let guardModules = expectedAgentId || expectedAgentDir || expectedModelRef ? await Promise.all([import("./agent-scope-BHLMs19p.js"), import("./model-selection-DEDE6TxI.js")]) : void 0;
const assertExpectedTarget = (config) => {
if (!guardModules) return;
assertSetupTarget({
config,
expectedAgentId: guardedExpectedAgentId,
expectedAgentDir: guardedExpectedAgentDir,
expectedModelRef,
resolveAgentDir: guardModules[0].resolveAgentDir,
resolveDefaultAgentId: (currentConfig) => resolveSystemAgentOnboardingTarget(currentConfig).agentId,
resolveDefaultModelForAgent: guardModules[1].resolveDefaultModelForAgent
});
};
assertExpectedTarget(snapshotConfig.runtimeConfig);
const assertVerifiedRoute = async (setupSnapshot, expectedRoute = verifiedRoute, phase = "before", ignoreAgentIdentity = false) => {
if (!expectedRoute) return;
const verifiedSnapshot = await readConfigFileSnapshot();
const setupSource = setupSnapshot.exists ? setupSnapshot.sourceConfig ?? setupSnapshot.config : {};
const verifiedSource = verifiedSnapshot.exists ? verifiedSnapshot.sourceConfig ?? verifiedSnapshot.config : {};
const currentRoute = verifiedSnapshot.exists && verifiedSnapshot.valid && verifiedSnapshot.path === setupSnapshot.path && verifiedSnapshot.hash === setupSnapshot.hash && isDeepStrictEqual(verifiedSource, setupSource) ? await projectDefaultInferenceRoute(verifiedSnapshot.runtimeConfig ?? verifiedSnapshot.config) : null;
if (!currentRoute || !sameSetupInferenceRoute(currentRoute, expectedRoute, ignoreAgentIdentity)) throw new Error(phase === "before" ? "The default-agent inference route changed before setup could start, so no workspace or Gateway settings were changed. Retry setup from the current OpenClaw session." : "The default-agent inference route changed after the config write, so no further setup effects were applied. Retry setup from the current OpenClaw session.");
return currentRoute;
};
await assertVerifiedRoute(snapshot);
let expectedWriteHash = expectedConfigHash;
if (startedWithoutAuthoredRoster) {
const { ensureOnboardingAgent } = await import("./onboard-agent-BaPkGQp-.js");
beforePersistentApply?.();
const created = await ensureOnboardingAgent({
config: onboardingSourceConfig,
workspace: setupWorkspace,
baseConfig: onboardingSourceConfig,
firstAgent: params.firstAgent ?? { name: "main" },
expectedConfigHash: configHashBefore ?? null,
beforePersistentApply
});
if (!created.createdAgent || !created.configHash) throw new Error("OpenClaw did not create the approved first agent because the roster changed. Retry setup.");
snapshot = await readSetupConfigFileSnapshot();
snapshotConfig = requireValidSystemAgentSetupSnapshot(snapshot);
if ((resolveConfigSnapshotHash(snapshot) ?? null) !== created.configHash) throw new Error("OpenClaw config changed after first-agent creation. Retry setup.");
const createdRoster = listAgentEntries(snapshotConfig.sourceConfig);
if (createdRoster.length !== 1 || normalizeAgentId(createdRoster[0]?.id ?? "") !== created.agentId) throw new Error("OpenClaw first-agent ownership changed during setup. Retry setup.");
verifiedRoute = await assertVerifiedRoute(snapshot, verifiedRoute, "before", true) ?? verifiedRoute;
guardModules ??= await Promise.all([import("./agent-scope-BHLMs19p.js"), import("./model-selection-DEDE6TxI.js")]);
guardedExpectedAgentId = created.agentId;
guardedExpectedAgentDir = guardModules[0].resolveAgentDir(snapshotConfig.runtimeConfig, created.agentId);
assertExpectedTarget(snapshotConfig.runtimeConfig);
expectedWriteHash = created.configHash;
sessionMigrationWarnings = created.sessionMigrationWarnings ?? [];
}
const prompter = createQuickstartNotePrompter(runtime);
const { configureGatewayForSetup } = await import("./setup.gateway-config-DiwA0U0H.js");
const buildSetupCandidate = async (currentBaseConfig, hasAuthoredRosterEntries) => {
const roster = listAgentEntries(currentBaseConfig);
const currentHasRoster = hasAuthoredRosterEntries && roster.length > 0;
const allowWorkspaceWrite = params.allowWorkspaceChange || !currentHasRoster;
let setupBaseConfig = currentBaseConfig;
if (enablePluginId) {
const enabled = enablePluginInConfig(setupBaseConfig, enablePluginId);
if (!enabled.enabled) throw new Error(`Provider plugin ${enablePluginId} is ${enabled.reason}.`);
setupBaseConfig = enabled.config;
}
if (configPatch !== void 0) setupBaseConfig = applyMergePatch(setupBaseConfig, configPatch);
if (currentHasRoster) {
const { list: _legacyList, ...agents } = setupBaseConfig.agents ?? {};
setupBaseConfig = {
...setupBaseConfig,
agents: {
...agents,
entries: toAgentEntriesRecord(roster)
}
};
}
const preserveWorkspace = currentHasRoster && !params.allowWorkspaceChange;
if (preserveWorkspace) {
const defaults = { ...setupBaseConfig.agents?.defaults };
const currentDefaults = currentBaseConfig.agents?.defaults;
if (currentDefaults && Object.hasOwn(currentDefaults, "workspace")) defaults.workspace = currentDefaults.workspace;
else delete defaults.workspace;
setupBaseConfig = {
...setupBaseConfig,
agents: {
...setupBaseConfig.agents,
defaults
}
};
}
let candidate = applyLocalSetupWorkspaceConfig(setupBaseConfig, setupWorkspace, {
allowWorkspaceChange: allowWorkspaceWrite,
preserveWorkspace
});
if (model) {
const targetAgentId = candidate.agents?.defaults?.systemAgent?.agentId;
candidate = await applySystemAgentModelSelection({
config: candidate,
model,
...targetAgentId ? { targetAgentId } : {},
...agentRuntimeId ? { agentRuntimeId } : {},
...authProfileId ? { authProfileId } : {}
});
}
candidate = applySecurityAcknowledgement(candidate);
const gateway = await configureGatewayForSetup({
flow: "quickstart",
baseConfig: currentBaseConfig,
nextConfig: candidate,
localPort: resolveGatewayPort(currentBaseConfig),
quickstartGateway: resolveQuickstartGatewayDefaults(currentBaseConfig),
prompter,
runtime
});
return {
nextConfig: onboardHelpers.applyWizardMetadata(gateway.nextConfig, {
command: "onboard",
mode: "local"
}),
settings: gateway.settings
};
};
beforePersistentApply?.();
const committed = await transformConfigWithPendingPluginInstalls({
afterWrite: { mode: "auto" },
writeOptions: {
auditOrigin: "system-agent",
allowConfigSizeDrop: false,
assertConfigPathForWrite: beforePersistentApply
},
transform: async (currentConfig, context) => {
const currentSnapshot = requireValidSystemAgentSetupSnapshot(context.snapshot);
if ((hasExpectedConfigHash || startedWithoutAuthoredRoster) && context.previousHash !== expectedWriteHash) throw new Error("OpenClaw config changed while AI access was being tested. Try setup again.");
await assertVerifiedRoute(context.snapshot);
assertExpectedTarget(currentSnapshot.runtimeConfig);
const setupCandidate = await buildSetupCandidate(currentConfig, startedWithoutAuthoredRoster ? false : hasResolvedRosterBeforeMigrations(context.snapshot));
const finalizedConfig = finalizeConfig ? finalizeConfig(setupCandidate.nextConfig, currentSnapshot.sourceConfig) : setupCandidate.nextConfig;
const expectedSourceRoute = verifiedRoute ? await projectDefaultInferenceRoute(finalizedConfig) : void 0;
if (verifiedRoute && (!verifiedRoute.route || !expectedSourceRoute?.route || !sameSetupConfiguredRoute(expectedSourceRoute.route, verifiedRoute.route, false))) throw new Error("The setup candidate no longer preserves the exact verified inference route, so it was not saved. Retry setup from the current OpenClaw session.");
if (assertCommitPreconditions) {
assertCommitPreconditions(currentSnapshot.sourceConfig);
if (resolveUserPath(resolveSystemAgentOnboardingTarget(finalizedConfig).workspaceDir) !== resolveUserPath(setupWorkspace)) throw new Error("Another onboarding run owns a different workspace. Retry onboarding with its approved workspace.");
}
return {
nextConfig: finalizedConfig,
result: { settings: setupCandidate.settings }
};
}
});
const nextConfig = committed.nextConfig;
const settings = committed.result?.settings;
if (!settings) throw new Error("OpenClaw setup committed without resolved Gateway settings.");
const onboardingTarget = resolveSystemAgentOnboardingTarget(nextConfig);
const effectiveWorkspace = onboardingTarget.workspaceDir;
if (verifiedRoute) {
const afterRead = await readConfigFileSnapshotWithPluginMetadata();
const afterSnapshot = afterRead.snapshot;
requireValidSystemAgentSetupSnapshot(afterSnapshot);
const expectedRuntime = validateConfigObjectWithPlugins(committed.nextConfig, {
env: process.env,
pluginMetadataSnapshot: afterRead.pluginMetadataSnapshot
});
if (!expectedRuntime.ok) {
const issue = expectedRuntime.issues[0];
const detail = issue ? ` (${issue.path ? `${issue.path}: ` : ""}${issue.message})` : "";
throw new Error(`OpenClaw could not validate the setup route after its config write${detail}. No further setup effects were applied. Retry setup from the current OpenClaw session.`);
}
const expectedPersistedRoute = await projectDefaultInferenceRoute(expectedRuntime.config);
await assertVerifiedRoute(afterSnapshot, expectedPersistedRoute, "after");
if (!sameSetupConfiguredRoute(expectedPersistedRoute.route, verifiedRoute.route, false)) throw new Error("The materialized inference route no longer matches the exact verified route, so no further setup effects were applied. Retry setup from the current OpenClaw session.");
}
const lines = [
...sessionMigrationWarnings,
`Workspace: ${shortenHomePath(effectiveWorkspace)}`,
model ? `Default model: ${model}` : void 0
].filter((line) => line !== void 0);
const runCommittedFollowUp = async (effect, onFailure) => {
beforePersistentApply?.();
try {
return await effect();
} catch (error) {
beforePersistentApply?.();
onFailure(error);
return;
}
};
const effectiveAgentId = onboardingTarget.agentId;
const workspaceResult = await runCommittedFollowUp(async () => await onboardHelpers.ensureWorkspaceAndSessions(effectiveWorkspace, runtime, {
agentId: effectiveAgentId,
skipBootstrap: Boolean(nextConfig.agents?.defaults?.skipBootstrap),
skipOptionalBootstrapFiles: nextConfig.agents?.defaults?.skipOptionalBootstrapFiles,
beforePersistentApply
}), (error) => lines.push(`Workspace files: ${formatErrorMessage(error)}`));
await runCommittedFollowUp(async () => {
const { updateExecApprovals } = await import("./exec-approvals-B3RLY7c2.js");
beforePersistentApply?.();
await updateExecApprovals({ update: (approvals) => approvals.agents?.openclaw ? null : {
...approvals,
agents: {
...approvals.agents,
openclaw: {
security: "full",
ask: "off"
}
}
} });
}, (error) => lines.push(`OpenClaw exec approval: ${formatErrorMessage(error)}; local model harnesses may ask again.`));
if (refreshPluginRegistry && enablePluginId) await runCommittedFollowUp(async () => {
const { refreshPluginRegistryAfterConfigMutation } = await import("./registry-refresh-x5mIxBEc.js");
beforePersistentApply?.();
await refreshPluginRegistryAfterConfigMutation({
config: nextConfig,
reason: "source-changed",
workspaceDir: onboardingTarget.workspaceDir,
traceCommand: "openclaw-setup",
logger: { warn: (message) => lines.push(message) }
});
}, (error) => lines.push(`Plugin registry refresh failed: ${formatErrorMessage(error)}`));
let gateway = {
status: "ready",
action: "reused"
};
if (surface === "cli") await runCommittedFollowUp(async () => {
const { ensureGatewayServiceForOnboarding } = await import("./setup.finalize-CirJgk3n.js");
beforePersistentApply?.();
gateway = (await ensureGatewayServiceForOnboarding({
flow: "quickstart",
opts: { installDaemon: params.installDaemon },
nextConfig,
settings,
prompter,
runtime,
loadedAction: params.resume ? "resume" : "restart"
})).gateway;
if (gateway.status === "failed") lines.push(`Gateway service: ${gateway.error}`);
else if (gateway.status === "ready") {
const probeLinks = onboardHelpers.resolveLocalControlUiProbeLinks({
bind: settings.bind,
port: settings.port,
customBindHost: settings.customBindHost,
basePath: void 0,
tlsEnabled: nextConfig.gateway?.tls?.enabled === true
});
const probe = await onboardHelpers.waitForGatewayReachable({
url: probeLinks.wsUrl,
token: settings.authMode === "token" ? settings.gatewayToken : void 0,
password: settings.authMode === "password" ? await (await import("./setup.secret-input-XAVtf-6M.js")).resolveSetupSecretInputString({
config: nextConfig,
value: nextConfig.gateway?.auth?.password,
path: "gateway.auth.password",
env: process.env
}) : void 0,
...gateway.action === "reused" ? { deadlineMs: 15e3 } : resolveGatewayStartupTiming()
});
if (probe.ok) lines.push(`Gateway: running at ${probeLinks.wsUrl}`);
else {
const detail = probe.detail ?? "still starting";
gateway = {
status: "failed",
error: `Gateway is not reachable yet (${detail}).`
};
lines.push(`Gateway: not reachable yet (${detail}) — say \`gateway status\` to check`);
}
} else if (gateway.reason === "external") lines.push(`Gateway: ${formatExternalSupervisorActionRequired("start the gateway")}`);
else if (params.installDaemon === false) lines.push("Gateway: will run in the foreground.");
else lines.push("Gateway: service install skipped — say `start gateway` when you want it running.");
}, (error) => {
const message = formatErrorMessage(error);
gateway = {
status: "failed",
error: message
};
lines.push(`Gateway service: ${message}`);
});
else lines.push("Gateway: running (managed by this app).");
return {
configPath: committed.path,
configHashBefore,
configHashAfter: committed.persistedHash,
bootstrapPending: workspaceResult?.bootstrapPending === true,
workspaceReady: workspaceResult !== void 0,
gateway,
lines
};
}
//#endregion
export { createQuickstartNotePrompter as n, applySystemAgentSetup as t };