aws-runtime-bridge 1.9.78 → 1.9.80

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 (596) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +0 -1
  2. package/dist/adapter/AcodeSdkAdapter.js +11 -1986
  3. package/dist/adapter/AdapterRegistry.d.ts +0 -1
  4. package/dist/adapter/AdapterRegistry.js +2 -100
  5. package/dist/adapter/ClaudeSdkAdapter.d.ts +0 -1
  6. package/dist/adapter/ClaudeSdkAdapter.js +4 -1300
  7. package/dist/adapter/CodexSdkAdapter.d.ts +0 -1
  8. package/dist/adapter/CodexSdkAdapter.js +4 -836
  9. package/dist/adapter/OpencodeSdkAdapter.d.ts +0 -1
  10. package/dist/adapter/OpencodeSdkAdapter.js +4 -879
  11. package/dist/adapter/SdkProviderSpi.d.ts +0 -1
  12. package/dist/adapter/SdkProviderSpi.js +2 -55
  13. package/dist/adapter/acode-tool-names.d.ts +0 -1
  14. package/dist/adapter/acode-tool-names.js +2 -37
  15. package/dist/adapter/idle-poll-loop.d.ts +0 -1
  16. package/dist/adapter/idle-poll-loop.js +2 -59
  17. package/dist/adapter/index.d.ts +0 -1
  18. package/dist/adapter/index.js +2 -11
  19. package/dist/adapter/instance-ai-config-resolver.d.ts +0 -1
  20. package/dist/adapter/instance-ai-config-resolver.js +2 -46
  21. package/dist/adapter/types.d.ts +0 -1
  22. package/dist/adapter/types.js +2 -443
  23. package/dist/adapters/cc-switch/common.d.ts +0 -1
  24. package/dist/adapters/cc-switch/common.js +2 -61
  25. package/dist/adapters/cc-switch/index.d.ts +0 -1
  26. package/dist/adapters/cc-switch/index.js +2 -57
  27. package/dist/adapters/cc-switch/mcp-acode.d.ts +0 -1
  28. package/dist/adapters/cc-switch/mcp-acode.js +2 -71
  29. package/dist/adapters/cc-switch/mcp-claude.d.ts +0 -1
  30. package/dist/adapters/cc-switch/mcp-claude.js +2 -89
  31. package/dist/adapters/cc-switch/mcp-claudecode.d.ts +0 -1
  32. package/dist/adapters/cc-switch/mcp-claudecode.js +2 -77
  33. package/dist/adapters/cc-switch/mcp-codex.d.ts +0 -1
  34. package/dist/adapters/cc-switch/mcp-codex.js +11 -249
  35. package/dist/adapters/cc-switch/mcp-opencode.d.ts +0 -1
  36. package/dist/adapters/cc-switch/mcp-opencode.js +2 -97
  37. package/dist/adapters/cc-switch/mcp-placeholder.d.ts +0 -1
  38. package/dist/adapters/cc-switch/mcp-placeholder.js +2 -19
  39. package/dist/adapters/cc-switch/skill-acode.d.ts +0 -1
  40. package/dist/adapters/cc-switch/skill-acode.js +2 -12
  41. package/dist/adapters/cc-switch/skill-claude.d.ts +0 -1
  42. package/dist/adapters/cc-switch/skill-claude.js +2 -11
  43. package/dist/adapters/cc-switch/skill-claudecode.d.ts +0 -1
  44. package/dist/adapters/cc-switch/skill-claudecode.js +2 -11
  45. package/dist/adapters/cc-switch/skill-codex.d.ts +0 -1
  46. package/dist/adapters/cc-switch/skill-codex.js +2 -6
  47. package/dist/adapters/cc-switch/skill-directory.d.ts +0 -1
  48. package/dist/adapters/cc-switch/skill-directory.js +3 -137
  49. package/dist/adapters/cc-switch/skill-opencode.d.ts +0 -1
  50. package/dist/adapters/cc-switch/skill-opencode.js +2 -11
  51. package/dist/adapters/cc-switch/skill-placeholder.d.ts +0 -1
  52. package/dist/adapters/cc-switch/skill-placeholder.js +2 -19
  53. package/dist/config.d.ts +0 -1
  54. package/dist/config.js +2 -290
  55. package/dist/index.d.ts +0 -1
  56. package/dist/index.js +2 -573
  57. package/dist/middleware/auth.d.ts +0 -1
  58. package/dist/middleware/auth.js +2 -98
  59. package/dist/routes/acode-todos.d.ts +0 -1
  60. package/dist/routes/acode-todos.js +2 -75
  61. package/dist/routes/ai-sources.d.ts +0 -1
  62. package/dist/routes/ai-sources.js +2 -132
  63. package/dist/routes/dashboard.d.ts +0 -1
  64. package/dist/routes/dashboard.js +182 -649
  65. package/dist/routes/events.d.ts +0 -1
  66. package/dist/routes/events.js +2 -22
  67. package/dist/routes/file-browser.d.ts +0 -1
  68. package/dist/routes/file-browser.js +2 -781
  69. package/dist/routes/file-diff-rollback.d.ts +0 -1
  70. package/dist/routes/file-diff-rollback.js +2 -301
  71. package/dist/routes/file-snapshot.d.ts +0 -1
  72. package/dist/routes/file-snapshot.js +2 -156
  73. package/dist/routes/git.d.ts +0 -1
  74. package/dist/routes/git.js +28 -1900
  75. package/dist/routes/instance.d.ts +0 -1
  76. package/dist/routes/instance.js +3 -748
  77. package/dist/routes/mcp.d.ts +0 -1
  78. package/dist/routes/mcp.js +2 -196
  79. package/dist/routes/processes.d.ts +0 -1
  80. package/dist/routes/processes.js +2 -252
  81. package/dist/routes/properties.d.ts +0 -1
  82. package/dist/routes/properties.js +2 -72
  83. package/dist/routes/pty.d.ts +0 -1
  84. package/dist/routes/pty.js +6 -560
  85. package/dist/routes/runtime-binding.d.ts +0 -1
  86. package/dist/routes/runtime-binding.js +2 -263
  87. package/dist/routes/sessions.d.ts +0 -1
  88. package/dist/routes/sessions.js +2 -758
  89. package/dist/routes/skills.d.ts +0 -1
  90. package/dist/routes/skills.js +2 -187
  91. package/dist/routes/sub-agents.d.ts +0 -1
  92. package/dist/routes/sub-agents.js +20 -279
  93. package/dist/routes/system-metrics.d.ts +0 -1
  94. package/dist/routes/system-metrics.js +2 -20
  95. package/dist/routes/task-context.d.ts +0 -1
  96. package/dist/routes/task-context.js +2 -78
  97. package/dist/routes/terminal.d.ts +0 -1
  98. package/dist/routes/terminal.js +54 -2882
  99. package/dist/routes/yml.d.ts +0 -1
  100. package/dist/routes/yml.js +2 -223
  101. package/dist/services/acode-package.d.ts +0 -1
  102. package/dist/services/acode-package.js +2 -111
  103. package/dist/services/agent-event-store.d.ts +0 -1
  104. package/dist/services/agent-event-store.js +2 -81
  105. package/dist/services/agent-process-manager.d.ts +0 -1
  106. package/dist/services/agent-process-manager.js +2 -796
  107. package/dist/services/ai-source-apply.d.ts +0 -1
  108. package/dist/services/ai-source-apply.js +2 -83
  109. package/dist/services/auto-register.d.ts +0 -1
  110. package/dist/services/auto-register.js +2 -1053
  111. package/dist/services/aws-client-agent-mcp.d.ts +0 -1
  112. package/dist/services/aws-client-agent-mcp.js +3 -337
  113. package/dist/services/bridge-package-publish-hooks.d.ts +0 -1
  114. package/dist/services/bridge-package-publish-hooks.js +2 -115
  115. package/dist/services/cc-switch-sdk.d.ts +0 -1
  116. package/dist/services/cc-switch-sdk.js +2 -135
  117. package/dist/services/cli-commands.d.ts +6 -1
  118. package/dist/services/cli-commands.js +6 -645
  119. package/dist/services/event-delta-pusher.d.ts +0 -1
  120. package/dist/services/event-delta-pusher.js +2 -95
  121. package/dist/services/fileSnapshotStore.d.ts +0 -1
  122. package/dist/services/fileSnapshotStore.js +2 -500
  123. package/dist/services/instance-ai-config-service.d.ts +0 -1
  124. package/dist/services/instance-ai-config-service.js +2 -174
  125. package/dist/services/instance-init-service.d.ts +0 -1
  126. package/dist/services/instance-init-service.js +2 -193
  127. package/dist/services/instance-service.d.ts +0 -1
  128. package/dist/services/instance-service.js +2 -237
  129. package/dist/services/instance-state.d.ts +0 -1
  130. package/dist/services/instance-state.js +2 -94
  131. package/dist/services/legacy-path-migration.d.ts +0 -1
  132. package/dist/services/legacy-path-migration.js +2 -66
  133. package/dist/services/lifecycle-state.d.ts +0 -1
  134. package/dist/services/lifecycle-state.js +3 -110
  135. package/dist/services/mcp-launch-binding-queue.d.ts +0 -1
  136. package/dist/services/mcp-launch-binding-queue.js +2 -175
  137. package/dist/services/orphan-monitor.d.ts +0 -1
  138. package/dist/services/orphan-monitor.js +2 -382
  139. package/dist/services/panel-auth.d.ts +0 -1
  140. package/dist/services/panel-auth.js +2 -168
  141. package/dist/services/process-detector.d.ts +0 -1
  142. package/dist/services/process-detector.js +18 -1019
  143. package/dist/services/process-registry.d.ts +0 -1
  144. package/dist/services/process-registry.js +2 -375
  145. package/dist/services/ring-buffer.d.ts +0 -1
  146. package/dist/services/ring-buffer.js +2 -103
  147. package/dist/services/runtime-binding.d.ts +0 -1
  148. package/dist/services/runtime-binding.js +6 -363
  149. package/dist/services/runtime-lifecycle-policy.d.ts +0 -1
  150. package/dist/services/runtime-lifecycle-policy.js +2 -35
  151. package/dist/services/session-output.d.ts +0 -1
  152. package/dist/services/session-output.js +5 -539
  153. package/dist/services/startup-config-wizard.d.ts +0 -1
  154. package/dist/services/startup-config-wizard.js +14 -258
  155. package/dist/services/sub-agent-manager-instance.d.ts +0 -1
  156. package/dist/services/sub-agent-manager-instance.js +2 -12
  157. package/dist/services/system-metrics.d.ts +0 -1
  158. package/dist/services/system-metrics.js +2 -252
  159. package/dist/services/terminal-persistence.d.ts +0 -1
  160. package/dist/services/terminal-persistence.js +2 -176
  161. package/dist/services/tool-installer.d.ts +0 -1
  162. package/dist/services/tool-installer.js +5 -751
  163. package/dist/services/tunnel-client.d.ts +0 -1
  164. package/dist/services/tunnel-client.js +2 -402
  165. package/dist/services/windows-service-manager.d.ts +3 -2
  166. package/dist/services/windows-service-manager.js +2 -54
  167. package/dist/services/workspace-files.d.ts +0 -1
  168. package/dist/services/workspace-files.js +2 -1275
  169. package/dist/services/workspace-watch.d.ts +0 -1
  170. package/dist/services/workspace-watch.js +2 -123
  171. package/dist/services/workspaceTaskChangeWatcher.d.ts +0 -1
  172. package/dist/services/workspaceTaskChangeWatcher.js +2 -483
  173. package/dist/types.d.ts +0 -1
  174. package/dist/types.js +1 -1
  175. package/dist/utils/file-utils.d.ts +0 -1
  176. package/dist/utils/file-utils.js +3 -208
  177. package/dist/utils/instance-ai-config-debug-logger.d.ts +0 -1
  178. package/dist/utils/instance-ai-config-debug-logger.js +4 -63
  179. package/dist/utils/logger.d.ts +0 -1
  180. package/dist/utils/logger.js +2 -154
  181. package/dist/utils/mcp-utils.d.ts +0 -1
  182. package/dist/utils/mcp-utils.js +2 -187
  183. package/dist/utils/path-utils.d.ts +0 -1
  184. package/dist/utils/path-utils.js +2 -44
  185. package/dist/utils/sdk-package-loader.d.ts +0 -1
  186. package/dist/utils/sdk-package-loader.js +2 -78
  187. package/dist/utils/validation.d.ts +0 -1
  188. package/dist/utils/validation.js +2 -53
  189. package/dist/utils/yaml-utils.d.ts +0 -1
  190. package/dist/utils/yaml-utils.js +2 -312
  191. package/node_modules/@cc-switch/sdk/dist/constants.d.ts +0 -1
  192. package/node_modules/@cc-switch/sdk/dist/constants.js +2 -63
  193. package/node_modules/@cc-switch/sdk/dist/errors.d.ts +0 -1
  194. package/node_modules/@cc-switch/sdk/dist/errors.js +2 -8
  195. package/node_modules/@cc-switch/sdk/dist/index.d.ts +0 -1
  196. package/node_modules/@cc-switch/sdk/dist/index.js +2 -9
  197. package/node_modules/@cc-switch/sdk/dist/schemas.d.ts +0 -1
  198. package/node_modules/@cc-switch/sdk/dist/schemas.js +2 -38
  199. package/node_modules/@cc-switch/sdk/dist/sdk.d.ts +0 -1
  200. package/node_modules/@cc-switch/sdk/dist/sdk.js +2 -470
  201. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts +0 -1
  202. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.js +4 -307
  203. package/node_modules/@cc-switch/sdk/dist/services/mcp-model.d.ts +0 -1
  204. package/node_modules/@cc-switch/sdk/dist/services/mcp-model.js +2 -37
  205. package/node_modules/@cc-switch/sdk/dist/services/mcp-service.d.ts +0 -1
  206. package/node_modules/@cc-switch/sdk/dist/services/mcp-service.js +2 -9
  207. package/node_modules/@cc-switch/sdk/dist/services/skill-model.d.ts +0 -1
  208. package/node_modules/@cc-switch/sdk/dist/services/skill-model.js +2 -41
  209. package/node_modules/@cc-switch/sdk/dist/services/skill-service.d.ts +0 -1
  210. package/node_modules/@cc-switch/sdk/dist/services/skill-service.js +2 -9
  211. package/node_modules/@cc-switch/sdk/dist/state.d.ts +0 -1
  212. package/node_modules/@cc-switch/sdk/dist/state.js +2 -19
  213. package/node_modules/@cc-switch/sdk/dist/types.d.ts +0 -1
  214. package/node_modules/@cc-switch/sdk/dist/types.js +1 -1
  215. package/node_modules/@cc-switch/sdk/dist/utils/fs.d.ts +0 -1
  216. package/node_modules/@cc-switch/sdk/dist/utils/fs.js +3 -114
  217. package/node_modules/@cc-switch/sdk/dist/utils/id.d.ts +0 -1
  218. package/node_modules/@cc-switch/sdk/dist/utils/id.js +2 -12
  219. package/node_modules/@cc-switch/sdk/package.json +1 -1
  220. package/package/acode/dist/ask-user-question-tool.d.ts +0 -1
  221. package/package/acode/dist/ask-user-question-tool.js +4 -233
  222. package/package/acode/dist/built-in-file-tools.d.ts +0 -1
  223. package/package/acode/dist/built-in-file-tools.js +4 -966
  224. package/package/acode/dist/built-in-symbols-tool.d.ts +0 -1
  225. package/package/acode/dist/built-in-symbols-tool.js +2 -117
  226. package/package/acode/dist/builtins/commandArtifacts.d.ts +0 -1
  227. package/package/acode/dist/builtins/commandArtifacts.js +9 -97
  228. package/package/acode/dist/builtins/commandOutputEvents.d.ts +0 -1
  229. package/package/acode/dist/builtins/commandOutputEvents.js +2 -56
  230. package/package/acode/dist/builtins/shellCommandPreparation.d.ts +0 -1
  231. package/package/acode/dist/builtins/shellCommandPreparation.js +5 -44
  232. package/package/acode/dist/builtins/shellCommandResult.d.ts +0 -1
  233. package/package/acode/dist/builtins/shellCommandResult.js +2 -71
  234. package/package/acode/dist/builtins/shellCommandRunner.d.ts +0 -1
  235. package/package/acode/dist/builtins/shellCommandRunner.js +2 -115
  236. package/package/acode/dist/builtins/shellOutputDecoder.d.ts +0 -1
  237. package/package/acode/dist/builtins/shellOutputDecoder.js +2 -94
  238. package/package/acode/dist/cli.d.ts +0 -1
  239. package/package/acode/dist/cli.js +4 -38
  240. package/package/acode/dist/commands/commandLoader.d.ts +0 -1
  241. package/package/acode/dist/commands/commandLoader.js +2 -33
  242. package/package/acode/dist/commands/commandParser.d.ts +0 -1
  243. package/package/acode/dist/commands/commandParser.js +3 -77
  244. package/package/acode/dist/commands/commandRegistry.d.ts +0 -1
  245. package/package/acode/dist/commands/commandRegistry.js +2 -30
  246. package/package/acode/dist/compaction.d.ts +0 -1
  247. package/package/acode/dist/compaction.js +7 -919
  248. package/package/acode/dist/config.d.ts +0 -1
  249. package/package/acode/dist/config.js +5 -105
  250. package/package/acode/dist/fs-utils.d.ts +0 -1
  251. package/package/acode/dist/fs-utils.js +2 -79
  252. package/package/acode/dist/hooks/hookBus.d.ts +0 -1
  253. package/package/acode/dist/hooks/hookBus.js +2 -40
  254. package/package/acode/dist/index.d.ts +0 -1
  255. package/package/acode/dist/index.js +2 -19
  256. package/package/acode/dist/mcpToolNames.d.ts +0 -1
  257. package/package/acode/dist/mcpToolNames.js +2 -17
  258. package/package/acode/dist/parsers/ast-cache.d.ts +0 -1
  259. package/package/acode/dist/parsers/ast-cache.js +2 -76
  260. package/package/acode/dist/parsers/language-detector.d.ts +0 -1
  261. package/package/acode/dist/parsers/language-detector.js +2 -44
  262. package/package/acode/dist/parsers/language-queries.d.ts +0 -1
  263. package/package/acode/dist/parsers/language-queries.js +12 -602
  264. package/package/acode/dist/parsers/symbol-extractor.d.ts +0 -1
  265. package/package/acode/dist/parsers/symbol-extractor.js +3 -164
  266. package/package/acode/dist/parsers/tree-sitter-loader.d.ts +0 -1
  267. package/package/acode/dist/parsers/tree-sitter-loader.js +2 -64
  268. package/package/acode/dist/parsers/types.d.ts +0 -1
  269. package/package/acode/dist/parsers/types.js +1 -7
  270. package/package/acode/dist/runtime.d.ts +0 -1
  271. package/package/acode/dist/runtime.js +86 -3771
  272. package/package/acode/dist/skill-tool.d.ts +0 -1
  273. package/package/acode/dist/skill-tool.js +5 -174
  274. package/package/acode/dist/sub-agent-manager.d.ts +0 -1
  275. package/package/acode/dist/sub-agent-manager.js +2 -1159
  276. package/package/acode/dist/sub-agent-store.d.ts +0 -1
  277. package/package/acode/dist/sub-agent-store.js +4 -236
  278. package/package/acode/dist/sub-agent-tools.d.ts +0 -1
  279. package/package/acode/dist/sub-agent-tools.js +15 -369
  280. package/package/acode/dist/sub-agent-types.d.ts +0 -1
  281. package/package/acode/dist/sub-agent-types.js +1 -8
  282. package/package/acode/dist/todo-store.d.ts +0 -1
  283. package/package/acode/dist/todo-store.js +2 -73
  284. package/package/acode/dist/todo-tool.d.ts +0 -1
  285. package/package/acode/dist/todo-tool.js +5 -121
  286. package/package/acode/dist/types.d.ts +0 -1
  287. package/package/acode/dist/types.js +1 -1
  288. package/package/acode/package.json +2 -2
  289. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +0 -1
  290. package/package/aws-client-agent-mcp/dist/agent-client.js +2 -890
  291. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts +0 -1
  292. package/package/aws-client-agent-mcp/dist/agent-client.test.js +0 -1
  293. package/package/aws-client-agent-mcp/dist/config.d.ts +0 -1
  294. package/package/aws-client-agent-mcp/dist/config.js +2 -243
  295. package/package/aws-client-agent-mcp/dist/config.test.d.ts +0 -1
  296. package/package/aws-client-agent-mcp/dist/config.test.js +0 -1
  297. package/package/aws-client-agent-mcp/dist/constants.d.ts +0 -1
  298. package/package/aws-client-agent-mcp/dist/constants.js +2 -24
  299. package/package/aws-client-agent-mcp/dist/context-manager.d.ts +0 -1
  300. package/package/aws-client-agent-mcp/dist/context-manager.js +2 -477
  301. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +0 -1
  302. package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -1
  303. package/package/aws-client-agent-mcp/dist/http-client.d.ts +0 -1
  304. package/package/aws-client-agent-mcp/dist/http-client.js +2 -446
  305. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts +0 -1
  306. package/package/aws-client-agent-mcp/dist/http-client.test.js +0 -1
  307. package/package/aws-client-agent-mcp/dist/index.d.ts +0 -1
  308. package/package/aws-client-agent-mcp/dist/index.js +2 -34
  309. package/package/aws-client-agent-mcp/dist/logger.d.ts +0 -1
  310. package/package/aws-client-agent-mcp/dist/logger.js +3 -178
  311. package/package/aws-client-agent-mcp/dist/logger.test.d.ts +0 -1
  312. package/package/aws-client-agent-mcp/dist/logger.test.js +0 -1
  313. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -1
  314. package/package/aws-client-agent-mcp/dist/mcp-server.js +38 -2709
  315. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts +0 -1
  316. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +0 -1
  317. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +0 -1
  318. package/package/aws-client-agent-mcp/dist/mcp-tools.js +22 -562
  319. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts +0 -1
  320. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +0 -1
  321. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +0 -1
  322. package/package/aws-client-agent-mcp/dist/memory-store.js +4 -826
  323. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts +0 -1
  324. package/package/aws-client-agent-mcp/dist/memory-store.test.js +0 -1
  325. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +0 -1
  326. package/package/aws-client-agent-mcp/dist/memory-tools.js +31 -294
  327. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +0 -1
  328. package/package/aws-client-agent-mcp/dist/message-buffer.js +2 -288
  329. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts +0 -1
  330. package/package/aws-client-agent-mcp/dist/message-buffer.test.js +0 -1
  331. package/package/aws-client-agent-mcp/dist/messageContent.d.ts +0 -1
  332. package/package/aws-client-agent-mcp/dist/messageContent.js +2 -125
  333. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts +0 -1
  334. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +2 -103
  335. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts +0 -1
  336. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +0 -1
  337. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +0 -1
  338. package/package/aws-client-agent-mcp/dist/server-memory-store.js +2 -96
  339. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +0 -1
  340. package/package/aws-client-agent-mcp/dist/status-reporter.js +3 -327
  341. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts +0 -1
  342. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +0 -1
  343. package/package/aws-client-agent-mcp/dist/types.d.ts +0 -1
  344. package/package/aws-client-agent-mcp/dist/types.js +2 -9
  345. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +0 -1
  346. package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -677
  347. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts +0 -1
  348. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +0 -1
  349. package/package/aws-client-agent-mcp/package.json +1 -1
  350. package/package/cc-switch-sdk/dist/constants.d.ts +0 -1
  351. package/package/cc-switch-sdk/dist/constants.js +2 -63
  352. package/package/cc-switch-sdk/dist/errors.d.ts +0 -1
  353. package/package/cc-switch-sdk/dist/errors.js +2 -8
  354. package/package/cc-switch-sdk/dist/index.d.ts +0 -1
  355. package/package/cc-switch-sdk/dist/index.js +2 -9
  356. package/package/cc-switch-sdk/dist/schemas.d.ts +0 -1
  357. package/package/cc-switch-sdk/dist/schemas.js +2 -38
  358. package/package/cc-switch-sdk/dist/sdk.d.ts +0 -1
  359. package/package/cc-switch-sdk/dist/sdk.js +2 -470
  360. package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts +0 -1
  361. package/package/cc-switch-sdk/dist/services/ai-config-service.js +4 -307
  362. package/package/cc-switch-sdk/dist/services/mcp-model.d.ts +0 -1
  363. package/package/cc-switch-sdk/dist/services/mcp-model.js +2 -37
  364. package/package/cc-switch-sdk/dist/services/mcp-service.d.ts +0 -1
  365. package/package/cc-switch-sdk/dist/services/mcp-service.js +2 -9
  366. package/package/cc-switch-sdk/dist/services/skill-model.d.ts +0 -1
  367. package/package/cc-switch-sdk/dist/services/skill-model.js +2 -41
  368. package/package/cc-switch-sdk/dist/services/skill-service.d.ts +0 -1
  369. package/package/cc-switch-sdk/dist/services/skill-service.js +2 -9
  370. package/package/cc-switch-sdk/dist/state.d.ts +0 -1
  371. package/package/cc-switch-sdk/dist/state.js +2 -19
  372. package/package/cc-switch-sdk/dist/types.d.ts +0 -1
  373. package/package/cc-switch-sdk/dist/types.js +1 -1
  374. package/package/cc-switch-sdk/dist/utils/fs.d.ts +0 -1
  375. package/package/cc-switch-sdk/dist/utils/fs.js +3 -114
  376. package/package/cc-switch-sdk/dist/utils/id.d.ts +0 -1
  377. package/package/cc-switch-sdk/dist/utils/id.js +2 -12
  378. package/package/cc-switch-sdk/package.json +1 -1
  379. package/package.json +4 -2
  380. package/dist/adapter/AcodeSdkAdapter.d.ts.map +0 -1
  381. package/dist/adapter/AdapterRegistry.d.ts.map +0 -1
  382. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +0 -1
  383. package/dist/adapter/CodexSdkAdapter.d.ts.map +0 -1
  384. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +0 -1
  385. package/dist/adapter/SdkProviderSpi.d.ts.map +0 -1
  386. package/dist/adapter/acode-tool-names.d.ts.map +0 -1
  387. package/dist/adapter/idle-poll-loop.d.ts.map +0 -1
  388. package/dist/adapter/index.d.ts.map +0 -1
  389. package/dist/adapter/instance-ai-config-resolver.d.ts.map +0 -1
  390. package/dist/adapter/types.d.ts.map +0 -1
  391. package/dist/adapters/cc-switch/common.d.ts.map +0 -1
  392. package/dist/adapters/cc-switch/index.d.ts.map +0 -1
  393. package/dist/adapters/cc-switch/mcp-acode.d.ts.map +0 -1
  394. package/dist/adapters/cc-switch/mcp-claude.d.ts.map +0 -1
  395. package/dist/adapters/cc-switch/mcp-claudecode.d.ts.map +0 -1
  396. package/dist/adapters/cc-switch/mcp-codex.d.ts.map +0 -1
  397. package/dist/adapters/cc-switch/mcp-opencode.d.ts.map +0 -1
  398. package/dist/adapters/cc-switch/mcp-placeholder.d.ts.map +0 -1
  399. package/dist/adapters/cc-switch/skill-acode.d.ts.map +0 -1
  400. package/dist/adapters/cc-switch/skill-claude.d.ts.map +0 -1
  401. package/dist/adapters/cc-switch/skill-claudecode.d.ts.map +0 -1
  402. package/dist/adapters/cc-switch/skill-codex.d.ts.map +0 -1
  403. package/dist/adapters/cc-switch/skill-directory.d.ts.map +0 -1
  404. package/dist/adapters/cc-switch/skill-opencode.d.ts.map +0 -1
  405. package/dist/adapters/cc-switch/skill-placeholder.d.ts.map +0 -1
  406. package/dist/config.d.ts.map +0 -1
  407. package/dist/index.d.ts.map +0 -1
  408. package/dist/middleware/auth.d.ts.map +0 -1
  409. package/dist/routes/acode-todos.d.ts.map +0 -1
  410. package/dist/routes/ai-sources.d.ts.map +0 -1
  411. package/dist/routes/dashboard.d.ts.map +0 -1
  412. package/dist/routes/events.d.ts.map +0 -1
  413. package/dist/routes/file-browser.d.ts.map +0 -1
  414. package/dist/routes/file-diff-rollback.d.ts.map +0 -1
  415. package/dist/routes/file-snapshot.d.ts.map +0 -1
  416. package/dist/routes/git.d.ts.map +0 -1
  417. package/dist/routes/instance.d.ts.map +0 -1
  418. package/dist/routes/mcp.d.ts.map +0 -1
  419. package/dist/routes/processes.d.ts.map +0 -1
  420. package/dist/routes/properties.d.ts.map +0 -1
  421. package/dist/routes/pty.d.ts.map +0 -1
  422. package/dist/routes/runtime-binding.d.ts.map +0 -1
  423. package/dist/routes/sessions.d.ts.map +0 -1
  424. package/dist/routes/skills.d.ts.map +0 -1
  425. package/dist/routes/sub-agents.d.ts.map +0 -1
  426. package/dist/routes/system-metrics.d.ts.map +0 -1
  427. package/dist/routes/task-context.d.ts.map +0 -1
  428. package/dist/routes/terminal.d.ts.map +0 -1
  429. package/dist/routes/yml.d.ts.map +0 -1
  430. package/dist/services/acode-package.d.ts.map +0 -1
  431. package/dist/services/agent-event-store.d.ts.map +0 -1
  432. package/dist/services/agent-process-manager.d.ts.map +0 -1
  433. package/dist/services/ai-source-apply.d.ts.map +0 -1
  434. package/dist/services/auto-register.d.ts.map +0 -1
  435. package/dist/services/aws-client-agent-mcp.d.ts.map +0 -1
  436. package/dist/services/bridge-package-publish-hooks.d.ts.map +0 -1
  437. package/dist/services/cc-switch-sdk.d.ts.map +0 -1
  438. package/dist/services/cli-commands.d.ts.map +0 -1
  439. package/dist/services/event-delta-pusher.d.ts.map +0 -1
  440. package/dist/services/fileSnapshotStore.d.ts.map +0 -1
  441. package/dist/services/instance-ai-config-service.d.ts.map +0 -1
  442. package/dist/services/instance-init-service.d.ts.map +0 -1
  443. package/dist/services/instance-service.d.ts.map +0 -1
  444. package/dist/services/instance-state.d.ts.map +0 -1
  445. package/dist/services/legacy-path-migration.d.ts.map +0 -1
  446. package/dist/services/lifecycle-state.d.ts.map +0 -1
  447. package/dist/services/mcp-launch-binding-queue.d.ts.map +0 -1
  448. package/dist/services/orphan-monitor.d.ts.map +0 -1
  449. package/dist/services/panel-auth.d.ts.map +0 -1
  450. package/dist/services/process-detector.d.ts.map +0 -1
  451. package/dist/services/process-registry.d.ts.map +0 -1
  452. package/dist/services/ring-buffer.d.ts.map +0 -1
  453. package/dist/services/runtime-binding.d.ts.map +0 -1
  454. package/dist/services/runtime-lifecycle-policy.d.ts.map +0 -1
  455. package/dist/services/session-output.d.ts.map +0 -1
  456. package/dist/services/startup-config-wizard.d.ts.map +0 -1
  457. package/dist/services/sub-agent-manager-instance.d.ts.map +0 -1
  458. package/dist/services/system-metrics.d.ts.map +0 -1
  459. package/dist/services/terminal-persistence.d.ts.map +0 -1
  460. package/dist/services/tool-installer.d.ts.map +0 -1
  461. package/dist/services/tunnel-client.d.ts.map +0 -1
  462. package/dist/services/windows-service-manager.d.ts.map +0 -1
  463. package/dist/services/workspace-files.d.ts.map +0 -1
  464. package/dist/services/workspace-watch.d.ts.map +0 -1
  465. package/dist/services/workspaceTaskChangeWatcher.d.ts.map +0 -1
  466. package/dist/types.d.ts.map +0 -1
  467. package/dist/utils/file-utils.d.ts.map +0 -1
  468. package/dist/utils/instance-ai-config-debug-logger.d.ts.map +0 -1
  469. package/dist/utils/logger.d.ts.map +0 -1
  470. package/dist/utils/mcp-utils.d.ts.map +0 -1
  471. package/dist/utils/path-utils.d.ts.map +0 -1
  472. package/dist/utils/sdk-package-loader.d.ts.map +0 -1
  473. package/dist/utils/validation.d.ts.map +0 -1
  474. package/dist/utils/yaml-utils.d.ts.map +0 -1
  475. package/node_modules/@cc-switch/sdk/dist/constants.d.ts.map +0 -1
  476. package/node_modules/@cc-switch/sdk/dist/errors.d.ts.map +0 -1
  477. package/node_modules/@cc-switch/sdk/dist/index.d.ts.map +0 -1
  478. package/node_modules/@cc-switch/sdk/dist/schemas.d.ts.map +0 -1
  479. package/node_modules/@cc-switch/sdk/dist/sdk.d.ts.map +0 -1
  480. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts.map +0 -1
  481. package/node_modules/@cc-switch/sdk/dist/services/mcp-model.d.ts.map +0 -1
  482. package/node_modules/@cc-switch/sdk/dist/services/mcp-service.d.ts.map +0 -1
  483. package/node_modules/@cc-switch/sdk/dist/services/skill-model.d.ts.map +0 -1
  484. package/node_modules/@cc-switch/sdk/dist/services/skill-service.d.ts.map +0 -1
  485. package/node_modules/@cc-switch/sdk/dist/state.d.ts.map +0 -1
  486. package/node_modules/@cc-switch/sdk/dist/types.d.ts.map +0 -1
  487. package/node_modules/@cc-switch/sdk/dist/utils/fs.d.ts.map +0 -1
  488. package/node_modules/@cc-switch/sdk/dist/utils/id.d.ts.map +0 -1
  489. package/package/acode/dist/ask-user-question-tool.d.ts.map +0 -1
  490. package/package/acode/dist/built-in-file-tools.d.ts.map +0 -1
  491. package/package/acode/dist/built-in-symbols-tool.d.ts.map +0 -1
  492. package/package/acode/dist/builtins/commandArtifacts.d.ts.map +0 -1
  493. package/package/acode/dist/builtins/commandOutputEvents.d.ts.map +0 -1
  494. package/package/acode/dist/builtins/shellCommandPreparation.d.ts.map +0 -1
  495. package/package/acode/dist/builtins/shellCommandResult.d.ts.map +0 -1
  496. package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +0 -1
  497. package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +0 -1
  498. package/package/acode/dist/cli.d.ts.map +0 -1
  499. package/package/acode/dist/commands/commandLoader.d.ts.map +0 -1
  500. package/package/acode/dist/commands/commandParser.d.ts.map +0 -1
  501. package/package/acode/dist/commands/commandRegistry.d.ts.map +0 -1
  502. package/package/acode/dist/compaction.d.ts.map +0 -1
  503. package/package/acode/dist/config.d.ts.map +0 -1
  504. package/package/acode/dist/fs-utils.d.ts.map +0 -1
  505. package/package/acode/dist/hooks/hookBus.d.ts.map +0 -1
  506. package/package/acode/dist/index.d.ts.map +0 -1
  507. package/package/acode/dist/mcpToolNames.d.ts.map +0 -1
  508. package/package/acode/dist/parsers/ast-cache.d.ts.map +0 -1
  509. package/package/acode/dist/parsers/language-detector.d.ts.map +0 -1
  510. package/package/acode/dist/parsers/language-queries.d.ts.map +0 -1
  511. package/package/acode/dist/parsers/symbol-extractor.d.ts.map +0 -1
  512. package/package/acode/dist/parsers/tree-sitter-loader.d.ts.map +0 -1
  513. package/package/acode/dist/parsers/types.d.ts.map +0 -1
  514. package/package/acode/dist/runtime.d.ts.map +0 -1
  515. package/package/acode/dist/skill-tool.d.ts.map +0 -1
  516. package/package/acode/dist/sub-agent-manager.d.ts.map +0 -1
  517. package/package/acode/dist/sub-agent-store.d.ts.map +0 -1
  518. package/package/acode/dist/sub-agent-tools.d.ts.map +0 -1
  519. package/package/acode/dist/sub-agent-types.d.ts.map +0 -1
  520. package/package/acode/dist/todo-store.d.ts.map +0 -1
  521. package/package/acode/dist/todo-tool.d.ts.map +0 -1
  522. package/package/acode/dist/types.d.ts.map +0 -1
  523. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +0 -1
  524. package/package/aws-client-agent-mcp/dist/agent-client.js.map +0 -1
  525. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts.map +0 -1
  526. package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +0 -1
  527. package/package/aws-client-agent-mcp/dist/config.d.ts.map +0 -1
  528. package/package/aws-client-agent-mcp/dist/config.js.map +0 -1
  529. package/package/aws-client-agent-mcp/dist/config.test.d.ts.map +0 -1
  530. package/package/aws-client-agent-mcp/dist/config.test.js.map +0 -1
  531. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +0 -1
  532. package/package/aws-client-agent-mcp/dist/constants.js.map +0 -1
  533. package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +0 -1
  534. package/package/aws-client-agent-mcp/dist/context-manager.js.map +0 -1
  535. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +0 -1
  536. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +0 -1
  537. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +0 -1
  538. package/package/aws-client-agent-mcp/dist/http-client.js.map +0 -1
  539. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts.map +0 -1
  540. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +0 -1
  541. package/package/aws-client-agent-mcp/dist/index.d.ts.map +0 -1
  542. package/package/aws-client-agent-mcp/dist/index.js.map +0 -1
  543. package/package/aws-client-agent-mcp/dist/logger.d.ts.map +0 -1
  544. package/package/aws-client-agent-mcp/dist/logger.js.map +0 -1
  545. package/package/aws-client-agent-mcp/dist/logger.test.d.ts.map +0 -1
  546. package/package/aws-client-agent-mcp/dist/logger.test.js.map +0 -1
  547. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +0 -1
  548. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +0 -1
  549. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts.map +0 -1
  550. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +0 -1
  551. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +0 -1
  552. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +0 -1
  553. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts.map +0 -1
  554. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js.map +0 -1
  555. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +0 -1
  556. package/package/aws-client-agent-mcp/dist/memory-store.js.map +0 -1
  557. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts.map +0 -1
  558. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +0 -1
  559. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +0 -1
  560. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +0 -1
  561. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +0 -1
  562. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +0 -1
  563. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts.map +0 -1
  564. package/package/aws-client-agent-mcp/dist/message-buffer.test.js.map +0 -1
  565. package/package/aws-client-agent-mcp/dist/messageContent.d.ts.map +0 -1
  566. package/package/aws-client-agent-mcp/dist/messageContent.js.map +0 -1
  567. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +0 -1
  568. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +0 -1
  569. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts.map +0 -1
  570. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +0 -1
  571. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +0 -1
  572. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +0 -1
  573. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +0 -1
  574. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +0 -1
  575. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts.map +0 -1
  576. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +0 -1
  577. package/package/aws-client-agent-mcp/dist/types.d.ts.map +0 -1
  578. package/package/aws-client-agent-mcp/dist/types.js.map +0 -1
  579. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +0 -1
  580. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +0 -1
  581. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts.map +0 -1
  582. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +0 -1
  583. package/package/cc-switch-sdk/dist/constants.d.ts.map +0 -1
  584. package/package/cc-switch-sdk/dist/errors.d.ts.map +0 -1
  585. package/package/cc-switch-sdk/dist/index.d.ts.map +0 -1
  586. package/package/cc-switch-sdk/dist/schemas.d.ts.map +0 -1
  587. package/package/cc-switch-sdk/dist/sdk.d.ts.map +0 -1
  588. package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts.map +0 -1
  589. package/package/cc-switch-sdk/dist/services/mcp-model.d.ts.map +0 -1
  590. package/package/cc-switch-sdk/dist/services/mcp-service.d.ts.map +0 -1
  591. package/package/cc-switch-sdk/dist/services/skill-model.d.ts.map +0 -1
  592. package/package/cc-switch-sdk/dist/services/skill-service.d.ts.map +0 -1
  593. package/package/cc-switch-sdk/dist/state.d.ts.map +0 -1
  594. package/package/cc-switch-sdk/dist/types.d.ts.map +0 -1
  595. package/package/cc-switch-sdk/dist/utils/fs.d.ts.map +0 -1
  596. package/package/cc-switch-sdk/dist/utils/id.d.ts.map +0 -1
