aws-runtime-bridge 1.9.33 → 1.9.41

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 (324) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +66 -5
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +771 -69
  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 +24 -1
  7. package/dist/adapter/OpencodeSdkAdapter.d.ts +19 -0
  8. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  9. package/dist/adapter/OpencodeSdkAdapter.js +62 -0
  10. package/dist/adapter/idle-poll-loop.d.ts +2 -1
  11. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  12. package/dist/adapter/types.d.ts +86 -9
  13. package/dist/adapter/types.d.ts.map +1 -1
  14. package/dist/adapter/types.js +15 -5
  15. package/dist/index.js +15 -0
  16. package/dist/routes/acode-todos.d.ts +11 -0
  17. package/dist/routes/acode-todos.d.ts.map +1 -0
  18. package/dist/routes/acode-todos.js +75 -0
  19. package/dist/routes/ai-sources.js +4 -1
  20. package/dist/routes/dashboard.d.ts.map +1 -1
  21. package/dist/routes/dashboard.js +1 -1
  22. package/dist/routes/memory.d.ts +1 -1
  23. package/dist/routes/memory.d.ts.map +1 -1
  24. package/dist/routes/memory.js +3 -3
  25. package/dist/routes/pty.d.ts.map +1 -1
  26. package/dist/routes/pty.js +9 -0
  27. package/dist/routes/runtime-binding.d.ts.map +1 -1
  28. package/dist/routes/runtime-binding.js +6 -3
  29. package/dist/routes/sessions.js +89 -4
  30. package/dist/routes/sub-agents.d.ts +14 -0
  31. package/dist/routes/sub-agents.d.ts.map +1 -0
  32. package/dist/routes/sub-agents.js +267 -0
  33. package/dist/routes/terminal.d.ts +12 -1
  34. package/dist/routes/terminal.d.ts.map +1 -1
  35. package/dist/routes/terminal.js +326 -24
  36. package/dist/services/acode-package.d.ts +8 -1
  37. package/dist/services/acode-package.d.ts.map +1 -1
  38. package/dist/services/acode-package.js +33 -4
  39. package/dist/services/agent-event-store.d.ts +1 -0
  40. package/dist/services/agent-event-store.d.ts.map +1 -1
  41. package/dist/services/aws-client-agent-mcp.d.ts.map +1 -1
  42. package/dist/services/aws-client-agent-mcp.js +11 -4
  43. package/dist/services/event-delta-pusher.js +4 -4
  44. package/dist/services/fileSnapshotStore.js +1 -1
  45. package/dist/services/legacy-path-migration.d.ts +3 -0
  46. package/dist/services/legacy-path-migration.d.ts.map +1 -0
  47. package/dist/services/legacy-path-migration.js +66 -0
  48. package/dist/services/lifecycle-state.d.ts.map +1 -1
  49. package/dist/services/lifecycle-state.js +1 -1
  50. package/dist/services/runtime-binding.d.ts +5 -0
  51. package/dist/services/runtime-binding.d.ts.map +1 -1
  52. package/dist/services/runtime-binding.js +33 -1
  53. package/dist/services/session-output.d.ts +10 -1
  54. package/dist/services/session-output.d.ts.map +1 -1
  55. package/dist/services/session-output.js +54 -4
  56. package/dist/services/sub-agent-manager-instance.d.ts +4 -0
  57. package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
  58. package/dist/services/sub-agent-manager-instance.js +12 -0
  59. package/dist/services/workspace-files.d.ts +1 -0
  60. package/dist/services/workspace-files.d.ts.map +1 -1
  61. package/dist/services/workspace-files.js +57 -1
  62. package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
  63. package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
  64. package/package/acode/dist/ask-user-question-tool.js +233 -0
  65. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  66. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  67. package/package/acode/dist/built-in-file-tools.js +504 -3
  68. package/package/acode/dist/compaction.d.ts +242 -0
  69. package/package/acode/dist/compaction.d.ts.map +1 -0
  70. package/package/acode/dist/compaction.js +919 -0
  71. package/package/acode/dist/config.d.ts +6 -1
  72. package/package/acode/dist/config.d.ts.map +1 -1
  73. package/package/acode/dist/config.js +27 -2
  74. package/package/acode/dist/index.d.ts +4 -0
  75. package/package/acode/dist/index.d.ts.map +1 -1
  76. package/package/acode/dist/index.js +4 -0
  77. package/package/acode/dist/runtime.d.ts +257 -0
  78. package/package/acode/dist/runtime.d.ts.map +1 -1
  79. package/package/acode/dist/runtime.js +1843 -191
  80. package/package/acode/dist/skill-tool.d.ts +30 -0
  81. package/package/acode/dist/skill-tool.d.ts.map +1 -0
  82. package/package/acode/dist/skill-tool.js +174 -0
  83. package/package/acode/dist/sub-agent-manager.d.ts +264 -0
  84. package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
  85. package/package/acode/dist/sub-agent-manager.js +825 -0
  86. package/package/acode/dist/sub-agent-store.d.ts +40 -0
  87. package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
  88. package/package/acode/dist/sub-agent-store.js +164 -0
  89. package/package/acode/dist/sub-agent-tools.d.ts +72 -0
  90. package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
  91. package/package/acode/dist/sub-agent-tools.js +429 -0
  92. package/package/acode/dist/sub-agent-types.d.ts +85 -0
  93. package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
  94. package/package/acode/dist/sub-agent-types.js +8 -0
  95. package/package/acode/dist/todo-store.d.ts +42 -0
  96. package/package/acode/dist/todo-store.d.ts.map +1 -0
  97. package/package/acode/dist/todo-store.js +68 -0
  98. package/package/acode/dist/todo-tool.d.ts +6 -0
  99. package/package/acode/dist/todo-tool.d.ts.map +1 -0
  100. package/package/acode/dist/todo-tool.js +138 -0
  101. package/package/acode/dist/types.d.ts +41 -1
  102. package/package/acode/dist/types.d.ts.map +1 -1
  103. package/package/acode/package.json +13 -2
  104. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +9 -0
  105. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
  106. package/package/aws-client-agent-mcp/dist/agent-client.js +69 -10
  107. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
  108. package/package/aws-client-agent-mcp/dist/config.d.ts.map +1 -1
  109. package/package/aws-client-agent-mcp/dist/config.js +4 -0
  110. package/package/aws-client-agent-mcp/dist/config.js.map +1 -1
  111. package/package/aws-client-agent-mcp/dist/constants.d.ts +2 -0
  112. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
  113. package/package/aws-client-agent-mcp/dist/constants.js +3 -1
  114. package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
  115. package/package/aws-client-agent-mcp/dist/context-manager.d.ts +4 -1
  116. package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +1 -1
  117. package/package/aws-client-agent-mcp/dist/context-manager.js +6 -2
  118. package/package/aws-client-agent-mcp/dist/context-manager.js.map +1 -1
  119. package/package/aws-client-agent-mcp/dist/context-manager.test.js +5 -3
  120. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
  121. package/package/aws-client-agent-mcp/dist/http-client.d.ts +5 -0
  122. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  123. package/package/aws-client-agent-mcp/dist/http-client.js +15 -1
  124. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  125. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +162 -71
  126. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  127. package/package/aws-client-agent-mcp/dist/mcp-server.js +1007 -504
  128. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  129. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +371 -253
  130. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  131. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +6 -17
  132. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  133. package/package/aws-client-agent-mcp/dist/mcp-tools.js +20 -75
  134. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  135. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +45 -45
  136. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
  137. package/package/aws-client-agent-mcp/dist/memory-store.js +114 -162
  138. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
  139. package/package/aws-client-agent-mcp/dist/memory-store.test.js +175 -164
  140. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
  141. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -9
  142. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
  143. package/package/aws-client-agent-mcp/dist/memory-tools.js +31 -58
  144. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
  145. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +40 -9
  146. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
  147. package/package/aws-client-agent-mcp/dist/message-buffer.js +123 -23
  148. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
  149. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +13 -13
  150. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
  151. package/package/aws-client-agent-mcp/dist/server-memory-store.js +23 -16
  152. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
  153. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  154. package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
  155. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  156. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
  157. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
  158. package/package/aws-client-agent-mcp/dist/types.d.ts +12 -25
  159. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  160. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  161. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +34 -1
  162. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  163. package/package/aws-client-agent-mcp/dist/websocket-client.js +146 -18
  164. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  165. package/package.json +17 -7
  166. package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
  167. package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
  168. package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
  169. package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
  170. package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
  171. package/dist/adapter/AdapterRegistry.test.js +0 -118
  172. package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
  173. package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
  174. package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
  175. package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
  176. package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
  177. package/dist/adapter/CodexSdkAdapter.test.js +0 -270
  178. package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
  179. package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
  180. package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
  181. package/dist/adapter/acode-tool-names.test.d.ts +0 -2
  182. package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
  183. package/dist/adapter/acode-tool-names.test.js +0 -28
  184. package/dist/adapter/adapter.test.d.ts +0 -5
  185. package/dist/adapter/adapter.test.d.ts.map +0 -1
  186. package/dist/adapter/adapter.test.js +0 -180
  187. package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
  188. package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
  189. package/dist/adapter/idle-poll-loop.test.js +0 -85
  190. package/dist/adapter/types.test.d.ts +0 -2
  191. package/dist/adapter/types.test.d.ts.map +0 -1
  192. package/dist/adapter/types.test.js +0 -50
  193. package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
  194. package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
  195. package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
  196. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
  197. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
  198. package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
  199. package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
  200. package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
  201. package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
  202. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
  203. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
  204. package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
  205. package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
  206. package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
  207. package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
  208. package/dist/config.test.d.ts +0 -2
  209. package/dist/config.test.d.ts.map +0 -1
  210. package/dist/config.test.js +0 -107
  211. package/dist/routes/ai-sources.test.d.ts +0 -2
  212. package/dist/routes/ai-sources.test.d.ts.map +0 -1
  213. package/dist/routes/ai-sources.test.js +0 -169
  214. package/dist/routes/file-browser.test.d.ts +0 -5
  215. package/dist/routes/file-browser.test.d.ts.map +0 -1
  216. package/dist/routes/file-browser.test.js +0 -155
  217. package/dist/routes/git.test.d.ts +0 -2
  218. package/dist/routes/git.test.d.ts.map +0 -1
  219. package/dist/routes/git.test.js +0 -330
  220. package/dist/routes/instance.test.d.ts +0 -5
  221. package/dist/routes/instance.test.d.ts.map +0 -1
  222. package/dist/routes/instance.test.js +0 -389
  223. package/dist/routes/mcp.test.d.ts +0 -5
  224. package/dist/routes/mcp.test.d.ts.map +0 -1
  225. package/dist/routes/mcp.test.js +0 -92
  226. package/dist/routes/memory-route-wiring.test.d.ts +0 -2
  227. package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
  228. package/dist/routes/memory-route-wiring.test.js +0 -15
  229. package/dist/routes/memory.test.d.ts +0 -2
  230. package/dist/routes/memory.test.d.ts.map +0 -1
  231. package/dist/routes/memory.test.js +0 -177
  232. package/dist/routes/properties.test.d.ts +0 -5
  233. package/dist/routes/properties.test.d.ts.map +0 -1
  234. package/dist/routes/properties.test.js +0 -72
  235. package/dist/routes/pty.test.d.ts +0 -2
  236. package/dist/routes/pty.test.d.ts.map +0 -1
  237. package/dist/routes/pty.test.js +0 -82
  238. package/dist/routes/runtime-binding.test.d.ts +0 -2
  239. package/dist/routes/runtime-binding.test.d.ts.map +0 -1
  240. package/dist/routes/runtime-binding.test.js +0 -32
  241. package/dist/routes/sessions.test.d.ts +0 -5
  242. package/dist/routes/sessions.test.d.ts.map +0 -1
  243. package/dist/routes/sessions.test.js +0 -86
  244. package/dist/routes/skills.test.d.ts +0 -5
  245. package/dist/routes/skills.test.d.ts.map +0 -1
  246. package/dist/routes/skills.test.js +0 -50
  247. package/dist/routes/system-metrics.test.d.ts +0 -2
  248. package/dist/routes/system-metrics.test.d.ts.map +0 -1
  249. package/dist/routes/system-metrics.test.js +0 -16
  250. package/dist/routes/terminal.stop-options.test.d.ts +0 -2
  251. package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
  252. package/dist/routes/terminal.stop-options.test.js +0 -39
  253. package/dist/routes/terminal.test.d.ts +0 -2
  254. package/dist/routes/terminal.test.d.ts.map +0 -1
  255. package/dist/routes/terminal.test.js +0 -746
  256. package/dist/routes/yml.test.d.ts +0 -5
  257. package/dist/routes/yml.test.d.ts.map +0 -1
  258. package/dist/routes/yml.test.js +0 -58
  259. package/dist/services/ai-source-apply.test.d.ts +0 -2
  260. package/dist/services/ai-source-apply.test.d.ts.map +0 -1
  261. package/dist/services/ai-source-apply.test.js +0 -138
  262. package/dist/services/auto-register.test.d.ts +0 -2
  263. package/dist/services/auto-register.test.d.ts.map +0 -1
  264. package/dist/services/auto-register.test.js +0 -184
  265. package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
  266. package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
  267. package/dist/services/aws-client-agent-mcp.test.js +0 -242
  268. package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
  269. package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
  270. package/dist/services/bridge-package-publish-hooks.test.js +0 -136
  271. package/dist/services/cc-switch-discovery.test.d.ts +0 -2
  272. package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
  273. package/dist/services/cc-switch-discovery.test.js +0 -56
  274. package/dist/services/cli-commands.test.d.ts +0 -2
  275. package/dist/services/cli-commands.test.d.ts.map +0 -1
  276. package/dist/services/cli-commands.test.js +0 -230
  277. package/dist/services/instance-init-service.test.d.ts +0 -2
  278. package/dist/services/instance-init-service.test.d.ts.map +0 -1
  279. package/dist/services/instance-init-service.test.js +0 -140
  280. package/dist/services/instance-state.test.d.ts +0 -2
  281. package/dist/services/instance-state.test.d.ts.map +0 -1
  282. package/dist/services/instance-state.test.js +0 -245
  283. package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
  284. package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
  285. package/dist/services/mcp-launch-binding-queue.test.js +0 -181
  286. package/dist/services/runtime-binding.test.d.ts +0 -2
  287. package/dist/services/runtime-binding.test.d.ts.map +0 -1
  288. package/dist/services/runtime-binding.test.js +0 -78
  289. package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
  290. package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
  291. package/dist/services/runtime-lifecycle-policy.test.js +0 -41
  292. package/dist/services/session-output.test.d.ts +0 -5
  293. package/dist/services/session-output.test.d.ts.map +0 -1
  294. package/dist/services/session-output.test.js +0 -177
  295. package/dist/services/startup-config-wizard.test.d.ts +0 -2
  296. package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
  297. package/dist/services/startup-config-wizard.test.js +0 -184
  298. package/dist/services/terminal-persistence.test.d.ts +0 -5
  299. package/dist/services/terminal-persistence.test.d.ts.map +0 -1
  300. package/dist/services/terminal-persistence.test.js +0 -159
  301. package/dist/services/tool-installer.test.d.ts +0 -2
  302. package/dist/services/tool-installer.test.d.ts.map +0 -1
  303. package/dist/services/tool-installer.test.js +0 -225
  304. package/dist/services/workspace-files.test.d.ts +0 -2
  305. package/dist/services/workspace-files.test.d.ts.map +0 -1
  306. package/dist/services/workspace-files.test.js +0 -648
  307. package/dist/services/workspace-watch.test.d.ts +0 -2
  308. package/dist/services/workspace-watch.test.d.ts.map +0 -1
  309. package/dist/services/workspace-watch.test.js +0 -38
  310. package/dist/utils/file-utils.test.d.ts +0 -2
  311. package/dist/utils/file-utils.test.d.ts.map +0 -1
  312. package/dist/utils/file-utils.test.js +0 -244
  313. package/dist/utils/logger.test.d.ts +0 -2
  314. package/dist/utils/logger.test.d.ts.map +0 -1
  315. package/dist/utils/logger.test.js +0 -93
  316. package/dist/utils/sdk-package-loader.test.d.ts +0 -2
  317. package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
  318. package/dist/utils/sdk-package-loader.test.js +0 -84
  319. package/dist/utils/validation.test.d.ts +0 -2
  320. package/dist/utils/validation.test.d.ts.map +0 -1
  321. package/dist/utils/validation.test.js +0 -88
  322. package/dist/utils/yaml-utils.test.d.ts +0 -2
  323. package/dist/utils/yaml-utils.test.d.ts.map +0 -1
  324. package/dist/utils/yaml-utils.test.js +0 -363
