@roj-ai/sdk 0.1.3 → 0.1.4

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,763 @@
1
+ import { beforeEach, describe, expect, it } from 'bun:test';
2
+ import { generateTestAgentId } from '../../core/agents/schema.js';
3
+ import { ModelId } from '../../core/llm/schema.js';
4
+ import { generateSessionId } from '../../core/sessions/schema.js';
5
+ import { generateToolCallId } from '../../core/tools/schema.js';
6
+ import { Err, Ok } from '../../lib/utils/result.js';
7
+ import { silentLogger } from '../../lib/logger/logger.js';
8
+ import { ContextCompactor, createContextCompactedEvent, formatMessageForSummary } from './context-compactor.js';
9
+ // ============================================================================
10
+ // Test Constants
11
+ // ============================================================================
12
+ const TEST_MODEL_ID = ModelId('test/model');
13
+ // ============================================================================
14
+ // Mock LLM Provider
15
+ // ============================================================================
16
+ class MockLLMProvider {
17
+ name = 'mock';
18
+ responses = [];
19
+ responseIndex = 0;
20
+ calls = [];
21
+ setResponses(responses) {
22
+ this.responses = responses;
23
+ this.responseIndex = 0;
24
+ }
25
+ async inference(request) {
26
+ this.calls.push(request);
27
+ if (this.responseIndex >= this.responses.length) {
28
+ return Err({ type: 'server_error', message: 'No more mock responses' });
29
+ }
30
+ return Ok(this.responses[this.responseIndex++]);
31
+ }
32
+ }
33
+ // ============================================================================
34
+ // Tests: ContextCompactor.needsCompaction
35
+ // ============================================================================
36
+ describe('ContextCompactor.needsCompaction', () => {
37
+ let mockLLM;
38
+ let compactor;
39
+ beforeEach(() => {
40
+ mockLLM = new MockLLMProvider();
41
+ compactor = new ContextCompactor(mockLLM, silentLogger, {
42
+ model: TEST_MODEL_ID,
43
+ maxTokens: 100,
44
+ keepRecentMessages: 2,
45
+ });
46
+ });
47
+ it('returns false when below threshold', () => {
48
+ const messages = [{ role: 'user', content: 'short' }];
49
+ expect(compactor.needsCompaction(messages)).toBe(false);
50
+ });
51
+ it('returns true when above threshold', () => {
52
+ // Create messages with > 100 tokens
53
+ const messages = [
54
+ { role: 'user', content: 'a'.repeat(200) }, // ~50 tokens + overhead
55
+ { role: 'assistant', content: 'b'.repeat(200) }, // ~50 tokens + overhead
56
+ { role: 'user', content: 'c'.repeat(200) }, // ~50 tokens + overhead
57
+ ];
58
+ expect(compactor.needsCompaction(messages)).toBe(true);
59
+ });
60
+ });
61
+ // ============================================================================
62
+ // Tests: ContextCompactor.compact
63
+ // ============================================================================
64
+ describe('ContextCompactor.compact', () => {
65
+ let mockLLM;
66
+ let compactor;
67
+ let sessionId;
68
+ let agentId;
69
+ beforeEach(() => {
70
+ mockLLM = new MockLLMProvider();
71
+ compactor = new ContextCompactor(mockLLM, silentLogger, {
72
+ model: TEST_MODEL_ID,
73
+ maxTokens: 100,
74
+ keepRecentMessages: 2,
75
+ });
76
+ sessionId = generateSessionId();
77
+ agentId = generateTestAgentId();
78
+ });
79
+ it('returns empty summary when no messages to compact', async () => {
80
+ // Only 2 messages, keepRecentMessages = 2
81
+ const messages = [
82
+ { role: 'user', content: 'message 1' },
83
+ { role: 'assistant', content: 'message 2' },
84
+ ];
85
+ const result = await compactor.compact(sessionId, agentId, messages);
86
+ expect(result.ok).toBe(true);
87
+ if (!result.ok)
88
+ return;
89
+ expect(result.value.summary).toBe('');
90
+ expect(result.value.messagesRemoved).toBe(0);
91
+ expect(result.value.compactedMessages).toEqual(messages);
92
+ });
93
+ it('compacts old messages and keeps recent ones', async () => {
94
+ mockLLM.setResponses([
95
+ {
96
+ content: 'Summary of the conversation',
97
+ toolCalls: [],
98
+ finishReason: 'stop',
99
+ metrics: {
100
+ promptTokens: 50,
101
+ completionTokens: 20,
102
+ totalTokens: 70,
103
+ latencyMs: 100,
104
+ model: 'mock',
105
+ },
106
+ },
107
+ ]);
108
+ const messages = [
109
+ { role: 'user', content: 'old message 1' },
110
+ { role: 'assistant', content: 'old message 2' },
111
+ { role: 'user', content: 'old message 3' },
112
+ { role: 'user', content: 'recent message 1' },
113
+ { role: 'assistant', content: 'recent message 2' },
114
+ ];
115
+ const result = await compactor.compact(sessionId, agentId, messages);
116
+ expect(result.ok).toBe(true);
117
+ if (!result.ok)
118
+ return;
119
+ expect(result.value.summary).toBe('Summary of the conversation');
120
+ expect(result.value.messagesRemoved).toBe(3);
121
+ expect(result.value.compactedMessages.length).toBe(3); // summary + 2 recent
122
+ // First message is summary
123
+ expect(result.value.compactedMessages[0].role).toBe('system');
124
+ expect(result.value.compactedMessages[0].content).toContain('[CONVERSATION SUMMARY]');
125
+ expect(result.value.compactedMessages[0].content).toContain('Summary of the conversation');
126
+ // Recent messages preserved
127
+ expect(result.value.compactedMessages[1].content).toBe('recent message 1');
128
+ expect(result.value.compactedMessages[2].content).toBe('recent message 2');
129
+ });
130
+ it('calls LLM with formatted conversation and configured model', async () => {
131
+ mockLLM.setResponses([
132
+ {
133
+ content: 'Summary',
134
+ toolCalls: [],
135
+ finishReason: 'stop',
136
+ metrics: {
137
+ promptTokens: 50,
138
+ completionTokens: 20,
139
+ totalTokens: 70,
140
+ latencyMs: 100,
141
+ model: 'mock',
142
+ },
143
+ },
144
+ ]);
145
+ const messages = [
146
+ { role: 'user', content: 'user message' },
147
+ { role: 'assistant', content: 'assistant message' },
148
+ { role: 'user', content: 'recent' },
149
+ { role: 'assistant', content: 'recent too' },
150
+ ];
151
+ await compactor.compact(sessionId, agentId, messages);
152
+ expect(mockLLM.calls.length).toBe(1);
153
+ const request = mockLLM.calls[0];
154
+ // Verify model from config is used
155
+ expect(request.model).toBe(TEST_MODEL_ID);
156
+ expect(request.messages[0].role).toBe('user');
157
+ expect(request.messages[0].content).toContain('User: user message');
158
+ expect(request.messages[0].content).toContain('Agent: assistant message');
159
+ // Recent messages should not be in the summarization request
160
+ expect(request.messages[0].content).not.toContain('recent');
161
+ });
162
+ it('returns error when LLM fails', async () => {
163
+ // No responses set, so LLM will fail
164
+ const messages = [
165
+ { role: 'user', content: 'old 1' },
166
+ { role: 'assistant', content: 'old 2' },
167
+ { role: 'user', content: 'old 3' },
168
+ { role: 'user', content: 'recent 1' },
169
+ { role: 'assistant', content: 'recent 2' },
170
+ ];
171
+ const result = await compactor.compact(sessionId, agentId, messages);
172
+ expect(result.ok).toBe(false);
173
+ if (result.ok)
174
+ return;
175
+ expect(result.error.message).toContain('Compaction failed');
176
+ });
177
+ });
178
+ // ============================================================================
179
+ // Tests: ContextCompactor.compactIfNeeded
180
+ // ============================================================================
181
+ describe('ContextCompactor.compactIfNeeded', () => {
182
+ let mockLLM;
183
+ let compactor;
184
+ let sessionId;
185
+ let agentId;
186
+ beforeEach(() => {
187
+ mockLLM = new MockLLMProvider();
188
+ compactor = new ContextCompactor(mockLLM, silentLogger, {
189
+ model: TEST_MODEL_ID,
190
+ maxTokens: 50,
191
+ keepRecentMessages: 1,
192
+ });
193
+ sessionId = generateSessionId();
194
+ agentId = generateTestAgentId();
195
+ });
196
+ it('returns null when compaction not needed', async () => {
197
+ const messages = [{ role: 'user', content: 'short' }];
198
+ const result = await compactor.compactIfNeeded(sessionId, agentId, messages);
199
+ expect(result.ok).toBe(true);
200
+ if (!result.ok)
201
+ return;
202
+ expect(result.value).toBeNull();
203
+ expect(mockLLM.calls.length).toBe(0);
204
+ });
205
+ it('compacts when needed', async () => {
206
+ mockLLM.setResponses([
207
+ {
208
+ content: 'Summary',
209
+ toolCalls: [],
210
+ finishReason: 'stop',
211
+ metrics: {
212
+ promptTokens: 50,
213
+ completionTokens: 20,
214
+ totalTokens: 70,
215
+ latencyMs: 100,
216
+ model: 'mock',
217
+ },
218
+ },
219
+ ]);
220
+ // Create messages exceeding threshold
221
+ const messages = [
222
+ { role: 'user', content: 'a'.repeat(100) },
223
+ { role: 'assistant', content: 'b'.repeat(100) },
224
+ { role: 'user', content: 'c'.repeat(100) },
225
+ ];
226
+ const result = await compactor.compactIfNeeded(sessionId, agentId, messages);
227
+ expect(result.ok).toBe(true);
228
+ if (!result.ok)
229
+ return;
230
+ expect(result.value).not.toBeNull();
231
+ expect(result.value.summary).toBe('Summary');
232
+ });
233
+ });
234
+ // ============================================================================
235
+ // Tests: createContextCompactedEvent
236
+ // ============================================================================
237
+ describe('createContextCompactedEvent', () => {
238
+ it('creates event with correct fields', () => {
239
+ const sessionId = generateSessionId();
240
+ const agentId = generateTestAgentId();
241
+ const result = {
242
+ compactedMessages: [
243
+ { role: 'system', content: 'summary' },
244
+ { role: 'user', content: 'recent' },
245
+ ],
246
+ summary: 'The summary',
247
+ originalTokens: 1000,
248
+ compactedTokens: 200,
249
+ messagesRemoved: 5,
250
+ };
251
+ const event = createContextCompactedEvent(sessionId, agentId, result);
252
+ expect(event.type).toBe('context_compacted');
253
+ expect(event.sessionId).toBe(sessionId);
254
+ expect(event.agentId).toBe(agentId);
255
+ expect(event.compactedContent).toBe('The summary');
256
+ expect(event.originalTokens).toBe(1000);
257
+ expect(event.compactedTokens).toBe(200);
258
+ expect(event.messagesRemoved).toBe(5);
259
+ expect(event.newConversationHistory.length).toBe(2);
260
+ expect(event.newConversationHistory[0].role).toBe('system');
261
+ expect(event.newConversationHistory[0].content).toBe('summary');
262
+ expect(event.timestamp).toBeDefined();
263
+ });
264
+ it('converts tool role to system in history', () => {
265
+ const sessionId = generateSessionId();
266
+ const agentId = generateTestAgentId();
267
+ const toolCallId = generateToolCallId();
268
+ const result = {
269
+ compactedMessages: [{ role: 'tool', content: 'tool result', toolCallId }],
270
+ summary: '',
271
+ originalTokens: 100,
272
+ compactedTokens: 50,
273
+ messagesRemoved: 0,
274
+ };
275
+ const event = createContextCompactedEvent(sessionId, agentId, result);
276
+ // tool role should be converted to system
277
+ expect(event.newConversationHistory[0].role).toBe('system');
278
+ });
279
+ });
280
+ // ============================================================================
281
+ // Tests: Default config
282
+ // ============================================================================
283
+ describe('ContextCompactor with custom config', () => {
284
+ it('respects custom maxTokens', () => {
285
+ const mockLLM = new MockLLMProvider();
286
+ const config = {
287
+ model: TEST_MODEL_ID,
288
+ maxTokens: 20,
289
+ keepRecentMessages: 1,
290
+ };
291
+ const compactor = new ContextCompactor(mockLLM, silentLogger, config);
292
+ const smallMessages = [{ role: 'user', content: 'hi' }];
293
+ expect(compactor.needsCompaction(smallMessages)).toBe(false);
294
+ const largeMessages = [
295
+ { role: 'user', content: 'a'.repeat(100) },
296
+ ];
297
+ expect(compactor.needsCompaction(largeMessages)).toBe(true);
298
+ });
299
+ it('respects custom keepRecentMessages', async () => {
300
+ const mockLLM = new MockLLMProvider();
301
+ mockLLM.setResponses([
302
+ {
303
+ content: 'Summary',
304
+ toolCalls: [],
305
+ finishReason: 'stop',
306
+ metrics: {
307
+ promptTokens: 50,
308
+ completionTokens: 20,
309
+ totalTokens: 70,
310
+ latencyMs: 100,
311
+ model: 'mock',
312
+ },
313
+ },
314
+ ]);
315
+ const config = {
316
+ model: TEST_MODEL_ID,
317
+ maxTokens: 10,
318
+ keepRecentMessages: 3,
319
+ };
320
+ const compactor = new ContextCompactor(mockLLM, silentLogger, config);
321
+ const messages = [
322
+ { role: 'user', content: 'old 1' },
323
+ { role: 'assistant', content: 'old 2' },
324
+ { role: 'user', content: 'recent 1' },
325
+ { role: 'assistant', content: 'recent 2' },
326
+ { role: 'user', content: 'recent 3' },
327
+ ];
328
+ const result = await compactor.compact(generateSessionId(), generateTestAgentId(), messages);
329
+ expect(result.ok).toBe(true);
330
+ if (!result.ok)
331
+ return;
332
+ // 2 old messages removed, 3 kept + 1 summary = 4 total
333
+ expect(result.value.messagesRemoved).toBe(2);
334
+ expect(result.value.compactedMessages.length).toBe(4);
335
+ });
336
+ it('uses custom summaryPrompt', async () => {
337
+ const mockLLM = new MockLLMProvider();
338
+ mockLLM.setResponses([
339
+ {
340
+ content: 'Summary',
341
+ toolCalls: [],
342
+ finishReason: 'stop',
343
+ metrics: {
344
+ promptTokens: 50,
345
+ completionTokens: 20,
346
+ totalTokens: 70,
347
+ latencyMs: 100,
348
+ model: 'mock',
349
+ },
350
+ },
351
+ ]);
352
+ const customPrompt = 'Custom summary instructions';
353
+ const config = {
354
+ model: TEST_MODEL_ID,
355
+ maxTokens: 10,
356
+ keepRecentMessages: 1,
357
+ summaryPrompt: customPrompt,
358
+ };
359
+ const compactor = new ContextCompactor(mockLLM, silentLogger, config);
360
+ const messages = [
361
+ { role: 'user', content: 'old' },
362
+ { role: 'user', content: 'recent' },
363
+ ];
364
+ await compactor.compact(generateSessionId(), generateTestAgentId(), messages);
365
+ expect(mockLLM.calls.length).toBe(1);
366
+ expect(mockLLM.calls[0].systemPrompt).toBe(customPrompt);
367
+ });
368
+ });
369
+ // ============================================================================
370
+ // Tests: formatMessageForSummary
371
+ // ============================================================================
372
+ describe('formatMessageForSummary', () => {
373
+ it('formats user message', () => {
374
+ const msg = { role: 'user', content: 'Hello world' };
375
+ expect(formatMessageForSummary(msg)).toBe('User: Hello world');
376
+ });
377
+ it('formats user message with multimodal content', () => {
378
+ const msg = {
379
+ role: 'user',
380
+ content: [{ type: 'text', text: 'Check this image' }],
381
+ };
382
+ const result = formatMessageForSummary(msg);
383
+ expect(result).toContain('User:');
384
+ expect(result).toContain('Check this image');
385
+ });
386
+ it('formats assistant message with text only', () => {
387
+ const msg = { role: 'assistant', content: 'Sure, I can help' };
388
+ expect(formatMessageForSummary(msg)).toBe('Agent: Sure, I can help');
389
+ });
390
+ it('formats assistant message with tool calls', () => {
391
+ const msg = {
392
+ role: 'assistant',
393
+ content: '',
394
+ toolCalls: [{ id: generateToolCallId(), name: 'read', input: { path: '/src/index.ts' } }],
395
+ };
396
+ const result = formatMessageForSummary(msg);
397
+ expect(result).toBe('Agent: [Called tools: read(path)]');
398
+ });
399
+ it('formats assistant message with text and tool calls', () => {
400
+ const msg = {
401
+ role: 'assistant',
402
+ content: 'Let me read that file.',
403
+ toolCalls: [{ id: generateToolCallId(), name: 'read', input: { path: '/src/index.ts' } }],
404
+ };
405
+ const result = formatMessageForSummary(msg);
406
+ expect(result).toContain('Agent: Let me read that file.');
407
+ expect(result).toContain('[Called tools: read(path)]');
408
+ });
409
+ it('formats assistant message with multiple tool calls', () => {
410
+ const msg = {
411
+ role: 'assistant',
412
+ content: '',
413
+ toolCalls: [
414
+ { id: generateToolCallId(), name: 'read', input: { path: '/a.ts' } },
415
+ { id: generateToolCallId(), name: 'edit', input: { path: '/b.ts', old_string: 'x', new_string: 'y' } },
416
+ ],
417
+ };
418
+ const result = formatMessageForSummary(msg);
419
+ expect(result).toBe('Agent: [Called tools: read(path), edit(path, old_string, new_string)]');
420
+ });
421
+ it('formats tool result with tool name', () => {
422
+ const msg = {
423
+ role: 'tool',
424
+ content: 'export function main() {}',
425
+ toolCallId: generateToolCallId(),
426
+ toolName: 'read',
427
+ };
428
+ const result = formatMessageForSummary(msg);
429
+ expect(result).toBe('Tool(read): export function main() {}');
430
+ });
431
+ it('formats tool result without tool name as unknown', () => {
432
+ const msg = {
433
+ role: 'tool',
434
+ content: 'some result',
435
+ toolCallId: generateToolCallId(),
436
+ };
437
+ const result = formatMessageForSummary(msg);
438
+ expect(result).toBe('Tool(unknown): some result');
439
+ });
440
+ it('truncates large tool results', () => {
441
+ const largeContent = 'x'.repeat(1000);
442
+ const msg = {
443
+ role: 'tool',
444
+ content: largeContent,
445
+ toolCallId: generateToolCallId(),
446
+ toolName: 'read',
447
+ };
448
+ const result = formatMessageForSummary(msg);
449
+ expect(result).toContain('Tool(read):');
450
+ expect(result).toContain('...(truncated)...');
451
+ expect(result.length).toBeLessThan(largeContent.length);
452
+ });
453
+ it('does not truncate small tool results', () => {
454
+ const smallContent = 'small result';
455
+ const msg = {
456
+ role: 'tool',
457
+ content: smallContent,
458
+ toolCallId: generateToolCallId(),
459
+ toolName: 'read',
460
+ };
461
+ const result = formatMessageForSummary(msg);
462
+ expect(result).toBe('Tool(read): small result');
463
+ expect(result).not.toContain('truncated');
464
+ });
465
+ it('formats system message', () => {
466
+ const msg = { role: 'system', content: 'You are an assistant.' };
467
+ expect(formatMessageForSummary(msg)).toBe('System: You are an assistant.');
468
+ });
469
+ });
470
+ // ============================================================================
471
+ // Tests: Tool calls in compaction
472
+ // ============================================================================
473
+ describe('ContextCompactor with tool calls', () => {
474
+ let mockLLM;
475
+ let compactor;
476
+ let sessionId;
477
+ let agentId;
478
+ beforeEach(() => {
479
+ mockLLM = new MockLLMProvider();
480
+ compactor = new ContextCompactor(mockLLM, silentLogger, {
481
+ model: TEST_MODEL_ID,
482
+ maxTokens: 100,
483
+ keepRecentMessages: 1,
484
+ });
485
+ sessionId = generateSessionId();
486
+ agentId = generateTestAgentId();
487
+ });
488
+ it('does not leave orphaned tool results at the start of kept messages', async () => {
489
+ mockLLM.setResponses([
490
+ {
491
+ content: 'Summary',
492
+ toolCalls: [],
493
+ finishReason: 'stop',
494
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
495
+ },
496
+ ]);
497
+ const toolCallId = generateToolCallId();
498
+ // keepRecentMessages=1 would split between the assistant (tool call) and tool result
499
+ const messages = [
500
+ { role: 'user', content: 'old message' },
501
+ { role: 'assistant', content: 'old response' },
502
+ { role: 'user', content: 'Read the file' },
503
+ {
504
+ role: 'assistant',
505
+ content: '',
506
+ toolCalls: [{ id: toolCallId, name: 'read', input: { path: '/src/index.ts' } }],
507
+ },
508
+ { role: 'tool', content: 'export const foo = 1', toolCallId, toolName: 'read' },
509
+ ];
510
+ const result = await compactor.compact(sessionId, agentId, messages);
511
+ expect(result.ok).toBe(true);
512
+ if (!result.ok)
513
+ return;
514
+ // The tool result must NOT be the first kept message — it should be compacted along with its tool call
515
+ const keptMessages = result.value.compactedMessages.filter(m => m.role !== 'system');
516
+ for (const msg of keptMessages) {
517
+ expect(msg.role).not.toBe('tool');
518
+ }
519
+ // All 5 original messages should be compacted (none kept except summary)
520
+ expect(result.value.messagesRemoved).toBe(5);
521
+ });
522
+ it('includes tool calls in summarization request', async () => {
523
+ mockLLM.setResponses([
524
+ {
525
+ content: 'Summary',
526
+ toolCalls: [],
527
+ finishReason: 'stop',
528
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
529
+ },
530
+ ]);
531
+ const toolCallId = generateToolCallId();
532
+ const messages = [
533
+ { role: 'user', content: 'Read the file' },
534
+ {
535
+ role: 'assistant',
536
+ content: '',
537
+ toolCalls: [{ id: toolCallId, name: 'read', input: { path: '/src/index.ts' } }],
538
+ },
539
+ { role: 'tool', content: 'export const foo = 1', toolCallId, toolName: 'read' },
540
+ { role: 'user', content: 'recent message' },
541
+ ];
542
+ await compactor.compact(sessionId, agentId, messages);
543
+ expect(mockLLM.calls.length).toBe(1);
544
+ const request = mockLLM.calls[0];
545
+ const summaryContent = request.messages[0].content;
546
+ // Verify tool call is included
547
+ expect(summaryContent).toContain('[Called tools: read(path)]');
548
+ // Verify tool result includes tool name
549
+ expect(summaryContent).toContain('Tool(read):');
550
+ expect(summaryContent).toContain('export const foo = 1');
551
+ });
552
+ });
553
+ // ============================================================================
554
+ // Tests: History offloading
555
+ // ============================================================================
556
+ describe('ContextCompactor with history offloading', () => {
557
+ let mockLLM;
558
+ let sessionId;
559
+ let agentId;
560
+ beforeEach(() => {
561
+ mockLLM = new MockLLMProvider();
562
+ sessionId = generateSessionId();
563
+ agentId = generateTestAgentId();
564
+ });
565
+ it('offloads history when enabled and offloader is provided', async () => {
566
+ const offloadedPaths = [];
567
+ const mockOffloader = {
568
+ async offload(agentId, content, pathPrefix) {
569
+ offloadedPaths.push({ agentId, content, pathPrefix });
570
+ return `${pathPrefix}${agentId}/history.md`;
571
+ },
572
+ };
573
+ mockLLM.setResponses([
574
+ {
575
+ content: 'Summary',
576
+ toolCalls: [],
577
+ finishReason: 'stop',
578
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
579
+ },
580
+ ]);
581
+ const compactor = new ContextCompactor(mockLLM, silentLogger, {
582
+ model: TEST_MODEL_ID,
583
+ maxTokens: 100,
584
+ keepRecentMessages: 1,
585
+ offloadHistory: true,
586
+ }, mockOffloader);
587
+ const messages = [
588
+ { role: 'user', content: 'old message 1' },
589
+ { role: 'assistant', content: 'old message 2' },
590
+ { role: 'user', content: 'recent message' },
591
+ ];
592
+ const result = await compactor.compact(sessionId, agentId, messages);
593
+ expect(result.ok).toBe(true);
594
+ if (!result.ok)
595
+ return;
596
+ // Verify offloader was called
597
+ expect(offloadedPaths.length).toBe(1);
598
+ expect(offloadedPaths[0].agentId).toBe(agentId);
599
+ expect(offloadedPaths[0].content).toContain('User: old message 1');
600
+ expect(offloadedPaths[0].pathPrefix).toBe('/session/.history/');
601
+ // Verify result contains historyPath
602
+ expect(result.value.historyPath).toBe(`/session/.history/${agentId}/history.md`);
603
+ // Verify summary message contains history reference
604
+ expect(result.value.compactedMessages[0].content).toContain('has been saved to');
605
+ expect(result.value.compactedMessages[0].content).toContain(`/session/.history/${agentId}/history.md`);
606
+ });
607
+ it('does not offload history when disabled', async () => {
608
+ const mockOffloader = {
609
+ async offload() {
610
+ throw new Error('Should not be called');
611
+ },
612
+ };
613
+ mockLLM.setResponses([
614
+ {
615
+ content: 'Summary',
616
+ toolCalls: [],
617
+ finishReason: 'stop',
618
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
619
+ },
620
+ ]);
621
+ const compactor = new ContextCompactor(mockLLM, silentLogger, {
622
+ model: TEST_MODEL_ID,
623
+ maxTokens: 100,
624
+ keepRecentMessages: 1,
625
+ offloadHistory: false, // explicitly disabled
626
+ }, mockOffloader);
627
+ const messages = [
628
+ { role: 'user', content: 'old' },
629
+ { role: 'user', content: 'recent' },
630
+ ];
631
+ const result = await compactor.compact(sessionId, agentId, messages);
632
+ expect(result.ok).toBe(true);
633
+ if (!result.ok)
634
+ return;
635
+ expect(result.value.historyPath).toBeUndefined();
636
+ expect(result.value.compactedMessages[0].content).not.toContain('has been saved to');
637
+ });
638
+ it('does not offload history when offloader is not provided', async () => {
639
+ mockLLM.setResponses([
640
+ {
641
+ content: 'Summary',
642
+ toolCalls: [],
643
+ finishReason: 'stop',
644
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
645
+ },
646
+ ]);
647
+ const compactor = new ContextCompactor(mockLLM, silentLogger, {
648
+ model: TEST_MODEL_ID,
649
+ maxTokens: 100,
650
+ keepRecentMessages: 1,
651
+ offloadHistory: true, // enabled but no offloader
652
+ });
653
+ const messages = [
654
+ { role: 'user', content: 'old' },
655
+ { role: 'user', content: 'recent' },
656
+ ];
657
+ const result = await compactor.compact(sessionId, agentId, messages);
658
+ expect(result.ok).toBe(true);
659
+ if (!result.ok)
660
+ return;
661
+ expect(result.value.historyPath).toBeUndefined();
662
+ });
663
+ it('uses custom historyPathPrefix', async () => {
664
+ const offloadedPaths = [];
665
+ const mockOffloader = {
666
+ async offload(_agentId, _content, pathPrefix) {
667
+ offloadedPaths.push({ pathPrefix });
668
+ return `/custom/path/history.md`;
669
+ },
670
+ };
671
+ mockLLM.setResponses([
672
+ {
673
+ content: 'Summary',
674
+ toolCalls: [],
675
+ finishReason: 'stop',
676
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
677
+ },
678
+ ]);
679
+ const compactor = new ContextCompactor(mockLLM, silentLogger, {
680
+ model: TEST_MODEL_ID,
681
+ maxTokens: 100,
682
+ keepRecentMessages: 1,
683
+ offloadHistory: true,
684
+ historyPathPrefix: '/session/.custom-history/',
685
+ }, mockOffloader);
686
+ const messages = [
687
+ { role: 'user', content: 'old' },
688
+ { role: 'user', content: 'recent' },
689
+ ];
690
+ await compactor.compact(sessionId, agentId, messages);
691
+ expect(offloadedPaths.length).toBe(1);
692
+ expect(offloadedPaths[0].pathPrefix).toBe('/session/.custom-history/');
693
+ });
694
+ it('continues compaction even if offloading fails', async () => {
695
+ const mockOffloader = {
696
+ async offload() {
697
+ throw new Error('Disk full');
698
+ },
699
+ };
700
+ mockLLM.setResponses([
701
+ {
702
+ content: 'Summary despite offload failure',
703
+ toolCalls: [],
704
+ finishReason: 'stop',
705
+ metrics: { promptTokens: 50, completionTokens: 20, totalTokens: 70, latencyMs: 100, model: 'mock' },
706
+ },
707
+ ]);
708
+ const compactor = new ContextCompactor(mockLLM, silentLogger, {
709
+ model: TEST_MODEL_ID,
710
+ maxTokens: 100,
711
+ keepRecentMessages: 1,
712
+ offloadHistory: true,
713
+ }, mockOffloader);
714
+ const messages = [
715
+ { role: 'user', content: 'old' },
716
+ { role: 'user', content: 'recent' },
717
+ ];
718
+ const result = await compactor.compact(sessionId, agentId, messages);
719
+ // Compaction should succeed despite offload failure
720
+ expect(result.ok).toBe(true);
721
+ if (!result.ok)
722
+ return;
723
+ expect(result.value.summary).toBe('Summary despite offload failure');
724
+ expect(result.value.historyPath).toBeUndefined();
725
+ });
726
+ });
727
+ // ============================================================================
728
+ // Tests: createContextCompactedEvent with historyPath
729
+ // ============================================================================
730
+ describe('createContextCompactedEvent with historyPath', () => {
731
+ it('includes historyPath in event when provided', () => {
732
+ const sessionId = generateSessionId();
733
+ const agentId = generateTestAgentId();
734
+ const result = {
735
+ compactedMessages: [
736
+ { role: 'system', content: 'summary' },
737
+ ],
738
+ summary: 'The summary',
739
+ originalTokens: 1000,
740
+ compactedTokens: 200,
741
+ messagesRemoved: 5,
742
+ historyPath: '/session/.history/agent-1/history.md',
743
+ };
744
+ const event = createContextCompactedEvent(sessionId, agentId, result);
745
+ expect(event.historyPath).toBe('/session/.history/agent-1/history.md');
746
+ });
747
+ it('does not include historyPath when not provided', () => {
748
+ const sessionId = generateSessionId();
749
+ const agentId = generateTestAgentId();
750
+ const result = {
751
+ compactedMessages: [
752
+ { role: 'system', content: 'summary' },
753
+ ],
754
+ summary: 'The summary',
755
+ originalTokens: 1000,
756
+ compactedTokens: 200,
757
+ messagesRemoved: 5,
758
+ };
759
+ const event = createContextCompactedEvent(sessionId, agentId, result);
760
+ expect(event.historyPath).toBeUndefined();
761
+ });
762
+ });
763
+ //# sourceMappingURL=context-compactor.test.js.map