mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:10:49 +00:00
refactor: split Crestodian planner backend selection
This commit is contained in:
@@ -7,6 +7,24 @@ import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
||||
import { runCrestodian } from "../../src/crestodian/crestodian.js";
|
||||
import type { RuntimeEnv } from "../../src/runtime.js";
|
||||
|
||||
type CrestodianFirstRunCommand = {
|
||||
id: string;
|
||||
message: string;
|
||||
expectOutput: string;
|
||||
approve: boolean;
|
||||
};
|
||||
|
||||
type CrestodianFirstRunSpec = {
|
||||
dockerDefaultWorkspace: string;
|
||||
dockerAgentWorkspace: string;
|
||||
agentId: string;
|
||||
model: string;
|
||||
discordEnv: string;
|
||||
discordToken: string;
|
||||
commands: CrestodianFirstRunCommand[];
|
||||
auditOperations: string[];
|
||||
};
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
@@ -27,7 +45,21 @@ function createRuntime(): { runtime: RuntimeEnv; lines: string[] } {
|
||||
};
|
||||
}
|
||||
|
||||
async function readFirstRunSpec(): Promise<CrestodianFirstRunSpec> {
|
||||
return JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(process.cwd(), "scripts", "e2e", "crestodian-first-run-spec.json"),
|
||||
"utf8",
|
||||
),
|
||||
) as CrestodianFirstRunSpec;
|
||||
}
|
||||
|
||||
function renderCommandTemplate(template: string, vars: Record<string, string>): string {
|
||||
return template.replace(/\{([A-Za-z0-9_]+)\}/g, (match, key: string) => vars[key] ?? match);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const spec = await readFirstRunSpec();
|
||||
const stateDir =
|
||||
process.env.OPENCLAW_STATE_DIR ??
|
||||
(await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-crestodian-first-run-")));
|
||||
@@ -61,137 +93,49 @@ async function main() {
|
||||
"fresh overview did not include setup recommendation",
|
||||
);
|
||||
|
||||
process.env.DISCORD_BOT_TOKEN = "openclaw-crestodian-discord-e2e-token";
|
||||
process.env[spec.discordEnv] = spec.discordToken;
|
||||
|
||||
const setupRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: "setup workspace /tmp/openclaw-first-run model openai/gpt-5.2",
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
setupRuntime.runtime,
|
||||
);
|
||||
const setupOutput = setupRuntime.lines.join("\n");
|
||||
assert(
|
||||
setupOutput.includes("[crestodian] done: crestodian.setup"),
|
||||
"Crestodian setup did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const modelRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: "set default model openai/gpt-5.2",
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
modelRuntime.runtime,
|
||||
);
|
||||
assert(
|
||||
modelRuntime.lines.join("\n").includes("[crestodian] done: config.setDefaultModel"),
|
||||
"Crestodian default model update did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const agentRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: "create agent reef workspace /tmp/openclaw-reef model openai/gpt-5.2",
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
agentRuntime.runtime,
|
||||
);
|
||||
assert(
|
||||
agentRuntime.lines.join("\n").includes("[crestodian] done: agents.create"),
|
||||
"Crestodian agent creation did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const discordPluginAllowRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: 'config set plugins.allow ["discord"]',
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
discordPluginAllowRuntime.runtime,
|
||||
);
|
||||
assert(
|
||||
discordPluginAllowRuntime.lines.join("\n").includes("[crestodian] done: config.set"),
|
||||
"Crestodian Discord plugin allowlist did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const discordPluginEntryRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: "config set plugins.entries.discord.enabled true",
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
discordPluginEntryRuntime.runtime,
|
||||
);
|
||||
assert(
|
||||
discordPluginEntryRuntime.lines.join("\n").includes("[crestodian] done: config.set"),
|
||||
"Crestodian Discord plugin entry did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const discordTokenRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: "config set-ref channels.discord.token env DISCORD_BOT_TOKEN",
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
discordTokenRuntime.runtime,
|
||||
);
|
||||
assert(
|
||||
discordTokenRuntime.lines.join("\n").includes("[crestodian] done: config.setRef"),
|
||||
"Crestodian Discord token SecretRef did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const discordEnabledRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: "config set channels.discord.enabled true",
|
||||
yes: true,
|
||||
interactive: false,
|
||||
},
|
||||
discordEnabledRuntime.runtime,
|
||||
);
|
||||
assert(
|
||||
discordEnabledRuntime.lines.join("\n").includes("[crestodian] done: config.set"),
|
||||
"Crestodian Discord enabled flag did not apply",
|
||||
);
|
||||
|
||||
clearConfigCache();
|
||||
const validateRuntime = createRuntime();
|
||||
await runCrestodian({ message: "validate config", interactive: false }, validateRuntime.runtime);
|
||||
assert(
|
||||
validateRuntime.lines.join("\n").includes("Config valid:"),
|
||||
"post-setup config validation did not pass",
|
||||
);
|
||||
const commandVars = {
|
||||
defaultWorkspace: spec.dockerDefaultWorkspace,
|
||||
agentWorkspace: spec.dockerAgentWorkspace,
|
||||
agentId: spec.agentId,
|
||||
model: spec.model,
|
||||
discordEnv: spec.discordEnv,
|
||||
};
|
||||
for (const command of spec.commands) {
|
||||
clearConfigCache();
|
||||
const commandRuntime = createRuntime();
|
||||
await runCrestodian(
|
||||
{
|
||||
message: renderCommandTemplate(command.message, commandVars),
|
||||
yes: command.approve,
|
||||
interactive: false,
|
||||
},
|
||||
commandRuntime.runtime,
|
||||
);
|
||||
const output = commandRuntime.lines.join("\n");
|
||||
assert(
|
||||
output.includes(command.expectOutput),
|
||||
`Crestodian first-run command ${command.id} did not apply: ${output}`,
|
||||
);
|
||||
}
|
||||
|
||||
const config = JSON.parse(await fs.readFile(configPath, "utf8")) as OpenClawConfig;
|
||||
assert(
|
||||
config.agents?.defaults?.workspace === "/tmp/openclaw-first-run",
|
||||
config.agents?.defaults?.workspace === spec.dockerDefaultWorkspace,
|
||||
"first-run setup did not write default workspace",
|
||||
);
|
||||
assert(
|
||||
config.agents?.defaults?.model &&
|
||||
typeof config.agents.defaults.model === "object" &&
|
||||
"primary" in config.agents.defaults.model &&
|
||||
config.agents.defaults.model.primary === "openai/gpt-5.2",
|
||||
config.agents.defaults.model.primary === spec.model,
|
||||
"first-run setup did not write default model",
|
||||
);
|
||||
const reef = config.agents?.list?.find((agent) => agent.id === "reef");
|
||||
const reef = config.agents?.list?.find((agent) => agent.id === spec.agentId);
|
||||
assert(reef, "Crestodian did not create reef agent");
|
||||
assert(reef.workspace === "/tmp/openclaw-reef", "Crestodian did not write reef workspace");
|
||||
assert(reef.model === "openai/gpt-5.2", "Crestodian did not write reef model");
|
||||
assert(reef.workspace === spec.dockerAgentWorkspace, "Crestodian did not write reef workspace");
|
||||
assert(reef.model === spec.model, "Crestodian did not write reef model");
|
||||
assert(config.plugins?.allow?.includes("discord"), "Crestodian did not allow Discord plugin");
|
||||
assert(
|
||||
config.plugins?.entries?.discord?.enabled === true,
|
||||
@@ -205,24 +149,19 @@ async function main() {
|
||||
"source" in discordToken &&
|
||||
discordToken.source === "env" &&
|
||||
"id" in discordToken &&
|
||||
discordToken.id === "DISCORD_BOT_TOKEN",
|
||||
discordToken.id === spec.discordEnv,
|
||||
"Crestodian did not write Discord token SecretRef",
|
||||
);
|
||||
assert(
|
||||
!JSON.stringify(config.channels.discord).includes(process.env.DISCORD_BOT_TOKEN),
|
||||
!JSON.stringify(config.channels.discord).includes(spec.discordToken),
|
||||
"Crestodian persisted the raw Discord token",
|
||||
);
|
||||
|
||||
const auditPath = path.join(stateDir, "audit", "crestodian.jsonl");
|
||||
const audit = (await fs.readFile(auditPath, "utf8")).trim();
|
||||
assert(audit.includes('"operation":"crestodian.setup"'), "setup audit entry missing");
|
||||
assert(
|
||||
audit.includes('"operation":"config.setDefaultModel"'),
|
||||
"default model audit entry missing",
|
||||
);
|
||||
assert(audit.includes('"operation":"agents.create"'), "agent creation audit entry missing");
|
||||
assert(audit.includes('"operation":"config.setRef"'), "Discord SecretRef audit entry missing");
|
||||
assert(audit.includes('"operation":"config.set"'), "Discord enabled audit entry missing");
|
||||
for (const operation of spec.auditOperations) {
|
||||
assert(audit.includes(`"operation":"${operation}"`), `${operation} audit entry missing`);
|
||||
}
|
||||
|
||||
console.log("Crestodian first-run Docker E2E passed");
|
||||
}
|
||||
|
||||
68
scripts/e2e/crestodian-first-run-spec.json
Normal file
68
scripts/e2e/crestodian-first-run-spec.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"stateDirName": "crestodian-ring-zero-state",
|
||||
"defaultWorkspaceName": "crestodian-main-workspace",
|
||||
"agentWorkspaceName": "crestodian-reef-workspace",
|
||||
"dockerDefaultWorkspace": "/tmp/openclaw-first-run",
|
||||
"dockerAgentWorkspace": "/tmp/openclaw-reef",
|
||||
"agentId": "reef",
|
||||
"model": "openai/gpt-5.2",
|
||||
"discordEnv": "DISCORD_BOT_TOKEN",
|
||||
"discordToken": "openclaw-crestodian-discord-e2e-token",
|
||||
"commands": [
|
||||
{
|
||||
"id": "setup",
|
||||
"message": "setup workspace {defaultWorkspace} model {model}",
|
||||
"expectOutput": "[crestodian] done: crestodian.setup",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "default-model",
|
||||
"message": "set default model {model}",
|
||||
"expectOutput": "[crestodian] done: config.setDefaultModel",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "agent",
|
||||
"message": "create agent {agentId} workspace {agentWorkspace} model {model}",
|
||||
"expectOutput": "[crestodian] done: agents.create",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "discord-plugin-allow",
|
||||
"message": "config set plugins.allow [\"discord\"]",
|
||||
"expectOutput": "[crestodian] done: config.set",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "discord-plugin-entry",
|
||||
"message": "config set plugins.entries.discord.enabled true",
|
||||
"expectOutput": "[crestodian] done: config.set",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "discord-token",
|
||||
"message": "config set-ref channels.discord.token env {discordEnv}",
|
||||
"expectOutput": "[crestodian] done: config.setRef",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "discord-enabled",
|
||||
"message": "config set channels.discord.enabled true",
|
||||
"expectOutput": "[crestodian] done: config.set",
|
||||
"approve": true
|
||||
},
|
||||
{
|
||||
"id": "validate",
|
||||
"message": "validate config",
|
||||
"expectOutput": "Config valid:",
|
||||
"approve": false
|
||||
}
|
||||
],
|
||||
"auditOperations": [
|
||||
"crestodian.setup",
|
||||
"config.setDefaultModel",
|
||||
"agents.create",
|
||||
"config.setRef",
|
||||
"config.set"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user