@@ -19,16 +19,34 @@ 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.",
29
41
  "Do not answer in plain text before calling aws-mcp__poll_message.",
30
42
  "After handling any received work, reply through the appropriate aws-mcp messaging tool, then continue listening again.",
31
43
  ].join(" ");
44
+ // 异步看门狗轮询间隔:每 5s 检查一次运行时状态
45
+ const ACODE_WATCHDOG_POLL_INTERVAL_MS = 5_000;
46
+ // 数据流看门狗超时:ai_request 发出后,若 40s 内没有收到任何流式数据(assistant_delta/thinking_delta),
47
+ // 触发 recoverTurnFailure 强制恢复。覆盖 provider requestTimeoutMs 配置不当或流挂死等场景。
48
+ // 仅在 thinking/tool_using 状态下累计计时,waiting_input 状态下不计时(agent 正在有意等待)。
49
+ const ACODE_DATA_FLOW_STREAM_WATCHDOG_MS = 40_000;
32
50
  const ACODE_AUTO_POLL_DELAY_MS = 250;
33
51
  // Backoff used when a polling-driven turn fails (e.g., 60s MCP tool timeout,
34
52
  // model 5xx, provider unreachable). Larger than ACODE_AUTO_POLL_DELAY_MS to
@@ -37,6 +55,9 @@ const ACODE_AUTO_POLL_FAILURE_DELAY_MS = 2000;
37
55
  // 新增:submit 超时检测配置(治本修复:防止状态永久卡住)
38
56
  // 当 submit 超过此时间未返回,强制触发恢复机制
39
57
  const ACODE_SUBMIT_TIMEOUT_MS = 120000; // 120秒(2分钟)
58
+ // 兜底超时:mcp_status 缓存能力后,等待 skill_status 的最长时间。
59
+ // 超时后单独发出 MCP 能力清单,避免 skill_status 事件缺失导致"暂无能力清单"。
60
+ const CAPABILITIES_FALLBACK_MS = 3000;
40
61
  const ACODE_MESSAGE_POLLING_IDLE_PROMPT = "系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。";
