aws-runtime-bridge 1.9.32 → 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.
Files changed (276) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +35 -1
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +417 -40
  4. package/dist/adapter/ClaudeSdkAdapter.d.ts +6 -0
  5. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
  6. package/dist/adapter/ClaudeSdkAdapter.js +23 -1
  7. package/dist/adapter/idle-poll-loop.d.ts +2 -1
  8. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  9. package/dist/adapter/types.d.ts +65 -3
  10. package/dist/adapter/types.d.ts.map +1 -1
  11. package/dist/adapter/types.js +5 -4
  12. package/dist/index.js +4 -0
  13. package/dist/routes/acode-todos.d.ts +11 -0
  14. package/dist/routes/acode-todos.d.ts.map +1 -0
  15. package/dist/routes/acode-todos.js +75 -0
  16. package/dist/routes/memory.d.ts +1 -1
  17. package/dist/routes/memory.d.ts.map +1 -1
  18. package/dist/routes/memory.js +3 -3
  19. package/dist/routes/pty.d.ts.map +1 -1
  20. package/dist/routes/pty.js +9 -0
  21. package/dist/routes/sub-agents.d.ts +14 -0
  22. package/dist/routes/sub-agents.d.ts.map +1 -0
  23. package/dist/routes/sub-agents.js +253 -0
  24. package/dist/routes/terminal.d.ts +9 -1
  25. package/dist/routes/terminal.d.ts.map +1 -1
  26. package/dist/routes/terminal.js +207 -22
  27. package/dist/services/agent-event-store.d.ts +1 -0
  28. package/dist/services/agent-event-store.d.ts.map +1 -1
  29. package/dist/services/event-delta-pusher.js +4 -4
  30. package/dist/services/session-output.d.ts +3 -0
  31. package/dist/services/session-output.d.ts.map +1 -1
  32. package/dist/services/session-output.js +38 -2
  33. package/dist/services/sub-agent-manager-instance.d.ts +4 -0
  34. package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
  35. package/dist/services/sub-agent-manager-instance.js +12 -0
  36. package/dist/services/workspace-files.d.ts +1 -0
  37. package/dist/services/workspace-files.d.ts.map +1 -1
  38. package/dist/services/workspace-files.js +57 -1
  39. package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
  40. package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
  41. package/package/acode/dist/ask-user-question-tool.js +233 -0
  42. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  43. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  44. package/package/acode/dist/compaction.d.ts +206 -0
  45. package/package/acode/dist/compaction.d.ts.map +1 -0
  46. package/package/acode/dist/compaction.js +664 -0
  47. package/package/acode/dist/config.d.ts +6 -1
  48. package/package/acode/dist/config.d.ts.map +1 -1
  49. package/package/acode/dist/config.js +27 -2
  50. package/package/acode/dist/index.d.ts +4 -0
  51. package/package/acode/dist/index.d.ts.map +1 -1
  52. package/package/acode/dist/index.js +4 -0
  53. package/package/acode/dist/runtime.d.ts +137 -0
  54. package/package/acode/dist/runtime.d.ts.map +1 -1
  55. package/package/acode/dist/runtime.js +974 -172
  56. package/package/acode/dist/skill-tool.d.ts +30 -0
  57. package/package/acode/dist/skill-tool.d.ts.map +1 -0
  58. package/package/acode/dist/skill-tool.js +174 -0
  59. package/package/acode/dist/sub-agent-manager.d.ts +169 -0
  60. package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
  61. package/package/acode/dist/sub-agent-manager.js +535 -0
  62. package/package/acode/dist/sub-agent-store.d.ts +33 -0
  63. package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
  64. package/package/acode/dist/sub-agent-store.js +109 -0
  65. package/package/acode/dist/sub-agent-tools.d.ts +72 -0
  66. package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
  67. package/package/acode/dist/sub-agent-tools.js +429 -0
  68. package/package/acode/dist/sub-agent-types.d.ts +81 -0
  69. package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
  70. package/package/acode/dist/sub-agent-types.js +8 -0
  71. package/package/acode/dist/todo-store.d.ts +42 -0
  72. package/package/acode/dist/todo-store.d.ts.map +1 -0
  73. package/package/acode/dist/todo-store.js +68 -0
  74. package/package/acode/dist/todo-tool.d.ts +6 -0
  75. package/package/acode/dist/todo-tool.d.ts.map +1 -0
  76. package/package/acode/dist/todo-tool.js +138 -0
  77. package/package/acode/dist/types.d.ts +22 -1
  78. package/package/acode/dist/types.d.ts.map +1 -1
  79. package/package/acode/package.json +13 -2
  80. package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -2
  81. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
  82. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +83 -45
  83. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  84. package/package/aws-client-agent-mcp/dist/mcp-server.js +451 -325
  85. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  86. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +259 -241
  87. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  88. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -19
  89. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  90. package/package/aws-client-agent-mcp/dist/mcp-tools.js +2 -69
  91. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  92. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +17 -19
  93. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
  94. package/package/aws-client-agent-mcp/dist/memory-store.js +62 -99
  95. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
  96. package/package/aws-client-agent-mcp/dist/memory-store.test.js +207 -125
  97. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
  98. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -4
  99. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
  100. package/package/aws-client-agent-mcp/dist/memory-tools.js +28 -33
  101. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
  102. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +27 -9
  103. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
  104. package/package/aws-client-agent-mcp/dist/message-buffer.js +100 -23
  105. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
  106. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -3
  107. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
  108. package/package/aws-client-agent-mcp/dist/server-memory-store.js +7 -11
  109. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
  110. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  111. package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
  112. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  113. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
  114. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
  115. package/package/aws-client-agent-mcp/dist/types.d.ts +3 -25
  116. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  117. package/package.json +17 -7
  118. package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
  119. package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
  120. package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
  121. package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
  122. package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
  123. package/dist/adapter/AdapterRegistry.test.js +0 -118
  124. package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
  125. package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
  126. package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
  127. package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
  128. package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
  129. package/dist/adapter/CodexSdkAdapter.test.js +0 -270
  130. package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
  131. package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
  132. package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
  133. package/dist/adapter/acode-tool-names.test.d.ts +0 -2
  134. package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
  135. package/dist/adapter/acode-tool-names.test.js +0 -28
  136. package/dist/adapter/adapter.test.d.ts +0 -5
  137. package/dist/adapter/adapter.test.d.ts.map +0 -1
  138. package/dist/adapter/adapter.test.js +0 -180
  139. package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
  140. package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
  141. package/dist/adapter/idle-poll-loop.test.js +0 -85
  142. package/dist/adapter/types.test.d.ts +0 -2
  143. package/dist/adapter/types.test.d.ts.map +0 -1
  144. package/dist/adapter/types.test.js +0 -50
  145. package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
  146. package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
  147. package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
  148. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
  149. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
  150. package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
  151. package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
  152. package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
  153. package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
  154. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
  155. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
  156. package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
  157. package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
  158. package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
  159. package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
  160. package/dist/config.test.d.ts +0 -2
  161. package/dist/config.test.d.ts.map +0 -1
  162. package/dist/config.test.js +0 -107
  163. package/dist/routes/ai-sources.test.d.ts +0 -2
  164. package/dist/routes/ai-sources.test.d.ts.map +0 -1
  165. package/dist/routes/ai-sources.test.js +0 -169
  166. package/dist/routes/file-browser.test.d.ts +0 -5
  167. package/dist/routes/file-browser.test.d.ts.map +0 -1
  168. package/dist/routes/file-browser.test.js +0 -155
  169. package/dist/routes/git.test.d.ts +0 -2
  170. package/dist/routes/git.test.d.ts.map +0 -1
  171. package/dist/routes/git.test.js +0 -330
  172. package/dist/routes/instance.test.d.ts +0 -5
  173. package/dist/routes/instance.test.d.ts.map +0 -1
  174. package/dist/routes/instance.test.js +0 -389
  175. package/dist/routes/mcp.test.d.ts +0 -5
  176. package/dist/routes/mcp.test.d.ts.map +0 -1
  177. package/dist/routes/mcp.test.js +0 -92
  178. package/dist/routes/memory-route-wiring.test.d.ts +0 -2
  179. package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
  180. package/dist/routes/memory-route-wiring.test.js +0 -15
  181. package/dist/routes/memory.test.d.ts +0 -2
  182. package/dist/routes/memory.test.d.ts.map +0 -1
  183. package/dist/routes/memory.test.js +0 -177
  184. package/dist/routes/properties.test.d.ts +0 -5
  185. package/dist/routes/properties.test.d.ts.map +0 -1
  186. package/dist/routes/properties.test.js +0 -72
  187. package/dist/routes/pty.test.d.ts +0 -2
  188. package/dist/routes/pty.test.d.ts.map +0 -1
  189. package/dist/routes/pty.test.js +0 -82
  190. package/dist/routes/runtime-binding.test.d.ts +0 -2
  191. package/dist/routes/runtime-binding.test.d.ts.map +0 -1
  192. package/dist/routes/runtime-binding.test.js +0 -32
  193. package/dist/routes/sessions.test.d.ts +0 -5
  194. package/dist/routes/sessions.test.d.ts.map +0 -1
  195. package/dist/routes/sessions.test.js +0 -86
  196. package/dist/routes/skills.test.d.ts +0 -5
  197. package/dist/routes/skills.test.d.ts.map +0 -1
  198. package/dist/routes/skills.test.js +0 -50
  199. package/dist/routes/system-metrics.test.d.ts +0 -2
  200. package/dist/routes/system-metrics.test.d.ts.map +0 -1
  201. package/dist/routes/system-metrics.test.js +0 -16
  202. package/dist/routes/terminal.stop-options.test.d.ts +0 -2
  203. package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
  204. package/dist/routes/terminal.stop-options.test.js +0 -39
  205. package/dist/routes/terminal.test.d.ts +0 -2
  206. package/dist/routes/terminal.test.d.ts.map +0 -1
  207. package/dist/routes/terminal.test.js +0 -746
  208. package/dist/routes/yml.test.d.ts +0 -5
  209. package/dist/routes/yml.test.d.ts.map +0 -1
  210. package/dist/routes/yml.test.js +0 -58
  211. package/dist/services/ai-source-apply.test.d.ts +0 -2
  212. package/dist/services/ai-source-apply.test.d.ts.map +0 -1
  213. package/dist/services/ai-source-apply.test.js +0 -138
  214. package/dist/services/auto-register.test.d.ts +0 -2
  215. package/dist/services/auto-register.test.d.ts.map +0 -1
  216. package/dist/services/auto-register.test.js +0 -184
  217. package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
  218. package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
  219. package/dist/services/aws-client-agent-mcp.test.js +0 -242
  220. package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
  221. package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
  222. package/dist/services/bridge-package-publish-hooks.test.js +0 -136
  223. package/dist/services/cc-switch-discovery.test.d.ts +0 -2
  224. package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
  225. package/dist/services/cc-switch-discovery.test.js +0 -56
  226. package/dist/services/cli-commands.test.d.ts +0 -2
  227. package/dist/services/cli-commands.test.d.ts.map +0 -1
  228. package/dist/services/cli-commands.test.js +0 -230
  229. package/dist/services/instance-init-service.test.d.ts +0 -2
  230. package/dist/services/instance-init-service.test.d.ts.map +0 -1
  231. package/dist/services/instance-init-service.test.js +0 -140
  232. package/dist/services/instance-state.test.d.ts +0 -2
  233. package/dist/services/instance-state.test.d.ts.map +0 -1
  234. package/dist/services/instance-state.test.js +0 -245
  235. package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
  236. package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
  237. package/dist/services/mcp-launch-binding-queue.test.js +0 -181
  238. package/dist/services/runtime-binding.test.d.ts +0 -2
  239. package/dist/services/runtime-binding.test.d.ts.map +0 -1
  240. package/dist/services/runtime-binding.test.js +0 -78
  241. package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
  242. package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
  243. package/dist/services/runtime-lifecycle-policy.test.js +0 -41
  244. package/dist/services/session-output.test.d.ts +0 -5
  245. package/dist/services/session-output.test.d.ts.map +0 -1
  246. package/dist/services/session-output.test.js +0 -177
  247. package/dist/services/startup-config-wizard.test.d.ts +0 -2
  248. package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
  249. package/dist/services/startup-config-wizard.test.js +0 -184
  250. package/dist/services/terminal-persistence.test.d.ts +0 -5
  251. package/dist/services/terminal-persistence.test.d.ts.map +0 -1
  252. package/dist/services/terminal-persistence.test.js +0 -159
  253. package/dist/services/tool-installer.test.d.ts +0 -2
  254. package/dist/services/tool-installer.test.d.ts.map +0 -1
  255. package/dist/services/tool-installer.test.js +0 -225
  256. package/dist/services/workspace-files.test.d.ts +0 -2
  257. package/dist/services/workspace-files.test.d.ts.map +0 -1
  258. package/dist/services/workspace-files.test.js +0 -648
  259. package/dist/services/workspace-watch.test.d.ts +0 -2
  260. package/dist/services/workspace-watch.test.d.ts.map +0 -1
  261. package/dist/services/workspace-watch.test.js +0 -38
  262. package/dist/utils/file-utils.test.d.ts +0 -2
  263. package/dist/utils/file-utils.test.d.ts.map +0 -1
  264. package/dist/utils/file-utils.test.js +0 -244
  265. package/dist/utils/logger.test.d.ts +0 -2
  266. package/dist/utils/logger.test.d.ts.map +0 -1
  267. package/dist/utils/logger.test.js +0 -93
  268. package/dist/utils/sdk-package-loader.test.d.ts +0 -2
  269. package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
  270. package/dist/utils/sdk-package-loader.test.js +0 -84
  271. package/dist/utils/validation.test.d.ts +0 -2
  272. package/dist/utils/validation.test.d.ts.map +0 -1
  273. package/dist/utils/validation.test.js +0 -88
  274. package/dist/utils/yaml-utils.test.d.ts +0 -2
  275. package/dist/utils/yaml-utils.test.d.ts.map +0 -1
  276. package/dist/utils/yaml-utils.test.js +0 -363
