@alfe.ai/openclaw-chat 0.0.5 → 0.0.6
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 +1 -0
- package/dist/plugin.d.ts +1 -5
- package/dist/plugin2.js +80 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { a as AlfeResolvedAccount, i as AlfePluginConfig, n as createAlfeChannel
|
|
|
10
10
|
*
|
|
11
11
|
* Each session file contains metadata and the full message history.
|
|
12
12
|
* Sessions are written on every message to ensure durability.
|
|
13
|
+
* Old sessions are cleaned up automatically (30-day TTL, 1000 max).
|
|
13
14
|
*/
|
|
14
15
|
interface ChatMessage {
|
|
15
16
|
role: 'user' | 'assistant';
|
package/dist/plugin.d.ts
CHANGED
|
@@ -205,6 +205,7 @@ interface AgentEventPayload {
|
|
|
205
205
|
interface PluginApi {
|
|
206
206
|
logger: PluginLogger;
|
|
207
207
|
config?: Record<string, unknown>;
|
|
208
|
+
runtime?: PluginRuntime;
|
|
208
209
|
registerChannel(channel: ReturnType<typeof createAlfeChannelPlugin>): void;
|
|
209
210
|
registerGatewayMethod?(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
|
|
210
211
|
on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
|
|
@@ -216,11 +217,6 @@ declare const plugin: {
|
|
|
216
217
|
name: string;
|
|
217
218
|
description: string;
|
|
218
219
|
version: string;
|
|
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
220
|
activate(api: PluginApi): void;
|
|
225
221
|
deactivate(api: PluginApi): void;
|
|
226
222
|
};
|
package/dist/plugin2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
|
|
2
|
-
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
@@ -135,6 +135,37 @@ function createAlfeChannelPlugin() {
|
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
137
|
//#endregion
|
|
138
|
+
//#region src/session-keys.ts
|
|
139
|
+
/**
|
|
140
|
+
* Session key helpers — handles both raw and prefixed formats.
|
|
141
|
+
*
|
|
142
|
+
* Raw format: "chat-{tenantId}-{agentId}-{suffix}"
|
|
143
|
+
* Prefixed format: "agent:{agentId}:chat-{tenantId}-{agentId}-{suffix}"
|
|
144
|
+
*
|
|
145
|
+
* The chat adapter wraps keys with "agent:{agentId}:" before sending
|
|
146
|
+
* to the daemon. The plugin may receive either format depending on
|
|
147
|
+
* which OpenClaw event fires.
|
|
148
|
+
*/
|
|
149
|
+
/**
|
|
150
|
+
* Check if a session key belongs to the Alfe chat channel.
|
|
151
|
+
* Handles both raw and prefixed formats.
|
|
152
|
+
*/
|
|
153
|
+
function isAlfeSessionKey(key) {
|
|
154
|
+
return key.includes("chat-") || key.includes("alfe:") || key.includes(":alfe:");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Strip the "agent:{agentId}:" prefix and extract tenantId + agentId.
|
|
158
|
+
* Returns empty strings if the key doesn't match the expected format.
|
|
159
|
+
*/
|
|
160
|
+
function parseAlfeSessionKey(key) {
|
|
161
|
+
const rawKey = key.includes(":") ? key.slice(key.lastIndexOf(":") + 1) : key;
|
|
162
|
+
const match = /^chat-([^-]+)-([^-]+)/.exec(rawKey);
|
|
163
|
+
return {
|
|
164
|
+
tenantId: match?.[1] ?? "",
|
|
165
|
+
agentId: match?.[2] ?? ""
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
138
169
|
//#region src/session-store.ts
|
|
139
170
|
/**
|
|
140
171
|
* Session Store — persists chat sessions to the local filesystem.
|
|
@@ -144,14 +175,53 @@ function createAlfeChannelPlugin() {
|
|
|
144
175
|
*
|
|
145
176
|
* Each session file contains metadata and the full message history.
|
|
146
177
|
* Sessions are written on every message to ensure durability.
|
|
178
|
+
* Old sessions are cleaned up automatically (30-day TTL, 1000 max).
|
|
147
179
|
*/
|
|
148
180
|
const SESSIONS_DIR = join(homedir(), ".alfe", "sessions", "chat");
|
|
181
|
+
const MAX_SESSIONS = 1e3;
|
|
182
|
+
const MAX_AGE_MS = 720 * 60 * 60 * 1e3;
|
|
183
|
+
const CLEANUP_INTERVAL_MS = 36e5;
|
|
184
|
+
let lastCleanupAt = 0;
|
|
149
185
|
async function ensureDir() {
|
|
150
186
|
if (!existsSync(SESSIONS_DIR)) await mkdir(SESSIONS_DIR, { recursive: true });
|
|
151
187
|
}
|
|
152
188
|
function sessionPath(sessionId) {
|
|
153
189
|
return join(SESSIONS_DIR, `${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
|
|
154
190
|
}
|
|
191
|
+
async function cleanupOldSessions() {
|
|
192
|
+
if (Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) return;
|
|
193
|
+
lastCleanupAt = Date.now();
|
|
194
|
+
try {
|
|
195
|
+
const jsonFiles = (await readdir(SESSIONS_DIR)).filter((f) => f.endsWith(".json"));
|
|
196
|
+
if (jsonFiles.length <= MAX_SESSIONS) {
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
for (const file of jsonFiles) try {
|
|
199
|
+
const filePath = join(SESSIONS_DIR, file);
|
|
200
|
+
if (now - (await stat(filePath)).mtimeMs > MAX_AGE_MS) await unlink(filePath);
|
|
201
|
+
} catch {}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const fileStats = [];
|
|
205
|
+
for (const file of jsonFiles) try {
|
|
206
|
+
const filePath = join(SESSIONS_DIR, file);
|
|
207
|
+
const fileStat = await stat(filePath);
|
|
208
|
+
fileStats.push({
|
|
209
|
+
path: filePath,
|
|
210
|
+
mtimeMs: fileStat.mtimeMs
|
|
211
|
+
});
|
|
212
|
+
} catch {}
|
|
213
|
+
fileStats.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
let remaining = fileStats.length;
|
|
216
|
+
for (const entry of fileStats) {
|
|
217
|
+
if (!(now - entry.mtimeMs > MAX_AGE_MS) && !(remaining > MAX_SESSIONS)) break;
|
|
218
|
+
try {
|
|
219
|
+
await unlink(entry.path);
|
|
220
|
+
remaining--;
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
155
225
|
async function getSession(sessionId) {
|
|
156
226
|
try {
|
|
157
227
|
const data = await readFile(sessionPath(sessionId), "utf-8");
|
|
@@ -178,6 +248,7 @@ async function createSession(sessionId, agentId, channel, tenantId, userId) {
|
|
|
178
248
|
messages: []
|
|
179
249
|
};
|
|
180
250
|
await saveSession(session);
|
|
251
|
+
cleanupOldSessions();
|
|
181
252
|
return session;
|
|
182
253
|
}
|
|
183
254
|
async function addMessage(sessionId, role, content) {
|
|
@@ -190,7 +261,7 @@ async function addMessage(sessionId, role, content) {
|
|
|
190
261
|
});
|
|
191
262
|
await saveSession(session);
|
|
192
263
|
}
|
|
193
|
-
async function listSessions(filters) {
|
|
264
|
+
async function listSessions(filters, limit = 50) {
|
|
194
265
|
await ensureDir();
|
|
195
266
|
let files;
|
|
196
267
|
try {
|
|
@@ -221,7 +292,7 @@ async function listSessions(filters) {
|
|
|
221
292
|
const aTime = a.lastMessageAt ?? a.createdAt;
|
|
222
293
|
return (b.lastMessageAt ?? b.createdAt).localeCompare(aTime);
|
|
223
294
|
});
|
|
224
|
-
return summaries;
|
|
295
|
+
return summaries.slice(0, limit);
|
|
225
296
|
}
|
|
226
297
|
//#endregion
|
|
227
298
|
//#region src/plugin.ts
|
|
@@ -287,15 +358,13 @@ const plugin = {
|
|
|
287
358
|
name: "Alfe Chat Plugin",
|
|
288
359
|
description: "Alfe conversation channel — web widget and mobile app share unified chat sessions",
|
|
289
360
|
version: "0.3.0",
|
|
290
|
-
setRuntime(runtime) {
|
|
291
|
-
pluginRuntime = runtime;
|
|
292
|
-
},
|
|
293
361
|
activate(api) {
|
|
294
362
|
if (globalThis.__alfeChatPluginActivated) {
|
|
295
363
|
api.logger.debug("Alfe Chat plugin already activated, skipping re-init");
|
|
296
364
|
return;
|
|
297
365
|
}
|
|
298
366
|
globalThis.__alfeChatPluginActivated = true;
|
|
367
|
+
pluginRuntime = api.runtime ?? null;
|
|
299
368
|
const log = api.logger;
|
|
300
369
|
log.info("Alfe Chat plugin registering...");
|
|
301
370
|
const alfeChannel = createAlfeChannelPlugin();
|
|
@@ -361,23 +430,20 @@ const plugin = {
|
|
|
361
430
|
}
|
|
362
431
|
api.on("session_start", async (...eventArgs) => {
|
|
363
432
|
const key = eventArgs[0].sessionKey;
|
|
364
|
-
if (!key) return;
|
|
365
|
-
if (!(key.startsWith("chat-") || key.includes("alfe:") || key.includes(":alfe:"))) return;
|
|
433
|
+
if (!key || !isAlfeSessionKey(key)) return;
|
|
366
434
|
log.info(`Alfe chat session starting: ${key}`);
|
|
367
|
-
const
|
|
368
|
-
await createSession(key,
|
|
435
|
+
const { tenantId, agentId: parsedAgentId } = parseAlfeSessionKey(key);
|
|
436
|
+
await createSession(key, parsedAgentId, "alfe", tenantId);
|
|
369
437
|
}, { priority: 50 });
|
|
370
438
|
api.on("message", async (...eventArgs) => {
|
|
371
439
|
const event = eventArgs[0];
|
|
372
440
|
const key = event.sessionKey;
|
|
373
|
-
if (!key) return;
|
|
374
|
-
if (!(key.startsWith("chat-") || key.includes("alfe:") || key.includes(":alfe:"))) return;
|
|
441
|
+
if (!key || !isAlfeSessionKey(key)) return;
|
|
375
442
|
await addMessage(key, event.role, event.content);
|
|
376
443
|
});
|
|
377
444
|
api.on("session_end", (...eventArgs) => {
|
|
378
445
|
const key = eventArgs[0].sessionKey;
|
|
379
|
-
if (!key) return;
|
|
380
|
-
if (!(key.startsWith("chat-") || key.includes("alfe:") || key.includes(":alfe:"))) return;
|
|
446
|
+
if (!key || !isAlfeSessionKey(key)) return;
|
|
381
447
|
log.info(`Alfe chat session ending: ${key}`);
|
|
382
448
|
});
|
|
383
449
|
log.info("Alfe Chat plugin registered");
|