Skip to content

Commit e0b476d

Browse files
committed
fix(codex): simplify remote workspace root
1 parent d386d8e commit e0b476d

11 files changed

Lines changed: 94 additions & 84 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
1616
### Changes
1717

1818
- Codex harness: remove the configurable remote app-server plugin mutation policy; configured native Codex plugins now always use install-and-refresh activation on the connected app-server. (NOT_IN_SPEC)
19+
- Codex harness: replace the remote workspace mapping object with `remoteWorkspaceRoot` so app-server config names only remote filesystem paths while OpenClaw infers local cwd suffixes. (NOT_IN_SPEC)
1920
- Providers/models: add GLM-5.2 support and Claude Haiku 4.5 catalog entries while keeping provider-qualified model IDs normalized across OpenRouter and Google Vertex paths. (#92796, #90116, #92627, #91218) Thanks @arkyu2077, @liuhao1024, and @bymle.
2021
- Web search: keep key-free providers such as Parallel Free, DuckDuckGo, Ollama, and Codex Hosted Search as explicit opt-ins instead of selecting them automatically when no API-backed provider is configured. (#93616) Thanks @davemorin and @vincentkoc.
2122
- Channel plugins: ship Telegram rich-message delivery and WhatsApp ACP binding support, including preserved intentional line breaks, rich prompt handoff to CLI backends, and transport fixtures for richer drafts. (#92679, #93164, #92513) Thanks @obviyus and @TurboTheTurtle.

docs/plugins/codex-harness-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Supported `appServer` fields:
9494
| `authToken` | unset | Bearer token for WebSocket transport. |
9595
| `headers` | `{}` | Extra WebSocket headers. |
9696
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. |
97-
| `remoteWorkspace` | unset | Maps local OpenClaw workspace path prefixes to equivalent remote app-server path prefixes before OpenClaw sends working directories to Codex. Configure both `localRoot` and `remoteRoot` when the gateway and app-server see the same checkout at different filesystem paths. |
97+
| `remoteWorkspaceRoot` | unset | Remote Codex app-server workspace root. When set, OpenClaw infers the local workspace root from the resolved OpenClaw workspace, preserves the current cwd suffix under this remote root, and sends only the final app-server cwd to Codex. If the cwd is outside the resolved OpenClaw workspace root, OpenClaw fails closed instead of sending a gateway-local path to the remote app-server. |
9898
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
9999
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after Codex accepts a turn or after a turn-scoped app-server request while OpenClaw waits for `turn/completed`. |
100100
| `postToolRawAssistantCompletionIdleTimeoutMs` | `300000` | Completion-idle and progress guard used after a tool handoff, native tool completion, post-tool raw assistant progress, raw reasoning completion, or reasoning progress while OpenClaw waits for `turn/completed`. Use this for trusted or heavy workloads where post-tool synthesis can legitimately stay quiet longer than the final assistant release budget. |

docs/plugins/codex-harness.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ Supported `appServer` fields:
556556
| `headers` | `{}` | Extra WebSocket headers. |
557557
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. OpenClaw keeps per-agent `CODEX_HOME` and inherited `HOME` for local launches. |
558558
| `codeModeOnly` | `false` | Opt into Codex's code-mode-only tool surface. OpenClaw dynamic tools remain registered with Codex so nested `tools.*` calls return through the app-server `item/tool/call` bridge. |
559-
| `remoteWorkspace` | unset | Maps local OpenClaw workspace path prefixes to equivalent remote app-server path prefixes before OpenClaw sends working directories to Codex. Configure both `localRoot` and `remoteRoot` when the gateway and app-server see the same checkout at different filesystem paths. |
559+
| `remoteWorkspaceRoot` | unset | Remote Codex app-server workspace root. When set, OpenClaw infers the local workspace root from the resolved OpenClaw workspace, preserves the current cwd suffix under this remote root, and sends only the final app-server cwd to Codex. If the cwd is outside the resolved OpenClaw workspace root, OpenClaw fails closed instead of sending a gateway-local path to the remote app-server. |
560560
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
561561
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after Codex accepts a turn or after a turn-scoped app-server request while OpenClaw waits for `turn/completed`. |
562562
| `postToolRawAssistantCompletionIdleTimeoutMs` | `300000` | Completion-idle and progress guard used after a tool handoff, native tool completion, post-tool raw assistant progress, raw reasoning completion, or reasoning progress while OpenClaw waits for `turn/completed`. Use this for trusted or heavy workloads where post-tool synthesis can legitimately stay quiet longer than the final assistant release budget. |

extensions/codex/openclaw.plugin.json

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,8 @@
161161
"type": "array",
162162
"items": { "type": "string" }
163163
},
164-
"remoteWorkspace": {
165-
"type": "object",
166-
"additionalProperties": false,
167-
"properties": {
168-
"localRoot": { "type": "string" },
169-
"remoteRoot": { "type": "string" }
170-
},
171-
"required": ["localRoot", "remoteRoot"]
164+
"remoteWorkspaceRoot": {
165+
"type": "string"
172166
},
173167
"codeModeOnly": {
174168
"type": "boolean",
@@ -395,9 +389,9 @@
395389
"help": "Environment variable names removed from the spawned stdio app-server process after overrides are applied.",
396390
"advanced": true
397391
},
398-
"appServer.remoteWorkspace": {
399-
"label": "Remote Workspace",
400-
"help": "Maps a local OpenClaw workspace path prefix to the equivalent remote Codex app-server path prefix.",
392+
"appServer.remoteWorkspaceRoot": {
393+
"label": "Remote Workspace Root",
394+
"help": "Remote Codex app-server workspace root used to project OpenClaw cwd suffixes before starting Codex threads.",
401395
"advanced": true
402396
},
403397
"appServer.codeModeOnly": {

extensions/codex/src/app-server/attempt-startup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,10 @@ export async function startCodexAttemptThread(params: {
301301
);
302302
const startupExecutionCwd = resolveCodexAppServerExecutionCwd({
303303
effectiveCwd: params.effectiveCwd,
304+
localWorkspaceRoot: params.effectiveWorkspace,
304305
environment: startupSandboxEnvironment,
305306
nativeToolSurfaceEnabled: params.nativeToolSurfaceEnabled,
306-
remoteWorkspace: params.appServer.remoteWorkspace,
307+
remoteWorkspaceRoot: params.appServer.remoteWorkspaceRoot,
307308
});
308309
const startupSandboxPolicy = startupSandboxEnvironment
309310
? resolveCodexExternalSandboxPolicyForOpenClawSandbox(params.sandbox)

extensions/codex/src/app-server/config.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,20 @@ describe("Codex app-server config", () => {
366366
authToken: "capability-token",
367367
connectionClass: "remote",
368368
remoteAppsSubstrate: "preconfigured",
369+
remoteWorkspace: {
370+
localRoot: "/Users/kevinlin/code/openclaw",
371+
remoteRoot: "/home/oai/openclaw-workspaces",
372+
},
373+
},
374+
}),
375+
).toStrictEqual({});
376+
expect(
377+
readCodexPluginConfig({
378+
appServer: {
379+
remoteWorkspace: {
380+
localRoot: "/Users/kevinlin/code/openclaw",
381+
remoteRoot: "/home/oai/openclaw-workspaces",
382+
},
369383
},
370384
}),
371385
).toStrictEqual({});
@@ -387,21 +401,15 @@ describe("Codex app-server config", () => {
387401
transport: "websocket",
388402
url: "wss://codex-app-server.example.internal/ws",
389403
authToken: "capability-token",
390-
remoteWorkspace: {
391-
localRoot: " /Users/kevinlin/code/openclaw ",
392-
remoteRoot: " /home/oai/openclaw-workspaces ",
393-
},
404+
remoteWorkspaceRoot: " /home/oai/openclaw-workspaces ",
394405
},
395406
},
396407
});
397408

398409
expectFields(runtime, "runtime", {
399410
connectionClass: "remote",
400411
remoteAppsSubstrate: "preconfigured",
401-
remoteWorkspace: {
402-
localRoot: "/Users/kevinlin/code/openclaw",
403-
remoteRoot: "/home/oai/openclaw-workspaces",
404-
},
412+
remoteWorkspaceRoot: "/home/oai/openclaw-workspaces",
405413
});
406414
});
407415