@@ -19,10 +19,22 @@ import { getAcodeConfigFile, getAcodeSkillsDir, getRuntimeHomeDir, } from "../co
19
19
  import { getReleasedAcodeEntryPath } from "../services/acode-package.js";
20
20
  import { getInstanceAiConfigDebugLogger } from "../utils/instance-ai-config-debug-logger.js";
21
21
  import { redactForLog } from "../services/instance-ai-config-service.js";
22
+ import { subAgentManager } from "../services/sub-agent-manager-instance.js";
22
23
  import { resolveAcodeProviderConfig, } from "./instance-ai-config-resolver.js";
23
24
  import { isMessagePollingIdleCommand, isPollMessageToolDescriptor, } from "./acode-tool-names.js";
24
25
  import { IdlePollLoop } from "./idle-poll-loop.js";
25
26
  import { getCanonicalMcpDetail, getToolActionInfo } from "./types.js";
27
+ /**
28
+ * Adapter 调试日志:受 ACODE_COMPACTION_DEBUG=1 开关控制(与 runtime.ts 共用同一开关)。
29
+ * 默认关闭以避免生产环境噪声与潜在的 sessionId/agentId/event 信息泄漏。
30
+ * 文件中所有原 `console.log` 调用均改为走此 helper。
31
+ */
32
+ const ACODE_DEBUG_ENABLED = process.env.ACODE_COMPACTION_DEBUG === "1";
33
+ function adapterDebugLog(...args) {
34
+ if (!ACODE_DEBUG_ENABLED)
35
+ return;
36
+ console.log(...args);
37
+ }
26
38
  const ACODE_AUTO_POLL_PROMPT = [
27
39
  "Continue the AgentsWork message loop now.",
28
40
  "You MUST call the exact MCP tool named aws-mcp__poll_message as your next action and wait for the next assigned message or task.",
@@ -37,6 +49,9 @@ const ACODE_AUTO_POLL_FAILURE_DELAY_MS = 2000;
37
49
  // 新增:submit 超时检测配置(治本修复:防止状态永久卡住)
38
50
  // 当 submit 超过此时间未返回,强制触发恢复机制
39
51
  const ACODE_SUBMIT_TIMEOUT_MS = 120000; // 120秒(2分钟)
52
+ // 兜底超时:mcp_status 缓存能力后,等待 skill_status 的最长时间。
53
+ // 超时后单独发出 MCP 能力清单,避免 skill_status 事件缺失导致"暂无能力清单"。
54
+ const CAPABILITIES_FALLBACK_MS = 3000;
40
55
  const ACODE_MESSAGE_POLLING_IDLE_PROMPT = "系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。";
41
56
  function getAcodePackageEntryPath() {
42
57
  return path.resolve(getReleasedAcodeEntryPath());
@@ -249,20 +264,24 @@ export class AcodeSdkAdapter extends EventEmitter {
249
264
  session.status = "error";
250
265
  this.emitRuntimeErrorEvent(sessionId, error);
251
266
  this.emit("status-change", sessionId, "error");
267
+ const failedEntry = this.sessions.get(sessionId);
268
+ if (failedEntry?.capabilitiesFallbackTimer) {
269
+ clearTimeout(failedEntry.capabilitiesFallbackTimer);
270
+ }
252
271
  this.sessions.delete(sessionId);
253
272
  throw error;
254
273
  }
255
274
  }
256
275
  async sendMessage(sessionId, message) {
257
276
  const sendStartTime = Date.now();
258
- console.log(`[ACode] ═══════════════════════════════════════════════════`);
259
- console.log(`[ACode] ▶️ sendMessage STARTED`);
260
- console.log(`[ACode] Session: ${sessionId}`);
261
- console.log(`[ACode] Message: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}`);
262
- console.log(`[ACode] ════════════════════════════════════════════════════`);
277
+ adapterDebugLog(`[ACode] ═══════════════════════════════════════════════════`);
278
+ adapterDebugLog(`[ACode] ▶️ sendMessage STARTED`);
279
+ adapterDebugLog(`[ACode] Session: ${sessionId}`);
280
+ adapterDebugLog(`[ACode] Message: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}`);
281
+ adapterDebugLog(`[ACode] ════════════════════════════════════════════════════`);
263
282
  const entry = this.requireSession(sessionId);
264
283
  const previousStatus = entry.adapterSession.status;
265
- console.log(`[ACode] Previous status: ${previousStatus}`);
284
+ adapterDebugLog(`[ACode] Previous status: ${previousStatus}`);
266
285
  this.cancelScheduledAutoPoll(entry);
267
286
  entry.failedTurnRecoveryScheduled = false;
268
287
  entry.idlePollLoop?.stop();
@@ -275,39 +294,63 @@ export class AcodeSdkAdapter extends EventEmitter {
275
294
  };
276
295
  entry.adapterSession.messages.push(userMessage);
277
296
  this.emit("conversation-message", sessionId, userMessage);
278
- console.log(`[ACode] Changing status: ${previousStatus} → thinking`);
297
+ adapterDebugLog(`[ACode] Changing status: ${previousStatus} → thinking`);
279
298
  entry.adapterSession.status = "thinking";
280
299
  this.emit("status-change", sessionId, "thinking");
281
300
  // 启动超时检测
282
301
  this.startSubmitTimeoutDetection(sessionId, entry, "sendMessage");
283
302
  try {
284
- console.log(`[ACode] ▶️ Calling runtime.submit()...`);
303
+ adapterDebugLog(`[ACode] ▶️ Calling runtime.submit()...`);
285
304
  const submitStartTime = Date.now();
286
305
  await entry.runtime.submit(message);
287
306
  const submitDuration = Date.now() - submitStartTime;
288
307
  // submit 成功返回,取消超时检测
289
308
  this.cancelSubmitTimeoutDetection(entry);
290
- console.log(`[ACode] ✅ runtime.submit() completed in ${submitDuration}ms`);
291
- console.log(`[ACode] Current status after submit: ${entry.adapterSession.status}`);
292
- console.log(`[ACode] ▶️ Calling recoverResolvedTurnWithoutTerminalEvent()...`);
309
+ adapterDebugLog(`[ACode] ✅ runtime.submit() completed in ${submitDuration}ms`);
310
+ adapterDebugLog(`[ACode] Current status after submit: ${entry.adapterSession.status}`);
311
+ adapterDebugLog(`[ACode] ▶️ Calling recoverResolvedTurnWithoutTerminalEvent()...`);
293
312
  this.recoverResolvedTurnWithoutTerminalEvent(sessionId);
294
- console.log(`[ACode] Status after recovery: ${entry.adapterSession.status}`);
313
+ adapterDebugLog(`[ACode] Status after recovery: ${entry.adapterSession.status}`);
295
314
  const totalDuration = Date.now() - sendStartTime;
296
- console.log(`[ACode] ════════════════════════════════════════════════════`);
297
- console.log(`[ACode] ✅ sendMessage COMPLETED in ${totalDuration}ms`);
298
- console.log(`[ACode] ═══════════════════════════════════════════════════`);
315
+ adapterDebugLog(`[ACode] ════════════════════════════════════════════════════`);
316
+ adapterDebugLog(`[ACode] ✅ sendMessage COMPLETED in ${totalDuration}ms`);
317
+ adapterDebugLog(`[ACode] ═══════════════════════════════════════════════════`);
299
318
  }
300
319
  catch (error) {
301
320
  const errorDuration = Date.now() - sendStartTime;
302
321
  console.error(`[ACode] ❌ sendMessage FAILED after ${errorDuration}ms:`, error instanceof Error ? error.message : String(error));
303
322
  // submit 抛出异常,取消超时检测
304
323
  this.cancelSubmitTimeoutDetection(entry);
305
- console.log(`[ACode] ▶️ Calling emitRuntimeError with recover=true...`);
324
+ adapterDebugLog(`[ACode] ▶️ Calling emitRuntimeError with recover=true...`);
306
325
  this.emitRuntimeError(sessionId, error, { recover: true });
307
- console.log(`[ACode] Status after error recovery: ${entry.adapterSession.status}`);
326
+ adapterDebugLog(`[ACode] Status after error recovery: ${entry.adapterSession.status}`);
308
327
  }
309
328
  }
310
329
  async sendConfirmation(_sessionId, _accept) { }
330
+ /**
331
+ * 接收前端对 ask_user_question 的回答(来自 terminal.ts /sdk/answer 路由)。
332
+ * 通过 pendingQuestionToolCallId 找到 runtime.submitQuestionAnswer 的入参 toolCallId,
333
+ * resolve 阻塞的工具 execute(),让 ACode 继续下一轮 LLM 请求。
334
+ * 重新启动 submit 超时检测(继续计费后续工具执行时间)。
335
+ */
336
+ async sendQuestionAnswer(sessionId, answers) {
337
+ const entry = this.sessions.get(sessionId);
338
+ if (!entry) {
339
+ return;
340
+ }
341
+ const toolCallId = entry.pendingQuestionToolCallId;
342
+ if (!toolCallId) {
343
+ adapterDebugLog(`[AcodeSdkAdapter] sendQuestionAnswer: no pending question for session ${sessionId}`);
344
+ return;
345
+ }
346
+ entry.pendingQuestionToolCallId = undefined;
347
+ entry.pendingQuestions = undefined;
348
+ if (entry.runtime.submitQuestionAnswer) {
349
+ entry.runtime.submitQuestionAnswer(toolCallId, answers);
350
+ }
351
+ // 用户回答后,重新启动 submit 超时检测(剩余工具执行时间计入)
352
+ this.startSubmitTimeoutDetection(sessionId, entry, "sendQuestionAnswer");
353
+ }
311
354
  async abortCurrentTurn(sessionId) {
312
355
  await this.requireSession(sessionId).runtime.abort();
313
356
  }
@@ -344,6 +387,10 @@ export class AcodeSdkAdapter extends EventEmitter {
344
387
  this.cancelScheduledAutoPoll(entry);
345
388
  entry.failedTurnRecoveryScheduled = false;
346
389
  entry.idlePollLoop?.stop();
390
+ if (entry.capabilitiesFallbackTimer) {
391
+ clearTimeout(entry.capabilitiesFallbackTimer);
392
+ entry.capabilitiesFallbackTimer = undefined;
393
+ }
347
394
  await entry.runtime.stop();
348
395
  entry.adapterSession.status = "terminated";
349
396
  this.sessions.delete(sessionId);
@@ -361,6 +408,29 @@ export class AcodeSdkAdapter extends EventEmitter {
361
408
  getSessionStatus(sessionId) {
362
409
  return this.sessions.get(sessionId)?.adapterSession.status;
363
410
  }
411
+ /**
412
+ * 主动查询会话的运行时能力清单(MCP 工具 + skills)。
413
+ * 只读 adapter 内存缓存(lastCombinedCapabilities),不触发 runtime 重新探测。
414
+ * 由 mcp_status / skill_status 事件维护;若尚未收到任何 runtime_capabilities 事件则返回 undefined。
415
+ */
416
+ getCapabilities(sessionId) {
417
+ return this.sessions.get(sessionId)?.lastCombinedCapabilities;
418
+ }
419
+ /**
420
+ * 主动查询会话当前挂起的 ask_user_question。
421
+ * 只读 adapter 内存缓存(pendingQuestionToolCallId + pendingQuestions)。
422
+ * 由 bridge GET /runtime/sdk/pending-questions 端点调用,用于前端刷新后重建提问 UI。
423
+ * 返回 undefined 表示当前无挂起提问(会话不存在或 agent 未提问)。
424
+ */
425
+ getPendingQuestion(sessionId) {
426
+ const entry = this.sessions.get(sessionId);
427
+ if (!entry?.pendingQuestionToolCallId)
428
+ return undefined;
429
+ return {
430
+ toolCallId: entry.pendingQuestionToolCallId,
431
+ questions: entry.pendingQuestions ?? [],
432
+ };
433
+ }
364
434
  cleanup() {
365
435
  for (const sessionId of this.sessions.keys()) {
366
436
  void this.terminateSession(sessionId);
@@ -548,6 +618,7 @@ export class AcodeSdkAdapter extends EventEmitter {
548
618
  ? config.memory
549
619
  : undefined,
550
620
  env: config.envOverrides,
621
+ subAgentManager,
551
622
  });
552
623
  const debugLogger = getInstanceAiConfigDebugLogger();
553
624
  void debugLogger.log({
@@ -672,10 +743,23 @@ export class AcodeSdkAdapter extends EventEmitter {
672
743
  if (event.type === "ai_request") {
673
744
  const estimatedTokens = toEstimatedContextTokens(event.data);
674
745
  if (estimatedTokens > 0) {
675
- entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
676
- entry.adapterSession.totalUsage.outputTokens = 0;
746
+ // ★ 不再覆盖 totalUsage.inputTokens 和重置 outputTokens=0。
747
+ // runModelTurn 的多轮迭代中每个 iter 都会发 ai_request,
748
+ // 重置 outputTokens=0 导致面板在整个 executeSubmit 期间看到 0。
749
+ // 改用 Math.max 确保 inputTokens 单调递增即可。
750
+ // 注意:如果上下文缩减(如释放历史),Math.max 不会反映真实缩减,
751
+ // 但对面板展示而言单调递增是可接受的设计取舍。
752
+ if (estimatedTokens > entry.adapterSession.totalUsage.inputTokens) {
753
+ entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
754
+ }
755
+ // ★ 使用消息历史估算实时的 output tokens,确保面板在会话期间看到非零值
756
+ const liveOutputEstimate = this.estimateOutputTokensFromMessages(entry.adapterSession.messages);
757
+ const displayUsage = {
758
+ ...entry.adapterSession.totalUsage,
759
+ outputTokens: Math.max(entry.adapterSession.totalUsage.outputTokens, liveOutputEstimate),
760
+ };
677
761
  this.emit("status-change", event.sessionId, entry.adapterSession.status, {
678
- usage: entry.adapterSession.totalUsage,
762
+ usage: displayUsage,
679
763
  });
680
764
  }
681
765
  this.emitProviderEvent(event, "ai_request", {
@@ -711,12 +795,12 @@ export class AcodeSdkAdapter extends EventEmitter {
711
795
  if (event.type === "turn_complete") {
712
796
  const usage = toUsage(event.data.usage);
713
797
  if (usage) {
714
- entry.adapterSession.totalUsage.inputTokens += usage.inputTokens;
798
+ // ★ 只累加 outputTokens;inputTokens ai_request 的事件估算维护(Math.max 单调递增)
715
799
  entry.adapterSession.totalUsage.outputTokens += usage.outputTokens;
716
800
  }
717
801
  // 关键日志:记录 turn_complete 事件
718
802
  const previousStatus = entry.adapterSession.status;
719
- console.log(`[ACode] turn_complete event received (session: ${event.sessionId}), ` +
803
+ adapterDebugLog(`[ACode] turn_complete event received (session: ${event.sessionId}), ` +
720
804
  `previous status: ${previousStatus}, failed: ${event.data.failed === true}, ` +
721
805
  `aborted: ${event.data.aborted === true}`);
722
806
  if (isFailedAcodeTurnComplete(event.data)) {
@@ -732,7 +816,7 @@ export class AcodeSdkAdapter extends EventEmitter {
732
816
  // initial prompt turn failed, autoPollActive was false and the
733
817
  // session got permanently stuck in 'error'. Now we always recover
734
818
  // for non-terminal sessions so the idle loop can retry.
735
- console.log(`[ACode] Recovering from failed turn_complete (session: ${event.sessionId})`);
819
+ adapterDebugLog(`[ACode] Recovering from failed turn_complete (session: ${event.sessionId})`);
736
820
  this.recoverTurnFailure(event.sessionId, entry);
737
821
  return;
738
822
  }
@@ -749,7 +833,7 @@ export class AcodeSdkAdapter extends EventEmitter {
749
833
  this.emit("status-change", event.sessionId, "waiting_input", {
750
834
  usage: entry.adapterSession.totalUsage,
751
835
  });
752
- console.log(`[ACode] Status changed to waiting_input after turn_complete (session: ${event.sessionId})`);
836
+ adapterDebugLog(`[ACode] Status changed to waiting_input after turn_complete (session: ${event.sessionId})`);
753
837
  if (entry.autoPollActive &&
754
838
  entry.autoPollObservedAssistantText &&
755
839
  !entry.autoPollObservedPollingTool) {
@@ -771,7 +855,37 @@ export class AcodeSdkAdapter extends EventEmitter {
771
855
  this.emit("status-change", event.sessionId, "completed");
772
856
  return;
773
857
  }
858
+ if (event.type === "question_request") {
859
+ // ask_user_question 工具被调用:runtime 在 execute() 中阻塞等待答案。
860
+ // 取消 submit 超时检测(用户回答时间不计入 2 分钟超时),
861
+ // 记录 toolCallId + questions 供 sendQuestionAnswer 和 getPendingQuestion 使用,
862
+ // 转发事件让 terminal.ts 推到前端。
863
+ this.cancelSubmitTimeoutDetection(entry);
864
+ const toolCallId = typeof event.data.toolCallId === "string"
865
+ ? event.data.toolCallId
866
+ : "";
867
+ const questions = Array.isArray(event.data.questions)
868
+ ? event.data.questions
869
+ : [];
870
+ if (toolCallId) {
871
+ entry.pendingQuestionToolCallId = toolCallId;
872
+ entry.pendingQuestions = questions;
873
+ }
874
+ this.emit("ask-user-question", {
875
+ sessionId: event.sessionId,
876
+ questions,
877
+ });
878
+ return;
879
+ }
774
880
  if (event.type === "error") {
881
+ // nonFatal=true 表示工具执行失败等非致命错误(Agent 会继续处理),
882
+ // 仅推送事件到时间线,不变更运行态为 "error",避免面板启动/停止按钮异常。
883
+ if (event.data.nonFatal) {
884
+ this.emitProviderEvent(event, "error", {
885
+ text: String(event.data.message || "ACode runtime error"),
886
+ });
887
+ return;
888
+ }
775
889
  entry.adapterSession.status = "error";
776
890
  this.emitProviderEvent(event, "error", {
777
891
  text: String(event.data.message || "ACode runtime error"),
@@ -812,6 +926,69 @@ export class AcodeSdkAdapter extends EventEmitter {
812
926
  "Please configure an MCP server that provides poll_message (e.g. aws-mcp) in the dashboard panel for this runtime.";
813
927
  console.warn(`[${this.displayName}] ${message}`);
814
928
  }
929
+ // 将启动时加载的 MCP 工具清单缓存到 entry,等待 skill_status 事件到达后合并发出,
930
+ // 避免两个 runtime_capabilities 事件并发 HTTP POST 导致其中一个丢失
931
+ const mcpServers = Array.isArray(event.data.servers)
932
+ ? event.data.servers
933
+ : [];
934
+ const mcpTools = tools
935
+ .map((tool) => ({
936
+ name: String(tool.name ?? ""),
937
+ source: String(tool.source ?? ""),
938
+ description: String(tool.description ?? ""),
939
+ }))
940
+ .filter((tool) => tool.name);
941
+ const mcpCapabilities = {
942
+ kind: "mcp",
943
+ mcpServers,
944
+ mcpTools,
945
+ };
946
+ entry.cachedMcpCapabilities = mcpCapabilities;
947
+ // 同步保存到 lastMcpCapabilities,运行中再次发 skill_status(如 skill 目录变更)
948
+ // 时,会用它合并 MCP 信息,避免覆盖为空数组
949
+ entry.lastMcpCapabilities = mcpCapabilities;
950
+ // 更新 lastCombinedCapabilities 的 MCP 部分,保留已有的 skills(若 skill_status 已先到达)
951
+ entry.lastCombinedCapabilities = {
952
+ kind: "combined",
953
+ mcpServers,
954
+ mcpTools,
955
+ skills: entry.lastCombinedCapabilities?.skills ?? [],
956
+ skillsDir: entry.lastCombinedCapabilities?.skillsDir,
957
+ skillGroups: entry.lastCombinedCapabilities?.skillGroups ?? [],
958
+ };
959
+ adapterDebugLog(`[cap-diag] MCP capabilities cached: ${mcpServers.length} servers, ${mcpTools.length} tools`, {
960
+ sessionId: event.sessionId,
961
+ servers: mcpServers,
962
+ toolNames: mcpTools.map(t => t.name),
963
+ });
964
+ // 兜底:如果 skill_status 事件在 CAPABILITIES_FALLBACK_MS 内未到达,
965
+ // 单独发出 MCP 能力清单,避免 skill_status 缺失导致"暂无能力清单"。
966
+ if (entry.capabilitiesFallbackTimer) {
967
+ clearTimeout(entry.capabilitiesFallbackTimer);
968
+ }
969
+ entry.capabilitiesFallbackTimer = setTimeout(() => {
970
+ const cached = entry.cachedMcpCapabilities;
971
+ if (!cached)
972
+ return;
973
+ const fallbackCapabilities = {
974
+ ...cached,
975
+ skills: [],
976
+ skillGroups: [],
977
+ };
978
+ this.emitProviderEvent(event, "runtime_capabilities", {
979
+ capabilities: fallbackCapabilities,
980
+ actionType: "runtime_capabilities",
981
+ actionLabel: "运行时能力",
982
+ actionDetail: JSON.stringify(fallbackCapabilities),
983
+ });
984
+ adapterDebugLog(`[cap-diag] MCP capabilities emitted via fallback (skill_status not received within ${CAPABILITIES_FALLBACK_MS}ms)`, {
985
+ sessionId: event.sessionId,
986
+ mcpServers: cached.mcpServers,
987
+ mcpToolsCount: cached.mcpTools?.length ?? 0,
988
+ });
989
+ entry.cachedMcpCapabilities = undefined;
990
+ entry.capabilitiesFallbackTimer = undefined;
991
+ }, CAPABILITIES_FALLBACK_MS);
815
992
  return;
816
993
  }
817
994
  const server = typeof event.data.server === "string" ? event.data.server : "mcp";
@@ -843,6 +1020,8 @@ export class AcodeSdkAdapter extends EventEmitter {
843
1020
  ...(actionInput ? { actionInput } : {}),
844
1021
  ...(toolInput ? { toolInput } : {}),
845
1022
  ...(toolUseId ? { toolUseId, actionId: toolUseId } : {}),
1023
+ // 传递 MCP 服务器名,供前端时间线展示工具来源
1024
+ metadata: { mcpServer: server },
846
1025
  };
847
1026
  if (event.data.action === "tool_result") {
848
1027
  this.emitProviderEvent(event, "tool_use_end", {
@@ -856,11 +1035,12 @@ export class AcodeSdkAdapter extends EventEmitter {
856
1035
  ? "waiting_input"
857
1036
  : "thinking";
858
1037
  entry.adapterSession.status = status;
859
- // tool_result 只传递 actionResult,通过 buildToolResultTimelineActionInfo 加工,
860
- // status-change 中分情况处理 actionInput:
861
- // - thinking 时传 undefined,清理 old action 避免前端残留之前的 tool 状态
862
- // - waiting_input 时传 eventData(含 actionInput),保留输入参数供前端展示
863
- this.emit("status-change", event.sessionId, status, status === "thinking" ? undefined : eventData);
1038
+ // 修复重复时间线条目:idle 类型(poll_message)的 tool_use_end status-change
1039
+ // 始终传 undefined,避免 terminal.ts 的 restoredActionInfo 恢复出 actionType+actionLabel
1040
+ // 导致 dashboard 创建第二个"调用MCP"条目。tool_use_start status-change 已创建条目。
1041
+ // idle 类型仍按原逻辑传 eventData(waiting_input 时)以保留输入参数展示。
1042
+ const isIdleTool = actionInfo.actionType === "idle";
1043
+ this.emit("status-change", event.sessionId, status, isIdleTool ? undefined : (status === "thinking" ? undefined : eventData));
864
1044
  return;
865
1045
  }
866
1046
  const status = actionInfo.actionType === "idle" ? "waiting_input" : "tool_using";
@@ -869,6 +1049,89 @@ export class AcodeSdkAdapter extends EventEmitter {
869
1049
  this.emit("status-change", event.sessionId, status, eventData);
870
1050
  return;
871
1051
  }
1052
+ if (event.type === "skill_status") {
1053
+ // skill_status 事件可能在两个时机到达:
1054
+ // 1. ACode runtime 启动时(包含已加载的 Skill 列表)
1055
+ // 2. 运行中 skills 目录变更触发重扫后(动态感知新 skill)
1056
+ // 合并 MCP 能力清单后,作为单个 runtime_capabilities 事件发出,
1057
+ // 避免与 mcp_status 的 runtime_capabilities 事件并发 HTTP POST 导致其中一个丢失。
1058
+ // 启动时用 cachedMcpCapabilities(等待合并);运行中 cachedMcp 已清理,
1059
+ // 回退到 lastMcpCapabilities(保留最近一次 MCP 信息,避免覆盖为空数组)。
1060
+ const skillsRaw = Array.isArray(event.data.skills)
1061
+ ? event.data.skills
1062
+ : [];
1063
+ const skills = skillsRaw
1064
+ .map((skill) => ({
1065
+ name: String(skill.name ?? ""),
1066
+ description: typeof skill.description === "string"
1067
+ ? skill.description
1068
+ : undefined,
1069
+ directory: typeof skill.directory === "string"
1070
+ ? skill.directory
1071
+ : undefined,
1072
+ }))
1073
+ .filter((skill) => skill.name);
1074
+ const skillsDir = typeof event.data.skillsDir === "string"
1075
+ ? event.data.skillsDir
1076
+ : undefined;
1077
+ // 按目录分组的 skills(含空目录节点),供前端按目录展示
1078
+ const skillGroupsRaw = Array.isArray(event.data.skillGroups)
1079
+ ? event.data.skillGroups
1080
+ : [];
1081
+ const skillGroups = skillGroupsRaw
1082
+ .map((group) => {
1083
+ const groupSkills = Array.isArray(group.skills)
1084
+ ? group.skills
1085
+ : [];
1086
+ return {
1087
+ dir: typeof group.dir === "string" ? group.dir : "",
1088
+ exists: group.exists === true,
1089
+ skills: groupSkills
1090
+ .map((skill) => ({
1091
+ name: String(skill.name ?? ""),
1092
+ description: typeof skill.description === "string"
1093
+ ? skill.description
1094
+ : undefined,
1095
+ directory: typeof skill.directory === "string"
1096
+ ? skill.directory
1097
+ : undefined,
1098
+ }))
1099
+ .filter((skill) => skill.name),
1100
+ };
1101
+ })
1102
+ .filter((group) => group.dir);
1103
+ // 优先用启动缓存(cachedMcp),运行中回退到 lastMcpCapabilities
1104
+ const mcpSource = entry.cachedMcpCapabilities ?? entry.lastMcpCapabilities;
1105
+ const isStartupMerge = entry.cachedMcpCapabilities !== undefined;
1106
+ const mergedCapabilities = {
1107
+ kind: "combined",
1108
+ mcpServers: mcpSource?.mcpServers ?? [],
1109
+ mcpTools: mcpSource?.mcpTools ?? [],
1110
+ skills,
1111
+ skillsDir,
1112
+ skillGroups,
1113
+ };
1114
+ this.emitProviderEvent(event, "runtime_capabilities", {
1115
+ capabilities: mergedCapabilities,
1116
+ actionType: "runtime_capabilities",
1117
+ actionLabel: "运行时能力",
1118
+ actionDetail: JSON.stringify(mergedCapabilities),
1119
+ });
1120
+ // 更新 lastCombinedCapabilities,作为主动查询的单一数据源
1121
+ entry.lastCombinedCapabilities = mergedCapabilities;
1122
+ adapterDebugLog(`[cap-diag] Combined capabilities emitted ${isStartupMerge ? "(startup merge)" : "(runtime refresh)"}: ${(mcpSource?.mcpTools ?? []).length} MCP tools, ${skills.length} skills`, {
1123
+ sessionId: event.sessionId,
1124
+ mcpServers: mcpSource?.mcpServers ?? [],
1125
+ skillsDir,
1126
+ });
1127
+ // 清理启动缓存与兜底定时器(仅启动合并路径需要;运行中刷新时 cachedMcp 已是 undefined)
1128
+ if (entry.capabilitiesFallbackTimer) {
1129
+ clearTimeout(entry.capabilitiesFallbackTimer);
1130
+ entry.capabilitiesFallbackTimer = undefined;
1131
+ }
1132
+ entry.cachedMcpCapabilities = undefined;
1133
+ return;
1134
+ }
872
1135
  if (event.type === "session_started") {
873
1136
  const estimatedTokens = toEstimatedContextTokens(event.data);
874
1137
  if (estimatedTokens > 0) {
@@ -898,6 +1161,90 @@ export class AcodeSdkAdapter extends EventEmitter {
898
1161
  });
899
1162
  return;
900
1163
  }
1164
+ if (event.type === "context_compaction") {
1165
+ const data = event.data;
1166
+ const action = data.action;
1167
+ const isStart = action === "start";
1168
+ const isEnd = action === "end";
1169
+ const isPhase = action === "phase";
1170
+ // parentId 用于将子事件(phase)关联到父压缩事件
1171
+ const parentId = data.parentId;
1172
+ // compactionId 作为父事件的 actionId,子事件通过 parentId 引用它
1173
+ const compactionId = data.compactionId;
1174
+ let actionLabel;
1175
+ let actionDetail;
1176
+ if (isStart) {
1177
+ const sizeKB = String(data.sizeKB ?? data.sizeBeforeKB ?? "?");
1178
+ const msgCount = String(data.messageCount ?? data.messageCountBefore ?? "?");
1179
+ const actualInput = data.actualInputTokens ? `input=${data.actualInputTokens}` : "";
1180
+ const actualOutput = data.actualOutputTokens ? `output=${data.actualOutputTokens}` : "";
1181
+ const trigger = data.trigger ? `, 触发: ${data.trigger}` : "";
1182
+ actionLabel = "上下文压缩开始";
1183
+ actionDetail = `压缩前: ${sizeKB}KB, 消息: ${msgCount}条` +
1184
+ (actualInput ? `, 实际 tokens: ${actualInput} ${actualOutput}` : "") + trigger;
1185
+ }
1186
+ else if (isPhase) {
1187
+ // 子事件(阶段事件):使用 runtime 传入的 actionLabel 和 actionDetail
1188
+ actionLabel = String(data.phaseLabel ?? "压缩阶段");
1189
+ actionDetail = String(data.phaseDetail ?? "");
1190
+ }
1191
+ else {
1192
+ // action === "end" —— 作为压缩完成子事件,嵌套在父事件下
1193
+ const sizeBefore = String(data.sizeBeforeKB ?? "?");
1194
+ const sizeAfter = String(data.sizeAfterKB ?? "?");
1195
+ const freedKB = String(data.freedKB ?? "?");
1196
+ const strategy = String(data.strategy ?? "?");
1197
+ const pruned = Number(data.prunedToolMessages ?? 0);
1198
+ const summarized = Number(data.summarizedMessages ?? 0);
1199
+ actionLabel = "压缩完成";
1200
+ actionDetail = `释放: ${freedKB}KB, ${sizeBefore}KB → ${sizeAfter}KB, 策略: ${strategy}` +
1201
+ (pruned ? `, 裁剪工具: ${pruned}条` : "") +
1202
+ (summarized ? `, 摘要化: ${summarized}条` : "");
1203
+ }
1204
+ // ★ 更新 adapter 内部状态为 compacting,使前端 agent 卡片显示压缩中
1205
+ // 子事件(phase/end)不重复更新状态,仅推送到时间线
1206
+ if (isStart) {
1207
+ entry.adapterSession.status = "compacting";
1208
+ }
1209
+ const actionInfo = {
1210
+ actionType: "context_compaction",
1211
+ actionLabel,
1212
+ actionDetail,
1213
+ };
1214
+ // ★ 事件层级:start 为父事件(root),phase 和 end 为子事件(nested)
1215
+ // - 父事件:actionId = compactionId
1216
+ // - 子事件:parentId = compactionId, actionId 唯一(避免前端 seenActionIds 去重冲突)
1217
+ if (isStart && compactionId) {
1218
+ actionInfo.actionId = compactionId;
1219
+ }
1220
+ else if ((isPhase || isEnd) && compactionId) {
1221
+ actionInfo.parentId = compactionId;
1222
+ if (isPhase) {
1223
+ const phase = String(data.phase ?? "unknown");
1224
+ actionInfo.actionId = `${compactionId}-phase-${phase}`;
1225
+ }
1226
+ else {
1227
+ // end 事件使用独立 actionId,避免与 start 的 compactionId 重复
1228
+ actionInfo.actionId = `${compactionId}-end`;
1229
+ }
1230
+ }
1231
+ // 兼容旧路径:runtime 可能通过 parentId 字段传入
1232
+ if (isPhase && parentId && !actionInfo.parentId) {
1233
+ actionInfo.parentId = parentId;
1234
+ }
1235
+ // ★ 压缩结束时重置 totalUsage.inputTokens,使面板显示压缩后的真实上下文大小。
1236
+ // 将 usage 合并到 end 事件的 actionInfo 中,避免额外的无 actionInfo status-change
1237
+ // 触发前端 handleDeltaStatusEvent 创建无 actionId 的占位事件(会导致扁平化渲染)。
1238
+ if (isEnd) {
1239
+ entry.adapterSession.totalUsage.inputTokens = 0;
1240
+ actionInfo.usage = entry.adapterSession.totalUsage;
1241
+ }
1242
+ this.emitProviderEvent(event, "context_compaction", actionInfo);
1243
+ // ★ 与其他事件(tool_use_start/turn_complete 等)一致,
1244
+ // 通过 status-change 事件将压缩状态推送到面板时间线。
1245
+ this.emit("status-change", event.sessionId, "compacting", actionInfo);
1246
+ return;
1247
+ }
901
1248
  this.emitProviderEvent(event, "thinking", {
902
1249
  text: JSON.stringify(event.data),
903
1250
  });
@@ -1057,10 +1404,34 @@ export class AcodeSdkAdapter extends EventEmitter {
1057
1404
  data: { text: error instanceof Error ? error.message : String(error) },
1058
1405
  });
1059
1406
  }
1407
+ /**
1408
+ * 从会话消息中估算 AI 输出 token 数(字符数 / 4 粗略估算)。
1409
+ * 注意:该估算对 CJK 文本偏高(实际每字约 1-2 tokens),
1410
+ * 对纯 ASCII 文本偏低(约 3-5 字符/token),仅供面板展示实时趋势使用,
1411
+ * 不应用于精确计费或配额判定。精确值以 turn_complete 事件中的 usage 为准。
1412
+ */
1413
+ estimateOutputTokensFromMessages(messages) {
1414
+ const assistantText = messages
1415
+ .filter((m) => m.role === "assistant")
1416
+ .map((m) => m.content)
1417
+ .join("");
1418
+ return Math.max(0, Math.round(assistantText.length / 4));
1419
+ }
1060
1420
  recoverTurnFailure(sessionId, entry) {
1061
1421
  if (this.isTerminalStatus(entry.adapterSession.status)) {
1062
1422
  return;
1063
1423
  }
1424
+ // ★ 修复:超时恢复前强制 emit turn_complete,计入已有的 output tokens,
1425
+ // 否则面板上 output_tokens 永远是 0(turn_complete 从未发射)。
1426
+ // 使用消息历史中的 assistant 文本粗略估算 output tokens。
1427
+ const estimatedOutput = this.estimateOutputTokensFromMessages(entry.adapterSession.messages);
1428
+ if (estimatedOutput > 0 && entry.adapterSession.totalUsage.outputTokens < estimatedOutput) {
1429
+ const delta = estimatedOutput - entry.adapterSession.totalUsage.outputTokens;
1430
+ entry.adapterSession.totalUsage.outputTokens += delta;
1431
+ adapterDebugLog(`[ACode] recoverTurnFailure: 强制计入 ${delta} output tokens ` +
1432
+ `(totalUsage now: input=${entry.adapterSession.totalUsage.inputTokens}, ` +
1433
+ `output=${entry.adapterSession.totalUsage.outputTokens})`);
1434
+ }
1064
1435
  // ★ 修复:检测 idle 命令是否包含 poll_message,在 waiting_input 状态变更中携带 idle 动作信息
1065
1436
  const idleCommands = entry.idleCommands?.idleInputCommand;
1066
1437
  const willResumePolling = Boolean(idleCommands && isMessagePollingIdleCommand(idleCommands));
@@ -1072,13 +1443,19 @@ export class AcodeSdkAdapter extends EventEmitter {
1072
1443
  if (entry.autoPollTimer || entry.failedTurnRecoveryScheduled) {
1073
1444
  if (entry.adapterSession.status !== "waiting_input") {
1074
1445
  entry.adapterSession.status = "waiting_input";
1075
- this.emit("status-change", sessionId, "waiting_input", idleActionInfo);
1446
+ this.emit("status-change", sessionId, "waiting_input", {
1447
+ usage: entry.adapterSession.totalUsage,
1448
+ ...idleActionInfo,
1449
+ });
1076
1450
  }
1077
1451
  return;
1078
1452
  }
1079
1453
  this.cancelScheduledAutoPoll(entry);
1080
1454
  entry.adapterSession.status = "waiting_input";
1081
- this.emit("status-change", sessionId, "waiting_input", idleActionInfo);
1455
+ this.emit("status-change", sessionId, "waiting_input", {
1456
+ usage: entry.adapterSession.totalUsage,
1457
+ ...idleActionInfo,
1458
+ });
1082
1459
  entry.failedTurnRecoveryScheduled = true;
1083
1460
  entry.autoPollTimer = setTimeout(() => {
1084
1461
  const currentEntry = this.sessions.get(sessionId);
@@ -1112,20 +1489,20 @@ export class AcodeSdkAdapter extends EventEmitter {
1112
1489
  * look permanently stuck. Recover to waiting_input and arm the listening loop.
1113
1490
  */
1114
1491
  recoverResolvedTurnWithoutTerminalEvent(sessionId) {
1115
- console.log(`[ACode] ▶️ recoverResolvedTurnWithoutTerminalEvent called for session ${sessionId}`);
1492
+ adapterDebugLog(`[ACode] ▶️ recoverResolvedTurnWithoutTerminalEvent called for session ${sessionId}`);
1116
1493
  const entry = this.sessions.get(sessionId);
1117
1494
  if (!entry || this.isTerminalStatus(entry.adapterSession.status)) {
1118
- console.log(`[ACode] Skipping recovery: entry=${!!entry}, isTerminal=${entry ? this.isTerminalStatus(entry.adapterSession.status) : 'N/A'}`);
1495
+ adapterDebugLog(`[ACode] Skipping recovery: entry=${!!entry}, isTerminal=${entry ? this.isTerminalStatus(entry.adapterSession.status) : 'N/A'}`);
1119
1496
  return;
1120
1497
  }
1121
- console.log(`[ACode] Current status: ${entry.adapterSession.status}`);
1498
+ adapterDebugLog(`[ACode] Current status: ${entry.adapterSession.status}`);
1122
1499
  if (entry.adapterSession.status !== "thinking" &&
1123
1500
  entry.adapterSession.status !== "tool_using") {
1124
- console.log(`[ACode] Skipping recovery: status is ${entry.adapterSession.status} (not thinking/tool_using)`);
1501
+ adapterDebugLog(`[ACode] Skipping recovery: status is ${entry.adapterSession.status} (not thinking/tool_using)`);
1125
1502
  return;
1126
1503
  }
1127
- console.log(`[ACode] ⚠️ Detected stuck state: ${entry.adapterSession.status} after submit resolved!`);
1128
- console.log(`[ACode] Recovering to waiting_input...`);
1504
+ adapterDebugLog(`[ACode] ⚠️ Detected stuck state: ${entry.adapterSession.status} after submit resolved!`);
1505
+ adapterDebugLog(`[ACode] Recovering to waiting_input...`);
1129
1506
  entry.adapterSession.status = "waiting_input";
1130
1507
  // ★ 修复:检测 idle 命令是否包含 poll_message,在 waiting_input 状态变更中携带 idle 动作信息
1131
1508
  const idleCommands = entry.idleCommands?.idleInputCommand;
@@ -1136,10 +1513,10 @@ export class AcodeSdkAdapter extends EventEmitter {
1136
1513
  actionDetail: 'aws-mcp/poll_message',
1137
1514
  } : undefined;
1138
1515
  this.emit("status-change", sessionId, "waiting_input", idleActionInfo);
1139
- console.log(`[ACode] ✅ Recovery complete, status now: ${entry.adapterSession.status}`);
1140
- console.log(`[ACode] ▶️ Arming idle loop or auto-poll...`);
1516
+ adapterDebugLog(`[ACode] ✅ Recovery complete, status now: ${entry.adapterSession.status}`);
1517
+ adapterDebugLog(`[ACode] ▶️ Arming idle loop or auto-poll...`);
1141
1518
  this.armIdleLoopOrAutoPoll(sessionId);
1142
- console.log(`[ACode] ✅ Idle loop armed, recovery process complete`);
1519
+ adapterDebugLog(`[ACode] ✅ Idle loop armed, recovery process complete`);
1143
1520
  }
1144
1521
  /**
1145
1522
  * 启动 submit 超时检测(治本修复:防止状态永久卡住)