

























@@ -7,19 +7,32 @@ type TestSessionStore = {
77save(record: Record<string, unknown>): Promise<void>;
88};
9910-function makeRuntime(baseStore: TestSessionStore): {
10+function makeRuntime(
11+baseStore: TestSessionStore,
12+options: Partial<ConstructorParameters<typeof AcpxRuntime>[0]> = {},
13+): {
1114runtime: AcpxRuntime;
1215wrappedStore: TestSessionStore & { markFresh: (sessionKey: string) => void };
13-delegate: { close: AcpRuntime["close"] };
16+delegate: {
17+close: AcpRuntime["close"];
18+ensureSession: AcpRuntime["ensureSession"];
19+getStatus: NonNullable<AcpRuntime["getStatus"]>;
20+};
21+bridgeSafeDelegate: {
22+close: AcpRuntime["close"];
23+ensureSession: AcpRuntime["ensureSession"];
24+getStatus: NonNullable<AcpRuntime["getStatus"]>;
25+};
1426} {
1527const runtime = new AcpxRuntime({
1628cwd: "/tmp",
1729sessionStore: baseStore,
1830agentRegistry: {
19-resolve: () => "codex",
20-list: () => ["codex"],
31+resolve: (agentName: string) => (agentName === "openclaw" ? "openclaw acp" : agentName),
32+list: () => ["codex", "openclaw"],
2133},
2234permissionMode: "approve-reads",
35+ ...options,
2336});
24372538return {
@@ -29,7 +42,24 @@ function makeRuntime(baseStore: TestSessionStore): {
2942sessionStore: TestSessionStore & { markFresh: (sessionKey: string) => void };
3043}
3144).sessionStore,
32-delegate: (runtime as unknown as { delegate: { close: AcpRuntime["close"] } }).delegate,
45+delegate: (
46+runtime as unknown as {
47+delegate: {
48+close: AcpRuntime["close"];
49+ensureSession: AcpRuntime["ensureSession"];
50+getStatus: NonNullable<AcpRuntime["getStatus"]>;
51+};
52+}
53+).delegate,
54+bridgeSafeDelegate: (
55+runtime as unknown as {
56+bridgeSafeDelegate: {
57+close: AcpRuntime["close"];
58+ensureSession: AcpRuntime["ensureSession"];
59+getStatus: NonNullable<AcpRuntime["getStatus"]>;
60+};
61+}
62+).bridgeSafeDelegate,
3363};
3464}
3565@@ -102,4 +132,164 @@ describe("AcpxRuntime fresh reset wrapper", () => {
102132expect(await wrappedStore.load("agent:codex:acp:binding:test")).toBeUndefined();
103133expect(baseStore.load).not.toHaveBeenCalled();
104134});
135+136+it("routes openclaw ensureSession through the bridge-safe delegate when MCP servers are configured", async () => {
137+const baseStore: TestSessionStore = {
138+load: vi.fn(async () => undefined),
139+save: vi.fn(async () => {}),
140+};
141+142+const { runtime, delegate, bridgeSafeDelegate } = makeRuntime(baseStore, {
143+mcpServers: [{ name: "tools", command: "mcp-tools" }] as never,
144+});
145+const defaultEnsure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
146+sessionKey: "agent:codex:acp:test",
147+backend: "acpx",
148+runtimeSessionName: "default",
149+});
150+const bridgeEnsure = vi.spyOn(bridgeSafeDelegate, "ensureSession").mockResolvedValue({
151+sessionKey: "agent:openclaw:acp:test",
152+backend: "acpx",
153+runtimeSessionName: "bridge",
154+});
155+156+const result = await runtime.ensureSession({
157+sessionKey: "agent:openclaw:acp:test",
158+agent: "openclaw",
159+mode: "persistent",
160+});
161+162+expect(result.runtimeSessionName).toBe("bridge");
163+expect(bridgeEnsure).toHaveBeenCalledOnce();
164+expect(defaultEnsure).not.toHaveBeenCalled();
165+});
166+167+it("routes non-openclaw sessions through the default delegate", async () => {
168+const baseStore: TestSessionStore = {
169+load: vi.fn(async () => undefined),
170+save: vi.fn(async () => {}),
171+};
172+173+const { runtime, delegate, bridgeSafeDelegate } = makeRuntime(baseStore, {
174+mcpServers: [{ name: "tools", command: "mcp-tools" }] as never,
175+});
176+const defaultEnsure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
177+sessionKey: "agent:codex:acp:test",
178+backend: "acpx",
179+runtimeSessionName: "default",
180+});
181+const bridgeEnsure = vi.spyOn(bridgeSafeDelegate, "ensureSession").mockResolvedValue({
182+sessionKey: "agent:openclaw:acp:test",
183+backend: "acpx",
184+runtimeSessionName: "bridge",
185+});
186+187+const result = await runtime.ensureSession({
188+sessionKey: "agent:codex:acp:test",
189+agent: "codex",
190+mode: "persistent",
191+});
192+193+expect(result.runtimeSessionName).toBe("default");
194+expect(defaultEnsure).toHaveBeenCalledOnce();
195+expect(bridgeEnsure).not.toHaveBeenCalled();
196+});
197+198+it("routes handle-based follow-up calls for openclaw sessions through the bridge-safe delegate", async () => {
199+const baseStore: TestSessionStore = {
200+load: vi.fn(async () => undefined),
201+save: vi.fn(async () => {}),
202+};
203+204+const { runtime, delegate, bridgeSafeDelegate } = makeRuntime(baseStore, {
205+mcpServers: [{ name: "tools", command: "mcp-tools" }] as never,
206+});
207+const defaultStatus = vi.spyOn(delegate, "getStatus").mockResolvedValue({
208+summary: "default",
209+});
210+const bridgeStatus = vi.spyOn(bridgeSafeDelegate, "getStatus").mockResolvedValue({
211+summary: "bridge",
212+});
213+const handle: Parameters<NonNullable<AcpRuntime["getStatus"]>>[0]["handle"] = {
214+sessionKey: "agent:openclaw:acp:test",
215+backend: "acpx",
216+runtimeSessionName: "openclaw-session-handle",
217+};
218+219+const status = await runtime.getStatus({ handle });
220+221+expect(status.summary).toBe("bridge");
222+expect(bridgeStatus).toHaveBeenCalledWith({ handle });
223+expect(defaultStatus).not.toHaveBeenCalled();
224+});
225+226+it("keeps MCP-enabled routing when the openclaw agent is overridden to a non-bridge adapter", async () => {
227+const baseStore: TestSessionStore = {
228+load: vi.fn(async () => undefined),
229+save: vi.fn(async () => {}),
230+};
231+232+const { runtime, delegate, bridgeSafeDelegate } = makeRuntime(baseStore, {
233+mcpServers: [{ name: "tools", command: "mcp-tools" }] as never,
234+agentRegistry: {
235+resolve: (agentName: string) => (agentName === "openclaw" ? "codex" : agentName),
236+list: () => ["codex", "openclaw"],
237+},
238+});
239+const defaultEnsure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
240+sessionKey: "agent:openclaw:acp:test",
241+backend: "acpx",
242+runtimeSessionName: "default",
243+});
244+const bridgeEnsure = vi.spyOn(bridgeSafeDelegate, "ensureSession").mockResolvedValue({
245+sessionKey: "agent:openclaw:acp:test",
246+backend: "acpx",
247+runtimeSessionName: "bridge",
248+});
249+250+const result = await runtime.ensureSession({
251+sessionKey: "agent:openclaw:acp:test",
252+agent: "openclaw",
253+mode: "persistent",
254+});
255+256+expect(result.runtimeSessionName).toBe("default");
257+expect(defaultEnsure).toHaveBeenCalledOnce();
258+expect(bridgeEnsure).not.toHaveBeenCalled();
259+});
260+261+it("uses the bridge-safe delegate for any agent mapped to the openclaw bridge command", async () => {
262+const baseStore: TestSessionStore = {
263+load: vi.fn(async () => undefined),
264+save: vi.fn(async () => {}),
265+};
266+267+const { runtime, delegate, bridgeSafeDelegate } = makeRuntime(baseStore, {
268+mcpServers: [{ name: "tools", command: "mcp-tools" }] as never,
269+agentRegistry: {
270+resolve: (agentName: string) => (agentName === "codex" ? "openclaw acp" : agentName),
271+list: () => ["codex", "openclaw"],
272+},
273+});
274+const defaultEnsure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
275+sessionKey: "agent:codex:acp:test",
276+backend: "acpx",
277+runtimeSessionName: "default",
278+});
279+const bridgeEnsure = vi.spyOn(bridgeSafeDelegate, "ensureSession").mockResolvedValue({
280+sessionKey: "agent:codex:acp:test",
281+backend: "acpx",
282+runtimeSessionName: "bridge",
283+});
284+285+const result = await runtime.ensureSession({
286+sessionKey: "agent:codex:acp:test",
287+agent: "codex",
288+mode: "persistent",
289+});
290+291+expect(result.runtimeSessionName).toBe("bridge");
292+expect(bridgeEnsure).toHaveBeenCalledOnce();
293+expect(defaultEnsure).not.toHaveBeenCalled();
294+});
105295});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。