



























@@ -8,6 +8,7 @@ import {
88bridgeCodexAppServerStartOptions,
99refreshCodexAppServerAuthTokens,
1010} from "./auth-bridge.js";
11+import type { CodexAppServerStartOptions } from "./config.js";
11121213const oauthMocks = vi.hoisted(() => ({
1314refreshOpenAICodexToken: vi.fn(),
@@ -96,25 +97,54 @@ afterEach(() => {
9697providerRuntimeMocks.refreshProviderOAuthCredentialWithPlugin.mockClear();
9798});
9899100+function createStartOptions(
101+overrides: Partial<CodexAppServerStartOptions> = {},
102+): CodexAppServerStartOptions {
103+return {
104+transport: "stdio",
105+command: "codex",
106+args: ["app-server"],
107+headers: { authorization: "Bearer dev-token" },
108+ ...overrides,
109+};
110+}
111+112+async function writeCodexCliAuthFile(codexHome: string): Promise<void> {
113+await fs.mkdir(codexHome, { recursive: true });
114+await fs.writeFile(
115+path.join(codexHome, "auth.json"),
116+JSON.stringify({
117+tokens: {
118+access_token: "cli-access-token",
119+refresh_token: "cli-refresh-token",
120+account_id: "cli-account-123",
121+},
122+}),
123+);
124+}
125+99126describe("bridgeCodexAppServerStartOptions", () => {
100-it("leaves Codex app-server start options unchanged", async () => {
127+it("clears an inherited OpenAI API key when local Codex CLI OAuth is available", async () => {
101128const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
102-const startOptions = {
103-transport: "stdio" as const,
104-command: "codex",
105-args: ["app-server"],
106-headers: { authorization: "Bearer dev-token" },
129+const codexHome = path.join(agentDir, "codex-home");
130+const startOptions = createStartOptions({
107131env: { CODEX_HOME: "/tmp/source-codex-home", EXISTING: "1" },
108132clearEnv: ["FOO"],
109-};
133+});
134+vi.stubEnv("CODEX_HOME", codexHome);
110135try {
136+await writeCodexCliAuthFile(codexHome);
137+111138await expect(
112139bridgeCodexAppServerStartOptions({
113140 startOptions,
114141 agentDir,
115-authProfileId: "openai-codex:default",
116142}),
117-).resolves.toBe(startOptions);
143+).resolves.toEqual({
144+ ...startOptions,
145+clearEnv: ["FOO", "OPENAI_API_KEY"],
146+});
147+expect(startOptions.clearEnv).toEqual(["FOO"]);
118148await expect(fs.access(path.join(agentDir, "harness-auth"))).rejects.toMatchObject({
119149code: "ENOENT",
120150});
@@ -123,6 +153,126 @@ describe("bridgeCodexAppServerStartOptions", () => {
123153}
124154});
125155156+it("clears an inherited OpenAI API key for an explicit Codex OAuth profile", async () => {
157+const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
158+const startOptions = createStartOptions({ clearEnv: ["FOO"] });
159+try {
160+upsertAuthProfile({
161+ agentDir,
162+profileId: "openai-codex:work",
163+credential: {
164+type: "oauth",
165+provider: "openai-codex",
166+access: "access-token",
167+refresh: "refresh-token",
168+expires: Date.now() + 24 * 60 * 60_000,
169+accountId: "account-123",
170+},
171+});
172+173+await expect(
174+bridgeCodexAppServerStartOptions({
175+ startOptions,
176+ agentDir,
177+authProfileId: "openai-codex:work",
178+}),
179+).resolves.toEqual({
180+ ...startOptions,
181+clearEnv: ["FOO", "OPENAI_API_KEY"],
182+});
183+} finally {
184+await fs.rm(agentDir, { recursive: true, force: true });
185+}
186+});
187+188+it("clears an inherited OpenAI API key for an explicit Codex token profile", async () => {
189+const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
190+const startOptions = createStartOptions({ clearEnv: ["FOO"] });
191+try {
192+upsertAuthProfile({
193+ agentDir,
194+profileId: "openai-codex:work",
195+credential: {
196+type: "token",
197+provider: "openai-codex",
198+token: "access-token",
199+},
200+});
201+202+await expect(
203+bridgeCodexAppServerStartOptions({
204+ startOptions,
205+ agentDir,
206+authProfileId: "openai-codex:work",
207+}),
208+).resolves.toEqual({
209+ ...startOptions,
210+clearEnv: ["FOO", "OPENAI_API_KEY"],
211+});
212+} finally {
213+await fs.rm(agentDir, { recursive: true, force: true });
214+}
215+});
216+217+it("keeps an inherited OpenAI API key for an explicit Codex api-key profile", async () => {
218+const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
219+const startOptions = createStartOptions({ clearEnv: ["FOO"] });
220+try {
221+upsertAuthProfile({
222+ agentDir,
223+profileId: "openai-codex:work",
224+credential: {
225+type: "api_key",
226+provider: "openai-codex",
227+key: "explicit-api-key",
228+},
229+});
230+231+await expect(
232+bridgeCodexAppServerStartOptions({
233+ startOptions,
234+ agentDir,
235+authProfileId: "openai-codex:work",
236+}),
237+).resolves.toBe(startOptions);
238+} finally {
239+await fs.rm(agentDir, { recursive: true, force: true });
240+}
241+});
242+243+it("does not clear process environment for websocket app-server connections", async () => {
244+const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
245+const startOptions = createStartOptions({
246+transport: "websocket",
247+url: "ws://127.0.0.1:1455",
248+clearEnv: ["FOO"],
249+});
250+try {
251+upsertAuthProfile({
252+ agentDir,
253+profileId: "openai-codex:work",
254+credential: {
255+type: "oauth",
256+provider: "openai-codex",
257+access: "access-token",
258+refresh: "refresh-token",
259+expires: Date.now() + 24 * 60 * 60_000,
260+accountId: "account-123",
261+},
262+});
263+264+await expect(
265+bridgeCodexAppServerStartOptions({
266+ startOptions,
267+ agentDir,
268+authProfileId: "openai-codex:work",
269+}),
270+).resolves.toBe(startOptions);
271+} finally {
272+await fs.rm(agentDir, { recursive: true, force: true });
273+}
274+});
275+126276it("applies an OpenAI Codex OAuth profile through app-server login", async () => {
127277const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
128278const request = vi.fn(async () => ({ type: "chatgptAuthTokens" }));
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。