@shareai-lab/kode-sdk 2.7.1 → 2.7.2
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/core/agent/breakpoint-manager.js +36 -0
- package/dist/core/agent/message-queue.js +57 -0
- package/dist/core/agent/permission-manager.js +32 -0
- package/dist/core/agent/todo-manager.js +91 -0
- package/dist/core/agent/tool-runner.js +45 -0
- package/dist/core/agent.js +2035 -0
- package/dist/core/config.js +2 -0
- package/dist/core/context-manager.js +241 -0
- package/dist/core/errors.js +49 -0
- package/dist/core/events.js +329 -0
- package/dist/core/file-pool.d.ts +2 -0
- package/dist/core/file-pool.js +125 -0
- package/dist/core/hooks.js +71 -0
- package/dist/core/permission-modes.js +61 -0
- package/dist/core/pool.js +301 -0
- package/dist/core/room.js +57 -0
- package/dist/core/scheduler.js +58 -0
- package/dist/core/skills/index.js +20 -0
- package/dist/core/skills/management-manager.js +557 -0
- package/dist/core/skills/manager.js +243 -0
- package/dist/core/skills/operation-queue.js +113 -0
- package/dist/core/skills/sandbox-file-manager.js +183 -0
- package/dist/core/skills/types.js +9 -0
- package/dist/core/skills/xml-generator.js +70 -0
- package/dist/core/template.js +35 -0
- package/dist/core/time-bridge.js +100 -0
- package/dist/core/todo.js +89 -0
- package/dist/core/types.js +3 -0
- package/dist/index.js +148 -60461
- package/dist/infra/db/postgres/postgres-store.js +1073 -0
- package/dist/infra/db/sqlite/sqlite-store.js +800 -0
- package/dist/infra/e2b/e2b-fs.js +128 -0
- package/dist/infra/e2b/e2b-sandbox.js +156 -0
- package/dist/infra/e2b/e2b-template.js +105 -0
- package/dist/infra/e2b/index.js +9 -0
- package/dist/infra/e2b/types.js +2 -0
- package/dist/infra/provider.js +67 -0
- package/dist/infra/providers/anthropic.js +308 -0
- package/dist/infra/providers/core/errors.js +353 -0
- package/dist/infra/providers/core/fork.js +418 -0
- package/dist/infra/providers/core/index.js +76 -0
- package/dist/infra/providers/core/logger.js +191 -0
- package/dist/infra/providers/core/retry.js +189 -0
- package/dist/infra/providers/core/usage.js +376 -0
- package/dist/infra/providers/gemini.js +493 -0
- package/dist/infra/providers/index.js +83 -0
- package/dist/infra/providers/openai.js +662 -0
- package/dist/infra/providers/types.js +20 -0
- package/dist/infra/providers/utils.js +400 -0
- package/dist/infra/sandbox-factory.js +30 -0
- package/dist/infra/sandbox.js +243 -0
- package/dist/infra/store/factory.js +80 -0
- package/dist/infra/store/index.js +26 -0
- package/dist/infra/store/json-store.js +606 -0
- package/dist/infra/store/types.js +2 -0
- package/dist/infra/store.js +29 -0
- package/dist/tools/bash_kill/index.js +35 -0
- package/dist/tools/bash_kill/prompt.js +14 -0
- package/dist/tools/bash_logs/index.js +40 -0
- package/dist/tools/bash_logs/prompt.js +14 -0
- package/dist/tools/bash_run/index.js +61 -0
- package/dist/tools/bash_run/prompt.js +18 -0
- package/dist/tools/builtin.js +26 -0
- package/dist/tools/define.js +214 -0
- package/dist/tools/fs_edit/index.js +62 -0
- package/dist/tools/fs_edit/prompt.js +15 -0
- package/dist/tools/fs_glob/index.js +40 -0
- package/dist/tools/fs_glob/prompt.js +15 -0
- package/dist/tools/fs_grep/index.js +66 -0
- package/dist/tools/fs_grep/prompt.js +16 -0
- package/dist/tools/fs_multi_edit/index.js +106 -0
- package/dist/tools/fs_multi_edit/prompt.js +16 -0
- package/dist/tools/fs_read/index.js +40 -0
- package/dist/tools/fs_read/prompt.js +16 -0
- package/dist/tools/fs_write/index.js +40 -0
- package/dist/tools/fs_write/prompt.js +15 -0
- package/dist/tools/index.js +61 -0
- package/dist/tools/mcp.js +185 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/scripts.js +205 -0
- package/dist/tools/skills.js +115 -0
- package/dist/tools/task_run/index.js +58 -0
- package/dist/tools/task_run/prompt.js +25 -0
- package/dist/tools/todo_read/index.js +29 -0
- package/dist/tools/todo_read/prompt.js +18 -0
- package/dist/tools/todo_write/index.js +42 -0
- package/dist/tools/todo_write/prompt.js +23 -0
- package/dist/tools/tool.js +211 -0
- package/dist/tools/toolkit.js +98 -0
- package/dist/tools/type-inference.js +207 -0
- package/dist/utils/agent-id.js +28 -0
- package/dist/utils/logger.js +44 -0
- package/dist/utils/session-id.js +64 -0
- package/package.json +7 -38
- package/dist/index.js.map +0 -7
- package/dist/index.mjs +0 -60385
- package/dist/index.mjs.map +0 -7
|
@@ -0,0 +1,1073 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PostgresStore = void 0;
|
|
37
|
+
const pg_1 = require("pg");
|
|
38
|
+
const store_1 = require("../../store");
|
|
39
|
+
/**
|
|
40
|
+
* PostgresStore 实现
|
|
41
|
+
*
|
|
42
|
+
* 混合存储策略:
|
|
43
|
+
* - 数据库:AgentInfo, Messages, ToolCallRecords, Snapshots(支持查询)
|
|
44
|
+
* - 文件系统:Events, Todos, History, MediaCache(高频写入)
|
|
45
|
+
*
|
|
46
|
+
* PostgreSQL 特性:
|
|
47
|
+
* - JSONB 类型 + GIN 索引
|
|
48
|
+
* - 连接池管理
|
|
49
|
+
* - 事务支持
|
|
50
|
+
*/
|
|
51
|
+
class PostgresStore {
|
|
52
|
+
constructor(config, fileStoreBaseDir) {
|
|
53
|
+
// 指标追踪
|
|
54
|
+
this.metrics = {
|
|
55
|
+
saves: 0,
|
|
56
|
+
loads: 0,
|
|
57
|
+
queries: 0,
|
|
58
|
+
deletes: 0,
|
|
59
|
+
latencies: []
|
|
60
|
+
};
|
|
61
|
+
// 合并默认配置
|
|
62
|
+
const poolConfig = {
|
|
63
|
+
...config,
|
|
64
|
+
port: config.port ?? 5432,
|
|
65
|
+
max: config.max ?? 10,
|
|
66
|
+
idleTimeoutMillis: config.idleTimeoutMillis ?? 30000,
|
|
67
|
+
connectionTimeoutMillis: config.connectionTimeoutMillis ?? 5000,
|
|
68
|
+
};
|
|
69
|
+
this.pool = new pg_1.Pool(poolConfig);
|
|
70
|
+
// 监听连接池错误,防止未处理的异常
|
|
71
|
+
this.pool.on('error', (err) => {
|
|
72
|
+
console.error('[PostgresStore] Unexpected pool error:', err.message);
|
|
73
|
+
});
|
|
74
|
+
this.fileStore = new store_1.JSONStore(fileStoreBaseDir);
|
|
75
|
+
this.initPromise = this.initialize();
|
|
76
|
+
}
|
|
77
|
+
// ========== 数据库初始化 ==========
|
|
78
|
+
/**
|
|
79
|
+
* 确保数据库已初始化
|
|
80
|
+
* 在所有公开的数据库操作方法开头调用
|
|
81
|
+
*/
|
|
82
|
+
async ensureInitialized() {
|
|
83
|
+
await this.initPromise;
|
|
84
|
+
}
|
|
85
|
+
async initialize() {
|
|
86
|
+
await this.createTables();
|
|
87
|
+
await this.createIndexes();
|
|
88
|
+
}
|
|
89
|
+
async createTables() {
|
|
90
|
+
const client = await this.pool.connect();
|
|
91
|
+
try {
|
|
92
|
+
// 表 1: agents - Agent 元信息
|
|
93
|
+
await client.query(`
|
|
94
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
95
|
+
agent_id TEXT PRIMARY KEY,
|
|
96
|
+
template_id TEXT NOT NULL,
|
|
97
|
+
created_at TIMESTAMP NOT NULL,
|
|
98
|
+
config_version TEXT NOT NULL,
|
|
99
|
+
lineage JSONB NOT NULL,
|
|
100
|
+
message_count INTEGER NOT NULL DEFAULT 0,
|
|
101
|
+
last_sfp_index INTEGER NOT NULL DEFAULT -1,
|
|
102
|
+
last_bookmark JSONB,
|
|
103
|
+
breakpoint TEXT,
|
|
104
|
+
metadata JSONB NOT NULL
|
|
105
|
+
);
|
|
106
|
+
`);
|
|
107
|
+
// 表 2: messages - 对话消息
|
|
108
|
+
await client.query(`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
agent_id TEXT NOT NULL,
|
|
112
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
|
|
113
|
+
content JSONB NOT NULL,
|
|
114
|
+
seq INTEGER NOT NULL,
|
|
115
|
+
metadata JSONB,
|
|
116
|
+
created_at BIGINT NOT NULL,
|
|
117
|
+
FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE
|
|
118
|
+
);
|
|
119
|
+
`);
|
|
120
|
+
// 表 3: tool_calls - 工具调用记录
|
|
121
|
+
await client.query(`
|
|
122
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
123
|
+
id TEXT PRIMARY KEY,
|
|
124
|
+
agent_id TEXT NOT NULL,
|
|
125
|
+
name TEXT NOT NULL,
|
|
126
|
+
input JSONB NOT NULL,
|
|
127
|
+
state TEXT NOT NULL,
|
|
128
|
+
approval JSONB NOT NULL,
|
|
129
|
+
result JSONB,
|
|
130
|
+
error TEXT,
|
|
131
|
+
is_error BOOLEAN DEFAULT FALSE,
|
|
132
|
+
started_at BIGINT,
|
|
133
|
+
completed_at BIGINT,
|
|
134
|
+
duration_ms INTEGER,
|
|
135
|
+
created_at BIGINT NOT NULL,
|
|
136
|
+
updated_at BIGINT NOT NULL,
|
|
137
|
+
audit_trail JSONB NOT NULL,
|
|
138
|
+
FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE
|
|
139
|
+
);
|
|
140
|
+
`);
|
|
141
|
+
// 表 4: snapshots - 快照
|
|
142
|
+
await client.query(`
|
|
143
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
144
|
+
agent_id TEXT NOT NULL,
|
|
145
|
+
snapshot_id TEXT NOT NULL,
|
|
146
|
+
messages JSONB NOT NULL,
|
|
147
|
+
last_sfp_index INTEGER NOT NULL,
|
|
148
|
+
last_bookmark JSONB NOT NULL,
|
|
149
|
+
created_at TIMESTAMP NOT NULL,
|
|
150
|
+
metadata JSONB,
|
|
151
|
+
PRIMARY KEY (agent_id, snapshot_id),
|
|
152
|
+
FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE
|
|
153
|
+
);
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
client.release();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async createIndexes() {
|
|
161
|
+
const client = await this.pool.connect();
|
|
162
|
+
try {
|
|
163
|
+
// agents 索引
|
|
164
|
+
await client.query(`
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_agents_template_id ON agents(template_id);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_agents_created_at ON agents(created_at DESC);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_agents_lineage_gin ON agents USING GIN(lineage);
|
|
168
|
+
`);
|
|
169
|
+
// messages 索引
|
|
170
|
+
await client.query(`
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_messages_agent_id ON messages(agent_id);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_messages_role ON messages(role);
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at DESC);
|
|
174
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_agent_seq ON messages(agent_id, seq);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_messages_content_gin ON messages USING GIN(content);
|
|
176
|
+
`);
|
|
177
|
+
// tool_calls 索引
|
|
178
|
+
await client.query(`
|
|
179
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_agent_id ON tool_calls(agent_id);
|
|
180
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_name ON tool_calls(name);
|
|
181
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_created_at ON tool_calls(created_at DESC);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_input_gin ON tool_calls USING GIN(input);
|
|
184
|
+
`);
|
|
185
|
+
// snapshots 索引
|
|
186
|
+
await client.query(`
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_agent_id ON snapshots(agent_id);
|
|
188
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_created_at ON snapshots(created_at DESC);
|
|
189
|
+
`);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
client.release();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// ========== 运行时状态管理(数据库) ==========
|
|
196
|
+
async saveMessages(agentId, messages) {
|
|
197
|
+
await this.ensureInitialized();
|
|
198
|
+
const client = await this.pool.connect();
|
|
199
|
+
try {
|
|
200
|
+
await client.query('BEGIN');
|
|
201
|
+
// 1. 删除旧消息
|
|
202
|
+
await client.query('DELETE FROM messages WHERE agent_id = $1', [agentId]);
|
|
203
|
+
// 2. 批量插入新消息
|
|
204
|
+
for (let index = 0; index < messages.length; index++) {
|
|
205
|
+
const msg = messages[index];
|
|
206
|
+
const id = this.generateMessageId();
|
|
207
|
+
await client.query(`INSERT INTO messages (
|
|
208
|
+
id, agent_id, role, content, seq, metadata, created_at
|
|
209
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
|
|
210
|
+
id,
|
|
211
|
+
agentId,
|
|
212
|
+
msg.role,
|
|
213
|
+
JSON.stringify(msg.content),
|
|
214
|
+
index,
|
|
215
|
+
msg.metadata ? JSON.stringify(msg.metadata) : null,
|
|
216
|
+
Date.now()
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
// 3. 更新 agents 表的 message_count
|
|
220
|
+
await client.query('UPDATE agents SET message_count = $1 WHERE agent_id = $2', [messages.length, agentId]);
|
|
221
|
+
await client.query('COMMIT');
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
await client.query('ROLLBACK');
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
client.release();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async loadMessages(agentId) {
|
|
232
|
+
await this.ensureInitialized();
|
|
233
|
+
const client = await this.pool.connect();
|
|
234
|
+
try {
|
|
235
|
+
const result = await client.query(`SELECT role, content, metadata
|
|
236
|
+
FROM messages
|
|
237
|
+
WHERE agent_id = $1
|
|
238
|
+
ORDER BY seq ASC`, [agentId]);
|
|
239
|
+
return result.rows.map(row => ({
|
|
240
|
+
role: row.role,
|
|
241
|
+
content: row.content,
|
|
242
|
+
metadata: row.metadata || undefined
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
client.release();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
generateMessageId() {
|
|
250
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
251
|
+
}
|
|
252
|
+
async saveToolCallRecords(agentId, records) {
|
|
253
|
+
await this.ensureInitialized();
|
|
254
|
+
const client = await this.pool.connect();
|
|
255
|
+
try {
|
|
256
|
+
await client.query('BEGIN');
|
|
257
|
+
// 1. 删除旧记录
|
|
258
|
+
await client.query('DELETE FROM tool_calls WHERE agent_id = $1', [agentId]);
|
|
259
|
+
// 2. 批量插入新记录
|
|
260
|
+
for (const record of records) {
|
|
261
|
+
await client.query(`INSERT INTO tool_calls (
|
|
262
|
+
id, agent_id, name, input, state, approval,
|
|
263
|
+
result, error, is_error,
|
|
264
|
+
started_at, completed_at, duration_ms,
|
|
265
|
+
created_at, updated_at, audit_trail
|
|
266
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`, [
|
|
267
|
+
record.id,
|
|
268
|
+
agentId,
|
|
269
|
+
record.name,
|
|
270
|
+
JSON.stringify(record.input),
|
|
271
|
+
record.state,
|
|
272
|
+
JSON.stringify(record.approval),
|
|
273
|
+
record.result ? JSON.stringify(record.result) : null,
|
|
274
|
+
record.error || null,
|
|
275
|
+
record.isError,
|
|
276
|
+
record.startedAt || null,
|
|
277
|
+
record.completedAt || null,
|
|
278
|
+
record.durationMs || null,
|
|
279
|
+
record.createdAt,
|
|
280
|
+
record.updatedAt,
|
|
281
|
+
JSON.stringify(record.auditTrail)
|
|
282
|
+
]);
|
|
283
|
+
}
|
|
284
|
+
await client.query('COMMIT');
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
await client.query('ROLLBACK');
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
client.release();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async loadToolCallRecords(agentId) {
|
|
295
|
+
await this.ensureInitialized();
|
|
296
|
+
const client = await this.pool.connect();
|
|
297
|
+
try {
|
|
298
|
+
const result = await client.query(`SELECT id, name, input, state, approval,
|
|
299
|
+
result, error, is_error,
|
|
300
|
+
started_at, completed_at, duration_ms,
|
|
301
|
+
created_at, updated_at, audit_trail
|
|
302
|
+
FROM tool_calls
|
|
303
|
+
WHERE agent_id = $1
|
|
304
|
+
ORDER BY created_at ASC`, [agentId]);
|
|
305
|
+
return result.rows.map(row => ({
|
|
306
|
+
id: row.id,
|
|
307
|
+
name: row.name,
|
|
308
|
+
input: row.input,
|
|
309
|
+
state: row.state,
|
|
310
|
+
approval: row.approval,
|
|
311
|
+
result: row.result || undefined,
|
|
312
|
+
error: row.error || undefined,
|
|
313
|
+
isError: row.is_error,
|
|
314
|
+
startedAt: row.started_at || undefined,
|
|
315
|
+
completedAt: row.completed_at || undefined,
|
|
316
|
+
durationMs: row.duration_ms || undefined,
|
|
317
|
+
createdAt: row.created_at,
|
|
318
|
+
updatedAt: row.updated_at,
|
|
319
|
+
auditTrail: row.audit_trail
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
client.release();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// ========== 事件流管理(文件系统) ==========
|
|
327
|
+
async saveTodos(agentId, snapshot) {
|
|
328
|
+
return this.fileStore.saveTodos(agentId, snapshot);
|
|
329
|
+
}
|
|
330
|
+
async loadTodos(agentId) {
|
|
331
|
+
return this.fileStore.loadTodos(agentId);
|
|
332
|
+
}
|
|
333
|
+
async appendEvent(agentId, timeline) {
|
|
334
|
+
return this.fileStore.appendEvent(agentId, timeline);
|
|
335
|
+
}
|
|
336
|
+
async *readEvents(agentId, opts) {
|
|
337
|
+
yield* this.fileStore.readEvents(agentId, opts);
|
|
338
|
+
}
|
|
339
|
+
// ========== 历史与压缩管理(文件系统) ==========
|
|
340
|
+
async saveHistoryWindow(agentId, window) {
|
|
341
|
+
return this.fileStore.saveHistoryWindow(agentId, window);
|
|
342
|
+
}
|
|
343
|
+
async loadHistoryWindows(agentId) {
|
|
344
|
+
return this.fileStore.loadHistoryWindows(agentId);
|
|
345
|
+
}
|
|
346
|
+
async saveCompressionRecord(agentId, record) {
|
|
347
|
+
return this.fileStore.saveCompressionRecord(agentId, record);
|
|
348
|
+
}
|
|
349
|
+
async loadCompressionRecords(agentId) {
|
|
350
|
+
return this.fileStore.loadCompressionRecords(agentId);
|
|
351
|
+
}
|
|
352
|
+
async saveRecoveredFile(agentId, file) {
|
|
353
|
+
return this.fileStore.saveRecoveredFile(agentId, file);
|
|
354
|
+
}
|
|
355
|
+
async loadRecoveredFiles(agentId) {
|
|
356
|
+
return this.fileStore.loadRecoveredFiles(agentId);
|
|
357
|
+
}
|
|
358
|
+
// ========== 多模态缓存管理(文件系统) ==========
|
|
359
|
+
async saveMediaCache(agentId, records) {
|
|
360
|
+
return this.fileStore.saveMediaCache(agentId, records);
|
|
361
|
+
}
|
|
362
|
+
async loadMediaCache(agentId) {
|
|
363
|
+
return this.fileStore.loadMediaCache(agentId);
|
|
364
|
+
}
|
|
365
|
+
// ========== 快照管理(数据库) ==========
|
|
366
|
+
async saveSnapshot(agentId, snapshot) {
|
|
367
|
+
await this.ensureInitialized();
|
|
368
|
+
const client = await this.pool.connect();
|
|
369
|
+
try {
|
|
370
|
+
await client.query(`INSERT INTO snapshots (
|
|
371
|
+
agent_id, snapshot_id, messages, last_sfp_index,
|
|
372
|
+
last_bookmark, created_at, metadata
|
|
373
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
374
|
+
ON CONFLICT (agent_id, snapshot_id)
|
|
375
|
+
DO UPDATE SET
|
|
376
|
+
messages = EXCLUDED.messages,
|
|
377
|
+
last_sfp_index = EXCLUDED.last_sfp_index,
|
|
378
|
+
last_bookmark = EXCLUDED.last_bookmark,
|
|
379
|
+
created_at = EXCLUDED.created_at,
|
|
380
|
+
metadata = EXCLUDED.metadata`, [
|
|
381
|
+
agentId,
|
|
382
|
+
snapshot.id,
|
|
383
|
+
JSON.stringify(snapshot.messages),
|
|
384
|
+
snapshot.lastSfpIndex,
|
|
385
|
+
JSON.stringify(snapshot.lastBookmark),
|
|
386
|
+
snapshot.createdAt,
|
|
387
|
+
snapshot.metadata ? JSON.stringify(snapshot.metadata) : null
|
|
388
|
+
]);
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
client.release();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async loadSnapshot(agentId, snapshotId) {
|
|
395
|
+
await this.ensureInitialized();
|
|
396
|
+
const client = await this.pool.connect();
|
|
397
|
+
try {
|
|
398
|
+
const result = await client.query(`SELECT snapshot_id, messages, last_sfp_index,
|
|
399
|
+
last_bookmark, created_at, metadata
|
|
400
|
+
FROM snapshots
|
|
401
|
+
WHERE agent_id = $1 AND snapshot_id = $2`, [agentId, snapshotId]);
|
|
402
|
+
if (result.rows.length === 0) {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
const row = result.rows[0];
|
|
406
|
+
return {
|
|
407
|
+
id: row.snapshot_id,
|
|
408
|
+
messages: row.messages,
|
|
409
|
+
lastSfpIndex: row.last_sfp_index,
|
|
410
|
+
lastBookmark: row.last_bookmark,
|
|
411
|
+
createdAt: row.created_at,
|
|
412
|
+
metadata: row.metadata || undefined
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
client.release();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async listSnapshots(agentId) {
|
|
420
|
+
await this.ensureInitialized();
|
|
421
|
+
const client = await this.pool.connect();
|
|
422
|
+
try {
|
|
423
|
+
const result = await client.query(`SELECT snapshot_id, messages, last_sfp_index,
|
|
424
|
+
last_bookmark, created_at, metadata
|
|
425
|
+
FROM snapshots
|
|
426
|
+
WHERE agent_id = $1
|
|
427
|
+
ORDER BY created_at DESC`, [agentId]);
|
|
428
|
+
return result.rows.map(row => ({
|
|
429
|
+
id: row.snapshot_id,
|
|
430
|
+
messages: row.messages,
|
|
431
|
+
lastSfpIndex: row.last_sfp_index,
|
|
432
|
+
lastBookmark: row.last_bookmark,
|
|
433
|
+
createdAt: row.created_at,
|
|
434
|
+
metadata: row.metadata || undefined
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
client.release();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// ========== 元数据管理(数据库) ==========
|
|
442
|
+
async saveInfo(agentId, info) {
|
|
443
|
+
await this.ensureInitialized();
|
|
444
|
+
const client = await this.pool.connect();
|
|
445
|
+
try {
|
|
446
|
+
await client.query(`INSERT INTO agents (
|
|
447
|
+
agent_id, template_id, created_at, config_version,
|
|
448
|
+
lineage, message_count, last_sfp_index, last_bookmark,
|
|
449
|
+
breakpoint, metadata
|
|
450
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
451
|
+
ON CONFLICT (agent_id)
|
|
452
|
+
DO UPDATE SET
|
|
453
|
+
template_id = EXCLUDED.template_id,
|
|
454
|
+
created_at = EXCLUDED.created_at,
|
|
455
|
+
config_version = EXCLUDED.config_version,
|
|
456
|
+
lineage = EXCLUDED.lineage,
|
|
457
|
+
message_count = EXCLUDED.message_count,
|
|
458
|
+
last_sfp_index = EXCLUDED.last_sfp_index,
|
|
459
|
+
last_bookmark = EXCLUDED.last_bookmark,
|
|
460
|
+
breakpoint = EXCLUDED.breakpoint,
|
|
461
|
+
metadata = EXCLUDED.metadata`, [
|
|
462
|
+
info.agentId,
|
|
463
|
+
info.templateId,
|
|
464
|
+
info.createdAt,
|
|
465
|
+
info.configVersion,
|
|
466
|
+
JSON.stringify(info.lineage),
|
|
467
|
+
info.messageCount,
|
|
468
|
+
info.lastSfpIndex,
|
|
469
|
+
info.lastBookmark ? JSON.stringify(info.lastBookmark) : null,
|
|
470
|
+
info.breakpoint || null,
|
|
471
|
+
JSON.stringify(info.metadata)
|
|
472
|
+
]);
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
client.release();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async loadInfo(agentId) {
|
|
479
|
+
await this.ensureInitialized();
|
|
480
|
+
const client = await this.pool.connect();
|
|
481
|
+
try {
|
|
482
|
+
const result = await client.query(`SELECT agent_id, template_id, created_at, config_version,
|
|
483
|
+
lineage, message_count, last_sfp_index, last_bookmark,
|
|
484
|
+
breakpoint, metadata
|
|
485
|
+
FROM agents
|
|
486
|
+
WHERE agent_id = $1`, [agentId]);
|
|
487
|
+
if (result.rows.length === 0) {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
const row = result.rows[0];
|
|
491
|
+
const info = {
|
|
492
|
+
agentId: row.agent_id,
|
|
493
|
+
templateId: row.template_id,
|
|
494
|
+
createdAt: row.created_at,
|
|
495
|
+
configVersion: row.config_version,
|
|
496
|
+
lineage: row.lineage,
|
|
497
|
+
messageCount: row.message_count,
|
|
498
|
+
lastSfpIndex: row.last_sfp_index,
|
|
499
|
+
lastBookmark: row.last_bookmark || undefined,
|
|
500
|
+
metadata: row.metadata
|
|
501
|
+
};
|
|
502
|
+
// Restore breakpoint to AgentInfo if present
|
|
503
|
+
if (row.breakpoint) {
|
|
504
|
+
info.breakpoint = row.breakpoint;
|
|
505
|
+
}
|
|
506
|
+
return info;
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
client.release();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// ========== 生命周期管理 ==========
|
|
513
|
+
async exists(agentId) {
|
|
514
|
+
await this.ensureInitialized();
|
|
515
|
+
const client = await this.pool.connect();
|
|
516
|
+
try {
|
|
517
|
+
const result = await client.query('SELECT 1 FROM agents WHERE agent_id = $1', [agentId]);
|
|
518
|
+
return result.rows.length > 0;
|
|
519
|
+
}
|
|
520
|
+
finally {
|
|
521
|
+
client.release();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async delete(agentId) {
|
|
525
|
+
await this.ensureInitialized();
|
|
526
|
+
const client = await this.pool.connect();
|
|
527
|
+
try {
|
|
528
|
+
// 删除数据库记录(级联删除)
|
|
529
|
+
await client.query('DELETE FROM agents WHERE agent_id = $1', [agentId]);
|
|
530
|
+
// 删除文件系统数据
|
|
531
|
+
await this.fileStore.delete(agentId);
|
|
532
|
+
}
|
|
533
|
+
finally {
|
|
534
|
+
client.release();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async list(prefix) {
|
|
538
|
+
await this.ensureInitialized();
|
|
539
|
+
const client = await this.pool.connect();
|
|
540
|
+
try {
|
|
541
|
+
let query = 'SELECT agent_id FROM agents ORDER BY created_at DESC';
|
|
542
|
+
let params = [];
|
|
543
|
+
if (prefix) {
|
|
544
|
+
query = 'SELECT agent_id FROM agents WHERE agent_id LIKE $1 ORDER BY created_at DESC';
|
|
545
|
+
params = [`${prefix}%`];
|
|
546
|
+
}
|
|
547
|
+
const result = await client.query(query, params);
|
|
548
|
+
return result.rows.map(row => row.agent_id);
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
client.release();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// ========== QueryableStore 接口实现 ==========
|
|
555
|
+
async querySessions(filters) {
|
|
556
|
+
await this.ensureInitialized();
|
|
557
|
+
const client = await this.pool.connect();
|
|
558
|
+
try {
|
|
559
|
+
let sql = `
|
|
560
|
+
SELECT agent_id, template_id, created_at, message_count,
|
|
561
|
+
last_sfp_index, breakpoint
|
|
562
|
+
FROM agents
|
|
563
|
+
WHERE 1=1
|
|
564
|
+
`;
|
|
565
|
+
const params = [];
|
|
566
|
+
let paramIndex = 1;
|
|
567
|
+
if (filters.agentId) {
|
|
568
|
+
sql += ` AND agent_id = $${paramIndex++}`;
|
|
569
|
+
params.push(filters.agentId);
|
|
570
|
+
}
|
|
571
|
+
if (filters.templateId) {
|
|
572
|
+
sql += ` AND template_id = $${paramIndex++}`;
|
|
573
|
+
params.push(filters.templateId);
|
|
574
|
+
}
|
|
575
|
+
if (filters.startDate) {
|
|
576
|
+
sql += ` AND created_at >= $${paramIndex++}`;
|
|
577
|
+
params.push(new Date(filters.startDate).toISOString());
|
|
578
|
+
}
|
|
579
|
+
if (filters.endDate) {
|
|
580
|
+
sql += ` AND created_at <= $${paramIndex++}`;
|
|
581
|
+
params.push(new Date(filters.endDate).toISOString());
|
|
582
|
+
}
|
|
583
|
+
// Sorting
|
|
584
|
+
const sortBy = filters.sortBy || 'created_at';
|
|
585
|
+
const sortOrder = filters.sortOrder || 'desc';
|
|
586
|
+
sql += ` ORDER BY ${sortBy} ${sortOrder.toUpperCase()}`;
|
|
587
|
+
// Pagination
|
|
588
|
+
if (filters.limit) {
|
|
589
|
+
sql += ` LIMIT $${paramIndex++}`;
|
|
590
|
+
params.push(filters.limit);
|
|
591
|
+
if (filters.offset) {
|
|
592
|
+
sql += ` OFFSET $${paramIndex++}`;
|
|
593
|
+
params.push(filters.offset);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const result = await client.query(sql, params);
|
|
597
|
+
return result.rows.map(row => ({
|
|
598
|
+
agentId: row.agent_id,
|
|
599
|
+
templateId: row.template_id,
|
|
600
|
+
createdAt: row.created_at,
|
|
601
|
+
messageCount: row.message_count,
|
|
602
|
+
lastSfpIndex: row.last_sfp_index,
|
|
603
|
+
breakpoint: row.breakpoint
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
client.release();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async queryMessages(filters) {
|
|
611
|
+
await this.ensureInitialized();
|
|
612
|
+
const client = await this.pool.connect();
|
|
613
|
+
try {
|
|
614
|
+
let sql = 'SELECT role, content, metadata FROM messages WHERE 1=1';
|
|
615
|
+
const params = [];
|
|
616
|
+
let paramIndex = 1;
|
|
617
|
+
if (filters.agentId) {
|
|
618
|
+
sql += ` AND agent_id = $${paramIndex++}`;
|
|
619
|
+
params.push(filters.agentId);
|
|
620
|
+
}
|
|
621
|
+
if (filters.role) {
|
|
622
|
+
sql += ` AND role = $${paramIndex++}`;
|
|
623
|
+
params.push(filters.role);
|
|
624
|
+
}
|
|
625
|
+
if (filters.startDate) {
|
|
626
|
+
sql += ` AND created_at >= $${paramIndex++}`;
|
|
627
|
+
params.push(filters.startDate);
|
|
628
|
+
}
|
|
629
|
+
if (filters.endDate) {
|
|
630
|
+
sql += ` AND created_at <= $${paramIndex++}`;
|
|
631
|
+
params.push(filters.endDate);
|
|
632
|
+
}
|
|
633
|
+
sql += ' ORDER BY created_at DESC';
|
|
634
|
+
if (filters.limit) {
|
|
635
|
+
sql += ` LIMIT $${paramIndex++}`;
|
|
636
|
+
params.push(filters.limit);
|
|
637
|
+
if (filters.offset) {
|
|
638
|
+
sql += ` OFFSET $${paramIndex++}`;
|
|
639
|
+
params.push(filters.offset);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const result = await client.query(sql, params);
|
|
643
|
+
return result.rows.map(row => ({
|
|
644
|
+
role: row.role,
|
|
645
|
+
content: row.content,
|
|
646
|
+
metadata: row.metadata || undefined
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
finally {
|
|
650
|
+
client.release();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async queryToolCalls(filters) {
|
|
654
|
+
await this.ensureInitialized();
|
|
655
|
+
const client = await this.pool.connect();
|
|
656
|
+
try {
|
|
657
|
+
let sql = `
|
|
658
|
+
SELECT id, name, input, state, approval,
|
|
659
|
+
result, error, is_error,
|
|
660
|
+
started_at, completed_at, duration_ms,
|
|
661
|
+
created_at, updated_at, audit_trail
|
|
662
|
+
FROM tool_calls
|
|
663
|
+
WHERE 1=1
|
|
664
|
+
`;
|
|
665
|
+
const params = [];
|
|
666
|
+
let paramIndex = 1;
|
|
667
|
+
if (filters.agentId) {
|
|
668
|
+
sql += ` AND agent_id = $${paramIndex++}`;
|
|
669
|
+
params.push(filters.agentId);
|
|
670
|
+
}
|
|
671
|
+
if (filters.toolName) {
|
|
672
|
+
sql += ` AND name = $${paramIndex++}`;
|
|
673
|
+
params.push(filters.toolName);
|
|
674
|
+
}
|
|
675
|
+
if (filters.state) {
|
|
676
|
+
sql += ` AND state = $${paramIndex++}`;
|
|
677
|
+
params.push(filters.state);
|
|
678
|
+
}
|
|
679
|
+
if (filters.startDate) {
|
|
680
|
+
sql += ` AND created_at >= $${paramIndex++}`;
|
|
681
|
+
params.push(filters.startDate);
|
|
682
|
+
}
|
|
683
|
+
if (filters.endDate) {
|
|
684
|
+
sql += ` AND created_at <= $${paramIndex++}`;
|
|
685
|
+
params.push(filters.endDate);
|
|
686
|
+
}
|
|
687
|
+
sql += ' ORDER BY created_at DESC';
|
|
688
|
+
if (filters.limit) {
|
|
689
|
+
sql += ` LIMIT $${paramIndex++}`;
|
|
690
|
+
params.push(filters.limit);
|
|
691
|
+
if (filters.offset) {
|
|
692
|
+
sql += ` OFFSET $${paramIndex++}`;
|
|
693
|
+
params.push(filters.offset);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const result = await client.query(sql, params);
|
|
697
|
+
return result.rows.map(row => ({
|
|
698
|
+
id: row.id,
|
|
699
|
+
name: row.name,
|
|
700
|
+
input: row.input,
|
|
701
|
+
state: row.state,
|
|
702
|
+
approval: row.approval,
|
|
703
|
+
result: row.result || undefined,
|
|
704
|
+
error: row.error || undefined,
|
|
705
|
+
isError: row.is_error,
|
|
706
|
+
startedAt: row.started_at || undefined,
|
|
707
|
+
completedAt: row.completed_at || undefined,
|
|
708
|
+
durationMs: row.duration_ms || undefined,
|
|
709
|
+
createdAt: row.created_at,
|
|
710
|
+
updatedAt: row.updated_at,
|
|
711
|
+
auditTrail: row.audit_trail
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
714
|
+
finally {
|
|
715
|
+
client.release();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
async aggregateStats(agentId) {
|
|
719
|
+
await this.ensureInitialized();
|
|
720
|
+
const client = await this.pool.connect();
|
|
721
|
+
try {
|
|
722
|
+
// Total messages
|
|
723
|
+
const messageStats = await client.query('SELECT COUNT(*) as total FROM messages WHERE agent_id = $1', [agentId]);
|
|
724
|
+
// Total tool calls
|
|
725
|
+
const toolCallStats = await client.query('SELECT COUNT(*) as total FROM tool_calls WHERE agent_id = $1', [agentId]);
|
|
726
|
+
// Total snapshots
|
|
727
|
+
const snapshotStats = await client.query('SELECT COUNT(*) as total FROM snapshots WHERE agent_id = $1', [agentId]);
|
|
728
|
+
// Tool calls by name
|
|
729
|
+
const toolCallsByName = await client.query(`SELECT name, COUNT(*) as count
|
|
730
|
+
FROM tool_calls
|
|
731
|
+
WHERE agent_id = $1
|
|
732
|
+
GROUP BY name`, [agentId]);
|
|
733
|
+
// Tool calls by state
|
|
734
|
+
const toolCallsByState = await client.query(`SELECT state, COUNT(*) as count
|
|
735
|
+
FROM tool_calls
|
|
736
|
+
WHERE agent_id = $1
|
|
737
|
+
GROUP BY state`, [agentId]);
|
|
738
|
+
return {
|
|
739
|
+
totalMessages: parseInt(messageStats.rows[0].total),
|
|
740
|
+
totalToolCalls: parseInt(toolCallStats.rows[0].total),
|
|
741
|
+
totalSnapshots: parseInt(snapshotStats.rows[0].total),
|
|
742
|
+
avgMessagesPerSession: parseInt(messageStats.rows[0].total),
|
|
743
|
+
toolCallsByName: toolCallsByName.rows.reduce((acc, row) => {
|
|
744
|
+
acc[row.name] = parseInt(row.count);
|
|
745
|
+
return acc;
|
|
746
|
+
}, {}),
|
|
747
|
+
toolCallsByState: toolCallsByState.rows.reduce((acc, row) => {
|
|
748
|
+
acc[row.state] = parseInt(row.count);
|
|
749
|
+
return acc;
|
|
750
|
+
}, {})
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
finally {
|
|
754
|
+
client.release();
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// ========== 连接管理 ==========
|
|
758
|
+
/**
|
|
759
|
+
* 关闭连接池
|
|
760
|
+
*/
|
|
761
|
+
async close() {
|
|
762
|
+
await this.ensureInitialized();
|
|
763
|
+
await this.pool.end();
|
|
764
|
+
}
|
|
765
|
+
// ========== ExtendedStore 高级功能 ==========
|
|
766
|
+
/**
|
|
767
|
+
* 健康检查
|
|
768
|
+
*/
|
|
769
|
+
async healthCheck() {
|
|
770
|
+
const checkedAt = Date.now();
|
|
771
|
+
let dbConnected = false;
|
|
772
|
+
let dbLatencyMs;
|
|
773
|
+
let fsWritable = false;
|
|
774
|
+
// 检查数据库连接
|
|
775
|
+
try {
|
|
776
|
+
const start = Date.now();
|
|
777
|
+
const client = await this.pool.connect();
|
|
778
|
+
try {
|
|
779
|
+
await client.query('SELECT 1');
|
|
780
|
+
dbConnected = true;
|
|
781
|
+
dbLatencyMs = Date.now() - start;
|
|
782
|
+
}
|
|
783
|
+
finally {
|
|
784
|
+
client.release();
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
dbConnected = false;
|
|
789
|
+
}
|
|
790
|
+
// 检查文件系统
|
|
791
|
+
try {
|
|
792
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
793
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
794
|
+
const baseDir = this.fileStore.baseDir;
|
|
795
|
+
// 确保目录存在
|
|
796
|
+
if (!fs.existsSync(baseDir)) {
|
|
797
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
798
|
+
}
|
|
799
|
+
const testFile = path.join(baseDir, '.health-check');
|
|
800
|
+
fs.writeFileSync(testFile, 'ok');
|
|
801
|
+
fs.unlinkSync(testFile);
|
|
802
|
+
fsWritable = true;
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
fsWritable = false;
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
healthy: dbConnected && fsWritable,
|
|
809
|
+
database: {
|
|
810
|
+
connected: dbConnected,
|
|
811
|
+
latencyMs: dbLatencyMs
|
|
812
|
+
},
|
|
813
|
+
fileSystem: {
|
|
814
|
+
writable: fsWritable
|
|
815
|
+
},
|
|
816
|
+
checkedAt
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* 一致性检查
|
|
821
|
+
* 检查数据库和文件系统之间的数据一致性
|
|
822
|
+
*/
|
|
823
|
+
async checkConsistency(agentId) {
|
|
824
|
+
await this.ensureInitialized();
|
|
825
|
+
const issues = [];
|
|
826
|
+
const checkedAt = Date.now();
|
|
827
|
+
// 检查 Agent 是否存在于数据库
|
|
828
|
+
const dbExists = await this.exists(agentId);
|
|
829
|
+
if (!dbExists) {
|
|
830
|
+
issues.push(`Agent ${agentId} 不存在于数据库中`);
|
|
831
|
+
return { consistent: false, issues, checkedAt };
|
|
832
|
+
}
|
|
833
|
+
// 检查文件系统中的数据
|
|
834
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs'))).then(m => m.promises);
|
|
835
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
836
|
+
const agentDir = path.join(this.fileStore.baseDir, agentId);
|
|
837
|
+
try {
|
|
838
|
+
await fs.access(agentDir);
|
|
839
|
+
}
|
|
840
|
+
catch {
|
|
841
|
+
// 文件系统目录不存在不一定是问题(可能还没有事件/todos等)
|
|
842
|
+
}
|
|
843
|
+
// 检查消息数量一致性
|
|
844
|
+
const info = await this.loadInfo(agentId);
|
|
845
|
+
const messages = await this.loadMessages(agentId);
|
|
846
|
+
if (info && info.messageCount !== messages.length) {
|
|
847
|
+
issues.push(`消息数量不一致: info.messageCount=${info.messageCount}, 实际消息数=${messages.length}`);
|
|
848
|
+
}
|
|
849
|
+
// 检查工具调用记录
|
|
850
|
+
const toolCalls = await this.loadToolCallRecords(agentId);
|
|
851
|
+
for (const call of toolCalls) {
|
|
852
|
+
if (!call.id || !call.name) {
|
|
853
|
+
issues.push(`工具调用记录缺少必要字段: ${JSON.stringify(call)}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
consistent: issues.length === 0,
|
|
858
|
+
issues,
|
|
859
|
+
checkedAt
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* 获取指标统计
|
|
864
|
+
*/
|
|
865
|
+
async getMetrics() {
|
|
866
|
+
await this.ensureInitialized();
|
|
867
|
+
const client = await this.pool.connect();
|
|
868
|
+
try {
|
|
869
|
+
// 获取存储统计
|
|
870
|
+
const agentCount = await client.query('SELECT COUNT(*) as count FROM agents');
|
|
871
|
+
const messageCount = await client.query('SELECT COUNT(*) as count FROM messages');
|
|
872
|
+
const toolCallCount = await client.query('SELECT COUNT(*) as count FROM tool_calls');
|
|
873
|
+
// 尝试获取数据库大小(PostgreSQL 特有)
|
|
874
|
+
let dbSizeBytes;
|
|
875
|
+
try {
|
|
876
|
+
const sizeResult = await client.query("SELECT pg_database_size(current_database()) as size");
|
|
877
|
+
dbSizeBytes = parseInt(sizeResult.rows[0].size);
|
|
878
|
+
}
|
|
879
|
+
catch {
|
|
880
|
+
// 忽略,某些环境可能没有权限
|
|
881
|
+
}
|
|
882
|
+
// 计算性能指标
|
|
883
|
+
const latencies = this.metrics.latencies;
|
|
884
|
+
const avgLatencyMs = latencies.length > 0
|
|
885
|
+
? latencies.reduce((a, b) => a + b, 0) / latencies.length
|
|
886
|
+
: 0;
|
|
887
|
+
const maxLatencyMs = latencies.length > 0 ? Math.max(...latencies) : 0;
|
|
888
|
+
const minLatencyMs = latencies.length > 0 ? Math.min(...latencies) : 0;
|
|
889
|
+
return {
|
|
890
|
+
operations: {
|
|
891
|
+
saves: this.metrics.saves,
|
|
892
|
+
loads: this.metrics.loads,
|
|
893
|
+
queries: this.metrics.queries,
|
|
894
|
+
deletes: this.metrics.deletes
|
|
895
|
+
},
|
|
896
|
+
performance: {
|
|
897
|
+
avgLatencyMs,
|
|
898
|
+
maxLatencyMs,
|
|
899
|
+
minLatencyMs
|
|
900
|
+
},
|
|
901
|
+
storage: {
|
|
902
|
+
totalAgents: parseInt(agentCount.rows[0].count),
|
|
903
|
+
totalMessages: parseInt(messageCount.rows[0].count),
|
|
904
|
+
totalToolCalls: parseInt(toolCallCount.rows[0].count),
|
|
905
|
+
dbSizeBytes
|
|
906
|
+
},
|
|
907
|
+
collectedAt: Date.now()
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
finally {
|
|
911
|
+
client.release();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* 获取分布式锁
|
|
916
|
+
* 使用 PostgreSQL Advisory Lock
|
|
917
|
+
*/
|
|
918
|
+
async acquireAgentLock(agentId, timeoutMs = 30000) {
|
|
919
|
+
await this.ensureInitialized();
|
|
920
|
+
// 将 agentId 转换为数字用于 advisory lock
|
|
921
|
+
const lockKey = this.hashStringToInt(agentId);
|
|
922
|
+
const client = await this.pool.connect();
|
|
923
|
+
try {
|
|
924
|
+
// 尝试获取锁(带超时)
|
|
925
|
+
const result = await client.query('SELECT pg_try_advisory_lock($1) as acquired', [lockKey]);
|
|
926
|
+
if (!result.rows[0].acquired) {
|
|
927
|
+
client.release();
|
|
928
|
+
throw new Error(`无法获取 Agent ${agentId} 的锁,可能被其他进程占用`);
|
|
929
|
+
}
|
|
930
|
+
// 设置超时自动释放
|
|
931
|
+
const timeoutId = setTimeout(async () => {
|
|
932
|
+
try {
|
|
933
|
+
await client.query('SELECT pg_advisory_unlock($1)', [lockKey]);
|
|
934
|
+
client.release();
|
|
935
|
+
}
|
|
936
|
+
catch {
|
|
937
|
+
// 忽略释放错误
|
|
938
|
+
}
|
|
939
|
+
}, timeoutMs);
|
|
940
|
+
// 返回释放函数
|
|
941
|
+
return async () => {
|
|
942
|
+
clearTimeout(timeoutId);
|
|
943
|
+
try {
|
|
944
|
+
await client.query('SELECT pg_advisory_unlock($1)', [lockKey]);
|
|
945
|
+
}
|
|
946
|
+
finally {
|
|
947
|
+
client.release();
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
client.release();
|
|
953
|
+
throw error;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* 批量 Fork Agent
|
|
958
|
+
*/
|
|
959
|
+
async batchFork(agentId, count) {
|
|
960
|
+
await this.ensureInitialized();
|
|
961
|
+
// 加载源 Agent 数据
|
|
962
|
+
const sourceInfo = await this.loadInfo(agentId);
|
|
963
|
+
if (!sourceInfo) {
|
|
964
|
+
throw new Error(`源 Agent ${agentId} 不存在`);
|
|
965
|
+
}
|
|
966
|
+
const sourceMessages = await this.loadMessages(agentId);
|
|
967
|
+
const sourceToolCalls = await this.loadToolCallRecords(agentId);
|
|
968
|
+
const client = await this.pool.connect();
|
|
969
|
+
const newAgentIds = [];
|
|
970
|
+
try {
|
|
971
|
+
await client.query('BEGIN');
|
|
972
|
+
for (let i = 0; i < count; i++) {
|
|
973
|
+
// 生成新的 Agent ID
|
|
974
|
+
const newAgentId = this.generateAgentId();
|
|
975
|
+
newAgentIds.push(newAgentId);
|
|
976
|
+
// 创建新 Agent Info
|
|
977
|
+
const newInfo = {
|
|
978
|
+
...sourceInfo,
|
|
979
|
+
agentId: newAgentId,
|
|
980
|
+
createdAt: new Date().toISOString(),
|
|
981
|
+
lineage: [...sourceInfo.lineage, agentId]
|
|
982
|
+
};
|
|
983
|
+
// 插入 Agent Info
|
|
984
|
+
await client.query(`INSERT INTO agents (
|
|
985
|
+
agent_id, template_id, created_at, config_version,
|
|
986
|
+
lineage, message_count, last_sfp_index, last_bookmark,
|
|
987
|
+
breakpoint, metadata
|
|
988
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
|
|
989
|
+
newInfo.agentId,
|
|
990
|
+
newInfo.templateId,
|
|
991
|
+
newInfo.createdAt,
|
|
992
|
+
newInfo.configVersion,
|
|
993
|
+
JSON.stringify(newInfo.lineage),
|
|
994
|
+
newInfo.messageCount,
|
|
995
|
+
newInfo.lastSfpIndex,
|
|
996
|
+
newInfo.lastBookmark ? JSON.stringify(newInfo.lastBookmark) : null,
|
|
997
|
+
newInfo.breakpoint || null,
|
|
998
|
+
JSON.stringify(newInfo.metadata)
|
|
999
|
+
]);
|
|
1000
|
+
// 复制消息
|
|
1001
|
+
for (let index = 0; index < sourceMessages.length; index++) {
|
|
1002
|
+
const msg = sourceMessages[index];
|
|
1003
|
+
await client.query(`INSERT INTO messages (
|
|
1004
|
+
id, agent_id, role, content, seq, metadata, created_at
|
|
1005
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
|
|
1006
|
+
this.generateMessageId(),
|
|
1007
|
+
newAgentId,
|
|
1008
|
+
msg.role,
|
|
1009
|
+
JSON.stringify(msg.content),
|
|
1010
|
+
index,
|
|
1011
|
+
msg.metadata ? JSON.stringify(msg.metadata) : null,
|
|
1012
|
+
Date.now()
|
|
1013
|
+
]);
|
|
1014
|
+
}
|
|
1015
|
+
// 复制工具调用记录
|
|
1016
|
+
for (const record of sourceToolCalls) {
|
|
1017
|
+
await client.query(`INSERT INTO tool_calls (
|
|
1018
|
+
id, agent_id, name, input, state, approval,
|
|
1019
|
+
result, error, is_error,
|
|
1020
|
+
started_at, completed_at, duration_ms,
|
|
1021
|
+
created_at, updated_at, audit_trail
|
|
1022
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`, [
|
|
1023
|
+
`${record.id}_fork_${i}`,
|
|
1024
|
+
newAgentId,
|
|
1025
|
+
record.name,
|
|
1026
|
+
JSON.stringify(record.input),
|
|
1027
|
+
record.state,
|
|
1028
|
+
JSON.stringify(record.approval),
|
|
1029
|
+
record.result ? JSON.stringify(record.result) : null,
|
|
1030
|
+
record.error || null,
|
|
1031
|
+
record.isError,
|
|
1032
|
+
record.startedAt || null,
|
|
1033
|
+
record.completedAt || null,
|
|
1034
|
+
record.durationMs || null,
|
|
1035
|
+
record.createdAt,
|
|
1036
|
+
record.updatedAt,
|
|
1037
|
+
JSON.stringify(record.auditTrail)
|
|
1038
|
+
]);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
await client.query('COMMIT');
|
|
1042
|
+
return newAgentIds;
|
|
1043
|
+
}
|
|
1044
|
+
catch (error) {
|
|
1045
|
+
await client.query('ROLLBACK');
|
|
1046
|
+
throw error;
|
|
1047
|
+
}
|
|
1048
|
+
finally {
|
|
1049
|
+
client.release();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* 将字符串哈希为整数(用于 advisory lock)
|
|
1054
|
+
*/
|
|
1055
|
+
hashStringToInt(str) {
|
|
1056
|
+
let hash = 0;
|
|
1057
|
+
for (let i = 0; i < str.length; i++) {
|
|
1058
|
+
const char = str.charCodeAt(i);
|
|
1059
|
+
hash = ((hash << 5) - hash) + char;
|
|
1060
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
1061
|
+
}
|
|
1062
|
+
return Math.abs(hash);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* 生成 Agent ID
|
|
1066
|
+
*/
|
|
1067
|
+
generateAgentId() {
|
|
1068
|
+
const timestamp = Date.now().toString(36);
|
|
1069
|
+
const random = Math.random().toString(36).substring(2, 18);
|
|
1070
|
+
return `agt-${timestamp}${random}`;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
exports.PostgresStore = PostgresStore;
|