@moltzap/openclaw-channel 2026.329.0 → 2026.413.0

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.
@@ -7,8 +7,9 @@
7
7
  * Installed via: openclaw plugin install @moltzap/openclaw-channel
8
8
  * Config: channels.moltzap.accounts[].{apiKey, serverUrl, agentName}
9
9
  */
10
- import { MoltZapWsClient } from "./ws-client.js";
11
- import { extractMessage, extractReadReceipt, extractReaction, extractDelivery, extractDeletion, extractConversationCreated, extractConversationUpdated, extractContactRequest, extractContactAccepted, extractPresenceChanged, extractTypingIndicator, mapMessageToEnvelope, } from "./mapping.js";
10
+ import { MoltZapChannelCore, MoltZapService, formatCrossConversationBlock, } from "@moltzap/client";
11
+ import { extractReadReceipt, extractReaction, extractDelivery, extractDeletion, extractConversationCreated, extractConversationUpdated, extractContactRequest, extractContactAccepted, extractPresenceChanged, extractTypingIndicator, } from "./mapping.js";
12
+ import { EventNames } from "@moltzap/protocol";
12
13
  const DEFAULT_ACCOUNT_ID = "default";
13
14
  const CHANNEL_ID = "moltzap";
14
15
  const TARGET_PREFIX_AGENT = "agent:";
@@ -30,8 +31,6 @@ function resolveAccount(cfg, accountId) {
30
31
  accounts[0] ?? { id, apiKey: "", serverUrl: "", agentName: "" });
31
32
  }
32
33
  const activeClients = new Map();
