@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,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FilePool = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
class FilePool {
|
|
6
|
+
constructor(sandbox, opts) {
|
|
7
|
+
this.sandbox = sandbox;
|
|
8
|
+
this.records = new Map();
|
|
9
|
+
this.watchers = new Map();
|
|
10
|
+
this.watchPending = new Map(); // per-path 锁,防止并发创建 watcher
|
|
11
|
+
this.watchEnabled = opts?.watch ?? true;
|
|
12
|
+
this.onChange = opts?.onChange;
|
|
13
|
+
}
|
|
14
|
+
async getMtime(path) {
|
|
15
|
+
try {
|
|
16
|
+
const stat = await this.sandbox.fs.stat(path);
|
|
17
|
+
return stat.mtimeMs;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async recordRead(path) {
|
|
24
|
+
const resolved = this.sandbox.fs.resolve(path);
|
|
25
|
+
const record = this.records.get(resolved) || { path: resolved };
|
|
26
|
+
record.lastRead = Date.now();
|
|
27
|
+
record.lastReadMtime = await this.getMtime(resolved);
|
|
28
|
+
record.lastKnownMtime = record.lastReadMtime;
|
|
29
|
+
this.records.set(resolved, record);
|
|
30
|
+
await this.ensureWatch(resolved);
|
|
31
|
+
}
|
|
32
|
+
async recordEdit(path) {
|
|
33
|
+
const resolved = this.sandbox.fs.resolve(path);
|
|
34
|
+
const record = this.records.get(resolved) || { path: resolved };
|
|
35
|
+
record.lastEdit = Date.now();
|
|
36
|
+
record.lastKnownMtime = await this.getMtime(resolved);
|
|
37
|
+
this.records.set(resolved, record);
|
|
38
|
+
await this.ensureWatch(resolved);
|
|
39
|
+
}
|
|
40
|
+
async validateWrite(path) {
|
|
41
|
+
const resolved = this.sandbox.fs.resolve(path);
|
|
42
|
+
const record = this.records.get(resolved);
|
|
43
|
+
const currentMtime = await this.getMtime(resolved);
|
|
44
|
+
if (!record) {
|
|
45
|
+
return { isFresh: true, currentMtime };
|
|
46
|
+
}
|
|
47
|
+
const isFresh = record.lastRead !== undefined &&
|
|
48
|
+
(currentMtime === undefined || record.lastReadMtime === undefined || currentMtime === record.lastReadMtime);
|
|
49
|
+
return {
|
|
50
|
+
isFresh,
|
|
51
|
+
lastRead: record.lastRead,
|
|
52
|
+
lastEdit: record.lastEdit,
|
|
53
|
+
currentMtime,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async checkFreshness(path) {
|
|
57
|
+
const resolved = this.sandbox.fs.resolve(path);
|
|
58
|
+
const record = this.records.get(resolved);
|
|
59
|
+
const currentMtime = await this.getMtime(resolved);
|
|
60
|
+
if (!record) {
|
|
61
|
+
return { isFresh: false, currentMtime };
|
|
62
|
+
}
|
|
63
|
+
const isFresh = record.lastRead !== undefined &&
|
|
64
|
+
(currentMtime === undefined || record.lastKnownMtime === undefined || currentMtime === record.lastKnownMtime);
|
|
65
|
+
return {
|
|
66
|
+
isFresh,
|
|
67
|
+
lastRead: record.lastRead,
|
|
68
|
+
lastEdit: record.lastEdit,
|
|
69
|
+
currentMtime,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
getTrackedFiles() {
|
|
73
|
+
return Array.from(this.records.keys());
|
|
74
|
+
}
|
|
75
|
+
async ensureWatch(path) {
|
|
76
|
+
if (!this.watchEnabled)
|
|
77
|
+
return;
|
|
78
|
+
if (!this.sandbox.watchFiles)
|
|
79
|
+
return;
|
|
80
|
+
if (this.watchers.has(path))
|
|
81
|
+
return;
|
|
82
|
+
// 检查是否有正在进行的 watch 操作(per-path 锁)
|
|
83
|
+
const pending = this.watchPending.get(path);
|
|
84
|
+
if (pending) {
|
|
85
|
+
await pending; // 等待已有操作完成
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// 创建 watch 操作并存储 Promise
|
|
89
|
+
const watchPromise = this.doWatch(path);
|
|
90
|
+
this.watchPending.set(path, watchPromise);
|
|
91
|
+
try {
|
|
92
|
+
await watchPromise;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.watchPending.delete(path);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async doWatch(path) {
|
|
99
|
+
// 再次检查(可能在等待期间已被设置)
|
|
100
|
+
if (this.watchers.has(path))
|
|
101
|
+
return;
|
|
102
|
+
if (!this.sandbox.watchFiles)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
const id = await this.sandbox.watchFiles([path], (event) => {
|
|
106
|
+
const record = this.records.get(path);
|
|
107
|
+
if (record) {
|
|
108
|
+
record.lastKnownMtime = event.mtimeMs;
|
|
109
|
+
}
|
|
110
|
+
this.onChange?.({ path, mtime: event.mtimeMs });
|
|
111
|
+
});
|
|
112
|
+
this.watchers.set(path, id);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
// 记录 watch 失败,但不中断流程
|
|
116
|
+
logger_1.logger.warn(`[FilePool] Failed to watch file: ${path}`, err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
getAccessedFiles() {
|
|
120
|
+
return Array.from(this.records.values())
|
|
121
|
+
.filter((r) => r.lastKnownMtime !== undefined)
|
|
122
|
+
.map((r) => ({ path: r.path, mtime: r.lastKnownMtime }));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.FilePool = FilePool;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HookManager = void 0;
|
|
4
|
+
class HookManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.hooks = [];
|
|
7
|
+
}
|
|
8
|
+
register(hooks, origin = 'agent') {
|
|
9
|
+
this.hooks.push({ hooks, origin });
|
|
10
|
+
}
|
|
11
|
+
getRegistered() {
|
|
12
|
+
return this.hooks.map(({ hooks, origin }) => ({
|
|
13
|
+
origin,
|
|
14
|
+
names: [
|
|
15
|
+
hooks.preToolUse && 'preToolUse',
|
|
16
|
+
hooks.postToolUse && 'postToolUse',
|
|
17
|
+
hooks.preModel && 'preModel',
|
|
18
|
+
hooks.postModel && 'postModel',
|
|
19
|
+
].filter(Boolean),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
async runPreToolUse(call, ctx) {
|
|
23
|
+
for (const { hooks } of this.hooks) {
|
|
24
|
+
if (hooks.preToolUse) {
|
|
25
|
+
const result = await hooks.preToolUse(call, ctx);
|
|
26
|
+
if (result)
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
async runPostToolUse(outcome, ctx) {
|
|
33
|
+
let current = outcome;
|
|
34
|
+
for (const { hooks } of this.hooks) {
|
|
35
|
+
if (hooks.postToolUse) {
|
|
36
|
+
const result = await hooks.postToolUse(current, ctx);
|
|
37
|
+
if (result && typeof result === 'object') {
|
|
38
|
+
if ('replace' in result) {
|
|
39
|
+
current = result.replace;
|
|
40
|
+
}
|
|
41
|
+
else if ('update' in result) {
|
|
42
|
+
current = { ...current, ...result.update };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return current;
|
|
48
|
+
}
|
|
49
|
+
async runPreModel(request) {
|
|
50
|
+
for (const { hooks } of this.hooks) {
|
|
51
|
+
if (hooks.preModel) {
|
|
52
|
+
await hooks.preModel(request);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async runPostModel(response) {
|
|
57
|
+
for (const { hooks } of this.hooks) {
|
|
58
|
+
if (hooks.postModel) {
|
|
59
|
+
await hooks.postModel(response);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async runMessagesChanged(snapshot) {
|
|
64
|
+
for (const { hooks } of this.hooks) {
|
|
65
|
+
if (hooks.messagesChanged) {
|
|
66
|
+
await hooks.messagesChanged(snapshot);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.HookManager = HookManager;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.permissionModes = exports.PermissionModeRegistry = void 0;
|
|
4
|
+
class PermissionModeRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.handlers = new Map();
|
|
7
|
+
this.customModes = new Set();
|
|
8
|
+
}
|
|
9
|
+
register(mode, handler, isBuiltIn = false) {
|
|
10
|
+
this.handlers.set(mode, handler);
|
|
11
|
+
if (!isBuiltIn) {
|
|
12
|
+
this.customModes.add(mode);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
get(mode) {
|
|
16
|
+
return this.handlers.get(mode);
|
|
17
|
+
}
|
|
18
|
+
list() {
|
|
19
|
+
return Array.from(this.handlers.keys());
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 序列化权限模式配置
|
|
23
|
+
* 仅序列化自定义模式的名称,内置模式在 Resume 时自动恢复
|
|
24
|
+
*/
|
|
25
|
+
serialize() {
|
|
26
|
+
return Array.from(this.handlers.keys()).map(name => ({
|
|
27
|
+
name,
|
|
28
|
+
builtIn: !this.customModes.has(name)
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 验证序列化的权限模式是否可恢复
|
|
33
|
+
* 返回缺失的自定义模式列表
|
|
34
|
+
*/
|
|
35
|
+
validateRestore(serialized) {
|
|
36
|
+
const missing = [];
|
|
37
|
+
for (const mode of serialized) {
|
|
38
|
+
if (!mode.builtIn && !this.handlers.has(mode.name)) {
|
|
39
|
+
missing.push(mode.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return missing;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.PermissionModeRegistry = PermissionModeRegistry;
|
|
46
|
+
exports.permissionModes = new PermissionModeRegistry();
|
|
47
|
+
const MUTATING_ACCESS = new Set(['write', 'execute', 'manage', 'mutate']);
|
|
48
|
+
// 内置模式
|
|
49
|
+
exports.permissionModes.register('auto', () => 'allow', true);
|
|
50
|
+
exports.permissionModes.register('approval', () => 'ask', true);
|
|
51
|
+
exports.permissionModes.register('readonly', (ctx) => {
|
|
52
|
+
const metadata = ctx.descriptor?.metadata || {};
|
|
53
|
+
if (metadata.mutates === true)
|
|
54
|
+
return 'deny';
|
|
55
|
+
if (metadata.mutates === false)
|
|
56
|
+
return 'allow';
|
|
57
|
+
const access = typeof metadata.access === 'string' ? metadata.access.toLowerCase() : undefined;
|
|
58
|
+
if (access && MUTATING_ACCESS.has(access))
|
|
59
|
+
return 'deny';
|
|
60
|
+
return 'ask';
|
|
61
|
+
}, true);
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentPool = void 0;
|
|
4
|
+
const agent_1 = require("./agent");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
class AgentPool {
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this.agents = new Map();
|
|
9
|
+
this.deps = opts.dependencies;
|
|
10
|
+
this.maxAgents = opts.maxAgents || 50;
|
|
11
|
+
}
|
|
12
|
+
async create(agentId, config) {
|
|
13
|
+
if (this.agents.has(agentId)) {
|
|
14
|
+
throw new Error(`Agent already exists: ${agentId}`);
|
|
15
|
+
}
|
|
16
|
+
if (this.agents.size >= this.maxAgents) {
|
|
17
|
+
throw new Error(`Pool is full (max ${this.maxAgents} agents)`);
|
|
18
|
+
}
|
|
19
|
+
const agent = await agent_1.Agent.create({ ...config, agentId }, this.deps);
|
|
20
|
+
this.agents.set(agentId, agent);
|
|
21
|
+
return agent;
|
|
22
|
+
}
|
|
23
|
+
get(agentId) {
|
|
24
|
+
return this.agents.get(agentId);
|
|
25
|
+
}
|
|
26
|
+
list(opts) {
|
|
27
|
+
const ids = Array.from(this.agents.keys());
|
|
28
|
+
return opts?.prefix ? ids.filter((id) => id.startsWith(opts.prefix)) : ids;
|
|
29
|
+
}
|
|
30
|
+
async status(agentId) {
|
|
31
|
+
const agent = this.agents.get(agentId);
|
|
32
|
+
return agent ? await agent.status() : undefined;
|
|
33
|
+
}
|
|
34
|
+
async fork(agentId, snapshotSel) {
|
|
35
|
+
const agent = this.agents.get(agentId);
|
|
36
|
+
if (!agent) {
|
|
37
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
38
|
+
}
|
|
39
|
+
return agent.fork(snapshotSel);
|
|
40
|
+
}
|
|
41
|
+
async resume(agentId, config, opts) {
|
|
42
|
+
// 1. Check if already in pool
|
|
43
|
+
if (this.agents.has(agentId)) {
|
|
44
|
+
return this.agents.get(agentId);
|
|
45
|
+
}
|
|
46
|
+
// 2. Check pool capacity
|
|
47
|
+
if (this.agents.size >= this.maxAgents) {
|
|
48
|
+
throw new Error(`Pool is full (max ${this.maxAgents} agents)`);
|
|
49
|
+
}
|
|
50
|
+
// 3. Verify session exists
|
|
51
|
+
const exists = await this.deps.store.exists(agentId);
|
|
52
|
+
if (!exists) {
|
|
53
|
+
throw new Error(`Agent not found in store: ${agentId}`);
|
|
54
|
+
}
|
|
55
|
+
// 4. Use Agent.resume() to restore
|
|
56
|
+
const agent = await agent_1.Agent.resume(agentId, { ...config, agentId }, this.deps, opts);
|
|
57
|
+
// 5. Add to pool
|
|
58
|
+
this.agents.set(agentId, agent);
|
|
59
|
+
return agent;
|
|
60
|
+
}
|
|
61
|
+
async resumeAll(configFactory, opts) {
|
|
62
|
+
const agentIds = await this.deps.store.list();
|
|
63
|
+
const resumed = [];
|
|
64
|
+
for (const agentId of agentIds) {
|
|
65
|
+
if (this.agents.size >= this.maxAgents)
|
|
66
|
+
break;
|
|
67
|
+
if (this.agents.has(agentId))
|
|
68
|
+
continue;
|
|
69
|
+
try {
|
|
70
|
+
const config = configFactory(agentId);
|
|
71
|
+
const agent = await this.resume(agentId, config, opts);
|
|
72
|
+
resumed.push(agent);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger_1.logger.error(`Failed to resume ${agentId}:`, error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return resumed;
|
|
79
|
+
}
|
|
80
|
+
async delete(agentId) {
|
|
81
|
+
this.agents.delete(agentId);
|
|
82
|
+
await this.deps.store.delete(agentId);
|
|
83
|
+
}
|
|
84
|
+
size() {
|
|
85
|
+
return this.agents.size;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Gracefully shutdown all agents in the pool
|
|
89
|
+
* 1. Stop accepting new operations
|
|
90
|
+
* 2. Wait for running agents to complete current step
|
|
91
|
+
* 3. Persist all agent states
|
|
92
|
+
* 4. Optionally save running agents list for recovery
|
|
93
|
+
*/
|
|
94
|
+
async gracefulShutdown(opts) {
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
const timeout = opts?.timeout ?? 30000;
|
|
97
|
+
const saveRunningList = opts?.saveRunningList ?? true;
|
|
98
|
+
const forceInterrupt = opts?.forceInterrupt ?? true;
|
|
99
|
+
const result = {
|
|
100
|
+
completed: [],
|
|
101
|
+
interrupted: [],
|
|
102
|
+
failed: [],
|
|
103
|
+
durationMs: 0,
|
|
104
|
+
};
|
|
105
|
+
const agentIds = Array.from(this.agents.keys());
|
|
106
|
+
logger_1.logger.info(`[AgentPool] Starting graceful shutdown for ${agentIds.length} agents`);
|
|
107
|
+
// Group agents by state
|
|
108
|
+
const workingAgents = [];
|
|
109
|
+
const readyAgents = [];
|
|
110
|
+
for (const [id, agent] of this.agents) {
|
|
111
|
+
const status = await agent.status();
|
|
112
|
+
if (status.state === 'WORKING') {
|
|
113
|
+
workingAgents.push({ id, agent });
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
readyAgents.push({ id, agent });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 1. Persist ready agents immediately
|
|
120
|
+
for (const { id, agent } of readyAgents) {
|
|
121
|
+
try {
|
|
122
|
+
await this.persistAgentState(agent);
|
|
123
|
+
result.completed.push(id);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
logger_1.logger.error(`[AgentPool] Failed to persist agent ${id}:`, error);
|
|
127
|
+
result.failed.push(id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// 2. Wait for working agents with timeout
|
|
131
|
+
if (workingAgents.length > 0) {
|
|
132
|
+
logger_1.logger.info(`[AgentPool] Waiting for ${workingAgents.length} working agents...`);
|
|
133
|
+
const waitPromises = workingAgents.map(async ({ id, agent }) => {
|
|
134
|
+
try {
|
|
135
|
+
const completed = await this.waitForAgentReady(agent, timeout);
|
|
136
|
+
if (completed) {
|
|
137
|
+
await this.persistAgentState(agent);
|
|
138
|
+
return { id, status: 'completed' };
|
|
139
|
+
}
|
|
140
|
+
else if (forceInterrupt) {
|
|
141
|
+
await agent.interrupt({ note: 'Graceful shutdown timeout' });
|
|
142
|
+
await this.persistAgentState(agent);
|
|
143
|
+
return { id, status: 'interrupted' };
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
return { id, status: 'interrupted' };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
logger_1.logger.error(`[AgentPool] Error during shutdown for agent ${id}:`, error);
|
|
151
|
+
return { id, status: 'failed' };
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const results = await Promise.all(waitPromises);
|
|
155
|
+
for (const { id, status } of results) {
|
|
156
|
+
if (status === 'completed') {
|
|
157
|
+
result.completed.push(id);
|
|
158
|
+
}
|
|
159
|
+
else if (status === 'interrupted') {
|
|
160
|
+
result.interrupted.push(id);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
result.failed.push(id);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// 3. Save running agents list for recovery
|
|
168
|
+
if (saveRunningList) {
|
|
169
|
+
try {
|
|
170
|
+
await this.saveRunningAgentsList(agentIds);
|
|
171
|
+
logger_1.logger.info(`[AgentPool] Saved running agents list: ${agentIds.length} agents`);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
logger_1.logger.error(`[AgentPool] Failed to save running agents list:`, error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
result.durationMs = Date.now() - startTime;
|
|
178
|
+
logger_1.logger.info(`[AgentPool] Graceful shutdown completed in ${result.durationMs}ms`, {
|
|
179
|
+
completed: result.completed.length,
|
|
180
|
+
interrupted: result.interrupted.length,
|
|
181
|
+
failed: result.failed.length,
|
|
182
|
+
});
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Resume agents from a previous graceful shutdown
|
|
187
|
+
* Reads the running agents list and resumes each agent
|
|
188
|
+
*/
|
|
189
|
+
async resumeFromShutdown(configFactory, opts) {
|
|
190
|
+
const runningList = await this.loadRunningAgentsList();
|
|
191
|
+
if (!runningList || runningList.length === 0) {
|
|
192
|
+
logger_1.logger.info('[AgentPool] No running agents list found, nothing to resume');
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
logger_1.logger.info(`[AgentPool] Resuming ${runningList.length} agents from shutdown`);
|
|
196
|
+
const resumed = [];
|
|
197
|
+
for (const agentId of runningList) {
|
|
198
|
+
if (this.agents.size >= this.maxAgents) {
|
|
199
|
+
logger_1.logger.warn(`[AgentPool] Pool is full, cannot resume more agents`);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const config = configFactory(agentId);
|
|
204
|
+
const agent = await this.resume(agentId, config, {
|
|
205
|
+
autoRun: opts?.autoRun ?? false,
|
|
206
|
+
strategy: opts?.strategy ?? 'crash',
|
|
207
|
+
});
|
|
208
|
+
resumed.push(agent);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
logger_1.logger.error(`[AgentPool] Failed to resume agent ${agentId}:`, error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Clear the running agents list after successful resume
|
|
215
|
+
await this.clearRunningAgentsList();
|
|
216
|
+
logger_1.logger.info(`[AgentPool] Resumed ${resumed.length}/${runningList.length} agents`);
|
|
217
|
+
return resumed;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Register signal handlers for graceful shutdown
|
|
221
|
+
* Call this in your server setup code
|
|
222
|
+
*/
|
|
223
|
+
registerShutdownHandlers(configFactory, opts) {
|
|
224
|
+
const handler = async (signal) => {
|
|
225
|
+
logger_1.logger.info(`[AgentPool] Received ${signal}, initiating graceful shutdown...`);
|
|
226
|
+
try {
|
|
227
|
+
const result = await this.gracefulShutdown(opts);
|
|
228
|
+
logger_1.logger.info(`[AgentPool] Shutdown complete:`, result);
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
logger_1.logger.error(`[AgentPool] Shutdown failed:`, error);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
process.on('SIGTERM', () => handler('SIGTERM'));
|
|
237
|
+
process.on('SIGINT', () => handler('SIGINT'));
|
|
238
|
+
logger_1.logger.info('[AgentPool] Shutdown handlers registered for SIGTERM and SIGINT');
|
|
239
|
+
}
|
|
240
|
+
// ========== Private Helper Methods ==========
|
|
241
|
+
async waitForAgentReady(agent, timeout) {
|
|
242
|
+
const startTime = Date.now();
|
|
243
|
+
const pollInterval = 100; // ms
|
|
244
|
+
while (Date.now() - startTime < timeout) {
|
|
245
|
+
const status = await agent.status();
|
|
246
|
+
if (status.state !== 'WORKING') {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
await this.sleep(pollInterval);
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
async persistAgentState(agent) {
|
|
254
|
+
// Agent's internal persist methods are private, so we rely on the fact that
|
|
255
|
+
// state is automatically persisted during normal operation.
|
|
256
|
+
// This is a no-op placeholder for potential future explicit persist calls.
|
|
257
|
+
// The agent's state is already persisted via WAL mechanism.
|
|
258
|
+
}
|
|
259
|
+
async saveRunningAgentsList(agentIds) {
|
|
260
|
+
const meta = {
|
|
261
|
+
agentIds,
|
|
262
|
+
shutdownAt: new Date().toISOString(),
|
|
263
|
+
version: '1.0.0',
|
|
264
|
+
};
|
|
265
|
+
// Use the store's saveInfo to persist to a special key
|
|
266
|
+
// We use a well-known agent ID prefix for pool metadata
|
|
267
|
+
const poolMetaId = '__pool_meta__';
|
|
268
|
+
await this.deps.store.saveInfo(poolMetaId, {
|
|
269
|
+
agentId: poolMetaId,
|
|
270
|
+
templateId: '__pool_meta__',
|
|
271
|
+
createdAt: new Date().toISOString(),
|
|
272
|
+
runningAgents: meta,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async loadRunningAgentsList() {
|
|
276
|
+
const poolMetaId = '__pool_meta__';
|
|
277
|
+
try {
|
|
278
|
+
const info = await this.deps.store.loadInfo(poolMetaId);
|
|
279
|
+
if (info && info.runningAgents) {
|
|
280
|
+
return info.runningAgents.agentIds;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Ignore errors, return null
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
async clearRunningAgentsList() {
|
|
289
|
+
const poolMetaId = '__pool_meta__';
|
|
290
|
+
try {
|
|
291
|
+
await this.deps.store.delete(poolMetaId);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Ignore errors
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
sleep(ms) {
|
|
298
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.AgentPool = AgentPool;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Room = void 0;
|
|
4
|
+
class Room {
|
|
5
|
+
constructor(pool) {
|
|
6
|
+
this.pool = pool;
|
|
7
|
+
this.members = new Map();
|
|
8
|
+
}
|
|
9
|
+
join(name, agentId) {
|
|
10
|
+
if (this.members.has(name)) {
|
|
11
|
+
throw new Error(`Member already exists: ${name}`);
|
|
12
|
+
}
|
|
13
|
+
this.members.set(name, agentId);
|
|
14
|
+
}
|
|
15
|
+
leave(name) {
|
|
16
|
+
this.members.delete(name);
|
|
17
|
+
}
|
|
18
|
+
async say(from, text) {
|
|
19
|
+
const mentions = this.extractMentions(text);
|
|
20
|
+
if (mentions.length > 0) {
|
|
21
|
+
// Directed message
|
|
22
|
+
for (const mention of mentions) {
|
|
23
|
+
const agentId = this.members.get(mention);
|
|
24
|
+
if (agentId) {
|
|
25
|
+
const agent = this.pool.get(agentId);
|
|
26
|
+
if (agent) {
|
|
27
|
+
await agent.complete(`[from:${from}] ${text}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Broadcast to all except sender
|
|
34
|
+
for (const [name, agentId] of this.members) {
|
|
35
|
+
if (name !== from) {
|
|
36
|
+
const agent = this.pool.get(agentId);
|
|
37
|
+
if (agent) {
|
|
38
|
+
await agent.complete(`[from:${from}] ${text}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
getMembers() {
|
|
45
|
+
return Array.from(this.members.entries()).map(([name, agentId]) => ({ name, agentId }));
|
|
46
|
+
}
|
|
47
|
+
extractMentions(text) {
|
|
48
|
+
const regex = /@(\w+)/g;
|
|
49
|
+
const mentions = [];
|
|
50
|
+
let match;
|
|
51
|
+
while ((match = regex.exec(text)) !== null) {
|
|
52
|
+
mentions.push(match[1]);
|
|
53
|
+
}
|
|
54
|
+
return mentions;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.Room = Room;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scheduler = void 0;
|
|
4
|
+
class Scheduler {
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
this.stepTasks = new Map();
|
|
7
|
+
this.listeners = new Set();
|
|
8
|
+
this.queued = Promise.resolve();
|
|
9
|
+
this.onTrigger = opts?.onTrigger;
|
|
10
|
+
}
|
|
11
|
+
everySteps(every, callback) {
|
|
12
|
+
if (!Number.isFinite(every) || every <= 0) {
|
|
13
|
+
throw new Error('everySteps: interval must be positive');
|
|
14
|
+
}
|
|
15
|
+
const id = this.generateId('steps');
|
|
16
|
+
this.stepTasks.set(id, {
|
|
17
|
+
id,
|
|
18
|
+
every,
|
|
19
|
+
callback,
|
|
20
|
+
lastTriggered: 0,
|
|
21
|
+
});
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
onStep(callback) {
|
|
25
|
+
this.listeners.add(callback);
|
|
26
|
+
return () => this.listeners.delete(callback);
|
|
27
|
+
}
|
|
28
|
+
enqueue(callback) {
|
|
29
|
+
this.queued = this.queued.then(() => Promise.resolve(callback())).catch(() => undefined);
|
|
30
|
+
}
|
|
31
|
+
notifyStep(stepCount) {
|
|
32
|
+
for (const listener of this.listeners) {
|
|
33
|
+
void Promise.resolve(listener({ stepCount }));
|
|
34
|
+
}
|
|
35
|
+
for (const task of this.stepTasks.values()) {
|
|
36
|
+
const shouldTrigger = stepCount - task.lastTriggered >= task.every;
|
|
37
|
+
if (!shouldTrigger)
|
|
38
|
+
continue;
|
|
39
|
+
task.lastTriggered = stepCount;
|
|
40
|
+
void Promise.resolve(task.callback({ stepCount }));
|
|
41
|
+
this.onTrigger?.({ taskId: task.id, spec: `steps:${task.every}`, kind: 'steps' });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
cancel(taskId) {
|
|
45
|
+
this.stepTasks.delete(taskId);
|
|
46
|
+
}
|
|
47
|
+
clear() {
|
|
48
|
+
this.stepTasks.clear();
|
|
49
|
+
this.listeners.clear();
|
|
50
|
+
}
|
|
51
|
+
notifyExternalTrigger(info) {
|
|
52
|
+
this.onTrigger?.(info);
|
|
53
|
+
}
|
|
54
|
+
generateId(prefix) {
|
|
55
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.Scheduler = Scheduler;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Skills 模块导出
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SandboxFileManager = exports.OperationStatus = exports.OperationType = exports.OperationQueue = exports.SkillsManagementManager = exports.generateSkillsMetadataXml = exports.SkillsManager = void 0;
|
|
7
|
+
// 路径2: Agent使用(现有模块)
|
|
8
|
+
var manager_1 = require("./manager");
|
|
9
|
+
Object.defineProperty(exports, "SkillsManager", { enumerable: true, get: function () { return manager_1.SkillsManager; } });
|
|
10
|
+
var xml_generator_1 = require("./xml-generator");
|
|
11
|
+
Object.defineProperty(exports, "generateSkillsMetadataXml", { enumerable: true, get: function () { return xml_generator_1.generateSkillsMetadataXml; } });
|
|
12
|
+
// 路径1: 技能管理(新增模块)
|
|
13
|
+
var management_manager_1 = require("./management-manager");
|
|
14
|
+
Object.defineProperty(exports, "SkillsManagementManager", { enumerable: true, get: function () { return management_manager_1.SkillsManagementManager; } });
|
|
15
|
+
var operation_queue_1 = require("./operation-queue");
|
|
16
|
+
Object.defineProperty(exports, "OperationQueue", { enumerable: true, get: function () { return operation_queue_1.OperationQueue; } });
|
|
17
|
+
Object.defineProperty(exports, "OperationType", { enumerable: true, get: function () { return operation_queue_1.OperationType; } });
|
|
18
|
+
Object.defineProperty(exports, "OperationStatus", { enumerable: true, get: function () { return operation_queue_1.OperationStatus; } });
|
|
19
|
+
var sandbox_file_manager_1 = require("./sandbox-file-manager");
|
|
20
|
+
Object.defineProperty(exports, "SandboxFileManager", { enumerable: true, get: function () { return sandbox_file_manager_1.SandboxFileManager; } });
|