@ai-setting/roy-agent-core 1.0.0

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 (378) hide show
  1. package/dist/index.js +99145 -0
  2. package/package.json +114 -0
  3. package/src/config/config-component.test.ts +627 -0
  4. package/src/config/config-component.ts +906 -0
  5. package/src/config/config-parser.test.ts +319 -0
  6. package/src/config/config-parser.ts +203 -0
  7. package/src/config/decentralized-config.test.ts +740 -0
  8. package/src/config/env-key.ts +210 -0
  9. package/src/config/env-source.test.ts +252 -0
  10. package/src/config/env-source.ts +301 -0
  11. package/src/config/file-source.test.ts +357 -0
  12. package/src/config/file-source.ts +421 -0
  13. package/src/config/index.ts +24 -0
  14. package/src/config/protocol-resolver.test.ts +217 -0
  15. package/src/config/protocol-resolver.ts +228 -0
  16. package/src/env/agent/agent-component.abort.test.ts +511 -0
  17. package/src/env/agent/agent-component.record-session.test.ts +349 -0
  18. package/src/env/agent/agent-component.test.ts +1389 -0
  19. package/src/env/agent/agent-component.tool-error.test.ts +327 -0
  20. package/src/env/agent/agent-component.ts +1711 -0
  21. package/src/env/agent/agent-config-registration.test.ts +226 -0
  22. package/src/env/agent/agent-config-registration.ts +46 -0
  23. package/src/env/agent/agent-reminder-plugin.integration.test.ts +243 -0
  24. package/src/env/agent/index.ts +10 -0
  25. package/src/env/agent/summary-agent.parse-hint.test.ts +360 -0
  26. package/src/env/agent/summary-agent.ts +508 -0
  27. package/src/env/agent/types.ts +536 -0
  28. package/src/env/commands/commands-component.test.ts +364 -0
  29. package/src/env/commands/commands-component.ts +604 -0
  30. package/src/env/commands/commands-config-registration.test.ts +198 -0
  31. package/src/env/commands/commands-config-registration.ts +38 -0
  32. package/src/env/commands/index.ts +21 -0
  33. package/src/env/commands/parser.test.ts +203 -0
  34. package/src/env/commands/parser.ts +115 -0
  35. package/src/env/commands/types.ts +184 -0
  36. package/src/env/commands-prompt-integration.test.ts +243 -0
  37. package/src/env/component-env.test.ts +119 -0
  38. package/src/env/component.ts +335 -0
  39. package/src/env/constants.test.ts +72 -0
  40. package/src/env/constants.ts +123 -0
  41. package/src/env/debug/debug-component.test.ts +114 -0
  42. package/src/env/debug/debug-component.ts +547 -0
  43. package/src/env/debug/formatters/index.ts +9 -0
  44. package/src/env/debug/formatters/repl-formatter.test.ts +139 -0
  45. package/src/env/debug/formatters/repl-formatter.ts +358 -0
  46. package/src/env/debug/formatters/trace-formatter.test.ts +119 -0
  47. package/src/env/debug/formatters/trace-formatter.ts +191 -0
  48. package/src/env/debug/formatters/tree-formatter.test.ts +107 -0
  49. package/src/env/debug/formatters/tree-formatter.ts +325 -0
  50. package/src/env/debug/index.ts +38 -0
  51. package/src/env/debug/parser/regex-parser.test.ts +201 -0
  52. package/src/env/debug/parser/regex-parser.ts +196 -0
  53. package/src/env/debug/parser/span-builder.test.ts +241 -0
  54. package/src/env/debug/parser/span-builder.ts +386 -0
  55. package/src/env/debug/reader/log-reader.test.ts +170 -0
  56. package/src/env/debug/reader/log-reader.ts +186 -0
  57. package/src/env/debug/reader/span-db-reader.test.ts +118 -0
  58. package/src/env/debug/reader/span-db-reader.ts +201 -0
  59. package/src/env/debug/types.test.ts +187 -0
  60. package/src/env/debug/types.ts +171 -0
  61. package/src/env/environment-init.test.ts +183 -0
  62. package/src/env/environment-lifecycle.test.ts +516 -0
  63. package/src/env/environment-service.test.ts +332 -0
  64. package/src/env/environment.handle-query.test.ts +96 -0
  65. package/src/env/environment.test.ts +232 -0
  66. package/src/env/environment.ts +708 -0
  67. package/src/env/errors.test.ts +165 -0
  68. package/src/env/errors.ts +157 -0
  69. package/src/env/event-source/event-source-agent-handler.test.ts +193 -0
  70. package/src/env/event-source/event-source-agent-handler.ts +111 -0
  71. package/src/env/event-source/event-source-component.process-cleanup.test.ts +236 -0
  72. package/src/env/event-source/event-source-component.stop.test.ts +346 -0
  73. package/src/env/event-source/event-source-component.test.ts +1207 -0
  74. package/src/env/event-source/event-source-component.ts +1379 -0
  75. package/src/env/event-source/event-source-config-registration.test.ts +242 -0
  76. package/src/env/event-source/event-source-config-registration.ts +37 -0
  77. package/src/env/event-source/event-source-integration.test.ts +320 -0
  78. package/src/env/event-source/event-source-platform.test.ts +630 -0
  79. package/src/env/event-source/types.ts +298 -0
  80. package/src/env/hook/global-hook-manager.ts +162 -0
  81. package/src/env/hook/hook-manager.test.ts +374 -0
  82. package/src/env/hook/hook-manager.ts +309 -0
  83. package/src/env/hook/index.ts +38 -0
  84. package/src/env/hook/types.ts +138 -0
  85. package/src/env/index.ts +144 -0
  86. package/src/env/interface.ts +203 -0
  87. package/src/env/llm/hooks.test.ts +293 -0
  88. package/src/env/llm/hooks.ts +316 -0
  89. package/src/env/llm/index.ts +61 -0
  90. package/src/env/llm/invoke-threshold-check.test.ts +88 -0
  91. package/src/env/llm/invoke-timeout.test.ts +54 -0
  92. package/src/env/llm/invoke.test.ts +71 -0
  93. package/src/env/llm/invoke.ts +1039 -0
  94. package/src/env/llm/llm-config.test.ts +523 -0
  95. package/src/env/llm/llm.test.ts +233 -0
  96. package/src/env/llm/llm.ts +568 -0
  97. package/src/env/llm/provider.test.ts +182 -0
  98. package/src/env/llm/provider.ts +108 -0
  99. package/src/env/llm/transform.test.ts +251 -0
  100. package/src/env/llm/transform.ts +286 -0
  101. package/src/env/llm/types.test.ts +580 -0
  102. package/src/env/llm/types.ts +424 -0
  103. package/src/env/log-trace/decorator-otel.test.ts +182 -0
  104. package/src/env/log-trace/decorator.ts +230 -0
  105. package/src/env/log-trace/index.ts +79 -0
  106. package/src/env/log-trace/log-trace-component.test.ts +242 -0
  107. package/src/env/log-trace/log-trace-component.ts +497 -0
  108. package/src/env/log-trace/log-trace-config-registration.test.ts +348 -0
  109. package/src/env/log-trace/log-trace-config-registration.ts +45 -0
  110. package/src/env/log-trace/logger.test.ts +149 -0
  111. package/src/env/log-trace/logger.ts +522 -0
  112. package/src/env/log-trace/opentelemetry/cli-propagation.test.ts +147 -0
  113. package/src/env/log-trace/opentelemetry/cli-propagation.ts +194 -0
  114. package/src/env/log-trace/opentelemetry/integration.test.ts +668 -0
  115. package/src/env/log-trace/opentelemetry/mod.ts +25 -0
  116. package/src/env/log-trace/opentelemetry/propagation-env.test.ts +181 -0
  117. package/src/env/log-trace/opentelemetry/propagation-env.ts +136 -0
  118. package/src/env/log-trace/opentelemetry/propagation.test.ts +259 -0
  119. package/src/env/log-trace/opentelemetry/propagation.ts +215 -0
  120. package/src/env/log-trace/opentelemetry/tracer-provider-context.test.ts +166 -0
  121. package/src/env/log-trace/opentelemetry/tracer-provider.test.ts +379 -0
  122. package/src/env/log-trace/opentelemetry/tracer-provider.ts +612 -0
  123. package/src/env/log-trace/span-storage.test.ts +145 -0
  124. package/src/env/log-trace/span-storage.ts +230 -0
  125. package/src/env/log-trace/trace-context.test.ts +187 -0
  126. package/src/env/log-trace/trace-context.ts +162 -0
  127. package/src/env/log-trace/types.test.ts +63 -0
  128. package/src/env/log-trace/types.ts +172 -0
  129. package/src/env/mcp/README.md +244 -0
  130. package/src/env/mcp/__integration__/mcp-component.integration.test.ts +373 -0
  131. package/src/env/mcp/config.test.ts +74 -0
  132. package/src/env/mcp/config.ts +116 -0
  133. package/src/env/mcp/index.ts +41 -0
  134. package/src/env/mcp/loader.test.ts +161 -0
  135. package/src/env/mcp/loader.ts +209 -0
  136. package/src/env/mcp/mcp-component.test.ts +111 -0
  137. package/src/env/mcp/mcp-component.ts +358 -0
  138. package/src/env/mcp/mcp-config-registration.test.ts +304 -0
  139. package/src/env/mcp/mcp-config-registration.ts +50 -0
  140. package/src/env/mcp/scanner.test.ts +170 -0
  141. package/src/env/mcp/scanner.ts +246 -0
  142. package/src/env/mcp/tool/adapter.test.ts +520 -0
  143. package/src/env/mcp/tool/adapter.ts +521 -0
  144. package/src/env/mcp/tool/index.ts +5 -0
  145. package/src/env/mcp/types.test.ts +171 -0
  146. package/src/env/mcp/types.ts +79 -0
  147. package/src/env/memory/README.md +177 -0
  148. package/src/env/memory/built-in/index.ts +59 -0
  149. package/src/env/memory/built-in/recall-memory.ts +103 -0
  150. package/src/env/memory/built-in/record-memory.ts +148 -0
  151. package/src/env/memory/index.ts +20 -0
  152. package/src/env/memory/memory-component.test.ts +239 -0
  153. package/src/env/memory/memory-component.ts +503 -0
  154. package/src/env/memory/memory-config-registration.test.ts +67 -0
  155. package/src/env/memory/memory-config-registration.ts +48 -0
  156. package/src/env/memory/memory-config.ts +45 -0
  157. package/src/env/memory/memory-file.test.ts +268 -0
  158. package/src/env/memory/plugin/index.ts +48 -0
  159. package/src/env/memory/plugin/memory-agent.test.ts +249 -0
  160. package/src/env/memory/plugin/memory-agent.ts +365 -0
  161. package/src/env/memory/plugin/memory-manager.ts +198 -0
  162. package/src/env/memory/plugin/memory-plugin-agent.test.ts +145 -0
  163. package/src/env/memory/plugin/memory-plugin.ts +210 -0
  164. package/src/env/memory/plugin/plugin-simplified.test.ts +51 -0
  165. package/src/env/memory/plugin/recall-memory.test.ts +106 -0
  166. package/src/env/memory/plugin/recall-memory.ts +53 -0
  167. package/src/env/memory/plugin/types.ts +101 -0
  168. package/src/env/memory/tools/memory-agent-tools.ts +228 -0
  169. package/src/env/memory/types.ts +85 -0
  170. package/src/env/paths.ts +118 -0
  171. package/src/env/prompt/index.ts +18 -0
  172. package/src/env/prompt/memory-prompts.test.ts +91 -0
  173. package/src/env/prompt/prompt-component.test.ts +491 -0
  174. package/src/env/prompt/prompt-component.ts +619 -0
  175. package/src/env/prompt/prompt-config-registration.test.ts +213 -0
  176. package/src/env/prompt/prompt-config-registration.ts +39 -0
  177. package/src/env/prompt/prompts-index.ts +504 -0
  178. package/src/env/prompt/renderer.ts +67 -0
  179. package/src/env/prompt/types.ts +136 -0
  180. package/src/env/session/hooks.ts +18 -0
  181. package/src/env/session/index.ts +37 -0
  182. package/src/env/session/search-query-parser.test.ts +425 -0
  183. package/src/env/session/search-query-parser.ts +171 -0
  184. package/src/env/session/session-checkpoint.test.ts +523 -0
  185. package/src/env/session/session-component.extract-recent-messages.test.ts +209 -0
  186. package/src/env/session/session-component.test.ts +132 -0
  187. package/src/env/session/session-component.ts +1249 -0
  188. package/src/env/session/session-config-registration.test.ts +138 -0
  189. package/src/env/session/session-config-registration.ts +52 -0
  190. package/src/env/session/session-message-converter.test.ts +763 -0
  191. package/src/env/session/session-message-converter.ts +415 -0
  192. package/src/env/session/session-message-e2e.test.ts +448 -0
  193. package/src/env/session/session-search.test.ts +391 -0
  194. package/src/env/session/session-store.test.ts +362 -0
  195. package/src/env/session/session-store.ts +141 -0
  196. package/src/env/session/storage/index.ts +6 -0
  197. package/src/env/session/storage/memory.ts +502 -0
  198. package/src/env/session/storage/sqlite.ts +794 -0
  199. package/src/env/session/types.ts +742 -0
  200. package/src/env/skill/config.ts +39 -0
  201. package/src/env/skill/index.ts +6 -0
  202. package/src/env/skill/parser.test.ts +116 -0
  203. package/src/env/skill/parser.ts +77 -0
  204. package/src/env/skill/scanner.test.ts +211 -0
  205. package/src/env/skill/scanner.ts +119 -0
  206. package/src/env/skill/skill-component.test.ts +234 -0
  207. package/src/env/skill/skill-component.ts +352 -0
  208. package/src/env/skill/skill-config-registration.test.ts +60 -0
  209. package/src/env/skill/skill-config-registration.ts +43 -0
  210. package/src/env/skill/tool/index.ts +1 -0
  211. package/src/env/skill/tool/skill-tool.test.ts +100 -0
  212. package/src/env/skill/tool/skill-tool.ts +72 -0
  213. package/src/env/skill/types.ts +64 -0
  214. package/src/env/task/delegate/delegate-tool.test.ts +498 -0
  215. package/src/env/task/delegate/delegate-tool.ts +1014 -0
  216. package/src/env/task/delegate/index.ts +18 -0
  217. package/src/env/task/delegate/stop-tool.test.ts +140 -0
  218. package/src/env/task/delegate/stop-tool.ts +119 -0
  219. package/src/env/task/delegate/task-events.test.ts +178 -0
  220. package/src/env/task/delegate/task-events.ts +143 -0
  221. package/src/env/task/hooks/contexts.test.ts +92 -0
  222. package/src/env/task/hooks/contexts.ts +192 -0
  223. package/src/env/task/hooks/index.ts +23 -0
  224. package/src/env/task/hooks/task-hook-points.test.ts +32 -0
  225. package/src/env/task/hooks/task-hook-points.ts +54 -0
  226. package/src/env/task/index.ts +7 -0
  227. package/src/env/task/plugins/index.ts +13 -0
  228. package/src/env/task/plugins/task-plugin.test.ts +74 -0
  229. package/src/env/task/plugins/task-plugin.ts +89 -0
  230. package/src/env/task/plugins/task-tag-plugin.test.ts +377 -0
  231. package/src/env/task/plugins/task-tag-plugin.ts +319 -0
  232. package/src/env/task/plugins/task-workflow-extractor.integration.test.ts +226 -0
  233. package/src/env/task/plugins/workflow-extractor-agent.test.ts +107 -0
  234. package/src/env/task/plugins/workflow-extractor-agent.ts +225 -0
  235. package/src/env/task/storage/index.ts +6 -0
  236. package/src/env/task/storage/sqlite-task-store.test.ts +283 -0
  237. package/src/env/task/storage/sqlite-task-store.ts +903 -0
  238. package/src/env/task/storage/task-search.test.ts +291 -0
  239. package/src/env/task/tag-service.test.ts +198 -0
  240. package/src/env/task/tag-service.ts +264 -0
  241. package/src/env/task/task-component.test.ts +193 -0
  242. package/src/env/task/task-component.ts +658 -0
  243. package/src/env/task/task-config-registration.test.ts +57 -0
  244. package/src/env/task/task-config-registration.ts +37 -0
  245. package/src/env/task/task-types.test.ts +137 -0
  246. package/src/env/task/tools/complete-tool.ts +44 -0
  247. package/src/env/task/tools/create-tool.ts +49 -0
  248. package/src/env/task/tools/delete-tool.ts +43 -0
  249. package/src/env/task/tools/get-tool.ts +59 -0
  250. package/src/env/task/tools/index.ts +10 -0
  251. package/src/env/task/tools/list-tool.ts +40 -0
  252. package/src/env/task/tools/operation/create-tool.ts +48 -0
  253. package/src/env/task/tools/operation/delete-tool.ts +43 -0
  254. package/src/env/task/tools/operation/get-tool.ts +43 -0
  255. package/src/env/task/tools/operation/index.ts +9 -0
  256. package/src/env/task/tools/operation/list-tool.ts +40 -0
  257. package/src/env/task/tools/operation/operation-tools.test.ts +274 -0
  258. package/src/env/task/tools/operation/operation-types.ts +75 -0
  259. package/src/env/task/tools/operation/update-tool.ts +47 -0
  260. package/src/env/task/tools/task-tools.test.ts +203 -0
  261. package/src/env/task/tools/task-types.test.ts +75 -0
  262. package/src/env/task/tools/task-types.ts +68 -0
  263. package/src/env/task/tools/update-tool.ts +70 -0
  264. package/src/env/task/types.ts +160 -0
  265. package/src/env/tool/built-in/bash.ts +201 -0
  266. package/src/env/tool/built-in/echo.ts +29 -0
  267. package/src/env/tool/built-in/edit-file.test.ts +136 -0
  268. package/src/env/tool/built-in/edit-file.ts +92 -0
  269. package/src/env/tool/built-in/glob.test.ts +94 -0
  270. package/src/env/tool/built-in/glob.ts +65 -0
  271. package/src/env/tool/built-in/grep.test.ts +122 -0
  272. package/src/env/tool/built-in/grep.ts +108 -0
  273. package/src/env/tool/built-in/index.ts +44 -0
  274. package/src/env/tool/built-in/read-file.test.ts +84 -0
  275. package/src/env/tool/built-in/read-file.ts +75 -0
  276. package/src/env/tool/built-in/write-file.test.ts +119 -0
  277. package/src/env/tool/built-in/write-file.ts +68 -0
  278. package/src/env/tool/index.ts +24 -0
  279. package/src/env/tool/registry.test.ts +257 -0
  280. package/src/env/tool/registry.ts +167 -0
  281. package/src/env/tool/tool-component.test.ts +559 -0
  282. package/src/env/tool/tool-component.ts +563 -0
  283. package/src/env/tool/tool-config-registration.test.ts +249 -0
  284. package/src/env/tool/tool-config-registration.ts +46 -0
  285. package/src/env/tool/types.ts +267 -0
  286. package/src/env/tool/validator.test.ts +143 -0
  287. package/src/env/tool/validator.ts +44 -0
  288. package/src/env/types.ts +180 -0
  289. package/src/env/workflow/ask-user-tool-registration.test.ts +216 -0
  290. package/src/env/workflow/complex-workflow.integration.test.ts +1900 -0
  291. package/src/env/workflow/decorators/decorator-node.ts +229 -0
  292. package/src/env/workflow/decorators/decorator.test.ts +196 -0
  293. package/src/env/workflow/decorators/edge.ts +82 -0
  294. package/src/env/workflow/decorators/index.ts +31 -0
  295. package/src/env/workflow/decorators/node-as.ts +98 -0
  296. package/src/env/workflow/decorators/workflow.ts +54 -0
  297. package/src/env/workflow/engine/dag-manager.test.ts +570 -0
  298. package/src/env/workflow/engine/dag-manager.ts +594 -0
  299. package/src/env/workflow/engine/engine.ts +1422 -0
  300. package/src/env/workflow/engine/event-bus.test.ts +359 -0
  301. package/src/env/workflow/engine/event-bus.ts +156 -0
  302. package/src/env/workflow/engine/executor-agent-session.test.ts +84 -0
  303. package/src/env/workflow/engine/executor.test.ts +619 -0
  304. package/src/env/workflow/engine/executor.ts +593 -0
  305. package/src/env/workflow/engine/index.ts +24 -0
  306. package/src/env/workflow/engine/node-registry.test.ts +560 -0
  307. package/src/env/workflow/engine/node-registry.ts +289 -0
  308. package/src/env/workflow/engine/resume-removed.test.ts +22 -0
  309. package/src/env/workflow/engine/scheduler.test.ts +715 -0
  310. package/src/env/workflow/engine/scheduler.ts +318 -0
  311. package/src/env/workflow/engine/workflow-engine.test.ts +815 -0
  312. package/src/env/workflow/extractor/workflow-converter.ts +306 -0
  313. package/src/env/workflow/fixtures.ts +380 -0
  314. package/src/env/workflow/index.ts +38 -0
  315. package/src/env/workflow/integration/run-resume-unified.test.ts +186 -0
  316. package/src/env/workflow/integration/service-integration.test.ts +267 -0
  317. package/src/env/workflow/metadata/keys.ts +12 -0
  318. package/src/env/workflow/nodes/agent-component-adapter.test.ts +318 -0
  319. package/src/env/workflow/nodes/agent-component-adapter.ts +448 -0
  320. package/src/env/workflow/nodes/agent-node.test.ts +371 -0
  321. package/src/env/workflow/nodes/agent-node.ts +598 -0
  322. package/src/env/workflow/nodes/ask-user-node.ts +113 -0
  323. package/src/env/workflow/nodes/condition-node.ts +200 -0
  324. package/src/env/workflow/nodes/index.ts +9 -0
  325. package/src/env/workflow/nodes/merge-node.ts +141 -0
  326. package/src/env/workflow/nodes/skill-node.test.ts +253 -0
  327. package/src/env/workflow/nodes/skill-node.ts +393 -0
  328. package/src/env/workflow/nodes/tool-node.test.ts +251 -0
  329. package/src/env/workflow/nodes/tool-node.ts +493 -0
  330. package/src/env/workflow/nodes/workflow-llm-history.test.ts +455 -0
  331. package/src/env/workflow/nodes/workflow-node.test.ts +315 -0
  332. package/src/env/workflow/nodes/workflow-node.ts +311 -0
  333. package/src/env/workflow/service/index.ts +27 -0
  334. package/src/env/workflow/service/registry.test.ts +133 -0
  335. package/src/env/workflow/service/registry.ts +71 -0
  336. package/src/env/workflow/service/workflow-service.test.ts +310 -0
  337. package/src/env/workflow/service/workflow-service.ts +393 -0
  338. package/src/env/workflow/storage/index.ts +28 -0
  339. package/src/env/workflow/storage/mock-repositories.ts +385 -0
  340. package/src/env/workflow/storage/sqlite.test.ts +179 -0
  341. package/src/env/workflow/storage/sqlite.ts +163 -0
  342. package/src/env/workflow/storage/workflow-repo.test.ts +780 -0
  343. package/src/env/workflow/storage/workflow-repo.ts +342 -0
  344. package/src/env/workflow/tools/ask-user-tool.ts +82 -0
  345. package/src/env/workflow/tools/index.ts +26 -0
  346. package/src/env/workflow/tools/run-workflow.test.ts +352 -0
  347. package/src/env/workflow/tools/run-workflow.ts +214 -0
  348. package/src/env/workflow/types/context.ts +18 -0
  349. package/src/env/workflow/types/decorators-types.ts +198 -0
  350. package/src/env/workflow/types/event.test.ts +515 -0
  351. package/src/env/workflow/types/event.ts +193 -0
  352. package/src/env/workflow/types/index.ts +49 -0
  353. package/src/env/workflow/types/run.test.ts +437 -0
  354. package/src/env/workflow/types/run.ts +173 -0
  355. package/src/env/workflow/types/workflow-hil.ts +114 -0
  356. package/src/env/workflow/types/workflow-message.test.ts +138 -0
  357. package/src/env/workflow/types/workflow-message.ts +196 -0
  358. package/src/env/workflow/types/workflow-session.test.ts +95 -0
  359. package/src/env/workflow/types/workflow-session.ts +59 -0
  360. package/src/env/workflow/types/workflow.test.ts +495 -0
  361. package/src/env/workflow/types/workflow.ts +195 -0
  362. package/src/env/workflow/types_compat.ts +51 -0
  363. package/src/env/workflow/utils/create-workflow.ts +47 -0
  364. package/src/env/workflow/utils/execution-state.ts +245 -0
  365. package/src/env/workflow/utils/index.ts +18 -0
  366. package/src/env/workflow/utils/node-registry-helper.ts +58 -0
  367. package/src/env/workflow/utils/recovery-validator.test.ts +460 -0
  368. package/src/env/workflow/utils/recovery-validator.ts +377 -0
  369. package/src/env/workflow/utils/session-parser.test.ts +111 -0
  370. package/src/env/workflow/utils/session-parser.ts +94 -0
  371. package/src/env/workflow/utils/session-recovery.test.ts +334 -0
  372. package/src/env/workflow/utils/session-recovery.ts +188 -0
  373. package/src/env/workflow/utils/template-resolver.test.ts +258 -0
  374. package/src/env/workflow/utils/template-resolver.ts +436 -0
  375. package/src/env/workflow/utils/validation-rules.ts +149 -0
  376. package/src/env/workflow/workflow-component.ts +544 -0
  377. package/src/index.ts +422 -0
  378. package/src/utils/id.ts +21 -0
