@alfe.ai/openclaw-chat 0.0.15 → 0.0.17
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 +126 -15
- package/dist/plugin2.js +126 -15
- package/package.json +2 -2
package/dist/plugin2.cjs
CHANGED
|
@@ -138,28 +138,128 @@ function createAlfeChannelPlugin() {
|
|
|
138
138
|
//#endregion
|
|
139
139
|
//#region src/session-keys.ts
|
|
140
140
|
/**
|
|
141
|
-
* Session key helpers — handles
|
|
141
|
+
* Session key helpers — handles standardized, canonical, and legacy formats.
|
|
142
142
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
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
|
-
*
|
|
147
|
+
* Wrapped standardized format (from chat inject via callAgent):
|
|
148
|
+
* agent:{ocAgentId}:alfe:{mode}:{identity}
|
|
149
|
+
*
|
|
150
|
+
* OpenClaw canonical format (from resolveAgentRoute):
|
|
151
|
+
* agent:{ocAgentId}:alfe:[default:]direct:{senderId}[:thread:{conversationId}]
|
|
152
|
+
*
|
|
153
|
+
* Legacy formats (deprecated):
|
|
154
|
+
* sms-{agentId}-{phone}
|
|
155
|
+
* wa-{agentId}-{phone}
|
|
148
156
|
* chat-{tenantId}-{agentId}-{suffix}
|
|
149
157
|
* 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
158
|
*/
|
|
159
|
+
/** Single-threaded channel modes — identity IS the conversation. */
|
|
160
|
+
const SINGLE_THREADED_MODES = new Set([
|
|
161
|
+
"sms",
|
|
162
|
+
"whatsapp",
|
|
163
|
+
"mobile"
|
|
164
|
+
]);
|
|
154
165
|
/**
|
|
155
166
|
* Check if a session key belongs to the Alfe chat channel.
|
|
156
|
-
* Handles
|
|
167
|
+
* Handles standardized, canonical, and legacy formats.
|
|
157
168
|
*/
|
|
158
169
|
function isAlfeSessionKey(key) {
|
|
170
|
+
if (key.startsWith("alfe:")) return true;
|
|
159
171
|
if (key.includes(":alfe:")) return true;
|
|
160
|
-
if (key.includes("chat-")) return true;
|
|
172
|
+
if (key.includes("chat-") || key.startsWith("sms-") || key.startsWith("wa-")) return true;
|
|
161
173
|
return false;
|
|
162
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Parse session key metadata. Returns available fields from any format.
|
|
177
|
+
*
|
|
178
|
+
* For the callback flow, `conversationId` is the critical field — it maps
|
|
179
|
+
* to the channel registry key used by getChannelCallback().
|
|
180
|
+
*/
|
|
181
|
+
function parseAlfeSessionKey(key) {
|
|
182
|
+
const standardMatch = /^alfe:(\w+):(.+)$/.exec(key);
|
|
183
|
+
if (standardMatch) {
|
|
184
|
+
const [, mode, rest] = standardMatch;
|
|
185
|
+
if (SINGLE_THREADED_MODES.has(mode)) return {
|
|
186
|
+
agentId: "",
|
|
187
|
+
userId: rest,
|
|
188
|
+
conversationId: key,
|
|
189
|
+
tenantId: "",
|
|
190
|
+
mode
|
|
191
|
+
};
|
|
192
|
+
const lastColon = rest.lastIndexOf(":");
|
|
193
|
+
if (lastColon > 0) return {
|
|
194
|
+
agentId: "",
|
|
195
|
+
userId: rest.slice(0, lastColon),
|
|
196
|
+
conversationId: key,
|
|
197
|
+
tenantId: "",
|
|
198
|
+
mode
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
agentId: "",
|
|
202
|
+
userId: rest,
|
|
203
|
+
conversationId: key,
|
|
204
|
+
tenantId: "",
|
|
205
|
+
mode
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const wrappedStandardMatch = /^agent:([^:]+):alfe:(?!default:|direct:)(\w+):(.+)$/.exec(key);
|
|
209
|
+
if (wrappedStandardMatch) {
|
|
210
|
+
const [, matchAgentId, wrappedMode, rest] = wrappedStandardMatch;
|
|
211
|
+
const standardKey = `alfe:${wrappedMode}:${rest}`;
|
|
212
|
+
if (SINGLE_THREADED_MODES.has(wrappedMode)) return {
|
|
213
|
+
agentId: matchAgentId,
|
|
214
|
+
userId: rest,
|
|
215
|
+
conversationId: standardKey,
|
|
216
|
+
tenantId: "",
|
|
217
|
+
mode: wrappedMode
|
|
218
|
+
};
|
|
219
|
+
const lastColon = rest.lastIndexOf(":");
|
|
220
|
+
if (lastColon > 0) return {
|
|
221
|
+
agentId: matchAgentId,
|
|
222
|
+
userId: rest.slice(0, lastColon),
|
|
223
|
+
conversationId: standardKey,
|
|
224
|
+
tenantId: "",
|
|
225
|
+
mode: wrappedMode
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
agentId: matchAgentId,
|
|
229
|
+
userId: rest,
|
|
230
|
+
conversationId: standardKey,
|
|
231
|
+
tenantId: "",
|
|
232
|
+
mode: wrappedMode
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const canonicalMatch = /^agent:([^:]+):alfe:(?:default:)?direct:(.+?)(?::thread:(.+))?$/.exec(key);
|
|
236
|
+
if (canonicalMatch) {
|
|
237
|
+
const [, matchAgentId, matchUserId, matchConvId] = canonicalMatch;
|
|
238
|
+
return {
|
|
239
|
+
agentId: matchAgentId,
|
|
240
|
+
userId: matchUserId,
|
|
241
|
+
conversationId: matchConvId || "",
|
|
242
|
+
tenantId: "",
|
|
243
|
+
mode: ""
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const rawKey = key.includes(":") ? key.slice(key.lastIndexOf(":") + 1) : key;
|
|
247
|
+
const legacyMatch = /^chat-([^-]+)-([^-]+)/.exec(rawKey);
|
|
248
|
+
return {
|
|
249
|
+
agentId: legacyMatch?.[2] ?? "",
|
|
250
|
+
userId: "",
|
|
251
|
+
conversationId: "",
|
|
252
|
+
tenantId: legacyMatch?.[1] ?? "",
|
|
253
|
+
mode: "chat"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Extract the channel mode from a standardized session key or conversationId.
|
|
258
|
+
* Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat') or fallback.
|
|
259
|
+
*/
|
|
260
|
+
function extractChannelMode(conversationId, fallback = "chat") {
|
|
261
|
+
return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
|
|
262
|
+
}
|
|
163
263
|
//#endregion
|
|
164
264
|
//#region src/session-store.ts
|
|
165
265
|
/**
|
|
@@ -363,14 +463,16 @@ async function handleAgentRequest(request, log) {
|
|
|
363
463
|
});
|
|
364
464
|
});
|
|
365
465
|
try {
|
|
466
|
+
const channelMode = extractChannelMode(conversationId ?? "", clientType ?? "chat");
|
|
467
|
+
const channelLabel = channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : "Alfe";
|
|
366
468
|
const shortConvId = conversationId?.slice(-8) ?? "";
|
|
367
469
|
const userLabel = displayName ?? userId ?? senderId;
|
|
368
|
-
const conversationLabel = conversationType === "group" ? shortConvId ? `[
|
|
470
|
+
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
369
471
|
resolvedOpenClawKey = (await dispatchInbound({
|
|
370
472
|
cfg,
|
|
371
473
|
runtime: { channel: runtime.channel },
|
|
372
474
|
channel: "alfe",
|
|
373
|
-
channelLabel
|
|
475
|
+
channelLabel,
|
|
374
476
|
accountId: "default",
|
|
375
477
|
peer: conversationType === "group" ? {
|
|
376
478
|
kind: "group",
|
|
@@ -390,7 +492,8 @@ async function handleAgentRequest(request, log) {
|
|
|
390
492
|
...tenantId ? { TenantId: tenantId } : {},
|
|
391
493
|
...clientType ? { ClientType: clientType } : {},
|
|
392
494
|
...conversationId ? { ConversationId: conversationId } : {},
|
|
393
|
-
...displayName ? { SenderName: displayName } : {}
|
|
495
|
+
...displayName ? { SenderName: displayName } : {},
|
|
496
|
+
ChannelMode: channelMode
|
|
394
497
|
},
|
|
395
498
|
deliver: async (payload) => {
|
|
396
499
|
const responseText = payload.text ?? "";
|
|
@@ -559,8 +662,16 @@ const plugin = {
|
|
|
559
662
|
api.on("message", async (...eventArgs) => {
|
|
560
663
|
const event = eventArgs[0];
|
|
561
664
|
const key = event.sessionKey;
|
|
562
|
-
if (!key
|
|
563
|
-
|
|
665
|
+
if (!key) return;
|
|
666
|
+
if (isAlfeSessionKey(key)) {
|
|
667
|
+
if (event.role === "assistant" && chatClient) {
|
|
668
|
+
const parsed = parseAlfeSessionKey(key);
|
|
669
|
+
if (parsed.conversationId) chatClient.notify("agent-message", {
|
|
670
|
+
conversationId: parsed.conversationId,
|
|
671
|
+
text: event.content
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
} else await addMessage(key, event.role, event.content);
|
|
564
675
|
});
|
|
565
676
|
api.on("session_end", (...eventArgs) => {
|
|
566
677
|
const key = eventArgs[0].sessionKey;
|
package/dist/plugin2.js
CHANGED
|
@@ -138,28 +138,128 @@ function createAlfeChannelPlugin() {
|
|
|
138
138
|
//#endregion
|
|
139
139
|
//#region src/session-keys.ts
|
|
140
140
|
/**
|
|
141
|
-
* Session key helpers — handles
|
|
141
|
+
* Session key helpers — handles standardized, canonical, and legacy formats.
|
|
142
142
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
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
|
-
*
|
|
147
|
+
* Wrapped standardized format (from chat inject via callAgent):
|
|
148
|
+
* agent:{ocAgentId}:alfe:{mode}:{identity}
|
|
149
|
+
*
|
|
150
|
+
* OpenClaw canonical format (from resolveAgentRoute):
|
|
151
|
+
* agent:{ocAgentId}:alfe:[default:]direct:{senderId}[:thread:{conversationId}]
|
|
152
|
+
*
|
|
153
|
+
* Legacy formats (deprecated):
|
|
154
|
+
* sms-{agentId}-{phone}
|
|
155
|
+
* wa-{agentId}-{phone}
|
|
148
156
|
* chat-{tenantId}-{agentId}-{suffix}
|
|
149
157
|
* 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
158
|
*/
|
|
159
|
+
/** Single-threaded channel modes — identity IS the conversation. */
|
|
160
|
+
const SINGLE_THREADED_MODES = new Set([
|
|
161
|
+
"sms",
|
|
162
|
+
"whatsapp",
|
|
163
|
+
"mobile"
|
|
164
|
+
]);
|
|
154
165
|
/**
|
|
155
166
|
* Check if a session key belongs to the Alfe chat channel.
|
|
156
|
-
* Handles
|
|
167
|
+
* Handles standardized, canonical, and legacy formats.
|
|
157
168
|
*/
|
|
158
169
|
function isAlfeSessionKey(key) {
|
|
170
|
+
if (key.startsWith("alfe:")) return true;
|
|
159
171
|
if (key.includes(":alfe:")) return true;
|
|
160
|
-
if (key.includes("chat-")) return true;
|
|
172
|
+
if (key.includes("chat-") || key.startsWith("sms-") || key.startsWith("wa-")) return true;
|
|
161
173
|
return false;
|
|
162
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Parse session key metadata. Returns available fields from any format.
|
|
177
|
+
*
|
|
178
|
+
* For the callback flow, `conversationId` is the critical field — it maps
|
|
179
|
+
* to the channel registry key used by getChannelCallback().
|
|
180
|
+
*/
|
|
181
|
+
function parseAlfeSessionKey(key) {
|
|
182
|
+
const standardMatch = /^alfe:(\w+):(.+)$/.exec(key);
|
|
183
|
+
if (standardMatch) {
|
|
184
|
+
const [, mode, rest] = standardMatch;
|
|
185
|
+
if (SINGLE_THREADED_MODES.has(mode)) return {
|
|
186
|
+
agentId: "",
|
|
187
|
+
userId: rest,
|
|
188
|
+
conversationId: key,
|
|
189
|
+
tenantId: "",
|
|
190
|
+
mode
|
|
191
|
+
};
|
|
192
|
+
const lastColon = rest.lastIndexOf(":");
|
|
193
|
+
if (lastColon > 0) return {
|
|
194
|
+
agentId: "",
|
|
195
|
+
userId: rest.slice(0, lastColon),
|
|
196
|
+
conversationId: key,
|
|
197
|
+
tenantId: "",
|
|
198
|
+
mode
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
agentId: "",
|
|
202
|
+
userId: rest,
|
|
203
|
+
conversationId: key,
|
|
204
|
+
tenantId: "",
|
|
205
|
+
mode
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const wrappedStandardMatch = /^agent:([^:]+):alfe:(?!default:|direct:)(\w+):(.+)$/.exec(key);
|
|
209
|
+
if (wrappedStandardMatch) {
|
|
210
|
+
const [, matchAgentId, wrappedMode, rest] = wrappedStandardMatch;
|
|
211
|
+
const standardKey = `alfe:${wrappedMode}:${rest}`;
|
|
212
|
+
if (SINGLE_THREADED_MODES.has(wrappedMode)) return {
|
|
213
|
+
agentId: matchAgentId,
|
|
214
|
+
userId: rest,
|
|
215
|
+
conversationId: standardKey,
|
|
216
|
+
tenantId: "",
|
|
217
|
+
mode: wrappedMode
|
|
218
|
+
};
|
|
219
|
+
const lastColon = rest.lastIndexOf(":");
|
|
220
|
+
if (lastColon > 0) return {
|
|
221
|
+
agentId: matchAgentId,
|
|
222
|
+
userId: rest.slice(0, lastColon),
|
|
223
|
+
conversationId: standardKey,
|
|
224
|
+
tenantId: "",
|
|
225
|
+
mode: wrappedMode
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
agentId: matchAgentId,
|
|
229
|
+
userId: rest,
|
|
230
|
+
conversationId: standardKey,
|
|
231
|
+
tenantId: "",
|
|
232
|
+
mode: wrappedMode
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const canonicalMatch = /^agent:([^:]+):alfe:(?:default:)?direct:(.+?)(?::thread:(.+))?$/.exec(key);
|
|
236
|
+
if (canonicalMatch) {
|
|
237
|
+
const [, matchAgentId, matchUserId, matchConvId] = canonicalMatch;
|
|
238
|
+
return {
|
|
239
|
+
agentId: matchAgentId,
|
|
240
|
+
userId: matchUserId,
|
|
241
|
+
conversationId: matchConvId || "",
|
|
242
|
+
tenantId: "",
|
|
243
|
+
mode: ""
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const rawKey = key.includes(":") ? key.slice(key.lastIndexOf(":") + 1) : key;
|
|
247
|
+
const legacyMatch = /^chat-([^-]+)-([^-]+)/.exec(rawKey);
|
|
248
|
+
return {
|
|
249
|
+
agentId: legacyMatch?.[2] ?? "",
|
|
250
|
+
userId: "",
|
|
251
|
+
conversationId: "",
|
|
252
|
+
tenantId: legacyMatch?.[1] ?? "",
|
|
253
|
+
mode: "chat"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Extract the channel mode from a standardized session key or conversationId.
|
|
258
|
+
* Returns the mode segment (e.g. 'sms', 'whatsapp', 'chat') or fallback.
|
|
259
|
+
*/
|
|
260
|
+
function extractChannelMode(conversationId, fallback = "chat") {
|
|
261
|
+
return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
|
|
262
|
+
}
|
|
163
263
|
//#endregion
|
|
164
264
|
//#region src/session-store.ts
|
|
165
265
|
/**
|
|
@@ -363,14 +463,16 @@ async function handleAgentRequest(request, log) {
|
|
|
363
463
|
});
|
|
364
464
|
});
|
|
365
465
|
try {
|
|
466
|
+
const channelMode = extractChannelMode(conversationId ?? "", clientType ?? "chat");
|
|
467
|
+
const channelLabel = channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : "Alfe";
|
|
366
468
|
const shortConvId = conversationId?.slice(-8) ?? "";
|
|
367
469
|
const userLabel = displayName ?? userId ?? senderId;
|
|
368
|
-
const conversationLabel = conversationType === "group" ? shortConvId ? `[
|
|
470
|
+
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
369
471
|
resolvedOpenClawKey = (await dispatchInbound({
|
|
370
472
|
cfg,
|
|
371
473
|
runtime: { channel: runtime.channel },
|
|
372
474
|
channel: "alfe",
|
|
373
|
-
channelLabel
|
|
475
|
+
channelLabel,
|
|
374
476
|
accountId: "default",
|
|
375
477
|
peer: conversationType === "group" ? {
|
|
376
478
|
kind: "group",
|
|
@@ -390,7 +492,8 @@ async function handleAgentRequest(request, log) {
|
|
|
390
492
|
...tenantId ? { TenantId: tenantId } : {},
|
|
391
493
|
...clientType ? { ClientType: clientType } : {},
|
|
392
494
|
...conversationId ? { ConversationId: conversationId } : {},
|
|
393
|
-
...displayName ? { SenderName: displayName } : {}
|
|
495
|
+
...displayName ? { SenderName: displayName } : {},
|
|
496
|
+
ChannelMode: channelMode
|
|
394
497
|
},
|
|
395
498
|
deliver: async (payload) => {
|
|
396
499
|
const responseText = payload.text ?? "";
|
|
@@ -559,8 +662,16 @@ const plugin = {
|
|
|
559
662
|
api.on("message", async (...eventArgs) => {
|
|
560
663
|
const event = eventArgs[0];
|
|
561
664
|
const key = event.sessionKey;
|
|
562
|
-
if (!key
|
|
563
|
-
|
|
665
|
+
if (!key) return;
|
|
666
|
+
if (isAlfeSessionKey(key)) {
|
|
667
|
+
if (event.role === "assistant" && chatClient) {
|
|
668
|
+
const parsed = parseAlfeSessionKey(key);
|
|
669
|
+
if (parsed.conversationId) chatClient.notify("agent-message", {
|
|
670
|
+
conversationId: parsed.conversationId,
|
|
671
|
+
text: event.content
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
} else await addMessage(key, event.role, event.content);
|
|
564
675
|
});
|
|
565
676
|
api.on("session_end", (...eventArgs) => {
|
|
566
677
|
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.
|
|
3
|
+
"version": "0.0.17",
|
|
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.
|
|
30
|
+
"@alfe.ai/chat": "^0.0.6"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"openclaw": ">=2026.3.0"
|