fix(cron): add cron edit --clear-model

Completes the CLI half of #91298.
This commit is contained in:
ly-wang19
2026-06-15 23:54:00 +08:00
committed by GitHub
parent 794bd89fa0
commit 0888debcd3
4 changed files with 47 additions and 2 deletions

View File

@@ -157,6 +157,9 @@ If stdout is non-empty, that text is the delivered result. If stdout is empty an
<ParamField path="--model" type="string">
Model override; uses the selected allowed model for the job.
</ParamField>
<ParamField path="--clear-model" type="boolean">
On `cron edit`, removes the per-job model override so the job follows normal cron model-selection precedence (a stored cron-session override if set, otherwise the agent/default model). Cannot be combined with `--model`.
</ParamField>
<ParamField path="--thinking" type="string">
Thinking level override.
</ParamField>
@@ -471,6 +474,7 @@ Model override note:
- If the model is allowed, that exact provider/model reaches the isolated agent run.
- If it is not allowed or cannot be resolved, cron fails the run with an explicit validation error.
- API `cron.update` payload patches can set `model: null` to clear a stored job model override.
- `openclaw cron edit <job-id> --clear-model` clears that override from the CLI (same effect as the `model: null` patch) and cannot be combined with `--model`.
- Configured fallback chains still apply because cron `--model` is a job primary, not a session `/model` override.
- Payload `fallbacks` replaces configured fallbacks for that job; `fallbacks: []` disables fallback and makes the run strict.
- A plain `--model` with no explicit or configured fallback list does not fall through to the agent primary as a silent extra retry target.

View File

@@ -168,7 +168,7 @@ Use `--due` when you want the manual command to run only if the job is currently
## Models
`cron add|edit --model <ref>` selects an allowed model for the job.
`cron add|edit --model <ref>` selects an allowed model for the job. `cron edit <job-id> --clear-model` removes the per-job model override so the job follows normal cron model-selection precedence (a stored cron-session override if present, otherwise the agent/default model); it cannot be combined with `--model`.
<Warning>
If the model is not allowed or cannot be resolved, cron fails the run with an explicit validation error instead of falling back to the job's agent or default model selection.

View File

@@ -200,4 +200,32 @@ describe("cron edit command", () => {
},
);
});
it("clears the model override with --clear-model (CLI parity with cron.update model:null)", async () => {
const program = createCronProgram();
await program.parseAsync(["edit", "job-1", "--clear-model"], { from: "user" });
expect(callGatewayFromCli).toHaveBeenCalledWith(
"cron.update",
expect.objectContaining({ clearModel: true }),
{
id: "job-1",
patch: {
payload: {
kind: "agentTurn",
model: null,
},
},
},
);
});
it("documents the --clear-model flag alongside the sibling --clear-tools", () => {
const editCommand = createCronProgram().commands.find((command) => command.name() === "edit");
const help = editCommand?.helpInformation() ?? "";
expect(help).toContain("--clear-model");
expect(help).toContain("--clear-tools");
});
});

View File

@@ -115,6 +115,11 @@ export function registerCronEditCommand(cron: Command) {
"Thinking level for agent jobs (off|minimal|low|medium|high|xhigh)",
)
.option("--model <model>", "Model override for agent jobs")
.option(
"--clear-model",
"Remove the per-job model override (restore normal cron model precedence)",
false,
)
.option("--timeout-seconds <n>", "Timeout seconds for agent or command jobs")
.option("--no-output-timeout-seconds <n>", "No-output timeout seconds for command jobs")
.option("--output-max-bytes <n>", "Maximum captured stdout/stderr bytes for command jobs")
@@ -279,6 +284,9 @@ export function registerCronEditCommand(cron: Command) {
);
}
const model = normalizeOptionalString(opts.model);
if (model && opts.clearModel) {
throw new Error("Use --model or --clear-model, not both");
}
const thinking = normalizeOptionalString(opts.thinking);
const toolsAllow = parseCronToolsAllow(opts.tools);
const rawTimeoutSeconds =
@@ -346,6 +354,7 @@ export function registerCronEditCommand(cron: Command) {
const hasAgentTurnPayloadField =
typeof opts.message === "string" ||
Boolean(model) ||
Boolean(opts.clearModel) ||
Boolean(thinking) ||
(hasTimeoutSeconds &&
!hasCommandSpecificPayloadField &&
@@ -373,7 +382,11 @@ export function registerCronEditCommand(cron: Command) {
} else if (hasAgentTurnPatch) {
const payload: Record<string, unknown> = { kind: "agentTurn" };
assignIf(payload, "message", String(opts.message), typeof opts.message === "string");
assignIf(payload, "model", model, Boolean(model));
if (opts.clearModel) {
payload.model = null;
} else {
assignIf(payload, "model", model, Boolean(model));
}
assignIf(payload, "thinking", thinking, Boolean(thinking));
assignIf(payload, "timeoutSeconds", timeoutSeconds, hasTimeoutSeconds);
assignIf(