@@ -1,2882 +1,54 @@
1
- /**
2
- * 终端 API 路由
3
- *
4
- * 提供 SDK Agent 会话的启动、输入、调整大小和停止功能
5
- * 终端 UI 仅作为 SDK Agent 的消息输入/状态输出载体
6
- */
7
- import { spawn } from "node:child_process";
8
- import { promises as fs } from "node:fs";
9
- import os from "node:os";
10
- import path from "node:path";
11
- import { TextDecoder } from "node:util";
12
- import { Router } from "express";
13
- import { v4 as uuidv4 } from "uuid";
14
- import axios from "axios";
15
- import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath, redactTransientClipboardPastePaths, registerSdkProviders, resolveSdkProviderIdByCommand, } from "../adapter/index.js";
16
- import { validateToken } from "../middleware/auth.js";
17
- import { getAgentProcessManager } from "../services/agent-process-manager.js";
18
- import { getAiConfigService } from "../services/cc-switch-sdk.js";
19
- import { prepareAcodeInstanceConfig, redactForLog } from "../services/instance-ai-config-service.js";
20
- import { enqueueMcpLaunchBinding } from "../services/mcp-launch-binding-queue.js";
21
- import { resolveSchedulerBaseUrlFromToken } from "../services/auto-register.js";
22
- import { findClaudeCodeProcesses, isProcessRunning, } from "../services/process-detector.js";
23
- import { bridgeEventBuffer, sendOutput, sendQuestionRequest, sendStatus, } from "../services/session-output.js";
24
- import { getAllPushStats, startHeartbeatScheduler } from "../services/event-delta-pusher.js";
25
- import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, updatePersistedSessionRuntimeState, upsertPersistedSession, } from "../services/terminal-persistence.js";
26
- import { getInstanceAiConfigDebugLogger } from "../utils/instance-ai-config-debug-logger.js";
27
- import { logger as log } from "../utils/logger.js";
28
- export const terminalRouter = Router();
29
- /**
30
- * 提取入站请求的 runtime token(X-Runtime-Token 或 Authorization: Bearer)。
31
- * /runtime/start body 未携带 schedulerBaseUrl 时反查来源 server 使用。
32
- */
33
- function extractIncomingRuntimeToken(req) {
34
- const headerToken = req.header("X-Runtime-Token");
35
- if (headerToken && headerToken.trim()) {
36
- return headerToken.trim();
37
- }
38
- const authorization = req.header("Authorization") || "";
39
- const bearerMatch = authorization.match(/^Bearer\s+(.+)$/i);
40
- const bearer = bearerMatch?.[1]?.trim();
41
- return bearer || undefined;
42
- }
43
- // 导出 SDK 会话存储,供其他模块使用(如 sessions.ts)
44
- export const sdkSessions = new Map();
45
- // ── 活跃状态定期刷新,防止长时间 LLM 调用期间 WebSocket 心跳超时导致 LOST ──
46
- /**
47
- * 状态刷新间隔(毫秒)。
48
- * 服务端 WebSocket 空闲超时默认 60 秒(maxIdleTimeMs),取 45 秒作为刷新周期,
49
- * 留出足够的余量防止因网络抖动或事件循环延迟导致的超时。
50
- */
51
- const STATUS_REFRESH_INTERVAL_MS = 45_000;
52
- /** 每个 session 的定期刷新定时器,仅在 thinking/tool_using/compacting 时运行 */
53
- const statusRefreshTimers = new Map();
54
- /**
55
- * 启动 session 定期状态刷新。
56
- * 每隔 STATUS_REFRESH_INTERVAL_MS 向服务端发送一次当前运行状态,
57
- * 刷新服务端 WebSocket 会话的 lastActiveTime,避免长时间无消息导致连接断开 → LOST。
58
- */
59
- function startStatusRefresh(sessionId) {
60
- if (statusRefreshTimers.has(sessionId))
61
- return;
62
- const timer = setInterval(() => {
63
- const entry = sdkSessions.get(sessionId);
64
- if (!entry) {
65
- stopStatusRefresh(sessionId);
66
- return;
67
- }
68
- const status = entry.currentRuntimeStatus;
69
- // 仅活跃状态需要刷新:thinking / tool_using / compacting
70
- if (status !== "thinking" && status !== "tool_using" && status !== "compacting") {
71
- stopStatusRefresh(sessionId);
72
- return;
73
- }
74
- // 发送轻量级状态刷新(不带 actionInfo),保持 WebSocket 会话活跃
75
- void sendStatus(entry.agentId, sessionId, status, undefined, // 不传 actionInfo,避免创建额外时间线条目
76
- undefined, {
77
- runtimeAccessToken: entry.runtimeAccessToken,
78
- schedulerBaseUrl: entry.schedulerBaseUrl,
79
- }).catch(() => {
80
- // sendStatus 失败静默处理,不影响主流程
81
- });
82
- }, STATUS_REFRESH_INTERVAL_MS);
83
- statusRefreshTimers.set(sessionId, timer);
84
- }
85
- /**
86
- * 停止 session 定期状态刷新。
87
- */
88
- function stopStatusRefresh(sessionId) {
89
- const timer = statusRefreshTimers.get(sessionId);
90
- if (timer) {
91
- clearInterval(timer);
92
- statusRefreshTimers.delete(sessionId);
93
- }
94
- }
95
- const TOOL_RESULT_PREVIEW_MAX_LENGTH = 4000;
96
- const AI_REQUEST_BODY_MARKER = "\n\n请求体:\n";
97
- /**
98
- * 把请求体中的 instanceAiConfig 归一化为 AdapterInstanceAiConfig。
99
- * 任意字段类型错误或非字符串都返回 undefined,调用方应继续走 env/file fallback。
100
- */
101
- function sanitizeInstanceAiConfig(value) {
102
- if (value == null)
103
- return undefined;
104
- if (typeof value !== "object" || Array.isArray(value))
105
- return undefined;
106
- const raw = value;
107
- const out = {};
108
- if (typeof raw.model === "string" && raw.model.trim()) {
109
- out.model = raw.model.trim();
110
- }
111
- if (typeof raw.baseURL === "string" && raw.baseURL.trim()) {
112
- out.baseURL = raw.baseURL.trim();
113
- }
114
- if (typeof raw.apiKey === "string" && raw.apiKey.trim()) {
115
- out.apiKey = raw.apiKey.trim();
116
- }
117
- if (typeof raw.effort === "string" && raw.effort.trim()) {
118
- out.effort = raw.effort.trim();
119
- }
120
- return out;
121
- }
122
- const AI_REQUEST_HEADERS_MARKER = "\n\n请求头:\n";
123
- const startingAgents = new Set();
124
- // ============ 启动记忆加载 ============
125
- /**
126
- * 从工作区的 .agentswork/memory/ 目录加载 recently_memory 和 agent_memory,
127
- * 格式化为可注入 System 3 的文本。
128
- *
129
- * 主流程:遍历目录下所有 domain JSON 文件 -> 提取 recently_memory/agent_memory 记录 ->
130
- * 按类型分组排序 -> 返回格式化文本;目录不存在或解析失败时返回 undefined。
131
- */
132
- async function loadAgentStartupMemories(workspacePath, options) {
133
- // 收集记录
134
- const taskList = [];
135
- const recentMemories = [];
136
- // 1) 尝试从本地 .agentswork/memory/ 加载
137
- const memoryDir = path.join(workspacePath, ".agentswork", "memory");
138
- try {
139
- const entries = await fs.readdir(memoryDir);
140
- const jsonFiles = entries.filter(e => e.endsWith(".json"));
141
- // 并行读取所有 JSON 文件,消除 N+1 串行 I/O
142
- const parsedResults = await Promise.all(jsonFiles.map(async (entry) => {
143
- try {
144
- const raw = await fs.readFile(path.join(memoryDir, entry), "utf-8");
145
- return JSON.parse(raw);
146
- }
147
- catch {
148
- log.warn("[loadAgentStartupMemories] Failed to parse memory file: %s", path.join(memoryDir, entry));
149
- return null;
150
- }
151
- }));
152
- // 汇总解析结果
153
- for (const parsed of parsedResults) {
154
- if (!parsed?.by_uri)
155
- continue;
156
- for (const record of Object.values(parsed.by_uri)) {
157
- const type = record.memory_type || "memory";
158
- const content = (record.content || "").trim();
159
- if (!content)
160
- continue;
161
- if (type === "recently_memory") {
162
- taskList.push({ title: record.title, content, uri: record.uri || "" });
163
- }
164
- else if (type === "agent_memory") {
165
- recentMemories.push({ title: record.title, content, uri: record.uri || "" });
166
- }
167
- }
168
- }
169
- }
170
- catch {
171
- // 目录不存在(首次启动正常)或权限拒绝等错误,记录 debug 日志便于排障
172
- log.debug("[loadAgentStartupMemories] 无法读取记忆目录: %s", memoryDir);
173
- }
174
- // 2) 本地未找到且 server 地址可用时,尝试从 server MCP 加载
175
- if (taskList.length === 0 && recentMemories.length === 0
176
- && options?.schedulerBaseUrl && options?.runtimeAccessToken && options?.agentId) {
177
- const mcpUrl = `${options.schedulerBaseUrl.replace(/\/+$/, "")}/mcp/call`;
178
- const headers = {
179
- "Content-Type": "application/json",
180
- "X-Runtime-Token": options.runtimeAccessToken,
181
- Authorization: `Bearer ${options.runtimeAccessToken}`,
182
- };
183
- // 并行请求 recently_memory 和 agent_memory
184
- const typesToFetch = ["recently_memory", "agent_memory"];
185
- const results = await Promise.allSettled(typesToFetch.map((memoryType) => fetch(mcpUrl, {
186
- method: "POST",
187
- headers,
188
- body: JSON.stringify({
189
- tool: "list_memories",
190
- arguments: { agentId: options.agentId, memory_type: memoryType },
191
- }),
192
- }).then((r) => {
193
- if (!r.ok)
194
- throw new Error(`Server returned ${r.status}`);
195
- return r.json();
196
- })));
197
- for (let i = 0; i < results.length; i++) {
198
- const settled = results[i];
199
- if (settled.status !== "fulfilled")
200
- continue;
201
- const records = settled.value;
202
- if (!Array.isArray(records))
203
- continue;
204
- const target = typesToFetch[i] === "recently_memory" ? taskList : recentMemories;
205
- for (const r of records) {
206
- const content = (r.content || "").trim();
207
- if (!content)
208
- continue;
209
- target.push({ title: r.title, content, uri: r.uri || "" });
210
- }
211
- }
212
- if (taskList.length > 0 || recentMemories.length > 0) {
213
- log.info(`[loadAgentStartupMemories] Loaded ${taskList.length} task(s) and ${recentMemories.length} memory(s) from server for ${options.agentId}`);
214
- }
215
- }
216
- // 3) 格式化输出
217
- const MAX_PER_TYPE = 10;
218
- taskList.sort((a, b) => b.uri.localeCompare(a.uri));
219
- recentMemories.sort((a, b) => b.uri.localeCompare(a.uri));
220
- const sections = [];
221
- if (taskList.length > 0) {
222
- const items = taskList.slice(0, MAX_PER_TYPE);
223
- const lines = items.map((t, i) => {
224
- const titleLine = t.title ? `【${t.title}】` : `条目 ${i + 1}`;
225
- return `${titleLine}\n${t.content}`;
226
- });
227
- sections.push(`【短期记忆】\n${lines.join("\n\n")}`);
228
- if (taskList.length > MAX_PER_TYPE) {
229
- sections.push(`... 及另外 ${taskList.length - MAX_PER_TYPE} 条短期记忆`);
230
- }
231
- }
232
- if (recentMemories.length > 0) {
233
- const items = recentMemories.slice(0, MAX_PER_TYPE);
234
- const lines = items.map((t) => {
235
- const titleLine = t.title ? `【${t.title}】` : "";
236
- return titleLine ? `${titleLine}\n${t.content}` : t.content;
237
- });
238
- sections.push(`【agent认知】\n${lines.join("\n\n")}`);
239
- if (recentMemories.length > MAX_PER_TYPE) {
240
- sections.push(`... 及另外 ${recentMemories.length - MAX_PER_TYPE} 条 agent 认知`);
241
- }
242
- }
243
- if (sections.length === 0)
244
- return undefined;
245
- return sections.join("\n\n");
246
- }
247
- export function resolveRuntimeContextResetSupport(providerId, hasResetContext) {
248
- if (providerId !== "acode") {
249
- return {
250
- supported: false,
251
- statusCode: 400,
252
- error: "context reset without restart is only supported by ACode",
253
- };
254
- }
255
- if (!hasResetContext) {
256
- return {
257
- supported: false,
258
- statusCode: 500,
259
- error: "ACode adapter does not expose resetContext",
260
- };
261
- }
262
- return { supported: true };
263
- }
264
- /**
265
- * Decide whether SDK startup should wait for an external process PID.
266
- * Main flow: in-process providers return immediately; external CLI providers keep PID probing for orphan cleanup.
267
- */
268
- export function shouldProbeSdkProcessPid(providerId) {
269
- return providerId !== "acode";
270
- }
271
- /**
272
- * 主流程:解析 runtime stop 请求的状态通知选项。
273
- * 内部上下文重置可抑制 stopped 广播;普通手动停止默认继续通知调度端。
274
- */
275
- export function parseRuntimeStopOptions(body) {
276
- const suppressStatus = typeof body === "object" &&
277
- body !== null &&
278
- "suppressStatus" in body &&
279
- body.suppressStatus === true;
280
- const reason = typeof body === "object" &&
281
- body !== null &&
282
- "reason" in body &&
283
- typeof body.reason === "string" &&
284
- body.reason.trim()
285
- ? body.reason.trim()
286
- : undefined;
287
- return {
288
- statusNotified: !suppressStatus,
289
- reason,
290
- };
291
- }
292
- export function resolveStatusChangeUsage(actionInfo) {
293
- const usage = actionInfo?.usage;
294
- if (!usage) {
295
- return undefined;
296
- }
297
- const inputTokens = Number.isFinite(usage.inputTokens)
298
- ? usage.inputTokens
299
- : 0;
300
- const outputTokens = Number.isFinite(usage.outputTokens)
301
- ? usage.outputTokens
302
- : 0;
303
- // ★ 即使 input/output token 都为 0(如 session_started 初始状态),只要有任一压缩配置字段也应传出,
304
- // 否则前端拿不到 compactionThreshold,压缩点竖线无法显示。
305
- if (inputTokens <= 0 &&
306
- outputTokens <= 0 &&
307
- !usage.contextLimit &&
308
- !usage.maxOutput &&
309
- !usage.inputLimit &&
310
- !usage.compactionThreshold) {
311
- return undefined;
312
- }
313
- const result = {
314
- inputTokens,
315
- outputTokens,
316
- ...(usage.contextLimit ? { contextLimit: usage.contextLimit } : {}),
317
- ...(usage.maxOutput ? { maxOutput: usage.maxOutput } : {}),
318
- ...(usage.inputLimit ? { inputLimit: usage.inputLimit } : {}),
319
- ...(usage.compactionThreshold ? { compactionThreshold: usage.compactionThreshold } : {}),
320
- };
321
- return result;
322
- }
323
- export function resolveStatusChangeRuntimeState(status, actionInfo) {
324
- const normalizedStatus = String(status || "")
325
- .trim()
326
- .toLowerCase();
327
- const normalizedActionType = String(actionInfo?.actionType || "")
328
- .trim()
329
- .toLowerCase();
330
- if (normalizedStatus === "tool_using" && normalizedActionType === "idle") {
331
- return actionInfo
332
- ? { status: "waiting_input", actionInfo }
333
- : { status: "waiting_input" };
334
- }
335
- return actionInfo ? { status, actionInfo } : { status };
336
- }
337
- const terminalCommandStates = new Map();
338
- const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = "utf-8";
339
- const DEFAULT_TERMINAL_OUTPUT_ENCODING = "utf-8";
340
- const TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT = 300;
341
- const TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS = 60_000;
342
- function padDatePart(value) {
343
- return String(value).padStart(2, "0");
344
- }
345
- export function buildAcodeCommandOutputPath(agentId, now = new Date()) {
346
- const year = now.getFullYear();
347
- const month = padDatePart(now.getMonth() + 1);
348
- const day = padDatePart(now.getDate());
349
- const hour = padDatePart(now.getHours());
350
- const minute = padDatePart(now.getMinutes());
351
- const timestamp = `${year}${month}${day}-${hour}${minute}${padDatePart(now.getSeconds())}-${String(now.getMilliseconds()).padStart(3, "0")}`;
352
- return path.join(os.homedir(), ".acode", "cmd-dist", agentId, `${year}-${month}-${day}`, hour, minute, `${timestamp}-cmd.out`);
353
- }
354
- export function countTerminalOutputLines(output) {
355
- if (!output) {
356
- return 0;
357
- }
358
- const normalized = output.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
359
- const withoutTrailingLineBreak = normalized.endsWith("\n")
360
- ? normalized.slice(0, -1)
361
- : normalized;
362
- return withoutTrailingLineBreak
363
- ? withoutTrailingLineBreak.split("\n").length
364
- : 1;
365
- }
366
- export function formatCommandOutputSpilloverNotice(outputPath, lineLimit = TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
367
- return `\r\n[ACode] 命令输出超过 ${lineLimit} 行,完整输出已写入: ${outputPath}\r\n[ACode] Agent 可自行读取该文件,或在该文件中搜索需要的内容。\r\n`;
368
- }
369
- export function formatCommandInactivityTimeoutNotice(timeoutMs = TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS) {
370
- return `\r\n[ACode] 命令超过 ${Math.round(timeoutMs / 1000)} 秒没有新的输出,已停止执行并保留此前输出。\r\n`;
371
- }
372
- async function persistTerminalCommandOutput(state) {
373
- const outputPath = state.outputPath ||
374
- buildAcodeCommandOutputPath(state.agentId, state.startedAt);
375
- state.outputPath = outputPath;
376
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
377
- await fs.writeFile(outputPath, state.chunks.join(""), "utf-8");
378
- return outputPath;
379
- }
380
- /**
381
- * 标准化终端输入:将前端回车符还原为 shell 可执行命令文本。
382
- */
383
- export function normalizeTerminalCommandInput(value) {
384
- return String(value ?? "")
385
- .replace(/\r/g, "\n")
386
- .trim();
387
- }
388
- /**
389
- * 解析终端命令输出解码编码。
390
- * 主流程:优先使用环境变量覆盖 -> Windows 默认 GB18030 -> 其他平台默认 UTF-8。
391
- */
392
- export function resolveTerminalOutputEncoding(platform = process.platform, env = process.env) {
393
- const configuredEncoding = env.AWS_TERMINAL_OUTPUT_ENCODING?.trim();
394
- if (configuredEncoding) {
395
- return configuredEncoding;
396
- }
397
- return platform === "win32"
398
- ? DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING
399
- : DEFAULT_TERMINAL_OUTPUT_ENCODING;
400
- }
401
- /**
402
- * 创建终端输出流式解码器,避免多字节中文被 chunk 边界截断。
403
- *
404
- * 主流程:环境变量显式指定非 utf-8 时固定编码 → 否则自动检测
405
- * (先 UTF-8,遇替换字符回退 GB18030,覆盖不同程序的不同编码)。
406
- */
407
- export function createTerminalOutputDecoder(encoding = resolveTerminalOutputEncoding()) {
408
- // 环境变量显式指定了非 utf-8 编码 → 固定编码,不做自动检测
409
- if (encoding !== "utf-8") {
410
- try {
411
- return new TextDecoder(encoding, { fatal: false });
412
- }
413
- catch {
414
- return new TextDecoder(DEFAULT_TERMINAL_OUTPUT_ENCODING, { fatal: false });
415
- }
416
- }
417
- // 默认 utf-8 → 启用自动检测
418
- return createAutoDetectTerminalDecoder();
419
- }
420
- function createAutoDetectTerminalDecoder() {
421
- let utf8Decoder = null;
422
- let gbDecoder = null;
423
- let detectedEncoding = null;
424
- const rawChunks = [];
425
- function getDecoder(enc) {
426
- try {
427
- return new TextDecoder(enc, { fatal: false });
428
- }
429
- catch {
430
- return new TextDecoder("utf-8", { fatal: false });
431
- }
432
- }
433
- return {
434
- decode(chunk, options) {
435
- if (chunk !== undefined && chunk !== null) {
436
- rawChunks.push(chunk instanceof ArrayBuffer
437
- ? Buffer.from(chunk)
438
- : Buffer.isBuffer(chunk)
439
- ? chunk
440
- : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength));
441
- }
442
- if (detectedEncoding === "gb18030") {
443
- if (!gbDecoder)
444
- gbDecoder = getDecoder("gb18030");
445
- return gbDecoder.decode(chunk, options);
446
- }
447
- if (!utf8Decoder)
448
- utf8Decoder = getDecoder("utf-8");
449
- const result = utf8Decoder.decode(chunk, options);
450
- if (detectedEncoding === null && result.includes("\uFFFD")) {
451
- detectedEncoding = "gb18030";
452
- gbDecoder = getDecoder("gb18030");
453
- const fullBuffer = Buffer.concat(rawChunks);
454
- return gbDecoder.decode(fullBuffer);
455
- }
456
- if (detectedEncoding === null && !options?.stream) {
457
- detectedEncoding = "utf-8";
458
- }
459
- return result;
460
- },
461
- };
462
- }
463
- /**
464
- * 解码终端输出 chunk;end=true 时刷新解码器内部剩余字节。
465
- */
466
- export function decodeTerminalOutputChunk(decoder, chunk, end = false) {
467
- if (end) {
468
- return decoder.decode();
469
- }
470
- if (!chunk || chunk.length === 0) {
471
- return "";
472
- }
473
- return decoder.decode(chunk, { stream: true });
474
- }
475
- /**
476
- * 解析 cd 命令目标;返回 undefined 表示不是目录切换命令,null 表示仅查看当前目录。
477
- */
478
- export function parseTerminalDirectoryChangeTarget(command) {
479
- const match = command.trim().match(/^cd(?:\s+(.*))?$/i);
480
- if (!match) {
481
- return undefined;
482
- }
483
- const rawTarget = match[1]?.trim();
484
- if (!rawTarget) {
485
- return null;
486
- }
487
- const withoutDriveSwitch = rawTarget.replace(/^\/d\s+/i, "").trim();
488
- if ((withoutDriveSwitch.startsWith('"') && withoutDriveSwitch.endsWith('"')) ||
489
- (withoutDriveSwitch.startsWith("'") && withoutDriveSwitch.endsWith("'"))) {
490
- return withoutDriveSwitch.slice(1, -1);
491
- }
492
- return withoutDriveSwitch;
493
- }
494
- function getTerminalCommandState(entry) {
495
- const existing = terminalCommandStates.get(entry.sessionId);
496
- if (existing) {
497
- return existing;
498
- }
499
- const state = {
500
- currentDirectory: entry.workspacePath,
501
- runningProcess: null,
502
- };
503
- terminalCommandStates.set(entry.sessionId, state);
504
- return state;
505
- }
506
- function findActiveSdkSessionByAgentId(agentId) {
507
- for (const entry of sdkSessions.values()) {
508
- if (entry.agentId === agentId) {
509
- return entry;
510
- }
511
- }
512
- return undefined;
513
- }
514
- async function buildExistingSessionResponse(entry) {
515
- const adapter = adapterRegistry.get(entry.providerId || "claude-code");
516
- const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus, entry.currentRuntimeActionType);
517
- return {
518
- sessionId: entry.sessionId,
519
- status,
520
- runtimeStatus: status,
521
- runtimeActionType: entry.currentRuntimeActionType,
522
- runtimeActionLabel: entry.currentRuntimeActionLabel,
523
- runtimeActionDetail: entry.currentRuntimeActionDetail,
524
- agentId: entry.agentId,
525
- workspacePath: entry.workspacePath,
526
- command: entry.config.command,
527
- mode: "sdk",
528
- providerSessionId: adapter.getProviderSessionId(entry.sessionId),
529
- reused: true,
530
- };
531
- }
532
- /**
533
- * 解析 SDK 会话对外展示状态。
534
- * 主流程:优先使用 adapter 当前细粒度状态,其次使用路由内存记录的最近状态,最后才兼容旧会话回退 running。
535
- */
536
- export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus, currentRuntimeActionType) {
537
- const normalizedAdapterStatus = String(adapterStatus || "").trim();
538
- if (normalizedAdapterStatus) {
539
- const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
540
- const normalizedActionType = String(currentRuntimeActionType || "")
541
- .trim()
542
- .toLowerCase();
543
- if (normalizedAdapterStatus.toLowerCase() === "tool_using" &&
544
- normalizedRuntimeStatus.toLowerCase() === "waiting_input" &&
545
- normalizedActionType === "idle") {
546
- return normalizedRuntimeStatus;
547
- }
548
- return normalizedAdapterStatus;
549
- }
550
- const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
551
- return normalizedRuntimeStatus || "running";
552
- }
553
- /**
554
- * Decide whether a persisted SDK session can be reused for a start request.
555
- * Main flow: require a live PID, then reject in-process providers whose adapter control channel cannot be restored.
556
- */
557
- export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessRunning) {
558
- if (!session.pid) {
559
- return { reusable: false, reason: "missing-pid" };
560
- }
561
- if (!isPidRunning(session.pid)) {
562
- return { reusable: false, reason: "dead-pid" };
563
- }
564
- if (resolveSdkProviderId(session.command) === "acode") {
565
- return { reusable: false, reason: "in-process-provider" };
566
- }
567
- return { reusable: true, reason: "active-pid" };
568
- }
569
- /**
570
- * 根据 agentId 清理 SDK 会话的所有内存状态。
571
- *
572
- * 用于强制停止(force-kill)场景:当进程已被外部/强制手段终止后,
573
- * 此函数清理 bridge 内部的内存残留(sdkSessions、statusRefreshTimers、
574
- * startingAgents、terminalCommandStates),防止下次启动时
575
- * findActiveSdkSessionByAgentId 命中旧条目导致「无法上线」。
576
- *
577
- * @param agentId - 要清理的 Agent ID
578
- * @returns 如果找到并清理了会话则返回 sessionId,否则返回 undefined
579
- */
580
- export function cleanupSdkSessionByAgentId(agentId) {
581
- // 1. 从 sdkSessions 查找该 agentId 的会话
582
- const entry = findActiveSdkSessionByAgentId(agentId);
583
- if (!entry) {
584
- // 可能不在 sdkSessions 中但还在 other 状态缓存里,继续清理
585
- log.info(`[cleanupSdkSessionByAgentId] No active SDK session found for agentId=${agentId}, cleaning residual state anyway`);
586
- }
587
- else {
588
- const sessionId = entry.sessionId;
589
- log.info(`[cleanupSdkSessionByAgentId] Cleaning up SDK session for agentId=${agentId}, sessionId=${sessionId}`);
590
- // 2. 停止状态刷新定时器
591
- stopStatusRefresh(sessionId);
592
- // 3. 从 sdkSessions 中移除
593
- sdkSessions.delete(sessionId);
594
- // 4. 清理终端命令进程
595
- stopTerminalCommandProcess(sessionId);
596
- // 5. 清理持久化会话
597
- removePersistedSession(sessionId).catch((err) => {
598
- log.warn(`[cleanupSdkSessionByAgentId] Failed to remove persisted session for agentId=${agentId}: ${err}`);
599
- });
600
- return sessionId;
601
- }
602
- // 6. 确保 startingAgents 锁也被释放(防止 Phase 1 锁定后进程被杀导致锁残留)
603
- if (startingAgents.has(agentId)) {
604
- startingAgents.delete(agentId);
605
- log.info(`[cleanupSdkSessionByAgentId] Released startingAgents lock for agentId=${agentId}`);
606
- }
607
- return undefined;
608
- }
609
- function stopTerminalCommandProcess(sessionId) {
610
- const state = terminalCommandStates.get(sessionId);
611
- if (!state?.runningProcess) {
612
- terminalCommandStates.delete(sessionId);
613
- return;
614
- }
615
- try {
616
- state.runningProcess.kill();
617
- }
618
- catch {
619
- // ignore best-effort cleanup failures; session shutdown must continue
620
- }
621
- finally {
622
- state.runningProcess = null;
623
- terminalCommandStates.delete(sessionId);
624
- }
625
- }
626
- function appendCommandOutput(entry, output) {
627
- if (!output) {
628
- return;
629
- }
630
- entry.seq += 1;
631
- // ★ sendOutput 对非 abort 错误会 rethrow(供 raceSendOutputWithRetry 重试),
632
- // 此处 fire-and-forget 调用必须 .catch 避免 unhandledRejection。
633
- // 命令输出丢失可接受(本地 buffer 已在 finally 记录),不重试。
634
- void sendOutput(entry.agentId, output, entry.sessionId, entry.seq, undefined, {
635
- runtimeAccessToken: entry.runtimeAccessToken,
636
- schedulerBaseUrl: entry.schedulerBaseUrl,
637
- }).catch(() => {
638
- // sendOutput 内部已 logger.warn,此处静默即可
639
- });
640
- }
641
- /**
642
- * 生成显示在终端输出流中的工作目录提示符。
643
- */
644
- export function formatTerminalPrompt(currentDirectory) {
645
- return `\x1b[36m${currentDirectory}>\x1b[0m `;
646
- }
647
- function resolveDirectoryTarget(currentDirectory, target) {
648
- if (target === "~") {
649
- return os.homedir();
650
- }
651
- return path.resolve(currentDirectory, target);
652
- }
653
- /**
654
- * 处理内置目录切换命令,保持终端后续命令的当前目录连续。
655
- */
656
- async function handleDirectoryChangeCommand(entry, state, command) {
657
- const target = parseTerminalDirectoryChangeTarget(command);
658
- if (target === undefined) {
659
- return false;
660
- }
661
- if (target === null) {
662
- appendCommandOutput(entry, `${state.currentDirectory}\r\n`);
663
- return true;
664
- }
665
- const nextDirectory = resolveDirectoryTarget(state.currentDirectory, target);
666
- try {
667
- const stat = await fs.stat(nextDirectory);
668
- if (!stat.isDirectory()) {
669
- appendCommandOutput(entry, `cd: not a directory: ${nextDirectory}\r\n`);
670
- return true;
671
- }
672
- state.currentDirectory = nextDirectory;
673
- appendCommandOutput(entry, `${state.currentDirectory}\r\n`);
674
- return true;
675
- }
676
- catch (error) {
677
- const message = error instanceof Error ? error.message : String(error);
678
- appendCommandOutput(entry, `cd: ${message}\r\n`);
679
- return true;
680
- }
681
- }
682
- /**
683
- * 在当前 Agent 工作目录对应的终端上下文中执行命令。
684
- */
685
- async function executeTerminalCommand(sessionId, commandValue) {
686
- const entry = sdkSessions.get(sessionId);
687
- if (!entry) {
688
- throw new Error("SDK session not found");
689
- }
690
- const command = normalizeTerminalCommandInput(commandValue);
691
- const state = getTerminalCommandState(entry);
692
- if (!command) {
693
- return { started: false, cwd: state.currentDirectory };
694
- }
695
- if (state.runningProcess) {
696
- throw new Error("A terminal command is already running for this session");
697
- }
698
- const displayCommand = redactTransientClipboardPastePaths(command);
699
- appendCommandOutput(entry, `${formatTerminalPrompt(state.currentDirectory)}${displayCommand}\r\n`);
700
- if (isTransientClipboardPasteFilePath(command)) {
701
- appendCommandOutput(entry, "[Terminal Warning] 检测到剪贴板临时 paste 文件路径,已跳过执行。请粘贴文件内容或使用显式读取命令。\r\n");
702
- appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
703
- return { started: false, cwd: state.currentDirectory };
704
- }
705
- const handledDirectoryChange = await handleDirectoryChangeCommand(entry, state, command);
706
- if (handledDirectoryChange) {
707
- return { started: false, cwd: state.currentDirectory };
708
- }
709
- await sendStatus(entry.agentId, sessionId, "tool_using", {
710
- actionType: "bash",
711
- actionLabel: "终端命令",
712
- actionDetail: displayCommand,
713
- }, undefined, {
714
- runtimeAccessToken: entry.runtimeAccessToken,
715
- schedulerBaseUrl: entry.schedulerBaseUrl,
716
- });
717
- const child = spawn(command, {
718
- cwd: state.currentDirectory,
719
- shell: true,
720
- env: buildRuntimeEnv(entry.agentId, entry.workspacePath, process.env, entry.config.envOverrides),
721
- });
722
- state.runningProcess = child;
723
- const stdoutDecoder = createTerminalOutputDecoder();
724
- const stderrDecoder = createTerminalOutputDecoder();
725
- const usesAcodeCommandOutputPolicy = entry.providerId === "acode";
726
- const outputSpilloverState = {
727
- agentId: entry.agentId,
728
- startedAt: new Date(),
729
- chunks: [],
730
- spilled: false,
731
- };
732
- let inactivityTimer;
733
- const clearInactivityTimer = () => {
734
- if (inactivityTimer) {
735
- clearTimeout(inactivityTimer);
736
- inactivityTimer = undefined;
737
- }
738
- };
739
- const resetInactivityTimer = () => {
740
- clearInactivityTimer();
741
- inactivityTimer = setTimeout(() => {
742
- if (state.runningProcess === child) {
743
- appendCommandOutput(entry, formatCommandInactivityTimeoutNotice());
744
- child.kill();
745
- }
746
- }, TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS);
747
- };
748
- const appendProcessOutput = (output, trackActivity = true) => {
749
- if (!output) {
750
- return;
751
- }
752
- if (usesAcodeCommandOutputPolicy) {
753
- outputSpilloverState.chunks.push(output);
754
- }
755
- if (usesAcodeCommandOutputPolicy && trackActivity) {
756
- resetInactivityTimer();
757
- }
758
- if (usesAcodeCommandOutputPolicy && outputSpilloverState.spilled) {
759
- return;
760
- }
761
- if (usesAcodeCommandOutputPolicy &&
762
- countTerminalOutputLines(outputSpilloverState.chunks.join("")) >
763
- TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
764
- outputSpilloverState.spilled = true;
765
- void persistTerminalCommandOutput(outputSpilloverState)
766
- .then((outputPath) => {
767
- appendCommandOutput(entry, formatCommandOutputSpilloverNotice(outputPath));
768
- })
769
- .catch((error) => {
770
- const message = error instanceof Error ? error.message : String(error);
771
- appendCommandOutput(entry, `\r\n[ACode] 命令输出落盘失败: ${message}\r\n`);
772
- });
773
- return;
774
- }
775
- appendCommandOutput(entry, output);
776
- };
777
- if (usesAcodeCommandOutputPolicy) {
778
- resetInactivityTimer();
779
- }
780
- child.stdout.on("data", (chunk) => {
781
- appendProcessOutput(decodeTerminalOutputChunk(stdoutDecoder, chunk));
782
- });
783
- child.stderr.on("data", (chunk) => {
784
- appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, chunk));
785
- });
786
- child.on("error", (error) => {
787
- appendCommandOutput(entry, `\r\n[Terminal Error] ${error.message}\r\n`);
788
- });
789
- child.on("close", (code, signal) => {
790
- clearInactivityTimer();
791
- state.runningProcess = null;
792
- appendProcessOutput(decodeTerminalOutputChunk(stdoutDecoder, undefined, true), false);
793
- appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, undefined, true), false);
794
- if (outputSpilloverState.spilled) {
795
- void persistTerminalCommandOutput(outputSpilloverState).catch((error) => {
796
- const message = error instanceof Error ? error.message : String(error);
797
- appendCommandOutput(entry, `\r\n[ACode] 命令输出最终落盘失败: ${message}\r\n`);
798
- });
799
- }
800
- if (code && code !== 0) {
801
- appendCommandOutput(entry, `\r\n[exit ${code}]\r\n`);
802
- }
803
- else if (signal) {
804
- appendCommandOutput(entry, `\r\n[signal ${signal}]\r\n`);
805
- }
806
- appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
807
- void sendStatus(entry.agentId, sessionId, "waiting_input", undefined, undefined, {
808
- runtimeAccessToken: entry.runtimeAccessToken,
809
- schedulerBaseUrl: entry.schedulerBaseUrl,
810
- });
811
- });
812
- return { started: true, cwd: state.currentDirectory };
813
- }
814
- export function buildRuntimeEnv(agentId, workspacePath, baseEnv, envOverrides) {
815
- const env = {};
816
- for (const [key, value] of Object.entries(baseEnv)) {
817
- if (value !== undefined) {
818
- env[key] = value;
819
- }
820
- }
821
- if (workspacePath) {
822
- env.AWS_WORKSPACE_PATH = String(workspacePath);
823
- }
824
- if (envOverrides) {
825
- for (const [key, value] of Object.entries(envOverrides)) {
826
- const normalizedKey = key.trim();
827
- if (!normalizedKey || value === undefined || value === null) {
828
- continue;
829
- }
830
- const normalizedValue = String(value).trim();
831
- if (!normalizedValue) {
832
- continue;
833
- }
834
- env[normalizedKey] = normalizedValue;
835
- }
836
- }
837
- env.AWS_AGENT_ID = String(agentId);
838
- env.AWS_MCP_CLAIM_LAUNCH_BINDING = "true";
839
- return env;
840
- }
841
- function stripWrappingQuotes(value) {
842
- const trimmed = value.trim();
843
- const first = trimmed[0];
844
- const last = trimmed[trimmed.length - 1];
845
- if ((first === '"' || first === "'") && first === last) {
846
- return trimmed.slice(1, -1);
847
- }
848
- return trimmed;
849
- }
850
- /**
851
- * 主流程:SDK 模式输入若只是 mini-term 剪贴板临时文件路径,则展开为文件内容再发给模型。
852
- * 具体实现:只处理整串匹配的绝对临时 paste 文件路径,避免影响显式 `type "path"` 等正常命令。
853
- */
854
- export async function resolveSdkInputMessage(input) {
855
- const message = String(input);
856
- if (!isTransientClipboardPasteFilePath(message)) {
857
- return message;
858
- }
859
- const filePath = stripWrappingQuotes(message);
860
- if (!path.isAbsolute(filePath)) {
861
- return message;
862
- }
863
- return fs.readFile(filePath, "utf-8");
864
- }
865
- /**
866
- * 主流程:将已持久化的 ClaudeCode AI 配置合并到启动配置,再让请求级覆盖保持最高优先级。
867
- */
868
- export function buildClaudeCodeLaunchConfig(savedClaudeConfig, requestEnvOverrides) {
869
- const envOverrides = {};
870
- for (const source of [savedClaudeConfig?.env, requestEnvOverrides]) {
871
- if (!source) {
872
- continue;
873
- }
874
- for (const [key, value] of Object.entries(source)) {
875
- const normalizedKey = key.trim();
876
- if (!normalizedKey || value === undefined || value === null) {
877
- continue;
878
- }
879
- const normalizedValue = String(value).trim();
880
- if (!normalizedValue) {
881
- continue;
882
- }
883
- envOverrides[normalizedKey] = normalizedValue;
884
- }
885
- }
886
- const model = envOverrides.ANTHROPIC_MODEL?.trim() ||
887
- envOverrides.CODEX_MODEL?.trim() ||
888
- undefined;
889
- return { envOverrides, model };
890
- }
891
- /**
892
- * 具体逻辑:读取 ClaudeCode live settings;读取失败不阻断启动,保持原 SDK 启动路径可用。
893
- */
894
- async function readSavedClaudeCodeLaunchConfig() {
895
- try {
896
- return await getAiConfigService().readClaudeCodeConfig();
897
- }
898
- catch (error) {
899
- const err = error;
900
- console.warn(`[Runtime] Failed to read saved ClaudeCode AI config: ${err.message}`);
901
- return {};
902
- }
903
- }
904
- function formatToolResultForTimeline(result) {
905
- if (result === undefined || result === null) {
906
- return undefined;
907
- }
908
- const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
909
- const normalized = text.trim();
910
- if (!normalized) {
911
- return undefined;
912
- }
913
- if (normalized.length <= TOOL_RESULT_PREVIEW_MAX_LENGTH) {
914
- return normalized;
915
- }
916
- return `${normalized.slice(0, TOOL_RESULT_PREVIEW_MAX_LENGTH)}\n...(结果过长,已截断)`;
917
- }
918
- function isMcpProviderEvent(event) {
919
- const actionType = event.data.actionType;
920
- if (actionType === "mcp") {
921
- return true;
922
- }
923
- const toolName = String(event.data.toolName || "").toLowerCase();
924
- return (toolName.startsWith("mcp__") ||
925
- toolName.includes("-mcp__") ||
926
- toolName.includes("mcp_"));
927
- }
928
- /**
929
- * 将 SDK 工具结束事件转换为时间线返回数据状态。
930
- * 主流程:格式化工具结果 -> 保留 MCP 专用展示 -> 其它工具按动作类型追加到对应时间线事件。
931
- */
932
- export function buildToolResultTimelineActionInfo(event) {
933
- if (event.type !== "tool_use_end") {
934
- return undefined;
935
- }
936
- const actionResult = formatToolResultForTimeline(event.data.toolResult ?? event.data.actionResult);
937
- if (!actionResult) {
938
- return undefined;
939
- }
940
- const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
941
- const actionType = event.data.actionType || inferredAction.actionType;
942
- if (actionType === "idle") {
943
- return undefined;
944
- }
945
- if (actionType === "mcp" && isMcpProviderEvent(event)) {
946
- return {
947
- actionType: "mcp",
948
- actionLabel: "MCP返回",
949
- actionDetail: event.data.actionDetail || event.data.toolName,
950
- actionId: event.data.actionId || event.data.toolUseId,
951
- actionResult,
952
- };
953
- }
954
- return {
955
- actionType,
956
- actionLabel: event.data.actionLabel || inferredAction.actionLabel,
957
- actionDetail: event.data.actionDetail ||
958
- inferredAction.actionDetail ||
959
- event.data.toolName,
960
- actionId: event.data.actionId || event.data.toolUseId,
961
- actionResult,
962
- };
963
- }
964
- export function formatSdkOutputEvent(event) {
965
- if (event.type === "ai_request") {
966
- const model = event.data.model ? ` ${event.data.model}` : "";
967
- const preview = event.data.lastUserMessagePreview
968
- ? `:${event.data.lastUserMessagePreview}`
969
- : "";
970
- return `\r\n[ACode] 正在请求模型${model}${preview}\r\n`;
971
- }
972
- if (event.type === "text_delta") {
973
- return event.data.text || undefined;
974
- }
975
- if (event.type === "thinking") {
976
- return event.data.text || undefined;
977
- }
978
- if (event.type === "command_output") {
979
- return event.data.text || undefined;
980
- }
981
- if (event.type === "error") {
982
- return event.data.text
983
- ? `\r\n[SDK Error] ${event.data.text}\r\n`
984
- : undefined;
985
- }
986
- return undefined;
987
- }
988
- export function buildSdkOutputMetadata(event) {
989
- const logMessage = formatSdkLogMessage(event);
990
- return {
991
- sourceEventType: event.type,
992
- logCategory: "acode",
993
- ...buildCommandOutputMetadata(event),
994
- ...(logMessage
995
- ? {
996
- logLevel: resolveSdkLogLevel(event),
997
- logMessage,
998
- }
999
- : {}),
1000
- };
1001
- }
1002
- function resolveSdkLogLevel(event) {
1003
- if (event.type === "error") {
1004
- return "error";
1005
- }
1006
- if (event.type === "command_output" && event.data.stream === "stderr") {
1007
- return "warn";
1008
- }
1009
- return "info";
1010
- }
1011
- function buildCommandOutputMetadata(event) {
1012
- if (event.type !== "command_output") {
1013
- return {};
1014
- }
1015
- return {
1016
- ...(typeof event.data.stream === "string"
1017
- ? { stream: event.data.stream }
1018
- : {}),
1019
- ...(typeof event.data.sequence === "number"
1020
- ? { sequence: event.data.sequence }
1021
- : {}),
1022
- ...(typeof event.data.cwd === "string" ? { cwd: event.data.cwd } : {}),
1023
- ...(typeof event.data.commandFilePath === "string"
1024
- ? { commandFilePath: event.data.commandFilePath }
1025
- : {}),
1026
- ...(typeof event.data.commandLineCount === "number"
1027
- ? { commandLineCount: event.data.commandLineCount }
1028
- : {}),
1029
- };
1030
- }
1031
- function formatSdkLogMessage(event) {
1032
- if (event.type === "ai_request") {
1033
- const model = event.data.model || "unknown-model";
1034
- const endpoint = event.data.endpoint ? ` @ ${event.data.endpoint}` : "";
1035
- const toolCount = typeof event.data.toolCount === "number"
1036
- ? `, tools=${event.data.toolCount}`
1037
- : "";
1038
- return `AI request started: ${model}${endpoint}${toolCount}`;
1039
- }
1040
- if (event.type === "tool_use_start") {
1041
- return formatSdkToolLogMessage(event, "开始");
1042
- }
1043
- if (event.type === "tool_use_end") {
1044
- return formatSdkToolLogMessage(event, "完成");
1045
- }
1046
- if (event.type === "command_output") {
1047
- return formatCommandOutputLogMessage(event);
1048
- }
1049
- if (event.type === "turn_complete") {
1050
- const finishReason = event.data.finishReason
1051
- ? `, finish=${event.data.finishReason}`
1052
- : "";
1053
- return `Turn completed${finishReason}`;
1054
- }
1055
- if (event.type === "session_complete") {
1056
- return `Session completed: exitCode=${event.data.exitCode ?? 0}`;
1057
- }
1058
- if (event.type === "error") {
1059
- return event.data.text || "ACode runtime error";
1060
- }
1061
- return undefined;
1062
- }
1063
- // 匹配 CSI 类 ANSI 转义序列(颜色/光标/清屏等),用于 DevTools 日志预览剥离
1064
- const ANSI_ESCAPE_PATTERN = /\x1b\[[0-9;?]*[a-zA-Z]/g;
1065
- function stripAnsiEscapeCodes(text) {
1066
- return text.replace(ANSI_ESCAPE_PATTERN, "");
1067
- }
1068
- function formatCommandOutputLogMessage(event) {
1069
- const stream = typeof event.data.stream === "string" ? event.data.stream : "mixed";
1070
- const sequence = typeof event.data.sequence === "number" ? ` #${event.data.sequence}` : "";
1071
- const normalized = stripAnsiEscapeCodes(String(event.data.text || ""))
1072
- .replace(/\s+/g, " ")
1073
- .trim();
1074
- const preview = normalized.length > 240 ? `${normalized.slice(0, 240)}...` : normalized;
1075
- return `命令输出(${stream}${sequence}): ${preview || "(empty)"}`;
1076
- }
1077
- function formatSdkToolLogMessage(event, phaseLabel) {
1078
- const actionInfo = getToolActionInfo(event.data.toolName, event.data.toolInput);
1079
- const actionLabel = event.data.actionLabel || actionInfo.actionLabel || "调用工具";
1080
- const detail = resolveSdkToolLogDetail(event, actionInfo);
1081
- return detail
1082
- ? `${phaseLabel}${actionLabel}: ${detail}`
1083
- : `${phaseLabel}${actionLabel}: ${event.data.actionDetail || event.data.toolName || "tool"}`;
1084
- }
1085
- function resolveSdkToolLogDetail(event, actionInfo) {
1086
- const input = event.data.toolInput;
1087
- if (actionInfo.actionType === "bash") {
1088
- return (getCommandDisplayDetail(input) ||
1089
- event.data.actionDetail ||
1090
- actionInfo.actionDetail);
1091
- }
1092
- if (actionInfo.actionType === "read_file" ||
1093
- actionInfo.actionType === "edit_file" ||
1094
- actionInfo.actionType === "write_file") {
1095
- return (getFileDisplayDetail(input) ||
1096
- event.data.actionDetail ||
1097
- actionInfo.actionDetail);
1098
- }
1099
- return event.data.actionDetail || actionInfo.actionDetail;
1100
- }
1101
- function getCommandDisplayDetail(input) {
1102
- const command = firstDisplayString(input?.command, input?.cmd);
1103
- if (command)
1104
- return command;
1105
- if (Array.isArray(input?.argv)) {
1106
- const argv = input.argv
1107
- .map((item) => String(item ?? "").trim())
1108
- .filter(Boolean)
1109
- .join(" ");
1110
- return argv ? redactTransientClipboardPastePaths(argv) : undefined;
1111
- }
1112
- return firstDisplayString(input?.args);
1113
- }
1114
- function getFileDisplayDetail(input) {
1115
- const paths = displayStringList(input?.paths);
1116
- if (paths.length > 0) {
1117
- return paths.length === 1
1118
- ? paths[0]
1119
- : `${paths.length}个文件: ${paths.join(", ")}`;
1120
- }
1121
- return firstDisplayString(input?.file_path, input?.filePath, input?.path, input?.target_file, input?.targetFile);
1122
- }
1123
- function firstDisplayString(...values) {
1124
- for (const value of values) {
1125
- if (typeof value === "string" && value.trim()) {
1126
- return redactTransientClipboardPastePaths(value.trim());
1127
- }
1128
- if (Array.isArray(value)) {
1129
- const first = displayStringList(value)[0];
1130
- if (first)
1131
- return first;
1132
- }
1133
- }
1134
- return undefined;
1135
- }
1136
- function displayStringList(value) {
1137
- if (!Array.isArray(value))
1138
- return [];
1139
- return value
1140
- .map((item) => (typeof item === "string" ? item.trim() : ""))
1141
- .filter(Boolean)
1142
- .map((item) => redactTransientClipboardPastePaths(item));
1143
- }
1144
- /**
1145
- * 格式化工具调用输入参数为 JSON 字符串,用于时间线展示。
1146
- * 对敏感内容(如剪贴板临时路径)进行脱敏处理。
1147
- */
1148
- function formatToolInput(toolInput) {
1149
- if (!toolInput || Object.keys(toolInput).length === 0)
1150
- return undefined;
1151
- try {
1152
- const sanitized = redactTransientClipboardPastePaths(JSON.stringify(toolInput, null, 2));
1153
- // 限制输入参数长度,避免超大参数撑爆时间线
1154
- return sanitized.length > 8000
1155
- ? `${sanitized.slice(0, 8000)}\n...(输入参数过长,已截断)`
1156
- : sanitized;
1157
- }
1158
- catch {
1159
- return undefined;
1160
- }
1161
- }
1162
- function formatAiRequestDetail(data) {
1163
- const lines = [
1164
- data.model ? `模型: ${data.model}` : undefined,
1165
- data.endpoint || data.requestUrl
1166
- ? `接口: ${data.endpoint || data.requestUrl}`
1167
- : undefined,
1168
- data.requestMethod ? `方法: ${data.requestMethod}` : undefined,
1169
- typeof data.stream === "boolean"
1170
- ? `流式: ${data.stream ? "是" : "否"}`
1171
- : undefined,
1172
- typeof data.messageCount === "number"
1173
- ? `上下文消息: ${data.messageCount}`
1174
- : undefined,
1175
- typeof data.toolCount === "number"
1176
- ? `可用工具: ${data.toolCount}`
1177
- : undefined,
1178
- data.lastUserMessagePreview
1179
- ? `最后请求: ${data.lastUserMessagePreview}`
1180
- : undefined,
1181
- ].filter((line) => Boolean(line));
1182
- const requestHeaderLines = formatAiRequestHeaders(data.requestHeaders);
1183
- const base = lines.join("\n") || "ACode 正在发送 AI 请求";
1184
- const headerBlock = requestHeaderLines.length > 0
1185
- ? `${AI_REQUEST_HEADERS_MARKER}${requestHeaderLines.join("\n")}`
1186
- : "";
1187
- const bodyBlock = data.requestBody
1188
- ? `${AI_REQUEST_BODY_MARKER}${data.requestBody}`
1189
- : "";
1190
- return `${base}${headerBlock}${bodyBlock}`;
1191
- }
1192
- function formatAiRequestHeaders(headers) {
1193
- if (!headers)
1194
- return [];
1195
- return Object.entries(headers)
1196
- .map(([name, value]) => `${name}: ${value}`)
1197
- .filter((line) => line.trim().length > 0);
1198
- }
1199
- /**
1200
- * 主干流程:把 provider 事件转换为面板运行状态。
1201
- *
1202
- * text_delta 表示助手正在生成;thinking 表示推理/内部过程。
1203
- * tool_use_start 表示正在执行工具;turn_complete 才表示真正空闲。
1204
- */
1205
- function isWaitingInputProgressEvent(event, currentRuntimeStatus) {
1206
- const normalizedStatus = String(currentRuntimeStatus || "")
1207
- .trim()
1208
- .toLowerCase();
1209
- if (normalizedStatus !== "waiting_input") {
1210
- return false;
1211
- }
1212
- return event.type === "thinking" || event.type === "text_delta";
1213
- }
1214
- /**
1215
- * 从事件 data 中提取 estimatedContextTokens(兼容多种字段名)。
1216
- * 返回 undefined 表示无有效值,使调用方能区分"无数据"和"0"。
1217
- */
1218
- function extractEstimatedContextTokens(data) {
1219
- for (const key of [
1220
- "estimatedContextTokens",
1221
- "contextTokens",
1222
- "context_tokens",
1223
- "contextUsedTokens",
1224
- "context_used_tokens",
1225
- ]) {
1226
- const value = data[key];
1227
- const count = Number(value);
1228
- if (Number.isFinite(count) && count > 0) {
1229
- return count;
1230
- }
1231
- }
1232
- return undefined;
1233
- }
1234
- export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatus) {
1235
- if (isWaitingInputProgressEvent(event, currentRuntimeStatus)) {
1236
- return undefined;
1237
- }
1238
- if (event.type === "ai_request") {
1239
- // ★ 从 event.data 提取 estimatedContextTokens 构建 usage,
1240
- // 确保 event handler 路径也能传递 token 用量到前端。
1241
- // 这对压缩后面板刷新尤为关键:压缩后 ai_request 的 estimatedContextTokens
1242
- // 反映了压缩后的真实上下文大小,前端据此更新进度条。
1243
- const estimatedTokens = extractEstimatedContextTokens(event.data);
1244
- return {
1245
- status: "thinking",
1246
- actionInfo: {
1247
- actionType: "ai_request",
1248
- actionLabel: "AI请求",
1249
- actionDetail: formatAiRequestDetail(event.data),
1250
- ...(estimatedTokens !== undefined
1251
- ? { usage: { inputTokens: estimatedTokens, outputTokens: 0 } }
1252
- : {}),
1253
- },
1254
- };
1255
- }
1256
- if (event.type === "thinking" || event.type === "text_delta") {
1257
- return { status: "thinking" };
1258
- }
1259
- if (event.type === "tool_use_start") {
1260
- const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
1261
- const actionDetail = resolveSdkToolLogDetail(event, inferredAction);
1262
- const actionInput = formatToolInput(event.data.toolInput);
1263
- if (event.data.actionType === "idle" ||
1264
- inferredAction.actionType === "idle") {
1265
- return {
1266
- status: "waiting_input",
1267
- actionInfo: {
1268
- actionType: event.data.actionType || inferredAction.actionType,
1269
- actionLabel: event.data.actionLabel || inferredAction.actionLabel,
1270
- actionDetail: actionDetail ||
1271
- event.data.actionDetail ||
1272
- inferredAction.actionDetail ||
1273
- event.data.toolName,
1274
- actionInput,
1275
- actionId: event.data.actionId || event.data.toolUseId,
1276
- },
1277
- };
1278
- }
1279
- return {
1280
- status: "tool_using",
1281
- actionInfo: {
1282
- actionType: event.data.actionType || inferredAction.actionType,
1283
- actionLabel: event.data.actionLabel || inferredAction.actionLabel,
1284
- actionDetail: actionDetail ||
1285
- event.data.actionDetail ||
1286
- inferredAction.actionDetail ||
1287
- event.data.toolName,
1288
- actionInput,
1289
- actionId: event.data.actionId || event.data.toolUseId,
1290
- },
1291
- };
1292
- }
1293
- if (event.type === "turn_complete") {
1294
- // 方案 A:不设置 "AI响应" action 信息,避免状态卡在 action 展示
1295
- // 只返回干净的 waiting_input 状态,usage 信息保留用于 token 统计
1296
- return {
1297
- status: "waiting_input",
1298
- actionInfo: event.data.usage ? { usage: event.data.usage } : undefined,
1299
- };
1300
- }
1301
- if (event.type === "error") {
1302
- return { status: "error" };
1303
- }
1304
- if (event.type === "context_compaction") {
1305
- return {
1306
- status: "compacting",
1307
- actionInfo: {
1308
- actionType: "context_compaction",
1309
- actionLabel: event.data.actionLabel,
1310
- actionDetail: event.data.actionDetail,
1311
- ...(event.data.actionId ? { actionId: event.data.actionId } : {}),
1312
- ...(event.data.parentId ? { parentId: event.data.parentId } : {}),
1313
- },
1314
- };
1315
- }
1316
- if (event.type === "runtime_capabilities") {
1317
- // 运行时能力清单(MCP 工具 / Skill 列表)—— 保持当前运行态不变,仅推送 actionInfo 到时间线供调试面板展示
1318
- return {
1319
- status: currentRuntimeStatus || "waiting_input",
1320
- actionInfo: {
1321
- actionType: "runtime_capabilities",
1322
- actionLabel: event.data.actionLabel || "运行时能力",
1323
- actionDetail: event.data.actionDetail,
1324
- },
1325
- };
1326
- }
1327
- return undefined;
1328
- }
1329
- // Per-session 输出队列:确保 sendOutput HTTP POST 按事件顺序串行到达 Server,
1330
- // 避免并发 POST 因网络时序导致流式文本 chunk 乱序。
1331
- const sdkOutputQueueBySession = new Map();
1332
- // Per-session 压缩状态事件队列:确保 context_compaction 事件(start/detect/prune/.../end)
1333
- // 按发射顺序串行发送 HTTP POST,避免并发请求导致部分事件丢失(DB 证据:所有压缩都只保留了 2-4 个事件)。
1334
- // ★ 根因:runtime.ts 同步连续发射 start/detect 等事件,多个 void sendStatus 并发执行 HTTP POST,
1335
- // 部分 HTTP 请求因竞争/超时/Java 后端 FOR UPDATE 锁等待而失败,事件永久丢失(本地 buffer 不重推)。
1336
- const compactionStatusChainBySession = new Map();
1337
- /**
1338
- * ★ 修复 BUG:idle 类工具(poll_message)状态变更的串行重试队列。
1339
- * poll_message 的 tool_use_start/tool_use_end 切换 status 至关重要,
1340
- * 若 sendStatus HTTP 失败,前端 base.runtimeStatus 会卡在上一个状态,
1341
- * 导致"实际在 poll_message 阻塞但显示思考中"的假象。
1342
- * 串行化 + 2 次重试确保关键状态变更最终到达,仍失败则前端兜底恢复显示。
1343
- */
1344
- const idleStatusChainBySession = new Map();
1345
- /** 小 chunk 积攒缓存:sessionId:sourceEventType → { buffer, seq, timer, sourceEventType, entry },不足 20 字符时积攒最多 500ms 再推送 */
1346
- const sdkOutputSmallChunkAccumulators = new Map();
1347
- /** 小 chunk 积攒阈值:字符数少于此值的 output 事件积攒发送 */
1348
- const SMALL_CHUNK_CHAR_THRESHOLD = 20;
1349
- /** 小 chunk 最大积攒时间(毫秒) */
1350
- const SMALL_CHUNK_MAX_ACCUMULATE_MS = 500;
1351
- /**
1352
- * 串行队列单条 sendOutput 的最大允许时长(毫秒)。
1353
- * ★ 修复 BUG(原 raceSendOutput):
1354
- * 1. 超时后不取消底层请求 → 顺序错乱(超时的 A 在 B 之后到达服务端)
1355
- * 2. 20s 超时切断 sendOutput 内部合法的 401 重试链路(15s×2=30s)
1356
- * 新方案:AbortController 真正取消底层请求 + 超时重试 + 服务端 (agentId,sessionId,seq) 幂等。
1357
- * timeout 取 30s 覆盖 sendOutput 内部 401 重试链路(15s + 15s)。
1358
- */
1359
- const SEND_OUTPUT_QUEUE_TIMEOUT_MS = 30_000;
1360
- /**
1361
- * 带超时 + 重试的 sendOutput 包装器。
1362
- * ★ 修复 BUG:原 raceSendOutput 超时后仅放行队列,不取消底层请求,
1363
- * 导致顺序错乱 + 切断合法重试。新方案:
1364
- * 1. AbortController 在超时时真正取消底层 axios 请求(防止迟到请求污染顺序)
1365
- * 2. 超时或 sendOutput 抛出非 abort 错误后重试(最多 2 次,1s/2s 退避),
1366
- * 覆盖瞬时网络问题 / 服务端 5xx / axios 超时等
1367
- * 3. 服务端按 (agentId, sessionId, seq) 幂等去重,即使超时的请求最终到达也不会重复
1368
- * 4. 所有重试失败后 resolve(不 reject),放行队列下一条
1369
- * 5. 成功路径必须 clearTimeout,否则 30s 后 timeout promise reject 无人 await,
1370
- * 触发 unhandledRejection 导致进程崩溃
1371
- *
1372
- * @param factory - 接收 AbortSignal,返回 sendOutput Promise(每次重试创建新请求)
1373
- * @param timeoutMs - 单次请求超时
1374
- * @param maxRetries - 最大重试次数(不含首次)
1375
- */
1376
- export async function raceSendOutputWithRetry(factory, timeoutMs = SEND_OUTPUT_QUEUE_TIMEOUT_MS, maxRetries = 2) {
1377
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1378
- const controller = new AbortController();
1379
- let timer;
1380
- try {
1381
- // 创建请求 promise,预 attach catch 避免 abort 导致 unhandled rejection
1382
- const requestPromise = factory(controller.signal);
1383
- requestPromise.catch(() => { });
1384
- await Promise.race([
1385
- requestPromise,
1386
- new Promise((_, reject) => {
1387
- timer = setTimeout(() => {
1388
- controller.abort();
1389
- reject(new Error(`sendOutput timeout after ${timeoutMs}ms`));
1390
- }, timeoutMs);
1391
- }),
1392
- ]);
1393
- // ★ 成功路径必须清除 timer:否则 30s 后 timer 触发 reject,
1394
- // 该 timeout promise 无人 await,会触发 unhandledRejection 导致进程崩溃。
1395
- if (timer)
1396
- clearTimeout(timer);
1397
- return; // 成功
1398
- }
1399
- catch (err) {
1400
- if (timer)
1401
- clearTimeout(timer);
1402
- const errMsg = err instanceof Error ? err.message : String(err);
1403
- if (attempt < maxRetries) {
1404
- const backoff = 1000 * (attempt + 1);
1405
- log.warn(`sendOutput attempt ${attempt + 1}/${maxRetries + 1} failed (${errMsg}), ` +
1406
- `retrying in ${backoff}ms...`);
1407
- await new Promise((r) => setTimeout(r, backoff));
1408
- continue;
1409
- }
1410
- log.warn(`sendOutput failed after ${maxRetries + 1} attempts (${errMsg}), ` +
1411
- `giving up to unblock serial queue`);
1412
- }
1413
- }
1414
- }
1415
- function flushSmallChunkAccumulator(sessionId, sourceEventType) {
1416
- const key = sourceEventType
1417
- ? `${sessionId}:${sourceEventType}`
1418
- : undefined;
1419
- // 如果指定了 key 则精确刷新;否则回退到按 sessionId 前缀查找并刷新所有(兼容大 chunk 触发的全量刷新)
1420
- let acc;
1421
- let accKey;
1422
- if (key) {
1423
- acc = sdkOutputSmallChunkAccumulators.get(key);
1424
- accKey = key;
1425
- }
1426
- else {
1427
- // 刷新该 sessionId 下的所有 accumulator(可能有多种 sourceEventType)
1428
- // ★ 先收集匹配 key 再逐个 flush,避免递归调用在迭代中 delete 同一 Map(虽然 JS 规范安全,但代码坏味道)。
1429
- const keysToFlush = [];
1430
- for (const [k, v] of sdkOutputSmallChunkAccumulators) {
1431
- if (k.startsWith(sessionId + ":") && v.buffer) {
1432
- keysToFlush.push(v.sourceEventType);
1433
- }
1434
- }
1435
- for (const sourceType of keysToFlush) {
1436
- flushSmallChunkAccumulator(sessionId, sourceType);
1437
- }
1438
- return;
1439
- }
1440
- if (!acc || !acc.buffer)
1441
- return;
1442
- acc.timer = null;
1443
- const output = acc.buffer;
1444
- const seq = acc.seq;
1445
- const entry = acc.entry;
1446
- const eventType = acc.sourceEventType;
1447
- acc.buffer = "";
1448
- if (accKey)
1449
- sdkOutputSmallChunkAccumulators.delete(accKey);
1450
- const metadata = {
1451
- sourceEventType: eventType,
1452
- logCategory: "acode",
1453
- };
1454
- const authOptions = {
1455
- runtimeAccessToken: entry.runtimeAccessToken,
1456
- schedulerBaseUrl: entry.schedulerBaseUrl,
1457
- };
1458
- const prev = sdkOutputQueueBySession.get(sessionId) ?? Promise.resolve();
1459
- const next = prev.then(() => raceSendOutputWithRetry((signal) => sendOutput(entry.agentId, output, sessionId, seq, metadata, authOptions, signal)), () => raceSendOutputWithRetry((signal) => sendOutput(entry.agentId, output, sessionId, seq, metadata, authOptions, signal)));
1460
- sdkOutputQueueBySession.set(sessionId, next);
1461
- void next.finally(() => {
1462
- if (sdkOutputQueueBySession.get(sessionId) === next) {
1463
- sdkOutputQueueBySession.delete(sessionId);
1464
- }
1465
- });
1466
- }
1467
- function enqueueSdkOutputEvent(event, entry) {
1468
- const output = formatSdkOutputEvent(event);
1469
- if (!output) {
1470
- return;
1471
- }
1472
- entry.seq += 1;
1473
- const seq = entry.seq;
1474
- const metadata = buildSdkOutputMetadata(event);
1475
- const authOptions = {
1476
- runtimeAccessToken: entry.runtimeAccessToken,
1477
- schedulerBaseUrl: entry.schedulerBaseUrl,
1478
- };
1479
- // 小 chunk 积攒优化:命令输出和 AI 文本增量等小片段不足阈值时积攒再推送
1480
- if ((event.type === "command_output" ||
1481
- event.type === "text_delta" ||
1482
- event.type === "thinking") &&
1483
- output.length < SMALL_CHUNK_CHAR_THRESHOLD) {
1484
- const accKey = `${event.sessionId}:${event.type}`;
1485
- let acc = sdkOutputSmallChunkAccumulators.get(accKey);
1486
- if (!acc) {
1487
- acc = {
1488
- buffer: "",
1489
- seq: 0,
1490
- timer: null,
1491
- entry,
1492
- sourceEventType: event.type,
1493
- };
1494
- }
1495
- acc.buffer += output;
1496
- acc.seq = seq;
1497
- acc.entry = entry;
1498
- if (!acc.timer) {
1499
- const capturedSessionId = event.sessionId;
1500
- const capturedType = event.type;
1501
- acc.timer = setTimeout(() => flushSmallChunkAccumulator(capturedSessionId, capturedType), SMALL_CHUNK_MAX_ACCUMULATE_MS);
1502
- }
1503
- sdkOutputSmallChunkAccumulators.set(accKey, acc);
1504
- return;
1505
- }
1506
- // 大 chunk 直接发送(先刷新可能存在的积攒缓存)
1507
- flushSmallChunkAccumulator(event.sessionId);
1508
- const prev = sdkOutputQueueBySession.get(event.sessionId) ?? Promise.resolve();
1509
- const next = prev.then(() => raceSendOutputWithRetry((signal) => sendOutput(entry.agentId, output, event.sessionId, seq, metadata, authOptions, signal)), () => raceSendOutputWithRetry((signal) => sendOutput(entry.agentId, output, event.sessionId, seq, metadata, authOptions, signal)));
1510
- sdkOutputQueueBySession.set(event.sessionId, next);
1511
- // 队列空闲时自动清理,避免内存泄漏
1512
- void next.finally(() => {
1513
- if (sdkOutputQueueBySession.get(event.sessionId) === next) {
1514
- sdkOutputQueueBySession.delete(event.sessionId);
1515
- }
1516
- });
1517
- }
1518
- export function resolveSdkProviderId(command) {
1519
- return resolveSdkProviderIdByCommand(command);
1520
- }
1521
- // ============ 初始化 Adapter ============
1522
- let adapterInitialized = false;
1523
- function ensureAdapterInitialized() {
1524
- if (adapterInitialized)
1525
- return;
1526
- registerSdkProviders(adapterRegistry, wireSdkAdapterEvents);
1527
- // 需求 4.2.1 #2:启动全局心跳调度器(每 30s 推送空包检测 Server 连接存活)
1528
- startHeartbeatScheduler();
1529
- adapterInitialized = true;
1530
- }
1531
- function wireSdkAdapterEvents(adapter, definition) {
1532
- adapter.on("event", (event) => {
1533
- const entry = sdkSessions.get(event.sessionId);
1534
- if (entry) {
1535
- console.log(`[${definition.displayName} Adapter] Event: ${event.type} for session ${event.sessionId}`);
1536
- // Skip sendStatus for thinking/text_delta/tool_use_start/context_compaction/ai_request events — the status-change
1537
- // listener already emits the authoritative status with restored actionInfo,
1538
- // avoiding duplicate timeline entries in agentEventStore.
1539
- // ★ context_compaction 加入此列表:adapter 对每个压缩事件(start/phase/end)都会同时
1540
- // 调用 emitProviderEvent(触发 event 监听器)和 emit("status-change")(触发 status-change 监听器),
1541
- // 两条路径都会 sendStatus,导致 Bridge 收到重复事件。虽然前端 seenActionIds 会去重,
1542
- // 但异步 sendStatus 到达顺序不确定,会造成同时间戳事件顺序错乱(如 prune_end 排在 prune_start 前)。
1543
- // 只保留 status-change 路径(含完整 actionId/parentId),确保父子事件正确匹配与排序。
1544
- // ★ ai_request 加入此列表(修复 BUG:Agent 永久卡在 "AI请求" 状态):
1545
- // ai_request 同时触发 provider event 路径和 status-change 路径两条 sendStatus,
1546
- // 而 tool_use_start(编辑文件等)仅通过 status-change 路径发送。
1547
- // 异步 HTTP 乱序导致 ai_request 的 provider-event sendStatus 可能晚于 tool_use_start 到达,
1548
- // 覆盖前端状态为 "AI请求",而时间线正确显示 "编辑文件"。
1549
- // 将 ai_request 加入 deferred 列表,使其仅通过 status-change 路径发送,
1550
- // 与 tool_use_start 保持一致,消除双路径竞态。
1551
- const isDeferredStatusEvent = event.type === "thinking" || event.type === "text_delta" || event.type === "tool_use_start" || event.type === "context_compaction" || event.type === "ai_request";
1552
- if (!isDeferredStatusEvent) {
1553
- // ★ 修复时序竞争:runtime_capabilities 事件缓存 actionDetail,
1554
- // 用于 session 稳定后重发(防止 "starting" 状态尚未更新 runtime_session_id
1555
- // 导致服务端丢弃事件)
1556
- if (event.type === "runtime_capabilities" && event.data.actionDetail) {
1557
- entry.cachedCapabilitiesActionDetail = event.data.actionDetail;
1558
- }
1559
- const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event, entry.currentRuntimeStatus);
1560
- if (runtimeStatus) {
1561
- void sendStatus(entry.agentId, event.sessionId, runtimeStatus.status, runtimeStatus.actionInfo, resolveStatusChangeUsage(runtimeStatus.actionInfo), {
1562
- runtimeAccessToken: entry.runtimeAccessToken,
1563
- schedulerBaseUrl: entry.schedulerBaseUrl,
1564
- },
1565
- // Pass original timestamp from ACode runtime for correct timeline ordering
1566
- // Convert ISO string timestamp to epoch milliseconds
1567
- event.timestamp ? new Date(event.timestamp).getTime() : undefined,
1568
- // Pass original event type for frontend gray-zone rendering (thinking vs text_delta)
1569
- event.type);
1570
- }
1571
- }
1572
- enqueueSdkOutputEvent(event, entry);
1573
- const toolResultActionInfo = buildToolResultTimelineActionInfo(event);
1574
- if (toolResultActionInfo) {
1575
- void sendStatus(entry.agentId, event.sessionId, "tool_result", toolResultActionInfo, undefined, {
1576
- runtimeAccessToken: entry.runtimeAccessToken,
1577
- schedulerBaseUrl: entry.schedulerBaseUrl,
1578
- },
1579
- // Pass original timestamp from ACode runtime for correct timeline ordering
1580
- // Convert ISO string timestamp to epoch milliseconds
1581
- event.timestamp ? new Date(event.timestamp).getTime() : undefined,
1582
- // Pass original event type (tool_use_end) for timeline sourceEventType
1583
- event.type);
1584
- }
1585
- }
1586
- });
1587
- adapter.on("status-change", (sessionId, status, actionInfo) => {
1588
- const entry = sdkSessions.get(sessionId);
1589
- if (entry) {
1590
- // ★ 状态切换时主动 flush 小 chunk accumulator:
1591
- // 原实现只在收到大 chunk 时 flush,状态切换(如 thinking → tool_using)会
1592
- // 让 < 20 字符的思考内容残留在 accumulator 中,直到下次同类型 chunk 到达才被
1593
- // 一起发出,导致时间线顺序错乱(思考内容显示在工具调用之后)。
1594
- // 现在每次状态切换都先 flush 该 session 下所有 accumulator,保证顺序正确。
1595
- flushSmallChunkAccumulator(sessionId);
1596
- // ★ 活跃状态定期刷新:防止长时间 LLM 调用期间服务端 WebSocket 空闲超时导致 LOST
1597
- if (status === "thinking" || status === "tool_using" || status === "compacting") {
1598
- startStatusRefresh(sessionId);
1599
- }
1600
- else {
1601
- stopStatusRefresh(sessionId);
1602
- }
1603
- const runtimeState = resolveStatusChangeRuntimeState(status, actionInfo);
1604
- entry.currentRuntimeStatus = runtimeState.status;
1605
- // 当新状态未携带动作且进入 thinking 时,保留上一个 idle 类动作(如 poll_message),
1606
- // 避免页面刷新后丢失"监听消息"状态而错误显示"思考中"。
1607
- // 注意:MCP client 发送的 actionType 是 "mcp" 而非 "idle",需要通过 actionDetail 判断。
1608
- // ★ 关键修复: turn_complete 后进入 waiting_input 时,如果携带的是 ai_request 动作,
1609
- // 不应该保留之前的 tool_using 动作,避免永久卡在工具调用状态。
1610
- const prevActionDetail = entry.currentRuntimeActionDetail ?? "";
1611
- const isPrevPollMessage = prevActionDetail.includes("poll_message") || prevActionDetail.includes("get_message");
1612
- const isNewAiRequestAction = runtimeState.actionInfo?.actionType === "ai_request";
1613
- const shouldPreservePrevAction = !isNewAiRequestAction && // 新动作不是 AI 响应
1614
- (runtimeState.status === "thinking" || runtimeState.status === "waiting_input") && // 状态是 thinking 或 waiting_input
1615
- (entry.currentRuntimeActionType === "idle" || entry.currentRuntimeActionType === "mcp") && // 之前是 idle/mcp 动作
1616
- isPrevPollMessage; // 且之前是 poll_message
1617
- if (runtimeState.actionInfo?.actionType || // 新状态携带了动作
1618
- !shouldPreservePrevAction // 不满足保留条件
1619
- ) {
1620
- entry.currentRuntimeActionType = runtimeState.actionInfo?.actionType;
1621
- entry.currentRuntimeActionLabel = runtimeState.actionInfo?.actionLabel;
1622
- entry.currentRuntimeActionDetail =
1623
- runtimeState.actionInfo?.actionDetail;
1624
- entry.currentRuntimeActionInput =
1625
- runtimeState.actionInfo?.actionInput;
1626
- }
1627
- console.log(`[${definition.displayName} Adapter] Status changed: ${runtimeState.status} for agent ${entry.agentId}`, runtimeState.actionInfo || "");
1628
- void updatePersistedSessionRuntimeState(sessionId, {
1629
- runtimeStatus: runtimeState.status,
1630
- runtimeActionType: entry.currentRuntimeActionType,
1631
- runtimeActionLabel: entry.currentRuntimeActionLabel,
1632
- runtimeActionDetail: entry.currentRuntimeActionDetail,
1633
- });
1634
- // thinking + poll_message 动作保留时,派生恢复用 actionInfo 传给 sendStatus
1635
- // 支持 actionType 为 "idle" 或 "mcp",通过 actionDetail 中的 poll_message/get_message 判断
1636
- const restoredActionInfo = runtimeState.actionInfo?.actionType
1637
- ? runtimeState.actionInfo
1638
- : entry.currentRuntimeActionType
1639
- ? {
1640
- actionType: entry.currentRuntimeActionType,
1641
- actionLabel: entry.currentRuntimeActionLabel ?? "",
1642
- actionDetail: entry.currentRuntimeActionDetail,
1643
- actionInput: entry.currentRuntimeActionInput,
1644
- }
1645
- : undefined;
1646
- // ★ 对 compacting 状态(context_compaction 事件)使用 per-session 序列化队列,
1647
- // 避免 start/detect/prune/.../end 等事件并发 HTTP POST 导致部分事件丢失。
1648
- // DB 证据:所有压缩都只保留了 2-4 个事件(应该有 7 个),根因是并发请求竞争。
1649
- if (runtimeState.status === "compacting") {
1650
- const prev = compactionStatusChainBySession.get(sessionId) ?? Promise.resolve();
1651
- const next = prev.then(async () => {
1652
- // ★ 重试机制:HTTP 失败(sendStatus 返回 undefined)时重试,
1653
- // 避免因网络超时/瞬时故障导致压缩事件(特别是父 start 事件)永久丢失。
1654
- // scheduleEventPush 是 no-op,HTTP 回调是唯一投递路径,无 fallback。
1655
- const maxRetries = 2;
1656
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1657
- const response = await sendStatus(entry.agentId, sessionId, runtimeState.status, restoredActionInfo, resolveStatusChangeUsage(runtimeState.actionInfo), {
1658
- runtimeAccessToken: entry.runtimeAccessToken,
1659
- schedulerBaseUrl: entry.schedulerBaseUrl,
1660
- });
1661
- if (response !== undefined)
1662
- return; // HTTP 成功
1663
- if (attempt < maxRetries) {
1664
- log.warn(`[${definition.displayName}] compaction sendStatus attempt ${attempt + 1} failed (HTTP), retrying in ${1000 * (attempt + 1)}ms...`);
1665
- await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
1666
- }
1667
- }
1668
- log.warn(`[${definition.displayName}] compaction sendStatus failed after ${maxRetries + 1} attempts, event may be lost`);
1669
- }).catch((err) => {
1670
- // 链中某次失败不影响后续事件发送
1671
- log.warn(`[${definition.displayName}] compaction status chain error:`, err);
1672
- });
1673
- compactionStatusChainBySession.set(sessionId, next);
1674
- // 链结束后清理,避免内存泄漏
1675
- next.finally(() => {
1676
- if (compactionStatusChainBySession.get(sessionId) === next) {
1677
- compactionStatusChainBySession.delete(sessionId);
1678
- }
1679
- });
1680
- }
1681
- else {
1682
- // ★ 修复重复时间线条目:当 shouldPreservePrevAction 为 true 且新 actionInfo 为 undefined
1683
- // (idle 类型 poll_message 的 tool_use_end status-change 传 undefined)时,
1684
- // 不传 restoredActionInfo,避免 dashboard 创建第二个"调用MCP"时间线条目。
1685
- // tool_use_start 的 status-change 已创建了一个条目,这里只需纯状态变更。
1686
- const skipRestoredActionInfo = shouldPreservePrevAction && !runtimeState.actionInfo?.actionType;
1687
- // ★ 修复 BUG:idle 类工具(poll_message)的关键状态切换走串行重试队列,
1688
- // 避免 sendStatus HTTP 失败导致前端 base.runtimeStatus 卡在上一个状态。
1689
- // 场景:poll_message 返回 → tool_use_end → status="thinking" →
1690
- // agent 立即调用下一个 poll_message → tool_use_start → status="waiting_input" →
1691
- // 若 sendStatus 失败,前端 base.runtimeStatus 卡在 thinking,但实际 agent 已在 poll_message 阻塞。
1692
- // 重试 2 次(1s/2s 退避),仍失败则前端兜底(findRecentPollingBlockEvent)恢复显示。
1693
- const isIdleToolStatusChange = restoredActionInfo?.actionType === "idle" ||
1694
- (entry.currentRuntimeActionType === "idle" && shouldPreservePrevAction);
1695
- if (isIdleToolStatusChange) {
1696
- const prev = idleStatusChainBySession.get(sessionId) ?? Promise.resolve();
1697
- const next = prev.then(async () => {
1698
- const maxRetries = 2;
1699
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1700
- const response = await sendStatus(entry.agentId, sessionId, runtimeState.status, skipRestoredActionInfo ? undefined : restoredActionInfo, resolveStatusChangeUsage(runtimeState.actionInfo), {
1701
- runtimeAccessToken: entry.runtimeAccessToken,
1702
- schedulerBaseUrl: entry.schedulerBaseUrl,
1703
- });
1704
- if (response !== undefined)
1705
- return;
1706
- if (attempt < maxRetries) {
1707
- await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
1708
- }
1709
- }
1710
- log.warn(`[${definition.displayName}] idle tool sendStatus failed after 3 attempts, status may be stale (frontend will fall back to timeline tool_use event)`);
1711
- }).catch((err) => {
1712
- log.warn(`[${definition.displayName}] idle status chain error:`, err);
1713
- });
1714
- idleStatusChainBySession.set(sessionId, next);
1715
- next.finally(() => {
1716
- if (idleStatusChainBySession.get(sessionId) === next) {
1717
- idleStatusChainBySession.delete(sessionId);
1718
- }
1719
- });
1720
- }
1721
- else {
1722
- void sendStatus(entry.agentId, sessionId, runtimeState.status, skipRestoredActionInfo ? undefined : restoredActionInfo, resolveStatusChangeUsage(runtimeState.actionInfo), {
1723
- runtimeAccessToken: entry.runtimeAccessToken,
1724
- schedulerBaseUrl: entry.schedulerBaseUrl,
1725
- });
1726
- }
1727
- }
1728
- // ★ 修复时序竞争:session 稳定到 waiting_input 时,若缓存了 runtime_capabilities
1729
- // 但尚未被清除(说明第一次发送可能因 runtime_session_id 未及时更新而被服务端丢弃),
1730
- // 在此重发确保前端最终能收到能力清单。
1731
- if (runtimeState.status === "waiting_input" &&
1732
- entry.cachedCapabilitiesActionDetail) {
1733
- const cachedDetail = entry.cachedCapabilitiesActionDetail;
1734
- entry.cachedCapabilitiesActionDetail = undefined;
1735
- console.log(`[${definition.displayName} Adapter] Re-sending cached runtime_capabilities for agent ${entry.agentId} (session stabilized to waiting_input)`);
1736
- void sendStatus(entry.agentId, sessionId, "waiting_input", {
1737
- actionType: "runtime_capabilities",
1738
- actionLabel: "运行时能力",
1739
- actionDetail: cachedDetail,
1740
- }, undefined, {
1741
- runtimeAccessToken: entry.runtimeAccessToken,
1742
- schedulerBaseUrl: entry.schedulerBaseUrl,
1743
- });
1744
- }
1745
- }
1746
- });
1747
- adapter.on("ask-user-question", (data) => {
1748
- const entry = sdkSessions.get(data.sessionId);
1749
- if (entry) {
1750
- console.log(`[${definition.displayName} Adapter] AskUserQuestion for agent ${entry.agentId}`);
1751
- void sendQuestionRequest(entry.agentId, data.sessionId, Array.isArray(data.questions) ? data.questions : [], {
1752
- runtimeAccessToken: entry.runtimeAccessToken,
1753
- schedulerBaseUrl: entry.schedulerBaseUrl,
1754
- }, data.toolCallId);
1755
- }
1756
- });
1757
- adapter.on("permission-request", (data) => {
1758
- const entry = sdkSessions.get(data.sessionId);
1759
- if (entry) {
1760
- console.log(`[${definition.displayName} Adapter] Permission request: ${data.toolName} for agent ${entry.agentId}`);
1761
- const actionDetail = data.toolInput
1762
- ? redactTransientClipboardPastePaths(JSON.stringify(data.toolInput).slice(0, 400))
1763
- : undefined;
1764
- void sendStatus(entry.agentId, data.sessionId, "waiting_input", {
1765
- actionType: "other",
1766
- actionLabel: "权限确认",
1767
- actionDetail: actionDetail || data.toolName,
1768
- }, undefined, {
1769
- runtimeAccessToken: entry.runtimeAccessToken,
1770
- schedulerBaseUrl: entry.schedulerBaseUrl,
1771
- });
1772
- }
1773
- });
1774
- }
1775
- /**
1776
- * 启动 SDK Agent 会话
1777
- * POST /runtime/start
1778
- */
1779
- terminalRouter.post("/start", validateToken, async (req, res) => {
1780
- const { agentId, workspacePath, mode = "sdk" } = req.body || {};
1781
- if (!agentId || !workspacePath) {
1782
- res.status(400).json({ error: "agentId and workspacePath are required" });
1783
- return;
1784
- }
1785
- // ★ 多 autoRegisterTargets 兜底:launch body 未带 schedulerBaseUrl 时,按优先级
1786
- // 回退识别来源 server,避免 resolveSchedulerBaseUrlFrom 在多 target 时落到
1787
- // DEFAULT=localhost:8080 导致回调打错服务器(实测会引发 401 死循环)。
1788
- // 1) body.schedulerBaseUrl(server 显式配置,最高优先级)
1789
- // 2) x-bridge-tunnel-source 头(隧道客户端注入,隧道模式下确定性可靠)
1790
- // 3) 入站 X-Runtime-Token 反查 scoped store(直连 HTTP 回退,可能因 server
1791
- // 端 rotate 导致 token 失配而返回 undefined)
1792
- // 注意:body.serverUrl 是 WS 地址(toAgentWebSocketUrl),不能当 HTTP 回调地址。
1793
- const resolvedSchedulerBaseUrl = String(req.body?.schedulerBaseUrl || "").trim() ||
1794
- String(req.header("x-bridge-tunnel-source") || "").trim() ||
1795
- resolveSchedulerBaseUrlFromToken(extractIncomingRuntimeToken(req)) ||
1796
- undefined;
1797
- enqueueMcpLaunchBinding({
1798
- agentId: String(agentId),
1799
- workspacePath: String(workspacePath),
1800
- serverUrl: req.body?.serverUrl || resolvedSchedulerBaseUrl,
1801
- runtimeAccessToken: req.body?.runtimeAccessToken,
1802
- userId: req.body?.userId,
1803
- schedulerBaseUrl: resolvedSchedulerBaseUrl,
1804
- });
1805
- if (mode && mode !== "sdk") {
1806
- console.warn(`[Runtime] Unsupported runtime mode "${mode}" requested for agent ${agentId}; starting SDK session instead.`);
1807
- }
1808
- await startSdkSession(req, res);
1809
- });
1810
- /**
1811
- * 启动 SDK 会话(两阶段架构)
1812
- *
1813
- * 主流程:
1814
- * 1. Phase 1(同步,快):校验 → 创建会话记录 → 立即响应(status=starting)
1815
- * 2. Phase 2(后台异步,慢):SDK 进程启动 → PID 探测 → 持久化 → 通知 server 会话就绪
1816
- *
1817
- * 此架构确保 bridge HTTP 响应不等待 SDK 子进程初始化,
1818
- * 前端可在数百毫秒内收到响应,实时展示启动进度。
1819
- */
1820
- async function startSdkSession(req, res) {
1821
- const { agentId, workspacePath, command, autoAccept = true, initialPrompt,
1822
- // 系统提示词(SDK 原生注入为 role=system,不再混入 initialPrompt)
1823
- systemPrompt,
1824
- // 三层 system messages(ACode 使用独立字段,三方 SDK 合并为 systemPrompt)
1825
- globalPrompt, rolePrompt, memory,
1826
- // MCP 配置 - 加载 aws-client-agent-mcp
1827
- mcpConfigPath, extraMcpServers, envOverrides,
1828
- // 空闲命令配置
1829
- idleInputAutoCommand, nonInputAutoCommand,
1830
- // Per-agent model override(优先于全局配置)
1831
- model: perAgentModel,
1832
- // Per-instance AI provider 覆盖(仅 ACode 消费;优先级:api > env > file > global)
1833
- instanceAiConfig: perInstanceAiConfig,
1834
- // Sub Agent 模型/强度覆盖(仅 ACode 消费,空值=继承主 Agent)
1835
- subAgentModel: perAgentSubAgentModel, subAgentEffort: perAgentSubAgentEffort, } = req.body || {};
1836
- const _dbg = getInstanceAiConfigDebugLogger();
1837
- void _dbg.log({
1838
- level: "info",
1839
- category: "launch",
1840
- message: "startSdkSession: 收到启动请求",
1841
- source: "bridge",
1842
- agentId: String(agentId || "").trim() || undefined,
1843
- details: {
1844
- reqBodyKeys: Object.keys(req.body || {}),
1845
- perInstanceAiConfig: perInstanceAiConfig ? redactForLog(perInstanceAiConfig) : null,
1846
- perAgentModel: perAgentModel ?? null,
1847
- },
1848
- });
1849
- let sessionId = null;
1850
- const normalizedAgentId = String(agentId || "").trim();
1851
- const runtimeAccessToken = String(req.body?.runtimeAccessToken || "").trim() || undefined;
1852
- // ★ body 未显式携带 schedulerBaseUrl 时,回退到隧道来源头 / 入站 token 反查,
1853
- // 确保回调打到签发该 token 的调度中心(详见 /start handler 注释)。
1854
- const requestedSchedulerBaseUrl = String(req.body?.schedulerBaseUrl || "").trim() ||
1855
- String(req.header("x-bridge-tunnel-source") || "").trim() ||
1856
- resolveSchedulerBaseUrlFromToken(extractIncomingRuntimeToken(req)) ||
1857
- undefined;
1858
- if (requestedSchedulerBaseUrl) {
1859
- log.info(`[startSdkSession] 回调目标 server: ${requestedSchedulerBaseUrl} (agentId=${normalizedAgentId})`);
1860
- }
1861
- else {
1862
- log.warn(`[startSdkSession] 未解析到 schedulerBaseUrl,将回退到默认地址 (agentId=${normalizedAgentId})`);
1863
- }
1864
- try {
1865
- ensureAdapterInitialized();
1866
- if (!normalizedAgentId) {
1867
- res.status(400).json({ error: "agentId is required" });
1868
- return;
1869
- }
1870
- const activeSession = findActiveSdkSessionByAgentId(normalizedAgentId);
1871
- if (activeSession) {
1872
- res.json(await buildExistingSessionResponse(activeSession));
1873
- return;
1874
- }
1875
- const persistedSession = await findPersistedSessionByAgentId(normalizedAgentId);
1876
- if (persistedSession) {
1877
- const reuseDecision = evaluatePersistedSessionReuse(persistedSession);
1878
- if (!reuseDecision.reusable) {
1879
- console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, ` +
1880
- `sessionId=${persistedSession.sessionId}, reason=${reuseDecision.reason}`);
1881
- await removePersistedSession(persistedSession.sessionId);
1882
- }
1883
- else {
1884
- const displayStatus = resolveSdkSessionDisplayStatus(persistedSession.runtimeStatus, persistedSession.runtimeStatus, persistedSession.runtimeActionType);
1885
- res.json({
1886
- sessionId: persistedSession.sessionId,
1887
- status: displayStatus,
1888
- runtimeStatus: displayStatus,
1889
- runtimeActionType: persistedSession.runtimeActionType,
1890
- runtimeActionLabel: persistedSession.runtimeActionLabel,
1891
- runtimeActionDetail: persistedSession.runtimeActionDetail,
1892
- agentId: persistedSession.agentId,
1893
- workspacePath: persistedSession.workspacePath,
1894
- command: persistedSession.command,
1895
- mode: persistedSession.mode || "sdk",
1896
- pid: persistedSession.pid,
1897
- reused: true,
1898
- persistedOnly: true,
1899
- });
1900
- return;
1901
- }
1902
- }
1903
- if (startingAgents.has(normalizedAgentId)) {
1904
- res
1905
- .status(409)
1906
- .json({ error: "SDK session is already starting for this agent" });
1907
- return;
1908
- }
1909
- startingAgents.add(normalizedAgentId);
1910
- // 根据命令选择对应的 adapter
1911
- const normalizedCommand = (command || "acode").toLowerCase();
1912
- const providerId = resolveSdkProviderId(command);
1913
- const adapter = adapterRegistry.get(providerId);
1914
- sessionId = uuidv4();
1915
- const savedClaudeConfig = providerId === "claude-code"
1916
- ? await readSavedClaudeCodeLaunchConfig()
1917
- : null;
1918
- const claudeLaunchConfig = providerId === "claude-code"
1919
- ? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
1920
- : buildClaudeCodeLaunchConfig(null, envOverrides);
1921
- // 模型解析优先级(ACode 与非 ACode 不同):
1922
- //
1923
- // ACode:模型仲裁收敛到 AcodeSdkAdapter.resolveAcodeProvider → resolveAcodeProviderConfig 纯函数
1924
- // 优先级:perSessionModel(request) > instanceAiConfig(instance) > acode.json(global) > default
1925
- // 这里不预解析,将原始输入传递给 adapter
1926
- //
1927
- // 非 ACode (claude-code/codex/opencode):
1928
- // 优先级:perAgentModel(request) > claudeLaunchConfig.model(global)
1929
- // 通过 ANTHROPIC_MODEL/CODEX_MODEL 环境变量注入
1930
- const resolvedModel = providerId === "acode"
1931
- ? (perAgentModel ? String(perAgentModel).trim() : undefined)
1932
- : (() => {
1933
- const requestModel = perAgentModel ? String(perAgentModel).trim() : undefined;
1934
- const globalModel = claudeLaunchConfig.model ? String(claudeLaunchConfig.model).trim() : undefined;
1935
- return requestModel || globalModel;
1936
- })();
1937
- void _dbg.log({
1938
- level: "info",
1939
- category: "resolve",
1940
- message: "模型解析结果",
1941
- source: "bridge",
1942
- agentId: normalizedAgentId,
1943
- sessionId: sessionId ?? undefined,
1944
- details: {
1945
- perAgentModel: perAgentModel ?? null,
1946
- globalModel: claudeLaunchConfig.model ?? null,
1947
- resolvedModel: resolvedModel ?? null,
1948
- providerId,
1949
- note: providerId === "acode" ? "adapter handles full priority" : "request > global",
1950
- },
1951
- });
1952
- // 构建 effectiveEnvOverrides:将 resolvedModel 注入各 provider 对应的环境变量
1953
- const effectiveEnvOverrides = { ...claudeLaunchConfig.envOverrides };
1954
- if (resolvedModel) {
1955
- // Claude Code 使用 ANTHROPIC_MODEL
1956
- effectiveEnvOverrides.ANTHROPIC_MODEL = resolvedModel;
1957
- // Codex 使用 CODEX_MODEL
1958
- effectiveEnvOverrides.CODEX_MODEL = resolvedModel;
1959
- // ACode / OpenCode: 不通过 env var 传递模型
1960
- }
1961
- // ACode 实例自配置:解析 api > env > file,deep-merge 全局后写入 per-instance acode.json
1962
- // 其它 provider 不消费 instanceAiConfig,保持向后兼容
1963
- // 模型仲裁由 AcodeSdkAdapter.resolveAcodeProvider 统一处理
1964
- let resolvedInstanceAiConfig;
1965
- let resolvedMcpConfigPath = mcpConfigPath;
1966
- if (providerId === "acode") {
1967
- const mergedInstanceConfig = perInstanceAiConfig || {};
1968
- const prepared = await prepareAcodeInstanceConfig({
1969
- apiOverrides: sanitizeInstanceAiConfig(mergedInstanceConfig) ?? undefined,
1970
- agentId: normalizedAgentId,
1971
- sessionId: sessionId ?? undefined,
1972
- runtimeBridgeBaseUrl: requestedSchedulerBaseUrl,
1973
- });
1974
- resolvedInstanceAiConfig = prepared.instanceConfig.config;
1975
- resolvedMcpConfigPath = prepared.perInstanceConfigPath;
1976
- void _dbg.log({
1977
- level: "info",
1978
- category: "apply",
1979
- message: "prepareAcodeInstanceConfig 完成",
1980
- source: "bridge",
1981
- agentId: normalizedAgentId,
1982
- sessionId: sessionId ?? undefined,
1983
- details: {
1984
- sanitizedApiOverrides: sanitizeInstanceAiConfig(mergedInstanceConfig) ? redactForLog(sanitizeInstanceAiConfig(mergedInstanceConfig)) : null,
1985
- resolvedInstanceAiConfig: resolvedInstanceAiConfig ? redactForLog(resolvedInstanceAiConfig) : null,
1986
- perInstanceConfigPath: resolvedMcpConfigPath,
1987
- instanceConfigSources: prepared.instanceConfig.source ?? null,
1988
- },
1989
- });
1990
- }
1991
- // 三层 system messages 路由:
1992
- // ACode → 作为独立字段传入,runtime 构造三条 system 消息
1993
- // 三方 SDK → 合并到 systemPrompt 一条字符串
1994
- let resolvedSystemPrompt = systemPrompt || undefined;
1995
- let resolvedGlobalPrompt;
1996
- let resolvedRolePrompt;
1997
- let resolvedMemory;
1998
- if (providerId === "acode") {
1999
- // ACode:分传三个字段(memory 自动从工作区加载)
2000
- resolvedGlobalPrompt = typeof globalPrompt === "string" ? globalPrompt.trim() || undefined : undefined;
2001
- resolvedRolePrompt = typeof rolePrompt === "string" ? rolePrompt.trim() || undefined : undefined;
2002
- if (typeof memory === "string" && memory.trim()) {
2003
- resolvedMemory = memory.trim();
2004
- }
2005
- else {
2006
- // 未显式传入 memory 时,从工作区自动加载短期记忆和近期记忆
2007
- const autoMemory = await loadAgentStartupMemories(workspacePath, {
2008
- schedulerBaseUrl: requestedSchedulerBaseUrl,
2009
- runtimeAccessToken,
2010
- agentId: normalizedAgentId,
2011
- });
2012
- if (autoMemory) {
2013
- resolvedMemory = autoMemory;
2014
- log.info(`[startSdkSession] Auto-loaded memories for ACode agent ${normalizedAgentId}`);
2015
- }
2016
- }
2017
- }
2018
- else {
2019
- // 三方 SDK:如果传入了结构化字段则合并到 systemPrompt
2020
- const hasExtraPrompts = typeof globalPrompt === "string" && globalPrompt.trim() ||
2021
- typeof rolePrompt === "string" && rolePrompt.trim() ||
2022
- typeof memory === "string" && memory.trim();
2023
- if (hasExtraPrompts) {
2024
- const mergedParts = [
2025
- globalPrompt,
2026
- rolePrompt,
2027
- memory,
2028
- systemPrompt,
2029
- ]
2030
- .filter((p) => typeof p === "string" && p.trim().length > 0)
2031
- .map((p) => p.trim());
2032
- resolvedSystemPrompt = mergedParts.length > 0 ? mergedParts.join("\n\n") : undefined;
2033
- }
2034
- }
2035
- // 构建SDK配置
2036
- const config = {
2037
- command: command || "acode",
2038
- workingDirectory: workspacePath,
2039
- agentId: normalizedAgentId,
2040
- autoAccept: Boolean(autoAccept),
2041
- initialPrompt,
2042
- systemPrompt: resolvedSystemPrompt,
2043
- ...(resolvedGlobalPrompt !== undefined ? { globalPrompt: resolvedGlobalPrompt } : {}),
2044
- ...(resolvedRolePrompt !== undefined ? { rolePrompt: resolvedRolePrompt } : {}),
2045
- ...(resolvedMemory !== undefined ? { memory: resolvedMemory } : {}),
2046
- model: resolvedModel,
2047
- // MCP 配置:不再默认注入 aws-mcp;由用户在面板中安装/配置
2048
- // ACode 场景下,per-instance acode.json 已由 instance-ai-config-service 写入
2049
- mcpConfigPath: resolvedMcpConfigPath,
2050
- // ACode 实例自配置:来源与值会写入 d:/Instance-ai-configuration-debug.log
2051
- ...(resolvedInstanceAiConfig
2052
- ? { instanceAiConfig: resolvedInstanceAiConfig }
2053
- : {}),
2054
- // Sub Agent 模型/强度覆盖(仅 ACode 消费)
2055
- ...(typeof perAgentSubAgentModel === "string" && perAgentSubAgentModel.trim()
2056
- ? { subAgentModel: perAgentSubAgentModel.trim() }
2057
- : {}),
2058
- ...(typeof perAgentSubAgentEffort === "string" && perAgentSubAgentEffort.trim()
2059
- ? { subAgentEffort: perAgentSubAgentEffort.trim() }
2060
- : {}),
2061
- extraMcpServers: extraMcpServers || {},
2062
- envOverrides: buildRuntimeEnv(String(agentId), String(workspacePath), process.env, effectiveEnvOverrides),
2063
- };
2064
- void _dbg.log({
2065
- level: "info",
2066
- category: "launch",
2067
- message: "传递给 Adapter 的完整 AdapterSessionConfig",
2068
- source: "bridge",
2069
- agentId: normalizedAgentId,
2070
- sessionId,
2071
- details: {
2072
- adapterModel: config.model ?? null,
2073
- adapterInstanceAiConfig: config.instanceAiConfig ? redactForLog(config.instanceAiConfig) : null,
2074
- adapterMcpConfigPath: config.mcpConfigPath ?? null,
2075
- },
2076
- });
2077
- await _dbg.flush();
2078
- // 存储会话信息
2079
- sdkSessions.set(sessionId, {
2080
- agentId: normalizedAgentId,
2081
- workspacePath,
2082
- sessionId,
2083
- config,
2084
- providerId, // 记录使用的 provider
2085
- seq: 0,
2086
- runtimeAccessToken,
2087
- schedulerBaseUrl: requestedSchedulerBaseUrl,
2088
- });
2089
- // ★ Phase 1:立即响应(不等待 SDK 进程启动)
2090
- res.json({
2091
- sessionId,
2092
- status: "starting",
2093
- runtimeStatus: "starting",
2094
- agentId: normalizedAgentId,
2095
- workspacePath,
2096
- command: config.command,
2097
- mode: "sdk",
2098
- });
2099
- // ★ Phase 2:后台异步启动 SDK 进程 + 后处理
2100
- startSdkSessionBg({
2101
- sessionId,
2102
- agentId: normalizedAgentId,
2103
- config,
2104
- adapter,
2105
- providerId,
2106
- idleInputAutoCommand,
2107
- nonInputAutoCommand,
2108
- runtimeAccessToken,
2109
- requestedSchedulerBaseUrl,
2110
- });
2111
- }
2112
- catch (error) {
2113
- // 同步错误处理(校验失败等)
2114
- if (sessionId) {
2115
- stopStatusRefresh(sessionId);
2116
- sdkSessions.delete(sessionId);
2117
- }
2118
- if (normalizedAgentId) {
2119
- startingAgents.delete(normalizedAgentId);
2120
- }
2121
- if (!res.headersSent) {
2122
- const errorMessage = error instanceof Error ? error.message : String(error);
2123
- res
2124
- .status(500)
2125
- .json({ error: `Failed to start SDK session: ${errorMessage}` });
2126
- }
2127
- }
2128
- }
2129
- /**
2130
- * Phase 2:后台异步启动 SDK 进程,完成后通知 server。
2131
- *
2132
- * 主流程:adapter.startSession(SDK 进程启动)→ 空闲命令注入 → PID 探测
2133
- * → 持久化会话 → 注册进程管理 → 通知 server 状态为 running
2134
- * → finally 清理 startingAgents 锁
2135
- */
2136
- async function startSdkSessionBg(params) {
2137
- const { sessionId, agentId, config, adapter, providerId, idleInputAutoCommand, nonInputAutoCommand, runtimeAccessToken, requestedSchedulerBaseUrl, } = params;
2138
- try {
2139
- // 启动 SDK 会话(真正的耗时操作)
2140
- await adapter.startSession(sessionId, config);
2141
- // 尽早注入空闲命令
2142
- if (idleInputAutoCommand || nonInputAutoCommand) {
2143
- adapter.setIdleCommands?.(sessionId, {
2144
- idleInputCommand: idleInputAutoCommand || "",
2145
- nonInputCommand: nonInputAutoCommand || "",
2146
- });
2147
- }
2148
- // ★ 尝试获取 SDK 进程的 PID(用于孤儿进程检测)
2149
- let sdkPid;
2150
- if (shouldProbeSdkProcessPid(providerId)) {
2151
- try {
2152
- await new Promise((resolve) => setTimeout(resolve, 500));
2153
- sdkPid = adapter.getSessionPid?.(sessionId);
2154
- if (!sdkPid) {
2155
- const processes = await findClaudeCodeProcesses();
2156
- const matched = processes.find((p) => p.agentId === agentId ||
2157
- p.cwd === config.workingDirectory ||
2158
- p.command.includes(config.workingDirectory));
2159
- if (matched) {
2160
- sdkPid = matched.pid;
2161
- console.log(`[Runtime] Found SDK process PID: ${sdkPid} for agentId=${agentId}`);
2162
- }
2163
- }
2164
- }
2165
- catch (pidError) {
2166
- console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
2167
- }
2168
- }
2169
- // ★ 持久化会话信息(包含 PID)
2170
- const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus, sdkSessions.get(sessionId)?.currentRuntimeActionType);
2171
- await upsertPersistedSession({
2172
- sessionId,
2173
- agentId,
2174
- workspacePath: config.workingDirectory,
2175
- command: config.command,
2176
- startedAt: new Date().toISOString(),
2177
- status: "running",
2178
- runtimeStatus: startedStatus,
2179
- mode: "sdk",
2180
- pid: sdkPid,
2181
- providerSessionId: adapter.getProviderSessionId(sessionId),
2182
- });
2183
- // ★ 注册到进程管理器(如果有 PID)
2184
- if (sdkPid) {
2185
- const processManager = getAgentProcessManager();
2186
- await processManager.startProcess({
2187
- agentId,
2188
- sessionId,
2189
- pid: sdkPid,
2190
- mode: "sdk",
2191
- workspacePath: config.workingDirectory,
2192
- command: config.command,
2193
- });
2194
- }
2195
- // ★ 通知 server 会话已就绪
2196
- await notifyServerSessionReady({
2197
- agentId,
2198
- sessionId,
2199
- sdkPid,
2200
- status: startedStatus,
2201
- schedulerBaseUrl: requestedSchedulerBaseUrl,
2202
- runtimeAccessToken,
2203
- });
2204
- }
2205
- catch (error) {
2206
- // 后台启动失败:清理本地状态 + 通知 server
2207
- stopStatusRefresh(sessionId);
2208
- sdkSessions.delete(sessionId);
2209
- await removePersistedSession(sessionId);
2210
- const errorMessage = error instanceof Error ? error.message : String(error);
2211
- console.error(`[Runtime] Background SDK session startup failed for agentId=${agentId}: ${errorMessage}`);
2212
- await notifyServerSessionFailed({
2213
- agentId,
2214
- sessionId,
2215
- schedulerBaseUrl: requestedSchedulerBaseUrl,
2216
- runtimeAccessToken,
2217
- errorMessage,
2218
- });
2219
- }
2220
- finally {
2221
- startingAgents.delete(agentId);
2222
- }
2223
- }
2224
- /**
2225
- * 通知调度中心 SDK 会话已完全就绪。
2226
- * 主流程:Phase 2 完成后调用,复用 /callback/status 端点更新状态为 running。
2227
- * Fire-and-forget 风格,失败不影响 bridge 自身状态。
2228
- */
2229
- async function notifyServerSessionReady(params) {
2230
- const { agentId, sessionId, sdkPid, status, schedulerBaseUrl, runtimeAccessToken } = params;
2231
- if (!schedulerBaseUrl || !runtimeAccessToken) {
2232
- log.debug(`[Runtime] notifyServerSessionReady skipped: missing schedulerBaseUrl or runtimeAccessToken for agentId=${agentId}`);
2233
- return;
2234
- }
2235
- const url = `${schedulerBaseUrl.replace(/\/+$/, "")}/api/runtime/callback/status`;
2236
- try {
2237
- await axios.post(url, { agentId, sessionId, status, pid: sdkPid }, {
2238
- headers: {
2239
- "X-Runtime-Token": runtimeAccessToken,
2240
- "Content-Type": "application/json",
2241
- },
2242
- timeout: 5000,
2243
- });
2244
- log.debug(`[Runtime] Notified server session ready: agentId=${agentId}, sessionId=${sessionId}, status=${status}`);
2245
- }
2246
- catch (err) {
2247
- const message = err instanceof Error ? err.message : String(err);
2248
- log.warn(`[Runtime] Failed to notify server session ready: agentId=${agentId}, error=${message}`);
2249
- }
2250
- }
2251
- /**
2252
- * 通知调度中心 SDK 会话启动失败。
2253
- * 主流程:Phase 2 catch 块调用,复用 /callback/status 端点更新状态为 stopped。
2254
- */
2255
- async function notifyServerSessionFailed(params) {
2256
- const { agentId, sessionId, schedulerBaseUrl, runtimeAccessToken } = params;
2257
- if (!schedulerBaseUrl || !runtimeAccessToken) {
2258
- return;
2259
- }
2260
- const url = `${schedulerBaseUrl.replace(/\/+$/, "")}/api/runtime/callback/status`;
2261
- try {
2262
- await axios.post(url, { agentId, sessionId, status: "stopped" }, {
2263
- headers: {
2264
- "X-Runtime-Token": runtimeAccessToken,
2265
- "Content-Type": "application/json",
2266
- },
2267
- timeout: 5000,
2268
- });
2269
- log.debug(`[Runtime] Notified server session failed: agentId=${agentId}, sessionId=${sessionId}`);
2270
- }
2271
- catch (err) {
2272
- const message = err instanceof Error ? err.message : String(err);
2273
- log.warn(`[Runtime] Failed to notify server session failed: agentId=${agentId}, error=${message}`);
2274
- }
2275
- }
2276
- // ============ SDK 专用路由 ============
2277
- /**
2278
- * 发送消息到 SDK 会话
2279
- * POST /runtime/sdk/message
2280
- */
2281
- terminalRouter.post("/sdk/message", validateToken, async (req, res) => {
2282
- const { sessionId, message } = req.body || {};
2283
- if (!sessionId || !message) {
2284
- res.status(400).json({ error: "sessionId and message are required" });
2285
- return;
2286
- }
2287
- const entry = sdkSessions.get(sessionId);
2288
- if (!entry) {
2289
- res.status(404).json({ error: "SDK session not found" });
2290
- return;
2291
- }
2292
- try {
2293
- // 根据 providerId 获取对应的 adapter
2294
- const providerId = entry.providerId || "claude-code";
2295
- const adapter = adapterRegistry.get(providerId);
2296
- await adapter.sendMessage(sessionId, message);
2297
- res.json({ ok: true });
2298
- }
2299
- catch (error) {
2300
- const errorMessage = error instanceof Error ? error.message : String(error);
2301
- res.status(500).json({ error: errorMessage });
2302
- }
2303
- });
2304
- /**
2305
- * 回答 AskUserQuestion
2306
- * POST /runtime/sdk/answer
2307
- */
2308
- terminalRouter.post("/sdk/answer", validateToken, async (req, res) => {
2309
- const { sessionId, answers } = req.body || {};
2310
- if (!sessionId || !answers) {
2311
- res.status(400).json({ error: "sessionId and answers are required" });
2312
- return;
2313
- }
2314
- const entry = sdkSessions.get(sessionId);
2315
- if (!entry) {
2316
- res.status(404).json({ error: "SDK session not found" });
2317
- return;
2318
- }
2319
- try {
2320
- // 根据 providerId 获取对应的 adapter
2321
- const providerId = entry.providerId || "claude-code";
2322
- const adapter = adapterRegistry.get(providerId);
2323
- if (adapter.sendQuestionAnswer) {
2324
- await adapter.sendQuestionAnswer(sessionId, answers);
2325
- }
2326
- res.json({ ok: true });
2327
- }
2328
- catch (error) {
2329
- const errorMessage = error instanceof Error ? error.message : String(error);
2330
- res.status(500).json({ error: errorMessage });
2331
- }
2332
- });
2333
- /**
2334
- * 确认权限请求
2335
- * POST /runtime/sdk/confirm
2336
- */
2337
- terminalRouter.post("/sdk/confirm", validateToken, async (req, res) => {
2338
- const { sessionId, accept } = req.body || {};
2339
- if (!sessionId) {
2340
- res.status(400).json({ error: "sessionId is required" });
2341
- return;
2342
- }
2343
- const entry = sdkSessions.get(sessionId);
2344
- if (!entry) {
2345
- res.status(404).json({ error: "SDK session not found" });
2346
- return;
2347
- }
2348
- try {
2349
- // 根据 providerId 获取对应的 adapter
2350
- const providerId = entry.providerId || "claude-code";
2351
- const adapter = adapterRegistry.get(providerId);
2352
- await adapter.sendConfirmation(sessionId, Boolean(accept));
2353
- res.json({ ok: true });
2354
- }
2355
- catch (error) {
2356
- const errorMessage = error instanceof Error ? error.message : String(error);
2357
- res.status(500).json({ error: errorMessage });
2358
- }
2359
- });
2360
- /**
2361
- * 中止当前轮次
2362
- * POST /runtime/sdk/abort
2363
- */
2364
- terminalRouter.post("/sdk/abort", validateToken, async (req, res) => {
2365
- const { sessionId } = req.body || {};
2366
- if (!sessionId) {
2367
- res.status(400).json({ error: "sessionId is required" });
2368
- return;
2369
- }
2370
- const entry = sdkSessions.get(sessionId);
2371
- if (!entry) {
2372
- res.status(404).json({ error: "SDK session not found" });
2373
- return;
2374
- }
2375
- try {
2376
- // 根据 providerId 获取对应的 adapter
2377
- const providerId = entry.providerId || "claude-code";
2378
- const adapter = adapterRegistry.get(providerId);
2379
- await adapter.abortCurrentTurn(sessionId);
2380
- res.json({ ok: true });
2381
- }
2382
- catch (error) {
2383
- const errorMessage = error instanceof Error ? error.message : String(error);
2384
- res.status(500).json({ error: errorMessage });
2385
- }
2386
- });
2387
- /**
2388
- * 停止 SDK 会话
2389
- * POST /runtime/sdk/stop
2390
- */
2391
- terminalRouter.post("/sdk/stop", validateToken, async (req, res) => {
2392
- const { sessionId } = req.body || {};
2393
- if (!sessionId) {
2394
- res.status(400).json({ error: "sessionId is required" });
2395
- return;
2396
- }
2397
- const entry = sdkSessions.get(sessionId);
2398
- if (!entry) {
2399
- res.json({ ok: true, status: "stopped" });
2400
- return;
2401
- }
2402
- try {
2403
- // 根据 providerId 获取对应的 adapter
2404
- const providerId = entry.providerId || "claude-code";
2405
- const adapter = adapterRegistry.get(providerId);
2406
- // 如果有活跃 AI 请求,先立即中止(fire-and-forget),再清理资源
2407
- adapter.abortCurrentTurn(sessionId).catch(() => { });
2408
- await adapter.terminateSession(sessionId);
2409
- stopStatusRefresh(sessionId);
2410
- sdkSessions.delete(sessionId);
2411
- stopTerminalCommandProcess(sessionId);
2412
- await removePersistedSession(sessionId);
2413
- // ★ 从进程管理器中移除
2414
- const processManager = getAgentProcessManager();
2415
- await processManager.removeProcess(entry.agentId);
2416
- await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
2417
- runtimeAccessToken: entry.runtimeAccessToken,
2418
- schedulerBaseUrl: entry.schedulerBaseUrl,
2419
- });
2420
- res.json({ ok: true, status: "stopped" });
2421
- }
2422
- catch (error) {
2423
- const errorMessage = error instanceof Error ? error.message : String(error);
2424
- res.status(500).json({ error: errorMessage });
2425
- }
2426
- });
2427
- /**
2428
- * 获取 SDK 会话状态
2429
- * GET /runtime/sdk/status/:sessionId
2430
- */
2431
- terminalRouter.get("/sdk/status/:sessionId", validateToken, (req, res) => {
2432
- const sessionId = req.params.sessionId;
2433
- if (!sessionId) {
2434
- res.status(400).json({ error: "sessionId is required" });
2435
- return;
2436
- }
2437
- const entry = sdkSessions.get(sessionId);
2438
- if (!entry) {
2439
- res.status(404).json({ error: "SDK session not found" });
2440
- return;
2441
- }
2442
- try {
2443
- // 根据 providerId 获取对应的 adapter
2444
- const providerId = entry.providerId || "claude-code";
2445
- const adapter = adapterRegistry.get(providerId);
2446
- const status = adapter.getSessionStatus(sessionId);
2447
- const conversation = adapter.getConversation(sessionId);
2448
- res.json({
2449
- sessionId,
2450
- status,
2451
- agentId: entry.agentId,
2452
- workspacePath: entry.workspacePath,
2453
- messageCount: conversation.length,
2454
- });
2455
- }
2456
- catch (error) {
2457
- const errorMessage = error instanceof Error ? error.message : String(error);
2458
- res.status(500).json({ error: errorMessage });
2459
- }
2460
- });
2461
- /**
2462
- * GET /runtime/sdk/capabilities/:sessionId
2463
- *
2464
- * 主动查询会话的运行时能力清单(MCP 工具 + skills)。
2465
- * 只读 adapter 内存缓存(lastCombinedCapabilities),不触发 runtime 重新探测。
2466
- * 缓存由 mcp_status / skill_status 事件维护;若尚未收到任何事件则返回 capabilities: null。
2467
- */
2468
- terminalRouter.get("/sdk/capabilities/:sessionId", validateToken, (req, res) => {
2469
- const sessionId = req.params.sessionId;
2470
- if (!sessionId) {
2471
- res.status(400).json({ error: "sessionId is required" });
2472
- return;
2473
- }
2474
- const entry = sdkSessions.get(sessionId);
2475
- if (!entry) {
2476
- res.status(404).json({ error: "SDK session not found" });
2477
- return;
2478
- }
2479
- try {
2480
- const providerId = entry.providerId || "claude-code";
2481
- const adapter = adapterRegistry.get(providerId);
2482
- // 仅 AcodeSdkAdapter 实现 getCapabilities;其他 adapter 返回 undefined
2483
- const capabilities = adapter.getCapabilities?.(sessionId) ?? null;
2484
- res.json({
2485
- ok: true,
2486
- sessionId,
2487
- agentId: entry.agentId,
2488
- capabilities,
2489
- fetchedAt: Date.now(),
2490
- });
2491
- }
2492
- catch (error) {
2493
- const errorMessage = error instanceof Error ? error.message : String(error);
2494
- res.status(500).json({ error: errorMessage });
2495
- }
2496
- });
2497
- /**
2498
- * 查询会话当前挂起的 ask_user_question(agent 提问)。
2499
- *
2500
- * 只读 adapter 内存缓存(pendingQuestionToolCallId + pendingQuestions)。
2501
- * 用于前端刷新后重建提问 UI:bridge 在 session entry 中保留了最近一次提问的
2502
- * toolCallId 与 questions 数组,前端刷新后通过此端点取回并重建 directMessages
2503
- * 与 header 通知。toolCallId 封闭在 bridge 内部,前端无需感知。
2504
- *
2505
- * GET /runtime/sdk/pending-questions/:sessionId
2506
- */
2507
- terminalRouter.get("/sdk/pending-questions/:sessionId", validateToken, (req, res) => {
2508
- const sessionId = req.params.sessionId;
2509
- if (!sessionId) {
2510
- res.status(400).json({ error: "sessionId is required" });
2511
- return;
2512
- }
2513
- const entry = sdkSessions.get(sessionId);
2514
- if (!entry) {
2515
- res.status(404).json({ error: "SDK session not found" });
2516
- return;
2517
- }
2518
- try {
2519
- const providerId = entry.providerId || "claude-code";
2520
- const adapter = adapterRegistry.get(providerId);
2521
- // 仅 AcodeSdkAdapter 实现 getPendingQuestion;其他 adapter 返回 undefined
2522
- const pendingQuestion = adapter.getPendingQuestion?.(sessionId) ?? null;
2523
- res.json({
2524
- ok: true,
2525
- sessionId,
2526
- agentId: entry.agentId,
2527
- pendingQuestion: pendingQuestion
2528
- ? {
2529
- toolCallId: pendingQuestion.toolCallId,
2530
- questions: pendingQuestion.questions,
2531
- }
2532
- : null,
2533
- fetchedAt: Date.now(),
2534
- });
2535
- }
2536
- catch (error) {
2537
- const errorMessage = error instanceof Error ? error.message : String(error);
2538
- res.status(500).json({ error: errorMessage });
2539
- }
2540
- });
2541
- /**
2542
- * 执行当前 Agent 工作目录下的终端命令。
2543
- *
2544
- * 主流程:校验 session/command -> 读取会话 workspacePath -> 在该 cwd 下启动 shell 命令 -> 通过终端输出回调流式返回。
2545
- * POST /runtime/command
2546
- */
2547
- terminalRouter.post("/command", validateToken, async (req, res) => {
2548
- const { sessionId, command } = req.body || {};
2549
- if (!sessionId || command === undefined || command === null) {
2550
- res.status(400).json({ error: "sessionId and command are required" });
2551
- return;
2552
- }
2553
- try {
2554
- const result = await executeTerminalCommand(String(sessionId), command);
2555
- res.json({ ok: true, mode: "terminal", ...result });
2556
- }
2557
- catch (error) {
2558
- const errorMessage = error instanceof Error ? error.message : String(error);
2559
- res.status(500).json({ error: errorMessage });
2560
- }
2561
- });
2562
- // ============ 兼容终端 UI 的 SDK 路由 ============
2563
- /**
2564
- * 发送消息到 SDK 会话。
2565
- *
2566
- * 主流程:校验 session/input -> 定位 SDK 会话 -> 通过 provider adapter 发送用户消息。
2567
- * POST /runtime/input
2568
- */
2569
- terminalRouter.post("/input", validateToken, async (req, res) => {
2570
- const { sessionId, input, mode } = req.body || {};
2571
- if (!sessionId || input === undefined || input === null) {
2572
- res.status(400).json({ error: "sessionId and input are required" });
2573
- return;
2574
- }
2575
- const entry = sdkSessions.get(sessionId);
2576
- if (!entry) {
2577
- res.status(404).json({ error: "SDK session not found" });
2578
- return;
2579
- }
2580
- try {
2581
- const providerId = entry.providerId || "claude-code";
2582
- const adapter = adapterRegistry.get(providerId);
2583
- if (mode === "shortcut") {
2584
- if (!adapter.sendShortcutInput) {
2585
- res.status(400).json({
2586
- error: "shortcut input is not supported by this SDK provider",
2587
- });
2588
- return;
2589
- }
2590
- await adapter.sendShortcutInput(sessionId, String(input));
2591
- res.json({ ok: true, mode: "shortcut" });
2592
- return;
2593
- }
2594
- await adapter.sendMessage(sessionId, await resolveSdkInputMessage(input));
2595
- res.json({ ok: true, mode: "sdk" });
2596
- }
2597
- catch (error) {
2598
- const errorMessage = error instanceof Error ? error.message : String(error);
2599
- res.status(500).json({ error: errorMessage });
2600
- }
2601
- });
2602
- /**
2603
- * 热更新运行中 SDK 会话的自动命令配置。
2604
- *
2605
- * 主流程:按 agentId 定位 SDK 会话 -> 更新 adapter 内存配置 -> 同步持久化会话字段。
2606
- * POST /runtime/update-auto-commands
2607
- */
2608
- terminalRouter.post("/update-auto-commands", validateToken, async (req, res) => {
2609
- const { agentId, idleInputAutoCommand = "", nonInputAutoCommand = "", } = req.body || {};
2610
- const normalizedAgentId = String(agentId || "").trim();
2611
- if (!normalizedAgentId) {
2612
- res.status(400).json({ error: "agentId is required" });
2613
- return;
2614
- }
2615
- const entry = [...sdkSessions.values()].find((session) => session.agentId === normalizedAgentId);
2616
- const commands = {
2617
- idleInputCommand: String(idleInputAutoCommand || ""),
2618
- nonInputCommand: String(nonInputAutoCommand || ""),
2619
- };
2620
- try {
2621
- let sessionUpdated = false;
2622
- if (entry) {
2623
- const adapter = adapterRegistry.get(entry.providerId || "claude-code");
2624
- adapter.setIdleCommands?.(entry.sessionId, commands);
2625
- sessionUpdated = true;
2626
- }
2627
- const persistedUpdated = await updatePersistedSessionAutoCommands(normalizedAgentId, {
2628
- idleInputAutoCommand: commands.idleInputCommand,
2629
- nonInputAutoCommand: commands.nonInputCommand,
2630
- });
2631
- res.json({ ok: true, mode: "sdk", sessionUpdated, persistedUpdated });
2632
- }
2633
- catch (error) {
2634
- const errorMessage = error instanceof Error ? error.message : String(error);
2635
- res.status(500).json({ ok: false, mode: "sdk", error: errorMessage });
2636
- }
2637
- });
2638
- /**
2639
- * 兼容终端尺寸调整 API。
2640
- *
2641
- * SDK 会话没有终端尺寸概念;保留该端点用于前端 xterm fit 回调,返回 no-op。
2642
- * POST /runtime/resize
2643
- */
2644
- terminalRouter.post("/resize", validateToken, (req, res) => {
2645
- const { sessionId, cols, rows } = req.body || {};
2646
- if (!sessionId || !cols || !rows) {
2647
- res.status(400).json({ error: "sessionId, cols, rows are required" });
2648
- return;
2649
- }
2650
- if (!sdkSessions.has(sessionId)) {
2651
- res.status(404).json({ error: "SDK session not found" });
2652
- return;
2653
- }
2654
- res.json({
2655
- ok: true,
2656
- mode: "sdk",
2657
- noop: true,
2658
- cols: Number(cols),
2659
- rows: Number(rows),
2660
- });
2661
- });
2662
- terminalRouter.post("/reset-context", validateToken, async (req, res) => {
2663
- const { sessionId, reason } = req.body || {};
2664
- if (!sessionId) {
2665
- res.status(400).json({ error: "sessionId is required" });
2666
- return;
2667
- }
2668
- const sdkEntry = sdkSessions.get(sessionId);
2669
- if (!sdkEntry) {
2670
- res.status(404).json({ error: "SDK session not found" });
2671
- return;
2672
- }
2673
- const providerId = sdkEntry.providerId || "claude-code";
2674
- const adapter = adapterRegistry.get(providerId);
2675
- const resetContext = adapter.resetContext?.bind(adapter);
2676
- const support = resolveRuntimeContextResetSupport(providerId, Boolean(resetContext));
2677
- if (!support.supported) {
2678
- res.status(support.statusCode).json({ error: support.error });
2679
- return;
2680
- }
2681
- if (!resetContext) {
2682
- res
2683
- .status(500)
2684
- .json({ error: "ACode adapter does not expose resetContext" });
2685
- return;
2686
- }
2687
- try {
2688
- await resetContext(String(sessionId));
2689
- const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(String(sessionId)), sdkEntry.currentRuntimeStatus, sdkEntry.currentRuntimeActionType);
2690
- res.json({
2691
- ok: true,
2692
- contextReset: true,
2693
- sessionId,
2694
- status,
2695
- runtimeStatus: status,
2696
- agentId: sdkEntry.agentId,
2697
- mode: "sdk",
2698
- providerId,
2699
- reason: typeof reason === "string" && reason.trim()
2700
- ? reason.trim()
2701
- : undefined,
2702
- });
2703
- }
2704
- catch (error) {
2705
- const errorMessage = error instanceof Error ? error.message : String(error);
2706
- res.status(500).json({
2707
- ok: false,
2708
- contextReset: false,
2709
- mode: "sdk",
2710
- error: errorMessage,
2711
- });
2712
- }
2713
- });
2714
- /**
2715
- * 停止 SDK 会话。
2716
- * POST /runtime/stop
2717
- */
2718
- terminalRouter.post("/stop", validateToken, async (req, res) => {
2719
- const { sessionId } = req.body || {};
2720
- const stopOptions = parseRuntimeStopOptions(req.body);
2721
- if (!sessionId) {
2722
- res.status(400).json({ error: "sessionId is required" });
2723
- return;
2724
- }
2725
- const sdkEntry = sdkSessions.get(sessionId);
2726
- if (!sdkEntry) {
2727
- await removePersistedSession(sessionId);
2728
- res.json({
2729
- ok: true,
2730
- status: "stopped",
2731
- mode: "none",
2732
- statusNotified: false,
2733
- reason: stopOptions.reason,
2734
- });
2735
- return;
2736
- }
2737
- const agentId = sdkEntry.agentId;
2738
- try {
2739
- console.log(`[Runtime] Stopping SDK session ${sessionId}; suppressStatus=${!stopOptions.statusNotified}, reason=${stopOptions.reason || "unspecified"}`);
2740
- const providerId = sdkEntry.providerId || "claude-code";
2741
- const adapter = adapterRegistry.get(providerId);
2742
- // 如果有活跃 AI 请求,先立即中止(fire-and-forget),再清理资源
2743
- adapter.abortCurrentTurn(sessionId).catch(() => { });
2744
- await adapter.terminateSession(sessionId);
2745
- stopStatusRefresh(sessionId);
2746
- sdkSessions.delete(sessionId);
2747
- stopTerminalCommandProcess(sessionId);
2748
- await removePersistedSession(sessionId);
2749
- const processManager = getAgentProcessManager();
2750
- await processManager.removeProcess(agentId);
2751
- if (stopOptions.statusNotified) {
2752
- await sendStatus(agentId, null, "stopped", undefined, undefined, {
2753
- runtimeAccessToken: sdkEntry.runtimeAccessToken,
2754
- schedulerBaseUrl: sdkEntry.schedulerBaseUrl,
2755
- });
2756
- }
2757
- else {
2758
- console.log(`[Runtime] Stopped status notification suppressed for SDK session ${sessionId}; reason=${stopOptions.reason || "unspecified"}`);
2759
- }
2760
- res.json({
2761
- ok: true,
2762
- status: "stopped",
2763
- mode: "sdk",
2764
- statusNotified: stopOptions.statusNotified,
2765
- reason: stopOptions.reason,
2766
- });
2767
- }
2768
- catch (error) {
2769
- const errorMessage = error instanceof Error ? error.message : String(error);
2770
- console.error(`[Runtime] Failed to terminate SDK session ${sessionId}:`, errorMessage);
2771
- res.status(500).json({
2772
- ok: false,
2773
- status: "stop_failed",
2774
- mode: "sdk",
2775
- error: errorMessage,
2776
- });
2777
- }
2778
- });
2779
- /**
2780
- * 停止所有 SDK 会话
2781
- * POST /runtime/stop-all
2782
- * 用于 aws-mcp-server 重启时清理所有 SDK Agent 会话
2783
- */
2784
- terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
2785
- const stoppedSdk = [];
2786
- const errors = [];
2787
- // 1. 停止所有 SDK 会话
2788
- for (const [sessionId, entry] of sdkSessions.entries()) {
2789
- try {
2790
- // 根据 providerId 获取对应的 adapter
2791
- const providerId = entry.providerId || "claude-code";
2792
- const adapter = adapterRegistry.get(providerId);
2793
- // 如果有活跃 AI 请求,先立即中止(fire-and-forget),再清理资源
2794
- adapter.abortCurrentTurn(sessionId).catch(() => { });
2795
- await adapter.terminateSession(sessionId);
2796
- stopStatusRefresh(sessionId);
2797
- sdkSessions.delete(sessionId);
2798
- stopTerminalCommandProcess(sessionId);
2799
- stoppedSdk.push(sessionId);
2800
- await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
2801
- runtimeAccessToken: entry.runtimeAccessToken,
2802
- schedulerBaseUrl: entry.schedulerBaseUrl,
2803
- });
2804
- }
2805
- catch (error) {
2806
- const errorMessage = error instanceof Error ? error.message : String(error);
2807
- errors.push(`SDK ${sessionId}: ${errorMessage}`);
2808
- }
2809
- }
2810
- // 2. 清理持久化状态,避免 stop-all 后还能被恢复
2811
- // 直接清空整个持久化文件,确保不会有残留
2812
- try {
2813
- await savePersistedSessions([]);
2814
- console.log("[Runtime] 已清空所有持久化会话");
2815
- }
2816
- catch (error) {
2817
- const errorMessage = error instanceof Error ? error.message : String(error);
2818
- errors.push(`Persistence: ${errorMessage}`);
2819
- }
2820
- res.json({
2821
- ok: true,
2822
- stopped: {
2823
- sdk: stoppedSdk.length,
2824
- },
2825
- errors: errors.length > 0 ? errors : undefined,
2826
- });
2827
- });
2828
- // ============ 桥接侧事件查询 ============
2829
- /**
2830
- * 查询最近 N 条桥接侧本地事件。
2831
- *
2832
- * 用于前端面板或调试工具获取最近的状态/输出事件快照。
2833
- * GET /runtime/events/last/:n
2834
- */
2835
- terminalRouter.get("/events/last/:n", validateToken, (req, res) => {
2836
- const nParam = parseInt(req.params.n, 10);
2837
- if (!Number.isFinite(nParam) || nParam < 1) {
2838
- res.status(400).json({ error: ":n must be a positive integer" });
2839
- return;
2840
- }
2841
- const entries = bridgeEventBuffer.getLastN(nParam);
2842
- res.json({
2843
- ok: true,
2844
- count: entries.length,
2845
- entries,
2846
- });
2847
- });
2848
- /**
2849
- * 查询自指定 index(不含)之后的桥接侧本地事件。
2850
- *
2851
- * 增量轮询端点:客户端保存上一次看到的 index,
2852
- * 下次传入该 index 仅获取增量事件,适用于实时刷新场景。
2853
- * GET /runtime/events/since/:index
2854
- */
2855
- terminalRouter.get("/events/since/:index", validateToken, (req, res) => {
2856
- const indexParam = parseInt(req.params.index, 10);
2857
- if (!Number.isFinite(indexParam) || indexParam < 0) {
2858
- res.status(400).json({ error: ":index must be a non-negative integer" });
2859
- return;
2860
- }
2861
- const slice = bridgeEventBuffer.getSinceIndex(indexParam);
2862
- res.json({
2863
- ok: true,
2864
- entries: slice.entries,
2865
- nextIndex: slice.nextIndex,
2866
- oldestIndex: slice.oldestIndex,
2867
- dataLoss: slice.oldestIndex > indexParam + 1,
2868
- });
2869
- });
2870
- /**
2871
- * 获取 Bridge 侧事件推送统计(需求 4.2.1 #3)。
2872
- * 返回每个 Agent 的待推送数、累计成功/失败、连续失败次数、最近延迟等。
2873
- * GET /runtime/events/push-stats
2874
- */
2875
- terminalRouter.get("/events/push-stats", validateToken, (_req, res) => {
2876
- const stats = getAllPushStats();
2877
- res.json({
2878
- ok: true,
2879
- count: stats.length,
2880
- agents: stats,
2881
- });
2882
- });
1
+ import{spawn as Fe}from"node:child_process";import{promises as N}from"node:fs";import Te from"node:os";import U from"node:path";import{TextDecoder as Z}from"node:util";import{Router as ze}from"express";import{v4 as We}from"uuid";import ke from"axios";import{adapterRegistry as C,getToolActionInfo as ge,isTransientClipboardPasteFilePath as Ce,redactTransientClipboardPastePaths as q,registerSdkProviders as He,resolveSdkProviderIdByCommand as Qe}from"../adapter/index.js";import{validateToken as S}from"../middleware/auth.js";import{getAgentProcessManager as ye}from"../services/agent-process-manager.js";import{getAiConfigService as Xe}from"../services/cc-switch-sdk.js";import{prepareAcodeInstanceConfig as Ge,redactForLog as ee}from"../services/instance-ai-config-service.js";import{enqueueMcpLaunchBinding as Ve}from"../services/mcp-launch-binding-queue.js";import{resolveSchedulerBaseUrlFromToken as we}from"../services/auto-register.js";import{findClaudeCodeProcesses as Je,isProcessRunning as Ye}from"../services/process-detector.js";import{bridgeEventBuffer as be,sendOutput as H,sendQuestionRequest as Ze,sendStatus as D}from"../services/session-output.js";import{getAllPushStats as et,startHeartbeatScheduler as tt}from"../services/event-delta-pusher.js";import{findPersistedSessionByAgentId as nt,removePersistedSession as v,savePersistedSessions as rt,updatePersistedSessionAutoCommands as ot,updatePersistedSessionRuntimeState as st,upsertPersistedSession as it}from"../services/terminal-persistence.js";import{getInstanceAiConfigDebugLogger as at}from"../utils/instance-ai-config-debug-logger.js";import{logger as h}from"../utils/logger.js";const A=ze();function $e(e){const t=e.header("X-Runtime-Token");return t&&t.trim()?t.trim():(e.header("Authorization")||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||void 0}const f=new Map,ct=45e3,te=new Map;function ut(e){if(te.has(e))return;const t=setInterval(()=>{const n=f.get(e);if(!n){E(e);return}const r=n.currentRuntimeStatus;if(r!=="thinking"&&r!=="tool_using"&&r!=="compacting"){E(e);return}D(n.agentId,e,r,void 0,void 0,{runtimeAccessToken:n.runtimeAccessToken,schedulerBaseUrl:n.schedulerBaseUrl}).catch(()=>{})},ct);te.set(e,t)}function E(e){const t=te.get(e);t&&(clearInterval(t),te.delete(e))}const De=4e3,dt=`
2
+
3
+ \u8BF7\u6C42\u4F53:
4
+ `;function he(e){if(e==null||typeof e!="object"||Array.isArray(e))return;const t=e,n={};return typeof t.model=="string"&&t.model.trim()&&(n.model=t.model.trim()),typeof t.baseURL=="string"&&t.baseURL.trim()&&(n.baseURL=t.baseURL.trim()),typeof t.apiKey=="string"&&t.apiKey.trim()&&(n.apiKey=t.apiKey.trim()),typeof t.effort=="string"&&t.effort.trim()&&(n.effort=t.effort.trim()),n}const lt=`
5
+
6
+ \u8BF7\u6C42\u5934:
7
+ `,K=new Set;async function mt(e,t){const n=[],r=[],s=U.join(e,".agentswork","memory");try{const c=(await N.readdir(s)).filter(d=>d.endsWith(".json")),u=await Promise.all(c.map(async d=>{try{const l=await N.readFile(U.join(s,d),"utf-8");return JSON.parse(l)}catch{return h.warn("[loadAgentStartupMemories] Failed to parse memory file: %s",U.join(s,d)),null}}));for(const d of u)if(d?.by_uri)for(const l of Object.values(d.by_uri)){const m=l.memory_type||"memory",p=(l.content||"").trim();p&&(m==="recently_memory"?n.push({title:l.title,content:p,uri:l.uri||""}):m==="agent_memory"&&r.push({title:l.title,content:p,uri:l.uri||""}))}}catch{h.debug("[loadAgentStartupMemories] \u65E0\u6CD5\u8BFB\u53D6\u8BB0\u5FC6\u76EE\u5F55: %s",s)}if(n.length===0&&r.length===0&&t?.schedulerBaseUrl&&t?.runtimeAccessToken&&t?.agentId){const a=`${t.schedulerBaseUrl.replace(/\/+$/,"")}/mcp/call`,c={"Content-Type":"application/json","X-Runtime-Token":t.runtimeAccessToken,Authorization:`Bearer ${t.runtimeAccessToken}`},u=["recently_memory","agent_memory"],d=await Promise.allSettled(u.map(l=>fetch(a,{method:"POST",headers:c,body:JSON.stringify({tool:"list_memories",arguments:{agentId:t.agentId,memory_type:l}})}).then(m=>{if(!m.ok)throw new Error(`Server returned ${m.status}`);return m.json()})));for(let l=0;l<d.length;l++){const m=d[l];if(m.status!=="fulfilled")continue;const p=m.value;if(!Array.isArray(p))continue;const T=u[l]==="recently_memory"?n:r;for(const y of p){const g=(y.content||"").trim();g&&T.push({title:y.title,content:g,uri:y.uri||""})}}(n.length>0||r.length>0)&&h.info(`[loadAgentStartupMemories] Loaded ${n.length} task(s) and ${r.length} memory(s) from server for ${t.agentId}`)}const o=10;n.sort((a,c)=>c.uri.localeCompare(a.uri)),r.sort((a,c)=>c.uri.localeCompare(a.uri));const i=[];if(n.length>0){const c=n.slice(0,o).map((u,d)=>`${u.title?`\u3010${u.title}\u3011`:`\u6761\u76EE ${d+1}`}
8
+ ${u.content}`);i.push(`\u3010\u77ED\u671F\u8BB0\u5FC6\u3011
9
+ ${c.join(`
10
+
11
+ `)}`),n.length>o&&i.push(`... \u53CA\u53E6\u5916 ${n.length-o} \u6761\u77ED\u671F\u8BB0\u5FC6`)}if(r.length>0){const c=r.slice(0,o).map(u=>{const d=u.title?`\u3010${u.title}\u3011`:"";return d?`${d}
12
+ ${u.content}`:u.content});i.push(`\u3010agent\u8BA4\u77E5\u3011
13
+ ${c.join(`
14
+
15
+ `)}`),r.length>o&&i.push(`... \u53CA\u53E6\u5916 ${r.length-o} \u6761 agent \u8BA4\u77E5`)}if(i.length!==0)return i.join(`
16
+
17
+ `)}function ft(e,t){return e!=="acode"?{supported:!1,statusCode:400,error:"context reset without restart is only supported by ACode"}:t?{supported:!0}:{supported:!1,statusCode:500,error:"ACode adapter does not expose resetContext"}}function pt(e){return e!=="acode"}function gt(e){const t=typeof e=="object"&&e!==null&&"suppressStatus"in e&&e.suppressStatus===!0,n=typeof e=="object"&&e!==null&&"reason"in e&&typeof e.reason=="string"&&e.reason.trim()?e.reason.trim():void 0;return{statusNotified:!t,reason:n}}function ne(e){const t=e?.usage;if(!t)return;const n=Number.isFinite(t.inputTokens)?t.inputTokens:0,r=Number.isFinite(t.outputTokens)?t.outputTokens:0;return n<=0&&r<=0&&!t.contextLimit&&!t.maxOutput&&!t.inputLimit&&!t.compactionThreshold?void 0:{inputTokens:n,outputTokens:r,...t.contextLimit?{contextLimit:t.contextLimit}:{},...t.maxOutput?{maxOutput:t.maxOutput}:{},...t.inputLimit?{inputLimit:t.inputLimit}:{},...t.compactionThreshold?{compactionThreshold:t.compactionThreshold}:{}}}function yt(e,t){const n=String(e||"").trim().toLowerCase(),r=String(t?.actionType||"").trim().toLowerCase();return n==="tool_using"&&r==="idle"?t?{status:"waiting_input",actionInfo:t}:{status:"waiting_input"}:t?{status:e,actionInfo:t}:{status:e}}const Q=new Map,ht="utf-8",_e="utf-8",Pe=300,Re=6e4;function X(e){return String(e).padStart(2,"0")}function It(e,t=new Date){const n=t.getFullYear(),r=X(t.getMonth()+1),s=X(t.getDate()),o=X(t.getHours()),i=X(t.getMinutes()),a=`${n}${r}${s}-${o}${i}${X(t.getSeconds())}-${String(t.getMilliseconds()).padStart(3,"0")}`;return U.join(Te.homedir(),".acode","cmd-dist",e,`${n}-${r}-${s}`,o,i,`${a}-cmd.out`)}function St(e){if(!e)return 0;const t=e.replace(/\r\n/g,`
18
+ `).replace(/\r/g,`
19
+ `),n=t.endsWith(`
20
+ `)?t.slice(0,-1):t;return n?n.split(`
21
+ `).length:1}function At(e,t=Pe){return`\r
22
+ [ACode] \u547D\u4EE4\u8F93\u51FA\u8D85\u8FC7 ${t} \u884C\uFF0C\u5B8C\u6574\u8F93\u51FA\u5DF2\u5199\u5165: ${e}\r
23
+ [ACode] Agent \u53EF\u81EA\u884C\u8BFB\u53D6\u8BE5\u6587\u4EF6\uFF0C\u6216\u5728\u8BE5\u6587\u4EF6\u4E2D\u641C\u7D22\u9700\u8981\u7684\u5185\u5BB9\u3002\r
24
+ `}function Tt(e=Re){return`\r
25
+ [ACode] \u547D\u4EE4\u8D85\u8FC7 ${Math.round(e/1e3)} \u79D2\u6CA1\u6709\u65B0\u7684\u8F93\u51FA\uFF0C\u5DF2\u505C\u6B62\u6267\u884C\u5E76\u4FDD\u7559\u6B64\u524D\u8F93\u51FA\u3002\r
26
+ `}async function xe(e){const t=e.outputPath||It(e.agentId,e.startedAt);return e.outputPath=t,await N.mkdir(U.dirname(t),{recursive:!0}),await N.writeFile(t,e.chunks.join(""),"utf-8"),t}function kt(e){return String(e??"").replace(/\r/g,`
27
+ `).trim()}function Ct(e=process.platform,t=process.env){const n=t.AWS_TERMINAL_OUTPUT_ENCODING?.trim();return n||(e==="win32"?ht:_e)}function Me(e=Ct()){if(e!=="utf-8")try{return new Z(e,{fatal:!1})}catch{return new Z(_e,{fatal:!1})}return wt()}function wt(){let e=null,t=null,n=null;const r=[];function s(o){try{return new Z(o,{fatal:!1})}catch{return new Z("utf-8",{fatal:!1})}}return{decode(o,i){if(o!=null&&r.push(o instanceof ArrayBuffer?Buffer.from(o):Buffer.isBuffer(o)?o:Buffer.from(o.buffer,o.byteOffset,o.byteLength)),n==="gb18030")return t||(t=s("gb18030")),t.decode(o,i);e||(e=s("utf-8"));const a=e.decode(o,i);if(n===null&&a.includes("\uFFFD")){n="gb18030",t=s("gb18030");const c=Buffer.concat(r);return t.decode(c)}return n===null&&!i?.stream&&(n="utf-8"),a}}}function re(e,t,n=!1){return n?e.decode():!t||t.length===0?"":e.decode(t,{stream:!0})}function bt(e){const t=e.trim().match(/^cd(?:\s+(.*))?$/i);if(!t)return;const n=t[1]?.trim();if(!n)return null;const r=n.replace(/^\/d\s+/i,"").trim();return r.startsWith('"')&&r.endsWith('"')||r.startsWith("'")&&r.endsWith("'")?r.slice(1,-1):r}function $t(e){const t=Q.get(e.sessionId);if(t)return t;const n={currentDirectory:e.workspacePath,runningProcess:null};return Q.set(e.sessionId,n),n}function Ee(e){for(const t of f.values())if(t.agentId===e)return t}async function Dt(e){const t=C.get(e.providerId||"claude-code"),n=oe(t.getSessionStatus(e.sessionId),e.currentRuntimeStatus,e.currentRuntimeActionType);return{sessionId:e.sessionId,status:n,runtimeStatus:n,runtimeActionType:e.currentRuntimeActionType,runtimeActionLabel:e.currentRuntimeActionLabel,runtimeActionDetail:e.currentRuntimeActionDetail,agentId:e.agentId,workspacePath:e.workspacePath,command:e.config.command,mode:"sdk",providerSessionId:t.getProviderSessionId(e.sessionId),reused:!0}}function oe(e,t,n){const r=String(e||"").trim();if(r){const o=String(t||"").trim(),i=String(n||"").trim().toLowerCase();return r.toLowerCase()==="tool_using"&&o.toLowerCase()==="waiting_input"&&i==="idle"?o:r}return String(t||"").trim()||"running"}function _t(e,t=Ye){return e.pid?t(e.pid)?Ne(e.command)==="acode"?{reusable:!1,reason:"in-process-provider"}:{reusable:!0,reason:"active-pid"}:{reusable:!1,reason:"dead-pid"}:{reusable:!1,reason:"missing-pid"}}function En(e){const t=Ee(e);if(!t)h.info(`[cleanupSdkSessionByAgentId] No active SDK session found for agentId=${e}, cleaning residual state anyway`);else{const n=t.sessionId;return h.info(`[cleanupSdkSessionByAgentId] Cleaning up SDK session for agentId=${e}, sessionId=${n}`),E(n),f.delete(n),se(n),v(n).catch(r=>{h.warn(`[cleanupSdkSessionByAgentId] Failed to remove persisted session for agentId=${e}: ${r}`)}),n}K.has(e)&&(K.delete(e),h.info(`[cleanupSdkSessionByAgentId] Released startingAgents lock for agentId=${e}`))}function se(e){const t=Q.get(e);if(!t?.runningProcess){Q.delete(e);return}try{t.runningProcess.kill()}catch{}finally{t.runningProcess=null,Q.delete(e)}}function w(e,t){t&&(e.seq+=1,H(e.agentId,t,e.sessionId,e.seq,void 0,{runtimeAccessToken:e.runtimeAccessToken,schedulerBaseUrl:e.schedulerBaseUrl}).catch(()=>{}))}function Ie(e){return`\x1B[36m${e}>\x1B[0m `}function Pt(e,t){return t==="~"?Te.homedir():U.resolve(e,t)}async function Rt(e,t,n){const r=bt(n);if(r===void 0)return!1;if(r===null)return w(e,`${t.currentDirectory}\r
28
+ `),!0;const s=Pt(t.currentDirectory,r);try{return(await N.stat(s)).isDirectory()?(t.currentDirectory=s,w(e,`${t.currentDirectory}\r
29
+ `),!0):(w(e,`cd: not a directory: ${s}\r
30
+ `),!0)}catch(o){const i=o instanceof Error?o.message:String(o);return w(e,`cd: ${i}\r
31
+ `),!0}}async function xt(e,t){const n=f.get(e);if(!n)throw new Error("SDK session not found");const r=kt(t),s=$t(n);if(!r)return{started:!1,cwd:s.currentDirectory};if(s.runningProcess)throw new Error("A terminal command is already running for this session");const o=q(r);if(w(n,`${Ie(s.currentDirectory)}${o}\r
32
+ `),Ce(r))return w(n,`[Terminal Warning] \u68C0\u6D4B\u5230\u526A\u8D34\u677F\u4E34\u65F6 paste \u6587\u4EF6\u8DEF\u5F84\uFF0C\u5DF2\u8DF3\u8FC7\u6267\u884C\u3002\u8BF7\u7C98\u8D34\u6587\u4EF6\u5185\u5BB9\u6216\u4F7F\u7528\u663E\u5F0F\u8BFB\u53D6\u547D\u4EE4\u3002\r
33
+ `),w(n,Ie(s.currentDirectory)),{started:!1,cwd:s.currentDirectory};if(await Rt(n,s,r))return{started:!1,cwd:s.currentDirectory};await D(n.agentId,e,"tool_using",{actionType:"bash",actionLabel:"\u7EC8\u7AEF\u547D\u4EE4",actionDetail:o},void 0,{runtimeAccessToken:n.runtimeAccessToken,schedulerBaseUrl:n.schedulerBaseUrl});const a=Fe(r,{cwd:s.currentDirectory,shell:!0,env:Le(n.agentId,n.workspacePath,process.env,n.config.envOverrides)});s.runningProcess=a;const c=Me(),u=Me(),d=n.providerId==="acode",l={agentId:n.agentId,startedAt:new Date,chunks:[],spilled:!1};let m;const p=()=>{m&&(clearTimeout(m),m=void 0)},T=()=>{p(),m=setTimeout(()=>{s.runningProcess===a&&(w(n,Tt()),a.kill())},Re)},y=(g,b=!0)=>{if(g&&(d&&l.chunks.push(g),d&&b&&T(),!(d&&l.spilled))){if(d&&St(l.chunks.join(""))>Pe){l.spilled=!0,xe(l).then($=>{w(n,At($))}).catch($=>{const M=$ instanceof Error?$.message:String($);w(n,`\r
34
+ [ACode] \u547D\u4EE4\u8F93\u51FA\u843D\u76D8\u5931\u8D25: ${M}\r
35
+ `)});return}w(n,g)}};return d&&T(),a.stdout.on("data",g=>{y(re(c,g))}),a.stderr.on("data",g=>{y(re(u,g))}),a.on("error",g=>{w(n,`\r
36
+ [Terminal Error] ${g.message}\r
37
+ `)}),a.on("close",(g,b)=>{p(),s.runningProcess=null,y(re(c,void 0,!0),!1),y(re(u,void 0,!0),!1),l.spilled&&xe(l).catch($=>{const M=$ instanceof Error?$.message:String($);w(n,`\r
38
+ [ACode] \u547D\u4EE4\u8F93\u51FA\u6700\u7EC8\u843D\u76D8\u5931\u8D25: ${M}\r
39
+ `)}),g&&g!==0?w(n,`\r
40
+ [exit ${g}]\r
41
+ `):b&&w(n,`\r
42
+ [signal ${b}]\r
43
+ `),w(n,Ie(s.currentDirectory)),D(n.agentId,e,"waiting_input",void 0,void 0,{runtimeAccessToken:n.runtimeAccessToken,schedulerBaseUrl:n.schedulerBaseUrl})}),{started:!0,cwd:s.currentDirectory}}function Le(e,t,n,r){const s={};for(const[o,i]of Object.entries(n))i!==void 0&&(s[o]=i);if(t&&(s.AWS_WORKSPACE_PATH=String(t)),r)for(const[o,i]of Object.entries(r)){const a=o.trim();if(!a||i===void 0||i===null)continue;const c=String(i).trim();c&&(s[a]=c)}return s.AWS_AGENT_ID=String(e),s.AWS_MCP_CLAIM_LAUNCH_BINDING="true",s}function Mt(e){const t=e.trim(),n=t[0],r=t[t.length-1];return(n==='"'||n==="'")&&n===r?t.slice(1,-1):t}async function Et(e){const t=String(e);if(!Ce(t))return t;const n=Mt(t);return U.isAbsolute(n)?N.readFile(n,"utf-8"):t}function je(e,t){const n={};for(const s of[e?.env,t])if(s)for(const[o,i]of Object.entries(s)){const a=o.trim();if(!a||i===void 0||i===null)continue;const c=String(i).trim();c&&(n[a]=c)}const r=n.ANTHROPIC_MODEL?.trim()||n.CODEX_MODEL?.trim()||void 0;return{envOverrides:n,model:r}}async function Lt(){try{return await Xe().readClaudeCodeConfig()}catch(e){return console.warn(`[Runtime] Failed to read saved ClaudeCode AI config: ${e.message}`),{}}}function jt(e){if(e==null)return;const n=(typeof e=="string"?e:JSON.stringify(e,null,2)).trim();if(n)return n.length<=De?n:`${n.slice(0,De)}
44
+ ...\uFF08\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`}function Ut(e){if(e.data.actionType==="mcp")return!0;const n=String(e.data.toolName||"").toLowerCase();return n.startsWith("mcp__")||n.includes("-mcp__")||n.includes("mcp_")}function Bt(e){if(e.type!=="tool_use_end")return;const t=jt(e.data.toolResult??e.data.actionResult);if(!t)return;const n=ge(e.data.toolName,e.data.toolInput),r=e.data.actionType||n.actionType;if(r!=="idle")return r==="mcp"&&Ut(e)?{actionType:"mcp",actionLabel:"MCP\u8FD4\u56DE",actionDetail:e.data.actionDetail||e.data.toolName,actionId:e.data.actionId||e.data.toolUseId,actionResult:t}:{actionType:r,actionLabel:e.data.actionLabel||n.actionLabel,actionDetail:e.data.actionDetail||n.actionDetail||e.data.toolName,actionId:e.data.actionId||e.data.toolUseId,actionResult:t}}function Ot(e){if(e.type==="ai_request"){const t=e.data.model?` ${e.data.model}`:"",n=e.data.lastUserMessagePreview?`\uFF1A${e.data.lastUserMessagePreview}`:"";return`\r
45
+ [ACode] \u6B63\u5728\u8BF7\u6C42\u6A21\u578B${t}${n}\r
46
+ `}if(e.type==="text_delta"||e.type==="thinking"||e.type==="command_output")return e.data.text||void 0;if(e.type==="error")return e.data.text?`\r
47
+ [SDK Error] ${e.data.text}\r
48
+ `:void 0}function Nt(e){const t=Kt(e);return{sourceEventType:e.type,logCategory:"acode",...vt(e),...t?{logLevel:qt(e),logMessage:t}:{}}}function qt(e){return e.type==="error"?"error":e.type==="command_output"&&e.data.stream==="stderr"?"warn":"info"}function vt(e){return e.type!=="command_output"?{}:{...typeof e.data.stream=="string"?{stream:e.data.stream}:{},...typeof e.data.sequence=="number"?{sequence:e.data.sequence}:{},...typeof e.data.cwd=="string"?{cwd:e.data.cwd}:{},...typeof e.data.commandFilePath=="string"?{commandFilePath:e.data.commandFilePath}:{},...typeof e.data.commandLineCount=="number"?{commandLineCount:e.data.commandLineCount}:{}}}function Kt(e){if(e.type==="ai_request"){const t=e.data.model||"unknown-model",n=e.data.endpoint?` @ ${e.data.endpoint}`:"",r=typeof e.data.toolCount=="number"?`, tools=${e.data.toolCount}`:"";return`AI request started: ${t}${n}${r}`}if(e.type==="tool_use_start")return Ue(e,"\u5F00\u59CB");if(e.type==="tool_use_end")return Ue(e,"\u5B8C\u6210");if(e.type==="command_output")return Wt(e);if(e.type==="turn_complete")return`Turn completed${e.data.finishReason?`, finish=${e.data.finishReason}`:""}`;if(e.type==="session_complete")return`Session completed: exitCode=${e.data.exitCode??0}`;if(e.type==="error")return e.data.text||"ACode runtime error"}const Ft=/\x1b\[[0-9;?]*[a-zA-Z]/g;function zt(e){return e.replace(Ft,"")}function Wt(e){const t=typeof e.data.stream=="string"?e.data.stream:"mixed",n=typeof e.data.sequence=="number"?` #${e.data.sequence}`:"",r=zt(String(e.data.text||"")).replace(/\s+/g," ").trim(),s=r.length>240?`${r.slice(0,240)}...`:r;return`\u547D\u4EE4\u8F93\u51FA(${t}${n}): ${s||"(empty)"}`}function Ue(e,t){const n=ge(e.data.toolName,e.data.toolInput),r=e.data.actionLabel||n.actionLabel||"\u8C03\u7528\u5DE5\u5177",s=Be(e,n);return s?`${t}${r}: ${s}`:`${t}${r}: ${e.data.actionDetail||e.data.toolName||"tool"}`}function Be(e,t){const n=e.data.toolInput;return t.actionType==="bash"?Ht(n)||e.data.actionDetail||t.actionDetail:t.actionType==="read_file"||t.actionType==="edit_file"||t.actionType==="write_file"?Qt(n)||e.data.actionDetail||t.actionDetail:e.data.actionDetail||t.actionDetail}function Ht(e){const t=Se(e?.command,e?.cmd);if(t)return t;if(Array.isArray(e?.argv)){const n=e.argv.map(r=>String(r??"").trim()).filter(Boolean).join(" ");return n?q(n):void 0}return Se(e?.args)}function Qt(e){const t=Oe(e?.paths);return t.length>0?t.length===1?t[0]:`${t.length}\u4E2A\u6587\u4EF6: ${t.join(", ")}`:Se(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)}function Se(...e){for(const t of e){if(typeof t=="string"&&t.trim())return q(t.trim());if(Array.isArray(t)){const n=Oe(t)[0];if(n)return n}}}function Oe(e){return Array.isArray(e)?e.map(t=>typeof t=="string"?t.trim():"").filter(Boolean).map(t=>q(t)):[]}function Xt(e){if(!(!e||Object.keys(e).length===0))try{const t=q(JSON.stringify(e,null,2));return t.length>8e3?`${t.slice(0,8e3)}
49
+ ...\uFF08\u8F93\u5165\u53C2\u6570\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`:t}catch{return}}function Gt(e){const t=[e.model?`\u6A21\u578B: ${e.model}`:void 0,e.endpoint||e.requestUrl?`\u63A5\u53E3: ${e.endpoint||e.requestUrl}`:void 0,e.requestMethod?`\u65B9\u6CD5: ${e.requestMethod}`:void 0,typeof e.stream=="boolean"?`\u6D41\u5F0F: ${e.stream?"\u662F":"\u5426"}`:void 0,typeof e.messageCount=="number"?`\u4E0A\u4E0B\u6587\u6D88\u606F: ${e.messageCount}`:void 0,typeof e.toolCount=="number"?`\u53EF\u7528\u5DE5\u5177: ${e.toolCount}`:void 0,e.lastUserMessagePreview?`\u6700\u540E\u8BF7\u6C42: ${e.lastUserMessagePreview}`:void 0].filter(i=>!!i),n=Vt(e.requestHeaders),r=t.join(`
50
+ `)||"ACode \u6B63\u5728\u53D1\u9001 AI \u8BF7\u6C42",s=n.length>0?`${lt}${n.join(`
51
+ `)}`:"",o=e.requestBody?`${dt}${e.requestBody}`:"";return`${r}${s}${o}`}function Vt(e){return e?Object.entries(e).map(([t,n])=>`${t}: ${n}`).filter(t=>t.trim().length>0):[]}function Jt(e,t){return String(t||"").trim().toLowerCase()!=="waiting_input"?!1:e.type==="thinking"||e.type==="text_delta"}function Yt(e){for(const t of["estimatedContextTokens","contextTokens","context_tokens","contextUsedTokens","context_used_tokens"]){const n=e[t],r=Number(n);if(Number.isFinite(r)&&r>0)return r}}function Zt(e,t){if(!Jt(e,t)){if(e.type==="ai_request"){const n=Yt(e.data);return{status:"thinking",actionInfo:{actionType:"ai_request",actionLabel:"AI\u8BF7\u6C42",actionDetail:Gt(e.data),...n!==void 0?{usage:{inputTokens:n,outputTokens:0}}:{}}}}if(e.type==="thinking"||e.type==="text_delta")return{status:"thinking"};if(e.type==="tool_use_start"){const n=ge(e.data.toolName,e.data.toolInput),r=Be(e,n),s=Xt(e.data.toolInput);return e.data.actionType==="idle"||n.actionType==="idle"?{status:"waiting_input",actionInfo:{actionType:e.data.actionType||n.actionType,actionLabel:e.data.actionLabel||n.actionLabel,actionDetail:r||e.data.actionDetail||n.actionDetail||e.data.toolName,actionInput:s,actionId:e.data.actionId||e.data.toolUseId}}:{status:"tool_using",actionInfo:{actionType:e.data.actionType||n.actionType,actionLabel:e.data.actionLabel||n.actionLabel,actionDetail:r||e.data.actionDetail||n.actionDetail||e.data.toolName,actionInput:s,actionId:e.data.actionId||e.data.toolUseId}}}if(e.type==="turn_complete")return{status:"waiting_input",actionInfo:e.data.usage?{usage:e.data.usage}:void 0};if(e.type==="error")return{status:"error"};if(e.type==="context_compaction")return{status:"compacting",actionInfo:{actionType:"context_compaction",actionLabel:e.data.actionLabel,actionDetail:e.data.actionDetail,...e.data.actionId?{actionId:e.data.actionId}:{},...e.data.parentId?{parentId:e.data.parentId}:{}}};if(e.type==="runtime_capabilities")return{status:t||"waiting_input",actionInfo:{actionType:"runtime_capabilities",actionLabel:e.data.actionLabel||"\u8FD0\u884C\u65F6\u80FD\u529B",actionDetail:e.data.actionDetail}}}}const L=new Map,ie=new Map,ae=new Map,G=new Map,en=20,tn=500,nn=3e4;async function ce(e,t=nn,n=2){for(let r=0;r<=n;r++){const s=new AbortController;let o;try{const i=e(s.signal);i.catch(()=>{}),await Promise.race([i,new Promise((a,c)=>{o=setTimeout(()=>{s.abort(),c(new Error(`sendOutput timeout after ${t}ms`))},t)})]),o&&clearTimeout(o);return}catch(i){o&&clearTimeout(o);const a=i instanceof Error?i.message:String(i);if(r<n){const c=1e3*(r+1);h.warn(`sendOutput attempt ${r+1}/${n+1} failed (${a}), retrying in ${c}ms...`),await new Promise(u=>setTimeout(u,c));continue}h.warn(`sendOutput failed after ${n+1} attempts (${a}), giving up to unblock serial queue`)}}}function ue(e,t){const n=t?`${e}:${t}`:void 0;let r,s;if(n)r=G.get(n),s=n;else{const p=[];for(const[T,y]of G)T.startsWith(e+":")&&y.buffer&&p.push(y.sourceEventType);for(const T of p)ue(e,T);return}if(!r||!r.buffer)return;r.timer=null;const o=r.buffer,i=r.seq,a=r.entry,c=r.sourceEventType;r.buffer="",s&&G.delete(s);const u={sourceEventType:c,logCategory:"acode"},d={runtimeAccessToken:a.runtimeAccessToken,schedulerBaseUrl:a.schedulerBaseUrl},m=(L.get(e)??Promise.resolve()).then(()=>ce(p=>H(a.agentId,o,e,i,u,d,p)),()=>ce(p=>H(a.agentId,o,e,i,u,d,p)));L.set(e,m),m.finally(()=>{L.get(e)===m&&L.delete(e)})}function rn(e,t){const n=Ot(e);if(!n)return;t.seq+=1;const r=t.seq,s=Nt(e),o={runtimeAccessToken:t.runtimeAccessToken,schedulerBaseUrl:t.schedulerBaseUrl};if((e.type==="command_output"||e.type==="text_delta"||e.type==="thinking")&&n.length<en){const c=`${e.sessionId}:${e.type}`;let u=G.get(c);if(u||(u={buffer:"",seq:0,timer:null,entry:t,sourceEventType:e.type}),u.buffer+=n,u.seq=r,u.entry=t,!u.timer){const d=e.sessionId,l=e.type;u.timer=setTimeout(()=>ue(d,l),tn)}G.set(c,u);return}ue(e.sessionId);const a=(L.get(e.sessionId)??Promise.resolve()).then(()=>ce(c=>H(t.agentId,n,e.sessionId,r,s,o,c)),()=>ce(c=>H(t.agentId,n,e.sessionId,r,s,o,c)));L.set(e.sessionId,a),a.finally(()=>{L.get(e.sessionId)===a&&L.delete(e.sessionId)})}function Ne(e){return Qe(e)}let qe=!1;function on(){qe||(He(C,sn),tt(),qe=!0)}function sn(e,t){e.on("event",n=>{const r=f.get(n.sessionId);if(r){if(console.log(`[${t.displayName} Adapter] Event: ${n.type} for session ${n.sessionId}`),!(n.type==="thinking"||n.type==="text_delta"||n.type==="tool_use_start"||n.type==="context_compaction"||n.type==="ai_request")){n.type==="runtime_capabilities"&&n.data.actionDetail&&(r.cachedCapabilitiesActionDetail=n.data.actionDetail);const i=Zt(n,r.currentRuntimeStatus);i&&D(r.agentId,n.sessionId,i.status,i.actionInfo,ne(i.actionInfo),{runtimeAccessToken:r.runtimeAccessToken,schedulerBaseUrl:r.schedulerBaseUrl},n.timestamp?new Date(n.timestamp).getTime():void 0,n.type)}rn(n,r);const o=Bt(n);o&&D(r.agentId,n.sessionId,"tool_result",o,void 0,{runtimeAccessToken:r.runtimeAccessToken,schedulerBaseUrl:r.schedulerBaseUrl},n.timestamp?new Date(n.timestamp).getTime():void 0,n.type)}}),e.on("status-change",(n,r,s)=>{const o=f.get(n);if(o){ue(n),r==="thinking"||r==="tool_using"||r==="compacting"?ut(n):E(n);const i=yt(r,s);o.currentRuntimeStatus=i.status;const a=o.currentRuntimeActionDetail??"",c=a.includes("poll_message")||a.includes("get_message"),d=!(i.actionInfo?.actionType==="ai_request")&&(i.status==="thinking"||i.status==="waiting_input")&&(o.currentRuntimeActionType==="idle"||o.currentRuntimeActionType==="mcp")&&c;(i.actionInfo?.actionType||!d)&&(o.currentRuntimeActionType=i.actionInfo?.actionType,o.currentRuntimeActionLabel=i.actionInfo?.actionLabel,o.currentRuntimeActionDetail=i.actionInfo?.actionDetail,o.currentRuntimeActionInput=i.actionInfo?.actionInput),console.log(`[${t.displayName} Adapter] Status changed: ${i.status} for agent ${o.agentId}`,i.actionInfo||""),st(n,{runtimeStatus:i.status,runtimeActionType:o.currentRuntimeActionType,runtimeActionLabel:o.currentRuntimeActionLabel,runtimeActionDetail:o.currentRuntimeActionDetail});const l=i.actionInfo?.actionType?i.actionInfo:o.currentRuntimeActionType?{actionType:o.currentRuntimeActionType,actionLabel:o.currentRuntimeActionLabel??"",actionDetail:o.currentRuntimeActionDetail,actionInput:o.currentRuntimeActionInput}:void 0;if(i.status==="compacting"){const p=(ie.get(n)??Promise.resolve()).then(async()=>{for(let y=0;y<=2;y++){if(await D(o.agentId,n,i.status,l,ne(i.actionInfo),{runtimeAccessToken:o.runtimeAccessToken,schedulerBaseUrl:o.schedulerBaseUrl})!==void 0)return;y<2&&(h.warn(`[${t.displayName}] compaction sendStatus attempt ${y+1} failed (HTTP), retrying in ${1e3*(y+1)}ms...`),await new Promise(b=>setTimeout(b,1e3*(y+1))))}h.warn(`[${t.displayName}] compaction sendStatus failed after 3 attempts, event may be lost`)}).catch(T=>{h.warn(`[${t.displayName}] compaction status chain error:`,T)});ie.set(n,p),p.finally(()=>{ie.get(n)===p&&ie.delete(n)})}else{const m=d&&!i.actionInfo?.actionType;if(l?.actionType==="idle"||o.currentRuntimeActionType==="idle"&&d){const y=(ae.get(n)??Promise.resolve()).then(async()=>{for(let b=0;b<=2;b++){if(await D(o.agentId,n,i.status,m?void 0:l,ne(i.actionInfo),{runtimeAccessToken:o.runtimeAccessToken,schedulerBaseUrl:o.schedulerBaseUrl})!==void 0)return;b<2&&await new Promise(M=>setTimeout(M,1e3*(b+1)))}h.warn(`[${t.displayName}] idle tool sendStatus failed after 3 attempts, status may be stale (frontend will fall back to timeline tool_use event)`)}).catch(g=>{h.warn(`[${t.displayName}] idle status chain error:`,g)});ae.set(n,y),y.finally(()=>{ae.get(n)===y&&ae.delete(n)})}else D(o.agentId,n,i.status,m?void 0:l,ne(i.actionInfo),{runtimeAccessToken:o.runtimeAccessToken,schedulerBaseUrl:o.schedulerBaseUrl})}if(i.status==="waiting_input"&&o.cachedCapabilitiesActionDetail){const m=o.cachedCapabilitiesActionDetail;o.cachedCapabilitiesActionDetail=void 0,console.log(`[${t.displayName} Adapter] Re-sending cached runtime_capabilities for agent ${o.agentId} (session stabilized to waiting_input)`),D(o.agentId,n,"waiting_input",{actionType:"runtime_capabilities",actionLabel:"\u8FD0\u884C\u65F6\u80FD\u529B",actionDetail:m},void 0,{runtimeAccessToken:o.runtimeAccessToken,schedulerBaseUrl:o.schedulerBaseUrl})}}}),e.on("ask-user-question",n=>{const r=f.get(n.sessionId);r&&(console.log(`[${t.displayName} Adapter] AskUserQuestion for agent ${r.agentId}`),Ze(r.agentId,n.sessionId,Array.isArray(n.questions)?n.questions:[],{runtimeAccessToken:r.runtimeAccessToken,schedulerBaseUrl:r.schedulerBaseUrl},n.toolCallId))}),e.on("permission-request",n=>{const r=f.get(n.sessionId);if(r){console.log(`[${t.displayName} Adapter] Permission request: ${n.toolName} for agent ${r.agentId}`);const s=n.toolInput?q(JSON.stringify(n.toolInput).slice(0,400)):void 0;D(r.agentId,n.sessionId,"waiting_input",{actionType:"other",actionLabel:"\u6743\u9650\u786E\u8BA4",actionDetail:s||n.toolName},void 0,{runtimeAccessToken:r.runtimeAccessToken,schedulerBaseUrl:r.schedulerBaseUrl})}})}A.post("/start",S,async(e,t)=>{const{agentId:n,workspacePath:r,mode:s="sdk"}=e.body||{};if(!n||!r){t.status(400).json({error:"agentId and workspacePath are required"});return}const o=String(e.body?.schedulerBaseUrl||"").trim()||String(e.header("x-bridge-tunnel-source")||"").trim()||we($e(e))||void 0;Ve({agentId:String(n),workspacePath:String(r),serverUrl:e.body?.serverUrl||o,runtimeAccessToken:e.body?.runtimeAccessToken,userId:e.body?.userId,schedulerBaseUrl:o}),s&&s!=="sdk"&&console.warn(`[Runtime] Unsupported runtime mode "${s}" requested for agent ${n}; starting SDK session instead.`),await an(e,t)});async function an(e,t){const{agentId:n,workspacePath:r,command:s,autoAccept:o=!0,initialPrompt:i,systemPrompt:a,globalPrompt:c,rolePrompt:u,memory:d,mcpConfigPath:l,extraMcpServers:m,envOverrides:p,idleInputAutoCommand:T,nonInputAutoCommand:y,model:g,instanceAiConfig:b,subAgentModel:$,subAgentEffort:M}=e.body||{},F=at();F.log({level:"info",category:"launch",message:"startSdkSession: \u6536\u5230\u542F\u52A8\u8BF7\u6C42",source:"bridge",agentId:String(n||"").trim()||void 0,details:{reqBodyKeys:Object.keys(e.body||{}),perInstanceAiConfig:b?ee(b):null,perAgentModel:g??null}});let _=null;const I=String(n||"").trim(),de=String(e.body?.runtimeAccessToken||"").trim()||void 0,B=String(e.body?.schedulerBaseUrl||"").trim()||String(e.header("x-bridge-tunnel-source")||"").trim()||we($e(e))||void 0;B?h.info(`[startSdkSession] \u56DE\u8C03\u76EE\u6807 server: ${B} (agentId=${I})`):h.warn(`[startSdkSession] \u672A\u89E3\u6790\u5230 schedulerBaseUrl\uFF0C\u5C06\u56DE\u9000\u5230\u9ED8\u8BA4\u5730\u5740 (agentId=${I})`);try{if(on(),!I){t.status(400).json({error:"agentId is required"});return}const O=Ee(I);if(O){t.json(await Dt(O));return}const k=await nt(I);if(k){const P=_t(k);if(!P.reusable)console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${I}, sessionId=${k.sessionId}, reason=${P.reason}`),await v(k.sessionId);else{const R=oe(k.runtimeStatus,k.runtimeStatus,k.runtimeActionType);t.json({sessionId:k.sessionId,status:R,runtimeStatus:R,runtimeActionType:k.runtimeActionType,runtimeActionLabel:k.runtimeActionLabel,runtimeActionDetail:k.runtimeActionDetail,agentId:k.agentId,workspacePath:k.workspacePath,command:k.command,mode:k.mode||"sdk",pid:k.pid,reused:!0,persistedOnly:!0});return}}if(K.has(I)){t.status(409).json({error:"SDK session is already starting for this agent"});return}K.add(I);const ln=(s||"acode").toLowerCase(),x=Ne(s),ve=C.get(x);_=We();const Ke=x==="claude-code"?await Lt():null,V=je(x==="claude-code"?Ke:null,p),z=x==="acode"?g?String(g).trim():void 0:(()=>{const P=g?String(g).trim():void 0,R=V.model?String(V.model).trim():void 0;return P||R})();F.log({level:"info",category:"resolve",message:"\u6A21\u578B\u89E3\u6790\u7ED3\u679C",source:"bridge",agentId:I,sessionId:_??void 0,details:{perAgentModel:g??null,globalModel:V.model??null,resolvedModel:z??null,providerId:x,note:x==="acode"?"adapter handles full priority":"request > global"}});const le={...V.envOverrides};z&&(le.ANTHROPIC_MODEL=z,le.CODEX_MODEL=z);let W,me=l;if(x==="acode"){const P=b||{},R=await Ge({apiOverrides:he(P)??void 0,agentId:I,sessionId:_??void 0,runtimeBridgeBaseUrl:B});W=R.instanceConfig.config,me=R.perInstanceConfigPath,F.log({level:"info",category:"apply",message:"prepareAcodeInstanceConfig \u5B8C\u6210",source:"bridge",agentId:I,sessionId:_??void 0,details:{sanitizedApiOverrides:he(P)?ee(he(P)):null,resolvedInstanceAiConfig:W?ee(W):null,perInstanceConfigPath:me,instanceConfigSources:R.instanceConfig.source??null}})}let Ae=a||void 0,fe,pe,J;if(x==="acode")if(fe=typeof c=="string"&&c.trim()||void 0,pe=typeof u=="string"&&u.trim()||void 0,typeof d=="string"&&d.trim())J=d.trim();else{const P=await mt(r,{schedulerBaseUrl:B,runtimeAccessToken:de,agentId:I});P&&(J=P,h.info(`[startSdkSession] Auto-loaded memories for ACode agent ${I}`))}else if(typeof c=="string"&&c.trim()||typeof u=="string"&&u.trim()||typeof d=="string"&&d.trim()){const R=[c,u,d,a].filter(Y=>typeof Y=="string"&&Y.trim().length>0).map(Y=>Y.trim());Ae=R.length>0?R.join(`
52
+
53
+ `):void 0}const j={command:s||"acode",workingDirectory:r,agentId:I,autoAccept:!!o,initialPrompt:i,systemPrompt:Ae,...fe!==void 0?{globalPrompt:fe}:{},...pe!==void 0?{rolePrompt:pe}:{},...J!==void 0?{memory:J}:{},model:z,mcpConfigPath:me,...W?{instanceAiConfig:W}:{},...typeof $=="string"&&$.trim()?{subAgentModel:$.trim()}:{},...typeof M=="string"&&M.trim()?{subAgentEffort:M.trim()}:{},extraMcpServers:m||{},envOverrides:Le(String(n),String(r),process.env,le)};F.log({level:"info",category:"launch",message:"\u4F20\u9012\u7ED9 Adapter \u7684\u5B8C\u6574 AdapterSessionConfig",source:"bridge",agentId:I,sessionId:_,details:{adapterModel:j.model??null,adapterInstanceAiConfig:j.instanceAiConfig?ee(j.instanceAiConfig):null,adapterMcpConfigPath:j.mcpConfigPath??null}}),await F.flush(),f.set(_,{agentId:I,workspacePath:r,sessionId:_,config:j,providerId:x,seq:0,runtimeAccessToken:de,schedulerBaseUrl:B}),t.json({sessionId:_,status:"starting",runtimeStatus:"starting",agentId:I,workspacePath:r,command:j.command,mode:"sdk"}),cn({sessionId:_,agentId:I,config:j,adapter:ve,providerId:x,idleInputAutoCommand:T,nonInputAutoCommand:y,runtimeAccessToken:de,requestedSchedulerBaseUrl:B})}catch(O){if(_&&(E(_),f.delete(_)),I&&K.delete(I),!t.headersSent){const k=O instanceof Error?O.message:String(O);t.status(500).json({error:`Failed to start SDK session: ${k}`})}}}async function cn(e){const{sessionId:t,agentId:n,config:r,adapter:s,providerId:o,idleInputAutoCommand:i,nonInputAutoCommand:a,runtimeAccessToken:c,requestedSchedulerBaseUrl:u}=e;try{await s.startSession(t,r),(i||a)&&s.setIdleCommands?.(t,{idleInputCommand:i||"",nonInputCommand:a||""});let d;if(pt(o))try{if(await new Promise(m=>setTimeout(m,500)),d=s.getSessionPid?.(t),!d){const p=(await Je()).find(T=>T.agentId===n||T.cwd===r.workingDirectory||T.command.includes(r.workingDirectory));p&&(d=p.pid,console.log(`[Runtime] Found SDK process PID: ${d} for agentId=${n}`))}}catch(m){console.warn(`[Runtime] Failed to get SDK PID for agentId=${n}:`,m)}const l=oe(s.getSessionStatus(t),f.get(t)?.currentRuntimeStatus,f.get(t)?.currentRuntimeActionType);await it({sessionId:t,agentId:n,workspacePath:r.workingDirectory,command:r.command,startedAt:new Date().toISOString(),status:"running",runtimeStatus:l,mode:"sdk",pid:d,providerSessionId:s.getProviderSessionId(t)}),d&&await ye().startProcess({agentId:n,sessionId:t,pid:d,mode:"sdk",workspacePath:r.workingDirectory,command:r.command}),await un({agentId:n,sessionId:t,sdkPid:d,status:l,schedulerBaseUrl:u,runtimeAccessToken:c})}catch(d){E(t),f.delete(t),await v(t);const l=d instanceof Error?d.message:String(d);console.error(`[Runtime] Background SDK session startup failed for agentId=${n}: ${l}`),await dn({agentId:n,sessionId:t,schedulerBaseUrl:u,runtimeAccessToken:c,errorMessage:l})}finally{K.delete(n)}}async function un(e){const{agentId:t,sessionId:n,sdkPid:r,status:s,schedulerBaseUrl:o,runtimeAccessToken:i}=e;if(!o||!i){h.debug(`[Runtime] notifyServerSessionReady skipped: missing schedulerBaseUrl or runtimeAccessToken for agentId=${t}`);return}const a=`${o.replace(/\/+$/,"")}/api/runtime/callback/status`;try{await ke.post(a,{agentId:t,sessionId:n,status:s,pid:r},{headers:{"X-Runtime-Token":i,"Content-Type":"application/json"},timeout:5e3}),h.debug(`[Runtime] Notified server session ready: agentId=${t}, sessionId=${n}, status=${s}`)}catch(c){const u=c instanceof Error?c.message:String(c);h.warn(`[Runtime] Failed to notify server session ready: agentId=${t}, error=${u}`)}}async function dn(e){const{agentId:t,sessionId:n,schedulerBaseUrl:r,runtimeAccessToken:s}=e;if(!r||!s)return;const o=`${r.replace(/\/+$/,"")}/api/runtime/callback/status`;try{await ke.post(o,{agentId:t,sessionId:n,status:"stopped"},{headers:{"X-Runtime-Token":s,"Content-Type":"application/json"},timeout:5e3}),h.debug(`[Runtime] Notified server session failed: agentId=${t}, sessionId=${n}`)}catch(i){const a=i instanceof Error?i.message:String(i);h.warn(`[Runtime] Failed to notify server session failed: agentId=${t}, error=${a}`)}}A.post("/sdk/message",S,async(e,t)=>{const{sessionId:n,message:r}=e.body||{};if(!n||!r){t.status(400).json({error:"sessionId and message are required"});return}const s=f.get(n);if(!s){t.status(404).json({error:"SDK session not found"});return}try{const o=s.providerId||"claude-code";await C.get(o).sendMessage(n,r),t.json({ok:!0})}catch(o){const i=o instanceof Error?o.message:String(o);t.status(500).json({error:i})}}),A.post("/sdk/answer",S,async(e,t)=>{const{sessionId:n,answers:r}=e.body||{};if(!n||!r){t.status(400).json({error:"sessionId and answers are required"});return}const s=f.get(n);if(!s){t.status(404).json({error:"SDK session not found"});return}try{const o=s.providerId||"claude-code",i=C.get(o);i.sendQuestionAnswer&&await i.sendQuestionAnswer(n,r),t.json({ok:!0})}catch(o){const i=o instanceof Error?o.message:String(o);t.status(500).json({error:i})}}),A.post("/sdk/confirm",S,async(e,t)=>{const{sessionId:n,accept:r}=e.body||{};if(!n){t.status(400).json({error:"sessionId is required"});return}const s=f.get(n);if(!s){t.status(404).json({error:"SDK session not found"});return}try{const o=s.providerId||"claude-code";await C.get(o).sendConfirmation(n,!!r),t.json({ok:!0})}catch(o){const i=o instanceof Error?o.message:String(o);t.status(500).json({error:i})}}),A.post("/sdk/abort",S,async(e,t)=>{const{sessionId:n}=e.body||{};if(!n){t.status(400).json({error:"sessionId is required"});return}const r=f.get(n);if(!r){t.status(404).json({error:"SDK session not found"});return}try{const s=r.providerId||"claude-code";await C.get(s).abortCurrentTurn(n),t.json({ok:!0})}catch(s){const o=s instanceof Error?s.message:String(s);t.status(500).json({error:o})}}),A.post("/sdk/stop",S,async(e,t)=>{const{sessionId:n}=e.body||{};if(!n){t.status(400).json({error:"sessionId is required"});return}const r=f.get(n);if(!r){t.json({ok:!0,status:"stopped"});return}try{const s=r.providerId||"claude-code",o=C.get(s);o.abortCurrentTurn(n).catch(()=>{}),await o.terminateSession(n),E(n),f.delete(n),se(n),await v(n),await ye().removeProcess(r.agentId),await D(r.agentId,null,"stopped",void 0,void 0,{runtimeAccessToken:r.runtimeAccessToken,schedulerBaseUrl:r.schedulerBaseUrl}),t.json({ok:!0,status:"stopped"})}catch(s){const o=s instanceof Error?s.message:String(s);t.status(500).json({error:o})}}),A.get("/sdk/status/:sessionId",S,(e,t)=>{const n=e.params.sessionId;if(!n){t.status(400).json({error:"sessionId is required"});return}const r=f.get(n);if(!r){t.status(404).json({error:"SDK session not found"});return}try{const s=r.providerId||"claude-code",o=C.get(s),i=o.getSessionStatus(n),a=o.getConversation(n);t.json({sessionId:n,status:i,agentId:r.agentId,workspacePath:r.workspacePath,messageCount:a.length})}catch(s){const o=s instanceof Error?s.message:String(s);t.status(500).json({error:o})}}),A.get("/sdk/capabilities/:sessionId",S,(e,t)=>{const n=e.params.sessionId;if(!n){t.status(400).json({error:"sessionId is required"});return}const r=f.get(n);if(!r){t.status(404).json({error:"SDK session not found"});return}try{const s=r.providerId||"claude-code",i=C.get(s).getCapabilities?.(n)??null;t.json({ok:!0,sessionId:n,agentId:r.agentId,capabilities:i,fetchedAt:Date.now()})}catch(s){const o=s instanceof Error?s.message:String(s);t.status(500).json({error:o})}}),A.get("/sdk/pending-questions/:sessionId",S,(e,t)=>{const n=e.params.sessionId;if(!n){t.status(400).json({error:"sessionId is required"});return}const r=f.get(n);if(!r){t.status(404).json({error:"SDK session not found"});return}try{const s=r.providerId||"claude-code",i=C.get(s).getPendingQuestion?.(n)??null;t.json({ok:!0,sessionId:n,agentId:r.agentId,pendingQuestion:i?{toolCallId:i.toolCallId,questions:i.questions}:null,fetchedAt:Date.now()})}catch(s){const o=s instanceof Error?s.message:String(s);t.status(500).json({error:o})}}),A.post("/command",S,async(e,t)=>{const{sessionId:n,command:r}=e.body||{};if(!n||r===void 0||r===null){t.status(400).json({error:"sessionId and command are required"});return}try{const s=await xt(String(n),r);t.json({ok:!0,mode:"terminal",...s})}catch(s){const o=s instanceof Error?s.message:String(s);t.status(500).json({error:o})}}),A.post("/input",S,async(e,t)=>{const{sessionId:n,input:r,mode:s}=e.body||{};if(!n||r===void 0||r===null){t.status(400).json({error:"sessionId and input are required"});return}const o=f.get(n);if(!o){t.status(404).json({error:"SDK session not found"});return}try{const i=o.providerId||"claude-code",a=C.get(i);if(s==="shortcut"){if(!a.sendShortcutInput){t.status(400).json({error:"shortcut input is not supported by this SDK provider"});return}await a.sendShortcutInput(n,String(r)),t.json({ok:!0,mode:"shortcut"});return}await a.sendMessage(n,await Et(r)),t.json({ok:!0,mode:"sdk"})}catch(i){const a=i instanceof Error?i.message:String(i);t.status(500).json({error:a})}}),A.post("/update-auto-commands",S,async(e,t)=>{const{agentId:n,idleInputAutoCommand:r="",nonInputAutoCommand:s=""}=e.body||{},o=String(n||"").trim();if(!o){t.status(400).json({error:"agentId is required"});return}const i=[...f.values()].find(c=>c.agentId===o),a={idleInputCommand:String(r||""),nonInputCommand:String(s||"")};try{let c=!1;i&&(C.get(i.providerId||"claude-code").setIdleCommands?.(i.sessionId,a),c=!0);const u=await ot(o,{idleInputAutoCommand:a.idleInputCommand,nonInputAutoCommand:a.nonInputCommand});t.json({ok:!0,mode:"sdk",sessionUpdated:c,persistedUpdated:u})}catch(c){const u=c instanceof Error?c.message:String(c);t.status(500).json({ok:!1,mode:"sdk",error:u})}}),A.post("/resize",S,(e,t)=>{const{sessionId:n,cols:r,rows:s}=e.body||{};if(!n||!r||!s){t.status(400).json({error:"sessionId, cols, rows are required"});return}if(!f.has(n)){t.status(404).json({error:"SDK session not found"});return}t.json({ok:!0,mode:"sdk",noop:!0,cols:Number(r),rows:Number(s)})}),A.post("/reset-context",S,async(e,t)=>{const{sessionId:n,reason:r}=e.body||{};if(!n){t.status(400).json({error:"sessionId is required"});return}const s=f.get(n);if(!s){t.status(404).json({error:"SDK session not found"});return}const o=s.providerId||"claude-code",i=C.get(o),a=i.resetContext?.bind(i),c=ft(o,!!a);if(!c.supported){t.status(c.statusCode).json({error:c.error});return}if(!a){t.status(500).json({error:"ACode adapter does not expose resetContext"});return}try{await a(String(n));const u=oe(i.getSessionStatus(String(n)),s.currentRuntimeStatus,s.currentRuntimeActionType);t.json({ok:!0,contextReset:!0,sessionId:n,status:u,runtimeStatus:u,agentId:s.agentId,mode:"sdk",providerId:o,reason:typeof r=="string"&&r.trim()?r.trim():void 0})}catch(u){const d=u instanceof Error?u.message:String(u);t.status(500).json({ok:!1,contextReset:!1,mode:"sdk",error:d})}}),A.post("/stop",S,async(e,t)=>{const{sessionId:n}=e.body||{},r=gt(e.body);if(!n){t.status(400).json({error:"sessionId is required"});return}const s=f.get(n);if(!s){await v(n),t.json({ok:!0,status:"stopped",mode:"none",statusNotified:!1,reason:r.reason});return}const o=s.agentId;try{console.log(`[Runtime] Stopping SDK session ${n}; suppressStatus=${!r.statusNotified}, reason=${r.reason||"unspecified"}`);const i=s.providerId||"claude-code",a=C.get(i);a.abortCurrentTurn(n).catch(()=>{}),await a.terminateSession(n),E(n),f.delete(n),se(n),await v(n),await ye().removeProcess(o),r.statusNotified?await D(o,null,"stopped",void 0,void 0,{runtimeAccessToken:s.runtimeAccessToken,schedulerBaseUrl:s.schedulerBaseUrl}):console.log(`[Runtime] Stopped status notification suppressed for SDK session ${n}; reason=${r.reason||"unspecified"}`),t.json({ok:!0,status:"stopped",mode:"sdk",statusNotified:r.statusNotified,reason:r.reason})}catch(i){const a=i instanceof Error?i.message:String(i);console.error(`[Runtime] Failed to terminate SDK session ${n}:`,a),t.status(500).json({ok:!1,status:"stop_failed",mode:"sdk",error:a})}}),A.post("/stop-all",S,async(e,t)=>{const n=[],r=[];for(const[s,o]of f.entries())try{const i=o.providerId||"claude-code",a=C.get(i);a.abortCurrentTurn(s).catch(()=>{}),await a.terminateSession(s),E(s),f.delete(s),se(s),n.push(s),await D(o.agentId,null,"stopped",void 0,void 0,{runtimeAccessToken:o.runtimeAccessToken,schedulerBaseUrl:o.schedulerBaseUrl})}catch(i){const a=i instanceof Error?i.message:String(i);r.push(`SDK ${s}: ${a}`)}try{await rt([]),console.log("[Runtime] \u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD")}catch(s){const o=s instanceof Error?s.message:String(s);r.push(`Persistence: ${o}`)}t.json({ok:!0,stopped:{sdk:n.length},errors:r.length>0?r:void 0})}),A.get("/events/last/:n",S,(e,t)=>{const n=parseInt(e.params.n,10);if(!Number.isFinite(n)||n<1){t.status(400).json({error:":n must be a positive integer"});return}const r=be.getLastN(n);t.json({ok:!0,count:r.length,entries:r})}),A.get("/events/since/:index",S,(e,t)=>{const n=parseInt(e.params.index,10);if(!Number.isFinite(n)||n<0){t.status(400).json({error:":index must be a non-negative integer"});return}const r=be.getSinceIndex(n);t.json({ok:!0,entries:r.entries,nextIndex:r.nextIndex,oldestIndex:r.oldestIndex,dataLoss:r.oldestIndex>n+1})}),A.get("/events/push-stats",S,(e,t)=>{const n=et();t.json({ok:!0,count:n.length,agents:n})});export{It as buildAcodeCommandOutputPath,je as buildClaudeCodeLaunchConfig,Le as buildRuntimeEnv,Nt as buildSdkOutputMetadata,Bt as buildToolResultTimelineActionInfo,En as cleanupSdkSessionByAgentId,St as countTerminalOutputLines,Me as createTerminalOutputDecoder,re as decodeTerminalOutputChunk,_t as evaluatePersistedSessionReuse,Tt as formatCommandInactivityTimeoutNotice,At as formatCommandOutputSpilloverNotice,Ot as formatSdkOutputEvent,Ie as formatTerminalPrompt,kt as normalizeTerminalCommandInput,gt as parseRuntimeStopOptions,bt as parseTerminalDirectoryChangeTarget,ce as raceSendOutputWithRetry,ft as resolveRuntimeContextResetSupport,Zt as resolveRuntimeStatusFromProviderEvent,Et as resolveSdkInputMessage,Ne as resolveSdkProviderId,oe as resolveSdkSessionDisplayStatus,yt as resolveStatusChangeRuntimeState,ne as resolveStatusChangeUsage,Ct as resolveTerminalOutputEncoding,f as sdkSessions,pt as shouldProbeSdkProcessPid,A as terminalRouter};
54
+