@alfe.ai/openclaw-chat 0.0.34 → 0.1.1

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/index.d.cts CHANGED
@@ -33,6 +33,8 @@ interface SessionSummary {
33
33
  sessionId: string;
34
34
  agentId: string;
35
35
  channel: string;
36
+ tenantId?: string;
37
+ userId?: string;
36
38
  createdAt: string;
37
39
  lastMessageAt?: string;
38
40
  preview?: string;
package/dist/index.d.ts CHANGED
@@ -33,6 +33,8 @@ interface SessionSummary {
33
33
  sessionId: string;
34
34
  agentId: string;
35
35
  channel: string;
36
+ tenantId?: string;
37
+ userId?: string;
36
38
  createdAt: string;
37
39
  lastMessageAt?: string;
38
40
  preview?: string;
package/dist/plugin.d.cts CHANGED
@@ -33,6 +33,28 @@ interface AlfePluginConfig {
33
33
  /** API key for chat service auth */
34
34
  apiKey?: string;
35
35
  }
36
+ interface AlfeOutboundChatClient {
37
+ notify(event: string, payload: Record<string, unknown>): void;
38
+ }
39
+ interface AlfeOutboundSessionSummary {
40
+ sessionId: string;
41
+ userId?: string;
42
+ createdAt: string;
43
+ lastMessageAt?: string;
44
+ }
45
+ interface AlfeOutboundSession {
46
+ sessionId: string;
47
+ userId?: string;
48
+ }
49
+ interface AlfeChannelOutboundDeps {
50
+ getChatClient: () => AlfeOutboundChatClient | null;
51
+ listSessions: (filters?: {
52
+ userId?: string;
53
+ }) => Promise<AlfeOutboundSessionSummary[]>;
54
+ getSession: (sessionId: string) => Promise<AlfeOutboundSession | null>;
55
+ createSession: (sessionId: string, agentId: string, channel: string, tenantId?: string, userId?: string) => Promise<unknown>;
56
+ addMessage: (sessionId: string, role: 'user' | 'assistant', content: string, senderId?: string, senderName?: string) => Promise<void>;
57
+ }
36
58
  //#endregion
37
59
  //#region src/alfe-channel.d.ts
38
60
  /** OpenClaw config shape — inline to avoid runtime dependency on openclaw package */
@@ -43,13 +65,23 @@ interface OpenClawConfig {
43
65
  };
44
66
  [key: string]: unknown;
45
67
  }
68
+ interface OutboundDeliveryResult {
69
+ channel: 'alfe';
70
+ messageId: string;
71
+ conversationId: string;
72
+ timestamp: number;
73
+ }
46
74
  /**
47
75
  * Creates the Alfe ChannelPlugin object for registration with OpenClaw.
48
76
  *
49
77
  * This follows the same pattern as built-in channels (Telegram, Discord, etc.)
50
78
  * but is registered dynamically via api.registerChannel().
79
+ *
80
+ * `deps` enables outbound delivery and peer discovery. When omitted (e.g.
81
+ * during CLI metadata registration), outbound calls throw and the directory
82
+ * is empty — but the channel is still registered for inbound use.
51
83
  */
52
- declare function createAlfeChannelPlugin(): {
84
+ declare function createAlfeChannelPlugin(deps?: AlfeChannelOutboundDeps): {
53
85
  id: string;
54
86
  meta: {
55
87
  id: string;
@@ -118,13 +150,47 @@ declare function createAlfeChannelPlugin(): {
118
150
  }): string | undefined;
119
151
  };
120
152
  /**
121
- * Outbound delivery via gateway.
122
- * The chat relay service on Fly.io handles actual delivery
123
- * to connected web/mobile clients via the gateway.
153
+ * Outbound delivery via the plugin (`deliveryMode: 'direct'`).
154
+ *
155
+ * OpenClaw calls `resolveTarget` to validate the agent-supplied `to`,
156
+ * then `sendText` / `sendMedia` per chunk. We push each chunk to the
157
+ * chat relay service via `chatClient.notify('agent-message', …)`,
158
+ * which broadcasts to all connected web/mobile clients on that
159
+ * conversation and notifies offline participants.
124
160
  */
125
161
  outbound: {
126
- deliveryMode: "gateway";
162
+ deliveryMode: "direct";
127
163
  textChunkLimit: number;
164
+ resolveTarget(params: {
165
+ to?: string;
166
+ }): {
167
+ ok: true;
168
+ to: string;
169
+ } | {
170
+ ok: false;
171
+ error: Error;
172
+ };
173
+ sendText(ctx: {
174
+ to: string;
175
+ text: string;
176
+ }): Promise<OutboundDeliveryResult>;
177
+ sendMedia(ctx: {
178
+ to: string;
179
+ text: string;
180
+ mediaUrl?: string;
181
+ }): Promise<OutboundDeliveryResult>;
182
+ };
183
+ /**
184
+ * Directory adapter — exposes users the agent has chatted with so the
185
+ * `message` tool can suggest valid targets. Backed by the on-disk
186
+ * session store; live-list is the same as static-list since sessions
187
+ * are the only source of truth.
188
+ */
189
+ directory: {
190
+ listPeers(): Promise<{
191
+ kind: "user";
192
+ id: string;
193
+ }[]>;
128
194
  };
129
195
  /**
130
196
  * Setup adapter — minimal for Alfe since no external tokens are needed.
package/dist/plugin.d.ts CHANGED
@@ -33,6 +33,28 @@ interface AlfePluginConfig {
33
33
  /** API key for chat service auth */
34
34
  apiKey?: string;
35
35
  }
36
+ interface AlfeOutboundChatClient {
37
+ notify(event: string, payload: Record<string, unknown>): void;
38
+ }
39
+ interface AlfeOutboundSessionSummary {
40
+ sessionId: string;
41
+ userId?: string;
42
+ createdAt: string;
43
+ lastMessageAt?: string;
44
+ }
45
+ interface AlfeOutboundSession {
46
+ sessionId: string;
47
+ userId?: string;
48
+ }
49
+ interface AlfeChannelOutboundDeps {
50
+ getChatClient: () => AlfeOutboundChatClient | null;
51
+ listSessions: (filters?: {
52
+ userId?: string;
53
+ }) => Promise<AlfeOutboundSessionSummary[]>;
54
+ getSession: (sessionId: string) => Promise<AlfeOutboundSession | null>;
55
+ createSession: (sessionId: string, agentId: string, channel: string, tenantId?: string, userId?: string) => Promise<unknown>;
56
+ addMessage: (sessionId: string, role: 'user' | 'assistant', content: string, senderId?: string, senderName?: string) => Promise<void>;
57
+ }
36
58
  //#endregion
37
59
  //#region src/alfe-channel.d.ts
38
60
  /** OpenClaw config shape — inline to avoid runtime dependency on openclaw package */
@@ -43,13 +65,23 @@ interface OpenClawConfig {
43
65
  };
44
66
  [key: string]: unknown;
45
67
  }
