@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.
Files changed (187) hide show
  1. package/dist/channel-core.d.ts +1 -4
  2. package/dist/channel-core.d.ts.map +1 -1
  3. package/dist/channel-core.js +0 -9
  4. package/dist/channel-core.js.map +1 -1
  5. package/dist/channel-core.test.js +90 -71
  6. package/dist/channel-core.test.js.map +1 -1
  7. package/dist/cli/commands/agents.js +2 -2
  8. package/dist/cli/commands/agents.js.map +1 -1
  9. package/dist/cli/commands/apps.d.ts +2 -24
  10. package/dist/cli/commands/apps.d.ts.map +1 -1
  11. package/dist/cli/commands/apps.js +22 -47
  12. package/dist/cli/commands/apps.js.map +1 -1
  13. package/dist/cli/commands/apps.test.js +59 -72
  14. package/dist/cli/commands/apps.test.js.map +1 -1
  15. package/dist/cli/commands/contacts.d.ts +1 -5
  16. package/dist/cli/commands/contacts.d.ts.map +1 -1
  17. package/dist/cli/commands/contacts.js +11 -26
  18. package/dist/cli/commands/contacts.js.map +1 -1
  19. package/dist/cli/commands/conversations-v2.test.js +30 -26
  20. package/dist/cli/commands/conversations-v2.test.js.map +1 -1
  21. package/dist/cli/commands/conversations.d.ts.map +1 -1
  22. package/dist/cli/commands/conversations.js +48 -33
  23. package/dist/cli/commands/conversations.js.map +1 -1
  24. package/dist/cli/commands/messages.d.ts.map +1 -1
  25. package/dist/cli/commands/messages.js +11 -8
  26. package/dist/cli/commands/messages.js.map +1 -1
  27. package/dist/cli/commands/messages.test.js +28 -30
  28. package/dist/cli/commands/messages.test.js.map +1 -1
  29. package/dist/cli/commands/presence.d.ts.map +1 -1
  30. package/dist/cli/commands/presence.js +5 -4
  31. package/dist/cli/commands/presence.js.map +1 -1
  32. package/dist/cli/commands/send.d.ts.map +1 -1
  33. package/dist/cli/commands/send.js +20 -11
  34. package/dist/cli/commands/send.js.map +1 -1
  35. package/dist/cli/commands/send.test.js +11 -9
  36. package/dist/cli/commands/send.test.js.map +1 -1
  37. package/dist/cli/commands/status.js +2 -2
  38. package/dist/cli/commands/status.js.map +1 -1
  39. package/dist/cli/commands/test-transport.d.ts +10 -0
  40. package/dist/cli/commands/test-transport.d.ts.map +1 -0
  41. package/dist/cli/commands/test-transport.js +29 -0
  42. package/dist/cli/commands/test-transport.js.map +1 -0
  43. package/dist/cli/index.d.ts.map +1 -1
  44. package/dist/cli/index.js +0 -2
  45. package/dist/cli/index.js.map +1 -1
  46. package/dist/cli/socket-client.d.ts +9 -4
  47. package/dist/cli/socket-client.d.ts.map +1 -1
  48. package/dist/cli/socket-client.js +27 -12
  49. package/dist/cli/socket-client.js.map +1 -1
  50. package/dist/cli/transport.d.ts +3 -2
  51. package/dist/cli/transport.d.ts.map +1 -1
  52. package/dist/cli/transport.js +6 -6
  53. package/dist/cli/transport.js.map +1 -1
  54. package/dist/cli/transport.test.js +4 -4
  55. package/dist/cli/transport.test.js.map +1 -1
  56. package/dist/index.d.ts +2 -3
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/internal/__tests__/app-callback-partition-key.test.d.ts +2 -0
  61. package/dist/internal/__tests__/app-callback-partition-key.test.d.ts.map +1 -0
  62. package/dist/internal/__tests__/app-callback-partition-key.test.js +33 -0
  63. package/dist/internal/__tests__/app-callback-partition-key.test.js.map +1 -0
  64. package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts +2 -0
  65. package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts.map +1 -0
  66. package/dist/internal/__tests__/{s2c-partition-worker.test.js → app-callback-partition-worker.test.js} +6 -9
  67. package/dist/internal/__tests__/app-callback-partition-worker.test.js.map +1 -0
  68. package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts +2 -0
  69. package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts.map +1 -0
  70. package/dist/internal/__tests__/{s2c-partitioned-dispatcher-real-ws.test.js → app-callback-partitioned-dispatcher-real-ws.test.js} +69 -94
  71. package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.js.map +1 -0
  72. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts +2 -0
  73. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts.map +1 -0
  74. package/dist/internal/__tests__/{s2c-partitioned-dispatcher.test.js → app-callback-partitioned-dispatcher.test.js} +35 -69
  75. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js.map +1 -0
  76. package/dist/internal/__tests__/app-callback-test-requests.d.ts +43 -0
  77. package/dist/internal/__tests__/app-callback-test-requests.d.ts.map +1 -0
  78. package/dist/internal/__tests__/app-callback-test-requests.js +101 -0
  79. package/dist/internal/__tests__/app-callback-test-requests.js.map +1 -0
  80. package/dist/internal/{s2c-dispatcher-errors.d.ts → app-callback-dispatcher-errors.d.ts} +5 -21
  81. package/dist/internal/app-callback-dispatcher-errors.d.ts.map +1 -0
  82. package/dist/internal/{s2c-dispatcher-errors.js → app-callback-dispatcher-errors.js} +3 -13
  83. package/dist/internal/app-callback-dispatcher-errors.js.map +1 -0
  84. package/dist/internal/app-callback-partition-key.d.ts +68 -0
  85. package/dist/internal/app-callback-partition-key.d.ts.map +1 -0
  86. package/dist/internal/app-callback-partition-key.js +90 -0
  87. package/dist/internal/app-callback-partition-key.js.map +1 -0
  88. package/dist/internal/{s2c-partition-worker.d.ts → app-callback-partition-worker.d.ts} +5 -5
  89. package/dist/internal/app-callback-partition-worker.d.ts.map +1 -0
  90. package/dist/internal/{s2c-partition-worker.js → app-callback-partition-worker.js} +5 -5
  91. package/dist/internal/app-callback-partition-worker.js.map +1 -0
  92. package/dist/internal/{s2c-partitioned-dispatcher.d.ts → app-callback-partitioned-dispatcher.d.ts} +9 -10
  93. package/dist/internal/app-callback-partitioned-dispatcher.d.ts.map +1 -0
  94. package/dist/internal/{s2c-partitioned-dispatcher.js → app-callback-partitioned-dispatcher.js} +12 -19
  95. package/dist/internal/app-callback-partitioned-dispatcher.js.map +1 -0
  96. package/dist/runtime/errors.d.ts +2 -28
  97. package/dist/runtime/errors.d.ts.map +1 -1
  98. package/dist/runtime/errors.js +0 -9
  99. package/dist/runtime/errors.js.map +1 -1
  100. package/dist/runtime/errors.test.js +2 -2
  101. package/dist/runtime/errors.test.js.map +1 -1
  102. package/dist/runtime/frame.d.ts +17 -18
  103. package/dist/runtime/frame.d.ts.map +1 -1
  104. package/dist/runtime/frame.js +83 -31
  105. package/dist/runtime/frame.js.map +1 -1
  106. package/dist/runtime/frame.test.js +15 -11
  107. package/dist/runtime/frame.test.js.map +1 -1
  108. package/dist/runtime/index.d.ts +1 -1
  109. package/dist/runtime/index.d.ts.map +1 -1
  110. package/dist/runtime/index.js +1 -1
  111. package/dist/runtime/index.js.map +1 -1
  112. package/dist/runtime/local-service-commands.d.ts +7 -0
  113. package/dist/runtime/local-service-commands.d.ts.map +1 -0
  114. package/dist/runtime/local-service-commands.js +6 -0
  115. package/dist/runtime/local-service-commands.js.map +1 -0
  116. package/dist/runtime/runtime.test.js +1 -2
  117. package/dist/runtime/runtime.test.js.map +1 -1
  118. package/dist/runtime/subscribers.d.ts +34 -29
  119. package/dist/runtime/subscribers.d.ts.map +1 -1
  120. package/dist/runtime/subscribers.js +19 -19
  121. package/dist/runtime/subscribers.js.map +1 -1
  122. package/dist/runtime/subscribers.test.js +50 -31
  123. package/dist/runtime/subscribers.test.js.map +1 -1
  124. package/dist/service.d.ts +24 -39
  125. package/dist/service.d.ts.map +1 -1
  126. package/dist/service.js +165 -161
  127. package/dist/service.js.map +1 -1
  128. package/dist/service.test.js +111 -170
  129. package/dist/service.test.js.map +1 -1
  130. package/dist/test-utils/channel-service-fixture.d.ts +1 -2
  131. package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
  132. package/dist/test-utils/channel-service-fixture.js +28 -20
  133. package/dist/test-utils/channel-service-fixture.js.map +1 -1
  134. package/dist/test-utils/conformance-adapter.d.ts +2 -2
  135. package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
  136. package/dist/test-utils/conformance-adapter.js +33 -26
  137. package/dist/test-utils/conformance-adapter.js.map +1 -1
  138. package/dist/test-utils/fake-service.d.ts +14 -39
  139. package/dist/test-utils/fake-service.d.ts.map +1 -1
  140. package/dist/test-utils/fake-service.js +33 -39
  141. package/dist/test-utils/fake-service.js.map +1 -1
  142. package/dist/test-utils/ids.d.ts +9 -0
  143. package/dist/test-utils/ids.d.ts.map +1 -0
  144. package/dist/test-utils/ids.js +52 -0
  145. package/dist/test-utils/ids.js.map +1 -0
  146. package/dist/test-utils/index.d.ts +10 -2
  147. package/dist/test-utils/index.d.ts.map +1 -1
  148. package/dist/test-utils/index.js +12 -5
  149. package/dist/test-utils/index.js.map +1 -1
  150. package/dist/ws-client.d.ts +58 -64
  151. package/dist/ws-client.d.ts.map +1 -1
  152. package/dist/ws-client.js +151 -133
  153. package/dist/ws-client.js.map +1 -1
  154. package/dist/ws-client.test.js +157 -167
  155. package/dist/ws-client.test.js.map +1 -1
  156. package/package.json +3 -3
  157. package/dist/cli/commands/permissions.d.ts +0 -70
  158. package/dist/cli/commands/permissions.d.ts.map +0 -1
  159. package/dist/cli/commands/permissions.js +0 -100
  160. package/dist/cli/commands/permissions.js.map +0 -1
  161. package/dist/cli/commands/permissions.test.d.ts +0 -2
  162. package/dist/cli/commands/permissions.test.d.ts.map +0 -1
  163. package/dist/cli/commands/permissions.test.js +0 -127
  164. package/dist/cli/commands/permissions.test.js.map +0 -1
  165. package/dist/internal/__tests__/s2c-partition-key.test.d.ts +0 -2
  166. package/dist/internal/__tests__/s2c-partition-key.test.d.ts.map +0 -1
  167. package/dist/internal/__tests__/s2c-partition-key.test.js +0 -231
  168. package/dist/internal/__tests__/s2c-partition-key.test.js.map +0 -1
  169. package/dist/internal/__tests__/s2c-partition-worker.test.d.ts +0 -2
  170. package/dist/internal/__tests__/s2c-partition-worker.test.d.ts.map +0 -1
  171. package/dist/internal/__tests__/s2c-partition-worker.test.js.map +0 -1
  172. package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts +0 -2
  173. package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts.map +0 -1
  174. package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.js.map +0 -1
  175. package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts +0 -2
  176. package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts.map +0 -1
  177. package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.js.map +0 -1
  178. package/dist/internal/s2c-dispatcher-errors.d.ts.map +0 -1
  179. package/dist/internal/s2c-dispatcher-errors.js.map +0 -1
  180. package/dist/internal/s2c-partition-key.d.ts +0 -79
  181. package/dist/internal/s2c-partition-key.d.ts.map +0 -1
  182. package/dist/internal/s2c-partition-key.js +0 -146
  183. package/dist/internal/s2c-partition-key.js.map +0 -1
  184. package/dist/internal/s2c-partition-worker.d.ts.map +0 -1
  185. package/dist/internal/s2c-partition-worker.js.map +0 -1
  186. package/dist/internal/s2c-partitioned-dispatcher.d.ts.map +0 -1
  187. package/dist/internal/s2c-partitioned-dispatcher.js.map +0 -1