@@ -0,0 +1,1711 @@
1
+ /**
2
+ * @fileoverview AgentComponent - Agent执行核心组件
3
+ *
4
+ * 基于 roy-agent docs/agent-component-design.md
5
+ * 参考 agent-core Agent.run 实现
6
+ *
7
+ * 核心功能:
8
+ * - ReAct 循环编排
9
+ * - Hook 机制和 Plugin 扩展
10
+ * - 多 Agent 管理
11
+ * - 工具调用
12
+ *
13
+ * 依赖:
14
+ * - ../llm - LLMComponent 用于 LLM 调用
15
+ * - ../log-trace/logger - createLogger 用于日志
16
+ */
17
+
18
+ import { BaseComponent, type ComponentConfig } from "../component";
19
+ import { createLogger } from "../log-trace/logger";
20
+ import type {
21
+ AgentInstance,
22
+ AgentInstanceConfig,
23
+ AgentRunResult,
24
+ AgentContext,
25
+ Plugin,
26
+ HookContext,
27
+ HookPoint,
28
+ Tool,
29
+ ToolCallResult,
30
+ AgentLLMOutput,
31
+ HookActionType,
32
+ } from "./types";
33
+ import type { EnvEvent } from "../types";
34
+ import type { ModelMessage } from "ai";
35
+ import type { LLMMessage } from "../llm/types";
36
+ import { z } from "zod";
37
+
38
+ // LLMComponent 和 ToolComponent 类型
39
+ import type { LLMComponent } from "../llm/llm";
40
+ import type { ToolComponent } from "../tool/tool-component";
41
+ import type { ToolContext, ToolExecuteRequest } from "../tool/types";
42
+ import type { ConfigComponent } from "../../config/config-component";
43
+ import { envKeyToConfigKey } from "../../config/env-key";
44
+ import { AGENT_CONFIG_REGISTRATION, AGENT_DEFAULTS } from "./agent-config-registration";
45
+ import { TracedAs } from "../log-trace/decorator";
46
+ // SessionComponent 类型
47
+ import type { SessionComponent, SessionMessage } from "../session/session-component";
48
+
49
+ // Session Message 转换器
50
+ import { SessionMessageConverter } from "../session/session-message-converter";
51
+
52
+ // AskUserError - 用于工作流暂停的特定错误
53
+ import { AskUserError } from "../workflow/types/workflow-hil";
54
+
55
+ // ============================================================================
56
+ // Logger
57
+ // ============================================================================
58
+
59
+ const logger = createLogger("agent:component");
60
+
61
+ // ============================================================================
62
+ // Type Conversion Utilities
63
+ // ============================================================================
64
+
65
+ /**
66
+ * 将 ModelMessage 转换为 LLMMessage
67
+ *
68
+ * ModelMessage 来自 ai SDK,LLMMessage 是内部格式
69
+ */
70
+ function toLLMMessage(msg: ModelMessage): LLMMessage {
71
+ // 处理 content 字段
72
+ let content: string;
73
+ let toolCallId: string | undefined;
74
+ let toolName: string | undefined;
75
+ // 使用 any 类型避免 ai SDK 和内部 ToolCall 类型不兼容
76
+ let toolCalls: any[] | undefined;
77
+
78
+ if (typeof msg.content === "string") {
79
+ content = msg.content;
80
+ // 对于 tool 消息,提取 toolCallId 和 toolName
81
+ if (msg.role === "tool") {
82
+ toolCallId = (msg as any).toolCallId;
83
+ toolName = (msg as any).toolName;
84
+ }
85
+ // assistant 消息可能包含 toolCalls(ai SDK v6 格式)
86
+ if (msg.role === "assistant" && (msg as any).toolCalls) {
87
+ toolCalls = (msg as any).toolCalls;
88
+ }
89
+ } else if (Array.isArray(msg.content)) {
90
+ // 处理 tool 消息(AI SDK v6 格式)
91
+ if (msg.role === "tool") {
92
+ // 提取 tool-result 的 output
93
+ const toolResult = msg.content.find((part: any) => part.type === "tool-result") as any;
94
+ // output 可能是 { type: 'text', value: '...' } 或纯字符串
95
+ const output = toolResult?.output;
96
+ if (typeof output === "string") {
97
+ content = output;
98
+ } else if (output && typeof output === "object" && "value" in output) {
99
+ content = output.value?.toString() || "";
100
+ } else {
101
+ content = JSON.stringify(output) || "";
102
+ }
103
+ toolCallId = toolResult?.toolCallId;
104
+ toolName = toolResult?.toolName;
105
+ } else {
106
+ // 处理多模态内容,提取文本和 toolCalls
107
+ const textParts = msg.content.filter((part: any) => part.type === "text");
108
+ const toolCallParts = msg.content.filter((part: any) => part.type === "tool-call");
109
+
110
+ // 提取文本内容
111
+ content = textParts.map((part: any) => part.text || "").join("");
112
+
113
+ // 对于 assistant 消息,从 tool-call part 中提取 toolCalls
114
+ // 注意:toModelMessage 生成的格式是扁平的 { toolCallId, toolName, input }
115
+ // 不是嵌套的 { toolCall: { name, arguments } }
116
+ if (msg.role === "assistant" && toolCallParts.length > 0) {
117
+ toolCalls = toolCallParts.map((part: any) => {
118
+ // input 是已解析的参数对象,需要转回字符串给 convertToSDKMessages
119
+ const argsString = typeof part.input === "string"
120
+ ? part.input
121
+ : JSON.stringify(part.input || {});
122
+ const toolName = part.toolName || "unknown";
123
+
124
+ return {
125
+ id: part.toolCallId,
126
+ name: toolName,
127
+ arguments: argsString,
128
+ function: {
129
+ name: toolName,
130
+ arguments: argsString,
131
+ },
132
+ };
133
+ });
134
+
135
+ }
136
+ }
137
+ } else {
138
+ content = "";
139
+ }
140
+
141
+ return {
142
+ role: msg.role as "system" | "user" | "assistant" | "tool",
143
+ content,
144
+ ...(toolCallId ? { toolCallId } : {}),
145
+ ...(toolName ? { name: toolName } : {}),
146
+ ...(toolCalls && toolCalls.length > 0 ? { toolCalls } : {}),
147
+ };
148
+ }
149
+
150
+ // ============================================================================
151
+ // Config Schema
152
+ // ============================================================================
153
+
154
+ /**
155
+ * AgentComponent 默认配置
156
+ */
157
+ const DEFAULT_AGENT_CONFIG = {
158
+ type: "primary" as const,
159
+ maxIterations: 200,
160
+ maxErrorRetries: 3,
161
+ doomLoopThreshold: 5,
162
+ toolTimeout: 60000,
163
+ toolRetries: 3,
164
+ /** 是否过滤 history 中的 tool 消息(默认不过滤,保留完整上下文) */
165
+ filterHistory: false,
166
+ };
167
+
168
+ /**
169
+ * Agent 实例配置 Schema
170
+ */
171
+ const AgentInstanceSchema = z.object({
172
+ type: z.enum(["primary", "sub"]).default("primary"),
173
+ systemPrompt: z.string().optional(),
174
+ behaviorSpecId: z.string().optional(),
175
+ model: z.string().optional(),
176
+ maxIterations: z.number().default(200),
177
+ maxErrorRetries: z.number().default(3),
178
+ doomLoopThreshold: z.number().default(5),
179
+ allowedTools: z.array(z.string()).optional(),
180
+ deniedTools: z.array(z.string()).optional(),
181
+ toolTimeout: z.number().default(60000),
182
+ toolRetries: z.number().default(3),
183
+ /** 是否过滤 history 中的 tool 消息(默认不过滤,保留完整上下文) */
184
+ filterHistory: z.boolean().default(false),
185
+ });
186
+
187
+ /**
188
+ * AgentComponent 配置 Schema
189
+ */
190
+ export const AgentComponentConfigSchema = z.object({
191
+ defaultAgent: AgentInstanceSchema,
192
+ });
193
+
194
+ export type AgentComponentConfig = z.infer<typeof AgentComponentConfigSchema>;
195
+
196
+ // ============================================================================
197
+ // AgentComponentOptions (统一配置机制)
198
+ // ============================================================================
199
+
200
+ /**
201
+ * AgentComponent 配置选项(通过 options 传递)
202
+ *
203
+ * 配置加载顺序(优先级从低到高):
204
+ * 1. File - 配置文件(通过 configPath 指定)
205
+ * 2. Env - 环境变量(通过 envPrefix 配置前缀)
206
+ * 3. Object - 直接传入的 config 对象(最高优先级)
207
+ */
208
+ export interface AgentComponentOptions {
209
+ /** ConfigComponent 实例(必填) */
210
+ configComponent: ConfigComponent;
211
+
212
+ /** 配置文件相对路径(可选,基于 XDG_DATA_HOME) */
213
+ configPath?: string;
214
+
215
+ /** 环境变量前缀(可选,默认 "AGENT") */
216
+ envPrefix?: string;
217
+
218
+ /** 直接传入的配置对象(可选,优先级最高) */
219
+ config?: Partial<AgentComponentConfig>;
220
+ }
221
+
222
+ // ============================================================================
223
+ // AgentComponent
224
+ // ============================================================================
225
+
226
+ /**
227
+ * AgentComponent
228
+ *
229
+ * 提供 Agent 执行能力和 Hook 扩展机制
230
+ */
231
+ export class AgentComponent extends BaseComponent {
232
+ readonly name = "agent";
233
+ readonly version = "1.0.0";
234
+
235
+ private agents: Map<string, AgentInstance> = new Map();
236
+ private config: AgentComponentConfig;
237
+ private llmComponent: LLMComponent | null = null;
238
+ private toolComponent: ToolComponent | null = null;
239
+ private aborted: Map<string, boolean> = new Map();
240
+ private abortControllers: Map<string, AbortController> = new Map(); // Agent Run 的 AbortController,使用 runId 作为 key
241
+ private defaultTools: Tool[] = [];
242
+
243
+ // Doom loop detection cache (per-agent for isolation)
244
+ private doomLoopCaches: Map<string, Map<string, number>> = new Map();
245
+
246
+ /** ConfigComponent 用于配置管理 */
247
+ private configComponent?: ConfigComponent;
248
+
249
+ /** 配置变更 watcher 清理函数 */
250
+ private configWatcher?: () => void;
251
+
252
+ /** Session 消息转换器 */
253
+ private messageConverter = new SessionMessageConverter();
254
+
255
+ /** 构造函数传入的配置(最高优先级) */
256
+ private _constructorConfig?: { defaultAgent?: Partial<AgentComponentConfig["defaultAgent"]> };
257
+
258
+ /** Run ID 计数器(用于生成唯一的 runId) */
259
+ private runCounter = 0;
260
+
261
+ constructor(options: {
262
+ config?: Partial<AgentComponentConfig>;
263
+ } = {}) {
264
+ super();
265
+
266
+ // 保存构造函数传入的配置(最高优先级)
267
+ this._constructorConfig = options.config;
268
+
269
+ this.config = {
270
+ defaultAgent: {
271
+ ...DEFAULT_AGENT_CONFIG,
272
+ ...options.config?.defaultAgent,
273
+ },
274
+ };
275
+ }
276
+
277
+ /**
278
+ * 初始化组件
279
+ *
280
+ * 配置加载优先级(从高到低):
281
+ * 1. Object - 直接传入的 config 对象
282
+ * 2. Env - 环境变量(通过 envPrefix 配置前缀)
283
+ * 3. File - 配置文件(通过 configPath 指定)
284
+ */
285
+ async init(config: ComponentConfig): Promise<void> {
286
+ // 调用基类 init,注入 env
287
+ await super.init(config);
288
+
289
+ // 从 options 获取 ConfigComponent
290
+ const options = config.options as unknown as AgentComponentOptions | undefined;
291
+ if (!options?.configComponent) {
292
+ throw new Error("ConfigComponent is required for AgentComponent initialization");
293
+ }
294
+
295
+ this.configComponent = options.configComponent;
296
+ await this.registerConfig(options);
297
+
298
+ this.setStatus("running");
299
+ }
300
+
301
+ /**
302
+ * 注册配置到 ConfigComponent
303
+ *
304
+ * 配置加载顺序(优先级从低到高):
305
+ * 1. Defaults - 默认值(最低)
306
+ * 2. FileSource - 从配置文件加载
307
+ * 3. EnvSource - 从环境变量加载
308
+ * 4. config 对象 - 直接传入的配置(最高)
309
+ */
310
+ private async registerConfig(options: AgentComponentOptions): Promise<void> {
311
+ const configComponent = options.configComponent;
312
+ if (!configComponent) return;
313
+
314
+ const { configPath, envPrefix, config } = options;
315
+ const prefix = envPrefix ?? "AGENT";
316
+
317
+ // 1. 使用 registerComponent 注册配置结构(keys 和 sources,不含 defaults)
318
+ configComponent.registerComponent(AGENT_CONFIG_REGISTRATION);
319
+
320
+ // 2. 注册 FileSource(如果提供了 configPath)
321
+ if (configPath) {
322
+ configComponent.registerSource({
323
+ type: "file",
324
+ relativePath: configPath,
325
+ optional: true,
326
+ watch: false
327
+ });
328
+ }
329
+
330
+ // 3. 注册 EnvSource
331
+ configComponent.registerSource({
332
+ type: "env",
333
+ envPrefix: prefix,
334
+ priority: 20,
335
+ watch: false
336
+ });
337
+
338
+ // 4. 从 sources 加载配置(FileSource 和 EnvSource)
339
+ await configComponent.load("agent");
340
+
341
+ // 5. 后备方案:直接读取环境变量(确保所有环境变量都被处理)
342
+ for (const envKey of Object.keys(process.env)) {
343
+ const configKey = envKeyToConfigKey(envKey, prefix, "agent");
344
+ if (!configKey) continue;
345
+ const value = process.env[envKey];
346
+ if (value !== undefined) {
347
+ await configComponent.set(configKey, value);
348
+ }
349
+ }
350
+
351
+ // 6. 设置默认值(只有当配置不存在时)
352
+ for (const [key, value] of Object.entries(AGENT_DEFAULTS)) {
353
+ if (configComponent.get(key) === undefined) {
354
+ await configComponent.set(key, value);
355
+ }
356
+ }
357
+
358
+ // 7. 直接配置对象(最高优先级)
359
+ if (config) {
360
+ const flatConfig = this.flattenConfig(config);
361
+ for (const [key, value] of Object.entries(flatConfig)) {
362
+ await configComponent.set(key, value);
363
+ }
364
+ }
365
+
366
+ // 8. 从 ConfigComponent 同步配置到 this.config
367
+ // 重要:配置文件/环境变量的值必须同步回实例配置,供 registerAgent() 使用
368
+ // 注意:构造函数传入的配置优先级最高
369
+ const constructorDefaultAgent = this._constructorConfig?.defaultAgent;
370
+
371
+ // 从 ConfigComponent 读取值,并设置默认值
372
+ // 构造函数传入的值优先级最高,会覆盖从 ConfigComponent 读取的值
373
+ this.config.defaultAgent = {
374
+ maxIterations: constructorDefaultAgent?.maxIterations
375
+ ?? (configComponent.get("agent.defaultAgent.maxIterations") as number)
376
+ ?? 200,
377
+ maxErrorRetries: constructorDefaultAgent?.maxErrorRetries
378
+ ?? (configComponent.get("agent.defaultAgent.maxErrorRetries") as number)
379
+ ?? 3,
380
+ doomLoopThreshold: constructorDefaultAgent?.doomLoopThreshold
381
+ ?? (configComponent.get("agent.defaultAgent.doomLoopThreshold") as number)
382
+ ?? 5,
383
+ toolTimeout: constructorDefaultAgent?.toolTimeout
384
+ ?? (configComponent.get("agent.defaultAgent.toolTimeout") as number)
385
+ ?? 60000,
386
+ toolRetries: constructorDefaultAgent?.toolRetries
387
+ ?? (configComponent.get("agent.defaultAgent.toolRetries") as number)
388
+ ?? 3,
389
+ filterHistory: constructorDefaultAgent?.filterHistory
390
+ ?? (configComponent.get("agent.defaultAgent.filterHistory") as boolean)
391
+ ?? false,
392
+ type: constructorDefaultAgent?.type
393
+ ?? (configComponent.get("agent.defaultAgent.type") as "primary" | "sub")
394
+ ?? "primary",
395
+ systemPrompt: constructorDefaultAgent?.systemPrompt
396
+ ?? (configComponent.get("agent.defaultAgent.systemPrompt") as string | undefined),
397
+ model: constructorDefaultAgent?.model
398
+ ?? (configComponent.get("agent.defaultAgent.model") as string | undefined),
399
+ behaviorSpecId: constructorDefaultAgent?.behaviorSpecId
400
+ ?? (configComponent.get("agent.defaultAgent.behaviorSpecId") as string | undefined),
401
+ };
402
+
403
+ logger.info(`[registerConfig] Synced config from ConfigComponent: maxIterations=${this.config.defaultAgent.maxIterations}`);
404
+
405
+ // 9. 注册配置热更新监听
406
+ this.registerConfigWatcher(configComponent);
407
+ }
408
+
409
+ /**
410
+ * 将配置对象展平为点号路径
411
+ */
412
+ private flattenConfig(
413
+ obj: Record<string, unknown>,
414
+ prefix = "agent"
415
+ ): Record<string, unknown> {
416
+ const result: Record<string, unknown> = {};
417
+ for (const [key, value] of Object.entries(obj)) {
418
+ const fullKey = `${prefix}.${key}`;
419
+ if (value && typeof value === "object" && !Array.isArray(value)) {
420
+ Object.assign(
421
+ result,
422
+ this.flattenConfig(value as Record<string, unknown>, fullKey)
423
+ );
424
+ } else {
425
+ result[fullKey] = value;
426
+ }
427
+ }
428
+ return result;
429
+ }
430
+
431
+ /**
432
+ * 注册配置热更新监听
433
+ */
434
+ private registerConfigWatcher(configComponent: ConfigComponent): void {
435
+ if (typeof configComponent.watch !== "function") {
436
+ return;
437
+ }
438
+
439
+ this.configWatcher = configComponent.watch("agent.*", (event) => {
440
+ this.onConfigChange(event);
441
+ });
442
+ }
443
+
444
+ /**
445
+ * 处理配置变更
446
+ */
447
+ protected onConfigChange(event: { key: string; oldValue?: unknown; newValue?: unknown }): void {
448
+ logger.info(`Agent config changed: ${event.key}`, {
449
+ oldValue: event.oldValue,
450
+ newValue: event.newValue
451
+ });
452
+ }
453
+
454
+ /**
455
+ * 组件停止时的清理逻辑
456
+ *
457
+ * 清理内容:
458
+ * 1. 终止所有正在运行的 Agent(调用 abort() 以中断 LLM 调用)
459
+ * 2. 清空 AbortController 注册表
460
+ * 3. 清空 Doom Loop 缓存
461
+ * 4. 取消配置变更监听
462
+ * 5. 清空 Agent 注册表
463
+ */
464
+ protected async onStop(): Promise<void> {
465
+ logger.info("[AgentComponent] Stopping and cleaning up resources...");
466
+
467
+ // 1. 终止所有正在运行的 Agent(调用 abort() 以中断 LLM 调用)
468
+ for (const [name, agent] of this.agents) {
469
+ if (agent.status === "running" || agent.status === "idle") {
470
+ // 调用 abort() 方法会触发 AbortController.abort(),从而中断正在进行的 LLM 调用
471
+ this.abort(name);
472
+ logger.debug(`[AgentComponent] Aborted agent: ${name}`);
473
+ }
474
+ }
475
+
476
+ // 2. 清空 AbortController 注册表
477
+ this.abortControllers.clear();
478
+
479
+ // 3. 清空 Doom Loop 缓存
480
+ this.doomLoopCaches.clear();
481
+
482
+ // 4. 取消配置变更监听
483
+ if (this.configWatcher) {
484
+ this.configWatcher();
485
+ this.configWatcher = undefined;
486
+ }
487
+
488
+ // 5. 清空 Agent 注册表
489
+ this.agents.clear();
490
+
491
+ this.setStatus("stopped");
492
+ logger.info("[AgentComponent] Cleanup completed");
493
+ }
494
+
495
+ /**
496
+ * 刷新依赖组件引用
497
+ *
498
+ * 当组件被添加到 Environment 后调用,以确保获取到最新的组件引用
499
+ */
500
+ refreshDependencies(): void {
501
+ if (this.env) {
502
+ const llm = this.env.getComponent("llm");
503
+ const tool = this.env.getComponent("tool");
504
+ this.llmComponent = llm ? llm as unknown as LLMComponent : null;
505
+ this.toolComponent = tool ? tool as unknown as ToolComponent : null;
506
+
507
+ logger.debug("[refreshDependencies] AgentComponent refreshed", {
508
+ hasLLM: !!this.llmComponent,
509
+ hasTool: !!this.toolComponent,
510
+ });
511
+ }
512
+ }
513
+
514
+ /**
515
+ * 获取 Agent 实例
516
+ */
517
+ getAgent(agentName: string): AgentInstance | undefined {
518
+ return this.agents.get(agentName);
519
+ }
520
+
521
+ /**
522
+ * 获取所有 Agent 实例
523
+ */
524
+ listAgents(): AgentInstance[] {
525
+ return Array.from(this.agents.values());
526
+ }
527
+
528
+ /**
529
+ * Add placeholder tool results for remaining unprocessed tool calls
530
+ *
531
+ * This ensures that the message history maintains a 1:1 correspondence
532
+ * between tool-calls and tool-results, which is required by AI SDK v6.
533
+ *
534
+ * @param ctx - Hook context containing messages
535
+ * @param allToolCalls - All tool calls from the LLM response
536
+ * @param processedCount - Number of tool calls that have been processed
537
+ * @param reason - Reason for the abort/interruption
538
+ */
539
+ private addRemainingToolResults(
540
+ ctx: HookContext,
541
+ allToolCalls: Array<{ id: string; name?: string; function?: { name?: string; arguments?: string | unknown } }>,
542
+ processedCount: number,
543
+ reason: string
544
+ ): void {
545
+ const remainingToolCalls = allToolCalls.slice(processedCount);
546
+
547
+ for (const tc of remainingToolCalls) {
548
+ const tcName = tc.function?.name || tc.name || "unknown";
549
+
550
+ this.pushMessage(ctx, {
551
+ role: "tool",
552
+ content: [{
553
+ type: "tool-result" as const,
554
+ toolCallId: tc.id,
555
+ toolName: tcName,
556
+ output: JSON.stringify({ error: reason }),
557
+ }],
558
+ } as unknown as ModelMessage);
559
+ }
560
+ }
561
+
562
+ /**
563
+ * 注册 Agent
564
+ *
565
+ * 配置优先级(从高到低):
566
+ * 1. registerAgent 传入的 config(来自 PromptComponent)
567
+ * 2. defaultAgent 配置(来自 ConfigComponent,作为 fallback)
568
+ */
569
+ registerAgent(name: string, config: Partial<AgentInstanceConfig>): AgentInstance {
570
+ // 合并配置:defaultAgent 填充默认值,传入的 config 覆盖
571
+ // 注意:需要显式设置每个字段,避免 spread 操作导致默认值被覆盖的问题
572
+ const mergedConfig = {
573
+ // 基础字段
574
+ type: config.type ?? this.config.defaultAgent.type,
575
+ name,
576
+ // 数值类型字段(显式设置以避免被覆盖)
577
+ maxIterations: config.maxIterations ?? this.config.defaultAgent.maxIterations,
578
+ maxErrorRetries: config.maxErrorRetries ?? this.config.defaultAgent.maxErrorRetries,
579
+ doomLoopThreshold: config.doomLoopThreshold ?? this.config.defaultAgent.doomLoopThreshold,
580
+ toolTimeout: config.toolTimeout ?? this.config.defaultAgent.toolTimeout,
581
+ toolRetries: config.toolRetries ?? this.config.defaultAgent.toolRetries,
582
+ // 字符串类型字段
583
+ systemPrompt: config.systemPrompt ?? this.config.defaultAgent.systemPrompt,
584
+ model: config.model ?? this.config.defaultAgent.model,
585
+ behaviorSpecId: config.behaviorSpecId ?? this.config.defaultAgent.behaviorSpecId,
586
+ // 工具过滤字段
587
+ allowedTools: config.allowedTools ?? this.config.defaultAgent.allowedTools,
588
+ deniedTools: config.deniedTools ?? this.config.defaultAgent.deniedTools,
589
+ // History 过滤字段
590
+ filterHistory: config.filterHistory ?? this.config.defaultAgent.filterHistory,
591
+ };
592
+
593
+ const instance: AgentInstance = {
594
+ name,
595
+ config: mergedConfig as AgentInstance["config"],
596
+ status: "idle",
597
+ plugins: new Map(),
598
+ };
599
+
600
+ this.agents.set(name, instance);
601
+ logger.info(`Agent registered: ${name}`, { type: instance.config.type });
602
+ return instance;
603
+ }
604
+
605
+ /**
606
+ * 注销 Agent
607
+ */
608
+ unregisterAgent(name: string): boolean {
609
+ const deleted = this.agents.delete(name);
610
+ if (deleted) {
611
+ logger.info(`Agent unregistered: ${name}`);
612
+ }
613
+ return deleted;
614
+ }
615
+
616
+ /**
617
+ * 注册 Plugin 到指定 Agent
618
+ */
619
+ registerPlugin(agentName: string, plugin: Plugin): void {
620
+ const agent = this.agents.get(agentName);
621
+ if (!agent) {
622
+ throw new Error(`Agent not found: ${agentName}`);
623
+ }
624
+
625
+ agent.plugins.set(plugin.name, plugin);
626
+ logger.info(`Plugin registered: ${plugin.name} to ${agentName}`, {
627
+ hooks: plugin.hooks.map(h => h.point),
628
+ });
629
+ }
630
+
631
+ /**
632
+ * 注销 Plugin
633
+ */
634
+ unregisterPlugin(agentName: string, pluginName: string): boolean {
635
+ const agent = this.agents.get(agentName);
636
+ if (!agent) {
637
+ return false;
638
+ }
639
+
640
+ const deleted = agent.plugins.delete(pluginName);
641
+ if (deleted) {
642
+ logger.info(`Plugin unregistered: ${pluginName} from ${agentName}`);
643
+ }
644
+ return deleted;
645
+ }
646
+
647
+ /**
648
+ * 设置默认工具列表
649
+ */
650
+ setDefaultTools(tools: Tool[]): void {
651
+ this.defaultTools = tools;
652
+ }
653
+
654
+ /**
655
+ * 获取可用工具
656
+ */
657
+ getAvailableTools(agent: AgentInstance, context?: AgentContext): Tool[] {
658
+ // 首先添加 defaultTools(来自 setDefaultTools 的 Memory Agent tools)
659
+ let tools: Tool[] = [...this.defaultTools];
660
+
661
+ logger.debug(`[getAvailableTools] After defaultTools: ${tools.length} tools`, {
662
+ toolNames: tools.map(t => t.name)
663
+ });
664
+
665
+ // 如果有 ToolComponent,从组件获取
666
+ if (this.toolComponent?.listTools) {
667
+ // 类型转换:ToolComponent 的 Tool 与 AgentComponent 的 Tool 兼容
668
+ const componentTools = this.toolComponent.listTools() as unknown as Tool[];
669
+ tools = [...tools, ...componentTools];
670
+ logger.debug(`[getAvailableTools] After adding componentTools: ${tools.length} tools`, {
671
+ componentToolNames: componentTools.map(t => t.name)
672
+ });
673
+ }
674
+
675
+ // 获取工具过滤配置(context 优先于 agent 配置)
676
+ const allowedTools = context?.allowedTools ?? agent.config.allowedTools;
677
+ const deniedTools = context?.deniedTools ?? agent.config.deniedTools;
678
+
679
+ logger.debug(`[getAvailableTools] Filtering: allowed=${allowedTools}, denied=${deniedTools}`);
680
+
681
+ // 应用 allowedTools 过滤
682
+ if (allowedTools?.length) {
683
+ tools = tools.filter(t => allowedTools.includes(t.name));
684
+ logger.debug(`[getAvailableTools] After allowedTools filter: ${tools.length} tools`, {
685
+ toolNames: tools.map(t => t.name)
686
+ });
687
+ }
688
+
689
+ // 应用 deniedTools 过滤
690
+ if (deniedTools?.length) {
691
+ tools = tools.filter(t => !deniedTools.includes(t.name));
692
+ logger.debug(`[getAvailableTools] After deniedTools filter: ${tools.length} tools`, {
693
+ toolNames: tools.map(t => t.name)
694
+ });
695
+ }
696
+
697
+ logger.debug(`[getAvailableTools] Final tools: ${tools.length}`, {
698
+ toolNames: tools.map(t => t.name)
699
+ });
700
+
701
+ return tools;
702
+ }
703
+
704
+ /**
705
+ * 执行 Agent
706
+ *
707
+ * @param agentName Agent 名称
708
+ * @param query 用户查询
709
+ * @param context 执行上下文
710
+ * @returns Agent 执行结果
711
+ */
712
+ async run(
713
+ agentName: string,
714
+ query: string,
715
+ context?: AgentContext
716
+ ): Promise<AgentRunResult> {
717
+ return this._run(agentName, query, context);
718
+ }
719
+
720
+ private pushMessage(hookCtx: HookContext, message: ModelMessage){
721
+ hookCtx.messages.push(message);
722
+ this.notifyMessageAdded(message);
723
+ }
724
+
725
+ /**
726
+ * 执行 Agent(核心方法,内部实现)
727
+ */
728
+ @TracedAs("agent.component.run", { recordParams: true, recordResult: true, log: true })
729
+ private async _run(
730
+ agentName: string,
731
+ query: string,
732
+ context?: AgentContext
733
+ ): Promise<AgentRunResult> {
734
+ // 刷新依赖组件引用(确保获取到最新的组件)
735
+ this.refreshDependencies();
736
+
737
+ const agent = this.getAgent(agentName);
738
+ if (!agent) {
739
+ throw new Error(`Agent not found: ${agentName}`);
740
+ }
741
+
742
+ // 生成唯一的 runId(支持并发调用同一个 agent)
743
+ const runId = `${agentName}:${++this.runCounter}`;
744
+ logger.info(`Starting agent run: ${agentName} (runId=${runId})`, { query: query.substring(0, 100) });
745
+
746
+ // Mark agent as running
747
+ agent.status = "running";
748
+ this.aborted.set(runId, false);
749
+
750
+ // Create AbortController for this agent run
751
+ const abortController = new AbortController();
752
+ this.abortControllers.set(runId, abortController);
753
+
754
+ // Initialize doom loop cache for this agent
755
+ if (!this.doomLoopCaches.has(runId)) {
756
+ this.doomLoopCaches.set(runId, new Map());
757
+ }
758
+
759
+ // Merge context
760
+ const effectiveContext: AgentContext = {
761
+ ...context,
762
+ agentType: agent.config.type,
763
+ model: agent.config.model,
764
+ // 设置 abort signal(支持通过 abort() 方法中断 LLM 调用)
765
+ abort: abortController.signal,
766
+ };
767
+
768
+ // Initialize result
769
+ const result: AgentRunResult = {
770
+ iterations: 0,
771
+ toolCalls: [],
772
+ };
773
+
774
+ // Create HookContext
775
+ const hookCtx: HookContext = {
776
+ agent,
777
+ iteration: 0,
778
+ maxIterations: agent.config.maxIterations,
779
+ messages: [],
780
+ tools: this.getAvailableTools(agent, effectiveContext),
781
+ systemPrompt: agent.config.systemPrompt || "You are a helpful assistant.",
782
+ context: effectiveContext,
783
+ };
784
+
785
+ // Load session history messages
786
+ let historyMessageCount = 0;
787
+ if (effectiveContext.sessionId) {
788
+ try {
789
+ const sessionComponent = this.env.getComponent<SessionComponent>("session");
790
+ if (sessionComponent) {
791
+ // Get conversation history starting from the N-th latest user message
792
+ // This ensures we have complete conversation context
793
+ // Determine filterHistory: context > agent config > default (false)
794
+ const filterHistory = effectiveContext.filterHistory ?? agent.config.filterHistory ?? false;
795
+ const messages = await this.getConversationHistory(
796
+ sessionComponent,
797
+ effectiveContext.sessionId,
798
+ { minUserMessages: 100, filterHistory }
799
+ );
800
+
801
+ hookCtx.messages = messages.map(msg => this.messageConverter.toModelMessage(msg)) as unknown as ModelMessage[];
802
+ historyMessageCount = hookCtx.messages.length;
803
+ }
804
+ } catch (err) {
805
+ logger.warn(`Failed to load session history: ${err}`);
806
+ }
807
+ }
808
+
809
+ // Send start event via env
810
+ this.pushEnvEvent({
811
+ type: "agent.start",
812
+ payload: {
813
+ sessionId: effectiveContext.sessionId,
814
+ model: effectiveContext.model,
815
+ },
816
+ });
817
+
818
+ // Always add user query to messages
819
+ // History messages are already loaded above, this is the new user input
820
+ this.pushMessage(hookCtx, {
821
+ role: "user",
822
+ content: query,
823
+ } as unknown as ModelMessage);
824
+
825
+ // before.start hook
826
+ await this.executePluginHooks(agent, "agent:before.start", hookCtx);
827
+ if (hookCtx._stopped) {
828
+ return this.finalizeResult(result, hookCtx);
829
+ }
830
+
831
+ // ReAct Loop
832
+ let iteration = 0;
833
+ let finalText: string | undefined;
834
+
835
+ while (iteration < agent.config.maxIterations) {
836
+ // Check _stopped state (may be set by previous iteration's hooks)
837
+ if (hookCtx._stopped) {
838
+ break;
839
+ }
840
+
841
+ // Check abort
842
+ if (this.aborted.get(runId)) {
843
+ hookCtx._stopped = true;
844
+ hookCtx._stopReason = "aborted";
845
+ break;
846
+ }
847
+
848
+ // Check abort signal
849
+ if (effectiveContext.abort?.aborted) {
850
+ hookCtx._stopped = true;
851
+ hookCtx._stopReason = "abort_signal";
852
+ break;
853
+ }
854
+
855
+ iteration++;
856
+ hookCtx.iteration = iteration;
857
+ result.iterations = iteration;
858
+
859
+ logger.debug(`Iteration ${iteration} started`);
860
+
861
+ // Reset doom loop cache at the start of each iteration
862
+ // Doom loop detection should only apply within a single LLM response cycle
863
+ this.doomLoopCaches.set(runId, new Map());
864
+
865
+ // 设置迭代信息,供 before.llm hook 使用(如 ReminderPlugin)
866
+ hookCtx.maxIterations = agent.config.maxIterations;
867
+
868
+ try {
869
+
870
+ // 记录消息构建结果
871
+ logger.debug(`[ReAct] Iteration ${iteration} buildMessages result: ${hookCtx.messages.length} messages`);
872
+
873
+ // before.llm hook
874
+ await this.executePluginHooks(agent, "agent:before.llm", hookCtx);
875
+ if (hookCtx._stopped) break;
876
+
877
+ // Call LLM
878
+ const llmOutput = await this.invokeLLM(hookCtx);
879
+ hookCtx.llmOutput = llmOutput;
880
+
881
+ // Check abort after LLM call (important: abort might have interrupted the call)
882
+ if (this.aborted.get(runId) || effectiveContext.abort?.aborted) {
883
+ hookCtx._stopped = true;
884
+ hookCtx._stopReason = "aborted";
885
+ break;
886
+ }
887
+
888
+ // after.llm hook
889
+ await this.executePluginHooks(agent, "agent:after.llm", hookCtx);
890
+ if (hookCtx._stopped) break;
891
+
892
+ // Process LLM output
893
+ if (llmOutput.content && !llmOutput.toolCalls?.length) {
894
+ // Text response - complete
895
+ // Check abort before adding message and breaking
896
+ if (this.aborted.get(runId) || effectiveContext.abort?.aborted) {
897
+ hookCtx._stopped = true;
898
+ hookCtx._stopReason = "aborted";
899
+ break;
900
+ }
901
+ finalText = llmOutput.content;
902
+ this.pushMessage(hookCtx, {
903
+ role: "assistant",
904
+ content: llmOutput.content,
905
+ } as unknown as ModelMessage);
906
+ break;
907
+ }
908
+
909
+ if (llmOutput.toolCalls?.length) {
910
+ // Add assistant message with tool calls to messages (CRITICAL: must be before tool results)
911
+ // AI SDK v6 format: assistant message with content array containing text/reasoning/tool-call parts
912
+
913
+ // Build assistant message content parts: text/reasoning + tool-call parts
914
+ const assistantParts: any[] = [];
915
+
916
+ // 1. Add text content if present
917
+ if (llmOutput.content) {
918
+ assistantParts.push({
919
+ type: "text" as const,
920
+ text: llmOutput.content,
921
+ });
922
+ }
923
+
924
+ // 2. Add reasoning content if present (for reasoning models)
925
+ if (llmOutput.reasoning) {
926
+ assistantParts.push({
927
+ type: "reasoning" as const,
928
+ text: llmOutput.reasoning,
929
+ reasoningType: "core",
930
+ });
931
+ }
932
+
933
+ // 3. Add tool-call parts
934
+ for (const tc of llmOutput.toolCalls) {
935
+ let argsStr = "";
936
+ let argsObj: Record<string, unknown> = {};
937
+
938
+ // 提取参数:优先从 function.arguments 获取
939
+ if (tc.function?.arguments) {
940
+ if (typeof tc.function.arguments === "string") {
941
+ argsStr = tc.function.arguments;
942
+ try {
943
+ argsObj = JSON.parse(argsStr);
944
+ } catch {
945
+ argsObj = { _raw: argsStr };
946
+ }
947
+ } else {
948
+ argsObj = tc.function.arguments as Record<string, unknown>;
949
+ argsStr = JSON.stringify(argsObj);
950
+ }
951
+ } else if (tc.arguments) {
952
+ if (typeof tc.arguments === "string") {
953
+ argsStr = tc.arguments;
954
+ try {
955
+ argsObj = JSON.parse(argsStr);
956
+ } catch {
957
+ argsObj = { _raw: argsStr };
958
+ }
959
+ } else {
960
+ argsObj = tc.arguments as Record<string, unknown>;
961
+ argsStr = JSON.stringify(argsObj);
962
+ }
963
+ }
964
+
965
+ assistantParts.push({
966
+ type: "tool-call" as const,
967
+ toolCallId: tc.id,
968
+ toolName: tc.function?.name || tc.name || "unknown",
969
+ // 关键修复:添加 input 字段供 session-message-converter 读取和存储
970
+ input: argsObj,
971
+ // 保留 toolCall 以兼容某些依赖此结构的代码
972
+ toolCall: {
973
+ name: tc.function?.name || tc.name || "unknown",
974
+ arguments: argsStr,
975
+ }
976
+ });
977
+ }
978
+
979
+ // 记录添加 assistant 消息
980
+ const assistantContent = llmOutput.content?.substring(0, 80) || "(no text)";
981
+ const toolCallNames = llmOutput.toolCalls?.map(tc => tc.function?.name || tc.name).join(", ") || "none";
982
+ logger.debug(`[ReAct] Iteration ${iteration} Adding assistant message: text="${assistantContent}" toolCalls=[${toolCallNames}]`);
983
+
984
+ this.pushMessage(hookCtx, {
985
+ role: "assistant",
986
+ content: assistantParts,
987
+ } as unknown as ModelMessage);
988
+
989
+ // Tool calls
990
+ const allToolCalls = llmOutput.toolCalls;
991
+ let processedCount = 0; // Track number of successfully processed tool calls
992
+
993
+ for (const toolCall of allToolCalls) {
994
+ // Check abort before tool execution
995
+ if (this.aborted.get(runId) || effectiveContext.abort?.aborted) {
996
+ hookCtx._stopped = true;
997
+ hookCtx._stopReason = "aborted";
998
+ // Add placeholder results for remaining unprocessed tool calls
999
+ this.addRemainingToolResults(hookCtx, allToolCalls, processedCount, "Execution aborted");
1000
+ break;
1001
+ }
1002
+
1003
+ // 优先使用 function.name,兼容旧格式
1004
+ const func = toolCall.function;
1005
+ const tcName = func?.name || toolCall.name || "unknown";
1006
+ const tcArgs = func?.arguments
1007
+ ? (typeof func.arguments === "string" ? JSON.parse(func.arguments) : func.arguments)
1008
+ : toolCall.arguments;
1009
+
1010
+ hookCtx.currentToolCall = {
1011
+ id: toolCall.id,
1012
+ name: tcName,
1013
+ arguments: tcArgs as Record<string, unknown>,
1014
+ };
1015
+
1016
+ // Doom loop detection
1017
+ const doomKey = `${tcName}:${JSON.stringify(tcArgs)}`;
1018
+ const agentCache = this.doomLoopCaches.get(runId) || new Map();
1019
+ const doomCount = agentCache.get(doomKey) || 0;
1020
+ if (doomCount >= agent.config.doomLoopThreshold) {
1021
+ hookCtx.error = new Error(`Doom loop detected: ${tcName}`);
1022
+ await this.executePluginHooks(agent, "on.error", hookCtx);
1023
+ result.error = hookCtx.error.message;
1024
+ // Add placeholder results for remaining unprocessed tool calls
1025
+ this.addRemainingToolResults(hookCtx, allToolCalls, processedCount, "Execution aborted: Doom loop detected");
1026
+ break;
1027
+ }
1028
+ agentCache.set(doomKey, doomCount + 1);
1029
+ this.doomLoopCaches.set(runId, agentCache);
1030
+
1031
+ // before.tool hook
1032
+ await this.executePluginHooks(agent, "agent:before.tool", hookCtx);
1033
+ if (hookCtx._stopped) {
1034
+ // Add placeholder results for remaining unprocessed tool calls
1035
+ this.addRemainingToolResults(hookCtx, allToolCalls, processedCount, "Execution aborted by plugin hook");
1036
+ break;
1037
+ }
1038
+
1039
+ // Execute tool
1040
+ const toolResult = await this.executeTool(hookCtx);
1041
+ hookCtx.toolResult = toolResult;
1042
+
1043
+ // after.tool hook
1044
+ await this.executePluginHooks(agent, "agent:after.tool", hookCtx);
1045
+ if (hookCtx._stopped) {
1046
+ // Add placeholder results for remaining unprocessed tool calls
1047
+ this.addRemainingToolResults(hookCtx, allToolCalls, processedCount + 1, "Execution aborted by plugin hook");
1048
+ break;
1049
+ }
1050
+
1051
+ // Add tool result to messages (AI SDK v6 format)
1052
+ const toolOutput = toolResult.success ? toolResult.result.output || "" : toolResult.result.error || "Unknown error";
1053
+ const toolOutputPreview = typeof toolOutput === "string" ? toolOutput.substring(0, 50) : JSON.stringify(toolOutput).substring(0, 50);
1054
+ logger.debug(`[ReAct] Iteration ${iteration} Adding tool message: tool=${toolResult.name} output="${toolOutputPreview}..."`);
1055
+
1056
+ this.pushMessage(hookCtx, {
1057
+ role: "tool",
1058
+ content: [{
1059
+ type: "tool-result",
1060
+ toolCallId: hookCtx.currentToolCall!.id,
1061
+ toolName: toolResult.name,
1062
+ output: toolOutput,
1063
+ }],
1064
+ } as unknown as ModelMessage);
1065
+
1066
+ // Record tool call
1067
+ result.toolCalls.push(hookCtx.currentToolCall);
1068
+ processedCount++;
1069
+ }
1070
+
1071
+ // on.iteration hook
1072
+ await this.executePluginHooks(agent, "agent:on.iteration", hookCtx);
1073
+ }
1074
+ } catch (error) {
1075
+ logger.error(`Iteration ${iteration} error`, { error });
1076
+ hookCtx.error = error instanceof Error ? error : new Error(String(error));
1077
+ await this.executePluginHooks(agent, "agent:on.error", hookCtx);
1078
+
1079
+ // 设置 _stopped 标志(如果还没有被设置)
1080
+ if (!hookCtx._stopped) {
1081
+ hookCtx._stopped = true;
1082
+ // 如果是 AskUserError,特殊处理
1083
+ if (error instanceof AskUserError) {
1084
+ hookCtx._stopReason = "ask-user";
1085
+
1086
+ // ✅ 关键修复:当 ask_user 抛出异常时,也需要添加 tool result 消息
1087
+ // 这样可以保证 session 中有完整的 tool-call + tool-result 配对
1088
+ // Resume 时可以用用户响应替换这个 tool result
1089
+ if (hookCtx.currentToolCall) {
1090
+ this.pushMessage(hookCtx, {
1091
+ role: "tool",
1092
+ content: [{
1093
+ type: "tool-result",
1094
+ toolCallId: hookCtx.currentToolCall.id,
1095
+ toolName: "ask_user",
1096
+ output: JSON.stringify({
1097
+ error: "AskUserError",
1098
+ query: error.query,
1099
+ message: error.message,
1100
+ }),
1101
+ }],
1102
+ } as unknown as ModelMessage);
1103
+ }
1104
+
1105
+ // 将 AskUserError 信息编码到 result.error 中,供 AgentComponentAdapter 检测和恢复抛出
1106
+ // 使用 JSON 编码避免 query 中包含冒号导致解析错误
1107
+ const errorInfo = {
1108
+ query: error.query,
1109
+ sessionId: error.sessionId,
1110
+ nodeId: error.nodeId,
1111
+ nodeType: error.nodeType,
1112
+ };
1113
+ result.error = `__ASK_USER_ERROR__:${JSON.stringify(errorInfo)}`;
1114
+ } else {
1115
+ // 其他错误 - 也需要添加 tool result 消息保持配对完整
1116
+ result.error = hookCtx.error.message;
1117
+ // 如果是 AbortError,保留 abort 相关的 reason
1118
+ if ((error as any)?.name === "AbortError") {
1119
+ hookCtx._stopReason = hookCtx._stopReason || "aborted";
1120
+ } else {
1121
+ hookCtx._stopReason = "error";
1122
+ }
1123
+
1124
+ // ✅ 添加 tool result 消息(包含错误信息)
1125
+ if (hookCtx.currentToolCall) {
1126
+ this.pushMessage(hookCtx, {
1127
+ role: "tool",
1128
+ content: [{
1129
+ type: "tool-result",
1130
+ toolCallId: hookCtx.currentToolCall.id,
1131
+ toolName: hookCtx.currentToolCall.name,
1132
+ output: JSON.stringify({ error: hookCtx.error.message }),
1133
+ }],
1134
+ } as unknown as ModelMessage);
1135
+ }
1136
+ }
1137
+ } else {
1138
+ // 如果 _stopped 已经被设置为 true(例如在 executeTool 中),保留错误信息
1139
+ result.error = hookCtx.error.message;
1140
+
1141
+ // ✅ 即使 _stopped 已为 true,也需要添加 tool result 消息
1142
+ // 这确保即使在 abort/doom-loop 等场景下,消息配对也是完整的
1143
+ if (hookCtx.currentToolCall) {
1144
+ const errorMsg = hookCtx.error?.message || "Execution interrupted";
1145
+ this.pushMessage(hookCtx, {
1146
+ role: "tool",
1147
+ content: [{
1148
+ type: "tool-result",
1149
+ toolCallId: hookCtx.currentToolCall.id,
1150
+ toolName: hookCtx.currentToolCall.name,
1151
+ output: JSON.stringify({ error: errorMsg }),
1152
+ }],
1153
+ } as unknown as ModelMessage);
1154
+ }
1155
+ }
1156
+ break;
1157
+ }
1158
+ }
1159
+
1160
+ // Update result
1161
+ result.finalText = finalText;
1162
+ if (iteration >= agent.config.maxIterations && !result.finalText) {
1163
+ result.finalText = "Maximum iterations reached.";
1164
+ }
1165
+
1166
+ // ✅ agent:after.react hook - React 循环结束后,用于记忆提取
1167
+ // 传递 reactContext 包含 messages, sessionId, summary
1168
+ const reactContext = {
1169
+ messages: hookCtx.messages,
1170
+ sessionId: effectiveContext.sessionId,
1171
+ summary: result.finalText,
1172
+ };
1173
+ await this.executePluginHooks(agent, "agent:after.react", {
1174
+ ...hookCtx,
1175
+ ...reactContext,
1176
+ });
1177
+
1178
+ // after.complete hook
1179
+ await this.executePluginHooks(agent, "agent:after.complete", hookCtx);
1180
+
1181
+ // Finalize - 根据 abort 状态设置 agent 状态
1182
+ if (this.aborted.get(runId) === true || hookCtx._stopped) {
1183
+ agent.status = "stopped";
1184
+ } else {
1185
+ agent.status = "idle";
1186
+ }
1187
+ this.aborted.delete(runId);
1188
+ // 清理 AbortController
1189
+ this.abortControllers.delete(runId);
1190
+
1191
+ // 发送 agent.completed 或 agent.error 事件
1192
+ if (result.error) {
1193
+ this.pushEnvEvent({
1194
+ type: "agent.error",
1195
+ payload: {
1196
+ error: result.error,
1197
+ iterations: result.iterations,
1198
+ stopReason: result.stopReason,
1199
+ },
1200
+ });
1201
+ } else {
1202
+ this.pushEnvEvent({
1203
+ type: "agent.completed",
1204
+ payload: {
1205
+ finalText: result.finalText,
1206
+ iterations: result.iterations,
1207
+ stopReason: result.stopReason,
1208
+ },
1209
+ });
1210
+ }
1211
+
1212
+ let newMessages = hookCtx.messages.slice(historyMessageCount);
1213
+ logger.info(`Agent run completed: ${agentName}`, {
1214
+ iterations: result.iterations,
1215
+ hasError: !!result.error,
1216
+ hookCtxMessages: hookCtx.messages.length,
1217
+ historyMessageCount: historyMessageCount,
1218
+ sessionId: effectiveContext.sessionId,
1219
+ });
1220
+ await this.recordSessionMessages(
1221
+ effectiveContext.sessionId,
1222
+ newMessages
1223
+ );
1224
+
1225
+ return this.finalizeResult(result, hookCtx);
1226
+ }
1227
+
1228
+ /**
1229
+ * 中止 Agent
1230
+ *
1231
+ * 调用此方法会:
1232
+ * 1. 设置 aborted 标志(用于 ReAct 循环检查)
1233
+ * 2. 触发 AbortController.abort()(用于中断正在进行的 LLM 调用)
1234
+ *
1235
+ * 注意:不直接修改 agent.status,由 _run() 的 finally 统一处理
1236
+ */
1237
+ abort(agentName: string): void {
1238
+ // 设置 aborted 标志 - 遍历所有该 agent 的 runId
1239
+ for (const key of this.aborted.keys()) {
1240
+ if (key.startsWith(agentName + ":")) {
1241
+ this.aborted.set(key, true);
1242
+ }
1243
+ }
1244
+
1245
+ // 触发 AbortController 中断正在进行的 LLM 调用
1246
+ for (const [key, controller] of this.abortControllers) {
1247
+ if (key.startsWith(agentName + ":")) {
1248
+ controller.abort();
1249
+ logger.debug(`[abort] AbortController.abort() called for run: ${key}`);
1250
+ }
1251
+ }
1252
+
1253
+ logger.info(`Agent aborted: ${agentName}`);
1254
+ }
1255
+
1256
+ /**
1257
+ * 构建消息列表
1258
+ *
1259
+ * 注意:ctx.messages 已经包含了历史消息(包括 system prompt 和 user query)
1260
+ * 这里只需要构建当前请求的消息,不重复添加
1261
+ */
1262
+ private async buildMessages(
1263
+ ctx: HookContext,
1264
+ ): Promise<ModelMessage[]> {
1265
+ const messages: ModelMessage[] = [];
1266
+
1267
+ if (ctx.systemPrompt) {
1268
+ messages.push({
1269
+ role: "system",
1270
+ content: ctx.systemPrompt,
1271
+ });
1272
+ }
1273
+
1274
+ // Addition info (temporary context) - 也需要检查是否已存在
1275
+ if (ctx.additionInfo) {
1276
+ messages.push({
1277
+ role: "user",
1278
+ content: `额外信息:\n${ctx.additionInfo}`,
1279
+ });
1280
+ }
1281
+
1282
+ // History messages(包含之前的所有消息)
1283
+ messages.push(...ctx.messages);
1284
+
1285
+ return messages;
1286
+ }
1287
+
1288
+ /**
1289
+ * 调用 LLM
1290
+ *
1291
+ * 流式事件由 LLMComponent 通过 env.pushEnvEvent() 发布
1292
+ */
1293
+ @TracedAs("agent.component.invokeLLM", { recordParams: true, recordResult: true, log: true })
1294
+ private async invokeLLM(ctx: HookContext): Promise<AgentLLMOutput> {
1295
+ logger.debug("[invokeLLM] Starting LLM invocation");
1296
+
1297
+ // 如果有 LLMComponent,使用它
1298
+ if (this.llmComponent?.invoke) {
1299
+ const messages = await this.buildMessages(ctx);
1300
+ const convertedMessages = messages.map(toLLMMessage);
1301
+
1302
+ logger.debug(`[invokeLLM] Calling LLMComponent.invoke with ${convertedMessages.length} messages`);
1303
+
1304
+ try {
1305
+ const result = await this.llmComponent.invoke({
1306
+ // 类型转换:ModelMessage[] -> LLMMessage[]
1307
+ messages: convertedMessages,
1308
+ // 类型转换:Tool[] -> ToolInfo[]
1309
+ // 注意:Tool.parameters 是 ZodType,需要提取其 JSON schema
1310
+ tools: ctx.tools.map(t => ({
1311
+ name: t.name,
1312
+ description: t.description || "",
1313
+ parameters: (t.parameters as unknown as Record<string, unknown>) ?? {},
1314
+ })),
1315
+ model: ctx.context.model,
1316
+ // 传递上下文用于事件元数据
1317
+ context: {
1318
+ sessionId: ctx.context.sessionId,
1319
+ messageId: ctx.context.messageId,
1320
+ },
1321
+ // 传递 abort signal 以支持 Agent 的优雅终止
1322
+ abortSignal: ctx.context.abort,
1323
+ });
1324
+
1325
+ logger.debug("[invokeLLM] LLMComponent.invoke returned successfully");
1326
+
1327
+ return {
1328
+ content: result.output?.content || "",
1329
+ reasoning: result.output?.reasoning,
1330
+ toolCalls: result.output?.toolCalls?.map((tc) => ({
1331
+ id: tc.id,
1332
+ function: tc.function ? {
1333
+ name: tc.function.name,
1334
+ arguments: typeof tc.function.arguments === "string"
1335
+ ? tc.function.arguments
1336
+ : JSON.stringify(tc.function.arguments),
1337
+ } : undefined,
1338
+ name: tc.name,
1339
+ arguments: tc.arguments,
1340
+ })),
1341
+ finishReason: result.output?.finishReason,
1342
+ };
1343
+ } catch (error) {
1344
+ logger.error("[invokeLLM] LLMComponent.invoke threw an error:", error);
1345
+ throw error;
1346
+ }
1347
+ }
1348
+
1349
+ // Fallback: Mock response for testing
1350
+ logger.warn("[invokeLLM] No LLMComponent available, using mock");
1351
+
1352
+ return {
1353
+ content: "Mock response: Hello!",
1354
+ finishReason: "stop",
1355
+ };
1356
+ }
1357
+
1358
+ /**
1359
+ * 执行工具
1360
+ */
1361
+ @TracedAs("agent.component.executeTool", { recordParams: true, recordResult: true, log: true })
1362
+ private async executeTool(ctx: HookContext): Promise<ToolCallResult> {
1363
+ const toolCall = ctx.currentToolCall!;
1364
+
1365
+ // 获取工具名称:优先使用 function.name(AI SDK v6 格式),兼容 toolCall.name
1366
+ // AI SDK v6 格式: { id, name, function: { name, arguments } }
1367
+ const toolName = (toolCall as any).function?.name || toolCall.name || "unknown";
1368
+
1369
+ // Debug: 打印工具查找信息
1370
+ logger.debug(`[executeTool] Looking for tool: "${toolName}"`);
1371
+ logger.debug(`[executeTool] Available tools: [${ctx.tools.map(t => t.name).join(", ")}]`);
1372
+
1373
+ // Find tool
1374
+ const tool = ctx.tools.find(t => t.name === toolName);
1375
+ if (!tool) {
1376
+ // 发送错误事件
1377
+ this.pushEnvEvent({
1378
+ type: "tool.error",
1379
+ payload: {
1380
+ error: `Tool not found: ${toolName}`,
1381
+ toolName: toolName,
1382
+ },
1383
+ });
1384
+
1385
+ return {
1386
+ id: toolCall.id,
1387
+ name: toolName,
1388
+ success: false,
1389
+ result: {
1390
+ success: false,
1391
+ output: "",
1392
+ error: `Tool not found: ${toolName}`,
1393
+ },
1394
+ };
1395
+ }
1396
+
1397
+ try {
1398
+ // Convert AgentContext to ToolContext format
1399
+ // AgentContext uses sessionId (camelCase), ToolContext uses session_id (snake_case)
1400
+ const toolContext: ToolContext = {
1401
+ session_id: ctx.context.sessionId,
1402
+ message_id: ctx.context.messageId,
1403
+ abort: ctx.context.abort,
1404
+ workdir: ctx.context.metadata?.workdir as string | undefined,
1405
+ metadata: ctx.context.metadata,
1406
+ };
1407
+
1408
+ // Build ToolExecuteRequest
1409
+ const request: ToolExecuteRequest = {
1410
+ name: toolName,
1411
+ args: toolCall.arguments,
1412
+ context: toolContext,
1413
+ };
1414
+
1415
+ // Use ToolComponent.execute() to trigger tool hooks
1416
+ // Check if we have a ToolComponent reference (via refreshDependencies)
1417
+ let result;
1418
+ if (this.toolComponent?.execute) {
1419
+ result = await this.toolComponent.execute(request);
1420
+ } else {
1421
+ // Fallback to direct tool execution (for backwards compatibility / testing)
1422
+ logger.debug(`[executeTool] No ToolComponent available, executing tool directly`);
1423
+ result = await tool.execute(toolCall.arguments, toolContext);
1424
+ }
1425
+
1426
+ // 发送工具结果事件
1427
+ this.pushEnvEvent({
1428
+ type: "tool.result",
1429
+ payload: {
1430
+ id: toolCall.id,
1431
+ name: toolName,
1432
+ result: result,
1433
+ success: result.success,
1434
+ },
1435
+ });
1436
+
1437
+ return {
1438
+ id: toolCall.id,
1439
+ name: toolName,
1440
+ success: result.success,
1441
+ result,
1442
+ };
1443
+ } catch (error) {
1444
+ // 检测 AskUserError - 这是用于工作流暂停的特殊错误,需要向上传播
1445
+ if (error instanceof AskUserError) {
1446
+ throw error; // 重新抛出,让调用栈上层(AgentComponentAdapter)处理
1447
+ }
1448
+
1449
+ // 发送错误事件
1450
+ this.pushEnvEvent({
1451
+ type: "tool.error",
1452
+ payload: {
1453
+ error: error instanceof Error ? error.message : String(error),
1454
+ toolName: toolName,
1455
+ },
1456
+ });
1457
+
1458
+ return {
1459
+ id: toolCall.id,
1460
+ name: toolName,
1461
+ success: false,
1462
+ result: {
1463
+ success: false,
1464
+ output: "",
1465
+ error: error instanceof Error ? error.message : String(error),
1466
+ },
1467
+ };
1468
+ }
1469
+ }
1470
+
1471
+ /**
1472
+ * 执行 Plugin Hooks (Agent 特有的 Hook 执行机制)
1473
+ */
1474
+ private async executePluginHooks(
1475
+ agent: AgentInstance,
1476
+ point: HookPoint,
1477
+ ctx: HookContext
1478
+ ): Promise<void> {
1479
+ // Collect all hooks for this point
1480
+ const hooks: Array<{ plugin: Plugin; priority: number; execute: Function }> = [];
1481
+
1482
+ for (const [name, plugin] of agent.plugins) {
1483
+ for (const hook of plugin.hooks) {
1484
+ if (hook.point === point) {
1485
+ hooks.push({
1486
+ plugin,
1487
+ priority: hook.priority ?? 0,
1488
+ execute: plugin.execute,
1489
+ });
1490
+ }
1491
+ }
1492
+ }
1493
+
1494
+ // Sort by priority (higher first)
1495
+ hooks.sort((a, b) => b.priority - a.priority);
1496
+
1497
+ // Execute hooks
1498
+ for (const { plugin, execute } of hooks) {
1499
+ try {
1500
+ const result = await execute(ctx);
1501
+ if (!result.continue) {
1502
+ ctx._stopped = true;
1503
+ ctx._stopReason = `Plugin ${plugin.name} stopped execution`;
1504
+ logger.info(`Hook ${point} stopped by plugin ${plugin.name}`);
1505
+ break;
1506
+ }
1507
+
1508
+ // Handle actions
1509
+ if (result.action) {
1510
+ await this.handleHookAction(result.action, ctx);
1511
+ }
1512
+ } catch (error) {
1513
+ logger.error(`Hook ${point} error in plugin ${plugin.name}`, { error });
1514
+ ctx._errors = ctx._errors || [];
1515
+ ctx._errors.push({
1516
+ plugin: plugin.name,
1517
+ error: error instanceof Error ? error.message : String(error),
1518
+ });
1519
+ }
1520
+ }
1521
+ }
1522
+
1523
+ /**
1524
+ * 处理 Hook 动作
1525
+ */
1526
+ private async handleHookAction(
1527
+ action: { type: string; payload?: unknown },
1528
+ ctx: HookContext
1529
+ ): Promise<void> {
1530
+ switch (action.type) {
1531
+ case "stop":
1532
+ ctx._stopped = true;
1533
+ ctx._stopReason = "stopped_by_hook";
1534
+ break;
1535
+ case "inject_message":
1536
+ if (action.payload && typeof action.payload === "object") {
1537
+ const msg = action.payload as ModelMessage;
1538
+ ctx.messages.push(msg);
1539
+ }
1540
+ break;
1541
+ case "skip_tool":
1542
+ // Skip current tool execution
1543
+ ctx._pendingAction = action as { type: HookActionType; payload?: unknown };
1544
+ break;
1545
+ // Other actions can be extended
1546
+ }
1547
+ }
1548
+
1549
+ /**
1550
+ * 通知消息添加(扩展点)
1551
+ */
1552
+ protected notifyMessageAdded(message: ModelMessage): void {
1553
+ // 可被子类或插件覆盖
1554
+ logger.debug(`Message added: ${message.role}`);
1555
+ }
1556
+
1557
+ /**
1558
+ * 结束结果
1559
+ */
1560
+ private finalizeResult(result: AgentRunResult, ctx: HookContext): AgentRunResult {
1561
+ return {
1562
+ ...result,
1563
+ stopped: ctx._stopped,
1564
+ stopReason: ctx._stopReason,
1565
+ };
1566
+ }
1567
+
1568
+ /**
1569
+ * Record messages to session
1570
+ *
1571
+ * 存储策略:
1572
+ * 1. 存储 allMessages 中的所有 user、assistant、tool 消息
1573
+ * 2. 跳过 system 消息
1574
+ * 3. 跳过空内容的 assistant/tool 消息
1575
+ */
1576
+ @TracedAs("agent.component.recordSessionMessages", { recordParams: true, recordResult: true, log: true })
1577
+ private async recordSessionMessages(
1578
+ sessionId: string | undefined,
1579
+ allMessages: ModelMessage[] = []
1580
+ ): Promise<void> {
1581
+ if (!sessionId) {
1582
+ return;
1583
+ }
1584
+
1585
+ try {
1586
+ const sessionComponent = this.env.getComponent<SessionComponent>("session");
1587
+ if (!sessionComponent) {
1588
+ logger.warn("SessionComponent not found, skipping session recording");
1589
+ return;
1590
+ }
1591
+
1592
+ for (const msg of allMessages) {
1593
+ // 跳过 system 消息
1594
+ if (msg.role === "system") {
1595
+ continue;
1596
+ }
1597
+
1598
+ // 跳过空内容的 assistant/tool 消息
1599
+ if (msg.role === "assistant" || msg.role === "tool") {
1600
+ if (this.isEmptyMessage(msg.content)) {
1601
+ continue;
1602
+ }
1603
+ }
1604
+
1605
+ await sessionComponent.addMessage(sessionId,
1606
+ this.messageConverter.fromModelMessage(msg as unknown as ModelMessage, { sessionID: sessionId })
1607
+ );
1608
+ }
1609
+
1610
+ } catch (err) {
1611
+ logger.warn(`Failed to record session messages: ${err}`);
1612
+ }
1613
+ }
1614
+
1615
+ /**
1616
+ * 检查消息内容是否为空
1617
+ *
1618
+ * 空内容定义:
1619
+ * - 空字符串 ""
1620
+ * - 空数组 []
1621
+ * - null
1622
+ * - undefined
1623
+ *
1624
+ * @param content - 消息内容
1625
+ * @returns 是否为空
1626
+ */
1627
+ private isEmptyMessage(content: unknown): boolean {
1628
+ if (content === null || content === undefined) {
1629
+ return true;
1630
+ }
1631
+ if (typeof content === "string" && content.trim() === "") {
1632
+ return true;
1633
+ }
1634
+ if (Array.isArray(content) && content.length === 0) {
1635
+ return true;
1636
+ }
1637
+ return false;
1638
+ }
1639
+
1640
+ /**
1641
+ * 通过 Environment 发送事件
1642
+ */
1643
+ private pushEnvEvent(event: { type: string; payload: Record<string, unknown> }): void {
1644
+ if (this.env && "pushEnvEvent" in this.env) {
1645
+ try {
1646
+ const fullEvent: EnvEvent = {
1647
+ id: `evt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
1648
+ type: event.type,
1649
+ timestamp: Date.now(),
1650
+ metadata: {
1651
+ source: "agent",
1652
+ agent_name: this.name,
1653
+ },
1654
+ payload: event.payload,
1655
+ };
1656
+ this.env?.pushEnvEvent(fullEvent);
1657
+ } catch (err) {
1658
+ logger.warn(`[pushEnvEvent] Failed to push event: ${err}`);
1659
+ }
1660
+ }
1661
+ }
1662
+
1663
+ /**
1664
+ * 获取会话对话历史
1665
+ *
1666
+ * 核心逻辑:
1667
+ * 1. 使用 sessionComponent.getContext() 获取消息(自动从 checkpoint 开始)
1668
+ * 2. 根据 messageLimit 限制消息数量(向后兼容 minUserMessages)
1669
+ * 3. 过滤掉 tool_call/tool_result 消息(如果 filterHistory=true)
1670
+ *
1671
+ * 关键改进:利用 checkpoint 概念,避免重复获取已压缩的历史消息,
1672
+ * 从而减少重复触发压缩的问题。
1673
+ *
1674
+ * @param sessionComponent - SessionComponent 实例
1675
+ * @param sessionId - 会话 ID
1676
+ * @param options - 配置选项
1677
+ * @param options.minUserMessages - 最少包含的 user 消息数量(默认 100)
1678
+ * @param options.filterHistory - 是否过滤 tool 消息(默认 true)
1679
+ * @returns 过滤后的会话消息列表
1680
+ */
1681
+ @TracedAs("agent.component.getConversationHistory", { recordParams: true, recordResult: true, log: true })
1682
+ private async getConversationHistory(
1683
+ sessionComponent: SessionComponent,
1684
+ sessionId: string,
1685
+ options: { minUserMessages?: number; filterHistory?: boolean } = {}
1686
+ ): Promise<SessionMessage[]> {
1687
+ const minUserMessages = options.minUserMessages ?? 100;
1688
+ const filterHistory = options.filterHistory ?? false;
1689
+
1690
+ // 使用 getContext 获取消息,自动从最新 checkpoint 开始
1691
+ // 且确保从 user 消息边界截取,避免 tool_call/tool_result 配对被截断
1692
+ const ctx = await sessionComponent.getContext(sessionId, {
1693
+ fullHistory: false,
1694
+ minUserMessages,
1695
+ });
1696
+
1697
+ let messages = ctx.messages;
1698
+
1699
+ // 根据 filterHistory 配置决定是否过滤 tool 消息
1700
+ if (filterHistory) {
1701
+ messages = messages.filter(msg => {
1702
+ if (msg.metadata?.type === "tool_call" || msg.metadata?.type === "tool_result") {
1703
+ return false;
1704
+ }
1705
+ return msg.role === "user" || msg.role === "assistant";
1706
+ });
1707
+ }
1708
+
1709
+ return messages;
1710
+ }
1711
+ }