@ai-setting/roy-agent-core 1.5.88 → 1.5.90
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/env/agent/index.js +8 -8
- package/dist/env/event-source/index.js +12 -4
- package/dist/env/index.js +20 -15
- package/dist/env/llm/index.js +4 -2
- package/dist/env/prompt/index.js +2 -1
- package/dist/env/session/index.js +3 -4
- package/dist/env/session/storage/index.js +2 -2
- package/dist/env/task/delegate/index.js +3 -2
- package/dist/env/task/index.js +4 -3
- package/dist/env/task/plugins/index.js +7 -4
- package/dist/env/tool/built-in/index.js +2 -1
- package/dist/env/tool/index.js +11 -8
- package/dist/env/workflow/engine/index.js +5 -3
- package/dist/env/workflow/index.js +6 -4
- package/dist/env/workflow/nodes/index.js +1 -1
- package/dist/env/workflow/tools/index.js +7 -1
- package/dist/env/workflow/utils/index.js +5 -0
- package/dist/index.js +144 -32
- package/dist/shared/@ai-setting/{roy-agent-core-r0m0at3x.js → roy-agent-core-0r4ndyn9.js} +119 -7
- package/dist/shared/@ai-setting/{roy-agent-core-jymz9fzp.js → roy-agent-core-2c8eraxq.js} +126 -41
- package/dist/shared/@ai-setting/{roy-agent-core-0y64qaac.js → roy-agent-core-32m0nb9j.js} +119 -30
- package/dist/shared/@ai-setting/roy-agent-core-4yq23m5g.js +421 -0
- package/dist/shared/@ai-setting/{roy-agent-core-h2d1s8yd.js → roy-agent-core-5ykms33a.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-mmkyydw7.js → roy-agent-core-83d035pp.js} +91 -579
- package/dist/shared/@ai-setting/roy-agent-core-8wd3qwx5.js +35 -0
- package/dist/shared/@ai-setting/{roy-agent-core-yx0vw1aw.js → roy-agent-core-8zjntsbb.js} +102 -32
- package/dist/shared/@ai-setting/{roy-agent-core-29fh9mxg.js → roy-agent-core-bwjpte58.js} +1 -2
- package/dist/shared/@ai-setting/{roy-agent-core-vneyghpg.js → roy-agent-core-ddq5hcp5.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qhhxx2x2.js → roy-agent-core-ds5f75pg.js} +2 -1
- package/dist/shared/@ai-setting/{roy-agent-core-4cdtdxqx.js → roy-agent-core-ewr1nw7t.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-hvdfgvfz.js +114 -0
- package/dist/shared/@ai-setting/{roy-agent-core-3arrpf7n.js → roy-agent-core-hxsbegfc.js} +229 -9
- package/dist/shared/@ai-setting/{roy-agent-core-9y09xfav.js → roy-agent-core-j0107ww1.js} +6 -418
- package/dist/shared/@ai-setting/{roy-agent-core-6mk0m4t3.js → roy-agent-core-m683wd1n.js} +283 -15
- package/dist/shared/@ai-setting/{roy-agent-core-bgw4dq11.js → roy-agent-core-mw4ty0ba.js} +44 -5
- package/dist/shared/@ai-setting/{roy-agent-core-7z4xtrmw.js → roy-agent-core-nqzt9ne4.js} +53 -0
- package/dist/shared/@ai-setting/roy-agent-core-pt7as39r.js +0 -0
- package/dist/shared/@ai-setting/{roy-agent-core-6n7xwv4v.js → roy-agent-core-rkz8r2sx.js} +5 -3
- package/dist/shared/@ai-setting/{roy-agent-core-c1j7ev4e.js → roy-agent-core-txwf64pd.js} +70 -0
- package/dist/shared/@ai-setting/{roy-agent-core-7fdzfsm6.js → roy-agent-core-w0kb72ve.js} +42 -213
- package/dist/shared/@ai-setting/roy-agent-core-w6bmrgap.js +581 -0
- package/dist/shared/@ai-setting/{roy-agent-core-qhjb153z.js → roy-agent-core-xnxyzaw4.js} +27 -4
- package/dist/shared/@ai-setting/roy-agent-core-yanpq5gb.js +116 -0
- package/dist/shared/@ai-setting/{roy-agent-core-hc20420t.js → roy-agent-core-z240ts1r.js} +27 -3
- package/dist/shared/@ai-setting/{roy-agent-core-q7sqeax6.js → roy-agent-core-zky9jse4.js} +163 -3
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-x3gtyqax.js +0 -378
- /package/dist/shared/@ai-setting/{roy-agent-core-4f3976cd.js → roy-agent-core-k5hxvaf0.js} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_decorator,
|
|
3
|
+
wrapFunction
|
|
4
|
+
} from "./roy-agent-core-k05v31rc.js";
|
|
5
|
+
import {
|
|
6
|
+
createLogger,
|
|
7
|
+
init_logger
|
|
8
|
+
} from "./roy-agent-core-shme7set.js";
|
|
9
|
+
|
|
10
|
+
// src/env/task/plugins/auto-task-plugin.ts
|
|
11
|
+
init_logger();
|
|
12
|
+
init_decorator();
|
|
13
|
+
var logger = createLogger("AutoTaskPlugin");
|
|
14
|
+
var AUTO_TASK_METADATA_BEGIN = `
|
|
15
|
+
[[AUTO_TASK_METADATA_BEGIN]]
|
|
16
|
+
`;
|
|
17
|
+
var AUTO_TASK_METADATA_END = `[[AUTO_TASK_METADATA_END]]
|
|
18
|
+
`;
|
|
19
|
+
function wrapAutoTaskMetadata(content) {
|
|
20
|
+
return `${AUTO_TASK_METADATA_BEGIN}${content}${AUTO_TASK_METADATA_END}`;
|
|
21
|
+
}
|
|
22
|
+
function createAutoTaskPlugin(config) {
|
|
23
|
+
const threshold = config?.threshold ?? 20;
|
|
24
|
+
const agentName = config?.agentName ?? "default";
|
|
25
|
+
const agentComponent = config?.agentComponent ?? null;
|
|
26
|
+
const subagentType = config?.subagentType ?? "task-agent";
|
|
27
|
+
let lastProcessedTraceId = "";
|
|
28
|
+
async function executeImpl(ctx) {
|
|
29
|
+
try {
|
|
30
|
+
logger.debug(`[AutoTaskPlugin] agentName=${ctx.agent.name} (expected=${agentName})`);
|
|
31
|
+
if (ctx.agent.name !== agentName)
|
|
32
|
+
return { continue: true };
|
|
33
|
+
logger.debug(`[AutoTaskPlugin] iteration=${ctx.iteration} (threshold=${threshold})`);
|
|
34
|
+
if (ctx.iteration !== threshold)
|
|
35
|
+
return { continue: true };
|
|
36
|
+
const traceId = ctx.context?.traceId ?? ctx.context?.sessionId ?? "unknown";
|
|
37
|
+
const sid = ctx.context?.sessionId ?? "unknown";
|
|
38
|
+
if (traceId === lastProcessedTraceId) {
|
|
39
|
+
logger.debug(`[AutoTaskPlugin] skip - traceId=${traceId.slice(0, 20)}... already processed`);
|
|
40
|
+
return { continue: true };
|
|
41
|
+
}
|
|
42
|
+
lastProcessedTraceId = traceId;
|
|
43
|
+
logger.debug(`[AutoTaskPlugin] new query detected (traceId=${traceId.slice(0, 20)}...)`);
|
|
44
|
+
if (!agentComponent || typeof agentComponent.run !== "function") {
|
|
45
|
+
logger.warn(`[AutoTaskPlugin] agentComponent not provided or invalid (session=${sid.slice(0, 12)}...)`);
|
|
46
|
+
return { continue: true };
|
|
47
|
+
}
|
|
48
|
+
let userQuery = "";
|
|
49
|
+
for (let i = ctx.messages.length - 1;i >= 0; i--) {
|
|
50
|
+
const m = ctx.messages[i];
|
|
51
|
+
if (m.role === "user") {
|
|
52
|
+
userQuery = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!userQuery) {
|
|
57
|
+
userQuery = "分析项目结构并执行相关任务";
|
|
58
|
+
}
|
|
59
|
+
logger.info(`[AutoTaskPlugin] Calling agentComponent.run("${subagentType}") at iteration ${ctx.iteration} (session=${sid.slice(0, 12)}..., persistSession=false)`);
|
|
60
|
+
const shortDesc = typeof userQuery === "string" ? userQuery.slice(0, 50) : "分析需求";
|
|
61
|
+
const placeholderContent = `⏳ **${subagentType} 正在执行**: ${shortDesc}(等待结果...)`;
|
|
62
|
+
const placeholderBlock = wrapAutoTaskMetadata(placeholderContent);
|
|
63
|
+
if (ctx.toolResult?.result?.output !== undefined) {
|
|
64
|
+
const originalOutput = ctx.toolResult.result.output;
|
|
65
|
+
ctx.toolResult.result.output = placeholderBlock + `
|
|
66
|
+
|
|
67
|
+
` + originalOutput;
|
|
68
|
+
logger.info(`[AutoTaskPlugin] placeholder prepended (session=${sid.slice(0, 12)}...)`);
|
|
69
|
+
}
|
|
70
|
+
let taskAgentFinalText = "";
|
|
71
|
+
try {
|
|
72
|
+
const taskAgentResult = await agentComponent.run(subagentType, userQuery, {
|
|
73
|
+
sessionId: sid,
|
|
74
|
+
traceId,
|
|
75
|
+
persistSession: false
|
|
76
|
+
});
|
|
77
|
+
taskAgentFinalText = taskAgentResult?.finalText || "";
|
|
78
|
+
logger.info(`[AutoTaskPlugin] task-agent completed (session=${sid.slice(0, 12)}..., finalTextLength=${taskAgentFinalText.length})`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.error(`[AutoTaskPlugin] task-agent failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
81
|
+
taskAgentFinalText = `❌ **${subagentType} 执行失败**: ${err instanceof Error ? err.message : String(err)}`;
|
|
82
|
+
}
|
|
83
|
+
if (taskAgentFinalText && ctx.toolResult?.result?.output !== undefined) {
|
|
84
|
+
const finalBlock = wrapAutoTaskMetadata(taskAgentFinalText);
|
|
85
|
+
const current = ctx.toolResult.result.output;
|
|
86
|
+
const updated = placeholderBlock && current.includes(placeholderBlock) ? current.replace(placeholderBlock, finalBlock) : finalBlock + `
|
|
87
|
+
|
|
88
|
+
` + current;
|
|
89
|
+
ctx.toolResult.result.output = updated;
|
|
90
|
+
logger.info(`[AutoTaskPlugin] task-agent finalText injected (session=${sid.slice(0, 12)}...)`);
|
|
91
|
+
} else if (!ctx.toolResult?.result?.output) {
|
|
92
|
+
logger.warn(`[AutoTaskPlugin] toolResult unavailable for injection`);
|
|
93
|
+
}
|
|
94
|
+
return { continue: true };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger.error(`[AutoTaskPlugin] hook failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
97
|
+
return { continue: true };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const execute = wrapFunction(executeImpl, "plugin.auto-task.launch-agent", {
|
|
101
|
+
recordParams: false,
|
|
102
|
+
recordResult: true,
|
|
103
|
+
log: true
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
name: "AutoTaskPlugin",
|
|
107
|
+
version: "4.6.0",
|
|
108
|
+
description: "ReAct iteration 达到阈值时 await task-agent 执行并提取 delegate_task 结果注入 tool result",
|
|
109
|
+
hooks: [
|
|
110
|
+
{ point: "agent:after.tool", priority: 50 }
|
|
111
|
+
],
|
|
112
|
+
execute
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { AUTO_TASK_METADATA_BEGIN, AUTO_TASK_METADATA_END, createAutoTaskPlugin };
|
|
@@ -8,7 +8,22 @@ import {
|
|
|
8
8
|
} from "./roy-agent-core-fs0mn2jk.js";
|
|
9
9
|
|
|
10
10
|
// src/env/workflow/nodes/tool-node.ts
|
|
11
|
-
|
|
11
|
+
function isPositiveNumber(v) {
|
|
12
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
13
|
+
}
|
|
14
|
+
function getToolTimeoutMs(input, config) {
|
|
15
|
+
if (input && typeof input === "object") {
|
|
16
|
+
const inputTimeout = input.timeout;
|
|
17
|
+
if (isPositiveNumber(inputTimeout)) {
|
|
18
|
+
return inputTimeout;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (config && isPositiveNumber(config.timeout)) {
|
|
22
|
+
return config.timeout;
|
|
23
|
+
}
|
|
24
|
+
return DEFAULT_TOOL_TIMEOUT_MS;
|
|
25
|
+
}
|
|
26
|
+
var DEFAULT_TOOL_TIMEOUT_MS = 60000, ToolNode;
|
|
12
27
|
var init_tool_node = __esm(() => {
|
|
13
28
|
init_decorator();
|
|
14
29
|
ToolNode = class ToolNode {
|
|
@@ -88,11 +103,20 @@ var init_tool_node = __esm(() => {
|
|
|
88
103
|
nodeId: context.nodeId
|
|
89
104
|
}
|
|
90
105
|
};
|
|
106
|
+
const toolTimeoutMs = getToolTimeoutMs(input, this.definition.config);
|
|
91
107
|
const executePromise = tool.execute(input, toolContext);
|
|
108
|
+
let timeoutHandle;
|
|
92
109
|
const timeoutPromise = new Promise((_, reject) => {
|
|
93
|
-
setTimeout(() => reject(new Error(`Tool '${toolName}' execution timeout after
|
|
110
|
+
timeoutHandle = setTimeout(() => reject(new Error(`Tool '${toolName}' execution timeout after ${Math.round(toolTimeoutMs / 1000)} seconds`)), toolTimeoutMs);
|
|
94
111
|
});
|
|
95
|
-
|
|
112
|
+
try {
|
|
113
|
+
output = await Promise.race([executePromise, timeoutPromise]);
|
|
114
|
+
} finally {
|
|
115
|
+
if (timeoutHandle !== undefined) {
|
|
116
|
+
clearTimeout(timeoutHandle);
|
|
117
|
+
timeoutHandle = undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
96
120
|
} catch (toolError) {
|
|
97
121
|
if (toolError instanceof TypeError && toolError.message.includes("context") && toolError.message.includes("not a function")) {
|
|
98
122
|
output = await tool.execute(input);
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionMessageConverter
|
|
3
|
-
} from "./roy-agent-core-
|
|
3
|
+
} from "./roy-agent-core-txwf64pd.js";
|
|
4
|
+
import {
|
|
5
|
+
createLogger,
|
|
6
|
+
init_logger
|
|
7
|
+
} from "./roy-agent-core-shme7set.js";
|
|
4
8
|
import {
|
|
5
9
|
__require
|
|
6
10
|
} from "./roy-agent-core-fs0mn2jk.js";
|
|
7
11
|
|
|
12
|
+
// src/env/session/session-store.ts
|
|
13
|
+
class BaseSessionStore {
|
|
14
|
+
async addMessages(sessionId, messages) {
|
|
15
|
+
if (messages.length === 0)
|
|
16
|
+
return [];
|
|
17
|
+
const ids = [];
|
|
18
|
+
for (const msg of messages) {
|
|
19
|
+
ids.push(await this.addMessage(sessionId, msg));
|
|
20
|
+
}
|
|
21
|
+
return ids;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
// src/env/session/storage/memory.ts
|
|
9
26
|
import { randomUUID } from "crypto";
|
|
10
27
|
function generateSessionId() {
|
|
@@ -14,7 +31,7 @@ function generateMessageId() {
|
|
|
14
31
|
return `msg_${randomUUID()}`;
|
|
15
32
|
}
|
|
16
33
|
|
|
17
|
-
class MemorySessionStore {
|
|
34
|
+
class MemorySessionStore extends BaseSessionStore {
|
|
18
35
|
sessions = new Map;
|
|
19
36
|
messages = new Map;
|
|
20
37
|
checkpoints = new Map;
|
|
@@ -138,6 +155,40 @@ class MemorySessionStore {
|
|
|
138
155
|
session.updatedAt = Date.now();
|
|
139
156
|
return msg.id;
|
|
140
157
|
}
|
|
158
|
+
async addMessages(sessionId, messages) {
|
|
159
|
+
if (messages.length === 0) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const session = this.sessions.get(sessionId);
|
|
163
|
+
if (!session) {
|
|
164
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
165
|
+
}
|
|
166
|
+
const sessionMessages = this.messages.get(sessionId) ?? [];
|
|
167
|
+
const ids = [];
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
for (const message of messages) {
|
|
170
|
+
const rawMessage = {
|
|
171
|
+
sessionID: sessionId,
|
|
172
|
+
role: message.role,
|
|
173
|
+
content: message.content,
|
|
174
|
+
parts: message.parts,
|
|
175
|
+
metadata: message.metadata
|
|
176
|
+
};
|
|
177
|
+
const msg = this.messageConverter.migrateMessage(rawMessage);
|
|
178
|
+
msg.id = generateMessageId();
|
|
179
|
+
msg.timestamp = now;
|
|
180
|
+
sessionMessages.push(msg);
|
|
181
|
+
ids.push(msg.id);
|
|
182
|
+
}
|
|
183
|
+
this.messages.set(sessionId, sessionMessages);
|
|
184
|
+
session.messageCount = sessionMessages.filter((m) => !m.isArchived).length;
|
|
185
|
+
session.metadata = {
|
|
186
|
+
...session.metadata,
|
|
187
|
+
messageCount: session.messageCount
|
|
188
|
+
};
|
|
189
|
+
session.updatedAt = now;
|
|
190
|
+
return ids;
|
|
191
|
+
}
|
|
141
192
|
async updateMessage(sessionId, messageId, updates) {
|
|
142
193
|
const sessionMessages = this.messages.get(sessionId);
|
|
143
194
|
if (!sessionMessages) {
|
|
@@ -371,6 +422,8 @@ class MemorySessionStore {
|
|
|
371
422
|
// src/env/session/storage/sqlite.ts
|
|
372
423
|
import { join } from "path";
|
|
373
424
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
425
|
+
init_logger();
|
|
426
|
+
var logger = createLogger("session:storage:sqlite");
|
|
374
427
|
function getDatabaseClass() {
|
|
375
428
|
if (typeof Bun !== "undefined") {
|
|
376
429
|
const { Database } = __require("bun:sqlite");
|
|
@@ -379,6 +432,17 @@ function getDatabaseClass() {
|
|
|
379
432
|
return __require("better-sqlite3");
|
|
380
433
|
}
|
|
381
434
|
}
|
|
435
|
+
function isSqliteBusyError(err) {
|
|
436
|
+
if (err == null)
|
|
437
|
+
return false;
|
|
438
|
+
const e = err;
|
|
439
|
+
if (typeof e.code === "string" && /^SQLITE_(BUSY|LOCKED)$/i.test(e.code))
|
|
440
|
+
return true;
|
|
441
|
+
if (typeof e.errno === "number" && (e.errno === 5 || e.errno === 6))
|
|
442
|
+
return true;
|
|
443
|
+
const msg = e.message ?? "";
|
|
444
|
+
return /SQLiteError:\s+(database is locked|database table is locked)/i.test(msg);
|
|
445
|
+
}
|
|
382
446
|
function createCompatibleDatabase(path) {
|
|
383
447
|
const Database = getDatabaseClass();
|
|
384
448
|
const db = new Database(path);
|
|
@@ -407,7 +471,7 @@ function getDefaultSessionDbPath() {
|
|
|
407
471
|
return join(getDefaultDataDir(), "roy-agent", "sessions.db");
|
|
408
472
|
}
|
|
409
473
|
|
|
410
|
-
class SQLiteSessionStore {
|
|
474
|
+
class SQLiteSessionStore extends BaseSessionStore {
|
|
411
475
|
db = null;
|
|
412
476
|
dbPath;
|
|
413
477
|
initialized = false;
|
|
@@ -416,6 +480,7 @@ class SQLiteSessionStore {
|
|
|
416
480
|
checkpointsCache = new Map;
|
|
417
481
|
messageConverter = new SessionMessageConverter;
|
|
418
482
|
constructor(dbPath) {
|
|
483
|
+
super();
|
|
419
484
|
this.dbPath = dbPath || getDefaultSessionDbPath();
|
|
420
485
|
}
|
|
421
486
|
async initialize() {
|
|
@@ -430,6 +495,10 @@ class SQLiteSessionStore {
|
|
|
430
495
|
}
|
|
431
496
|
}
|
|
432
497
|
this.db = createCompatibleDatabase(this.dbPath);
|
|
498
|
+
this.applyPragma("PRAGMA journal_mode = WAL");
|
|
499
|
+
this.applyPragma("PRAGMA busy_timeout = 5000");
|
|
500
|
+
this.applyPragma("PRAGMA synchronous = NORMAL");
|
|
501
|
+
this.applyPragma("PRAGMA wal_autocheckpoint = 1000");
|
|
433
502
|
this.db.run(`
|
|
434
503
|
CREATE TABLE IF NOT EXISTS session (
|
|
435
504
|
id TEXT PRIMARY KEY,
|
|
@@ -611,6 +680,90 @@ class SQLiteSessionStore {
|
|
|
611
680
|
this.sessionsCache.set(sessionId, session);
|
|
612
681
|
return msg.id;
|
|
613
682
|
}
|
|
683
|
+
async addMessages(sessionId, messages) {
|
|
684
|
+
if (messages.length === 0) {
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
const BATCH_BACKOFFS_MS = [50, 100, 200];
|
|
688
|
+
let lastError = undefined;
|
|
689
|
+
for (let attempt = 0;attempt <= BATCH_BACKOFFS_MS.length; attempt++) {
|
|
690
|
+
try {
|
|
691
|
+
return await this.addMessagesOnce(sessionId, messages);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
if (!isSqliteBusyError(err) || attempt === BATCH_BACKOFFS_MS.length) {
|
|
694
|
+
this.messagesCache.delete(sessionId);
|
|
695
|
+
this.sessionsCache.delete(sessionId);
|
|
696
|
+
lastError = err;
|
|
697
|
+
if (!isSqliteBusyError(err))
|
|
698
|
+
throw err;
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
await new Promise((resolve) => setTimeout(resolve, BATCH_BACKOFFS_MS[attempt]));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
throw lastError;
|
|
705
|
+
}
|
|
706
|
+
async addMessagesOnce(sessionId, messages) {
|
|
707
|
+
await this.initialize();
|
|
708
|
+
const session = await this.get(sessionId);
|
|
709
|
+
if (!session) {
|
|
710
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
711
|
+
}
|
|
712
|
+
const now = Date.now();
|
|
713
|
+
const ids = [];
|
|
714
|
+
const insertStmt = this.db.prepare(`
|
|
715
|
+
INSERT INTO message (id, session_id, role, content, timestamp, parts, metadata)
|
|
716
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
717
|
+
`);
|
|
718
|
+
const sessionUpdateStmt = this.db.prepare(`
|
|
719
|
+
UPDATE session SET message_count = ?, metadata = ?, updated_at = ? WHERE id = ?
|
|
720
|
+
`);
|
|
721
|
+
this.db.run("BEGIN");
|
|
722
|
+
try {
|
|
723
|
+
for (const message of messages) {
|
|
724
|
+
const id = generateMessageId2();
|
|
725
|
+
ids.push(id);
|
|
726
|
+
insertStmt.run(id, sessionId, message.role, message.content, now, message.parts ? JSON.stringify(message.parts) : null, message.metadata ? JSON.stringify(message.metadata) : null);
|
|
727
|
+
}
|
|
728
|
+
const countResult = this.db.prepare("SELECT COUNT(*) as count FROM message WHERE session_id = ? AND is_archived = 0").get(sessionId);
|
|
729
|
+
const newCount = countResult?.count ?? 0;
|
|
730
|
+
session.messageCount = newCount;
|
|
731
|
+
session.updatedAt = now;
|
|
732
|
+
session.metadata = {
|
|
733
|
+
...session.metadata ?? {},
|
|
734
|
+
messageCount: newCount
|
|
735
|
+
};
|
|
736
|
+
sessionUpdateStmt.run(newCount, JSON.stringify(session.metadata), now, sessionId);
|
|
737
|
+
this.db.run("COMMIT");
|
|
738
|
+
} catch (err) {
|
|
739
|
+
try {
|
|
740
|
+
this.db.run("ROLLBACK");
|
|
741
|
+
} catch {}
|
|
742
|
+
throw err;
|
|
743
|
+
}
|
|
744
|
+
try {
|
|
745
|
+
const cached = this.messagesCache.get(sessionId) ?? [];
|
|
746
|
+
for (let i = 0;i < messages.length; i++) {
|
|
747
|
+
const message = messages[i];
|
|
748
|
+
cached.push({
|
|
749
|
+
id: ids[i],
|
|
750
|
+
sessionID: sessionId,
|
|
751
|
+
role: message.role,
|
|
752
|
+
content: message.content,
|
|
753
|
+
timestamp: now,
|
|
754
|
+
parts: message.parts,
|
|
755
|
+
metadata: message.metadata
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
this.messagesCache.set(sessionId, cached);
|
|
759
|
+
this.sessionsCache.set(sessionId, session);
|
|
760
|
+
} catch (cacheErr) {
|
|
761
|
+
logger.warn(`[SQLiteSessionStore] cache update failed after commit; invalidating: ${cacheErr}`);
|
|
762
|
+
this.messagesCache.delete(sessionId);
|
|
763
|
+
this.sessionsCache.delete(sessionId);
|
|
764
|
+
}
|
|
765
|
+
return ids;
|
|
766
|
+
}
|
|
614
767
|
async updateMessage(sessionId, messageId, updates) {
|
|
615
768
|
await this.initialize();
|
|
616
769
|
const row = this.db.prepare("SELECT id FROM message WHERE id = ? AND session_id = ?").get(messageId, sessionId);
|
|
@@ -679,6 +832,13 @@ class SQLiteSessionStore {
|
|
|
679
832
|
const rows = this.db.prepare(sql).all(...params);
|
|
680
833
|
return rows.map((row) => this.rowToMessage(row));
|
|
681
834
|
}
|
|
835
|
+
applyPragma(pragmaSql) {
|
|
836
|
+
this.db.run(pragmaSql);
|
|
837
|
+
const readback = this.db.prepare(pragmaSql.split("=")[0].trim()).get();
|
|
838
|
+
const key = Object.keys(readback ?? {})[0];
|
|
839
|
+
const actual = key ? readback[key] : undefined;
|
|
840
|
+
logger.info(`[SQLiteSessionStore] Applied ${pragmaSql} → ${actual}`);
|
|
841
|
+
}
|
|
682
842
|
async getMessageCount(sessionId, includeArchived = false) {
|
|
683
843
|
await this.initialize();
|
|
684
844
|
let sql = "SELECT COUNT(*) as count FROM message WHERE session_id = ?";
|