leduo-patrol 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/server/__tests__/acp-session.test.js +92 -0
- package/dist/server/__tests__/activity-monitor.test.js +13 -1
- package/dist/server/__tests__/session-manager.test.js +215 -1
- package/dist/server/acp-session.js +476 -0
- package/dist/server/activity-monitor.js +22 -7
- package/dist/server/index.js +54 -1
- package/dist/server/session-manager.js +1117 -121
- package/dist/web/assets/index-B-YXVUoQ.css +1 -0
- package/dist/web/assets/index-Bu0K7QgY.js +21 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/README.md +53 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +823 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.js +965 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +839 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +225 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +130 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +35 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +5 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +27 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +28 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +2870 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +5333 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +1554 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +24 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.js +64 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/package.json +66 -0
- package/vendor/claude-code-acp/node_modules/@agentclientprotocol/sdk/schema/schema.json +4125 -0
- package/vendor/claude-code-acp/node_modules/@types/node/LICENSE +21 -0
- package/vendor/claude-code-acp/node_modules/@types/node/README.md +15 -0
- package/vendor/claude-code-acp/node_modules/@types/node/assert/strict.d.ts +105 -0
- package/vendor/claude-code-acp/node_modules/@types/node/assert.d.ts +955 -0
- package/vendor/claude-code-acp/node_modules/@types/node/async_hooks.d.ts +623 -0
- package/vendor/claude-code-acp/node_modules/@types/node/buffer.buffer.d.ts +466 -0
- package/vendor/claude-code-acp/node_modules/@types/node/buffer.d.ts +1810 -0
- package/vendor/claude-code-acp/node_modules/@types/node/child_process.d.ts +1428 -0
- package/vendor/claude-code-acp/node_modules/@types/node/cluster.d.ts +486 -0
- package/vendor/claude-code-acp/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
- package/vendor/claude-code-acp/node_modules/@types/node/console.d.ts +151 -0
- package/vendor/claude-code-acp/node_modules/@types/node/constants.d.ts +20 -0
- package/vendor/claude-code-acp/node_modules/@types/node/crypto.d.ts +4065 -0
- package/vendor/claude-code-acp/node_modules/@types/node/dgram.d.ts +564 -0
- package/vendor/claude-code-acp/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
- package/vendor/claude-code-acp/node_modules/@types/node/dns/promises.d.ts +503 -0
- package/vendor/claude-code-acp/node_modules/@types/node/dns.d.ts +922 -0
- package/vendor/claude-code-acp/node_modules/@types/node/domain.d.ts +166 -0
- package/vendor/claude-code-acp/node_modules/@types/node/events.d.ts +1054 -0
- package/vendor/claude-code-acp/node_modules/@types/node/fs/promises.d.ts +1329 -0
- package/vendor/claude-code-acp/node_modules/@types/node/fs.d.ts +4676 -0
- package/vendor/claude-code-acp/node_modules/@types/node/globals.d.ts +150 -0
- package/vendor/claude-code-acp/node_modules/@types/node/globals.typedarray.d.ts +101 -0
- package/vendor/claude-code-acp/node_modules/@types/node/http.d.ts +2167 -0
- package/vendor/claude-code-acp/node_modules/@types/node/http2.d.ts +2480 -0
- package/vendor/claude-code-acp/node_modules/@types/node/https.d.ts +405 -0
- package/vendor/claude-code-acp/node_modules/@types/node/index.d.ts +115 -0
- package/vendor/claude-code-acp/node_modules/@types/node/inspector/promises.d.ts +41 -0
- package/vendor/claude-code-acp/node_modules/@types/node/inspector.d.ts +224 -0
- package/vendor/claude-code-acp/node_modules/@types/node/inspector.generated.d.ts +4226 -0
- package/vendor/claude-code-acp/node_modules/@types/node/module.d.ts +819 -0
- package/vendor/claude-code-acp/node_modules/@types/node/net.d.ts +933 -0
- package/vendor/claude-code-acp/node_modules/@types/node/os.d.ts +507 -0
- package/vendor/claude-code-acp/node_modules/@types/node/package.json +155 -0
- package/vendor/claude-code-acp/node_modules/@types/node/path/posix.d.ts +8 -0
- package/vendor/claude-code-acp/node_modules/@types/node/path/win32.d.ts +8 -0
- package/vendor/claude-code-acp/node_modules/@types/node/path.d.ts +187 -0
- package/vendor/claude-code-acp/node_modules/@types/node/perf_hooks.d.ts +643 -0
- package/vendor/claude-code-acp/node_modules/@types/node/process.d.ts +2161 -0
- package/vendor/claude-code-acp/node_modules/@types/node/punycode.d.ts +117 -0
- package/vendor/claude-code-acp/node_modules/@types/node/querystring.d.ts +152 -0
- package/vendor/claude-code-acp/node_modules/@types/node/quic.d.ts +910 -0
- package/vendor/claude-code-acp/node_modules/@types/node/readline/promises.d.ts +161 -0
- package/vendor/claude-code-acp/node_modules/@types/node/readline.d.ts +541 -0
- package/vendor/claude-code-acp/node_modules/@types/node/repl.d.ts +415 -0
- package/vendor/claude-code-acp/node_modules/@types/node/sea.d.ts +162 -0
- package/vendor/claude-code-acp/node_modules/@types/node/sqlite.d.ts +955 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream/consumers.d.ts +38 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream/promises.d.ts +211 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream/web.d.ts +296 -0
- package/vendor/claude-code-acp/node_modules/@types/node/stream.d.ts +1760 -0
- package/vendor/claude-code-acp/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/vendor/claude-code-acp/node_modules/@types/node/test/reporters.d.ts +96 -0
- package/vendor/claude-code-acp/node_modules/@types/node/test.d.ts +2240 -0
- package/vendor/claude-code-acp/node_modules/@types/node/timers/promises.d.ts +108 -0
- package/vendor/claude-code-acp/node_modules/@types/node/timers.d.ts +159 -0
- package/vendor/claude-code-acp/node_modules/@types/node/tls.d.ts +1198 -0
- package/vendor/claude-code-acp/node_modules/@types/node/trace_events.d.ts +197 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.6/index.d.ts +117 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
- package/vendor/claude-code-acp/node_modules/@types/node/ts5.7/index.d.ts +117 -0
- package/vendor/claude-code-acp/node_modules/@types/node/tty.d.ts +250 -0
- package/vendor/claude-code-acp/node_modules/@types/node/url.d.ts +519 -0
- package/vendor/claude-code-acp/node_modules/@types/node/util/types.d.ts +558 -0
- package/vendor/claude-code-acp/node_modules/@types/node/util.d.ts +1662 -0
- package/vendor/claude-code-acp/node_modules/@types/node/v8.d.ts +983 -0
- package/vendor/claude-code-acp/node_modules/@types/node/vm.d.ts +1208 -0
- package/vendor/claude-code-acp/node_modules/@types/node/wasi.d.ts +202 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/blob.d.ts +23 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/console.d.ts +9 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/events.d.ts +106 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/performance.d.ts +45 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/storage.d.ts +24 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/streams.d.ts +115 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/timers.d.ts +44 -0
- package/vendor/claude-code-acp/node_modules/@types/node/web-globals/url.d.ts +24 -0
- package/vendor/claude-code-acp/node_modules/@types/node/worker_threads.d.ts +717 -0
- package/vendor/claude-code-acp/node_modules/@types/node/zlib.d.ts +618 -0
- package/vendor/claude-code-acp/node_modules/undici-types/LICENSE +21 -0
- package/vendor/claude-code-acp/node_modules/undici-types/README.md +6 -0
- package/vendor/claude-code-acp/node_modules/undici-types/agent.d.ts +32 -0
- package/vendor/claude-code-acp/node_modules/undici-types/api.d.ts +43 -0
- package/vendor/claude-code-acp/node_modules/undici-types/balanced-pool.d.ts +29 -0
- package/vendor/claude-code-acp/node_modules/undici-types/cache-interceptor.d.ts +172 -0
- package/vendor/claude-code-acp/node_modules/undici-types/cache.d.ts +36 -0
- package/vendor/claude-code-acp/node_modules/undici-types/client-stats.d.ts +15 -0
- package/vendor/claude-code-acp/node_modules/undici-types/client.d.ts +108 -0
- package/vendor/claude-code-acp/node_modules/undici-types/connector.d.ts +34 -0
- package/vendor/claude-code-acp/node_modules/undici-types/content-type.d.ts +21 -0
- package/vendor/claude-code-acp/node_modules/undici-types/cookies.d.ts +30 -0
- package/vendor/claude-code-acp/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
- package/vendor/claude-code-acp/node_modules/undici-types/dispatcher.d.ts +276 -0
- package/vendor/claude-code-acp/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
- package/vendor/claude-code-acp/node_modules/undici-types/errors.d.ts +161 -0
- package/vendor/claude-code-acp/node_modules/undici-types/eventsource.d.ts +66 -0
- package/vendor/claude-code-acp/node_modules/undici-types/fetch.d.ts +211 -0
- package/vendor/claude-code-acp/node_modules/undici-types/formdata.d.ts +108 -0
- package/vendor/claude-code-acp/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- package/vendor/claude-code-acp/node_modules/undici-types/global-origin.d.ts +7 -0
- package/vendor/claude-code-acp/node_modules/undici-types/h2c-client.d.ts +73 -0
- package/vendor/claude-code-acp/node_modules/undici-types/handlers.d.ts +15 -0
- package/vendor/claude-code-acp/node_modules/undici-types/header.d.ts +160 -0
- package/vendor/claude-code-acp/node_modules/undici-types/index.d.ts +80 -0
- package/vendor/claude-code-acp/node_modules/undici-types/interceptors.d.ts +39 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-agent.d.ts +68 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-call-history.d.ts +111 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-client.d.ts +27 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-errors.d.ts +12 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-interceptor.d.ts +94 -0
- package/vendor/claude-code-acp/node_modules/undici-types/mock-pool.d.ts +27 -0
- package/vendor/claude-code-acp/node_modules/undici-types/package.json +55 -0
- package/vendor/claude-code-acp/node_modules/undici-types/patch.d.ts +29 -0
- package/vendor/claude-code-acp/node_modules/undici-types/pool-stats.d.ts +19 -0
- package/vendor/claude-code-acp/node_modules/undici-types/pool.d.ts +41 -0
- package/vendor/claude-code-acp/node_modules/undici-types/proxy-agent.d.ts +29 -0
- package/vendor/claude-code-acp/node_modules/undici-types/readable.d.ts +68 -0
- package/vendor/claude-code-acp/node_modules/undici-types/retry-agent.d.ts +8 -0
- package/vendor/claude-code-acp/node_modules/undici-types/retry-handler.d.ts +125 -0
- package/vendor/claude-code-acp/node_modules/undici-types/snapshot-agent.d.ts +109 -0
- package/vendor/claude-code-acp/node_modules/undici-types/util.d.ts +18 -0
- package/vendor/claude-code-acp/node_modules/undici-types/utility.d.ts +7 -0
- package/vendor/claude-code-acp/node_modules/undici-types/webidl.d.ts +341 -0
- package/vendor/claude-code-acp/node_modules/undici-types/websocket.d.ts +186 -0
- package/dist/web/assets/index-B5Dh2E8j.css +0 -1
- package/dist/web/assets/index-xPPPaEde.js +0 -13
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { ClientSideConnection, AgentSideConnection, PROTOCOL_VERSION, ndJsonStream, } from "./acp.js";
|
|
3
|
+
describe("Connection", () => {
|
|
4
|
+
let clientToAgent;
|
|
5
|
+
let agentToClient;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
clientToAgent = new TransformStream();
|
|
8
|
+
agentToClient = new TransformStream();
|
|
9
|
+
});
|
|
10
|
+
it("handles errors in bidirectional communication", async () => {
|
|
11
|
+
// Create client that throws errors
|
|
12
|
+
class TestClient {
|
|
13
|
+
async writeTextFile(_) {
|
|
14
|
+
throw new Error("Write failed");
|
|
15
|
+
}
|
|
16
|
+
async readTextFile(_) {
|
|
17
|
+
throw new Error("Read failed");
|
|
18
|
+
}
|
|
19
|
+
async requestPermission(_) {
|
|
20
|
+
throw new Error("Permission denied");
|
|
21
|
+
}
|
|
22
|
+
async sessionUpdate(_) {
|
|
23
|
+
// no-op
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Create agent that throws errors
|
|
27
|
+
class TestAgent {
|
|
28
|
+
async initialize(_) {
|
|
29
|
+
throw new Error("Failed to initialize");
|
|
30
|
+
}
|
|
31
|
+
async newSession(_) {
|
|
32
|
+
throw new Error("Failed to create session");
|
|
33
|
+
}
|
|
34
|
+
async loadSession(_) {
|
|
35
|
+
throw new Error("Failed to load session");
|
|
36
|
+
}
|
|
37
|
+
async authenticate(_) {
|
|
38
|
+
throw new Error("Authentication failed");
|
|
39
|
+
}
|
|
40
|
+
async prompt(_) {
|
|
41
|
+
throw new Error("Prompt failed");
|
|
42
|
+
}
|
|
43
|
+
async cancel(_) {
|
|
44
|
+
// no-op
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Set up connections
|
|
48
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
49
|
+
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
50
|
+
// Test error handling in client->agent direction
|
|
51
|
+
await expect(clientConnection.writeTextFile({
|
|
52
|
+
path: "/test.txt",
|
|
53
|
+
content: "test",
|
|
54
|
+
sessionId: "test-session",
|
|
55
|
+
})).rejects.toThrow();
|
|
56
|
+
// Test error handling in agent->client direction
|
|
57
|
+
await expect(agentConnection.newSession({
|
|
58
|
+
cwd: "/test",
|
|
59
|
+
mcpServers: [],
|
|
60
|
+
})).rejects.toThrow();
|
|
61
|
+
});
|
|
62
|
+
it("handles concurrent requests", async () => {
|
|
63
|
+
let requestCount = 0;
|
|
64
|
+
// Create client
|
|
65
|
+
class TestClient {
|
|
66
|
+
async writeTextFile(_) {
|
|
67
|
+
requestCount++;
|
|
68
|
+
const currentCount = requestCount;
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
70
|
+
console.log(`Write request ${currentCount} completed`);
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
async readTextFile(params) {
|
|
74
|
+
return { content: `Content of ${params.path}` };
|
|
75
|
+
}
|
|
76
|
+
async requestPermission(_) {
|
|
77
|
+
return {
|
|
78
|
+
outcome: {
|
|
79
|
+
outcome: "selected",
|
|
80
|
+
optionId: "allow",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async sessionUpdate(_) {
|
|
85
|
+
// no-op
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Create agent
|
|
89
|
+
class TestAgent {
|
|
90
|
+
async initialize(_) {
|
|
91
|
+
return {
|
|
92
|
+
protocolVersion: 1,
|
|
93
|
+
agentCapabilities: { loadSession: false },
|
|
94
|
+
authMethods: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async newSession(_) {
|
|
98
|
+
return {
|
|
99
|
+
sessionId: "test-session",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async loadSession(_) {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
async authenticate(_) {
|
|
106
|
+
// no-op
|
|
107
|
+
}
|
|
108
|
+
async prompt(_) {
|
|
109
|
+
return { stopReason: "end_turn" };
|
|
110
|
+
}
|
|
111
|
+
async cancel(_) {
|
|
112
|
+
// no-op
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Set up connections
|
|
116
|
+
new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
117
|
+
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
118
|
+
// Send multiple concurrent requests
|
|
119
|
+
const promises = [
|
|
120
|
+
clientConnection.writeTextFile({
|
|
121
|
+
path: "/file1.txt",
|
|
122
|
+
content: "content1",
|
|
123
|
+
sessionId: "session1",
|
|
124
|
+
}),
|
|
125
|
+
clientConnection.writeTextFile({
|
|
126
|
+
path: "/file2.txt",
|
|
127
|
+
content: "content2",
|
|
128
|
+
sessionId: "session1",
|
|
129
|
+
}),
|
|
130
|
+
clientConnection.writeTextFile({
|
|
131
|
+
path: "/file3.txt",
|
|
132
|
+
content: "content3",
|
|
133
|
+
sessionId: "session1",
|
|
134
|
+
}),
|
|
135
|
+
];
|
|
136
|
+
const results = await Promise.all(promises);
|
|
137
|
+
// Verify all requests completed successfully
|
|
138
|
+
expect(results).toHaveLength(3);
|
|
139
|
+
expect(results[0]).toEqual({});
|
|
140
|
+
expect(results[1]).toEqual({});
|
|
141
|
+
expect(results[2]).toEqual({});
|
|
142
|
+
expect(requestCount).toBe(3);
|
|
143
|
+
});
|
|
144
|
+
it("handles message ordering correctly", async () => {
|
|
145
|
+
const messageLog = [];
|
|
146
|
+
// Create client
|
|
147
|
+
class TestClient {
|
|
148
|
+
async writeTextFile(params) {
|
|
149
|
+
messageLog.push(`writeTextFile called: ${params.path}`);
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
async readTextFile(params) {
|
|
153
|
+
messageLog.push(`readTextFile called: ${params.path}`);
|
|
154
|
+
return { content: "test content" };
|
|
155
|
+
}
|
|
156
|
+
async requestPermission(params) {
|
|
157
|
+
messageLog.push(`requestPermission called: ${params.toolCall.title}`);
|
|
158
|
+
return {
|
|
159
|
+
outcome: {
|
|
160
|
+
outcome: "selected",
|
|
161
|
+
optionId: "allow",
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async sessionUpdate(_params) {
|
|
166
|
+
messageLog.push("sessionUpdate called");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Create agent
|
|
170
|
+
class TestAgent {
|
|
171
|
+
async initialize(_) {
|
|
172
|
+
return {
|
|
173
|
+
protocolVersion: 1,
|
|
174
|
+
agentCapabilities: { loadSession: false },
|
|
175
|
+
authMethods: [],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async newSession(request) {
|
|
179
|
+
messageLog.push(`newSession called: ${request.cwd}`);
|
|
180
|
+
return {
|
|
181
|
+
sessionId: "test-session",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async loadSession(params) {
|
|
185
|
+
messageLog.push(`loadSession called: ${params.sessionId}`);
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
async authenticate(params) {
|
|
189
|
+
messageLog.push(`authenticate called: ${params.methodId}`);
|
|
190
|
+
}
|
|
191
|
+
async prompt(params) {
|
|
192
|
+
messageLog.push(`prompt called: ${params.sessionId}`);
|
|
193
|
+
return { stopReason: "end_turn" };
|
|
194
|
+
}
|
|
195
|
+
async cancel(params) {
|
|
196
|
+
messageLog.push(`cancelled called: ${params.sessionId}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Set up connections
|
|
200
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
201
|
+
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
202
|
+
// Send requests in specific order
|
|
203
|
+
await agentConnection.newSession({
|
|
204
|
+
cwd: "/test",
|
|
205
|
+
mcpServers: [],
|
|
206
|
+
});
|
|
207
|
+
await clientConnection.writeTextFile({
|
|
208
|
+
path: "/test.txt",
|
|
209
|
+
content: "test",
|
|
210
|
+
sessionId: "test-session",
|
|
211
|
+
});
|
|
212
|
+
await clientConnection.readTextFile({
|
|
213
|
+
path: "/test.txt",
|
|
214
|
+
sessionId: "test-session",
|
|
215
|
+
});
|
|
216
|
+
await clientConnection.requestPermission({
|
|
217
|
+
sessionId: "test-session",
|
|
218
|
+
toolCall: {
|
|
219
|
+
title: "Execute command",
|
|
220
|
+
kind: "execute",
|
|
221
|
+
status: "pending",
|
|
222
|
+
toolCallId: "tool-123",
|
|
223
|
+
content: [
|
|
224
|
+
{
|
|
225
|
+
type: "content",
|
|
226
|
+
content: {
|
|
227
|
+
type: "text",
|
|
228
|
+
text: "ls -la",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
options: [
|
|
234
|
+
{
|
|
235
|
+
kind: "allow_once",
|
|
236
|
+
name: "Allow",
|
|
237
|
+
optionId: "allow",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
kind: "reject_once",
|
|
241
|
+
name: "Reject",
|
|
242
|
+
optionId: "reject",
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
// Verify order
|
|
247
|
+
expect(messageLog).toEqual([
|
|
248
|
+
"newSession called: /test",
|
|
249
|
+
"writeTextFile called: /test.txt",
|
|
250
|
+
"readTextFile called: /test.txt",
|
|
251
|
+
"requestPermission called: Execute command",
|
|
252
|
+
]);
|
|
253
|
+
});
|
|
254
|
+
it("handles notifications correctly", async () => {
|
|
255
|
+
const notificationLog = [];
|
|
256
|
+
// Create client
|
|
257
|
+
class TestClient {
|
|
258
|
+
async writeTextFile(_) {
|
|
259
|
+
return {};
|
|
260
|
+
}
|
|
261
|
+
async readTextFile(_) {
|
|
262
|
+
return { content: "test" };
|
|
263
|
+
}
|
|
264
|
+
async requestPermission(_) {
|
|
265
|
+
return {
|
|
266
|
+
outcome: {
|
|
267
|
+
outcome: "selected",
|
|
268
|
+
optionId: "allow",
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async sessionUpdate(notification) {
|
|
273
|
+
if (notification.update &&
|
|
274
|
+
"sessionUpdate" in notification.update &&
|
|
275
|
+
notification.update.sessionUpdate === "agent_message_chunk") {
|
|
276
|
+
notificationLog.push(`agent message: ${notification.update.content.text}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Create agent
|
|
281
|
+
class TestAgent {
|
|
282
|
+
async initialize(_) {
|
|
283
|
+
return {
|
|
284
|
+
protocolVersion: 1,
|
|
285
|
+
agentCapabilities: { loadSession: false },
|
|
286
|
+
authMethods: [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async newSession(_) {
|
|
290
|
+
return {
|
|
291
|
+
sessionId: "test-session",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async loadSession(_) {
|
|
295
|
+
return {};
|
|
296
|
+
}
|
|
297
|
+
async authenticate(_) {
|
|
298
|
+
// no-op
|
|
299
|
+
}
|
|
300
|
+
async prompt(_) {
|
|
301
|
+
return { stopReason: "end_turn" };
|
|
302
|
+
}
|
|
303
|
+
async cancel(params) {
|
|
304
|
+
notificationLog.push(`cancelled: ${params.sessionId}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Create shared instances
|
|
308
|
+
const testClient = () => new TestClient();
|
|
309
|
+
const testAgent = () => new TestAgent();
|
|
310
|
+
// Set up connections
|
|
311
|
+
const agentConnection = new ClientSideConnection(testClient, ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
312
|
+
const clientConnection = new AgentSideConnection(testAgent, ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
313
|
+
// Send notifications
|
|
314
|
+
await clientConnection.sessionUpdate({
|
|
315
|
+
sessionId: "test-session",
|
|
316
|
+
update: {
|
|
317
|
+
sessionUpdate: "agent_message_chunk",
|
|
318
|
+
content: {
|
|
319
|
+
type: "text",
|
|
320
|
+
text: "Hello from agent",
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
await agentConnection.cancel({
|
|
325
|
+
sessionId: "test-session",
|
|
326
|
+
});
|
|
327
|
+
// Wait a bit for async handlers
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
329
|
+
// Verify notifications were received
|
|
330
|
+
expect(notificationLog).toContain("agent message: Hello from agent");
|
|
331
|
+
expect(notificationLog).toContain("cancelled: test-session");
|
|
332
|
+
});
|
|
333
|
+
it("handles initialize method", async () => {
|
|
334
|
+
// Create client
|
|
335
|
+
class TestClient {
|
|
336
|
+
async writeTextFile(_) {
|
|
337
|
+
return {};
|
|
338
|
+
}
|
|
339
|
+
async readTextFile(_) {
|
|
340
|
+
return { content: "test" };
|
|
341
|
+
}
|
|
342
|
+
async requestPermission(_) {
|
|
343
|
+
return {
|
|
344
|
+
outcome: {
|
|
345
|
+
outcome: "selected",
|
|
346
|
+
optionId: "allow",
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async sessionUpdate(_) {
|
|
351
|
+
// no-op
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Create agent
|
|
355
|
+
class TestAgent {
|
|
356
|
+
async initialize(params) {
|
|
357
|
+
return {
|
|
358
|
+
protocolVersion: params.protocolVersion,
|
|
359
|
+
agentCapabilities: { loadSession: true },
|
|
360
|
+
authMethods: [
|
|
361
|
+
{
|
|
362
|
+
id: "oauth",
|
|
363
|
+
name: "OAuth",
|
|
364
|
+
description: "Authenticate with OAuth",
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
async newSession(_) {
|
|
370
|
+
return { sessionId: "test-session" };
|
|
371
|
+
}
|
|
372
|
+
async loadSession(_) {
|
|
373
|
+
return {};
|
|
374
|
+
}
|
|
375
|
+
async authenticate(_) {
|
|
376
|
+
// no-op
|
|
377
|
+
}
|
|
378
|
+
async prompt(_) {
|
|
379
|
+
return { stopReason: "end_turn" };
|
|
380
|
+
}
|
|
381
|
+
async cancel(_) {
|
|
382
|
+
// no-op
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Set up connections
|
|
386
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
387
|
+
new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
388
|
+
// Test initialize request
|
|
389
|
+
const response = await agentConnection.initialize({
|
|
390
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
391
|
+
clientCapabilities: {
|
|
392
|
+
fs: {
|
|
393
|
+
readTextFile: false,
|
|
394
|
+
writeTextFile: false,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
expect(response.protocolVersion).toBe(PROTOCOL_VERSION);
|
|
399
|
+
expect(response.agentCapabilities?.loadSession).toBe(true);
|
|
400
|
+
expect(response.authMethods).toHaveLength(1);
|
|
401
|
+
expect(response.authMethods?.[0].id).toBe("oauth");
|
|
402
|
+
});
|
|
403
|
+
it("handles extension methods and notifications", async () => {
|
|
404
|
+
const extensionLog = [];
|
|
405
|
+
// Create client with extension method support
|
|
406
|
+
class TestClient {
|
|
407
|
+
async writeTextFile(_) {
|
|
408
|
+
return {};
|
|
409
|
+
}
|
|
410
|
+
async readTextFile(_) {
|
|
411
|
+
return { content: "test" };
|
|
412
|
+
}
|
|
413
|
+
async requestPermission(_) {
|
|
414
|
+
return {
|
|
415
|
+
outcome: {
|
|
416
|
+
outcome: "selected",
|
|
417
|
+
optionId: "allow",
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
async sessionUpdate(_) {
|
|
422
|
+
// no-op
|
|
423
|
+
}
|
|
424
|
+
async extMethod(method, params) {
|
|
425
|
+
if (method === "example.com/ping") {
|
|
426
|
+
return { response: "pong", params };
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`Unknown method: ${method}`);
|
|
429
|
+
}
|
|
430
|
+
async extNotification(method, _params) {
|
|
431
|
+
extensionLog.push(`client extNotification: ${method}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// Create agent with extension method support
|
|
435
|
+
class TestAgent {
|
|
436
|
+
async initialize(_) {
|
|
437
|
+
return {
|
|
438
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
439
|
+
agentCapabilities: { loadSession: false },
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async newSession(_) {
|
|
443
|
+
return { sessionId: "test-session" };
|
|
444
|
+
}
|
|
445
|
+
async authenticate(_) {
|
|
446
|
+
// no-op
|
|
447
|
+
}
|
|
448
|
+
async prompt(_) {
|
|
449
|
+
return { stopReason: "end_turn" };
|
|
450
|
+
}
|
|
451
|
+
async cancel(_) {
|
|
452
|
+
// no-op
|
|
453
|
+
}
|
|
454
|
+
async extMethod(method, params) {
|
|
455
|
+
if (method === "example.com/echo") {
|
|
456
|
+
return { echo: params };
|
|
457
|
+
}
|
|
458
|
+
throw new Error(`Unknown method: ${method}`);
|
|
459
|
+
}
|
|
460
|
+
async extNotification(method, _params) {
|
|
461
|
+
extensionLog.push(`agent extNotification: ${method}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Set up connections
|
|
465
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
466
|
+
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
467
|
+
// Test agent calling client extension method
|
|
468
|
+
const clientResponse = await clientConnection.extMethod("example.com/ping", {
|
|
469
|
+
data: "test",
|
|
470
|
+
});
|
|
471
|
+
expect(clientResponse).toEqual({
|
|
472
|
+
response: "pong",
|
|
473
|
+
params: { data: "test" },
|
|
474
|
+
});
|
|
475
|
+
// Test client calling agent extension method
|
|
476
|
+
const agentResponse = await agentConnection.extMethod("example.com/echo", {
|
|
477
|
+
message: "hello",
|
|
478
|
+
});
|
|
479
|
+
expect(agentResponse).toEqual({ echo: { message: "hello" } });
|
|
480
|
+
// Test extension notifications
|
|
481
|
+
await clientConnection.extNotification("example.com/client/notify", {
|
|
482
|
+
info: "client notification",
|
|
483
|
+
});
|
|
484
|
+
await agentConnection.extNotification("example.com/agent/notify", {
|
|
485
|
+
info: "agent notification",
|
|
486
|
+
});
|
|
487
|
+
// Wait a bit for async handlers
|
|
488
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
489
|
+
// Verify notifications were logged
|
|
490
|
+
expect(extensionLog).toContain("client extNotification: example.com/client/notify");
|
|
491
|
+
expect(extensionLog).toContain("agent extNotification: example.com/agent/notify");
|
|
492
|
+
});
|
|
493
|
+
it("handles optional extension methods correctly", async () => {
|
|
494
|
+
// Create client WITHOUT extension methods
|
|
495
|
+
class TestClientWithoutExtensions {
|
|
496
|
+
async writeTextFile(_) {
|
|
497
|
+
return {};
|
|
498
|
+
}
|
|
499
|
+
async readTextFile(_) {
|
|
500
|
+
return { content: "test" };
|
|
501
|
+
}
|
|
502
|
+
async requestPermission(_) {
|
|
503
|
+
return {
|
|
504
|
+
outcome: {
|
|
505
|
+
outcome: "selected",
|
|
506
|
+
optionId: "allow",
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async sessionUpdate(_) {
|
|
511
|
+
// no-op
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Create agent WITHOUT extension methods
|
|
515
|
+
class TestAgentWithoutExtensions {
|
|
516
|
+
async initialize(_) {
|
|
517
|
+
return {
|
|
518
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
519
|
+
agentCapabilities: { loadSession: false },
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async newSession(_) {
|
|
523
|
+
return { sessionId: "test-session" };
|
|
524
|
+
}
|
|
525
|
+
async authenticate(_) {
|
|
526
|
+
// no-op
|
|
527
|
+
}
|
|
528
|
+
async prompt(_) {
|
|
529
|
+
return { stopReason: "end_turn" };
|
|
530
|
+
}
|
|
531
|
+
async cancel(_) {
|
|
532
|
+
// no-op
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// Set up connections
|
|
536
|
+
const agentConnection = new ClientSideConnection(() => new TestClientWithoutExtensions(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
537
|
+
const clientConnection = new AgentSideConnection(() => new TestAgentWithoutExtensions(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
538
|
+
// Test that calling extension methods on connections without them throws method not found
|
|
539
|
+
try {
|
|
540
|
+
await clientConnection.extMethod("_example.com/ping", { data: "test" });
|
|
541
|
+
expect.fail("Should have thrown method not found error");
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
expect(error.code).toBe(-32601); // Method not found
|
|
545
|
+
expect(error.data.method).toBe("_example.com/ping");
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
await agentConnection.extMethod("_example.com/echo", {
|
|
549
|
+
message: "hello",
|
|
550
|
+
});
|
|
551
|
+
expect.fail("Should have thrown method not found error");
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
expect(error.code).toBe(-32601); // Method not found
|
|
555
|
+
expect(error.data.method).toBe("_example.com/echo");
|
|
556
|
+
}
|
|
557
|
+
// Notifications should be ignored when not implemented (no error thrown)
|
|
558
|
+
await clientConnection.extNotification("example.com/notify", {
|
|
559
|
+
info: "test",
|
|
560
|
+
});
|
|
561
|
+
await agentConnection.extNotification("example.com/notify", {
|
|
562
|
+
info: "test",
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
it("resolves closed promise when stream ends", async () => {
|
|
566
|
+
const closeLog = [];
|
|
567
|
+
// Create simple client and agent
|
|
568
|
+
class TestClient {
|
|
569
|
+
async writeTextFile(_) {
|
|
570
|
+
return {};
|
|
571
|
+
}
|
|
572
|
+
async readTextFile(_) {
|
|
573
|
+
return { content: "test" };
|
|
574
|
+
}
|
|
575
|
+
async requestPermission(_) {
|
|
576
|
+
return {
|
|
577
|
+
outcome: {
|
|
578
|
+
outcome: "selected",
|
|
579
|
+
optionId: "allow",
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
async sessionUpdate(_) {
|
|
584
|
+
// no-op
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
class TestAgent {
|
|
588
|
+
async initialize(_) {
|
|
589
|
+
return {
|
|
590
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
591
|
+
agentCapabilities: { loadSession: false },
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async newSession(_) {
|
|
595
|
+
return { sessionId: "test-session" };
|
|
596
|
+
}
|
|
597
|
+
async authenticate(_) {
|
|
598
|
+
// no-op
|
|
599
|
+
}
|
|
600
|
+
async prompt(_) {
|
|
601
|
+
return { stopReason: "end_turn" };
|
|
602
|
+
}
|
|
603
|
+
async cancel(_) {
|
|
604
|
+
// no-op
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Set up connections
|
|
608
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
609
|
+
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
610
|
+
// Listen for close via signal
|
|
611
|
+
agentConnection.signal.addEventListener("abort", () => {
|
|
612
|
+
closeLog.push("agent connection closed (signal)");
|
|
613
|
+
});
|
|
614
|
+
clientConnection.signal.addEventListener("abort", () => {
|
|
615
|
+
closeLog.push("client connection closed (signal)");
|
|
616
|
+
});
|
|
617
|
+
// Verify connections are not closed yet
|
|
618
|
+
expect(agentConnection.signal.aborted).toBe(false);
|
|
619
|
+
expect(clientConnection.signal.aborted).toBe(false);
|
|
620
|
+
expect(closeLog).toHaveLength(0);
|
|
621
|
+
// Close the streams by closing the writable ends
|
|
622
|
+
await clientToAgent.writable.close();
|
|
623
|
+
await agentToClient.writable.close();
|
|
624
|
+
// Wait for closed promises to resolve
|
|
625
|
+
await agentConnection.closed;
|
|
626
|
+
await clientConnection.closed;
|
|
627
|
+
// Verify connections are now closed
|
|
628
|
+
expect(agentConnection.signal.aborted).toBe(true);
|
|
629
|
+
expect(clientConnection.signal.aborted).toBe(true);
|
|
630
|
+
expect(closeLog).toContain("agent connection closed (signal)");
|
|
631
|
+
expect(closeLog).toContain("client connection closed (signal)");
|
|
632
|
+
});
|
|
633
|
+
it("supports removing signal event listeners", async () => {
|
|
634
|
+
const closeLog = [];
|
|
635
|
+
// Create simple client and agent
|
|
636
|
+
class TestClient {
|
|
637
|
+
async writeTextFile(_) {
|
|
638
|
+
return {};
|
|
639
|
+
}
|
|
640
|
+
async readTextFile(_) {
|
|
641
|
+
return { content: "test" };
|
|
642
|
+
}
|
|
643
|
+
async requestPermission(_) {
|
|
644
|
+
return {
|
|
645
|
+
outcome: {
|
|
646
|
+
outcome: "selected",
|
|
647
|
+
optionId: "allow",
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
async sessionUpdate(_) {
|
|
652
|
+
// no-op
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
class TestAgent {
|
|
656
|
+
async initialize(_) {
|
|
657
|
+
return {
|
|
658
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
659
|
+
agentCapabilities: { loadSession: false },
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
async newSession(_) {
|
|
663
|
+
return { sessionId: "test-session" };
|
|
664
|
+
}
|
|
665
|
+
async authenticate(_) {
|
|
666
|
+
// no-op
|
|
667
|
+
}
|
|
668
|
+
async prompt(_) {
|
|
669
|
+
return { stopReason: "end_turn" };
|
|
670
|
+
}
|
|
671
|
+
async cancel(_) {
|
|
672
|
+
// no-op
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// Set up connections
|
|
676
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
677
|
+
new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
678
|
+
// Register and then remove a listener
|
|
679
|
+
const listener = () => {
|
|
680
|
+
closeLog.push("this should not be called");
|
|
681
|
+
};
|
|
682
|
+
agentConnection.signal.addEventListener("abort", listener);
|
|
683
|
+
agentConnection.signal.removeEventListener("abort", listener);
|
|
684
|
+
// Register another listener that should be called
|
|
685
|
+
agentConnection.signal.addEventListener("abort", () => {
|
|
686
|
+
closeLog.push("agent connection closed");
|
|
687
|
+
});
|
|
688
|
+
// Close the streams
|
|
689
|
+
await clientToAgent.writable.close();
|
|
690
|
+
await agentToClient.writable.close();
|
|
691
|
+
// Wait for closed promise
|
|
692
|
+
await agentConnection.closed;
|
|
693
|
+
// Verify only the non-removed listener was called
|
|
694
|
+
expect(closeLog).toEqual(["agent connection closed"]);
|
|
695
|
+
expect(closeLog).not.toContain("this should not be called");
|
|
696
|
+
});
|
|
697
|
+
it("handles methods returning response objects with _meta or void", async () => {
|
|
698
|
+
// Create client that returns both response objects and void
|
|
699
|
+
class TestClient {
|
|
700
|
+
async writeTextFile(_params) {
|
|
701
|
+
// Return response object with _meta
|
|
702
|
+
return {
|
|
703
|
+
_meta: {
|
|
704
|
+
timestamp: new Date().toISOString(),
|
|
705
|
+
version: "1.0.0",
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
async readTextFile(_params) {
|
|
710
|
+
return {
|
|
711
|
+
content: "test content",
|
|
712
|
+
_meta: {
|
|
713
|
+
encoding: "utf-8",
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
async requestPermission(_params) {
|
|
718
|
+
return {
|
|
719
|
+
outcome: {
|
|
720
|
+
outcome: "selected",
|
|
721
|
+
optionId: "allow",
|
|
722
|
+
},
|
|
723
|
+
_meta: {
|
|
724
|
+
userId: "test-user",
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
async sessionUpdate(_params) {
|
|
729
|
+
// Returns void
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// Create agent that returns both response objects and void
|
|
733
|
+
class TestAgent {
|
|
734
|
+
async initialize(params) {
|
|
735
|
+
return {
|
|
736
|
+
protocolVersion: params.protocolVersion,
|
|
737
|
+
agentCapabilities: { loadSession: true },
|
|
738
|
+
_meta: {
|
|
739
|
+
agentVersion: "2.0.0",
|
|
740
|
+
},
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
async newSession(_params) {
|
|
744
|
+
return {
|
|
745
|
+
sessionId: "test-session",
|
|
746
|
+
_meta: {
|
|
747
|
+
sessionType: "ephemeral",
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
async loadSession(_params) {
|
|
752
|
+
// Test returning minimal response
|
|
753
|
+
return {};
|
|
754
|
+
}
|
|
755
|
+
async authenticate(params) {
|
|
756
|
+
if (params.methodId === "none") {
|
|
757
|
+
// Test returning void
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
// Test returning response with _meta
|
|
761
|
+
return {
|
|
762
|
+
_meta: {
|
|
763
|
+
authenticated: true,
|
|
764
|
+
method: params.methodId,
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
async prompt(_params) {
|
|
769
|
+
return { stopReason: "end_turn" };
|
|
770
|
+
}
|
|
771
|
+
async cancel(_params) {
|
|
772
|
+
// Returns void
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// Set up connections
|
|
776
|
+
const agentConnection = new ClientSideConnection(() => new TestClient(), ndJsonStream(clientToAgent.writable, agentToClient.readable));
|
|
777
|
+
const clientConnection = new AgentSideConnection(() => new TestAgent(), ndJsonStream(agentToClient.writable, clientToAgent.readable));
|
|
778
|
+
// Test writeTextFile returns response with _meta
|
|
779
|
+
const writeResponse = await clientConnection.writeTextFile({
|
|
780
|
+
path: "/test.txt",
|
|
781
|
+
content: "test",
|
|
782
|
+
sessionId: "test-session",
|
|
783
|
+
});
|
|
784
|
+
expect(writeResponse).toEqual({
|
|
785
|
+
_meta: {
|
|
786
|
+
timestamp: expect.any(String),
|
|
787
|
+
version: "1.0.0",
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
// Test readTextFile returns response with content and _meta
|
|
791
|
+
const readResponse = await clientConnection.readTextFile({
|
|
792
|
+
path: "/test.txt",
|
|
793
|
+
sessionId: "test-session",
|
|
794
|
+
});
|
|
795
|
+
expect(readResponse.content).toBe("test content");
|
|
796
|
+
expect(readResponse._meta).toEqual({
|
|
797
|
+
encoding: "utf-8",
|
|
798
|
+
});
|
|
799
|
+
// Test initialize with _meta
|
|
800
|
+
const initResponse = await agentConnection.initialize({
|
|
801
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
802
|
+
clientCapabilities: {},
|
|
803
|
+
});
|
|
804
|
+
expect(initResponse._meta).toEqual({
|
|
805
|
+
agentVersion: "2.0.0",
|
|
806
|
+
});
|
|
807
|
+
// Test authenticate returning void
|
|
808
|
+
const authResponseVoid = await agentConnection.authenticate({
|
|
809
|
+
methodId: "none",
|
|
810
|
+
});
|
|
811
|
+
expect(authResponseVoid).toEqual({});
|
|
812
|
+
// Test authenticate returning response with _meta
|
|
813
|
+
const authResponse = await agentConnection.authenticate({
|
|
814
|
+
methodId: "oauth",
|
|
815
|
+
});
|
|
816
|
+
expect(authResponse).toEqual({
|
|
817
|
+
_meta: {
|
|
818
|
+
authenticated: true,
|
|
819
|
+
method: "oauth",
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
// Test newSession with _meta
|
|
823
|
+
const sessionResponse = await agentConnection.newSession({
|
|
824
|
+
cwd: "/test",
|
|
825
|
+
mcpServers: [],
|
|
826
|
+
});
|
|
827
|
+
expect(sessionResponse._meta).toEqual({
|
|
828
|
+
sessionType: "ephemeral",
|
|
829
|
+
});
|
|
830
|
+
// Test loadSession returning minimal response
|
|
831
|
+
const loadResponse = await agentConnection.loadSession({
|
|
832
|
+
sessionId: "test-session",
|
|
833
|
+
mcpServers: [],
|
|
834
|
+
cwd: "/test",
|
|
835
|
+
});
|
|
836
|
+
expect(loadResponse).toEqual({});
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
//# sourceMappingURL=acp.test.js.map
|