68
+ interface OutboundDeliveryResult {
69
+ channel: 'alfe';
70
+ messageId: string;
71
+ conversationId: string;
72
+ timestamp: number;
73
+ }
46
74
  /**
47
75
  * Creates the Alfe ChannelPlugin object for registration with OpenClaw.
48
76
  *
49
77
  * This follows the same pattern as built-in channels (Telegram, Discord, etc.)
50
78
  * but is registered dynamically via api.registerChannel().
79
+ *
80
+ * `deps` enables outbound delivery and peer discovery. When omitted (e.g.
81
+ * during CLI metadata registration), outbound calls throw and the directory
82
+ * is empty — but the channel is still registered for inbound use.
51
83
  */
52
- declare function createAlfeChannelPlugin(): {
84
+ declare function createAlfeChannelPlugin(deps?: AlfeChannelOutboundDeps): {
53
85
  id: string;
54
86
  meta: {
55
87
  id: string;
@@ -118,13 +150,47 @@ declare function createAlfeChannelPlugin(): {
118
150
  }): string | undefined;
119
151
  };
120
152
  /**
121
- * Outbound delivery via gateway.
122
- * The chat relay service on Fly.io handles actual delivery
123
- * to connected web/mobile clients via the gateway.
153
+ * Outbound delivery via the plugin (`deliveryMode: 'direct'`).
154
+ *
155
+ * OpenClaw calls `resolveTarget` to validate the agent-supplied `to`,
156
+ * then `sendText` / `sendMedia` per chunk. We push each chunk to the
157
+ * chat relay service via `chatClient.notify('agent-message', …)`,
158
+ * which broadcasts to all connected web/mobile clients on that
159
+ * conversation and notifies offline participants.
124
160
  */
125
161
  outbound: {
126
- deliveryMode: "gateway";
162
+ deliveryMode: "direct";
127
163
  textChunkLimit: number;
164
+ resolveTarget(params: {
165
+ to?: string;
166
+ }): {
167
+ ok: true;
168
+ to: string;
169
+ } | {
170
+ ok: false;
171
+ error: Error;
172
+ };
173
+ sendText(ctx: {
174
+ to: string;
175
+ text: string;
176
+ }): Promise<OutboundDeliveryResult>;
177
+ sendMedia(ctx: {
178
+ to: string;
179
+ text: string;
180
+ mediaUrl?: string;
181
+ }): Promise<OutboundDeliveryResult>;
182
+ };
183
+ /**
184
+ * Directory adapter — exposes users the agent has chatted with so the
185
+ * `message` tool can suggest valid targets. Backed by the on-disk
186
+ * session store; live-list is the same as static-list since sessions
187
+ * are the only source of truth.
188
+ */
189
+ directory: {
190
+ listPeers(): Promise<{
191
+ kind: "user";
192
+ id: string;
193
+ }[]>;
128
194
  };
129
195
  /**
130
196
  * Setup adapter — minimal for Alfe since no external tokens are needed.
package/dist/plugin2.cjs CHANGED
@@ -3,12 +3,56 @@ let node_fs_promises = require("node:fs/promises");
3
3
  let node_path = require("node:path");
4
4
  let node_os = require("node:os");
5
5
  let _alfe_ai_chat = require("@alfe.ai/chat");
6
- let _alfe_ai_config = require("@alfe.ai/config");
7
- let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
6
+ let node_crypto = require("node:crypto");
8
7
  let node_fs = require("node:fs");
9
8
  //#region src/alfe-channel.ts
9
+ /**
10
+ * Alfe channel plugin definition — registers 'alfe' as an OpenClaw channel.
11
+ *
12
+ * Web and mobile clients share the same channel and conversation sessions.
13
+ * Outbound delivery uses `deliveryMode: 'direct'` — the plugin's sendText /
14
+ * sendMedia handlers push messages through the chat relay service.
15
+ *
16
+ * Target formats accepted by `resolveTarget`:
17
+ * user:{clerkUserId} — most recent conversation for that user (created on demand)
18
+ * conv:{conversationId} — a specific existing conversation
19
+ *
20
+ * Config section: channels.alfe in openclaw.yaml
21
+ */
10
22
  const CHANNEL_ID = "alfe";
11
23
  const DEFAULT_ACCOUNT_ID = "default";
24
+ async function sendViaChat(deps, ctx, mediaUrl) {
25
+ const client = deps.getChatClient();
26
+ if (!client) throw new Error("Chat service not connected — cannot deliver");
27
+ let conversationId;
28
+ if (ctx.to.startsWith("conv:")) {
29
+ conversationId = ctx.to.slice(5);
30
+ if (!await deps.getSession(conversationId)) throw new Error(`Conversation not found: ${conversationId}`);
31
+ } else {
32
+ const userId = ctx.to.startsWith("user:") ? ctx.to.slice(5) : ctx.to;
33
+ if (!userId) throw new Error("Empty userId in target");
34
+ const sessions = await deps.listSessions({ userId });
35
+ if (sessions.length > 0) conversationId = sessions[0].sessionId;
36
+ else {
37
+ conversationId = `alfe:chat:${userId}:${(0, node_crypto.randomUUID)()}`;
38
+ await deps.createSession(conversationId, "", "alfe", void 0, userId);
39
+ }
40
+ }
41
+ await deps.addMessage(conversationId, "assistant", ctx.text);
42
+ const messageId = (0, node_crypto.randomUUID)();
43
+ client.notify("agent-message", {
44
+ conversationId,
45
+ text: ctx.text,
46
+ sessionKey: conversationId,
47
+ ...mediaUrl ? { mediaUrls: [mediaUrl] } : {}
48
+ });
49
+ return {
50
+ channel: "alfe",
51
+ messageId,
52
+ conversationId,
53
+ timestamp: Date.now()
54
+ };
55
+ }
12
56
  function getChannelSection(cfg) {
13
57
  return cfg.channels?.alfe ?? {};
14
58
  }
@@ -17,8 +61,12 @@ function getChannelSection(cfg) {
17
61
  *
18
62
  * This follows the same pattern as built-in channels (Telegram, Discord, etc.)
19
63
  * but is registered dynamically via api.registerChannel().
64
+ *
65
+ * `deps` enables outbound delivery and peer discovery. When omitted (e.g.
66
+ * during CLI metadata registration), outbound calls throw and the directory
67
+ * is empty — but the channel is still registered for inbound use.
20
68
  */
21
- function createAlfeChannelPlugin() {
69
+ function createAlfeChannelPlugin(deps) {
22
70
  return {
23
71
  id: CHANNEL_ID,
24
72
  meta: {
@@ -97,9 +145,58 @@ function createAlfeChannelPlugin() {
97
145
  }
98
146
  },
99
147
  outbound: {
100
- deliveryMode: "gateway",
101
- textChunkLimit: 4e3
148
+ deliveryMode: "direct",
149
+ textChunkLimit: 4e3,
150
+ resolveTarget(params) {
151
+ const to = params.to;
152
+ if (!to) return {
153
+ ok: false,
154
+ error: /* @__PURE__ */ new Error("Missing target — use user:{userId} or conv:{conversationId}")
155
+ };
156
+ if (to.startsWith("conv:")) {
157
+ if (!to.slice(5)) return {
158
+ ok: false,
159
+ error: /* @__PURE__ */ new Error("Empty conversation ID")
160
+ };
161
+ return {
162
+ ok: true,
163
+ to
164
+ };
165
+ }
166
+ const userId = to.startsWith("user:") ? to.slice(5) : to;
167
+ if (!userId || userId === "anon") return {
168
+ ok: false,
169
+ error: /* @__PURE__ */ new Error("Invalid target: userId is required")
170
+ };
171
+ return {
172
+ ok: true,
173
+ to: `user:${userId}`
174
+ };
175
+ },
176
+ async sendText(ctx) {
177
+ if (!deps) throw new Error("Alfe channel deps not configured — outbound disabled");
178
+ return await sendViaChat(deps, ctx, void 0);
179
+ },
180
+ async sendMedia(ctx) {
181
+ if (!deps) throw new Error("Alfe channel deps not configured — outbound disabled");
182
+ return await sendViaChat(deps, ctx, ctx.mediaUrl);
183
+ }
102
184
  },
185
+ directory: { async listPeers() {
186
+ if (!deps) return [];
187
+ const sessions = await deps.listSessions();
188
+ const seen = /* @__PURE__ */ new Set();
189
+ const peers = [];
190
+ for (const s of sessions) {
191
+ if (!s.userId || seen.has(s.userId)) continue;
192
+ seen.add(s.userId);
193
+ peers.push({
194
+ kind: "user",
195
+ id: s.userId
196
+ });
197
+ }
198
+ return peers;
199
+ } },
103
200
  setup: {
104
201
  resolveAccountId(params) {
105
202
  return params.accountId ?? DEFAULT_ACCOUNT_ID;
@@ -268,6 +365,8 @@ async function listSessions(filters, limit = 50) {
268
365
  sessionId: session.sessionId,
269
366
  agentId: session.agentId,
270
367
  channel: session.channel,
368
+ ...session.tenantId ? { tenantId: session.tenantId } : {},
369
+ ...session.userId ? { userId: session.userId } : {},
271
370
  createdAt: session.createdAt,
272
371
  lastMessageAt: lastMsg ? new Date(lastMsg.timestamp).toISOString() : void 0,
273
372
  preview: lastMsg?.content.slice(0, 100),
@@ -537,7 +636,6 @@ function resolveOpenClawSdk(log) {
537
636
  let pluginRuntime = null;
538
637
  let chatClient = null;
539
638
  let connectingPromise = null;
540
- let metricsClient = null;
541
639
  const MAX_FILE_SIZE = 50 * 1024 * 1024;
542
640
  const DOWNLOAD_TIMEOUT_MS = 3e4;
543
641
  const MAX_REDIRECTS = 5;
@@ -629,13 +727,6 @@ async function handleAgentRequest(request, log) {
629
727
  const sessionId = conversationId ?? legacySessionKey;
630
728
  if (!await getSession(sessionId)) await createSession(sessionId, "", "alfe", tenantId, userId);
631
729
  await addMessage(sessionId, "user", message, userId ?? senderId, displayName ?? senderId);
632
- if (metricsClient && userId) metricsClient.recordActivity({
633
- userId,
634
- channel: "alfe",
635
- role: "user"
636
- }).catch((err) => {
637
- log.warn(`Metrics report failed: ${err instanceof Error ? err.message : String(err)}`);
638
- });
639
730
  let resolvedOpenClawKey = null;
640
731
  const unsubscribe = runtime.events.onAgentEvent((evt) => {
641
732
  if (!evt.sessionKey) return;
@@ -717,13 +808,6 @@ async function handleAgentRequest(request, log) {
717
808
  sessionKey: resolvedOpenClawKey ?? legacySessionKey,
718
809
  ...mediaUrls.length ? { mediaUrls } : {}
719
810
  });
720
- if (metricsClient && userId) metricsClient.recordActivity({
721
- userId,
722
- channel: "alfe",
723
- role: "assistant"
724
- }).catch((err) => {
725
- log.warn(`Metrics report failed: ${err instanceof Error ? err.message : String(err)}`);
726
- });
727
811
  },
728
812
  onRecordError: (err) => {
729
813
  log.error(`Session record error: ${err instanceof Error ? err.message : String(err)}`);
@@ -807,7 +891,13 @@ const plugin = {
807
891
  activate(api) {
808
892
  const log = api.logger;
809
893
  const alreadyActivated = globalThis.__alfeChatPluginActivated === true;
810
- const alfeChannel = createAlfeChannelPlugin();
894
+ const alfeChannel = createAlfeChannelPlugin({
895
+ getChatClient: () => chatClient,
896
+ listSessions,
897
+ getSession,
898
+ createSession,
899
+ addMessage
900
+ });
811
901
  api.registerChannel(alfeChannel);
812
902
  log.info(`Registered channel: ${alfeChannel.id}`);
813
903
  const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
@@ -820,15 +910,6 @@ const plugin = {
820
910
  log.info("Chat plugin registering...");
821
911
  resolveOpenClawSdk(log);
822
912
  pluginRuntime = api.runtime ?? null;
823
- try {
824
- const cfg = (0, _alfe_ai_config.resolveConfig)();
825
- metricsClient = new _alfe_ai_agent_api_client.AgentApiClient({
826
- apiKey: cfg.apiKey,
827
- apiUrl: cfg.apiUrl
828
- });
829
- } catch {
830
- log.debug("Metrics client not initialized — activity tracking disabled");
831
- }
832
913
  connectingPromise = Promise.resolve().then(() => {
833
914
  try {
834
915
  const { apiKey, chatWsUrl } = (0, _alfe_ai_chat.resolveAlfeChat)({
@@ -908,7 +989,6 @@ const plugin = {
908
989
  }
909
990
  pluginRuntime = null;
910
991
  dispatchInbound = null;
911
- metricsClient = null;
912
992
  log.info("Chat plugin deactivated");
913
993
  };
914
994
  if (typeof api.registerGatewayMethod === "function") {
@@ -975,7 +1055,6 @@ const plugin = {
975
1055
  }
976
1056
  pluginRuntime = null;
977
1057
  dispatchInbound = null;
978
- metricsClient = null;
979
1058
  log.info("Chat plugin deactivated");
980
1059
  }
981
1060
  };
package/dist/plugin2.js CHANGED
@@ -3,12 +3,56 @@ import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promi
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
6
- import { resolveConfig } from "@alfe.ai/config";
7
- import { AgentApiClient } from "@alfe.ai/agent-api-client";
6
+ import { randomUUID } from "node:crypto";
8
7
  import { existsSync } from "node:fs";
9
8
  //#region src/alfe-channel.ts
9
+ /**
10
+ * Alfe channel plugin definition — registers 'alfe' as an OpenClaw channel.
11
+ *
12
+ * Web and mobile clients share the same channel and conversation sessions.
13
+ * Outbound delivery uses `deliveryMode: 'direct'` — the plugin's sendText /
14
+ * sendMedia handlers push messages through the chat relay service.
15
+ *
16
+ * Target formats accepted by `resolveTarget`:
17
+ * user:{clerkUserId} — most recent conversation for that user (created on demand)
18
+ * conv:{conversationId} — a specific existing conversation
19
+ *
20
+ * Config section: channels.alfe in openclaw.yaml
21
+ */
10
22
  const CHANNEL_ID = "alfe";
11
23
  const DEFAULT_ACCOUNT_ID = "default";
24
+ async function sendViaChat(deps, ctx, mediaUrl) {
25
+ const client = deps.getChatClient();
26
+ if (!client) throw new Error("Chat service not connected — cannot deliver");
27
+ let conversationId;
28
+ if (ctx.to.startsWith("conv:")) {
29
+ conversationId = ctx.to.slice(5);
30
+ if (!await deps.getSession(conversationId)) throw new Error(`Conversation not found: ${conversationId}`);
31
+ } else {
32
+ const userId = ctx.to.startsWith("user:") ? ctx.to.slice(5) : ctx.to;
33
+ if (!userId) throw new Error("Empty userId in target");
34
+ const sessions = await deps.listSessions({ userId });
35
+ if (sessions.length > 0) conversationId = sessions[0].sessionId;
36
+ else {
37
+ conversationId = `alfe:chat:${userId}:${randomUUID()}`;
38
+ await deps.createSession(conversationId, "", "alfe", void 0, userId);
39
+ }
40
+ }
41
+ await deps.addMessage(conversationId, "assistant", ctx.text);
42
+ const messageId = randomUUID();
43
+ client.notify("agent-message", {
44
+ conversationId,
45
+ text: ctx.text,
46
+ sessionKey: conversationId,
47
+ ...mediaUrl ? { mediaUrls: [mediaUrl] } : {}
48
+ });
49
+ return {
50
+ channel: "alfe",
51
+ messageId,
52
+ conversationId,
53
+ timestamp: Date.now()
54
+ };
55
+ }
12
56
  function getChannelSection(cfg) {
13
57
  return cfg.channels?.alfe ?? {};
14
58
  }
@@ -17,8 +61,12 @@ function getChannelSection(cfg) {
17
61
  *
18
62
  * This follows the same pattern as built-in channels (Telegram, Discord, etc.)
19
63
  * but is registered dynamically via api.registerChannel().
64
+ *
65
+ * `deps` enables outbound delivery and peer discovery. When omitted (e.g.
66
+ * during CLI metadata registration), outbound calls throw and the directory
67
+ * is empty — but the channel is still registered for inbound use.
20
68
  */
21
- function createAlfeChannelPlugin() {
69
+ function createAlfeChannelPlugin(deps) {
22
70
  return {
23
71
  id: CHANNEL_ID,
24
72
  meta: {
@@ -97,9 +145,58 @@ function createAlfeChannelPlugin() {
97
145
  }
98
146
  },
99
147
  outbound: {
100
- deliveryMode: "gateway",
101
- textChunkLimit: 4e3
148
+ deliveryMode: "direct",
149
+ textChunkLimit: 4e3,
150
+ resolveTarget(params) {
151
+ const to = params.to;
152
+ if (!to) return {
153
+ ok: false,
154
+ error: /* @__PURE__ */ new Error("Missing target — use user:{userId} or conv:{conversationId}")
155
+ };
156
+ if (to.startsWith("conv:")) {
157
+ if (!to.slice(5)) return {
158
+ ok: false,
159
+ error: /* @__PURE__ */ new Error("Empty conversation ID")
160
+ };
161
+ return {
162
+ ok: true,
163
+ to
164
+ };
165
+ }
166
+ const userId = to.startsWith("user:") ? to.slice(5) : to;
167
+ if (!userId || userId === "anon") return {
168
+ ok: false,
169
+ error: /* @__PURE__ */ new Error("Invalid target: userId is required")
170
+ };
171
+ return {
172
+ ok: true,
173
+ to: `user:${userId}`
174
+ };
175
+ },
176
+ async sendText(ctx) {
177
+ if (!deps) throw new Error("Alfe channel deps not configured — outbound disabled");
178
+ return await sendViaChat(deps, ctx, void 0);
179
+ },
180
+ async sendMedia(ctx) {
181
+ if (!deps) throw new Error("Alfe channel deps not configured — outbound disabled");
182
+ return await sendViaChat(deps, ctx, ctx.mediaUrl);
183
+ }
102
184
  },
185
+ directory: { async listPeers() {
186
+ if (!deps) return [];
187
+ const sessions = await deps.listSessions();
188
+ const seen = /* @__PURE__ */ new Set();
189
+ const peers = [];
190
+ for (const s of sessions) {
191
+ if (!s.userId || seen.has(s.userId)) continue;
192
+ seen.add(s.userId);
193
+ peers.push({
194
+ kind: "user",
195
+ id: s.userId
196
+ });
197
+ }
198
+ return peers;
199
+ } },
103
200
  setup: {
104
201
  resolveAccountId(params) {
105
202
  return params.accountId ?? DEFAULT_ACCOUNT_ID;
@@ -268,6 +365,8 @@ async function listSessions(filters, limit = 50) {
268
365
  sessionId: session.sessionId,
269
366
  agentId: session.agentId,
270
367
  channel: session.channel,
368
+ ...session.tenantId ? { tenantId: session.tenantId } : {},
369
+ ...session.userId ? { userId: session.userId } : {},
271
370
  createdAt: session.createdAt,
272
371
  lastMessageAt: lastMsg ? new Date(lastMsg.timestamp).toISOString() : void 0,
273
372
  preview: lastMsg?.content.slice(0, 100),
@@ -537,7 +636,6 @@ function resolveOpenClawSdk(log) {
537
636
  let pluginRuntime = null;
538
637
  let chatClient = null;
539
638
  let connectingPromise = null;
540
- let metricsClient = null;
541
639
  const MAX_FILE_SIZE = 50 * 1024 * 1024;
542
640
  const DOWNLOAD_TIMEOUT_MS = 3e4;
543
641
  const MAX_REDIRECTS = 5;
@@ -629,13 +727,6 @@ async function handleAgentRequest(request, log) {
629
727
  const sessionId = conversationId ?? legacySessionKey;
630
728
  if (!await getSession(sessionId)) await createSession(sessionId, "", "alfe", tenantId, userId);
631
729
  await addMessage(sessionId, "user", message, userId ?? senderId, displayName ?? senderId);
632
- if (metricsClient && userId) metricsClient.recordActivity({
633
- userId,
634
- channel: "alfe",
635
- role: "user"
636
- }).catch((err) => {
637
- log.warn(`Metrics report failed: ${err instanceof Error ? err.message : String(err)}`);
638
- });
639
730
  let resolvedOpenClawKey = null;
640
731
  const unsubscribe = runtime.events.onAgentEvent((evt) => {
641
732
  if (!evt.sessionKey) return;
@@ -717,13 +808,6 @@ async function handleAgentRequest(request, log) {
717
808
  sessionKey: resolvedOpenClawKey ?? legacySessionKey,
718
809
  ...mediaUrls.length ? { mediaUrls } : {}
719
810
  });
720
- if (metricsClient && userId) metricsClient.recordActivity({
721
- userId,
722
- channel: "alfe",
723
- role: "assistant"
724
- }).catch((err) => {
725
- log.warn(`Metrics report failed: ${err instanceof Error ? err.message : String(err)}`);
726
- });
727
811
  },
728
812
  onRecordError: (err) => {
729
813
  log.error(`Session record error: ${err instanceof Error ? err.message : String(err)}`);
@@ -807,7 +891,13 @@ const plugin = {
807
891
  activate(api) {
808
892
  const log = api.logger;
809
893
  const alreadyActivated = globalThis.__alfeChatPluginActivated === true;
810
- const alfeChannel = createAlfeChannelPlugin();
894
+ const alfeChannel = createAlfeChannelPlugin({
895
+ getChatClient: () => chatClient,
896
+ listSessions,
897
+ getSession,
898
+ createSession,
899
+ addMessage
900
+ });
811
901
  api.registerChannel(alfeChannel);
812
902
  log.info(`Registered channel: ${alfeChannel.id}`);
813
903
  const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
@@ -820,15 +910,6 @@ const plugin = {
820
910
  log.info("Chat plugin registering...");
821
911
  resolveOpenClawSdk(log);
822
912
  pluginRuntime = api.runtime ?? null;
823
- try {
824
- const cfg = resolveConfig();
825
- metricsClient = new AgentApiClient({
826
- apiKey: cfg.apiKey,
827
- apiUrl: cfg.apiUrl
828
- });
829
- } catch {
830
- log.debug("Metrics client not initialized — activity tracking disabled");
831
- }
832
913
  connectingPromise = Promise.resolve().then(() => {
833
914
  try {
834
915
  const { apiKey, chatWsUrl } = resolveAlfeChat({
@@ -908,7 +989,6 @@ const plugin = {
908
989
  }
909
990
  pluginRuntime = null;
910
991
  dispatchInbound = null;
911
- metricsClient = null;
912
992
  log.info("Chat plugin deactivated");
913
993
  };
914
994
  if (typeof api.registerGatewayMethod === "function") {
@@ -975,7 +1055,6 @@ const plugin = {
975
1055
  }
976
1056
  pluginRuntime = null;
977
1057
  dispatchInbound = null;
978
- metricsClient = null;
979
1058
  log.info("Chat plugin deactivated");
980
1059
  }
981
1060
  };
@@ -7,5 +7,25 @@
7
7
  "type": "object",
8
8
  "additionalProperties": false,
9
9
  "properties": {}
10
+ },
11
+ "activation": { "onStartup": true },
12
+ "channels": ["alfe"],
13
+ "channelConfigs": {
14
+ "alfe": {
15
+ "schema": {
16
+ "$schema": "http://json-schema.org/draft-07/schema#",
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "name": { "type": "string" },
21
+ "enabled": { "type": "boolean" }
22
+ }
23
+ },
24
+ "label": "Alfe",
25
+ "description": "Alfe chat channel — web widget and mobile app share unified chat sessions"
26
+ }
27
+ },
28
+ "contracts": {
29
+ "tools": ["list_agents", "message_agent", "end_conversation"]
10
30
  }
11
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.0.34",
3
+ "version": "0.1.1",
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/agent-api-client": "^0.1.0",
30
+ "@alfe.ai/agent-api-client": "^0.1.1",
31
31
  "@alfe.ai/chat": "^0.0.8",
32
32
  "@alfe.ai/config": "^0.0.8"
33
33
  },