aws-runtime-bridge 1.9.33 → 1.9.40
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/adapter/AcodeSdkAdapter.d.ts +35 -1
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +417 -40
- package/dist/adapter/ClaudeSdkAdapter.d.ts +6 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +23 -1
- package/dist/adapter/idle-poll-loop.d.ts +2 -1
- package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
- package/dist/adapter/types.d.ts +65 -3
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +5 -4
- package/dist/index.js +4 -0
- package/dist/routes/acode-todos.d.ts +11 -0
- package/dist/routes/acode-todos.d.ts.map +1 -0
- package/dist/routes/acode-todos.js +75 -0
- package/dist/routes/memory.d.ts +1 -1
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +3 -3
- package/dist/routes/pty.d.ts.map +1 -1
- package/dist/routes/pty.js +9 -0
- package/dist/routes/sub-agents.d.ts +14 -0
- package/dist/routes/sub-agents.d.ts.map +1 -0
- package/dist/routes/sub-agents.js +253 -0
- package/dist/routes/terminal.d.ts +9 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +207 -22
- package/dist/services/agent-event-store.d.ts +1 -0
- package/dist/services/agent-event-store.d.ts.map +1 -1
- package/dist/services/event-delta-pusher.js +4 -4
- package/dist/services/session-output.d.ts +3 -0
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +38 -2
- package/dist/services/sub-agent-manager-instance.d.ts +4 -0
- package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
- package/dist/services/sub-agent-manager-instance.js +12 -0
- package/dist/services/workspace-files.d.ts +1 -0
- package/dist/services/workspace-files.d.ts.map +1 -1
- package/dist/services/workspace-files.js +57 -1
- package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
- package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
- package/package/acode/dist/ask-user-question-tool.js +233 -0
- package/package/acode/dist/built-in-file-tools.d.ts +2 -0
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/compaction.d.ts +206 -0
- package/package/acode/dist/compaction.d.ts.map +1 -0
- package/package/acode/dist/compaction.js +664 -0
- package/package/acode/dist/config.d.ts +6 -1
- package/package/acode/dist/config.d.ts.map +1 -1
- package/package/acode/dist/config.js +27 -2
- package/package/acode/dist/index.d.ts +4 -0
- package/package/acode/dist/index.d.ts.map +1 -1
- package/package/acode/dist/index.js +4 -0
- package/package/acode/dist/runtime.d.ts +137 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +974 -172
- package/package/acode/dist/skill-tool.d.ts +30 -0
- package/package/acode/dist/skill-tool.d.ts.map +1 -0
- package/package/acode/dist/skill-tool.js +174 -0
- package/package/acode/dist/sub-agent-manager.d.ts +169 -0
- package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-manager.js +535 -0
- package/package/acode/dist/sub-agent-store.d.ts +33 -0
- package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-store.js +109 -0
- package/package/acode/dist/sub-agent-tools.d.ts +72 -0
- package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-tools.js +429 -0
- package/package/acode/dist/sub-agent-types.d.ts +81 -0
- package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-types.js +8 -0
- package/package/acode/dist/todo-store.d.ts +42 -0
- package/package/acode/dist/todo-store.d.ts.map +1 -0
- package/package/acode/dist/todo-store.js +68 -0
- package/package/acode/dist/todo-tool.d.ts +6 -0
- package/package/acode/dist/todo-tool.d.ts.map +1 -0
- package/package/acode/dist/todo-tool.js +138 -0
- package/package/acode/dist/types.d.ts +22 -1
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/acode/package.json +13 -2
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -3
- package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +83 -50
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +451 -346
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +259 -241
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -19
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +4 -77
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +17 -40
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +62 -149
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js +175 -164
- package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -9
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js +28 -55
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +27 -9
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.js +100 -23
- package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -14
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +7 -18
- package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +3 -25
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package.json +17 -7
- package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
- package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
- package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
- package/dist/adapter/AdapterRegistry.test.js +0 -118
- package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
- package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/CodexSdkAdapter.test.js +0 -270
- package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
- package/dist/adapter/acode-tool-names.test.d.ts +0 -2
- package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
- package/dist/adapter/acode-tool-names.test.js +0 -28
- package/dist/adapter/adapter.test.d.ts +0 -5
- package/dist/adapter/adapter.test.d.ts.map +0 -1
- package/dist/adapter/adapter.test.js +0 -180
- package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
- package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
- package/dist/adapter/idle-poll-loop.test.js +0 -85
- package/dist/adapter/types.test.d.ts +0 -2
- package/dist/adapter/types.test.d.ts.map +0 -1
- package/dist/adapter/types.test.js +0 -50
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
- package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
- package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
- package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
- package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
- package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -107
- package/dist/routes/ai-sources.test.d.ts +0 -2
- package/dist/routes/ai-sources.test.d.ts.map +0 -1
- package/dist/routes/ai-sources.test.js +0 -169
- package/dist/routes/file-browser.test.d.ts +0 -5
- package/dist/routes/file-browser.test.d.ts.map +0 -1
- package/dist/routes/file-browser.test.js +0 -155
- package/dist/routes/git.test.d.ts +0 -2
- package/dist/routes/git.test.d.ts.map +0 -1
- package/dist/routes/git.test.js +0 -330
- package/dist/routes/instance.test.d.ts +0 -5
- package/dist/routes/instance.test.d.ts.map +0 -1
- package/dist/routes/instance.test.js +0 -389
- package/dist/routes/mcp.test.d.ts +0 -5
- package/dist/routes/mcp.test.d.ts.map +0 -1
- package/dist/routes/mcp.test.js +0 -92
- package/dist/routes/memory-route-wiring.test.d.ts +0 -2
- package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
- package/dist/routes/memory-route-wiring.test.js +0 -15
- package/dist/routes/memory.test.d.ts +0 -2
- package/dist/routes/memory.test.d.ts.map +0 -1
- package/dist/routes/memory.test.js +0 -177
- package/dist/routes/properties.test.d.ts +0 -5
- package/dist/routes/properties.test.d.ts.map +0 -1
- package/dist/routes/properties.test.js +0 -72
- package/dist/routes/pty.test.d.ts +0 -2
- package/dist/routes/pty.test.d.ts.map +0 -1
- package/dist/routes/pty.test.js +0 -82
- package/dist/routes/runtime-binding.test.d.ts +0 -2
- package/dist/routes/runtime-binding.test.d.ts.map +0 -1
- package/dist/routes/runtime-binding.test.js +0 -32
- package/dist/routes/sessions.test.d.ts +0 -5
- package/dist/routes/sessions.test.d.ts.map +0 -1
- package/dist/routes/sessions.test.js +0 -86
- package/dist/routes/skills.test.d.ts +0 -5
- package/dist/routes/skills.test.d.ts.map +0 -1
- package/dist/routes/skills.test.js +0 -50
- package/dist/routes/system-metrics.test.d.ts +0 -2
- package/dist/routes/system-metrics.test.d.ts.map +0 -1
- package/dist/routes/system-metrics.test.js +0 -16
- package/dist/routes/terminal.stop-options.test.d.ts +0 -2
- package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
- package/dist/routes/terminal.stop-options.test.js +0 -39
- package/dist/routes/terminal.test.d.ts +0 -2
- package/dist/routes/terminal.test.d.ts.map +0 -1
- package/dist/routes/terminal.test.js +0 -746
- package/dist/routes/yml.test.d.ts +0 -5
- package/dist/routes/yml.test.d.ts.map +0 -1
- package/dist/routes/yml.test.js +0 -58
- package/dist/services/ai-source-apply.test.d.ts +0 -2
- package/dist/services/ai-source-apply.test.d.ts.map +0 -1
- package/dist/services/ai-source-apply.test.js +0 -138
- package/dist/services/auto-register.test.d.ts +0 -2
- package/dist/services/auto-register.test.d.ts.map +0 -1
- package/dist/services/auto-register.test.js +0 -184
- package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
- package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
- package/dist/services/aws-client-agent-mcp.test.js +0 -242
- package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
- package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
- package/dist/services/bridge-package-publish-hooks.test.js +0 -136
- package/dist/services/cc-switch-discovery.test.d.ts +0 -2
- package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
- package/dist/services/cc-switch-discovery.test.js +0 -56
- package/dist/services/cli-commands.test.d.ts +0 -2
- package/dist/services/cli-commands.test.d.ts.map +0 -1
- package/dist/services/cli-commands.test.js +0 -230
- package/dist/services/instance-init-service.test.d.ts +0 -2
- package/dist/services/instance-init-service.test.d.ts.map +0 -1
- package/dist/services/instance-init-service.test.js +0 -140
- package/dist/services/instance-state.test.d.ts +0 -2
- package/dist/services/instance-state.test.d.ts.map +0 -1
- package/dist/services/instance-state.test.js +0 -245
- package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
- package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
- package/dist/services/mcp-launch-binding-queue.test.js +0 -181
- package/dist/services/runtime-binding.test.d.ts +0 -2
- package/dist/services/runtime-binding.test.d.ts.map +0 -1
- package/dist/services/runtime-binding.test.js +0 -78
- package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
- package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
- package/dist/services/runtime-lifecycle-policy.test.js +0 -41
- package/dist/services/session-output.test.d.ts +0 -5
- package/dist/services/session-output.test.d.ts.map +0 -1
- package/dist/services/session-output.test.js +0 -177
- package/dist/services/startup-config-wizard.test.d.ts +0 -2
- package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
- package/dist/services/startup-config-wizard.test.js +0 -184
- package/dist/services/terminal-persistence.test.d.ts +0 -5
- package/dist/services/terminal-persistence.test.d.ts.map +0 -1
- package/dist/services/terminal-persistence.test.js +0 -159
- package/dist/services/tool-installer.test.d.ts +0 -2
- package/dist/services/tool-installer.test.d.ts.map +0 -1
- package/dist/services/tool-installer.test.js +0 -225
- package/dist/services/workspace-files.test.d.ts +0 -2
- package/dist/services/workspace-files.test.d.ts.map +0 -1
- package/dist/services/workspace-files.test.js +0 -648
- package/dist/services/workspace-watch.test.d.ts +0 -2
- package/dist/services/workspace-watch.test.d.ts.map +0 -1
- package/dist/services/workspace-watch.test.js +0 -38
- package/dist/utils/file-utils.test.d.ts +0 -2
- package/dist/utils/file-utils.test.d.ts.map +0 -1
- package/dist/utils/file-utils.test.js +0 -244
- package/dist/utils/logger.test.d.ts +0 -2
- package/dist/utils/logger.test.d.ts.map +0 -1
- package/dist/utils/logger.test.js +0 -93
- package/dist/utils/sdk-package-loader.test.d.ts +0 -2
- package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
- package/dist/utils/sdk-package-loader.test.js +0 -84
- package/dist/utils/validation.test.d.ts +0 -2
- package/dist/utils/validation.test.d.ts.map +0 -1
- package/dist/utils/validation.test.js +0 -88
- package/dist/utils/yaml-utils.test.d.ts +0 -2
- package/dist/utils/yaml-utils.test.d.ts.map +0 -1
- package/dist/utils/yaml-utils.test.js +0 -363
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ACodeRuntime } from "./runtime.js";
|
|
3
|
+
/** 已完成 entry 在内存中的保留时长(30 分钟),超时后清理释放内存 */
|
|
4
|
+
const COMPLETED_ENTRY_TTL_MS = 30 * 60 * 1000;
|
|
5
|
+
/**
|
|
6
|
+
* SubAgentManager:管理独立 ACodeRuntime 实例池。
|
|
7
|
+
*
|
|
8
|
+
* 核心职责:
|
|
9
|
+
* 1. spawn 创建独立 ACodeRuntime 实例(避免父 agent 的 submitQueue 死锁)
|
|
10
|
+
* 2. start 异步启动子 agent(不阻塞),awaitCompletion 显式等待完成
|
|
11
|
+
* 3. abort 取消运行中的 sub-agent
|
|
12
|
+
* 4. 维护父子关系映射(parentAgentId -> subIds)
|
|
13
|
+
* 5. SSE 事件订阅(subscribe/unsubscribe)
|
|
14
|
+
* 6. 状态持久化到 SubAgentStore
|
|
15
|
+
*
|
|
16
|
+
* 线程模型:单进程 Node.js,所有 ACodeRuntime 实例共享事件循环。
|
|
17
|
+
* 非阻塞模式:spawn 后调 start 立即返回,主 agent 可继续工作;
|
|
18
|
+
* 主 agent 通过 awaitCompletion 显式阻塞等待指定 sub-agent 数组完成。
|
|
19
|
+
*
|
|
20
|
+
* 重要:ACodeRuntime.submit() 返回 Promise<void>,assistant 文本通过
|
|
21
|
+
* "event" 上的 "assistant_delta" 事件传递,turn 结束时发出 "turn_complete"。
|
|
22
|
+
* 因此 start 内部注册事件监听器累积输出文本,awaitCompletion 通过 Promise 等待。
|
|
23
|
+
*
|
|
24
|
+
* Entry 生命周期:
|
|
25
|
+
* - pending: spawn 后未 start
|
|
26
|
+
* - running: start 后运行中
|
|
27
|
+
* - completed/failed/cancelled: 终态,entry 保留在内存中 TTL=30min,
|
|
28
|
+
* 供 list/get_status 查询;超时后从内存清理(store 仍持久化)
|
|
29
|
+
*/
|
|
30
|
+
export class SubAgentManager {
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.subAgents = new Map();
|
|
33
|
+
this.parentToSubs = new Map();
|
|
34
|
+
this.subscribers = new Map(); // subId 或 "*" -> subs
|
|
35
|
+
/** 已完成 entry 的清理定时器 */
|
|
36
|
+
this.cleanupTimer = null;
|
|
37
|
+
this.store = opts.store;
|
|
38
|
+
this.maxConcurrent = opts.maxConcurrent ?? 5;
|
|
39
|
+
// 启动定期清理已完成 entry 的定时器(每 5 分钟扫一次)
|
|
40
|
+
this.cleanupTimer = setInterval(() => this.cleanupCompletedEntries(), 5 * 60 * 1000);
|
|
41
|
+
// 不阻止进程退出
|
|
42
|
+
if (this.cleanupTimer.unref)
|
|
43
|
+
this.cleanupTimer.unref();
|
|
44
|
+
}
|
|
45
|
+
/** 创建 sub-agent(不立即运行;调 start 才执行) */
|
|
46
|
+
async spawn(opts) {
|
|
47
|
+
// 并发上限检查(pending + running 都算占用配额)
|
|
48
|
+
const activeCount = this.countByStatus(opts.parentAgentId, "running", "pending");
|
|
49
|
+
if (activeCount >= this.maxConcurrent) {
|
|
50
|
+
throw new Error(`concurrent sub-agent limit reached (${this.maxConcurrent}) for agent ${opts.parentAgentId}`);
|
|
51
|
+
}
|
|
52
|
+
const subId = `sub-${randomUUID()}`;
|
|
53
|
+
const startedAt = Date.now();
|
|
54
|
+
const abortController = new AbortController();
|
|
55
|
+
// 构造子 agent 的 config(继承父 agent 的 model/prompt,但独立 sessionId)
|
|
56
|
+
const childConfig = {
|
|
57
|
+
...opts.parentConfig,
|
|
58
|
+
sessionId: subId,
|
|
59
|
+
workingDirectory: opts.workingDirectory,
|
|
60
|
+
agentId: `${opts.parentAgentId}__sub__${subId}`,
|
|
61
|
+
// sub-agent 不继承 initialPrompt(用 submit 触发)
|
|
62
|
+
initialPrompt: undefined,
|
|
63
|
+
};
|
|
64
|
+
const runtime = new ACodeRuntime(childConfig);
|
|
65
|
+
const entry = {
|
|
66
|
+
subId,
|
|
67
|
+
parentAgentId: opts.parentAgentId,
|
|
68
|
+
parentSessionId: opts.parentSessionId,
|
|
69
|
+
subagentType: opts.subagentType,
|
|
70
|
+
description: opts.description,
|
|
71
|
+
prompt: opts.prompt,
|
|
72
|
+
status: "pending",
|
|
73
|
+
startedAt,
|
|
74
|
+
runtime,
|
|
75
|
+
abortController,
|
|
76
|
+
contextSnapshot: {
|
|
77
|
+
tokenUsage: { input: 0, output: 0 },
|
|
78
|
+
messageCount: 0,
|
|
79
|
+
updatedAt: new Date().toISOString(),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
this.subAgents.set(subId, entry);
|
|
83
|
+
this.addToParentMap(opts.parentAgentId, subId);
|
|
84
|
+
// 持久化初始记录
|
|
85
|
+
const record = this.toRecord(entry);
|
|
86
|
+
await this.store.upsert(opts.parentAgentId, record);
|
|
87
|
+
// 监听 runtime 事件以更新 contextSnapshot
|
|
88
|
+
runtime.on("event", (event) => {
|
|
89
|
+
this.handleRuntimeEvent(subId, event);
|
|
90
|
+
});
|
|
91
|
+
this.emitEvent(subId, "spawned", {});
|
|
92
|
+
return subId;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 异步启动 sub-agent(不阻塞,立即返回)。
|
|
96
|
+
*
|
|
97
|
+
* 内部创建 completionPromise 供后续 awaitCompletion 等待。
|
|
98
|
+
* 启动后立即返回,主 agent 可继续做其他事。
|
|
99
|
+
*
|
|
100
|
+
* 同一 sub-agent 只能 start 一次;重复 start 抛错。
|
|
101
|
+
*/
|
|
102
|
+
async start(subId) {
|
|
103
|
+
const entry = this.subAgents.get(subId);
|
|
104
|
+
if (!entry) {
|
|
105
|
+
throw new Error(`sub-agent not found: ${subId}`);
|
|
106
|
+
}
|
|
107
|
+
if (entry.status !== "pending") {
|
|
108
|
+
throw new Error(`sub-agent ${subId} cannot start from status: ${entry.status}`);
|
|
109
|
+
}
|
|
110
|
+
// 创建 completionPromise(在 finally 中 settle)
|
|
111
|
+
entry.completionPromise = new Promise((resolve, reject) => {
|
|
112
|
+
entry.completionResolve = resolve;
|
|
113
|
+
entry.completionReject = reject;
|
|
114
|
+
});
|
|
115
|
+
// 异步启动,不阻塞调用方
|
|
116
|
+
void this.runEntryToCompletion(entry).catch(() => {
|
|
117
|
+
// runEntryToCompletion 内部已处理异常并更新状态;
|
|
118
|
+
// 这里捕获防止 unhandled rejection
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 阻塞等待指定 sub-agent 数组全部完成(completed/failed/cancelled)。
|
|
123
|
+
*
|
|
124
|
+
* - 已完成的立即 resolve
|
|
125
|
+
* - 未 start 的会先 start(自动启动)
|
|
126
|
+
* - 返回每个 sub-agent 的最终结果
|
|
127
|
+
*
|
|
128
|
+
* 注意:本方法不取消 sub-agent,仅等待。如需取消用 abort。
|
|
129
|
+
*/
|
|
130
|
+
async awaitCompletion(subIds, timeoutMs = 0) {
|
|
131
|
+
if (subIds.length === 0)
|
|
132
|
+
return [];
|
|
133
|
+
const results = [];
|
|
134
|
+
// 对每个 subId 准备等待 Promise
|
|
135
|
+
const waitPromises = subIds.map(async (subId) => {
|
|
136
|
+
const entry = this.subAgents.get(subId);
|
|
137
|
+
if (!entry) {
|
|
138
|
+
// entry 已从内存清理,从 store 查
|
|
139
|
+
const record = await this.findRecordInStore(subId);
|
|
140
|
+
if (record) {
|
|
141
|
+
results.push({
|
|
142
|
+
subId,
|
|
143
|
+
output: record.result ?? "",
|
|
144
|
+
error: record.errorMessage,
|
|
145
|
+
status: record.status,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
results.push({
|
|
150
|
+
subId,
|
|
151
|
+
output: "",
|
|
152
|
+
error: `sub-agent not found: ${subId}`,
|
|
153
|
+
status: "failed",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// 如果还在 pending,自动 start
|
|
159
|
+
if (entry.status === "pending") {
|
|
160
|
+
await this.start(subId);
|
|
161
|
+
}
|
|
162
|
+
// 等待 completionPromise(可能已被 start 创建)
|
|
163
|
+
const promise = entry.completionPromise ?? Promise.resolve({ output: "", error: "not started" });
|
|
164
|
+
try {
|
|
165
|
+
const result = await promise;
|
|
166
|
+
results.push({
|
|
167
|
+
subId,
|
|
168
|
+
output: result.output,
|
|
169
|
+
error: result.error,
|
|
170
|
+
status: entry.status,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
results.push({
|
|
175
|
+
subId,
|
|
176
|
+
output: "",
|
|
177
|
+
error: err instanceof Error ? err.message : String(err),
|
|
178
|
+
status: entry.status,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
if (timeoutMs > 0) {
|
|
183
|
+
await Promise.race([
|
|
184
|
+
Promise.allSettled(waitPromises),
|
|
185
|
+
new Promise((resolve) => setTimeout(() => resolve(), timeoutMs)),
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
await Promise.allSettled(waitPromises);
|
|
190
|
+
}
|
|
191
|
+
return results;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 内部方法:执行 entry 直到完成。
|
|
195
|
+
*
|
|
196
|
+
* 实际 ACodeRuntime.submit() 返回 Promise<void>,assistant 文本通过
|
|
197
|
+
* assistant_delta 事件传递。本方法注册临时监听器累积输出文本,
|
|
198
|
+
* 同时检测 turn_complete 事件中的 failed 标志以处理运行时内部错误。
|
|
199
|
+
*
|
|
200
|
+
* 完成后:
|
|
201
|
+
* - 更新 entry 状态(completed/failed)
|
|
202
|
+
* - 持久化到 store
|
|
203
|
+
* - settle completionPromise
|
|
204
|
+
* - 清理 runtime 实例(释放内存)
|
|
205
|
+
* - **不删除 entry**(保留在内存供 list/get_status 查询,30min 后由定时器清理)
|
|
206
|
+
*/
|
|
207
|
+
async runEntryToCompletion(entry) {
|
|
208
|
+
// 收集 assistant_delta 文本 + 检测 turn 失败标志
|
|
209
|
+
const chunks = [];
|
|
210
|
+
let turnFailed = false;
|
|
211
|
+
let turnErrorMessage;
|
|
212
|
+
const onEvent = (event) => {
|
|
213
|
+
if (!event)
|
|
214
|
+
return;
|
|
215
|
+
switch (event.type) {
|
|
216
|
+
case "assistant_delta":
|
|
217
|
+
if (typeof event.data?.text === "string") {
|
|
218
|
+
chunks.push(event.data.text);
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
case "turn_complete":
|
|
222
|
+
if (event.data?.failed) {
|
|
223
|
+
turnFailed = true;
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
case "error":
|
|
227
|
+
if (typeof event.data?.message === "string") {
|
|
228
|
+
turnErrorMessage = event.data.message;
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
entry.runtime.on("event", onEvent);
|
|
234
|
+
// 状态 -> running
|
|
235
|
+
entry.status = "running";
|
|
236
|
+
await this.store.updateStatus(entry.parentAgentId, entry.subId, { status: "running" });
|
|
237
|
+
this.emitEvent(entry.subId, "running", {});
|
|
238
|
+
try {
|
|
239
|
+
await entry.runtime.start();
|
|
240
|
+
await entry.runtime.submit(entry.prompt);
|
|
241
|
+
// 实际 API 中 submit 解析后所有事件已同步触发完毕(EventEmitter.emit 是同步的)
|
|
242
|
+
// 若 turn_complete 标记 failed,抛错走 catch
|
|
243
|
+
if (turnFailed) {
|
|
244
|
+
throw new Error(turnErrorMessage ?? "sub-agent turn failed");
|
|
245
|
+
}
|
|
246
|
+
const output = chunks.join("");
|
|
247
|
+
entry.status = "completed";
|
|
248
|
+
entry.result = output;
|
|
249
|
+
entry.endedAt = Date.now();
|
|
250
|
+
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
251
|
+
await this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
252
|
+
status: "completed",
|
|
253
|
+
result: output,
|
|
254
|
+
endedAt: entry.endedAt,
|
|
255
|
+
durationMs: entry.durationMs,
|
|
256
|
+
contextSnapshot: undefined, // 完成后清空快照
|
|
257
|
+
});
|
|
258
|
+
this.emitEvent(entry.subId, "completed", { result: output });
|
|
259
|
+
entry.completionResolve?.({ output });
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
263
|
+
entry.status = "failed";
|
|
264
|
+
entry.errorMessage = errorMessage;
|
|
265
|
+
entry.endedAt = Date.now();
|
|
266
|
+
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
267
|
+
await this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
268
|
+
status: "failed",
|
|
269
|
+
errorMessage,
|
|
270
|
+
endedAt: entry.endedAt,
|
|
271
|
+
durationMs: entry.durationMs,
|
|
272
|
+
contextSnapshot: undefined,
|
|
273
|
+
});
|
|
274
|
+
this.emitEvent(entry.subId, "failed", { error: errorMessage });
|
|
275
|
+
entry.completionResolve?.({ output: "", error: errorMessage });
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
// 移除临时监听器
|
|
279
|
+
try {
|
|
280
|
+
entry.runtime.removeListener?.("event", onEvent);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
/* 忽略移除失败 */
|
|
284
|
+
}
|
|
285
|
+
// 完成后清理 runtime 实例(释放内存),但保留 entry 供查询
|
|
286
|
+
try {
|
|
287
|
+
await entry.runtime.stop();
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
/* 忽略 stop 失败 */
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* @deprecated 兼容旧接口:spawn + 阻塞等待完成。
|
|
296
|
+
* 新代码应使用 spawn + start + awaitCompletion。
|
|
297
|
+
*/
|
|
298
|
+
async runToCompletion(subId) {
|
|
299
|
+
const entry = this.subAgents.get(subId);
|
|
300
|
+
if (!entry) {
|
|
301
|
+
throw new Error(`sub-agent not found: ${subId}`);
|
|
302
|
+
}
|
|
303
|
+
if (entry.status === "running") {
|
|
304
|
+
throw new Error(`sub-agent already running: ${subId}`);
|
|
305
|
+
}
|
|
306
|
+
// 重置 status 以便 runEntryToCompletion 能进入 running
|
|
307
|
+
if (entry.status !== "pending") {
|
|
308
|
+
entry.status = "pending";
|
|
309
|
+
}
|
|
310
|
+
await this.start(subId);
|
|
311
|
+
return (await entry.completionPromise) ?? { output: "", error: "not started" };
|
|
312
|
+
}
|
|
313
|
+
/** 取消运行中的 sub-agent */
|
|
314
|
+
async abort(subId) {
|
|
315
|
+
const entry = this.subAgents.get(subId);
|
|
316
|
+
if (!entry)
|
|
317
|
+
return;
|
|
318
|
+
if (entry.status === "completed" || entry.status === "failed" || entry.status === "cancelled") {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
entry.abortController.abort();
|
|
322
|
+
try {
|
|
323
|
+
await entry.runtime.abort();
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
/* 忽略 */
|
|
327
|
+
}
|
|
328
|
+
entry.status = "cancelled";
|
|
329
|
+
entry.endedAt = Date.now();
|
|
330
|
+
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
331
|
+
await this.store.updateStatus(entry.parentAgentId, subId, {
|
|
332
|
+
status: "cancelled",
|
|
333
|
+
endedAt: entry.endedAt,
|
|
334
|
+
durationMs: entry.durationMs,
|
|
335
|
+
contextSnapshot: undefined,
|
|
336
|
+
});
|
|
337
|
+
this.emitEvent(subId, "cancelled", {});
|
|
338
|
+
// settle completionPromise(cancelled 视为 error)
|
|
339
|
+
entry.completionResolve?.({ output: "", error: "aborted" });
|
|
340
|
+
}
|
|
341
|
+
/** 获取内存中的 entry */
|
|
342
|
+
getSubAgent(subId) {
|
|
343
|
+
return this.subAgents.get(subId);
|
|
344
|
+
}
|
|
345
|
+
/** 列出某 agent 的全部内存中 sub-agent(含运行中和已完成未清理) */
|
|
346
|
+
listByParent(parentAgentId) {
|
|
347
|
+
const subIds = this.parentToSubs.get(parentAgentId);
|
|
348
|
+
if (!subIds)
|
|
349
|
+
return [];
|
|
350
|
+
return Array.from(subIds)
|
|
351
|
+
.map((id) => this.subAgents.get(id))
|
|
352
|
+
.filter((e) => e !== undefined);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* 列出某父 agent 下处于活跃状态(pending / running)的 sub-agent。
|
|
356
|
+
*
|
|
357
|
+
* 已完成/失败/取消的 entry 在内存中仍保留 TTL=30min 供查询,但不应计入
|
|
358
|
+
* "进行中" 列表。前端 "进行中" tab 与 SSE snapshot 都应使用此方法。
|
|
359
|
+
*/
|
|
360
|
+
listActiveByParent(parentAgentId) {
|
|
361
|
+
return this.listByParent(parentAgentId).filter((e) => e.status === "pending" || e.status === "running");
|
|
362
|
+
}
|
|
363
|
+
/** 订阅 sub-agent 事件(subId 或 "*" 全局) */
|
|
364
|
+
subscribe(subId, cb) {
|
|
365
|
+
let set = this.subscribers.get(subId);
|
|
366
|
+
if (!set) {
|
|
367
|
+
set = new Set();
|
|
368
|
+
this.subscribers.set(subId, set);
|
|
369
|
+
}
|
|
370
|
+
set.add(cb);
|
|
371
|
+
return () => {
|
|
372
|
+
set?.delete(cb);
|
|
373
|
+
if (set && set.size === 0)
|
|
374
|
+
this.subscribers.delete(subId);
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/** 清理所有运行中的 sub-agent(bridge 关闭时调用) */
|
|
378
|
+
async dispose() {
|
|
379
|
+
if (this.cleanupTimer) {
|
|
380
|
+
clearInterval(this.cleanupTimer);
|
|
381
|
+
this.cleanupTimer = null;
|
|
382
|
+
}
|
|
383
|
+
const entries = Array.from(this.subAgents.values());
|
|
384
|
+
await Promise.allSettled(entries.map((e) => this.abort(e.subId)));
|
|
385
|
+
this.subAgents.clear();
|
|
386
|
+
this.parentToSubs.clear();
|
|
387
|
+
this.subscribers.clear();
|
|
388
|
+
}
|
|
389
|
+
// ---------- 内部方法 ----------
|
|
390
|
+
countByStatus(parentAgentId, ...statuses) {
|
|
391
|
+
return this.listByParent(parentAgentId).filter((e) => statuses.includes(e.status)).length;
|
|
392
|
+
}
|
|
393
|
+
addToParentMap(parentAgentId, subId) {
|
|
394
|
+
let set = this.parentToSubs.get(parentAgentId);
|
|
395
|
+
if (!set) {
|
|
396
|
+
set = new Set();
|
|
397
|
+
this.parentToSubs.set(parentAgentId, set);
|
|
398
|
+
}
|
|
399
|
+
set.add(subId);
|
|
400
|
+
}
|
|
401
|
+
removeFromParentMap(parentAgentId, subId) {
|
|
402
|
+
const set = this.parentToSubs.get(parentAgentId);
|
|
403
|
+
if (set) {
|
|
404
|
+
set.delete(subId);
|
|
405
|
+
if (set.size === 0)
|
|
406
|
+
this.parentToSubs.delete(parentAgentId);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 清理已超过 TTL 的终态 entry(completed/failed/cancelled)。
|
|
411
|
+
* 释放内存,store 仍保留历史记录供查询。
|
|
412
|
+
*/
|
|
413
|
+
cleanupCompletedEntries() {
|
|
414
|
+
const now = Date.now();
|
|
415
|
+
for (const [subId, entry] of this.subAgents) {
|
|
416
|
+
if ((entry.status === "completed" || entry.status === "failed" || entry.status === "cancelled") &&
|
|
417
|
+
entry.endedAt &&
|
|
418
|
+
now - entry.endedAt > COMPLETED_ENTRY_TTL_MS) {
|
|
419
|
+
this.subAgents.delete(subId);
|
|
420
|
+
this.removeFromParentMap(entry.parentAgentId, subId);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/** 从 store 中查找记录(entry 已从内存清理时使用) */
|
|
425
|
+
async findRecordInStore(subId) {
|
|
426
|
+
// store 按父 agent 组织,无法直接按 subId 查;遍历所有 parent
|
|
427
|
+
// 注意:此方法仅在 awaitCompletion 时调用,频率低
|
|
428
|
+
for (const parentAgentId of this.parentToSubs.keys()) {
|
|
429
|
+
const doc = await this.store.read(parentAgentId);
|
|
430
|
+
const record = doc.subAgents.find((r) => r.subId === subId);
|
|
431
|
+
if (record)
|
|
432
|
+
return record;
|
|
433
|
+
}
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
handleRuntimeEvent(subId, event) {
|
|
437
|
+
const entry = this.subAgents.get(subId);
|
|
438
|
+
if (!entry)
|
|
439
|
+
return;
|
|
440
|
+
// 更新 contextSnapshot
|
|
441
|
+
const snapshot = entry.contextSnapshot;
|
|
442
|
+
snapshot.updatedAt = new Date().toISOString();
|
|
443
|
+
switch (event?.type) {
|
|
444
|
+
case "ai_request":
|
|
445
|
+
// estimatedContextTokens 是请求前的上下文估算,可作为 input 近似值
|
|
446
|
+
if (event.data?.estimatedContextTokens != null) {
|
|
447
|
+
snapshot.tokenUsage.input = (event.data.estimatedContextTokens ?? snapshot.tokenUsage.input);
|
|
448
|
+
}
|
|
449
|
+
break;
|
|
450
|
+
case "turn_complete":
|
|
451
|
+
// turn_complete 携带实际的 token 用量,覆盖 ai_request 的估算值
|
|
452
|
+
if (event.data?.usage) {
|
|
453
|
+
snapshot.tokenUsage.input = (event.data.usage.inputTokens ?? snapshot.tokenUsage.input);
|
|
454
|
+
snapshot.tokenUsage.output = (event.data.usage.outputTokens ?? snapshot.tokenUsage.output);
|
|
455
|
+
}
|
|
456
|
+
else if (event.data?.failed || event.data?.aborted) {
|
|
457
|
+
// 失败/中止路径不携带 usage,重置 output 避免残留上一轮值
|
|
458
|
+
snapshot.tokenUsage.output = 0;
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
case "assistant_delta":
|
|
462
|
+
snapshot.lastAssistantText = (event.data?.text ?? "").slice(-500);
|
|
463
|
+
snapshot.messageCount += 1;
|
|
464
|
+
this.emitEvent(subId, "delta", { text: event.data?.text });
|
|
465
|
+
break;
|
|
466
|
+
case "mcp_status":
|
|
467
|
+
// runtime 发射的 mcp_status 使用 action 字段(而非 phase)
|
|
468
|
+
if (event.data?.action === "call_tool") {
|
|
469
|
+
snapshot.currentTool = event.data?.tool;
|
|
470
|
+
this.emitEvent(subId, "tool_start", { tool: snapshot.currentTool });
|
|
471
|
+
}
|
|
472
|
+
else if (event.data?.action === "tool_result") {
|
|
473
|
+
this.emitEvent(subId, "tool_end", { tool: snapshot.currentTool });
|
|
474
|
+
snapshot.currentTool = undefined;
|
|
475
|
+
}
|
|
476
|
+
break;
|
|
477
|
+
case "session_complete":
|
|
478
|
+
snapshot.messageCount += 1;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
emitEvent(subId, type, data) {
|
|
483
|
+
const event = {
|
|
484
|
+
type,
|
|
485
|
+
subId,
|
|
486
|
+
timestamp: new Date().toISOString(),
|
|
487
|
+
data,
|
|
488
|
+
};
|
|
489
|
+
// 通知该 subId 的订阅者
|
|
490
|
+
const subs = this.subscribers.get(subId);
|
|
491
|
+
if (subs) {
|
|
492
|
+
for (const cb of subs) {
|
|
493
|
+
try {
|
|
494
|
+
cb(event);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
/* 忽略订阅者错误 */
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// 通知全局订阅者
|
|
502
|
+
const globalSubs = this.subscribers.get("*");
|
|
503
|
+
if (globalSubs) {
|
|
504
|
+
for (const cb of globalSubs) {
|
|
505
|
+
try {
|
|
506
|
+
cb(event);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
/* 忽略 */
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
toRecord(entry) {
|
|
515
|
+
return {
|
|
516
|
+
subId: entry.subId,
|
|
517
|
+
parentAgentId: entry.parentAgentId,
|
|
518
|
+
parentSessionId: entry.parentSessionId,
|
|
519
|
+
subagentType: entry.subagentType,
|
|
520
|
+
description: entry.description,
|
|
521
|
+
prompt: entry.prompt,
|
|
522
|
+
status: entry.status,
|
|
523
|
+
result: entry.result,
|
|
524
|
+
errorMessage: entry.errorMessage,
|
|
525
|
+
startedAt: entry.startedAt,
|
|
526
|
+
endedAt: entry.endedAt,
|
|
527
|
+
durationMs: entry.durationMs,
|
|
528
|
+
contextSnapshot: entry.status === "running" ? entry.contextSnapshot : undefined,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
/** 获取当前上下文快照(供 HTTP 路由调用) */
|
|
532
|
+
getContextSnapshot(subId) {
|
|
533
|
+
return this.subAgents.get(subId)?.contextSnapshot;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SubAgentDocument, SubAgentRecord } from "./sub-agent-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sub Agent JSON 文件存储,按 agentId 隔离。
|
|
4
|
+
* 文件路径:{homedir}/.acode/sub-agents/{agentId}.json
|
|
5
|
+
*
|
|
6
|
+
* 设计与 TodoStore 完全同构:
|
|
7
|
+
* - 不依赖 agent 工作目录,避免 workspacePath 错误时读取失败
|
|
8
|
+
* - sub-agent 跟随 agent(agentId),不跟随项目工作区
|
|
9
|
+
* - 写操作通过 Promise 链串行化,tmp + rename 原子替换
|
|
10
|
+
*
|
|
11
|
+
* 历史保留策略:全部 sub-agent 记录都持久化(含 completed/failed/cancelled),
|
|
12
|
+
* 按 startedAt 倒序排列。dashboard 按需分页读取。
|
|
13
|
+
*/
|
|
14
|
+
export declare class SubAgentStore {
|
|
15
|
+
private readonly dir;
|
|
16
|
+
private writeChain;
|
|
17
|
+
constructor();
|
|
18
|
+
/** 文件路径(按 agentId 隔离,防路径穿越) */
|
|
19
|
+
private filePath;
|
|
20
|
+
/** 读取该 agentId 的完整文档。文件不存在或损坏时返回空文档。 */
|
|
21
|
+
read(agentId: string): Promise<SubAgentDocument>;
|
|
22
|
+
/** 读取单个 sub-agent 记录 */
|
|
23
|
+
get(agentId: string, subId: string): Promise<SubAgentRecord | null>;
|
|
24
|
+
/** 全量替换该 agentId 的文档。串行化以避免并发覆盖。 */
|
|
25
|
+
write(agentId: string, subAgents: SubAgentRecord[]): Promise<void>;
|
|
26
|
+
/** 插入或更新单条记录(保留其他记录) */
|
|
27
|
+
upsert(agentId: string, record: SubAgentRecord): Promise<void>;
|
|
28
|
+
/** 部分更新单条记录 */
|
|
29
|
+
updateStatus(agentId: string, subId: string, patch: Partial<SubAgentRecord>): Promise<void>;
|
|
30
|
+
/** 列出该 agent 的全部 sub-agent(已按 startedAt 倒序) */
|
|
31
|
+
list(agentId: string): Promise<SubAgentRecord[]>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=sub-agent-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sub-agent-store.d.ts","sourceRoot":"","sources":["../src/sub-agent-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,UAAU,CAAoC;;IAMtD,+BAA+B;IAC/B,OAAO,CAAC,QAAQ;IAKhB,wCAAwC;IAClC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoBtD,wBAAwB;IAClB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAKzE,oCAAoC;IAC9B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBxE,wBAAwB;IAClB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE,eAAe;IACT,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,CAAC,cAAc,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC;IAQhB,+CAA+C;IACzC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAGvD"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Sub Agent JSON 文件存储,按 agentId 隔离。
|
|
6
|
+
* 文件路径:{homedir}/.acode/sub-agents/{agentId}.json
|
|
7
|
+
*
|
|
8
|
+
* 设计与 TodoStore 完全同构:
|
|
9
|
+
* - 不依赖 agent 工作目录,避免 workspacePath 错误时读取失败
|
|
10
|
+
* - sub-agent 跟随 agent(agentId),不跟随项目工作区
|
|
11
|
+
* - 写操作通过 Promise 链串行化,tmp + rename 原子替换
|
|
12
|
+
*
|
|
13
|
+
* 历史保留策略:全部 sub-agent 记录都持久化(含 completed/failed/cancelled),
|
|
14
|
+
* 按 startedAt 倒序排列。dashboard 按需分页读取。
|
|
15
|
+
*/
|
|
16
|
+
export class SubAgentStore {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.writeChain = Promise.resolve();
|
|
19
|
+
this.dir = path.join(os.homedir(), ".acode", "sub-agents");
|
|
20
|
+
}
|
|
21
|
+
/** 文件路径(按 agentId 隔离,防路径穿越) */
|
|
22
|
+
filePath(agentId) {
|
|
23
|
+
const safe = String(agentId).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
24
|
+
return path.join(this.dir, `${safe}.json`);
|
|
25
|
+
}
|
|
26
|
+
/** 读取该 agentId 的完整文档。文件不存在或损坏时返回空文档。 */
|
|
27
|
+
async read(agentId) {
|
|
28
|
+
const empty = { agentId, updatedAt: "", subAgents: [] };
|
|
29
|
+
try {
|
|
30
|
+
const raw = await fs.readFile(this.filePath(agentId), "utf-8");
|
|
31
|
+
const doc = JSON.parse(raw);
|
|
32
|
+
return {
|
|
33
|
+
agentId,
|
|
34
|
+
updatedAt: typeof doc.updatedAt === "string" ? doc.updatedAt : "",
|
|
35
|
+
subAgents: Array.isArray(doc.subAgents)
|
|
36
|
+
? doc.subAgents.filter(isValidRecord)
|
|
37
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
38
|
+
: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (err && err.code === "ENOENT")
|
|
43
|
+
return empty;
|
|
44
|
+
if (err instanceof SyntaxError)
|
|
45
|
+
return empty;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** 读取单个 sub-agent 记录 */
|
|
50
|
+
async get(agentId, subId) {
|
|
51
|
+
const doc = await this.read(agentId);
|
|
52
|
+
return doc.subAgents.find((r) => r.subId === subId) ?? null;
|
|
53
|
+
}
|
|
54
|
+
/** 全量替换该 agentId 的文档。串行化以避免并发覆盖。 */
|
|
55
|
+
async write(agentId, subAgents) {
|
|
56
|
+
const target = this.filePath(agentId);
|
|
57
|
+
const run = this.writeChain.then(async () => {
|
|
58
|
+
await fs.mkdir(this.dir, { recursive: true });
|
|
59
|
+
const doc = {
|
|
60
|
+
agentId,
|
|
61
|
+
updatedAt: new Date().toISOString(),
|
|
62
|
+
subAgents: subAgents.sort((a, b) => b.startedAt - a.startedAt),
|
|
63
|
+
};
|
|
64
|
+
const tmp = `${target}.tmp`;
|
|
65
|
+
await fs.writeFile(tmp, JSON.stringify(doc, null, 2), "utf-8");
|
|
66
|
+
await fs.rename(tmp, target);
|
|
67
|
+
});
|
|
68
|
+
this.writeChain = run.catch(() => { });
|
|
69
|
+
await run;
|
|
70
|
+
}
|
|
71
|
+
/** 插入或更新单条记录(保留其他记录) */
|
|
72
|
+
async upsert(agentId, record) {
|
|
73
|
+
const doc = await this.read(agentId);
|
|
74
|
+
const idx = doc.subAgents.findIndex((r) => r.subId === record.subId);
|
|
75
|
+
if (idx >= 0) {
|
|
76
|
+
doc.subAgents[idx] = record;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
doc.subAgents.unshift(record);
|
|
80
|
+
}
|
|
81
|
+
await this.write(agentId, doc.subAgents);
|
|
82
|
+
}
|
|
83
|
+
/** 部分更新单条记录 */
|
|
84
|
+
async updateStatus(agentId, subId, patch) {
|
|
85
|
+
const doc = await this.read(agentId);
|
|
86
|
+
const idx = doc.subAgents.findIndex((r) => r.subId === subId);
|
|
87
|
+
if (idx < 0)
|
|
88
|
+
return;
|
|
89
|
+
doc.subAgents[idx] = { ...doc.subAgents[idx], ...patch };
|
|
90
|
+
await this.write(agentId, doc.subAgents);
|
|
91
|
+
}
|
|
92
|
+
/** 列出该 agent 的全部 sub-agent(已按 startedAt 倒序) */
|
|
93
|
+
async list(agentId) {
|
|
94
|
+
return (await this.read(agentId)).subAgents;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** 运行时校验记录结构(防损坏文件污染内存) */
|
|
98
|
+
function isValidRecord(r) {
|
|
99
|
+
return (r !== null &&
|
|
100
|
+
typeof r === "object" &&
|
|
101
|
+
typeof r.subId === "string" &&
|
|
102
|
+
typeof r.parentAgentId === "string" &&
|
|
103
|
+
typeof r.parentSessionId === "string" &&
|
|
104
|
+
typeof r.subagentType === "string" &&
|
|
105
|
+
typeof r.description === "string" &&
|
|
106
|
+
typeof r.prompt === "string" &&
|
|
107
|
+
typeof r.status === "string" &&
|
|
108
|
+
typeof r.startedAt === "number");
|
|
109
|
+
}
|