extensions/codex/src/app-server/config.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ type CodexAppServerTransportMode = "stdio" | "websocket";
3030
type CodexAppServerPolicyMode = "yolo" | "guardian";
3131
export type CodexAppServerConnectionClass = "local-loopback" | "remote";
3232
export type CodexAppServerRemoteAppsSubstrate = "preconfigured";
33-
export type CodexAppServerRemoteWorkspaceMapping = {
34-
localRoot: string;
35-
remoteRoot: string;
36-
};
3733
type OpenClawExecMode = "deny" | "allowlist" | "ask" | "auto" | "full";
3834
type OpenClawExecSecurity = "deny" | "allowlist" | "full";
3935
type OpenClawExecAsk = "off" | "on-miss" | "always";
@@ -178,7 +174,7 @@ export type CodexAppServerRuntimeOptions = {
178174
start: CodexAppServerStartOptions;
179175
connectionClass: CodexAppServerConnectionClass;
180176
remoteAppsSubstrate: CodexAppServerRemoteAppsSubstrate;
181-
remoteWorkspace?: CodexAppServerRemoteWorkspaceMapping;
177+
remoteWorkspaceRoot?: string;
182178
codeModeOnly: boolean;
183179
requestTimeoutMs: number;
184180
turnCompletionIdleTimeoutMs: number;
@@ -218,7 +214,7 @@ export type CodexPluginConfig = {
218214
authToken?: string;
219215
headers?: Record<string, string>;
220216
clearEnv?: string[];
221-
remoteWorkspace?: CodexAppServerRemoteWorkspaceMapping;
217+
remoteWorkspaceRoot?: string;
222218
codeModeOnly?: boolean;
223219
requestTimeoutMs?: number;
224220
turnCompletionIdleTimeoutMs?: number;
@@ -252,7 +248,7 @@ export const CODEX_APP_SERVER_CONFIG_KEYS = [
252248
"authToken",
253249
"headers",
254250
"clearEnv",
255-
"remoteWorkspace",
251+
"remoteWorkspaceRoot",
256252
"codeModeOnly",
257253
"requestTimeoutMs",
258254
"turnCompletionIdleTimeoutMs",
@@ -320,12 +316,7 @@ const codexAppServerExperimentalSchema = z
320316
sandboxExecServer: z.boolean().optional(),
321317
})
322318
.strict();
323-
const codexAppServerRemoteWorkspaceSchema = z
324-
.object({
325-
localRoot: z.string().trim().min(1),
326-
remoteRoot: z.string().trim().min(1),
327-
})
328-
.strict();
319+
const codexAppServerRemoteWorkspaceRootSchema = z.string().trim().min(1);
329320
const codexAppServerNetworkProxyDomainPermissionSchema = z.enum(["allow", "deny"]);
330321
const codexAppServerNetworkProxyUnixSocketPermissionSchema = z.enum(["allow", "none"]);
331322
const codexAppServerNetworkProxySchema = z
@@ -399,7 +390,7 @@ const codexPluginConfigSchema = z
399390
authToken: z.string().optional(),
400391
headers: z.record(z.string(), z.string()).optional(),
401392
clearEnv: z.array(z.string()).optional(),
402-
remoteWorkspace: codexAppServerRemoteWorkspaceSchema.optional(),
393+
remoteWorkspaceRoot: codexAppServerRemoteWorkspaceRootSchema.optional(),
403394
codeModeOnly: z.boolean().optional(),
404395
requestTimeoutMs: z.number().positive().optional(),
405396
turnCompletionIdleTimeoutMs: z.number().positive().optional(),
@@ -544,7 +535,7 @@ export function resolveCodexAppServerRuntimeOptions(
544535
const url = readNonEmptyString(config.url);
545536
const connectionClass = inferCodexAppServerConnectionClass({ transport, url });
546537
const remoteAppsSubstrate: CodexAppServerRemoteAppsSubstrate = "preconfigured";
547-
const remoteWorkspace = normalizeRemoteWorkspaceMapping(config.remoteWorkspace);
538+
const remoteWorkspaceRoot = normalizeRemoteWorkspaceRoot(config.remoteWorkspaceRoot);
548539
const execMode = resolveEffectiveOpenClawExecModeForCodexAppServer({
549540
execMode: params.execMode,
550541
execPolicy: params.execPolicy,
@@ -671,7 +662,7 @@ export function resolveCodexAppServerRuntimeOptions(
671662
},
672663
connectionClass,
673664
remoteAppsSubstrate,
674-
...(remoteWorkspace ? { remoteWorkspace } : {}),
665+
...(remoteWorkspaceRoot ? { remoteWorkspaceRoot } : {}),
675666
codeModeOnly: config.codeModeOnly === true,
676667
requestTimeoutMs: normalizePositiveNumber(config.requestTimeoutMs, 60_000),
677668
turnCompletionIdleTimeoutMs: normalizePositiveNumber(
@@ -1040,12 +1031,8 @@ function resolveTransport(value: unknown): CodexAppServerTransportMode {
10401031
return value === "websocket" ? "websocket" : "stdio";
10411032
}
10421033

1043-
function normalizeRemoteWorkspaceMapping(
1044-
value: CodexAppServerRemoteWorkspaceMapping | undefined,
1045-
): CodexAppServerRemoteWorkspaceMapping | undefined {
1046-
const localRoot = readNonEmptyString(value?.localRoot);
1047-
const remoteRoot = readNonEmptyString(value?.remoteRoot);
1048-
return localRoot && remoteRoot ? { localRoot, remoteRoot } : undefined;
1034+
function normalizeRemoteWorkspaceRoot(value: string | undefined): string | undefined {
1035+
return readNonEmptyString(value);
10491036
}
10501037

10511038
function inferCodexAppServerConnectionClass(params: {

extensions/codex/src/app-server/dynamic-tool-build.test.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -148,29 +148,39 @@ describe("Codex app-server dynamic tool build", () => {
148148
).toBe("discord");
149149
});
150150

151-
it("maps local gateway workspace paths to remote Codex app-server paths", () => {
151+
it("maps local gateway workspace suffixes to the remote Codex app-server root", () => {
152152
expect(
153-
mapCodexAppServerRemoteWorkspacePath(
154-
"/Users/kevinlin/code/openclaw/packages/example",
155-
{
156-
localRoot: "/Users/kevinlin/code/openclaw",
157-
remoteRoot: "/home/oai/openclaw-workspaces",
158-
},
159-
),
153+
mapCodexAppServerRemoteWorkspacePath({
154+
value: "/Users/kevinlin/code/openclaw/packages/example",
155+
localWorkspaceRoot: "/Users/kevinlin/code/openclaw",
156+
remoteWorkspaceRoot: "/home/oai/openclaw-workspaces",
157+
}),
160158
).toBe("/home/oai/openclaw-workspaces/packages/example");
161159
expect(
162-
mapCodexAppServerRemoteWorkspacePath("/Users/kevinlin/code/other", {
163-
localRoot: "/Users/kevinlin/code/openclaw",
164-
remoteRoot: "/home/oai/openclaw-workspaces",
160+
mapCodexAppServerRemoteWorkspacePath({
161+
value: "/Users/kevinlin/code/openclaw",
162+
localWorkspaceRoot: "/Users/kevinlin/code/openclaw",
163+
remoteWorkspaceRoot: "/home/oai/openclaw-workspaces",
165164
}),
166-
).toBe("/Users/kevinlin/code/other");
165+
).toBe("/home/oai/openclaw-workspaces");
166+
});
167+
168+
it("fails closed when remote cwd projection cannot stay under the remote workspace root", () => {
169+
expect(() =>
170+
mapCodexAppServerRemoteWorkspacePath({
171+
value: "/Users/kevinlin/code/other",
172+
localWorkspaceRoot: "/Users/kevinlin/code/openclaw",
173+
remoteWorkspaceRoot: "/home/oai/openclaw-workspaces",
174+
}),
175+
).toThrow("outside OpenClaw workspace root");
167176
});
168177

169178
it("maps Windows child paths through remote Codex app-server workspaces", () => {
170179
expect(
171-
mapCodexAppServerRemoteWorkspacePath("C:\\Users\\kevinlin\\code\\openclaw\\packages\\example", {
172-
localRoot: "C:\\Users\\kevinlin\\code\\openclaw",
173-
remoteRoot: "/home/oai/openclaw-workspaces",
180+
mapCodexAppServerRemoteWorkspacePath({
181+
value: "C:\\Users\\kevinlin\\code\\openclaw\\packages\\example",
182+
localWorkspaceRoot: "C:\\Users\\kevinlin\\code\\openclaw",
183+
remoteWorkspaceRoot: "/home/oai/openclaw-workspaces",
174184
}),
175185
).toBe("/home/oai/openclaw-workspaces/packages/example");
176186
});
@@ -184,10 +194,8 @@ describe("Codex app-server dynamic tool build", () => {
184194
cwd: "/Users/kevinlin/code/openclaw/sandbox",
185195
} as never,
186196
nativeToolSurfaceEnabled: true,
187-
remoteWorkspace: {
188-
localRoot: "/Users/kevinlin/code/openclaw",
189-
remoteRoot: "/home/oai/openclaw-workspaces",
190-
},
197+
localWorkspaceRoot: "/Users/kevinlin/code/openclaw",
198+
remoteWorkspaceRoot: "/home/oai/openclaw-workspaces",
191199
}),
192200
).toBe("/home/oai/openclaw-workspaces/sandbox");
193201
});

0 commit comments

Comments
 (0)