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,87 @@
|
|
|
1
|
+
export type SandboxKind = 'local' | 'docker' | 'k8s' | 'remote' | 'vfs';
|
|
2
|
+
export interface SandboxFS {
|
|
3
|
+
resolve(path: string): string;
|
|
4
|
+
isInside(path: string, access?: 'read' | 'write'): boolean;
|
|
5
|
+
read(path: string): Promise<string>;
|
|
6
|
+
write(path: string, content: string): Promise<void>;
|
|
7
|
+
rm(path: string, opts?: {
|
|
8
|
+
recursive?: boolean;
|
|
9
|
+
force?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
temp(name?: string): string;
|
|
12
|
+
stat(path: string): Promise<{
|
|
13
|
+
mtimeMs: number;
|
|
14
|
+
}>;
|
|
15
|
+
glob(pattern: string, opts?: {
|
|
16
|
+
cwd?: string;
|
|
17
|
+
ignore?: string[];
|
|
18
|
+
dot?: boolean;
|
|
19
|
+
absolute?: boolean;
|
|
20
|
+
}): Promise<string[]>;
|
|
21
|
+
}
|
|
22
|
+
export interface SandboxExecResult {
|
|
23
|
+
code: number;
|
|
24
|
+
stdout: string;
|
|
25
|
+
stderr: string;
|
|
26
|
+
}
|
|
27
|
+
export interface Sandbox {
|
|
28
|
+
kind: SandboxKind;
|
|
29
|
+
workDir?: string;
|
|
30
|
+
fs: SandboxFS;
|
|
31
|
+
exec(cmd: string, opts?: {
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}): Promise<SandboxExecResult>;
|
|
34
|
+
watchFiles?(paths: string[], listener: (event: {
|
|
35
|
+
path: string;
|
|
36
|
+
mtimeMs: number;
|
|
37
|
+
}) => void): Promise<string>;
|
|
38
|
+
unwatchFiles?(id: string): void;
|
|
39
|
+
dispose?(): Promise<void> | void;
|
|
40
|
+
}
|
|
41
|
+
export interface LocalSandboxOptions {
|
|
42
|
+
workDir?: string;
|
|
43
|
+
baseDir?: string;
|
|
44
|
+
pwd?: string;
|
|
45
|
+
enforceBoundary?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Use allowReadPaths/allowWritePaths instead.
|
|
48
|
+
* If provided, it will be used for both read & write access.
|
|
49
|
+
*/
|
|
50
|
+
allowPaths?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Extra paths allowed for read-like operations (read/stat/glob/watch).
|
|
53
|
+
* Writes are still blocked unless allowWritePaths (or legacy allowPaths) includes them.
|
|
54
|
+
*/
|
|
55
|
+
allowReadPaths?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* Extra paths allowed for write-like operations (write/rm).
|
|
58
|
+
* NOTE: Bash commands are not OS-sandboxed; this only affects SandboxFS-based tools.
|
|
59
|
+
*/
|
|
60
|
+
allowWritePaths?: string[];
|
|
61
|
+
watchFiles?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export declare class LocalSandbox implements Sandbox {
|
|
64
|
+
kind: SandboxKind;
|
|
65
|
+
workDir: string;
|
|
66
|
+
fs: SandboxFS;
|
|
67
|
+
private watchers;
|
|
68
|
+
private readonly enforceBoundary;
|
|
69
|
+
private readonly allowReadPaths;
|
|
70
|
+
private readonly allowWritePaths;
|
|
71
|
+
private readonly watchEnabled;
|
|
72
|
+
constructor(opts?: LocalSandboxOptions);
|
|
73
|
+
exec(cmd: string, opts?: {
|
|
74
|
+
timeoutMs?: number;
|
|
75
|
+
}): Promise<SandboxExecResult>;
|
|
76
|
+
static local(opts: {
|
|
77
|
+
workDir?: string;
|
|
78
|
+
baseDir?: string;
|
|
79
|
+
pwd?: string;
|
|
80
|
+
}): LocalSandbox;
|
|
81
|
+
watchFiles(paths: string[], listener: (event: {
|
|
82
|
+
path: string;
|
|
83
|
+
mtimeMs: number;
|
|
84
|
+
}) => void): Promise<string>;
|
|
85
|
+
unwatchFiles(id: string): void;
|
|
86
|
+
dispose(): Promise<void>;
|
|
87
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalSandbox = void 0;
|
|
4
|
+
// 危险命令模式 - 防止执行破坏性操作
|
|
5
|
+
const DANGEROUS_PATTERNS = [
|
|
6
|
+
/rm\s+-rf\s+\/($|\s)/, // rm -rf /
|
|
7
|
+
/sudo\s+/, // sudo 提权
|
|
8
|
+
/shutdown/, // 系统关机
|
|
9
|
+
/reboot/, // 系统重启
|
|
10
|
+
/mkfs\./, // 格式化文件系统
|
|
11
|
+
/dd\s+.*of=/, // dd 写入设备
|
|
12
|
+
/:\(\)\{\s*:\|\:&\s*\};:/, // fork bomb
|
|
13
|
+
/chmod\s+777\s+\//, // 修改根目录权限
|
|
14
|
+
/curl\s+.*\|\s*(bash|sh)/, // 管道执行远程脚本
|
|
15
|
+
/wget\s+.*\|\s*(bash|sh)/, // wget 执行远程脚本
|
|
16
|
+
/>\s*\/dev\/sda/, // 直接写入硬盘
|
|
17
|
+
/mkswap/, // 创建交换分区
|
|
18
|
+
/swapon/, // 启用交换分区
|
|
19
|
+
];
|
|
20
|
+
class LocalSandbox {
|
|
21
|
+
constructor(opts = {}) {
|
|
22
|
+
this.kind = 'local';
|
|
23
|
+
this.watchers = new Map();
|
|
24
|
+
const path = require('path');
|
|
25
|
+
this.workDir = path.resolve(opts.workDir || opts.baseDir || opts.pwd || process.cwd());
|
|
26
|
+
this.enforceBoundary = opts.enforceBoundary !== false;
|
|
27
|
+
const legacy = (opts.allowPaths || []).map((p) => path.resolve(p));
|
|
28
|
+
this.allowReadPaths = (opts.allowReadPaths || legacy).map((p) => path.resolve(p));
|
|
29
|
+
this.allowWritePaths = (opts.allowWritePaths || legacy).map((p) => path.resolve(p));
|
|
30
|
+
this.watchEnabled = opts.watchFiles !== false; // default true
|
|
31
|
+
this.fs = new LocalFS(this.workDir, {
|
|
32
|
+
enforceBoundary: this.enforceBoundary,
|
|
33
|
+
allowReadPaths: this.allowReadPaths,
|
|
34
|
+
allowWritePaths: this.allowWritePaths,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async exec(cmd, opts) {
|
|
38
|
+
// 安全检查:阻止危险命令
|
|
39
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
40
|
+
if (pattern.test(cmd)) {
|
|
41
|
+
const error = `Dangerous command blocked for security: ${cmd.slice(0, 100)}`;
|
|
42
|
+
return {
|
|
43
|
+
code: 1,
|
|
44
|
+
stdout: '',
|
|
45
|
+
stderr: error,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const { exec } = require('child_process');
|
|
50
|
+
const util = require('util');
|
|
51
|
+
const execPromise = util.promisify(exec);
|
|
52
|
+
const timeout = opts?.timeoutMs || 120000;
|
|
53
|
+
try {
|
|
54
|
+
const { stdout, stderr } = await execPromise(cmd, {
|
|
55
|
+
cwd: this.workDir,
|
|
56
|
+
timeout,
|
|
57
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
58
|
+
});
|
|
59
|
+
return { code: 0, stdout: stdout || '', stderr: stderr || '' };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
code: error.code || 1,
|
|
64
|
+
stdout: error.stdout || '',
|
|
65
|
+
stderr: error.stderr || error.message || '',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
static local(opts) {
|
|
70
|
+
return new LocalSandbox(opts);
|
|
71
|
+
}
|
|
72
|
+
async watchFiles(paths, listener) {
|
|
73
|
+
if (!this.watchEnabled) {
|
|
74
|
+
return `watch-disabled-${Date.now()}`;
|
|
75
|
+
}
|
|
76
|
+
const id = `watch-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
77
|
+
const fs = require('fs');
|
|
78
|
+
const watchers = [];
|
|
79
|
+
for (const path of paths) {
|
|
80
|
+
const resolved = this.fs.resolve(path);
|
|
81
|
+
if (!this.fs.isInside(resolved, 'read'))
|
|
82
|
+
continue;
|
|
83
|
+
const watcher = fs.watch(resolved, async () => {
|
|
84
|
+
try {
|
|
85
|
+
const stat = await this.fs.stat(resolved);
|
|
86
|
+
listener({ path: resolved, mtimeMs: stat.mtimeMs });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
listener({ path: resolved, mtimeMs: Date.now() });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
watchers.push(watcher);
|
|
93
|
+
}
|
|
94
|
+
this.watchers.set(id, {
|
|
95
|
+
paths,
|
|
96
|
+
close: () => watchers.forEach((w) => w.close()),
|
|
97
|
+
});
|
|
98
|
+
return id;
|
|
99
|
+
}
|
|
100
|
+
unwatchFiles(id) {
|
|
101
|
+
const entry = this.watchers.get(id);
|
|
102
|
+
if (entry) {
|
|
103
|
+
entry.close();
|
|
104
|
+
this.watchers.delete(id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async dispose() {
|
|
108
|
+
for (const entry of this.watchers.values()) {
|
|
109
|
+
entry.close();
|
|
110
|
+
}
|
|
111
|
+
this.watchers.clear();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.LocalSandbox = LocalSandbox;
|
|
115
|
+
class LocalFS {
|
|
116
|
+
constructor(workDir, options) {
|
|
117
|
+
this.workDir = workDir;
|
|
118
|
+
this.options = options;
|
|
119
|
+
}
|
|
120
|
+
resolve(p) {
|
|
121
|
+
const path = require('path');
|
|
122
|
+
if (path.isAbsolute(p))
|
|
123
|
+
return p;
|
|
124
|
+
return path.resolve(this.workDir, p);
|
|
125
|
+
}
|
|
126
|
+
isInside(p, access = 'write') {
|
|
127
|
+
const path = require('path');
|
|
128
|
+
const resolved = path.resolve(this.resolve(p)); // resolve 去除 ..
|
|
129
|
+
// 1. 检查是否在 workDir 内
|
|
130
|
+
const relativeToWork = path.relative(this.workDir, resolved);
|
|
131
|
+
if (!relativeToWork.startsWith('..') && !path.isAbsolute(relativeToWork)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// 2. 如果不强制边界检查,允许所有路径
|
|
135
|
+
if (!this.options.enforceBoundary)
|
|
136
|
+
return true;
|
|
137
|
+
// 3. 检查白名单(先 resolve 防止绕过)
|
|
138
|
+
const allow = access === 'read' ? this.options.allowReadPaths : this.options.allowWritePaths;
|
|
139
|
+
return allow.some((allowed) => {
|
|
140
|
+
const resolvedAllowed = path.resolve(allowed); // 先 resolve
|
|
141
|
+
const relative = path.relative(resolvedAllowed, resolved);
|
|
142
|
+
return !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async read(p) {
|
|
146
|
+
const fs = require('fs').promises;
|
|
147
|
+
const resolved = this.resolve(p);
|
|
148
|
+
if (!this.isInside(resolved, 'read')) {
|
|
149
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
150
|
+
}
|
|
151
|
+
return await fs.readFile(resolved, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
async write(p, content) {
|
|
154
|
+
const fs = require('fs').promises;
|
|
155
|
+
const path = require('path');
|
|
156
|
+
const resolved = this.resolve(p);
|
|
157
|
+
if (!this.isInside(resolved, 'write')) {
|
|
158
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
159
|
+
}
|
|
160
|
+
const dir = path.dirname(resolved);
|
|
161
|
+
await fs.mkdir(dir, { recursive: true });
|
|
162
|
+
await fs.writeFile(resolved, content, 'utf-8');
|
|
163
|
+
}
|
|
164
|
+
async rm(p, opts) {
|
|
165
|
+
const fs = require('fs').promises;
|
|
166
|
+
const resolved = this.resolve(p);
|
|
167
|
+
if (!this.isInside(resolved, 'write')) {
|
|
168
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
169
|
+
}
|
|
170
|
+
await fs.rm(resolved, { recursive: opts?.recursive ?? false, force: opts?.force ?? false });
|
|
171
|
+
}
|
|
172
|
+
temp(name) {
|
|
173
|
+
const path = require('path');
|
|
174
|
+
const tempName = name || `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
175
|
+
return path.relative(this.workDir, path.join(this.workDir, '.temp', tempName));
|
|
176
|
+
}
|
|
177
|
+
async stat(p) {
|
|
178
|
+
const fs = require('fs').promises;
|
|
179
|
+
const resolved = this.resolve(p);
|
|
180
|
+
if (!this.isInside(resolved, 'read')) {
|
|
181
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
182
|
+
}
|
|
183
|
+
const stat = await fs.stat(resolved);
|
|
184
|
+
return { mtimeMs: stat.mtimeMs };
|
|
185
|
+
}
|
|
186
|
+
async glob(pattern, opts) {
|
|
187
|
+
const path = require('path');
|
|
188
|
+
const cwd = opts?.cwd ? this.resolve(opts.cwd) : this.workDir;
|
|
189
|
+
let matches;
|
|
190
|
+
try {
|
|
191
|
+
const fg = require('fast-glob');
|
|
192
|
+
matches = await fg(pattern, {
|
|
193
|
+
cwd,
|
|
194
|
+
dot: opts?.dot ?? false,
|
|
195
|
+
absolute: true,
|
|
196
|
+
ignore: opts?.ignore,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
matches = await this.manualGlob(pattern, { cwd, dot: opts?.dot ?? false });
|
|
201
|
+
}
|
|
202
|
+
const filtered = matches.filter((entry) => this.isInside(entry, 'read'));
|
|
203
|
+
if (opts?.absolute) {
|
|
204
|
+
return filtered;
|
|
205
|
+
}
|
|
206
|
+
return filtered.map((entry) => path.relative(this.workDir, entry));
|
|
207
|
+
}
|
|
208
|
+
async manualGlob(pattern, opts) {
|
|
209
|
+
const fs = require('fs').promises;
|
|
210
|
+
const path = require('path');
|
|
211
|
+
const normalizedPattern = pattern.split(path.sep).join('/');
|
|
212
|
+
const results = [];
|
|
213
|
+
const walk = async (dir) => {
|
|
214
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
if (!opts.dot && entry.name.startsWith('.'))
|
|
217
|
+
continue;
|
|
218
|
+
const full = path.join(dir, entry.name);
|
|
219
|
+
const rel = path.relative(opts.cwd, full).split(path.sep).join('/');
|
|
220
|
+
if (matchesGlob(normalizedPattern, rel)) {
|
|
221
|
+
results.push(full);
|
|
222
|
+
}
|
|
223
|
+
if (entry.isDirectory()) {
|
|
224
|
+
await walk(full);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
await walk(opts.cwd);
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function matchesGlob(pattern, target) {
|
|
233
|
+
const pSegs = pattern.split('/');
|
|
234
|
+
const tSegs = target.split('/');
|
|
235
|
+
return matchSegments(pSegs, tSegs);
|
|
236
|
+
}
|
|
237
|
+
function matchSegments(pattern, target) {
|
|
238
|
+
if (pattern.length === 0)
|
|
239
|
+
return target.length === 0;
|
|
240
|
+
const [head, ...rest] = pattern;
|
|
241
|
+
if (head === '**') {
|
|
242
|
+
return (matchSegments(rest, target) ||
|
|
243
|
+
(target.length > 0 && matchSegments(pattern, target.slice(1))));
|
|
244
|
+
}
|
|
245
|
+
if (target.length === 0)
|
|
246
|
+
return false;
|
|
247
|
+
if (!matchSegment(head, target[0]))
|
|
248
|
+
return false;
|
|
249
|
+
return matchSegments(rest, target.slice(1));
|
|
250
|
+
}
|
|
251
|
+
function matchSegment(pattern, target) {
|
|
252
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
253
|
+
const regex = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
254
|
+
return new RegExp(`^${regex}$`).test(target);
|
|
255
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Message, Timeline, Snapshot, AgentInfo, ToolCallRecord, Bookmark, AgentChannel } from '../core/types';
|
|
2
|
+
import { TodoSnapshot } from '../core/todo';
|
|
3
|
+
import { SkillsState } from '../skills/types';
|
|
4
|
+
export interface HistoryWindow {
|
|
5
|
+
id: string;
|
|
6
|
+
messages: Message[];
|
|
7
|
+
events: Timeline[];
|
|
8
|
+
stats: {
|
|
9
|
+
messageCount: number;
|
|
10
|
+
tokenCount: number;
|
|
11
|
+
eventCount: number;
|
|
12
|
+
};
|
|
13
|
+
timestamp: number;
|
|
14
|
+
}
|
|
15
|
+
export interface CompressionRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
windowId: string;
|
|
18
|
+
config: {
|
|
19
|
+
model: string;
|
|
20
|
+
prompt: string;
|
|
21
|
+
threshold: number;
|
|
22
|
+
};
|
|
23
|
+
summary: string;
|
|
24
|
+
ratio: number;
|
|
25
|
+
recoveredFiles: string[];
|
|
26
|
+
timestamp: number;
|
|
27
|
+
}
|
|
28
|
+
export interface RecoveredFile {
|
|
29
|
+
path: string;
|
|
30
|
+
content: string;
|
|
31
|
+
mtime: number;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Store 接口定义 Agent 持久化的所有能力
|
|
36
|
+
*
|
|
37
|
+
* 设计原则:
|
|
38
|
+
* 1. 所有方法都是必需的,不使用可选方法
|
|
39
|
+
* 2. 职责清晰:运行时状态、历史管理、事件流、元数据管理
|
|
40
|
+
* 3. 实现无关:接口不暴露存储细节(如 WAL、文件格式等)
|
|
41
|
+
*/
|
|
42
|
+
export interface Store {
|
|
43
|
+
/** 保存对话消息 */
|
|
44
|
+
saveMessages(agentId: string, messages: Message[]): Promise<void>;
|
|
45
|
+
/** 加载对话消息 */
|
|
46
|
+
loadMessages(agentId: string): Promise<Message[]>;
|
|
47
|
+
/** 保存工具调用记录 */
|
|
48
|
+
saveToolCallRecords(agentId: string, records: ToolCallRecord[]): Promise<void>;
|
|
49
|
+
/** 加载工具调用记录 */
|
|
50
|
+
loadToolCallRecords(agentId: string): Promise<ToolCallRecord[]>;
|
|
51
|
+
/** 保存 Todo 快照 */
|
|
52
|
+
saveTodos(agentId: string, snapshot: TodoSnapshot): Promise<void>;
|
|
53
|
+
/** 加载 Todo 快照 */
|
|
54
|
+
loadTodos(agentId: string): Promise<TodoSnapshot | undefined>;
|
|
55
|
+
/** 追加事件到流中 */
|
|
56
|
+
appendEvent(agentId: string, timeline: Timeline): Promise<void>;
|
|
57
|
+
/** 读取事件流(支持 Bookmark 续读和 Channel 过滤) */
|
|
58
|
+
readEvents(agentId: string, opts?: {
|
|
59
|
+
since?: Bookmark;
|
|
60
|
+
channel?: AgentChannel;
|
|
61
|
+
}): AsyncIterable<Timeline>;
|
|
62
|
+
/** 保存历史窗口(压缩前的完整快照) */
|
|
63
|
+
saveHistoryWindow(agentId: string, window: HistoryWindow): Promise<void>;
|
|
64
|
+
/** 加载所有历史窗口 */
|
|
65
|
+
loadHistoryWindows(agentId: string): Promise<HistoryWindow[]>;
|
|
66
|
+
/** 保存压缩记录 */
|
|
67
|
+
saveCompressionRecord(agentId: string, record: CompressionRecord): Promise<void>;
|
|
68
|
+
/** 加载所有压缩记录 */
|
|
69
|
+
loadCompressionRecords(agentId: string): Promise<CompressionRecord[]>;
|
|
70
|
+
/** 保存恢复文件快照 */
|
|
71
|
+
saveRecoveredFile(agentId: string, file: RecoveredFile): Promise<void>;
|
|
72
|
+
/** 加载所有恢复文件 */
|
|
73
|
+
loadRecoveredFiles(agentId: string): Promise<RecoveredFile[]>;
|
|
74
|
+
/** 保存快照 */
|
|
75
|
+
saveSnapshot(agentId: string, snapshot: Snapshot): Promise<void>;
|
|
76
|
+
/** 加载指定快照 */
|
|
77
|
+
loadSnapshot(agentId: string, snapshotId: string): Promise<Snapshot | undefined>;
|
|
78
|
+
/** 列出所有快照 */
|
|
79
|
+
listSnapshots(agentId: string): Promise<Snapshot[]>;
|
|
80
|
+
/** 保存 Agent 元信息 */
|
|
81
|
+
saveInfo(agentId: string, info: AgentInfo): Promise<void>;
|
|
82
|
+
/** 加载 Agent 元信息 */
|
|
83
|
+
loadInfo(agentId: string): Promise<AgentInfo | undefined>;
|
|
84
|
+
/** 保存 Skills 状态 */
|
|
85
|
+
saveSkillsState(agentId: string, state: SkillsState): Promise<void>;
|
|
86
|
+
/** 加载 Skills 状态 */
|
|
87
|
+
loadSkillsState(agentId: string): Promise<SkillsState | undefined>;
|
|
88
|
+
/** 检查 Agent 是否存在 */
|
|
89
|
+
exists(agentId: string): Promise<boolean>;
|
|
90
|
+
/** 删除 Agent 所有数据 */
|
|
91
|
+
delete(agentId: string): Promise<void>;
|
|
92
|
+
/** 列出所有 Agent ID */
|
|
93
|
+
list(prefix?: string): Promise<string[]>;
|
|
94
|
+
}
|
|
95
|
+
export declare class JSONStore implements Store {
|
|
96
|
+
private baseDir;
|
|
97
|
+
private flushIntervalMs;
|
|
98
|
+
private eventWriters;
|
|
99
|
+
private walQueue;
|
|
100
|
+
private walRecovered;
|
|
101
|
+
constructor(baseDir: string, flushIntervalMs?: number);
|
|
102
|
+
private getAgentDir;
|
|
103
|
+
private getRuntimePath;
|
|
104
|
+
private getEventsPath;
|
|
105
|
+
private getHistoryDir;
|
|
106
|
+
private getSnapshotsDir;
|
|
107
|
+
private getMetaPath;
|
|
108
|
+
saveMessages(agentId: string, messages: Message[]): Promise<void>;
|
|
109
|
+
loadMessages(agentId: string): Promise<Message[]>;
|
|
110
|
+
saveToolCallRecords(agentId: string, records: ToolCallRecord[]): Promise<void>;
|
|
111
|
+
loadToolCallRecords(agentId: string): Promise<ToolCallRecord[]>;
|
|
112
|
+
saveTodos(agentId: string, snapshot: TodoSnapshot): Promise<void>;
|
|
113
|
+
loadTodos(agentId: string): Promise<TodoSnapshot | undefined>;
|
|
114
|
+
saveSkillsState(agentId: string, state: SkillsState): Promise<void>;
|
|
115
|
+
loadSkillsState(agentId: string): Promise<SkillsState | undefined>;
|
|
116
|
+
private saveWithWal;
|
|
117
|
+
private loadWithWal;
|
|
118
|
+
private queueWalWrite;
|
|
119
|
+
/**
|
|
120
|
+
* Store 初始化时主动恢复所有 WAL 文件
|
|
121
|
+
*/
|
|
122
|
+
private recoverAllWALs;
|
|
123
|
+
/**
|
|
124
|
+
* 恢复运行时数据的 WAL
|
|
125
|
+
*/
|
|
126
|
+
private recoverRuntimeWAL;
|
|
127
|
+
/**
|
|
128
|
+
* 恢复事件流的 WAL
|
|
129
|
+
*/
|
|
130
|
+
private recoverEventWALFile;
|
|
131
|
+
appendEvent(agentId: string, timeline: Timeline): Promise<void>;
|
|
132
|
+
readEvents(agentId: string, opts?: {
|
|
133
|
+
since?: Bookmark;
|
|
134
|
+
channel?: AgentChannel;
|
|
135
|
+
}): AsyncIterable<Timeline>;
|
|
136
|
+
private getEventWriters;
|
|
137
|
+
private flushEvents;
|
|
138
|
+
private recoverEventWal;
|
|
139
|
+
private writeEventWal;
|
|
140
|
+
saveHistoryWindow(agentId: string, window: HistoryWindow): Promise<void>;
|
|
141
|
+
loadHistoryWindows(agentId: string): Promise<HistoryWindow[]>;
|
|
142
|
+
saveCompressionRecord(agentId: string, record: CompressionRecord): Promise<void>;
|
|
143
|
+
loadCompressionRecords(agentId: string): Promise<CompressionRecord[]>;
|
|
144
|
+
saveRecoveredFile(agentId: string, file: RecoveredFile): Promise<void>;
|
|
145
|
+
loadRecoveredFiles(agentId: string): Promise<RecoveredFile[]>;
|
|
146
|
+
saveSnapshot(agentId: string, snapshot: Snapshot): Promise<void>;
|
|
147
|
+
loadSnapshot(agentId: string, snapshotId: string): Promise<Snapshot | undefined>;
|
|
148
|
+
listSnapshots(agentId: string): Promise<Snapshot[]>;
|
|
149
|
+
saveInfo(agentId: string, info: AgentInfo): Promise<void>;
|
|
150
|
+
loadInfo(agentId: string): Promise<AgentInfo | undefined>;
|
|
151
|
+
exists(agentId: string): Promise<boolean>;
|
|
152
|
+
delete(agentId: string): Promise<void>;
|
|
153
|
+
list(prefix?: string): Promise<string[]>;
|
|
154
|
+
}
|