@moltzap/client 2026.414.0 → 2026.425.1
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/dist/channel-core.d.ts +28 -15
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +113 -74
- package/dist/channel-core.js.map +1 -1
- package/dist/channel-core.test.js +126 -34
- package/dist/channel-core.test.js.map +1 -1
- package/dist/cli/__tests__/cli-multi-agent.int.test.d.ts +2 -0
- package/dist/cli/__tests__/cli-multi-agent.int.test.d.ts.map +1 -0
- package/dist/cli/__tests__/cli-multi-agent.int.test.js +26 -0
- package/dist/cli/__tests__/cli-multi-agent.int.test.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +13 -2
- package/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +30 -18
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/apps.d.ts +96 -0
- package/dist/cli/commands/apps.d.ts.map +1 -0
- package/dist/cli/commands/apps.js +193 -0
- package/dist/cli/commands/apps.js.map +1 -0
- package/dist/cli/commands/apps.test.d.ts +2 -0
- package/dist/cli/commands/apps.test.d.ts.map +1 -0
- package/dist/cli/commands/apps.test.js +220 -0
- package/dist/cli/commands/apps.test.js.map +1 -0
- package/dist/cli/commands/contacts.d.ts +18 -2
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +44 -52
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations-v2.test.d.ts +2 -0
- package/dist/cli/commands/conversations-v2.test.d.ts.map +1 -0
- package/dist/cli/commands/conversations-v2.test.js +92 -0
- package/dist/cli/commands/conversations-v2.test.js.map +1 -0
- package/dist/cli/commands/conversations.d.ts +73 -3
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +157 -108
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/invite.d.ts +6 -2
- package/dist/cli/commands/invite.d.ts.map +1 -1
- package/dist/cli/commands/invite.js +8 -6
- package/dist/cli/commands/invite.js.map +1 -1
- package/dist/cli/commands/messages.d.ts +48 -0
- package/dist/cli/commands/messages.d.ts.map +1 -0
- package/dist/cli/commands/messages.js +68 -0
- package/dist/cli/commands/messages.js.map +1 -0
- package/dist/cli/commands/messages.test.d.ts +2 -0
- package/dist/cli/commands/messages.test.d.ts.map +1 -0
- package/dist/cli/commands/messages.test.js +73 -0
- package/dist/cli/commands/messages.test.js.map +1 -0
- package/dist/cli/commands/permissions.d.ts +66 -0
- package/dist/cli/commands/permissions.d.ts.map +1 -0
- package/dist/cli/commands/permissions.js +101 -0
- package/dist/cli/commands/permissions.js.map +1 -0
- package/dist/cli/commands/permissions.test.d.ts +2 -0
- package/dist/cli/commands/permissions.test.d.ts.map +1 -0
- package/dist/cli/commands/permissions.test.js +126 -0
- package/dist/cli/commands/permissions.test.js.map +1 -0
- package/dist/cli/commands/ping.d.ts +6 -2
- package/dist/cli/commands/ping.d.ts.map +1 -1
- package/dist/cli/commands/ping.js +21 -20
- package/dist/cli/commands/ping.js.map +1 -1
- package/dist/cli/commands/presence.d.ts +10 -2
- package/dist/cli/commands/presence.d.ts.map +1 -1
- package/dist/cli/commands/presence.js +27 -15
- package/dist/cli/commands/presence.js.map +1 -1
- package/dist/cli/commands/register.d.ts +24 -2
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +123 -47
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/register.test.js +45 -27
- package/dist/cli/commands/register.test.js.map +1 -1
- package/dist/cli/commands/send.d.ts +43 -2
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +55 -13
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/send.test.js +20 -19
- package/dist/cli/commands/send.test.js.map +1 -1
- package/dist/cli/commands/status.d.ts +6 -2
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +16 -10
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/whoami.d.ts +7 -2
- package/dist/cli/commands/whoami.d.ts.map +1 -1
- package/dist/cli/commands/whoami.js +13 -7
- package/dist/cli/commands/whoami.js.map +1 -1
- package/dist/cli/config.d.ts +24 -8
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +65 -39
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +12 -10
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/http-client.d.ts +6 -2
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +25 -24
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts +16 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +160 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/profile.d.ts +134 -0
- package/dist/cli/profile.d.ts.map +1 -0
- package/dist/cli/profile.js +217 -0
- package/dist/cli/profile.js.map +1 -0
- package/dist/cli/profile.test.d.ts +2 -0
- package/dist/cli/profile.test.d.ts.map +1 -0
- package/dist/cli/profile.test.js +263 -0
- package/dist/cli/profile.test.js.map +1 -0
- package/dist/cli/runtime.d.ts +34 -0
- package/dist/cli/runtime.d.ts.map +1 -0
- package/dist/cli/runtime.js +94 -0
- package/dist/cli/runtime.js.map +1 -0
- package/dist/cli/socket-client.d.ts +15 -12
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +72 -75
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +181 -0
- package/dist/cli/transport.d.ts.map +1 -0
- package/dist/cli/transport.js +324 -0
- package/dist/cli/transport.js.map +1 -0
- package/dist/cli/transport.test.d.ts +2 -0
- package/dist/cli/transport.test.d.ts.map +1 -0
- package/dist/cli/transport.test.js +197 -0
- package/dist/cli/transport.test.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/close-info.d.ts +94 -0
- package/dist/runtime/close-info.d.ts.map +1 -0
- package/dist/runtime/close-info.js +109 -0
- package/dist/runtime/close-info.js.map +1 -0
- package/dist/runtime/close-info.test.d.ts +2 -0
- package/dist/runtime/close-info.test.d.ts.map +1 -0
- package/dist/runtime/close-info.test.js +102 -0
- package/dist/runtime/close-info.test.js.map +1 -0
- package/dist/runtime/errors.d.ts +47 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +20 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/errors.test.d.ts +2 -0
- package/dist/runtime/errors.test.d.ts.map +1 -0
- package/dist/runtime/errors.test.js +66 -0
- package/dist/runtime/errors.test.js.map +1 -0
- package/dist/runtime/frame.d.ts +25 -0
- package/dist/runtime/frame.d.ts.map +1 -0
- package/dist/runtime/frame.js +106 -0
- package/dist/runtime/frame.js.map +1 -0
- package/dist/runtime/frame.test.d.ts +2 -0
- package/dist/runtime/frame.test.d.ts.map +1 -0
- package/dist/runtime/frame.test.js +32 -0
- package/dist/runtime/frame.test.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/refs.d.ts +10 -0
- package/dist/runtime/refs.d.ts.map +1 -0
- package/dist/runtime/refs.js +10 -0
- package/dist/runtime/refs.js.map +1 -0
- package/dist/runtime/runtime.test.d.ts +2 -0
- package/dist/runtime/runtime.test.d.ts.map +1 -0
- package/dist/runtime/runtime.test.js +29 -0
- package/dist/runtime/runtime.test.js.map +1 -0
- package/dist/runtime/subscribers.d.ts +157 -0
- package/dist/runtime/subscribers.d.ts.map +1 -0
- package/dist/runtime/subscribers.js +131 -0
- package/dist/runtime/subscribers.js.map +1 -0
- package/dist/runtime/subscribers.test.d.ts +2 -0
- package/dist/runtime/subscribers.test.d.ts.map +1 -0
- package/dist/runtime/subscribers.test.js +155 -0
- package/dist/runtime/subscribers.test.js.map +1 -0
- package/dist/service.d.ts +68 -17
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +461 -307
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +266 -77
- package/dist/service.test.js.map +1 -1
- package/dist/test/index.d.ts +33 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +44 -0
- package/dist/test/index.js.map +1 -0
- package/dist/test-utils/channel-service-fixture.d.ts +2 -1
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +31 -11
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/conformance-adapter.d.ts +50 -0
- package/dist/test-utils/conformance-adapter.d.ts.map +1 -0
- package/dist/test-utils/conformance-adapter.js +206 -0
- package/dist/test-utils/conformance-adapter.js.map +1 -0
- package/dist/test-utils/fake-service.d.ts +85 -0
- package/dist/test-utils/fake-service.d.ts.map +1 -0
- package/dist/test-utils/fake-service.js +103 -0
- package/dist/test-utils/fake-service.js.map +1 -0
- package/dist/test-utils/index.d.ts +2 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +3 -2
- package/dist/test-utils/index.js.map +1 -1
- package/dist/ws-client.d.ts +157 -18
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +432 -94
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-client.test.d.ts +2 -0
- package/dist/ws-client.test.d.ts.map +1 -0
- package/dist/ws-client.test.js +672 -0
- package/dist/ws-client.test.js.map +1 -0
- package/package.json +17 -8
- package/dist/__tests__/service.integration.test.d.ts +0 -2
- package/dist/__tests__/service.integration.test.d.ts.map +0 -1
- package/dist/__tests__/service.integration.test.js +0 -1087
- package/dist/__tests__/service.integration.test.js.map +0 -1
- package/dist/cli/commands/delete.d.ts +0 -3
- package/dist/cli/commands/delete.d.ts.map +0 -1
- package/dist/cli/commands/delete.js +0 -10
- package/dist/cli/commands/delete.js.map +0 -1
- package/dist/cli/commands/react.d.ts +0 -3
- package/dist/cli/commands/react.d.ts.map +0 -1
- package/dist/cli/commands/react.js +0 -18
- package/dist/cli/commands/react.js.map +0 -1
|
@@ -1,1087 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, beforeEach, inject, } from "vitest";
|
|
2
|
-
import { startCoreTestServer, stopCoreTestServer, resetCoreTestDb, } from "@moltzap/server-core/test-utils";
|
|
3
|
-
import { MoltZapTestClient } from "@moltzap/protocol/test-client";
|
|
4
|
-
import { MoltZapService } from "../service.js";
|
|
5
|
-
let baseUrl;
|
|
6
|
-
let wsUrl;
|
|
7
|
-
beforeAll(async () => {
|
|
8
|
-
const pgHost = inject("testPgHost");
|
|
9
|
-
const pgPort = inject("testPgPort");
|
|
10
|
-
const server = await startCoreTestServer({ pgHost, pgPort });
|
|
11
|
-
baseUrl = server.baseUrl;
|
|
12
|
-
wsUrl = server.wsUrl;
|
|
13
|
-
}, 60_000);
|
|
14
|
-
afterAll(async () => {
|
|
15
|
-
await stopCoreTestServer();
|
|
16
|
-
});
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
await resetCoreTestDb();
|
|
19
|
-
});
|
|
20
|
-
/** Register an agent and return its credentials. */
|
|
21
|
-
async function registerAgent(name) {
|
|
22
|
-
const client = new MoltZapTestClient(baseUrl, wsUrl);
|
|
23
|
-
const reg = await client.register(name);
|
|
24
|
-
return { client, ...reg };
|
|
25
|
-
}
|
|
26
|
-
/** Create a MoltZapService, connect, and return it. Caller must close. */
|
|
27
|
-
async function connectService(apiKey) {
|
|
28
|
-
// Use baseUrl — WsClient appends /ws and converts http→ws internally
|
|
29
|
-
const service = new MoltZapService({ serverUrl: baseUrl, agentKey: apiKey });
|
|
30
|
-
await service.connect();
|
|
31
|
-
return service;
|
|
32
|
-
}
|
|
33
|
-
/** Send a message from testClient to a conversation and wait for settle. */
|
|
34
|
-
async function sendAndSettle(client, conversationId, text) {
|
|
35
|
-
await client.rpc("messages/send", {
|
|
36
|
-
conversationId,
|
|
37
|
-
parts: [{ type: "text", text }],
|
|
38
|
-
});
|
|
39
|
-
// Let the message propagate through WebSocket events
|
|
40
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
41
|
-
}
|
|
42
|
-
// ─── Group 1: Connection & Core API ──────────────────────────────────────────
|
|
43
|
-
describe("Connection & Core API", () => {
|
|
44
|
-
it("connect() returns HelloOk with agentId", async () => {
|
|
45
|
-
const reg = await registerAgent("svc-agent");
|
|
46
|
-
const service = await connectService(reg.apiKey);
|
|
47
|
-
expect(service.ownAgentId).toBe(reg.agentId);
|
|
48
|
-
expect(service.connected).toBe(true);
|
|
49
|
-
service.close();
|
|
50
|
-
reg.client.close();
|
|
51
|
-
});
|
|
52
|
-
it("getConversation() is populated after connect with existing conversations", async () => {
|
|
53
|
-
const regA = await registerAgent("agent-a");
|
|
54
|
-
const regB = await registerAgent("agent-b");
|
|
55
|
-
// Connect agent-a and create a conversation before agent-b connects as service
|
|
56
|
-
await regA.client.connect(regA.apiKey);
|
|
57
|
-
const conv = (await regA.client.rpc("conversations/create", {
|
|
58
|
-
type: "dm",
|
|
59
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
60
|
-
}));
|
|
61
|
-
// Now connect agent-b as MoltZapService — should see the conversation in HelloOk
|
|
62
|
-
const service = await connectService(regB.apiKey);
|
|
63
|
-
const found = service.getConversation(conv.conversation.id);
|
|
64
|
-
expect(found).toBeDefined();
|
|
65
|
-
expect(found.type).toBe("dm");
|
|
66
|
-
service.close();
|
|
67
|
-
regA.client.close();
|
|
68
|
-
regB.client.close();
|
|
69
|
-
});
|
|
70
|
-
it("on('message') fires for incoming message from another agent", async () => {
|
|
71
|
-
const regSender = await registerAgent("sender");
|
|
72
|
-
const regReceiver = await registerAgent("receiver");
|
|
73
|
-
await regSender.client.connect(regSender.apiKey);
|
|
74
|
-
const service = await connectService(regReceiver.apiKey);
|
|
75
|
-
const conv = (await regSender.client.rpc("conversations/create", {
|
|
76
|
-
type: "dm",
|
|
77
|
-
participants: [{ type: "agent", id: regReceiver.agentId }],
|
|
78
|
-
}));
|
|
79
|
-
const received = [];
|
|
80
|
-
service.on("message", (msg) => received.push(msg));
|
|
81
|
-
await sendAndSettle(regSender.client, conv.conversation.id, "Hello receiver");
|
|
82
|
-
expect(received.length).toBe(1);
|
|
83
|
-
const msg = received[0];
|
|
84
|
-
expect(msg.parts[0].text).toBe("Hello receiver");
|
|
85
|
-
service.close();
|
|
86
|
-
regSender.client.close();
|
|
87
|
-
regReceiver.client.close();
|
|
88
|
-
});
|
|
89
|
-
it("on('message') skips own agent's messages", async () => {
|
|
90
|
-
const regA = await registerAgent("self-sender");
|
|
91
|
-
const regB = await registerAgent("other");
|
|
92
|
-
await regB.client.connect(regB.apiKey);
|
|
93
|
-
const service = await connectService(regA.apiKey);
|
|
94
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
95
|
-
type: "dm",
|
|
96
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
97
|
-
}));
|
|
98
|
-
const received = [];
|
|
99
|
-
service.on("message", (msg) => received.push(msg));
|
|
100
|
-
// Send from the service (own agent) — should NOT fire on("message")
|
|
101
|
-
await service.send(conv.conversation.id, "Self message");
|
|
102
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
103
|
-
expect(received.length).toBe(0);
|
|
104
|
-
service.close();
|
|
105
|
-
regA.client.close();
|
|
106
|
-
regB.client.close();
|
|
107
|
-
});
|
|
108
|
-
it("getHistory() stores received messages", async () => {
|
|
109
|
-
const regSender = await registerAgent("hist-sender");
|
|
110
|
-
const regReceiver = await registerAgent("hist-receiver");
|
|
111
|
-
await regSender.client.connect(regSender.apiKey);
|
|
112
|
-
const service = await connectService(regReceiver.apiKey);
|
|
113
|
-
const conv = (await regSender.client.rpc("conversations/create", {
|
|
114
|
-
type: "dm",
|
|
115
|
-
participants: [{ type: "agent", id: regReceiver.agentId }],
|
|
116
|
-
}));
|
|
117
|
-
await sendAndSettle(regSender.client, conv.conversation.id, "msg 1");
|
|
118
|
-
await sendAndSettle(regSender.client, conv.conversation.id, "msg 2");
|
|
119
|
-
const history = service.getHistory(conv.conversation.id);
|
|
120
|
-
expect(history.length).toBe(2);
|
|
121
|
-
service.close();
|
|
122
|
-
regSender.client.close();
|
|
123
|
-
regReceiver.client.close();
|
|
124
|
-
});
|
|
125
|
-
it("resolveAgentName() returns and caches name", async () => {
|
|
126
|
-
const reg = await registerAgent("name-test");
|
|
127
|
-
const service = await connectService(reg.apiKey);
|
|
128
|
-
// Before resolution, getAgentName returns undefined
|
|
129
|
-
expect(service.getAgentName(reg.agentId)).toBeUndefined();
|
|
130
|
-
const name = await service.resolveAgentName(reg.agentId);
|
|
131
|
-
expect(name).toBe("name-test");
|
|
132
|
-
// After resolution, getAgentName returns cached value
|
|
133
|
-
expect(service.getAgentName(reg.agentId)).toBe("name-test");
|
|
134
|
-
service.close();
|
|
135
|
-
reg.client.close();
|
|
136
|
-
});
|
|
137
|
-
it("send() delivers message to other agent", async () => {
|
|
138
|
-
const regA = await registerAgent("send-a");
|
|
139
|
-
const regB = await registerAgent("send-b");
|
|
140
|
-
await regB.client.connect(regB.apiKey);
|
|
141
|
-
const service = await connectService(regA.apiKey);
|
|
142
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
143
|
-
type: "dm",
|
|
144
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
145
|
-
}));
|
|
146
|
-
await service.send(conv.conversation.id, "Hello from service");
|
|
147
|
-
const event = await regB.client.waitForEvent("messages/received", 5000);
|
|
148
|
-
const msg = event.data
|
|
149
|
-
.message;
|
|
150
|
-
expect(msg.parts[0].text).toBe("Hello from service");
|
|
151
|
-
service.close();
|
|
152
|
-
regA.client.close();
|
|
153
|
-
regB.client.close();
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
// ─── Group 2: Cross-Conversation Context ─────────────────────────────────────
|
|
157
|
-
describe("Cross-Conversation Context", () => {
|
|
158
|
-
it("returns null with only one conversation active", async () => {
|
|
159
|
-
const regA = await registerAgent("ctx-a");
|
|
160
|
-
const regB = await registerAgent("ctx-b");
|
|
161
|
-
await regB.client.connect(regB.apiKey);
|
|
162
|
-
const service = await connectService(regA.apiKey);
|
|
163
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
164
|
-
type: "dm",
|
|
165
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
166
|
-
}));
|
|
167
|
-
await sendAndSettle(regB.client, conv.conversation.id, "Hello");
|
|
168
|
-
// Only one conversation — no "other" conversations to report
|
|
169
|
-
const ctx = service.getContext(conv.conversation.id);
|
|
170
|
-
expect(ctx).toBeNull();
|
|
171
|
-
service.close();
|
|
172
|
-
regA.client.close();
|
|
173
|
-
regB.client.close();
|
|
174
|
-
});
|
|
175
|
-
it("returns null when other conversations have no messages", async () => {
|
|
176
|
-
const regA = await registerAgent("null-a");
|
|
177
|
-
const regB = await registerAgent("null-b");
|
|
178
|
-
const regC = await registerAgent("null-c");
|
|
179
|
-
await regB.client.connect(regB.apiKey);
|
|
180
|
-
await regC.client.connect(regC.apiKey);
|
|
181
|
-
const service = await connectService(regA.apiKey);
|
|
182
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
183
|
-
type: "dm",
|
|
184
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
185
|
-
}));
|
|
186
|
-
// Create conv with C but don't send any messages in it
|
|
187
|
-
await service.sendRpc("conversations/create", {
|
|
188
|
-
type: "dm",
|
|
189
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
190
|
-
});
|
|
191
|
-
await sendAndSettle(regB.client, convB.conversation.id, "msg in B");
|
|
192
|
-
const ctx = service.getContext(convB.conversation.id);
|
|
193
|
-
// Conv C has no messages → no context
|
|
194
|
-
expect(ctx).toBeNull();
|
|
195
|
-
service.close();
|
|
196
|
-
regA.client.close();
|
|
197
|
-
regB.client.close();
|
|
198
|
-
regC.client.close();
|
|
199
|
-
});
|
|
200
|
-
it("returns system-reminder with new messages from other conversation", async () => {
|
|
201
|
-
const regA = await registerAgent("xc-a");
|
|
202
|
-
const regB = await registerAgent("xc-b");
|
|
203
|
-
const regC = await registerAgent("xc-c");
|
|
204
|
-
await regB.client.connect(regB.apiKey);
|
|
205
|
-
await regC.client.connect(regC.apiKey);
|
|
206
|
-
const service = await connectService(regA.apiKey);
|
|
207
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
208
|
-
type: "dm",
|
|
209
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
210
|
-
}));
|
|
211
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
212
|
-
type: "dm",
|
|
213
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
214
|
-
}));
|
|
215
|
-
// Send message in conv C
|
|
216
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Hello from C");
|
|
217
|
-
// Get context from conv B's perspective — should see conv C's message
|
|
218
|
-
const ctx = service.getContext(convB.conversation.id);
|
|
219
|
-
expect(ctx).not.toBeNull();
|
|
220
|
-
expect(ctx).toContain("<system-reminder>");
|
|
221
|
-
expect(ctx).toContain("</system-reminder>");
|
|
222
|
-
expect(ctx).toContain("Hello from C");
|
|
223
|
-
service.close();
|
|
224
|
-
regA.client.close();
|
|
225
|
-
regB.client.close();
|
|
226
|
-
regC.client.close();
|
|
227
|
-
});
|
|
228
|
-
it("format matches @name (Xm ago): (N new) pattern", async () => {
|
|
229
|
-
const regA = await registerAgent("fmt-a");
|
|
230
|
-
const regB = await registerAgent("fmt-b");
|
|
231
|
-
const regC = await registerAgent("fmt-c");
|
|
232
|
-
await regB.client.connect(regB.apiKey);
|
|
233
|
-
await regC.client.connect(regC.apiKey);
|
|
234
|
-
const service = await connectService(regA.apiKey);
|
|
235
|
-
// Resolve C's name so it appears in context
|
|
236
|
-
await service.resolveAgentName(regC.agentId);
|
|
237
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
238
|
-
type: "dm",
|
|
239
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
240
|
-
}));
|
|
241
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
242
|
-
type: "dm",
|
|
243
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
244
|
-
}));
|
|
245
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Test message");
|
|
246
|
-
const ctx = service.getContext(convB.conversation.id);
|
|
247
|
-
expect(ctx).toMatch(/@fmt-c \(\d+m ago\): \(1 new\) "Test message"/);
|
|
248
|
-
service.close();
|
|
249
|
-
regA.client.close();
|
|
250
|
-
regB.client.close();
|
|
251
|
-
regC.client.close();
|
|
252
|
-
});
|
|
253
|
-
it("truncates long messages at 120 chars", async () => {
|
|
254
|
-
const regA = await registerAgent("trunc-a");
|
|
255
|
-
const regB = await registerAgent("trunc-b");
|
|
256
|
-
const regC = await registerAgent("trunc-c");
|
|
257
|
-
await regB.client.connect(regB.apiKey);
|
|
258
|
-
await regC.client.connect(regC.apiKey);
|
|
259
|
-
const service = await connectService(regA.apiKey);
|
|
260
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
261
|
-
type: "dm",
|
|
262
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
263
|
-
}));
|
|
264
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
265
|
-
type: "dm",
|
|
266
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
267
|
-
}));
|
|
268
|
-
const longMsg = "A".repeat(500);
|
|
269
|
-
await sendAndSettle(regC.client, convC.conversation.id, longMsg);
|
|
270
|
-
const ctx = service.getContext(convB.conversation.id);
|
|
271
|
-
// The preview should be truncated — full 500-char message should not appear
|
|
272
|
-
expect(ctx).not.toContain("A".repeat(500));
|
|
273
|
-
expect(ctx.length).toBeLessThan(500);
|
|
274
|
-
service.close();
|
|
275
|
-
regA.client.close();
|
|
276
|
-
regB.client.close();
|
|
277
|
-
regC.client.close();
|
|
278
|
-
});
|
|
279
|
-
it("advances markers — second call returns null", async () => {
|
|
280
|
-
const regA = await registerAgent("mark-a");
|
|
281
|
-
const regB = await registerAgent("mark-b");
|
|
282
|
-
const regC = await registerAgent("mark-c");
|
|
283
|
-
await regB.client.connect(regB.apiKey);
|
|
284
|
-
await regC.client.connect(regC.apiKey);
|
|
285
|
-
const service = await connectService(regA.apiKey);
|
|
286
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
287
|
-
type: "dm",
|
|
288
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
289
|
-
}));
|
|
290
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
291
|
-
type: "dm",
|
|
292
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
293
|
-
}));
|
|
294
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Once");
|
|
295
|
-
const first = service.getContext(convB.conversation.id);
|
|
296
|
-
expect(first).not.toBeNull();
|
|
297
|
-
// Second call — no new messages since marker advanced
|
|
298
|
-
const second = service.getContext(convB.conversation.id);
|
|
299
|
-
expect(second).toBeNull();
|
|
300
|
-
service.close();
|
|
301
|
-
regA.client.close();
|
|
302
|
-
regB.client.close();
|
|
303
|
-
regC.client.close();
|
|
304
|
-
});
|
|
305
|
-
it("markers are per-viewing-conversation", async () => {
|
|
306
|
-
const regA = await registerAgent("perv-a");
|
|
307
|
-
const regB = await registerAgent("perv-b");
|
|
308
|
-
const regC = await registerAgent("perv-c");
|
|
309
|
-
await regB.client.connect(regB.apiKey);
|
|
310
|
-
await regC.client.connect(regC.apiKey);
|
|
311
|
-
const service = await connectService(regA.apiKey);
|
|
312
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
313
|
-
type: "dm",
|
|
314
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
315
|
-
}));
|
|
316
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
317
|
-
type: "dm",
|
|
318
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
319
|
-
}));
|
|
320
|
-
// Send message in conv C
|
|
321
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Shared update");
|
|
322
|
-
// Conv B views — sees the update, marker advances
|
|
323
|
-
const fromB = service.getContext(convB.conversation.id);
|
|
324
|
-
expect(fromB).not.toBeNull();
|
|
325
|
-
expect(fromB).toContain("Shared update");
|
|
326
|
-
// Conv B's marker advanced, so second call returns null
|
|
327
|
-
expect(service.getContext(convB.conversation.id)).toBeNull();
|
|
328
|
-
// Send message in conv B
|
|
329
|
-
await sendAndSettle(regB.client, convB.conversation.id, "B update");
|
|
330
|
-
// Conv C views — should see BOTH conv C's message hasn't been "seen" from C's perspective
|
|
331
|
-
// AND conv B's new message
|
|
332
|
-
const fromC = service.getContext(convC.conversation.id);
|
|
333
|
-
expect(fromC).not.toBeNull();
|
|
334
|
-
expect(fromC).toContain("B update");
|
|
335
|
-
service.close();
|
|
336
|
-
regA.client.close();
|
|
337
|
-
regB.client.close();
|
|
338
|
-
regC.client.close();
|
|
339
|
-
});
|
|
340
|
-
it("multiple other conversations appear in context", async () => {
|
|
341
|
-
const regA = await registerAgent("multi-a");
|
|
342
|
-
const regB = await registerAgent("multi-b");
|
|
343
|
-
const regC = await registerAgent("multi-c");
|
|
344
|
-
const regD = await registerAgent("multi-d");
|
|
345
|
-
await regB.client.connect(regB.apiKey);
|
|
346
|
-
await regC.client.connect(regC.apiKey);
|
|
347
|
-
await regD.client.connect(regD.apiKey);
|
|
348
|
-
const service = await connectService(regA.apiKey);
|
|
349
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
350
|
-
type: "dm",
|
|
351
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
352
|
-
}));
|
|
353
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
354
|
-
type: "dm",
|
|
355
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
356
|
-
}));
|
|
357
|
-
const convD = (await service.sendRpc("conversations/create", {
|
|
358
|
-
type: "dm",
|
|
359
|
-
participants: [{ type: "agent", id: regD.agentId }],
|
|
360
|
-
}));
|
|
361
|
-
await sendAndSettle(regC.client, convC.conversation.id, "From C");
|
|
362
|
-
await sendAndSettle(regD.client, convD.conversation.id, "From D");
|
|
363
|
-
const ctx = service.getContext(convB.conversation.id);
|
|
364
|
-
expect(ctx).toContain("From C");
|
|
365
|
-
expect(ctx).toContain("From D");
|
|
366
|
-
service.close();
|
|
367
|
-
regA.client.close();
|
|
368
|
-
regB.client.close();
|
|
369
|
-
regC.client.close();
|
|
370
|
-
regD.client.close();
|
|
371
|
-
});
|
|
372
|
-
it("maxConversations limits output", async () => {
|
|
373
|
-
const regA = await registerAgent("lim-a");
|
|
374
|
-
const agents = await Promise.all(["lim-b", "lim-c", "lim-d", "lim-e"].map((n) => registerAgent(n)));
|
|
375
|
-
for (const a of agents)
|
|
376
|
-
await a.client.connect(a.apiKey);
|
|
377
|
-
const service = await connectService(regA.apiKey);
|
|
378
|
-
const convs = [];
|
|
379
|
-
for (const a of agents) {
|
|
380
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
381
|
-
type: "dm",
|
|
382
|
-
participants: [{ type: "agent", id: a.agentId }],
|
|
383
|
-
}));
|
|
384
|
-
convs.push(conv.conversation.id);
|
|
385
|
-
}
|
|
386
|
-
// Send messages in all 4 other conversations
|
|
387
|
-
for (let i = 0; i < agents.length; i++) {
|
|
388
|
-
await sendAndSettle(agents[i].client, convs[i], `Msg from ${i}`);
|
|
389
|
-
}
|
|
390
|
-
// Limit to 2 conversations
|
|
391
|
-
const ctx = service.getContext(convs[0], {
|
|
392
|
-
type: "cross-conversation",
|
|
393
|
-
maxConversations: 2,
|
|
394
|
-
});
|
|
395
|
-
const lines = ctx.split("\n").filter((l) => l.startsWith("@"));
|
|
396
|
-
expect(lines.length).toBe(2);
|
|
397
|
-
service.close();
|
|
398
|
-
regA.client.close();
|
|
399
|
-
for (const a of agents)
|
|
400
|
-
a.client.close();
|
|
401
|
-
});
|
|
402
|
-
it("excludes current conversation's messages", async () => {
|
|
403
|
-
const regA = await registerAgent("excl-a");
|
|
404
|
-
const regB = await registerAgent("excl-b");
|
|
405
|
-
await regB.client.connect(regB.apiKey);
|
|
406
|
-
const service = await connectService(regA.apiKey);
|
|
407
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
408
|
-
type: "dm",
|
|
409
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
410
|
-
}));
|
|
411
|
-
await sendAndSettle(regB.client, conv.conversation.id, "Same conv msg");
|
|
412
|
-
// getContext for the same conversation — should NOT include its own messages
|
|
413
|
-
const ctx = service.getContext(conv.conversation.id);
|
|
414
|
-
expect(ctx).toBeNull();
|
|
415
|
-
service.close();
|
|
416
|
-
regA.client.close();
|
|
417
|
-
regB.client.close();
|
|
418
|
-
});
|
|
419
|
-
it("shows resolved agent name, not UUID", async () => {
|
|
420
|
-
const regA = await registerAgent("name-res-a");
|
|
421
|
-
const regB = await registerAgent("name-res-b");
|
|
422
|
-
const regC = await registerAgent("name-res-c");
|
|
423
|
-
await regB.client.connect(regB.apiKey);
|
|
424
|
-
await regC.client.connect(regC.apiKey);
|
|
425
|
-
const service = await connectService(regA.apiKey);
|
|
426
|
-
await service.resolveAgentName(regC.agentId);
|
|
427
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
428
|
-
type: "dm",
|
|
429
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
430
|
-
}));
|
|
431
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
432
|
-
type: "dm",
|
|
433
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
434
|
-
}));
|
|
435
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Named msg");
|
|
436
|
-
const ctx = service.getContext(convB.conversation.id);
|
|
437
|
-
expect(ctx).toContain("@name-res-c");
|
|
438
|
-
expect(ctx).not.toContain(regC.agentId);
|
|
439
|
-
service.close();
|
|
440
|
-
regA.client.close();
|
|
441
|
-
regB.client.close();
|
|
442
|
-
regC.client.close();
|
|
443
|
-
});
|
|
444
|
-
it("new message between calls produces new context", async () => {
|
|
445
|
-
const regA = await registerAgent("new-a");
|
|
446
|
-
const regB = await registerAgent("new-b");
|
|
447
|
-
const regC = await registerAgent("new-c");
|
|
448
|
-
await regB.client.connect(regB.apiKey);
|
|
449
|
-
await regC.client.connect(regC.apiKey);
|
|
450
|
-
const service = await connectService(regA.apiKey);
|
|
451
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
452
|
-
type: "dm",
|
|
453
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
454
|
-
}));
|
|
455
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
456
|
-
type: "dm",
|
|
457
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
458
|
-
}));
|
|
459
|
-
await sendAndSettle(regC.client, convC.conversation.id, "First");
|
|
460
|
-
const first = service.getContext(convB.conversation.id);
|
|
461
|
-
expect(first).toContain("First");
|
|
462
|
-
// Marker advanced — second call returns null
|
|
463
|
-
expect(service.getContext(convB.conversation.id)).toBeNull();
|
|
464
|
-
// New message arrives
|
|
465
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Second");
|
|
466
|
-
const third = service.getContext(convB.conversation.id);
|
|
467
|
-
expect(third).not.toBeNull();
|
|
468
|
-
expect(third).toContain("Second");
|
|
469
|
-
service.close();
|
|
470
|
-
regA.client.close();
|
|
471
|
-
regB.client.close();
|
|
472
|
-
regC.client.close();
|
|
473
|
-
});
|
|
474
|
-
it("buffer stores all messages without eviction", async () => {
|
|
475
|
-
const regA = await registerAgent("ring-a");
|
|
476
|
-
const regB = await registerAgent("ring-b");
|
|
477
|
-
const regC = await registerAgent("ring-c");
|
|
478
|
-
await regB.client.connect(regB.apiKey);
|
|
479
|
-
await regC.client.connect(regC.apiKey);
|
|
480
|
-
const service = await connectService(regA.apiKey);
|
|
481
|
-
await service.sendRpc("conversations/create", {
|
|
482
|
-
type: "dm",
|
|
483
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
484
|
-
});
|
|
485
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
486
|
-
type: "dm",
|
|
487
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
488
|
-
}));
|
|
489
|
-
for (let i = 0; i < 25; i++) {
|
|
490
|
-
await regC.client.rpc("messages/send", {
|
|
491
|
-
conversationId: convC.conversation.id,
|
|
492
|
-
parts: [{ type: "text", text: `msg-${i}` }],
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
496
|
-
const history = service.getHistory(convC.conversation.id);
|
|
497
|
-
expect(history.length).toBe(25);
|
|
498
|
-
const texts = history.map((m) => m.parts
|
|
499
|
-
.filter((p) => p.type === "text")
|
|
500
|
-
.map((p) => p.text)
|
|
501
|
-
.join(""));
|
|
502
|
-
expect(texts).toContain("msg-0");
|
|
503
|
-
expect(texts).toContain("msg-24");
|
|
504
|
-
service.close();
|
|
505
|
-
regA.client.close();
|
|
506
|
-
regB.client.close();
|
|
507
|
-
regC.client.close();
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
// ─── Group 2b: peekFullMessages ──────────────────────────────────────────────
|
|
511
|
-
describe("peekFullMessages", () => {
|
|
512
|
-
it("returns full messages from other conversations sorted by timestamp", async () => {
|
|
513
|
-
const regA = await registerAgent("pfm-a");
|
|
514
|
-
const regB = await registerAgent("pfm-b");
|
|
515
|
-
const regC = await registerAgent("pfm-c");
|
|
516
|
-
await regB.client.connect(regB.apiKey);
|
|
517
|
-
await regC.client.connect(regC.apiKey);
|
|
518
|
-
const service = await connectService(regA.apiKey);
|
|
519
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
520
|
-
type: "dm",
|
|
521
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
522
|
-
}));
|
|
523
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
524
|
-
type: "dm",
|
|
525
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
526
|
-
}));
|
|
527
|
-
await sendAndSettle(regC.client, convC.conversation.id, "from C");
|
|
528
|
-
await sendAndSettle(regB.client, convB.conversation.id, "from B");
|
|
529
|
-
const { messages } = service.peekFullMessages(convB.conversation.id);
|
|
530
|
-
expect(messages.length).toBeGreaterThanOrEqual(1);
|
|
531
|
-
// convC message should appear (it's a different conversation)
|
|
532
|
-
const texts = messages.map((m) => m.text);
|
|
533
|
-
expect(texts).toContain("from C");
|
|
534
|
-
// Messages sorted chronologically — "from C" sent before "from B"
|
|
535
|
-
const cIdx = texts.indexOf("from C");
|
|
536
|
-
expect(cIdx).toBeGreaterThanOrEqual(0);
|
|
537
|
-
service.close();
|
|
538
|
-
regA.client.close();
|
|
539
|
-
regB.client.close();
|
|
540
|
-
regC.client.close();
|
|
541
|
-
});
|
|
542
|
-
it("returns all messages without artificial caps", async () => {
|
|
543
|
-
const regA = await registerAgent("nocap-a");
|
|
544
|
-
const agents = [];
|
|
545
|
-
for (let i = 0; i < 7; i++) {
|
|
546
|
-
const reg = await registerAgent(`nocap-b${i}`);
|
|
547
|
-
await reg.client.connect(reg.apiKey);
|
|
548
|
-
agents.push(reg);
|
|
549
|
-
}
|
|
550
|
-
const service = await connectService(regA.apiKey);
|
|
551
|
-
const convIds = [];
|
|
552
|
-
for (const agent of agents) {
|
|
553
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
554
|
-
type: "dm",
|
|
555
|
-
participants: [{ type: "agent", id: agent.agentId }],
|
|
556
|
-
}));
|
|
557
|
-
convIds.push(conv.conversation.id);
|
|
558
|
-
await sendAndSettle(agent.client, conv.conversation.id, `hi from ${agent.agentId.slice(0, 8)}`);
|
|
559
|
-
}
|
|
560
|
-
// Peek from the perspective of conv 0 — should see messages from all 6 other convs
|
|
561
|
-
const { messages } = service.peekFullMessages(convIds[0]);
|
|
562
|
-
expect(messages.length).toBeGreaterThanOrEqual(6);
|
|
563
|
-
service.close();
|
|
564
|
-
regA.client.close();
|
|
565
|
-
for (const a of agents)
|
|
566
|
-
a.client.close();
|
|
567
|
-
});
|
|
568
|
-
it("commit advances markers — second peek returns only new messages", async () => {
|
|
569
|
-
const regA = await registerAgent("commit-a");
|
|
570
|
-
const regB = await registerAgent("commit-b");
|
|
571
|
-
const regC = await registerAgent("commit-c");
|
|
572
|
-
await regB.client.connect(regB.apiKey);
|
|
573
|
-
await regC.client.connect(regC.apiKey);
|
|
574
|
-
const service = await connectService(regA.apiKey);
|
|
575
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
576
|
-
type: "dm",
|
|
577
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
578
|
-
}));
|
|
579
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
580
|
-
type: "dm",
|
|
581
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
582
|
-
}));
|
|
583
|
-
await sendAndSettle(regC.client, convC.conversation.id, "old msg");
|
|
584
|
-
const first = service.peekFullMessages(convB.conversation.id);
|
|
585
|
-
expect(first.messages.length).toBeGreaterThanOrEqual(1);
|
|
586
|
-
first.commit();
|
|
587
|
-
// Peek again — old message should be gone
|
|
588
|
-
const second = service.peekFullMessages(convB.conversation.id);
|
|
589
|
-
expect(second.messages.length).toBe(0);
|
|
590
|
-
// Send a new message — should appear
|
|
591
|
-
await sendAndSettle(regC.client, convC.conversation.id, "new msg");
|
|
592
|
-
const third = service.peekFullMessages(convB.conversation.id);
|
|
593
|
-
expect(third.messages.length).toBeGreaterThanOrEqual(1);
|
|
594
|
-
expect(third.messages.map((m) => m.text)).toContain("new msg");
|
|
595
|
-
service.close();
|
|
596
|
-
regA.client.close();
|
|
597
|
-
regB.client.close();
|
|
598
|
-
regC.client.close();
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
// ─── Group 3: History with Session Key ───────────────────────────────────────
|
|
602
|
-
describe("History with session key", () => {
|
|
603
|
-
it("messages/list returns both own and other agent messages", async () => {
|
|
604
|
-
const regA = await registerAgent("hist-a");
|
|
605
|
-
const regB = await registerAgent("hist-b");
|
|
606
|
-
await regB.client.connect(regB.apiKey);
|
|
607
|
-
const service = await connectService(regA.apiKey);
|
|
608
|
-
// Create DM between A and B
|
|
609
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
610
|
-
type: "dm",
|
|
611
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
612
|
-
}));
|
|
613
|
-
// A sends a message
|
|
614
|
-
await service.send(conv.conversation.id, "Hello from A");
|
|
615
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
616
|
-
// B sends a message
|
|
617
|
-
await sendAndSettle(regB.client, conv.conversation.id, "Hello from B");
|
|
618
|
-
// A sends another message
|
|
619
|
-
await service.send(conv.conversation.id, "Follow up from A");
|
|
620
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
621
|
-
// Fetch history via RPC (same as CLI moltzap history would do)
|
|
622
|
-
const result = (await service.sendRpc("messages/list", {
|
|
623
|
-
conversationId: conv.conversation.id,
|
|
624
|
-
limit: 10,
|
|
625
|
-
}));
|
|
626
|
-
// Should contain messages from BOTH agents
|
|
627
|
-
expect(result.messages.length).toBeGreaterThanOrEqual(3);
|
|
628
|
-
const senderIds = result.messages.map((m) => m.sender.id);
|
|
629
|
-
expect(senderIds).toContain(regA.agentId); // own messages
|
|
630
|
-
expect(senderIds).toContain(regB.agentId); // other's messages
|
|
631
|
-
// Verify own messages are identifiable via ownAgentId
|
|
632
|
-
const ownMessages = result.messages.filter((m) => m.sender.id === service.ownAgentId);
|
|
633
|
-
expect(ownMessages.length).toBeGreaterThanOrEqual(2);
|
|
634
|
-
const otherMessages = result.messages.filter((m) => m.sender.id === regB.agentId);
|
|
635
|
-
expect(otherMessages.length).toBeGreaterThanOrEqual(1);
|
|
636
|
-
service.close();
|
|
637
|
-
regA.client.close();
|
|
638
|
-
regB.client.close();
|
|
639
|
-
});
|
|
640
|
-
it("group conversation history shows all participants", async () => {
|
|
641
|
-
const regA = await registerAgent("grp-a");
|
|
642
|
-
const regB = await registerAgent("grp-b");
|
|
643
|
-
const regC = await registerAgent("grp-c");
|
|
644
|
-
await regB.client.connect(regB.apiKey);
|
|
645
|
-
await regC.client.connect(regC.apiKey);
|
|
646
|
-
const service = await connectService(regA.apiKey);
|
|
647
|
-
// Create group
|
|
648
|
-
const conv = (await service.sendRpc("conversations/create", {
|
|
649
|
-
type: "group",
|
|
650
|
-
name: "Test Group",
|
|
651
|
-
participants: [
|
|
652
|
-
{ type: "agent", id: regB.agentId },
|
|
653
|
-
{ type: "agent", id: regC.agentId },
|
|
654
|
-
],
|
|
655
|
-
}));
|
|
656
|
-
// Each agent sends a message
|
|
657
|
-
await service.send(conv.conversation.id, "Agent A here");
|
|
658
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
659
|
-
await sendAndSettle(regB.client, conv.conversation.id, "Agent B here");
|
|
660
|
-
await sendAndSettle(regC.client, conv.conversation.id, "Agent C here");
|
|
661
|
-
// Fetch history
|
|
662
|
-
const result = (await service.sendRpc("messages/list", {
|
|
663
|
-
conversationId: conv.conversation.id,
|
|
664
|
-
limit: 10,
|
|
665
|
-
}));
|
|
666
|
-
// All 3 agents should appear
|
|
667
|
-
const senderIds = new Set(result.messages.map((m) => m.sender.id));
|
|
668
|
-
expect(senderIds.size).toBe(3);
|
|
669
|
-
expect(senderIds).toContain(regA.agentId);
|
|
670
|
-
expect(senderIds).toContain(regB.agentId);
|
|
671
|
-
expect(senderIds).toContain(regC.agentId);
|
|
672
|
-
service.close();
|
|
673
|
-
regA.client.close();
|
|
674
|
-
regB.client.close();
|
|
675
|
-
regC.client.close();
|
|
676
|
-
});
|
|
677
|
-
});
|
|
678
|
-
// ─── Group 4: Socket Server ──────────────────────────────────────────────────
|
|
679
|
-
describe("Socket Server", () => {
|
|
680
|
-
let socketRequest;
|
|
681
|
-
beforeAll(async () => {
|
|
682
|
-
const mod = await import("../cli/socket-client.js");
|
|
683
|
-
socketRequest = mod.request;
|
|
684
|
-
});
|
|
685
|
-
it("ping responds with agentId", async () => {
|
|
686
|
-
const reg = await registerAgent("sock-ping");
|
|
687
|
-
const service = await connectService(reg.apiKey);
|
|
688
|
-
service.startSocketServer();
|
|
689
|
-
try {
|
|
690
|
-
const result = (await socketRequest("ping"));
|
|
691
|
-
expect(result.ok).toBe(true);
|
|
692
|
-
expect(result.agentId).toBe(reg.agentId);
|
|
693
|
-
}
|
|
694
|
-
finally {
|
|
695
|
-
service.close();
|
|
696
|
-
reg.client.close();
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
it("status returns connection info", async () => {
|
|
700
|
-
const reg = await registerAgent("sock-status");
|
|
701
|
-
const service = await connectService(reg.apiKey);
|
|
702
|
-
service.startSocketServer();
|
|
703
|
-
try {
|
|
704
|
-
const result = (await socketRequest("status"));
|
|
705
|
-
expect(result.agentId).toBe(reg.agentId);
|
|
706
|
-
expect(result.connected).toBe(true);
|
|
707
|
-
}
|
|
708
|
-
finally {
|
|
709
|
-
service.close();
|
|
710
|
-
reg.client.close();
|
|
711
|
-
}
|
|
712
|
-
});
|
|
713
|
-
it("passthrough RPC works via socket", async () => {
|
|
714
|
-
const regA = await registerAgent("sock-rpc-a");
|
|
715
|
-
const regB = await registerAgent("sock-rpc-b");
|
|
716
|
-
await regB.client.connect(regB.apiKey);
|
|
717
|
-
const service = await connectService(regA.apiKey);
|
|
718
|
-
service.startSocketServer();
|
|
719
|
-
try {
|
|
720
|
-
const conv = (await socketRequest("conversations/create", {
|
|
721
|
-
type: "dm",
|
|
722
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
723
|
-
}));
|
|
724
|
-
expect(conv.conversation.id).toBeDefined();
|
|
725
|
-
const msg = (await socketRequest("messages/send", {
|
|
726
|
-
conversationId: conv.conversation.id,
|
|
727
|
-
parts: [{ type: "text", text: "via socket" }],
|
|
728
|
-
}));
|
|
729
|
-
expect(msg.message.id).toBeDefined();
|
|
730
|
-
}
|
|
731
|
-
finally {
|
|
732
|
-
service.close();
|
|
733
|
-
regA.client.close();
|
|
734
|
-
regB.client.close();
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
it("history via socket returns messages with isOwn labels", async () => {
|
|
738
|
-
const regA = await registerAgent("sock-hist-a");
|
|
739
|
-
const regB = await registerAgent("sock-hist-b");
|
|
740
|
-
await regB.client.connect(regB.apiKey);
|
|
741
|
-
const service = await connectService(regA.apiKey);
|
|
742
|
-
service.startSocketServer();
|
|
743
|
-
try {
|
|
744
|
-
const conv = (await socketRequest("conversations/create", {
|
|
745
|
-
type: "dm",
|
|
746
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
747
|
-
}));
|
|
748
|
-
await socketRequest("messages/send", {
|
|
749
|
-
conversationId: conv.conversation.id,
|
|
750
|
-
parts: [{ type: "text", text: "Hello from A" }],
|
|
751
|
-
});
|
|
752
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
753
|
-
await sendAndSettle(regB.client, conv.conversation.id, "Hello from B");
|
|
754
|
-
const result = (await socketRequest("history", {
|
|
755
|
-
conversationId: conv.conversation.id,
|
|
756
|
-
limit: 10,
|
|
757
|
-
}));
|
|
758
|
-
expect(result.messages.length).toBeGreaterThanOrEqual(2);
|
|
759
|
-
const ownMsgs = result.messages.filter((m) => m.isOwn);
|
|
760
|
-
expect(ownMsgs.length).toBeGreaterThanOrEqual(1);
|
|
761
|
-
expect(ownMsgs[0].senderName).toBe("you");
|
|
762
|
-
const otherMsgs = result.messages.filter((m) => !m.isOwn);
|
|
763
|
-
expect(otherMsgs.length).toBeGreaterThanOrEqual(1);
|
|
764
|
-
expect(otherMsgs[0].senderName).toBe("sock-hist-b");
|
|
765
|
-
}
|
|
766
|
-
finally {
|
|
767
|
-
service.close();
|
|
768
|
-
regA.client.close();
|
|
769
|
-
regB.client.close();
|
|
770
|
-
}
|
|
771
|
-
});
|
|
772
|
-
it("messages stay *NEW* after getContext notification until history is read", async () => {
|
|
773
|
-
const regA = await registerAgent("wm-a");
|
|
774
|
-
const regB = await registerAgent("wm-b");
|
|
775
|
-
const regC = await registerAgent("wm-c");
|
|
776
|
-
await regB.client.connect(regB.apiKey);
|
|
777
|
-
await regC.client.connect(regC.apiKey);
|
|
778
|
-
const service = await connectService(regA.apiKey);
|
|
779
|
-
service.startSocketServer();
|
|
780
|
-
try {
|
|
781
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
782
|
-
type: "dm",
|
|
783
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
784
|
-
}));
|
|
785
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
786
|
-
type: "dm",
|
|
787
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
788
|
-
}));
|
|
789
|
-
// Seller sends message in conv C
|
|
790
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Price is $4000");
|
|
791
|
-
// System-reminder fires for conv B → advances lastNotified
|
|
792
|
-
const reminder = service.getContext(convB.conversation.id, {
|
|
793
|
-
type: "cross-conversation",
|
|
794
|
-
});
|
|
795
|
-
expect(reminder).toContain("(1 new)");
|
|
796
|
-
// System-reminder won't repeat (lastNotified advanced)
|
|
797
|
-
const reminder2 = service.getContext(convB.conversation.id, {
|
|
798
|
-
type: "cross-conversation",
|
|
799
|
-
});
|
|
800
|
-
expect(reminder2).toBeNull();
|
|
801
|
-
// BUT history via socket still shows *NEW* (lastRead not advanced yet)
|
|
802
|
-
const hist1 = (await socketRequest("history", {
|
|
803
|
-
conversationId: convC.conversation.id,
|
|
804
|
-
sessionKey: convB.conversation.id,
|
|
805
|
-
limit: 10,
|
|
806
|
-
}));
|
|
807
|
-
expect(hist1.newCount).toBe(1);
|
|
808
|
-
expect(hist1.messages[0].isNew).toBe(true);
|
|
809
|
-
expect(hist1.messages[0].text).toBe("Price is $4000");
|
|
810
|
-
// After reading, lastRead advances → second fetch shows 0 new
|
|
811
|
-
const hist2 = (await socketRequest("history", {
|
|
812
|
-
conversationId: convC.conversation.id,
|
|
813
|
-
sessionKey: convB.conversation.id,
|
|
814
|
-
limit: 10,
|
|
815
|
-
}));
|
|
816
|
-
expect(hist2.newCount).toBe(0);
|
|
817
|
-
}
|
|
818
|
-
finally {
|
|
819
|
-
service.close();
|
|
820
|
-
regA.client.close();
|
|
821
|
-
regB.client.close();
|
|
822
|
-
regC.client.close();
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
it("new messages after history read are marked *NEW*", async () => {
|
|
826
|
-
const regA = await registerAgent("wm2-a");
|
|
827
|
-
const regB = await registerAgent("wm2-b");
|
|
828
|
-
const regC = await registerAgent("wm2-c");
|
|
829
|
-
await regB.client.connect(regB.apiKey);
|
|
830
|
-
await regC.client.connect(regC.apiKey);
|
|
831
|
-
const service = await connectService(regA.apiKey);
|
|
832
|
-
service.startSocketServer();
|
|
833
|
-
try {
|
|
834
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
835
|
-
type: "dm",
|
|
836
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
837
|
-
}));
|
|
838
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
839
|
-
type: "dm",
|
|
840
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
841
|
-
}));
|
|
842
|
-
// First message
|
|
843
|
-
await sendAndSettle(regC.client, convC.conversation.id, "First");
|
|
844
|
-
service.getContext(convB.conversation.id, { type: "cross-conversation" });
|
|
845
|
-
// Read history → advances lastRead
|
|
846
|
-
const hist1 = (await socketRequest("history", {
|
|
847
|
-
conversationId: convC.conversation.id,
|
|
848
|
-
sessionKey: convB.conversation.id,
|
|
849
|
-
limit: 10,
|
|
850
|
-
}));
|
|
851
|
-
expect(hist1.newCount).toBe(1); // first read: 1 new
|
|
852
|
-
// Second read → 0 new (already read)
|
|
853
|
-
const hist2 = (await socketRequest("history", {
|
|
854
|
-
conversationId: convC.conversation.id,
|
|
855
|
-
sessionKey: convB.conversation.id,
|
|
856
|
-
limit: 10,
|
|
857
|
-
}));
|
|
858
|
-
expect(hist2.newCount).toBe(0);
|
|
859
|
-
// New message arrives AFTER read
|
|
860
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Second");
|
|
861
|
-
// Third read → 1 new (the new message)
|
|
862
|
-
const hist3 = (await socketRequest("history", {
|
|
863
|
-
conversationId: convC.conversation.id,
|
|
864
|
-
sessionKey: convB.conversation.id,
|
|
865
|
-
limit: 10,
|
|
866
|
-
}));
|
|
867
|
-
expect(hist3.newCount).toBe(1);
|
|
868
|
-
const newMsgs = hist3.messages.filter((m) => m.isNew);
|
|
869
|
-
expect(newMsgs[0].text).toBe("Second");
|
|
870
|
-
}
|
|
871
|
-
finally {
|
|
872
|
-
service.close();
|
|
873
|
-
regA.client.close();
|
|
874
|
-
regB.client.close();
|
|
875
|
-
regC.client.close();
|
|
876
|
-
}
|
|
877
|
-
});
|
|
878
|
-
it("different sessions have independent read markers", async () => {
|
|
879
|
-
const regA = await registerAgent("wm3-a");
|
|
880
|
-
const regB = await registerAgent("wm3-b");
|
|
881
|
-
const regC = await registerAgent("wm3-c");
|
|
882
|
-
const regD = await registerAgent("wm3-d");
|
|
883
|
-
await regB.client.connect(regB.apiKey);
|
|
884
|
-
await regC.client.connect(regC.apiKey);
|
|
885
|
-
await regD.client.connect(regD.apiKey);
|
|
886
|
-
const service = await connectService(regA.apiKey);
|
|
887
|
-
service.startSocketServer();
|
|
888
|
-
try {
|
|
889
|
-
const convB = (await service.sendRpc("conversations/create", {
|
|
890
|
-
type: "dm",
|
|
891
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
892
|
-
}));
|
|
893
|
-
const convC = (await service.sendRpc("conversations/create", {
|
|
894
|
-
type: "dm",
|
|
895
|
-
participants: [{ type: "agent", id: regC.agentId }],
|
|
896
|
-
}));
|
|
897
|
-
const convD = (await service.sendRpc("conversations/create", {
|
|
898
|
-
type: "dm",
|
|
899
|
-
participants: [{ type: "agent", id: regD.agentId }],
|
|
900
|
-
}));
|
|
901
|
-
// Message in conv C
|
|
902
|
-
await sendAndSettle(regC.client, convC.conversation.id, "Shared update");
|
|
903
|
-
// Conv B reads history → advances lastRead for convB→convC
|
|
904
|
-
const histB = (await socketRequest("history", {
|
|
905
|
-
conversationId: convC.conversation.id,
|
|
906
|
-
sessionKey: convB.conversation.id,
|
|
907
|
-
limit: 10,
|
|
908
|
-
}));
|
|
909
|
-
expect(histB.newCount).toBe(1); // first read
|
|
910
|
-
// Conv B reads again → 0 new
|
|
911
|
-
const histB2 = (await socketRequest("history", {
|
|
912
|
-
conversationId: convC.conversation.id,
|
|
913
|
-
sessionKey: convB.conversation.id,
|
|
914
|
-
limit: 10,
|
|
915
|
-
}));
|
|
916
|
-
expect(histB2.newCount).toBe(0);
|
|
917
|
-
// Conv D reads same conversation → still 1 new (independent markers)
|
|
918
|
-
const histD = (await socketRequest("history", {
|
|
919
|
-
conversationId: convC.conversation.id,
|
|
920
|
-
sessionKey: convD.conversation.id,
|
|
921
|
-
limit: 10,
|
|
922
|
-
}));
|
|
923
|
-
expect(histD.newCount).toBe(1);
|
|
924
|
-
}
|
|
925
|
-
finally {
|
|
926
|
-
service.close();
|
|
927
|
-
regA.client.close();
|
|
928
|
-
regB.client.close();
|
|
929
|
-
regC.client.close();
|
|
930
|
-
regD.client.close();
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
it("socket request resolves without 10s hang (timer leak regression)", async () => {
|
|
934
|
-
const reg = await registerAgent("sock-timer");
|
|
935
|
-
const service = await connectService(reg.apiKey);
|
|
936
|
-
service.startSocketServer();
|
|
937
|
-
try {
|
|
938
|
-
const start = performance.now();
|
|
939
|
-
await socketRequest("ping");
|
|
940
|
-
const elapsed = performance.now() - start;
|
|
941
|
-
expect(elapsed).toBeLessThan(2000);
|
|
942
|
-
}
|
|
943
|
-
finally {
|
|
944
|
-
service.close();
|
|
945
|
-
reg.client.close();
|
|
946
|
-
}
|
|
947
|
-
});
|
|
948
|
-
it("two services use separate socket paths", async () => {
|
|
949
|
-
const regA = await registerAgent("sock-multi-a");
|
|
950
|
-
const regB = await registerAgent("sock-multi-b");
|
|
951
|
-
const serviceA = await connectService(regA.apiKey);
|
|
952
|
-
const serviceB = await connectService(regB.apiKey);
|
|
953
|
-
serviceA.startSocketServer();
|
|
954
|
-
serviceB.startSocketServer();
|
|
955
|
-
try {
|
|
956
|
-
expect(serviceA.socketPath).not.toBe(serviceB.socketPath);
|
|
957
|
-
// Both respond via their own socket path
|
|
958
|
-
const resultA = (await socketRequest("ping", undefined, serviceA.socketPath));
|
|
959
|
-
const resultB = (await socketRequest("ping", undefined, serviceB.socketPath));
|
|
960
|
-
expect(resultA.agentId).toBe(regA.agentId);
|
|
961
|
-
expect(resultB.agentId).toBe(regB.agentId);
|
|
962
|
-
}
|
|
963
|
-
finally {
|
|
964
|
-
serviceA.close();
|
|
965
|
-
serviceB.close();
|
|
966
|
-
regA.client.close();
|
|
967
|
-
regB.client.close();
|
|
968
|
-
}
|
|
969
|
-
});
|
|
970
|
-
it("lastRead advances monotonically and afterSeq enables pagination", async () => {
|
|
971
|
-
const regA = await registerAgent("sock-page-a");
|
|
972
|
-
const regB = await registerAgent("sock-page-b");
|
|
973
|
-
await regB.client.connect(regB.apiKey);
|
|
974
|
-
const service = await connectService(regA.apiKey);
|
|
975
|
-
service.startSocketServer();
|
|
976
|
-
try {
|
|
977
|
-
const conv = (await socketRequest("conversations/create", {
|
|
978
|
-
type: "dm",
|
|
979
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
980
|
-
}));
|
|
981
|
-
// Send 5 messages from B
|
|
982
|
-
for (let i = 0; i < 5; i++) {
|
|
983
|
-
await sendAndSettle(regB.client, conv.conversation.id, `paginate-msg-${i}`);
|
|
984
|
-
}
|
|
985
|
-
// Read with limit=2 — gets newest 2 messages, advances lastRead
|
|
986
|
-
const hist1 = (await socketRequest("history", {
|
|
987
|
-
conversationId: conv.conversation.id,
|
|
988
|
-
sessionKey: "page-test-session",
|
|
989
|
-
limit: 2,
|
|
990
|
-
}));
|
|
991
|
-
expect(hist1.messages.length).toBe(2);
|
|
992
|
-
// New message arrives after read
|
|
993
|
-
await sendAndSettle(regB.client, conv.conversation.id, "paginate-msg-new");
|
|
994
|
-
// Read again — only the new message should be marked new
|
|
995
|
-
const hist2 = (await socketRequest("history", {
|
|
996
|
-
conversationId: conv.conversation.id,
|
|
997
|
-
sessionKey: "page-test-session",
|
|
998
|
-
limit: 10,
|
|
999
|
-
}));
|
|
1000
|
-
expect(hist2.newCount).toBe(1);
|
|
1001
|
-
const newMsg = hist2.messages.find((m) => m.isNew);
|
|
1002
|
-
expect(newMsg?.text).toBe("paginate-msg-new");
|
|
1003
|
-
}
|
|
1004
|
-
finally {
|
|
1005
|
-
service.close();
|
|
1006
|
-
regA.client.close();
|
|
1007
|
-
regB.client.close();
|
|
1008
|
-
}
|
|
1009
|
-
});
|
|
1010
|
-
it("non-text message parts render as markers in socket history", async () => {
|
|
1011
|
-
const regA = await registerAgent("sock-attach-a");
|
|
1012
|
-
const regB = await registerAgent("sock-attach-b");
|
|
1013
|
-
await regB.client.connect(regB.apiKey);
|
|
1014
|
-
const service = await connectService(regA.apiKey);
|
|
1015
|
-
service.startSocketServer();
|
|
1016
|
-
try {
|
|
1017
|
-
const conv = (await socketRequest("conversations/create", {
|
|
1018
|
-
type: "dm",
|
|
1019
|
-
participants: [{ type: "agent", id: regB.agentId }],
|
|
1020
|
-
}));
|
|
1021
|
-
await regB.client.rpc("messages/send", {
|
|
1022
|
-
conversationId: conv.conversation.id,
|
|
1023
|
-
parts: [
|
|
1024
|
-
{ type: "text", text: "Check this out" },
|
|
1025
|
-
{ type: "image", url: "https://example.com/photo.jpg" },
|
|
1026
|
-
],
|
|
1027
|
-
});
|
|
1028
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
1029
|
-
const result = (await socketRequest("history", {
|
|
1030
|
-
conversationId: conv.conversation.id,
|
|
1031
|
-
limit: 10,
|
|
1032
|
-
}));
|
|
1033
|
-
const msg = result.messages.find((m) => m.text.includes("Check this out"));
|
|
1034
|
-
expect(msg).toBeDefined();
|
|
1035
|
-
expect(msg.text).toContain("[image]");
|
|
1036
|
-
}
|
|
1037
|
-
finally {
|
|
1038
|
-
service.close();
|
|
1039
|
-
regA.client.close();
|
|
1040
|
-
regB.client.close();
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
it("socketPath is stable after connect (cached at startSocketServer time)", async () => {
|
|
1044
|
-
const reg = await registerAgent("sock-stable");
|
|
1045
|
-
const service = await connectService(reg.apiKey);
|
|
1046
|
-
service.startSocketServer();
|
|
1047
|
-
const pathAtStart = service.socketPath;
|
|
1048
|
-
try {
|
|
1049
|
-
const result = (await socketRequest("ping", undefined, pathAtStart));
|
|
1050
|
-
expect(result.ok).toBe(true);
|
|
1051
|
-
}
|
|
1052
|
-
finally {
|
|
1053
|
-
service.close();
|
|
1054
|
-
reg.client.close();
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
it("unknown socket method rejects with error", async () => {
|
|
1058
|
-
const reg = await registerAgent("sock-unknown");
|
|
1059
|
-
const service = await connectService(reg.apiKey);
|
|
1060
|
-
service.startSocketServer();
|
|
1061
|
-
try {
|
|
1062
|
-
await expect(socketRequest("nonexistent/method", { foo: "bar" })).rejects.toThrow();
|
|
1063
|
-
}
|
|
1064
|
-
finally {
|
|
1065
|
-
service.close();
|
|
1066
|
-
reg.client.close();
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
it("history rejects when conversationId is missing or wrong type", async () => {
|
|
1070
|
-
const reg = await registerAgent("sock-validate");
|
|
1071
|
-
const service = await connectService(reg.apiKey);
|
|
1072
|
-
service.startSocketServer();
|
|
1073
|
-
try {
|
|
1074
|
-
await expect(socketRequest("history", {})).rejects.toThrow("conversationId is required");
|
|
1075
|
-
await expect(socketRequest("history", { conversationId: 123 })).rejects.toThrow("conversationId is required");
|
|
1076
|
-
await expect(socketRequest("history", {
|
|
1077
|
-
conversationId: "abc",
|
|
1078
|
-
limit: "not-a-number",
|
|
1079
|
-
})).rejects.toThrow("limit must be a number");
|
|
1080
|
-
}
|
|
1081
|
-
finally {
|
|
1082
|
-
service.close();
|
|
1083
|
-
reg.client.close();
|
|
1084
|
-
}
|
|
1085
|
-
});
|
|
1086
|
-
});
|
|
1087
|
-
//# sourceMappingURL=service.integration.test.js.map
|