@myassis/gateway 1.0.85 → 1.0.87
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/config/capabilities.js +46 -0
- package/dist/main.js +64 -28
- package/dist/routes/agent.js +27 -0
- package/dist/routes/auth.js +78 -0
- package/dist/routes/relay.js +173 -0
- package/dist/services/ServiceManager.js +79 -9
- package/dist/services/memory/MemoryManager.js +18 -2
- package/dist/services/relay/LoopbackForwarder.js +292 -0
- package/dist/services/relay/RelayClient.js +541 -0
- package/dist/services/relay/protocol.js +526 -0
- package/dist/services/relay/relayConfig.js +64 -0
- package/dist/services/session/Session.js +38 -15
- package/dist/services/session/SessionStore.js +64 -15
- package/migrations/016_add_message_seq.sql +27 -0
- package/package.json +1 -1
|
@@ -141,11 +141,36 @@ class SessionStore {
|
|
|
141
141
|
return rows.map(row => this.rowToSessionData(row));
|
|
142
142
|
}
|
|
143
143
|
// ========== Message Operations ==========
|
|
144
|
+
/**
|
|
145
|
+
* 取下一个消息行版本号。
|
|
146
|
+
*
|
|
147
|
+
* 刷新(insert / update)都要取新号,因此 seq 是「行版本」而不是「插入序号」:
|
|
148
|
+
* 助手消息在一轮里会被反复更新,只有每次都抬号,断线重连的终端才能
|
|
149
|
+
* 靠「seq > 本地游标」把被改过的消息也拉回来。
|
|
150
|
+
*
|
|
151
|
+
* 刷号器单独持久化(message_seq 表),不用 MAX(seq)+1:
|
|
152
|
+
* 删掉最后一条消息后 MAX 会回退,号被重用,已经看过该 seq 的终端就会
|
|
153
|
+
* 永久错过新消息。
|
|
154
|
+
*
|
|
155
|
+
* 注意排序仍用 created_at:seq 只作为变更游标,抬号不得改变消息在界面上的位置。
|
|
156
|
+
*/
|
|
157
|
+
nextSeq() {
|
|
158
|
+
// 用 UPDATE ... RETURNING 一步完成自增与读取:
|
|
159
|
+
// 每个 Session 都新建一个 SessionStore(各自一个到同一文件的连接),
|
|
160
|
+
// 先 UPDATE 再 SELECT 在连接间不原子,会发出重复的 seq。
|
|
161
|
+
const row = this.db.prepare('UPDATE message_seq SET value = value + 1 WHERE id = 1 RETURNING value').get();
|
|
162
|
+
return row?.value ?? 1;
|
|
163
|
+
}
|
|
164
|
+
/** 当前最大消息行版本号(终端用作同步游标基准) */
|
|
165
|
+
getMaxMessageSeq() {
|
|
166
|
+
const row = this.db.prepare('SELECT value FROM message_seq WHERE id = 1').get();
|
|
167
|
+
return row?.value ?? 0;
|
|
168
|
+
}
|
|
144
169
|
insertMessage(sessionId, msg) {
|
|
145
170
|
this.db.prepare(`
|
|
146
|
-
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls, created_at, updated_at, model_name, feedback)
|
|
147
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
148
|
-
`).run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null);
|
|
171
|
+
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls, created_at, updated_at, model_name, feedback, seq)
|
|
172
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
173
|
+
`).run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null, this.nextSeq());
|
|
149
174
|
}
|
|
150
175
|
/**
|
|
151
176
|
* 写入或更新消息(按 id 幂等)。
|
|
@@ -159,32 +184,33 @@ class SessionStore {
|
|
|
159
184
|
*/
|
|
160
185
|
upsertMessage(sessionId, msg) {
|
|
161
186
|
this.db.prepare(`
|
|
162
|
-
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls, created_at, updated_at, model_name, feedback)
|
|
163
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
187
|
+
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls, created_at, updated_at, model_name, feedback, seq)
|
|
188
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
164
189
|
ON CONFLICT(id) DO UPDATE SET
|
|
165
190
|
content = excluded.content,
|
|
166
191
|
attachments = excluded.attachments,
|
|
167
192
|
tool_calls = excluded.tool_calls,
|
|
168
193
|
updated_at = excluded.updated_at,
|
|
169
194
|
model_name = excluded.model_name,
|
|
170
|
-
feedback = excluded.feedback
|
|
171
|
-
|
|
195
|
+
feedback = excluded.feedback,
|
|
196
|
+
seq = excluded.seq
|
|
197
|
+
`).run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null, this.nextSeq());
|
|
172
198
|
}
|
|
173
199
|
insertMessages(sessionId, messages) {
|
|
174
200
|
const stmt = this.db.prepare(`
|
|
175
|
-
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls,created_at, updated_at, model_name, feedback)
|
|
176
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
201
|
+
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls,created_at, updated_at, model_name, feedback, seq)
|
|
202
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
177
203
|
`);
|
|
178
204
|
for (const msg of messages) {
|
|
179
|
-
stmt.run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null);
|
|
205
|
+
stmt.run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null, this.nextSeq());
|
|
180
206
|
}
|
|
181
207
|
}
|
|
182
208
|
updateMessage(sessionId, msg) {
|
|
183
209
|
this.db.prepare(`
|
|
184
210
|
UPDATE messages
|
|
185
|
-
SET content = ?, attachments = ?, tool_calls = ?, updated_at = ?, model_name = ?, feedback = ?
|
|
211
|
+
SET content = ?, attachments = ?, tool_calls = ?, updated_at = ?, model_name = ?, feedback = ?, seq = ?
|
|
186
212
|
WHERE id = ? AND session_id = ?
|
|
187
|
-
`).run(msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null, msg.id, sessionId);
|
|
213
|
+
`).run(msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null, this.nextSeq(), msg.id, sessionId);
|
|
188
214
|
}
|
|
189
215
|
deleteMessage(messageId) {
|
|
190
216
|
this.db.prepare('DELETE FROM messages WHERE id = ?').run(messageId);
|
|
@@ -194,7 +220,29 @@ class SessionStore {
|
|
|
194
220
|
}
|
|
195
221
|
findMessagesBySessionId(sessionId) {
|
|
196
222
|
const rows = this.db.prepare('SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC').all(sessionId);
|
|
197
|
-
return rows.map(row => (
|
|
223
|
+
return rows.map(row => this.rowToMessage(row));
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 取会话内 seq 大于 sinceSeq 的消息(新增与被修改的都在内)。
|
|
227
|
+
*
|
|
228
|
+
* 终端断线重连后报上自己的游标,只拉缺失的部分,不必全量重拉。
|
|
229
|
+
*/
|
|
230
|
+
findMessagesChangedSince(sessionId, sinceSeq) {
|
|
231
|
+
const rows = this.db.prepare('SELECT * FROM messages WHERE session_id = ? AND seq > ? ORDER BY seq ASC').all(sessionId, sinceSeq);
|
|
232
|
+
return rows.map(row => this.rowToMessage(row));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 取会话内全部消息 id(按展示顺序)。
|
|
236
|
+
*
|
|
237
|
+
* 删除不会留下带 seq 的行,光靠增量查询发现不了「离线期间被删掉的消息」,
|
|
238
|
+
* 因此同步时一并返回 id 列表让终端剪除多余消息。
|
|
239
|
+
*/
|
|
240
|
+
findMessageIdsBySessionId(sessionId) {
|
|
241
|
+
const rows = this.db.prepare('SELECT id FROM messages WHERE session_id = ? ORDER BY created_at ASC').all(sessionId);
|
|
242
|
+
return rows.map(row => row.id);
|
|
243
|
+
}
|
|
244
|
+
rowToMessage(row) {
|
|
245
|
+
return {
|
|
198
246
|
id: row.id,
|
|
199
247
|
sessionId: row.session_id,
|
|
200
248
|
role: row.role,
|
|
@@ -204,8 +252,9 @@ class SessionStore {
|
|
|
204
252
|
createdAt: row.created_at,
|
|
205
253
|
updatedAt: row.updated_at,
|
|
206
254
|
modelName: row.model_name,
|
|
207
|
-
feedback: row.feedback || undefined
|
|
208
|
-
|
|
255
|
+
feedback: row.feedback || undefined,
|
|
256
|
+
seq: row.seq ?? undefined
|
|
257
|
+
};
|
|
209
258
|
}
|
|
210
259
|
rowToSessionData(row) {
|
|
211
260
|
return {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- 为消息表增加全局单调递增的 seq,用于多终端增量同步。
|
|
2
|
+
--
|
|
3
|
+
-- 原先消息只按 created_at 排序,没有单调游标:WebSocket 断线重连
|
|
4
|
+
-- (退避最长 30s)期间丢的事件无法补拉,各终端会永久分歧。
|
|
5
|
+
-- 有了 seq,终端只需报上自己看到的最大 seq 就能拉回缺失部分。
|
|
6
|
+
ALTER TABLE messages ADD COLUMN seq INTEGER;
|
|
7
|
+
|
|
8
|
+
-- 存量数据按 created_at(同时间再按 id)回填,保证与原有展示顺序一致
|
|
9
|
+
UPDATE messages SET seq = (
|
|
10
|
+
SELECT COUNT(*) FROM messages m2
|
|
11
|
+
WHERE m2.created_at < messages.created_at
|
|
12
|
+
OR (m2.created_at = messages.created_at AND m2.id <= messages.id)
|
|
13
|
+
) WHERE seq IS NULL;
|
|
14
|
+
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_messages_seq ON messages(seq);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session_seq ON messages(session_id, seq);
|
|
17
|
+
|
|
18
|
+
-- seq 发号器单独持久化:不能用 MAX(seq)+1 发号,
|
|
19
|
+
-- 删除最后一条消息后号会被重用,已经看到该 seq 的终端就会永久错过新消息。
|
|
20
|
+
CREATE TABLE IF NOT EXISTS message_seq (
|
|
21
|
+
id INTEGER PRIMARY KEY,
|
|
22
|
+
value INTEGER NOT NULL
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
INSERT INTO message_seq (id, value)
|
|
26
|
+
SELECT 1, IFNULL((SELECT MAX(seq) FROM messages), 0)
|
|
27
|
+
WHERE NOT EXISTS (SELECT 1 FROM message_seq WHERE id = 1);
|