@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 CHANGED
@@ -192,7 +192,25 @@ interface PluginRuntime {
192
192
  error?: string;
193
193
  }>;
194
194
  };
195
- channel: unknown;
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 raw and prefixed formats.
141
+ * Session key helpers — handles both legacy and canonical OpenClaw formats.
141
142
  *
142
- * Raw format: "chat-{tenantId}-{agentId}-{suffix}"
143
- * Prefixed format: "agent:{agentId}:chat-{tenantId}-{agentId}-{suffix}"
143
+ * Canonical format (from resolveAgentRoute):
144
+ * agent:{agentId}:alfe:direct:{userId}
145
+ * agent:{agentId}:alfe:direct:{userId}:thread:{conversationId}
144
146
  *
145
- * The chat adapter wraps keys with "agent:{agentId}:" before sending
146
- * to the daemon. The plugin may receive either format depending on
147
- * which OpenClaw event fires.
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 raw and prefixed formats.
156
+ * Handles both canonical and legacy formats.
152
157
  */
153
158
  function isAlfeSessionKey(key) {
154
- return key.includes("chat-") || key.includes("alfe:") || key.includes(":alfe:");
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
- * in-process via runtime.subagent.run() — no separate WebSocket to the
304
- * runtime needed.
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 → runtime.subagent.run() → Agent (in-process)
308
- * onAgentEvent streaming
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
- const { message, sessionKey } = request.params;
319
- if (!message || !sessionKey) {
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 !== sessionKey) return;
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 { runId } = await runtime.subagent.run({
335
- sessionKey,
336
- message,
337
- deliver: true
338
- });
339
- const result = await runtime.subagent.waitForRun({
340
- runId,
341
- timeoutMs: 12e4
342
- });
343
- if (result.status === "ok") chatClient?.sendResponse(request.id, true, {
344
- text: "",
345
- sessionKey
346
- });
347
- else chatClient?.sendResponse(request.id, false, { message: result.error ?? `Agent run ${result.status}` });
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.3.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
- const { tenantId, agentId: parsedAgentId } = parseAlfeSessionKey(key);
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.7",
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",