mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
refactor: split Crestodian planner backend selection
This commit is contained in:
@@ -24,40 +24,47 @@ docsRefs:
|
||||
- docs/help/testing.md
|
||||
codeRefs:
|
||||
- src/crestodian/operations.ts
|
||||
- scripts/e2e/crestodian-first-run-spec.json
|
||||
- scripts/e2e/crestodian-first-run-docker-client.ts
|
||||
- extensions/qa-lab/src/suite-runtime-agent-process.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Drive the public Crestodian CLI in an isolated fresh state dir and verify setup/model/agent/Discord/audit results.
|
||||
config:
|
||||
stateDirName: crestodian-ring-zero-state
|
||||
defaultWorkspaceName: crestodian-main-workspace
|
||||
agentWorkspaceName: crestodian-reef-workspace
|
||||
agentId: reef
|
||||
model: openai/gpt-5.2
|
||||
discordEnv: DISCORD_BOT_TOKEN
|
||||
discordToken: openclaw-crestodian-qa-discord-token
|
||||
specPath: scripts/e2e/crestodian-first-run-spec.json
|
||||
```
|
||||
|
||||
```yaml qa-flow
|
||||
steps:
|
||||
- name: bootstraps config through Crestodian CLI
|
||||
actions:
|
||||
- set: setupSpec
|
||||
value:
|
||||
expr: "JSON.parse(await fs.readFile(path.join(env.repoRoot, config.specPath), 'utf8'))"
|
||||
- set: stateDir
|
||||
value:
|
||||
expr: "path.join(env.gateway.tempRoot, config.stateDirName)"
|
||||
expr: "path.join(env.gateway.tempRoot, setupSpec.stateDirName)"
|
||||
- set: configPath
|
||||
value:
|
||||
expr: "path.join(stateDir, 'openclaw.json')"
|
||||
- set: defaultWorkspace
|
||||
value:
|
||||
expr: "path.join(env.gateway.tempRoot, config.defaultWorkspaceName)"
|
||||
expr: "path.join(env.gateway.tempRoot, setupSpec.defaultWorkspaceName)"
|
||||
- set: agentWorkspace
|
||||
value:
|
||||
expr: "path.join(env.gateway.tempRoot, config.agentWorkspaceName)"
|
||||
expr: "path.join(env.gateway.tempRoot, setupSpec.agentWorkspaceName)"
|
||||
- set: commandVars
|
||||
value:
|
||||
expr: "({ defaultWorkspace, agentWorkspace, agentId: setupSpec.agentId, model: setupSpec.model, discordEnv: setupSpec.discordEnv })"
|
||||
- set: renderCommand
|
||||
value:
|
||||
lambda:
|
||||
params:
|
||||
- template
|
||||
expr: "String(template).replace(/\\{([A-Za-z0-9_]+)\\}/g, (match, key) => String(commandVars[key] ?? match))"
|
||||
- set: crestodianEnv
|
||||
value:
|
||||
expr: "({ OPENCLAW_STATE_DIR: stateDir, OPENCLAW_CONFIG_PATH: configPath, OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(env.repoRoot, 'dist', 'extensions'), [config.discordEnv]: config.discordToken })"
|
||||
expr: "({ OPENCLAW_STATE_DIR: stateDir, OPENCLAW_CONFIG_PATH: configPath, OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(env.repoRoot, 'dist', 'extensions'), [setupSpec.discordEnv]: setupSpec.discordToken })"
|
||||
- call: fs.rm
|
||||
args:
|
||||
- ref: stateDir
|
||||
@@ -85,141 +92,39 @@ steps:
|
||||
expr: 'String(overviewOutput).includes(''Next: run "setup" to create a starter config'')'
|
||||
message:
|
||||
expr: "`fresh Crestodian overview did not recommend setup: ${overviewOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: setupOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- expr: "`setup workspace ${defaultWorkspace} model ${config.model}`"
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(setupOutput).includes('[crestodian] done: crestodian.setup')"
|
||||
message:
|
||||
expr: "`Crestodian setup did not apply: ${setupOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: modelOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- expr: "`set default model ${config.model}`"
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(modelOutput).includes('[crestodian] done: config.setDefaultModel')"
|
||||
message:
|
||||
expr: "`Crestodian model update did not apply: ${modelOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: agentOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- expr: "`create agent ${config.agentId} workspace ${agentWorkspace} model ${config.model}`"
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(agentOutput).includes('[crestodian] done: agents.create')"
|
||||
message:
|
||||
expr: "`Crestodian agent creation did not apply: ${agentOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: discordPluginAllowOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- config set plugins.allow ["discord"]
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(discordPluginAllowOutput).includes('[crestodian] done: config.set')"
|
||||
message:
|
||||
expr: "`Crestodian Discord plugin allowlist did not apply: ${discordPluginAllowOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: discordPluginEntryOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- config set plugins.entries.discord.enabled true
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(discordPluginEntryOutput).includes('[crestodian] done: config.set')"
|
||||
message:
|
||||
expr: "`Crestodian Discord plugin entry did not apply: ${discordPluginEntryOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: discordTokenOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- expr: "`config set-ref channels.discord.token env ${config.discordEnv}`"
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(discordTokenOutput).includes('[crestodian] done: config.setRef')"
|
||||
message:
|
||||
expr: "`Crestodian Discord SecretRef did not apply: ${discordTokenOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: discordEnabledOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- --yes
|
||||
- -m
|
||||
- config set channels.discord.enabled true
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(discordEnabledOutput).includes('[crestodian] done: config.set')"
|
||||
message:
|
||||
expr: "`Crestodian Discord enable did not apply: ${discordEnabledOutput}`"
|
||||
- call: runQaCli
|
||||
saveAs: validationOutput
|
||||
args:
|
||||
- ref: env
|
||||
- - crestodian
|
||||
- -m
|
||||
- validate config
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(validationOutput).includes('Config valid:')"
|
||||
message:
|
||||
expr: "`Crestodian config validation did not pass: ${validationOutput}`"
|
||||
- forEach:
|
||||
items:
|
||||
ref: setupSpec.commands
|
||||
item: commandStep
|
||||
actions:
|
||||
- call: runQaCli
|
||||
saveAs: commandOutput
|
||||
args:
|
||||
- ref: env
|
||||
- expr: "['crestodian', ...(commandStep.approve ? ['--yes'] : []), '-m', renderCommand(commandStep.message)]"
|
||||
- timeoutMs: 60000
|
||||
env:
|
||||
ref: crestodianEnv
|
||||
- assert:
|
||||
expr: "String(commandOutput).includes(commandStep.expectOutput)"
|
||||
message:
|
||||
expr: "`Crestodian command ${commandStep.id} did not produce ${commandStep.expectOutput}: ${commandOutput}`"
|
||||
- set: writtenConfig
|
||||
value:
|
||||
expr: "JSON.parse(await fs.readFile(configPath, 'utf8'))"
|
||||
- set: agent
|
||||
value:
|
||||
expr: "writtenConfig.agents?.list?.find((candidate) => candidate.id === config.agentId)"
|
||||
expr: "writtenConfig.agents?.list?.find((candidate) => candidate.id === setupSpec.agentId)"
|
||||
- assert:
|
||||
expr: "writtenConfig.agents?.defaults?.workspace === defaultWorkspace"
|
||||
message:
|
||||
expr: "`default workspace mismatch: ${JSON.stringify(writtenConfig.agents?.defaults)}`"
|
||||
- assert:
|
||||
expr: "writtenConfig.agents?.defaults?.model?.primary === config.model"
|
||||
expr: "writtenConfig.agents?.defaults?.model?.primary === setupSpec.model"
|
||||
message:
|
||||
expr: "`default model mismatch: ${JSON.stringify(writtenConfig.agents?.defaults?.model)}`"
|
||||
- assert:
|
||||
expr: "agent?.workspace === agentWorkspace && agent?.model === config.model"
|
||||
expr: "agent?.workspace === agentWorkspace && agent?.model === setupSpec.model"
|
||||
message:
|
||||
expr: "`agent config mismatch: ${JSON.stringify(agent)}`"
|
||||
- assert:
|
||||
@@ -231,22 +136,18 @@ steps:
|
||||
message:
|
||||
expr: "`Discord was not enabled: ${JSON.stringify(writtenConfig.channels?.discord)}`"
|
||||
- assert:
|
||||
expr: "writtenConfig.channels?.discord?.token?.source === 'env' && writtenConfig.channels?.discord?.token?.id === config.discordEnv"
|
||||
expr: "writtenConfig.channels?.discord?.token?.source === 'env' && writtenConfig.channels?.discord?.token?.id === setupSpec.discordEnv"
|
||||
message:
|
||||
expr: "`Discord token was not an env SecretRef: ${JSON.stringify(writtenConfig.channels?.discord?.token)}`"
|
||||
- assert:
|
||||
expr: "!JSON.stringify(writtenConfig.channels?.discord ?? {}).includes(config.discordToken)"
|
||||
expr: "!JSON.stringify(writtenConfig.channels?.discord ?? {}).includes(setupSpec.discordToken)"
|
||||
message: Crestodian persisted the raw Discord token.
|
||||
- set: auditText
|
||||
value:
|
||||
expr: "await fs.readFile(path.join(stateDir, 'audit', 'crestodian.jsonl'), 'utf8')"
|
||||
- forEach:
|
||||
items:
|
||||
- crestodian.setup
|
||||
- config.setDefaultModel
|
||||
- agents.create
|
||||
- config.setRef
|
||||
- config.set
|
||||
ref: setupSpec.auditOperations
|
||||
item: operation
|
||||
actions:
|
||||
- assert:
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
84
src/crestodian/assistant-backends.ts
Normal file
84
src/crestodian/assistant-backends.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { CrestodianOverview } from "./overview.js";
|
||||
|
||||
export const CRESTODIAN_CLAUDE_CLI_MODEL = "claude-opus-4-7";
|
||||
export const CRESTODIAN_CODEX_MODEL = "gpt-5.5";
|
||||
|
||||
export type CrestodianLocalPlannerBackend = {
|
||||
kind: "claude-cli" | "codex-app-server" | "codex-cli";
|
||||
label: string;
|
||||
runner: "cli" | "embedded";
|
||||
provider: string;
|
||||
model: string;
|
||||
buildConfig: (workspaceDir: string) => OpenClawConfig;
|
||||
};
|
||||
|
||||
const CLAUDE_CLI_BACKEND: CrestodianLocalPlannerBackend = {
|
||||
kind: "claude-cli",
|
||||
label: `claude-cli/${CRESTODIAN_CLAUDE_CLI_MODEL}`,
|
||||
runner: "cli",
|
||||
provider: "claude-cli",
|
||||
model: CRESTODIAN_CLAUDE_CLI_MODEL,
|
||||
buildConfig: (workspaceDir) =>
|
||||
buildCliPlannerConfig(workspaceDir, `claude-cli/${CRESTODIAN_CLAUDE_CLI_MODEL}`),
|
||||
};
|
||||
|
||||
const CODEX_APP_SERVER_BACKEND: CrestodianLocalPlannerBackend = {
|
||||
kind: "codex-app-server",
|
||||
label: `openai/${CRESTODIAN_CODEX_MODEL} via codex`,
|
||||
runner: "embedded",
|
||||
provider: "openai",
|
||||
model: CRESTODIAN_CODEX_MODEL,
|
||||
buildConfig: buildCodexAppServerPlannerConfig,
|
||||
};
|
||||
|
||||
const CODEX_CLI_BACKEND: CrestodianLocalPlannerBackend = {
|
||||
kind: "codex-cli",
|
||||
label: `codex-cli/${CRESTODIAN_CODEX_MODEL}`,
|
||||
runner: "cli",
|
||||
provider: "codex-cli",
|
||||
model: CRESTODIAN_CODEX_MODEL,
|
||||
buildConfig: (workspaceDir) =>
|
||||
buildCliPlannerConfig(workspaceDir, `codex-cli/${CRESTODIAN_CODEX_MODEL}`),
|
||||
};
|
||||
|
||||
export function selectCrestodianLocalPlannerBackends(
|
||||
overview: CrestodianOverview,
|
||||
): CrestodianLocalPlannerBackend[] {
|
||||
const backends: CrestodianLocalPlannerBackend[] = [];
|
||||
if (overview.tools.claude.found) {
|
||||
backends.push(CLAUDE_CLI_BACKEND);
|
||||
}
|
||||
if (overview.tools.codex.found) {
|
||||
backends.push(CODEX_APP_SERVER_BACKEND, CODEX_CLI_BACKEND);
|
||||
}
|
||||
return backends;
|
||||
}
|
||||
|
||||
function buildCliPlannerConfig(workspaceDir: string, modelRef: string): OpenClawConfig {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
model: { primary: modelRef },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildCodexAppServerPlannerConfig(workspaceDir: string): OpenClawConfig {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
embeddedHarness: { runtime: "codex", fallback: "none" },
|
||||
model: { primary: `openai/${CRESTODIAN_CODEX_MODEL}` },
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
codex: { enabled: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
122
src/crestodian/assistant-prompts.ts
Normal file
122
src/crestodian/assistant-prompts.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { CrestodianOverview } from "./overview.js";
|
||||
|
||||
export const CRESTODIAN_ASSISTANT_TIMEOUT_MS = 10_000;
|
||||
export const CRESTODIAN_ASSISTANT_MAX_TOKENS = 512;
|
||||
|
||||
export const CRESTODIAN_ASSISTANT_SYSTEM_PROMPT = [
|
||||
"You are Crestodian, OpenClaw's ring-zero setup helper.",
|
||||
"Turn the user's request into exactly one safe OpenClaw Crestodian command.",
|
||||
"Return only compact JSON with keys reply and command.",
|
||||
"Do not invent commands. Do not claim a write was applied.",
|
||||
"Do not use tools, shell commands, file edits, or network lookups; plan only from the supplied overview.",
|
||||
"Use the provided OpenClaw docs/source references when the user's request needs behavior, config, or architecture details.",
|
||||
"If local source is available, prefer inspecting it. Otherwise point to GitHub and strongly recommend reviewing source when docs are not enough.",
|
||||
"Allowed commands:",
|
||||
"- setup",
|
||||
"- status",
|
||||
"- health",
|
||||
"- doctor",
|
||||
"- doctor fix",
|
||||
"- gateway status",
|
||||
"- restart gateway",
|
||||
"- start gateway",
|
||||
"- stop gateway",
|
||||
"- agents",
|
||||
"- models",
|
||||
"- audit",
|
||||
"- validate config",
|
||||
"- set default model <provider/model>",
|
||||
"- config set <path> <value>",
|
||||
"- config set-ref <path> env <ENV_VAR>",
|
||||
"- create agent <id> workspace <path> model <provider/model>",
|
||||
"- talk to <id> agent",
|
||||
"- talk to agent",
|
||||
"If unsure, choose overview.",
|
||||
].join("\n");
|
||||
|
||||
export type CrestodianAssistantPlan = {
|
||||
command: string;
|
||||
reply?: string;
|
||||
modelLabel?: string;
|
||||
};
|
||||
|
||||
export function buildCrestodianAssistantUserPrompt(params: {
|
||||
input: string;
|
||||
overview: CrestodianOverview;
|
||||
}): string {
|
||||
const agents = params.overview.agents
|
||||
.map((agent) => {
|
||||
const fields = [
|
||||
`id=${agent.id}`,
|
||||
agent.name ? `name=${agent.name}` : undefined,
|
||||
agent.workspace ? `workspace=${agent.workspace}` : undefined,
|
||||
agent.model ? `model=${agent.model}` : undefined,
|
||||
agent.isDefault ? "default=true" : undefined,
|
||||
].filter(Boolean);
|
||||
return `- ${fields.join(", ")}`;
|
||||
})
|
||||
.join("\n");
|
||||
return [
|
||||
`User request: ${params.input}`,
|
||||
"",
|
||||
`Default agent: ${params.overview.defaultAgentId}`,
|
||||
`Default model: ${params.overview.defaultModel ?? "not configured"}`,
|
||||
`Config valid: ${params.overview.config.valid}`,
|
||||
`Gateway reachable: ${params.overview.gateway.reachable}`,
|
||||
`Codex CLI: ${params.overview.tools.codex.found ? "found" : "not found"}`,
|
||||
`Claude Code CLI: ${params.overview.tools.claude.found ? "found" : "not found"}`,
|
||||
`OpenAI API key: ${params.overview.tools.apiKeys.openai ? "found" : "not found"}`,
|
||||
`Anthropic API key: ${params.overview.tools.apiKeys.anthropic ? "found" : "not found"}`,
|
||||
`OpenClaw docs: ${params.overview.references.docsPath ?? params.overview.references.docsUrl}`,
|
||||
`OpenClaw source: ${
|
||||
params.overview.references.sourcePath ?? params.overview.references.sourceUrl
|
||||
}`,
|
||||
params.overview.references.sourcePath
|
||||
? "Source mode: local git checkout; inspect source directly when docs are insufficient."
|
||||
: "Source mode: package/install; use GitHub source when docs are insufficient.",
|
||||
"",
|
||||
"Agents:",
|
||||
agents || "- none",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function parseCrestodianAssistantPlanText(
|
||||
rawText: string | undefined,
|
||||
): CrestodianAssistantPlan | null {
|
||||
const text = rawText?.trim();
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const jsonText = extractFirstJsonObject(text);
|
||||
if (!jsonText) {
|
||||
return null;
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(jsonText);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
return null;
|
||||
}
|
||||
const record = parsed as Record<string, unknown>;
|
||||
const command = typeof record.command === "string" ? record.command.trim() : "";
|
||||
if (!command) {
|
||||
return null;
|
||||
}
|
||||
const reply = typeof record.reply === "string" ? record.reply.trim() : undefined;
|
||||
return {
|
||||
command,
|
||||
...(reply ? { reply } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function extractFirstJsonObject(text: string): string | null {
|
||||
const start = text.indexOf("{");
|
||||
const end = text.lastIndexOf("}");
|
||||
if (start < 0 || end <= start) {
|
||||
return null;
|
||||
}
|
||||
return text.slice(start, end + 1);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import type { RunCliAgentParams } from "../agents/cli-runner/types.js";
|
||||
import type { RunEmbeddedPiAgentParams } from "../agents/pi-embedded-runner/run/params.js";
|
||||
import type { EmbeddedPiRunResult } from "../agents/pi-embedded.js";
|
||||
import { selectCrestodianLocalPlannerBackends } from "./assistant-backends.js";
|
||||
import {
|
||||
buildCrestodianAssistantUserPrompt,
|
||||
planCrestodianCommandWithLocalRuntime,
|
||||
@@ -139,6 +140,41 @@ describe("Crestodian assistant", () => {
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects local planner backends without execution state", () => {
|
||||
expect(
|
||||
selectCrestodianLocalPlannerBackends(
|
||||
overview({
|
||||
claude: { command: "claude", found: true },
|
||||
codex: { command: "codex", found: true },
|
||||
}),
|
||||
).map((backend) => backend.kind),
|
||||
).toEqual(["claude-cli", "codex-app-server", "codex-cli"]);
|
||||
|
||||
const [codexAppServer, codexCli] = selectCrestodianLocalPlannerBackends(
|
||||
overview({
|
||||
codex: { command: "codex", found: true },
|
||||
}),
|
||||
);
|
||||
expect(codexAppServer?.buildConfig("/tmp/workspace")).toMatchObject({
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "/tmp/workspace",
|
||||
embeddedHarness: { runtime: "codex", fallback: "none" },
|
||||
model: { primary: "openai/gpt-5.5" },
|
||||
},
|
||||
},
|
||||
plugins: { entries: { codex: { enabled: true } } },
|
||||
});
|
||||
expect(codexCli?.buildConfig("/tmp/workspace")).toMatchObject({
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "/tmp/workspace",
|
||||
model: { primary: "codex-cli/gpt-5.5" },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to Codex app-server when Claude CLI planning fails", async () => {
|
||||
const runCliAgent = vi.fn(async () => {
|
||||
throw new Error("claude unavailable");
|
||||
|
||||
@@ -9,50 +9,22 @@ import {
|
||||
prepareSimpleCompletionModelForAgent,
|
||||
} from "../agents/simple-completion-runtime.js";
|
||||
import { readConfigFileSnapshot } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { selectCrestodianLocalPlannerBackends } from "./assistant-backends.js";
|
||||
import {
|
||||
CRESTODIAN_ASSISTANT_MAX_TOKENS,
|
||||
CRESTODIAN_ASSISTANT_SYSTEM_PROMPT,
|
||||
CRESTODIAN_ASSISTANT_TIMEOUT_MS,
|
||||
buildCrestodianAssistantUserPrompt,
|
||||
parseCrestodianAssistantPlanText,
|
||||
type CrestodianAssistantPlan,
|
||||
} from "./assistant-prompts.js";
|
||||
import type { CrestodianOverview } from "./overview.js";
|
||||
|
||||
const CRESTODIAN_ASSISTANT_TIMEOUT_MS = 10_000;
|
||||
const CRESTODIAN_ASSISTANT_MAX_TOKENS = 512;
|
||||
const CRESTODIAN_CLAUDE_CLI_MODEL = "claude-opus-4-7";
|
||||
const CRESTODIAN_CODEX_MODEL = "gpt-5.5";
|
||||
|
||||
const CRESTODIAN_ASSISTANT_SYSTEM_PROMPT = [
|
||||
"You are Crestodian, OpenClaw's ring-zero setup helper.",
|
||||
"Turn the user's request into exactly one safe OpenClaw Crestodian command.",
|
||||
"Return only compact JSON with keys reply and command.",
|
||||
"Do not invent commands. Do not claim a write was applied.",
|
||||
"Do not use tools, shell commands, file edits, or network lookups; plan only from the supplied overview.",
|
||||
"Use the provided OpenClaw docs/source references when the user's request needs behavior, config, or architecture details.",
|
||||
"If local source is available, prefer inspecting it. Otherwise point to GitHub and strongly recommend reviewing source when docs are not enough.",
|
||||
"Allowed commands:",
|
||||
"- setup",
|
||||
"- status",
|
||||
"- health",
|
||||
"- doctor",
|
||||
"- doctor fix",
|
||||
"- gateway status",
|
||||
"- restart gateway",
|
||||
"- start gateway",
|
||||
"- stop gateway",
|
||||
"- agents",
|
||||
"- models",
|
||||
"- audit",
|
||||
"- validate config",
|
||||
"- set default model <provider/model>",
|
||||
"- config set <path> <value>",
|
||||
"- config set-ref <path> env <ENV_VAR>",
|
||||
"- create agent <id> workspace <path> model <provider/model>",
|
||||
"- talk to <id> agent",
|
||||
"- talk to agent",
|
||||
"If unsure, choose overview.",
|
||||
].join("\n");
|
||||
|
||||
export type CrestodianAssistantPlan = {
|
||||
command: string;
|
||||
reply?: string;
|
||||
modelLabel?: string;
|
||||
};
|
||||
export {
|
||||
buildCrestodianAssistantUserPrompt,
|
||||
parseCrestodianAssistantPlanText,
|
||||
type CrestodianAssistantPlan,
|
||||
} from "./assistant-prompts.js";
|
||||
|
||||
export type CrestodianAssistantPlanner = (params: {
|
||||
input: string;
|
||||
@@ -69,8 +41,6 @@ export type CrestodianLocalRuntimePlannerDeps = {
|
||||
removeTempDir?: (dir: string) => Promise<void>;
|
||||
};
|
||||
|
||||
type LocalPlannerCandidate = "claude-cli" | "codex-app-server" | "codex-cli";
|
||||
|
||||
export async function planCrestodianCommand(params: {
|
||||
input: string;
|
||||
overview: CrestodianOverview;
|
||||
@@ -154,8 +124,8 @@ export async function planCrestodianCommandWithLocalRuntime(params: {
|
||||
if (!input) {
|
||||
return null;
|
||||
}
|
||||
const candidates = listLocalRuntimePlannerCandidates(params.overview);
|
||||
if (candidates.length === 0) {
|
||||
const backends = selectCrestodianLocalPlannerBackends(params.overview);
|
||||
if (backends.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const prompt = buildCrestodianAssistantUserPrompt({
|
||||
@@ -163,9 +133,9 @@ export async function planCrestodianCommandWithLocalRuntime(params: {
|
||||
overview: params.overview,
|
||||
});
|
||||
|
||||
for (const candidate of candidates) {
|
||||
for (const backend of backends) {
|
||||
try {
|
||||
const rawText = await runLocalRuntimePlanner(candidate, {
|
||||
const rawText = await runLocalRuntimePlanner(backend, {
|
||||
prompt,
|
||||
deps: params.deps,
|
||||
});
|
||||
@@ -173,7 +143,7 @@ export async function planCrestodianCommandWithLocalRuntime(params: {
|
||||
if (parsed) {
|
||||
return {
|
||||
...parsed,
|
||||
modelLabel: localRuntimePlannerLabel(candidate),
|
||||
modelLabel: backend.label,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
@@ -183,109 +153,8 @@ export async function planCrestodianCommandWithLocalRuntime(params: {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function buildCrestodianAssistantUserPrompt(params: {
|
||||
input: string;
|
||||
overview: CrestodianOverview;
|
||||
}): string {
|
||||
const agents = params.overview.agents
|
||||
.map((agent) => {
|
||||
const fields = [
|
||||
`id=${agent.id}`,
|
||||
agent.name ? `name=${agent.name}` : undefined,
|
||||
agent.workspace ? `workspace=${agent.workspace}` : undefined,
|
||||
agent.model ? `model=${agent.model}` : undefined,
|
||||
agent.isDefault ? "default=true" : undefined,
|
||||
].filter(Boolean);
|
||||
return `- ${fields.join(", ")}`;
|
||||
})
|
||||
.join("\n");
|
||||
return [
|
||||
`User request: ${params.input}`,
|
||||
"",
|
||||
`Default agent: ${params.overview.defaultAgentId}`,
|
||||
`Default model: ${params.overview.defaultModel ?? "not configured"}`,
|
||||
`Config valid: ${params.overview.config.valid}`,
|
||||
`Gateway reachable: ${params.overview.gateway.reachable}`,
|
||||
`Codex CLI: ${params.overview.tools.codex.found ? "found" : "not found"}`,
|
||||
`Claude Code CLI: ${params.overview.tools.claude.found ? "found" : "not found"}`,
|
||||
`OpenAI API key: ${params.overview.tools.apiKeys.openai ? "found" : "not found"}`,
|
||||
`Anthropic API key: ${params.overview.tools.apiKeys.anthropic ? "found" : "not found"}`,
|
||||
`OpenClaw docs: ${params.overview.references.docsPath ?? params.overview.references.docsUrl}`,
|
||||
`OpenClaw source: ${
|
||||
params.overview.references.sourcePath ?? params.overview.references.sourceUrl
|
||||
}`,
|
||||
params.overview.references.sourcePath
|
||||
? "Source mode: local git checkout; inspect source directly when docs are insufficient."
|
||||
: "Source mode: package/install; use GitHub source when docs are insufficient.",
|
||||
"",
|
||||
"Agents:",
|
||||
agents || "- none",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function parseCrestodianAssistantPlanText(
|
||||
rawText: string | undefined,
|
||||
): CrestodianAssistantPlan | null {
|
||||
const text = rawText?.trim();
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const jsonText = extractFirstJsonObject(text);
|
||||
if (!jsonText) {
|
||||
return null;
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(jsonText);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
return null;
|
||||
}
|
||||
const record = parsed as Record<string, unknown>;
|
||||
const command = typeof record.command === "string" ? record.command.trim() : "";
|
||||
if (!command) {
|
||||
return null;
|
||||
}
|
||||
const reply = typeof record.reply === "string" ? record.reply.trim() : undefined;
|
||||
return {
|
||||
command,
|
||||
...(reply ? { reply } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function extractFirstJsonObject(text: string): string | null {
|
||||
const start = text.indexOf("{");
|
||||
const end = text.lastIndexOf("}");
|
||||
if (start < 0 || end <= start) {
|
||||
return null;
|
||||
}
|
||||
return text.slice(start, end + 1);
|
||||
}
|
||||
|
||||
function listLocalRuntimePlannerCandidates(overview: CrestodianOverview): LocalPlannerCandidate[] {
|
||||
const candidates: LocalPlannerCandidate[] = [];
|
||||
if (overview.tools.claude.found) {
|
||||
candidates.push("claude-cli");
|
||||
}
|
||||
if (overview.tools.codex.found) {
|
||||
candidates.push("codex-app-server", "codex-cli");
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function localRuntimePlannerLabel(candidate: LocalPlannerCandidate): string {
|
||||
const labels: Record<LocalPlannerCandidate, string> = {
|
||||
"claude-cli": `claude-cli/${CRESTODIAN_CLAUDE_CLI_MODEL}`,
|
||||
"codex-app-server": `openai/${CRESTODIAN_CODEX_MODEL} via codex`,
|
||||
"codex-cli": `codex-cli/${CRESTODIAN_CODEX_MODEL}`,
|
||||
};
|
||||
return labels[candidate];
|
||||
}
|
||||
|
||||
async function runLocalRuntimePlanner(
|
||||
candidate: LocalPlannerCandidate,
|
||||
backend: ReturnType<typeof selectCrestodianLocalPlannerBackends>[number],
|
||||
params: {
|
||||
prompt: string;
|
||||
deps?: CrestodianLocalRuntimePlannerDeps;
|
||||
@@ -297,8 +166,8 @@ async function runLocalRuntimePlanner(
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const sessionId = `${runId}-session`;
|
||||
const sessionKey = `temp:crestodian-planner:${runId}`;
|
||||
switch (candidate) {
|
||||
case "claude-cli": {
|
||||
switch (backend.runner) {
|
||||
case "cli": {
|
||||
const runCli = params.deps?.runCliAgent ?? (await loadRunCliAgent());
|
||||
const result = await runCli({
|
||||
sessionId,
|
||||
@@ -307,10 +176,10 @@ async function runLocalRuntimePlanner(
|
||||
trigger: "manual",
|
||||
sessionFile,
|
||||
workspaceDir: tempDir,
|
||||
config: buildCliPlannerConfig(tempDir, `claude-cli/${CRESTODIAN_CLAUDE_CLI_MODEL}`),
|
||||
config: backend.buildConfig(tempDir),
|
||||
prompt: params.prompt,
|
||||
provider: "claude-cli",
|
||||
model: CRESTODIAN_CLAUDE_CLI_MODEL,
|
||||
provider: backend.provider,
|
||||
model: backend.model,
|
||||
timeoutMs: CRESTODIAN_ASSISTANT_TIMEOUT_MS,
|
||||
runId,
|
||||
extraSystemPrompt: CRESTODIAN_ASSISTANT_SYSTEM_PROMPT,
|
||||
@@ -322,7 +191,7 @@ async function runLocalRuntimePlanner(
|
||||
});
|
||||
return extractPlannerResultText(result);
|
||||
}
|
||||
case "codex-app-server": {
|
||||
case "embedded": {
|
||||
const runEmbedded = params.deps?.runEmbeddedPiAgent ?? (await loadRunEmbeddedPiAgent());
|
||||
const result = await runEmbedded({
|
||||
sessionId,
|
||||
@@ -331,10 +200,10 @@ async function runLocalRuntimePlanner(
|
||||
trigger: "manual",
|
||||
sessionFile,
|
||||
workspaceDir: tempDir,
|
||||
config: buildCodexAppServerPlannerConfig(tempDir),
|
||||
config: backend.buildConfig(tempDir),
|
||||
prompt: params.prompt,
|
||||
provider: "openai",
|
||||
model: CRESTODIAN_CODEX_MODEL,
|
||||
provider: backend.provider,
|
||||
model: backend.model,
|
||||
agentHarnessId: "codex",
|
||||
disableTools: true,
|
||||
toolsAllow: [],
|
||||
@@ -348,30 +217,6 @@ async function runLocalRuntimePlanner(
|
||||
});
|
||||
return extractPlannerResultText(result);
|
||||
}
|
||||
case "codex-cli": {
|
||||
const runCli = params.deps?.runCliAgent ?? (await loadRunCliAgent());
|
||||
const result = await runCli({
|
||||
sessionId,
|
||||
sessionKey,
|
||||
agentId: "crestodian",
|
||||
trigger: "manual",
|
||||
sessionFile,
|
||||
workspaceDir: tempDir,
|
||||
config: buildCliPlannerConfig(tempDir, `codex-cli/${CRESTODIAN_CODEX_MODEL}`),
|
||||
prompt: params.prompt,
|
||||
provider: "codex-cli",
|
||||
model: CRESTODIAN_CODEX_MODEL,
|
||||
timeoutMs: CRESTODIAN_ASSISTANT_TIMEOUT_MS,
|
||||
runId,
|
||||
extraSystemPrompt: CRESTODIAN_ASSISTANT_SYSTEM_PROMPT,
|
||||
extraSystemPromptStatic: CRESTODIAN_ASSISTANT_SYSTEM_PROMPT,
|
||||
messageChannel: "crestodian",
|
||||
messageProvider: "crestodian",
|
||||
senderIsOwner: true,
|
||||
cleanupCliLiveSessionOnRunEnd: true,
|
||||
});
|
||||
return extractPlannerResultText(result);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
} finally {
|
||||
@@ -379,34 +224,6 @@ async function runLocalRuntimePlanner(
|
||||
}
|
||||
}
|
||||
|
||||
function buildCliPlannerConfig(workspaceDir: string, modelRef: string): OpenClawConfig {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
model: { primary: modelRef },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildCodexAppServerPlannerConfig(workspaceDir: string): OpenClawConfig {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
embeddedHarness: { runtime: "codex", fallback: "none" },
|
||||
model: { primary: `openai/${CRESTODIAN_CODEX_MODEL}` },
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
codex: { enabled: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function createTempPlannerDir(): Promise<string> {
|
||||
return await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-crestodian-planner-"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user