@alfe.ai/openclaw-chat 0.0.18 → 0.0.20
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/plugin.d.cts +7 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin2.cjs +232 -27
- package/dist/plugin2.js +232 -27
- package/openclaw.plugin.json +0 -1
- package/package.json +2 -2
package/dist/plugin.d.cts
CHANGED
|
@@ -225,6 +225,13 @@ interface PluginApi {
|
|
|
225
225
|
config?: Record<string, unknown>;
|
|
226
226
|
runtime?: PluginRuntime;
|
|
227
227
|
registerChannel(channel: ReturnType<typeof createAlfeChannelPlugin>): void;
|
|
228
|
+
registerTool?(tool: {
|
|
229
|
+
name: string;
|
|
230
|
+
description: string;
|
|
231
|
+
label: string;
|
|
232
|
+
parameters: Record<string, unknown>;
|
|
233
|
+
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
234
|
+
}): void;
|
|
228
235
|
registerGatewayMethod?(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
|
|
229
236
|
on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
|
|
230
237
|
priority?: number;
|
package/dist/plugin.d.ts
CHANGED
|
@@ -225,6 +225,13 @@ interface PluginApi {
|
|
|
225
225
|
config?: Record<string, unknown>;
|
|
226
226
|
runtime?: PluginRuntime;
|
|
227
227
|
registerChannel(channel: ReturnType<typeof createAlfeChannelPlugin>): void;
|
|
228
|
+
registerTool?(tool: {
|
|
229
|
+
name: string;
|
|
230
|
+
description: string;
|
|
231
|
+
label: string;
|
|
232
|
+
parameters: Record<string, unknown>;
|
|
233
|
+
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
234
|
+
}): void;
|
|
228
235
|
registerGatewayMethod?(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
|
|
229
236
|
on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
|
|
230
237
|
priority?: number;
|
package/dist/plugin2.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
let node_module = require("node:module");
|
|
2
|
-
let _alfe_ai_chat = require("@alfe.ai/chat");
|
|
3
|
-
let _alfe_ai_config = require("@alfe.ai/config");
|
|
4
|
-
let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
|
|
5
2
|
let node_fs_promises = require("node:fs/promises");
|
|
6
3
|
let node_path = require("node:path");
|
|
7
4
|
let node_os = require("node:os");
|
|
5
|
+
let _alfe_ai_chat = require("@alfe.ai/chat");
|
|
6
|
+
let _alfe_ai_config = require("@alfe.ai/config");
|
|
7
|
+
let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
//#region src/alfe-channel.ts
|
|
10
10
|
const CHANNEL_ID = "alfe";
|
|
@@ -151,7 +151,7 @@ function isAlfeSessionKey(key) {
|
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
153
|
* Extract the channel mode from a standardized session key or conversationId.
|
|
154
|
-
* Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat') or fallback.
|
|
154
|
+
* Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat', 'a2a') or fallback.
|
|
155
155
|
*/
|
|
156
156
|
function extractChannelMode(conversationId, fallback = "chat") {
|
|
157
157
|
return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
|
|
@@ -181,7 +181,6 @@ function sessionPath(sessionId) {
|
|
|
181
181
|
}
|
|
182
182
|
async function cleanupOldSessions() {
|
|
183
183
|
if (Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) return;
|
|
184
|
-
lastCleanupAt = Date.now();
|
|
185
184
|
try {
|
|
186
185
|
const jsonFiles = (await (0, node_fs_promises.readdir)(SESSIONS_DIR)).filter((f) => f.endsWith(".json"));
|
|
187
186
|
if (jsonFiles.length <= MAX_SESSIONS) {
|
|
@@ -211,6 +210,7 @@ async function cleanupOldSessions() {
|
|
|
211
210
|
remaining--;
|
|
212
211
|
} catch {}
|
|
213
212
|
}
|
|
213
|
+
lastCleanupAt = Date.now();
|
|
214
214
|
} catch {}
|
|
215
215
|
}
|
|
216
216
|
async function getSession(sessionId) {
|
|
@@ -242,17 +242,24 @@ async function createSession(sessionId, agentId, channel, tenantId, userId) {
|
|
|
242
242
|
cleanupOldSessions();
|
|
243
243
|
return session;
|
|
244
244
|
}
|
|
245
|
+
const writeLocks = /* @__PURE__ */ new Map();
|
|
245
246
|
async function addMessage(sessionId, role, content, senderId, senderName) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
247
|
+
const next = (writeLocks.get(sessionId) ?? Promise.resolve()).then(async () => {
|
|
248
|
+
const session = await getSession(sessionId);
|
|
249
|
+
if (!session) return;
|
|
250
|
+
session.messages.push({
|
|
251
|
+
role,
|
|
252
|
+
content,
|
|
253
|
+
timestamp: Date.now(),
|
|
254
|
+
...senderId ? { senderId } : {},
|
|
255
|
+
...senderName ? { senderName } : {}
|
|
256
|
+
});
|
|
257
|
+
await saveSession(session);
|
|
258
|
+
}).finally(() => {
|
|
259
|
+
if (writeLocks.get(sessionId) === next) writeLocks.delete(sessionId);
|
|
254
260
|
});
|
|
255
|
-
|
|
261
|
+
writeLocks.set(sessionId, next);
|
|
262
|
+
await next;
|
|
256
263
|
}
|
|
257
264
|
async function listSessions(filters, limit = 50) {
|
|
258
265
|
await ensureDir();
|
|
@@ -289,6 +296,112 @@ async function listSessions(filters, limit = 50) {
|
|
|
289
296
|
return summaries.slice(0, limit);
|
|
290
297
|
}
|
|
291
298
|
//#endregion
|
|
299
|
+
//#region src/a2a-tools.ts
|
|
300
|
+
function ok(result) {
|
|
301
|
+
return { content: [{
|
|
302
|
+
type: "text",
|
|
303
|
+
text: JSON.stringify(result)
|
|
304
|
+
}] };
|
|
305
|
+
}
|
|
306
|
+
function errResult(message) {
|
|
307
|
+
return { content: [{
|
|
308
|
+
type: "text",
|
|
309
|
+
text: JSON.stringify({ error: message })
|
|
310
|
+
}] };
|
|
311
|
+
}
|
|
312
|
+
function defineTool(def) {
|
|
313
|
+
return {
|
|
314
|
+
name: def.name,
|
|
315
|
+
description: def.description,
|
|
316
|
+
label: def.name,
|
|
317
|
+
parameters: def.parameters,
|
|
318
|
+
execute: async (_toolCallId, params) => {
|
|
319
|
+
try {
|
|
320
|
+
return ok(await def.handler(params));
|
|
321
|
+
} catch (e) {
|
|
322
|
+
return errResult(e.message);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function buildA2ATools(chatClient, log) {
|
|
328
|
+
return [
|
|
329
|
+
defineTool({
|
|
330
|
+
name: "list_agents",
|
|
331
|
+
description: "List other agents in your organization that you can communicate with.",
|
|
332
|
+
parameters: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {},
|
|
335
|
+
required: []
|
|
336
|
+
},
|
|
337
|
+
handler: async () => {
|
|
338
|
+
log.info("list_agents tool called");
|
|
339
|
+
return await chatClient.sendRequest("a2a.list-agents", {});
|
|
340
|
+
}
|
|
341
|
+
}),
|
|
342
|
+
defineTool({
|
|
343
|
+
name: "message_agent",
|
|
344
|
+
description: "Send a message to another agent. Starts a new conversation thread, or continues an existing one. The conversation will bounce back and forth automatically until one agent calls end_conversation() or says [RESOLVED].",
|
|
345
|
+
parameters: {
|
|
346
|
+
type: "object",
|
|
347
|
+
properties: {
|
|
348
|
+
agent_id: {
|
|
349
|
+
type: "string",
|
|
350
|
+
description: "Target agent ID (use list_agents to find available agents)"
|
|
351
|
+
},
|
|
352
|
+
message: {
|
|
353
|
+
type: "string",
|
|
354
|
+
description: "Message to send to the agent"
|
|
355
|
+
},
|
|
356
|
+
thread_id: {
|
|
357
|
+
type: "string",
|
|
358
|
+
description: "Continue an existing conversation thread (omit to start a new one)"
|
|
359
|
+
},
|
|
360
|
+
max_depth: {
|
|
361
|
+
type: "number",
|
|
362
|
+
description: "Maximum number of back-and-forth exchanges (default: 10)"
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
required: ["agent_id", "message"]
|
|
366
|
+
},
|
|
367
|
+
handler: async (params) => {
|
|
368
|
+
const agentId = params.agent_id;
|
|
369
|
+
const message = params.message;
|
|
370
|
+
const threadId = params.thread_id;
|
|
371
|
+
const maxDepth = params.max_depth ?? 10;
|
|
372
|
+
if (!agentId || !message) throw new Error("agent_id and message are required");
|
|
373
|
+
log.info(`message_agent tool: sending to ${agentId}`);
|
|
374
|
+
return await chatClient.sendRequest("a2a.send", {
|
|
375
|
+
targetAgentId: agentId,
|
|
376
|
+
message,
|
|
377
|
+
threadId,
|
|
378
|
+
maxDepth
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}),
|
|
382
|
+
defineTool({
|
|
383
|
+
name: "end_conversation",
|
|
384
|
+
description: "End the current agent-to-agent conversation. Call this when the discussion is resolved and no further exchanges are needed.",
|
|
385
|
+
parameters: {
|
|
386
|
+
type: "object",
|
|
387
|
+
properties: { summary: {
|
|
388
|
+
type: "string",
|
|
389
|
+
description: "Brief summary of what was discussed or resolved"
|
|
390
|
+
} },
|
|
391
|
+
required: []
|
|
392
|
+
},
|
|
393
|
+
handler: (params) => {
|
|
394
|
+
const summary = params.summary;
|
|
395
|
+
log.info(`end_conversation tool called${summary ? `: ${summary}` : ""}`);
|
|
396
|
+
return Promise.resolve({
|
|
397
|
+
status: "conversation_ended",
|
|
398
|
+
summary
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
];
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
292
405
|
//#region src/plugin.ts
|
|
293
406
|
/**
|
|
294
407
|
* @alfe.ai/openclaw-chat — OpenClaw chat channel plugin.
|
|
@@ -325,6 +438,45 @@ let pluginRuntime = null;
|
|
|
325
438
|
let chatClient = null;
|
|
326
439
|
let connectingPromise = null;
|
|
327
440
|
let metricsClient = null;
|
|
441
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
442
|
+
const DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
443
|
+
async function downloadAttachments(attachments, log) {
|
|
444
|
+
const attachDir = (0, node_path.join)((0, node_os.homedir)(), ".alfe", "attachments");
|
|
445
|
+
await (0, node_fs_promises.mkdir)(attachDir, { recursive: true });
|
|
446
|
+
const results = [];
|
|
447
|
+
for (const att of attachments) {
|
|
448
|
+
const filename = (att.filename ?? att.id).replace(/[/\\]/g, "_").replace(/\.\./g, "_").replace(/\0/g, "");
|
|
449
|
+
const localPath = (0, node_path.join)(attachDir, `${att.id}_${filename}`);
|
|
450
|
+
const controller = new AbortController();
|
|
451
|
+
const timeout = setTimeout(() => {
|
|
452
|
+
controller.abort();
|
|
453
|
+
}, DOWNLOAD_TIMEOUT_MS);
|
|
454
|
+
try {
|
|
455
|
+
const res = await fetch(att.url, { signal: controller.signal });
|
|
456
|
+
if (!res.ok) {
|
|
457
|
+
log.warn(`Failed to download attachment ${att.id}: ${String(res.status)}`);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
461
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
462
|
+
log.warn(`Attachment ${att.id} exceeds max size (${String(buffer.length)} bytes) — skipping`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
await (0, node_fs_promises.writeFile)(localPath, buffer);
|
|
466
|
+
results.push({
|
|
467
|
+
localPath,
|
|
468
|
+
filename,
|
|
469
|
+
mimeType: att.mimeType
|
|
470
|
+
});
|
|
471
|
+
log.info(`Downloaded attachment: ${localPath} (${String(buffer.length)} bytes)`);
|
|
472
|
+
} catch (err) {
|
|
473
|
+
log.error(`Failed to download attachment ${att.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
474
|
+
} finally {
|
|
475
|
+
clearTimeout(timeout);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return results;
|
|
479
|
+
}
|
|
328
480
|
async function handleAgentRequest(request, log) {
|
|
329
481
|
const runtime = pluginRuntime;
|
|
330
482
|
if (!runtime) {
|
|
@@ -335,8 +487,9 @@ async function handleAgentRequest(request, log) {
|
|
|
335
487
|
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
336
488
|
return;
|
|
337
489
|
}
|
|
338
|
-
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName } = request.params;
|
|
339
|
-
|
|
490
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName, attachments: rawAttachments, a2a } = request.params;
|
|
491
|
+
const isA2A = !!a2a;
|
|
492
|
+
if (!message && !rawAttachments?.length) {
|
|
340
493
|
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
341
494
|
return;
|
|
342
495
|
}
|
|
@@ -360,11 +513,15 @@ async function handleAgentRequest(request, log) {
|
|
|
360
513
|
});
|
|
361
514
|
});
|
|
362
515
|
try {
|
|
363
|
-
const
|
|
364
|
-
const
|
|
516
|
+
const downloadedFiles = rawAttachments?.length ? await downloadAttachments(rawAttachments, log) : [];
|
|
517
|
+
const bodyForAgent = downloadedFiles.length ? `${message || ""}\n\n[Attached files:\n${downloadedFiles.map((f) => `- ${f.filename}: ${f.localPath}`).join("\n")}]` : void 0;
|
|
518
|
+
const channelMode = isA2A ? "a2a" : extractChannelMode(conversationId ?? "", clientType ?? "chat");
|
|
519
|
+
const channelLabel = channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : channelMode === "a2a" ? "Agent" : "Alfe";
|
|
365
520
|
const shortConvId = conversationId?.slice(-8) ?? "";
|
|
366
521
|
const userLabel = displayName ?? userId ?? senderId;
|
|
367
522
|
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
523
|
+
let a2aResponseBuffer = "";
|
|
524
|
+
let a2aResolved = false;
|
|
368
525
|
resolvedOpenClawKey = (await dispatchInbound({
|
|
369
526
|
cfg,
|
|
370
527
|
runtime: { channel: runtime.channel },
|
|
@@ -382,7 +539,8 @@ async function handleAgentRequest(request, log) {
|
|
|
382
539
|
senderAddress: `user:${senderId}`,
|
|
383
540
|
recipientAddress: "agent",
|
|
384
541
|
conversationLabel,
|
|
385
|
-
rawBody: message,
|
|
542
|
+
rawBody: message || "",
|
|
543
|
+
bodyForAgent,
|
|
386
544
|
messageId: request.id,
|
|
387
545
|
timestamp: Date.now(),
|
|
388
546
|
extraContext: {
|
|
@@ -390,22 +548,41 @@ async function handleAgentRequest(request, log) {
|
|
|
390
548
|
...clientType ? { ClientType: clientType } : {},
|
|
391
549
|
...conversationId ? { ConversationId: conversationId } : {},
|
|
392
550
|
...displayName ? { SenderName: displayName } : {},
|
|
393
|
-
ChannelMode: channelMode
|
|
551
|
+
ChannelMode: channelMode,
|
|
552
|
+
...isA2A ? {
|
|
553
|
+
CallerType: "agent",
|
|
554
|
+
CallerAgentId: a2a.sourceAgentId,
|
|
555
|
+
CallerAgentName: a2a.sourceAgentName,
|
|
556
|
+
InteractionDepth: String(a2a.depth),
|
|
557
|
+
A2ASystemPrompt: [
|
|
558
|
+
`This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
|
|
559
|
+
"Rules:",
|
|
560
|
+
"- Only respond if you have new information, a question, or an action to coordinate.",
|
|
561
|
+
"- If the conversation is complete, call the end_conversation() tool OR end your message with [RESOLVED].",
|
|
562
|
+
"- Do NOT respond just to acknowledge — that creates infinite loops."
|
|
563
|
+
].join("\n")
|
|
564
|
+
} : {}
|
|
394
565
|
},
|
|
395
566
|
deliver: async (payload) => {
|
|
396
567
|
const responseText = payload.text ?? "";
|
|
568
|
+
const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
|
|
397
569
|
await addMessage(sessionId, "assistant", responseText);
|
|
570
|
+
if (isA2A) {
|
|
571
|
+
a2aResponseBuffer += responseText;
|
|
572
|
+
if (responseText.includes("\"status\":\"conversation_ended\"") || responseText.includes("\"status\": \"conversation_ended\"")) a2aResolved = true;
|
|
573
|
+
}
|
|
398
574
|
chatClient?.notify("agent-message", {
|
|
399
575
|
conversationId: conversationId ?? legacySessionKey,
|
|
400
576
|
text: responseText,
|
|
401
|
-
sessionKey: resolvedOpenClawKey ?? legacySessionKey
|
|
577
|
+
sessionKey: resolvedOpenClawKey ?? legacySessionKey,
|
|
578
|
+
...mediaUrls.length ? { mediaUrls } : {}
|
|
402
579
|
});
|
|
403
580
|
if (metricsClient && userId) metricsClient.recordActivity({
|
|
404
581
|
userId,
|
|
405
582
|
channel: "alfe",
|
|
406
583
|
role: "assistant"
|
|
407
584
|
}).catch((err) => {
|
|
408
|
-
log.
|
|
585
|
+
log.warn(`Metrics report failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
409
586
|
});
|
|
410
587
|
},
|
|
411
588
|
onRecordError: (err) => {
|
|
@@ -415,6 +592,12 @@ async function handleAgentRequest(request, log) {
|
|
|
415
592
|
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
416
593
|
}
|
|
417
594
|
})).route.sessionKey;
|
|
595
|
+
if (isA2A && a2aResponseBuffer) chatClient?.notify("a2a-complete", {
|
|
596
|
+
conversationId: conversationId ?? legacySessionKey,
|
|
597
|
+
fullText: a2aResponseBuffer,
|
|
598
|
+
a2a,
|
|
599
|
+
resolved: a2aResolved
|
|
600
|
+
});
|
|
418
601
|
chatClient?.sendResponse(request.id, true, { sessionKey: resolvedOpenClawKey });
|
|
419
602
|
log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
|
|
420
603
|
} catch (err) {
|
|
@@ -484,10 +667,15 @@ const plugin = {
|
|
|
484
667
|
activate(api) {
|
|
485
668
|
const log = api.logger;
|
|
486
669
|
const alreadyActivated = globalThis.__alfeChatPluginActivated === true;
|
|
487
|
-
|
|
670
|
+
const isGatewayMode = !!api.runtime;
|
|
488
671
|
const alfeChannel = createAlfeChannelPlugin();
|
|
489
672
|
api.registerChannel(alfeChannel);
|
|
490
673
|
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
674
|
+
if (!isGatewayMode) {
|
|
675
|
+
log.debug("Management command context — skipping persistent resource init");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
globalThis.__alfeChatPluginActivated = true;
|
|
491
679
|
if (!alreadyActivated) {
|
|
492
680
|
log.info("Chat plugin registering...");
|
|
493
681
|
resolveOpenClawSdk(log);
|
|
@@ -515,10 +703,17 @@ const plugin = {
|
|
|
515
703
|
wsUrl: chatWsUrl,
|
|
516
704
|
apiKey,
|
|
517
705
|
onRequest: (request) => {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
706
|
+
const handle = async () => {
|
|
707
|
+
if (request.method === "agent") await handleAgentRequest(request, log);
|
|
708
|
+
else if (request.method === "sessions.list") await handleSessionsList(request, log);
|
|
709
|
+
else if (request.method === "sessions.get") await handleSessionsGet(request, log);
|
|
710
|
+
else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
|
|
711
|
+
};
|
|
712
|
+
handle().catch((err) => {
|
|
713
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
714
|
+
log.error(`Request handler crashed: ${errMsg}`);
|
|
715
|
+
chatClient?.sendResponse(request.id, false, { message: "Internal error" });
|
|
716
|
+
});
|
|
522
717
|
},
|
|
523
718
|
onConnectionChange: (connected) => {
|
|
524
719
|
log.info(`Chat service connection: ${connected ? "connected" : "disconnected"}`);
|
|
@@ -567,20 +762,30 @@ const plugin = {
|
|
|
567
762
|
});
|
|
568
763
|
log.info("Registered gateway RPC methods: sessions.list, sessions.get");
|
|
569
764
|
}
|
|
765
|
+
if (!alreadyActivated && typeof api.registerTool === "function") connectingPromise?.then(() => {
|
|
766
|
+
if (chatClient && typeof api.registerTool === "function") {
|
|
767
|
+
const a2aTools = buildA2ATools(chatClient, log);
|
|
768
|
+
for (const tool of a2aTools) api.registerTool(tool);
|
|
769
|
+
log.info(`Registered ${String(a2aTools.length)} agent-to-agent tools`);
|
|
770
|
+
}
|
|
771
|
+
}).catch(() => {});
|
|
570
772
|
if (!alreadyActivated) {
|
|
571
773
|
api.on("session_start", async (...eventArgs) => {
|
|
774
|
+
if (!pluginRuntime) return;
|
|
572
775
|
const key = eventArgs[0].sessionKey;
|
|
573
776
|
if (!key || isAlfeSessionKey(key)) return;
|
|
574
777
|
log.info(`Chat session starting: ${key}`);
|
|
575
778
|
await createSession(key, "", "alfe");
|
|
576
779
|
}, { priority: 50 });
|
|
577
780
|
api.on("message_received", async (...eventArgs) => {
|
|
781
|
+
if (!pluginRuntime) return;
|
|
578
782
|
const event = eventArgs[0];
|
|
579
783
|
const ctx = eventArgs[1];
|
|
580
784
|
if (!ctx.conversationId || ctx.channelId === "alfe") return;
|
|
581
785
|
await addMessage(ctx.conversationId, "user", event.content, event.from);
|
|
582
786
|
});
|
|
583
787
|
api.on("session_end", (...eventArgs) => {
|
|
788
|
+
if (!pluginRuntime) return;
|
|
584
789
|
const key = eventArgs[0].sessionKey;
|
|
585
790
|
if (!key || !isAlfeSessionKey(key)) return;
|
|
586
791
|
log.info(`Chat session ending: ${key}`);
|
package/dist/plugin2.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
|
|
3
|
-
import { resolveConfig } from "@alfe.ai/config";
|
|
4
|
-
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
5
2
|
import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
3
|
import { join } from "node:path";
|
|
7
4
|
import { homedir } from "node:os";
|
|
5
|
+
import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
|
|
6
|
+
import { resolveConfig } from "@alfe.ai/config";
|
|
7
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
//#region src/alfe-channel.ts
|
|
10
10
|
const CHANNEL_ID = "alfe";
|
|
@@ -151,7 +151,7 @@ function isAlfeSessionKey(key) {
|
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
153
|
* Extract the channel mode from a standardized session key or conversationId.
|
|
154
|
-
* Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat') or fallback.
|
|
154
|
+
* Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat', 'a2a') or fallback.
|
|
155
155
|
*/
|
|
156
156
|
function extractChannelMode(conversationId, fallback = "chat") {
|
|
157
157
|
return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
|
|
@@ -181,7 +181,6 @@ function sessionPath(sessionId) {
|
|
|
181
181
|
}
|
|
182
182
|
async function cleanupOldSessions() {
|
|
183
183
|
if (Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) return;
|
|
184
|
-
lastCleanupAt = Date.now();
|
|
185
184
|
try {
|
|
186
185
|
const jsonFiles = (await readdir(SESSIONS_DIR)).filter((f) => f.endsWith(".json"));
|
|
187
186
|
if (jsonFiles.length <= MAX_SESSIONS) {
|
|
@@ -211,6 +210,7 @@ async function cleanupOldSessions() {
|
|
|
211
210
|
remaining--;
|
|
212
211
|
} catch {}
|
|
213
212
|
}
|
|
213
|
+
lastCleanupAt = Date.now();
|
|
214
214
|
} catch {}
|
|
215
215
|
}
|
|
216
216
|
async function getSession(sessionId) {
|
|
@@ -242,17 +242,24 @@ async function createSession(sessionId, agentId, channel, tenantId, userId) {
|
|
|
242
242
|
cleanupOldSessions();
|
|
243
243
|
return session;
|
|
244
244
|
}
|
|
245
|
+
const writeLocks = /* @__PURE__ */ new Map();
|
|
245
246
|
async function addMessage(sessionId, role, content, senderId, senderName) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
247
|
+
const next = (writeLocks.get(sessionId) ?? Promise.resolve()).then(async () => {
|
|
248
|
+
const session = await getSession(sessionId);
|
|
249
|
+
if (!session) return;
|
|
250
|
+
session.messages.push({
|
|
251
|
+
role,
|
|
252
|
+
content,
|
|
253
|
+
timestamp: Date.now(),
|
|
254
|
+
...senderId ? { senderId } : {},
|
|
255
|
+
...senderName ? { senderName } : {}
|
|
256
|
+
});
|
|
257
|
+
await saveSession(session);
|
|
258
|
+
}).finally(() => {
|
|
259
|
+
if (writeLocks.get(sessionId) === next) writeLocks.delete(sessionId);
|
|
254
260
|
});
|
|
255
|
-
|
|
261
|
+
writeLocks.set(sessionId, next);
|
|
262
|
+
await next;
|
|
256
263
|
}
|
|
257
264
|
async function listSessions(filters, limit = 50) {
|
|
258
265
|
await ensureDir();
|
|
@@ -289,6 +296,112 @@ async function listSessions(filters, limit = 50) {
|
|
|
289
296
|
return summaries.slice(0, limit);
|
|
290
297
|
}
|
|
291
298
|
//#endregion
|
|
299
|
+
//#region src/a2a-tools.ts
|
|
300
|
+
function ok(result) {
|
|
301
|
+
return { content: [{
|
|
302
|
+
type: "text",
|
|
303
|
+
text: JSON.stringify(result)
|
|
304
|
+
}] };
|
|
305
|
+
}
|
|
306
|
+
function errResult(message) {
|
|
307
|
+
return { content: [{
|
|
308
|
+
type: "text",
|
|
309
|
+
text: JSON.stringify({ error: message })
|
|
310
|
+
}] };
|
|
311
|
+
}
|
|
312
|
+
function defineTool(def) {
|
|
313
|
+
return {
|
|
314
|
+
name: def.name,
|
|
315
|
+
description: def.description,
|
|
316
|
+
label: def.name,
|
|
317
|
+
parameters: def.parameters,
|
|
318
|
+
execute: async (_toolCallId, params) => {
|
|
319
|
+
try {
|
|
320
|
+
return ok(await def.handler(params));
|
|
321
|
+
} catch (e) {
|
|
322
|
+
return errResult(e.message);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function buildA2ATools(chatClient, log) {
|
|
328
|
+
return [
|
|
329
|
+
defineTool({
|
|
330
|
+
name: "list_agents",
|
|
331
|
+
description: "List other agents in your organization that you can communicate with.",
|
|
332
|
+
parameters: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {},
|
|
335
|
+
required: []
|
|
336
|
+
},
|
|
337
|
+
handler: async () => {
|
|
338
|
+
log.info("list_agents tool called");
|
|
339
|
+
return await chatClient.sendRequest("a2a.list-agents", {});
|
|
340
|
+
}
|
|
341
|
+
}),
|
|
342
|
+
defineTool({
|
|
343
|
+
name: "message_agent",
|
|
344
|
+
description: "Send a message to another agent. Starts a new conversation thread, or continues an existing one. The conversation will bounce back and forth automatically until one agent calls end_conversation() or says [RESOLVED].",
|
|
345
|
+
parameters: {
|
|
346
|
+
type: "object",
|
|
347
|
+
properties: {
|
|
348
|
+
agent_id: {
|
|
349
|
+
type: "string",
|
|
350
|
+
description: "Target agent ID (use list_agents to find available agents)"
|
|
351
|
+
},
|
|
352
|
+
message: {
|
|
353
|
+
type: "string",
|
|
354
|
+
description: "Message to send to the agent"
|
|
355
|
+
},
|
|
356
|
+
thread_id: {
|
|
357
|
+
type: "string",
|
|
358
|
+
description: "Continue an existing conversation thread (omit to start a new one)"
|
|
359
|
+
},
|
|
360
|
+
max_depth: {
|
|
361
|
+
type: "number",
|
|
362
|
+
description: "Maximum number of back-and-forth exchanges (default: 10)"
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
required: ["agent_id", "message"]
|
|
366
|
+
},
|
|
367
|
+
handler: async (params) => {
|
|
368
|
+
const agentId = params.agent_id;
|
|
369
|
+
const message = params.message;
|
|
370
|
+
const threadId = params.thread_id;
|
|
371
|
+
const maxDepth = params.max_depth ?? 10;
|
|
372
|
+
if (!agentId || !message) throw new Error("agent_id and message are required");
|
|
373
|
+
log.info(`message_agent tool: sending to ${agentId}`);
|
|
374
|
+
return await chatClient.sendRequest("a2a.send", {
|
|
375
|
+
targetAgentId: agentId,
|
|
376
|
+
message,
|
|
377
|
+
threadId,
|
|
378
|
+
maxDepth
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}),
|
|
382
|
+
defineTool({
|
|
383
|
+
name: "end_conversation",
|
|
384
|
+
description: "End the current agent-to-agent conversation. Call this when the discussion is resolved and no further exchanges are needed.",
|
|
385
|
+
parameters: {
|
|
386
|
+
type: "object",
|
|
387
|
+
properties: { summary: {
|
|
388
|
+
type: "string",
|
|
389
|
+
description: "Brief summary of what was discussed or resolved"
|
|
390
|
+
} },
|
|
391
|
+
required: []
|
|
392
|
+
},
|
|
393
|
+
handler: (params) => {
|
|
394
|
+
const summary = params.summary;
|
|
395
|
+
log.info(`end_conversation tool called${summary ? `: ${summary}` : ""}`);
|
|
396
|
+
return Promise.resolve({
|
|
397
|
+
status: "conversation_ended",
|
|
398
|
+
summary
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
];
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
292
405
|
//#region src/plugin.ts
|
|
293
406
|
/**
|
|
294
407
|
* @alfe.ai/openclaw-chat — OpenClaw chat channel plugin.
|
|
@@ -325,6 +438,45 @@ let pluginRuntime = null;
|
|
|
325
438
|
let chatClient = null;
|
|
326
439
|
let connectingPromise = null;
|
|
327
440
|
let metricsClient = null;
|
|
441
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
442
|
+
const DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
443
|
+
async function downloadAttachments(attachments, log) {
|
|
444
|
+
const attachDir = join(homedir(), ".alfe", "attachments");
|
|
445
|
+
await mkdir(attachDir, { recursive: true });
|
|
446
|
+
const results = [];
|
|
447
|
+
for (const att of attachments) {
|
|
448
|
+
const filename = (att.filename ?? att.id).replace(/[/\\]/g, "_").replace(/\.\./g, "_").replace(/\0/g, "");
|
|
449
|
+
const localPath = join(attachDir, `${att.id}_${filename}`);
|
|
450
|
+
const controller = new AbortController();
|
|
451
|
+
const timeout = setTimeout(() => {
|
|
452
|
+
controller.abort();
|
|
453
|
+
}, DOWNLOAD_TIMEOUT_MS);
|
|
454
|
+
try {
|
|
455
|
+
const res = await fetch(att.url, { signal: controller.signal });
|
|
456
|
+
if (!res.ok) {
|
|
457
|
+
log.warn(`Failed to download attachment ${att.id}: ${String(res.status)}`);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
461
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
462
|
+
log.warn(`Attachment ${att.id} exceeds max size (${String(buffer.length)} bytes) — skipping`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
await writeFile(localPath, buffer);
|
|
466
|
+
results.push({
|
|
467
|
+
localPath,
|
|
468
|
+
filename,
|
|
469
|
+
mimeType: att.mimeType
|
|
470
|
+
});
|
|
471
|
+
log.info(`Downloaded attachment: ${localPath} (${String(buffer.length)} bytes)`);
|
|
472
|
+
} catch (err) {
|
|
473
|
+
log.error(`Failed to download attachment ${att.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
474
|
+
} finally {
|
|
475
|
+
clearTimeout(timeout);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return results;
|
|
479
|
+
}
|
|
328
480
|
async function handleAgentRequest(request, log) {
|
|
329
481
|
const runtime = pluginRuntime;
|
|
330
482
|
if (!runtime) {
|
|
@@ -335,8 +487,9 @@ async function handleAgentRequest(request, log) {
|
|
|
335
487
|
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
336
488
|
return;
|
|
337
489
|
}
|
|
338
|
-
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName } = request.params;
|
|
339
|
-
|
|
490
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName, attachments: rawAttachments, a2a } = request.params;
|
|
491
|
+
const isA2A = !!a2a;
|
|
492
|
+
if (!message && !rawAttachments?.length) {
|
|
340
493
|
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
341
494
|
return;
|
|
342
495
|
}
|
|
@@ -360,11 +513,15 @@ async function handleAgentRequest(request, log) {
|
|
|
360
513
|
});
|
|
361
514
|
});
|
|
362
515
|
try {
|
|
363
|
-
const
|
|
364
|
-
const
|
|
516
|
+
const downloadedFiles = rawAttachments?.length ? await downloadAttachments(rawAttachments, log) : [];
|
|
517
|
+
const bodyForAgent = downloadedFiles.length ? `${message || ""}\n\n[Attached files:\n${downloadedFiles.map((f) => `- ${f.filename}: ${f.localPath}`).join("\n")}]` : void 0;
|
|
518
|
+
const channelMode = isA2A ? "a2a" : extractChannelMode(conversationId ?? "", clientType ?? "chat");
|
|
519
|
+
const channelLabel = channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : channelMode === "a2a" ? "Agent" : "Alfe";
|
|
365
520
|
const shortConvId = conversationId?.slice(-8) ?? "";
|
|
366
521
|
const userLabel = displayName ?? userId ?? senderId;
|
|
367
522
|
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
523
|
+
let a2aResponseBuffer = "";
|
|
524
|
+
let a2aResolved = false;
|
|
368
525
|
resolvedOpenClawKey = (await dispatchInbound({
|
|
369
526
|
cfg,
|
|
370
527
|
runtime: { channel: runtime.channel },
|
|
@@ -382,7 +539,8 @@ async function handleAgentRequest(request, log) {
|
|
|
382
539
|
senderAddress: `user:${senderId}`,
|
|
383
540
|
recipientAddress: "agent",
|
|
384
541
|
conversationLabel,
|
|
385
|
-
rawBody: message,
|
|
542
|
+
rawBody: message || "",
|
|
543
|
+
bodyForAgent,
|
|
386
544
|
messageId: request.id,
|
|
387
545
|
timestamp: Date.now(),
|
|
388
546
|
extraContext: {
|
|
@@ -390,22 +548,41 @@ async function handleAgentRequest(request, log) {
|
|
|
390
548
|
...clientType ? { ClientType: clientType } : {},
|
|
391
549
|
...conversationId ? { ConversationId: conversationId } : {},
|
|
392
550
|
...displayName ? { SenderName: displayName } : {},
|
|
393
|
-
ChannelMode: channelMode
|
|
551
|
+
ChannelMode: channelMode,
|
|
552
|
+
...isA2A ? {
|
|
553
|
+
CallerType: "agent",
|
|
554
|
+
CallerAgentId: a2a.sourceAgentId,
|
|
555
|
+
CallerAgentName: a2a.sourceAgentName,
|
|
556
|
+
InteractionDepth: String(a2a.depth),
|
|
557
|
+
A2ASystemPrompt: [
|
|
558
|
+
`This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
|
|
559
|
+
"Rules:",
|
|
560
|
+
"- Only respond if you have new information, a question, or an action to coordinate.",
|
|
561
|
+
"- If the conversation is complete, call the end_conversation() tool OR end your message with [RESOLVED].",
|
|
562
|
+
"- Do NOT respond just to acknowledge — that creates infinite loops."
|
|
563
|
+
].join("\n")
|
|
564
|
+
} : {}
|
|
394
565
|
},
|
|
395
566
|
deliver: async (payload) => {
|
|
396
567
|
const responseText = payload.text ?? "";
|
|
568
|
+
const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
|
|
397
569
|
await addMessage(sessionId, "assistant", responseText);
|
|
570
|
+
if (isA2A) {
|
|
571
|
+
a2aResponseBuffer += responseText;
|
|
572
|
+
if (responseText.includes("\"status\":\"conversation_ended\"") || responseText.includes("\"status\": \"conversation_ended\"")) a2aResolved = true;
|
|
573
|
+
}
|
|
398
574
|
chatClient?.notify("agent-message", {
|
|
399
575
|
conversationId: conversationId ?? legacySessionKey,
|
|
400
576
|
text: responseText,
|
|
401
|
-
sessionKey: resolvedOpenClawKey ?? legacySessionKey
|
|
577
|
+
sessionKey: resolvedOpenClawKey ?? legacySessionKey,
|
|
578
|
+
...mediaUrls.length ? { mediaUrls } : {}
|
|
402
579
|
});
|
|
403
580
|
if (metricsClient && userId) metricsClient.recordActivity({
|
|
404
581
|
userId,
|
|
405
582
|
channel: "alfe",
|
|
406
583
|
role: "assistant"
|
|
407
584
|
}).catch((err) => {
|
|
408
|
-
log.
|
|
585
|
+
log.warn(`Metrics report failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
409
586
|
});
|
|
410
587
|
},
|
|
411
588
|
onRecordError: (err) => {
|
|
@@ -415,6 +592,12 @@ async function handleAgentRequest(request, log) {
|
|
|
415
592
|
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
416
593
|
}
|
|
417
594
|
})).route.sessionKey;
|
|
595
|
+
if (isA2A && a2aResponseBuffer) chatClient?.notify("a2a-complete", {
|
|
596
|
+
conversationId: conversationId ?? legacySessionKey,
|
|
597
|
+
fullText: a2aResponseBuffer,
|
|
598
|
+
a2a,
|
|
599
|
+
resolved: a2aResolved
|
|
600
|
+
});
|
|
418
601
|
chatClient?.sendResponse(request.id, true, { sessionKey: resolvedOpenClawKey });
|
|
419
602
|
log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
|
|
420
603
|
} catch (err) {
|
|
@@ -484,10 +667,15 @@ const plugin = {
|
|
|
484
667
|
activate(api) {
|
|
485
668
|
const log = api.logger;
|
|
486
669
|
const alreadyActivated = globalThis.__alfeChatPluginActivated === true;
|
|
487
|
-
|
|
670
|
+
const isGatewayMode = !!api.runtime;
|
|
488
671
|
const alfeChannel = createAlfeChannelPlugin();
|
|
489
672
|
api.registerChannel(alfeChannel);
|
|
490
673
|
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
674
|
+
if (!isGatewayMode) {
|
|
675
|
+
log.debug("Management command context — skipping persistent resource init");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
globalThis.__alfeChatPluginActivated = true;
|
|
491
679
|
if (!alreadyActivated) {
|
|
492
680
|
log.info("Chat plugin registering...");
|
|
493
681
|
resolveOpenClawSdk(log);
|
|
@@ -515,10 +703,17 @@ const plugin = {
|
|
|
515
703
|
wsUrl: chatWsUrl,
|
|
516
704
|
apiKey,
|
|
517
705
|
onRequest: (request) => {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
706
|
+
const handle = async () => {
|
|
707
|
+
if (request.method === "agent") await handleAgentRequest(request, log);
|
|
708
|
+
else if (request.method === "sessions.list") await handleSessionsList(request, log);
|
|
709
|
+
else if (request.method === "sessions.get") await handleSessionsGet(request, log);
|
|
710
|
+
else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
|
|
711
|
+
};
|
|
712
|
+
handle().catch((err) => {
|
|
713
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
714
|
+
log.error(`Request handler crashed: ${errMsg}`);
|
|
715
|
+
chatClient?.sendResponse(request.id, false, { message: "Internal error" });
|
|
716
|
+
});
|
|
522
717
|
},
|
|
523
718
|
onConnectionChange: (connected) => {
|
|
524
719
|
log.info(`Chat service connection: ${connected ? "connected" : "disconnected"}`);
|
|
@@ -567,20 +762,30 @@ const plugin = {
|
|
|
567
762
|
});
|
|
568
763
|
log.info("Registered gateway RPC methods: sessions.list, sessions.get");
|
|
569
764
|
}
|
|
765
|
+
if (!alreadyActivated && typeof api.registerTool === "function") connectingPromise?.then(() => {
|
|
766
|
+
if (chatClient && typeof api.registerTool === "function") {
|
|
767
|
+
const a2aTools = buildA2ATools(chatClient, log);
|
|
768
|
+
for (const tool of a2aTools) api.registerTool(tool);
|
|
769
|
+
log.info(`Registered ${String(a2aTools.length)} agent-to-agent tools`);
|
|
770
|
+
}
|
|
771
|
+
}).catch(() => {});
|
|
570
772
|
if (!alreadyActivated) {
|
|
571
773
|
api.on("session_start", async (...eventArgs) => {
|
|
774
|
+
if (!pluginRuntime) return;
|
|
572
775
|
const key = eventArgs[0].sessionKey;
|
|
573
776
|
if (!key || isAlfeSessionKey(key)) return;
|
|
574
777
|
log.info(`Chat session starting: ${key}`);
|
|
575
778
|
await createSession(key, "", "alfe");
|
|
576
779
|
}, { priority: 50 });
|
|
577
780
|
api.on("message_received", async (...eventArgs) => {
|
|
781
|
+
if (!pluginRuntime) return;
|
|
578
782
|
const event = eventArgs[0];
|
|
579
783
|
const ctx = eventArgs[1];
|
|
580
784
|
if (!ctx.conversationId || ctx.channelId === "alfe") return;
|
|
581
785
|
await addMessage(ctx.conversationId, "user", event.content, event.from);
|
|
582
786
|
});
|
|
583
787
|
api.on("session_end", (...eventArgs) => {
|
|
788
|
+
if (!pluginRuntime) return;
|
|
584
789
|
const key = eventArgs[0].sessionKey;
|
|
585
790
|
if (!key || !isAlfeSessionKey(key)) return;
|
|
586
791
|
log.info(`Chat session ending: ${key}`);
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-chat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugin.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@alfe.ai/agent-api-client": "^0.0.7",
|
|
31
|
-
"@alfe.ai/chat": "^0.0.
|
|
31
|
+
"@alfe.ai/chat": "^0.0.7",
|
|
32
32
|
"@alfe.ai/config": "^0.0.7"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|