fix(daemon): guard runTask with per-model thinking_level validator (MUL-2339)

ValidateThinkingLevel existed but had no call site — `task.Agent.
ThinkingLevel` flowed straight into ExecOptions, so `xhigh` configured
on a non-Opus Claude model, or API-side stale values that escaped the
provider enum gate, would be injected anyway.

Run the validator before building ExecOptions. Invalid combinations
log a warning and drop the level instead of failing the task: the
agent still runs, just at the runtime's default reasoning effort.
Discovery errors fail open (keep the level, let the CLI surface any
objection) so a transient `claude --help` failure can't strand work.

Empty model is forwarded as-is; the validator resolves it to the
provider's default model internally per the cross-package contract.

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Jiang Bohan
2026-05-19 18:00:43 +08:00
parent 63a3bf334b
commit 8b790dfcd8

View File

@@ -2439,6 +2439,34 @@ func (d *Daemon) runTask(ctx context.Context, task Task, provider string, slot i
if task.Agent != nil {
thinkingLevel = task.Agent.ThinkingLevel
}
// Per-model guard: the server validates the literal token against the
// provider's enum, but per-model gaps (Claude's `xhigh` on a non-Opus
// model, Codex's per-model `supported_reasoning_levels`) only resolve
// here, against the daemon's local CLI catalog. Invalid combinations
// log a warning and drop the level rather than failing the task, so a
// stale persisted value never blocks execution. Empty model is passed
// through unchanged — ValidateThinkingLevel resolves it to the
// provider's default model internally so default-model tasks aren't
// misjudged. Discovery errors fail open: if we can't list models, we
// keep the persisted level and let the CLI surface any objection.
if thinkingLevel != "" {
ok, err := agent.ValidateThinkingLevel(ctx, provider, entry.Path, model, thinkingLevel)
if err != nil {
taskLog.Warn("thinking_level: catalog lookup failed; passing through",
"provider", provider,
"model", model,
"thinking_level", thinkingLevel,
"error", err,
)
} else if !ok {
taskLog.Warn("thinking_level: not valid for this (provider, model); skipping injection",
"provider", provider,
"model", model,
"thinking_level", thinkingLevel,
)
thinkingLevel = ""
}
}
execOpts := agent.ExecOptions{
Cwd: env.WorkDir,
Model: model,