惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
Vercel News
Vercel News
F
Fortinet All Blogs
月光博客
月光博客
G
Google Developers Blog
博客园 - Franky
GbyAI
GbyAI
The Cloudflare Blog
I
InfoQ
雷峰网
雷峰网
WordPress大学
WordPress大学
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
T
The Blog of Author Tim Ferriss
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
小众软件
小众软件
腾讯CDC
B
Blog
量子位
V
V2EX
S
SegmentFault 最新的问题
Google DeepMind News
Google DeepMind News

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: isolate Codex ACP auth · openclaw/openclaw@87f8e82
steipete · 2026-04-23 · via Recent Commits to openclaw:main

@@ -0,0 +1,157 @@

1+

import fs from "node:fs/promises";

2+

import os from "node:os";

3+

import path from "node:path";

4+

import { resolveOpenClawAgentDir } from "openclaw/plugin-sdk/provider-auth";

5+

import { prepareCodexAuthBridge } from "openclaw/plugin-sdk/provider-auth-runtime";

6+

import { writePrivateSecretFileAtomic } from "openclaw/plugin-sdk/secret-file-runtime";

7+

import type { PluginLogger } from "../runtime-api.js";

8+

import type { ResolvedAcpxPluginConfig } from "./config.js";

9+10+

const CODEX_AGENT_ID = "codex";

11+

const DEFAULT_CODEX_AUTH_PROFILE_ID = "openai-codex:default";

12+

const CODEX_AUTH_ENV_CLEAR_KEYS = ["OPENAI_API_KEY"];

13+14+

type PreparedAcpxCodexAuth = {

15+

codexHome: string;

16+

clearEnv: string[];

17+

};

18+19+

function resolveSourceCodexHome(env: NodeJS.ProcessEnv = process.env): string {

20+

const configured = env.CODEX_HOME?.trim();

21+

if (configured) {

22+

if (configured === "~") {

23+

return os.homedir();

24+

}

25+

if (configured.startsWith("~/")) {

26+

return path.join(os.homedir(), configured.slice(2));

27+

}

28+

return path.resolve(configured);

29+

}

30+

return path.join(os.homedir(), ".codex");

31+

}

32+33+

async function readOptionalFile(filePath: string): Promise<string | undefined> {

34+

try {

35+

return await fs.readFile(filePath, "utf8");

36+

} catch (error) {

37+

if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {

38+

return undefined;

39+

}

40+

throw error;

41+

}

42+

}

43+44+

async function prepareCopiedCodexHome(params: {

45+

agentDir: string;

46+

sourceCodexHome: string;

47+

}): Promise<PreparedAcpxCodexAuth | null> {

48+

const authJson = await readOptionalFile(path.join(params.sourceCodexHome, "auth.json"));

49+

if (!authJson) {

50+

return null;

51+

}

52+53+

const codexHome = path.join(params.agentDir, "acp-auth", "codex-source");

54+

await writePrivateSecretFileAtomic({

55+

rootDir: params.agentDir,

56+

filePath: path.join(codexHome, "auth.json"),

57+

content: authJson,

58+

});

59+60+

const configToml = await readOptionalFile(path.join(params.sourceCodexHome, "config.toml"));

61+

if (configToml) {

62+

await writePrivateSecretFileAtomic({

63+

rootDir: params.agentDir,

64+

filePath: path.join(codexHome, "config.toml"),

65+

content: configToml,

66+

});

67+

}

68+69+

return {

70+

codexHome,

71+

clearEnv: [...CODEX_AUTH_ENV_CLEAR_KEYS],

72+

};

73+

}

74+75+

function shellArg(value: string): string {

76+

return `'${value.replace(/'/g, `'\\''`)}'`;

77+

}

78+79+

async function writeCodexAcpWrapper(params: {

80+

wrapperPath: string;

81+

codexHome: string;

82+

clearEnv: string[];

83+

}): Promise<string> {

84+

await fs.mkdir(path.dirname(params.wrapperPath), { recursive: true, mode: 0o700 });

85+

const content = `#!/usr/bin/env node

86+

import { spawn } from "node:child_process";

87+88+

const env = { ...process.env, CODEX_HOME: ${JSON.stringify(params.codexHome)} };

89+

for (const key of ${JSON.stringify(params.clearEnv)}) {

90+

delete env[key];

91+

}

92+93+

const child = spawn("npx", ["@zed-industries/codex-acp@^0.11.1"], {

94+

stdio: "inherit",

95+

env,

96+

});

97+98+

child.on("exit", (code, signal) => {

99+

if (signal) {

100+

process.kill(process.pid, signal);

101+

return;

102+

}

103+

process.exit(code ?? 1);

104+

});

105+106+

child.on("error", (error) => {

107+

console.error(error instanceof Error ? error.message : String(error));

108+

process.exit(1);

109+

});

110+

`;

111+

await fs.writeFile(params.wrapperPath, content, { mode: 0o700 });

112+

await fs.chmod(params.wrapperPath, 0o700);

113+

return shellArg(params.wrapperPath);

114+

}

115+116+

export async function prepareAcpxCodexAuthConfig(params: {

117+

pluginConfig: ResolvedAcpxPluginConfig;

118+

stateDir: string;

119+

logger?: PluginLogger;

120+

}): Promise<ResolvedAcpxPluginConfig> {

121+

if (params.pluginConfig.agents[CODEX_AGENT_ID]) {

122+

return params.pluginConfig;

123+

}

124+125+

const agentDir = resolveOpenClawAgentDir();

126+

const sourceCodexHome = resolveSourceCodexHome();

127+

const bridge =

128+

(await prepareCodexAuthBridge({

129+

agentDir,

130+

bridgeDir: "acp-auth",

131+

profileId: DEFAULT_CODEX_AUTH_PROFILE_ID,

132+

sourceCodexHome,

133+

})) ??

134+

(await prepareCopiedCodexHome({

135+

agentDir,

136+

sourceCodexHome,

137+

}));

138+139+

if (!bridge) {

140+

params.logger?.debug?.("codex ACP auth bridge skipped: no Codex auth source found");

141+

return params.pluginConfig;

142+

}

143+144+

const wrapperCommand = await writeCodexAcpWrapper({

145+

wrapperPath: path.join(params.stateDir, "acpx", "codex-acp-wrapper.mjs"),

146+

codexHome: bridge.codexHome,

147+

clearEnv: bridge.clearEnv,

148+

});

149+150+

return {

151+

...params.pluginConfig,

152+

agents: {

153+

...params.pluginConfig.agents,

154+

[CODEX_AGENT_ID]: wrapperCommand,

155+

},

156+

};

157+

}