33
- /** Cache: accountId → (agentName → conversationId) for auto-created DM conversations. */
34
- export const agentConversationCache = new Map();
35
34
  export const moltzapChannelPlugin = {
36
35
  id: CHANNEL_ID,
37
36
  meta: {
@@ -73,17 +72,17 @@ export const moltzapChannelPlugin = {
73
72
  },
74
73
  directory: {
75
74
  async listPeers(params) {
76
- const client = activeClients.get(params.accountId ?? DEFAULT_ACCOUNT_ID);
77
- if (!client)
75
+ const service = activeClients.get(params.accountId ?? DEFAULT_ACCOUNT_ID);
76
+ if (!service)
78
77
  return [];
79
78
  try {
80
- const { contacts } = (await client.sendRpc("contacts/list", {
79
+ const { contacts } = (await service.sendRpc("contacts/list", {
81
80
  status: "accepted",
82
81
  }));
83
82
  const agentIds = contacts.flatMap((c) => (c.agents ?? []).map((a) => a.id));
84
83
  if (agentIds.length === 0)
85
84
  return [];
86
- const { agents } = (await client.sendRpc("agents/lookup", {
85
+ const { agents } = (await service.sendRpc("agents/lookup", {
87
86
  agentIds,
88
87
  }));
89
88
  return agents.map((a) => ({
@@ -97,11 +96,11 @@ export const moltzapChannelPlugin = {
97
96
  }
98
97
  },
99
98
  async listGroups(params) {
100
- const client = activeClients.get(params.accountId ?? DEFAULT_ACCOUNT_ID);
101
- if (!client)
99
+ const service = activeClients.get(params.accountId ?? DEFAULT_ACCOUNT_ID);
100
+ if (!service)
102
101
  return [];
103
102
  try {
104
- const { conversations } = (await client.sendRpc("conversations/list", {}));
103
+ const { conversations } = (await service.sendRpc("conversations/list", {}));
105
104
  return conversations
106
105
  .filter((c) => c.type === "group" && c.name)
107
106
  .map((c) => ({
@@ -143,332 +142,209 @@ export const moltzapChannelPlugin = {
143
142
  return;
144
143
  }
145
144
  log?.info?.(`MoltZap: connecting as ${account.agentName} to ${account.serverUrl}`);
146
- const senderNameCache = new Map();
147
- const conversationMetaCache = new Map();
148
- async function lookupSenderName(client, agentId) {
149
- const cached = senderNameCache.get(agentId);
150
- if (cached)
151
- return cached;
152
- try {
153
- const result = (await client.sendRpc("agents/lookup", {
154
- agentIds: [agentId],
155
- }));
156
- const agent = result.agents[0];
157
- if (agent) {
158
- senderNameCache.set(agentId, agent.name);
159
- return agent.name;
160
- }
161
- }
162
- catch {
163
- log?.warn?.(`MoltZap: agents/lookup failed for ${agentId}`);
145
+ const wsLogger = log
146
+ ? {
147
+ info: log.info ?? (() => { }),
148
+ warn: log.warn ?? (() => { }),
149
+ error: log.error ?? (() => { }),
164
150
  }
165
- return agentId;
166
- }
167
- async function getConversationMeta(client, conversationId) {
168
- const cached = conversationMetaCache.get(conversationId);
169
- if (cached)
170
- return cached;
171
- try {
172
- const result = (await client.sendRpc("conversations/get", {
173
- conversationId,
174
- }));
175
- const meta = {
176
- type: result.conversation.type,
177
- name: result.conversation.name,
178
- participants: result.participants.map((p) => `${p.participant.type}:${p.participant.id}`),
179
- };
180
- conversationMetaCache.set(conversationId, meta);
181
- return meta;
182
- }
183
- catch {
184
- log?.warn?.(`MoltZap: conversations/get failed for ${conversationId}`);
185
- return undefined;
186
- }
187
- }
188
- // Serialize dispatches — OpenClaw's workspace-state writer can't handle
189
- // concurrent writes within the same process.
190
- let dispatchChain = Promise.resolve();
191
- let ownAgentId;
192
- function dispatchMessage(message, client) {
193
- // Skip messages sent by this agent (avoid echo loops)
194
- if (ownAgentId &&
195
- message.sender.type === "agent" &&
196
- message.sender.id === ownAgentId) {
151
+ : undefined;
152
+ const service = new MoltZapService({
153
+ serverUrl: account.serverUrl,
154
+ agentKey: account.apiKey,
155
+ logger: wsLogger,
156
+ });
157
+ const core = new MoltZapChannelCore({ service, logger: wsLogger });
158
+ core.onInbound(async (enriched) => {
159
+ const chatType = enriched.conversationMeta?.type === "group" ? "group" : "direct";
160
+ const fromId = `${enriched.sender.type}:${enriched.sender.id}`;
161
+ log?.info?.(`MoltZap: inbound from ${fromId}: ${enriched.text.slice(0, 80)}`);
162
+ setStatus({
163
+ accountId,
164
+ lastInboundAt: Date.now(),
165
+ lastEventAt: Date.now(),
166
+ });
167
+ const crossConvBlock = formatCrossConversationBlock(enriched.contextBlocks.crossConversation ?? [], {
168
+ header: `Recent updates (you are in conv:${enriched.conversationId}):`,
169
+ });
170
+ const bodyForAgent = crossConvBlock
171
+ ? `${crossConvBlock}\n\n${enriched.text}`
172
+ : enriched.text;
173
+ if (!ctx.channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
197
174
  return;
198
175
  }
199
- dispatchChain = dispatchChain
200
- .then(async () => {
201
- const [senderName, convMeta] = await Promise.all([
202
- message.sender.type === "agent"
203
- ? lookupSenderName(client, message.sender.id)
204
- : Promise.resolve(undefined),
205
- getConversationMeta(client, message.conversationId),
206
- ]);
207
- const envelope = mapMessageToEnvelope(message, {
208
- senderName,
209
- chatType: convMeta?.type === "group"
210
- ? "group"
211
- : convMeta?.type === "dm"
212
- ? "direct"
213
- : undefined,
214
- groupSubject: convMeta?.name,
215
- groupMembers: convMeta?.participants.join(","),
216
- conversationLabel: convMeta?.name,
217
- });
218
- log?.info?.(`MoltZap: inbound from ${envelope.peer.id}: ${envelope.text.slice(0, 80)}`);
219
- setStatus({
220
- accountId,
221
- lastInboundAt: Date.now(),
222
- lastEventAt: Date.now(),
176
+ const groupSubject = enriched.conversationMeta?.name;
177
+ const groupMembers = enriched.conversationMeta?.type === "group"
178
+ ? enriched.conversationMeta.participants.join(",")
179
+ : undefined;
180
+ try {
181
+ const result = await ctx.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
182
+ ctx: {
183
+ Body: enriched.text,
184
+ BodyForAgent: bodyForAgent,
185
+ From: fromId,
186
+ To: account.agentName ?? accountId,
187
+ SessionKey: `agent:main:moltzap:${chatType === "group" ? "group" : "dm"}:${enriched.conversationId}`,
188
+ AccountId: accountId,
189
+ Provider: CHANNEL_ID,
190
+ Surface: CHANNEL_ID,
191
+ OriginatingChannel: CHANNEL_ID,
192
+ OriginatingTo: enriched.conversationId,
193
+ ChatType: chatType,
194
+ ...(groupSubject ? { GroupSubject: groupSubject } : {}),
195
+ ...(groupMembers ? { GroupMembers: groupMembers } : {}),
196
+ ...(enriched.conversationMeta?.name
197
+ ? { ConversationLabel: enriched.conversationMeta.name }
198
+ : {}),
199
+ SenderName: enriched.sender.name,
200
+ },
201
+ cfg: ctx.cfg,
202
+ dispatcherOptions: {
203
+ deliver: async (payload, info) => {
204
+ if (info?.kind !== "final")
205
+ return true;
206
+ const text = payload.text ?? payload.body;
207
+ if (!text)
208
+ return true;
209
+ try {
210
+ await core.sendReply(enriched.conversationId, text);
211
+ log?.info?.(`MoltZap: outbound reply to ${enriched.conversationId}: ${text.slice(0, 80)}`);
212
+ return true;
213
+ }
214
+ catch (sendErr) {
215
+ log?.error?.(`MoltZap: failed to send reply: ${sendErr}`);
216
+ return false;
217
+ }
218
+ },
219
+ },
223
220
  });
224
- if (ctx.channelRuntime?.reply
225
- ?.dispatchReplyWithBufferedBlockDispatcher) {
226
- try {
227
- const result = await ctx.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
228
- ctx: {
229
- Body: envelope.text,
230
- BodyForAgent: envelope.text,
231
- From: envelope.peer.id,
232
- To: account.agentName ?? accountId,
233
- SessionKey: `agent:main:moltzap:dm:${envelope.conversationId}`,
234
- AccountId: accountId,
235
- Provider: CHANNEL_ID,
236
- Surface: CHANNEL_ID,
237
- OriginatingChannel: CHANNEL_ID,
238
- OriginatingTo: envelope.conversationId,
239
- ...(envelope.chatType
240
- ? { ChatType: envelope.chatType }
241
- : {}),
242
- ...(envelope.groupSubject
243
- ? { GroupSubject: envelope.groupSubject }
244
- : {}),
245
- ...(envelope.groupMembers
246
- ? { GroupMembers: envelope.groupMembers }
247
- : {}),
248
- ...(envelope.conversationLabel
249
- ? { ConversationLabel: envelope.conversationLabel }
250
- : {}),
251
- ...(envelope.senderName
252
- ? { SenderName: envelope.senderName }
253
- : {}),
254
- },
255
- cfg: ctx.cfg,
256
- dispatcherOptions: {
257
- deliver: async (payload, info) => {
258
- if (info?.kind !== "final")
259
- return true;
260
- const text = payload.text ?? payload.body;
261
- if (!text)
262
- return true;
263
- try {
264
- await client.sendRpc("messages/send", {
265
- conversationId: envelope.conversationId,
266
- parts: [{ type: "text", text }],
267
- });
268
- log?.info?.(`MoltZap: outbound reply to ${envelope.conversationId}: ${text.slice(0, 80)}`);
269
- return true;
270
- }
271
- catch (sendErr) {
272
- log?.error?.(`MoltZap: failed to send reply: ${sendErr}`);
273
- return false;
274
- }
275
- },
276
- },
277
- });
278
- if (!result.queuedFinal) {
279
- log?.debug?.(`MoltZap: dispatch completed without final reply for ${envelope.conversationId}`);
280
- }
281
- }
282
- catch (err) {
283
- log?.error?.(`MoltZap: dispatch error: ${err}`);
284
- }
285
- }
286
- })
287
- .catch((err) => {
288
- log?.error?.(`MoltZap: dispatch chain error: ${err}`);
289
- });
290
- }
291
- async function fetchMissedMessages(client, helloOk) {
292
- const hello = helloOk;
293
- const unreadCounts = hello?.unreadCounts;
294
- if (!unreadCounts)
295
- return;
296
- const sorted = Object.entries(unreadCounts)
297
- .filter(([, count]) => count > 0)
298
- .sort(([, a], [, b]) => b - a)
299
- .slice(0, 5);
300
- let totalFetched = 0;
301
- for (const [conversationId] of sorted) {
302
- try {
303
- const result = (await client.sendRpc("messages/list", {
304
- conversationId,
305
- limit: 50,
306
- }));
307
- for (const msg of result.messages) {
308
- dispatchMessage(msg, client);
309
- }
310
- totalFetched += result.messages.length;
311
- }
312
- catch {
313
- log?.warn?.(`MoltZap: failed to fetch missed messages for ${conversationId}`);
221
+ if (!result.queuedFinal) {
222
+ log?.debug?.(`MoltZap: dispatch completed without final reply for ${enriched.conversationId}`);
314
223
  }
315
224
  }
316
- if (totalFetched > 0) {
317
- log?.info?.(`MoltZap: fetched ${totalFetched} missed messages from ${sorted.length} conversations`);
225
+ catch (err) {
226
+ log?.error?.(`MoltZap: dispatch error: ${err}`);
318
227
  }
319
- }
320
- const eventHandlers = {
321
- "messages/received": (event) => {
322
- const message = extractMessage(event);
323
- if (message)
324
- dispatchMessage(message, client);
325
- },
326
- "messages/read": (event) => {
327
- const receipt = extractReadReceipt(event);
328
- if (receipt) {
329
- log?.debug?.(`MoltZap: read receipt from ${receipt.participant.id} in ${receipt.conversationId} up to seq ${receipt.seq}`);
330
- setStatus({ accountId, lastEventAt: Date.now() });
331
- }
332
- },
333
- "messages/delivered": (event) => {
334
- const delivery = extractDelivery(event);
335
- if (delivery) {
336
- log?.debug?.(`MoltZap: delivery for ${delivery.messageId} in ${delivery.conversationId}`);
337
- setStatus({ accountId, lastEventAt: Date.now() });
338
- }
339
- },
340
- "messages/reacted": (event) => {
341
- const reaction = extractReaction(event);
342
- if (reaction) {
343
- log?.debug?.(`MoltZap: reaction ${reaction.action} ${reaction.emoji} on ${reaction.messageId}`);
344
- setStatus({ accountId, lastEventAt: Date.now() });
345
- }
346
- },
347
- "messages/deleted": (event) => {
348
- const deletion = extractDeletion(event);
349
- if (deletion) {
350
- log?.debug?.(`MoltZap: message ${deletion.messageId} deleted in ${deletion.conversationId}`);
351
- setStatus({ accountId, lastEventAt: Date.now() });
228
+ });
229
+ // Forward non-message events for status/logging
230
+ service.on("rawEvent", (event) => {
231
+ switch (event.event) {
232
+ case EventNames.MessageRead: {
233
+ const receipt = extractReadReceipt(event);
234
+ if (receipt) {
235
+ log?.debug?.(`MoltZap: read receipt from ${receipt.participant.id} in ${receipt.conversationId} up to seq ${receipt.seq}`);
236
+ setStatus({ accountId, lastEventAt: Date.now() });
237
+ }
238
+ break;
352
239
  }
353
- },
354
- "conversations/created": (event) => {
355
- const created = extractConversationCreated(event);
356
- if (created) {
357
- conversationMetaCache.set(created.conversation.id, {
358
- type: created.conversation.type,
359
- name: created.conversation.name,
360
- participants: [],
361
- });
362
- log?.debug?.(`MoltZap: conversation created ${created.conversation.id}`);
363
- setStatus({ accountId, lastEventAt: Date.now() });
240
+ case EventNames.MessageDelivered: {
241
+ const delivery = extractDelivery(event);
242
+ if (delivery) {
243
+ log?.debug?.(`MoltZap: delivery for ${delivery.messageId} in ${delivery.conversationId}`);
244
+ setStatus({ accountId, lastEventAt: Date.now() });
245
+ }
246
+ break;
364
247
  }
365
- },
366
- "conversations/updated": (event) => {
367
- const updated = extractConversationUpdated(event);
368
- if (updated) {
369
- const existing = conversationMetaCache.get(updated.conversation.id);
370
- conversationMetaCache.set(updated.conversation.id, {
371
- type: updated.conversation.type,
372
- name: updated.conversation.name,
373
- participants: existing?.participants ?? [],
374
- });
375
- log?.debug?.(`MoltZap: conversation updated ${updated.conversation.id}`);
376
- setStatus({ accountId, lastEventAt: Date.now() });
248
+ case EventNames.MessageReacted: {
249
+ const reaction = extractReaction(event);
250
+ if (reaction) {
251
+ log?.debug?.(`MoltZap: reaction ${reaction.action} ${reaction.emoji} on ${reaction.messageId}`);
252
+ setStatus({ accountId, lastEventAt: Date.now() });
253
+ }
254
+ break;
377
255
  }
378
- },
379
- "contact/request": (event) => {
380
- const contact = extractContactRequest(event);
381
- if (contact) {
382
- log?.debug?.(`MoltZap: contact request from ${contact.contact.requesterId}`);
383
- setStatus({ accountId, lastEventAt: Date.now() });
256
+ case EventNames.MessageDeleted: {
257
+ const deletion = extractDeletion(event);
258
+ if (deletion) {
259
+ log?.debug?.(`MoltZap: message ${deletion.messageId} deleted in ${deletion.conversationId}`);
260
+ setStatus({ accountId, lastEventAt: Date.now() });
261
+ }
262
+ break;
384
263
  }
385
- },
386
- "contact/accepted": (event) => {
387
- const contact = extractContactAccepted(event);
388
- if (contact) {
389
- log?.debug?.(`MoltZap: contact accepted ${contact.contact.id}`);
390
- setStatus({ accountId, lastEventAt: Date.now() });
264
+ case EventNames.ConversationCreated: {
265
+ const created = extractConversationCreated(event);
266
+ if (created) {
267
+ log?.debug?.(`MoltZap: conversation created ${created.conversation.id}`);
268
+ setStatus({ accountId, lastEventAt: Date.now() });
269
+ }
270
+ break;
391
271
  }
392
- },
393
- "presence/changed": (event) => {
394
- const presence = extractPresenceChanged(event);
395
- if (presence) {
396
- log?.debug?.(`MoltZap: ${presence.participant.id} is now ${presence.status}`);
397
- setStatus({ accountId, lastEventAt: Date.now() });
272
+ case EventNames.ConversationUpdated: {
273
+ const updated = extractConversationUpdated(event);
274
+ if (updated) {
275
+ log?.debug?.(`MoltZap: conversation updated ${updated.conversation.id}`);
276
+ setStatus({ accountId, lastEventAt: Date.now() });
277
+ }
278
+ break;
398
279
  }
399
- },
400
- "typing/indicator": (event) => {
401
- const typing = extractTypingIndicator(event);
402
- if (typing) {
403
- log?.debug?.(`MoltZap: typing in ${typing.conversationId} by ${typing.participant.id}`);
280
+ case EventNames.ContactRequest: {
281
+ const contact = extractContactRequest(event);
282
+ if (contact) {
283
+ log?.debug?.(`MoltZap: contact request from ${contact.contact.requesterId}`);
284
+ setStatus({ accountId, lastEventAt: Date.now() });
285
+ }
286
+ break;
404
287
  }
405
- },
406
- };
407
- const client = new MoltZapWsClient({
408
- serverUrl: account.serverUrl,
409
- agentKey: account.apiKey,
410
- logger: log
411
- ? {
412
- info: log.info ?? (() => { }),
413
- warn: log.warn ?? (() => { }),
414
- error: log.error ?? (() => { }),
288
+ case EventNames.ContactAccepted: {
289
+ const contact = extractContactAccepted(event);
290
+ if (contact) {
291
+ log?.debug?.(`MoltZap: contact accepted ${contact.contact.id}`);
292
+ setStatus({ accountId, lastEventAt: Date.now() });
293
+ }
294
+ break;
415
295
  }
416
- : undefined,
417
- onEvent: (event) => {
418
- const handler = eventHandlers[event.event];
419
- if (handler) {
420
- handler(event);
296
+ case EventNames.PresenceChanged: {
297
+ const presence = extractPresenceChanged(event);
298
+ if (presence) {
299
+ log?.debug?.(`MoltZap: ${presence.participant.id} is now ${presence.status}`);
300
+ setStatus({ accountId, lastEventAt: Date.now() });
301
+ }
302
+ break;
421
303
  }
422
- else {
423
- log?.debug?.(`MoltZap: unhandled event type: ${event.event}`);
304
+ case EventNames.TypingIndicator: {
305
+ const typing = extractTypingIndicator(event);
306
+ if (typing) {
307
+ log?.debug?.(`MoltZap: typing in ${typing.conversationId} by ${typing.participant.id}`);
308
+ }
309
+ break;
424
310
  }
425
- },
426
- onDisconnect: () => {
427
- log?.warn?.("MoltZap: disconnected");
428
- setStatus({
429
- accountId,
430
- connected: false,
431
- lastDisconnect: { at: Date.now() },
432
- });
433
- },
434
- onReconnect: (helloOk) => {
435
- log?.info?.("MoltZap: reconnected");
436
- setStatus({
437
- accountId,
438
- connected: true,
439
- lastConnectedAt: Date.now(),
440
- });
441
- populateConversationCache(helloOk);
442
- void fetchMissedMessages(client, helloOk);
443
- },
444
- });
445
- function populateConversationCache(helloOk) {
446
- const hello = helloOk;
447
- if (!hello?.conversations)
448
- return;
449
- for (const conv of hello.conversations) {
450
- conversationMetaCache.set(conv.id, {
451
- type: conv.type,
452
- name: conv.name,
453
- participants: (conv.participants ?? []).map((p) => `${p.type}:${p.id}`),
454
- });
455
311
  }
312
+ });
313
+ core.onDisconnect(() => {
314
+ log?.warn?.("MoltZap: disconnected");
315
+ setStatus({
316
+ accountId,
317
+ connected: false,
318
+ lastDisconnect: { at: Date.now() },
319
+ });
320
+ });
321
+ core.onReconnect(() => {
322
+ log?.info?.("MoltZap: reconnected");
323
+ setStatus({
324
+ accountId,
325
+ connected: true,
326
+ lastConnectedAt: Date.now(),
327
+ });
328
+ });
329
+ activeClients.set(accountId, service);
330
+ if (abortSignal.aborted) {
331
+ await core.disconnect();
332
+ activeClients.delete(accountId);
333
+ return;
456
334
  }
457
- activeClients.set(accountId, client);
458
335
  abortSignal.addEventListener("abort", () => {
459
- client.close();
336
+ void core.disconnect();
460
337
  activeClients.delete(accountId);
461
338
  }, { once: true });
462
339
  try {
463
- const helloOk = await client.connect();
464
- ownAgentId = helloOk?.agentId;
465
- log?.info?.(`MoltZap: connected as ${account.agentName} (${ownAgentId})`);
340
+ await core.connect();
341
+ service.startSocketServer();
342
+ log?.info?.(`MoltZap: connected as ${account.agentName} (${service.ownAgentId})`);
466
343
  setStatus({
467
344
  accountId,
468
345
  connected: true,
469
346
  lastConnectedAt: Date.now(),
470
347
  });
471
- populateConversationCache(helloOk);
472
348
  // Keep the gateway task alive until abort.
473
349
  await new Promise((resolve) => {
474
350
  abortSignal.addEventListener("abort", () => resolve(), {
@@ -482,12 +358,11 @@ export const moltzapChannelPlugin = {
482
358
  }
483
359
  },
484
360
  async stopAccount(ctx) {
485
- const client = activeClients.get(ctx.accountId);
486
- if (client) {
361
+ const service = activeClients.get(ctx.accountId);
362
+ if (service) {
487
363
  ctx.log?.info?.("MoltZap: stopping");
488
- client.close();
364
+ service.close();
489
365
  activeClients.delete(ctx.accountId);
490
- agentConversationCache.delete(ctx.accountId);
491
366
  }
492
367
  },
493
368
  },
@@ -508,43 +383,28 @@ export const moltzapChannelPlugin = {
508
383
  },
509
384
  async sendText(ctx) {
510
385
  const accountId = ctx.accountId ?? DEFAULT_ACCOUNT_ID;
511
- const client = activeClients.get(accountId);
512
- if (!client) {
386
+ const service = activeClients.get(accountId);
387
+ if (!service) {
513
388
  return {
514
389
  ok: false,
515
390
  error: new Error("MoltZap client not connected"),
516
391
  };
517
392
  }
518
393
  try {
519
- let conversationId;
520
394
  if (ctx.to.startsWith(TARGET_PREFIX_AGENT)) {
521
395
  const agentName = ctx.to.slice(TARGET_PREFIX_AGENT.length);
522
- const accountCache = agentConversationCache.get(accountId) ?? new Map();
523
- conversationId = accountCache.get(agentName);
524
- if (!conversationId) {
525
- const lookupResult = (await client.sendRpc("agents/lookupByName", {
526
- name: agentName,
527
- }));
528
- const createResult = (await client.sendRpc("conversations/create", {
529
- type: "dm",
530
- participants: [{ type: "agent", id: lookupResult.agent.id }],
531
- }));
532
- conversationId = createResult.conversation.id;
533
- accountCache.set(agentName, conversationId);
534
- agentConversationCache.set(accountId, accountCache);
535
- }
536
- }
537
- else if (ctx.to.startsWith(TARGET_PREFIX_CONV)) {
538
- conversationId = ctx.to.slice(TARGET_PREFIX_CONV.length);
396
+ await service.sendToAgent(agentName, ctx.text, {
397
+ replyTo: ctx.replyToId,
398
+ });
539
399
  }
540
400
  else {
541
- conversationId = ctx.to;
401
+ const conversationId = ctx.to.startsWith(TARGET_PREFIX_CONV)
402
+ ? ctx.to.slice(TARGET_PREFIX_CONV.length)
403
+ : ctx.to;
404
+ await service.send(conversationId, ctx.text, {
405
+ replyTo: ctx.replyToId,
406
+ });
542
407
  }
543
- await client.sendRpc("messages/send", {
544
- conversationId,
545
- parts: [{ type: "text", text: ctx.text }],
546
- ...(ctx.replyToId ? { replyToId: ctx.replyToId } : {}),
547
- });
548
408
  return { ok: true };
549
409
  }
550
410
  catch (err) {