feat: enable automatic Code Mode for preferred models (#155614)

* feat: enable automatic Code Mode for preferred models

* test: preserve parser boundary and automatic default expectations

* ci: refresh merge proof after upstream tooling type repair

Refresh the merge ref after main restored the tsx CLI shim declaration in 80b9608a25. No source changes.
This commit is contained in:
Peter Steinberger 2026-09-22 04:19:15 -07:00 • committed by GitHub
parent 6ba6f0c7a7
commit d5e6038577
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 348 additions and 276 deletions

View file

@ -158,7 +158,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
### Code Mode Executors
OpenClaw Code Mode is opt-in. When enabled, its default `node` executor runs JavaScript with Node.js `node:vm` in a worker thread. **`node:vm` is not a security boundary.** The worker keeps guest computation off the Gateway event loop, but it runs with the Gateway process's OS privileges. Enabling Node Code Mode is a trusted-host execution choice.
When global `tools.codeMode` is absent, OpenClaw uses automatic per-model activation. Explicit `false` disables it, and an authored object without `enabled` remains off. When engaged, its default `node` executor runs JavaScript with Node.js `node:vm` in a worker thread. **`node:vm` is not a security boundary.** The worker keeps guest computation off the Gateway event loop, but it runs with the Gateway process's OS privileges. Node Code Mode is a trusted-host execution choice.
The intended guest API omits filesystem, network, subprocess, environment, and module-loading APIs. Its limited globals and module guards are programming constraints, not containment against hostile JavaScript. Tool policy, approvals, hooks, and session ownership still apply to calls made through the shared tool bridge; they cannot contain code that escapes the Node VM context. Agent sandbox settings for nested tools do not turn this Gateway worker into an OS sandbox.

View file

@ -1,4 +1,4 @@
b500a49aa06e79522f384d2e797d4f76dfc4ec5546e6bd4fa9ef27a76bb911fc config-baseline.json
4d6cbb86d5d4a42f277d9712a87ece10c2f4b26f20c2c40b9d4e75a2cd9e1ea6 config-baseline.core.json
9ea48fefe3336c2f8aa95df72a98cfcda04209cd79caf41ecee8f1e5bb701f50 config-baseline.json
4e5cf87e3f7130abaf72e44ff76e55e28a926262ec8a3c86a4a0b670d6008db2 config-baseline.core.json
93b1f2e7e7121bf82d2d571bfa0c01ce710ae96185f363da1ffbc241afdf4549 config-baseline.channel.json
bfbe362937ed7c534a7a593ac5205b767f60e0b675ec05782da48fa6cee6f762 config-baseline.plugin.json

View file

@ -313,5 +313,5 @@ reject the turn. The completed result records the runtime that actually ran.
- [Agent loop](/concepts/agent-loop)
- [Models](/concepts/models)
- [Status](/cli/status)
- [Code Mode](/tools/code-mode) — an experimental, opt-in agent-runtime feature
- [Code Mode](/tools/code-mode) — an experimental agent-runtime feature with automatic per-model activation
- [Agent runtime architecture](/agent-runtime-architecture) — code layout, module boundaries, and how the built-in runtime is selected

View file

@ -406,8 +406,9 @@ Store environment values never enter the Codex app-server process, native
shell, sandbox exec-server, ACP children, sandbox exec, or node exec.
This Codex-native feature is separate from
[OpenClaw Code Mode](/tools/code-mode), an opt-in JavaScript runtime
for generic OpenClaw runs with a different `exec` input shape. For the
[OpenClaw Code Mode](/tools/code-mode), a separate JavaScript runtime with its
own automatic per-model activation and explicit overrides. It has a different
`exec` input shape. For the
broader model/provider/runtime split, start with
[Agent runtimes](/concepts/agent-runtimes): `openai/gpt-6-astra` is the model
ref, `codex` is the runtime, and Telegram, Discord, Slack, or another

View file

@ -31,7 +31,7 @@ Referral link for MiniMax Coding Plan (10% off): [MiniMax Coding Plan](https://p
Model refs follow the auth path: `minimax/<model>` for API-key setups, `minimax-portal/<model>` for OAuth setups.
MiniMax M3 is a preferred [Code Mode](/tools/code-mode) model on both API-key and OAuth routes. Set `tools.codeMode.enabled` to `"auto"` to use it; Code Mode is off by default.
MiniMax M3 is a preferred [Code Mode](/tools/code-mode) model on both API-key and OAuth routes. With no global Code Mode setting, the automatic tier can engage it; explicit agent or model settings still take precedence.
## Getting started

View file

@ -9,7 +9,7 @@ read_when:
- You are looking for the Code Mode page that matches your task
---
Code mode is an experimental, opt-in OpenClaw agent-runtime feature. When
Code mode is an experimental OpenClaw agent-runtime feature. When
enabled, the model no longer sees every enabled tool schema. Instead, it sees
`exec`, `wait`, and any direct-only tool whose structured result cannot cross
the JSON-only guest bridge. The model writes a small JavaScript
@ -18,10 +18,11 @@ TypeScript-style signatures describe the available tools; executable cells use
plain JavaScript without type annotations.
<Note>
OpenClaw Code Mode is off by default. To try it, open **Settings → Agents &
Tools → Labs** and turn on **Code Mode**. The Labs switch writes the `"auto"`
tier, which engages only for models marked as preferred Code Mode performers.
This is the global default. Agent and model overrides take precedence.
When `tools.codeMode` is absent, OpenClaw uses the `"auto"` tier and engages
Code Mode only for models marked as preferred Code Mode performers. An authored
object without `enabled` remains off, as do `false` and `{ enabled: false }`.
Agent and model overrides take precedence. Use **Settings → Agents & Tools →
Labs → Code Mode** to choose the global setting.
</Note>
This page documents OpenClaw Code Mode, not Codex Code Mode. The two features

View file

@ -10,13 +10,15 @@ read_when:
## Configuration
`tools.codeMode.enabled` sets the global activation default. It defaults to
`false`, including when the Code Mode object configures other fields. Set
`true` or `"auto"` explicitly, or use an [agent or model override](/tools/code-mode/quickstart#override-one-model).
An absent global `tools.codeMode` setting defaults to `"auto"`. If you author a
Code Mode object, its activation stays off unless that object explicitly sets
`enabled`; this lets you stage executor or limit settings without enabling the
feature. Shorthand `false` also disables it. Agent and model overrides retain
their existing precedence.
| Field | Default | Clamp |
| --------------------- | ---------- | ----------------------------------------------- |
| `enabled` | `false` | `false`, `true`, or `"auto"` (per-model) |
| `enabled` | See above | `false`, `true`, or `"auto"` (per-model) |
| `executor` | `"node"` | `"node"` or `"quickjs"` |
| `mode` | `"only"` | exposes control/direct tools, catalogs the rest |
| `timeoutMs` | `10000` | `100`-`60000` |
@ -60,14 +62,16 @@ tool exposure.
`tools.codeMode.enabled` accepts three values:
- `false` (default): code mode is off unless an agent or model override enables it.
- `false`: code mode is off unless an agent or model override enables it.
- `true`: code mode engages for tool-capable runs unless an override disables it.
- `"auto"`: code mode engages only when the run's model is flagged as a
preferred code-mode performer in its provider catalog.
These values supply the default when no agent or model override takes
precedence. `"auto"` uses catalog capability; an explicit per-model boolean
bypasses that capability preference.
When the global setting is completely absent, OpenClaw behaves as if it were
`"auto"`. An authored object without `enabled` behaves as `false`. These values
supply the default when no agent or model override takes precedence. `"auto"`
uses catalog capability; an explicit per-model boolean bypasses that capability
preference.
### The `compat.codeMode` catalog flag
@ -89,7 +93,7 @@ Bundled provider catalogs currently flag these models as `"preferred"`:
| Provider | Models |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| anthropic | `claude-fable-5`, `claude-opus-5`, `claude-sonnet-5`, `claude-mythos-5`, `claude-opus-4-8`, `claude-haiku-4-5` |
| anthropic | `claude-fable-5`, `claude-opus-5`, `claude-sonnet-5`, `claude-mythos-5`, `claude-opus-4-8` |
| deepseek | `deepseek-v4-pro`, `deepseek-v4-flash` |
| google | `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-3.6-flash`, `gemini-3.7-flash` |
| kimi | `k3`, `k3-256k` |
@ -99,6 +103,9 @@ Bundled provider catalogs currently flag these models as `"preferred"`:
| xiaomi | `mimo-v2.6-pro`, `mimo-v2.6-flash` |
| zai | `glm-5.3`, `glm-5.2`, `glm-5.1` |
`claude-haiku-4-5` is marked `"capable"`: it remains available through an
explicit `true` agent or model setting, but `"auto"` does not engage it.
Everything else, including all Ollama-served local models, stays unflagged and
keeps normal tool exposure under `"auto"`.
@ -133,14 +140,13 @@ changes that routing decision.
### Choosing when to enable
In A/B evaluations on the preferred models above, code mode reduced total
token usage by roughly 30-50% at equal-or-better task pass rates, mostly by
replacing many full tool schemas and per-tool round trips with one compact
program surface. Models below the preferred tier showed no consistent win and
sometimes regressed, which is why `"auto"` leaves them on direct tools.
Code Mode can reduce token use by replacing repeated full tool schemas and
intermediate model turns with a compact catalog and one program. Results depend
on the model and workload. Compare correctness and full root-plus-descendant
token use on representative tasks before forcing it broadly.
Use `"auto"` when agents switch between models: strong models get the compact
surface, weaker or local ones keep the exposure they handle best. Use `true`
Use `"auto"` when agents switch between models: preferred models get the compact
surface, while other models keep normal tool exposure. Use `true`
on an exact model entry when you have verified an unflagged model performs well
with code mode. For open-weight or uncached serving where every prompt token is billed or
recomputed, prefer enabling per model (via `"auto"` or an explicit model override)

View file

@ -11,7 +11,9 @@ read_when:
Code Mode uses **Node** by default when enabled. Select **QuickJS** when you
need a hardened guest runtime. Both executors run the same plain JavaScript
cells, expose the same typed tool discovery, and use the same `exec` and `wait`
tools. Code Mode itself remains off by default.
tools. With no global Code Mode setting, automatic per-model activation applies.
Choosing an executor in Labs preserves activation. When writing an object in
config, include `enabled: "auto"` to retain automatic activation.
## Choose an executor

View file

@ -9,9 +9,12 @@ read_when:
## Enable code mode
The recommended path is **Settings → Agents & Tools → Labs → Code Mode**. The
switch takes effect for future agent runs without restarting the Gateway and
selects the `"auto"` tier.
When `tools.codeMode` is absent, OpenClaw uses the `"auto"` tier for future
agent runs. It engages Code Mode only for models marked preferred in the
provider catalog. No configuration is required for this default.
Use **Settings → Agents & Tools → Labs → Code Mode** to change the global
setting. Changes apply to future agent runs without restarting the Gateway.
To enable the same tier without the Control UI, set it in config:
@ -34,10 +37,9 @@ To default code mode on for every tool-capable run, regardless of model:
```
Object form works too: `tools.codeMode.enabled` accepts the same `false`,
`true`, and `"auto"` values. Code mode stays off when `tools.codeMode` is
omitted, `false`, or an object without an explicit `enabled` value, unless an
agent or model override enables it. Configuring limits or other Code Mode
options does not enable it.
`true`, and `"auto"` values. Code Mode stays off for `false` or an authored
object without an explicit `enabled` value, unless an agent or model override
enables it. Configuring limits or other Code Mode options does not enable it.
When enabled, Code Mode defaults to Node's `node:vm` executor for trusted
execution. Select QuickJS in the same settings panel or set
@ -112,7 +114,9 @@ Activation resolves from the first explicit setting in this order:
1. `agents.entries.<agent>.models["provider/model"].codeMode`.
2. `agents.entries.<agent>.tools.codeMode.enabled` (or its boolean/`"auto"` shorthand).
3. `agents.defaults.models["provider/model"].codeMode`.
4. `tools.codeMode.enabled` (or its shorthand), defaulting to `false`.
4. `tools.codeMode.enabled` (or its shorthand); a completely absent global
setting defaults to `"auto"`, while an authored object without `enabled`
defaults to `false`.
In the Control UI, open **Settings → Agents → Agent defaults**, show **Advanced**
settings, and find **Models** under **Agent Defaults**. Each model has a
@ -281,7 +285,7 @@ With code mode active, the logged model-facing tool names should be `exec` and
[Swarm](/tools/swarm) adds `agents.run()`, `phase()`, and `log()` guest globals
for orchestrating concurrent sub-agents from Code Mode scripts. Swarm is enabled
by default; Code Mode remains separately opt-in through `"auto"` or `true`.
by default; Code Mode activation remains separate.
Use normal JavaScript control flow for fan-out, decision gates, and structured
collection.

View file

@ -39,11 +39,17 @@ remain successful.
JavaScript syntax errors are rejected during source preparation, before any
nested tool dispatch. The bounded diagnostic includes a one-based source line
and column. Correct the source and submit a new `exec`; OpenClaw does not repair
and column. Malformed JavaScript reports its syntax error before module-access
checks. Correct the source and submit a new `exec`; OpenClaw does not repair
or replay it automatically. This no-dispatch outcome does not enable
`restartSafe` or change the result's `replaySafe` flag. Exceptions thrown by valid
guest code, including `SyntaxError`, remain runtime failures.
The current parser has a known limitation with division immediately after an
optional keyword-named property, such as `value?.return / 2 / 3`. This is valid
JavaScript, but source preparation rejects it before tool dispatch. Parenthesize
the property access: `(value?.return) / 2 / 3`.
Errors returned to the guest are plain data; host `Error` instances, stack
objects and prototypes are not passed through the JSON result bridge. This
bridge contract does not make the Node executor a security boundary; see

View file

@ -335,6 +335,6 @@ Notes:
- [Sandboxing](/gateway/sandboxing) — running commands in sandboxed environments
- [Background Process](/gateway/background-process) — long-running exec and process tool
- [Security](/gateway/security) — tool policy and elevated access
- [Code Mode](/tools/code-mode) — an opt-in runtime where the model writes a program that calls the hidden tool catalog
- [Code Mode](/tools/code-mode) — a runtime where the model writes a program that calls the hidden tool catalog
- [`apply_patch`](/tools/apply-patch) — apply a structured edit instead of shelling out
- [Tokenjuice](/tools/tokenjuice) — compacting large command output

View file

@ -65,7 +65,7 @@ describe("unreleased Claude generations", () => {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 64_000,
compat: { codeMode: "preferred" },
compat: { codeMode: "capable" },
});
expect(resolveModel("claude-haiku-4-9-20251001")).toBeUndefined();
});

View file

@ -279,7 +279,7 @@
},
"contextWindow": 200000,
"maxTokens": 64000,
"compat": { "codeMode": "preferred" }
"compat": { "codeMode": "capable" }
}
]
}

View file

@ -52,11 +52,13 @@ const selectableContextWindowMetadata = {
};
describe("Anthropic plugin manifest", () => {
it("flags every static Anthropic API model as code-mode preferred", () => {
it("keeps Haiku opt-in while preferring Code Mode for the other API models", () => {
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
expect(models.length).toBeGreaterThan(0);
for (const model of models) {
expect(model.compat?.codeMode, model.id).toBe("preferred");
expect(model.compat?.codeMode, model.id).toBe(
model.id === "claude-haiku-4-5" ? "capable" : "preferred",
);
}
});
@ -156,7 +158,7 @@ describe("Anthropic plugin manifest", () => {
},
contextWindow: 200000,
maxTokens: 64000,
compat: { codeMode: "preferred" },
compat: { codeMode: "capable" },
});
expect(models.find((model) => model.id === "claude-haiku-4-5-20251001")).toBeUndefined();
});

View file

@ -555,29 +555,42 @@ describe("headless Code Mode", () => {
);
it.each([
String.raw`return r\u0065quire('node:fs');`,
"return require?.('node:fs');",
"return (require)('node:fs');",
"return (0, require)('node:fs');",
"const load = require; return load('node:fs');",
"return module.require('node:fs');",
"return process.getBuiltinModule('node:fs');",
"return `${import('node:fs')}`;",
"return `${require('node:fs')}`;",
"return `${`nested ${import('node:fs')}`}`;",
"return `${`nested ${require('node:fs')}`}`;",
"const message = `import('node:fs')`; return require('node:fs');",
"let value = 1; return value++ / import('node:fs');",
"let value = 1; return value-- / import('node:fs');",
"const value = { of: 1 }; return value.of / import('node:fs');",
"const value = { return: 1 }; return value.return / import('node:fs');",
"const value = { if() { return 1; } }; return value.if() / import('node:fs');",
"const value = { return: 1 }; return value?.return / import('node:fs') / 1;",
"const value = { return: 1 }; return value?.return / require('node:fs') / 1;",
"const value = { if() { return 1; } }; return value?.if() / import('node:fs');",
"function run() { const await = 1; return await / (globalThis.pending = import('node:fs')); } run(); return globalThis.pending;",
"class Guest { #return = 1; run() { return this.#return / (globalThis.pending = import('node:fs')); } } new Guest().run(); return globalThis.pending;",
])("rejects executable module access in a headless guest: %s", async (code) => {
...[
String.raw`return r\u0065quire('node:fs');`,
"return require?.('node:fs');",
"return (require)('node:fs');",
"return (0, require)('node:fs');",
"const load = require; return load('node:fs');",
"return module.require('node:fs');",
"return process.getBuiltinModule('node:fs');",
"return `${import('node:fs')}`;",
"return `${require('node:fs')}`;",
"return `${`nested ${import('node:fs')}`}`;",
"return `${`nested ${require('node:fs')}`}`;",
"const message = `import('node:fs')`; return require('node:fs');",
"let value = 1; return value++ / import('node:fs');",
"let value = 1; return value-- / import('node:fs');",
"const value = { of: 1 }; return value.of / import('node:fs');",
"const value = { return: 1 }; return value.return / import('node:fs');",
"const value = { if() { return 1; } }; return value.if() / import('node:fs');",
"const value = { if() { return 1; } }; return value?.if() / import('node:fs');",
"function run() { const await = 1; return await / (globalThis.pending = import('node:fs')); } run(); return globalThis.pending;",
"class Guest { #return = 1; run() { return this.#return / (globalThis.pending = import('node:fs')); } } new Guest().run(); return globalThis.pending;",
].map((code) => ({
code,
reason: "executable module access",
expectedError: "module access is disabled",
})),
...[
"const value = { return: 1 }; return value?.return / import('node:fs') / 1;",
"const value = { return: 1 }; return value?.return / require('node:fs') / 1;",
].map((code) => ({
code,
reason: "existing parser limitation: optional keyword property before division",
expectedError:
"SyntaxError at openclaw-code-mode:user.js:1:51: Unexpected token. No tools were dispatched; correct the JavaScript source and submit it again.",
})),
])("rejects $reason in a headless guest: $code", async ({ code, expectedError }) => {
const result = expectFailed(
await runCodeModeScriptHeadless({
ctx: createHeadlessCodeModeHarness(),
@ -586,7 +599,7 @@ describe("headless Code Mode", () => {
);
expect(result.code).toBe("invalid_input");
expect(result.error).toContain("module access is disabled");
expect(result.error).toContain(expectedError);
expect(result.toolCallCount).toBe(0);
});

View file

@ -155,7 +155,7 @@ describe("Code Mode master switch resolution", () => {
{ name: "object enabled auto", codeMode: { enabled: "auto" }, enabled: "auto" },
{ name: "object with options", codeMode: { timeoutMs: 5000 }, enabled: false },
{ name: "empty object", codeMode: {}, enabled: false },
{ name: "omitted", codeMode: undefined, enabled: false },
{ name: "omitted", codeMode: undefined, enabled: "auto" },
])("resolves enabled for $name", ({ codeMode, enabled }) => {
expect(resolveCodeModeConfig({ tools: { codeMode } } as never).enabled).toBe(enabled);
});
@ -206,6 +206,17 @@ describe("Code Mode guest source validation", () => {
it.each([
{ code: "const answer = ;", location: "1:16" },
{ code: "const first = 1;\nconst answer = ;", location: "2:16" },
{ code: "const answer = ; return import('node:fs');", location: "1:16" },
{
code: `const label = "${"😀".repeat(96)}";\nconst answer = ; return import('node:fs');`,
location: "2:16",
},
{
code: `const label = "${"😀".repeat(96)}";\nconst answer = ; return require('node:fs');`,
location: "2:16",
},
{ code: "import fs from 'node:fs';", location: "1:1" },
{ code: "return import.meta.url;", location: "1:8" },
])("rejects malformed JavaScript at $location", ({ code, location }) => {
expect(() => prepareSource(code)).toThrow(
"SyntaxError at openclaw-code-mode:user.js:" + location,
@ -334,7 +345,6 @@ describe("Code Mode guest source validation", () => {
it.each([
["direct require", "return require('node:fs');"],
["direct dynamic import", "return import('node:fs');"],
["direct import.meta", "return import.meta.url;"],
["comment-separated require", "return require /* hidden */ ('node:fs');"],
["Unicode-escaped direct require", String.raw`return r\u0065quire('node:fs');`],
["optional direct require", "return require?.('node:fs');"],
@ -386,12 +396,14 @@ describe("Code Mode guest source validation", () => {
"const value = { if() { return 1; } }; return value.if() / import('node:fs');",
],
[
"dynamic import after an optional keyword-shaped return property",
"existing parser limitation: dynamic import after an optional keyword property",
"const value = { return: 1 }; return value?.return / import('node:fs') / 1;",
"SyntaxError at openclaw-code-mode:user.js:1:51: Unexpected token. No tools were dispatched; correct the JavaScript source and submit it again.",
],
[
"require after an optional keyword-shaped return property",
"existing parser limitation: require after an optional keyword property",
"const value = { return: 1 }; return value?.return / require('node:fs') / 1;",
"SyntaxError at openclaw-code-mode:user.js:1:51: Unexpected token. No tools were dispatched; correct the JavaScript source and submit it again.",
],
[
"dynamic import after an optional keyword-shaped control method",
@ -417,10 +429,6 @@ describe("Code Mode guest source validation", () => {
"require after a nested contextual await identifier",
"function run() { const await = 1; return await / require('node:fs'); } return run();",
],
[
"malformed input containing an executable module loader",
"const answer = ; return import('node:fs');",
],
[
"dynamic import after an astral-filled JavaScript string",
`const label = "${"😀".repeat(96)}"; return import('node:fs');`,
@ -429,23 +437,14 @@ describe("Code Mode guest source validation", () => {
"require after an astral-filled JavaScript string",
`const label = "${"😀".repeat(96)}"; return require('node:fs');`,
],
[
"dynamic import after astral Unicode in malformed JavaScript",
`const label = "${"😀".repeat(96)}"; const answer = ; return import('node:fs');`,
],
[
"require after astral Unicode in malformed JavaScript",
`const label = "${"😀".repeat(96)}"; const answer = ; return require('node:fs');`,
],
])("rejects %s", (_name, code) => {
expect(() => prepareSource(code)).toThrow("code mode module access is disabled");
])("rejects %s", (_name, code, expectedError = "code mode module access is disabled") => {
expect(() => prepareSource(code)).toThrow(expectedError);
});
it("separates every deterministic literal and executable module-shaped input", () => {
const moduleExpressions = [
"require('node:fs')",
"import('node:fs')",
"import.meta.url",
'require /* comment */ ("node:fs")',
'import /* comment */ ("node:fs")',
];

View file

@ -86,7 +86,7 @@ function readCodeModeRawConfig(
model?: { provider: string; modelId: string },
): Record<string, unknown> {
const tools = isRecord(config?.tools) ? config.tools : undefined;
const globalRaw = normalizeCodeModeRawConfig(tools?.codeMode) ?? {};
const globalRaw = normalizeCodeModeRawConfig(tools?.codeMode) ?? { enabled: "auto" };
const agent = config && agentId ? resolveAgentConfig(config, agentId) : undefined;
const agentRaw = normalizeCodeModeRawConfig(agent?.tools?.codeMode);
const key = model
@ -106,8 +106,7 @@ function readCodeModeRawConfig(
}
function readEnabled(value: unknown): boolean | "auto" {
// Stable option-bearing objects made `enabled` optional and defaulted it off.
// Automatic activation therefore requires an explicit `"auto"` selection.
// Authored option-bearing objects keep their historical opt-in behavior.
return typeof value === "boolean" || value === "auto" ? value : false;
}

View file

@ -1,5 +1,4 @@
/** Validate guest JavaScript before execution. */
import { tokenizer } from "acorn";
import { parseCodeModeScriptSyntax } from "./code-mode-script-syntax.js";
import {
CODE_MODE_SHELL_SOURCE_ERROR,
@ -7,34 +6,6 @@ import {
} from "./code-mode-shell-source.js";
import { ToolInputError } from "./tool-input-error.js";
function maskCodeLiteralsAndComments(code: string): string {
// Parser and tokenizer offsets are UTF-16 code units, not Unicode points.
const masked = code.split("");
const maskRange = (start: number, end: number) => {
for (let index = start; index < Math.min(end, masked.length); index += 1) {
if (masked[index] !== "\n" && masked[index] !== "\r") {
masked[index] = " ";
}
}
};
// Malformed JavaScript needs a conservative lexical pass: never trust a
// context-free regexp token to hide executable module access.
try {
for (const token of tokenizer(code, {
ecmaVersion: "latest",
onComment: (_isBlock, _text, start, end) => maskRange(start, end),
})) {
if (token.type.label === "string" || token.type.label === "template") {
maskRange(token.start, token.end);
}
}
return masked.join("");
} catch {
// Never inspect partially masked input after a tokenizer failure.
return code;
}
}
function isModuleLoaderCallee(callee: import("acorn").Expression | import("acorn").Super): boolean {
if (callee.type === "ParenthesizedExpression") {
return isModuleLoaderCallee(callee.expression);
@ -51,9 +22,7 @@ function isModuleLoaderCallee(callee: import("acorn").Expression | import("acorn
function containsModuleAccess(node: import("acorn").AnyNode): boolean {
if (
node.type === "ImportDeclaration" ||
node.type === "ImportExpression" ||
(node.type === "MetaProperty" && node.meta.name === "import") ||
(node.type === "CallExpression" && isModuleLoaderCallee(node.callee))
) {
return true;
@ -89,28 +58,8 @@ function containsModuleAccess(node: import("acorn").AnyNode): boolean {
return false;
}
function rejectsModuleAccess(
code: string,
parsed: ReturnType<typeof parseCodeModeScriptSyntax>,
): boolean {
// Unicode escapes can spell a loader identifier without its literal name.
if (!code.includes("import") && !code.includes("require") && !code.includes("\\u")) {
return false;
}
if (parsed.ok) {
// The WASI guest has no host module loader. Only executable module syntax
// belongs in this early check; ordinary guest methods are not capabilities.
return containsModuleAccess(parsed.program);
}
const source = maskCodeLiteralsAndComments(code);
return /\bimport\b\s*(?:\.|\(|["'`{*]|\w)|\brequire\b\s*\(/u.test(source);
}
export function prepareSource(code: string): string {
const parsed = parseCodeModeScriptSyntax(code);
if (rejectsModuleAccess(code, parsed)) {
throw new ToolInputError("code mode module access is disabled.");
}
if (isShellLikeCodeModeSource(code)) {
throw new ToolInputError(CODE_MODE_SHELL_SOURCE_ERROR);
}
@ -121,5 +70,12 @@ export function prepareSource(code: string): string {
`SyntaxError at openclaw-code-mode:user.js:${parsed.line}:${parsed.column + 1}: ${message}. No tools were dispatched; correct the JavaScript source and submit it again.`,
);
}
// Unicode escapes can spell a loader identifier without its literal name.
if (
(code.includes("import") || code.includes("require") || code.includes("\\u")) &&
containsModuleAccess(parsed.program)
) {
throw new ToolInputError("code mode module access is disabled.");
}
return code;
}

View file

@ -81,7 +81,7 @@ describe("Code Mode configuration", () => {
});
it("resolves object config defaults", () => {
expect(resolveCodeModeConfig().executor).toBe("node");
expect(resolveCodeModeConfig()).toMatchObject({ enabled: "auto", executor: "node" });
expect(resolveCodeModeConfig({ tools: { codeMode: true } })).toMatchObject({
enabled: true,
executor: "node",
@ -190,7 +190,7 @@ describe("Code Mode configuration", () => {
} as never,
"ops",
);
expect(configuredAgent.enabled).toBe(false);
expect(configuredAgent.enabled).toBe("auto");
expect(configuredAgent.timeoutMs).toBe(2345);
});
});

View file

@ -294,34 +294,46 @@ describe("Code Mode guest source validation", () => {
});
it.each([
"const fs = require('node:fs'); return fs;",
String.raw`return r\u0065quire('node:fs');`,
"return require?.('node:fs');",
"return (require)('node:fs');",
"return (0, require)('node:fs');",
"const load = require; return load('node:fs');",
"return module.require('node:fs');",
"return process.getBuiltinModule('node:fs');",
"return import('node:fs');",
"return import.meta.url;",
"return `${import('node:fs')}`;",
"return `${require('node:fs')}`;",
"return `${`nested ${import('node:fs')}`}`;",
"return `${`nested ${require('node:fs')}`}`;",
"return `${({ value: import('node:fs') }).value}`;",
"const message = `import('node:fs')`; return require('node:fs');",
"const pattern = /import.meta/; return import('node:fs');",
"let value = 1; return value++ / import('node:fs');",
"let value = 1; return value-- / import('node:fs');",
"const value = { of: 1 }; return value.of / import('node:fs');",
"const value = { return: 1 }; return value.return / import('node:fs');",
"const value = { if() { return 1; } }; return value.if() / import('node:fs');",
"const value = { return: 1 }; return value?.return / import('node:fs') / 1;",
"const value = { return: 1 }; return value?.return / require('node:fs') / 1;",
"const value = { if() { return 1; } }; return value?.if() / import('node:fs');",
"function run() { const await = 1; return await / (globalThis.pending = import('node:fs')); } run(); return globalThis.pending;",
"class Guest { #return = 1; run() { return this.#return / (globalThis.pending = import('node:fs')); } } new Guest().run(); return globalThis.pending;",
])("rejects module access: %s", async (code) => {
...[
"const fs = require('node:fs'); return fs;",
String.raw`return r\u0065quire('node:fs');`,
"return require?.('node:fs');",
"return (require)('node:fs');",
"return (0, require)('node:fs');",
"const load = require; return load('node:fs');",
"return module.require('node:fs');",
"return process.getBuiltinModule('node:fs');",
"return import('node:fs');",
"return `${import('node:fs')}`;",
"return `${require('node:fs')}`;",
"return `${`nested ${import('node:fs')}`}`;",
"return `${`nested ${require('node:fs')}`}`;",
"return `${({ value: import('node:fs') }).value}`;",
"const message = `import('node:fs')`; return require('node:fs');",
"const pattern = /import.meta/; return import('node:fs');",
"let value = 1; return value++ / import('node:fs');",
"let value = 1; return value-- / import('node:fs');",
"const value = { of: 1 }; return value.of / import('node:fs');",
"const value = { return: 1 }; return value.return / import('node:fs');",
"const value = { if() { return 1; } }; return value.if() / import('node:fs');",
"const value = { if() { return 1; } }; return value?.if() / import('node:fs');",
"function run() { const await = 1; return await / (globalThis.pending = import('node:fs')); } run(); return globalThis.pending;",
"class Guest { #return = 1; run() { return this.#return / (globalThis.pending = import('node:fs')); } } new Guest().run(); return globalThis.pending;",
].map((code) => ({
code,
reason: "module access",
expectedError: "module access is disabled",
})),
...[
"const value = { return: 1 }; return value?.return / import('node:fs') / 1;",
"const value = { return: 1 }; return value?.return / require('node:fs') / 1;",
].map((code) => ({
code,
reason: "existing parser limitation: optional keyword property before division",
expectedError:
"SyntaxError at openclaw-code-mode:user.js:1:51: Unexpected token. No tools were dispatched; correct the JavaScript source and submit it again.",
})),
])("rejects $reason: $code", async ({ code, expectedError }) => {
const tools = createSourceValidationTools();
const details = resultDetails(
await expectDefined(tools[0], "tools[0] test invariant").execute("code-call-import", {
@ -329,7 +341,14 @@ describe("Code Mode guest source validation", () => {
}),
);
expect(details.status).toBe("failed");
expect(String(details.error)).toContain("module access is disabled");
expect(details).toMatchObject({
status: "failed",
code: "invalid_input",
failurePhase: "input",
bridgeDispatchStarted: false,
telemetry: { callCount: 0 },
});
expect(String(details.error)).toContain(expectedError);
expect(testing.activeRuns.size).toBe(0);
});
});

View file

@ -751,7 +751,8 @@ describe("Code Mode guest execution", () => {
const execTool = expectDefined(codeModeTools[0], "Code Mode exec");
const malformed = [
'await fake_command({ value: "first" });',
'return await fake_command({ value: "jq .["2"]" });',
String.raw`const patch = { 'newText:' const value = 1;\n };`,
"return await fake_command({ value: \"import test from 'node:test';\" });",
].join("\n");
const details = resultDetails(
await execTool.execute("code-call-syntax", { code: malformed }),
@ -772,12 +773,15 @@ describe("Code Mode guest execution", () => {
const corrected = await runUntilCompleted({
execTool,
waitTool: expectDefined(codeModeTools[1], "Code Mode wait"),
code: "return await fake_command({ value: " + JSON.stringify('jq .["2"]') + " });",
code:
"return await fake_command({ value: " +
JSON.stringify("import test from 'node:test';") +
" });",
});
expect(corrected).toMatchObject({
status: "completed",
replaySafe: false,
value: { name: "fake_command", input: { value: 'jq .["2"]' } },
value: { name: "fake_command", input: { value: "import test from 'node:test';" } },
});
expect(command.execute).toHaveBeenCalledTimes(1);
expect(testing.activeRuns.size).toBe(0);

View file

@ -122,9 +122,8 @@ describe("resolveAgentToolSurfacePlan", () => {
},
);
it("uses the selected model policy before transport aliases and reevaluates fallbacks", () => {
it("uses automatic activation by default while honoring model policy and fallback capability", () => {
const config: OpenClawConfig = {
tools: { codeMode: "auto" },
agents: { defaults: { models: { "test/family": { codeMode: false } } } },
};
const model = { id: "family-current", provider: "test", compat: { codeMode: "preferred" } };
@ -135,6 +134,16 @@ describe("resolveAgentToolSurfacePlan", () => {
expect(
resolveAgentToolSurfacePlan({ ...params, modelId: "fallback" }).codeModeControlsEnabled,
).toBe(true);
for (const compat of [{ codeMode: "capable" }, {}]) {
const plan = resolveAgentToolSurfacePlan({
...params,
modelId: "fallback",
model: { ...model, compat },
});
expect(plan.codeModeControlsEnabled).toBe(false);
expect(plan.toolSearchControlsEnabled).toBe(true);
}
expect(config.tools).toBeUndefined();
});
it.each([

View file

@ -134,9 +134,9 @@ export const RUNTIME_FIELD_HELP: Record<string, string> = {
"tools.toolSearch.maxSearchLimit":
"Maximum number of Tool Search results a model can request. Runtime clamps values to the supported 1..50 range.",
"tools.codeMode":
"Generic OpenClaw Code Mode. When enabled, agent runs expose only `exec` and `wait` to the model and access normal tools through the catalog bridge.",
'Generic OpenClaw Code Mode. When omitted globally, defaults to `"auto"`; an authored object without `enabled` remains off. Engaged agent runs expose only `exec` and `wait` to the model and access normal tools through the catalog bridge.',
"tools.codeMode.enabled":
'Global OpenClaw Code Mode default. Off when omitted, including objects without `enabled`. `"auto"` engages catalog-preferred models; `true` engages tool-capable runs. Agent and model activation overrides take precedence. An engaged run fails closed if the runtime is unavailable instead of exposing the full tool list.',
'Global OpenClaw Code Mode activation. A completely absent global setting defaults to `"auto"`; an authored object without `enabled` remains off. `"auto"` engages catalog-preferred models, while `true` engages tool-capable runs. Agent and model activation overrides take precedence. An engaged run fails closed if the runtime is unavailable instead of exposing the full tool list.',
"tools.codeMode.executor":
'JavaScript executor: "node" (default) uses Node vm for trusted code and is not a security sandbox; "quickjs" uses the bundled QuickJS WASM plugin for hardened guest execution. Tool permissions apply to both. A missing selected executor fails closed.',
"tools.codeMode.mode":

View file

@ -521,7 +521,7 @@ const CodeModeSchema = z
z.literal("auto"),
z
.object({
/** OpenClaw Code Mode default, overridden by per-model codeMode. Default: false; "auto" engages catalog-preferred models. */
/** Explicit object-form activation. Omitted stays off; "auto" engages catalog-preferred models. A completely absent global codeMode setting defaults separately to auto. */
enabled: z.union([z.boolean(), z.literal("auto")]).optional(),
/** Executor. Node is the default; QuickJS provides a separate WASM guest. */
executor: z.enum(["node", "quickjs"]).optional(),

View file

@ -139,78 +139,94 @@ async function capture(page: Page, name: string, content: Locator): Promise<void
}
suite.define(() => {
it("saves and reloads the Code Mode executor without changing enablement or limits", async () => {
await suite.withPage(
{
colorScheme: "dark",
locale: "en-US",
serviceWorkers: "block",
viewport: { height: 1000, width: 1440 },
},
async ({ page }) => {
const initialConfig = {
tools: { codeMode: { enabled: "auto", timeoutMs: 5000 } },
};
const quickjsConfig = {
tools: { codeMode: { ...initialConfig.tools.codeMode, executor: "quickjs" } },
};
const gateway = await installMockGateway(page, {
methodResponses: {
"config.get": configResponse(initialConfig, "executor-node"),
},
});
it.each([
{
name: "authored activation and limits",
initialMode: { enabled: "auto", timeoutMs: 5000 },
expectedPatch: { executor: "quickjs" },
},
{
name: "inherited automatic activation",
initialMode: undefined,
expectedPatch: { enabled: "auto", executor: "quickjs" },
},
])(
"saves and reloads the Code Mode executor with $name",
async ({ initialMode, expectedPatch }) => {
await suite.withPage(
{
colorScheme: "dark",
locale: "en-US",
serviceWorkers: "block",
viewport: { height: 1000, width: 1440 },
},
async ({ page }) => {
const initialConfig = initialMode ? { tools: { codeMode: initialMode } } : {};
const retainedMode = { enabled: "auto", ...initialMode };
const quickjsConfig = {
tools: { codeMode: { ...retainedMode, executor: "quickjs" } },
};
const gateway = await installMockGateway(page, {
methodResponses: {
"config.get": configResponse(initialConfig, "executor-node"),
},
});
expect((await page.goto(`${suite.server.baseUrl}settings/labs`))?.status()).toBe(200);
const executorRow = settingsRow(page, "Code Mode executor");
const executor = executorRow.getByRole("combobox", { name: "Code Mode executor" });
const enabled = settingsRow(page, "Code Mode").getByRole("switch", {
name: "Code Mode",
exact: true,
});
await expect.poll(() => executor.inputValue()).toBe("node");
expect(await executorRow.textContent()).toContain("not a security sandbox");
expect(await enabled.getAttribute("aria-checked")).toBe("true");
await capture(page, "code-mode-node-default.png", executor);
expect((await page.goto(`${suite.server.baseUrl}settings/labs`))?.status()).toBe(200);
const executorRow = settingsRow(page, "Code Mode executor");
const executor = executorRow.getByRole("combobox", { name: "Code Mode executor" });
const enabled = settingsRow(page, "Code Mode").getByRole("switch", {
name: "Code Mode",
exact: true,
});
await expect.poll(() => executor.inputValue()).toBe("node");
expect(await executorRow.textContent()).toContain("not a security sandbox");
expect(await enabled.getAttribute("aria-checked")).toBe("true");
await capture(page, "code-mode-node-default.png", executor);
await gateway.deferNext("config.patch");
await executor.selectOption("quickjs");
const quickjsPatch = mutationParams(await gateway.waitForRequest("config.patch"));
expect(quickjsPatch.baseHash).toBe("executor-node");
expect(JSON.parse(String(quickjsPatch.raw))).toEqual({
tools: { codeMode: { executor: "quickjs" } },
});
expect(await executor.isDisabled()).toBe(true);
await gateway.deferNext("config.patch");
await executor.selectOption("quickjs");
const quickjsPatch = mutationParams(await gateway.waitForRequest("config.patch"));
expect(quickjsPatch.baseHash).toBe("executor-node");
expect(JSON.parse(String(quickjsPatch.raw))).toEqual({
tools: { codeMode: expectedPatch },
});
expect(await executor.isDisabled()).toBe(true);
const quickjsResponse = configResponse(quickjsConfig, "executor-quickjs");
await gateway.setMethodResponse("config.get", quickjsResponse);
await gateway.resolveDeferred("config.patch", { ok: true, ...quickjsResponse });
await expect.poll(() => executor.isDisabled()).toBe(false);
expect((await page.reload())?.status()).toBe(200);
await expect.poll(() => executor.inputValue()).toBe("quickjs");
expect(await enabled.getAttribute("aria-checked")).toBe("true");
await capture(page, "code-mode-quickjs-reloaded.png", executor);
const quickjsResponse = configResponse(quickjsConfig, "executor-quickjs");
await gateway.setMethodResponse("config.get", quickjsResponse);
await gateway.resolveDeferred("config.patch", { ok: true, ...quickjsResponse });
await expect.poll(() => executor.isDisabled()).toBe(false);
expect((await page.reload())?.status()).toBe(200);
await expect.poll(() => executor.inputValue()).toBe("quickjs");
expect(await enabled.getAttribute("aria-checked")).toBe("true");
await capture(page, "code-mode-quickjs-reloaded.png", executor);
const priorPatches = (await gateway.getRequests("config.patch")).length;
await gateway.deferNext("config.patch");
await executor.selectOption("node");
const nodePatch = mutationParams(
await gateway.waitForRequest("config.patch", { after: priorPatches }),
);
expect(nodePatch.baseHash).toBe("executor-quickjs");
expect(JSON.parse(String(nodePatch.raw))).toEqual({
tools: { codeMode: { executor: null } },
});
const priorPatches = (await gateway.getRequests("config.patch")).length;
await gateway.deferNext("config.patch");
await executor.selectOption("node");
const nodePatch = mutationParams(
await gateway.waitForRequest("config.patch", { after: priorPatches }),
);
expect(nodePatch.baseHash).toBe("executor-quickjs");
expect(JSON.parse(String(nodePatch.raw))).toEqual({
tools: { codeMode: { executor: null } },
});
const nodeResponse = configResponse(initialConfig, "executor-node-restored");
await gateway.setMethodResponse("config.get", nodeResponse);
await gateway.resolveDeferred("config.patch", { ok: true, ...nodeResponse });
await expect.poll(() => executor.isDisabled()).toBe(false);
expect((await page.reload())?.status()).toBe(200);
await expect.poll(() => executor.inputValue()).toBe("node");
expect(await enabled.getAttribute("aria-checked")).toBe("true");
},
);
});
const nodeResponse = configResponse(
{ tools: { codeMode: retainedMode } },
"executor-node-restored",
);
await gateway.setMethodResponse("config.get", nodeResponse);
await gateway.resolveDeferred("config.patch", { ok: true, ...nodeResponse });
await expect.poll(() => executor.isDisabled()).toBe(false);
expect((await page.reload())?.status()).toBe(200);
await expect.poll(() => executor.inputValue()).toBe("node");
expect(await enabled.getAttribute("aria-checked")).toBe("true");
},
);
},
);
it("retains a Raw revert when an autosave commits after its connection closes", async () => {
await suite.withPage(

View file

@ -114,7 +114,7 @@ suite.define(() => {
const afterLabsReset = {
agents: initialConfig.agents,
browser: initialConfig.browser,
tools: { profile: "minimal" },
tools: { codeMode: {}, profile: "minimal" },
};
const afterThinkingReset = {
agents: {
@ -357,7 +357,7 @@ suite.define(() => {
await reloadedCodeModeRow
.getByRole("switch", { name: "Code Mode", exact: true })
.getAttribute("aria-checked"),
).toBe("false");
).toBe("true");
if (captureUiProofEnabled) {
await page.screenshot({

View file

@ -172,24 +172,44 @@ describe("LabsPage", () => {
expect(codeModeToggle(page).checked).toBe(true);
});
it.each([true, false, "auto"])(
"preserves the %s Code Mode shorthand when choosing an executor",
async (enabled) => {
const { page, runtimeConfig } = await mountPage({ tools: { codeMode: enabled } });
const select = page.querySelector<HTMLSelectElement>(
'select[aria-label="Code Mode executor"]',
);
expect(select).not.toBeNull();
select!.value = "quickjs";
select!.dispatchEvent(new Event("change", { bubbles: true }));
await vi.waitFor(() => expect(runtimeConfig.patch).toHaveBeenCalledOnce());
expect(runtimeConfig.patch).toHaveBeenCalledWith({
raw: { tools: { codeMode: { enabled, executor: "quickjs" } } },
note: "labs: update codeModeExecutor",
});
it.each([
...[true, false, "auto"].map((enabled) => ({
name: `${enabled} shorthand`,
config: enabled,
executor: "quickjs",
expectedPatch: { enabled, executor: "quickjs" },
})),
{
name: "inherited auto",
config: undefined,
executor: "quickjs",
expectedPatch: { enabled: "auto", executor: "quickjs" },
},
);
{
name: "authored limits without activation",
config: { timeoutMs: 5000 },
executor: "quickjs",
expectedPatch: { executor: "quickjs" },
},
{
name: "explicit auto and limits when restoring Node",
config: { enabled: "auto", executor: "quickjs", timeoutMs: 5000 },
executor: "node",
expectedPatch: { executor: null },
},
])("preserves $name when choosing an executor", async ({ config, executor, expectedPatch }) => {
const { page, runtimeConfig } = await mountPage({ tools: { codeMode: config } });
const select = page.querySelector<HTMLSelectElement>('select[aria-label="Code Mode executor"]');
expect(select).not.toBeNull();
select!.value = executor;
select!.dispatchEvent(new Event("change", { bubbles: true }));
await vi.waitFor(() => expect(runtimeConfig.patch).toHaveBeenCalledOnce());
expect(runtimeConfig.patch).toHaveBeenCalledWith({
raw: { tools: { codeMode: expectedPatch } },
note: "labs: update codeModeExecutor",
});
});
it.each([
{
@ -264,6 +284,12 @@ describe("LabsPage", () => {
expectedPatch: { tools: { codeMode: { enabled: "auto" } } },
note: "labs: update codeMode",
},
{
label: "Code Mode",
sourceConfig: { tools: { codeMode: false } },
expectedPatch: { tools: { codeMode: null } },
note: "labs: update codeMode",
},
{
label: "Custom plugin UI",
sourceConfig: {},
@ -313,10 +339,9 @@ describe("LabsPage", () => {
});
describe("LabsPage code mode enablement", () => {
// Mirrors resolveCodeModeConfig: omitted `enabled` is off for every object
// shape, while explicit `true` and `"auto"` remain opt-ins.
// An absent global node inherits auto; authored objects remain opt-in.
it.each([
["unset", false, {}],
["unset", true, {}],
["empty object", false, { tools: { codeMode: {} } }],
["object with options", false, { tools: { codeMode: { timeoutMs: 5000 } } }],
["explicit true", true, { tools: { codeMode: { enabled: true } } }],
@ -330,16 +355,17 @@ describe("LabsPage code mode enablement", () => {
provider.remove();
});
it("writes the auto tier when enabling the shipped default", async () => {
it("writes explicit false when disabling the automatic default", async () => {
const { page, runtimeConfig } = await mountPage({});
const toggle = codeModeToggle(page);
toggle.checked = true;
expect(toggle.checked).toBe(true);
toggle.checked = false;
toggle.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
await vi.waitFor(() => expect(runtimeConfig.patch).toHaveBeenCalledOnce());
expect(runtimeConfig.patch).toHaveBeenCalledWith({
raw: { tools: { codeMode: { enabled: "auto" } } },
raw: { tools: { codeMode: { enabled: false } } },
note: "labs: update codeMode",
});
});

View file

@ -150,7 +150,11 @@ class LabsPage extends OpenClawLightDomElement {
void this.updateSetting("codeModeExecutor", executor, {
tools: {
codeMode: {
...(typeof config === "boolean" || config === "auto" ? { enabled: config } : {}),
...(config === undefined
? { enabled: "auto" }
: typeof config === "boolean" || config === "auto"
? { enabled: config }
: {}),
executor: executor === "node" ? null : executor,
},
},

View file

@ -82,7 +82,12 @@ export const LAB_FEATURES = [
onValue: "auto",
offValue: false,
activeValues: [true, "auto"],
readEnabled: null,
// Mirrors resolveCodeModeConfig: absence inherits auto; authored objects opt in.
readEnabled: (raw) =>
raw === undefined ||
raw === true ||
raw === "auto" ||
(isRecord(raw) && (raw.enabled === true || raw.enabled === "auto")),
enableAlso: null,
resetScope: "gate",
},