@roj-ai/sdk 0.1.3 → 0.1.5

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 (493) hide show
  1. package/dist/bootstrap.js +191 -0
  2. package/dist/bootstrap.js.map +1 -0
  3. package/dist/builtin-events.js +8 -0
  4. package/dist/builtin-events.js.map +1 -0
  5. package/dist/bun-platform/fs.js +39 -0
  6. package/dist/bun-platform/fs.js.map +1 -0
  7. package/dist/bun-platform/index.js +18 -0
  8. package/dist/bun-platform/index.js.map +1 -0
  9. package/dist/bun-platform/process.js +21 -0
  10. package/dist/bun-platform/process.js.map +1 -0
  11. package/dist/config.js +54 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/config.test.js +155 -0
  14. package/dist/config.test.js.map +1 -0
  15. package/dist/core/agent-loop.integration.test.js +414 -0
  16. package/dist/core/agent-loop.integration.test.js.map +1 -0
  17. package/dist/core/agents/agent-config.test.js +194 -0
  18. package/dist/core/agents/agent-config.test.js.map +1 -0
  19. package/dist/core/agents/agent-roles.js +25 -0
  20. package/dist/core/agents/agent-roles.js.map +1 -0
  21. package/dist/core/agents/agent-shutdown.test.js +180 -0
  22. package/dist/core/agents/agent-shutdown.test.js.map +1 -0
  23. package/dist/core/agents/agent.js +1205 -0
  24. package/dist/core/agents/agent.js.map +1 -0
  25. package/dist/core/agents/agent.test.js +313 -0
  26. package/dist/core/agents/agent.test.js.map +1 -0
  27. package/dist/core/agents/communicator.js +13 -0
  28. package/dist/core/agents/communicator.js.map +1 -0
  29. package/dist/core/agents/config.js +5 -0
  30. package/dist/core/agents/config.js.map +1 -0
  31. package/dist/core/agents/context.js +2 -0
  32. package/dist/core/agents/context.js.map +1 -0
  33. package/dist/core/agents/debounce.js +74 -0
  34. package/dist/core/agents/debounce.js.map +1 -0
  35. package/dist/core/agents/handler-events.test.js +115 -0
  36. package/dist/core/agents/handler-events.test.js.map +1 -0
  37. package/dist/core/agents/index.js +2 -0
  38. package/dist/core/agents/index.js.map +1 -0
  39. package/dist/core/agents/response-sanitizer.js +46 -0
  40. package/dist/core/agents/response-sanitizer.js.map +1 -0
  41. package/dist/core/agents/response-sanitizer.test.js +101 -0
  42. package/dist/core/agents/response-sanitizer.test.js.map +1 -0
  43. package/dist/core/agents/retry.js +105 -0
  44. package/dist/core/agents/retry.js.map +1 -0
  45. package/dist/core/agents/schema.js +39 -0
  46. package/dist/core/agents/schema.js.map +1 -0
  47. package/dist/core/agents/state.js +90 -0
  48. package/dist/core/agents/state.js.map +1 -0
  49. package/dist/core/context/state.js +23 -0
  50. package/dist/core/context/state.js.map +1 -0
  51. package/dist/core/errors.js +38 -0
  52. package/dist/core/errors.js.map +1 -0
  53. package/dist/core/event-sourcing.integration.test.js +154 -0
  54. package/dist/core/event-sourcing.integration.test.js.map +1 -0
  55. package/dist/core/events/base-event-store.js +201 -0
  56. package/dist/core/events/base-event-store.js.map +1 -0
  57. package/dist/core/events/event-store.js +26 -0
  58. package/dist/core/events/event-store.js.map +1 -0
  59. package/dist/core/events/file.js +320 -0
  60. package/dist/core/events/file.js.map +1 -0
  61. package/dist/core/events/file.test.js +284 -0
  62. package/dist/core/events/file.test.js.map +1 -0
  63. package/dist/core/events/index.js +3 -0
  64. package/dist/core/events/index.js.map +1 -0
  65. package/dist/core/events/memory.js +101 -0
  66. package/dist/core/events/memory.js.map +1 -0
  67. package/dist/core/events/memory.test.js +502 -0
  68. package/dist/core/events/memory.test.js.map +1 -0
  69. package/dist/core/events/metadata-utils.js +107 -0
  70. package/dist/core/events/metadata-utils.js.map +1 -0
  71. package/dist/core/events/test-helpers.js +15 -0
  72. package/dist/core/events/test-helpers.js.map +1 -0
  73. package/dist/core/events/types.js +21 -0
  74. package/dist/core/events/types.js.map +1 -0
  75. package/dist/core/file-store/file-store.js +250 -0
  76. package/dist/core/file-store/file-store.js.map +1 -0
  77. package/dist/core/file-store/types.js +7 -0
  78. package/dist/core/file-store/types.js.map +1 -0
  79. package/dist/core/image/image-processor.js +106 -0
  80. package/dist/core/image/image-processor.js.map +1 -0
  81. package/dist/core/image/image-processor.test.js +171 -0
  82. package/dist/core/image/image-processor.test.js.map +1 -0
  83. package/dist/core/image/index.js +4 -0
  84. package/dist/core/image/index.js.map +1 -0
  85. package/dist/core/image/noop-resizer.js +6 -0
  86. package/dist/core/image/noop-resizer.js.map +1 -0
  87. package/dist/core/image/types.js +2 -0
  88. package/dist/core/image/types.js.map +1 -0
  89. package/dist/core/image/vips-resizer.js +100 -0
  90. package/dist/core/image/vips-resizer.js.map +1 -0
  91. package/dist/core/image/vips-resizer.test.js +324 -0
  92. package/dist/core/image/vips-resizer.test.js.map +1 -0
  93. package/dist/core/llm/anthropic.js +396 -0
  94. package/dist/core/llm/anthropic.js.map +1 -0
  95. package/dist/core/llm/anthropic.test.js +434 -0
  96. package/dist/core/llm/anthropic.test.js.map +1 -0
  97. package/dist/core/llm/cache-breakpoints.js +37 -0
  98. package/dist/core/llm/cache-breakpoints.js.map +1 -0
  99. package/dist/core/llm/cache-live.test.js +137 -0
  100. package/dist/core/llm/cache-live.test.js.map +1 -0
  101. package/dist/core/llm/index.js +9 -0
  102. package/dist/core/llm/index.js.map +1 -0
  103. package/dist/core/llm/llm-log-types.js +12 -0
  104. package/dist/core/llm/llm-log-types.js.map +1 -0
  105. package/dist/core/llm/logger.js +241 -0
  106. package/dist/core/llm/logger.js.map +1 -0
  107. package/dist/core/llm/logger.test.js +228 -0
  108. package/dist/core/llm/logger.test.js.map +1 -0
  109. package/dist/core/llm/logging-provider.js +49 -0
  110. package/dist/core/llm/logging-provider.js.map +1 -0
  111. package/dist/core/llm/middleware.js +114 -0
  112. package/dist/core/llm/middleware.js.map +1 -0
  113. package/dist/core/llm/mock.js +186 -0
  114. package/dist/core/llm/mock.js.map +1 -0
  115. package/dist/core/llm/mock.test.js +318 -0
  116. package/dist/core/llm/mock.test.js.map +1 -0
  117. package/dist/core/llm/openrouter-mapping.test.js +125 -0
  118. package/dist/core/llm/openrouter-mapping.test.js.map +1 -0
  119. package/dist/core/llm/openrouter.js +298 -0
  120. package/dist/core/llm/openrouter.js.map +1 -0
  121. package/dist/core/llm/openrouter.test.js +377 -0
  122. package/dist/core/llm/openrouter.test.js.map +1 -0
  123. package/dist/core/llm/provider-integration.test.js +350 -0
  124. package/dist/core/llm/provider-integration.test.js.map +1 -0
  125. package/dist/core/llm/provider.js +18 -0
  126. package/dist/core/llm/provider.js.map +1 -0
  127. package/dist/core/llm/routing-provider.js +52 -0
  128. package/dist/core/llm/routing-provider.js.map +1 -0
  129. package/dist/core/llm/routing-provider.test.js +94 -0
  130. package/dist/core/llm/routing-provider.test.js.map +1 -0
  131. package/dist/core/llm/schema.js +31 -0
  132. package/dist/core/llm/schema.js.map +1 -0
  133. package/dist/core/llm/snapshot-fetch.js +122 -0
  134. package/dist/core/llm/snapshot-fetch.js.map +1 -0
  135. package/dist/core/llm/snapshot-middleware.js +142 -0
  136. package/dist/core/llm/snapshot-middleware.js.map +1 -0
  137. package/dist/core/llm/snapshot-middleware.test.js +144 -0
  138. package/dist/core/llm/snapshot-middleware.test.js.map +1 -0
  139. package/dist/core/llm/state.js +48 -0
  140. package/dist/core/llm/state.js.map +1 -0
  141. package/dist/core/llm/tokens.js +40 -0
  142. package/dist/core/llm/tokens.js.map +1 -0
  143. package/dist/core/multi-agent.integration.test.js +298 -0
  144. package/dist/core/multi-agent.integration.test.js.map +1 -0
  145. package/dist/core/plugin-hooks.integration.test.js +344 -0
  146. package/dist/core/plugin-hooks.integration.test.js.map +1 -0
  147. package/dist/core/plugins/hook-types.js +5 -0
  148. package/dist/core/plugins/hook-types.js.map +1 -0
  149. package/dist/core/plugins/index.js +5 -0
  150. package/dist/core/plugins/index.js.map +1 -0
  151. package/dist/core/plugins/plugin-builder.js +321 -0
  152. package/dist/core/plugins/plugin-builder.js.map +1 -0
  153. package/dist/core/preset/config.js +54 -0
  154. package/dist/core/preset/config.js.map +1 -0
  155. package/dist/core/preset/index.js +6 -0
  156. package/dist/core/preset/index.js.map +1 -0
  157. package/dist/core/preset/preset-builder.js +63 -0
  158. package/dist/core/preset/preset-builder.js.map +1 -0
  159. package/dist/core/session-lifecycle.integration.test.js +159 -0
  160. package/dist/core/session-lifecycle.integration.test.js.map +1 -0
  161. package/dist/core/sessions/apply-event.js +41 -0
  162. package/dist/core/sessions/apply-event.js.map +1 -0
  163. package/dist/core/sessions/context.js +2 -0
  164. package/dist/core/sessions/context.js.map +1 -0
  165. package/dist/core/sessions/fork-utils.js +42 -0
  166. package/dist/core/sessions/fork-utils.js.map +1 -0
  167. package/dist/core/sessions/fork-utils.test.js +129 -0
  168. package/dist/core/sessions/fork-utils.test.js.map +1 -0
  169. package/dist/core/sessions/reducer.js +55 -0
  170. package/dist/core/sessions/reducer.js.map +1 -0
  171. package/dist/core/sessions/schema.js +66 -0
  172. package/dist/core/sessions/schema.js.map +1 -0
  173. package/dist/core/sessions/session-environment.js +2 -0
  174. package/dist/core/sessions/session-environment.js.map +1 -0
  175. package/dist/core/sessions/session-manager.js +650 -0
  176. package/dist/core/sessions/session-manager.js.map +1 -0
  177. package/dist/core/sessions/session-store.js +118 -0
  178. package/dist/core/sessions/session-store.js.map +1 -0
  179. package/dist/core/sessions/session.js +675 -0
  180. package/dist/core/sessions/session.js.map +1 -0
  181. package/dist/core/sessions/session.test.js +1095 -0
  182. package/dist/core/sessions/session.test.js.map +1 -0
  183. package/dist/core/sessions/state.js +377 -0
  184. package/dist/core/sessions/state.js.map +1 -0
  185. package/dist/core/system.js +66 -0
  186. package/dist/core/system.js.map +1 -0
  187. package/dist/core/tools/context.js +2 -0
  188. package/dist/core/tools/context.js.map +1 -0
  189. package/dist/core/tools/definition.js +4 -0
  190. package/dist/core/tools/definition.js.map +1 -0
  191. package/dist/core/tools/executor.js +82 -0
  192. package/dist/core/tools/executor.js.map +1 -0
  193. package/dist/core/tools/executor.test.js +143 -0
  194. package/dist/core/tools/executor.test.js.map +1 -0
  195. package/dist/core/tools/index.js +4 -0
  196. package/dist/core/tools/index.js.map +1 -0
  197. package/dist/core/tools/schema.js +20 -0
  198. package/dist/core/tools/schema.js.map +1 -0
  199. package/dist/core/tools/state.js +29 -0
  200. package/dist/core/tools/state.js.map +1 -0
  201. package/dist/index.js +70 -0
  202. package/dist/index.js.map +1 -0
  203. package/dist/lib/json/index.js +5 -0
  204. package/dist/lib/json/index.js.map +1 -0
  205. package/dist/lib/logger/console.js +147 -0
  206. package/dist/lib/logger/console.js.map +1 -0
  207. package/dist/lib/logger/console.test.js +258 -0
  208. package/dist/lib/logger/console.test.js.map +1 -0
  209. package/dist/lib/logger/file.js +54 -0
  210. package/dist/lib/logger/file.js.map +1 -0
  211. package/dist/lib/logger/index.js +4 -0
  212. package/dist/lib/logger/index.js.map +1 -0
  213. package/dist/lib/logger/logger.js +28 -0
  214. package/dist/lib/logger/logger.js.map +1 -0
  215. package/dist/lib/logger/ring-buffer.js +61 -0
  216. package/dist/lib/logger/ring-buffer.js.map +1 -0
  217. package/dist/lib/logger/tee.js +43 -0
  218. package/dist/lib/logger/tee.js.map +1 -0
  219. package/dist/lib/mime.js +22 -0
  220. package/dist/lib/mime.js.map +1 -0
  221. package/dist/lib/never.js +4 -0
  222. package/dist/lib/never.js.map +1 -0
  223. package/dist/lib/utils/hash.js +35 -0
  224. package/dist/lib/utils/hash.js.map +1 -0
  225. package/dist/lib/utils/result.js +21 -0
  226. package/dist/lib/utils/result.js.map +1 -0
  227. package/dist/platform/fs.js +8 -0
  228. package/dist/platform/fs.js.map +1 -0
  229. package/dist/platform/index.js +9 -0
  230. package/dist/platform/index.js.map +1 -0
  231. package/dist/platform/process.js +8 -0
  232. package/dist/platform/process.js.map +1 -0
  233. package/dist/plugins/agent-status/plugin.js +77 -0
  234. package/dist/plugins/agent-status/plugin.js.map +1 -0
  235. package/dist/plugins/agents/agents.integration.test.js +683 -0
  236. package/dist/plugins/agents/agents.integration.test.js.map +1 -0
  237. package/dist/plugins/agents/index.js +2 -0
  238. package/dist/plugins/agents/index.js.map +1 -0
  239. package/dist/plugins/agents/plugin.js +199 -0
  240. package/dist/plugins/agents/plugin.js.map +1 -0
  241. package/dist/plugins/context-compact/context-compact.integration.test.js +174 -0
  242. package/dist/plugins/context-compact/context-compact.integration.test.js.map +1 -0
  243. package/dist/plugins/context-compact/context-compactor.js +238 -0
  244. package/dist/plugins/context-compact/context-compactor.js.map +1 -0
  245. package/dist/plugins/context-compact/context-compactor.test.js +763 -0
  246. package/dist/plugins/context-compact/context-compactor.test.js.map +1 -0
  247. package/dist/plugins/context-compact/history-offloader.js +42 -0
  248. package/dist/plugins/context-compact/history-offloader.js.map +1 -0
  249. package/dist/plugins/context-compact/history-offloader.test.js +77 -0
  250. package/dist/plugins/context-compact/history-offloader.test.js.map +1 -0
  251. package/dist/plugins/context-compact/index.js +4 -0
  252. package/dist/plugins/context-compact/index.js.map +1 -0
  253. package/dist/plugins/context-compact/plugin.js +37 -0
  254. package/dist/plugins/context-compact/plugin.js.map +1 -0
  255. package/dist/plugins/filesystem/filesystem.integration.test.js +411 -0
  256. package/dist/plugins/filesystem/filesystem.integration.test.js.map +1 -0
  257. package/dist/plugins/filesystem/helpers.js +170 -0
  258. package/dist/plugins/filesystem/helpers.js.map +1 -0
  259. package/dist/plugins/filesystem/index.js +3 -0
  260. package/dist/plugins/filesystem/index.js.map +1 -0
  261. package/dist/plugins/filesystem/listing.js +247 -0
  262. package/dist/plugins/filesystem/listing.js.map +1 -0
  263. package/dist/plugins/filesystem/plugin.js +364 -0
  264. package/dist/plugins/filesystem/plugin.js.map +1 -0
  265. package/dist/plugins/filesystem/schema.js +2 -0
  266. package/dist/plugins/filesystem/schema.js.map +1 -0
  267. package/dist/plugins/git-status/index.js +2 -0
  268. package/dist/plugins/git-status/index.js.map +1 -0
  269. package/dist/plugins/git-status/plugin.js +144 -0
  270. package/dist/plugins/git-status/plugin.js.map +1 -0
  271. package/dist/plugins/limits-guard/config.js +5 -0
  272. package/dist/plugins/limits-guard/config.js.map +1 -0
  273. package/dist/plugins/limits-guard/index.js +3 -0
  274. package/dist/plugins/limits-guard/index.js.map +1 -0
  275. package/dist/plugins/limits-guard/limit-guard.js +125 -0
  276. package/dist/plugins/limits-guard/limit-guard.js.map +1 -0
  277. package/dist/plugins/limits-guard/limit-guard.test.js +121 -0
  278. package/dist/plugins/limits-guard/limit-guard.test.js.map +1 -0
  279. package/dist/plugins/limits-guard/limits-guard.integration.test.js +378 -0
  280. package/dist/plugins/limits-guard/limits-guard.integration.test.js.map +1 -0
  281. package/dist/plugins/limits-guard/plugin.js +240 -0
  282. package/dist/plugins/limits-guard/plugin.js.map +1 -0
  283. package/dist/plugins/llm-debug/index.js +2 -0
  284. package/dist/plugins/llm-debug/index.js.map +1 -0
  285. package/dist/plugins/llm-debug/llm-debug.integration.test.js +157 -0
  286. package/dist/plugins/llm-debug/llm-debug.integration.test.js.map +1 -0
  287. package/dist/plugins/llm-debug/plugin.js +148 -0
  288. package/dist/plugins/llm-debug/plugin.js.map +1 -0
  289. package/dist/plugins/logs/index.js +2 -0
  290. package/dist/plugins/logs/index.js.map +1 -0
  291. package/dist/plugins/logs/plugin.js +38 -0
  292. package/dist/plugins/logs/plugin.js.map +1 -0
  293. package/dist/plugins/mailbox/helpers.js +66 -0
  294. package/dist/plugins/mailbox/helpers.js.map +1 -0
  295. package/dist/plugins/mailbox/index.js +9 -0
  296. package/dist/plugins/mailbox/index.js.map +1 -0
  297. package/dist/plugins/mailbox/mailbox.integration.test.js +605 -0
  298. package/dist/plugins/mailbox/mailbox.integration.test.js.map +1 -0
  299. package/dist/plugins/mailbox/plugin.js +204 -0
  300. package/dist/plugins/mailbox/plugin.js.map +1 -0
  301. package/dist/plugins/mailbox/prompts.js +93 -0
  302. package/dist/plugins/mailbox/prompts.js.map +1 -0
  303. package/dist/plugins/mailbox/query.js +38 -0
  304. package/dist/plugins/mailbox/query.js.map +1 -0
  305. package/dist/plugins/mailbox/schema.js +32 -0
  306. package/dist/plugins/mailbox/schema.js.map +1 -0
  307. package/dist/plugins/mailbox/state.js +41 -0
  308. package/dist/plugins/mailbox/state.js.map +1 -0
  309. package/dist/plugins/resources/index.js +4 -0
  310. package/dist/plugins/resources/index.js.map +1 -0
  311. package/dist/plugins/resources/manifest.js +20 -0
  312. package/dist/plugins/resources/manifest.js.map +1 -0
  313. package/dist/plugins/resources/plugin.js +171 -0
  314. package/dist/plugins/resources/plugin.js.map +1 -0
  315. package/dist/plugins/resources/post-inject.js +32 -0
  316. package/dist/plugins/resources/post-inject.js.map +1 -0
  317. package/dist/plugins/resources/state.js +16 -0
  318. package/dist/plugins/resources/state.js.map +1 -0
  319. package/dist/plugins/result-eviction/index.js +2 -0
  320. package/dist/plugins/result-eviction/index.js.map +1 -0
  321. package/dist/plugins/result-eviction/plugin.js +43 -0
  322. package/dist/plugins/result-eviction/plugin.js.map +1 -0
  323. package/dist/plugins/result-eviction/result-eviction.integration.test.js +217 -0
  324. package/dist/plugins/result-eviction/result-eviction.integration.test.js.map +1 -0
  325. package/dist/plugins/services/plugin.js +453 -0
  326. package/dist/plugins/services/plugin.js.map +1 -0
  327. package/dist/plugins/services/port-pool.js +70 -0
  328. package/dist/plugins/services/port-pool.js.map +1 -0
  329. package/dist/plugins/services/prompt.js +40 -0
  330. package/dist/plugins/services/prompt.js.map +1 -0
  331. package/dist/plugins/services/schema.js +9 -0
  332. package/dist/plugins/services/schema.js.map +1 -0
  333. package/dist/plugins/services/service.js +470 -0
  334. package/dist/plugins/services/service.js.map +1 -0
  335. package/dist/plugins/services/services.integration.test.js +485 -0
  336. package/dist/plugins/services/services.integration.test.js.map +1 -0
  337. package/dist/plugins/session-lifecycle/index.js +2 -0
  338. package/dist/plugins/session-lifecycle/index.js.map +1 -0
  339. package/dist/plugins/session-lifecycle/plugin.js +273 -0
  340. package/dist/plugins/session-lifecycle/plugin.js.map +1 -0
  341. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js +498 -0
  342. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js.map +1 -0
  343. package/dist/plugins/session-state/plugin.js +159 -0
  344. package/dist/plugins/session-state/plugin.js.map +1 -0
  345. package/dist/plugins/session-stats/index.js +3 -0
  346. package/dist/plugins/session-stats/index.js.map +1 -0
  347. package/dist/plugins/session-stats/plugin.js +81 -0
  348. package/dist/plugins/session-stats/plugin.js.map +1 -0
  349. package/dist/plugins/shell/executor.js +339 -0
  350. package/dist/plugins/shell/executor.js.map +1 -0
  351. package/dist/plugins/shell/index.js +6 -0
  352. package/dist/plugins/shell/index.js.map +1 -0
  353. package/dist/plugins/shell/plugin.js +66 -0
  354. package/dist/plugins/shell/plugin.js.map +1 -0
  355. package/dist/plugins/shell/shell.integration.test.js +234 -0
  356. package/dist/plugins/shell/shell.integration.test.js.map +1 -0
  357. package/dist/plugins/shell/shell.test.js +236 -0
  358. package/dist/plugins/shell/shell.test.js.map +1 -0
  359. package/dist/plugins/skills/discovery.js +205 -0
  360. package/dist/plugins/skills/discovery.js.map +1 -0
  361. package/dist/plugins/skills/discovery.test.js +312 -0
  362. package/dist/plugins/skills/discovery.test.js.map +1 -0
  363. package/dist/plugins/skills/index.js +12 -0
  364. package/dist/plugins/skills/index.js.map +1 -0
  365. package/dist/plugins/skills/plugin.js +293 -0
  366. package/dist/plugins/skills/plugin.js.map +1 -0
  367. package/dist/plugins/skills/prompts.js +70 -0
  368. package/dist/plugins/skills/prompts.js.map +1 -0
  369. package/dist/plugins/skills/schema.js +18 -0
  370. package/dist/plugins/skills/schema.js.map +1 -0
  371. package/dist/plugins/skills/skills.integration.test.js +475 -0
  372. package/dist/plugins/skills/skills.integration.test.js.map +1 -0
  373. package/dist/plugins/snapshotting/index.js +3 -0
  374. package/dist/plugins/snapshotting/index.js.map +1 -0
  375. package/dist/plugins/snapshotting/jj-snapshotter.js +106 -0
  376. package/dist/plugins/snapshotting/jj-snapshotter.js.map +1 -0
  377. package/dist/plugins/snapshotting/plugin.js +28 -0
  378. package/dist/plugins/snapshotting/plugin.js.map +1 -0
  379. package/dist/plugins/snapshotting/snapshotter.js +2 -0
  380. package/dist/plugins/snapshotting/snapshotter.js.map +1 -0
  381. package/dist/plugins/todo/index.js +7 -0
  382. package/dist/plugins/todo/index.js.map +1 -0
  383. package/dist/plugins/todo/plugin.js +319 -0
  384. package/dist/plugins/todo/plugin.js.map +1 -0
  385. package/dist/plugins/todo/prompts.js +54 -0
  386. package/dist/plugins/todo/prompts.js.map +1 -0
  387. package/dist/plugins/todo/schema.js +18 -0
  388. package/dist/plugins/todo/schema.js.map +1 -0
  389. package/dist/plugins/todo/todo.integration.test.js +605 -0
  390. package/dist/plugins/todo/todo.integration.test.js.map +1 -0
  391. package/dist/plugins/uploads/index.js +8 -0
  392. package/dist/plugins/uploads/index.js.map +1 -0
  393. package/dist/plugins/uploads/plugin.js +346 -0
  394. package/dist/plugins/uploads/plugin.js.map +1 -0
  395. package/dist/plugins/uploads/preprocessor.js +44 -0
  396. package/dist/plugins/uploads/preprocessor.js.map +1 -0
  397. package/dist/plugins/uploads/preprocessors/image-classifier.js +127 -0
  398. package/dist/plugins/uploads/preprocessors/image-classifier.js.map +1 -0
  399. package/dist/plugins/uploads/preprocessors/index.js +7 -0
  400. package/dist/plugins/uploads/preprocessors/index.js.map +1 -0
  401. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js +204 -0
  402. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js.map +1 -0
  403. package/dist/plugins/uploads/preprocessors/zip-preprocessor.js +172 -0
  404. package/dist/plugins/uploads/preprocessors/zip-preprocessor.js.map +1 -0
  405. package/dist/plugins/uploads/schema.js +20 -0
  406. package/dist/plugins/uploads/schema.js.map +1 -0
  407. package/dist/plugins/uploads/state.js +22 -0
  408. package/dist/plugins/uploads/state.js.map +1 -0
  409. package/dist/plugins/uploads/uploads.integration.test.js +496 -0
  410. package/dist/plugins/uploads/uploads.integration.test.js.map +1 -0
  411. package/dist/plugins/user-chat/index.js +5 -0
  412. package/dist/plugins/user-chat/index.js.map +1 -0
  413. package/dist/plugins/user-chat/plugin.js +544 -0
  414. package/dist/plugins/user-chat/plugin.js.map +1 -0
  415. package/dist/plugins/user-chat/prompts.js +29 -0
  416. package/dist/plugins/user-chat/prompts.js.map +1 -0
  417. package/dist/plugins/user-chat/schema.js +46 -0
  418. package/dist/plugins/user-chat/schema.js.map +1 -0
  419. package/dist/plugins/user-chat/user-chat.integration.test.js +668 -0
  420. package/dist/plugins/user-chat/user-chat.integration.test.js.map +1 -0
  421. package/dist/plugins/workers/context.js +143 -0
  422. package/dist/plugins/workers/context.js.map +1 -0
  423. package/dist/plugins/workers/definition.js +30 -0
  424. package/dist/plugins/workers/definition.js.map +1 -0
  425. package/dist/plugins/workers/index.js +7 -0
  426. package/dist/plugins/workers/index.js.map +1 -0
  427. package/dist/plugins/workers/plugin.js +578 -0
  428. package/dist/plugins/workers/plugin.js.map +1 -0
  429. package/dist/plugins/workers/worker.js +18 -0
  430. package/dist/plugins/workers/worker.js.map +1 -0
  431. package/dist/plugins/workers/workers.integration.test.js +629 -0
  432. package/dist/plugins/workers/workers.integration.test.js.map +1 -0
  433. package/dist/prompts/base.js +239 -0
  434. package/dist/prompts/base.js.map +1 -0
  435. package/dist/prompts/builder.js +131 -0
  436. package/dist/prompts/builder.js.map +1 -0
  437. package/dist/prompts/index.js +20 -0
  438. package/dist/prompts/index.js.map +1 -0
  439. package/dist/prompts/macros.js +26 -0
  440. package/dist/prompts/macros.js.map +1 -0
  441. package/dist/prompts/macros.test.js +80 -0
  442. package/dist/prompts/macros.test.js.map +1 -0
  443. package/dist/testing/bootstrap-for-testing.js +28 -0
  444. package/dist/testing/bootstrap-for-testing.js.map +1 -0
  445. package/dist/testing/index.js +7 -0
  446. package/dist/testing/index.js.map +1 -0
  447. package/dist/testing/node-platform.js +65 -0
  448. package/dist/testing/node-platform.js.map +1 -0
  449. package/dist/testing/notification-collector.js +82 -0
  450. package/dist/testing/notification-collector.js.map +1 -0
  451. package/dist/testing/preset-helpers.js +37 -0
  452. package/dist/testing/preset-helpers.js.map +1 -0
  453. package/dist/testing/test-harness.js +226 -0
  454. package/dist/testing/test-harness.js.map +1 -0
  455. package/dist/testing/test-harness.test.js +51 -0
  456. package/dist/testing/test-harness.test.js.map +1 -0
  457. package/dist/testing/wait-helpers.js +64 -0
  458. package/dist/testing/wait-helpers.js.map +1 -0
  459. package/dist/transport/adapter/client-adapter.js +64 -0
  460. package/dist/transport/adapter/client-adapter.js.map +1 -0
  461. package/dist/transport/adapter/index.js +24 -0
  462. package/dist/transport/adapter/index.js.map +1 -0
  463. package/dist/transport/adapter/server-adapter.js +73 -0
  464. package/dist/transport/adapter/server-adapter.js.map +1 -0
  465. package/dist/transport/adapter/types.js +8 -0
  466. package/dist/transport/adapter/types.js.map +1 -0
  467. package/dist/transport/http/app.js +86 -0
  468. package/dist/transport/http/app.js.map +1 -0
  469. package/dist/transport/http/index.js +6 -0
  470. package/dist/transport/http/index.js.map +1 -0
  471. package/dist/transport/http/middleware/bearer-auth.js +33 -0
  472. package/dist/transport/http/middleware/bearer-auth.js.map +1 -0
  473. package/dist/transport/http/middleware/error-handler.js +56 -0
  474. package/dist/transport/http/middleware/error-handler.js.map +1 -0
  475. package/dist/transport/http/routes/files.js +237 -0
  476. package/dist/transport/http/routes/files.js.map +1 -0
  477. package/dist/transport/http/routes/resources.js +77 -0
  478. package/dist/transport/http/routes/resources.js.map +1 -0
  479. package/dist/transport/http/routes/rpc.integration.test.js +189 -0
  480. package/dist/transport/http/routes/rpc.integration.test.js.map +1 -0
  481. package/dist/transport/http/routes/rpc.js +110 -0
  482. package/dist/transport/http/routes/rpc.js.map +1 -0
  483. package/dist/transport/http/routes/rpc.test.js +316 -0
  484. package/dist/transport/http/routes/rpc.test.js.map +1 -0
  485. package/dist/transport/http/routes/upload.js +205 -0
  486. package/dist/transport/http/routes/upload.js.map +1 -0
  487. package/dist/transport/rpc/index.js +7 -0
  488. package/dist/transport/rpc/index.js.map +1 -0
  489. package/dist/transport/rpc/methods.js +8 -0
  490. package/dist/transport/rpc/methods.js.map +1 -0
  491. package/dist/user-config.js +14 -0
  492. package/dist/user-config.js.map +1 -0
  493. package/package.json +47 -57
