@alfe.ai/openclaw-chat 0.0.3 → 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 -347
- package/package.json +2 -6
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 { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
|
|
2
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
1
3
|
import { join } from "node:path";
|
|
2
4
|
import { homedir } from "node:os";
|
|
3
|
-
import { ChatServiceClient } from "@alfe.ai/chat";
|
|
4
|
-
import WebSocket from "ws";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
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,78 +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
|
-
if (!runtimeRelay?.isConnected) {
|
|
554
|
-
chatClient?.sendResponse(request.id, false, { message: "Runtime not connected" });
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
runtimeRelay.forwardRequest(request, (response) => {
|
|
558
|
-
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
|
|
559
323
|
});
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
chatClient.start();
|
|
568
|
-
log.info("Chat service relay started");
|
|
569
|
-
} 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
|
+
})();
|
|
570
331
|
if (typeof api.registerGatewayMethod === "function") {
|
|
571
|
-
api.registerGatewayMethod("chat.send", (...args) => {
|
|
572
|
-
const { sessionId, content, clientType } = args[0];
|
|
573
|
-
log.info(`chat.send RPC: session=${sessionId}, client=${clientType ?? "unknown"}, content=${content.slice(0, 50)}...`);
|
|
574
|
-
return Promise.resolve({
|
|
575
|
-
ok: true,
|
|
576
|
-
sessionId,
|
|
577
|
-
channel: "alfe"
|
|
578
|
-
});
|
|
579
|
-
});
|
|
580
|
-
log.info("Registered gateway RPC method: chat.send");
|
|
581
332
|
api.registerGatewayMethod("sessions.list", async (...args) => {
|
|
582
333
|
const params = args[0];
|
|
583
|
-
log.info(`sessions.list RPC: agentId=${params.agentId ?? "*"}, channel=${params.channel ?? "*"}`);
|
|
584
334
|
return { sessions: await listSessions({
|
|
585
335
|
agentId: params.agentId,
|
|
586
336
|
channel: params.channel,
|
|
587
337
|
tenantId: params.tenantId
|
|
588
338
|
}) };
|
|
589
339
|
});
|
|
590
|
-
log.info("Registered gateway RPC method: sessions.list");
|
|
591
340
|
api.registerGatewayMethod("sessions.get", async (...args) => {
|
|
592
341
|
const params = args[0];
|
|
593
|
-
log.info(`sessions.get RPC: sessionId=${params.sessionId}`);
|
|
594
342
|
const session = await getSession(params.sessionId);
|
|
595
343
|
if (!session) return {
|
|
596
344
|
ok: false,
|
|
@@ -609,7 +357,7 @@ const plugin = {
|
|
|
609
357
|
}))
|
|
610
358
|
};
|
|
611
359
|
});
|
|
612
|
-
log.info("Registered gateway RPC
|
|
360
|
+
log.info("Registered gateway RPC methods: sessions.list, sessions.get");
|
|
613
361
|
}
|
|
614
362
|
api.on("session_start", async (...eventArgs) => {
|
|
615
363
|
const key = eventArgs[0].sessionKey;
|
|
@@ -632,7 +380,7 @@ const plugin = {
|
|
|
632
380
|
if (!(key.startsWith("chat-") || key.includes("alfe:") || key.includes(":alfe:"))) return;
|
|
633
381
|
log.info(`Alfe chat session ending: ${key}`);
|
|
634
382
|
});
|
|
635
|
-
log.info("Alfe Chat plugin
|
|
383
|
+
log.info("Alfe Chat plugin registered");
|
|
636
384
|
},
|
|
637
385
|
deactivate(api) {
|
|
638
386
|
globalThis.__alfeChatPluginActivated = false;
|
|
@@ -643,20 +391,7 @@ const plugin = {
|
|
|
643
391
|
chatClient = null;
|
|
644
392
|
log.info("Chat service client stopped");
|
|
645
393
|
}
|
|
646
|
-
|
|
647
|
-
runtimeRelay.stop();
|
|
648
|
-
runtimeRelay = null;
|
|
649
|
-
log.info("Runtime relay stopped");
|
|
650
|
-
}
|
|
651
|
-
if (daemonIpcClient) {
|
|
652
|
-
try {
|
|
653
|
-
daemonIpcClient.stop();
|
|
654
|
-
log.info("Disconnected from Alfe daemon");
|
|
655
|
-
} catch (err) {
|
|
656
|
-
log.debug(`Error disconnecting from daemon: ${err.message}`);
|
|
657
|
-
}
|
|
658
|
-
daemonIpcClient = null;
|
|
659
|
-
}
|
|
394
|
+
pluginRuntime = null;
|
|
660
395
|
log.info("Alfe Chat plugin deactivated");
|
|
661
396
|
}
|
|
662
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,11 +25,7 @@
|
|
|
25
25
|
"openclaw.plugin.json"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"@alfe.ai/chat": "^0.0.1"
|
|
30
|
-
},
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"@types/ws": "^8.5.13"
|
|
28
|
+
"@alfe.ai/chat": "^0.0.2"
|
|
33
29
|
},
|
|
34
30
|
"license": "UNLICENSED",
|
|
35
31
|
"scripts": {
|