@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.
- package/LICENSE +21 -0
- package/dist/mapping.d.ts +0 -35
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +0 -36
- package/dist/mapping.js.map +1 -1
- package/dist/openclaw-entry.d.ts +1 -3
- package/dist/openclaw-entry.d.ts.map +1 -1
- package/dist/openclaw-entry.js +199 -339
- package/dist/openclaw-entry.js.map +1 -1
- package/dist/test-utils/container-core.d.ts +48 -0
- package/dist/test-utils/container-core.d.ts.map +1 -0
- package/dist/test-utils/container-core.js +242 -0
- package/dist/test-utils/container-core.js.map +1 -0
- package/package.json +11 -8
- package/dist/channel.d.ts +0 -24
- package/dist/channel.d.ts.map +0 -1
- package/dist/channel.js +0 -58
- package/dist/channel.js.map +0 -1
- package/dist/config.d.ts +0 -24
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -35
- package/dist/config.js.map +0 -1
- package/dist/ws-client.d.ts +0 -41
- package/dist/ws-client.d.ts.map +0 -1
- package/dist/ws-client.js +0 -138
- package/dist/ws-client.js.map +0 -1
package/dist/openclaw-entry.js
CHANGED
|
@@ -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 {
|
|
11
|
-
import {
|
|
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
|
|
77
|
-
if (!
|
|
75
|
+
const service = activeClients.get(params.accountId ?? DEFAULT_ACCOUNT_ID);
|
|
76
|
+
if (!service)
|
|
78
77
|
return [];
|
|
79
78
|
try {
|
|
80
|
-
const { contacts } = (await
|
|
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
|
|
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
|
|
101
|
-
if (!
|
|
99
|
+
const service = activeClients.get(params.accountId ?? DEFAULT_ACCOUNT_ID);
|
|
100
|
+
if (!service)
|
|
102
101
|
return [];
|
|
103
102
|
try {
|
|
104
|
-
const { conversations } = (await
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
? "group"
|
|
211
|
-
:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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 (
|
|
225
|
-
?.
|
|
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
|
-
|
|
317
|
-
log?.
|
|
225
|
+
catch (err) {
|
|
226
|
+
log?.error?.(`MoltZap: dispatch error: ${err}`);
|
|
318
227
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
:
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
423
|
-
|
|
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
|
-
|
|
336
|
+
void core.disconnect();
|
|
460
337
|
activeClients.delete(accountId);
|
|
461
338
|
}, { once: true });
|
|
462
339
|
try {
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
486
|
-
if (
|
|
361
|
+
const service = activeClients.get(ctx.accountId);
|
|
362
|
+
if (service) {
|
|
487
363
|
ctx.log?.info?.("MoltZap: stopping");
|
|
488
|
-
|
|
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
|
|
512
|
-
if (!
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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) {
|