@alfe.ai/openclaw-chat 0.0.15 → 0.0.16

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/plugin2.cjs CHANGED
@@ -138,28 +138,98 @@ function createAlfeChannelPlugin() {
138
138
  //#endregion
139
139
  //#region src/session-keys.ts
140
140
  /**
141
- * Session key helpers — handles both legacy and canonical OpenClaw formats.
141
+ * Session key helpers — handles standardized, canonical, and legacy formats.
142
142
  *
143
- * Canonical format (from resolveAgentRoute):
144
- * agent:{agentId}:alfe:direct:{userId}
145
- * agent:{agentId}:alfe:direct:{userId}:thread:{conversationId}
143
+ * Standardized format (Alfe-controlled):
144
+ * alfe:{mode}:{identity} — single-threaded (SMS, WhatsApp)
145
+ * alfe:{mode}:{identity}:{convId} — multi-threaded (web chat)
146
146
  *
147
- * Legacy format (from chat adapter):
147
+ * OpenClaw canonical format (from resolveAgentRoute):
148
+ * agent:{ocAgentId}:alfe:[default:]direct:{senderId}[:thread:{conversationId}]
149
+ *
150
+ * Legacy formats (deprecated):
151
+ * sms-{agentId}-{phone}
152
+ * wa-{agentId}-{phone}
148
153
  * chat-{tenantId}-{agentId}-{suffix}
149
154
  * 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.
153
155
  */
156
+ /** Single-threaded channel modes — identity IS the conversation. */
157
+ const SINGLE_THREADED_MODES = new Set([
158
+ "sms",
159
+ "whatsapp",
160
+ "mobile"
161
+ ]);
154
162
  /**
155
163
  * Check if a session key belongs to the Alfe chat channel.
156
- * Handles both canonical and legacy formats.
164
+ * Handles standardized, canonical, and legacy formats.
157
165
  */
158
166
  function isAlfeSessionKey(key) {
167
+ if (key.startsWith("alfe:")) return true;
159
168
  if (key.includes(":alfe:")) return true;
160
- if (key.includes("chat-")) return true;
169
+ if (key.includes("chat-") || key.startsWith("sms-") || key.startsWith("wa-")) return true;
161
170
  return false;
162
171
  }
172
+ /**
173
+ * Parse session key metadata. Returns available fields from any format.
174
+ *
175
+ * For the callback flow, `conversationId` is the critical field — it maps
176
+ * to the channel registry key used by getChannelCallback().
177
+ */
178
+ function parseAlfeSessionKey(key) {
179
+ const standardMatch = /^alfe:(\w+):(.+)$/.exec(key);
180
+ if (standardMatch) {
181
+ const [, mode, rest] = standardMatch;
182
+ if (SINGLE_THREADED_MODES.has(mode)) return {
183
+ agentId: "",
184
+ userId: rest,
185
+ conversationId: key,
186
+ tenantId: "",
187
+ mode
188
+ };
189
+ const lastColon = rest.lastIndexOf(":");
190
+ if (lastColon > 0) return {
191
+ agentId: "",
192
+ userId: rest.slice(0, lastColon),
193
+ conversationId: key,
194
+ tenantId: "",
195
+ mode
196
+ };
197
+ return {
198
+ agentId: "",
199
+ userId: rest,
200
+ conversationId: key,
201
+ tenantId: "",
202
+ mode
203
+ };
204
+ }
205
+ const canonicalMatch = /^agent:([^:]+):alfe:(?:default:)?direct:(.+?)(?::thread:(.+))?$/.exec(key);
206
+ if (canonicalMatch) {
207
+ const [, matchAgentId, matchUserId, matchConvId] = canonicalMatch;
208
+ return {
209
+ agentId: matchAgentId,
210
+ userId: matchUserId,
211
+ conversationId: matchConvId || "",
212
+ tenantId: "",
213
+ mode: ""
214
+ };
215
+ }
216
+ const rawKey = key.includes(":") ? key.slice(key.lastIndexOf(":") + 1) : key;
217
+ const legacyMatch = /^chat-([^-]+)-([^-]+)/.exec(rawKey);
218
+ return {
219
+ agentId: legacyMatch?.[2] ?? "",
220
+ userId: "",
221
+ conversationId: "",
222
+ tenantId: legacyMatch?.[1] ?? "",
223
+ mode: "chat"
224
+ };
225
+ }
226
+ /**
227
+ * Extract the channel mode from a standardized session key or conversationId.
228
+ * Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat') or fallback.
229
+ */
230
+ function extractChannelMode(conversationId, fallback = "chat") {
231
+ return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
232
+ }
163
233
  //#endregion
164
234
  //#region src/session-store.ts
165
235
  /**
@@ -363,14 +433,16 @@ async function handleAgentRequest(request, log) {
363
433
  });
364
434
  });
365
435
  try {
436
+ const channelMode = extractChannelMode(conversationId ?? "", clientType ?? "chat");
437
+ const channelLabel = channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : "Alfe";
366
438
  const shortConvId = conversationId?.slice(-8) ?? "";
367
439
  const userLabel = displayName ?? userId ?? senderId;
368
- const conversationLabel = conversationType === "group" ? shortConvId ? `[Alfe] Group (${shortConvId})` : "[Alfe] Group" : shortConvId ? `[Alfe] ${userLabel} (${shortConvId})` : `[Alfe] ${userLabel}`;
440
+ const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
369
441
  resolvedOpenClawKey = (await dispatchInbound({
370
442
  cfg,
371
443
  runtime: { channel: runtime.channel },
372
444
  channel: "alfe",
373
- channelLabel: "Alfe",
445
+ channelLabel,
374
446
  accountId: "default",
375
447
  peer: conversationType === "group" ? {
376
448
  kind: "group",
@@ -390,7 +462,8 @@ async function handleAgentRequest(request, log) {
390
462
  ...tenantId ? { TenantId: tenantId } : {},
391
463
  ...clientType ? { ClientType: clientType } : {},
392
464
  ...conversationId ? { ConversationId: conversationId } : {},
393
- ...displayName ? { SenderName: displayName } : {}
465
+ ...displayName ? { SenderName: displayName } : {},
466
+ ChannelMode: channelMode
394
467
  },
395
468
  deliver: async (payload) => {
396
469
  const responseText = payload.text ?? "";
@@ -559,8 +632,16 @@ const plugin = {
559
632
  api.on("message", async (...eventArgs) => {
560
633
  const event = eventArgs[0];
561
634
  const key = event.sessionKey;
562
- if (!key || isAlfeSessionKey(key)) return;
563
- await addMessage(key, event.role, event.content);
635
+ if (!key) return;
636
+ if (isAlfeSessionKey(key)) {
637
+ if (event.role === "assistant" && chatClient) {
638
+ const parsed = parseAlfeSessionKey(key);
639
+ if (parsed.conversationId) chatClient.notify("agent-message", {
640
+ conversationId: parsed.conversationId,
641
+ text: event.content
642
+ });
643
+ }
644
+ } else await addMessage(key, event.role, event.content);
564
645
  });
565
646
  api.on("session_end", (...eventArgs) => {
566
647
  const key = eventArgs[0].sessionKey;
package/dist/plugin2.js CHANGED
@@ -138,28 +138,98 @@ function createAlfeChannelPlugin() {
138
138
  //#endregion
139
139
  //#region src/session-keys.ts
140
140
  /**
141
- * Session key helpers — handles both legacy and canonical OpenClaw formats.
141
+ * Session key helpers — handles standardized, canonical, and legacy formats.
142
142
  *
143
- * Canonical format (from resolveAgentRoute):
144
- * agent:{agentId}:alfe:direct:{userId}
145
- * agent:{agentId}:alfe:direct:{userId}:thread:{conversationId}
143
+ * Standardized format (Alfe-controlled):
144
+ * alfe:{mode}:{identity} — single-threaded (SMS, WhatsApp)
145
+ * alfe:{mode}:{identity}:{convId} — multi-threaded (web chat)
146
146
  *
147
- * Legacy format (from chat adapter):
147
+ * OpenClaw canonical format (from resolveAgentRoute):
148
+ * agent:{ocAgentId}:alfe:[default:]direct:{senderId}[:thread:{conversationId}]
149
+ *
150
+ * Legacy formats (deprecated):
151
+ * sms-{agentId}-{phone}
152
+ * wa-{agentId}-{phone}
148
153
  * chat-{tenantId}-{agentId}-{suffix}
149
154
  * 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.
153
155
  */
156
+ /** Single-threaded channel modes — identity IS the conversation. */
157
+ const SINGLE_THREADED_MODES = new Set([
158
+ "sms",
159
+ "whatsapp",
160
+ "mobile"
161
+ ]);
154
162
  /**
155
163
  * Check if a session key belongs to the Alfe chat channel.
156
- * Handles both canonical and legacy formats.
164
+ * Handles standardized, canonical, and legacy formats.
157
165
  */
158
166
  function isAlfeSessionKey(key) {
167
+ if (key.startsWith("alfe:")) return true;
159
168
  if (key.includes(":alfe:")) return true;
160
- if (key.includes("chat-")) return true;
169
+ if (key.includes("chat-") || key.startsWith("sms-") || key.startsWith("wa-")) return true;
161
170
  return false;
162
171
  }
172
+ /**
173
+ * Parse session key metadata. Returns available fields from any format.
174
+ *
175
+ * For the callback flow, `conversationId` is the critical field — it maps
176
+ * to the channel registry key used by getChannelCallback().
177
+ */
178
+ function parseAlfeSessionKey(key) {
179
+ const standardMatch = /^alfe:(\w+):(.+)$/.exec(key);
180
+ if (standardMatch) {
181
+ const [, mode, rest] = standardMatch;
182
+ if (SINGLE_THREADED_MODES.has(mode)) return {
183
+ agentId: "",
184
+ userId: rest,
185
+ conversationId: key,
186
+ tenantId: "",
187
+ mode
188
+ };
189
+ const lastColon = rest.lastIndexOf(":");
190
+ if (lastColon > 0) return {
191
+ agentId: "",
192
+ userId: rest.slice(0, lastColon),
193
+ conversationId: key,
194
+ tenantId: "",
195
+ mode
196
+ };
197
+ return {
198
+ agentId: "",
199
+ userId: rest,
200
+ conversationId: key,
201
+ tenantId: "",
202
+ mode
203
+ };
204
+ }
205
+ const canonicalMatch = /^agent:([^:]+):alfe:(?:default:)?direct:(.+?)(?::thread:(.+))?$/.exec(key);
206
+ if (canonicalMatch) {
207
+ const [, matchAgentId, matchUserId, matchConvId] = canonicalMatch;
208
+ return {
209
+ agentId: matchAgentId,
210
+ userId: matchUserId,
211
+ conversationId: matchConvId || "",
212
+ tenantId: "",
213
+ mode: ""
214
+ };
215
+ }
216
+ const rawKey = key.includes(":") ? key.slice(key.lastIndexOf(":") + 1) : key;
217
+ const legacyMatch = /^chat-([^-]+)-([^-]+)/.exec(rawKey);
218
+ return {
219
+ agentId: legacyMatch?.[2] ?? "",
220
+ userId: "",
221
+ conversationId: "",
222
+ tenantId: legacyMatch?.[1] ?? "",
223
+ mode: "chat"
224
+ };
225
+ }
226
+ /**
227
+ * Extract the channel mode from a standardized session key or conversationId.
228
+ * Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat') or fallback.
229
+ */
230
+ function extractChannelMode(conversationId, fallback = "chat") {
231
+ return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
232
+ }
163
233
  //#endregion
164
234
  //#region src/session-store.ts
165
235
  /**
@@ -363,14 +433,16 @@ async function handleAgentRequest(request, log) {
363
433
  });
364
434
  });
365
435
  try {
436
+ const channelMode = extractChannelMode(conversationId ?? "", clientType ?? "chat");
437
+ const channelLabel = channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : "Alfe";
366
438
  const shortConvId = conversationId?.slice(-8) ?? "";
367
439
  const userLabel = displayName ?? userId ?? senderId;
368
- const conversationLabel = conversationType === "group" ? shortConvId ? `[Alfe] Group (${shortConvId})` : "[Alfe] Group" : shortConvId ? `[Alfe] ${userLabel} (${shortConvId})` : `[Alfe] ${userLabel}`;
440
+ const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
369
441
  resolvedOpenClawKey = (await dispatchInbound({
370
442
  cfg,
371
443
  runtime: { channel: runtime.channel },
372
444
  channel: "alfe",
373
- channelLabel: "Alfe",
445
+ channelLabel,
374
446
  accountId: "default",
375
447
  peer: conversationType === "group" ? {
376
448
  kind: "group",
@@ -390,7 +462,8 @@ async function handleAgentRequest(request, log) {
390
462
  ...tenantId ? { TenantId: tenantId } : {},
391
463
  ...clientType ? { ClientType: clientType } : {},
392
464
  ...conversationId ? { ConversationId: conversationId } : {},
393
- ...displayName ? { SenderName: displayName } : {}
465
+ ...displayName ? { SenderName: displayName } : {},
466
+ ChannelMode: channelMode
394
467
  },
395
468
  deliver: async (payload) => {
396
469
  const responseText = payload.text ?? "";
@@ -559,8 +632,16 @@ const plugin = {
559
632
  api.on("message", async (...eventArgs) => {
560
633
  const event = eventArgs[0];
561
634
  const key = event.sessionKey;
562
- if (!key || isAlfeSessionKey(key)) return;
563
- await addMessage(key, event.role, event.content);
635
+ if (!key) return;
636
+ if (isAlfeSessionKey(key)) {
637
+ if (event.role === "assistant" && chatClient) {
638
+ const parsed = parseAlfeSessionKey(key);
639
+ if (parsed.conversationId) chatClient.notify("agent-message", {
640
+ conversationId: parsed.conversationId,
641
+ text: event.content
642
+ });
643
+ }
644
+ } else await addMessage(key, event.role, event.content);
564
645
  });
565
646
  api.on("session_end", (...eventArgs) => {
566
647
  const key = eventArgs[0].sessionKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
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,7 +27,7 @@
27
27
  "openclaw.plugin.json"
28
28
  ],
29
29
  "dependencies": {
30
- "@alfe.ai/chat": "^0.0.5"
30
+ "@alfe.ai/chat": "^0.0.6"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "openclaw": ">=2026.3.0"