@@ -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, EventNames, MessagesSend, eventFrame, } from "@moltzap/protocol";
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 { AgentsLookupByName, ConversationsCreate, PermissionsGrant, } from "@moltzap/protocol";
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 `RpcMap[M]["result"]`. Both guard against the
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.name, {
25
+ service.setResponse(AgentsLookupByName, {
19
26
  agents: [
20
27
  {
21
- id: "agent-alice-id",
28
+ id: AGENT_ALICE_ID,
22
29
  name: "alice",
23
30
  status: "active",
24
31
  },
25
32
  ],
26
33
  });
27
- service.setResponse(ConversationsCreate.name, {
34
+ service.setResponse(ConversationsCreate, {
28
35
  conversation: {
29
- id: "conv-alice",
36
+ id: CONVERSATION_ALICE_ID,
30
37
  type: "dm",
31
- createdBy: "agent-self",
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.name, {
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: "agent-alice-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: "conv-alice",
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: "conv-alice",
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
- await run(service.sendToAgent("alice", "reply text", { replyTo: "msg-123" }));
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: "conv-alice",
92
+ conversationId: CONVERSATION_ALICE_ID,
85
93
  parts: [{ type: "text", text: "reply text" }],
86
- replyToId: "msg-123",
94
+ replyToId,
87
95
  });
88
96
  });
89
97
  it("maintains separate cache entries per agent name", async () => {
90
- service.setResponse(AgentsLookupByName.name, {
91
- agents: [{ id: "agent-alice-id", name: "alice", status: "active" }],
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.name, {
95
- agents: [{ id: "agent-bob-id", name: "bob", status: "active" }],
102
+ service.setResponse(AgentsLookupByName, {
103
+ agents: [
104
+ { id: testAgentId("agent-bob-id"), name: "bob", status: "active" },
105
+ ],
96
106
  });
97
- service.setResponse(ConversationsCreate.name, {
107
+ service.setResponse(ConversationsCreate, {
98
108
  conversation: {
99
- id: "conv-bob",
109
+ id: testConversationId("conv-bob"),
100
110
  type: "dm",
101
- createdBy: "agent-self",
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("conv-alice");
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.name, { agents: [] });
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.name);
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.name);
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.name);
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.name, {
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: "conv-1",
167
- senderAgentId: "agent-gm",
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: "conv-1",
181
+ domainId: CONVERSATION_ALICE_ID,
172
182
  epoch: 1,
173
- vector: { "agent-gm": 1 },
183
+ vector: { [AGENT_GM_ID]: 1 },
174
184
  },
175
185
  pending: [],
176
- message: {
177
- id: "msg-1",
178
- conversationId: "conv-1",
179
- senderId: "agent-gm",
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: overrides.id ?? "msg-1",
201
- conversationId: overrides.conversationId ?? "conv-other",
202
- senderId: overrides.senderId ?? "agent-attacker",
203
- parts: overrides.parts ?? [{ type: "text", text: "hello" }],
204
- createdAt: overrides.createdAt ?? new Date().toISOString(),
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.name, {
509
+ service.setResponse(ConversationsGet, {
543
510
  conversation: {
544
- id: "conv-archived",
511
+ id: CONVERSATION_ARCHIVED_ID,
545
512
  type: "group",
546
513
  name: "Archived",
547
- createdBy: "agent-self",
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.name, {
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(eventFrame(EventNames.ConversationCreated, {
529
+ service.emitEvent(notificationFrame(ConversationCreatedNotificationDefinition, {
563
530
  conversation: {
564
- id: "conv-archived",
531
+ id: CONVERSATION_ARCHIVED_ID,
565
532
  type: "group",
566
533
  name: "Archived",
567
- createdBy: "agent-self",
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("conv-archived", {
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 = eventFrame(EventNames.ConversationArchived, {
584
- conversationId: "conv-archived",
550
+ const archivedEvent = notificationFrame(ConversationArchivedNotificationDefinition, {
551
+ conversationId: CONVERSATION_ARCHIVED_ID,
585
552
  archivedAt: "2026-05-01T00:01:00.000Z",
586
- by: "agent-gm",
553
+ by: AGENT_GM_ID,
587
554
  });
588
555
  service.emitEvent(archivedEvent);
589
- expect(service.isConversationArchived("conv-archived")).toBe(true);
590
- expect(service.getConversation("conv-archived")).toBeUndefined();
591
- expect(service.getHistory("conv-archived")).toEqual([]);
592
- expect(archivedEvents).toEqual([archivedEvent.data]);
593
- const lateSend = await run(Effect.either(service.send("conv-archived", "should not hit rpc")));
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 = eventFrame(EventNames.ConversationUnarchived, {
603
- conversationId: "conv-archived",
604
- by: "agent-gm",
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("conv-archived")).toBe(false);
608
- expect(unarchivedEvents).toEqual([unarchivedEvent.data]);
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
- jsonrpc: "2.0",
738
- type: "event",
739
- event: EventNames.MessageReceived,
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();