@alfe.ai/openclaw-chat 0.0.14 → 0.0.15
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.cjs +7 -0
- package/dist/index.d.cts +42 -0
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.d.cts +242 -0
- package/dist/plugin2.cjs +605 -0
- package/dist/plugin2.d.cts +2 -0
- package/dist/plugin2.js +16 -2
- package/package.json +8 -6
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
const require_plugin = require("./plugin2.cjs");
|
|
6
|
+
exports.createAlfeChannelPlugin = require_plugin.createAlfeChannelPlugin;
|
|
7
|
+
exports.default = require_plugin.plugin;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { a as AlfeResolvedAccount, i as AlfePluginConfig, n as createAlfeChannelPlugin, r as AlfeChannelConfig, t as plugin } from "./plugin.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/session-store.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Session Store — persists chat sessions to the local filesystem.
|
|
7
|
+
*
|
|
8
|
+
* Storage layout:
|
|
9
|
+
* ~/.alfe/sessions/chat/{sessionId}.json
|
|
10
|
+
*
|
|
11
|
+
* Each session file contains metadata and the full message history.
|
|
12
|
+
* Sessions are written on every message to ensure durability.
|
|
13
|
+
* Old sessions are cleaned up automatically (30-day TTL, 1000 max).
|
|
14
|
+
*/
|
|
15
|
+
interface ChatMessage {
|
|
16
|
+
role: 'user' | 'assistant';
|
|
17
|
+
content: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
senderId?: string;
|
|
20
|
+
senderName?: string;
|
|
21
|
+
}
|
|
22
|
+
interface SessionData {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
agentId: string;
|
|
25
|
+
channel: string;
|
|
26
|
+
tenantId?: string;
|
|
27
|
+
userId?: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
messages: ChatMessage[];
|
|
31
|
+
}
|
|
32
|
+
interface SessionSummary {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
agentId: string;
|
|
35
|
+
channel: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
lastMessageAt?: string;
|
|
38
|
+
preview?: string;
|
|
39
|
+
messageCount: number;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { type AlfeChannelConfig, type AlfePluginConfig, type AlfeResolvedAccount, type ChatMessage, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default };
|
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
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
|
|
47
|
+
//#region src/alfe-channel.d.ts
|
|
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
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates the Alfe ChannelPlugin object for registration with OpenClaw.
|
|
58
|
+
*
|
|
59
|
+
* This follows the same pattern as built-in channels (Telegram, Discord, etc.)
|
|
60
|
+
* but is registered dynamically via api.registerChannel().
|
|
61
|
+
*/
|
|
62
|
+
declare function createAlfeChannelPlugin(): {
|
|
63
|
+
id: string;
|
|
64
|
+
meta: {
|
|
65
|
+
id: string;
|
|
66
|
+
label: string;
|
|
67
|
+
selectionLabel: string;
|
|
68
|
+
detailLabel: string;
|
|
69
|
+
docsPath: string;
|
|
70
|
+
docsLabel: string;
|
|
71
|
+
blurb: string;
|
|
72
|
+
systemImage: string;
|
|
73
|
+
order: number;
|
|
74
|
+
aliases: string[];
|
|
75
|
+
forceAccountBinding: boolean;
|
|
76
|
+
showConfigured: boolean;
|
|
77
|
+
};
|
|
78
|
+
capabilities: {
|
|
79
|
+
chatTypes: ("direct" | "group")[];
|
|
80
|
+
reactions: boolean;
|
|
81
|
+
edit: boolean;
|
|
82
|
+
unsend: boolean;
|
|
83
|
+
reply: boolean;
|
|
84
|
+
effects: boolean;
|
|
85
|
+
groupManagement: boolean;
|
|
86
|
+
threads: boolean;
|
|
87
|
+
media: boolean;
|
|
88
|
+
nativeCommands: boolean;
|
|
89
|
+
polls: boolean;
|
|
90
|
+
};
|
|
91
|
+
config: {
|
|
92
|
+
/**
|
|
93
|
+
* List configured account IDs.
|
|
94
|
+
* Supports multi-account via channels.alfe.accounts, with a
|
|
95
|
+
* default account derived from the top-level channels.alfe section.
|
|
96
|
+
*/
|
|
97
|
+
listAccountIds(cfg: OpenClawConfig): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Resolve account config for a given account ID.
|
|
100
|
+
*/
|
|
101
|
+
resolveAccount(cfg: OpenClawConfig, accountId?: string | null): AlfeResolvedAccount;
|
|
102
|
+
/**
|
|
103
|
+
* Default account ID.
|
|
104
|
+
*/
|
|
105
|
+
defaultAccountId(): string;
|
|
106
|
+
/**
|
|
107
|
+
* Check if account is enabled.
|
|
108
|
+
*/
|
|
109
|
+
isEnabled(account: AlfeResolvedAccount): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Check if account is configured (always true for Alfe — no external tokens needed).
|
|
112
|
+
*/
|
|
113
|
+
isConfigured(): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Describe the account state for status display.
|
|
116
|
+
*/
|
|
117
|
+
describeAccount(account: AlfeResolvedAccount): {
|
|
118
|
+
accountId: string;
|
|
119
|
+
enabled: boolean;
|
|
120
|
+
configured: boolean;
|
|
121
|
+
dmPolicy: string | undefined;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Resolve allow-from list for an account.
|
|
125
|
+
*/
|
|
126
|
+
resolveAllowFrom(params: {
|
|
127
|
+
cfg: OpenClawConfig;
|
|
128
|
+
accountId?: string | null;
|
|
129
|
+
}): string[];
|
|
130
|
+
/**
|
|
131
|
+
* Resolve default outbound target.
|
|
132
|
+
*/
|
|
133
|
+
resolveDefaultTo(params: {
|
|
134
|
+
cfg: OpenClawConfig;
|
|
135
|
+
accountId?: string | null;
|
|
136
|
+
}): string | undefined;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Outbound delivery via gateway.
|
|
140
|
+
* The chat relay service on Fly.io handles actual delivery
|
|
141
|
+
* to connected web/mobile clients via the gateway.
|
|
142
|
+
*/
|
|
143
|
+
outbound: {
|
|
144
|
+
deliveryMode: "gateway";
|
|
145
|
+
textChunkLimit: number;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Setup adapter — minimal for Alfe since no external tokens are needed.
|
|
149
|
+
*/
|
|
150
|
+
setup: {
|
|
151
|
+
resolveAccountId(params: {
|
|
152
|
+
cfg: OpenClawConfig;
|
|
153
|
+
accountId?: string;
|
|
154
|
+
input?: Record<string, unknown>;
|
|
155
|
+
}): string;
|
|
156
|
+
applyAccountConfig(params: {
|
|
157
|
+
cfg: OpenClawConfig;
|
|
158
|
+
accountId: string;
|
|
159
|
+
input: Record<string, unknown>;
|
|
160
|
+
}): OpenClawConfig;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/plugin.d.ts
|
|
165
|
+
interface PluginLogger {
|
|
166
|
+
info(msg: string, ...args: unknown[]): void;
|
|
167
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
168
|
+
error(msg: string, ...args: unknown[]): void;
|
|
169
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
170
|
+
}
|
|
171
|
+
interface PluginRuntime {
|
|
172
|
+
config: {
|
|
173
|
+
loadConfig(): Record<string, unknown>;
|
|
174
|
+
};
|
|
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;
|
|
193
|
+
}>;
|
|
194
|
+
};
|
|
195
|
+
channel: {
|
|
196
|
+
routing: {
|
|
197
|
+
resolveAgentRoute(input: Record<string, unknown>): Record<string, unknown>;
|
|
198
|
+
buildAgentSessionKey(params: Record<string, unknown>): string;
|
|
199
|
+
};
|
|
200
|
+
session: {
|
|
201
|
+
resolveStorePath(params: Record<string, unknown>): string;
|
|
202
|
+
readSessionUpdatedAt(params: Record<string, unknown>): number | undefined;
|
|
203
|
+
recordInboundSession(params: Record<string, unknown>): Promise<void>;
|
|
204
|
+
};
|
|
205
|
+
reply: {
|
|
206
|
+
resolveEnvelopeFormatOptions(cfg: Record<string, unknown>): Record<string, unknown>;
|
|
207
|
+
formatAgentEnvelope(params: Record<string, unknown>): string;
|
|
208
|
+
finalizeInboundContext(params: Record<string, unknown>): Record<string, unknown>;
|
|
209
|
+
dispatchReplyWithBufferedBlockDispatcher(params: Record<string, unknown>): Promise<unknown>;
|
|
210
|
+
dispatchReplyFromConfig(params: Record<string, unknown>): Promise<unknown>;
|
|
211
|
+
};
|
|
212
|
+
[key: string]: unknown;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
interface AgentEventPayload {
|
|
216
|
+
runId: string;
|
|
217
|
+
seq: number;
|
|
218
|
+
stream: string;
|
|
219
|
+
ts: number;
|
|
220
|
+
data: Record<string, unknown>;
|
|
221
|
+
sessionKey?: string;
|
|
222
|
+
}
|
|
223
|
+
interface PluginApi {
|
|
224
|
+
logger: PluginLogger;
|
|
225
|
+
config?: Record<string, unknown>;
|
|
226
|
+
runtime?: PluginRuntime;
|
|
227
|
+
registerChannel(channel: ReturnType<typeof createAlfeChannelPlugin>): void;
|
|
228
|
+
registerGatewayMethod?(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
|
|
229
|
+
on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
|
|
230
|
+
priority?: number;
|
|
231
|
+
}): void;
|
|
232
|
+
}
|
|
233
|
+
declare const plugin: {
|
|
234
|
+
id: string;
|
|
235
|
+
name: string;
|
|
236
|
+
description: string;
|
|
237
|
+
version: string;
|
|
238
|
+
activate(api: PluginApi): void;
|
|
239
|
+
deactivate(api: PluginApi): Promise<void>;
|
|
240
|
+
};
|
|
241
|
+
//#endregion
|
|
242
|
+
export { AlfeResolvedAccount as a, AlfePluginConfig as i, createAlfeChannelPlugin as n, AlfeChannelConfig as r, plugin as t };
|
package/dist/plugin2.cjs
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
let node_module = require("node:module");
|
|
2
|
+
let _alfe_ai_chat = require("@alfe.ai/chat");
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let node_os = require("node:os");
|
|
6
|
+
let node_fs = require("node:fs");
|
|
7
|
+
//#region src/alfe-channel.ts
|
|
8
|
+
const CHANNEL_ID = "alfe";
|
|
9
|
+
const DEFAULT_ACCOUNT_ID = "default";
|
|
10
|
+
function getChannelSection(cfg) {
|
|
11
|
+
return cfg.channels?.alfe ?? {};
|
|
12
|
+
}
|
|
13
|
+
function normalizeAllowFrom(raw) {
|
|
14
|
+
if (!raw) return [];
|
|
15
|
+
if (typeof raw === "string") return [raw];
|
|
16
|
+
return raw;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates the Alfe ChannelPlugin object for registration with OpenClaw.
|
|
20
|
+
*
|
|
21
|
+
* This follows the same pattern as built-in channels (Telegram, Discord, etc.)
|
|
22
|
+
* but is registered dynamically via api.registerChannel().
|
|
23
|
+
*/
|
|
24
|
+
function createAlfeChannelPlugin() {
|
|
25
|
+
return {
|
|
26
|
+
id: CHANNEL_ID,
|
|
27
|
+
meta: {
|
|
28
|
+
id: CHANNEL_ID,
|
|
29
|
+
label: "Alfe",
|
|
30
|
+
selectionLabel: "Alfe (Web & Mobile)",
|
|
31
|
+
detailLabel: "Alfe",
|
|
32
|
+
docsPath: "/channels/alfe",
|
|
33
|
+
docsLabel: "alfe",
|
|
34
|
+
blurb: "Alfe native chat — web widget and mobile app conversations.",
|
|
35
|
+
systemImage: "bubble.left.and.text.bubble.right",
|
|
36
|
+
order: 100,
|
|
37
|
+
aliases: ["alfe-web", "alfe-mobile"],
|
|
38
|
+
forceAccountBinding: false,
|
|
39
|
+
showConfigured: true
|
|
40
|
+
},
|
|
41
|
+
capabilities: {
|
|
42
|
+
chatTypes: ["direct", "group"],
|
|
43
|
+
reactions: false,
|
|
44
|
+
edit: false,
|
|
45
|
+
unsend: false,
|
|
46
|
+
reply: true,
|
|
47
|
+
effects: false,
|
|
48
|
+
groupManagement: false,
|
|
49
|
+
threads: false,
|
|
50
|
+
media: true,
|
|
51
|
+
nativeCommands: false,
|
|
52
|
+
polls: false
|
|
53
|
+
},
|
|
54
|
+
config: {
|
|
55
|
+
listAccountIds(cfg) {
|
|
56
|
+
const section = getChannelSection(cfg);
|
|
57
|
+
const ids = [];
|
|
58
|
+
if (section.enabled !== false && (section.allowFrom ?? section.defaultTo ?? section.dmPolicy)) ids.push(DEFAULT_ACCOUNT_ID);
|
|
59
|
+
if (section.accounts) {
|
|
60
|
+
for (const id of Object.keys(section.accounts)) if (!ids.includes(id)) ids.push(id);
|
|
61
|
+
}
|
|
62
|
+
if (ids.length === 0 && section.enabled !== false) ids.push(DEFAULT_ACCOUNT_ID);
|
|
63
|
+
return ids;
|
|
64
|
+
},
|
|
65
|
+
resolveAccount(cfg, accountId) {
|
|
66
|
+
const section = getChannelSection(cfg);
|
|
67
|
+
const id = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
68
|
+
const accountSection = section.accounts?.[id];
|
|
69
|
+
if (accountSection) return {
|
|
70
|
+
accountId: id,
|
|
71
|
+
enabled: accountSection.enabled !== false,
|
|
72
|
+
allowFrom: normalizeAllowFrom(accountSection.allowFrom),
|
|
73
|
+
defaultTo: accountSection.defaultTo,
|
|
74
|
+
dmPolicy: accountSection.dmPolicy
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
accountId: id,
|
|
78
|
+
enabled: section.enabled !== false,
|
|
79
|
+
allowFrom: normalizeAllowFrom(section.allowFrom),
|
|
80
|
+
defaultTo: section.defaultTo,
|
|
81
|
+
dmPolicy: section.dmPolicy
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
defaultAccountId() {
|
|
85
|
+
return DEFAULT_ACCOUNT_ID;
|
|
86
|
+
},
|
|
87
|
+
isEnabled(account) {
|
|
88
|
+
return account.enabled;
|
|
89
|
+
},
|
|
90
|
+
isConfigured() {
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
describeAccount(account) {
|
|
94
|
+
return {
|
|
95
|
+
accountId: account.accountId,
|
|
96
|
+
enabled: account.enabled,
|
|
97
|
+
configured: true,
|
|
98
|
+
dmPolicy: account.dmPolicy
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
resolveAllowFrom(params) {
|
|
102
|
+
const section = getChannelSection(params.cfg);
|
|
103
|
+
const id = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
104
|
+
return normalizeAllowFrom((section.accounts?.[id])?.allowFrom ?? section.allowFrom);
|
|
105
|
+
},
|
|
106
|
+
resolveDefaultTo(params) {
|
|
107
|
+
const section = getChannelSection(params.cfg);
|
|
108
|
+
const id = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
109
|
+
return section.accounts?.[id]?.defaultTo ?? section.defaultTo;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
outbound: {
|
|
113
|
+
deliveryMode: "gateway",
|
|
114
|
+
textChunkLimit: 4e3
|
|
115
|
+
},
|
|
116
|
+
setup: {
|
|
117
|
+
resolveAccountId(params) {
|
|
118
|
+
return params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
119
|
+
},
|
|
120
|
+
applyAccountConfig(params) {
|
|
121
|
+
const cfg = { ...params.cfg };
|
|
122
|
+
cfg.channels ??= {};
|
|
123
|
+
cfg.channels.alfe ??= {};
|
|
124
|
+
const section = cfg.channels.alfe;
|
|
125
|
+
if (params.accountId === DEFAULT_ACCOUNT_ID) section.enabled = true;
|
|
126
|
+
else {
|
|
127
|
+
section.accounts ??= {};
|
|
128
|
+
section.accounts[params.accountId] = {
|
|
129
|
+
enabled: true,
|
|
130
|
+
...params.input
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return cfg;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/session-keys.ts
|
|
140
|
+
/**
|
|
141
|
+
* Session key helpers — handles both legacy and canonical OpenClaw formats.
|
|
142
|
+
*
|
|
143
|
+
* Canonical format (from resolveAgentRoute):
|
|
144
|
+
* agent:{agentId}:alfe:direct:{userId}
|
|
145
|
+
* agent:{agentId}:alfe:direct:{userId}:thread:{conversationId}
|
|
146
|
+
*
|
|
147
|
+
* Legacy format (from chat adapter):
|
|
148
|
+
* chat-{tenantId}-{agentId}-{suffix}
|
|
149
|
+
* agent:{agentId}:chat-{tenantId}-{agentId}-{suffix}
|
|
150
|
+
*
|
|
151
|
+
* The plugin may receive either format depending on which
|
|
152
|
+
* OpenClaw event fires and whether the new dispatch path is active.
|
|
153
|
+
*/
|
|
154
|
+
/**
|
|
155
|
+
* Check if a session key belongs to the Alfe chat channel.
|
|
156
|
+
* Handles both canonical and legacy formats.
|
|
157
|
+
*/
|
|
158
|
+
function isAlfeSessionKey(key) {
|
|
159
|
+
if (key.includes(":alfe:")) return true;
|
|
160
|
+
if (key.includes("chat-")) return true;
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/session-store.ts
|
|
165
|
+
/**
|
|
166
|
+
* Session Store — persists chat sessions to the local filesystem.
|
|
167
|
+
*
|
|
168
|
+
* Storage layout:
|
|
169
|
+
* ~/.alfe/sessions/chat/{sessionId}.json
|
|
170
|
+
*
|
|
171
|
+
* Each session file contains metadata and the full message history.
|
|
172
|
+
* Sessions are written on every message to ensure durability.
|
|
173
|
+
* Old sessions are cleaned up automatically (30-day TTL, 1000 max).
|
|
174
|
+
*/
|
|
175
|
+
const SESSIONS_DIR = (0, node_path.join)((0, node_os.homedir)(), ".alfe", "sessions", "chat");
|
|
176
|
+
const MAX_SESSIONS = 1e3;
|
|
177
|
+
const MAX_AGE_MS = 720 * 60 * 60 * 1e3;
|
|
178
|
+
const CLEANUP_INTERVAL_MS = 36e5;
|
|
179
|
+
let lastCleanupAt = 0;
|
|
180
|
+
async function ensureDir() {
|
|
181
|
+
if (!(0, node_fs.existsSync)(SESSIONS_DIR)) await (0, node_fs_promises.mkdir)(SESSIONS_DIR, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
function sessionPath(sessionId) {
|
|
184
|
+
return (0, node_path.join)(SESSIONS_DIR, `${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
|
|
185
|
+
}
|
|
186
|
+
async function cleanupOldSessions() {
|
|
187
|
+
if (Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) return;
|
|
188
|
+
lastCleanupAt = Date.now();
|
|
189
|
+
try {
|
|
190
|
+
const jsonFiles = (await (0, node_fs_promises.readdir)(SESSIONS_DIR)).filter((f) => f.endsWith(".json"));
|
|
191
|
+
if (jsonFiles.length <= MAX_SESSIONS) {
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
for (const file of jsonFiles) try {
|
|
194
|
+
const filePath = (0, node_path.join)(SESSIONS_DIR, file);
|
|
195
|
+
if (now - (await (0, node_fs_promises.stat)(filePath)).mtimeMs > MAX_AGE_MS) await (0, node_fs_promises.unlink)(filePath);
|
|
196
|
+
} catch {}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const fileStats = [];
|
|
200
|
+
for (const file of jsonFiles) try {
|
|
201
|
+
const filePath = (0, node_path.join)(SESSIONS_DIR, file);
|
|
202
|
+
const fileStat = await (0, node_fs_promises.stat)(filePath);
|
|
203
|
+
fileStats.push({
|
|
204
|
+
path: filePath,
|
|
205
|
+
mtimeMs: fileStat.mtimeMs
|
|
206
|
+
});
|
|
207
|
+
} catch {}
|
|
208
|
+
fileStats.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
let remaining = fileStats.length;
|
|
211
|
+
for (const entry of fileStats) {
|
|
212
|
+
if (!(now - entry.mtimeMs > MAX_AGE_MS) && !(remaining > MAX_SESSIONS)) break;
|
|
213
|
+
try {
|
|
214
|
+
await (0, node_fs_promises.unlink)(entry.path);
|
|
215
|
+
remaining--;
|
|
216
|
+
} catch {}
|
|
217
|
+
}
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
async function getSession(sessionId) {
|
|
221
|
+
try {
|
|
222
|
+
const data = await (0, node_fs_promises.readFile)(sessionPath(sessionId), "utf-8");
|
|
223
|
+
return JSON.parse(data);
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function saveSession(session) {
|
|
229
|
+
await ensureDir();
|
|
230
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
231
|
+
await (0, node_fs_promises.writeFile)(sessionPath(session.sessionId), JSON.stringify(session, null, 2), "utf-8");
|
|
232
|
+
}
|
|
233
|
+
async function createSession(sessionId, agentId, channel, tenantId, userId) {
|
|
234
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
235
|
+
const session = {
|
|
236
|
+
sessionId,
|
|
237
|
+
agentId,
|
|
238
|
+
channel,
|
|
239
|
+
tenantId,
|
|
240
|
+
userId,
|
|
241
|
+
createdAt: now,
|
|
242
|
+
updatedAt: now,
|
|
243
|
+
messages: []
|
|
244
|
+
};
|
|
245
|
+
await saveSession(session);
|
|
246
|
+
cleanupOldSessions();
|
|
247
|
+
return session;
|
|
248
|
+
}
|
|
249
|
+
async function addMessage(sessionId, role, content, senderId, senderName) {
|
|
250
|
+
const session = await getSession(sessionId);
|
|
251
|
+
if (!session) return;
|
|
252
|
+
session.messages.push({
|
|
253
|
+
role,
|
|
254
|
+
content,
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
...senderId ? { senderId } : {},
|
|
257
|
+
...senderName ? { senderName } : {}
|
|
258
|
+
});
|
|
259
|
+
await saveSession(session);
|
|
260
|
+
}
|
|
261
|
+
async function listSessions(filters, limit = 50) {
|
|
262
|
+
await ensureDir();
|
|
263
|
+
let files;
|
|
264
|
+
try {
|
|
265
|
+
files = await (0, node_fs_promises.readdir)(SESSIONS_DIR);
|
|
266
|
+
} catch {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
270
|
+
const summaries = [];
|
|
271
|
+
for (const file of jsonFiles) try {
|
|
272
|
+
const data = await (0, node_fs_promises.readFile)((0, node_path.join)(SESSIONS_DIR, file), "utf-8");
|
|
273
|
+
const session = JSON.parse(data);
|
|
274
|
+
if (filters?.agentId && session.agentId !== filters.agentId) continue;
|
|
275
|
+
if (filters?.channel && session.channel !== filters.channel) continue;
|
|
276
|
+
if (filters?.tenantId && session.tenantId !== filters.tenantId) continue;
|
|
277
|
+
if (filters?.userId && session.userId !== filters.userId) continue;
|
|
278
|
+
const lastMsg = session.messages.at(-1);
|
|
279
|
+
summaries.push({
|
|
280
|
+
sessionId: session.sessionId,
|
|
281
|
+
agentId: session.agentId,
|
|
282
|
+
channel: session.channel,
|
|
283
|
+
createdAt: session.createdAt,
|
|
284
|
+
lastMessageAt: lastMsg ? new Date(lastMsg.timestamp).toISOString() : void 0,
|
|
285
|
+
preview: lastMsg?.content.slice(0, 100),
|
|
286
|
+
messageCount: session.messages.length
|
|
287
|
+
});
|
|
288
|
+
} catch {}
|
|
289
|
+
summaries.sort((a, b) => {
|
|
290
|
+
const aTime = a.lastMessageAt ?? a.createdAt;
|
|
291
|
+
return (b.lastMessageAt ?? b.createdAt).localeCompare(aTime);
|
|
292
|
+
});
|
|
293
|
+
return summaries.slice(0, limit);
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/plugin.ts
|
|
297
|
+
/**
|
|
298
|
+
* @alfe.ai/openclaw-chat — OpenClaw chat channel plugin.
|
|
299
|
+
*
|
|
300
|
+
* Registers the 'alfe' channel with OpenClaw. Messages are dispatched
|
|
301
|
+
* through OpenClaw's auto-reply pipeline via dispatchInboundDirectDmWithRuntime(),
|
|
302
|
+
* the same API built-in channels (Slack, Discord, Telegram) use.
|
|
303
|
+
*
|
|
304
|
+
* Architecture:
|
|
305
|
+
* Chat Service (Fly.io) ←WS→ ChatServiceClient
|
|
306
|
+
* → dispatchInboundDirectDmWithRuntime() → Agent (auto-reply pipeline)
|
|
307
|
+
* ← onAgentEvent streaming ← (real-time deltas)
|
|
308
|
+
* ← deliver() callback ← (final response)
|
|
309
|
+
*/
|
|
310
|
+
let dispatchInbound = null;
|
|
311
|
+
/**
|
|
312
|
+
* Resolve OpenClaw SDK functions from the global install.
|
|
313
|
+
* The openclaw package is installed globally (/usr/lib/node_modules/openclaw/)
|
|
314
|
+
* but is NOT in the plugin's node_modules. Built-in extensions can import
|
|
315
|
+
* directly; external plugins must use createRequire.
|
|
316
|
+
*/
|
|
317
|
+
function resolveOpenClawSdk(log) {
|
|
318
|
+
for (const globalPath of ["/usr/lib/node_modules/openclaw/package.json", "/usr/local/lib/node_modules/openclaw/package.json"]) try {
|
|
319
|
+
const channelInbound = (0, node_module.createRequire)(globalPath)("openclaw/plugin-sdk/channel-inbound");
|
|
320
|
+
if (channelInbound.dispatchInboundDirectDmWithRuntime) {
|
|
321
|
+
dispatchInbound = channelInbound.dispatchInboundDirectDmWithRuntime;
|
|
322
|
+
log.info(`Resolved OpenClaw SDK from ${globalPath}`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
} catch {}
|
|
326
|
+
log.warn("OpenClaw SDK not resolvable — chat dispatch will not work");
|
|
327
|
+
}
|
|
328
|
+
let pluginRuntime = null;
|
|
329
|
+
let chatClient = null;
|
|
330
|
+
let connectingPromise = null;
|
|
331
|
+
async function handleAgentRequest(request, log) {
|
|
332
|
+
const runtime = pluginRuntime;
|
|
333
|
+
if (!runtime) {
|
|
334
|
+
chatClient?.sendResponse(request.id, false, { message: "Plugin runtime not initialized" });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (!dispatchInbound) {
|
|
338
|
+
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName } = request.params;
|
|
342
|
+
if (!message) {
|
|
343
|
+
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const senderId = displayName ?? userId ?? "anon";
|
|
347
|
+
const cfg = runtime.config.loadConfig();
|
|
348
|
+
const sessionId = conversationId ?? legacySessionKey;
|
|
349
|
+
if (!await getSession(sessionId)) await createSession(sessionId, "", "alfe", tenantId, userId);
|
|
350
|
+
await addMessage(sessionId, "user", message, userId ?? senderId, displayName ?? senderId);
|
|
351
|
+
let resolvedOpenClawKey = null;
|
|
352
|
+
const unsubscribe = runtime.events.onAgentEvent((evt) => {
|
|
353
|
+
if (!evt.sessionKey) return;
|
|
354
|
+
if (evt.stream !== "assistant") return;
|
|
355
|
+
resolvedOpenClawKey ??= evt.sessionKey;
|
|
356
|
+
if (evt.sessionKey !== resolvedOpenClawKey) return;
|
|
357
|
+
chatClient?.sendEvent("chat", {
|
|
358
|
+
runId: evt.runId,
|
|
359
|
+
sessionKey: legacySessionKey,
|
|
360
|
+
seq: evt.seq,
|
|
361
|
+
state: "delta",
|
|
362
|
+
message: evt.data
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
try {
|
|
366
|
+
const shortConvId = conversationId?.slice(-8) ?? "";
|
|
367
|
+
const userLabel = displayName ?? userId ?? senderId;
|
|
368
|
+
const conversationLabel = conversationType === "group" ? shortConvId ? `[Alfe] Group (${shortConvId})` : "[Alfe] Group" : shortConvId ? `[Alfe] ${userLabel} (${shortConvId})` : `[Alfe] ${userLabel}`;
|
|
369
|
+
resolvedOpenClawKey = (await dispatchInbound({
|
|
370
|
+
cfg,
|
|
371
|
+
runtime: { channel: runtime.channel },
|
|
372
|
+
channel: "alfe",
|
|
373
|
+
channelLabel: "Alfe",
|
|
374
|
+
accountId: "default",
|
|
375
|
+
peer: conversationType === "group" ? {
|
|
376
|
+
kind: "group",
|
|
377
|
+
id: conversationId ?? senderId
|
|
378
|
+
} : {
|
|
379
|
+
kind: "direct",
|
|
380
|
+
id: conversationId ? `${senderId}:conv:${conversationId}` : senderId
|
|
381
|
+
},
|
|
382
|
+
senderId,
|
|
383
|
+
senderAddress: `user:${senderId}`,
|
|
384
|
+
recipientAddress: "agent",
|
|
385
|
+
conversationLabel,
|
|
386
|
+
rawBody: message,
|
|
387
|
+
messageId: request.id,
|
|
388
|
+
timestamp: Date.now(),
|
|
389
|
+
extraContext: {
|
|
390
|
+
...tenantId ? { TenantId: tenantId } : {},
|
|
391
|
+
...clientType ? { ClientType: clientType } : {},
|
|
392
|
+
...conversationId ? { ConversationId: conversationId } : {},
|
|
393
|
+
...displayName ? { SenderName: displayName } : {}
|
|
394
|
+
},
|
|
395
|
+
deliver: async (payload) => {
|
|
396
|
+
const responseText = payload.text ?? "";
|
|
397
|
+
await addMessage(sessionId, "assistant", responseText);
|
|
398
|
+
chatClient?.sendResponse(request.id, true, {
|
|
399
|
+
text: responseText,
|
|
400
|
+
sessionKey: resolvedOpenClawKey ?? legacySessionKey
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
onRecordError: (err) => {
|
|
404
|
+
log.error(`Session record error: ${err instanceof Error ? err.message : String(err)}`);
|
|
405
|
+
},
|
|
406
|
+
onDispatchError: (err, info) => {
|
|
407
|
+
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
408
|
+
}
|
|
409
|
+
})).route.sessionKey;
|
|
410
|
+
log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
413
|
+
log.error(`Agent dispatch failed: ${errMsg}`);
|
|
414
|
+
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
415
|
+
} finally {
|
|
416
|
+
unsubscribe();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function handleSessionsList(request, log) {
|
|
420
|
+
try {
|
|
421
|
+
const params = request.params;
|
|
422
|
+
const sessions = await listSessions({
|
|
423
|
+
channel: params.channel,
|
|
424
|
+
tenantId: params.tenantId,
|
|
425
|
+
userId: params.userId
|
|
426
|
+
});
|
|
427
|
+
chatClient?.sendResponse(request.id, true, { sessions });
|
|
428
|
+
} catch (err) {
|
|
429
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
430
|
+
log.error(`sessions.list failed: ${errMsg}`);
|
|
431
|
+
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function handleSessionsGet(request, log) {
|
|
435
|
+
try {
|
|
436
|
+
const params = request.params;
|
|
437
|
+
const session = await getSession(params.sessionId);
|
|
438
|
+
if (!session) {
|
|
439
|
+
chatClient?.sendResponse(request.id, true, {
|
|
440
|
+
ok: false,
|
|
441
|
+
error: "Session not found"
|
|
442
|
+
});
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (params.userId && session.userId && session.userId !== params.userId) {
|
|
446
|
+
chatClient?.sendResponse(request.id, true, {
|
|
447
|
+
ok: false,
|
|
448
|
+
error: "Session not found"
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
chatClient?.sendResponse(request.id, true, {
|
|
453
|
+
sessionId: session.sessionId,
|
|
454
|
+
agentId: session.agentId,
|
|
455
|
+
channel: session.channel,
|
|
456
|
+
createdAt: session.createdAt,
|
|
457
|
+
messages: session.messages.map((m) => ({
|
|
458
|
+
id: `msg-${String(m.timestamp)}`,
|
|
459
|
+
role: m.role,
|
|
460
|
+
content: m.content,
|
|
461
|
+
timestamp: m.timestamp
|
|
462
|
+
}))
|
|
463
|
+
});
|
|
464
|
+
} catch (err) {
|
|
465
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
466
|
+
log.error(`sessions.get failed: ${errMsg}`);
|
|
467
|
+
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const plugin = {
|
|
471
|
+
id: "@alfe.ai/openclaw-chat",
|
|
472
|
+
name: "Chat Plugin",
|
|
473
|
+
description: "Chat conversation channel — web widget and mobile app share unified chat sessions",
|
|
474
|
+
version: "0.0.8",
|
|
475
|
+
activate(api) {
|
|
476
|
+
const log = api.logger;
|
|
477
|
+
const alreadyActivated = globalThis.__alfeChatPluginActivated === true;
|
|
478
|
+
globalThis.__alfeChatPluginActivated = true;
|
|
479
|
+
const alfeChannel = createAlfeChannelPlugin();
|
|
480
|
+
api.registerChannel(alfeChannel);
|
|
481
|
+
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
482
|
+
if (!alreadyActivated) {
|
|
483
|
+
log.info("Chat plugin registering...");
|
|
484
|
+
resolveOpenClawSdk(log);
|
|
485
|
+
pluginRuntime = api.runtime ?? null;
|
|
486
|
+
}
|
|
487
|
+
const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
|
|
488
|
+
if (!alreadyActivated) connectingPromise = Promise.resolve().then(() => {
|
|
489
|
+
try {
|
|
490
|
+
const { apiKey, chatWsUrl } = (0, _alfe_ai_chat.resolveAlfeChat)({
|
|
491
|
+
apiKey: pluginConfig.apiKey,
|
|
492
|
+
chatWsUrl: pluginConfig.chatWsUrl
|
|
493
|
+
});
|
|
494
|
+
if (chatWsUrl && apiKey) {
|
|
495
|
+
log.info(`Connecting to chat service: ${chatWsUrl}`);
|
|
496
|
+
chatClient = new _alfe_ai_chat.ChatServiceClient({
|
|
497
|
+
wsUrl: chatWsUrl,
|
|
498
|
+
apiKey,
|
|
499
|
+
onRequest: (request) => {
|
|
500
|
+
if (request.method === "agent") handleAgentRequest(request, log);
|
|
501
|
+
else if (request.method === "sessions.list") handleSessionsList(request, log);
|
|
502
|
+
else if (request.method === "sessions.get") handleSessionsGet(request, log);
|
|
503
|
+
else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
|
|
504
|
+
},
|
|
505
|
+
onConnectionChange: (connected) => {
|
|
506
|
+
log.info(`Chat service connection: ${connected ? "connected" : "disconnected"}`);
|
|
507
|
+
},
|
|
508
|
+
logger: log
|
|
509
|
+
});
|
|
510
|
+
chatClient.start();
|
|
511
|
+
log.info("Chat service relay started");
|
|
512
|
+
} else log.info("Chat service URL not configured — running without chat service relay");
|
|
513
|
+
} catch (err) {
|
|
514
|
+
log.error(`Failed to initialize chat service: ${err instanceof Error ? err.message : String(err)}`);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
if (typeof api.registerGatewayMethod === "function") {
|
|
518
|
+
api.registerGatewayMethod("sessions.list", async (...args) => {
|
|
519
|
+
const params = args[0];
|
|
520
|
+
return { sessions: await listSessions({
|
|
521
|
+
channel: params.channel,
|
|
522
|
+
tenantId: params.tenantId,
|
|
523
|
+
userId: params.userId
|
|
524
|
+
}) };
|
|
525
|
+
});
|
|
526
|
+
api.registerGatewayMethod("sessions.get", async (...args) => {
|
|
527
|
+
const params = args[0];
|
|
528
|
+
const session = await getSession(params.sessionId);
|
|
529
|
+
if (!session) return {
|
|
530
|
+
ok: false,
|
|
531
|
+
error: "Session not found"
|
|
532
|
+
};
|
|
533
|
+
if (params.userId && session.userId && session.userId !== params.userId) return {
|
|
534
|
+
ok: false,
|
|
535
|
+
error: "Session not found"
|
|
536
|
+
};
|
|
537
|
+
return {
|
|
538
|
+
sessionId: session.sessionId,
|
|
539
|
+
agentId: session.agentId,
|
|
540
|
+
channel: session.channel,
|
|
541
|
+
createdAt: session.createdAt,
|
|
542
|
+
messages: session.messages.map((m) => ({
|
|
543
|
+
id: `msg-${String(m.timestamp)}`,
|
|
544
|
+
role: m.role,
|
|
545
|
+
content: m.content,
|
|
546
|
+
timestamp: m.timestamp
|
|
547
|
+
}))
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
log.info("Registered gateway RPC methods: sessions.list, sessions.get");
|
|
551
|
+
}
|
|
552
|
+
if (!alreadyActivated) {
|
|
553
|
+
api.on("session_start", async (...eventArgs) => {
|
|
554
|
+
const key = eventArgs[0].sessionKey;
|
|
555
|
+
if (!key || isAlfeSessionKey(key)) return;
|
|
556
|
+
log.info(`Chat session starting: ${key}`);
|
|
557
|
+
await createSession(key, "", "alfe");
|
|
558
|
+
}, { priority: 50 });
|
|
559
|
+
api.on("message", async (...eventArgs) => {
|
|
560
|
+
const event = eventArgs[0];
|
|
561
|
+
const key = event.sessionKey;
|
|
562
|
+
if (!key || isAlfeSessionKey(key)) return;
|
|
563
|
+
await addMessage(key, event.role, event.content);
|
|
564
|
+
});
|
|
565
|
+
api.on("session_end", (...eventArgs) => {
|
|
566
|
+
const key = eventArgs[0].sessionKey;
|
|
567
|
+
if (!key || !isAlfeSessionKey(key)) return;
|
|
568
|
+
log.info(`Chat session ending: ${key}`);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
log.info("Chat plugin registered");
|
|
572
|
+
},
|
|
573
|
+
async deactivate(api) {
|
|
574
|
+
globalThis.__alfeChatPluginActivated = false;
|
|
575
|
+
const log = api.logger;
|
|
576
|
+
log.info("Chat plugin deactivating...");
|
|
577
|
+
if (connectingPromise) {
|
|
578
|
+
await connectingPromise.catch((err) => {
|
|
579
|
+
api.logger.debug(`Connection attempt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
580
|
+
});
|
|
581
|
+
connectingPromise = null;
|
|
582
|
+
}
|
|
583
|
+
if (chatClient) {
|
|
584
|
+
chatClient.stop();
|
|
585
|
+
chatClient = null;
|
|
586
|
+
log.info("Chat service client stopped");
|
|
587
|
+
}
|
|
588
|
+
pluginRuntime = null;
|
|
589
|
+
dispatchInbound = null;
|
|
590
|
+
log.info("Chat plugin deactivated");
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
//#endregion
|
|
594
|
+
Object.defineProperty(exports, "createAlfeChannelPlugin", {
|
|
595
|
+
enumerable: true,
|
|
596
|
+
get: function() {
|
|
597
|
+
return createAlfeChannelPlugin;
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
Object.defineProperty(exports, "plugin", {
|
|
601
|
+
enumerable: true,
|
|
602
|
+
get: function() {
|
|
603
|
+
return plugin;
|
|
604
|
+
}
|
|
605
|
+
});
|
package/dist/plugin2.js
CHANGED
|
@@ -274,6 +274,7 @@ async function listSessions(filters, limit = 50) {
|
|
|
274
274
|
if (filters?.agentId && session.agentId !== filters.agentId) continue;
|
|
275
275
|
if (filters?.channel && session.channel !== filters.channel) continue;
|
|
276
276
|
if (filters?.tenantId && session.tenantId !== filters.tenantId) continue;
|
|
277
|
+
if (filters?.userId && session.userId !== filters.userId) continue;
|
|
277
278
|
const lastMsg = session.messages.at(-1);
|
|
278
279
|
summaries.push({
|
|
279
280
|
sessionId: session.sessionId,
|
|
@@ -420,7 +421,8 @@ async function handleSessionsList(request, log) {
|
|
|
420
421
|
const params = request.params;
|
|
421
422
|
const sessions = await listSessions({
|
|
422
423
|
channel: params.channel,
|
|
423
|
-
tenantId: params.tenantId
|
|
424
|
+
tenantId: params.tenantId,
|
|
425
|
+
userId: params.userId
|
|
424
426
|
});
|
|
425
427
|
chatClient?.sendResponse(request.id, true, { sessions });
|
|
426
428
|
} catch (err) {
|
|
@@ -440,6 +442,13 @@ async function handleSessionsGet(request, log) {
|
|
|
440
442
|
});
|
|
441
443
|
return;
|
|
442
444
|
}
|
|
445
|
+
if (params.userId && session.userId && session.userId !== params.userId) {
|
|
446
|
+
chatClient?.sendResponse(request.id, true, {
|
|
447
|
+
ok: false,
|
|
448
|
+
error: "Session not found"
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
443
452
|
chatClient?.sendResponse(request.id, true, {
|
|
444
453
|
sessionId: session.sessionId,
|
|
445
454
|
agentId: session.agentId,
|
|
@@ -510,7 +519,8 @@ const plugin = {
|
|
|
510
519
|
const params = args[0];
|
|
511
520
|
return { sessions: await listSessions({
|
|
512
521
|
channel: params.channel,
|
|
513
|
-
tenantId: params.tenantId
|
|
522
|
+
tenantId: params.tenantId,
|
|
523
|
+
userId: params.userId
|
|
514
524
|
}) };
|
|
515
525
|
});
|
|
516
526
|
api.registerGatewayMethod("sessions.get", async (...args) => {
|
|
@@ -520,6 +530,10 @@ const plugin = {
|
|
|
520
530
|
ok: false,
|
|
521
531
|
error: "Session not found"
|
|
522
532
|
};
|
|
533
|
+
if (params.userId && session.userId && session.userId !== params.userId) return {
|
|
534
|
+
ok: false,
|
|
535
|
+
error: "Session not found"
|
|
536
|
+
};
|
|
523
537
|
return {
|
|
524
538
|
sessionId: session.sessionId,
|
|
525
539
|
agentId: session.agentId,
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-chat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugin.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"import": "./dist/index.js"
|
|
12
13
|
},
|
|
13
14
|
"./plugin": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
15
|
+
"types": "./dist/plugin.d.ts",
|
|
16
|
+
"require": "./dist/plugin.cjs",
|
|
17
|
+
"import": "./dist/plugin.js"
|
|
16
18
|
}
|
|
17
19
|
},
|
|
18
20
|
"openclaw": {
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
"openclaw.plugin.json"
|
|
26
28
|
],
|
|
27
29
|
"dependencies": {
|
|
28
|
-
"@alfe.ai/chat": "^0.0.
|
|
30
|
+
"@alfe.ai/chat": "^0.0.5"
|
|
29
31
|
},
|
|
30
32
|
"peerDependencies": {
|
|
31
33
|
"openclaw": ">=2026.3.0"
|