openclaw-vchat-plugin 0.0.1
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/bin/openclaw-vchat.js +110 -0
- package/dist/commands.d.ts +18 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +509 -0
- package/dist/commands.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/gateway-client.d.ts +43 -0
- package/dist/gateway-client.d.ts.map +1 -0
- package/dist/gateway-client.js +623 -0
- package/dist/gateway-client.js.map +1 -0
- package/dist/group-manager.d.ts +30 -0
- package/dist/group-manager.d.ts.map +1 -0
- package/dist/group-manager.js +107 -0
- package/dist/group-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +382 -0
- package/dist/index.js.map +1 -0
- package/dist/media-handler.d.ts +31 -0
- package/dist/media-handler.d.ts.map +1 -0
- package/dist/media-handler.js +67 -0
- package/dist/media-handler.js.map +1 -0
- package/dist/message-handler.d.ts +52 -0
- package/dist/message-handler.d.ts.map +1 -0
- package/dist/message-handler.js +291 -0
- package/dist/message-handler.js.map +1 -0
- package/dist/relay-server.d.ts +16 -0
- package/dist/relay-server.d.ts.map +1 -0
- package/dist/relay-server.js +877 -0
- package/dist/relay-server.js.map +1 -0
- package/dist/routes/config.routes.d.ts +12 -0
- package/dist/routes/config.routes.d.ts.map +1 -0
- package/dist/routes/config.routes.js +175 -0
- package/dist/routes/config.routes.js.map +1 -0
- package/dist/services/config.service.d.ts +57 -0
- package/dist/services/config.service.d.ts.map +1 -0
- package/dist/services/config.service.js +361 -0
- package/dist/services/config.service.js.map +1 -0
- package/dist/session-key.d.ts +8 -0
- package/dist/session-key.d.ts.map +1 -0
- package/dist/session-key.js +28 -0
- package/dist/session-key.js.map +1 -0
- package/dist/session-manager.d.ts +32 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +303 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/nginx-proxy.conf +24 -0
- package/package.json +51 -0
- package/src/commands.ts +499 -0
- package/src/constants.ts +49 -0
- package/src/gateway-client.ts +648 -0
- package/src/group-manager.ts +119 -0
- package/src/index.ts +443 -0
- package/src/media-handler.ts +70 -0
- package/src/message-handler.ts +419 -0
- package/src/relay-server.ts +979 -0
- package/src/routes/config.routes.ts +144 -0
- package/src/services/config.service.ts +398 -0
- package/src/session-key.ts +30 -0
- package/src/session-manager.ts +374 -0
- package/src/types.ts +96 -0
- package/start.sh +5 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
import { Message, Session, SessionUser, MessageRole, MessageType } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 会话管理器 - SQLite
|
|
8
|
+
* 仅持久化会话元数据与会话键绑定;聊天明细默认不落地(可通过环境变量开启兼容模式)
|
|
9
|
+
*/
|
|
10
|
+
export class SessionManager {
|
|
11
|
+
private db: Database.Database;
|
|
12
|
+
private persistMessages: boolean;
|
|
13
|
+
|
|
14
|
+
constructor(dataDir: string, opts?: { persistMessages?: boolean }) {
|
|
15
|
+
const dbPath = path.join(dataDir, 'openclaw-wechat.db');
|
|
16
|
+
this.db = new Database(dbPath);
|
|
17
|
+
this.db.pragma('journal_mode = WAL');
|
|
18
|
+
this.db.pragma('foreign_keys = ON');
|
|
19
|
+
this.persistMessages = Boolean(
|
|
20
|
+
opts?.persistMessages ?? (process.env.WECHAT_PLUGIN_PERSIST_MESSAGES === '1')
|
|
21
|
+
);
|
|
22
|
+
this.initTables();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private initTables(): void {
|
|
26
|
+
this.db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
28
|
+
openid TEXT PRIMARY KEY,
|
|
29
|
+
nickname TEXT,
|
|
30
|
+
avatar_url TEXT,
|
|
31
|
+
created_at INTEGER NOT NULL,
|
|
32
|
+
last_login_at INTEGER NOT NULL
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
user_id TEXT NOT NULL,
|
|
38
|
+
title TEXT NOT NULL DEFAULT '新对话',
|
|
39
|
+
last_message TEXT DEFAULT '',
|
|
40
|
+
last_message_time INTEGER NOT NULL,
|
|
41
|
+
agent_id TEXT DEFAULT 'main',
|
|
42
|
+
gateway_key TEXT,
|
|
43
|
+
created_at INTEGER NOT NULL,
|
|
44
|
+
FOREIGN KEY (user_id) REFERENCES users(openid)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
50
|
+
id TEXT PRIMARY KEY,
|
|
51
|
+
session_id TEXT NOT NULL,
|
|
52
|
+
role TEXT NOT NULL,
|
|
53
|
+
type TEXT NOT NULL DEFAULT 'text',
|
|
54
|
+
content TEXT NOT NULL,
|
|
55
|
+
media_url TEXT,
|
|
56
|
+
duration REAL,
|
|
57
|
+
timestamp INTEGER NOT NULL,
|
|
58
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp);
|
|
63
|
+
`);
|
|
64
|
+
this.ensureColumn('sessions', 'agent_id', "TEXT DEFAULT 'main'");
|
|
65
|
+
this.ensureColumn('sessions', 'gateway_key', 'TEXT');
|
|
66
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_gateway_key ON sessions(gateway_key)');
|
|
67
|
+
this.compactSessionMetadata();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private compactSessionMetadata(): void {
|
|
71
|
+
if (this.persistMessages) return;
|
|
72
|
+
|
|
73
|
+
this.db.prepare(`
|
|
74
|
+
UPDATE sessions
|
|
75
|
+
SET last_message = '',
|
|
76
|
+
last_message_time = created_at,
|
|
77
|
+
title = '新对话'
|
|
78
|
+
WHERE COALESCE(last_message, '') <> ''
|
|
79
|
+
OR last_message_time <> created_at
|
|
80
|
+
OR COALESCE(title, '') <> '新对话'
|
|
81
|
+
`).run();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private normalizeStoredSession(row: any): Session {
|
|
85
|
+
const createdAt = Number(row.created_at) || Date.now();
|
|
86
|
+
const lastMessageTime = this.persistMessages
|
|
87
|
+
? (Number(row.last_message_time) || createdAt)
|
|
88
|
+
: createdAt;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
id: row.id,
|
|
92
|
+
userId: row.user_id,
|
|
93
|
+
title: row.title || '新对话',
|
|
94
|
+
lastMessage: this.persistMessages ? (row.last_message || '') : '',
|
|
95
|
+
lastMessageTime,
|
|
96
|
+
agentId: row.agent_id || 'main',
|
|
97
|
+
gatewaySessionKey: row.gateway_key || '',
|
|
98
|
+
createdAt,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private ensureColumn(table: string, column: string, sqlType: string): void {
|
|
103
|
+
const rows = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
104
|
+
if (!rows.some(r => r.name === column)) {
|
|
105
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${sqlType}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ===== 用户管理 =====
|
|
110
|
+
|
|
111
|
+
upsertUser(openid: string, nickname?: string, avatarUrl?: string): void {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const stmt = this.db.prepare(`
|
|
114
|
+
INSERT INTO users (openid, nickname, avatar_url, created_at, last_login_at)
|
|
115
|
+
VALUES (?, ?, ?, ?, ?)
|
|
116
|
+
ON CONFLICT(openid) DO UPDATE SET
|
|
117
|
+
nickname = COALESCE(excluded.nickname, users.nickname),
|
|
118
|
+
avatar_url = COALESCE(excluded.avatar_url, users.avatar_url),
|
|
119
|
+
last_login_at = excluded.last_login_at
|
|
120
|
+
`);
|
|
121
|
+
stmt.run(openid, nickname || null, avatarUrl || null, now, now);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getUser(openid: string): SessionUser | null {
|
|
125
|
+
const row = this.db.prepare('SELECT * FROM users WHERE openid = ?').get(openid) as any;
|
|
126
|
+
if (!row) return null;
|
|
127
|
+
return {
|
|
128
|
+
openid: row.openid,
|
|
129
|
+
nickname: row.nickname,
|
|
130
|
+
avatarUrl: row.avatar_url,
|
|
131
|
+
createdAt: row.created_at,
|
|
132
|
+
lastLoginAt: row.last_login_at,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ===== 会话管理 =====
|
|
137
|
+
|
|
138
|
+
createSession(userId: string, title?: string, agentId = 'main', sessionId?: string): Session {
|
|
139
|
+
this.upsertUser(userId);
|
|
140
|
+
const id = sessionId || uuidv4();
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const sessionTitle = title || '新对话';
|
|
143
|
+
|
|
144
|
+
this.db.prepare(`
|
|
145
|
+
INSERT OR REPLACE INTO sessions (id, user_id, title, last_message, last_message_time, agent_id, gateway_key, created_at)
|
|
146
|
+
VALUES (?, ?, ?, '', ?, ?, NULL, ?)
|
|
147
|
+
`).run(id, userId, sessionTitle, now, agentId || 'main', now);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
id,
|
|
151
|
+
userId,
|
|
152
|
+
title: sessionTitle,
|
|
153
|
+
lastMessage: '',
|
|
154
|
+
lastMessageTime: now,
|
|
155
|
+
agentId: agentId || 'main',
|
|
156
|
+
createdAt: now,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getSessions(userId: string): Session[] {
|
|
161
|
+
const rows = this.db.prepare(
|
|
162
|
+
'SELECT * FROM sessions WHERE user_id = ? ORDER BY created_at DESC'
|
|
163
|
+
).all(userId) as any[];
|
|
164
|
+
|
|
165
|
+
return rows.map(row => this.normalizeStoredSession(row));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getSession(sessionId: string, userId: string): Session | null {
|
|
169
|
+
const row = this.db.prepare(
|
|
170
|
+
'SELECT * FROM sessions WHERE id = ? AND user_id = ?'
|
|
171
|
+
).get(sessionId, userId) as any;
|
|
172
|
+
|
|
173
|
+
if (!row) return null;
|
|
174
|
+
return this.normalizeStoredSession(row);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
deleteSession(sessionId: string, userId: string): boolean {
|
|
178
|
+
const result = this.db.prepare(
|
|
179
|
+
'DELETE FROM sessions WHERE id = ? AND user_id = ?'
|
|
180
|
+
).run(sessionId, userId);
|
|
181
|
+
return result.changes > 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
upsertSession(
|
|
185
|
+
userId: string,
|
|
186
|
+
sessionId: string,
|
|
187
|
+
patch: Partial<Pick<Session, 'title' | 'lastMessage' | 'lastMessageTime' | 'createdAt' | 'agentId' | 'gatewaySessionKey'>> = {}
|
|
188
|
+
): Session {
|
|
189
|
+
const existing = this.getSession(sessionId, userId);
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
const nextTitle = typeof patch.title === 'string' && patch.title.trim()
|
|
192
|
+
? patch.title.trim()
|
|
193
|
+
: (existing?.title || '新对话');
|
|
194
|
+
const nextCreatedAt = patch.createdAt || existing?.createdAt || now;
|
|
195
|
+
const nextLastMessage = this.persistMessages
|
|
196
|
+
? (patch.lastMessage != null ? patch.lastMessage : (existing?.lastMessage || ''))
|
|
197
|
+
: '';
|
|
198
|
+
const nextLastMessageTime = this.persistMessages
|
|
199
|
+
? (patch.lastMessageTime || existing?.lastMessageTime || nextCreatedAt)
|
|
200
|
+
: nextCreatedAt;
|
|
201
|
+
const next = {
|
|
202
|
+
id: sessionId,
|
|
203
|
+
userId,
|
|
204
|
+
title: nextTitle,
|
|
205
|
+
lastMessage: nextLastMessage,
|
|
206
|
+
lastMessageTime: nextLastMessageTime,
|
|
207
|
+
createdAt: nextCreatedAt,
|
|
208
|
+
agentId: patch.agentId || existing?.agentId || 'main',
|
|
209
|
+
gatewaySessionKey: patch.gatewaySessionKey || existing?.gatewaySessionKey || '',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
this.upsertUser(userId);
|
|
213
|
+
this.db.prepare(`
|
|
214
|
+
INSERT INTO sessions (id, user_id, title, last_message, last_message_time, agent_id, gateway_key, created_at)
|
|
215
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
216
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
217
|
+
user_id = excluded.user_id,
|
|
218
|
+
title = excluded.title,
|
|
219
|
+
last_message = excluded.last_message,
|
|
220
|
+
last_message_time = excluded.last_message_time,
|
|
221
|
+
agent_id = excluded.agent_id,
|
|
222
|
+
gateway_key = excluded.gateway_key,
|
|
223
|
+
created_at = excluded.created_at
|
|
224
|
+
`).run(
|
|
225
|
+
next.id,
|
|
226
|
+
next.userId,
|
|
227
|
+
next.title,
|
|
228
|
+
next.lastMessage,
|
|
229
|
+
next.lastMessageTime,
|
|
230
|
+
next.agentId || 'main',
|
|
231
|
+
next.gatewaySessionKey || null,
|
|
232
|
+
next.createdAt,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return next;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
ensureSessionBinding(
|
|
239
|
+
userId: string,
|
|
240
|
+
sessionId: string,
|
|
241
|
+
agentId = 'main',
|
|
242
|
+
gatewaySessionKey?: string,
|
|
243
|
+
): Session {
|
|
244
|
+
const sid = String(sessionId || '').trim();
|
|
245
|
+
if (!sid) {
|
|
246
|
+
return this.createSession(userId, undefined, agentId);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const existing = this.getSession(sid, userId);
|
|
250
|
+
if (existing) {
|
|
251
|
+
if (
|
|
252
|
+
existing.agentId !== (agentId || 'main')
|
|
253
|
+
|| (gatewaySessionKey && existing.gatewaySessionKey !== gatewaySessionKey)
|
|
254
|
+
) {
|
|
255
|
+
return this.upsertSession(userId, sid, {
|
|
256
|
+
agentId: agentId || 'main',
|
|
257
|
+
gatewaySessionKey: gatewaySessionKey || existing.gatewaySessionKey,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return existing;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return this.upsertSession(userId, sid, {
|
|
264
|
+
agentId: agentId || 'main',
|
|
265
|
+
gatewaySessionKey: gatewaySessionKey || '',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
updateSessionMeta(sessionId: string, lastMessage: string, title?: string): void {
|
|
270
|
+
if (!this.persistMessages) return;
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
if (title) {
|
|
273
|
+
this.db.prepare(
|
|
274
|
+
'UPDATE sessions SET last_message = ?, last_message_time = ?, title = ? WHERE id = ?'
|
|
275
|
+
).run(lastMessage, now, title, sessionId);
|
|
276
|
+
} else {
|
|
277
|
+
this.db.prepare(
|
|
278
|
+
'UPDATE sessions SET last_message = ?, last_message_time = ? WHERE id = ?'
|
|
279
|
+
).run(lastMessage, now, sessionId);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
pruneSessions(userId: string, keepSessionIds: string[], agentId?: string): number {
|
|
284
|
+
const ids = Array.from(new Set((keepSessionIds || []).map(id => String(id || '').trim()).filter(Boolean)));
|
|
285
|
+
const params: any[] = [userId];
|
|
286
|
+
let sql = 'DELETE FROM sessions WHERE user_id = ?';
|
|
287
|
+
|
|
288
|
+
if (agentId) {
|
|
289
|
+
sql += ' AND agent_id = ?';
|
|
290
|
+
params.push(agentId);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (ids.length > 0) {
|
|
294
|
+
sql += ` AND id NOT IN (${ids.map(() => '?').join(', ')})`;
|
|
295
|
+
params.push(...ids);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const result = this.db.prepare(sql).run(...params);
|
|
299
|
+
return result.changes;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ===== 消息管理 =====
|
|
303
|
+
|
|
304
|
+
addMessage(
|
|
305
|
+
sessionId: string,
|
|
306
|
+
role: MessageRole,
|
|
307
|
+
type: MessageType,
|
|
308
|
+
content: string,
|
|
309
|
+
mediaUrl?: string,
|
|
310
|
+
duration?: number
|
|
311
|
+
): Message {
|
|
312
|
+
const id = uuidv4();
|
|
313
|
+
const timestamp = Date.now();
|
|
314
|
+
const message: Message = { id, sessionId, role, type, content, mediaUrl, duration, timestamp };
|
|
315
|
+
|
|
316
|
+
if (this.persistMessages) {
|
|
317
|
+
this.db.prepare(`
|
|
318
|
+
INSERT INTO messages (id, session_id, role, type, content, media_url, duration, timestamp)
|
|
319
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
320
|
+
`).run(id, sessionId, role, type, content, mediaUrl || null, duration || null, timestamp);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 更新会话摘要
|
|
324
|
+
const summary = type === 'text' ? content.substring(0, 50) :
|
|
325
|
+
type === 'voice' ? '[语音消息]' :
|
|
326
|
+
type === 'image' ? '[图片消息]' : content.substring(0, 50);
|
|
327
|
+
if (this.persistMessages) {
|
|
328
|
+
this.updateSessionMeta(sessionId, summary);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return message;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
getMessages(sessionId: string, limit = 50, before?: number): Message[] {
|
|
335
|
+
if (!this.persistMessages) return [];
|
|
336
|
+
|
|
337
|
+
let query: string;
|
|
338
|
+
let params: any[];
|
|
339
|
+
|
|
340
|
+
if (before) {
|
|
341
|
+
query = 'SELECT * FROM messages WHERE session_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT ?';
|
|
342
|
+
params = [sessionId, before, limit];
|
|
343
|
+
} else {
|
|
344
|
+
query = 'SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?';
|
|
345
|
+
params = [sessionId, limit];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const rows = this.db.prepare(query).all(...params) as any[];
|
|
349
|
+
|
|
350
|
+
return rows.reverse().map(row => ({
|
|
351
|
+
id: row.id,
|
|
352
|
+
sessionId: row.session_id,
|
|
353
|
+
role: row.role as MessageRole,
|
|
354
|
+
type: row.type as MessageType,
|
|
355
|
+
content: row.content,
|
|
356
|
+
mediaUrl: row.media_url,
|
|
357
|
+
duration: row.duration,
|
|
358
|
+
timestamp: row.timestamp,
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
clearMessages(sessionId: string): number {
|
|
363
|
+
const result = this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
|
|
364
|
+
return result.changes;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
close(): void {
|
|
368
|
+
this.db.close();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
isPersistMessagesEnabled(): boolean {
|
|
372
|
+
return this.persistMessages;
|
|
373
|
+
}
|
|
374
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// ===== 消息类型 =====
|
|
2
|
+
export type MessageType = 'text' | 'voice' | 'image' | 'system' | 'command';
|
|
3
|
+
export type MessageRole = 'user' | 'assistant' | 'system';
|
|
4
|
+
|
|
5
|
+
export interface Message {
|
|
6
|
+
id: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
role: MessageRole;
|
|
9
|
+
type: MessageType;
|
|
10
|
+
content: string;
|
|
11
|
+
mediaUrl?: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ===== 会话 =====
|
|
17
|
+
export interface Session {
|
|
18
|
+
id: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
title: string;
|
|
21
|
+
lastMessage: string;
|
|
22
|
+
lastMessageTime: number;
|
|
23
|
+
agentId?: string;
|
|
24
|
+
gatewaySessionKey?: string;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ===== 会话用户(轻量级,仅用于 SessionManager) =====
|
|
29
|
+
export interface SessionUser {
|
|
30
|
+
openid: string;
|
|
31
|
+
nickname?: string;
|
|
32
|
+
avatarUrl?: string;
|
|
33
|
+
createdAt: number;
|
|
34
|
+
lastLoginAt: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ===== 命令菜单 =====
|
|
38
|
+
export interface BotCommand {
|
|
39
|
+
command: string;
|
|
40
|
+
description: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CommandExecutionContext {
|
|
45
|
+
/** 当前消息对应的 Gateway 会话键(用于 /new /reset /compact /stop) */
|
|
46
|
+
gatewaySessionKey?: string;
|
|
47
|
+
/** 当前路由到的 Agent */
|
|
48
|
+
agentId?: string;
|
|
49
|
+
/** 当前运行中的 runId(可选) */
|
|
50
|
+
runId?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ===== OpenClaw Gateway =====
|
|
54
|
+
export interface OpenClawRequest {
|
|
55
|
+
model: string;
|
|
56
|
+
input: string;
|
|
57
|
+
user?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface OpenClawResponse {
|
|
61
|
+
id: string;
|
|
62
|
+
output: Array<{
|
|
63
|
+
type: string;
|
|
64
|
+
content: Array<{
|
|
65
|
+
type: string;
|
|
66
|
+
text: string;
|
|
67
|
+
}>;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ===== 提供商 =====
|
|
72
|
+
export interface Provider {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
apiKey: string;
|
|
77
|
+
api?: string;
|
|
78
|
+
authHeader?: string;
|
|
79
|
+
headers?: Record<string, string>;
|
|
80
|
+
models: any[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ===== 配置 (精简版) =====
|
|
84
|
+
export interface PluginConfig {
|
|
85
|
+
port: number;
|
|
86
|
+
openclawGatewayUrl: string;
|
|
87
|
+
openclawModel: string;
|
|
88
|
+
openclawAuthToken: string;
|
|
89
|
+
uploadDir: string;
|
|
90
|
+
maxFileSize: number;
|
|
91
|
+
maxVoiceDuration: number;
|
|
92
|
+
/** 与 wechat-backend 通信的共享密钥 */
|
|
93
|
+
internalSecret: string;
|
|
94
|
+
/** 是否允许在 Gateway 不可用时退回本地会话/历史缓存 */
|
|
95
|
+
allowLocalFallback?: boolean;
|
|
96
|
+
}
|
package/start.sh
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020"
|
|
7
|
+
],
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"declarationMap": true,
|
|
17
|
+
"sourceMap": true
|
|
18
|
+
},
|
|
19
|
+
"include": [
|
|
20
|
+
"src/**/*"
|
|
21
|
+
],
|
|
22
|
+
"exclude": [
|
|
23
|
+
"node_modules",
|
|
24
|
+
"dist"
|
|
25
|
+
]
|
|
26
|
+
}
|