kode-sdk 2.7.0
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/LICENSE +201 -0
- package/README.md +74 -0
- package/dist/core/agent/breakpoint-manager.d.ts +16 -0
- package/dist/core/agent/breakpoint-manager.js +36 -0
- package/dist/core/agent/message-queue.d.ts +26 -0
- package/dist/core/agent/message-queue.js +47 -0
- package/dist/core/agent/permission-manager.d.ts +9 -0
- package/dist/core/agent/permission-manager.js +32 -0
- package/dist/core/agent/todo-manager.d.ts +26 -0
- package/dist/core/agent/todo-manager.js +91 -0
- package/dist/core/agent/tool-runner.d.ts +9 -0
- package/dist/core/agent/tool-runner.js +45 -0
- package/dist/core/agent.d.ts +271 -0
- package/dist/core/agent.js +2334 -0
- package/dist/core/checkpointer.d.ts +96 -0
- package/dist/core/checkpointer.js +57 -0
- package/dist/core/checkpointers/file.d.ts +20 -0
- package/dist/core/checkpointers/file.js +153 -0
- package/dist/core/checkpointers/index.d.ts +3 -0
- package/dist/core/checkpointers/index.js +9 -0
- package/dist/core/checkpointers/redis.d.ts +35 -0
- package/dist/core/checkpointers/redis.js +113 -0
- package/dist/core/compression/ai-strategy.d.ts +53 -0
- package/dist/core/compression/ai-strategy.js +298 -0
- package/dist/core/compression/index.d.ts +12 -0
- package/dist/core/compression/index.js +27 -0
- package/dist/core/compression/prompts.d.ts +35 -0
- package/dist/core/compression/prompts.js +114 -0
- package/dist/core/compression/simple-strategy.d.ts +44 -0
- package/dist/core/compression/simple-strategy.js +240 -0
- package/dist/core/compression/token-estimator.d.ts +42 -0
- package/dist/core/compression/token-estimator.js +121 -0
- package/dist/core/compression/types.d.ts +140 -0
- package/dist/core/compression/types.js +9 -0
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.js +2 -0
- package/dist/core/context-manager.d.ts +115 -0
- package/dist/core/context-manager.js +107 -0
- package/dist/core/errors.d.ts +6 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/events.d.ts +49 -0
- package/dist/core/events.js +312 -0
- package/dist/core/file-pool.d.ts +43 -0
- package/dist/core/file-pool.js +120 -0
- package/dist/core/hooks.d.ts +23 -0
- package/dist/core/hooks.js +71 -0
- package/dist/core/permission-modes.d.ts +31 -0
- package/dist/core/permission-modes.js +61 -0
- package/dist/core/pool.d.ts +31 -0
- package/dist/core/pool.js +87 -0
- package/dist/core/room.d.ts +15 -0
- package/dist/core/room.js +57 -0
- package/dist/core/scheduler.d.ts +33 -0
- package/dist/core/scheduler.js +58 -0
- package/dist/core/template.d.ts +69 -0
- package/dist/core/template.js +35 -0
- package/dist/core/time-bridge.d.ts +18 -0
- package/dist/core/time-bridge.js +100 -0
- package/dist/core/todo.d.ts +34 -0
- package/dist/core/todo.js +89 -0
- package/dist/core/types.d.ts +380 -0
- package/dist/core/types.js +3 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +147 -0
- package/dist/infra/provider.d.ts +144 -0
- package/dist/infra/provider.js +294 -0
- package/dist/infra/sandbox-factory.d.ts +10 -0
- package/dist/infra/sandbox-factory.js +21 -0
- package/dist/infra/sandbox.d.ts +87 -0
- package/dist/infra/sandbox.js +255 -0
- package/dist/infra/store.d.ts +154 -0
- package/dist/infra/store.js +584 -0
- package/dist/skills/index.d.ts +12 -0
- package/dist/skills/index.js +36 -0
- package/dist/skills/injector.d.ts +29 -0
- package/dist/skills/injector.js +96 -0
- package/dist/skills/loader.d.ts +59 -0
- package/dist/skills/loader.js +215 -0
- package/dist/skills/manager.d.ts +85 -0
- package/dist/skills/manager.js +221 -0
- package/dist/skills/parser.d.ts +40 -0
- package/dist/skills/parser.js +107 -0
- package/dist/skills/types.d.ts +107 -0
- package/dist/skills/types.js +7 -0
- package/dist/skills/validator.d.ts +30 -0
- package/dist/skills/validator.js +121 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.js +5 -0
- package/dist/tools/bash_kill/index.d.ts +1 -0
- package/dist/tools/bash_kill/index.js +35 -0
- package/dist/tools/bash_kill/prompt.d.ts +2 -0
- package/dist/tools/bash_kill/prompt.js +14 -0
- package/dist/tools/bash_logs/index.d.ts +1 -0
- package/dist/tools/bash_logs/index.js +40 -0
- package/dist/tools/bash_logs/prompt.d.ts +2 -0
- package/dist/tools/bash_logs/prompt.js +14 -0
- package/dist/tools/bash_run/index.d.ts +16 -0
- package/dist/tools/bash_run/index.js +61 -0
- package/dist/tools/bash_run/prompt.d.ts +2 -0
- package/dist/tools/bash_run/prompt.js +18 -0
- package/dist/tools/builtin.d.ts +9 -0
- package/dist/tools/builtin.js +27 -0
- package/dist/tools/define.d.ts +101 -0
- package/dist/tools/define.js +214 -0
- package/dist/tools/fs_edit/index.d.ts +1 -0
- package/dist/tools/fs_edit/index.js +62 -0
- package/dist/tools/fs_edit/prompt.d.ts +2 -0
- package/dist/tools/fs_edit/prompt.js +15 -0
- package/dist/tools/fs_glob/index.d.ts +1 -0
- package/dist/tools/fs_glob/index.js +60 -0
- package/dist/tools/fs_glob/prompt.d.ts +2 -0
- package/dist/tools/fs_glob/prompt.js +18 -0
- package/dist/tools/fs_grep/index.d.ts +1 -0
- package/dist/tools/fs_grep/index.js +66 -0
- package/dist/tools/fs_grep/prompt.d.ts +2 -0
- package/dist/tools/fs_grep/prompt.js +16 -0
- package/dist/tools/fs_multi_edit/index.d.ts +1 -0
- package/dist/tools/fs_multi_edit/index.js +106 -0
- package/dist/tools/fs_multi_edit/prompt.d.ts +2 -0
- package/dist/tools/fs_multi_edit/prompt.js +16 -0
- package/dist/tools/fs_read/index.d.ts +1 -0
- package/dist/tools/fs_read/index.js +40 -0
- package/dist/tools/fs_read/prompt.d.ts +2 -0
- package/dist/tools/fs_read/prompt.js +16 -0
- package/dist/tools/fs_rm/index.d.ts +1 -0
- package/dist/tools/fs_rm/index.js +41 -0
- package/dist/tools/fs_rm/prompt.d.ts +2 -0
- package/dist/tools/fs_rm/prompt.js +14 -0
- package/dist/tools/fs_write/index.d.ts +1 -0
- package/dist/tools/fs_write/index.js +40 -0
- package/dist/tools/fs_write/prompt.d.ts +2 -0
- package/dist/tools/fs_write/prompt.js +15 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +56 -0
- package/dist/tools/mcp.d.ts +73 -0
- package/dist/tools/mcp.js +198 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/skill_activate/index.d.ts +5 -0
- package/dist/tools/skill_activate/index.js +63 -0
- package/dist/tools/skill_list/index.d.ts +5 -0
- package/dist/tools/skill_list/index.js +48 -0
- package/dist/tools/skill_resource/index.d.ts +5 -0
- package/dist/tools/skill_resource/index.js +82 -0
- package/dist/tools/task_run/index.d.ts +7 -0
- package/dist/tools/task_run/index.js +60 -0
- package/dist/tools/task_run/prompt.d.ts +5 -0
- package/dist/tools/task_run/prompt.js +29 -0
- package/dist/tools/todo_read/index.d.ts +1 -0
- package/dist/tools/todo_read/index.js +29 -0
- package/dist/tools/todo_read/prompt.d.ts +2 -0
- package/dist/tools/todo_read/prompt.js +18 -0
- package/dist/tools/todo_write/index.d.ts +1 -0
- package/dist/tools/todo_write/index.js +42 -0
- package/dist/tools/todo_write/prompt.d.ts +2 -0
- package/dist/tools/todo_write/prompt.js +23 -0
- package/dist/tools/tool.d.ts +43 -0
- package/dist/tools/tool.js +104 -0
- package/dist/tools/toolkit.d.ts +69 -0
- package/dist/tools/toolkit.js +98 -0
- package/dist/tools/type-inference.d.ts +127 -0
- package/dist/tools/type-inference.js +207 -0
- package/dist/utils/agent-id.d.ts +1 -0
- package/dist/utils/agent-id.js +28 -0
- package/dist/utils/session-id.d.ts +21 -0
- package/dist/utils/session-id.js +64 -0
- package/dist/utils/unicode.d.ts +17 -0
- package/dist/utils/unicode.js +62 -0
- package/package.json +117 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JSONStore = void 0;
|
|
4
|
+
class JSONStore {
|
|
5
|
+
constructor(baseDir, flushIntervalMs = 50) {
|
|
6
|
+
this.baseDir = baseDir;
|
|
7
|
+
this.flushIntervalMs = flushIntervalMs;
|
|
8
|
+
this.eventWriters = new Map();
|
|
9
|
+
this.walQueue = new Map();
|
|
10
|
+
this.walRecovered = new Set();
|
|
11
|
+
// 启动时主动扫描并恢复所有 WAL
|
|
12
|
+
void this.recoverAllWALs();
|
|
13
|
+
}
|
|
14
|
+
// ========== 路径管理 ==========
|
|
15
|
+
getAgentDir(agentId) {
|
|
16
|
+
const path = require('path');
|
|
17
|
+
return path.join(this.baseDir, agentId);
|
|
18
|
+
}
|
|
19
|
+
getRuntimePath(agentId, file) {
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const dir = path.join(this.baseDir, agentId, 'runtime');
|
|
23
|
+
if (!fs.existsSync(dir)) {
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
return path.join(dir, file);
|
|
27
|
+
}
|
|
28
|
+
getEventsPath(agentId, file) {
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
const dir = path.join(this.baseDir, agentId, 'events');
|
|
32
|
+
if (!fs.existsSync(dir)) {
|
|
33
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
return path.join(dir, file);
|
|
36
|
+
}
|
|
37
|
+
getHistoryDir(agentId, subdir) {
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
const dir = path.join(this.baseDir, agentId, 'history', subdir);
|
|
41
|
+
if (!fs.existsSync(dir)) {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
getSnapshotsDir(agentId) {
|
|
47
|
+
const fs = require('fs');
|
|
48
|
+
const path = require('path');
|
|
49
|
+
const dir = path.join(this.baseDir, agentId, 'snapshots');
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
return dir;
|
|
54
|
+
}
|
|
55
|
+
getMetaPath(agentId) {
|
|
56
|
+
const path = require('path');
|
|
57
|
+
return path.join(this.baseDir, agentId, 'meta.json');
|
|
58
|
+
}
|
|
59
|
+
// ========== 运行时状态管理(带 WAL) ==========
|
|
60
|
+
async saveMessages(agentId, messages) {
|
|
61
|
+
await this.saveWithWal(agentId, 'messages', messages);
|
|
62
|
+
}
|
|
63
|
+
async loadMessages(agentId) {
|
|
64
|
+
const raw = (await this.loadWithWal(agentId, 'messages')) || [];
|
|
65
|
+
if (!Array.isArray(raw))
|
|
66
|
+
return [];
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const item of raw) {
|
|
69
|
+
if (!item || typeof item !== 'object')
|
|
70
|
+
continue;
|
|
71
|
+
const role = item.role;
|
|
72
|
+
if (role !== 'system' && role !== 'user' && role !== 'assistant')
|
|
73
|
+
continue;
|
|
74
|
+
let content = item.content;
|
|
75
|
+
if (typeof content === 'string') {
|
|
76
|
+
content = [{ type: 'text', text: content }];
|
|
77
|
+
}
|
|
78
|
+
if (!Array.isArray(content))
|
|
79
|
+
content = [];
|
|
80
|
+
// Guard against sparse arrays getting serialized as `null` entries.
|
|
81
|
+
const cleaned = content.filter((block) => block && typeof block === 'object' && typeof block.type === 'string');
|
|
82
|
+
out.push({ ...item, role, content: cleaned });
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
async saveToolCallRecords(agentId, records) {
|
|
87
|
+
await this.saveWithWal(agentId, 'tool-calls', records);
|
|
88
|
+
}
|
|
89
|
+
async loadToolCallRecords(agentId) {
|
|
90
|
+
return await this.loadWithWal(agentId, 'tool-calls') || [];
|
|
91
|
+
}
|
|
92
|
+
async saveTodos(agentId, snapshot) {
|
|
93
|
+
const fs = require('fs').promises;
|
|
94
|
+
const path = this.getRuntimePath(agentId, 'todos.json');
|
|
95
|
+
await fs.writeFile(path, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
async loadTodos(agentId) {
|
|
98
|
+
const fs = require('fs').promises;
|
|
99
|
+
try {
|
|
100
|
+
const data = await fs.readFile(this.getRuntimePath(agentId, 'todos.json'), 'utf-8');
|
|
101
|
+
return JSON.parse(data);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ========== Skills 状态管理 ==========
|
|
108
|
+
async saveSkillsState(agentId, state) {
|
|
109
|
+
const fs = require('fs').promises;
|
|
110
|
+
const path = this.getRuntimePath(agentId, 'skills.json');
|
|
111
|
+
await fs.writeFile(path, JSON.stringify(state, null, 2), 'utf-8');
|
|
112
|
+
}
|
|
113
|
+
async loadSkillsState(agentId) {
|
|
114
|
+
const fs = require('fs').promises;
|
|
115
|
+
try {
|
|
116
|
+
const data = await fs.readFile(this.getRuntimePath(agentId, 'skills.json'), 'utf-8');
|
|
117
|
+
return JSON.parse(data);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ========== 统一的 WAL 读写策略 ==========
|
|
124
|
+
async saveWithWal(agentId, name, data) {
|
|
125
|
+
const fs = require('fs');
|
|
126
|
+
const fsp = fs.promises;
|
|
127
|
+
const path = this.getRuntimePath(agentId, `${name}.json`);
|
|
128
|
+
const walPath = this.getRuntimePath(agentId, `${name}.wal`);
|
|
129
|
+
// Serialize the entire WAL+main-file transaction per (agentId,name).
|
|
130
|
+
// Otherwise concurrent writes can race on the shared `${path}.tmp` and cause ENOENT on rename.
|
|
131
|
+
await this.queueWalWrite(agentId, name, async () => {
|
|
132
|
+
// 1. Write to WAL first
|
|
133
|
+
const walData = JSON.stringify({ data, timestamp: Date.now() });
|
|
134
|
+
await fsp.writeFile(walPath, walData, 'utf-8');
|
|
135
|
+
// 2. Write to main file (atomic: tmp + rename)
|
|
136
|
+
const tmp = `${path}.tmp`;
|
|
137
|
+
await fsp.writeFile(tmp, JSON.stringify(data, null, 2), 'utf-8');
|
|
138
|
+
await fsp.rename(tmp, path);
|
|
139
|
+
// 3. Remove WAL after successful write
|
|
140
|
+
if (fs.existsSync(walPath)) {
|
|
141
|
+
await fsp.unlink(walPath).catch(() => undefined);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async loadWithWal(agentId, name) {
|
|
146
|
+
const fs = require('fs');
|
|
147
|
+
const fsp = fs.promises;
|
|
148
|
+
const path = this.getRuntimePath(agentId, `${name}.json`);
|
|
149
|
+
const walPath = this.getRuntimePath(agentId, `${name}.wal`);
|
|
150
|
+
// 1. Check and recover from WAL if exists.
|
|
151
|
+
// Also serialize recovery with writes for the same (agentId,name) to avoid tmp/rename races.
|
|
152
|
+
if (fs.existsSync(walPath)) {
|
|
153
|
+
await this.queueWalWrite(agentId, name, async () => {
|
|
154
|
+
try {
|
|
155
|
+
const walData = JSON.parse(await fsp.readFile(walPath, 'utf-8'));
|
|
156
|
+
if (walData.data !== undefined) {
|
|
157
|
+
// Recover from WAL
|
|
158
|
+
const tmp = `${path}.tmp`;
|
|
159
|
+
await fsp.writeFile(tmp, JSON.stringify(walData.data, null, 2), 'utf-8');
|
|
160
|
+
await fsp.rename(tmp, path);
|
|
161
|
+
await fsp.unlink(walPath).catch(() => undefined);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.error(`Failed to recover ${name} from WAL:`, err);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// 2. Load from main file
|
|
170
|
+
try {
|
|
171
|
+
const data = await fsp.readFile(path, 'utf-8');
|
|
172
|
+
return JSON.parse(data);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async queueWalWrite(agentId, name, write) {
|
|
179
|
+
const key = `${agentId}:${name}`;
|
|
180
|
+
// 链式追加,确保顺序执行
|
|
181
|
+
const previous = this.walQueue.get(key) || Promise.resolve();
|
|
182
|
+
const next = previous
|
|
183
|
+
.then(() => write()) // 前一个成功后执行
|
|
184
|
+
.catch((err) => {
|
|
185
|
+
// 即使前一个失败,也尝试当前写入
|
|
186
|
+
console.error(`[WAL] Previous write failed for ${key}, attempting current write:`, err);
|
|
187
|
+
return write();
|
|
188
|
+
});
|
|
189
|
+
this.walQueue.set(key, next);
|
|
190
|
+
try {
|
|
191
|
+
await next;
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
// 记录但不阻塞调用者
|
|
195
|
+
console.error(`[WAL] Write failed for ${key}:`, err);
|
|
196
|
+
throw err; // 重新抛出让调用者处理
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
// 清理完成的 promise(避免内存泄漏)
|
|
200
|
+
if (this.walQueue.get(key) === next) {
|
|
201
|
+
this.walQueue.delete(key);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ========== WAL 主动恢复 ==========
|
|
206
|
+
/**
|
|
207
|
+
* Store 初始化时主动恢复所有 WAL 文件
|
|
208
|
+
*/
|
|
209
|
+
async recoverAllWALs() {
|
|
210
|
+
const fs = require('fs').promises;
|
|
211
|
+
const path = require('path');
|
|
212
|
+
try {
|
|
213
|
+
const agentDirs = await fs.readdir(this.baseDir).catch(() => []);
|
|
214
|
+
for (const agentId of agentDirs) {
|
|
215
|
+
const agentDir = path.join(this.baseDir, agentId);
|
|
216
|
+
const stat = await fs.stat(agentDir).catch(() => null);
|
|
217
|
+
if (!stat?.isDirectory())
|
|
218
|
+
continue;
|
|
219
|
+
// 恢复运行时 WAL
|
|
220
|
+
await this.recoverRuntimeWAL(agentId, 'messages');
|
|
221
|
+
await this.recoverRuntimeWAL(agentId, 'tool-calls');
|
|
222
|
+
// 恢复事件 WAL
|
|
223
|
+
await this.recoverEventWALFile(agentId, 'progress');
|
|
224
|
+
await this.recoverEventWALFile(agentId, 'control');
|
|
225
|
+
await this.recoverEventWALFile(agentId, 'monitor');
|
|
226
|
+
}
|
|
227
|
+
if (agentDirs.length > 0) {
|
|
228
|
+
console.log(`[Store] WAL recovery completed for ${agentDirs.length} agents`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
console.error('[Store] WAL recovery failed:', err);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* 恢复运行时数据的 WAL
|
|
237
|
+
*/
|
|
238
|
+
async recoverRuntimeWAL(agentId, name) {
|
|
239
|
+
const fs = require('fs');
|
|
240
|
+
const fsp = fs.promises;
|
|
241
|
+
const walKey = `${agentId}:${name}`;
|
|
242
|
+
if (this.walRecovered.has(walKey))
|
|
243
|
+
return;
|
|
244
|
+
this.walRecovered.add(walKey);
|
|
245
|
+
const path = this.getRuntimePath(agentId, `${name}.json`);
|
|
246
|
+
const walPath = this.getRuntimePath(agentId, `${name}.wal`);
|
|
247
|
+
if (!fs.existsSync(walPath))
|
|
248
|
+
return;
|
|
249
|
+
try {
|
|
250
|
+
const walData = JSON.parse(await fsp.readFile(walPath, 'utf-8'));
|
|
251
|
+
if (walData.data !== undefined) {
|
|
252
|
+
const tmp = `${path}.tmp`;
|
|
253
|
+
await fsp.writeFile(tmp, JSON.stringify(walData.data, null, 2), 'utf-8');
|
|
254
|
+
await fsp.rename(tmp, path);
|
|
255
|
+
await fsp.unlink(walPath);
|
|
256
|
+
console.log(`[Store] Recovered ${name} from WAL for ${agentId}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
console.error(`[Store] Failed to recover ${name} WAL for ${agentId}:`, err);
|
|
261
|
+
// 重命名损坏的 WAL 以便人工检查
|
|
262
|
+
await fsp.rename(walPath, `${walPath}.corrupted`).catch(() => { });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* 恢复事件流的 WAL
|
|
267
|
+
*/
|
|
268
|
+
async recoverEventWALFile(agentId, channel) {
|
|
269
|
+
const walKey = `${agentId}:${channel}`;
|
|
270
|
+
if (this.walRecovered.has(walKey))
|
|
271
|
+
return;
|
|
272
|
+
this.walRecovered.add(walKey);
|
|
273
|
+
const fs = require('fs');
|
|
274
|
+
const fsp = fs.promises;
|
|
275
|
+
const walPath = this.getEventsPath(agentId, `${channel}.wal`);
|
|
276
|
+
if (!fs.existsSync(walPath))
|
|
277
|
+
return;
|
|
278
|
+
try {
|
|
279
|
+
const data = await fsp.readFile(walPath, 'utf-8');
|
|
280
|
+
const lines = data.split('\n').filter(Boolean);
|
|
281
|
+
if (lines.length > 0) {
|
|
282
|
+
const payload = lines.join('\n') + '\n';
|
|
283
|
+
await fsp.appendFile(this.getEventsPath(agentId, `${channel}.log`), payload);
|
|
284
|
+
await fsp.unlink(walPath);
|
|
285
|
+
console.log(`[Store] Recovered ${lines.length} events from ${channel} WAL for ${agentId}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
console.error(`[Store] Failed to recover ${channel} WAL for ${agentId}:`, err);
|
|
290
|
+
await fsp.rename(walPath, `${walPath}.corrupted`).catch(() => { });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ========== 事件流管理(按通道缓冲 + WAL) ==========
|
|
294
|
+
async appendEvent(agentId, timeline) {
|
|
295
|
+
const entry = JSON.stringify(timeline);
|
|
296
|
+
const channel = timeline.event.channel;
|
|
297
|
+
await this.recoverEventWal(agentId, channel);
|
|
298
|
+
const writers = this.getEventWriters(agentId);
|
|
299
|
+
const writer = writers[channel];
|
|
300
|
+
writer.buffer.push(entry);
|
|
301
|
+
await this.writeEventWal(agentId, channel, writer);
|
|
302
|
+
if (!writer.timer) {
|
|
303
|
+
writer.timer = setTimeout(() => {
|
|
304
|
+
void this.flushEvents(agentId, channel);
|
|
305
|
+
}, this.flushIntervalMs);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async *readEvents(agentId, opts) {
|
|
309
|
+
const channels = opts?.channel ? [opts.channel] : ['progress', 'control', 'monitor'];
|
|
310
|
+
for (const channel of channels) {
|
|
311
|
+
await this.recoverEventWal(agentId, channel);
|
|
312
|
+
await this.flushEvents(agentId, channel);
|
|
313
|
+
const fs = require('fs');
|
|
314
|
+
const readline = require('readline');
|
|
315
|
+
const path = this.getEventsPath(agentId, `${channel}.log`);
|
|
316
|
+
if (!fs.existsSync(path))
|
|
317
|
+
continue;
|
|
318
|
+
const stream = fs.createReadStream(path, { encoding: 'utf-8' });
|
|
319
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
320
|
+
for await (const line of rl) {
|
|
321
|
+
if (!line.trim())
|
|
322
|
+
continue;
|
|
323
|
+
try {
|
|
324
|
+
const event = JSON.parse(line);
|
|
325
|
+
if (opts?.since && event.bookmark.seq <= opts.since.seq)
|
|
326
|
+
continue;
|
|
327
|
+
yield event;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// skip corrupted lines
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
getEventWriters(agentId) {
|
|
336
|
+
let writers = this.eventWriters.get(agentId);
|
|
337
|
+
if (!writers) {
|
|
338
|
+
writers = {
|
|
339
|
+
progress: { buffer: [], flushing: [] },
|
|
340
|
+
control: { buffer: [], flushing: [] },
|
|
341
|
+
monitor: { buffer: [], flushing: [] },
|
|
342
|
+
};
|
|
343
|
+
this.eventWriters.set(agentId, writers);
|
|
344
|
+
}
|
|
345
|
+
return writers;
|
|
346
|
+
}
|
|
347
|
+
async flushEvents(agentId, channel) {
|
|
348
|
+
const writers = this.eventWriters.get(agentId);
|
|
349
|
+
if (!writers)
|
|
350
|
+
return;
|
|
351
|
+
const writer = writers[channel];
|
|
352
|
+
if (!writer)
|
|
353
|
+
return;
|
|
354
|
+
if (writer.timer) {
|
|
355
|
+
clearTimeout(writer.timer);
|
|
356
|
+
writer.timer = undefined;
|
|
357
|
+
}
|
|
358
|
+
if (writer.buffer.length > 0) {
|
|
359
|
+
writer.flushing.push(...writer.buffer);
|
|
360
|
+
writer.buffer = [];
|
|
361
|
+
}
|
|
362
|
+
if (writer.flushing.length === 0) {
|
|
363
|
+
await this.writeEventWal(agentId, channel, writer);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const fsp = require('fs').promises;
|
|
367
|
+
const payload = writer.flushing.join('\n') + '\n';
|
|
368
|
+
await fsp.appendFile(this.getEventsPath(agentId, `${channel}.log`), payload);
|
|
369
|
+
writer.flushing = [];
|
|
370
|
+
await this.writeEventWal(agentId, channel, writer);
|
|
371
|
+
}
|
|
372
|
+
async recoverEventWal(agentId, channel) {
|
|
373
|
+
const walKey = `${agentId}:${channel}`;
|
|
374
|
+
if (this.walRecovered.has(walKey))
|
|
375
|
+
return;
|
|
376
|
+
const writers = this.getEventWriters(agentId);
|
|
377
|
+
const writer = writers[channel];
|
|
378
|
+
writer.recovered = true;
|
|
379
|
+
this.walRecovered.add(walKey);
|
|
380
|
+
const fs = require('fs');
|
|
381
|
+
const fsp = fs.promises;
|
|
382
|
+
const walPath = this.getEventsPath(agentId, `${channel}.wal`);
|
|
383
|
+
if (!fs.existsSync(walPath))
|
|
384
|
+
return;
|
|
385
|
+
try {
|
|
386
|
+
const data = await fsp.readFile(walPath, 'utf-8');
|
|
387
|
+
const lines = data.split('\n').filter(Boolean);
|
|
388
|
+
if (lines.length > 0) {
|
|
389
|
+
const payload = lines.join('\n') + '\n';
|
|
390
|
+
await fsp.appendFile(this.getEventsPath(agentId, `${channel}.log`), payload);
|
|
391
|
+
}
|
|
392
|
+
await fsp.unlink(walPath);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// WAL corrupted, keep it for manual inspection
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async writeEventWal(agentId, channel, writer) {
|
|
399
|
+
const fs = require('fs');
|
|
400
|
+
const fsp = fs.promises;
|
|
401
|
+
const walPath = this.getEventsPath(agentId, `${channel}.wal`);
|
|
402
|
+
const schedule = async () => {
|
|
403
|
+
const entries = [...writer.flushing, ...writer.buffer];
|
|
404
|
+
if (entries.length > 0) {
|
|
405
|
+
await fsp.writeFile(walPath, entries.join('\n') + '\n', 'utf-8');
|
|
406
|
+
}
|
|
407
|
+
else if (fs.existsSync(walPath)) {
|
|
408
|
+
await fsp.unlink(walPath).catch(() => undefined);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
writer.walWriting = (writer.walWriting || Promise.resolve()).then(schedule, schedule);
|
|
412
|
+
await writer.walWriting;
|
|
413
|
+
}
|
|
414
|
+
// ========== 历史与压缩管理 ==========
|
|
415
|
+
async saveHistoryWindow(agentId, window) {
|
|
416
|
+
const fs = require('fs').promises;
|
|
417
|
+
const path = require('path');
|
|
418
|
+
const dir = this.getHistoryDir(agentId, 'windows');
|
|
419
|
+
const filePath = path.join(dir, `${window.timestamp}.json`);
|
|
420
|
+
await fs.writeFile(filePath, JSON.stringify(window, null, 2), 'utf-8');
|
|
421
|
+
}
|
|
422
|
+
async loadHistoryWindows(agentId) {
|
|
423
|
+
const fs = require('fs').promises;
|
|
424
|
+
const path = require('path');
|
|
425
|
+
try {
|
|
426
|
+
const dir = this.getHistoryDir(agentId, 'windows');
|
|
427
|
+
const files = await fs.readdir(dir);
|
|
428
|
+
const windows = [];
|
|
429
|
+
for (const file of files) {
|
|
430
|
+
if (file.endsWith('.json')) {
|
|
431
|
+
const data = await fs.readFile(path.join(dir, file), 'utf-8');
|
|
432
|
+
windows.push(JSON.parse(data));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return windows.sort((a, b) => a.timestamp - b.timestamp);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async saveCompressionRecord(agentId, record) {
|
|
442
|
+
const fs = require('fs').promises;
|
|
443
|
+
const path = require('path');
|
|
444
|
+
const dir = this.getHistoryDir(agentId, 'compressions');
|
|
445
|
+
const filePath = path.join(dir, `${record.timestamp}.json`);
|
|
446
|
+
await fs.writeFile(filePath, JSON.stringify(record, null, 2), 'utf-8');
|
|
447
|
+
}
|
|
448
|
+
async loadCompressionRecords(agentId) {
|
|
449
|
+
const fs = require('fs').promises;
|
|
450
|
+
const path = require('path');
|
|
451
|
+
try {
|
|
452
|
+
const dir = this.getHistoryDir(agentId, 'compressions');
|
|
453
|
+
const files = await fs.readdir(dir);
|
|
454
|
+
const records = [];
|
|
455
|
+
for (const file of files) {
|
|
456
|
+
if (file.endsWith('.json')) {
|
|
457
|
+
const data = await fs.readFile(path.join(dir, file), 'utf-8');
|
|
458
|
+
records.push(JSON.parse(data));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return records.sort((a, b) => a.timestamp - b.timestamp);
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async saveRecoveredFile(agentId, file) {
|
|
468
|
+
const fs = require('fs').promises;
|
|
469
|
+
const path = require('path');
|
|
470
|
+
const dir = this.getHistoryDir(agentId, 'recovered');
|
|
471
|
+
const safePath = file.path.replace(/[\/\\]/g, '_');
|
|
472
|
+
const filePath = path.join(dir, `${safePath}_${file.timestamp}.txt`);
|
|
473
|
+
const header = `# Recovered: ${file.path}\n# Timestamp: ${file.timestamp}\n# Mtime: ${file.mtime}\n\n`;
|
|
474
|
+
await fs.writeFile(filePath, header + file.content, 'utf-8');
|
|
475
|
+
}
|
|
476
|
+
async loadRecoveredFiles(agentId) {
|
|
477
|
+
const fs = require('fs').promises;
|
|
478
|
+
const path = require('path');
|
|
479
|
+
try {
|
|
480
|
+
const dir = this.getHistoryDir(agentId, 'recovered');
|
|
481
|
+
const files = await fs.readdir(dir);
|
|
482
|
+
const recovered = [];
|
|
483
|
+
for (const file of files) {
|
|
484
|
+
const data = await fs.readFile(path.join(dir, file), 'utf-8');
|
|
485
|
+
const lines = data.split('\n');
|
|
486
|
+
const pathMatch = lines[0]?.match(/# Recovered: (.+)/);
|
|
487
|
+
const tsMatch = lines[1]?.match(/# Timestamp: (\d+)/);
|
|
488
|
+
const mtimeMatch = lines[2]?.match(/# Mtime: (\d+)/);
|
|
489
|
+
if (pathMatch && tsMatch && mtimeMatch) {
|
|
490
|
+
recovered.push({
|
|
491
|
+
path: pathMatch[1],
|
|
492
|
+
content: lines.slice(4).join('\n'),
|
|
493
|
+
mtime: parseInt(mtimeMatch[1]),
|
|
494
|
+
timestamp: parseInt(tsMatch[1]),
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return recovered.sort((a, b) => a.timestamp - b.timestamp);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// ========== 快照管理 ==========
|
|
505
|
+
async saveSnapshot(agentId, snapshot) {
|
|
506
|
+
const fs = require('fs').promises;
|
|
507
|
+
const path = require('path');
|
|
508
|
+
const dir = this.getSnapshotsDir(agentId);
|
|
509
|
+
const filePath = path.join(dir, `${snapshot.id}.json`);
|
|
510
|
+
await fs.writeFile(filePath, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
511
|
+
}
|
|
512
|
+
async loadSnapshot(agentId, snapshotId) {
|
|
513
|
+
const fs = require('fs').promises;
|
|
514
|
+
const path = require('path');
|
|
515
|
+
try {
|
|
516
|
+
const dir = this.getSnapshotsDir(agentId);
|
|
517
|
+
const data = await fs.readFile(path.join(dir, `${snapshotId}.json`), 'utf-8');
|
|
518
|
+
return JSON.parse(data);
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async listSnapshots(agentId) {
|
|
525
|
+
const fs = require('fs').promises;
|
|
526
|
+
const path = require('path');
|
|
527
|
+
try {
|
|
528
|
+
const dir = this.getSnapshotsDir(agentId);
|
|
529
|
+
const files = await fs.readdir(dir);
|
|
530
|
+
const snapshots = [];
|
|
531
|
+
for (const file of files) {
|
|
532
|
+
if (file.endsWith('.json')) {
|
|
533
|
+
const data = await fs.readFile(path.join(dir, file), 'utf-8');
|
|
534
|
+
snapshots.push(JSON.parse(data));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return snapshots;
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return [];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// ========== 元数据管理 ==========
|
|
544
|
+
async saveInfo(agentId, info) {
|
|
545
|
+
const fs = require('fs').promises;
|
|
546
|
+
await fs.writeFile(this.getMetaPath(agentId), JSON.stringify(info, null, 2), 'utf-8');
|
|
547
|
+
}
|
|
548
|
+
async loadInfo(agentId) {
|
|
549
|
+
const fs = require('fs').promises;
|
|
550
|
+
try {
|
|
551
|
+
const data = await fs.readFile(this.getMetaPath(agentId), 'utf-8');
|
|
552
|
+
return JSON.parse(data);
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// ========== 生命周期管理 ==========
|
|
559
|
+
async exists(agentId) {
|
|
560
|
+
const fs = require('fs').promises;
|
|
561
|
+
try {
|
|
562
|
+
await fs.access(this.getAgentDir(agentId));
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async delete(agentId) {
|
|
570
|
+
const fs = require('fs').promises;
|
|
571
|
+
await fs.rm(this.getAgentDir(agentId), { recursive: true, force: true });
|
|
572
|
+
}
|
|
573
|
+
async list(prefix) {
|
|
574
|
+
const fs = require('fs').promises;
|
|
575
|
+
try {
|
|
576
|
+
const dirs = await fs.readdir(this.baseDir);
|
|
577
|
+
return prefix ? dirs.filter((d) => d.startsWith(prefix)) : dirs;
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
exports.JSONStore = JSONStore;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Skills 模块
|
|
3
|
+
*
|
|
4
|
+
* 提供对 Agent Skills 开放标准的支持
|
|
5
|
+
* https://agentskills.io
|
|
6
|
+
*/
|
|
7
|
+
export * from './types';
|
|
8
|
+
export { SkillsParser } from './parser';
|
|
9
|
+
export { SkillsValidator } from './validator';
|
|
10
|
+
export { SkillsLoader } from './loader';
|
|
11
|
+
export { SkillsManager } from './manager';
|
|
12
|
+
export { SkillsInjector } from './injector';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Skills 模块
|
|
4
|
+
*
|
|
5
|
+
* 提供对 Agent Skills 开放标准的支持
|
|
6
|
+
* https://agentskills.io
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.SkillsInjector = exports.SkillsManager = exports.SkillsLoader = exports.SkillsValidator = exports.SkillsParser = void 0;
|
|
24
|
+
// 类型导出
|
|
25
|
+
__exportStar(require("./types"), exports);
|
|
26
|
+
// 核心类导出
|
|
27
|
+
var parser_1 = require("./parser");
|
|
28
|
+
Object.defineProperty(exports, "SkillsParser", { enumerable: true, get: function () { return parser_1.SkillsParser; } });
|
|
29
|
+
var validator_1 = require("./validator");
|
|
30
|
+
Object.defineProperty(exports, "SkillsValidator", { enumerable: true, get: function () { return validator_1.SkillsValidator; } });
|
|
31
|
+
var loader_1 = require("./loader");
|
|
32
|
+
Object.defineProperty(exports, "SkillsLoader", { enumerable: true, get: function () { return loader_1.SkillsLoader; } });
|
|
33
|
+
var manager_1 = require("./manager");
|
|
34
|
+
Object.defineProperty(exports, "SkillsManager", { enumerable: true, get: function () { return manager_1.SkillsManager; } });
|
|
35
|
+
var injector_1 = require("./injector");
|
|
36
|
+
Object.defineProperty(exports, "SkillsInjector", { enumerable: true, get: function () { return injector_1.SkillsInjector; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Skill, SkillMetadata } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Skills 系统提示注入器
|
|
4
|
+
*
|
|
5
|
+
* 将 Skills 元数据转换为 XML 格式并注入系统提示
|
|
6
|
+
*/
|
|
7
|
+
export declare class SkillsInjector {
|
|
8
|
+
/**
|
|
9
|
+
* 生成 available_skills XML
|
|
10
|
+
*
|
|
11
|
+
* 遵循 Agent Skills 规范的推荐格式
|
|
12
|
+
*
|
|
13
|
+
* @param skills - Skills 列表(仅需元数据)
|
|
14
|
+
* @returns XML 格式的系统提示片段
|
|
15
|
+
*/
|
|
16
|
+
static toPromptXML(skills: SkillMetadata[]): string;
|
|
17
|
+
/**
|
|
18
|
+
* 生成激活后的 skill_instructions XML
|
|
19
|
+
*/
|
|
20
|
+
static toActivatedXML(skill: Skill): string;
|
|
21
|
+
/**
|
|
22
|
+
* 转义 XML 特殊字符
|
|
23
|
+
*/
|
|
24
|
+
private static escapeXml;
|
|
25
|
+
/**
|
|
26
|
+
* 生成 Skills 总结(用于上下文压缩)
|
|
27
|
+
*/
|
|
28
|
+
static toSummary(skills: Skill[]): string;
|
|
29
|
+
}
|