@@ -0,0 +1,1205 @@
1
+ /**
2
+ * Agent - OOP wrapper for agent orchestration.
3
+ *
4
+ * Responsibilities:
5
+ * - Process mailbox (inference + tool execution loop)
6
+ * - Embedded scheduling with debounce
7
+ * - Spawn child agents
8
+ */
9
+ import { agentEvents } from '../../core/agents/state.js';
10
+ import { withSessionId } from '../../core/events/test-helpers.js';
11
+ import { applyCacheBreakpoint } from '../../core/llm/cache-breakpoints.js';
12
+ import { LLMCallId } from '../../core/llm/schema.js';
13
+ import { llmEvents } from '../../core/llm/state.js';
14
+ import { buildPluginDeps } from '../../core/plugins/plugin-builder.js';
15
+ import { ToolCallId } from '../../core/tools/schema.js';
16
+ import { toolEvents } from '../../core/tools/state.js';
17
+ import { getAgentUnconsumedMailbox, selectMailboxState } from '../../plugins/mailbox/query.js';
18
+ import { AGENT_BASE_BRIEFING } from '../../prompts/base.js';
19
+ import { buildEnvironmentSection } from '../../prompts/builder.js';
20
+ import { sanitizeLLMResponse } from './response-sanitizer.js';
21
+ import { withLLMRetry } from './retry.js';
22
+ // ============================================================================
23
+ // Agent
24
+ // ============================================================================
25
+ /**
26
+ * Agent handles mailbox processing, inference, and tool execution.
27
+ *
28
+ * Features:
29
+ * - Debounced processing (timer or callback-based)
30
+ * - LLM inference with retry
31
+ * - Context compaction
32
+ * - Tool execution
33
+ */
34
+ export class Agent {
35
+ id;
36
+ config;
37
+ sessionContext;
38
+ store;
39
+ logger;
40
+ llmProvider;
41
+ llmProviders;
42
+ toolExecutor;
43
+ plugins;
44
+ environment;
45
+ fileStore;
46
+ pluginContexts;
47
+ sendNotification;
48
+ pluginMethodCaller;
49
+ scheduleCallback;
50
+ /** Merged tools map: config tools + plugin tools (plugins override). Rebuilt each processing cycle. */
51
+ tools;
52
+ // Scheduler state (embedded)
53
+ debounceTimer;
54
+ processing = false;
55
+ scheduled = false;
56
+ pendingReschedule = false;
57
+ abortController = new AbortController();
58
+ /** Track conversation turn number for handler context */
59
+ turnNumber = 0;
60
+ constructor(deps) {
61
+ this.id = deps.id;
62
+ this.config = deps.config;
63
+ this.sessionContext = deps.sessionContext;
64
+ this.store = deps.store;
65
+ this.logger = deps.logger;
66
+ this.llmProvider = deps.llmProvider;
67
+ this.llmProviders = deps.llmProviders ?? new Map();
68
+ this.toolExecutor = deps.toolExecutor;
69
+ this.plugins = deps.plugins;
70
+ this.environment = deps.environment;
71
+ this.fileStore = deps.fileStore;
72
+ this.pluginContexts = deps.pluginContexts ?? new Map();
73
+ this.sendNotification = deps.sendNotification;
74
+ this.pluginMethodCaller = deps.pluginMethodCaller;
75
+ this.scheduleCallback = deps.schedule;
76
+ this.tools = this.buildToolsMap();
77
+ // Initialize turn number from conversation history
78
+ const state = this.state;
79
+ if (state) {
80
+ this.turnNumber = state.conversationHistory.filter((m) => m.role === 'assistant').length;
81
+ }
82
+ }
83
+ /**
84
+ * Get the current agent state from store.
85
+ */
86
+ get state() {
87
+ return this.store.getAgentState(this.id);
88
+ }
89
+ // ============================================================================
90
+ // Public API
91
+ // ============================================================================
92
+ /**
93
+ * Unified processing entry point - decides what to do next.
94
+ * Safe to call multiple times; skips if already processing.
95
+ */
96
+ async continue() {
97
+ if (this.processing)
98
+ return;
99
+ if (this.store.isClosed())
100
+ return;
101
+ this.processing = true;
102
+ this.scheduled = false;
103
+ try {
104
+ while (true) {
105
+ const agentState = this.state;
106
+ if (!agentState)
107
+ break;
108
+ // Rebuild tools map each cycle so plugin-dynamic tools reflect current state
109
+ this.tools = this.buildToolsMap();
110
+ const decision = this.decide(agentState);
111
+ switch (decision) {
112
+ case 'idle':
113
+ this.logger.debug('Agent has nothing to do', {
114
+ agentId: this.id,
115
+ status: agentState.status,
116
+ });
117
+ return;
118
+ case 'paused':
119
+ this.logger.debug('Agent is paused, skipping processing', { agentId: this.id });
120
+ return;
121
+ case 'on_start':
122
+ await this.executeOnStart(agentState);
123
+ continue;
124
+ case 'tool_exec':
125
+ this.logger.info('Executing pending tool calls', {
126
+ agentId: this.id,
127
+ count: agentState.pendingToolCalls.length,
128
+ });
129
+ for (const toolCall of agentState.pendingToolCalls) {
130
+ await this.executeToolCall(toolCall);
131
+ }
132
+ // Schedule re-entry via debounce after tool execution
133
+ // (allows debounce callback to wait for child responses, etc.)
134
+ this.scheduleProcessing();
135
+ return;
136
+ case 'resume_from_error':
137
+ await this.store.emit(withSessionId(this.store.sessionId, agentEvents.create('agent_resumed', { agentId: this.id })));
138
+ continue;
139
+ case 'infer':
140
+ await this.runInference(agentState);
141
+ continue;
142
+ case 'complete':
143
+ await this.executeOnComplete(agentState);
144
+ return;
145
+ }
146
+ }
147
+ }
148
+ catch (err) {
149
+ if (this.abortController.signal.aborted) {
150
+ this.logger.debug('Agent processing aborted', { agentId: this.id });
151
+ return;
152
+ }
153
+ this.logger.error('Unexpected error in agent processing', err instanceof Error ? err : new Error(String(err)), {
154
+ agentId: this.id,
155
+ sessionId: this.store.sessionId,
156
+ });
157
+ }
158
+ finally {
159
+ this.processing = false;
160
+ if (this.pendingReschedule) {
161
+ this.pendingReschedule = false;
162
+ this.scheduleProcessing();
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Schedule processing with debounce.
168
+ * Use this when receiving new messages or after tool execution.
169
+ */
170
+ scheduleProcessing() {
171
+ if (this.scheduled)
172
+ return;
173
+ if (this.store.isClosed())
174
+ return;
175
+ if (this.processing) {
176
+ this.pendingReschedule = true;
177
+ return;
178
+ }
179
+ this.cancelSchedule();
180
+ this.scheduled = true;
181
+ const agentState = this.state;
182
+ if (!agentState)
183
+ return;
184
+ if (this.config.debounceCallback) {
185
+ // Callback-based debounce using recursive setTimeout
186
+ const checkInterval = this.config.checkIntervalMs ?? 100;
187
+ const scheduleCheck = () => {
188
+ this.debounceTimer = setTimeout(async () => {
189
+ // Re-read state fresh each check — no stale data
190
+ const currentState = this.state;
191
+ if (!currentState) {
192
+ this.cancelSchedule();
193
+ return;
194
+ }
195
+ // Guard: schedule could be cancelled between timer fire and here
196
+ if (!this.scheduled)
197
+ return;
198
+ const sessionState = this.store.getState();
199
+ const unconsumed = getUnconsumedMessages(sessionState, this.id);
200
+ const pendingToolResults = currentState.pendingToolResults;
201
+ // If no messages, no pending tool results, and no plugin pending, nothing to do
202
+ if (unconsumed.length === 0 && pendingToolResults.length === 0 && !this.hasPluginPendingMessages()) {
203
+ this.cancelSchedule();
204
+ return;
205
+ }
206
+ const oldestTimestamp = unconsumed.length > 0
207
+ ? Math.min(...unconsumed.map((m) => m.timestamp))
208
+ : Date.now();
209
+ const oldestWaitingMs = Date.now() - oldestTimestamp;
210
+ const decision = await this.config.debounceCallback({
211
+ messages: unconsumed,
212
+ oldestWaitingMs,
213
+ totalPending: unconsumed.length,
214
+ pendingToolResults,
215
+ });
216
+ // Re-check after async callback — schedule could be cancelled during await
217
+ if (!this.scheduled)
218
+ return;
219
+ if (decision === 'process_now') {
220
+ this.cancelSchedule();
221
+ this.continue().catch((err) => {
222
+ this.logger.error('Unhandled error in continue()', err instanceof Error ? err : undefined, { agentId: this.id });
223
+ });
224
+ }
225
+ else {
226
+ // Callback said "wait" — schedule next check.
227
+ // Fresh state will be read on next iteration.
228
+ scheduleCheck();
229
+ }
230
+ }, checkInterval);
231
+ };
232
+ scheduleCheck();
233
+ }
234
+ else {
235
+ // Timer-based debounce (default: 500ms)
236
+ const debounceMs = this.config.debounceMs ?? 500;
237
+ this.debounceTimer = setTimeout(() => {
238
+ this.scheduled = false;
239
+ this.debounceTimer = undefined;
240
+ this.continue().catch((err) => {
241
+ this.logger.error('Unhandled error in continue()', err instanceof Error ? err : undefined, { agentId: this.id });
242
+ });
243
+ }, debounceMs);
244
+ }
245
+ }
246
+ /**
247
+ * Shutdown the agent - cancel any scheduled processing.
248
+ */
249
+ shutdown() {
250
+ try {
251
+ this.abortController.abort();
252
+ }
253
+ catch {
254
+ // AbortError may be thrown synchronously by abort signal listeners
255
+ }
256
+ this.cancelSchedule();
257
+ }
258
+ /**
259
+ * Check if processing is scheduled.
260
+ */
261
+ isScheduled() {
262
+ return this.scheduled;
263
+ }
264
+ // ============================================================================
265
+ // Private methods - Processing
266
+ // ============================================================================
267
+ /**
268
+ * Determine the next action based on current agent state.
269
+ */
270
+ decide(state) {
271
+ if (state.status === 'paused')
272
+ return 'paused';
273
+ if (!state.onStartCalled)
274
+ return 'on_start';
275
+ if (state.status === 'tool_exec' && state.pendingToolCalls.length > 0)
276
+ return 'tool_exec';
277
+ if (state.status === 'pending') {
278
+ if (hasWork(state) || this.hasPluginPendingMessages())
279
+ return 'infer';
280
+ return 'complete';
281
+ }
282
+ if (state.status === 'errored') {
283
+ // Errored with new messages (e.g. user sent a message): emit resume event, then retry
284
+ // Only check for new messages, not stale pendingToolResults — those were present when inference failed
285
+ if (this.hasPluginPendingMessages())
286
+ return 'resume_from_error';
287
+ return 'complete';
288
+ }
289
+ return 'idle';
290
+ }
291
+ static MAX_INFERENCE_RETRIES = 3;
292
+ /**
293
+ * Run inference on agent's mailbox.
294
+ * Runs when there are unconsumed messages OR pending tool results.
295
+ */
296
+ async runInference(initialAgentState, retryCount = 0) {
297
+ let agentState = initialAgentState;
298
+ const hasToolResults = agentState.pendingToolResults.length > 0;
299
+ // Collect plugin dequeue messages (includes mailbox messages)
300
+ const pluginDequeued = this.collectPluginMessages();
301
+ // Need either tool results or plugin messages to process
302
+ if (!hasToolResults && pluginDequeued.length === 0)
303
+ return;
304
+ this.turnNumber++;
305
+ // 0. beforeInference handler - can skip LLM entirely or pause
306
+ const beforeResult = await this.executeBeforeInference(agentState);
307
+ if (beforeResult !== null) {
308
+ if (beforeResult.action === 'skip') {
309
+ // Skip LLM, use provided response directly
310
+ await this.emitInferenceCompleted(beforeResult.response, undefined);
311
+ return;
312
+ }
313
+ if (beforeResult.action === 'pause') {
314
+ return;
315
+ }
316
+ }
317
+ // 1. Context compaction is now handled by the context-compact plugin's beforeInference hook
318
+ // (which runs above). If compaction occurred, agent state was updated via context_compacted event.
319
+ // Re-read agent state to pick up any compacted history.
320
+ const postHookState = this.state;
321
+ if (postHookState) {
322
+ agentState = postHookState;
323
+ }
324
+ // 2. Build pending messages (tool results only)
325
+ const pendingMessages = this.buildPendingMessages(agentState);
326
+ // 2b. Append plugin dequeued messages (includes mailbox messages)
327
+ for (const dequeued of pluginDequeued) {
328
+ pendingMessages.push(...dequeued.messages);
329
+ }
330
+ // 3. Inference start - emit with pending messages
331
+ await this.store.emit(withSessionId(this.store.sessionId, llmEvents.create('inference_started', {
332
+ agentId: this.id,
333
+ messages: pendingMessages,
334
+ consumedMessageIds: [],
335
+ })));
336
+ // 4. Build LLM messages — re-read state to include inference_started changes
337
+ const preInferenceState = this.state;
338
+ if (preInferenceState) {
339
+ agentState = preInferenceState;
340
+ }
341
+ const messages = this.buildLLMMessages(agentState, pendingMessages);
342
+ // 4b. Append ephemeral context (not stored in history, recreated each inference)
343
+ const ephemeralParts = [];
344
+ // Collect status messages from all plugins
345
+ const pluginStatus = this.getPluginStatus();
346
+ if (pluginStatus)
347
+ ephemeralParts.push(pluginStatus);
348
+ if (ephemeralParts.length > 0) {
349
+ messages.push({
350
+ role: 'user',
351
+ content: `<session-context>\n${ephemeralParts.join('\n\n')}\n</session-context>`,
352
+ });
353
+ }
354
+ // Mark cache breakpoint — ephemeral session-context suffix is excluded
355
+ // so it doesn't invalidate the cache on every inference.
356
+ const cachedMessages = applyCacheBreakpoint(messages, ephemeralParts.length > 0 ? 1 : 0);
357
+ // 5. LLM inference (with retry)
358
+ const request = {
359
+ model: this.config.model,
360
+ systemPrompt: this.buildSystemPrompt(),
361
+ messages: cachedMessages,
362
+ tools: this.tools.size > 0 ? [...this.tools.values()] : undefined,
363
+ // Stop sequences to prevent hallucination of message tags
364
+ stopSequences: ['<message'],
365
+ };
366
+ this.logger.debug('Running inference', {
367
+ sessionId: this.store.sessionId,
368
+ agentId: this.id,
369
+ messageCount: messages.length,
370
+ });
371
+ // Capture llmCallId from the logging provider
372
+ let llmCallId;
373
+ const llmResponse = await withLLMRetry(() => this.llmProvider.inference(request, {
374
+ sessionId: this.store.sessionId,
375
+ agentId: this.id,
376
+ onLLMCallCreated: (callId) => {
377
+ llmCallId = LLMCallId(callId);
378
+ },
379
+ signal: this.abortController.signal,
380
+ fileStore: this.fileStore,
381
+ providers: this.llmProviders,
382
+ }), { logger: this.logger, signal: this.abortController.signal });
383
+ // Mark plugin messages as consumed (regardless of inference outcome —
384
+ // messages are already appended to conversationHistory via inference_started)
385
+ {
386
+ const currentAgentState = this.state;
387
+ if (currentAgentState) {
388
+ const ctx = this.buildAgentContext(currentAgentState);
389
+ for (const dequeued of pluginDequeued) {
390
+ if (!dequeued.plugin.dequeue)
391
+ continue;
392
+ const pluginCtx = this.buildPluginHookContext(dequeued.plugin, ctx);
393
+ await dequeued.plugin.dequeue.markConsumed(pluginCtx, dequeued.token);
394
+ }
395
+ }
396
+ }
397
+ if (!llmResponse.ok) {
398
+ // 4a. Inference failed
399
+ await this.store.emit(withSessionId(this.store.sessionId, llmEvents.create('inference_failed', {
400
+ agentId: this.id,
401
+ error: llmResponse.error.message,
402
+ llmCallId,
403
+ })));
404
+ // Notify plugins (e.g. mailbox sends error message to parent)
405
+ const errorState = this.state;
406
+ if (errorState) {
407
+ await this.executeOnError(errorState, llmResponse.error.message);
408
+ }
409
+ return;
410
+ }
411
+ // 4c. Sanitize response to prevent hallucination
412
+ const sanitized = sanitizeLLMResponse(llmResponse.value.content);
413
+ if (sanitized.wasTruncated) {
414
+ this.logger.warn('LLM response was truncated (potential hallucination)', {
415
+ agentId: this.id,
416
+ sessionId: this.store.sessionId,
417
+ });
418
+ }
419
+ // Build response object
420
+ let response = {
421
+ content: sanitized.content,
422
+ toolCalls: llmResponse.value.toolCalls.map((tc) => ({
423
+ id: tc.id,
424
+ name: tc.name,
425
+ input: tc.input,
426
+ })),
427
+ };
428
+ // 4c. afterInference handler - can modify response, request retry, or pause
429
+ // Re-read state: inference events have been processed since last read
430
+ const postInferenceState = this.state;
431
+ if (postInferenceState) {
432
+ agentState = postInferenceState;
433
+ }
434
+ const afterResult = await this.executeAfterInference(agentState, response);
435
+ if (afterResult !== null) {
436
+ if (afterResult.action === 'pause') {
437
+ // Inference completed and messages were consumed — commit the turn
438
+ // before pausing so pendingMessages move to conversationHistory.
439
+ await this.emitInferenceCompleted(response, llmCallId, llmResponse.value.metrics);
440
+ await this.emitHandlerPause(afterResult.reason);
441
+ return;
442
+ }
443
+ if (afterResult.action === 'retry') {
444
+ if (retryCount >= Agent.MAX_INFERENCE_RETRIES) {
445
+ this.logger.warn('afterInference retry limit reached, continuing with current response', {
446
+ agentId: this.id,
447
+ retryCount,
448
+ });
449
+ }
450
+ else {
451
+ // Retry inference - decrement turn number and recursively call with fresh state
452
+ this.turnNumber--;
453
+ const freshState = this.state;
454
+ if (!freshState)
455
+ return;
456
+ await this.runInference(freshState, retryCount + 1);
457
+ return;
458
+ }
459
+ }
460
+ else if (afterResult.action === 'modify') {
461
+ response = afterResult.response;
462
+ }
463
+ }
464
+ // 4d. Inference completed
465
+ // Tool calls will be executed in the next continue() cycle
466
+ await this.emitInferenceCompleted(response, llmCallId, llmResponse.value.metrics);
467
+ }
468
+ /**
469
+ * Emit inference_completed event.
470
+ */
471
+ async emitInferenceCompleted(response, llmCallId, metrics) {
472
+ await this.store.emit(withSessionId(this.store.sessionId, llmEvents.create('inference_completed', {
473
+ agentId: this.id,
474
+ consumedMessageIds: [],
475
+ response: {
476
+ content: response.content,
477
+ toolCalls: response.toolCalls.map((tc) => ({
478
+ id: ToolCallId(tc.id),
479
+ name: tc.name,
480
+ input: tc.input,
481
+ })),
482
+ },
483
+ metrics: metrics ?? {
484
+ promptTokens: 0,
485
+ completionTokens: 0,
486
+ totalTokens: 0,
487
+ latencyMs: 0,
488
+ model: 'handler-skip',
489
+ },
490
+ llmCallId,
491
+ })));
492
+ }
493
+ // ============================================================================
494
+ // Private methods - Scheduling
495
+ // ============================================================================
496
+ /**
497
+ * Cancel any scheduled processing.
498
+ */
499
+ cancelSchedule() {
500
+ if (this.debounceTimer) {
501
+ clearTimeout(this.debounceTimer);
502
+ this.debounceTimer = undefined;
503
+ }
504
+ this.scheduled = false;
505
+ this.pendingReschedule = false;
506
+ }
507
+ /**
508
+ * Execute a single tool call.
509
+ */
510
+ async executeToolCall(toolCall) {
511
+ const agentState = this.state;
512
+ if (!agentState)
513
+ return;
514
+ // beforeToolCall handler - can block, replace, or pause the tool call
515
+ let effectiveToolCall = toolCall;
516
+ const beforeResult = await this.executeBeforeToolCall(agentState, toolCall);
517
+ if (beforeResult !== null) {
518
+ if (beforeResult.action === 'pause') {
519
+ return;
520
+ }
521
+ if (beforeResult.action === 'block') {
522
+ // Emit tool_failed with the block reason
523
+ await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_started', {
524
+ agentId: this.id,
525
+ toolCallId: toolCall.id,
526
+ toolName: toolCall.name,
527
+ input: toInputRecord(toolCall.input),
528
+ })));
529
+ await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_failed', {
530
+ agentId: this.id,
531
+ toolCallId: toolCall.id,
532
+ error: `Tool blocked by handler: ${beforeResult.reason}`,
533
+ })));
534
+ return;
535
+ }
536
+ else if (beforeResult.action === 'replace') {
537
+ effectiveToolCall = {
538
+ id: ToolCallId(beforeResult.toolCall.id),
539
+ name: beforeResult.toolCall.name,
540
+ input: beforeResult.toolCall.input,
541
+ };
542
+ }
543
+ }
544
+ // Start event
545
+ await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_started', {
546
+ agentId: this.id,
547
+ toolCallId: effectiveToolCall.id,
548
+ toolName: effectiveToolCall.name,
549
+ input: toInputRecord(effectiveToolCall.input),
550
+ })));
551
+ const tool = this.tools.get(effectiveToolCall.name);
552
+ if (!tool) {
553
+ await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_failed', {
554
+ agentId: this.id,
555
+ toolCallId: effectiveToolCall.id,
556
+ error: `Unknown tool: ${effectiveToolCall.name}`,
557
+ })));
558
+ return;
559
+ }
560
+ const context = {
561
+ ...this.buildAgentContext(agentState),
562
+ logger: this.logger.child({ toolName: toolCall.name }),
563
+ };
564
+ const result = await this.toolExecutor.execute(tool, effectiveToolCall.input, context);
565
+ // Build initial tool result with ToolResultContent
566
+ let toolResult = result.ok
567
+ ? { isError: false, content: result.value }
568
+ : { isError: true, content: result.error.message };
569
+ // afterToolCall handler - can modify result or pause
570
+ const currentAgentState = this.state;
571
+ if (currentAgentState) {
572
+ const afterResult = await this.executeAfterToolCall(currentAgentState, effectiveToolCall, toolResult);
573
+ if (afterResult !== null) {
574
+ if (afterResult.action === 'pause') {
575
+ return;
576
+ }
577
+ if (afterResult.action === 'modify') {
578
+ toolResult = afterResult.result;
579
+ }
580
+ }
581
+ }
582
+ // Result event
583
+ if (!toolResult.isError) {
584
+ await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_completed', {
585
+ agentId: this.id,
586
+ toolCallId: effectiveToolCall.id,
587
+ result: toolResult.content,
588
+ })));
589
+ }
590
+ else {
591
+ // Convert content to string for tool_failed error field
592
+ const errorMessage = typeof toolResult.content === 'string'
593
+ ? toolResult.content
594
+ : JSON.stringify(toolResult.content);
595
+ await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_failed', {
596
+ agentId: this.id,
597
+ toolCallId: effectiveToolCall.id,
598
+ error: errorMessage,
599
+ })));
600
+ }
601
+ }
602
+ // ============================================================================
603
+ // Handler execution methods
604
+ // ============================================================================
605
+ /**
606
+ * Emit agent_paused event with reason 'handler'.
607
+ */
608
+ async emitHandlerPause(message) {
609
+ await this.store.emit(withSessionId(this.store.sessionId, agentEvents.create('agent_paused', {
610
+ agentId: this.id,
611
+ reason: 'handler',
612
+ message,
613
+ })));
614
+ }
615
+ /**
616
+ * Build base AgentContext for handler/hook calls.
617
+ */
618
+ buildAgentContext(agentState) {
619
+ return {
620
+ // SessionContext fields (refreshed from store for up-to-date state)
621
+ sessionId: this.sessionContext.sessionId,
622
+ sessionState: this.store.getState(),
623
+ sessionInput: this.sessionContext.sessionInput,
624
+ environment: this.sessionContext.environment,
625
+ llm: this.sessionContext.llm,
626
+ files: this.sessionContext.files,
627
+ eventStore: this.sessionContext.eventStore,
628
+ llmLogger: this.sessionContext.llmLogger,
629
+ platform: this.sessionContext.platform,
630
+ logger: this.logger,
631
+ emitEvent: this.sessionContext.emitEvent,
632
+ notify: this.sessionContext.notify,
633
+ // AgentContext fields
634
+ agentId: this.id,
635
+ agentState,
636
+ agentConfig: this.config,
637
+ input: agentState.typedInput,
638
+ parentId: agentState.parentId,
639
+ };
640
+ }
641
+ /**
642
+ * Build PluginHookContext for a specific plugin.
643
+ * Adds pluginConfig, pluginAgentConfig, pluginContext, pluginState, self.
644
+ */
645
+ buildPluginHookContext(plugin, agentContext) {
646
+ const pluginState = plugin.slice
647
+ ? plugin.slice.select(this.store.getState())
648
+ : undefined;
649
+ const self = {};
650
+ for (const [methodName] of Object.entries(plugin.methods)) {
651
+ self[methodName] = async (input) => {
652
+ if (!this.pluginMethodCaller) {
653
+ throw new Error('pluginMethodCaller not available');
654
+ }
655
+ return this.pluginMethodCaller(plugin.name, methodName, input);
656
+ };
657
+ }
658
+ const sendNotification = this.sendNotification;
659
+ const pluginName = plugin.name;
660
+ const deps = this.pluginMethodCaller
661
+ ? buildPluginDeps(plugin.dependencyNames, this.plugins, this.pluginMethodCaller)
662
+ : {};
663
+ const schedule = this.scheduleCallback ?? (() => { });
664
+ return {
665
+ ...agentContext,
666
+ pluginConfig: undefined, // injected by plugin builder wrapper
667
+ pluginAgentConfig: this.config.plugins?.find(c => c.pluginName === plugin.name)?.config,
668
+ pluginContext: this.pluginContexts.get(plugin.name),
669
+ pluginState,
670
+ self,
671
+ schedule,
672
+ notify: (type, payload) => {
673
+ sendNotification?.({ pluginName, type, payload });
674
+ },
675
+ deps,
676
+ };
677
+ }
678
+ /**
679
+ * Emit handler_completed event.
680
+ *
681
+ * Skipped when the handler produced no action (null result) — those events are
682
+ * pure noise (64%+ of a typical session log). onStart is the one exception: the
683
+ * reducer uses its completion event to flip `onStartCalled`, so we always emit
684
+ * it even with a null result.
685
+ */
686
+ async emitHandlerCompleted(handlerName, result) {
687
+ if (result === null && handlerName !== 'onStart')
688
+ return;
689
+ await this.store.emit(withSessionId(this.store.sessionId, agentEvents.create('handler_completed', {
690
+ agentId: this.id,
691
+ handlerName,
692
+ result,
693
+ })));
694
+ }
695
+ /**
696
+ * Execute onStart handler - called once on first inference.
697
+ */
698
+ async executeOnStart(agentState) {
699
+ this.logger.debug('Executing onStart handlers', { agentId: this.id });
700
+ const agentContext = this.buildAgentContext(agentState);
701
+ // Run all plugin handlers with per-plugin isolation
702
+ // Note: Handlers emit preamble_added events directly via ctx.emitEvent()
703
+ let pauseResult = null;
704
+ for (const plugin of this.plugins) {
705
+ if (!plugin.agentHooks?.onStart)
706
+ continue;
707
+ try {
708
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
709
+ const result = await plugin.agentHooks.onStart(ctx);
710
+ if (result !== null && result.action === 'pause') {
711
+ // Record first pause, but continue running other handlers
712
+ if (pauseResult === null) {
713
+ pauseResult = result;
714
+ }
715
+ }
716
+ }
717
+ catch (error) {
718
+ this.logger.error(`Plugin '${plugin.name}' onStart hook failed`, error instanceof Error ? error : undefined, {
719
+ agentId: this.id,
720
+ plugin: plugin.name,
721
+ });
722
+ }
723
+ }
724
+ await this.emitHandlerCompleted('onStart', pauseResult);
725
+ if (pauseResult !== null) {
726
+ await this.emitHandlerPause(pauseResult.reason);
727
+ }
728
+ return pauseResult;
729
+ }
730
+ /**
731
+ * Execute beforeInference handler.
732
+ */
733
+ async executeBeforeInference(agentState) {
734
+ this.logger.debug('Executing beforeInference handlers', {
735
+ agentId: this.id,
736
+ turnNumber: this.turnNumber,
737
+ });
738
+ const agentContext = this.buildAgentContext(agentState);
739
+ // Run plugin handlers with per-plugin isolation - first skip/pause wins
740
+ for (const plugin of this.plugins) {
741
+ if (!plugin.agentHooks?.beforeInference)
742
+ continue;
743
+ try {
744
+ const ctx = {
745
+ ...this.buildPluginHookContext(plugin, agentContext),
746
+ pendingMessages: getUnconsumedMessages(this.store.getState(), this.id),
747
+ turnNumber: this.turnNumber,
748
+ };
749
+ const result = await plugin.agentHooks.beforeInference(ctx);
750
+ if (result === null) {
751
+ continue;
752
+ }
753
+ switch (result.action) {
754
+ case 'skip':
755
+ await this.emitHandlerCompleted('beforeInference', result);
756
+ return result;
757
+ case 'pause':
758
+ await this.emitHandlerCompleted('beforeInference', result);
759
+ await this.emitHandlerPause(result.reason);
760
+ return result;
761
+ default:
762
+ throw new Error(`Unhandled beforeInference action: ${result.action}`);
763
+ }
764
+ }
765
+ catch (error) {
766
+ this.logger.error(`Plugin '${plugin.name}' beforeInference hook failed`, error instanceof Error ? error : undefined, {
767
+ agentId: this.id,
768
+ plugin: plugin.name,
769
+ });
770
+ }
771
+ }
772
+ await this.emitHandlerCompleted('beforeInference', null);
773
+ return null;
774
+ }
775
+ /**
776
+ * Execute afterInference handler.
777
+ */
778
+ async executeAfterInference(agentState, response) {
779
+ this.logger.debug('Executing afterInference handlers', {
780
+ agentId: this.id,
781
+ turnNumber: this.turnNumber,
782
+ });
783
+ const agentContext = this.buildAgentContext(agentState);
784
+ // Run plugin handlers with per-plugin isolation - first retry/modify/pause wins
785
+ for (const plugin of this.plugins) {
786
+ if (!plugin.agentHooks?.afterInference)
787
+ continue;
788
+ try {
789
+ const ctx = {
790
+ ...this.buildPluginHookContext(plugin, agentContext),
791
+ response,
792
+ turnNumber: this.turnNumber,
793
+ };
794
+ const result = await plugin.agentHooks.afterInference(ctx);
795
+ if (result === null) {
796
+ continue;
797
+ }
798
+ switch (result.action) {
799
+ case 'retry':
800
+ await this.emitHandlerCompleted('afterInference', result);
801
+ return result;
802
+ case 'modify':
803
+ await this.emitHandlerCompleted('afterInference', result);
804
+ return result;
805
+ case 'pause':
806
+ await this.emitHandlerCompleted('afterInference', result);
807
+ // Don't emit agent_paused here — caller commits inference first,
808
+ // then pauses (so conversationHistory includes the completed turn).
809
+ return result;
810
+ default:
811
+ throw new Error(`Unhandled afterInference action: ${result.action}`);
812
+ }
813
+ }
814
+ catch (error) {
815
+ this.logger.error(`Plugin '${plugin.name}' afterInference hook failed`, error instanceof Error ? error : undefined, {
816
+ agentId: this.id,
817
+ plugin: plugin.name,
818
+ });
819
+ }
820
+ }
821
+ await this.emitHandlerCompleted('afterInference', null);
822
+ return null;
823
+ }
824
+ /**
825
+ * Execute beforeToolCall handler.
826
+ */
827
+ async executeBeforeToolCall(agentState, toolCall) {
828
+ this.logger.debug('Executing beforeToolCall handlers', {
829
+ agentId: this.id,
830
+ toolName: toolCall.name,
831
+ });
832
+ const agentContext = this.buildAgentContext(agentState);
833
+ // Run plugin handlers with per-plugin isolation - first block/replace/pause wins
834
+ for (const plugin of this.plugins) {
835
+ if (!plugin.agentHooks?.beforeToolCall)
836
+ continue;
837
+ try {
838
+ const ctx = {
839
+ ...this.buildPluginHookContext(plugin, agentContext),
840
+ toolCall: {
841
+ id: toolCall.id,
842
+ name: toolCall.name,
843
+ input: toolCall.input,
844
+ },
845
+ };
846
+ const result = await plugin.agentHooks.beforeToolCall(ctx);
847
+ if (result === null) {
848
+ continue;
849
+ }
850
+ switch (result.action) {
851
+ case 'block':
852
+ await this.emitHandlerCompleted('beforeToolCall', result);
853
+ return result;
854
+ case 'replace':
855
+ await this.emitHandlerCompleted('beforeToolCall', result);
856
+ return {
857
+ action: 'replace',
858
+ toolCall: {
859
+ id: result.toolCall.id,
860
+ name: result.toolCall.name,
861
+ input: result.toolCall.input,
862
+ },
863
+ };
864
+ case 'pause':
865
+ await this.emitHandlerCompleted('beforeToolCall', result);
866
+ await this.emitHandlerPause(result.reason);
867
+ return result;
868
+ default:
869
+ throw new Error(`Unhandled beforeToolCall action: ${result.action}`);
870
+ }
871
+ }
872
+ catch (error) {
873
+ this.logger.error(`Plugin '${plugin.name}' beforeToolCall hook failed`, error instanceof Error ? error : undefined, {
874
+ agentId: this.id,
875
+ plugin: plugin.name,
876
+ toolName: toolCall.name,
877
+ });
878
+ }
879
+ }
880
+ await this.emitHandlerCompleted('beforeToolCall', null);
881
+ return null;
882
+ }
883
+ /**
884
+ * Execute afterToolCall handler.
885
+ */
886
+ async executeAfterToolCall(agentState, toolCall, toolResult) {
887
+ this.logger.debug('Executing afterToolCall handlers', {
888
+ agentId: this.id,
889
+ toolName: toolCall.name,
890
+ });
891
+ const agentContext = this.buildAgentContext(agentState);
892
+ // Run plugin handlers with per-plugin isolation - first modify/pause wins
893
+ for (const plugin of this.plugins) {
894
+ if (!plugin.agentHooks?.afterToolCall)
895
+ continue;
896
+ try {
897
+ const ctx = {
898
+ ...this.buildPluginHookContext(plugin, agentContext),
899
+ toolCall: {
900
+ id: toolCall.id,
901
+ name: toolCall.name,
902
+ input: toolCall.input,
903
+ },
904
+ result: toolResult,
905
+ };
906
+ const result = await plugin.agentHooks.afterToolCall(ctx);
907
+ if (result === null) {
908
+ continue;
909
+ }
910
+ switch (result.action) {
911
+ case 'modify':
912
+ await this.emitHandlerCompleted('afterToolCall', result);
913
+ return result;
914
+ case 'pause':
915
+ await this.emitHandlerCompleted('afterToolCall', result);
916
+ await this.emitHandlerPause(result.reason);
917
+ return result;
918
+ default:
919
+ throw new Error(`Unhandled afterToolCall action: ${result.action}`);
920
+ }
921
+ }
922
+ catch (error) {
923
+ this.logger.error(`Plugin '${plugin.name}' afterToolCall hook failed`, error instanceof Error ? error : undefined, {
924
+ agentId: this.id,
925
+ plugin: plugin.name,
926
+ toolName: toolCall.name,
927
+ });
928
+ }
929
+ }
930
+ await this.emitHandlerCompleted('afterToolCall', null);
931
+ return null;
932
+ }
933
+ /**
934
+ * Execute onComplete handler.
935
+ */
936
+ async executeOnComplete(agentState) {
937
+ this.logger.debug('Executing onComplete handlers', { agentId: this.id });
938
+ const agentContext = this.buildAgentContext(agentState);
939
+ // Run all plugin handlers with per-plugin isolation - first pause wins
940
+ for (const plugin of this.plugins) {
941
+ if (!plugin.agentHooks?.onComplete)
942
+ continue;
943
+ try {
944
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
945
+ const pluginResult = await plugin.agentHooks.onComplete(ctx);
946
+ if (pluginResult === null) {
947
+ continue;
948
+ }
949
+ switch (pluginResult.action) {
950
+ case 'pause':
951
+ await this.emitHandlerCompleted('onComplete', pluginResult);
952
+ await this.emitHandlerPause(pluginResult.reason);
953
+ return;
954
+ default:
955
+ throw new Error(`Unhandled onComplete action: ${pluginResult.action}`);
956
+ }
957
+ }
958
+ catch (error) {
959
+ this.logger.error(`Plugin '${plugin.name}' onComplete hook failed`, error instanceof Error ? error : undefined, {
960
+ agentId: this.id,
961
+ plugin: plugin.name,
962
+ });
963
+ }
964
+ }
965
+ await this.emitHandlerCompleted('onComplete', null);
966
+ }
967
+ /**
968
+ * Execute onError handler.
969
+ */
970
+ async executeOnError(agentState, error) {
971
+ this.logger.debug('Executing onError handlers', { agentId: this.id });
972
+ const agentContext = this.buildAgentContext(agentState);
973
+ // Run all plugin handlers with per-plugin isolation - first pause wins
974
+ for (const plugin of this.plugins) {
975
+ if (!plugin.agentHooks?.onError)
976
+ continue;
977
+ try {
978
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
979
+ const pluginResult = await plugin.agentHooks.onError({ ...ctx, error });
980
+ if (pluginResult === null) {
981
+ continue;
982
+ }
983
+ switch (pluginResult.action) {
984
+ case 'pause':
985
+ await this.emitHandlerCompleted('onError', pluginResult);
986
+ await this.emitHandlerPause(pluginResult.reason);
987
+ return;
988
+ default:
989
+ throw new Error(`Unhandled onError action: ${pluginResult.action}`);
990
+ }
991
+ }
992
+ catch (error) {
993
+ this.logger.error(`Plugin '${plugin.name}' onError hook failed`, error instanceof Error ? error : undefined, {
994
+ agentId: this.id,
995
+ plugin: plugin.name,
996
+ });
997
+ }
998
+ }
999
+ await this.emitHandlerCompleted('onError', null);
1000
+ }
1001
+ // ============================================================================
1002
+ // Message building
1003
+ // ============================================================================
1004
+ /**
1005
+ * Build pending messages from tool results.
1006
+ * Mailbox messages are handled by the mailbox plugin's dequeue mechanism.
1007
+ */
1008
+ buildPendingMessages(agentState) {
1009
+ const pending = [];
1010
+ for (const ptr of agentState.pendingToolResults) {
1011
+ pending.push({
1012
+ role: 'tool',
1013
+ toolCallId: ptr.toolCallId,
1014
+ toolName: ptr.toolName,
1015
+ content: ptr.content,
1016
+ isError: ptr.isError,
1017
+ timestamp: ptr.timestamp,
1018
+ });
1019
+ }
1020
+ return pending;
1021
+ }
1022
+ /**
1023
+ * Build LLM messages from agent state and pending messages.
1024
+ * Order: [preamble, conversation history, pending messages]
1025
+ * - Preamble is never compacted (includes skills injected by plugin)
1026
+ * - Conversation history may be compacted
1027
+ * - Pending messages are ephemeral (for current turn)
1028
+ */
1029
+ buildLLMMessages(agentState, pendingMessages) {
1030
+ const messages = [];
1031
+ // 1. Preamble (ALWAYS prepended, NEVER compacted — includes skills from plugin)
1032
+ messages.push(...agentState.preamble);
1033
+ // 2. Conversation history (may be compacted)
1034
+ messages.push(...agentState.conversationHistory);
1035
+ // 3. Pending messages
1036
+ messages.push(...pendingMessages);
1037
+ return messages;
1038
+ }
1039
+ // ============================================================================
1040
+ // Plugin system helpers
1041
+ // ============================================================================
1042
+ /**
1043
+ * Build merged tools map from config (preset-level) and plugin tools.
1044
+ * Plugin tools override config tools with the same name.
1045
+ */
1046
+ buildToolsMap() {
1047
+ const tools = new Map();
1048
+ /** Track which source registered each tool name for collision detection */
1049
+ const toolSources = new Map();
1050
+ // 1. Static tools from config (preset-level)
1051
+ for (const tool of this.config.tools ?? []) {
1052
+ tools.set(tool.name, tool);
1053
+ toolSources.set(tool.name, 'config');
1054
+ }
1055
+ // 2. Plugin tools (override static)
1056
+ const agentState = this.state;
1057
+ if (agentState) {
1058
+ const agentContext = this.buildAgentContext(agentState);
1059
+ for (const plugin of this.plugins) {
1060
+ if (!plugin.getTools)
1061
+ continue;
1062
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
1063
+ for (const tool of plugin.getTools(ctx)) {
1064
+ const existing = toolSources.get(tool.name);
1065
+ if (existing) {
1066
+ this.logger.warn(`Tool name collision: '${tool.name}' from plugin '${plugin.name}' overrides '${existing}'`, {
1067
+ agentId: this.id,
1068
+ toolName: tool.name,
1069
+ });
1070
+ }
1071
+ tools.set(tool.name, tool);
1072
+ toolSources.set(tool.name, `plugin:${plugin.name}`);
1073
+ }
1074
+ }
1075
+ }
1076
+ return tools;
1077
+ }
1078
+ /**
1079
+ * Build composed system prompt from base briefing, plugin sections, environment, and preset prompt.
1080
+ */
1081
+ buildSystemPrompt() {
1082
+ const sections = [];
1083
+ // 1. Framework base briefing (always first)
1084
+ sections.push(AGENT_BASE_BRIEFING);
1085
+ // 2. Plugin system prompt sections
1086
+ const agentState = this.state;
1087
+ if (agentState) {
1088
+ const agentContext = this.buildAgentContext(agentState);
1089
+ for (const plugin of this.plugins) {
1090
+ if (!plugin.getSystemPrompt)
1091
+ continue;
1092
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
1093
+ const section = plugin.getSystemPrompt(ctx);
1094
+ if (section)
1095
+ sections.push(section);
1096
+ }
1097
+ }
1098
+ // 3. Environment section
1099
+ const roots = this.fileStore.getRoots();
1100
+ sections.push(buildEnvironmentSection({
1101
+ sessionPath: roots.session,
1102
+ workspacePath: roots.workspace,
1103
+ }));
1104
+ // 4. Custom prompt from preset (last)
1105
+ if (this.config.systemPrompt) {
1106
+ let customPrompt = this.config.systemPrompt;
1107
+ customPrompt = customPrompt.replaceAll('{{sessionDir}}', roots.session);
1108
+ if (roots.workspace) {
1109
+ customPrompt = customPrompt.replaceAll('{{workspaceDir}}', roots.workspace);
1110
+ }
1111
+ sections.push(customPrompt);
1112
+ }
1113
+ return sections.filter(Boolean).join('\n\n').trim();
1114
+ }
1115
+ /**
1116
+ * Get combined status from all plugins.
1117
+ */
1118
+ getPluginStatus() {
1119
+ const agentState = this.state;
1120
+ if (!agentState)
1121
+ return null;
1122
+ const agentContext = this.buildAgentContext(agentState);
1123
+ const parts = [];
1124
+ for (const plugin of this.plugins) {
1125
+ if (!plugin.getStatus)
1126
+ continue;
1127
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
1128
+ const status = plugin.getStatus(ctx);
1129
+ if (status)
1130
+ parts.push(status);
1131
+ }
1132
+ return parts.length > 0 ? parts.join('\n\n') : null;
1133
+ }
1134
+ // ============================================================================
1135
+ // Plugin dequeue helpers
1136
+ // ============================================================================
1137
+ /**
1138
+ * Check if any plugin has pending messages for this agent.
1139
+ */
1140
+ hasPluginPendingMessages() {
1141
+ const agentState = this.state;
1142
+ if (!agentState)
1143
+ return false;
1144
+ const agentContext = this.buildAgentContext(agentState);
1145
+ for (const plugin of this.plugins) {
1146
+ if (!plugin.dequeue)
1147
+ continue;
1148
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
1149
+ if (plugin.dequeue.hasPendingMessages(ctx))
1150
+ return true;
1151
+ }
1152
+ return false;
1153
+ }
1154
+ /**
1155
+ * Collect pending messages from all plugins that have dequeue hooks.
1156
+ * Returns array of { plugin, messages, token } for each plugin with pending messages.
1157
+ */
1158
+ collectPluginMessages() {
1159
+ const agentState = this.state;
1160
+ if (!agentState)
1161
+ return [];
1162
+ const agentContext = this.buildAgentContext(agentState);
1163
+ const collected = [];
1164
+ for (const plugin of this.plugins) {
1165
+ if (!plugin.dequeue)
1166
+ continue;
1167
+ const ctx = this.buildPluginHookContext(plugin, agentContext);
1168
+ const result = plugin.dequeue.getPendingMessages(ctx);
1169
+ if (result) {
1170
+ collected.push({
1171
+ plugin,
1172
+ messages: result.messages,
1173
+ token: result.token,
1174
+ });
1175
+ }
1176
+ }
1177
+ return collected;
1178
+ }
1179
+ }
1180
+ // ============================================================================
1181
+ // Local helpers (inlined from @roj-ai/core)
1182
+ // ============================================================================
1183
+ function getUnconsumedMessages(sessionState, agentId) {
1184
+ return getAgentUnconsumedMailbox(selectMailboxState(sessionState), agentId);
1185
+ }
1186
+ function hasWork(agent) {
1187
+ // Has pending tool results - needs LLM to process
1188
+ if (agent.pendingToolResults.length > 0)
1189
+ return true;
1190
+ return false;
1191
+ }
1192
+ /**
1193
+ * Narrow tool call input from `unknown` to `Record<string, unknown>`.
1194
+ * Tool inputs are always validated objects from Zod schemas.
1195
+ */
1196
+ function isRecord(value) {
1197
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1198
+ }
1199
+ function toInputRecord(input) {
1200
+ if (isRecord(input)) {
1201
+ return input;
1202
+ }
1203
+ return {};
1204
+ }
1205
+ //# sourceMappingURL=agent.js.map