

























@@ -21,8 +21,14 @@ type GatewayClientOptions = GatewayClientCallbacks &
2121caps?: string[];
2222url?: string;
2323};
24+type MockAcpStream = {
25+writable: WritableStream<unknown>;
26+readable: ReadableStream<unknown>;
27+};
24282529const mockState = vi.hoisted(() => ({
30+acpProtocolVersion: 1,
31+acpInputMessages: [] as unknown[],
2632gateways: [] as MockGatewayClient[],
2733gatewayAuth: [] as GatewayClientAuth[],
2834gatewayOptions: [] as GatewayClientOptions[],
@@ -67,14 +73,28 @@ class MockGatewayClient {
6773}
68746975vi.mock("@agentclientprotocol/sdk", () => ({
76+AGENT_METHODS: {
77+initialize: "initialize",
78+},
7079AgentSideConnection: function AgentSideConnection(
7180factory: (conn: unknown) => unknown,
7281stream: unknown,
7382) {
7483mockState.agentSideConnectionCtor(factory, stream);
7584factory({});
7685},
77-ndJsonStream: vi.fn(() => ({ type: "mock-stream" })),
86+PROTOCOL_VERSION: mockState.acpProtocolVersion,
87+ndJsonStream: vi.fn(() => ({
88+writable: new WritableStream(),
89+readable: new ReadableStream({
90+start(controller) {
91+for (const message of mockState.acpInputMessages) {
92+controller.enqueue(message);
93+}
94+controller.close();
95+},
96+}),
97+})),
7898}));
799980100vi.mock("../config/config.js", () => {
@@ -201,6 +221,50 @@ describe("serveAcpGateway startup", () => {
201221});
202222}
203223224+function getCapturedAcpStream(): MockAcpStream {
225+const stream = mockState.agentSideConnectionCtor.mock.calls[0]?.[1];
226+if (
227+!stream ||
228+typeof stream !== "object" ||
229+!(stream as MockAcpStream).readable ||
230+!(stream as MockAcpStream).writable
231+) {
232+throw new Error("Expected AgentSideConnection stream");
233+}
234+return stream as MockAcpStream;
235+}
236+237+async function readCapturedAcpMessages(): Promise<unknown[]> {
238+const reader = getCapturedAcpStream().readable.getReader();
239+const messages: unknown[] = [];
240+try {
241+while (true) {
242+const { done, value } = await reader.read();
243+if (done) {
244+return messages;
245+}
246+messages.push(value);
247+}
248+} finally {
249+reader.releaseLock();
250+}
251+}
252+253+async function captureAcpMessagesAfterStartup(inputMessages: unknown[]): Promise<unknown[]> {
254+mockState.acpInputMessages.push(...inputMessages);
255+const { signalHandlers, onceSpy } = captureProcessSignalHandlers();
256+const servePromise = serveAcpGateway({});
257+258+try {
259+await emitHelloAndWaitForAgentSideConnection();
260+return await readCapturedAcpMessages();
261+} finally {
262+signalHandlers.get("SIGINT")?.();
263+await servePromise;
264+onceSpy.mockRestore();
265+}
266+}
267+204268async function stopServeWithSigint(
205269signalHandlers: Map<NodeJS.Signals, () => void>,
206270servePromise: Promise<void>,
@@ -214,6 +278,7 @@ describe("serveAcpGateway startup", () => {
214278});
215279216280beforeEach(async () => {
281+mockState.acpInputMessages.length = 0;
217282mockState.gateways.length = 0;
218283mockState.gatewayAuth.length = 0;
219284mockState.gatewayOptions.length = 0;
@@ -397,4 +462,78 @@ describe("serveAcpGateway startup", () => {
397462onceSpy.mockRestore();
398463}
399464});
465+466+it("coerces MCP date-string initialize protocol versions", async () => {
467+const initializeRequest = {
468+jsonrpc: "2.0",
469+id: 1,
470+method: "initialize",
471+params: {
472+protocolVersion: "2025-11-25",
473+clientCapabilities: {},
474+},
475+};
476+477+await expect(captureAcpMessagesAfterStartup([initializeRequest])).resolves.toEqual([
478+{
479+ ...initializeRequest,
480+params: {
481+ ...initializeRequest.params,
482+protocolVersion: mockState.acpProtocolVersion,
483+},
484+},
485+]);
486+});
487+488+it("coerces non-integer numeric initialize protocol versions", async () => {
489+const initializeRequest = {
490+jsonrpc: "2.0",
491+id: 1,
492+method: "initialize",
493+params: {
494+protocolVersion: 1.5,
495+clientCapabilities: {},
496+},
497+};
498+499+await expect(captureAcpMessagesAfterStartup([initializeRequest])).resolves.toEqual([
500+{
501+ ...initializeRequest,
502+params: {
503+ ...initializeRequest.params,
504+protocolVersion: mockState.acpProtocolVersion,
505+},
506+},
507+]);
508+});
509+510+it("passes uint16 numeric initialize protocol versions through unchanged", async () => {
511+const initializeRequest = {
512+jsonrpc: "2.0",
513+id: 1,
514+method: "initialize",
515+params: {
516+protocolVersion: 42,
517+clientCapabilities: {},
518+},
519+};
520+521+const [message] = await captureAcpMessagesAfterStartup([initializeRequest]);
522+expect(message).toBe(initializeRequest);
523+});
524+525+it("passes non-initialize JSON-RPC messages through unchanged", async () => {
526+const sessionRequest = {
527+jsonrpc: "2.0",
528+id: 2,
529+method: "session/new",
530+params: {
531+protocolVersion: "2025-11-25",
532+cwd: "/tmp/openclaw",
533+},
534+};
535+536+const [message] = await captureAcpMessagesAfterStartup([sessionRequest]);
537+expect(message).toBe(sessionRequest);
538+});
400539});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。