openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
818 lines (817 loc) • 32.4 kB
JavaScript
import { y as resolveStateDir } from "./paths-mvMm5bYV.js";
import "./fs-safe-aqmM_n6V.js";
import { t as appendRegularFile } from "./regular-file-BD2zl6_l.js";
import { g as shortenHomePath, p as resolveUserPath } from "./utils-CCC-BEJH.js";
import { i as buildAgentMainSessionKey, u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { n as DEFAULT_MODEL, r as DEFAULT_PROVIDER } from "./defaults-mDjiWzE5.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/crestodian/audit.ts
/** Resolve the JSONL audit path for Crestodian persistent operations. */
function resolveCrestodianAuditPath(env = process.env, stateDir = resolveStateDir(env)) {
return path.join(stateDir, "audit", "crestodian.jsonl");
}
/** Append one Crestodian audit entry and return the file path written. */
async function appendCrestodianAuditEntry(entry, opts = {}) {
const auditPath = opts.auditPath ?? resolveCrestodianAuditPath(opts.env);
await fs.mkdir(path.dirname(auditPath), { recursive: true });
await appendRegularFile({
filePath: auditPath,
content: `${JSON.stringify({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
...entry
})}\n`,
rejectSymlinkParents: true
});
return auditPath;
}
//#endregion
//#region src/crestodian/operations.ts
const loadConfigModule = async () => await import("./config/config.js");
const loadOverviewModule = async () => await import("./overview-BAGE25yt.js");
const loadModelsSharedModule = async () => await import("./shared-DyBfRrDF.js");
const loadConfigCliModule = async () => await import("./config-cli-DC7j84k_.js");
const loadDoctorModule = async () => await import("./doctor-DMdCGvR-.js");
const SET_MODEL_RE = /(?:set|configure|use)\s+(?:the\s+)?(?:default\s+)?model\s+(.+)/i;
const CONFIGURE_MODELS_RE = /(?:set|configure|use)\s+models?\s+(?<model>\S+)/i;
const CREATE_AGENT_RE = /(?:create|add|setup|set\s+up)\s+(?:(?:an?|new|my)\s+)?agent\s+(?<agent>[a-z0-9_-]+)/i;
const TALK_AGENT_RE = /(?:talk\s+to|switch\s+to|open|enter)\s+(?:(?:my|the)\s+)?(?:(?<agent>[a-z0-9_-]+)\s+)?agent/i;
const WORKSPACE_RE = /(?:workspace|workdir|cwd|for|in)\s+(?<workspace>"[^"]+"|'[^']+'|\S+)/i;
const MODEL_RE = /\bmodel\s+(?<model>\S+)/i;
const CONFIG_SET_RE = /^(?:config\s+set|set\s+config)\s+(?<path>[A-Za-z0-9_.[\]-]+)\s+(?<value>.+)$/i;
const CONFIG_SET_REF_RE = /^(?:config\s+set-ref|set\s+secretref|set\s+secret\s+ref)\s+(?<path>[A-Za-z0-9_.[\]-]+)\s+(?:(?<source>env|file|exec)\s+)?(?<id>\S+)(?:\s+provider\s+(?<provider>[A-Za-z0-9_-]+))?$/i;
const SETUP_RE = /^(?:setup(?!\s+agent\b)|set\s+me\s+up|set\s+up\s+openclaw|onboard|onboard\s+me|bootstrap|first\s+run)(?:\b|$)/i;
const PLUGIN_LIST_RE = /^(?:plugins?|clawhub)\s+list$|^list\s+plugins?$/i;
const PLUGIN_SEARCH_RE = /^(?:(?:plugins?|clawhub)\s+search|search\s+plugins?(?:\s+for)?)\s+(?<query>.+)$/i;
const PLUGIN_INSTALL_RE = /^(?:(?:plugins?)\s+install|install\s+(?:(?<source>npm|clawhub)\s+)?plugins?)\s+(?<spec>\S+)$/i;
const PLUGIN_UNINSTALL_RE = /^(?:(?:plugins?)\s+(?:uninstall|remove)|(?:uninstall|remove)\s+plugins?)\s+(?<pluginId>[A-Za-z0-9_.@/-]+)$/i;
const OPENAI_API_DEFAULT_MODEL_REF = `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}`;
const ANTHROPIC_API_DEFAULT_MODEL_REF = "anthropic/claude-opus-4-8";
const CLAUDE_CLI_DEFAULT_MODEL_REF = "claude-cli/claude-opus-4-8";
const CODEX_APP_SERVER_DEFAULT_MODEL_REF = "openai/gpt-5.5";
/** Parse one user command into Crestodian's closed operation union. */
function parseCrestodianOperation(input) {
const trimmed = input.trim();
const lower = trimmed.toLowerCase();
if (!trimmed) return {
kind: "none",
message: "Tiny claw tap: say status, doctor, models, agents, or talk to agent."
};
if ([
"help",
"?",
"overview",
"system"
].includes(lower)) return { kind: "overview" };
if (lower === "audit" || lower.includes("audit log")) return { kind: "audit" };
const configSetRefMatch = trimmed.match(CONFIG_SET_REF_RE);
if (configSetRefMatch?.groups?.path && configSetRefMatch.groups.id?.trim()) {
const source = configSetRefMatch.groups.source?.toLowerCase() ?? "env";
return {
kind: "config-set-ref",
path: configSetRefMatch.groups.path,
source,
id: configSetRefMatch.groups.id.trim(),
...configSetRefMatch.groups.provider ? { provider: configSetRefMatch.groups.provider } : {}
};
}
const configSetMatch = trimmed.match(CONFIG_SET_RE);
if (configSetMatch?.groups?.path && configSetMatch.groups.value?.trim()) return {
kind: "config-set",
path: configSetMatch.groups.path,
value: configSetMatch.groups.value.trim()
};
if (lower === "config validate" || lower === "validate config" || lower.includes("validate config")) return { kind: "config-validate" };
if (PLUGIN_LIST_RE.test(trimmed)) return { kind: "plugin-list" };
const pluginSearchMatch = trimmed.match(PLUGIN_SEARCH_RE);
if (pluginSearchMatch?.groups?.query?.trim()) return {
kind: "plugin-search",
query: pluginSearchMatch.groups.query.trim()
};
const pluginInstallMatch = trimmed.match(PLUGIN_INSTALL_RE);
if (pluginInstallMatch?.groups?.spec?.trim()) return {
kind: "plugin-install",
spec: normalizePluginInstallSpec(pluginInstallMatch.groups.spec.trim(), pluginInstallMatch.groups.source)
};
const pluginUninstallMatch = trimmed.match(PLUGIN_UNINSTALL_RE);
if (pluginUninstallMatch?.groups?.pluginId?.trim()) return {
kind: "plugin-uninstall",
pluginId: pluginUninstallMatch.groups.pluginId.trim()
};
if (SETUP_RE.test(lower)) {
const workspace = trimShellishToken(trimmed.match(WORKSPACE_RE)?.groups?.workspace);
const model = trimmed.match(MODEL_RE)?.groups?.model;
return {
kind: "setup",
...workspace ? { workspace } : {},
...model ? { model } : {}
};
}
if (lower.includes("doctor")) {
if (lower.includes("fix") || lower.includes("repair")) return { kind: "doctor-fix" };
return { kind: "doctor" };
}
if (lower.includes("health")) return { kind: "health" };
if (lower.includes("gateway")) {
if (lower.includes("restart")) return { kind: "gateway-restart" };
if (lower.includes("start")) return { kind: "gateway-start" };
if (lower.includes("stop")) return { kind: "gateway-stop" };
return { kind: "gateway-status" };
}
if (lower.includes("status")) return { kind: "status" };
if (lower.includes("agent")) {
const createMatch = trimmed.match(CREATE_AGENT_RE);
if (createMatch?.groups?.agent) {
const workspace = trimShellishToken(trimmed.match(WORKSPACE_RE)?.groups?.workspace);
const model = trimmed.match(MODEL_RE)?.groups?.model;
return {
kind: "create-agent",
agentId: normalizeAgentId(createMatch.groups.agent),
...workspace ? { workspace } : {},
...model ? { model } : {}
};
}
const talkMatch = trimmed.match(TALK_AGENT_RE);
if (talkMatch) {
const workspace = trimShellishToken(trimmed.match(WORKSPACE_RE)?.groups?.workspace);
return {
kind: "open-tui",
agentId: talkMatch.groups?.agent,
...workspace ? { workspace } : {}
};
}
return { kind: "agents" };
}
if (lower.includes("model")) {
const match = trimmed.match(SET_MODEL_RE);
const pluralMatch = trimmed.match(CONFIGURE_MODELS_RE);
const model = match?.[1]?.trim() ?? pluralMatch?.groups?.model?.trim();
if (model) return {
kind: "set-default-model",
model
};
return { kind: "models" };
}
if (lower === "tui" || lower.includes("open tui") || lower.includes("chat")) return { kind: "open-tui" };
if (lower === "quit" || lower === "exit") return {
kind: "none",
message: "Crestodian retracts into shell. Bye."
};
return {
kind: "none",
message: "I can run doctor/status/health, check or restart Gateway, list agents/models, set default model, show audit, or switch to your agent TUI."
};
}
function trimShellishToken(value) {
const trimmed = value?.trim();
if (!trimmed) return;
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1).trim() || void 0;
return trimmed;
}
function normalizePluginInstallSpec(spec, source) {
const trimmed = spec.trim();
const normalizedSource = source?.toLowerCase();
if (normalizedSource === "npm" && !trimmed.toLowerCase().startsWith("npm:")) return `npm:${trimmed}`;
if (normalizedSource === "clawhub" && !trimmed.toLowerCase().startsWith("clawhub:")) return `clawhub:${trimmed}`;
return trimmed;
}
function validateCrestodianPluginInstallSpec(spec) {
const trimmed = spec.trim();
if (!trimmed) return "Plugin install spec is required.";
if (/\s/.test(trimmed)) return "Crestodian plugin install accepts one npm or ClawHub package spec.";
if (/^(?:\.{1,2}\/|\/|~\/|file:|git(?:\+ssh|\+https)?:|https?:)/i.test(trimmed)) return "Crestodian plugin install accepts npm or ClawHub package specs only.";
return null;
}
/** Return whether an operation can change local state or process lifecycle. */
function isPersistentCrestodianOperation(operation) {
return operation.kind === "set-default-model" || operation.kind === "config-set" || operation.kind === "config-set-ref" || operation.kind === "setup" || operation.kind === "doctor-fix" || operation.kind === "plugin-install" || operation.kind === "plugin-uninstall" || operation.kind === "create-agent" || operation.kind === "gateway-start" || operation.kind === "gateway-stop" || operation.kind === "gateway-restart";
}
/** Format a user-facing description for an operation requiring approval. */
function describeCrestodianPersistentOperation(operation) {
switch (operation.kind) {
case "set-default-model": return `set agents.defaults.model.primary to ${operation.model}`;
case "config-set": return `set config ${operation.path} to ${formatConfigSetValueForPlan(operation.path, operation.value)}`;
case "config-set-ref": return `set config ${operation.path} to ${operation.source} SecretRef ${operation.source === "env" ? operation.id : "<redacted>"}`;
case "setup": return formatSetupPlanDescription(operation);
case "doctor-fix": return "run doctor repairs";
case "plugin-install": return `install plugin ${operation.spec}`;
case "plugin-uninstall": return `uninstall plugin ${operation.pluginId}`;
case "create-agent": return `create agent ${operation.agentId} with workspace ${formatCreateAgentWorkspace(operation.workspace)}`;
case "gateway-start": return "start the Gateway";
case "gateway-stop": return "stop the Gateway";
case "gateway-restart": return "restart the Gateway";
default: return "apply this action";
}
}
/** Format the standard approval plan text for a persistent operation. */
function formatCrestodianPersistentPlan(operation) {
return `Plan: ${describeCrestodianPersistentOperation(operation)}. Say yes to apply.`;
}
function formatCreateAgentWorkspace(workspace) {
return workspace ? shortenHomePath(resolveUserPath(workspace)) : shortenHomePath(process.cwd());
}
function formatConfigSetValueForPlan(configPath, value) {
if (/(secret|token|password|key|credential)/i.test(configPath)) return "<redacted>";
return value;
}
function formatSetupPlanDescription(operation) {
return `bootstrap OpenClaw setup for workspace ${shortenHomePath(resolveUserPath(operation.workspace ?? process.cwd()))}${operation.model ? ` and default model ${operation.model}` : ""}`;
}
function chooseSetupModel(overview, requestedModel) {
if (requestedModel?.trim()) return {
model: requestedModel.trim(),
source: "requested"
};
if (overview.defaultModel) return { source: "existing default model" };
if (overview.tools.apiKeys.openai) return {
model: OPENAI_API_DEFAULT_MODEL_REF,
source: "OPENAI_API_KEY"
};
if (overview.tools.apiKeys.anthropic) return {
model: ANTHROPIC_API_DEFAULT_MODEL_REF,
source: "ANTHROPIC_API_KEY"
};
if (overview.tools.claude.found) return {
model: CLAUDE_CLI_DEFAULT_MODEL_REF,
source: "Claude Code CLI"
};
if (overview.tools.codex.found) return {
model: CODEX_APP_SERVER_DEFAULT_MODEL_REF,
source: "Codex app-server"
};
return { source: "none" };
}
function logQueued(runtime, operation) {
runtime.log(`[crestodian] queued: ${operation}`);
runtime.log(`[crestodian] running: ${operation}`);
}
function formatGatewayStatusLine(overview) {
return [
`Gateway: ${overview.gateway.reachable ? "reachable" : "not reachable"}`,
`URL: ${overview.gateway.url}`,
`Source: ${overview.gateway.source}`,
overview.gateway.error ? `Note: ${overview.gateway.error}` : void 0
].filter((line) => line !== void 0).join("\n");
}
async function runGatewayLifecycle(operation) {
const lifecycle = await import("./lifecycle-DN9r7mpZ.js");
if (operation === "start") {
await lifecycle.runDaemonStart();
return;
}
if (operation === "stop") {
await lifecycle.runDaemonStop();
return;
}
await lifecycle.runDaemonRestart();
}
async function readConfigFileSnapshotLazy() {
const { readConfigFileSnapshot } = await loadConfigModule();
return await readConfigFileSnapshot();
}
async function loadOverviewForOperation(deps) {
if (deps?.loadOverview) return await deps.loadOverview();
const { loadCrestodianOverview } = await loadOverviewModule();
return await loadCrestodianOverview();
}
async function formatOverviewForOperation(overview, deps) {
if (deps?.formatOverview) return deps.formatOverview(overview);
const { formatCrestodianOverview } = await loadOverviewModule();
return formatCrestodianOverview(overview);
}
async function loadConfigFileMutationHelpers() {
const { mutateConfigFile, readConfigFileSnapshot } = await loadConfigModule();
return {
mutateConfigFile,
readConfigFileSnapshot
};
}
function formatConfigValidationLine(snapshot) {
if (!snapshot.exists) return `Config missing: ${shortenHomePath(snapshot.path)}`;
if (snapshot.valid) return `Config valid: ${shortenHomePath(snapshot.path)}`;
return [`Config invalid: ${shortenHomePath(snapshot.path)}`, ...snapshot.issues.map((issue) => {
return ` - ${issue.path ? `${issue.path}: ` : ""}${issue.message}`;
})].join("\n");
}
function createNoExitRuntime(runtime) {
return {
...runtime,
exit: (code) => {
throw new Error(`operation exited with code ${code}`);
}
};
}
async function resolveTuiAgentId(params) {
const overview = await loadOverviewForOperation(params.deps);
const workspace = params.requestedWorkspace ? resolveUserPath(params.requestedWorkspace) : void 0;
if (workspace) {
const workspaceMatch = overview.agents.find((agent) => {
return agent.workspace ? resolveUserPath(agent.workspace) === workspace : false;
});
if (workspaceMatch) return workspaceMatch.id;
}
if (!params.requestedAgentId?.trim()) return overview.defaultAgentId;
const requested = normalizeAgentId(params.requestedAgentId);
return overview.agents.find((agent) => {
return normalizeAgentId(agent.id) === requested || (agent.name ? normalizeAgentId(agent.name) === requested : false);
})?.id ?? requested;
}
/** Execute a parsed Crestodian operation after applying approval gates and audit logging. */
async function executeCrestodianOperation(operation, runtime, opts = {}) {
if (operation.kind === "none") {
runtime.log(operation.message);
return {
applied: false,
exitsInteractive: operation.message.includes("Bye.")
};
}
if (operation.kind === "overview") {
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(await formatOverviewForOperation(overview, opts.deps));
return { applied: false };
}
if (operation.kind === "agents") {
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(["Agents:", ...overview.agents.map((agent) => {
return ` - ${[
agent.id,
agent.isDefault ? "default" : void 0,
agent.name ? `name=${agent.name}` : void 0,
agent.workspace ? `workspace=${shortenHomePath(resolveUserPath(agent.workspace))}` : void 0
].filter(Boolean).join(" | ")}`;
})].join("\n"));
return { applied: false };
}
if (operation.kind === "models") {
const overview = await loadOverviewForOperation(opts.deps);
runtime.log([
`Default model: ${overview.defaultModel ?? "not configured"}`,
`Codex: ${overview.tools.codex.found ? "found" : "not found"}`,
`Claude Code: ${overview.tools.claude.found ? "found" : "not found"}`,
`OpenAI key: ${overview.tools.apiKeys.openai ? "found" : "not found"}`,
`Anthropic key: ${overview.tools.apiKeys.anthropic ? "found" : "not found"}`
].join("\n"));
return { applied: false };
}
if (operation.kind === "plugin-list") {
logQueued(runtime, "plugins.list");
await (opts.deps?.runPluginsList ?? (async (pluginRuntime) => {
const { runPluginsListCommand } = await import("./plugins-list-command-DTntEbuI.js");
await runPluginsListCommand({}, pluginRuntime);
}))(runtime);
runtime.log("[crestodian] done: plugins.list");
return { applied: false };
}
if (operation.kind === "plugin-search") {
logQueued(runtime, "plugins.search");
await (opts.deps?.runPluginsSearch ?? (async (query, pluginRuntime) => {
const { runPluginsSearchCommand } = await import("./plugins-search-command-C_J1DMEQ.js");
await runPluginsSearchCommand(query, {}, pluginRuntime);
}))(operation.query, runtime);
runtime.log("[crestodian] done: plugins.search");
return { applied: false };
}
if (operation.kind === "audit") {
runtime.log(`Audit log: ${resolveCrestodianAuditPath()}`);
runtime.log("Only applied writes/actions are recorded; discovery stays quiet.");
return { applied: false };
}
if (operation.kind === "config-validate") {
const snapshot = await readConfigFileSnapshotLazy();
runtime.log(formatConfigValidationLine(snapshot));
return { applied: false };
}
if (operation.kind === "setup") {
const overview = await loadOverviewForOperation(opts.deps);
const setupModel = chooseSetupModel(overview, operation.model);
if (!opts.approved) {
const message = [formatCrestodianPersistentPlan(operation), setupModel.model ? `Model choice: ${setupModel.model} (${setupModel.source}).` : setupModel.source === "existing default model" ? `Model choice: keep existing default ${overview.defaultModel}.` : "Model choice: none found yet. I will only set the workspace; install/login Codex or Claude Code, or set OPENAI_API_KEY/ANTHROPIC_API_KEY, then run setup again."].join("\n");
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "crestodian.setup");
const { mutateConfigFile, readConfigFileSnapshot } = await loadConfigFileMutationHelpers();
const before = await readConfigFileSnapshot();
const workspace = resolveUserPath(operation.workspace ?? process.cwd());
const applyDefaultModelPrimaryUpdate = setupModel.model ? (await loadModelsSharedModule()).applyDefaultModelPrimaryUpdate : void 0;
const result = await mutateConfigFile({
base: "source",
mutate: (cfg) => {
let next = cfg;
if (setupModel.model && applyDefaultModelPrimaryUpdate) next = applyDefaultModelPrimaryUpdate({
cfg: next,
modelRaw: setupModel.model,
field: "model"
});
next = {
...next,
agents: {
...next.agents,
defaults: {
...next.agents?.defaults,
workspace
}
}
};
Object.assign(cfg, next);
}
});
const after = await readConfigFileSnapshot();
await appendCrestodianAuditEntry({
operation: "crestodian.setup",
summary: setupModel.model ? `Bootstrapped setup with ${setupModel.model}` : "Bootstrapped setup workspace",
configPath: result.path,
configHashBefore: before.hash ?? result.previousHash,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
workspace,
modelSource: setupModel.source,
...setupModel.model ? { model: setupModel.model } : {}
}
});
runtime.log(`Updated ${result.path}`);
runtime.log(`Workspace: ${shortenHomePath(workspace)}`);
if (setupModel.model) runtime.log(`Default model: ${setupModel.model} (${setupModel.source})`);
else if (overview.defaultModel) runtime.log(`Default model: ${overview.defaultModel} (kept)`);
else runtime.log("Default model: not configured yet");
runtime.log("[crestodian] done: crestodian.setup");
return { applied: true };
}
if (operation.kind === "config-set") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "config.set");
const { readConfigFileSnapshot } = await loadConfigModule();
const before = await readConfigFileSnapshot();
await (opts.deps?.runConfigSet ?? (async (setOpts) => {
const { runConfigSet: importedRunConfigSet } = await loadConfigCliModule();
await importedRunConfigSet({
...setOpts,
runtime: createNoExitRuntime(runtime)
});
}))({
path: operation.path,
value: operation.value,
cliOptions: {}
});
const after = await readConfigFileSnapshot();
await appendCrestodianAuditEntry({
operation: "config.set",
summary: `Set config ${operation.path}`,
configPath: after.path || before.path || void 0,
configHashBefore: before.hash ?? null,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
path: operation.path
}
});
runtime.log("[crestodian] done: config.set");
return { applied: true };
}
if (operation.kind === "config-set-ref") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "config.setRef");
const { readConfigFileSnapshot } = await loadConfigModule();
const before = await readConfigFileSnapshot();
await (opts.deps?.runConfigSet ?? (async (setOpts) => {
const { runConfigSet: importedRunConfigSet } = await loadConfigCliModule();
await importedRunConfigSet({
...setOpts,
runtime: createNoExitRuntime(runtime)
});
}))({
path: operation.path,
cliOptions: {
refProvider: operation.provider ?? "default",
refSource: operation.source,
refId: operation.id
}
});
const after = await readConfigFileSnapshot();
await appendCrestodianAuditEntry({
operation: "config.setRef",
summary: `Set config ${operation.path} SecretRef`,
configPath: after.path || before.path || void 0,
configHashBefore: before.hash ?? null,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
path: operation.path,
source: operation.source,
provider: operation.provider ?? "default"
}
});
runtime.log("[crestodian] done: config.setRef");
return { applied: true };
}
if (operation.kind === "plugin-install") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
const validationError = validateCrestodianPluginInstallSpec(operation.spec);
if (validationError) {
runtime.error(validationError);
runtime.exit(1);
return { applied: false };
}
logQueued(runtime, "plugin.install");
const before = await readConfigFileSnapshotLazy();
await (opts.deps?.runPluginInstall ?? (async (spec, pluginRuntime) => {
const { runPluginInstallCommand } = await import("./plugins-install-command-4srI9WVU.js");
await runPluginInstallCommand({
raw: spec,
opts: {},
runtime: pluginRuntime
});
}))(operation.spec, createNoExitRuntime(runtime));
const after = await readConfigFileSnapshotLazy();
await appendCrestodianAuditEntry({
operation: "plugin.install",
summary: `Installed plugin ${operation.spec}`,
configPath: after.path || before.path || void 0,
configHashBefore: before.hash ?? null,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
spec: operation.spec
}
});
runtime.log("[crestodian] done: plugin.install");
runtime.log("Restart the Gateway to apply installed plugin changes.");
return { applied: true };
}
if (operation.kind === "plugin-uninstall") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "plugin.uninstall");
const before = await readConfigFileSnapshotLazy();
await (opts.deps?.runPluginUninstall ?? (async (pluginId, pluginRuntime) => {
const { runPluginUninstallCommand } = await import("./plugins-uninstall-command-CciI5qPZ.js");
await runPluginUninstallCommand(pluginId, { force: true }, pluginRuntime);
}))(operation.pluginId, createNoExitRuntime(runtime));
const after = await readConfigFileSnapshotLazy();
await appendCrestodianAuditEntry({
operation: "plugin.uninstall",
summary: `Uninstalled plugin ${operation.pluginId}`,
configPath: after.path || before.path || void 0,
configHashBefore: before.hash ?? null,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
pluginId: operation.pluginId
}
});
runtime.log("[crestodian] done: plugin.uninstall");
runtime.log("Restart the Gateway to apply plugin changes.");
return { applied: true };
}
if (operation.kind === "create-agent") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "agents.create");
const { readConfigFileSnapshot } = await loadConfigModule();
const before = await readConfigFileSnapshot();
const workspace = resolveUserPath(operation.workspace ?? process.cwd());
await (opts.deps?.runAgentsAdd ?? (await import("./agents.commands.add-_8XCCPCg.js")).agentsAddCommand)({
name: operation.agentId,
workspace,
...operation.model ? { model: operation.model } : {},
nonInteractive: true
}, runtime, { hasFlags: true });
const after = await readConfigFileSnapshot();
await appendCrestodianAuditEntry({
operation: "agents.create",
summary: `Created agent ${operation.agentId}`,
configPath: after.path || before.path || void 0,
configHashBefore: before.hash ?? null,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
agentId: operation.agentId,
workspace,
...operation.model ? { model: operation.model } : {}
}
});
runtime.log("[crestodian] done: agents.create");
return { applied: true };
}
if (operation.kind === "doctor") {
logQueued(runtime, "doctor");
await (opts.deps?.runDoctor ?? (await loadDoctorModule()).doctorCommand)(runtime, { nonInteractive: true });
runtime.log("[crestodian] done: doctor");
return { applied: false };
}
if (operation.kind === "doctor-fix") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "doctor.fix");
const { readConfigFileSnapshot } = await loadConfigModule();
const before = await readConfigFileSnapshot();
await (opts.deps?.runDoctor ?? (await loadDoctorModule()).doctorCommand)(runtime, {
nonInteractive: true,
repair: true,
yes: true
});
const after = await readConfigFileSnapshot();
await appendCrestodianAuditEntry({
operation: "doctor.fix",
summary: "Ran doctor repairs",
configPath: after.path || before.path || void 0,
configHashBefore: before.hash ?? null,
configHashAfter: after.hash ?? null,
details: opts.auditDetails
});
runtime.log("[crestodian] done: doctor.fix");
return { applied: true };
}
if (operation.kind === "status") {
logQueued(runtime, "status.check");
const { statusCommand } = await import("./status.command-BIWnckoj.js");
await statusCommand({ timeoutMs: 1e4 }, runtime);
runtime.log("[crestodian] done: status.check");
return { applied: false };
}
if (operation.kind === "health") {
logQueued(runtime, "health.check");
const { healthCommand } = await import("./health-B9v-C93Q.js");
await healthCommand({ timeoutMs: 1e4 }, runtime);
runtime.log("[crestodian] done: health.check");
return { applied: false };
}
if (operation.kind === "gateway-status") {
const overview = await loadOverviewForOperation(opts.deps);
runtime.log(formatGatewayStatusLine(overview));
return { applied: false };
}
if (operation.kind === "gateway-start") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "gateway.start");
await (opts.deps?.runGatewayStart ?? (() => runGatewayLifecycle("start")))();
await appendCrestodianAuditEntry({
operation: "gateway.start",
summary: "Started Gateway",
details: opts.auditDetails
});
runtime.log("[crestodian] done: gateway.start");
return { applied: true };
}
if (operation.kind === "gateway-stop") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "gateway.stop");
await (opts.deps?.runGatewayStop ?? (() => runGatewayLifecycle("stop")))();
await appendCrestodianAuditEntry({
operation: "gateway.stop",
summary: "Stopped Gateway",
details: opts.auditDetails
});
runtime.log("[crestodian] done: gateway.stop");
return { applied: true };
}
if (operation.kind === "gateway-restart") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "gateway.restart");
await (opts.deps?.runGatewayRestart ?? (() => runGatewayLifecycle("restart")))();
await appendCrestodianAuditEntry({
operation: "gateway.restart",
summary: "Restarted Gateway",
details: opts.auditDetails
});
runtime.log("[crestodian] done: gateway.restart");
return { applied: true };
}
if (operation.kind === "open-tui") {
logQueued(runtime, "tui.open");
const agentId = await resolveTuiAgentId({
requestedAgentId: operation.agentId,
requestedWorkspace: operation.workspace,
deps: opts.deps
});
const session = agentId ? buildAgentMainSessionKey({ agentId }) : void 0;
const result = await (opts.deps?.runTui ?? (await import("./tui-C57_V_VY.js")).runTui)({
local: true,
session,
deliver: false,
historyLimit: 200
});
if (result?.exitReason === "return-to-crestodian") {
runtime.log(result.crestodianMessage ? `[crestodian] returned from agent with request: ${result.crestodianMessage}` : "[crestodian] returned from agent");
return {
applied: false,
nextInput: result.crestodianMessage
};
}
return {
applied: false,
exitsInteractive: true
};
}
if (operation.kind === "set-default-model") {
if (!opts.approved) {
const message = formatCrestodianPersistentPlan(operation);
runtime.log(message);
return {
applied: false,
message
};
}
logQueued(runtime, "config.setDefaultModel");
const { mutateConfigFile, readConfigFileSnapshot } = await loadConfigFileMutationHelpers();
const before = await readConfigFileSnapshot();
const { applyDefaultModelPrimaryUpdate } = await loadModelsSharedModule();
const result = await mutateConfigFile({
base: "source",
mutate: (cfg) => {
const next = applyDefaultModelPrimaryUpdate({
cfg,
modelRaw: operation.model,
field: "model"
});
Object.assign(cfg, next);
}
});
const after = await readConfigFileSnapshot();
const { resolveAgentModelPrimaryValue } = await import("./model-input-CTwgL4Oh.js");
const effectiveModel = resolveAgentModelPrimaryValue(result.nextConfig.agents?.defaults?.model);
await appendCrestodianAuditEntry({
operation: "config.setDefaultModel",
summary: `Set default model to ${operation.model}`,
configPath: result.path,
configHashBefore: before.hash ?? result.previousHash,
configHashAfter: after.hash ?? null,
details: {
...opts.auditDetails,
requestedModel: operation.model,
effectiveModel
}
});
runtime.log(`Updated ${result.path}`);
runtime.log(`Default model: ${effectiveModel ?? operation.model}`);
runtime.log("[crestodian] done: config.setDefaultModel");
return { applied: true };
}
return { applied: false };
}
//#endregion
export { parseCrestodianOperation as a, isPersistentCrestodianOperation as i, executeCrestodianOperation as n, formatCrestodianPersistentPlan as r, describeCrestodianPersistentOperation as t };