mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
fix(codex): remove dynamic tools profile option
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Dependencies: refresh workspace pins and patch targets, including ACPX `@agentclientprotocol/claude-agent-acp` `0.33.1`, Codex ACP `0.14.0`, Baileys `7.0.0-rc10`, Google GenAI `2.0.1`, OpenAI `6.37.0`, AWS SDK `3.1045.0`, Kysely `0.29.0`, Tlon skill `0.3.6`, Aimock `1.19.5`, and tsdown `0.22.0`.
|
||||
- Agents/compaction: preserve scoped background exec/process session references across embedded compaction and after-turn runtime contexts without exposing sessions from unrelated scopes. Fixes #79284. (#79307) Thanks @TurboTheTurtle.
|
||||
- CLI/onboarding: improve setup, onboarding, configure, and channel command wayfinding so terminal flows explain the next useful command instead of relying on terse setup labels.
|
||||
- Agents/Codex: remove the configurable Codex dynamic-tools profile so Codex app-server always owns workspace, edit, patch, exec, process, and plan tools while OpenClaw integration tools remain available.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
31ba805dfaa665fe16735d7c2f3a5e9fb0c349aeb5e301881b5da65cee62b1f8 config-baseline.json
|
||||
f10281b32feb151b97952fda0722bae46c66456783e82ccaa7f39f11306287fa config-baseline.json
|
||||
5552e7871057e05d5699f90f536ce58e62d5b8cfc6020d3e7106be7915fed041 config-baseline.core.json
|
||||
80f0f51caedf14dc2138d975b62852ff7c5cf085df1c734c9de279f5859a7eeb config-baseline.channel.json
|
||||
3a08e5422ce1422d9e2b75feac7e44cdcd0c3dc1eea594f664bceec13cbe3f58 config-baseline.plugin.json
|
||||
dba159f639977bb96d79f0b78de2c6de48d25ed6ba1590f55812affb7ca6e4b0 config-baseline.plugin.json
|
||||
|
||||
@@ -67,6 +67,7 @@ Notes:
|
||||
- Doctor includes a memory-search readiness check and can recommend `openclaw configure --section model` when embedding credentials are missing.
|
||||
- Doctor warns when no command owner is configured. The command owner is the human operator account allowed to run owner-only commands and approve dangerous actions. DM pairing only lets someone talk to the bot; if you approved a sender before first-owner bootstrap existed, set `commands.ownerAllowFrom` explicitly.
|
||||
- Doctor warns when Codex-mode agents are configured and personal Codex CLI assets exist in the operator's Codex home. Local Codex app-server launches use isolated per-agent homes, so use `openclaw migrate codex --dry-run` to inventory assets that should be promoted deliberately.
|
||||
- Doctor removes retired `plugins.entries.codex.config.codexDynamicToolsProfile`; Codex app-server always keeps Codex-native workspace tools native.
|
||||
- Doctor warns when skills allowed for the default agent are unavailable in the current runtime environment because bins, env vars, config, or OS requirements are missing. `doctor --fix` can disable those unavailable skills with `skills.entries.<skill>.enabled=false`; install/configure the missing requirement instead when you want to keep the skill active.
|
||||
- If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`).
|
||||
- If legacy sandbox registry files (`~/.openclaw/sandbox/containers.json` or `~/.openclaw/sandbox/browsers.json`) are present, doctor reports them; `openclaw doctor --fix` migrates valid entries into sharded registry directories and quarantines invalid legacy files.
|
||||
|
||||
@@ -223,6 +223,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- `browser.profiles.*.driver: "extension"` → `"existing-session"`
|
||||
- remove `browser.relayBindHost` (legacy extension relay setting)
|
||||
- legacy `models.providers.*.api: "openai"` → `"openai-completions"` (gateway startup also skips providers whose `api` is set to a future or unknown enum value rather than failing closed)
|
||||
- remove `plugins.entries.codex.config.codexDynamicToolsProfile`; Codex app-server always keeps Codex-native workspace tools native
|
||||
|
||||
Doctor warnings also include account-default guidance for multi-account channels:
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ Supported top-level fields:
|
||||
| -------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `discovery` | enabled | Model discovery settings for Codex app-server `model/list`. |
|
||||
| `appServer` | managed stdio app-server | Transport, command, auth, approval, sandbox, and timeout settings. |
|
||||
| `codexDynamicToolsProfile` | `"native-first"` | Use `"openclaw-compat"` to expose the full OpenClaw dynamic tool set to Codex app-server. |
|
||||
| `codexDynamicToolsLoading` | `"searchable"` | Use `"direct"` to put OpenClaw dynamic tools directly in the initial Codex tool context. |
|
||||
| `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. |
|
||||
| `codexPlugins` | disabled | Native Codex plugin/app support for migrated source-installed curated plugins. See [Native Codex plugins](/plugins/codex-native-plugins). |
|
||||
@@ -211,9 +210,8 @@ isolation on local launches.
|
||||
|
||||
## Dynamic tools
|
||||
|
||||
Codex dynamic tools default to the `native-first` profile and `searchable`
|
||||
loading. In that mode, OpenClaw does not expose dynamic tools that duplicate
|
||||
Codex-native workspace operations:
|
||||
Codex dynamic tools default to `searchable` loading. OpenClaw does not expose
|
||||
dynamic tools that duplicate Codex-native workspace operations:
|
||||
|
||||
- `read`
|
||||
- `write`
|
||||
|
||||
@@ -377,6 +377,128 @@ Get the thread id from the completed `/diagnostics` reply, `/codex binding`, or
|
||||
For upload mechanics and runtime-level diagnostics boundaries, see
|
||||
[Codex harness runtime](/plugins/codex-harness-runtime#codex-feedback-upload).
|
||||
|
||||
Auth is selected in this order:
|
||||
|
||||
1. An explicit OpenClaw Codex auth profile for the agent.
|
||||
2. The app-server's existing account in that agent's Codex home.
|
||||
3. For local stdio app-server launches only, `CODEX_API_KEY`, then
|
||||
`OPENAI_API_KEY`, when no app-server account is present and OpenAI auth is
|
||||
still required.
|
||||
|
||||
When OpenClaw sees a ChatGPT subscription-style Codex auth profile, it removes
|
||||
`CODEX_API_KEY` and `OPENAI_API_KEY` from the spawned Codex child process. That
|
||||
keeps Gateway-level API keys available for embeddings or direct OpenAI models
|
||||
without making native Codex app-server turns bill through the API by accident.
|
||||
Explicit Codex API-key profiles and local stdio env-key fallback use app-server
|
||||
login instead of inherited child-process env. WebSocket app-server connections
|
||||
do not receive Gateway env API-key fallback; use an explicit auth profile or the
|
||||
remote app-server's own account.
|
||||
|
||||
If a deployment needs additional environment isolation, add those variables to
|
||||
`appServer.clearEnv`:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
codex: {
|
||||
enabled: true,
|
||||
config: {
|
||||
appServer: {
|
||||
clearEnv: ["CODEX_API_KEY", "OPENAI_API_KEY"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`appServer.clearEnv` only affects the spawned Codex app-server child process.
|
||||
|
||||
Codex dynamic tools default to `searchable` loading. OpenClaw does not expose
|
||||
dynamic tools that duplicate Codex-native workspace operations: `read`, `write`,
|
||||
`edit`, `apply_patch`, `exec`, `process`, and `update_plan`. Remaining OpenClaw
|
||||
integration tools such as messaging, sessions, media, cron, browser, nodes,
|
||||
gateway, `heartbeat_respond`, and `web_search` are available through Codex tool
|
||||
search under the `openclaw` namespace, keeping the initial model context
|
||||
smaller.
|
||||
`sessions_yield` and message-tool-only source replies stay direct because those
|
||||
are turn-control contracts. Heartbeat collaboration instructions tell Codex to
|
||||
search for `heartbeat_respond` before ending a heartbeat turn when the tool is
|
||||
not already loaded.
|
||||
|
||||
Set `codexDynamicToolsLoading: "direct"` only when connecting to a custom Codex
|
||||
app-server that cannot search deferred dynamic tools or when debugging the full
|
||||
tool payload.
|
||||
|
||||
Supported top-level Codex plugin fields:
|
||||
|
||||
| Field | Default | Meaning |
|
||||
| -------------------------- | -------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `codexDynamicToolsLoading` | `"searchable"` | Use `"direct"` to put OpenClaw dynamic tools directly in the initial Codex tool context. |
|
||||
| `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. |
|
||||
| `codexPlugins` | disabled | Native Codex plugin/app support for migrated source-installed curated plugins. |
|
||||
|
||||
Supported `appServer` fields:
|
||||
|
||||
| Field | Default | Meaning |
|
||||
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
|
||||
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary; set it only for an explicit override. |
|
||||
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
|
||||
| `url` | unset | WebSocket app-server URL. |
|
||||
| `authToken` | unset | Bearer token for WebSocket transport. |
|
||||
| `headers` | `{}` | Extra WebSocket headers. |
|
||||
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. `CODEX_HOME` and `HOME` are reserved for OpenClaw's per-agent Codex isolation on local launches. |
|
||||
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
|
||||
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after a turn-scoped Codex app-server request while OpenClaw waits for `turn/completed`. Raise this for slow post-tool or status-only synthesis phases. |
|
||||
| `mode` | `"yolo"` unless local Codex requirements disallow YOLO | Preset for YOLO or guardian-reviewed execution. Local stdio requirements that omit `danger-full-access`, `never` approval, or the `user` reviewer make the implicit default guardian. |
|
||||
| `approvalPolicy` | `"never"` or an allowed guardian approval policy | Native Codex approval policy sent to thread start/resume/turn. Guardian defaults prefer `"on-request"` when allowed. |
|
||||
| `sandbox` | `"danger-full-access"` or an allowed guardian sandbox | Native Codex sandbox mode sent to thread start/resume. Guardian defaults prefer `"workspace-write"` when allowed, otherwise `"read-only"`. |
|
||||
| `approvalsReviewer` | `"user"` or an allowed guardian reviewer | Use `"auto_review"` to let Codex review native approval prompts when allowed, otherwise `guardian_subagent` or `user`. `guardian_subagent` remains a legacy alias. |
|
||||
| `serviceTier` | unset | Optional Codex app-server service tier. `"priority"` enables fast-mode routing, `"flex"` requests flex processing, `null` clears the override, and legacy `"fast"` is accepted as `"priority"`. |
|
||||
|
||||
OpenClaw-owned dynamic tool calls are bounded independently from
|
||||
`appServer.requestTimeoutMs`: Codex `item/tool/call` requests use a 30 second
|
||||
OpenClaw watchdog by default. A positive per-call `timeoutMs` argument extends
|
||||
or shortens that specific tool budget. The `image_generate` tool also uses
|
||||
`agents.defaults.imageGenerationModel.timeoutMs` when the tool call does not
|
||||
provide its own timeout, and the media-understanding `image` tool uses
|
||||
`tools.media.image.timeoutSeconds` or its 60 second media default. Dynamic tool
|
||||
budgets are capped at 600000 ms. On timeout, OpenClaw aborts the tool signal
|
||||
where supported and returns a failed dynamic-tool response to Codex so the turn
|
||||
can continue instead of leaving the session in `processing`.
|
||||
|
||||
After OpenClaw responds to a Codex turn-scoped app-server request, the harness
|
||||
also expects Codex to finish the native turn with `turn/completed`. If the
|
||||
app-server goes quiet for `appServer.turnCompletionIdleTimeoutMs` after that
|
||||
response, OpenClaw best-effort interrupts the Codex turn, records a diagnostic
|
||||
timeout, and releases the OpenClaw session lane so follow-up chat messages are
|
||||
not queued behind a stale native turn. Any non-terminal notification for the
|
||||
same turn, including `rawResponseItem/completed`, disarms that short watchdog
|
||||
because Codex has proven the turn is still alive; the longer terminal watchdog
|
||||
continues to protect genuinely stuck turns. Timeout diagnostics include the
|
||||
last app-server notification method and, for raw assistant response items, the
|
||||
item type, role, id, and a bounded assistant text preview.
|
||||
|
||||
Environment overrides remain available for local testing:
|
||||
|
||||
- `OPENCLAW_CODEX_APP_SERVER_BIN`
|
||||
- `OPENCLAW_CODEX_APP_SERVER_ARGS`
|
||||
- `OPENCLAW_CODEX_APP_SERVER_MODE=yolo|guardian`
|
||||
- `OPENCLAW_CODEX_APP_SERVER_APPROVAL_POLICY`
|
||||
- `OPENCLAW_CODEX_APP_SERVER_SANDBOX`
|
||||
|
||||
`OPENCLAW_CODEX_APP_SERVER_BIN` bypasses the managed binary when
|
||||
`appServer.command` is unset.
|
||||
|
||||
`OPENCLAW_CODEX_APP_SERVER_GUARDIAN=1` was removed. Use
|
||||
`plugins.entries.codex.config.appServer.mode: "guardian"` instead, or
|
||||
`OPENCLAW_CODEX_APP_SERVER_MODE=guardian` for one-off local testing. Config is
|
||||
preferred for repeatable deployments because it keeps the plugin behavior in the
|
||||
same reviewed file as the rest of the Codex harness setup.
|
||||
|
||||
## Native Codex plugins
|
||||
|
||||
Native Codex plugin support uses Codex app-server's own app and plugin
|
||||
|
||||
44
extensions/codex/doctor-contract-api.test.ts
Normal file
44
extensions/codex/doctor-contract-api.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { legacyConfigRules, normalizeCompatibilityConfig } from "./doctor-contract-api.js";
|
||||
|
||||
describe("codex doctor contract", () => {
|
||||
it("reports the retired dynamic tools profile config key", () => {
|
||||
expect(
|
||||
legacyConfigRules[0]?.match({
|
||||
codexDynamicToolsProfile: "openclaw-compat",
|
||||
codexDynamicToolsLoading: "direct",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(legacyConfigRules[0]?.match({ codexDynamicToolsLoading: "direct" })).toBe(false);
|
||||
});
|
||||
|
||||
it("removes the retired dynamic tools profile without dropping other Codex config", () => {
|
||||
const original = {
|
||||
plugins: {
|
||||
entries: {
|
||||
codex: {
|
||||
enabled: true,
|
||||
config: {
|
||||
codexDynamicToolsProfile: "openclaw-compat",
|
||||
codexDynamicToolsLoading: "direct",
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
appServer: { mode: "guardian" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = normalizeCompatibilityConfig({ cfg: original });
|
||||
|
||||
expect(result.changes).toEqual([
|
||||
"Removed retired plugins.entries.codex.config.codexDynamicToolsProfile; Codex app-server always keeps Codex-native workspace tools native.",
|
||||
]);
|
||||
expect(result.config.plugins?.entries?.codex?.config).toEqual({
|
||||
codexDynamicToolsLoading: "direct",
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
appServer: { mode: "guardian" },
|
||||
});
|
||||
expect(original.plugins.entries.codex.config).toHaveProperty("codexDynamicToolsProfile");
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,61 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import type { DoctorSessionRouteStateOwner } from "openclaw/plugin-sdk/runtime-doctor";
|
||||
|
||||
type LegacyConfigRule = {
|
||||
path: string[];
|
||||
message: string;
|
||||
match: (value: unknown) => boolean;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function hasRetiredDynamicToolsProfile(value: unknown): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(asRecord(value) ?? {}, "codexDynamicToolsProfile");
|
||||
}
|
||||
|
||||
export const legacyConfigRules: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["plugins", "entries", "codex", "config"],
|
||||
message:
|
||||
'plugins.entries.codex.config.codexDynamicToolsProfile is retired; Codex app-server always keeps Codex-native workspace tools native. Run "openclaw doctor --fix".',
|
||||
match: hasRetiredDynamicToolsProfile,
|
||||
},
|
||||
];
|
||||
|
||||
export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} {
|
||||
const rawEntry = asRecord(cfg.plugins?.entries?.codex);
|
||||
const rawPluginConfig = asRecord(rawEntry?.config);
|
||||
if (!rawPluginConfig || !hasRetiredDynamicToolsProfile(rawPluginConfig)) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const nextConfig = structuredClone(cfg) as OpenClawConfig & {
|
||||
plugins?: Record<string, unknown>;
|
||||
};
|
||||
const nextPlugins = asRecord(nextConfig.plugins);
|
||||
const nextEntries = asRecord(nextPlugins?.entries);
|
||||
const nextEntry = asRecord(nextEntries?.codex);
|
||||
const nextPluginConfig = asRecord(nextEntry?.config);
|
||||
if (!nextPluginConfig) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
delete nextPluginConfig.codexDynamicToolsProfile;
|
||||
return {
|
||||
config: nextConfig,
|
||||
changes: [
|
||||
"Removed retired plugins.entries.codex.config.codexDynamicToolsProfile; Codex app-server always keeps Codex-native workspace tools native.",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export const sessionRouteStateOwners: DoctorSessionRouteStateOwner[] = [
|
||||
{
|
||||
id: "codex",
|
||||
|
||||
@@ -33,11 +33,6 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"codexDynamicToolsProfile": {
|
||||
"type": "string",
|
||||
"enum": ["native-first", "openclaw-compat"],
|
||||
"default": "native-first"
|
||||
},
|
||||
"codexDynamicToolsLoading": {
|
||||
"type": "string",
|
||||
"enum": ["searchable", "direct"],
|
||||
@@ -197,11 +192,6 @@
|
||||
}
|
||||
},
|
||||
"uiHints": {
|
||||
"codexDynamicToolsProfile": {
|
||||
"label": "Dynamic Tools Profile",
|
||||
"help": "Select which OpenClaw dynamic tools are exposed to Codex app-server. native-first omits tools Codex already owns.",
|
||||
"advanced": true
|
||||
},
|
||||
"codexDynamicToolsLoading": {
|
||||
"label": "Dynamic Tools Loading",
|
||||
"help": "Use searchable to defer OpenClaw dynamic tools behind Codex tool search, or direct to expose them in the initial context.",
|
||||
|
||||
@@ -453,18 +453,25 @@ allowed_sandbox_modes = ["read-only", "workspace-write"]
|
||||
);
|
||||
});
|
||||
|
||||
it("parses dynamic tool profile controls", () => {
|
||||
it("parses dynamic tool controls", () => {
|
||||
expect(
|
||||
readCodexPluginConfig({
|
||||
codexDynamicToolsLoading: "direct",
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
}),
|
||||
).toEqual({
|
||||
codexDynamicToolsLoading: "direct",
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects the retired dynamic tool profile key", () => {
|
||||
expect(
|
||||
readCodexPluginConfig({
|
||||
codexDynamicToolsProfile: "openclaw-compat",
|
||||
codexDynamicToolsLoading: "direct",
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
}),
|
||||
).toMatchObject({
|
||||
codexDynamicToolsProfile: "openclaw-compat",
|
||||
codexDynamicToolsLoading: "direct",
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
});
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("parses native Codex plugin policy without treating wildcard as supported config", () => {
|
||||
|
||||
@@ -31,7 +31,6 @@ export type CodexAppServerEffectiveApprovalPolicy =
|
||||
export type CodexAppServerSandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
||||
type CodexAppServerApprovalsReviewer = "user" | "auto_review" | "guardian_subagent";
|
||||
type CodexAppServerCommandSource = "managed" | "resolved-managed" | "config" | "env";
|
||||
type CodexDynamicToolsProfile = "native-first" | "openclaw-compat";
|
||||
export type CodexDynamicToolsLoading = "searchable" | "direct";
|
||||
export type CodexPluginDestructivePolicy = boolean;
|
||||
|
||||
@@ -110,7 +109,6 @@ export type CodexAppServerRuntimeOptions = {
|
||||
};
|
||||
|
||||
export type CodexPluginConfig = {
|
||||
codexDynamicToolsProfile?: CodexDynamicToolsProfile;
|
||||
codexDynamicToolsLoading?: CodexDynamicToolsLoading;
|
||||
codexDynamicToolsExclude?: string[];
|
||||
discovery?: {
|
||||
@@ -194,7 +192,6 @@ const codexAppServerApprovalPolicySchema = z.enum([
|
||||
]);
|
||||
const codexAppServerSandboxSchema = z.enum(["read-only", "workspace-write", "danger-full-access"]);
|
||||
const codexAppServerApprovalsReviewerSchema = z.enum(["user", "auto_review", "guardian_subagent"]);
|
||||
const codexDynamicToolsProfileSchema = z.enum(["native-first", "openclaw-compat"]);
|
||||
const codexDynamicToolsLoadingSchema = z.enum(["searchable", "direct"]);
|
||||
const codexAppServerServiceTierSchema = z
|
||||
.preprocess(
|
||||
@@ -222,7 +219,6 @@ const codexPluginsConfigSchema = z
|
||||
|
||||
const codexPluginConfigSchema = z
|
||||
.object({
|
||||
codexDynamicToolsProfile: codexDynamicToolsProfileSchema.optional(),
|
||||
codexDynamicToolsLoading: codexDynamicToolsLoadingSchema.optional(),
|
||||
codexDynamicToolsExclude: z.array(z.string()).optional(),
|
||||
discovery: z
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CodexPluginConfig } from "./config.js";
|
||||
|
||||
export const CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES = [
|
||||
export const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
|
||||
"read",
|
||||
"write",
|
||||
"edit",
|
||||
@@ -20,16 +20,13 @@ export function normalizeCodexDynamicToolName(name: string): string {
|
||||
return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
|
||||
}
|
||||
|
||||
export function applyCodexDynamicToolProfile<T extends { name: string }>(
|
||||
export function filterCodexDynamicTools<T extends { name: string }>(
|
||||
tools: T[],
|
||||
config: Pick<CodexPluginConfig, "codexDynamicToolsProfile" | "codexDynamicToolsExclude">,
|
||||
config: Pick<CodexPluginConfig, "codexDynamicToolsExclude">,
|
||||
): T[] {
|
||||
const excludes = new Set<string>();
|
||||
const profile = config.codexDynamicToolsProfile ?? "native-first";
|
||||
if (profile === "native-first") {
|
||||
for (const name of CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES) {
|
||||
excludes.add(name);
|
||||
}
|
||||
for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
||||
excludes.add(name);
|
||||
}
|
||||
for (const name of config.codexDynamicToolsExclude ?? []) {
|
||||
const trimmed = normalizeCodexDynamicToolName(name);
|
||||
|
||||
@@ -544,7 +544,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("defaults Codex dynamic tools to the native-first profile", () => {
|
||||
it("filters Codex-native dynamic tools from app-server tool exposure", () => {
|
||||
const tools = [
|
||||
"read",
|
||||
"write",
|
||||
@@ -559,7 +559,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
"sessions_spawn",
|
||||
].map((name) => ({ name }));
|
||||
|
||||
expect(__testing.applyCodexDynamicToolProfile(tools, {}).map((tool) => tool.name)).toEqual([
|
||||
expect(__testing.filterCodexDynamicTools(tools, {}).map((tool) => tool.name)).toEqual([
|
||||
"web_search",
|
||||
"message",
|
||||
"heartbeat_respond",
|
||||
@@ -567,17 +567,16 @@ describe("runCodexAppServerAttempt", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("allows Codex dynamic tool filtering to opt back into OpenClaw compatibility", () => {
|
||||
it("applies additional Codex dynamic tool excludes without exposing Codex-native tools", () => {
|
||||
const tools = ["read", "exec", "message", "custom_tool"].map((name) => ({ name }));
|
||||
|
||||
expect(
|
||||
__testing
|
||||
.applyCodexDynamicToolProfile(tools, {
|
||||
codexDynamicToolsProfile: "openclaw-compat",
|
||||
.filterCodexDynamicTools(tools, {
|
||||
codexDynamicToolsExclude: ["custom_tool"],
|
||||
})
|
||||
.map((tool) => tool.name),
|
||||
).toEqual(["read", "exec", "message"]);
|
||||
).toEqual(["message"]);
|
||||
});
|
||||
|
||||
it("starts Codex threads without duplicate OpenClaw workspace tools by default", async () => {
|
||||
@@ -590,7 +589,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
});
|
||||
const dynamicTools = __testing.applyCodexDynamicToolProfile(
|
||||
const dynamicTools = __testing.filterCodexDynamicTools(
|
||||
[
|
||||
"read",
|
||||
"write",
|
||||
|
||||
@@ -73,10 +73,7 @@ import {
|
||||
type CodexPluginConfig,
|
||||
} from "./config.js";
|
||||
import { projectContextEngineAssemblyForCodex } from "./context-engine-projection.js";
|
||||
import {
|
||||
applyCodexDynamicToolProfile,
|
||||
normalizeCodexDynamicToolName,
|
||||
} from "./dynamic-tool-profile.js";
|
||||
import { filterCodexDynamicTools, normalizeCodexDynamicToolName } from "./dynamic-tool-profile.js";
|
||||
import { createCodexDynamicToolBridge, type CodexDynamicToolBridge } from "./dynamic-tools.js";
|
||||
import { handleCodexAppServerElicitationRequest } from "./elicitation-bridge.js";
|
||||
import { CodexAppServerEventProjector } from "./event-projector.js";
|
||||
@@ -1907,8 +1904,8 @@ async function buildDynamicTools(input: DynamicToolBuildParams) {
|
||||
input.runAbortController.abort("sessions_yield");
|
||||
},
|
||||
});
|
||||
const profiledTools = applyCodexDynamicToolProfile(allTools, input.pluginConfig);
|
||||
const visionFilteredTools = filterToolsForVisionInputs(profiledTools, {
|
||||
const codexFilteredTools = filterCodexDynamicTools(allTools, input.pluginConfig);
|
||||
const visionFilteredTools = filterToolsForVisionInputs(codexFilteredTools, {
|
||||
modelHasVision,
|
||||
hasInboundImages: (params.images?.length ?? 0) > 0,
|
||||
});
|
||||
@@ -2369,7 +2366,7 @@ export const __testing = {
|
||||
CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS,
|
||||
CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS,
|
||||
buildCodexNativeHookRelayId,
|
||||
applyCodexDynamicToolProfile,
|
||||
filterCodexDynamicTools,
|
||||
buildDynamicTools,
|
||||
filterCodexDynamicToolsForAllowlist,
|
||||
filterToolsForVisionInputs,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
resolveCodexAppServerRuntimeOptions,
|
||||
} from "./src/app-server/config.js";
|
||||
import type { CodexPluginConfig } from "./src/app-server/config.js";
|
||||
import { applyCodexDynamicToolProfile } from "./src/app-server/dynamic-tool-profile.js";
|
||||
import { filterCodexDynamicTools } from "./src/app-server/dynamic-tool-profile.js";
|
||||
import { createCodexDynamicToolBridge } from "./src/app-server/dynamic-tools.js";
|
||||
import type { CodexDynamicToolSpec, JsonObject } from "./src/app-server/protocol.js";
|
||||
import {
|
||||
@@ -70,15 +70,12 @@ export function buildCodexHarnessPromptSnapshot(params: {
|
||||
|
||||
export function createCodexDynamicToolSpecsForPromptSnapshot(params: {
|
||||
tools: AnyAgentTool[];
|
||||
pluginConfig?: Pick<
|
||||
CodexPluginConfig,
|
||||
"codexDynamicToolsProfile" | "codexDynamicToolsLoading" | "codexDynamicToolsExclude"
|
||||
>;
|
||||
pluginConfig?: Pick<CodexPluginConfig, "codexDynamicToolsLoading" | "codexDynamicToolsExclude">;
|
||||
directToolNames?: Iterable<string>;
|
||||
}): CodexDynamicToolSpec[] {
|
||||
const profiledTools = applyCodexDynamicToolProfile(params.tools, params.pluginConfig ?? {});
|
||||
const filteredTools = filterCodexDynamicTools(params.tools, params.pluginConfig ?? {});
|
||||
return createCodexDynamicToolBridge({
|
||||
tools: profiledTools,
|
||||
tools: filteredTools,
|
||||
signal: new AbortController().signal,
|
||||
loading: params.pluginConfig?.codexDynamicToolsLoading ?? "searchable",
|
||||
directToolNames: params.directToolNames,
|
||||
|
||||
@@ -19,6 +19,7 @@ const ANSI_ESCAPE_RE = new RegExp(String.raw`\u001B\[[0-9;]*m`, "g");
|
||||
const HASHED_ROOT_JS_RE = /^(?<base>.+)-[A-Za-z0-9_-]+\.js$/u;
|
||||
const DEFAULT_CAPTURE_BYTES = 8 * 1024 * 1024;
|
||||
const DEFAULT_HEARTBEAT_MS = 30_000;
|
||||
const DEFAULT_TSDOWN_NODE_OPTIONS = "--max-old-space-size=6144";
|
||||
const TERMINATION_GRACE_MS = 5_000;
|
||||
const TSDOWN_OUTPUT_ROOTS = ["dist", "dist-runtime"];
|
||||
const GENERATED_SOURCE_DECLARATION_PATHSPEC = ":(glob)extensions/**/*.d.ts";
|
||||
@@ -199,6 +200,19 @@ function parseNonNegativeInteger(value) {
|
||||
return Math.trunc(parsed);
|
||||
}
|
||||
|
||||
function resolveTsdownEnv(env) {
|
||||
const nodeOptions = env.NODE_OPTIONS?.trim() ?? "";
|
||||
if (/(?:^|\s)--max-old-space-size(?:=|\s+)/u.test(nodeOptions)) {
|
||||
return env;
|
||||
}
|
||||
return {
|
||||
...env,
|
||||
NODE_OPTIONS: nodeOptions
|
||||
? `${nodeOptions} ${DEFAULT_TSDOWN_NODE_OPTIONS}`
|
||||
: DEFAULT_TSDOWN_NODE_OPTIONS,
|
||||
};
|
||||
}
|
||||
|
||||
export function createTsdownOutputScanner(params = {}) {
|
||||
const maxCaptureBytes = params.maxCaptureBytes ?? DEFAULT_CAPTURE_BYTES;
|
||||
let captured = "";
|
||||
@@ -242,7 +256,7 @@ export function createTsdownOutputScanner(params = {}) {
|
||||
}
|
||||
|
||||
export function resolveTsdownBuildInvocation(params = {}) {
|
||||
const env = params.env ?? process.env;
|
||||
const env = resolveTsdownEnv(params.env ?? process.env);
|
||||
const runner = resolvePnpmRunner({
|
||||
pnpmArgs: [
|
||||
"exec",
|
||||
|
||||
@@ -82,7 +82,10 @@ type CodexPromptSnapshotApi = {
|
||||
};
|
||||
createCodexDynamicToolSpecsForPromptSnapshot: (params: {
|
||||
tools: AnyAgentTool[];
|
||||
pluginConfig?: { codexDynamicToolsProfile?: "native-first" | "openclaw-compat" };
|
||||
pluginConfig?: {
|
||||
codexDynamicToolsLoading?: "searchable" | "direct";
|
||||
codexDynamicToolsExclude?: string[];
|
||||
};
|
||||
directToolNames?: string[];
|
||||
}) => CodexDynamicToolSpec[];
|
||||
};
|
||||
@@ -359,7 +362,6 @@ function createDynamicTools(params: {
|
||||
});
|
||||
return codexApi.createCodexDynamicToolSpecsForPromptSnapshot({
|
||||
tools: normalized.filter((tool) => HAPPY_PATH_TOOL_NAMES.has(tool.name)),
|
||||
pluginConfig: { codexDynamicToolsProfile: "native-first" },
|
||||
directToolNames: ["message"],
|
||||
});
|
||||
}
|
||||
@@ -670,9 +672,7 @@ function renderScenarioSnapshot(scenario: PromptScenario): string {
|
||||
scenario,
|
||||
sessionKey: scenario.ctx.SessionKey ?? `agent:main:${scenario.id}`,
|
||||
});
|
||||
const appServer = codexApi.resolveCodexPromptSnapshotAppServerOptions({
|
||||
codexDynamicToolsProfile: "native-first",
|
||||
});
|
||||
const appServer = codexApi.resolveCodexPromptSnapshotAppServerOptions();
|
||||
const codexSnapshot = codexApi.buildCodexHarnessPromptSnapshot({
|
||||
attempt,
|
||||
cwd: WORKSPACE_DIR,
|
||||
|
||||
@@ -54,11 +54,21 @@ describe("resolveTsdownBuildInvocation", () => {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
shell: false,
|
||||
windowsVerbatimArguments: undefined,
|
||||
env: {},
|
||||
env: { NODE_OPTIONS: "--max-old-space-size=6144" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves explicit tsdown heap settings", () => {
|
||||
const result = resolveTsdownBuildInvocation({
|
||||
nodeExecPath: "/usr/bin/node",
|
||||
npmExecPath: "/tmp/pnpm.cjs",
|
||||
env: { NODE_OPTIONS: "--trace-warnings --max-old-space-size=8192" },
|
||||
});
|
||||
|
||||
expect(result.options.env.NODE_OPTIONS).toBe("--trace-warnings --max-old-space-size=8192");
|
||||
});
|
||||
|
||||
it("keeps source-checkout prune best-effort", () => {
|
||||
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const rmSync = vi.spyOn(fs, "rmSync");
|
||||
|
||||
Reference in New Issue
Block a user