


























@@ -1,6 +1,6 @@
11import { beforeEach, describe, expect, it, vi } from "vitest";
22import type { AcpRuntime } from "../runtime-api.js";
3-import { AcpxRuntime } from "./runtime.js";
3+import { AcpxRuntime, __testing } from "./runtime.js";
4455type TestSessionStore = {
66load(sessionId: string): Promise<Record<string, unknown> | undefined>;
@@ -9,6 +9,8 @@ type TestSessionStore = {
991010const DOCUMENTED_OPENCLAW_BRIDGE_COMMAND =
1111"env OPENCLAW_HIDE_BANNER=1 OPENCLAW_SUPPRESS_NOTES=1 openclaw acp --url ws://127.0.0.1:18789 --token-file ~/.openclaw/gateway.token --session agent:main:main";
12+const CODEX_ACP_COMMAND = "npx @zed-industries/codex-acp@^0.11.1";
13+const CODEX_ACP_WRAPPER_COMMAND = `node "/tmp/openclaw/acpx/codex-acp-wrapper.mjs"`;
12141315function makeRuntime(
1416baseStore: TestSessionStore,
@@ -20,13 +22,15 @@ function makeRuntime(
2022close: AcpRuntime["close"];
2123ensureSession: AcpRuntime["ensureSession"];
2224getStatus: NonNullable<AcpRuntime["getStatus"]>;
25+setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
2326isHealthy(): boolean;
2427probeAvailability(): Promise<void>;
2528};
2629bridgeSafeDelegate: {
2730close: AcpRuntime["close"];
2831ensureSession: AcpRuntime["ensureSession"];
2932getStatus: NonNullable<AcpRuntime["getStatus"]>;
33+setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
3034isHealthy(): boolean;
3135probeAvailability(): Promise<void>;
3236};
@@ -55,6 +59,7 @@ function makeRuntime(
5559close: AcpRuntime["close"];
5660ensureSession: AcpRuntime["ensureSession"];
5761getStatus: NonNullable<AcpRuntime["getStatus"]>;
62+setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
5863isHealthy(): boolean;
5964probeAvailability(): Promise<void>;
6065};
@@ -66,6 +71,7 @@ function makeRuntime(
6671close: AcpRuntime["close"];
6772ensureSession: AcpRuntime["ensureSession"];
6873getStatus: NonNullable<AcpRuntime["getStatus"]>;
74+setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
6975isHealthy(): boolean;
7076probeAvailability(): Promise<void>;
7177};
@@ -79,6 +85,274 @@ describe("AcpxRuntime fresh reset wrapper", () => {
7985vi.restoreAllMocks();
8086});
818788+it("normalizes OpenClaw Codex model ids for ACP startup", async () => {
89+const baseStore: TestSessionStore = {
90+load: vi.fn(async () => undefined),
91+save: vi.fn(async () => {}),
92+};
93+const { runtime, delegate } = makeRuntime(baseStore, {
94+agentRegistry: {
95+resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
96+list: () => ["codex", "openclaw"],
97+},
98+});
99+const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
100+sessionKey: "agent:codex:acp:test",
101+backend: "acpx",
102+runtimeSessionName: "codex",
103+});
104+105+await runtime.ensureSession({
106+sessionKey: "agent:codex:acp:test",
107+agent: "codex",
108+mode: "persistent",
109+model: "openai-codex/gpt-5.4",
110+});
111+112+expect(ensure).toHaveBeenCalledWith(
113+expect.objectContaining({
114+model: "gpt-5.4",
115+}),
116+);
117+});
118+119+it("leaves Codex ACP startup defaults alone when no model or thinking is provided", async () => {
120+const baseStore: TestSessionStore = {
121+load: vi.fn(async () => undefined),
122+save: vi.fn(async () => {}),
123+};
124+const { runtime, delegate } = makeRuntime(baseStore, {
125+agentRegistry: {
126+resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
127+list: () => ["codex", "openclaw"],
128+},
129+});
130+const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
131+sessionKey: "agent:codex:acp:test",
132+backend: "acpx",
133+runtimeSessionName: "codex",
134+});
135+136+await runtime.ensureSession({
137+sessionKey: "agent:codex:acp:test",
138+agent: "codex",
139+mode: "persistent",
140+});
141+142+expect(ensure).toHaveBeenCalledWith(
143+expect.objectContaining({
144+agent: "codex",
145+}),
146+);
147+expect(ensure.mock.calls[0]?.[0]).not.toHaveProperty("model");
148+expect(ensure.mock.calls[0]?.[0]).not.toHaveProperty("thinking");
149+});
150+151+it("does not normalize model startup for non-Codex ACP agents", async () => {
152+const baseStore: TestSessionStore = {
153+load: vi.fn(async () => undefined),
154+save: vi.fn(async () => {}),
155+};
156+const { runtime, delegate } = makeRuntime(baseStore, {
157+agentRegistry: {
158+resolve: (agentName: string) => (agentName === "main" ? CODEX_ACP_COMMAND : agentName),
159+list: () => ["main", "codex", "openclaw"],
160+},
161+});
162+const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
163+sessionKey: "agent:main:acp:test",
164+backend: "acpx",
165+runtimeSessionName: "main",
166+});
167+168+await runtime.ensureSession({
169+sessionKey: "agent:main:acp:test",
170+agent: "main",
171+mode: "persistent",
172+model: "openai-codex/gpt-5.5",
173+});
174+175+expect(ensure).toHaveBeenCalledWith(
176+expect.objectContaining({
177+agent: "main",
178+model: "openai-codex/gpt-5.5",
179+}),
180+);
181+});
182+183+it("injects Codex ACP startup config into the scoped registry", () => {
184+expect(__testing.isCodexAcpCommand(CODEX_ACP_COMMAND)).toBe(true);
185+expect(__testing.isCodexAcpCommand(CODEX_ACP_WRAPPER_COMMAND)).toBe(true);
186+expect(
187+__testing.appendCodexAcpConfigOverrides(CODEX_ACP_COMMAND, {
188+model: "gpt-5.4",
189+reasoningEffort: "medium",
190+}),
191+).toBe(
192+"npx @zed-industries/codex-acp@^0.11.1 -c model=gpt-5.4 -c model_reasoning_effort=medium",
193+);
194+expect(__testing.isCodexAcpCommand("openclaw acp")).toBe(false);
195+});
196+197+it("passes gpt-5.5 Codex ACP startup through instead of blocking it", async () => {
198+const baseStore: TestSessionStore = {
199+load: vi.fn(async () => undefined),
200+save: vi.fn(async () => {}),
201+};
202+const { runtime, delegate } = makeRuntime(baseStore, {
203+agentRegistry: {
204+resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
205+list: () => ["codex", "openclaw"],
206+},
207+});
208+const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
209+sessionKey: "agent:codex:acp:test",
210+backend: "acpx",
211+runtimeSessionName: "codex",
212+});
213+214+await runtime.ensureSession({
215+sessionKey: "agent:codex:acp:test",
216+agent: "codex",
217+mode: "persistent",
218+model: "openai-codex/gpt-5.5",
219+});
220+221+expect(ensure).toHaveBeenCalledWith(
222+expect.objectContaining({
223+model: "gpt-5.5",
224+}),
225+);
226+});
227+228+it("maps explicit Codex ACP thinking to startup reasoning effort", async () => {
229+const baseStore: TestSessionStore = {
230+load: vi.fn(async () => undefined),
231+save: vi.fn(async () => {}),
232+};
233+const { runtime, delegate } = makeRuntime(baseStore, {
234+agentRegistry: {
235+resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
236+list: () => ["codex", "openclaw"],
237+},
238+});
239+const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
240+sessionKey: "agent:codex:acp:test",
241+backend: "acpx",
242+runtimeSessionName: "codex",
243+});
244+245+await runtime.ensureSession({
246+sessionKey: "agent:codex:acp:test",
247+agent: "codex",
248+mode: "persistent",
249+model: "openai-codex/gpt-5.4",
250+thinking: "x-high",
251+});
252+253+expect(ensure).toHaveBeenCalledWith(
254+expect.objectContaining({
255+model: "gpt-5.4/xhigh",
256+}),
257+);
258+});
259+260+it("normalizes Codex ACP model config controls to adapter ids", async () => {
261+const baseStore: TestSessionStore = {
262+load: vi.fn(async () => ({
263+acpxRecordId: "agent:codex:acp:test",
264+agentCommand: CODEX_ACP_COMMAND,
265+})),
266+save: vi.fn(async () => {}),
267+};
268+const { runtime, delegate } = makeRuntime(baseStore);
269+const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
270+const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
271+sessionKey: "agent:codex:acp:test",
272+backend: "acpx",
273+runtimeSessionName: "agent:codex:acp:test",
274+acpxRecordId: "agent:codex:acp:test",
275+};
276+277+await runtime.setConfigOption({
278+ handle,
279+key: "model",
280+value: "openai-codex/gpt-5.4",
281+});
282+283+expect(setConfigOption).toHaveBeenNthCalledWith(1, {
284+ handle,
285+key: "model",
286+value: "gpt-5.4",
287+});
288+expect(setConfigOption).toHaveBeenCalledOnce();
289+});
290+291+it("normalizes Codex ACP slash reasoning suffixes to config controls", async () => {
292+const baseStore: TestSessionStore = {
293+load: vi.fn(async () => ({
294+acpxRecordId: "agent:codex:acp:test",
295+agentCommand: CODEX_ACP_COMMAND,
296+})),
297+save: vi.fn(async () => {}),
298+};
299+const { runtime, delegate } = makeRuntime(baseStore);
300+const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
301+const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
302+sessionKey: "agent:codex:acp:test",
303+backend: "acpx",
304+runtimeSessionName: "agent:codex:acp:test",
305+acpxRecordId: "agent:codex:acp:test",
306+};
307+308+await runtime.setConfigOption({
309+ handle,
310+key: "model",
311+value: "openai-codex/gpt-5.4/high",
312+});
313+314+expect(setConfigOption).toHaveBeenNthCalledWith(1, {
315+ handle,
316+key: "model",
317+value: "gpt-5.4",
318+});
319+expect(setConfigOption).toHaveBeenNthCalledWith(2, {
320+ handle,
321+key: "reasoning_effort",
322+value: "high",
323+});
324+});
325+326+it("normalizes Codex ACP thinking config controls to reasoning effort", async () => {
327+const baseStore: TestSessionStore = {
328+load: vi.fn(async () => ({
329+acpxRecordId: "agent:codex:acp:test",
330+agentCommand: CODEX_ACP_COMMAND,
331+})),
332+save: vi.fn(async () => {}),
333+};
334+const { runtime, delegate } = makeRuntime(baseStore);
335+const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
336+const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
337+sessionKey: "agent:codex:acp:test",
338+backend: "acpx",
339+runtimeSessionName: "agent:codex:acp:test",
340+acpxRecordId: "agent:codex:acp:test",
341+};
342+343+await runtime.setConfigOption({
344+ handle,
345+key: "thinking",
346+value: "minimal",
347+});
348+349+expect(setConfigOption).toHaveBeenCalledWith({
350+ handle,
351+key: "reasoning_effort",
352+value: "low",
353+});
354+});
355+82356it("keeps stale persistent loads hidden until a fresh record is saved", async () => {
83357const baseStore: TestSessionStore = {
84358load: vi.fn(async () => ({ acpxRecordId: "stale" }) as never),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。