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.
@@ -31,10 +31,29 @@ subAgentsRouter.get("/sub-agents/:agentId", validateToken, async (req, res) => {
31
31
  }
32
32
  try {
33
33
  // 从 store 读取全部历史
34
- const allRecords = await subAgentStore.list(agentId);
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 事件(从 store 读取,按时间升序)
225
+ // 1. 补发历史 tool 事件(从独立 jsonl 事件文件读取,按时间升序)
226
+ // 事件文件与主记录文件分离,避免高频 appendEvent 阻塞终态 updateStatus
207
227
  try {
208
- const record = await subAgentStore.get(agentId, subId);
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":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -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, exposedTools),
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: exposedTools.length,
1280
+ toolCount: filteredExposedTools.length,
1254
1281
  permissionMode: permissions.mode,
1255
1282
  allowedToolRules: permissions.allow,
1256
1283
  deniedToolRules: permissions.deny,
1257
- tools: exposedTools.map((tool) => ({
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
- ${urisText}
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. 查看相关绑定文件的最新内容(bound_files 中列出),理解变更点
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
- subagentType: 'general',
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":"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"}
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
- * ★ 曾为 30min,但实际现象:子 agent 卡死时前端 modal 长时间显示"运行中 + 0消息 + 0tokens",
18
- * pendingMemorySubAgents URI 锁会持续抑制该 memory 的重新委托,加上 10min 冷却
19
- * 总卡顿可达 30~45min。缩到 5min:自动 memory 委托是后台任务,5min 足够覆盖
20
- * 正常 LLM 子任务(通常 30s~2min),更长时间无终态基本可判定为卡死。
21
- * task 工具显式调用的 sub-agent 不走此自动路径,不受影响。
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 = 5 * 60 * 1000;
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
- await this.store.updateStatus(entry.parentAgentId, entry.subId, { status: "running" });
340
- this.emitEvent(entry.subId, "running", {});
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
- await this.store.updateStatus(entry.parentAgentId, entry.subId, {
357
- status: "completed",
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
- await this.store.updateStatus(entry.parentAgentId, entry.subId, {
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
- await this.store.updateStatus(entry.parentAgentId, entry.subId, { status: "running" });
442
- this.emitEvent(entry.subId, "running", {});
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
- await this.store.updateStatus(entry.parentAgentId, entry.subId, {
502
- status: "completed",
503
- result: output,
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
- await this.store.updateStatus(entry.parentAgentId, entry.subId, {
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
- await this.store.updateStatus(entry.parentAgentId, subId, {
584
- status: "cancelled",
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 {