@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 +2 -2
- package/dist/index.js +1 -1
- package/dist/plugin.d.ts +97 -82
- package/dist/plugin2.js +82 -349
- package/package.json +1 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AlfeResolvedAccount,
|
|
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 {
|
|
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
|
|
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/
|
|
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
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
[key: string]: unknown;
|
|
171
|
+
interface PluginRuntime {
|
|
172
|
+
config: {
|
|
173
|
+
loadConfig(): Record<string, unknown>;
|
|
169
174
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
|
180
|
-
logger:
|
|
181
|
-
config?:
|
|
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
|
-
|
|
210
|
-
|
|
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,
|
|
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
|
|
467
|
-
*
|
|
468
|
-
*
|
|
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
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
505
|
-
|
|
268
|
+
const result = await runtime.subagent.waitForRun({
|
|
269
|
+
runId,
|
|
270
|
+
timeoutMs: 12e4
|
|
506
271
|
});
|
|
507
|
-
|
|
508
|
-
|
|
272
|
+
if (result.status === "ok") chatClient?.sendResponse(request.id, true, {
|
|
273
|
+
text: "",
|
|
274
|
+
sessionKey
|
|
509
275
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
log.
|
|
514
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|