@alfe.ai/openclaw-chat 0.0.7 → 0.0.9
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.ts +20 -2
- package/dist/plugin2.js +151 -49
- package/package.json +9 -1
package/dist/plugin.d.ts
CHANGED
|
@@ -192,7 +192,25 @@ interface PluginRuntime {
|
|
|
192
192
|
error?: string;
|
|
193
193
|
}>;
|
|
194
194
|
};
|
|
195
|
-
channel:
|
|
195
|
+
channel: {
|
|
196
|
+
routing: {
|
|
197
|
+
resolveAgentRoute(input: Record<string, unknown>): Record<string, unknown>;
|
|
198
|
+
buildAgentSessionKey(params: Record<string, unknown>): string;
|
|
199
|
+
};
|
|
200
|
+
session: {
|
|
201
|
+
resolveStorePath(params: Record<string, unknown>): string;
|
|
202
|
+
readSessionUpdatedAt(params: Record<string, unknown>): number | undefined;
|
|
203
|
+
recordInboundSession(params: Record<string, unknown>): Promise<void>;
|
|
204
|
+
};
|
|
205
|
+
reply: {
|
|
206
|
+
resolveEnvelopeFormatOptions(cfg: Record<string, unknown>): Record<string, unknown>;
|
|
207
|
+
formatAgentEnvelope(params: Record<string, unknown>): string;
|
|
208
|
+
finalizeInboundContext(params: Record<string, unknown>): Record<string, unknown>;
|
|
209
|
+
dispatchReplyWithBufferedBlockDispatcher(params: Record<string, unknown>): Promise<unknown>;
|
|
210
|
+
dispatchReplyFromConfig(params: Record<string, unknown>): Promise<unknown>;
|
|
211
|
+
};
|
|
212
|
+
[key: string]: unknown;
|
|
213
|
+
};
|
|
196
214
|
}
|
|
197
215
|
interface AgentEventPayload {
|
|
198
216
|
runId: string;
|
|
@@ -218,7 +236,7 @@ declare const plugin: {
|
|
|
218
236
|
description: string;
|
|
219
237
|
version: string;
|
|
220
238
|
activate(api: PluginApi): void;
|
|
221
|
-
deactivate(api: PluginApi): void
|
|
239
|
+
deactivate(api: PluginApi): Promise<void>;
|
|
222
240
|
};
|
|
223
241
|
//#endregion
|
|
224
242
|
export { AlfeResolvedAccount as a, AlfePluginConfig as i, createAlfeChannelPlugin as n, AlfeChannelConfig as r, plugin as t };
|
package/dist/plugin2.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
|
|
2
3
|
import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
@@ -137,33 +138,27 @@ function createAlfeChannelPlugin() {
|
|
|
137
138
|
//#endregion
|
|
138
139
|
//#region src/session-keys.ts
|
|
139
140
|
/**
|
|
140
|
-
* Session key helpers — handles both
|
|
141
|
+
* Session key helpers — handles both legacy and canonical OpenClaw formats.
|
|
141
142
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
143
|
+
* Canonical format (from resolveAgentRoute):
|
|
144
|
+
* agent:{agentId}:alfe:direct:{userId}
|
|
145
|
+
* agent:{agentId}:alfe:direct:{userId}:thread:{conversationId}
|
|
144
146
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
147
|
+
* Legacy format (from chat adapter):
|
|
148
|
+
* chat-{tenantId}-{agentId}-{suffix}
|
|
149
|
+
* agent:{agentId}:chat-{tenantId}-{agentId}-{suffix}
|
|
150
|
+
*
|
|
151
|
+
* The plugin may receive either format depending on which
|
|
152
|
+
* OpenClaw event fires and whether the new dispatch path is active.
|
|
148
153
|
*/
|
|
149
154
|
/**
|
|
150
155
|
* Check if a session key belongs to the Alfe chat channel.
|
|
151
|
-
* Handles both
|
|
156
|
+
* Handles both canonical and legacy formats.
|
|
152
157
|
*/
|
|
153
158
|
function isAlfeSessionKey(key) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
* Strip the "agent:{agentId}:" prefix and extract tenantId + agentId.
|
|
158
|
-
* Returns empty strings if the key doesn't match the expected format.
|
|
159
|
-
*/
|
|
160
|
-
function parseAlfeSessionKey(key) {
|
|
161
|
-
const rawKey = key.includes(":") ? key.slice(key.lastIndexOf(":") + 1) : key;
|
|
162
|
-
const match = /^chat-([^-]+)-([^-]+)/.exec(rawKey);
|
|
163
|
-
return {
|
|
164
|
-
tenantId: match?.[1] ?? "",
|
|
165
|
-
agentId: match?.[2] ?? ""
|
|
166
|
-
};
|
|
159
|
+
if (key.includes(":alfe:")) return true;
|
|
160
|
+
if (key.includes("chat-")) return true;
|
|
161
|
+
return false;
|
|
167
162
|
}
|
|
168
163
|
//#endregion
|
|
169
164
|
//#region src/session-store.ts
|
|
@@ -300,51 +295,104 @@ async function listSessions(filters, limit = 50) {
|
|
|
300
295
|
* @alfe.ai/openclaw-chat — OpenClaw chat channel plugin.
|
|
301
296
|
*
|
|
302
297
|
* Registers the 'alfe' channel with OpenClaw. Messages are dispatched
|
|
303
|
-
*
|
|
304
|
-
*
|
|
298
|
+
* through OpenClaw's auto-reply pipeline via dispatchInboundDirectDmWithRuntime(),
|
|
299
|
+
* the same API built-in channels (Slack, Discord, Telegram) use.
|
|
305
300
|
*
|
|
306
301
|
* Architecture:
|
|
307
|
-
* Chat Service (Fly.io) ←WS→ ChatServiceClient
|
|
308
|
-
*
|
|
302
|
+
* Chat Service (Fly.io) ←WS→ ChatServiceClient
|
|
303
|
+
* → dispatchInboundDirectDmWithRuntime() → Agent (auto-reply pipeline)
|
|
304
|
+
* ← onAgentEvent streaming ← (real-time deltas)
|
|
305
|
+
* ← deliver() callback ← (final response)
|
|
309
306
|
*/
|
|
307
|
+
let dispatchInbound = null;
|
|
308
|
+
/**
|
|
309
|
+
* Resolve OpenClaw SDK functions from the global install.
|
|
310
|
+
* The openclaw package is installed globally (/usr/lib/node_modules/openclaw/)
|
|
311
|
+
* but is NOT in the plugin's node_modules. Built-in extensions can import
|
|
312
|
+
* directly; external plugins must use createRequire.
|
|
313
|
+
*/
|
|
314
|
+
function resolveOpenClawSdk(log) {
|
|
315
|
+
for (const globalPath of ["/usr/lib/node_modules/openclaw/package.json", "/usr/local/lib/node_modules/openclaw/package.json"]) try {
|
|
316
|
+
const channelInbound = createRequire(globalPath)("openclaw/plugin-sdk/channel-inbound");
|
|
317
|
+
if (channelInbound.dispatchInboundDirectDmWithRuntime) {
|
|
318
|
+
dispatchInbound = channelInbound.dispatchInboundDirectDmWithRuntime;
|
|
319
|
+
log.info(`Resolved OpenClaw SDK from ${globalPath}`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
} catch {}
|
|
323
|
+
log.warn("OpenClaw SDK not resolvable — chat dispatch will not work");
|
|
324
|
+
}
|
|
310
325
|
let pluginRuntime = null;
|
|
311
326
|
let chatClient = null;
|
|
327
|
+
let connectingPromise = null;
|
|
312
328
|
async function handleAgentRequest(request, log) {
|
|
313
329
|
const runtime = pluginRuntime;
|
|
314
330
|
if (!runtime) {
|
|
315
331
|
chatClient?.sendResponse(request.id, false, { message: "Plugin runtime not initialized" });
|
|
316
332
|
return;
|
|
317
333
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
chatClient?.sendResponse(request.id, false, { message: "Missing message or sessionKey" });
|
|
334
|
+
if (!dispatchInbound) {
|
|
335
|
+
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
321
336
|
return;
|
|
322
337
|
}
|
|
338
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, tenantId, clientType } = request.params;
|
|
339
|
+
if (!message) {
|
|
340
|
+
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const senderId = userId ?? `anon-${legacySessionKey.slice(0, 12)}`;
|
|
344
|
+
const cfg = runtime.config.loadConfig();
|
|
345
|
+
let resolvedSessionKey = legacySessionKey;
|
|
323
346
|
const unsubscribe = runtime.events.onAgentEvent((evt) => {
|
|
324
|
-
if (evt.sessionKey
|
|
347
|
+
if (!evt.sessionKey) return;
|
|
348
|
+
if (resolvedSessionKey && evt.sessionKey !== resolvedSessionKey) return;
|
|
325
349
|
if (evt.stream === "assistant") chatClient?.sendEvent("chat", {
|
|
326
350
|
runId: evt.runId,
|
|
327
|
-
sessionKey,
|
|
351
|
+
sessionKey: evt.sessionKey,
|
|
328
352
|
seq: evt.seq,
|
|
329
353
|
state: "delta",
|
|
330
354
|
message: evt.data
|
|
331
355
|
});
|
|
332
356
|
});
|
|
333
357
|
try {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
358
|
+
const conversationLabel = userId ? `Chat with ${userId}` : `Alfe chat`;
|
|
359
|
+
resolvedSessionKey = (await dispatchInbound({
|
|
360
|
+
cfg,
|
|
361
|
+
runtime: { channel: runtime.channel },
|
|
362
|
+
channel: "alfe",
|
|
363
|
+
channelLabel: "Alfe",
|
|
364
|
+
accountId: "default",
|
|
365
|
+
peer: {
|
|
366
|
+
kind: "direct",
|
|
367
|
+
id: senderId
|
|
368
|
+
},
|
|
369
|
+
senderId,
|
|
370
|
+
senderAddress: `user:${senderId}`,
|
|
371
|
+
recipientAddress: "agent",
|
|
372
|
+
conversationLabel,
|
|
373
|
+
rawBody: message,
|
|
374
|
+
messageId: request.id,
|
|
375
|
+
timestamp: Date.now(),
|
|
376
|
+
extraContext: {
|
|
377
|
+
...tenantId ? { TenantId: tenantId } : {},
|
|
378
|
+
...clientType ? { ClientType: clientType } : {},
|
|
379
|
+
...conversationId ? { ConversationId: conversationId } : {}
|
|
380
|
+
},
|
|
381
|
+
deliver: (payload) => {
|
|
382
|
+
chatClient?.sendResponse(request.id, true, {
|
|
383
|
+
text: payload.text ?? "",
|
|
384
|
+
sessionKey: resolvedSessionKey
|
|
385
|
+
});
|
|
386
|
+
return Promise.resolve();
|
|
387
|
+
},
|
|
388
|
+
onRecordError: (err) => {
|
|
389
|
+
log.error(`Session record error: ${err instanceof Error ? err.message : String(err)}`);
|
|
390
|
+
},
|
|
391
|
+
onDispatchError: (err, info) => {
|
|
392
|
+
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
393
|
+
}
|
|
394
|
+
})).route.sessionKey;
|
|
395
|
+
log.info(`Agent dispatch complete: sessionKey=${resolvedSessionKey}`);
|
|
348
396
|
} catch (err) {
|
|
349
397
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
350
398
|
log.error(`Agent dispatch failed: ${errMsg}`);
|
|
@@ -353,25 +401,70 @@ async function handleAgentRequest(request, log) {
|
|
|
353
401
|
unsubscribe();
|
|
354
402
|
}
|
|
355
403
|
}
|
|
404
|
+
async function handleSessionsList(request, log) {
|
|
405
|
+
try {
|
|
406
|
+
const params = request.params;
|
|
407
|
+
const sessions = await listSessions({
|
|
408
|
+
agentId: params.agentId,
|
|
409
|
+
channel: params.channel,
|
|
410
|
+
tenantId: params.tenantId
|
|
411
|
+
});
|
|
412
|
+
chatClient?.sendResponse(request.id, true, { sessions });
|
|
413
|
+
} catch (err) {
|
|
414
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
415
|
+
log.error(`sessions.list failed: ${errMsg}`);
|
|
416
|
+
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function handleSessionsGet(request, log) {
|
|
420
|
+
try {
|
|
421
|
+
const params = request.params;
|
|
422
|
+
const session = await getSession(params.sessionId);
|
|
423
|
+
if (!session) {
|
|
424
|
+
chatClient?.sendResponse(request.id, true, {
|
|
425
|
+
ok: false,
|
|
426
|
+
error: "Session not found"
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
chatClient?.sendResponse(request.id, true, {
|
|
431
|
+
sessionId: session.sessionId,
|
|
432
|
+
agentId: session.agentId,
|
|
433
|
+
channel: session.channel,
|
|
434
|
+
createdAt: session.createdAt,
|
|
435
|
+
messages: session.messages.map((m) => ({
|
|
436
|
+
id: `msg-${String(m.timestamp)}`,
|
|
437
|
+
role: m.role,
|
|
438
|
+
content: m.content,
|
|
439
|
+
timestamp: m.timestamp
|
|
440
|
+
}))
|
|
441
|
+
});
|
|
442
|
+
} catch (err) {
|
|
443
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
444
|
+
log.error(`sessions.get failed: ${errMsg}`);
|
|
445
|
+
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
356
448
|
const plugin = {
|
|
357
449
|
id: "@alfe.ai/openclaw-chat",
|
|
358
450
|
name: "Alfe Chat Plugin",
|
|
359
451
|
description: "Alfe conversation channel — web widget and mobile app share unified chat sessions",
|
|
360
|
-
version: "0.
|
|
452
|
+
version: "0.0.8",
|
|
361
453
|
activate(api) {
|
|
362
454
|
if (globalThis.__alfeChatPluginActivated) {
|
|
363
455
|
api.logger.debug("Alfe Chat plugin already activated, skipping re-init");
|
|
364
456
|
return;
|
|
365
457
|
}
|
|
366
458
|
globalThis.__alfeChatPluginActivated = true;
|
|
367
|
-
pluginRuntime = api.runtime ?? null;
|
|
368
459
|
const log = api.logger;
|
|
369
460
|
log.info("Alfe Chat plugin registering...");
|
|
461
|
+
resolveOpenClawSdk(log);
|
|
462
|
+
pluginRuntime = api.runtime ?? null;
|
|
370
463
|
const alfeChannel = createAlfeChannelPlugin();
|
|
371
464
|
api.registerChannel(alfeChannel);
|
|
372
465
|
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
373
466
|
const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
|
|
374
|
-
(async () => {
|
|
467
|
+
connectingPromise = (async () => {
|
|
375
468
|
try {
|
|
376
469
|
const { apiKey, chatWsUrl } = await resolveAlfeChat({
|
|
377
470
|
apiKey: pluginConfig.apiKey,
|
|
@@ -384,6 +477,9 @@ const plugin = {
|
|
|
384
477
|
apiKey,
|
|
385
478
|
onRequest: (request) => {
|
|
386
479
|
if (request.method === "agent") handleAgentRequest(request, log);
|
|
480
|
+
else if (request.method === "sessions.list") handleSessionsList(request, log);
|
|
481
|
+
else if (request.method === "sessions.get") handleSessionsGet(request, log);
|
|
482
|
+
else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
|
|
387
483
|
},
|
|
388
484
|
onConnectionChange: (connected) => {
|
|
389
485
|
log.info(`Chat service connection: ${connected ? "connected" : "disconnected"}`);
|
|
@@ -432,8 +528,7 @@ const plugin = {
|
|
|
432
528
|
const key = eventArgs[0].sessionKey;
|
|
433
529
|
if (!key || !isAlfeSessionKey(key)) return;
|
|
434
530
|
log.info(`Alfe chat session starting: ${key}`);
|
|
435
|
-
|
|
436
|
-
await createSession(key, parsedAgentId, "alfe", tenantId);
|
|
531
|
+
await createSession(key, "", "alfe");
|
|
437
532
|
}, { priority: 50 });
|
|
438
533
|
api.on("message", async (...eventArgs) => {
|
|
439
534
|
const event = eventArgs[0];
|
|
@@ -448,16 +543,23 @@ const plugin = {
|
|
|
448
543
|
});
|
|
449
544
|
log.info("Alfe Chat plugin registered");
|
|
450
545
|
},
|
|
451
|
-
deactivate(api) {
|
|
546
|
+
async deactivate(api) {
|
|
452
547
|
globalThis.__alfeChatPluginActivated = false;
|
|
453
548
|
const log = api.logger;
|
|
454
549
|
log.info("Alfe Chat plugin deactivating...");
|
|
550
|
+
if (connectingPromise) {
|
|
551
|
+
await connectingPromise.catch((err) => {
|
|
552
|
+
api.logger.debug(`Connection attempt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
553
|
+
});
|
|
554
|
+
connectingPromise = null;
|
|
555
|
+
}
|
|
455
556
|
if (chatClient) {
|
|
456
557
|
chatClient.stop();
|
|
457
558
|
chatClient = null;
|
|
458
559
|
log.info("Chat service client stopped");
|
|
459
560
|
}
|
|
460
561
|
pluginRuntime = null;
|
|
562
|
+
dispatchInbound = null;
|
|
461
563
|
log.info("Alfe Chat plugin deactivated");
|
|
462
564
|
}
|
|
463
565
|
};
|
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.9",
|
|
4
4
|
"description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugin.js",
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@alfe.ai/chat": "^0.0.3"
|
|
29
29
|
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"openclaw": ">=2026.3.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"openclaw": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
30
38
|
"license": "UNLICENSED",
|
|
31
39
|
"scripts": {
|
|
32
40
|
"build": "tsdown",
|