mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix: drop invalid Codex app-server service tiers
This commit is contained in:
@@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Codex harness: ignore dynamic tool descriptions when deciding whether to reuse a native app-server thread while still fingerprinting tool schemas, so channel-specific copy changes no longer reset otherwise compatible Codex conversations. (#69976) Thanks @chen-zhang-cs-code.
|
||||
- Codex harness: drop invalid legacy app-server `serviceTier` values such as `"priority"` before native thread and turn requests, while keeping supported Codex tiers limited to `"fast"` and `"flex"`. Fixes #64815.
|
||||
- Codex harness: show bounded, sanitized permission target samples in app-server approval prompts, so native permission requests keep their specific hosts, roots, and paths visible without leaking home usernames or URL credentials. (#70340) Thanks @Lucenx9.
|
||||
- Docs/Codex harness: narrow native compaction docs to the current start/completion signals, without promising a readable summary or kept-entry audit list yet. (#69612) Thanks @91wan.
|
||||
- Providers/Amazon Bedrock: use known context-window metadata for discovered models while keeping the unknown-model fallback conservative, so compaction and overflow handling improve for newer Bedrock models without overstating unlisted model limits. Thanks @wirjo.
|
||||
|
||||
@@ -289,7 +289,7 @@ To opt in to Codex guardian-reviewed approvals, set `appServer.mode:
|
||||
config: {
|
||||
appServer: {
|
||||
mode: "guardian",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "fast",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -361,20 +361,20 @@ For an already-running app-server, use WebSocket transport:
|
||||
|
||||
Supported `appServer` fields:
|
||||
|
||||
| Field | Default | Meaning |
|
||||
| ------------------- | ---------------------------------------- | --------------------------------------------------------------- |
|
||||
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
|
||||
| `command` | `"codex"` | Executable for stdio transport. |
|
||||
| `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. |
|
||||
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
|
||||
| `mode` | `"yolo"` | Preset for YOLO or guardian-reviewed execution. |
|
||||
| `approvalPolicy` | `"never"` | Native Codex approval policy sent to thread start/resume/turn. |
|
||||
| `sandbox` | `"danger-full-access"` | Native Codex sandbox mode sent to thread start/resume. |
|
||||
| `approvalsReviewer` | `"user"` | Use `"guardian_subagent"` to let Codex Guardian review prompts. |
|
||||
| `serviceTier` | unset | Optional Codex service tier, for example `"priority"`. |
|
||||
| Field | Default | Meaning |
|
||||
| ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
|
||||
| `command` | `"codex"` | Executable for stdio transport. |
|
||||
| `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. |
|
||||
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
|
||||
| `mode` | `"yolo"` | Preset for YOLO or guardian-reviewed execution. |
|
||||
| `approvalPolicy` | `"never"` | Native Codex approval policy sent to thread start/resume/turn. |
|
||||
| `sandbox` | `"danger-full-access"` | Native Codex sandbox mode sent to thread start/resume. |
|
||||
| `approvalsReviewer` | `"user"` | Use `"guardian_subagent"` to let Codex Guardian review prompts. |
|
||||
| `serviceTier` | unset | Optional Codex app-server service tier: `"fast"`, `"flex"`, or `null`. Invalid legacy values are ignored. |
|
||||
|
||||
The older environment variables still work as fallbacks for local testing when
|
||||
the matching config field is unset:
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"enum": ["user", "guardian_subagent"],
|
||||
"default": "user"
|
||||
},
|
||||
"serviceTier": { "type": "string" }
|
||||
"serviceTier": { "type": ["string", "null"], "enum": ["fast", "flex", null] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@
|
||||
},
|
||||
"appServer.serviceTier": {
|
||||
"label": "Service Tier",
|
||||
"help": "Optional Codex service tier passed when starting or resuming threads.",
|
||||
"help": "Optional Codex app-server service tier. Use fast, flex, or null.",
|
||||
"advanced": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("Codex app-server config", () => {
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "danger-full-access",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "flex",
|
||||
},
|
||||
},
|
||||
env: {
|
||||
@@ -33,7 +33,7 @@ describe("Codex app-server config", () => {
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "danger-full-access",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "flex",
|
||||
start: expect.objectContaining({
|
||||
transport: "websocket",
|
||||
url: "ws://127.0.0.1:39175",
|
||||
@@ -43,6 +43,29 @@ describe("Codex app-server config", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("drops invalid legacy service tiers without discarding the rest of the config", () => {
|
||||
const runtime = resolveCodexAppServerRuntimeOptions({
|
||||
pluginConfig: {
|
||||
appServer: {
|
||||
mode: "guardian",
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "read-only",
|
||||
serviceTier: "priority",
|
||||
},
|
||||
},
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(runtime).toEqual(
|
||||
expect.objectContaining({
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "read-only",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
}),
|
||||
);
|
||||
expect(runtime).not.toHaveProperty("serviceTier");
|
||||
});
|
||||
|
||||
it("rejects malformed plugin config instead of treating freeform strings as control values", () => {
|
||||
expect(
|
||||
readCodexPluginConfig({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { z } from "zod";
|
||||
import type { CodexServiceTier } from "./protocol.js";
|
||||
|
||||
export type CodexAppServerTransportMode = "stdio" | "websocket";
|
||||
export type CodexAppServerPolicyMode = "yolo" | "guardian";
|
||||
@@ -24,7 +25,7 @@ export type CodexAppServerRuntimeOptions = {
|
||||
approvalPolicy: CodexAppServerApprovalPolicy;
|
||||
sandbox: CodexAppServerSandboxMode;
|
||||
approvalsReviewer: CodexAppServerApprovalsReviewer;
|
||||
serviceTier?: string;
|
||||
serviceTier?: CodexServiceTier;
|
||||
};
|
||||
|
||||
export type CodexPluginConfig = {
|
||||
@@ -44,7 +45,7 @@ export type CodexPluginConfig = {
|
||||
approvalPolicy?: CodexAppServerApprovalPolicy;
|
||||
sandbox?: CodexAppServerSandboxMode;
|
||||
approvalsReviewer?: CodexAppServerApprovalsReviewer;
|
||||
serviceTier?: string;
|
||||
serviceTier?: CodexServiceTier | null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -73,6 +74,10 @@ const codexAppServerApprovalPolicySchema = z.enum([
|
||||
]);
|
||||
const codexAppServerSandboxSchema = z.enum(["read-only", "workspace-write", "danger-full-access"]);
|
||||
const codexAppServerApprovalsReviewerSchema = z.enum(["user", "guardian_subagent"]);
|
||||
const codexAppServerServiceTierSchema = z.preprocess(
|
||||
(value) => (value === null ? null : resolveServiceTier(value)),
|
||||
z.enum(["fast", "flex"]).nullable().optional(),
|
||||
);
|
||||
|
||||
const codexPluginConfigSchema = z
|
||||
.object({
|
||||
@@ -96,7 +101,7 @@ const codexPluginConfigSchema = z
|
||||
approvalPolicy: codexAppServerApprovalPolicySchema.optional(),
|
||||
sandbox: codexAppServerSandboxSchema.optional(),
|
||||
approvalsReviewer: codexAppServerApprovalsReviewerSchema.optional(),
|
||||
serviceTier: z.string().optional(),
|
||||
serviceTier: codexAppServerServiceTierSchema,
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
@@ -127,6 +132,7 @@ export function resolveCodexAppServerRuntimeOptions(
|
||||
resolvePolicyMode(config.mode) ??
|
||||
resolvePolicyMode(env.OPENCLAW_CODEX_APP_SERVER_MODE) ??
|
||||
"yolo";
|
||||
const serviceTier = resolveServiceTier(config.serviceTier);
|
||||
if (transport === "websocket" && !url) {
|
||||
throw new Error(
|
||||
"plugins.entries.codex.config.appServer.url is required when appServer.transport is websocket",
|
||||
@@ -154,9 +160,7 @@ export function resolveCodexAppServerRuntimeOptions(
|
||||
approvalsReviewer:
|
||||
resolveApprovalsReviewer(config.approvalsReviewer) ??
|
||||
(policyMode === "guardian" ? "guardian_subagent" : "user"),
|
||||
...(readNonEmptyString(config.serviceTier)
|
||||
? { serviceTier: readNonEmptyString(config.serviceTier) }
|
||||
: {}),
|
||||
...(serviceTier ? { serviceTier } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -202,6 +206,10 @@ function resolveApprovalsReviewer(value: unknown): CodexAppServerApprovalsReview
|
||||
return value === "guardian_subagent" || value === "user" ? value : undefined;
|
||||
}
|
||||
|
||||
function resolveServiceTier(value: unknown): CodexServiceTier | undefined {
|
||||
return value === "fast" || value === "flex" ? value : undefined;
|
||||
}
|
||||
|
||||
function normalizePositiveNumber(value: unknown, fallback: number): number {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type JsonPrimitive = null | boolean | number | string;
|
||||
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
||||
export type JsonObject = { [key: string]: JsonValue };
|
||||
export type CodexServiceTier = "fast" | "flex";
|
||||
|
||||
export type RpcRequest = {
|
||||
id?: number | string;
|
||||
@@ -55,7 +56,7 @@ export type CodexThreadStartParams = {
|
||||
approvalPolicy?: "never" | "on-request" | "on-failure" | "untrusted";
|
||||
approvalsReviewer?: "user" | "guardian_subagent";
|
||||
sandbox?: "read-only" | "workspace-write" | "danger-full-access";
|
||||
serviceTier?: string | null;
|
||||
serviceTier?: CodexServiceTier | null;
|
||||
config?: JsonObject | null;
|
||||
serviceName?: string | null;
|
||||
baseInstructions?: string | null;
|
||||
@@ -73,7 +74,7 @@ export type CodexThreadResumeParams = {
|
||||
approvalPolicy?: "never" | "on-request" | "on-failure" | "untrusted";
|
||||
approvalsReviewer?: "user" | "guardian_subagent";
|
||||
sandbox?: "read-only" | "workspace-write" | "danger-full-access";
|
||||
serviceTier?: string | null;
|
||||
serviceTier?: CodexServiceTier | null;
|
||||
baseInstructions?: string | null;
|
||||
developerInstructions?: string | null;
|
||||
persistExtendedHistory?: boolean;
|
||||
@@ -94,7 +95,7 @@ export type CodexTurnStartParams = {
|
||||
approvalPolicy?: "never" | "on-request" | "on-failure" | "untrusted";
|
||||
approvalsReviewer?: "user" | "guardian_subagent";
|
||||
model?: string | null;
|
||||
serviceTier?: string | null;
|
||||
serviceTier?: CodexServiceTier | null;
|
||||
effort?: "minimal" | "low" | "medium" | "high" | "xhigh" | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -851,7 +851,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
sandbox: "danger-full-access",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "fast",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -866,7 +866,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
sandbox: "danger-full-access",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "fast",
|
||||
developerInstructions: expect.stringContaining(CODEX_GPT5_BEHAVIOR_CONTRACT),
|
||||
persistExtendedHistory: true,
|
||||
});
|
||||
@@ -877,7 +877,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
params: expect.objectContaining({
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "fast",
|
||||
model: "gpt-5.4-codex",
|
||||
}),
|
||||
},
|
||||
@@ -885,6 +885,35 @@ describe("runCodexAppServerAttempt", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("drops invalid legacy service tiers before app-server resume and turn requests", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
await writeExistingBinding(sessionFile, workspaceDir, { model: "gpt-5.2" });
|
||||
const { requests, waitForMethod, completeTurn } = createResumeHarness();
|
||||
|
||||
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
|
||||
pluginConfig: {
|
||||
appServer: {
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "danger-full-access",
|
||||
serviceTier: "priority",
|
||||
},
|
||||
},
|
||||
});
|
||||
await waitForMethod("turn/start");
|
||||
await completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
|
||||
await run;
|
||||
|
||||
const resumeRequest = requests.find((request) => request.method === "thread/resume");
|
||||
expect(resumeRequest?.params).toEqual(
|
||||
expect.not.objectContaining({ serviceTier: expect.anything() }),
|
||||
);
|
||||
const turnRequest = requests.find((request) => request.method === "turn/start");
|
||||
expect(turnRequest?.params).toEqual(
|
||||
expect.not.objectContaining({ serviceTier: expect.anything() }),
|
||||
);
|
||||
});
|
||||
|
||||
it("builds resume and turn params from the currently selected OpenClaw model", () => {
|
||||
const params = createParams("/tmp/session.jsonl", "/tmp/workspace");
|
||||
const appServer = {
|
||||
@@ -898,7 +927,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
approvalPolicy: "on-request" as const,
|
||||
approvalsReviewer: "guardian_subagent" as const,
|
||||
sandbox: "danger-full-access" as const,
|
||||
serviceTier: "priority",
|
||||
serviceTier: "flex" as const,
|
||||
};
|
||||
|
||||
expect(buildThreadResumeParams(params, { threadId: "thread-1", appServer })).toEqual({
|
||||
@@ -908,7 +937,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
sandbox: "danger-full-access",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "flex",
|
||||
developerInstructions: expect.stringContaining(CODEX_GPT5_BEHAVIOR_CONTRACT),
|
||||
persistExtendedHistory: true,
|
||||
});
|
||||
@@ -921,7 +950,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
model: "gpt-5.4-codex",
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "guardian_subagent",
|
||||
serviceTier: "priority",
|
||||
serviceTier: "flex",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user