@moltzap/client 2026.503.4 → 2026.504.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 +1 -4
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +0 -9
- package/dist/channel-core.js.map +1 -1
- package/dist/channel-core.test.js +90 -71
- package/dist/channel-core.test.js.map +1 -1
- package/dist/cli/commands/agents.js +2 -2
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/apps.d.ts +2 -24
- package/dist/cli/commands/apps.d.ts.map +1 -1
- package/dist/cli/commands/apps.js +22 -47
- package/dist/cli/commands/apps.js.map +1 -1
- package/dist/cli/commands/apps.test.js +59 -72
- package/dist/cli/commands/apps.test.js.map +1 -1
- package/dist/cli/commands/contacts.d.ts +1 -5
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +11 -26
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations-v2.test.js +30 -26
- package/dist/cli/commands/conversations-v2.test.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +48 -33
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +11 -8
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/messages.test.js +28 -30
- package/dist/cli/commands/messages.test.js.map +1 -1
- package/dist/cli/commands/presence.d.ts.map +1 -1
- package/dist/cli/commands/presence.js +5 -4
- package/dist/cli/commands/presence.js.map +1 -1
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +20 -11
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/send.test.js +11 -9
- package/dist/cli/commands/send.test.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/test-transport.d.ts +10 -0
- package/dist/cli/commands/test-transport.d.ts.map +1 -0
- package/dist/cli/commands/test-transport.js +29 -0
- package/dist/cli/commands/test-transport.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/socket-client.d.ts +9 -4
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +27 -12
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +3 -2
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +6 -6
- package/dist/cli/transport.js.map +1 -1
- package/dist/cli/transport.test.js +4 -4
- package/dist/cli/transport.test.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js +33 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partition-worker.test.js → app-callback-partition-worker.test.js} +6 -9
- package/dist/internal/__tests__/app-callback-partition-worker.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher-real-ws.test.js → app-callback-partitioned-dispatcher-real-ws.test.js} +69 -94
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher.test.js → app-callback-partitioned-dispatcher.test.js} +35 -69
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts +43 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.js +101 -0
- package/dist/internal/__tests__/app-callback-test-requests.js.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.d.ts → app-callback-dispatcher-errors.d.ts} +5 -21
- package/dist/internal/app-callback-dispatcher-errors.d.ts.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.js → app-callback-dispatcher-errors.js} +3 -13
- package/dist/internal/app-callback-dispatcher-errors.js.map +1 -0
- package/dist/internal/app-callback-partition-key.d.ts +68 -0
- package/dist/internal/app-callback-partition-key.d.ts.map +1 -0
- package/dist/internal/app-callback-partition-key.js +90 -0
- package/dist/internal/app-callback-partition-key.js.map +1 -0
- package/dist/internal/{s2c-partition-worker.d.ts → app-callback-partition-worker.d.ts} +5 -5
- package/dist/internal/app-callback-partition-worker.d.ts.map +1 -0
- package/dist/internal/{s2c-partition-worker.js → app-callback-partition-worker.js} +5 -5
- package/dist/internal/app-callback-partition-worker.js.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.d.ts → app-callback-partitioned-dispatcher.d.ts} +9 -10
- package/dist/internal/app-callback-partitioned-dispatcher.d.ts.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.js → app-callback-partitioned-dispatcher.js} +12 -19
- package/dist/internal/app-callback-partitioned-dispatcher.js.map +1 -0
- package/dist/runtime/errors.d.ts +2 -28
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +0 -9
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/errors.test.js +2 -2
- package/dist/runtime/errors.test.js.map +1 -1
- package/dist/runtime/frame.d.ts +17 -18
- package/dist/runtime/frame.d.ts.map +1 -1
- package/dist/runtime/frame.js +83 -31
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/frame.test.js +15 -11
- package/dist/runtime/frame.test.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/local-service-commands.d.ts +7 -0
- package/dist/runtime/local-service-commands.d.ts.map +1 -0
- package/dist/runtime/local-service-commands.js +6 -0
- package/dist/runtime/local-service-commands.js.map +1 -0
- package/dist/runtime/runtime.test.js +1 -2
- package/dist/runtime/runtime.test.js.map +1 -1
- package/dist/runtime/subscribers.d.ts +34 -29
- package/dist/runtime/subscribers.d.ts.map +1 -1
- package/dist/runtime/subscribers.js +19 -19
- package/dist/runtime/subscribers.js.map +1 -1
- package/dist/runtime/subscribers.test.js +50 -31
- package/dist/runtime/subscribers.test.js.map +1 -1
- package/dist/service.d.ts +24 -39
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +165 -161
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +111 -170
- package/dist/service.test.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +1 -2
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +28 -20
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/conformance-adapter.d.ts +2 -2
- package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
- package/dist/test-utils/conformance-adapter.js +33 -26
- package/dist/test-utils/conformance-adapter.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts +14 -39
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +33 -39
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/ids.d.ts +9 -0
- package/dist/test-utils/ids.d.ts.map +1 -0
- package/dist/test-utils/ids.js +52 -0
- package/dist/test-utils/ids.js.map +1 -0
- package/dist/test-utils/index.d.ts +10 -2
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +12 -5
- package/dist/test-utils/index.js.map +1 -1
- package/dist/ws-client.d.ts +58 -64
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +151 -133
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-client.test.js +157 -167
- package/dist/ws-client.test.js.map +1 -1
- package/package.json +3 -3
- package/dist/cli/commands/permissions.d.ts +0 -70
- package/dist/cli/commands/permissions.d.ts.map +0 -1
- package/dist/cli/commands/permissions.js +0 -100
- package/dist/cli/commands/permissions.js.map +0 -1
- package/dist/cli/commands/permissions.test.d.ts +0 -2
- package/dist/cli/commands/permissions.test.d.ts.map +0 -1
- package/dist/cli/commands/permissions.test.js +0 -127
- package/dist/cli/commands/permissions.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.js +0 -231
- package/dist/internal/__tests__/s2c-partition-key.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.js.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.d.ts.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.js.map +0 -1
- package/dist/internal/s2c-partition-key.d.ts +0 -79
- package/dist/internal/s2c-partition-key.d.ts.map +0 -1
- package/dist/internal/s2c-partition-key.js +0 -146
- package/dist/internal/s2c-partition-key.js.map +0 -1
- package/dist/internal/s2c-partition-worker.d.ts.map +0 -1
- package/dist/internal/s2c-partition-worker.js.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.d.ts.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.js.map +0 -1
package/dist/service.test.js
CHANGED
|
@@ -2,45 +2,52 @@ import * as path from "node:path";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { Effect, Either } from "effect";
|
|
5
|
-
import { AppsAuthorizeDispatch, ConversationsGet, ErrorCodes,
|
|
5
|
+
import { AppsAuthorizeDispatch, ConversationsGet, ErrorCodes, ConversationArchivedNotificationDefinition, ConversationCreatedNotificationDefinition, ConversationUnarchivedNotificationDefinition, MessageReceivedNotificationDefinition, MessagesSend, notificationFrame, } from "@moltzap/protocol";
|
|
6
6
|
import { sanitizeForSystemReminder } from "./service.js";
|
|
7
7
|
import { FakeMoltZapService } from "./test-utils/fake-service.js";
|
|
8
|
-
import {
|
|
8
|
+
import { buildMessage, testAgentId, testConversationId, testMessageId, } from "./test-utils/index.js";
|
|
9
|
+
import { AgentsLookupByName, ConversationsCreate } from "@moltzap/protocol";
|
|
9
10
|
/** Run a service Effect to a Promise for test assertions. */
|
|
10
11
|
const run = (e) => Effect.runPromise(e);
|
|
12
|
+
const AGENT_ALICE_ID = testAgentId("agent-alice-id");
|
|
13
|
+
const AGENT_SELF_ID = testAgentId("agent-self");
|
|
14
|
+
const AGENT_GM_ID = testAgentId("agent-gm");
|
|
15
|
+
const CONVERSATION_ALICE_ID = testConversationId("conv-alice");
|
|
16
|
+
const CONVERSATION_ARCHIVED_ID = testConversationId("conv-archived");
|
|
17
|
+
const DISPATCH_MESSAGE_ID = testMessageId("msg-dispatch");
|
|
11
18
|
describe("MoltZapService.sendToAgent", () => {
|
|
12
19
|
let service;
|
|
13
20
|
beforeEach(() => {
|
|
14
21
|
service = new FakeMoltZapService();
|
|
15
22
|
// `setResponse` is typed: the wire name must be a `RpcMethodName` literal
|
|
16
|
-
// and the value must match `
|
|
23
|
+
// and the value must match `RpcResult<M>`. Both guard against the
|
|
17
24
|
// contract-drift bug (A7) that motivated this fake.
|
|
18
|
-
service.setResponse(AgentsLookupByName
|
|
25
|
+
service.setResponse(AgentsLookupByName, {
|
|
19
26
|
agents: [
|
|
20
27
|
{
|
|
21
|
-
id:
|
|
28
|
+
id: AGENT_ALICE_ID,
|
|
22
29
|
name: "alice",
|
|
23
30
|
status: "active",
|
|
24
31
|
},
|
|
25
32
|
],
|
|
26
33
|
});
|
|
27
|
-
service.setResponse(ConversationsCreate
|
|
34
|
+
service.setResponse(ConversationsCreate, {
|
|
28
35
|
conversation: {
|
|
29
|
-
id:
|
|
36
|
+
id: CONVERSATION_ALICE_ID,
|
|
30
37
|
type: "dm",
|
|
31
|
-
createdBy:
|
|
38
|
+
createdBy: AGENT_SELF_ID,
|
|
32
39
|
createdAt: "2026-04-16T00:00:00Z",
|
|
33
40
|
updatedAt: "2026-04-16T00:00:00Z",
|
|
34
41
|
},
|
|
35
42
|
});
|
|
36
|
-
service.setResponse(MessagesSend
|
|
37
|
-
message: {
|
|
43
|
+
service.setResponse(MessagesSend, {
|
|
44
|
+
message: buildMessage({
|
|
38
45
|
id: "msg-1",
|
|
39
46
|
conversationId: "conv-alice",
|
|
40
47
|
senderId: "agent-self",
|
|
41
48
|
parts: [{ type: "text", text: "placeholder" }],
|
|
42
49
|
createdAt: "2026-04-16T00:00:00Z",
|
|
43
|
-
},
|
|
50
|
+
}),
|
|
44
51
|
});
|
|
45
52
|
});
|
|
46
53
|
it("resolves agent name, creates a DM, and sends the message on first call", async () => {
|
|
@@ -51,13 +58,13 @@ describe("MoltZapService.sendToAgent", () => {
|
|
|
51
58
|
method: ConversationsCreate.name,
|
|
52
59
|
params: {
|
|
53
60
|
type: "dm",
|
|
54
|
-
participants: [{ type: "agent", id:
|
|
61
|
+
participants: [{ type: "agent", id: AGENT_ALICE_ID }],
|
|
55
62
|
},
|
|
56
63
|
},
|
|
57
64
|
{
|
|
58
65
|
method: MessagesSend.name,
|
|
59
66
|
params: {
|
|
60
|
-
conversationId:
|
|
67
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
61
68
|
parts: [{ type: "text", text: "hello" }],
|
|
62
69
|
},
|
|
63
70
|
},
|
|
@@ -71,34 +78,37 @@ describe("MoltZapService.sendToAgent", () => {
|
|
|
71
78
|
{
|
|
72
79
|
method: MessagesSend.name,
|
|
73
80
|
params: {
|
|
74
|
-
conversationId:
|
|
81
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
75
82
|
parts: [{ type: "text", text: "second" }],
|
|
76
83
|
},
|
|
77
84
|
},
|
|
78
85
|
]);
|
|
79
86
|
});
|
|
80
87
|
it("forwards replyTo to messages/send as replyToId", async () => {
|
|
81
|
-
|
|
88
|
+
const replyToId = testMessageId("msg-123");
|
|
89
|
+
await run(service.sendToAgent("alice", "reply text", { replyTo: replyToId }));
|
|
82
90
|
const sendCall = service.calls.find((c) => c.method === MessagesSend.name);
|
|
83
91
|
expect(sendCall?.params).toEqual({
|
|
84
|
-
conversationId:
|
|
92
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
85
93
|
parts: [{ type: "text", text: "reply text" }],
|
|
86
|
-
replyToId
|
|
94
|
+
replyToId,
|
|
87
95
|
});
|
|
88
96
|
});
|
|
89
97
|
it("maintains separate cache entries per agent name", async () => {
|
|
90
|
-
service.setResponse(AgentsLookupByName
|
|
91
|
-
agents: [{ id:
|
|
98
|
+
service.setResponse(AgentsLookupByName, {
|
|
99
|
+
agents: [{ id: AGENT_ALICE_ID, name: "alice", status: "active" }],
|
|
92
100
|
});
|
|
93
101
|
await run(service.sendToAgent("alice", "hello alice"));
|
|
94
|
-
service.setResponse(AgentsLookupByName
|
|
95
|
-
agents: [
|
|
102
|
+
service.setResponse(AgentsLookupByName, {
|
|
103
|
+
agents: [
|
|
104
|
+
{ id: testAgentId("agent-bob-id"), name: "bob", status: "active" },
|
|
105
|
+
],
|
|
96
106
|
});
|
|
97
|
-
service.setResponse(ConversationsCreate
|
|
107
|
+
service.setResponse(ConversationsCreate, {
|
|
98
108
|
conversation: {
|
|
99
|
-
id: "conv-bob",
|
|
109
|
+
id: testConversationId("conv-bob"),
|
|
100
110
|
type: "dm",
|
|
101
|
-
createdBy:
|
|
111
|
+
createdBy: AGENT_SELF_ID,
|
|
102
112
|
createdAt: "2026-04-16T00:00:00Z",
|
|
103
113
|
updatedAt: "2026-04-16T00:00:00Z",
|
|
104
114
|
},
|
|
@@ -109,23 +119,23 @@ describe("MoltZapService.sendToAgent", () => {
|
|
|
109
119
|
await run(service.sendToAgent("bob", "bob again"));
|
|
110
120
|
const sendCalls = service.calls.filter((c) => c.method === MessagesSend.name);
|
|
111
121
|
expect(sendCalls).toHaveLength(2);
|
|
112
|
-
expect(sendCalls[0].params.conversationId).toBe(
|
|
113
|
-
expect(sendCalls[1].params.conversationId).toBe("conv-bob");
|
|
122
|
+
expect(sendCalls[0].params.conversationId).toBe(CONVERSATION_ALICE_ID);
|
|
123
|
+
expect(sendCalls[1].params.conversationId).toBe(testConversationId("conv-bob"));
|
|
114
124
|
});
|
|
115
125
|
it("throws a clear error when no agent is found for the given name", async () => {
|
|
116
|
-
service.setResponse(AgentsLookupByName
|
|
126
|
+
service.setResponse(AgentsLookupByName, { agents: [] });
|
|
117
127
|
await expect(run(service.sendToAgent("nobody", "hi"))).rejects.toThrow(/Agent not found: nobody/);
|
|
118
128
|
});
|
|
119
129
|
it("propagates errors from agents/lookupByName", async () => {
|
|
120
|
-
service.deleteResponse(AgentsLookupByName
|
|
130
|
+
service.deleteResponse(AgentsLookupByName);
|
|
121
131
|
await expect(run(service.sendToAgent("alice", "hi"))).rejects.toThrow(/no canned response for agents\/lookupByName/);
|
|
122
132
|
});
|
|
123
133
|
it("propagates errors from conversations/create", async () => {
|
|
124
|
-
service.deleteResponse(ConversationsCreate
|
|
134
|
+
service.deleteResponse(ConversationsCreate);
|
|
125
135
|
await expect(run(service.sendToAgent("alice", "hi"))).rejects.toThrow(/no canned response for conversations\/create/);
|
|
126
136
|
});
|
|
127
137
|
it("propagates errors from messages/send", async () => {
|
|
128
|
-
service.deleteResponse(MessagesSend
|
|
138
|
+
service.deleteResponse(MessagesSend);
|
|
129
139
|
await expect(run(service.sendToAgent("alice", "hi"))).rejects.toThrow(/no canned response for messages\/send/);
|
|
130
140
|
});
|
|
131
141
|
});
|
|
@@ -155,7 +165,7 @@ describe("sanitizeForSystemReminder", () => {
|
|
|
155
165
|
describe("MoltZapService.authorizeDispatch", () => {
|
|
156
166
|
it("uses the long app-dispatch RPC timeout instead of the generic RPC timeout", async () => {
|
|
157
167
|
const service = new FakeMoltZapService();
|
|
158
|
-
service.setResponse(AppsAuthorizeDispatch
|
|
168
|
+
service.setResponse(AppsAuthorizeDispatch, {
|
|
159
169
|
admission: {
|
|
160
170
|
decision: "grant",
|
|
161
171
|
leaseId: "lease-1",
|
|
@@ -163,23 +173,23 @@ describe("MoltZapService.authorizeDispatch", () => {
|
|
|
163
173
|
},
|
|
164
174
|
});
|
|
165
175
|
const result = await run(service.authorizeDispatch({
|
|
166
|
-
conversationId:
|
|
167
|
-
senderAgentId:
|
|
176
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
177
|
+
senderAgentId: AGENT_GM_ID,
|
|
168
178
|
attempt: 0,
|
|
169
179
|
receivedAt: "2026-04-29T22:00:00.000Z",
|
|
170
180
|
clock: {
|
|
171
|
-
domainId:
|
|
181
|
+
domainId: CONVERSATION_ALICE_ID,
|
|
172
182
|
epoch: 1,
|
|
173
|
-
vector: {
|
|
183
|
+
vector: { [AGENT_GM_ID]: 1 },
|
|
174
184
|
},
|
|
175
185
|
pending: [],
|
|
176
|
-
message: {
|
|
177
|
-
id:
|
|
178
|
-
conversationId:
|
|
179
|
-
senderId:
|
|
186
|
+
message: buildMessage({
|
|
187
|
+
id: DISPATCH_MESSAGE_ID,
|
|
188
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
189
|
+
senderId: AGENT_GM_ID,
|
|
180
190
|
parts: [{ type: "text", text: "Time to vote!" }],
|
|
181
191
|
createdAt: "2026-04-29T22:00:00.000Z",
|
|
182
|
-
},
|
|
192
|
+
}),
|
|
183
193
|
}));
|
|
184
194
|
expect(result).toEqual({
|
|
185
195
|
_tag: "grant",
|
|
@@ -196,14 +206,14 @@ describe("MoltZapService.authorizeDispatch", () => {
|
|
|
196
206
|
describe("MoltZapService.getContext — XML injection hardening", () => {
|
|
197
207
|
/** Build a message that lands in `messages` via addMessage(). */
|
|
198
208
|
function msg(overrides) {
|
|
199
|
-
return {
|
|
200
|
-
id:
|
|
201
|
-
conversationId:
|
|
202
|
-
senderId:
|
|
203
|
-
parts:
|
|
204
|
-
createdAt:
|
|
209
|
+
return buildMessage({
|
|
210
|
+
id: "msg-1",
|
|
211
|
+
conversationId: "conv-other",
|
|
212
|
+
senderId: "agent-attacker",
|
|
213
|
+
parts: [{ type: "text", text: "hello" }],
|
|
214
|
+
createdAt: new Date().toISOString(),
|
|
205
215
|
...overrides,
|
|
206
|
-
};
|
|
216
|
+
});
|
|
207
217
|
}
|
|
208
218
|
it("escapes senderName with </system-reminder> injection attempt", () => {
|
|
209
219
|
const service = new FakeMoltZapService();
|
|
@@ -275,13 +285,13 @@ describe("MoltZapService.getContext — XML injection hardening", () => {
|
|
|
275
285
|
});
|
|
276
286
|
describe("MoltZapService.peekContextEntries", () => {
|
|
277
287
|
function addSimpleMessage(service, convId, seq, text = "hi") {
|
|
278
|
-
service.addMessage(convId, {
|
|
288
|
+
service.addMessage(convId, buildMessage({
|
|
279
289
|
id: `m-${seq}`,
|
|
280
290
|
conversationId: convId,
|
|
281
291
|
senderId: "agent-bob",
|
|
282
292
|
parts: [{ type: "text", text }],
|
|
283
293
|
createdAt: new Date().toISOString(),
|
|
284
|
-
});
|
|
294
|
+
}));
|
|
285
295
|
}
|
|
286
296
|
it("returns structured entries without advancing markers", () => {
|
|
287
297
|
const service = new FakeMoltZapService();
|
|
@@ -370,68 +380,68 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
370
380
|
const service = new FakeMoltZapService();
|
|
371
381
|
service.setAgentNameDirect("agent-bob", "Bob");
|
|
372
382
|
service.setAgentNameDirect("agent-alice", "Alice");
|
|
373
|
-
service.addMessage("conv-a", {
|
|
383
|
+
service.addMessage("conv-a", buildMessage({
|
|
374
384
|
id: "m-1",
|
|
375
385
|
conversationId: "conv-a",
|
|
376
386
|
senderId: "agent-bob",
|
|
377
387
|
parts: [{ type: "text", text: "first" }],
|
|
378
388
|
createdAt: "2026-04-13T22:00:00Z",
|
|
379
|
-
});
|
|
380
|
-
service.addMessage("conv-b", {
|
|
389
|
+
}));
|
|
390
|
+
service.addMessage("conv-b", buildMessage({
|
|
381
391
|
id: "m-2",
|
|
382
392
|
conversationId: "conv-b",
|
|
383
393
|
senderId: "agent-alice",
|
|
384
394
|
parts: [{ type: "text", text: "second" }],
|
|
385
395
|
createdAt: "2026-04-13T22:00:01Z",
|
|
386
|
-
});
|
|
387
|
-
service.addMessage("conv-a", {
|
|
396
|
+
}));
|
|
397
|
+
service.addMessage("conv-a", buildMessage({
|
|
388
398
|
id: "m-3",
|
|
389
399
|
conversationId: "conv-a",
|
|
390
400
|
senderId: "agent-bob",
|
|
391
401
|
parts: [{ type: "text", text: "third" }],
|
|
392
402
|
createdAt: "2026-04-13T22:00:02Z",
|
|
393
|
-
});
|
|
403
|
+
}));
|
|
394
404
|
const { messages } = service.peekFullMessages("conv-self");
|
|
395
405
|
expect(messages).toHaveLength(3);
|
|
396
406
|
expect(messages.map((m) => m.text)).toEqual(["first", "second", "third"]);
|
|
397
407
|
expect(messages[0].conversationId).toBe("conv-a");
|
|
398
408
|
expect(messages[0].senderName).toBe("Bob");
|
|
399
|
-
expect(messages[0].senderId).toBe("agent-bob");
|
|
409
|
+
expect(messages[0].senderId).toBe(testAgentId("agent-bob"));
|
|
400
410
|
expect(messages[1].conversationId).toBe("conv-b");
|
|
401
411
|
expect(messages[1].senderName).toBe("Alice");
|
|
402
|
-
expect(messages[1].senderId).toBe("agent-alice");
|
|
412
|
+
expect(messages[1].senderId).toBe(testAgentId("agent-alice"));
|
|
403
413
|
});
|
|
404
414
|
it("excludes messages from the current conversation", () => {
|
|
405
415
|
const service = new FakeMoltZapService();
|
|
406
|
-
service.addMessage("conv-self", {
|
|
416
|
+
service.addMessage("conv-self", buildMessage({
|
|
407
417
|
id: "m-1",
|
|
408
418
|
conversationId: "conv-self",
|
|
409
419
|
senderId: "agent-bob",
|
|
410
420
|
parts: [{ type: "text", text: "own conv" }],
|
|
411
421
|
createdAt: "2026-04-13T22:00:00Z",
|
|
412
|
-
});
|
|
422
|
+
}));
|
|
413
423
|
const { messages } = service.peekFullMessages("conv-self");
|
|
414
424
|
expect(messages).toHaveLength(0);
|
|
415
425
|
});
|
|
416
426
|
it("commit advances markers; subsequent peek returns only new messages", () => {
|
|
417
427
|
const service = new FakeMoltZapService();
|
|
418
|
-
service.addMessage("conv-a", {
|
|
428
|
+
service.addMessage("conv-a", buildMessage({
|
|
419
429
|
id: "m-1",
|
|
420
430
|
conversationId: "conv-a",
|
|
421
431
|
senderId: "agent-bob",
|
|
422
432
|
parts: [{ type: "text", text: "old" }],
|
|
423
433
|
createdAt: "2026-04-13T22:00:00Z",
|
|
424
|
-
});
|
|
434
|
+
}));
|
|
425
435
|
const first = service.peekFullMessages("conv-self");
|
|
426
436
|
first.commit();
|
|
427
437
|
expect(first.messages).toHaveLength(1);
|
|
428
|
-
service.addMessage("conv-a", {
|
|
438
|
+
service.addMessage("conv-a", buildMessage({
|
|
429
439
|
id: "m-2",
|
|
430
440
|
conversationId: "conv-a",
|
|
431
441
|
senderId: "agent-bob",
|
|
432
442
|
parts: [{ type: "text", text: "new" }],
|
|
433
443
|
createdAt: "2026-04-13T22:01:00Z",
|
|
434
|
-
});
|
|
444
|
+
}));
|
|
435
445
|
const second = service.peekFullMessages("conv-self");
|
|
436
446
|
expect(second.messages).toHaveLength(1);
|
|
437
447
|
expect(second.messages[0].text).toBe("new");
|
|
@@ -440,14 +450,13 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
440
450
|
const service = new FakeMoltZapService();
|
|
441
451
|
for (let c = 0; c < 10; c++) {
|
|
442
452
|
for (let m = 0; m < 5; m++) {
|
|
443
|
-
service.addMessage(`conv-${c}`, {
|
|
453
|
+
service.addMessage(`conv-${c}`, buildMessage({
|
|
444
454
|
id: `m-${c}-${m}`,
|
|
445
455
|
conversationId: `conv-${c}`,
|
|
446
456
|
senderId: "agent-bob",
|
|
447
|
-
seq: m + 1,
|
|
448
457
|
parts: [{ type: "text", text: `c${c}-m${m}` }],
|
|
449
458
|
createdAt: new Date(Date.now() + c * 10000 + m * 1000).toISOString(),
|
|
450
|
-
});
|
|
459
|
+
}));
|
|
451
460
|
}
|
|
452
461
|
}
|
|
453
462
|
const { messages } = service.peekFullMessages("conv-self");
|
|
@@ -455,13 +464,13 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
455
464
|
});
|
|
456
465
|
it("peek without commit is idempotent", () => {
|
|
457
466
|
const service = new FakeMoltZapService();
|
|
458
|
-
service.addMessage("conv-a", {
|
|
467
|
+
service.addMessage("conv-a", buildMessage({
|
|
459
468
|
id: "m-1",
|
|
460
469
|
conversationId: "conv-a",
|
|
461
470
|
senderId: "agent-bob",
|
|
462
471
|
parts: [{ type: "text", text: "hi" }],
|
|
463
472
|
createdAt: "2026-04-13T22:00:00Z",
|
|
464
|
-
});
|
|
473
|
+
}));
|
|
465
474
|
const a = service.peekFullMessages("conv-self").messages;
|
|
466
475
|
const b = service.peekFullMessages("conv-self").messages;
|
|
467
476
|
expect(a).toHaveLength(1);
|
|
@@ -469,128 +478,86 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
469
478
|
});
|
|
470
479
|
it("commit for one viewing conv does not affect another", () => {
|
|
471
480
|
const service = new FakeMoltZapService();
|
|
472
|
-
service.addMessage("conv-a", {
|
|
481
|
+
service.addMessage("conv-a", buildMessage({
|
|
473
482
|
id: "m-1",
|
|
474
483
|
conversationId: "conv-a",
|
|
475
484
|
senderId: "agent-bob",
|
|
476
485
|
parts: [{ type: "text", text: "hi" }],
|
|
477
486
|
createdAt: "2026-04-13T22:00:00Z",
|
|
478
|
-
});
|
|
487
|
+
}));
|
|
479
488
|
service.peekFullMessages("viewer-1").commit();
|
|
480
489
|
expect(service.peekFullMessages("viewer-2").messages).toHaveLength(1);
|
|
481
490
|
});
|
|
482
491
|
it("stores more than 20 messages per conversation without eviction", () => {
|
|
483
492
|
const service = new FakeMoltZapService();
|
|
484
493
|
for (let i = 1; i <= 30; i++) {
|
|
485
|
-
service.addMessage("conv-a", {
|
|
494
|
+
service.addMessage("conv-a", buildMessage({
|
|
486
495
|
id: `m-${i}`,
|
|
487
496
|
conversationId: "conv-a",
|
|
488
497
|
senderId: "agent-bob",
|
|
489
|
-
seq: i,
|
|
490
498
|
parts: [{ type: "text", text: `msg-${i}` }],
|
|
491
499
|
createdAt: new Date(Date.now() + i * 1000).toISOString(),
|
|
492
|
-
});
|
|
500
|
+
}));
|
|
493
501
|
}
|
|
494
502
|
const { messages } = service.peekFullMessages("conv-self");
|
|
495
503
|
expect(messages).toHaveLength(30);
|
|
496
504
|
});
|
|
497
505
|
});
|
|
498
|
-
describe("MoltZapService.on('permissionRequired')", () => {
|
|
499
|
-
it("fires handler when permissions/required event arrives", () => {
|
|
500
|
-
const service = new FakeMoltZapService();
|
|
501
|
-
const received = [];
|
|
502
|
-
service.on("permissionRequired", (data) => received.push(data));
|
|
503
|
-
const event = {
|
|
504
|
-
jsonrpc: "2.0",
|
|
505
|
-
type: "event",
|
|
506
|
-
event: EventNames.PermissionsRequired,
|
|
507
|
-
data: {
|
|
508
|
-
sessionId: "sess-1",
|
|
509
|
-
appId: "test-app",
|
|
510
|
-
resource: "contacts",
|
|
511
|
-
access: ["read"],
|
|
512
|
-
requestId: crypto.randomUUID(),
|
|
513
|
-
targetUserId: crypto.randomUUID(),
|
|
514
|
-
},
|
|
515
|
-
};
|
|
516
|
-
Reflect.get(service, "handleEvent").call(service, event);
|
|
517
|
-
expect(received).toHaveLength(1);
|
|
518
|
-
expect(received[0]).toMatchObject({
|
|
519
|
-
sessionId: "sess-1",
|
|
520
|
-
appId: "test-app",
|
|
521
|
-
resource: "contacts",
|
|
522
|
-
access: ["read"],
|
|
523
|
-
});
|
|
524
|
-
});
|
|
525
|
-
it("does not fire for unrelated events", () => {
|
|
526
|
-
const service = new FakeMoltZapService();
|
|
527
|
-
const received = [];
|
|
528
|
-
service.on("permissionRequired", (data) => received.push(data));
|
|
529
|
-
const event = {
|
|
530
|
-
jsonrpc: "2.0",
|
|
531
|
-
type: "event",
|
|
532
|
-
event: EventNames.PresenceChanged,
|
|
533
|
-
data: { agentId: "agent-1", status: "online" },
|
|
534
|
-
};
|
|
535
|
-
Reflect.get(service, "handleEvent").call(service, event);
|
|
536
|
-
expect(received).toHaveLength(0);
|
|
537
|
-
});
|
|
538
|
-
});
|
|
539
506
|
describe("MoltZapService conversation archive lifecycle", () => {
|
|
540
507
|
it("purges local state, fires conversationArchived, and locally rejects sends", async () => {
|
|
541
508
|
const service = new FakeMoltZapService();
|
|
542
|
-
service.setResponse(ConversationsGet
|
|
509
|
+
service.setResponse(ConversationsGet, {
|
|
543
510
|
conversation: {
|
|
544
|
-
id:
|
|
511
|
+
id: CONVERSATION_ARCHIVED_ID,
|
|
545
512
|
type: "group",
|
|
546
513
|
name: "Archived",
|
|
547
|
-
createdBy:
|
|
514
|
+
createdBy: AGENT_SELF_ID,
|
|
548
515
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
549
516
|
updatedAt: "2026-05-01T00:00:00.000Z",
|
|
550
517
|
},
|
|
551
518
|
participants: [],
|
|
552
519
|
});
|
|
553
|
-
service.setResponse(MessagesSend
|
|
554
|
-
message: {
|
|
520
|
+
service.setResponse(MessagesSend, {
|
|
521
|
+
message: buildMessage({
|
|
555
522
|
id: "msg-unreachable",
|
|
556
523
|
conversationId: "conv-archived",
|
|
557
524
|
senderId: "agent-self",
|
|
558
525
|
parts: [{ type: "text", text: "unreachable" }],
|
|
559
526
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
560
|
-
},
|
|
527
|
+
}),
|
|
561
528
|
});
|
|
562
|
-
service.emitEvent(
|
|
529
|
+
service.emitEvent(notificationFrame(ConversationCreatedNotificationDefinition, {
|
|
563
530
|
conversation: {
|
|
564
|
-
id:
|
|
531
|
+
id: CONVERSATION_ARCHIVED_ID,
|
|
565
532
|
type: "group",
|
|
566
533
|
name: "Archived",
|
|
567
|
-
createdBy:
|
|
534
|
+
createdBy: AGENT_SELF_ID,
|
|
568
535
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
569
536
|
updatedAt: "2026-05-01T00:00:00.000Z",
|
|
570
537
|
},
|
|
571
538
|
}));
|
|
572
|
-
service.addMessage(
|
|
539
|
+
service.addMessage(CONVERSATION_ARCHIVED_ID, buildMessage({
|
|
573
540
|
id: "msg-1",
|
|
574
541
|
conversationId: "conv-archived",
|
|
575
542
|
senderId: "agent-other",
|
|
576
543
|
parts: [{ type: "text", text: "old" }],
|
|
577
544
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
578
|
-
});
|
|
545
|
+
}));
|
|
579
546
|
const archivedEvents = [];
|
|
580
547
|
const unarchivedEvents = [];
|
|
581
548
|
service.on("conversationArchived", (data) => archivedEvents.push(data));
|
|
582
549
|
service.on("conversationUnarchived", (data) => unarchivedEvents.push(data));
|
|
583
|
-
const archivedEvent =
|
|
584
|
-
conversationId:
|
|
550
|
+
const archivedEvent = notificationFrame(ConversationArchivedNotificationDefinition, {
|
|
551
|
+
conversationId: CONVERSATION_ARCHIVED_ID,
|
|
585
552
|
archivedAt: "2026-05-01T00:01:00.000Z",
|
|
586
|
-
by:
|
|
553
|
+
by: AGENT_GM_ID,
|
|
587
554
|
});
|
|
588
555
|
service.emitEvent(archivedEvent);
|
|
589
|
-
expect(service.isConversationArchived(
|
|
590
|
-
expect(service.getConversation(
|
|
591
|
-
expect(service.getHistory(
|
|
592
|
-
expect(archivedEvents).toEqual([archivedEvent.
|
|
593
|
-
const lateSend = await run(Effect.either(service.send(
|
|
556
|
+
expect(service.isConversationArchived(CONVERSATION_ARCHIVED_ID)).toBe(true);
|
|
557
|
+
expect(service.getConversation(CONVERSATION_ARCHIVED_ID)).toBeUndefined();
|
|
558
|
+
expect(service.getHistory(CONVERSATION_ARCHIVED_ID)).toEqual([]);
|
|
559
|
+
expect(archivedEvents).toEqual([archivedEvent.params]);
|
|
560
|
+
const lateSend = await run(Effect.either(service.send(CONVERSATION_ARCHIVED_ID, "should not hit rpc")));
|
|
594
561
|
expect(Either.isLeft(lateSend)).toBe(true);
|
|
595
562
|
if (Either.isLeft(lateSend)) {
|
|
596
563
|
expect(lateSend.left).toMatchObject({
|
|
@@ -599,34 +566,13 @@ describe("MoltZapService conversation archive lifecycle", () => {
|
|
|
599
566
|
});
|
|
600
567
|
}
|
|
601
568
|
expect(service.calls.filter((c) => c.method === MessagesSend.name)).toEqual([]);
|
|
602
|
-
const unarchivedEvent =
|
|
603
|
-
conversationId:
|
|
604
|
-
by:
|
|
569
|
+
const unarchivedEvent = notificationFrame(ConversationUnarchivedNotificationDefinition, {
|
|
570
|
+
conversationId: CONVERSATION_ARCHIVED_ID,
|
|
571
|
+
by: AGENT_GM_ID,
|
|
605
572
|
});
|
|
606
573
|
service.emitEvent(unarchivedEvent);
|
|
607
|
-
expect(service.isConversationArchived(
|
|
608
|
-
expect(unarchivedEvents).toEqual([unarchivedEvent.
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
describe("MoltZapService.grantPermission", () => {
|
|
612
|
-
it("sends permissions/grant RPC", async () => {
|
|
613
|
-
const service = new FakeMoltZapService();
|
|
614
|
-
service.setResponse(PermissionsGrant.name, {});
|
|
615
|
-
await run(service.grantPermission({
|
|
616
|
-
sessionId: "sess-1",
|
|
617
|
-
agentId: "agent-2",
|
|
618
|
-
resource: "contacts",
|
|
619
|
-
access: ["read"],
|
|
620
|
-
}));
|
|
621
|
-
expect(service.calls).toContainEqual({
|
|
622
|
-
method: PermissionsGrant.name,
|
|
623
|
-
params: {
|
|
624
|
-
sessionId: "sess-1",
|
|
625
|
-
agentId: "agent-2",
|
|
626
|
-
resource: "contacts",
|
|
627
|
-
access: ["read"],
|
|
628
|
-
},
|
|
629
|
-
});
|
|
574
|
+
expect(service.isConversationArchived(CONVERSATION_ARCHIVED_ID)).toBe(false);
|
|
575
|
+
expect(unarchivedEvents).toEqual([unarchivedEvent.params]);
|
|
630
576
|
});
|
|
631
577
|
});
|
|
632
578
|
describe("MoltZapService.socketPath — agentId sanitization", () => {
|
|
@@ -726,22 +672,17 @@ describe("MoltZapService.fanout — message handlers", () => {
|
|
|
726
672
|
service.on("message", (m) => {
|
|
727
673
|
seen.push(m);
|
|
728
674
|
});
|
|
729
|
-
const msg = {
|
|
675
|
+
const msg = buildMessage({
|
|
730
676
|
id: "m-1",
|
|
731
677
|
conversationId: "conv-1",
|
|
732
678
|
senderId: "agent-other",
|
|
733
679
|
parts: [{ type: "text", text: "hi" }],
|
|
734
680
|
createdAt: "2026-04-16T00:00:00.000Z",
|
|
735
|
-
};
|
|
736
|
-
const event = {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
data: { message: msg },
|
|
741
|
-
};
|
|
742
|
-
// handleEvent is private; reach into it the same way the existing
|
|
743
|
-
// permissions test does.
|
|
744
|
-
Reflect.get(service, "handleEvent").call(service, event);
|
|
681
|
+
});
|
|
682
|
+
const event = notificationFrame(MessageReceivedNotificationDefinition, {
|
|
683
|
+
message: msg,
|
|
684
|
+
});
|
|
685
|
+
service.emitEvent(event);
|
|
745
686
|
// Second handler still fired despite first handler throwing.
|
|
746
687
|
expect(seen).toEqual([msg]);
|
|
747
688
|
expect(logger.error).toHaveBeenCalledOnce();
|