

























@@ -6,6 +6,7 @@ import { promisify } from "node:util";
66import { afterEach, describe, expect, it, vi } from "vitest";
77import { prepareAcpxCodexAuthConfig } from "./codex-auth-bridge.js";
88import { resolveAcpxPluginConfig } from "./config.js";
9+import { OPENCLAW_ACPX_LEASE_ID_ARG, OPENCLAW_GATEWAY_INSTANCE_ID_ARG } from "./process-lease.js";
9101011const execFileAsync = promisify(execFile);
1112const tempDirs: string[] = [];
@@ -241,11 +242,14 @@ describe("prepareAcpxCodexAuthConfig", () => {
241242expect(wrapper).toContain('killChildTree("SIGTERM")');
242243expect(wrapper).toContain('killChildTree("SIGKILL", { force: true })');
243244expect(wrapper).toMatch(
244-/forceKillTimer = setTimeout\(\(\) => \{\s*killChildTree\("SIGKILL", \{ force: true \}\);\s*process\.exit\(1\);/s,
245+/forceKillTimer = setTimeout\(\(\) => \{\s*killChildTree\("SIGKILL", \{ force: true \}\);\s*childExitCode = 1;/s,
245246);
246247expect(wrapper).toMatch(
247248/child\.on\("exit", \(code, signal\) => \{\s*if \(parentWatcher\) \{\s*clearInterval\(parentWatcher\);\s*\}\s*if \(orphanCleanupStarted\) \{\s*return;\s*\}/s,
248249);
250+expect(wrapper).toMatch(
251+/child\.on\("close", \(\) => \{\s*finishStderrLog\(\);\s*process\.exit\(childExitCode\);/s,
252+);
249253expect(wrapper).not.toMatch(
250254/forceKillTimer = setTimeout\(\(\) => killChildTree\("SIGKILL"\), 1_500\);\s*forceKillTimer\.unref\?\.\(\);\s*process\.exit\(1\);/s,
251255);
@@ -357,7 +361,33 @@ describe("prepareAcpxCodexAuthConfig", () => {
357361);
358362await fs.writeFile(
359363path.join(sourceCodexHome, "config.toml"),
360-'notify = ["SkyComputerUseClient", "turn-ended"]\n',
364+[
365+'model = "gpt-5.5-1"',
366+'model_provider = "azure_foundry"',
367+'model_reasoning_effort = "high"',
368+'sandbox_mode = "workspace-write"',
369+'notify = ["SkyComputerUseClient", "turn-ended"]',
370+"",
371+"[model_providers.azure_foundry]",
372+'name = "Azure Foundry"',
373+'base_url = "https://example.azure.com/openai/v1"',
374+'wire_api = "responses"',
375+'env_key = "AZURE_OPENAI_API_KEY"',
376+'http_headers = { "api-key" = "inline-secret-key" }',
377+'query_params = { "api-version" = "2026-01-01", "secret" = "inline-secret-param" }',
378+'experimental_bearer_token = "inline-secret-bearer"',
379+"",
380+"[model_providers.azure_foundry.auth]",
381+'command = "bash"',
382+'args = ["-lc", "printf %s test-key"]',
383+"",
384+"[model_providers.secret_only]",
385+'experimental_bearer_token = "secret-only-token"',
386+"",
387+`[projects.${JSON.stringify(path.join(root, "project-with-model-key"))}]`,
388+'model = "nested-project-model"',
389+"",
390+].join("\n"),
361391);
362392process.env.CODEX_HOME = sourceCodexHome;
363393process.env.OPENCLAW_AGENT_DIR = agentDir;
@@ -375,6 +405,21 @@ describe("prepareAcpxCodexAuthConfig", () => {
375405376406expectCodexWrapperCommand(resolved.agents.codex, generated.wrapperPath);
377407const isolatedConfig = await fs.readFile(generated.configPath, "utf8");
408+expect(isolatedConfig).toContain('model = "gpt-5.5-1"');
409+expect(isolatedConfig).toContain('model_provider = "azure_foundry"');
410+expect(isolatedConfig).toContain('model_reasoning_effort = "high"');
411+expect(isolatedConfig).toContain('sandbox_mode = "workspace-write"');
412+expect(isolatedConfig).toContain("[model_providers.azure_foundry]");
413+expect(isolatedConfig).toContain('base_url = "https://example.azure.com/openai/v1"');
414+expect(isolatedConfig).toContain('env_key = "AZURE_OPENAI_API_KEY"');
415+expect(isolatedConfig).not.toContain("http_headers");
416+expect(isolatedConfig).not.toContain("query_params");
417+expect(isolatedConfig).not.toContain("experimental_bearer_token");
418+expect(isolatedConfig).not.toContain("[model_providers.azure_foundry.auth]");
419+expect(isolatedConfig).not.toContain("[model_providers.secret_only]");
420+expect(isolatedConfig).not.toContain("nested-project-model");
421+expect(isolatedConfig).not.toContain("inline-secret");
422+expect(isolatedConfig).not.toContain('args = ["-lc", "printf %s test-key"]');
378423expect(isolatedConfig).not.toContain("notify");
379424expect(isolatedConfig).not.toContain("SkyComputerUseClient");
380425expect(isolatedConfig).toContain(`[projects.${JSON.stringify(path.resolve(root))}]`);
@@ -498,6 +543,101 @@ describe("prepareAcpxCodexAuthConfig", () => {
498543expect(resolved.agents.claude).toContain("bypass");
499544});
500545546+it("captures Codex wrapper stderr in a stream-aware redacted per-lease log", async () => {
547+const root = await makeTempDir();
548+const stateDir = path.join(root, "state");
549+const generated = generatedCodexPaths(stateDir);
550+const stderrScript = path.join(root, "emit-stderr.mjs");
551+await fs.writeFile(
552+stderrScript,
553+`const chunks = [
554+ "token=sk-test",
555+ "secret1234567890\\n",
556+ "Authorization: Bearer bearer-secret",
557+ "-token-1234567890\\n",
558+ '{"client_secret":"json-secret-1234567890","api_key":"json-api-key-1234567890"}\\n',
559+ "client-secret: kebab-secret-1234567890\\n",
560+ "standalone sk-live-secret",
561+ "1234567890\\n",
562+ "url=https://example.test/callback?token=query-secret",
563+ "-1234567890\\n",
564+ "github_pat_1234567890",
565+ "abcdefghijklmnopqrstuvwxyz\\n",
566+ "-----BEGIN PRIVATE KEY-----\\nprivate-secret-body\\n",
567+ "-----END PRIVATE KEY-----\\n",
568+ "tail-token=tail-secret-1234567890",
569+ "\\n-----BEGIN PRIVATE KEY-----\\ntruncated-private-secret",
570+ ];
571+ let index = 0;
572+ function writeNext() {
573+ if (index >= chunks.length) {
574+ process.exit(1);
575+ return;
576+ }
577+ process.stderr.write(chunks[index]);
578+ index += 1;
579+ setTimeout(writeNext, 5);
580+ }
581+ writeNext();`,
582+"utf8",
583+);
584+const pluginConfig = resolveAcpxPluginConfig({
585+rawConfig: {
586+agents: {
587+codex: {
588+command: `${process.execPath} ${stderrScript}`,
589+},
590+},
591+},
592+workspaceDir: root,
593+});
594+595+await prepareAcpxCodexAuthConfig({
596+ pluginConfig,
597+ stateDir,
598+resolveInstalledCodexAcpBinPath: async () => path.join(root, "codex-acp.js"),
599+});
600+601+await expect(
602+execFileAsync(process.execPath, [
603+generated.wrapperPath,
604+"--openclaw-run-configured",
605+process.execPath,
606+stderrScript,
607+OPENCLAW_ACPX_LEASE_ID_ARG,
608+"lease-secret",
609+OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
610+"gateway-test",
611+]),
612+).rejects.toMatchObject({ code: 1 });
613+614+const log = await fs.readFile(
615+path.join(stateDir, "acpx", "codex-acp-wrapper.stderr.lease-secret.log"),
616+"utf8",
617+);
618+expect(log).toContain("token=[REDACTED]");
619+expect(log).toContain("Authorization: Bearer [REDACTED]");
620+expect(log).toContain('"client_secret":"[REDACTED]"');
621+expect(log).toContain('"api_key":"[REDACTED]"');
622+expect(log).toContain("client-secret: [REDACTED]");
623+expect(log).toContain("standalone [REDACTED_OPENAI_KEY]");
624+expect(log).toContain("?token=[REDACTED]");
625+expect(log).toContain("[REDACTED_GITHUB_TOKEN]");
626+expect(log).toContain("[REDACTED_PRIVATE_KEY]");
627+expect(log).toContain("tail-token=[REDACTED]");
628+expect(log).not.toContain("sk-testsecret1234567890");
629+expect(log).not.toContain("bearer-secret-token-1234567890");
630+expect(log).not.toContain("json-secret-1234567890");
631+expect(log).not.toContain("json-api-key-1234567890");
632+expect(log).not.toContain("kebab-secret-1234567890");
633+expect(log).not.toContain("query-secret-1234567890");
634+expect(log).not.toContain("github_pat_1234567890abcdefghijklmnopqrstuvwxyz");
635+expect(log).not.toContain("private-secret-body");
636+expect(log).not.toContain("truncated-private-secret");
637+expect(log).not.toContain("tail-secret-1234567890");
638+await expectPathMissing(path.join(stateDir, "acpx", "codex-acp-wrapper.stderr.log"));
639+});
640+501641it("leaves a custom Claude agent command alone", async () => {
502642const root = await makeTempDir();
503643const stateDir = path.join(root, "state");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。