41
62
  function getAcodePackageEntryPath() {
42
63
  return path.resolve(getReleasedAcodeEntryPath());
@@ -101,6 +122,25 @@ function lookupModelContextLimit(modelName) {
101
122
  return MODEL_CONTEXT_LIMITS[matches[0]].contextLimit;
102
123
  return DEFAULT_CONTEXT_LIMIT;
103
124
  }
125
+ const DEFAULT_MAX_OUTPUT = 4_096;
126
+ /**
127
+ * Look up model max output token limit by model name.
128
+ * Mirrors lookupModelContextLimit: exact match first, then longest-prefix match.
129
+ * Used to compute the compaction point (contextLimit - maxOutput) on the frontend.
130
+ */
131
+ function lookupModelMaxOutput(modelName) {
132
+ if (!modelName)
133
+ return DEFAULT_MAX_OUTPUT;
134
+ const key = modelName.toLowerCase().trim();
135
+ if (MODEL_CONTEXT_LIMITS[key])
136
+ return MODEL_CONTEXT_LIMITS[key].maxOutput;
137
+ const matches = Object.keys(MODEL_CONTEXT_LIMITS)
138
+ .filter((prefix) => key.startsWith(prefix))
139
+ .sort((a, b) => b.length - a.length);
140
+ if (matches.length > 0)
141
+ return MODEL_CONTEXT_LIMITS[matches[0]].maxOutput;
142
+ return DEFAULT_MAX_OUTPUT;
143
+ }
104
144
  function toUsage(value) {
105
145
  if (!value || typeof value !== "object") {
106
146
  return undefined;
@@ -209,12 +249,13 @@ export class AcodeSdkAdapter extends EventEmitter {
209
249
  const resolvedModel = resolved.model;
210
250
  // priority: acode.json top-level contextLimit > model lookup table > 128K default
211
251
  const contextLimit = configContextLimit ?? lookupModelContextLimit(resolvedModel);
252
+ const maxOutput = lookupModelMaxOutput(resolvedModel);
212
253
  const session = {
213
254
  sessionId,
214
255
  status: "starting",
215
256
  messages: [],
216
257
  createdAt: new Date().toISOString(),
217
- totalUsage: { inputTokens: 0, outputTokens: 0, contextLimit },
258
+ totalUsage: { inputTokens: 0, outputTokens: 0, contextLimit, maxOutput },
218
259
  };
219
260
  this.sessions.set(sessionId, {
220
261
  adapterSession: session,
@@ -249,20 +290,24 @@ export class AcodeSdkAdapter extends EventEmitter {
249
290
  session.status = "error";
250
291
  this.emitRuntimeErrorEvent(sessionId, error);
251
292
  this.emit("status-change", sessionId, "error");
293
+ const failedEntry = this.sessions.get(sessionId);
294
+ if (failedEntry?.capabilitiesFallbackTimer) {
295
+ clearTimeout(failedEntry.capabilitiesFallbackTimer);
296
+ }
252
297
  this.sessions.delete(sessionId);
253
298
  throw error;
254
299
  }
255
300
  }
256
301
  async sendMessage(sessionId, message) {
257
302
  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] ════════════════════════════════════════════════════`);
303
+ adapterDebugLog(`[ACode] ═══════════════════════════════════════════════════`);
304
+ adapterDebugLog(`[ACode] ▶️ sendMessage STARTED`);
305
+ adapterDebugLog(`[ACode] Session: ${sessionId}`);
306
+ adapterDebugLog(`[ACode] Message: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}`);
307
+ adapterDebugLog(`[ACode] ════════════════════════════════════════════════════`);
263
308
  const entry = this.requireSession(sessionId);
264
309
  const previousStatus = entry.adapterSession.status;
265
- console.log(`[ACode] Previous status: ${previousStatus}`);
310
+ adapterDebugLog(`[ACode] Previous status: ${previousStatus}`);
266
311
  this.cancelScheduledAutoPoll(entry);
267
312
  entry.failedTurnRecoveryScheduled = false;
268
313
  entry.idlePollLoop?.stop();
@@ -275,39 +320,63 @@ export class AcodeSdkAdapter extends EventEmitter {
275
320
  };
276
321
  entry.adapterSession.messages.push(userMessage);
277
322
  this.emit("conversation-message", sessionId, userMessage);
278
- console.log(`[ACode] Changing status: ${previousStatus} → thinking`);
323
+ adapterDebugLog(`[ACode] Changing status: ${previousStatus} → thinking`);
279
324
  entry.adapterSession.status = "thinking";
280
325
  this.emit("status-change", sessionId, "thinking");
281
326
  // 启动超时检测
282
327
  this.startSubmitTimeoutDetection(sessionId, entry, "sendMessage");
283
328
  try {
284
- console.log(`[ACode] ▶️ Calling runtime.submit()...`);
329
+ adapterDebugLog(`[ACode] ▶️ Calling runtime.submit()...`);
285
330
  const submitStartTime = Date.now();
286
331
  await entry.runtime.submit(message);
287
332
  const submitDuration = Date.now() - submitStartTime;
288
333
  // submit 成功返回,取消超时检测
289
334
  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()...`);
335
+ adapterDebugLog(`[ACode] ✅ runtime.submit() completed in ${submitDuration}ms`);
336
+ adapterDebugLog(`[ACode] Current status after submit: ${entry.adapterSession.status}`);
337
+ adapterDebugLog(`[ACode] ▶️ Calling recoverResolvedTurnWithoutTerminalEvent()...`);
293
338
  this.recoverResolvedTurnWithoutTerminalEvent(sessionId);
294
- console.log(`[ACode] Status after recovery: ${entry.adapterSession.status}`);
339
+ adapterDebugLog(`[ACode] Status after recovery: ${entry.adapterSession.status}`);
295
340
  const totalDuration = Date.now() - sendStartTime;
296
- console.log(`[ACode] ════════════════════════════════════════════════════`);
297
- console.log(`[ACode] ✅ sendMessage COMPLETED in ${totalDuration}ms`);
298
- console.log(`[ACode] ═══════════════════════════════════════════════════`);
341
+ adapterDebugLog(`[ACode] ════════════════════════════════════════════════════`);
342
+ adapterDebugLog(`[ACode] ✅ sendMessage COMPLETED in ${totalDuration}ms`);
343
+ adapterDebugLog(`[ACode] ═══════════════════════════════════════════════════`);
299
344
  }
300
345
  catch (error) {
301
346
  const errorDuration = Date.now() - sendStartTime;
302
347
  console.error(`[ACode] ❌ sendMessage FAILED after ${errorDuration}ms:`, error instanceof Error ? error.message : String(error));
303
348
  // submit 抛出异常,取消超时检测
304
349
  this.cancelSubmitTimeoutDetection(entry);
305
- console.log(`[ACode] ▶️ Calling emitRuntimeError with recover=true...`);
350
+ adapterDebugLog(`[ACode] ▶️ Calling emitRuntimeError with recover=true...`);
306
351
  this.emitRuntimeError(sessionId, error, { recover: true });
307
- console.log(`[ACode] Status after error recovery: ${entry.adapterSession.status}`);
352
+ adapterDebugLog(`[ACode] Status after error recovery: ${entry.adapterSession.status}`);
308
353
  }
309
354
  }
310
355
  async sendConfirmation(_sessionId, _accept) { }
356
+ /**
357
+ * 接收前端对 ask_user_question 的回答(来自 terminal.ts /sdk/answer 路由)。
358
+ * 通过 pendingQuestionToolCallId 找到 runtime.submitQuestionAnswer 的入参 toolCallId,
359
+ * resolve 阻塞的工具 execute(),让 ACode 继续下一轮 LLM 请求。
360
+ * 重新启动 submit 超时检测(继续计费后续工具执行时间)。
361
+ */
362
+ async sendQuestionAnswer(sessionId, answers) {
363
+ const entry = this.sessions.get(sessionId);
364
+ if (!entry) {
365
+ return;
366
+ }
367
+ const toolCallId = entry.pendingQuestionToolCallId;
368
+ if (!toolCallId) {
369
+ adapterDebugLog(`[AcodeSdkAdapter] sendQuestionAnswer: no pending question for session ${sessionId}`);
370
+ return;
371
+ }
372
+ entry.pendingQuestionToolCallId = undefined;
373
+ entry.pendingQuestions = undefined;
374
+ if (entry.runtime.submitQuestionAnswer) {
375
+ entry.runtime.submitQuestionAnswer(toolCallId, answers);
376
+ }
377
+ // 用户回答后,重新启动 submit 超时检测(剩余工具执行时间计入)
378
+ this.startSubmitTimeoutDetection(sessionId, entry, "sendQuestionAnswer");
379
+ }
311
380
  async abortCurrentTurn(sessionId) {
312
381
  await this.requireSession(sessionId).runtime.abort();
313
382
  }
@@ -318,6 +387,7 @@ export class AcodeSdkAdapter extends EventEmitter {
318
387
  entry.autoPollActive = false;
319
388
  entry.autoPollObservedPollingTool = false;
320
389
  entry.autoPollObservedAssistantText = false;
390
+ entry.autoPollObservedAnyTool = false;
321
391
  entry.stopAutoPollAfterCurrentTurn = false;
322
392
  entry.idlePollLoop?.stop();
323
393
  if (!entry.runtime.resetContext) {
@@ -325,7 +395,17 @@ export class AcodeSdkAdapter extends EventEmitter {
325
395
  }
326
396
  await entry.runtime.resetContext();
327
397
  entry.adapterSession.messages = [];
328
- entry.adapterSession.totalUsage = { inputTokens: 0, outputTokens: 0, contextLimit: entry.adapterSession.totalUsage.contextLimit };
398
+ // 重置 token 计数但保留压缩配置(contextLimit/maxOutput/inputLimit/compactionThreshold),
399
+ // 这些值在 session 期间不变,避免重置后前端压缩点竖线消失。
400
+ const preservedUsage = entry.adapterSession.totalUsage;
401
+ entry.adapterSession.totalUsage = {
402
+ inputTokens: 0,
403
+ outputTokens: 0,
404
+ contextLimit: preservedUsage.contextLimit,
405
+ maxOutput: preservedUsage.maxOutput,
406
+ inputLimit: preservedUsage.inputLimit,
407
+ compactionThreshold: preservedUsage.compactionThreshold,
408
+ };
329
409
  entry.adapterSession.status = "waiting_input";
330
410
  this.emit("status-change", sessionId, "waiting_input", {
331
411
  usage: entry.adapterSession.totalUsage,
@@ -344,6 +424,10 @@ export class AcodeSdkAdapter extends EventEmitter {
344
424
  this.cancelScheduledAutoPoll(entry);
345
425
  entry.failedTurnRecoveryScheduled = false;
346
426
  entry.idlePollLoop?.stop();
427
+ if (entry.capabilitiesFallbackTimer) {
428
+ clearTimeout(entry.capabilitiesFallbackTimer);
429
+ entry.capabilitiesFallbackTimer = undefined;
430
+ }
347
431
  await entry.runtime.stop();
348
432
  entry.adapterSession.status = "terminated";
349
433
  this.sessions.delete(sessionId);
@@ -361,6 +445,29 @@ export class AcodeSdkAdapter extends EventEmitter {
361
445
  getSessionStatus(sessionId) {
362
446
  return this.sessions.get(sessionId)?.adapterSession.status;
363
447
  }
448
+ /**
449
+ * 主动查询会话的运行时能力清单(MCP 工具 + skills)。
450
+ * 只读 adapter 内存缓存(lastCombinedCapabilities),不触发 runtime 重新探测。
451
+ * 由 mcp_status / skill_status 事件维护;若尚未收到任何 runtime_capabilities 事件则返回 undefined。
452
+ */
453
+ getCapabilities(sessionId) {
454
+ return this.sessions.get(sessionId)?.lastCombinedCapabilities;
455
+ }
456
+ /**
457
+ * 主动查询会话当前挂起的 ask_user_question。
458
+ * 只读 adapter 内存缓存(pendingQuestionToolCallId + pendingQuestions)。
459
+ * 由 bridge GET /runtime/sdk/pending-questions 端点调用,用于前端刷新后重建提问 UI。
460
+ * 返回 undefined 表示当前无挂起提问(会话不存在或 agent 未提问)。
461
+ */
462
+ getPendingQuestion(sessionId) {
463
+ const entry = this.sessions.get(sessionId);
464
+ if (!entry?.pendingQuestionToolCallId)
465
+ return undefined;
466
+ return {
467
+ toolCallId: entry.pendingQuestionToolCallId,
468
+ questions: entry.pendingQuestions ?? [],
469
+ };
470
+ }
364
471
  cleanup() {
365
472
  for (const sessionId of this.sessions.keys()) {
366
473
  void this.terminateSession(sessionId);
@@ -548,6 +655,10 @@ export class AcodeSdkAdapter extends EventEmitter {
548
655
  ? config.memory
549
656
  : undefined,
550
657
  env: config.envOverrides,
658
+ subAgentManager,
659
+ ...(config.subAgentModel ? { subAgentModel: config.subAgentModel } : {}),
660
+ ...(config.subAgentEffort ? { subAgentEffort: config.subAgentEffort } : {}),
661
+ ...(config.instanceAiConfig?.effort ? { effort: config.instanceAiConfig.effort } : {}),
551
662
  });
552
663
  const debugLogger = getInstanceAiConfigDebugLogger();
553
664
  void debugLogger.log({
@@ -644,11 +755,15 @@ export class AcodeSdkAdapter extends EventEmitter {
644
755
  content: text,
645
756
  timestamp: event.timestamp,
646
757
  });
758
+ // 收到流式数据,重置看门狗
759
+ this.resetDataFlowWatchdog(event.sessionId, entry);
647
760
  this.emitProviderEvent(event, "text_delta", { text });
648
761
  return;
649
762
  }
650
763
  if (event.type === "thinking_delta") {
651
764
  const text = typeof event.data.text === "string" ? event.data.text : "";
765
+ // 收到流式思考过程数据,重置看门狗
766
+ this.resetDataFlowWatchdog(event.sessionId, entry);
652
767
  this.emitProviderEvent(event, "thinking", { text });
653
768
  return;
654
769
  }
@@ -671,13 +786,61 @@ export class AcodeSdkAdapter extends EventEmitter {
671
786
  }
672
787
  if (event.type === "ai_request") {
673
788
  const estimatedTokens = toEstimatedContextTokens(event.data);
789
+ let displayUsage;
674
790
  if (estimatedTokens > 0) {
675
- entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
676
- entry.adapterSession.totalUsage.outputTokens = 0;
677
- this.emit("status-change", event.sessionId, entry.adapterSession.status, {
678
- usage: entry.adapterSession.totalUsage,
679
- });
791
+ // 限制估算值的增长幅度,防止 JSON 结构开销导致的虚高跳变。
792
+ // estimateTokensFromText(requestBody) 基于 pretty-printed JSON 字符串长度 / 3 估算,
793
+ // JSON 结构开销(键名、引号、缩进、转义)导致估算值系统性虚高 3-5x+,
794
+ // 在 turn_complete 到达前会造成面板显示从 xx k 突跳到几 M 再跳回。
795
+ // 正常单轮上下文增长 < 50%,cap 1.5x 足够覆盖增长同时防止跳变。
796
+ // turn_complete 携带 provider 实际 prompt_tokens 时始终覆盖此估算值。
797
+ // 压缩后 context_compaction end 将 inputTokens 重置为 0,
798
+ // 此处 0 值直接使用估算值(压缩后对话更小,估算值也相应更小)。
799
+ const currentInput = entry.adapterSession.totalUsage.inputTokens;
800
+ if (currentInput > 0) {
801
+ entry.adapterSession.totalUsage.inputTokens = Math.min(estimatedTokens, Math.floor(currentInput * 1.5));
802
+ }
803
+ else {
804
+ entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
805
+ }
806
+ // ★ outputTokens 保持上一轮 turn_complete 写入的单轮值(覆盖式语义),
807
+ // 不再用 estimateOutputTokensFromMessages(累计估算)通过 Math.max 撑高。
808
+ // 该累计估算会把历史所有 assistant 输出叠加,与 prompt_tokens(已含历史) 重复,
809
+ // 导致显示 total 系统性虚高。结构与 runtime 的 actualUsed 一致:
810
+ // input(请求体估算) + output(当前轮 completion)。
811
+ displayUsage = { ...entry.adapterSession.totalUsage };
812
+ }
813
+ // ★ 修复 BUG:状态切换与 status-change 发射必须移到 if (estimatedTokens > 0) 块外。
814
+ // 原实现仅在 estimatedTokens > 0 时才切换状态并发射 status-change,
815
+ // 导致 estimatedTokens === 0(如 ai_request 无 requestBody 或无法解析)时:
816
+ // 1. adapter 内部 status 保持 waiting_input(未切换为 thinking)
817
+ // 2. 不发射 status-change 事件,但 emitProviderEvent 照常发射
818
+ // 3. terminal.ts 通过 provider event 路径发送 thinking + ai_request 到前端
819
+ // 4. recoverResolvedTurnWithoutTerminalEvent 因 status 是 waiting_input 而跳过恢复
820
+ // 5. 残留 actionType:"ai_request" 永远无法清理,前端永久显示 "AI请求: 模型: xxx"
821
+ // ★ 压缩完成后 adapterSession.status 仍为 "compacting",此处恢复为 "thinking",
822
+ // 使 status-change 走普通路径(而非 compacting 串行队列),
823
+ // 并带上 actionType 避免 restoredActionInfo 覆盖 usage。
824
+ if (entry.adapterSession.status === "compacting") {
825
+ entry.adapterSession.status = "thinking";
826
+ }
827
+ // ★ 修复:ai_request 表示 AI 正在请求模型,无论 keepWaitingStatus 如何,
828
+ // 状态都应切换为 "thinking"。否则 keepWaitingStatus=true 时 status 保持
829
+ // "waiting_input",导致 submit 完成后跳过 recoverResolvedTurnWithoutTerminalEvent,
830
+ // 残留 actionType:"ai_request" 永远无法清理,前端永久显示 "AI请求: 模型: xxx"。
831
+ if (entry.adapterSession.status === "waiting_input") {
832
+ entry.adapterSession.status = "thinking";
833
+ }
834
+ const aiRequestActionInfo = {
835
+ actionType: "ai_request",
836
+ actionLabel: "AI请求",
837
+ };
838
+ if (displayUsage) {
839
+ aiRequestActionInfo.usage = displayUsage;
680
840
  }
841
+ this.emit("status-change", event.sessionId, entry.adapterSession.status, aiRequestActionInfo);
842
+ // 启动数据流看门狗:ai_request 发出后开始监控流式数据返回
843
+ this.startDataFlowWatchdog(event.sessionId, entry);
681
844
  this.emitProviderEvent(event, "ai_request", {
682
845
  model: typeof event.data.model === "string" ? event.data.model : undefined,
683
846
  endpoint: typeof event.data.endpoint === "string"
@@ -711,12 +874,29 @@ export class AcodeSdkAdapter extends EventEmitter {
711
874
  if (event.type === "turn_complete") {
712
875
  const usage = toUsage(event.data.usage);
713
876
  if (usage) {
714
- entry.adapterSession.totalUsage.inputTokens += usage.inputTokens;
715
- entry.adapterSession.totalUsage.outputTokens += usage.outputTokens;
877
+ // ★ 同时更新 inputTokens outputTokens(均覆盖式赋值)。
878
+ // provider 返回的 prompt_tokens 是当前上下文大小的最准确度量,
879
+ // 优于 ai_request 的 requestBody/3 估算。直接赋值(非累加),
880
+ // 因为 prompt_tokens 代表本次请求时 provider 看到的完整上下文大小。
881
+ // outputTokens 也采用覆盖式(= 当前轮 completion_tokens),与 runtime 的
882
+ // state.accumulatedOutputTokens(runtime.ts,覆盖式)同构。
883
+ // 之前 outputTokens 用 += 累加,导致显示的 total = prompt_tokens(已含历史output)
884
+ // + Σcompletion 把历史 output 重复计算,面板系统性虚高,先于 runtime 达到压缩阈值。
885
+ // ★ 过滤 fallback usage:当 provider 不返回 usage 时,runtime 会用
886
+ // { inputTokens: trimmed.length, outputTokens: text.length } 作为 fallback。
887
+ // trimmed.length 只是用户消息长度(通常 < 1000),远小于实际上下文,
888
+ // 会把面板从几十 k 砸到几百,造成显示突降。检测方式:fallback inputTokens
889
+ // 远小于当前值(< 20%),说明不是真实 provider 数据,跳过更新。
890
+ const currentInput = entry.adapterSession.totalUsage.inputTokens;
891
+ const isFallbackUsage = currentInput > 0 && usage.inputTokens < Math.floor(currentInput * 0.2);
892
+ if (!isFallbackUsage) {
893
+ entry.adapterSession.totalUsage.inputTokens = usage.inputTokens;
894
+ entry.adapterSession.totalUsage.outputTokens = usage.outputTokens;
895
+ }
716
896
  }
717
897
  // 关键日志:记录 turn_complete 事件
718
898
  const previousStatus = entry.adapterSession.status;
719
- console.log(`[ACode] turn_complete event received (session: ${event.sessionId}), ` +
899
+ adapterDebugLog(`[ACode] turn_complete event received (session: ${event.sessionId}), ` +
720
900
  `previous status: ${previousStatus}, failed: ${event.data.failed === true}, ` +
721
901
  `aborted: ${event.data.aborted === true}`);
722
902
  if (isFailedAcodeTurnComplete(event.data)) {
@@ -732,10 +912,25 @@ export class AcodeSdkAdapter extends EventEmitter {
732
912
  // initial prompt turn failed, autoPollActive was false and the
733
913
  // session got permanently stuck in 'error'. Now we always recover
734
914
  // for non-terminal sessions so the idle loop can retry.
735
- console.log(`[ACode] Recovering from failed turn_complete (session: ${event.sessionId})`);
915
+ adapterDebugLog(`[ACode] Recovering from failed turn_complete (session: ${event.sessionId})`);
916
+ this.cancelDataFlowWatchdog(entry);
917
+ this.recoverTurnFailure(event.sessionId, entry);
918
+ return;
919
+ }
920
+ // ★ 修复竞态:看门狗触发 abort() 后,runtime 在 catch 块发射
921
+ // turn_complete { aborted: true }(而非 failed: true)。
922
+ // 原逻辑走正常完成路径,立即 armIdleLoopOrAutoPoll(250ms 重臂),
923
+ // 导致新 submit 在前一轮 executeSubmit 仍占用 submitQueue 时就排队,
924
+ // 加剧竞态。改为走 R2 恢复路径(2s 退避),给前一轮更多时间完成。
925
+ if (event.data.aborted === true && entry.autoPollActive) {
926
+ adapterDebugLog(`[ACode] Recovering from aborted turn_complete (session: ${event.sessionId}), ` +
927
+ `using R2 2s backoff instead of immediate rearm`);
928
+ this.cancelDataFlowWatchdog(entry);
736
929
  this.recoverTurnFailure(event.sessionId, entry);
737
930
  return;
738
931
  }
932
+ // turn 正常完成,取消数据流看门狗
933
+ this.cancelDataFlowWatchdog(entry);
739
934
  this.emitProviderEvent(event, "turn_complete", {
740
935
  usage: entry.adapterSession.totalUsage,
741
936
  responseBody: typeof event.data.responseBody === "string"
@@ -749,18 +944,20 @@ export class AcodeSdkAdapter extends EventEmitter {
749
944
  this.emit("status-change", event.sessionId, "waiting_input", {
750
945
  usage: entry.adapterSession.totalUsage,
751
946
  });
752
- console.log(`[ACode] Status changed to waiting_input after turn_complete (session: ${event.sessionId})`);
947
+ adapterDebugLog(`[ACode] Status changed to waiting_input after turn_complete (session: ${event.sessionId})`);
753
948
  if (entry.autoPollActive &&
754
949
  entry.autoPollObservedAssistantText &&
755
- !entry.autoPollObservedPollingTool) {
950
+ !entry.autoPollObservedPollingTool &&
951
+ !entry.autoPollObservedAnyTool) {
756
952
  entry.stopAutoPollAfterCurrentTurn = true;
757
- console.warn(`[${this.displayName}] Auto-poll turn produced assistant text without calling poll_message; stopping auto-poll rearm for session ${event.sessionId}`);
953
+ console.warn(`[${this.displayName}] Auto-poll turn produced assistant text without calling any tool; stopping auto-poll rearm for session ${event.sessionId}`);
758
954
  return;
759
955
  }
760
956
  this.armIdleLoopOrAutoPoll(event.sessionId);
761
957
  return;
762
958
  }
763
959
  if (event.type === "session_complete") {
960
+ this.cancelDataFlowWatchdog(entry);
764
961
  this.cancelScheduledAutoPoll(entry);
765
962
  entry.failedTurnRecoveryScheduled = false;
766
963
  entry.idlePollLoop?.stop();
@@ -771,7 +968,46 @@ export class AcodeSdkAdapter extends EventEmitter {
771
968
  this.emit("status-change", event.sessionId, "completed");
772
969
  return;
773
970
  }
971
+ if (event.type === "question_request") {
972
+ // ask_user_question 工具被调用:runtime 在 execute() 中阻塞等待答案。
973
+ // 取消数据流看门狗(用户思考/回答时间不计入 40s 超时),
974
+ // 等用户回答后 LLM 重新输出时 assistant_delta/thinking_delta 会重新启动看门狗。
975
+ this.cancelDataFlowWatchdog(entry);
976
+ // 取消 submit 超时检测(用户回答时间不计入 2 分钟超时),
977
+ // 记录 toolCallId + questions 供 sendQuestionAnswer 和 getPendingQuestion 使用,
978
+ // 转发事件让 terminal.ts 推到前端。
979
+ this.cancelSubmitTimeoutDetection(entry);
980
+ const toolCallId = typeof event.data.toolCallId === "string"
981
+ ? event.data.toolCallId
982
+ : "";
983
+ const questions = Array.isArray(event.data.questions)
984
+ ? event.data.questions
985
+ : [];
986
+ if (toolCallId) {
987
+ entry.pendingQuestionToolCallId = toolCallId;
988
+ entry.pendingQuestions = questions;
989
+ }
990
+ this.emit("ask-user-question", {
991
+ sessionId: event.sessionId,
992
+ toolCallId: toolCallId || undefined,
993
+ questions,
994
+ });
995
+ return;
996
+ }
774
997
  if (event.type === "error") {
998
+ // nonFatal=true 表示工具执行失败等非致命错误(Agent 会继续处理),
999
+ // 仅推送事件到时间线,不变更运行态为 "error",避免面板启动/停止按钮异常。
1000
+ if (event.data.nonFatal) {
1001
+ // ★ 修复:重试期间(每次 fetch failed 后退避)无流式数据到达,
1002
+ // 看门狗仍以"上次 delta + 40s"倒计时,可能在重试周期内误触发 40s 超时。
1003
+ // 重置数据流计时基准,让看门狗从重试时刻重新计时。
1004
+ this.resetDataFlowWatchdog(event.sessionId, entry);
1005
+ this.emitProviderEvent(event, "error", {
1006
+ text: String(event.data.message || "ACode runtime error"),
1007
+ });
1008
+ return;
1009
+ }
1010
+ this.cancelDataFlowWatchdog(entry);
775
1011
  entry.adapterSession.status = "error";
776
1012
  this.emitProviderEvent(event, "error", {
777
1013
  text: String(event.data.message || "ACode runtime error"),
@@ -812,8 +1048,78 @@ export class AcodeSdkAdapter extends EventEmitter {
812
1048
  "Please configure an MCP server that provides poll_message (e.g. aws-mcp) in the dashboard panel for this runtime.";
813
1049
  console.warn(`[${this.displayName}] ${message}`);
814
1050
  }
1051
+ // 将启动时加载的 MCP 工具清单缓存到 entry,等待 skill_status 事件到达后合并发出,
1052
+ // 避免两个 runtime_capabilities 事件并发 HTTP POST 导致其中一个丢失
1053
+ const mcpServers = Array.isArray(event.data.servers)
1054
+ ? event.data.servers
1055
+ : [];
1056
+ const mcpTools = tools
1057
+ .map((tool) => ({
1058
+ name: String(tool.name ?? ""),
1059
+ source: String(tool.source ?? ""),
1060
+ description: String(tool.description ?? ""),
1061
+ }))
1062
+ .filter((tool) => tool.name);
1063
+ const mcpCapabilities = {
1064
+ kind: "mcp",
1065
+ mcpServers,
1066
+ mcpTools,
1067
+ };
1068
+ entry.cachedMcpCapabilities = mcpCapabilities;
1069
+ // 同步保存到 lastMcpCapabilities,运行中再次发 skill_status(如 skill 目录变更)
1070
+ // 时,会用它合并 MCP 信息,避免覆盖为空数组
1071
+ entry.lastMcpCapabilities = mcpCapabilities;
1072
+ // 更新 lastCombinedCapabilities 的 MCP 部分,保留已有的 skills(若 skill_status 已先到达)
1073
+ entry.lastCombinedCapabilities = {
1074
+ kind: "combined",
1075
+ mcpServers,
1076
+ mcpTools,
1077
+ skills: entry.lastCombinedCapabilities?.skills ?? [],
1078
+ skillsDir: entry.lastCombinedCapabilities?.skillsDir,
1079
+ skillGroups: entry.lastCombinedCapabilities?.skillGroups ?? [],
1080
+ };
1081
+ adapterDebugLog(`[cap-diag] MCP capabilities cached: ${mcpServers.length} servers, ${mcpTools.length} tools`, {
1082
+ sessionId: event.sessionId,
1083
+ servers: mcpServers,
1084
+ toolNames: mcpTools.map(t => t.name),
1085
+ });
1086
+ // 兜底:如果 skill_status 事件在 CAPABILITIES_FALLBACK_MS 内未到达,
1087
+ // 单独发出 MCP 能力清单,避免 skill_status 缺失导致"暂无能力清单"。
1088
+ if (entry.capabilitiesFallbackTimer) {
1089
+ clearTimeout(entry.capabilitiesFallbackTimer);
1090
+ }
1091
+ entry.capabilitiesFallbackTimer = setTimeout(() => {
1092
+ const cached = entry.cachedMcpCapabilities;
1093
+ if (!cached)
1094
+ return;
1095
+ const fallbackCapabilities = {
1096
+ ...cached,
1097
+ skills: [],
1098
+ skillGroups: [],
1099
+ };
1100
+ this.emitProviderEvent(event, "runtime_capabilities", {
1101
+ capabilities: fallbackCapabilities,
1102
+ actionType: "runtime_capabilities",
1103
+ actionLabel: "运行时能力",
1104
+ actionDetail: JSON.stringify(fallbackCapabilities),
1105
+ });
1106
+ adapterDebugLog(`[cap-diag] MCP capabilities emitted via fallback (skill_status not received within ${CAPABILITIES_FALLBACK_MS}ms)`, {
1107
+ sessionId: event.sessionId,
1108
+ mcpServers: cached.mcpServers,
1109
+ mcpToolsCount: cached.mcpTools?.length ?? 0,
1110
+ });
1111
+ entry.cachedMcpCapabilities = undefined;
1112
+ entry.capabilitiesFallbackTimer = undefined;
1113
+ }, CAPABILITIES_FALLBACK_MS);
815
1114
  return;
816
1115
  }
1116
+ const eventAction = String(event.data.action ?? "");
1117
+ adapterDebugLog(`[ACode] 📡 mcp_status event: action=${eventAction}, ` +
1118
+ `server=${typeof event.data.server === "string" ? event.data.server : "?"}, ` +
1119
+ `tool=${typeof event.data.tool === "string" ? event.data.tool : "?"}, ` +
1120
+ `toolUseId=${typeof event.data.toolUseId === "string" ? event.data.toolUseId.slice(0, 16) + "…" : "none"}, ` +
1121
+ `failed=${event.data.failed === true}, ` +
1122
+ `session=${event.sessionId}, status=${entry.adapterSession.status}`);
817
1123
  const server = typeof event.data.server === "string" ? event.data.server : "mcp";
818
1124
  const tool = typeof event.data.tool === "string" ? event.data.tool : "";
819
1125
  const toolName = server && tool ? `${server}__${tool}` : tool || server;
@@ -823,8 +1129,13 @@ export class AcodeSdkAdapter extends EventEmitter {
823
1129
  ? event.data.arguments
824
1130
  : undefined;
825
1131
  const actionInfo = getToolActionInfo(toolName, toolInput);
826
- if (entry.autoPollActive && actionInfo.actionType === "idle") {
827
- entry.autoPollObservedPollingTool = true;
1132
+ if (entry.autoPollActive) {
1133
+ // 修复永久卡死:任何工具调用(包括工作工具)都标记为已观察,
1134
+ // 避免 turn_complete 时误判为"只输出文本不调用工具"而停止 auto-poll。
1135
+ entry.autoPollObservedAnyTool = true;
1136
+ if (actionInfo.actionType === "idle") {
1137
+ entry.autoPollObservedPollingTool = true;
1138
+ }
828
1139
  }
829
1140
  const actionDetail = isAcodeSemanticToolAction(actionInfo.actionType)
830
1141
  ? actionInfo.actionDetail ||
@@ -843,6 +1154,8 @@ export class AcodeSdkAdapter extends EventEmitter {
843
1154
  ...(actionInput ? { actionInput } : {}),
844
1155
  ...(toolInput ? { toolInput } : {}),
845
1156
  ...(toolUseId ? { toolUseId, actionId: toolUseId } : {}),
1157
+ // 传递 MCP 服务器名,供前端时间线展示工具来源
1158
+ metadata: { mcpServer: server },
846
1159
  };
847
1160
  if (event.data.action === "tool_result") {
848
1161
  this.emitProviderEvent(event, "tool_use_end", {
@@ -856,24 +1169,154 @@ export class AcodeSdkAdapter extends EventEmitter {
856
1169
  ? "waiting_input"
857
1170
  : "thinking";
858
1171
  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);
1172
+ // 修复 BUG(时间线永久卡在"编辑文件"):工具执行完成时取消数据流看门狗。
1173
+ // 原实现中 tool_use_start/tool_use_end 既不取消也不重置看门狗,
1174
+ // 导致工具执行期间看门狗仍以"上次 delta + 40s"倒计时,
1175
+ // 若工具执行时间接近或超过 40s(如大文件编辑、Windows 杀毒扫描),
1176
+ // 看门狗会在 tool_using 状态误触发,abort 无效(内置工具不可中断),
1177
+ // recoverTurnFailure 不补发 tool_use_end → 时间线永久卡在"编辑文件"。
1178
+ this.cancelDataFlowWatchdog(entry);
1179
+ entry.pendingToolEventData = undefined;
1180
+ // ★ 修复重复时间线条目:idle 类型(poll_message)的 tool_use_end status-change
1181
+ // 始终传 undefined,避免 terminal.ts 的 restoredActionInfo 恢复出 actionType+actionLabel
1182
+ // 导致 dashboard 创建第二个"调用MCP"条目。tool_use_start 的 status-change 已创建条目。
1183
+ // 非 idle 类型仍按原逻辑传 eventData(waiting_input 时)以保留输入参数展示。
1184
+ const isIdleTool = actionInfo.actionType === "idle";
1185
+ this.emit("status-change", event.sessionId, status, isIdleTool ? undefined : (status === "thinking" ? undefined : eventData));
864
1186
  return;
865
1187
  }
866
1188
  const status = actionInfo.actionType === "idle" ? "waiting_input" : "tool_using";
867
1189
  entry.adapterSession.status = status;
1190
+ // ★ 修复 BUG(时间线永久卡在"编辑文件"):工具开始执行时重置数据流看门狗。
1191
+ // 工具执行(如 edit_file)可能耗时较长(大文件、Windows I/O、杀毒扫描),
1192
+ // 期间无流式 delta 数据返回。若不重置看门狗,40s 超时后会在 tool_using 状态误触发,
1193
+ // 导致 recoverTurnFailure 不补发 tool_use_end,时间线永久卡住。
1194
+ // 重置看门狗让计时从工具开始时重新计算,给工具执行留出充足时间。
1195
+ if (status === "tool_using") {
1196
+ this.resetDataFlowWatchdog(event.sessionId, entry);
1197
+ // 缓存当前工具事件数据,供 recoverTurnFailure 补发 tool_use_end 使用
1198
+ entry.pendingToolEventData = { ...eventData };
1199
+ }
868
1200
  this.emitProviderEvent(event, "tool_use_start", eventData);
869
1201
  this.emit("status-change", event.sessionId, status, eventData);
870
1202
  return;
871
1203
  }
1204
+ if (event.type === "skill_status") {
1205
+ // skill_status 事件可能在两个时机到达:
1206
+ // 1. ACode runtime 启动时(包含已加载的 Skill 列表)
1207
+ // 2. 运行中 skills 目录变更触发重扫后(动态感知新 skill)
1208
+ // 合并 MCP 能力清单后,作为单个 runtime_capabilities 事件发出,
1209
+ // 避免与 mcp_status 的 runtime_capabilities 事件并发 HTTP POST 导致其中一个丢失。
1210
+ // 启动时用 cachedMcpCapabilities(等待合并);运行中 cachedMcp 已清理,
1211
+ // 回退到 lastMcpCapabilities(保留最近一次 MCP 信息,避免覆盖为空数组)。
1212
+ const skillsRaw = Array.isArray(event.data.skills)
1213
+ ? event.data.skills
1214
+ : [];
1215
+ const skills = skillsRaw
1216
+ .map((skill) => ({
1217
+ name: String(skill.name ?? ""),
1218
+ description: typeof skill.description === "string"
1219
+ ? skill.description
1220
+ : undefined,
1221
+ directory: typeof skill.directory === "string"
1222
+ ? skill.directory
1223
+ : undefined,
1224
+ }))
1225
+ .filter((skill) => skill.name);
1226
+ const skillsDir = typeof event.data.skillsDir === "string"
1227
+ ? event.data.skillsDir
1228
+ : undefined;
1229
+ // 按目录分组的 skills(含空目录节点),供前端按目录展示
1230
+ const skillGroupsRaw = Array.isArray(event.data.skillGroups)
1231
+ ? event.data.skillGroups
1232
+ : [];
1233
+ const skillGroups = skillGroupsRaw
1234
+ .map((group) => {
1235
+ const groupSkills = Array.isArray(group.skills)
1236
+ ? group.skills
1237
+ : [];
1238
+ return {
1239
+ dir: typeof group.dir === "string" ? group.dir : "",
1240
+ exists: group.exists === true,
1241
+ skills: groupSkills
1242
+ .map((skill) => ({
1243
+ name: String(skill.name ?? ""),
1244
+ description: typeof skill.description === "string"
1245
+ ? skill.description
1246
+ : undefined,
1247
+ directory: typeof skill.directory === "string"
1248
+ ? skill.directory
1249
+ : undefined,
1250
+ }))
1251
+ .filter((skill) => skill.name),
1252
+ };
1253
+ })
1254
+ .filter((group) => group.dir);
1255
+ // 优先用启动缓存(cachedMcp),运行中回退到 lastMcpCapabilities
1256
+ const mcpSource = entry.cachedMcpCapabilities ?? entry.lastMcpCapabilities;
1257
+ const isStartupMerge = entry.cachedMcpCapabilities !== undefined;
1258
+ const mergedCapabilities = {
1259
+ kind: "combined",
1260
+ mcpServers: mcpSource?.mcpServers ?? [],
1261
+ mcpTools: mcpSource?.mcpTools ?? [],
1262
+ skills,
1263
+ skillsDir,
1264
+ skillGroups,
1265
+ };
1266
+ this.emitProviderEvent(event, "runtime_capabilities", {
1267
+ capabilities: mergedCapabilities,
1268
+ actionType: "runtime_capabilities",
1269
+ actionLabel: "运行时能力",
1270
+ actionDetail: JSON.stringify(mergedCapabilities),
1271
+ });
1272
+ // 更新 lastCombinedCapabilities,作为主动查询的单一数据源
1273
+ entry.lastCombinedCapabilities = mergedCapabilities;
1274
+ adapterDebugLog(`[cap-diag] Combined capabilities emitted ${isStartupMerge ? "(startup merge)" : "(runtime refresh)"}: ${(mcpSource?.mcpTools ?? []).length} MCP tools, ${skills.length} skills`, {
1275
+ sessionId: event.sessionId,
1276
+ mcpServers: mcpSource?.mcpServers ?? [],
1277
+ skillsDir,
1278
+ });
1279
+ // 清理启动缓存与兜底定时器(仅启动合并路径需要;运行中刷新时 cachedMcp 已是 undefined)
1280
+ if (entry.capabilitiesFallbackTimer) {
1281
+ clearTimeout(entry.capabilitiesFallbackTimer);
1282
+ entry.capabilitiesFallbackTimer = undefined;
1283
+ }
1284
+ entry.cachedMcpCapabilities = undefined;
1285
+ return;
1286
+ }
872
1287
  if (event.type === "session_started") {
873
1288
  const estimatedTokens = toEstimatedContextTokens(event.data);
1289
+ // ★ 用 runtime emit 的实际压缩配置覆盖 totalUsage。
1290
+ // runtime 的 contextLimit/maxOutput/inputLimit 来自 resolveProviderConfig(读 provider.default.* 三级),
1291
+ // 是后端实际触发压缩用的值;adapter 启动时从顶层+查表解析的值仅作初始占位,必须被覆盖。
1292
+ // compactionThreshold 是后端算好的实际阈值,前端直接显示此值,不再自算。
1293
+ const runtimeContextLimit = toFiniteTokenCount(event.data.contextLimit) || undefined;
1294
+ const runtimeMaxOutput = toFiniteTokenCount(event.data.maxOutput) || undefined;
1295
+ const runtimeInputLimit = toFiniteTokenCount(event.data.inputLimit) || undefined;
1296
+ const runtimeCompactionThreshold = toFiniteTokenCount(event.data.compactionThreshold) || undefined;
1297
+ let usageChanged = false;
874
1298
  if (estimatedTokens > 0) {
875
1299
  entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
876
1300
  entry.adapterSession.totalUsage.outputTokens = 0;
1301
+ usageChanged = true;
1302
+ }
1303
+ if (runtimeContextLimit !== undefined) {
1304
+ entry.adapterSession.totalUsage.contextLimit = runtimeContextLimit;
1305
+ usageChanged = true;
1306
+ }
1307
+ if (runtimeMaxOutput !== undefined) {
1308
+ entry.adapterSession.totalUsage.maxOutput = runtimeMaxOutput;
1309
+ usageChanged = true;
1310
+ }
1311
+ if (runtimeInputLimit !== undefined) {
1312
+ entry.adapterSession.totalUsage.inputLimit = runtimeInputLimit;
1313
+ usageChanged = true;
1314
+ }
1315
+ if (runtimeCompactionThreshold !== undefined) {
1316
+ entry.adapterSession.totalUsage.compactionThreshold = runtimeCompactionThreshold;
1317
+ usageChanged = true;
1318
+ }
1319
+ if (usageChanged) {
877
1320
  this.emit("status-change", event.sessionId, entry.adapterSession.status, {
878
1321
  usage: entry.adapterSession.totalUsage,
879
1322
  });
@@ -898,6 +1341,113 @@ export class AcodeSdkAdapter extends EventEmitter {
898
1341
  });
899
1342
  return;
900
1343
  }
1344
+ if (event.type === "context_compaction") {
1345
+ const data = event.data;
1346
+ const action = data.action;
1347
+ const isStart = action === "start";
1348
+ const isEnd = action === "end";
1349
+ const isPhase = action === "phase";
1350
+ // parentId 用于将子事件(phase)关联到父压缩事件
1351
+ const parentId = data.parentId;
1352
+ // compactionId 作为父事件的 actionId,子事件通过 parentId 引用它
1353
+ const compactionId = data.compactionId;
1354
+ let actionLabel;
1355
+ let actionDetail;
1356
+ if (isStart) {
1357
+ const sizeKB = String(data.sizeKB ?? data.sizeBeforeKB ?? "?");
1358
+ const msgCount = String(data.messageCount ?? data.messageCountBefore ?? "?");
1359
+ const estimatedTokens = data.estimatedTokens;
1360
+ const actualInput = data.actualInputTokens ? `input=${data.actualInputTokens}` : "";
1361
+ const actualOutput = data.actualOutputTokens ? `output=${data.actualOutputTokens}` : "";
1362
+ const trigger = data.trigger ? `, 触发: ${data.trigger}` : "";
1363
+ actionLabel = "上下文压缩开始";
1364
+ if (estimatedTokens !== undefined) {
1365
+ actionDetail = `压缩前: ~${estimatedTokens} tokens, 消息: ${msgCount}条` +
1366
+ (actualInput ? `, 实际 tokens: ${actualInput} ${actualOutput}` : "") + trigger;
1367
+ }
1368
+ else {
1369
+ actionDetail = `压缩前: ${sizeKB}KB, 消息: ${msgCount}条` +
1370
+ (actualInput ? `, 实际 tokens: ${actualInput} ${actualOutput}` : "") + trigger;
1371
+ }
1372
+ }
1373
+ else if (isPhase) {
1374
+ // 子事件(阶段事件):使用 runtime 传入的 actionLabel 和 actionDetail
1375
+ actionLabel = String(data.phaseLabel ?? "压缩阶段");
1376
+ actionDetail = String(data.phaseDetail ?? "");
1377
+ }
1378
+ else {
1379
+ // action === "end" —— 作为压缩完成子事件,嵌套在父事件下
1380
+ const tokensBefore = data.tokensBefore;
1381
+ const tokensAfter = data.tokensAfter;
1382
+ const freedTokensNum = data.freedTokens;
1383
+ const sizeBefore = String(data.sizeBeforeKB ?? "?");
1384
+ const sizeAfter = String(data.sizeAfterKB ?? "?");
1385
+ const freedKB = String(data.freedKB ?? "?");
1386
+ const strategy = String(data.strategy ?? "?");
1387
+ const pruned = Number(data.prunedToolMessages ?? 0);
1388
+ const summarized = Number(data.summarizedMessages ?? 0);
1389
+ actionLabel = "压缩完成";
1390
+ if (tokensBefore !== undefined && tokensAfter !== undefined) {
1391
+ const diff = freedTokensNum ?? (tokensBefore - tokensAfter);
1392
+ actionDetail = `释放: ${diff} tokens, ${tokensBefore} tokens → ${tokensAfter} tokens, 策略: ${strategy}` +
1393
+ (pruned ? `, 裁剪工具: ${pruned}条` : "") +
1394
+ (summarized ? `, 摘要化: ${summarized}条` : "");
1395
+ }
1396
+ else {
1397
+ actionDetail = `释放: ${freedKB}KB, ${sizeBefore}KB → ${sizeAfter}KB, 策略: ${strategy}` +
1398
+ (pruned ? `, 裁剪工具: ${pruned}条` : "") +
1399
+ (summarized ? `, 摘要化: ${summarized}条` : "");
1400
+ }
1401
+ }
1402
+ // ★ 更新 adapter 内部状态为 compacting,使前端 agent 卡片显示压缩中
1403
+ // 子事件(phase/end)不重复更新状态,仅推送到时间线
1404
+ if (isStart) {
1405
+ entry.adapterSession.status = "compacting";
1406
+ }
1407
+ const actionInfo = {
1408
+ actionType: "context_compaction",
1409
+ actionLabel,
1410
+ actionDetail,
1411
+ };
1412
+ // ★ 事件层级:start 为父事件(root),phase 和 end 为子事件(nested)
1413
+ // - 父事件:actionId = compactionId
1414
+ // - 子事件:parentId = compactionId, actionId 唯一(避免前端 seenActionIds 去重冲突)
1415
+ if (isStart && compactionId) {
1416
+ actionInfo.actionId = compactionId;
1417
+ }
1418
+ else if ((isPhase || isEnd) && compactionId) {
1419
+ actionInfo.parentId = compactionId;
1420
+ if (isPhase) {
1421
+ const phase = String(data.phase ?? "unknown");
1422
+ actionInfo.actionId = `${compactionId}-phase-${phase}`;
1423
+ }
1424
+ else {
1425
+ // end 事件使用独立 actionId,避免与 start 的 compactionId 重复
1426
+ actionInfo.actionId = `${compactionId}-end`;
1427
+ }
1428
+ }
1429
+ // 兼容旧路径:runtime 可能通过 parentId 字段传入
1430
+ if (isPhase && parentId && !actionInfo.parentId) {
1431
+ actionInfo.parentId = parentId;
1432
+ }
1433
+ // ★ 压缩结束时重置 totalUsage(input 和 output 均归零),使面板显示压缩后的真实上下文大小。
1434
+ // 与 runtime 压缩后重置 accumulatedInputTokens/accumulatedOutputTokens = 0(runtime.ts)对齐。
1435
+ // 若只重置 input 不重置 output,压缩后面板 total 仍含压缩前 output,持续虚高。
1436
+ // 将 usage 合并到 end 事件的 actionInfo 中,避免额外的无 actionInfo status-change
1437
+ // 触发前端 handleDeltaStatusEvent 创建无 actionId 的占位事件(会导致扁平化渲染)。
1438
+ if (isEnd) {
1439
+ entry.adapterSession.totalUsage.inputTokens = 0;
1440
+ entry.adapterSession.totalUsage.outputTokens = 0;
1441
+ // ★ 使用 spread 副本,避免后续事件(如 ai_request)修改 totalUsage 时
1442
+ // 通过引用别名反向改变已发出的 compaction 事件 usage 快照。
1443
+ actionInfo.usage = { ...entry.adapterSession.totalUsage };
1444
+ }
1445
+ this.emitProviderEvent(event, "context_compaction", actionInfo);
1446
+ // ★ 与其他事件(tool_use_start/turn_complete 等)一致,
1447
+ // 通过 status-change 事件将压缩状态推送到面板时间线。
1448
+ this.emit("status-change", event.sessionId, "compacting", actionInfo);
1449
+ return;
1450
+ }
901
1451
  this.emitProviderEvent(event, "thinking", {
902
1452
  text: JSON.stringify(event.data),
903
1453
  });
@@ -944,9 +1494,16 @@ export class AcodeSdkAdapter extends EventEmitter {
944
1494
  const entry = this.sessions.get(sessionId);
945
1495
  if (!entry || this.isTerminalStatus(entry.adapterSession.status))
946
1496
  return;
1497
+ // ★ 修复竞态:递增 token,.finally() 只在 token 匹配时执行清理。
1498
+ // 防止前一轮 submit() 的 .finally() 清除新一轮的看门狗和 autoPollActive。
1499
+ // 场景:看门狗 120s 触发 abort → R2 恢复 → 新一轮 submit 排队 →
1500
+ // 前一轮 executeSubmit 数百秒后才完成 → .finally() 错误清除新一轮状态。
1501
+ const myToken = (entry.autoPollToken ?? 0) + 1;
1502
+ entry.autoPollToken = myToken;
947
1503
  entry.autoPollActive = true;
948
1504
  entry.autoPollObservedPollingTool = false;
949
1505
  entry.autoPollObservedAssistantText = false;
1506
+ entry.autoPollObservedAnyTool = false;
950
1507
  entry.stopAutoPollAfterCurrentTurn = false;
951
1508
  if (!options?.keepWaitingStatus) {
952
1509
  entry.adapterSession.status = "thinking";
@@ -963,6 +1520,16 @@ export class AcodeSdkAdapter extends EventEmitter {
963
1520
  const latestEntry = this.sessions.get(sessionId);
964
1521
  if (!latestEntry)
965
1522
  return;
1523
+ // ★ token 守卫:如果 token 不匹配,说明新一轮 submitAutoPollPrompt 已启动,
1524
+ // 当前轮的清理操作(取消看门狗、清除 autoPollActive)会错误覆盖新一轮状态。
1525
+ // 新一轮已设置了新的看门狗和 autoPollActive=true,直接跳过清理。
1526
+ // 关键:这防止了"前一轮 .finally() 清除新一轮看门狗 → 新一轮 fetch 挂死时无保护 → 永久卡死"的竞态。
1527
+ if (latestEntry.autoPollToken !== myToken) {
1528
+ adapterDebugLog(`[ACode] submitAutoPollPrompt.finally: token mismatch ` +
1529
+ `(my=${myToken}, current=${latestEntry.autoPollToken}), ` +
1530
+ `skipping cleanup — newer turn has taken over`);
1531
+ return;
1532
+ }
966
1533
  // 无论 submit 是正常返回还是超时恢复,都需要取消超时检测(超时回调里已设置 submitTimeoutTimer=undefined,再调 cancel 是安全的)
967
1534
  this.cancelSubmitTimeoutDetection(latestEntry);
968
1535
  latestEntry.autoPollActive = false;
@@ -980,6 +1547,7 @@ export class AcodeSdkAdapter extends EventEmitter {
980
1547
  latestEntry.stopAutoPollAfterCurrentTurn = false;
981
1548
  latestEntry.autoPollObservedPollingTool = false;
982
1549
  latestEntry.autoPollObservedAssistantText = false;
1550
+ latestEntry.autoPollObservedAnyTool = false;
983
1551
  return;
984
1552
  }
985
1553
  if (latestEntry.adapterSession.status === "error") {
@@ -1057,10 +1625,57 @@ export class AcodeSdkAdapter extends EventEmitter {
1057
1625
  data: { text: error instanceof Error ? error.message : String(error) },
1058
1626
  });
1059
1627
  }
1628
+ /**
1629
+ * 从会话消息中估算 AI 输出 token 数(字符数 / 4 粗略估算)。
1630
+ * 注意:该估算对 CJK 文本偏高(实际每字约 1-2 tokens),
1631
+ * 对纯 ASCII 文本偏低(约 3-5 字符/token),仅供面板展示实时趋势使用,
1632
+ * 不应用于精确计费或配额判定。精确值以 turn_complete 事件中的 usage 为准。
1633
+ */
1634
+ estimateOutputTokensFromMessages(messages) {
1635
+ const assistantText = messages
1636
+ .filter((m) => m.role === "assistant")
1637
+ .map((m) => m.content)
1638
+ .join("");
1639
+ return Math.max(0, Math.round(assistantText.length / 4));
1640
+ }
1060
1641
  recoverTurnFailure(sessionId, entry) {
1061
1642
  if (this.isTerminalStatus(entry.adapterSession.status)) {
1062
1643
  return;
1063
1644
  }
1645
+ // ★ 修复 BUG(时间线永久卡在"编辑文件"):当恢复前状态为 tool_using 时,
1646
+ // 补发 tool_use_end 事件,关闭前端时间线中由 tool_use_start 创建的工具条目。
1647
+ // 根因:数据流看门狗在 tool_using 状态触发后调用本方法,但原实现只切换状态为
1648
+ // waiting_input,从不补发 tool_use_end。而 runtime 端因 abortRequested===true,
1649
+ // 在 catch 块中 rethrow 前跳过了 tool_result 发射(3241 行),
1650
+ // 导致 adapter 永远收不到 tool_result,tool_use_end 永不发射,时间线永久卡住。
1651
+ if (entry.adapterSession.status === "tool_using" && entry.pendingToolEventData) {
1652
+ const toolData = entry.pendingToolEventData;
1653
+ adapterDebugLog(`[ACode] recoverTurnFailure: 补发 tool_use_end (tool: ${toolData.toolName ?? "unknown"}) ` +
1654
+ `关闭时间线条目,避免永久卡在工具执行状态`);
1655
+ this.emit("event", {
1656
+ type: "tool_use_end",
1657
+ sessionId,
1658
+ timestamp: new Date().toISOString(),
1659
+ data: {
1660
+ ...toolData,
1661
+ toolResult: "[aborted] 工具执行被数据流看门狗中断",
1662
+ },
1663
+ });
1664
+ entry.pendingToolEventData = undefined;
1665
+ }
1666
+ // ★ 修复:超时恢复前强制 emit turn_complete,计入已有的 output tokens,
1667
+ // 否则面板上 output_tokens 永远是 0(turn_complete 从未发射)。
1668
+ // 使用消息历史中的 assistant 文本粗略估算 output tokens(累计估算)。
1669
+ // 此为异常恢复路径(turn_complete 未到达,无真实 completion_tokens),
1670
+ // 直接覆盖式赋值(不再用 delta += 累加,避免与覆盖式语义冲突导致重复计算)。
1671
+ const estimatedOutput = this.estimateOutputTokensFromMessages(entry.adapterSession.messages);
1672
+ if (estimatedOutput > 0) {
1673
+ const prev = entry.adapterSession.totalUsage.outputTokens;
1674
+ entry.adapterSession.totalUsage.outputTokens = estimatedOutput;
1675
+ adapterDebugLog(`[ACode] recoverTurnFailure: 估算覆盖 output tokens ${prev} -> ${estimatedOutput} ` +
1676
+ `(totalUsage now: input=${entry.adapterSession.totalUsage.inputTokens}, ` +
1677
+ `output=${entry.adapterSession.totalUsage.outputTokens})`);
1678
+ }
1064
1679
  // ★ 修复:检测 idle 命令是否包含 poll_message,在 waiting_input 状态变更中携带 idle 动作信息
1065
1680
  const idleCommands = entry.idleCommands?.idleInputCommand;
1066
1681
  const willResumePolling = Boolean(idleCommands && isMessagePollingIdleCommand(idleCommands));
@@ -1072,13 +1687,19 @@ export class AcodeSdkAdapter extends EventEmitter {
1072
1687
  if (entry.autoPollTimer || entry.failedTurnRecoveryScheduled) {
1073
1688
  if (entry.adapterSession.status !== "waiting_input") {
1074
1689
  entry.adapterSession.status = "waiting_input";
1075
- this.emit("status-change", sessionId, "waiting_input", idleActionInfo);
1690
+ this.emit("status-change", sessionId, "waiting_input", {
1691
+ usage: entry.adapterSession.totalUsage,
1692
+ ...idleActionInfo,
1693
+ });
1076
1694
  }
1077
1695
  return;
1078
1696
  }
1079
1697
  this.cancelScheduledAutoPoll(entry);
1080
1698
  entry.adapterSession.status = "waiting_input";
1081
- this.emit("status-change", sessionId, "waiting_input", idleActionInfo);
1699
+ this.emit("status-change", sessionId, "waiting_input", {
1700
+ usage: entry.adapterSession.totalUsage,
1701
+ ...idleActionInfo,
1702
+ });
1082
1703
  entry.failedTurnRecoveryScheduled = true;
1083
1704
  entry.autoPollTimer = setTimeout(() => {
1084
1705
  const currentEntry = this.sessions.get(sessionId);
@@ -1112,20 +1733,23 @@ export class AcodeSdkAdapter extends EventEmitter {
1112
1733
  * look permanently stuck. Recover to waiting_input and arm the listening loop.
1113
1734
  */
1114
1735
  recoverResolvedTurnWithoutTerminalEvent(sessionId) {
1115
- console.log(`[ACode] ▶️ recoverResolvedTurnWithoutTerminalEvent called for session ${sessionId}`);
1736
+ adapterDebugLog(`[ACode] ▶️ recoverResolvedTurnWithoutTerminalEvent called for session ${sessionId}`);
1116
1737
  const entry = this.sessions.get(sessionId);
1117
1738
  if (!entry || this.isTerminalStatus(entry.adapterSession.status)) {
1118
- console.log(`[ACode] Skipping recovery: entry=${!!entry}, isTerminal=${entry ? this.isTerminalStatus(entry.adapterSession.status) : 'N/A'}`);
1739
+ adapterDebugLog(`[ACode] Skipping recovery: entry=${!!entry}, isTerminal=${entry ? this.isTerminalStatus(entry.adapterSession.status) : 'N/A'}`);
1119
1740
  return;
1120
1741
  }
1121
- console.log(`[ACode] Current status: ${entry.adapterSession.status}`);
1742
+ adapterDebugLog(`[ACode] Current status: ${entry.adapterSession.status}`);
1122
1743
  if (entry.adapterSession.status !== "thinking" &&
1123
1744
  entry.adapterSession.status !== "tool_using") {
1124
- console.log(`[ACode] Skipping recovery: status is ${entry.adapterSession.status} (not thinking/tool_using)`);
1745
+ adapterDebugLog(`[ACode] Skipping recovery: status is ${entry.adapterSession.status} (not thinking/tool_using)`);
1125
1746
  return;
1126
1747
  }
1127
- console.log(`[ACode] ⚠️ Detected stuck state: ${entry.adapterSession.status} after submit resolved!`);
1128
- console.log(`[ACode] Recovering to waiting_input...`);
1748
+ console.warn(`[ACode] ⚠️ Detected stuck state: ${entry.adapterSession.status} after submit resolved! ` +
1749
+ `Recovering to waiting_input. ` +
1750
+ `If runtime.submit() is still pending (e.g., built-in tool execution not interruptible by abort()), ` +
1751
+ `the submitQueue is deadlocked and subsequent auto-poll/user submits will never execute.`);
1752
+ adapterDebugLog(`[ACode] Recovering to waiting_input...`);
1129
1753
  entry.adapterSession.status = "waiting_input";
1130
1754
  // ★ 修复:检测 idle 命令是否包含 poll_message,在 waiting_input 状态变更中携带 idle 动作信息
1131
1755
  const idleCommands = entry.idleCommands?.idleInputCommand;
@@ -1136,42 +1760,120 @@ export class AcodeSdkAdapter extends EventEmitter {
1136
1760
  actionDetail: 'aws-mcp/poll_message',
1137
1761
  } : undefined;
1138
1762
  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...`);
1763
+ adapterDebugLog(`[ACode] ✅ Recovery complete, status now: ${entry.adapterSession.status}`);
1764
+ adapterDebugLog(`[ACode] ▶️ Arming idle loop or auto-poll...`);
1141
1765
  this.armIdleLoopOrAutoPoll(sessionId);
1142
- console.log(`[ACode] ✅ Idle loop armed, recovery process complete`);
1766
+ adapterDebugLog(`[ACode] ✅ Idle loop armed, recovery process complete`);
1143
1767
  }
1144
1768
  /**
1145
- * 启动 submit 超时检测(治本修复:防止状态永久卡住)
1769
+ * 启动异步看门狗:周期性轮询运行时状态,仅在真正卡死时恢复。
1146
1770
  *
1147
- * 如果 runtime.submit() 在指定时间内未返回,强制触发恢复机制,
1148
- * 避免会话永久卡在 thinking/tool_using 状态。
1771
+ * 替代原来两个独立的 setTimeout(submitTimeoutDetection + dataFlowWatchdog),
1772
+ * 改为单一 setInterval 轮询。核心设计:
1773
+ * - 每 ACODE_WATCHDOG_POLL_INTERVAL_MS(5s)检查一次状态
1774
+ * - waiting_input 状态下不累计超时(agent 正在有意等待,如 poll_message/ask_user_question)
1775
+ * - 仅在 thinking/tool_using 状态下累计"活跃持续时间"和"无数据流时间"
1776
+ * - submit 超时 120s 或数据流超时 40s 时触发 recoverTurnFailure
1777
+ *
1778
+ * 相比 setTimeout 方案的优势:
1779
+ * 1. 状态感知:每次轮询都读取当前真实状态,不会因状态在定时周期内多次变化而误判
1780
+ * 2. waiting_input 自动暂停计时:无需递归重新计时,天然跳过
1781
+ * 3. 单一 timer:避免两个独立 timer 的竞态和重复恢复
1149
1782
  */
1150
1783
  startSubmitTimeoutDetection(sessionId, entry, source) {
1151
- // 清除之前的超时检测
1152
1784
  this.cancelSubmitTimeoutDetection(entry);
1153
- entry.submitStartTime = Date.now();
1154
- entry.submitTimeoutTimer = setTimeout(() => {
1785
+ entry.watchdogSubmitStartTime = Date.now();
1786
+ entry.watchdogLastDataFlowTime = Date.now();
1787
+ entry.watchdogTimer = setInterval(() => {
1155
1788
  const currentEntry = this.sessions.get(sessionId);
1156
- if (!currentEntry)
1789
+ if (!currentEntry) {
1790
+ this.cancelSubmitTimeoutDetection(entry);
1157
1791
  return;
1158
- const elapsed = Date.now() - (currentEntry.submitStartTime || 0);
1159
- console.warn(`[ACode] CRITICAL: submit timeout detected after ${Math.round(elapsed / 1000)}s ` +
1160
- `(source: ${source}, session: ${sessionId}, status: ${currentEntry.adapterSession.status}). ` +
1161
- `Forcing recovery to prevent permanent state stuck.`);
1162
- // 强制恢复到 waiting_input 状态
1163
- currentEntry.submitTimeoutTimer = undefined;
1164
- this.recoverTurnFailure(sessionId, currentEntry);
1165
- }, ACODE_SUBMIT_TIMEOUT_MS);
1792
+ }
1793
+ const status = currentEntry.adapterSession.status;
1794
+ const now = Date.now();
1795
+ // waiting_input:agent 正在有意等待(poll_message/ask_user_question),不计时
1796
+ if (status === "waiting_input") {
1797
+ // 重置计时基准,下次进入 thinking/tool_using 时从 0 开始计
1798
+ currentEntry.watchdogSubmitStartTime = now;
1799
+ currentEntry.watchdogLastDataFlowTime = now;
1800
+ return;
1801
+ }
1802
+ // 仅 thinking/tool_using 状态下检查超时
1803
+ if (status !== "thinking" && status !== "tool_using") {
1804
+ return;
1805
+ }
1806
+ const submitElapsed = now - (currentEntry.watchdogSubmitStartTime || now);
1807
+ const dataFlowElapsed = now - (currentEntry.watchdogLastDataFlowTime || now);
1808
+ // 数据流超时(40s 无流式数据)— 比 submit 超时(120s)更短,优先触发
1809
+ if (dataFlowElapsed >= ACODE_DATA_FLOW_STREAM_WATCHDOG_MS) {
1810
+ console.warn(`[ACode] CRITICAL: Data flow watchdog triggered after ${Math.round(dataFlowElapsed / 1000)}s ` +
1811
+ `(source: ${source}, session: ${sessionId}, status: ${status}). ` +
1812
+ `No stream data received. Forcing recovery via recoverTurnFailure.`);
1813
+ this.executeWatchdogRecovery(sessionId, currentEntry, source, "data_flow", dataFlowElapsed);
1814
+ return;
1815
+ }
1816
+ // submit 超时(120s 未返回)
1817
+ if (submitElapsed >= ACODE_SUBMIT_TIMEOUT_MS) {
1818
+ console.warn(`[ACode] CRITICAL: submit timeout detected after ${Math.round(submitElapsed / 1000)}s ` +
1819
+ `(source: ${source}, session: ${sessionId}, status: ${status}). ` +
1820
+ `Forcing recovery to prevent permanent state stuck.`);
1821
+ this.executeWatchdogRecovery(sessionId, currentEntry, source, "submit_timeout", submitElapsed);
1822
+ return;
1823
+ }
1824
+ }, ACODE_WATCHDOG_POLL_INTERVAL_MS);
1166
1825
  }
1167
1826
  /**
1168
- * 取消 submit 超时检测
1827
+ * 执行看门狗恢复:abort runtime + recoverTurnFailure
1828
+ */
1829
+ executeWatchdogRecovery(sessionId, entry, source, reason, elapsedMs) {
1830
+ // ★ 关键修复:先 abort runtime 打断挂起的 fetch / stream reader,
1831
+ // 否则 submitQueue 永久阻塞,后续 submit 全部排队等待,agent 彻底死锁。
1832
+ adapterDebugLog(`[ACode] Watchdog (${reason}): calling runtime.abort() after ${Math.round(elapsedMs / 1000)}s...`);
1833
+ try {
1834
+ void entry.runtime.abort();
1835
+ adapterDebugLog(`[ACode] Watchdog (${reason}): runtime.abort() called`);
1836
+ }
1837
+ catch (abortError) {
1838
+ console.warn(`[ACode] ⚠️ Watchdog (${reason}): runtime.abort() threw: ${abortError instanceof Error ? abortError.message : String(abortError)}`);
1839
+ }
1840
+ // 取消看门狗(recoverTurnFailure 会重新 arm idle loop)
1841
+ this.cancelSubmitTimeoutDetection(entry);
1842
+ this.recoverTurnFailure(sessionId, entry);
1843
+ console.warn(`[ACode] ⚠️ Watchdog (${reason}) recovery complete. ` +
1844
+ `If abort() could not cancel the underlying operation, submitQueue may still be deadlocked.`);
1845
+ }
1846
+ /**
1847
+ * 取消看门狗
1169
1848
  */
1170
1849
  cancelSubmitTimeoutDetection(entry) {
1171
- if (entry.submitTimeoutTimer) {
1172
- clearTimeout(entry.submitTimeoutTimer);
1173
- entry.submitTimeoutTimer = undefined;
1850
+ if (entry.watchdogTimer) {
1851
+ clearInterval(entry.watchdogTimer);
1852
+ entry.watchdogTimer = undefined;
1174
1853
  }
1175
- entry.submitStartTime = undefined;
1854
+ entry.watchdogSubmitStartTime = undefined;
1855
+ entry.watchdogLastDataFlowTime = undefined;
1856
+ }
1857
+ /**
1858
+ * 重置数据流计时基准:收到流式数据(assistant_delta/thinking_delta)时调用。
1859
+ * 不再重启定时器,仅更新计时基准,下次轮询时从新基准计算。
1860
+ */
1861
+ resetDataFlowWatchdog(sessionId, entry) {
1862
+ void sessionId;
1863
+ entry.watchdogLastDataFlowTime = Date.now();
1864
+ }
1865
+ /**
1866
+ * 启动数据流看门狗:由 ai_request 调用,初始化数据流计时基准。
1867
+ * 实际的轮询由 startSubmitTimeoutDetection 的 interval 统一处理。
1868
+ */
1869
+ startDataFlowWatchdog(sessionId, entry) {
1870
+ void sessionId;
1871
+ entry.watchdogLastDataFlowTime = Date.now();
1872
+ }
1873
+ /**
1874
+ * 取消数据流看门狗(已合并到 cancelSubmitTimeoutDetection,此方法保留兼容性)
1875
+ */
1876
+ cancelDataFlowWatchdog(entry) {
1877
+ entry.watchdogLastDataFlowTime = Date.now();
1176
1878
  }
1177
1879
  }