diff --git a/skills/ce-work/scripts/cross-model-work.sh b/skills/ce-work/scripts/cross-model-work.sh index df8abf884e..cf364a8317 100755 --- a/skills/ce-work/scripts/cross-model-work.sh +++ b/skills/ce-work/scripts/cross-model-work.sh @@ -95,14 +95,32 @@ validate_model_override() { esac } +validate_effort_override() { + # Same per-route allowlists as the ce-code-review / ce-doc-review peer paths: + # reject a tier the selected route cannot honor instead of forwarding it to a + # CLI that will fail the attempt after controller authorization. Routes with + # no effort knob (cursor, composer, grok-cursor) reject any override. + local route="$1" effort="${CROSS_MODEL_EFFORT_OVERRIDE:-}" + [ -n "$effort" ] || return 0 + case "$route:$effort" in + claude:low|claude:medium|claude:high|claude:xhigh|claude:max) ;; + codex:minimal|codex:low|codex:medium|codex:high|codex:xhigh) ;; + grok-cli:low|grok-cli:medium|grok-cli:high) ;; + opencode:none|opencode:minimal|opencode:low|opencode:medium|opencode:high|opencode:xhigh|opencode:max|opencode:default) ;; + *) return 1 ;; + esac +} + adapter_argv() { case "$1" in codex) # --ignore-user-config drops the user's model_reasoning_effort, so pin the # editorial tier explicitly, matching the claude/grok routes' --effort high. + # CROSS_MODEL_EFFORT_OVERRIDE retunes all three effort-taking routes, the + # same knob the ce-code-review / ce-doc-review peer paths honor. printf '%s\0' codex exec --ignore-user-config --ignore-rules --ephemeral \ -s workspace-write -C "$WORKSPACE" --json -o "$RAW_RESULT" \ - -c model_reasoning_effort=high + -c model_reasoning_effort="${CROSS_MODEL_EFFORT_OVERRIDE:-high}" [ "$(route_model codex)" = auto ] || printf '%s\0' -m "$(route_model codex)" printf '%s\0' - ;; @@ -112,14 +130,14 @@ adapter_argv() { printf '%s\0' claude -p --safe-mode --no-session-persistence \ --permission-mode bypassPermissions --tools Read,Write,Edit,Bash \ --allowed-tools 'Bash(*)' \ - --effort high --output-format stream-json --verbose + --effort "${CROSS_MODEL_EFFORT_OVERRIDE:-high}" --output-format stream-json --verbose [ "$claude_model" = auto ] || printf '%s\0' --model "$claude_model" ;; grok-cli) local grok_model grok_model="$(route_model grok-cli)" printf '%s\0' grok --prompt-file "$PROMPT_FILE" --cwd "$WORKSPACE" \ - --effort high --permission-mode acceptEdits \ + --effort "${CROSS_MODEL_EFFORT_OVERRIDE:-high}" --permission-mode acceptEdits \ --tools Read,Write,Edit --disable-web-search --no-memory --no-subagents \ --no-plan --max-turns 50 --output-format streaming-json --verbatim [ "$grok_model" = auto ] || printf '%s\0' --model "$grok_model" @@ -143,6 +161,8 @@ adapter_argv() { printf '%s\0' opencode run --dir "$WORKSPACE" --format json --auto --file "$PROMPT_FILE" printf '%s\0' "Follow the attached unit packet. Return only the implementation result JSON." [ "$(route_model opencode)" = auto ] || printf '%s\0' --model "$(route_model opencode)" + # OpenCode carries effort through --variant, same as the review adapters. + [ -z "${CROSS_MODEL_EFFORT_OVERRIDE:-}" ] || printf '%s\0' --variant "$CROSS_MODEL_EFFORT_OVERRIDE" ;; *) return 1 ;; esac @@ -157,6 +177,10 @@ if [ "${1:-}" = "--emit-adapter" ]; then printf "model override '%s' not compatible with route '%s'\n" "${CE_WORK_MODEL_OVERRIDE:-}" "$ROUTE" >&2 exit 2 } + validate_effort_override "$ROUTE" || { + printf "effort override '%s' not compatible with route '%s'\n" "${CROSS_MODEL_EFFORT_OVERRIDE:-}" "$ROUTE" >&2 + exit 2 + } adapter_argv "$ROUTE" >/dev/null 2>&1 || { printf "unknown route '%s'\n" "$ROUTE" >&2; exit 2; } adapter_argv "$ROUTE" | tr '\0' ' ' printf '\n' @@ -692,6 +716,11 @@ if ! command -v "$BINARY" >/dev/null 2>&1; then exit 2 fi +validate_effort_override "$ROUTE" || { + publish_unavailable "effort override '${CROSS_MODEL_EFFORT_OVERRIDE:-}' not compatible with route '$ROUTE'" || exit 2 + exit 2 +} + ARGS=() while IFS= read -r -d '' token; do ARGS+=("$token"); done < <(adapter_argv "$ROUTE") diff --git a/tests/skills/ce-work-cross-model-routes.test.ts b/tests/skills/ce-work-cross-model-routes.test.ts index 50acd24aad..e26cea6698 100644 --- a/tests/skills/ce-work-cross-model-routes.test.ts +++ b/tests/skills/ce-work-cross-model-routes.test.ts @@ -21,6 +21,11 @@ import { createHash } from "node:crypto" setDefaultTimeout(20_000) +// cross-model-work.sh honors CROSS_MODEL_EFFORT_OVERRIDE; make a clean +// environment the suite-wide default so an ambient export cannot leak into +// baseline assertions. Tests that exercise the override set it explicitly. +delete process.env.CROSS_MODEL_EFFORT_OVERRIDE + const SCRIPT = path.join(process.cwd(), "skills/ce-work/scripts/cross-model-work.sh") const CONTROLLER = path.join(process.cwd(), "skills/ce-work/scripts/unit-workspace.py") const SCHEMA = path.join(process.cwd(), "skills/ce-work/references/implementation-result-schema.json") @@ -221,18 +226,26 @@ function emit(route: string, env: NodeJS.ProcessEnv = process.env) { return spawnSync("bash", [SCRIPT, "--emit-adapter", route], { encoding: "utf8", env }) } +// The script honors CROSS_MODEL_EFFORT_OVERRIDE, so default-posture assertions +// must not inherit an ambient override from the suite's own environment. +function cleanEnv(): NodeJS.ProcessEnv { + const env = { ...process.env } + delete env.CROSS_MODEL_EFFORT_OVERRIDE + return env +} + describe("ce-work fixed write routes", () => { test("production argv uses the qualified noninteractive write posture", () => { - for (const route of ROUTES) expect(emit(route).status).toBe(0) + for (const route of ROUTES) expect(emit(route, cleanEnv()).status).toBe(0) - const codex = emit("codex").stdout + const codex = emit("codex", cleanEnv()).stdout expect(codex).toContain("exec") expect(codex).toContain("--ephemeral") expect(codex).toContain("-s workspace-write") expect(codex).toContain("-C ") expect(codex).toContain("-c model_reasoning_effort=high") - const claude = emit("claude").stdout + const claude = emit("claude", cleanEnv()).stdout expect(claude).toContain("--safe-mode") expect(claude).toContain("--permission-mode bypassPermissions") expect(claude).toContain("--tools Read,Write,Edit,Bash") @@ -240,7 +253,7 @@ describe("ce-work fixed write routes", () => { expect(claude).toContain("--no-session-persistence") expect(claude).not.toContain("--model") - const grok = emit("grok-cli").stdout + const grok = emit("grok-cli", cleanEnv()).stdout expect(grok).toContain("--cwd ") expect(grok).toContain("--permission-mode acceptEdits") expect(grok).toContain("--no-memory") @@ -248,15 +261,15 @@ describe("ce-work fixed write routes", () => { expect(grok).not.toContain("--model") for (const route of ["cursor", "composer", "grok-cursor"]) { - const command = emit(route).stdout + const command = emit(route, cleanEnv()).stdout expect(command).toContain("--sandbox enabled") expect(command).toContain("--workspace ") expect(command).toContain("--output-format stream-json") } - expect(emit("cursor").stdout).not.toContain("--model") - expect(emit("composer").stdout).toContain("--model composer-2.5-fast") - expect(emit("grok-cursor").stdout).toContain("--model cursor-grok-4.6-high") - const opencode = emit("opencode").stdout + expect(emit("cursor", cleanEnv()).stdout).not.toContain("--model") + expect(emit("composer", cleanEnv()).stdout).toContain("--model composer-2.5-fast") + expect(emit("grok-cursor", cleanEnv()).stdout).toContain("--model cursor-grok-4.6-high") + const opencode = emit("opencode", cleanEnv()).stdout expect(opencode).toContain("opencode run") expect(opencode).toContain("--dir ") expect(opencode).toContain("--format json") @@ -265,6 +278,48 @@ describe("ce-work fixed write routes", () => { expect(opencode).not.toContain("--model") }) + test("CROSS_MODEL_EFFORT_OVERRIDE retunes the effort-taking routes and stays off by default", () => { + const withOverride = (route: string, value: string) => + emit(route, { ...cleanEnv(), CROSS_MODEL_EFFORT_OVERRIDE: value }) + + expect(emit("codex", cleanEnv()).stdout).toContain("-c model_reasoning_effort=high") + expect(withOverride("codex", "xhigh").stdout).toContain("-c model_reasoning_effort=xhigh") + expect(withOverride("codex", "minimal").stdout).toContain("-c model_reasoning_effort=minimal") + + expect(emit("claude", cleanEnv()).stdout).toContain("--effort high") + expect(withOverride("claude", "low").stdout).toContain("--effort low") + expect(withOverride("claude", "max").stdout).toContain("--effort max") + + expect(emit("grok-cli", cleanEnv()).stdout).toContain("--effort high") + expect(withOverride("grok-cli", "medium").stdout).toContain("--effort medium") + }) + + test("CROSS_MODEL_EFFORT_OVERRIDE rejects tiers the route cannot honor, failing closed before dispatch", () => { + const rejected = (route: string, value: string) => { + const proc = emit(route, { ...cleanEnv(), CROSS_MODEL_EFFORT_OVERRIDE: value }) + expect(proc.status).toBe(2) + expect(proc.stderr).toContain(`effort override '${value}' not compatible with route '${route}'`) + } + + rejected("codex", "max") // codex tops out at xhigh + rejected("codex", "none") + rejected("claude", "minimal") + rejected("grok-cli", "xhigh") + rejected("grok-cli", "max") + // routes with no effort knob reject any override rather than silently ignoring it + rejected("cursor", "high") + rejected("composer", "high") + rejected("grok-cursor", "high") + // opencode is effort-bearing through --variant, but only for its own enum + rejected("opencode", "bogus") + }) + + test("opencode carries the override through --variant, matching the review adapters", () => { + const out = emit("opencode", { ...cleanEnv(), CROSS_MODEL_EFFORT_OVERRIDE: "max" }).stdout + expect(out).toContain("--variant max") + expect(emit("opencode", cleanEnv()).stdout).not.toContain("--variant") + }) + test.each(ROUTES)("%s receives one workspace and bounded packet", (route) => { const f = fixture() const bin = fakeBin(route, f.capture)