@alfe.ai/openclaw-chat 0.0.4 → 0.0.5

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.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AlfeResolvedAccount, c as OpenClawConfig, d as createAlfeChannelPlugin, i as AlfePluginConfig, l as OpenClawModule, n as AlfeChannelAccountConfig, o as IPCClient, r as AlfeChannelConfig, s as Logger, t as plugin, u as OpenClawPluginApi } from "./plugin.js";
1
+ import { a as AlfeResolvedAccount, i as AlfePluginConfig, n as createAlfeChannelPlugin, r as AlfeChannelConfig, t as plugin } from "./plugin.js";
2
2
 
3
3
  //#region src/session-store.d.ts
4
4
 
@@ -36,4 +36,4 @@ interface SessionSummary {
36
36
  messageCount: number;
37
37
  }
38
38
  //#endregion
39
- export { AlfeChannelAccountConfig, AlfeChannelConfig, AlfePluginConfig, AlfeResolvedAccount, type ChatMessage, IPCClient, Logger, OpenClawConfig, OpenClawModule, OpenClawPluginApi, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default, plugin };
39
+ export { type AlfeChannelConfig, type AlfePluginConfig, type AlfeResolvedAccount, type ChatMessage, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  import { n as createAlfeChannelPlugin, t as plugin } from "./plugin2.js";
2
- export { createAlfeChannelPlugin, plugin as default, plugin };
2
+ export { createAlfeChannelPlugin, plugin as default };
package/dist/plugin.d.ts CHANGED
@@ -1,5 +1,58 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Types for the Alfe chat channel plugin.
4
+ *
5
+ * SDK types (PluginRuntime, OpenClawPluginApi, OpenClawConfig, etc.)
6
+ * are imported from openclaw/plugin-sdk at usage sites.
7
+ * This file only contains Alfe-specific domain types.
8
+ */
9
+ interface AlfeChannelAccountConfig {
10
+ /** Whether this account is enabled. */
11
+ enabled?: boolean;
12
+ /** Allowed sender identifiers (user IDs, email addresses). */
13
+ allowFrom?: string | string[];
14
+ /** Default delivery target. */
15
+ defaultTo?: string;
16
+ /** DM policy (open, allowlist, etc.). */
17
+ dmPolicy?: string;
18
+ }
19
+ interface AlfeChannelConfig {
20
+ /** Whether the Alfe channel is enabled. */
21
+ enabled?: boolean;
22
+ /** Allowed sender identifiers. */
23
+ allowFrom?: string | string[];
24
+ /** Default delivery target for outbound messages. */
25
+ defaultTo?: string;
26
+ /** DM policy. */
27
+ dmPolicy?: string;
28
+ /** Named accounts (multi-account support). */
29
+ accounts?: Record<string, AlfeChannelAccountConfig>;
30
+ }
31
+ interface AlfeResolvedAccount {
32
+ accountId: string;
33
+ enabled: boolean;
34
+ allowFrom: string[];
35
+ defaultTo?: string;
36
+ dmPolicy?: string;
37
+ }
38
+ interface AlfePluginConfig {
39
+ /** Agent ID this plugin is associated with. */
40
+ agentId?: string;
41
+ /** Chat service WebSocket URL (e.g. wss://chat.dev.alfe.ai/ws) */
42
+ chatWsUrl?: string;
43
+ /** API key for chat service auth */
44
+ apiKey?: string;
45
+ }
46
+ //#endregion
1
47
  //#region src/alfe-channel.d.ts
2
-
48
+ /** OpenClaw config shape — inline to avoid runtime dependency on openclaw package */
49
+ interface OpenClawConfig {
50
+ channels?: {
51
+ alfe?: AlfeChannelConfig;
52
+ [key: string]: unknown;
53
+ };
54
+ [key: string]: unknown;
55
+ }
3
56
  /**
4
57
  * Creates the Alfe ChannelPlugin object for registration with OpenClaw.
5
58
  *
@@ -108,106 +161,68 @@ declare function createAlfeChannelPlugin(): {
108
161
  };
109
162
  };
110
163
  //#endregion
111
- //#region src/types.d.ts
112
- /**
113
- * Types for the Alfe chat channel plugin.
114
- *
115
- * The Alfe channel registers with OpenClaw as a first-class channel,
116
- * allowing web and mobile clients to share conversation sessions.
117
- */
118
- interface AlfeChannelAccountConfig {
119
- /** Whether this account is enabled. */
120
- enabled?: boolean;
121
- /** Allowed sender identifiers (user IDs, email addresses). */
122
- allowFrom?: string | string[];
123
- /** Default delivery target. */
124
- defaultTo?: string;
125
- /** DM policy (open, allowlist, etc.). */
126
- dmPolicy?: string;
127
- }
128
- interface AlfeChannelConfig {
129
- /** Whether the Alfe channel is enabled. */
130
- enabled?: boolean;
131
- /** Allowed sender identifiers. */
132
- allowFrom?: string | string[];
133
- /** Default delivery target for outbound messages. */
134
- defaultTo?: string;
135
- /** DM policy. */
136
- dmPolicy?: string;
137
- /** Named accounts (multi-account support). */
138
- accounts?: Record<string, AlfeChannelAccountConfig>;
139
- }
140
- interface AlfeResolvedAccount {
141
- accountId: string;
142
- enabled: boolean;
143
- allowFrom: string[];
144
- defaultTo?: string;
145
- dmPolicy?: string;
146
- }
147
- interface AlfePluginConfig {
148
- /** Alfe daemon IPC socket path override. */
149
- daemonSocket?: string;
150
- /** Agent ID this plugin is associated with. */
151
- agentId?: string;
152
- /** Chat service WebSocket URL (e.g. wss://chat.dev.alfe.ai/ws) */
153
- chatWsUrl?: string;
154
- /** API key for chat service auth */
155
- apiKey?: string;
156
- /** OpenClaw local gateway URL override (default: ws://127.0.0.1:18789) */
157
- openclawGatewayUrl?: string;
158
- }
159
- interface Logger {
164
+ //#region src/plugin.d.ts
165
+ interface PluginLogger {
160
166
  info(msg: string, ...args: unknown[]): void;
161
167
  warn(msg: string, ...args: unknown[]): void;
162
168
  error(msg: string, ...args: unknown[]): void;
163
169
  debug(msg: string, ...args: unknown[]): void;
164
170
  }
165
- interface OpenClawConfig {
166
- channels?: {
167
- alfe?: AlfeChannelConfig;
168
- [key: string]: unknown;
171
+ interface PluginRuntime {
172
+ config: {
173
+ loadConfig(): Record<string, unknown>;
169
174
  };
170
- plugins?: {
171
- entries?: Record<string, {
172
- config?: AlfePluginConfig;
173
- [key: string]: unknown;
175
+ events: {
176
+ onAgentEvent(listener: (evt: AgentEventPayload) => void): () => void;
177
+ };
178
+ subagent: {
179
+ run(params: {
180
+ sessionKey: string;
181
+ message: string;
182
+ idempotencyKey?: string;
183
+ deliver?: boolean;
184
+ }): Promise<{
185
+ runId: string;
186
+ }>;
187
+ waitForRun(params: {
188
+ runId: string;
189
+ timeoutMs?: number;
190
+ }): Promise<{
191
+ status: string;
192
+ error?: string;
174
193
  }>;
175
- [key: string]: unknown;
176
194
  };
177
- [key: string]: unknown;
195
+ channel: unknown;
196
+ }
197
+ interface AgentEventPayload {
198
+ runId: string;
199
+ seq: number;
200
+ stream: string;
201
+ ts: number;
202
+ data: Record<string, unknown>;
203
+ sessionKey?: string;
178
204
  }
179
- interface OpenClawPluginApi {
180
- logger: Logger;
181
- config?: OpenClawConfig;
205
+ interface PluginApi {
206
+ logger: PluginLogger;
207
+ config?: Record<string, unknown>;
182
208
  registerChannel(channel: ReturnType<typeof createAlfeChannelPlugin>): void;
183
209
  registerGatewayMethod?(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
184
210
  on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
185
211
  priority?: number;
186
212
  }): void;
187
213
  }
188
- interface IPCClient {
189
- on(event: string, handler: (...args: unknown[]) => void | Promise<void>): void;
190
- start(): void;
191
- stop(): void;
192
- request(method: string, params: Record<string, unknown>): Promise<{
193
- ok: boolean;
194
- error?: {
195
- message: string;
196
- };
197
- }>;
198
- }
199
- interface OpenClawModule {
200
- IPCClient: new (socketPath: string, log: Logger) => IPCClient;
201
- }
202
- //#endregion
203
- //#region src/plugin.d.ts
204
214
  declare const plugin: {
205
215
  id: string;
206
216
  name: string;
207
217
  description: string;
208
218
  version: string;
209
- activate(api: OpenClawPluginApi): Promise<void>;
210
- deactivate(api: OpenClawPluginApi): void;
219
+ /**
220
+ * Called by OpenClaw to inject the in-process runtime.
221
+ * This replaces the need for a separate WS connection.
222
+ */
223
+ setRuntime(runtime: PluginRuntime): void;
224
+ activate(api: PluginApi): void;
225
+ deactivate(api: PluginApi): void;
211
226
  };
212
227
  //#endregion
213
- export { AlfeResolvedAccount as a, OpenClawConfig as c, createAlfeChannelPlugin as d, AlfePluginConfig as i, OpenClawModule as l, AlfeChannelAccountConfig as n, IPCClient as o, AlfeChannelConfig as r, Logger as s, plugin as t, OpenClawPluginApi as u };
228
+ export { AlfeResolvedAccount as a, AlfePluginConfig as i, createAlfeChannelPlugin as n, AlfeChannelConfig as r, plugin as t };
package/dist/plugin2.js CHANGED
@@ -1,9 +1,7 @@
1
- import { join } from "node:path";
2
- import { homedir } from "node:os";
3
1
  import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
4
- import WebSocket from "ws";
5
- import { randomUUID } from "node:crypto";
6
2
  import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
7
5
  import { existsSync } from "node:fs";
8
6
  //#region src/alfe-channel.ts
9
7
  const CHANNEL_ID = "alfe";
@@ -137,239 +135,6 @@ function createAlfeChannelPlugin() {
137
135
  };
138
136
  }
139
137
  //#endregion
140
- //#region src/runtime-relay.ts
141
- /**
142
- * RuntimeRelay — bridges the chat service to the local OpenClaw runtime.
143
- *
144
- * Connects to OpenClaw's local WebSocket server (typically ws://127.0.0.1:18789),
145
- * performs the protocol handshake, and forwards RPC requests from the chat
146
- * service to the runtime. Responses and streaming events are routed back
147
- * via callbacks.
148
- *
149
- * Modeled after the voice relay's LocalGatewayClient pattern.
150
- */
151
- const RECONNECT_DELAYS = [
152
- 500,
153
- 1e3,
154
- 2e3,
155
- 4e3,
156
- 8e3,
157
- 15e3,
158
- 3e4
159
- ];
160
- var RuntimeRelay = class {
161
- ws = null;
162
- pending = /* @__PURE__ */ new Map();
163
- connected = false;
164
- stopped = false;
165
- retryCount = 0;
166
- retryTimer = null;
167
- connectSent = false;
168
- wsUrl;
169
- token;
170
- log;
171
- constructor(options) {
172
- this.options = options;
173
- this.wsUrl = options.wsUrl ?? "ws://127.0.0.1:18789";
174
- this.token = options.token ?? "";
175
- this.log = options.logger;
176
- }
177
- get isConnected() {
178
- return this.connected && this.ws?.readyState === WebSocket.OPEN;
179
- }
180
- start() {
181
- this.log.debug("RuntimeRelay starting...");
182
- this.stopped = false;
183
- this.doConnect();
184
- }
185
- stop() {
186
- this.log.debug("RuntimeRelay stopping...");
187
- this.stopped = true;
188
- if (this.retryTimer) {
189
- clearTimeout(this.retryTimer);
190
- this.retryTimer = null;
191
- }
192
- if (this.ws) {
193
- try {
194
- this.ws.close(1e3);
195
- } catch {}
196
- this.ws = null;
197
- }
198
- this.connected = false;
199
- this.flushPending(/* @__PURE__ */ new Error("RuntimeRelay stopped"));
200
- }
201
- /**
202
- * Forward an RPC request from the chat service to the runtime.
203
- * The response routes back via the callback.
204
- */
205
- forwardRequest(msg, onResponse) {
206
- if (this.ws?.readyState !== WebSocket.OPEN) {
207
- onResponse({
208
- id: msg.id,
209
- ok: false,
210
- error: { message: "Runtime not connected" }
211
- });
212
- return;
213
- }
214
- const timeoutMs = msg.method === "agent" ? 13e4 : 3e4;
215
- const timer = setTimeout(() => {
216
- this.pending.delete(msg.id);
217
- onResponse({
218
- id: msg.id,
219
- ok: false,
220
- error: { message: `'${msg.method}' timed out` }
221
- });
222
- }, timeoutMs);
223
- this.pending.set(msg.id, {
224
- resolve: (payload) => {
225
- clearTimeout(timer);
226
- onResponse({
227
- id: msg.id,
228
- ok: true,
229
- payload
230
- });
231
- },
232
- reject: (err) => {
233
- clearTimeout(timer);
234
- onResponse({
235
- id: msg.id,
236
- ok: false,
237
- error: { message: err.message }
238
- });
239
- },
240
- expectFinal: msg.method === "agent"
241
- });
242
- this.ws.send(JSON.stringify({
243
- type: "req",
244
- id: msg.id,
245
- method: msg.method,
246
- params: msg.params
247
- }));
248
- }
249
- doConnect() {
250
- if (this.stopped) return;
251
- this.log.info(`Connecting to runtime at ${this.wsUrl}...`);
252
- this.ws = new WebSocket(this.wsUrl, { maxPayload: 25 * 1024 * 1024 });
253
- this.ws.on("open", () => {
254
- this.log.info("Connected to runtime — authenticating...");
255
- this.retryCount = 0;
256
- setTimeout(() => {
257
- this.sendConnect();
258
- }, 750);
259
- });
260
- this.ws.on("message", (data) => {
261
- const text = Buffer.isBuffer(data) ? data.toString("utf-8") : Buffer.from(data).toString("utf-8");
262
- this.handleMessage(text);
263
- });
264
- this.ws.on("close", (code) => {
265
- this.log.warn(`Runtime disconnected (${String(code)})`);
266
- this.connected = false;
267
- this.flushPending(/* @__PURE__ */ new Error(`Runtime closed (${String(code)})`));
268
- this.scheduleReconnect();
269
- });
270
- this.ws.on("error", (err) => {
271
- this.log.error(`Runtime WS error: ${err.message}`);
272
- });
273
- }
274
- async sendConnect() {
275
- if (this.connectSent) return;
276
- this.connectSent = true;
277
- const id = randomUUID();
278
- const timeoutMs = 1e4;
279
- try {
280
- await new Promise((resolve, reject) => {
281
- const timer = setTimeout(() => {
282
- this.pending.delete(id);
283
- reject(/* @__PURE__ */ new Error("Connect handshake timed out"));
284
- }, timeoutMs);
285
- this.pending.set(id, {
286
- resolve: () => {
287
- clearTimeout(timer);
288
- resolve();
289
- },
290
- reject: (err) => {
291
- clearTimeout(timer);
292
- reject(err);
293
- },
294
- expectFinal: false
295
- });
296
- this.ws?.send(JSON.stringify({
297
- type: "req",
298
- id,
299
- method: "connect",
300
- params: {
301
- minProtocol: 3,
302
- maxProtocol: 3,
303
- client: {
304
- id: "chat-relay",
305
- displayName: "Alfe Chat Relay",
306
- version: "1.0.0",
307
- platform: process.platform,
308
- mode: "backend",
309
- instanceId: `chat-${Date.now().toString(36)}`
310
- },
311
- caps: [],
312
- role: "operator",
313
- scopes: ["operator.admin"],
314
- auth: this.token ? { token: this.token } : void 0
315
- }
316
- }));
317
- });
318
- this.connected = true;
319
- this.log.info("Runtime authenticated — relay active");
320
- } catch (err) {
321
- const errMsg = err instanceof Error ? err.message : String(err);
322
- this.log.error(`Runtime connect failed: ${errMsg}`);
323
- this.ws?.close(1008, "connect failed");
324
- }
325
- }
326
- handleMessage(raw) {
327
- try {
328
- const parsed = JSON.parse(raw);
329
- if (parsed.event) {
330
- const payload = parsed.payload;
331
- if (parsed.event === "connect.challenge" && payload?.nonce) {
332
- this.connectSent = false;
333
- this.sendConnect();
334
- return;
335
- }
336
- if (payload) this.options.onEvent(parsed.event, payload);
337
- return;
338
- }
339
- if ("id" in parsed && "ok" in parsed) {
340
- const pending = this.pending.get(parsed.id);
341
- if (!pending) return;
342
- const payload = parsed.payload;
343
- if (pending.expectFinal && payload?.status === "accepted") return;
344
- this.pending.delete(parsed.id);
345
- if (parsed.ok) pending.resolve(payload);
346
- else {
347
- const error = parsed.error;
348
- pending.reject(new Error(error?.message ?? "unknown error"));
349
- }
350
- }
351
- } catch (err) {
352
- const errMsg = err instanceof Error ? err.message : String(err);
353
- this.log.error(`Runtime message parse error: ${errMsg}`);
354
- }
355
- }
356
- scheduleReconnect() {
357
- if (this.stopped) return;
358
- const delay = RECONNECT_DELAYS[Math.min(this.retryCount, RECONNECT_DELAYS.length - 1)];
359
- this.retryCount++;
360
- this.connectSent = false;
361
- this.log.info(`Reconnecting to runtime in ${String(delay)}ms (attempt ${String(this.retryCount)})...`);
362
- this.retryTimer = setTimeout(() => {
363
- this.retryTimer = null;
364
- this.doConnect();
365
- }, delay);
366
- }
367
- flushPending(err) {
368
- for (const [, p] of this.pending) p.reject(err);
369
- this.pending.clear();
370
- }
371
- };
372
- //#endregion
373
138
  //#region src/session-store.ts
374
139
  /**
375
140
  * Session Store — persists chat sessions to the local filesystem.
@@ -463,55 +228,58 @@ async function listSessions(filters) {
463
228
  /**
464
229
  * @alfe.ai/openclaw-chat — OpenClaw chat channel plugin.
465
230
  *
466
- * Registers the 'alfe' channel with OpenClaw, creating a first-class
467
- * channel for Alfe conversations. Web and mobile clients share the
468
- * same channel and conversation sessions.
231
+ * Registers the 'alfe' channel with OpenClaw. Messages are dispatched
232
+ * in-process via runtime.subagent.run() no separate WebSocket to the
233
+ * runtime needed.
469
234
  *
470
- * This follows the same pattern as the voice-gateway plugin:
471
- * - Registers channel via api.registerChannel()
472
- * - Connects to the Alfe daemon IPC for capability registration
473
- * - Registers gateway RPC methods for message delivery and session queries
474
- * - Hooks into session lifecycle events
475
- * - Persists chat sessions to ~/.alfe/sessions/chat/
476
- * - Gracefully degrades if the daemon is unavailable
235
+ * Architecture:
236
+ * Chat Service (Fly.io) ←WS→ ChatServiceClient → runtime.subagent.run() → Agent (in-process)
237
+ * onAgentEvent streaming
477
238
  */
478
- const DEFAULT_SOCKET_PATH = join(homedir(), ".alfe", "gateway.sock");
479
- const CHAT_CAPABILITIES = [
480
- "chat.web",
481
- "chat.mobile",
482
- "chat.sessions"
483
- ];
484
- let daemonIpcClient = null;
239
+ let pluginRuntime = null;
485
240
  let chatClient = null;
486
- let runtimeRelay = null;
487
- /**
488
- * Attempt to connect to the Alfe daemon IPC socket.
489
- * Returns null if @alfe.ai/openclaw is not available or daemon is unreachable.
490
- */
491
- async function connectToDaemon(socketPath, log) {
241
+ async function handleAgentRequest(request, log) {
242
+ const runtime = pluginRuntime;
243
+ if (!runtime) {
244
+ chatClient?.sendResponse(request.id, false, { message: "Plugin runtime not initialized" });
245
+ return;
246
+ }
247
+ const { message, sessionKey } = request.params;
248
+ if (!message || !sessionKey) {
249
+ chatClient?.sendResponse(request.id, false, { message: "Missing message or sessionKey" });
250
+ return;
251
+ }
252
+ const unsubscribe = runtime.events.onAgentEvent((evt) => {
253
+ if (evt.sessionKey !== sessionKey) return;
254
+ if (evt.stream === "assistant") chatClient?.sendEvent("chat", {
255
+ runId: evt.runId,
256
+ sessionKey,
257
+ seq: evt.seq,
258
+ state: "delta",
259
+ message: evt.data
260
+ });
261
+ });
492
262
  try {
493
- const IPCClientCtor = (await import("@alfe.ai/openclaw")).IPCClient;
494
- const client = new IPCClientCtor(socketPath, log);
495
- client.on("connected", async () => {
496
- log.info("Connected to Alfe daemon — registering chat capabilities...");
497
- const response = await client.request("capability.register", {
498
- plugin: "@alfe.ai/openclaw-chat",
499
- capabilities: [...CHAT_CAPABILITIES]
500
- });
501
- if (response.ok) log.info("Chat capabilities registered with daemon");
502
- else log.warn(`Failed to register chat capabilities: ${response.error?.message ?? "unknown"}`);
263
+ const { runId } = await runtime.subagent.run({
264
+ sessionKey,
265
+ message,
266
+ deliver: true
503
267
  });
504
- client.on("disconnected", (reason) => {
505
- log.warn(`Disconnected from Alfe daemon: ${String(reason)}`);
268
+ const result = await runtime.subagent.waitForRun({
269
+ runId,
270
+ timeoutMs: 12e4
506
271
  });
507
- client.on("error", (err) => {
508
- log.debug(`Daemon IPC error: ${err.message}`);
272
+ if (result.status === "ok") chatClient?.sendResponse(request.id, true, {
273
+ text: "",
274
+ sessionKey
509
275
  });
510
- client.start();
511
- return client;
512
- } catch {
513
- log.info("Alfe daemon not available — chat plugin running standalone");
514
- return null;
276
+ else chatClient?.sendResponse(request.id, false, { message: result.error ?? `Agent run ${result.status}` });
277
+ } catch (err) {
278
+ const errMsg = err instanceof Error ? err.message : String(err);
279
+ log.error(`Agent dispatch failed: ${errMsg}`);
280
+ chatClient?.sendResponse(request.id, false, { message: errMsg });
281
+ } finally {
282
+ unsubscribe();
515
283
  }
516
284
  }
517
285
  const plugin = {
@@ -519,80 +287,58 @@ const plugin = {
519
287
  name: "Alfe Chat Plugin",
520
288
  description: "Alfe conversation channel — web widget and mobile app share unified chat sessions",
521
289
  version: "0.3.0",
522
- async activate(api) {
290
+ setRuntime(runtime) {
291
+ pluginRuntime = runtime;
292
+ },
293
+ activate(api) {
523
294
  if (globalThis.__alfeChatPluginActivated) {
524
295
  api.logger.debug("Alfe Chat plugin already activated, skipping re-init");
525
296
  return;
526
297
  }
527
298
  globalThis.__alfeChatPluginActivated = true;
528
299
  const log = api.logger;
529
- log.info("Alfe Chat plugin activating...");
530
- const pluginConfig = (api.config ?? {}).plugins?.entries?.["@alfe.ai/openclaw-chat"]?.config ?? {};
300
+ log.info("Alfe Chat plugin registering...");
531
301
  const alfeChannel = createAlfeChannelPlugin();
532
302
  api.registerChannel(alfeChannel);
533
- log.info(`Registered channel: ${alfeChannel.id} (${alfeChannel.meta.label})`);
534
- daemonIpcClient = await connectToDaemon(pluginConfig.daemonSocket ?? process.env.ALFE_GATEWAY_SOCKET ?? DEFAULT_SOCKET_PATH, log);
535
- const { apiKey, chatWsUrl } = await resolveAlfeChat({
536
- apiKey: pluginConfig.apiKey,
537
- chatWsUrl: pluginConfig.chatWsUrl
538
- });
539
- const runtimeUrl = pluginConfig.openclawGatewayUrl ?? process.env.OPENCLAW_GATEWAY_URL ?? "ws://127.0.0.1:18789";
540
- const runtimeToken = process.env.OPENCLAW_GATEWAY_TOKEN ?? "";
541
- if (chatWsUrl && apiKey) {
542
- log.info(`Connecting to chat service: ${chatWsUrl}`);
543
- runtimeRelay = new RuntimeRelay({
544
- wsUrl: runtimeUrl,
545
- token: runtimeToken,
546
- onEvent: (event, payload) => {
547
- chatClient?.sendEvent(event, payload);
548
- },
549
- logger: log
550
- });
551
- chatClient = new ChatServiceClient({
552
- wsUrl: chatWsUrl,
553
- apiKey,
554
- onRequest: (request) => {
555
- if (!runtimeRelay?.isConnected) {
556
- chatClient?.sendResponse(request.id, false, { message: "Runtime not connected" });
557
- return;
558
- }
559
- runtimeRelay.forwardRequest(request, (response) => {
560
- chatClient?.send(response);
303
+ log.info(`Registered channel: ${alfeChannel.id}`);
304
+ const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
305
+ (async () => {
306
+ try {
307
+ const { apiKey, chatWsUrl } = await resolveAlfeChat({
308
+ apiKey: pluginConfig.apiKey,
309
+ chatWsUrl: pluginConfig.chatWsUrl
310
+ });
311
+ if (chatWsUrl && apiKey) {
312
+ log.info(`Connecting to chat service: ${chatWsUrl}`);
313
+ chatClient = new ChatServiceClient({
314
+ wsUrl: chatWsUrl,
315
+ apiKey,
316
+ onRequest: (request) => {
317
+ if (request.method === "agent") handleAgentRequest(request, log);
318
+ },
319
+ onConnectionChange: (connected) => {
320
+ log.info(`Chat service connection: ${connected ? "connected" : "disconnected"}`);
321
+ },
322
+ logger: log
561
323
  });
562
- },
563
- onConnectionChange: (connected) => {
564
- log.info(`Chat service connection: ${connected ? "connected" : "disconnected"}`);
565
- },
566
- logger: log
567
- });
568
- runtimeRelay.start();
569
- chatClient.start();
570
- log.info("Chat service relay started");
571
- } else log.info("Chat service URL not configured — running without chat service relay");
324
+ chatClient.start();
325
+ log.info("Chat service relay started");
326
+ } else log.info("Chat service URL not configured running without chat service relay");
327
+ } catch (err) {
328
+ log.error(`Failed to initialize chat service: ${err instanceof Error ? err.message : String(err)}`);
329
+ }
330
+ })();
572
331
  if (typeof api.registerGatewayMethod === "function") {
573
- api.registerGatewayMethod("chat.send", (...args) => {
574
- const { sessionId, content, clientType } = args[0];
575
- log.info(`chat.send RPC: session=${sessionId}, client=${clientType ?? "unknown"}, content=${content.slice(0, 50)}...`);
576
- return Promise.resolve({
577
- ok: true,
578
- sessionId,
579
- channel: "alfe"
580
- });
581
- });
582
- log.info("Registered gateway RPC method: chat.send");
583
332
  api.registerGatewayMethod("sessions.list", async (...args) => {
584
333
  const params = args[0];
585
- log.info(`sessions.list RPC: agentId=${params.agentId ?? "*"}, channel=${params.channel ?? "*"}`);
586
334
  return { sessions: await listSessions({
587
335
  agentId: params.agentId,
588
336
  channel: params.channel,
589
337
  tenantId: params.tenantId
590
338
  }) };
591
339
  });
592
- log.info("Registered gateway RPC method: sessions.list");
593
340
  api.registerGatewayMethod("sessions.get", async (...args) => {
594
341
  const params = args[0];
595
- log.info(`sessions.get RPC: sessionId=${params.sessionId}`);
596
342
  const session = await getSession(params.sessionId);
597
343
  if (!session) return {
598
344
  ok: false,
@@ -611,7 +357,7 @@ const plugin = {
611
357
  }))
612
358
  };
613
359
  });
614
- log.info("Registered gateway RPC method: sessions.get");
360
+ log.info("Registered gateway RPC methods: sessions.list, sessions.get");
615
361
  }
616
362
  api.on("session_start", async (...eventArgs) => {
617
363
  const key = eventArgs[0].sessionKey;
@@ -634,7 +380,7 @@ const plugin = {
634
380
  if (!(key.startsWith("chat-") || key.includes("alfe:") || key.includes(":alfe:"))) return;
635
381
  log.info(`Alfe chat session ending: ${key}`);
636
382
  });
637
- log.info("Alfe Chat plugin activated");
383
+ log.info("Alfe Chat plugin registered");
638
384
  },
639
385
  deactivate(api) {
640
386
  globalThis.__alfeChatPluginActivated = false;
@@ -645,20 +391,7 @@ const plugin = {
645
391
  chatClient = null;
646
392
  log.info("Chat service client stopped");
647
393
  }
648
- if (runtimeRelay) {
649
- runtimeRelay.stop();
650
- runtimeRelay = null;
651
- log.info("Runtime relay stopped");
652
- }
653
- if (daemonIpcClient) {
654
- try {
655
- daemonIpcClient.stop();
656
- log.info("Disconnected from Alfe daemon");
657
- } catch (err) {
658
- log.debug(`Error disconnecting from daemon: ${err.message}`);
659
- }
660
- daemonIpcClient = null;
661
- }
394
+ pluginRuntime = null;
662
395
  log.info("Alfe Chat plugin deactivated");
663
396
  }
664
397
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -25,12 +25,8 @@
25
25
  "openclaw.plugin.json"
26
26
  ],
27
27
  "dependencies": {
28
- "ws": "^8.18.0",
29
28
  "@alfe.ai/chat": "^0.0.2"
30
29
  },
31
- "devDependencies": {
32
- "@types/ws": "^8.5.13"
33
- },
34
30
  "license": "UNLICENSED",
35
31
  "scripts": {
36
32
  "build": "tsdown",