@alfe.ai/openclaw-chat 0.0.7 → 0.0.8
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 +19 -1
- package/dist/plugin2.js +95 -47
- 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;
|
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,13 +295,33 @@ 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;
|
|
312
327
|
async function handleAgentRequest(request, log) {
|
|
@@ -315,36 +330,68 @@ async function handleAgentRequest(request, log) {
|
|
|
315
330
|
chatClient?.sendResponse(request.id, false, { message: "Plugin runtime not initialized" });
|
|
316
331
|
return;
|
|
317
332
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
chatClient?.sendResponse(request.id, false, { message: "Missing message or sessionKey" });
|
|
333
|
+
if (!dispatchInbound) {
|
|
334
|
+
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
321
335
|
return;
|
|
322
336
|
}
|
|
337
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, tenantId, clientType } = request.params;
|
|
338
|
+
if (!message) {
|
|
339
|
+
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const senderId = userId ?? `anon-${legacySessionKey.slice(0, 12)}`;
|
|
343
|
+
const cfg = runtime.config.loadConfig();
|
|
344
|
+
let resolvedSessionKey = legacySessionKey;
|
|
323
345
|
const unsubscribe = runtime.events.onAgentEvent((evt) => {
|
|
324
|
-
if (evt.sessionKey
|
|
346
|
+
if (!evt.sessionKey) return;
|
|
347
|
+
if (resolvedSessionKey && evt.sessionKey !== resolvedSessionKey) return;
|
|
325
348
|
if (evt.stream === "assistant") chatClient?.sendEvent("chat", {
|
|
326
349
|
runId: evt.runId,
|
|
327
|
-
sessionKey,
|
|
350
|
+
sessionKey: evt.sessionKey,
|
|
328
351
|
seq: evt.seq,
|
|
329
352
|
state: "delta",
|
|
330
353
|
message: evt.data
|
|
331
354
|
});
|
|
332
355
|
});
|
|
333
356
|
try {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
357
|
+
const conversationLabel = userId ? `Chat with ${userId}` : `Alfe chat`;
|
|
358
|
+
resolvedSessionKey = (await dispatchInbound({
|
|
359
|
+
cfg,
|
|
360
|
+
runtime: { channel: runtime.channel },
|
|
361
|
+
channel: "alfe",
|
|
362
|
+
channelLabel: "Alfe",
|
|
363
|
+
accountId: "default",
|
|
364
|
+
peer: {
|
|
365
|
+
kind: "direct",
|
|
366
|
+
id: senderId
|
|
367
|
+
},
|
|
368
|
+
senderId,
|
|
369
|
+
senderAddress: `user:${senderId}`,
|
|
370
|
+
recipientAddress: "agent",
|
|
371
|
+
conversationLabel,
|
|
372
|
+
rawBody: message,
|
|
373
|
+
messageId: request.id,
|
|
374
|
+
timestamp: Date.now(),
|
|
375
|
+
extraContext: {
|
|
376
|
+
...tenantId ? { TenantId: tenantId } : {},
|
|
377
|
+
...clientType ? { ClientType: clientType } : {},
|
|
378
|
+
...conversationId ? { ConversationId: conversationId } : {}
|
|
379
|
+
},
|
|
380
|
+
deliver: (payload) => {
|
|
381
|
+
chatClient?.sendResponse(request.id, true, {
|
|
382
|
+
text: payload.text ?? "",
|
|
383
|
+
sessionKey: resolvedSessionKey
|
|
384
|
+
});
|
|
385
|
+
return Promise.resolve();
|
|
386
|
+
},
|
|
387
|
+
onRecordError: (err) => {
|
|
388
|
+
log.error(`Session record error: ${err instanceof Error ? err.message : String(err)}`);
|
|
389
|
+
},
|
|
390
|
+
onDispatchError: (err, info) => {
|
|
391
|
+
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
392
|
+
}
|
|
393
|
+
})).route.sessionKey;
|
|
394
|
+
log.info(`Agent dispatch complete: sessionKey=${resolvedSessionKey}`);
|
|
348
395
|
} catch (err) {
|
|
349
396
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
350
397
|
log.error(`Agent dispatch failed: ${errMsg}`);
|
|
@@ -357,16 +404,17 @@ const plugin = {
|
|
|
357
404
|
id: "@alfe.ai/openclaw-chat",
|
|
358
405
|
name: "Alfe Chat Plugin",
|
|
359
406
|
description: "Alfe conversation channel — web widget and mobile app share unified chat sessions",
|
|
360
|
-
version: "0.
|
|
407
|
+
version: "0.0.8",
|
|
361
408
|
activate(api) {
|
|
362
409
|
if (globalThis.__alfeChatPluginActivated) {
|
|
363
410
|
api.logger.debug("Alfe Chat plugin already activated, skipping re-init");
|
|
364
411
|
return;
|
|
365
412
|
}
|
|
366
413
|
globalThis.__alfeChatPluginActivated = true;
|
|
367
|
-
pluginRuntime = api.runtime ?? null;
|
|
368
414
|
const log = api.logger;
|
|
369
415
|
log.info("Alfe Chat plugin registering...");
|
|
416
|
+
resolveOpenClawSdk(log);
|
|
417
|
+
pluginRuntime = api.runtime ?? null;
|
|
370
418
|
const alfeChannel = createAlfeChannelPlugin();
|
|
371
419
|
api.registerChannel(alfeChannel);
|
|
372
420
|
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
@@ -432,8 +480,7 @@ const plugin = {
|
|
|
432
480
|
const key = eventArgs[0].sessionKey;
|
|
433
481
|
if (!key || !isAlfeSessionKey(key)) return;
|
|
434
482
|
log.info(`Alfe chat session starting: ${key}`);
|
|
435
|
-
|
|
436
|
-
await createSession(key, parsedAgentId, "alfe", tenantId);
|
|
483
|
+
await createSession(key, "", "alfe");
|
|
437
484
|
}, { priority: 50 });
|
|
438
485
|
api.on("message", async (...eventArgs) => {
|
|
439
486
|
const event = eventArgs[0];
|
|
@@ -458,6 +505,7 @@ const plugin = {
|
|
|
458
505
|
log.info("Chat service client stopped");
|
|
459
506
|
}
|
|
460
507
|
pluginRuntime = null;
|
|
508
|
+
dispatchInbound = null;
|
|
461
509
|
log.info("Alfe Chat plugin deactivated");
|
|
462
510
|
}
|
|
463
511
|
};
|
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.8",
|
|
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",
|