aws-runtime-bridge 1.9.57 → 1.9.60
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/routes/sub-agents.js +23 -4
- package/package/acode/dist/fs-utils.d.ts +12 -0
- package/package/acode/dist/fs-utils.d.ts.map +1 -0
- package/package/acode/dist/fs-utils.js +58 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +67 -8
- package/package/acode/dist/sub-agent-manager.d.ts.map +1 -1
- package/package/acode/dist/sub-agent-manager.js +149 -51
- package/package/acode/dist/sub-agent-store.d.ts +35 -5
- package/package/acode/dist/sub-agent-store.d.ts.map +1 -1
- package/package/acode/dist/sub-agent-store.js +79 -16
- package/package/acode/dist/todo-store.d.ts +1 -0
- package/package/acode/dist/todo-store.d.ts.map +1 -1
- package/package/acode/dist/todo-store.js +6 -1
- package/package/acode/dist/types.d.ts +11 -0
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +105 -7
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +4 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +26 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -31,10 +31,29 @@ subAgentsRouter.get("/sub-agents/:agentId", validateToken, async (req, res) => {
|
|
|
31
31
|
}
|
|
32
32
|
try {
|
|
33
33
|
// 从 store 读取全部历史
|
|
34
|
-
|
|
34
|
+
let allRecords = await subAgentStore.list(agentId);
|
|
35
35
|
// 从 manager 读取活跃 entry(pending / running),已完成但未 TTL 清理的不计入
|
|
36
36
|
const runningEntries = subAgentManager.listActiveByParent(agentId);
|
|
37
37
|
const runningIds = new Set(runningEntries.map((e) => e.subId));
|
|
38
|
+
// 修正数据不一致:持久化记录中 status 为 running/pending 但不在内存活跃列表中的条目,
|
|
39
|
+
// 说明 store 状态未同步(如 store.updateStatus 超时失败、或 bridge 重启后 entry 丢失)。
|
|
40
|
+
// 修正为 failed 并重新读取,避免前端"历史"列表中混入异常的 running 条目。
|
|
41
|
+
let dirty = false;
|
|
42
|
+
for (const record of allRecords) {
|
|
43
|
+
if ((record.status === "running" || record.status === "pending") && !runningIds.has(record.subId)) {
|
|
44
|
+
dirty = true;
|
|
45
|
+
await subAgentStore.updateStatus(agentId, record.subId, {
|
|
46
|
+
status: "failed",
|
|
47
|
+
errorMessage: "sub-agent status mismatch: store says running/pending but not in memory",
|
|
48
|
+
endedAt: record.startedAt,
|
|
49
|
+
durationMs: 0,
|
|
50
|
+
contextSnapshot: undefined,
|
|
51
|
+
}).catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (dirty) {
|
|
55
|
+
allRecords = await subAgentStore.list(agentId);
|
|
56
|
+
}
|
|
38
57
|
// 合并:运行中的用 manager 的实时状态覆盖 store 的记录
|
|
39
58
|
const running = runningEntries.map((e) => ({
|
|
40
59
|
subId: e.subId,
|
|
@@ -203,10 +222,10 @@ subAgentsRouter.get("/sub-agents/:agentId/:subId/events", validateToken, async (
|
|
|
203
222
|
"X-Accel-Buffering": "no", // nginx 关闭缓冲
|
|
204
223
|
});
|
|
205
224
|
res.write(": connected\n\n");
|
|
206
|
-
// 1. 补发历史 tool
|
|
225
|
+
// 1. 补发历史 tool 事件(从独立 jsonl 事件文件读取,按时间升序)
|
|
226
|
+
// 事件文件与主记录文件分离,避免高频 appendEvent 阻塞终态 updateStatus
|
|
207
227
|
try {
|
|
208
|
-
const
|
|
209
|
-
const historyEvents = record?.events ?? [];
|
|
228
|
+
const historyEvents = await subAgentStore.readEvents(agentId, subId);
|
|
210
229
|
for (const ev of historyEvents) {
|
|
211
230
|
res.write(`data: ${JSON.stringify(ev)}\n\n`);
|
|
212
231
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对 `fs.rename` 做指数退避重试,仅针对 Windows 上目标被打开时
|
|
3
|
+
* 的瞬时错误(EPERM/EBUSY/EACCES/ENOTEMPTY)。
|
|
4
|
+
*
|
|
5
|
+
* 其他错误(ENOENT 源不存在、ENOSPC 磁盘满等)直接抛出,不重试。
|
|
6
|
+
*
|
|
7
|
+
* @param src 源文件路径(通常是 `.tmp` 临时文件)
|
|
8
|
+
* @param dest 目标文件路径
|
|
9
|
+
* @returns 成功时 resolve,全部重试都失败时 reject 最后一次错误
|
|
10
|
+
*/
|
|
11
|
+
export declare function renameWithRetry(src: string, dest: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=fs-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AA6BA;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB9E"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* 文件系统原子写入相关的辅助工具。
|
|
4
|
+
*
|
|
5
|
+
* 主要解决 Windows 上 `fs.rename` 在目标文件被其他进程/句柄打开时
|
|
6
|
+
* 抛出 EPERM/EBUSY/EACCES 的问题(POSIX 上 rename 是真原子的,
|
|
7
|
+
* 不会触发此类错误)。这些错误通常是瞬时的——等 reader 释放 fd
|
|
8
|
+
* 后重试即可成功。
|
|
9
|
+
*
|
|
10
|
+
* 不引入 fs-ext/native 依赖,纯 JS 退避重试实现。
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* 视为可重试的瞬时错误码。
|
|
14
|
+
*
|
|
15
|
+
* - EPERM: Windows 上目标被另一进程打开时 rename 常见错误
|
|
16
|
+
* - EBUSY: 资源忙(Linux/Windows 均可能出现)
|
|
17
|
+
* - EACCES: 权限瞬时不可用(如杀软扫描中)
|
|
18
|
+
* - ENOTEMPTY: 极少数情况下 rename 目标存在残留
|
|
19
|
+
*/
|
|
20
|
+
const RENAME_RETRYABLE_CODES = ["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"];
|
|
21
|
+
/**
|
|
22
|
+
* 退避序列(毫秒)。总退避 ≈ 50+150+400+800 = 1400ms,
|
|
23
|
+
* 留充足余量给 5s 的 STORE_WRITE_TIMEOUT_MS。
|
|
24
|
+
*/
|
|
25
|
+
const RENAME_BACKOFF_MS = [50, 150, 400, 800];
|
|
26
|
+
/**
|
|
27
|
+
* 对 `fs.rename` 做指数退避重试,仅针对 Windows 上目标被打开时
|
|
28
|
+
* 的瞬时错误(EPERM/EBUSY/EACCES/ENOTEMPTY)。
|
|
29
|
+
*
|
|
30
|
+
* 其他错误(ENOENT 源不存在、ENOSPC 磁盘满等)直接抛出,不重试。
|
|
31
|
+
*
|
|
32
|
+
* @param src 源文件路径(通常是 `.tmp` 临时文件)
|
|
33
|
+
* @param dest 目标文件路径
|
|
34
|
+
* @returns 成功时 resolve,全部重试都失败时 reject 最后一次错误
|
|
35
|
+
*/
|
|
36
|
+
export async function renameWithRetry(src, dest) {
|
|
37
|
+
let lastErr;
|
|
38
|
+
// 尝试次数 = 1(初始)+ RENAME_BACKOFF_MS.length(重试)
|
|
39
|
+
const maxAttempts = 1 + RENAME_BACKOFF_MS.length;
|
|
40
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
41
|
+
try {
|
|
42
|
+
await fs.rename(src, dest);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
lastErr = err;
|
|
47
|
+
const code = err?.code ?? "";
|
|
48
|
+
if (!RENAME_RETRYABLE_CODES.includes(code)) {
|
|
49
|
+
// 非瞬时错误,直接抛出避免无谓重试
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
if (attempt < RENAME_BACKOFF_MS.length) {
|
|
53
|
+
await new Promise((r) => setTimeout(r, RENAME_BACKOFF_MS[attempt]));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw lastErr;
|
|
58
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AA+BlC,OAAO,KAAK,EAOV,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,KAAK,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,UAAU,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AA+BlC,OAAO,KAAK,EAOV,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,KAAK,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,UAAU,CAAC;CACzB;AAozCD,qBAAa,YAAa,SAAQ,YAAY;IAiGhC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAhGnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,mBAAmB,CAAqB;IAEhD,OAAO,CAAC,KAAK,CAwBX;IAEF,OAAO,CAAC,eAAe,CAAgC;IAEvD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAkC;IAEzE;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAkC;IAE9E,OAAO,CAAC,WAAW,CAAoC;IAEvD,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,cAAc,CAAS;IAE/B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,oBAAoB,CAA0B;IAEtD,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,4BAA4B;IAC5B,OAAO,CAAC,oBAAoB,CAA+C;IAC3E,0DAA0D;IAC1D,OAAO,CAAC,SAAS,CAA+B;IAChD,mCAAmC;IACnC,OAAO,CAAC,aAAa,CAAsB;IAC3C,oCAAoC;IACpC,OAAO,CAAC,iBAAiB,CAA8C;IACvE,uBAAuB;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAO;IAExD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAOlB;gBAEe,MAAM,EAAE,kBAAkB;IAevD,2CAA2C;IAC3C,iBAAiB,IAAI,MAAM,EAAE;IAI7B,OAAO,CAAC,cAAc;IAatB;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAgB3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgN5B;;;;;;;;;;OAUG;YACW,aAAa;IA+C3B;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;;;;;;;OAQG;YACW,aAAa;IAuC3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IA6C/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAO9B;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB5C;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAanC,OAAO,CAAC,gBAAgB;YAIV,aAAa;IA8O3B,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B5B;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAuBhC;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;IAmG7B;;;OAGG;IACH,oBAAoB,CAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO;IAeV;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAQhC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,6BAA6B;IAkBrC,uCAAuC;IACjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+C3B,OAAO,CAAC,kBAAkB;YAaZ,YAAY;IA6K1B;;;;;;;;;;;;;;;;;OAiBG;YACW,4BAA4B;YAgI5B,qBAAqB;YAmWrB,wBAAwB;YAsFxB,eAAe;YA0Vf,sBAAsB;IAmDpC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,oCAAoC;IAiCnD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,qCAAqC;IAiDpD;;;;;;;;;;;;;OAaG;YACW,qBAAqB;IAwCnC;;;;;;OAMG;YACW,gBAAgB;IA8B9B,gEAAgE;YAClD,uBAAuB;IAkCrC,kFAAkF;YACpE,uBAAuB;IAyCrC,qEAAqE;YACvD,oBAAoB;IAwElC;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IA4GjC;;;;;OAKG;YACW,0BAA0B;IA8KxC;;;;;;;;;;;;;;QAcI;YACU,+BAA+B;IAkG7C;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAY7B;;;;;;;;;;;;OAYG;YACW,qBAAqB;IAkBnC;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAiC/B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;YAQX,oCAAoC;IA0DlD,OAAO,CAAC,uBAAuB;IA8B/B;;;;;;;;OAQG;IACH,OAAO,CAAC,qCAAqC;YAW/B,iBAAiB;IA2C/B,OAAO,CAAC,kBAAkB;IA2B1B;;;;;OAKG;YACW,kBAAkB;IAgChC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;YA4BtB,oBAAoB;CAuBnC"}
|
|
@@ -105,6 +105,24 @@ const SUB_AGENT_ALLOWED_MCP_TOOLS = new Set([
|
|
|
105
105
|
"memory_recurrence",
|
|
106
106
|
"memory_recurrence_complete",
|
|
107
107
|
]);
|
|
108
|
+
/**
|
|
109
|
+
* explore 类型 sub-agent 被禁用的内建工具(只读模式黑名单)。
|
|
110
|
+
*
|
|
111
|
+
* 'explore' 语义上不修改主工作区文件,仅用于代码探索 / memory 整理等只读任务。
|
|
112
|
+
* 此处过滤直接写工作区文件的内建工具:
|
|
113
|
+
* - write_file / edit_file / delete_file:直接写工作区文件
|
|
114
|
+
*
|
|
115
|
+
* Bash 保留:只读探索常用(git diff / git log / ripgrep 高级查询等),
|
|
116
|
+
* 风险由工具内部的 workingDirectory 限制 + 用户授权流程管控。
|
|
117
|
+
*
|
|
118
|
+
* read_file / grep / symbols / Bash / ask_user_question / skill / todo_* 等只读工具保留。
|
|
119
|
+
* MCP 工具白名单由 SUB_AGENT_ALLOWED_MCP_TOOLS 独立施加,此处不重复。
|
|
120
|
+
*/
|
|
121
|
+
const EXPLORE_READONLY_DENIED_TOOLS = new Set([
|
|
122
|
+
"write_file",
|
|
123
|
+
"edit_file",
|
|
124
|
+
"delete_file",
|
|
125
|
+
]);
|
|
108
126
|
const DEFAULT_BASH_BUILT_IN_TOOL_TIMEOUT_MS = 10 * 60_000 + 2000;
|
|
109
127
|
const DEFAULT_RUNTIME_SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
110
128
|
/**
|
|
@@ -1172,6 +1190,15 @@ export class ACodeRuntime extends EventEmitter {
|
|
|
1172
1190
|
...subAgentTools,
|
|
1173
1191
|
...mcpConnections.flatMap((connection) => connection.tools),
|
|
1174
1192
|
];
|
|
1193
|
+
// ★ explore 类型 sub-agent 只读模式:过滤掉会修改主工作区文件的内建工具。
|
|
1194
|
+
// 'explore' 语义上不修改主工作区文件(见 SubAgentManager.spawn 注释),
|
|
1195
|
+
// 原设计仅靠 SUB_AGENT_ALLOWED_MCP_TOOLS 白名单限制 MCP 工具,
|
|
1196
|
+
// 内建工具(write_file/edit_file/delete_file)未被过滤,
|
|
1197
|
+
// 导致 explore 子 agent 仍能改写工作区文件,违背只读语义。
|
|
1198
|
+
// Bash 保留:只读探索常用(git diff/log 等)。
|
|
1199
|
+
const filteredExposedTools = this.config.subagentType === "explore"
|
|
1200
|
+
? exposedTools.filter((tool) => !EXPLORE_READONLY_DENIED_TOOLS.has(tool.exposedName))
|
|
1201
|
+
: exposedTools;
|
|
1175
1202
|
const commandDirs = this.resolveCommandDirs(acodeConfig);
|
|
1176
1203
|
this.emitAcodeEvent("startup_phase", { phase: "loading_commands" });
|
|
1177
1204
|
const commands = await loadAcodeCommands(commandDirs);
|
|
@@ -1214,7 +1241,7 @@ export class ACodeRuntime extends EventEmitter {
|
|
|
1214
1241
|
permissions,
|
|
1215
1242
|
mcpConnections,
|
|
1216
1243
|
ownsMcpConnections,
|
|
1217
|
-
exposedTools,
|
|
1244
|
+
exposedTools: filteredExposedTools,
|
|
1218
1245
|
commands,
|
|
1219
1246
|
commandRegistry,
|
|
1220
1247
|
conversation: cloneChatConversation(initialConversation),
|
|
@@ -1236,7 +1263,7 @@ export class ACodeRuntime extends EventEmitter {
|
|
|
1236
1263
|
workingDirectory: this.config.workingDirectory,
|
|
1237
1264
|
model: provider.model,
|
|
1238
1265
|
commandCount: commands.length,
|
|
1239
|
-
estimatedContextTokens: estimateContextTokens(systemMessagesPlainText,
|
|
1266
|
+
estimatedContextTokens: estimateContextTokens(systemMessagesPlainText, filteredExposedTools),
|
|
1240
1267
|
contextLimit: compactionConfig.contextLimit,
|
|
1241
1268
|
maxOutput: compactionConfig.maxOutput,
|
|
1242
1269
|
...(compactionConfig.inputLimit !== undefined
|
|
@@ -1250,11 +1277,11 @@ export class ACodeRuntime extends EventEmitter {
|
|
|
1250
1277
|
servers: Object.keys(acodeConfig.mcpServers ?? {}),
|
|
1251
1278
|
connectedCount: mcpConnections.length,
|
|
1252
1279
|
builtInToolCount: builtInTools.length,
|
|
1253
|
-
toolCount:
|
|
1280
|
+
toolCount: filteredExposedTools.length,
|
|
1254
1281
|
permissionMode: permissions.mode,
|
|
1255
1282
|
allowedToolRules: permissions.allow,
|
|
1256
1283
|
deniedToolRules: permissions.deny,
|
|
1257
|
-
tools:
|
|
1284
|
+
tools: filteredExposedTools.map((tool) => ({
|
|
1258
1285
|
name: tool.exposedName,
|
|
1259
1286
|
source: tool.kind === "mcp"
|
|
1260
1287
|
? `${tool.serverName}.${tool.toolName}`
|
|
@@ -3148,13 +3175,34 @@ ${urisText}
|
|
|
3148
3175
|
return `你是一条专门处理永久记忆文件变更更新的子 agent。
|
|
3149
3176
|
|
|
3150
3177
|
以下 ${delegate.uris.length} 个永久记忆绑定的文件已发生变更:
|
|
3151
|
-
${
|
|
3178
|
+
${delegate.uris.map(uri => {
|
|
3179
|
+
const fc = delegate.fileChanges?.find(f => f.uri === uri);
|
|
3180
|
+
if (!fc) {
|
|
3181
|
+
return `- ${uri}`;
|
|
3182
|
+
}
|
|
3183
|
+
const statusLabel = {
|
|
3184
|
+
no_change: '无变更',
|
|
3185
|
+
content_changed: '内容变更',
|
|
3186
|
+
not_found: '文件找不到',
|
|
3187
|
+
};
|
|
3188
|
+
// 逐文件状态:优先用 fileStatuses(含被删除文件),回退到 boundFiles 清单
|
|
3189
|
+
const fileStatuses = fc.fileStatuses && fc.fileStatuses.length > 0 ? fc.fileStatuses : [];
|
|
3190
|
+
// 展示全部绑定文件:无变更的仅列路径不加标注,内容变更/文件找不到的在路径后标注状态
|
|
3191
|
+
const bound = fileStatuses.length > 0
|
|
3192
|
+
? fileStatuses.map(s => s.status === 'no_change'
|
|
3193
|
+
? ` · ${s.path}`
|
|
3194
|
+
: ` · ${s.path}(${statusLabel[s.status] ?? s.status})`).join('\n')
|
|
3195
|
+
: (fc.boundFiles && fc.boundFiles.length > 0
|
|
3196
|
+
? fc.boundFiles.map(f => ` · ${f}`).join('\n')
|
|
3197
|
+
: ' 无(请通过 read_memory 查看 bound_files)');
|
|
3198
|
+
return `- ${uri}\n - 绑定文件:\n${bound}`;
|
|
3199
|
+
}).join('\n')}
|
|
3152
3200
|
|
|
3153
3201
|
请执行以下步骤(核心逻辑:更新 → 验证 → 未完成则继续):
|
|
3154
3202
|
|
|
3155
3203
|
=== 更新阶段 ===
|
|
3156
|
-
1. 调用 read_memory 读取这些 memory
|
|
3157
|
-
2.
|
|
3204
|
+
1. 调用 read_memory 读取这些 memory 的当前内容(上方已列出每个 memory 的绑定文件,有变更/找不到的已在路径后标注状态,可直接定位读取,无需等待 read_memory 返回 bound_files)
|
|
3205
|
+
2. 重点查看带(内容变更)/(文件找不到)标注的绑定文件,理解变更点;若列出的绑定文件均无标注,则表示本轮均无变更,可调用 read_memory 核对确认
|
|
3158
3206
|
3. 调用 update_memory 更新 memory 内容,确保与文件最新状态同步
|
|
3159
3207
|
|
|
3160
3208
|
=== 验证阶段(关键,不可跳过) ===
|
|
@@ -3222,15 +3270,26 @@ ${urisText}
|
|
|
3222
3270
|
continue;
|
|
3223
3271
|
}
|
|
3224
3272
|
// 用过滤后的 URI 构造 prompt
|
|
3273
|
+
// 若 delegate 携带了 fileChanges(memory_file_change 类型),同步过滤到 newUris,
|
|
3274
|
+
// 让子 agent 无需先 read_memory 即可知晓每个 memory 绑定/变更了哪些文件。
|
|
3275
|
+
const filteredFileChanges = Array.isArray(delegate.fileChanges)
|
|
3276
|
+
? delegate.fileChanges.filter(fc => newUris.includes(fc.uri))
|
|
3277
|
+
: undefined;
|
|
3225
3278
|
const prompt = this.buildMemoryDelegatePrompt({
|
|
3226
3279
|
type: delegate.type,
|
|
3227
3280
|
uris: newUris,
|
|
3281
|
+
fileChanges: filteredFileChanges,
|
|
3228
3282
|
});
|
|
3229
3283
|
try {
|
|
3230
3284
|
const subId = await manager.spawn({
|
|
3231
3285
|
parentAgentId: this.config.agentId || 'unknown',
|
|
3232
3286
|
parentSessionId: this.config.sessionId,
|
|
3233
|
-
|
|
3287
|
+
// 使用只读类型 'explore':
|
|
3288
|
+
// - MCP 工具由 SUB_AGENT_ALLOWED_MCP_TOOLS 白名单施加(仅 memory 工具)
|
|
3289
|
+
// - 内建工具由 runtime.start() 的 EXPLORE_READONLY_DENIED_TOOLS
|
|
3290
|
+
// 黑名单过滤(禁用 write_file/edit_file/delete_file,保留 Bash 用于 git diff 等)
|
|
3291
|
+
// 'explore' 语义上表示该子 agent 不修改主工作区文件,仅处理 memory 整理任务。
|
|
3292
|
+
subagentType: 'explore',
|
|
3234
3293
|
description: `Memory ${delegate.type} auto-delegate (${newUris.length} items)`,
|
|
3235
3294
|
prompt,
|
|
3236
3295
|
workingDirectory: this.config.workingDirectory,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sub-agent-manager.d.ts","sourceRoot":"","sources":["../src/sub-agent-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EACV,uBAAuB,EACvB,aAAa,EAEb,cAAc,EACd,YAAY,EACb,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"sub-agent-manager.d.ts","sourceRoot":"","sources":["../src/sub-agent-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,13 +14,22 @@ const SUBAGENT_PENDING_ZOMBIE_MS = 2 * 60 * 1000;
|
|
|
14
14
|
* 正常 LLM 子任务即便较长也应有上界;卡死多因 runtime.start()/submit() 挂起
|
|
15
15
|
* (如复用的父 MCP 连接异常、模型调用无响应),需回收以释放资源并解除锁。
|
|
16
16
|
*
|
|
17
|
-
* ★
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* ★ 设为 12min(> SUBAGENT_SUBMIT_TIMEOUT_MS=10min):
|
|
18
|
+
* - 子 agent 卡死时,submit() 10min 超时会先触发,给出明确的错误信息
|
|
19
|
+
* (如 "runtime.submit() turn=1 timed out after 600000ms")
|
|
20
|
+
* - zombie 兜底覆盖 submit 内部无超时保护的步骤(如 hookBus.dispatch)
|
|
21
|
+
* - 之前 5min 比 submit 10min 更早触发,导致用户永远看不到真正的 submit 超时错误
|
|
22
|
+
* - task 工具显式调用的 sub-agent 不走此自动路径,不受影响
|
|
22
23
|
*/
|
|
23
|
-
const SUBAGENT_RUNNING_ZOMBIE_MS =
|
|
24
|
+
const SUBAGENT_RUNNING_ZOMBIE_MS = 12 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* store 写入超时兜底(毫秒)。
|
|
27
|
+
* SubAgentStore.write 使用 writeChain 串行化所有文件写入,若前一个 write 卡住
|
|
28
|
+
* (磁盘 IO 慢、tmp 文件 rename 卡住、文件系统问题等),后续所有 write 都会
|
|
29
|
+
* 被永久阻塞,导致 sub-agent 卡在 store.updateStatus 处无法继续 start()。
|
|
30
|
+
* 5s 超时足够覆盖正常文件 IO(通常 < 100ms),超时后放弃落盘(不影响内存态)。
|
|
31
|
+
*/
|
|
32
|
+
const STORE_WRITE_TIMEOUT_MS = 5_000;
|
|
24
33
|
/**
|
|
25
34
|
* 子 agent runtime.start() 整体超时兜底(毫秒)。
|
|
26
35
|
* start() 内部正常路径秒级完成(loadSkills/resolveProvider/filterMcp/fetchProfile/emitStatus),
|
|
@@ -112,6 +121,10 @@ export class SubAgentManager {
|
|
|
112
121
|
initialPrompt: undefined,
|
|
113
122
|
// sub-agent 不允许再 spawn 自己的子 agent(禁止递归嵌套)
|
|
114
123
|
subAgentManager: undefined,
|
|
124
|
+
// ★ 注入 subagentType:runtime.start() 据此过滤内建写入工具。
|
|
125
|
+
// explore 类型仅暴露只读工具(read_file/grep/symbols 等),
|
|
126
|
+
// 防止 explore 子 agent 改写主工作区文件,违背只读语义。
|
|
127
|
+
subagentType: opts.subagentType,
|
|
115
128
|
// Sub Agent 模型/强度覆盖:有值则覆盖继承的 model/effort
|
|
116
129
|
...(opts.parentConfig.subAgentModel
|
|
117
130
|
? { model: opts.parentConfig.subAgentModel }
|
|
@@ -334,10 +347,24 @@ export class SubAgentManager {
|
|
|
334
347
|
}
|
|
335
348
|
};
|
|
336
349
|
entry.runtime.on("event", onEvent);
|
|
337
|
-
// 状态 -> running
|
|
350
|
+
// ★ 状态 -> running:先同步设置 + emit + currentTool 标记,再异步落盘。
|
|
351
|
+
// 之前顺序是 status=running → await store.updateStatus → emit running,
|
|
352
|
+
// 若 store.updateStatus 因 writeChain 串行化卡住(前一个 write 卡死),
|
|
353
|
+
// emit running 不会执行,start() 也不会被调用,前端 modal 永远显示
|
|
354
|
+
// 初始 snapshot(0消息 0tokens 当前工具-),与"从未运行"无法区分。
|
|
355
|
+
// 调整顺序后即使落盘卡住,前端也能立即看到 [starting] 标记。
|
|
338
356
|
entry.status = "running";
|
|
339
|
-
|
|
340
|
-
|
|
357
|
+
entry.contextSnapshot.currentTool = "[starting]";
|
|
358
|
+
entry.contextSnapshot.updatedAt = new Date().toISOString();
|
|
359
|
+
this.emitEvent(entry.subId, "running", { snapshot: { ...entry.contextSnapshot } });
|
|
360
|
+
// 落盘带超时保护:writeChain 串行化可能卡住,超时后放弃落盘(不影响内存态)
|
|
361
|
+
try {
|
|
362
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, { status: "running" }), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(running)`);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
console.error(`[SubAgentManager] store.updateStatus(running) failed for subId=${entry.subId}: ` +
|
|
366
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
367
|
+
}
|
|
341
368
|
try {
|
|
342
369
|
await entry.runtime.start();
|
|
343
370
|
// 记录启动后的可用工具列表
|
|
@@ -353,14 +380,31 @@ export class SubAgentManager {
|
|
|
353
380
|
entry.result = output;
|
|
354
381
|
entry.endedAt = Date.now();
|
|
355
382
|
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
result: output,
|
|
359
|
-
endedAt: entry.endedAt,
|
|
360
|
-
durationMs: entry.durationMs,
|
|
361
|
-
contextSnapshot: undefined, // 完成后清空快照
|
|
362
|
-
});
|
|
383
|
+
// ★ emit 前置:先把 completed 事件发出,让前端 modal 立即看到状态变化
|
|
384
|
+
// (subscribers 同步执行,SSE 路由立即推送)。
|
|
363
385
|
this.emitEvent(entry.subId, "completed", { result: output });
|
|
386
|
+
// ★ store.updateStatus 必须在 completionResolve 之前:否则 awaitCompletion 在
|
|
387
|
+
// resolve 后立即调 store.get 读文件,会与 store.updateStatus 的 fs.rename
|
|
388
|
+
// 并发,Windows 上 fs.rename 因 target 被打开而抛 EPERM,导致 store
|
|
389
|
+
// 永远写不进 completed 状态。
|
|
390
|
+
try {
|
|
391
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
392
|
+
status: "completed",
|
|
393
|
+
result: output,
|
|
394
|
+
endedAt: entry.endedAt,
|
|
395
|
+
durationMs: entry.durationMs,
|
|
396
|
+
// ★ 持久化 availableTools:start() 后才填充,必须随终态落盘,
|
|
397
|
+
// 否则 entry 从内存清理后 store 永远是 spawn 时的空数组,
|
|
398
|
+
// 前端 modal "可用工具" tab 始终显示"暂无工具信息"。
|
|
399
|
+
availableTools: entry.availableTools,
|
|
400
|
+
contextSnapshot: undefined, // 完成后清空快照
|
|
401
|
+
}), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(completed)`);
|
|
402
|
+
}
|
|
403
|
+
catch (storeErr) {
|
|
404
|
+
console.error(`[SubAgentManager] store.updateStatus(completed) failed for subId=${entry.subId}: ` +
|
|
405
|
+
`${storeErr instanceof Error ? storeErr.message : String(storeErr)}`);
|
|
406
|
+
}
|
|
407
|
+
// 持久化完成(或超时放弃)后,settle completionPromise + 入队通知
|
|
364
408
|
entry.completionResolve?.({ output });
|
|
365
409
|
this.enqueueCompletion(entry, output, undefined);
|
|
366
410
|
}
|
|
@@ -370,14 +414,22 @@ export class SubAgentManager {
|
|
|
370
414
|
entry.errorMessage = errorMessage;
|
|
371
415
|
entry.endedAt = Date.now();
|
|
372
416
|
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
373
|
-
|
|
374
|
-
status: "failed",
|
|
375
|
-
errorMessage,
|
|
376
|
-
endedAt: entry.endedAt,
|
|
377
|
-
durationMs: entry.durationMs,
|
|
378
|
-
contextSnapshot: undefined,
|
|
379
|
-
});
|
|
417
|
+
// ★ emit 前置 + store 超时保护(同 completed 路径的设计理由)
|
|
380
418
|
this.emitEvent(entry.subId, "failed", { error: errorMessage });
|
|
419
|
+
try {
|
|
420
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
421
|
+
status: "failed",
|
|
422
|
+
errorMessage,
|
|
423
|
+
endedAt: entry.endedAt,
|
|
424
|
+
durationMs: entry.durationMs,
|
|
425
|
+
availableTools: entry.availableTools,
|
|
426
|
+
contextSnapshot: undefined,
|
|
427
|
+
}), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(failed)`);
|
|
428
|
+
}
|
|
429
|
+
catch (storeErr) {
|
|
430
|
+
console.error(`[SubAgentManager] store.updateStatus(failed) failed for subId=${entry.subId}: ` +
|
|
431
|
+
`${storeErr instanceof Error ? storeErr.message : String(storeErr)}`);
|
|
432
|
+
}
|
|
381
433
|
entry.completionResolve?.({ output: "", error: errorMessage });
|
|
382
434
|
this.enqueueCompletion(entry, "", errorMessage);
|
|
383
435
|
}
|
|
@@ -436,10 +488,24 @@ export class SubAgentManager {
|
|
|
436
488
|
}
|
|
437
489
|
};
|
|
438
490
|
entry.runtime.on("event", onEvent);
|
|
439
|
-
// 状态 -> running
|
|
491
|
+
// ★ 状态 -> running:先同步设置 + emit + currentTool 标记,再异步落盘。
|
|
492
|
+
// 之前顺序是 status=running → await store.updateStatus → emit running,
|
|
493
|
+
// 若 store.updateStatus 因 writeChain 串行化卡住(前一个 write 卡死),
|
|
494
|
+
// emit running 不会执行,start() 也不会被调用,前端 modal 永远显示
|
|
495
|
+
// 初始 snapshot(0消息 0tokens 当前工具-),与"从未运行"无法区分。
|
|
496
|
+
// 调整顺序后即使落盘卡住,前端也能立即看到 [starting] 标记。
|
|
440
497
|
entry.status = "running";
|
|
441
|
-
|
|
442
|
-
|
|
498
|
+
entry.contextSnapshot.currentTool = "[starting]";
|
|
499
|
+
entry.contextSnapshot.updatedAt = new Date().toISOString();
|
|
500
|
+
this.emitEvent(entry.subId, "running", { snapshot: { ...entry.contextSnapshot } });
|
|
501
|
+
// 落盘带超时保护:writeChain 串行化可能卡住,超时后放弃落盘(不影响内存态)
|
|
502
|
+
try {
|
|
503
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, { status: "running" }), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(running)`);
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
console.error(`[SubAgentManager] store.updateStatus(running) failed for subId=${entry.subId}: ` +
|
|
507
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
508
|
+
}
|
|
443
509
|
try {
|
|
444
510
|
// ★ 超时兜底:start() 通常秒级完成,超时说明卡在 hookBus/fetchProfile/loadCommands
|
|
445
511
|
// 等无超时保护的步骤。抛错后被外层 catch 标记为 failed,前端可见。
|
|
@@ -498,14 +564,24 @@ export class SubAgentManager {
|
|
|
498
564
|
entry.result = output;
|
|
499
565
|
entry.endedAt = Date.now();
|
|
500
566
|
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
endedAt: entry.endedAt,
|
|
505
|
-
durationMs: entry.durationMs,
|
|
506
|
-
contextSnapshot: undefined,
|
|
507
|
-
});
|
|
567
|
+
// ★ emit 前置 + store 超时保护 + completionResolve 在 store 之后
|
|
568
|
+
// (设计理由同 runEntryToCompletion,避免 awaitCompletion 读 store
|
|
569
|
+
// 与 store.updateStatus 的 fs.rename 并发触发 Windows EPERM)
|
|
508
570
|
this.emitEvent(entry.subId, "completed", { result: output });
|
|
571
|
+
try {
|
|
572
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
573
|
+
status: "completed",
|
|
574
|
+
result: output,
|
|
575
|
+
endedAt: entry.endedAt,
|
|
576
|
+
durationMs: entry.durationMs,
|
|
577
|
+
availableTools: entry.availableTools,
|
|
578
|
+
contextSnapshot: undefined,
|
|
579
|
+
}), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(completed)`);
|
|
580
|
+
}
|
|
581
|
+
catch (storeErr) {
|
|
582
|
+
console.error(`[SubAgentManager] store.updateStatus(completed) failed for subId=${entry.subId}: ` +
|
|
583
|
+
`${storeErr instanceof Error ? storeErr.message : String(storeErr)}`);
|
|
584
|
+
}
|
|
509
585
|
entry.completionResolve?.({ output });
|
|
510
586
|
this.enqueueCompletion(entry, output, undefined);
|
|
511
587
|
}
|
|
@@ -515,14 +591,22 @@ export class SubAgentManager {
|
|
|
515
591
|
entry.errorMessage = errorMessage;
|
|
516
592
|
entry.endedAt = Date.now();
|
|
517
593
|
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
518
|
-
|
|
519
|
-
status: "failed",
|
|
520
|
-
errorMessage,
|
|
521
|
-
endedAt: entry.endedAt,
|
|
522
|
-
durationMs: entry.durationMs,
|
|
523
|
-
contextSnapshot: undefined,
|
|
524
|
-
});
|
|
594
|
+
// ★ emit 前置 + store 超时保护(同 completed 路径的设计理由)
|
|
525
595
|
this.emitEvent(entry.subId, "failed", { error: errorMessage });
|
|
596
|
+
try {
|
|
597
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
598
|
+
status: "failed",
|
|
599
|
+
errorMessage,
|
|
600
|
+
endedAt: entry.endedAt,
|
|
601
|
+
durationMs: entry.durationMs,
|
|
602
|
+
availableTools: entry.availableTools,
|
|
603
|
+
contextSnapshot: undefined,
|
|
604
|
+
}), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(failed)`);
|
|
605
|
+
}
|
|
606
|
+
catch (storeErr) {
|
|
607
|
+
console.error(`[SubAgentManager] store.updateStatus(failed) failed for subId=${entry.subId}: ` +
|
|
608
|
+
`${storeErr instanceof Error ? storeErr.message : String(storeErr)}`);
|
|
609
|
+
}
|
|
526
610
|
entry.completionResolve?.({ output: "", error: errorMessage });
|
|
527
611
|
this.enqueueCompletion(entry, "", errorMessage);
|
|
528
612
|
}
|
|
@@ -580,13 +664,22 @@ export class SubAgentManager {
|
|
|
580
664
|
entry.status = "cancelled";
|
|
581
665
|
entry.endedAt = Date.now();
|
|
582
666
|
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
endedAt: entry.endedAt,
|
|
586
|
-
durationMs: entry.durationMs,
|
|
587
|
-
contextSnapshot: undefined,
|
|
588
|
-
});
|
|
667
|
+
// ★ emit 前置 + store 超时保护 + completionResolve 在 store 之后
|
|
668
|
+
// (设计理由同 completed 路径,避免 abort 调用方读 store 与 fs.rename 并发 EPERM)
|
|
589
669
|
this.emitEvent(subId, "cancelled", {});
|
|
670
|
+
try {
|
|
671
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, subId, {
|
|
672
|
+
status: "cancelled",
|
|
673
|
+
endedAt: entry.endedAt,
|
|
674
|
+
durationMs: entry.durationMs,
|
|
675
|
+
availableTools: entry.availableTools,
|
|
676
|
+
contextSnapshot: undefined,
|
|
677
|
+
}), STORE_WRITE_TIMEOUT_MS, `sub-agent ${subId} store.updateStatus(cancelled)`);
|
|
678
|
+
}
|
|
679
|
+
catch (storeErr) {
|
|
680
|
+
console.error(`[SubAgentManager] store.updateStatus(cancelled) failed for subId=${subId}: ` +
|
|
681
|
+
`${storeErr instanceof Error ? storeErr.message : String(storeErr)}`);
|
|
682
|
+
}
|
|
590
683
|
// settle completionPromise(cancelled 视为 error)
|
|
591
684
|
entry.completionResolve?.({ output: "", error: "aborted" });
|
|
592
685
|
this.enqueueCompletion(entry, "", "aborted");
|
|
@@ -782,19 +875,24 @@ export class SubAgentManager {
|
|
|
782
875
|
entry.errorMessage = errorMessage;
|
|
783
876
|
entry.endedAt = Date.now();
|
|
784
877
|
entry.durationMs = entry.endedAt - entry.startedAt;
|
|
878
|
+
// ★ emit 前置 + store 超时保护 + completionResolve 在 store 之后
|
|
879
|
+
// (与其他终态路径保持一致,zombie 回收路径若 writeChain 卡住,
|
|
880
|
+
// 也应保证 runtime 收到 failed 事件从而释放 pendingMemorySubAgents URI 锁)
|
|
881
|
+
this.emitEvent(entry.subId, "failed", { error: errorMessage });
|
|
785
882
|
try {
|
|
786
|
-
await this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
883
|
+
await withTimeout(this.store.updateStatus(entry.parentAgentId, entry.subId, {
|
|
787
884
|
status: "failed",
|
|
788
885
|
errorMessage,
|
|
789
886
|
endedAt: entry.endedAt,
|
|
790
887
|
durationMs: entry.durationMs,
|
|
888
|
+
availableTools: entry.availableTools,
|
|
791
889
|
contextSnapshot: undefined,
|
|
792
|
-
});
|
|
890
|
+
}), STORE_WRITE_TIMEOUT_MS, `sub-agent ${entry.subId} store.updateStatus(zombie-failed)`);
|
|
793
891
|
}
|
|
794
|
-
catch {
|
|
795
|
-
|
|
892
|
+
catch (storeErr) {
|
|
893
|
+
console.error(`[SubAgentManager] store.updateStatus(zombie-failed) failed for subId=${entry.subId}: ` +
|
|
894
|
+
`${storeErr instanceof Error ? storeErr.message : String(storeErr)}`);
|
|
796
895
|
}
|
|
797
|
-
this.emitEvent(entry.subId, "failed", { error: errorMessage });
|
|
798
896
|
entry.completionResolve?.({ output: "", error: errorMessage });
|
|
799
897
|
this.enqueueCompletion(entry, "", errorMessage);
|
|
800
898
|
try {
|