@roj-ai/sdk 0.1.36 → 0.2.2

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 (925) hide show
  1. package/dist/bootstrap.d.ts +420 -26
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +55 -24
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/bun-platform/fs.d.ts.map +1 -1
  6. package/dist/bun-platform/fs.js +1 -0
  7. package/dist/bun-platform/fs.js.map +1 -1
  8. package/dist/bun-platform/index.d.ts +4 -1
  9. package/dist/bun-platform/index.d.ts.map +1 -1
  10. package/dist/bun-platform/index.js +7 -1
  11. package/dist/bun-platform/index.js.map +1 -1
  12. package/dist/bun-platform/server-lifecycle.d.ts +30 -0
  13. package/dist/bun-platform/server-lifecycle.d.ts.map +1 -0
  14. package/dist/bun-platform/server-lifecycle.js +182 -0
  15. package/dist/bun-platform/server-lifecycle.js.map +1 -0
  16. package/dist/bun-platform/shell.d.ts +33 -0
  17. package/dist/bun-platform/shell.d.ts.map +1 -0
  18. package/dist/bun-platform/shell.js +322 -0
  19. package/dist/bun-platform/shell.js.map +1 -0
  20. package/dist/config.d.ts +34 -0
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +97 -0
  23. package/dist/config.js.map +1 -1
  24. package/dist/core/agents/agent.d.ts +42 -7
  25. package/dist/core/agents/agent.d.ts.map +1 -1
  26. package/dist/core/agents/agent.js +202 -87
  27. package/dist/core/agents/agent.js.map +1 -1
  28. package/dist/core/agents/config.d.ts +8 -1
  29. package/dist/core/agents/config.d.ts.map +1 -1
  30. package/dist/core/agents/config.js +5 -1
  31. package/dist/core/agents/config.js.map +1 -1
  32. package/dist/core/agents/retry.d.ts +1 -1
  33. package/dist/core/agents/retry.d.ts.map +1 -1
  34. package/dist/core/agents/retry.js +14 -16
  35. package/dist/core/agents/retry.js.map +1 -1
  36. package/dist/core/agents/state.d.ts +3 -1
  37. package/dist/core/agents/state.d.ts.map +1 -1
  38. package/dist/core/agents/state.js +1 -1
  39. package/dist/core/agents/state.js.map +1 -1
  40. package/dist/core/context/state.d.ts +3 -3
  41. package/dist/core/context/state.js +2 -2
  42. package/dist/core/events/base-event-store.d.ts.map +1 -1
  43. package/dist/core/events/base-event-store.js +22 -14
  44. package/dist/core/events/base-event-store.js.map +1 -1
  45. package/dist/core/events/file.d.ts +39 -1
  46. package/dist/core/events/file.d.ts.map +1 -1
  47. package/dist/core/events/file.js +159 -20
  48. package/dist/core/events/file.js.map +1 -1
  49. package/dist/core/events/types.d.ts +1 -1
  50. package/dist/core/file-store/containment.d.ts +19 -0
  51. package/dist/core/file-store/containment.d.ts.map +1 -0
  52. package/dist/core/file-store/containment.js +62 -0
  53. package/dist/core/file-store/containment.js.map +1 -0
  54. package/dist/core/file-store/file-store.d.ts +22 -2
  55. package/dist/core/file-store/file-store.d.ts.map +1 -1
  56. package/dist/core/file-store/file-store.js +88 -20
  57. package/dist/core/file-store/file-store.js.map +1 -1
  58. package/dist/core/file-store/types.d.ts +13 -1
  59. package/dist/core/file-store/types.d.ts.map +1 -1
  60. package/dist/core/image/image-processor.d.ts.map +1 -1
  61. package/dist/core/image/image-processor.js +3 -1
  62. package/dist/core/image/image-processor.js.map +1 -1
  63. package/dist/core/image/types.d.ts +2 -0
  64. package/dist/core/image/types.d.ts.map +1 -1
  65. package/dist/core/image/vips-resizer.d.ts.map +1 -1
  66. package/dist/core/image/vips-resizer.js +81 -33
  67. package/dist/core/image/vips-resizer.js.map +1 -1
  68. package/dist/core/llm/anthropic.d.ts +0 -1
  69. package/dist/core/llm/anthropic.d.ts.map +1 -1
  70. package/dist/core/llm/anthropic.js +58 -72
  71. package/dist/core/llm/anthropic.js.map +1 -1
  72. package/dist/core/llm/logger.d.ts +14 -2
  73. package/dist/core/llm/logger.d.ts.map +1 -1
  74. package/dist/core/llm/logger.js +161 -36
  75. package/dist/core/llm/logger.js.map +1 -1
  76. package/dist/core/llm/openrouter.d.ts +0 -1
  77. package/dist/core/llm/openrouter.d.ts.map +1 -1
  78. package/dist/core/llm/openrouter.js +53 -66
  79. package/dist/core/llm/openrouter.js.map +1 -1
  80. package/dist/core/llm/provider-request.d.ts +10 -0
  81. package/dist/core/llm/provider-request.d.ts.map +1 -0
  82. package/dist/core/llm/provider-request.js +41 -0
  83. package/dist/core/llm/provider-request.js.map +1 -0
  84. package/dist/core/llm/provider.d.ts +11 -0
  85. package/dist/core/llm/provider.d.ts.map +1 -1
  86. package/dist/core/llm/provider.js +26 -0
  87. package/dist/core/llm/provider.js.map +1 -1
  88. package/dist/core/llm/state.d.ts +2 -2
  89. package/dist/core/llm/state.js +4 -4
  90. package/dist/core/plugins/plugin-builder.d.ts +57 -3
  91. package/dist/core/plugins/plugin-builder.d.ts.map +1 -1
  92. package/dist/core/plugins/plugin-builder.js +68 -14
  93. package/dist/core/plugins/plugin-builder.js.map +1 -1
  94. package/dist/core/preset/preset-builder.d.ts +3 -2
  95. package/dist/core/preset/preset-builder.d.ts.map +1 -1
  96. package/dist/core/preset/preset-builder.js +7 -16
  97. package/dist/core/preset/preset-builder.js.map +1 -1
  98. package/dist/core/sessions/context.d.ts +23 -5
  99. package/dist/core/sessions/context.d.ts.map +1 -1
  100. package/dist/core/sessions/runtime-activity.d.ts +33 -0
  101. package/dist/core/sessions/runtime-activity.d.ts.map +1 -0
  102. package/dist/core/sessions/runtime-activity.js +55 -0
  103. package/dist/core/sessions/runtime-activity.js.map +1 -0
  104. package/dist/core/sessions/schema.d.ts +23 -2
  105. package/dist/core/sessions/schema.d.ts.map +1 -1
  106. package/dist/core/sessions/schema.js +24 -3
  107. package/dist/core/sessions/schema.js.map +1 -1
  108. package/dist/core/sessions/session-manager.d.ts +83 -6
  109. package/dist/core/sessions/session-manager.d.ts.map +1 -1
  110. package/dist/core/sessions/session-manager.js +517 -118
  111. package/dist/core/sessions/session-manager.js.map +1 -1
  112. package/dist/core/sessions/session-store.d.ts +18 -0
  113. package/dist/core/sessions/session-store.d.ts.map +1 -1
  114. package/dist/core/sessions/session-store.js +33 -0
  115. package/dist/core/sessions/session-store.js.map +1 -1
  116. package/dist/core/sessions/session.d.ts +42 -6
  117. package/dist/core/sessions/session.d.ts.map +1 -1
  118. package/dist/core/sessions/session.js +176 -44
  119. package/dist/core/sessions/session.js.map +1 -1
  120. package/dist/core/sessions/state.d.ts +10 -1
  121. package/dist/core/sessions/state.d.ts.map +1 -1
  122. package/dist/core/sessions/state.js +11 -1
  123. package/dist/core/sessions/state.js.map +1 -1
  124. package/dist/core/system.d.ts +9 -0
  125. package/dist/core/system.d.ts.map +1 -1
  126. package/dist/core/system.js +4 -1
  127. package/dist/core/system.js.map +1 -1
  128. package/dist/core/tools/executor.d.ts +1 -1
  129. package/dist/core/wake-key.d.ts +34 -0
  130. package/dist/core/wake-key.d.ts.map +1 -0
  131. package/dist/core/wake-key.js +51 -0
  132. package/dist/core/wake-key.js.map +1 -0
  133. package/dist/index.d.ts +25 -13
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +23 -8
  136. package/dist/index.js.map +1 -1
  137. package/dist/lib/archive/archive-inspection.d.ts +48 -0
  138. package/dist/lib/archive/archive-inspection.d.ts.map +1 -0
  139. package/dist/lib/archive/archive-inspection.fixtures.d.ts +7 -0
  140. package/dist/lib/archive/archive-inspection.fixtures.d.ts.map +1 -0
  141. package/dist/lib/archive/archive-inspection.fixtures.js +134 -0
  142. package/dist/lib/archive/archive-inspection.fixtures.js.map +1 -0
  143. package/dist/lib/archive/archive-inspection.js +253 -0
  144. package/dist/lib/archive/archive-inspection.js.map +1 -0
  145. package/dist/lib/archive/index.d.ts +3 -0
  146. package/dist/lib/archive/index.d.ts.map +1 -0
  147. package/dist/lib/archive/index.js +2 -0
  148. package/dist/lib/archive/index.js.map +1 -0
  149. package/dist/lib/logger/console.d.ts +5 -12
  150. package/dist/lib/logger/console.d.ts.map +1 -1
  151. package/dist/lib/logger/console.js +5 -47
  152. package/dist/lib/logger/console.js.map +1 -1
  153. package/dist/lib/logger/file.d.ts +8 -11
  154. package/dist/lib/logger/file.d.ts.map +1 -1
  155. package/dist/lib/logger/file.js +26 -37
  156. package/dist/lib/logger/file.js.map +1 -1
  157. package/dist/lib/logger/index.d.ts +3 -1
  158. package/dist/lib/logger/index.d.ts.map +1 -1
  159. package/dist/lib/logger/index.js +3 -1
  160. package/dist/lib/logger/index.js.map +1 -1
  161. package/dist/lib/logger/jsonl.d.ts +21 -0
  162. package/dist/lib/logger/jsonl.d.ts.map +1 -0
  163. package/dist/lib/logger/jsonl.js +29 -0
  164. package/dist/lib/logger/jsonl.js.map +1 -0
  165. package/dist/lib/logger/logger.d.ts +22 -0
  166. package/dist/lib/logger/logger.d.ts.map +1 -1
  167. package/dist/lib/logger/logger.js +37 -0
  168. package/dist/lib/logger/logger.js.map +1 -1
  169. package/dist/lib/logger/session-log.d.ts +21 -0
  170. package/dist/lib/logger/session-log.d.ts.map +1 -0
  171. package/dist/lib/logger/session-log.js +37 -0
  172. package/dist/lib/logger/session-log.js.map +1 -0
  173. package/dist/lib/utils/concurrency.d.ts +1 -1
  174. package/dist/lib/utils/concurrency.d.ts.map +1 -1
  175. package/dist/lib/utils/concurrency.js +30 -7
  176. package/dist/lib/utils/concurrency.js.map +1 -1
  177. package/dist/lib/utils/fs-batch.d.ts +17 -0
  178. package/dist/lib/utils/fs-batch.d.ts.map +1 -0
  179. package/dist/lib/utils/fs-batch.js +38 -0
  180. package/dist/lib/utils/fs-batch.js.map +1 -0
  181. package/dist/lib/utils/result.d.ts +7 -22
  182. package/dist/lib/utils/result.d.ts.map +1 -1
  183. package/dist/lib/utils/result.js +6 -17
  184. package/dist/lib/utils/result.js.map +1 -1
  185. package/dist/lib/utils/sleep.d.ts +10 -0
  186. package/dist/lib/utils/sleep.d.ts.map +1 -0
  187. package/dist/lib/utils/sleep.js +26 -0
  188. package/dist/lib/utils/sleep.js.map +1 -0
  189. package/dist/platform/fs-revision.d.ts +21 -0
  190. package/dist/platform/fs-revision.d.ts.map +1 -0
  191. package/dist/platform/fs-revision.js +9 -0
  192. package/dist/platform/fs-revision.js.map +1 -0
  193. package/dist/platform/fs.d.ts +104 -1
  194. package/dist/platform/fs.d.ts.map +1 -1
  195. package/dist/platform/git.d.ts +59 -0
  196. package/dist/platform/git.d.ts.map +1 -0
  197. package/dist/platform/git.js +8 -0
  198. package/dist/platform/git.js.map +1 -0
  199. package/dist/platform/index.d.ts +40 -1
  200. package/dist/platform/index.d.ts.map +1 -1
  201. package/dist/platform/index.js +1 -1
  202. package/dist/platform/index.js.map +1 -1
  203. package/dist/platform/llm-call-log.d.ts +80 -0
  204. package/dist/platform/llm-call-log.d.ts.map +1 -0
  205. package/dist/platform/llm-call-log.js +12 -0
  206. package/dist/platform/llm-call-log.js.map +1 -0
  207. package/dist/platform/scheduler.d.ts +32 -0
  208. package/dist/platform/scheduler.d.ts.map +1 -0
  209. package/dist/platform/scheduler.js +46 -0
  210. package/dist/platform/scheduler.js.map +1 -0
  211. package/dist/platform/session-log.d.ts +36 -0
  212. package/dist/platform/session-log.d.ts.map +1 -0
  213. package/dist/platform/session-log.js +12 -0
  214. package/dist/platform/session-log.js.map +1 -0
  215. package/dist/platform/shell.d.ts +83 -0
  216. package/dist/platform/shell.d.ts.map +1 -0
  217. package/dist/platform/shell.js +13 -0
  218. package/dist/platform/shell.js.map +1 -0
  219. package/dist/plugins/agent-status/plugin.d.ts.map +1 -1
  220. package/dist/plugins/agent-status/plugin.js +1 -0
  221. package/dist/plugins/agent-status/plugin.js.map +1 -1
  222. package/dist/plugins/agents/plugin.d.ts.map +1 -1
  223. package/dist/plugins/agents/plugin.js +129 -62
  224. package/dist/plugins/agents/plugin.js.map +1 -1
  225. package/dist/plugins/filesystem/listing.d.ts +3 -0
  226. package/dist/plugins/filesystem/listing.d.ts.map +1 -1
  227. package/dist/plugins/filesystem/listing.js +66 -10
  228. package/dist/plugins/filesystem/listing.js.map +1 -1
  229. package/dist/plugins/filesystem/plugin.d.ts.map +1 -1
  230. package/dist/plugins/filesystem/plugin.js +8 -6
  231. package/dist/plugins/filesystem/plugin.js.map +1 -1
  232. package/dist/plugins/git-status/index.d.ts +1 -0
  233. package/dist/plugins/git-status/index.d.ts.map +1 -1
  234. package/dist/plugins/git-status/plugin.d.ts +38 -7
  235. package/dist/plugins/git-status/plugin.d.ts.map +1 -1
  236. package/dist/plugins/git-status/plugin.js +169 -64
  237. package/dist/plugins/git-status/plugin.js.map +1 -1
  238. package/dist/plugins/llm-debug/plugin.d.ts.map +1 -1
  239. package/dist/plugins/llm-debug/plugin.js +1 -0
  240. package/dist/plugins/llm-debug/plugin.js.map +1 -1
  241. package/dist/plugins/logs/plugin.d.ts +1 -4
  242. package/dist/plugins/logs/plugin.d.ts.map +1 -1
  243. package/dist/plugins/logs/plugin.js +7 -0
  244. package/dist/plugins/logs/plugin.js.map +1 -1
  245. package/dist/plugins/mailbox/helpers.d.ts +2 -2
  246. package/dist/plugins/mailbox/helpers.js +1 -1
  247. package/dist/plugins/mailbox/plugin.d.ts.map +1 -1
  248. package/dist/plugins/mailbox/plugin.js +38 -43
  249. package/dist/plugins/mailbox/plugin.js.map +1 -1
  250. package/dist/plugins/mailbox/query.d.ts +7 -2
  251. package/dist/plugins/mailbox/query.d.ts.map +1 -1
  252. package/dist/plugins/mailbox/query.js +14 -7
  253. package/dist/plugins/mailbox/query.js.map +1 -1
  254. package/dist/plugins/mailbox/state.d.ts +5 -4
  255. package/dist/plugins/mailbox/state.d.ts.map +1 -1
  256. package/dist/plugins/mailbox/state.js +6 -5
  257. package/dist/plugins/mailbox/state.js.map +1 -1
  258. package/dist/plugins/resources/filename.d.ts +4 -0
  259. package/dist/plugins/resources/filename.d.ts.map +1 -0
  260. package/dist/plugins/resources/filename.js +18 -0
  261. package/dist/plugins/resources/filename.js.map +1 -0
  262. package/dist/plugins/resources/plugin.d.ts +3 -0
  263. package/dist/plugins/resources/plugin.d.ts.map +1 -1
  264. package/dist/plugins/resources/plugin.js +97 -60
  265. package/dist/plugins/resources/plugin.js.map +1 -1
  266. package/dist/plugins/resources/state.d.ts +1 -1
  267. package/dist/plugins/resources/state.js +1 -1
  268. package/dist/plugins/result-eviction/plugin.d.ts.map +1 -1
  269. package/dist/plugins/result-eviction/plugin.js +6 -1
  270. package/dist/plugins/result-eviction/plugin.js.map +1 -1
  271. package/dist/plugins/services/plugin.d.ts +4 -1
  272. package/dist/plugins/services/plugin.d.ts.map +1 -1
  273. package/dist/plugins/services/plugin.js +251 -100
  274. package/dist/plugins/services/plugin.js.map +1 -1
  275. package/dist/plugins/services/prompt.d.ts.map +1 -1
  276. package/dist/plugins/services/prompt.js +1 -4
  277. package/dist/plugins/services/prompt.js.map +1 -1
  278. package/dist/plugins/services/schema.d.ts +15 -5
  279. package/dist/plugins/services/schema.d.ts.map +1 -1
  280. package/dist/plugins/services/service.d.ts +100 -18
  281. package/dist/plugins/services/service.d.ts.map +1 -1
  282. package/dist/plugins/services/service.js +680 -230
  283. package/dist/plugins/services/service.js.map +1 -1
  284. package/dist/plugins/session-lifecycle/plugin.d.ts +21 -1
  285. package/dist/plugins/session-lifecycle/plugin.d.ts.map +1 -1
  286. package/dist/plugins/session-lifecycle/plugin.js +53 -40
  287. package/dist/plugins/session-lifecycle/plugin.js.map +1 -1
  288. package/dist/plugins/session-state/plugin.d.ts +21 -13
  289. package/dist/plugins/session-state/plugin.d.ts.map +1 -1
  290. package/dist/plugins/session-state/plugin.js +214 -79
  291. package/dist/plugins/session-state/plugin.js.map +1 -1
  292. package/dist/plugins/session-stats/plugin.d.ts.map +1 -1
  293. package/dist/plugins/session-stats/plugin.js +1 -0
  294. package/dist/plugins/session-stats/plugin.js.map +1 -1
  295. package/dist/plugins/shell/executor.d.ts +17 -15
  296. package/dist/plugins/shell/executor.d.ts.map +1 -1
  297. package/dist/plugins/shell/executor.js +130 -195
  298. package/dist/plugins/shell/executor.js.map +1 -1
  299. package/dist/plugins/shell/index.d.ts +2 -2
  300. package/dist/plugins/shell/index.d.ts.map +1 -1
  301. package/dist/plugins/shell/index.js +2 -2
  302. package/dist/plugins/shell/index.js.map +1 -1
  303. package/dist/plugins/shell/plugin.d.ts +5 -4
  304. package/dist/plugins/shell/plugin.d.ts.map +1 -1
  305. package/dist/plugins/shell/plugin.js +4 -8
  306. package/dist/plugins/shell/plugin.js.map +1 -1
  307. package/dist/plugins/skills/discovery.d.ts.map +1 -1
  308. package/dist/plugins/skills/discovery.js +10 -8
  309. package/dist/plugins/skills/discovery.js.map +1 -1
  310. package/dist/plugins/skills/index.d.ts +1 -1
  311. package/dist/plugins/skills/index.d.ts.map +1 -1
  312. package/dist/plugins/skills/plugin.d.ts +1 -1
  313. package/dist/plugins/skills/plugin.d.ts.map +1 -1
  314. package/dist/plugins/skills/plugin.js +23 -30
  315. package/dist/plugins/skills/plugin.js.map +1 -1
  316. package/dist/plugins/skills/prompts.d.ts +3 -3
  317. package/dist/plugins/skills/prompts.d.ts.map +1 -1
  318. package/dist/plugins/skills/prompts.js.map +1 -1
  319. package/dist/plugins/skills/schema.d.ts +4 -2
  320. package/dist/plugins/skills/schema.d.ts.map +1 -1
  321. package/dist/plugins/todo/schema.d.ts +1 -1
  322. package/dist/plugins/uploads/plugin.d.ts +11 -4
  323. package/dist/plugins/uploads/plugin.d.ts.map +1 -1
  324. package/dist/plugins/uploads/plugin.js +891 -225
  325. package/dist/plugins/uploads/plugin.js.map +1 -1
  326. package/dist/plugins/uploads/preprocessor.d.ts +14 -0
  327. package/dist/plugins/uploads/preprocessor.d.ts.map +1 -1
  328. package/dist/plugins/uploads/preprocessor.js +11 -0
  329. package/dist/plugins/uploads/preprocessor.js.map +1 -1
  330. package/dist/plugins/uploads/preprocessors/image-classifier.d.ts +1 -1
  331. package/dist/plugins/uploads/preprocessors/image-classifier.d.ts.map +1 -1
  332. package/dist/plugins/uploads/preprocessors/image-classifier.js +51 -26
  333. package/dist/plugins/uploads/preprocessors/image-classifier.js.map +1 -1
  334. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts +2 -2
  335. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts.map +1 -1
  336. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js +41 -16
  337. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js.map +1 -1
  338. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts +1 -1
  339. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts.map +1 -1
  340. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js +29 -14
  341. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js.map +1 -1
  342. package/dist/plugins/uploads/preprocessors/zip-preprocessor.d.ts +1 -1
  343. package/dist/plugins/uploads/preprocessors/zip-preprocessor.d.ts.map +1 -1
  344. package/dist/plugins/uploads/preprocessors/zip-preprocessor.js +62 -34
  345. package/dist/plugins/uploads/preprocessors/zip-preprocessor.js.map +1 -1
  346. package/dist/plugins/uploads/schema.d.ts +25 -2
  347. package/dist/plugins/uploads/schema.d.ts.map +1 -1
  348. package/dist/plugins/uploads/schema.js +24 -3
  349. package/dist/plugins/uploads/schema.js.map +1 -1
  350. package/dist/plugins/uploads/state.d.ts +13 -1
  351. package/dist/plugins/uploads/state.d.ts.map +1 -1
  352. package/dist/plugins/uploads/state.js +4 -1
  353. package/dist/plugins/uploads/state.js.map +1 -1
  354. package/dist/plugins/user-chat/plugin.d.ts +12 -5
  355. package/dist/plugins/user-chat/plugin.d.ts.map +1 -1
  356. package/dist/plugins/user-chat/plugin.js +141 -32
  357. package/dist/plugins/user-chat/plugin.js.map +1 -1
  358. package/dist/plugins/workers/context.d.ts +35 -13
  359. package/dist/plugins/workers/context.d.ts.map +1 -1
  360. package/dist/plugins/workers/context.js +82 -32
  361. package/dist/plugins/workers/context.js.map +1 -1
  362. package/dist/plugins/workers/definition.d.ts +7 -0
  363. package/dist/plugins/workers/definition.d.ts.map +1 -1
  364. package/dist/plugins/workers/definition.js.map +1 -1
  365. package/dist/plugins/workers/index.d.ts +4 -4
  366. package/dist/plugins/workers/index.d.ts.map +1 -1
  367. package/dist/plugins/workers/index.js +2 -2
  368. package/dist/plugins/workers/index.js.map +1 -1
  369. package/dist/plugins/workers/plugin.d.ts +10 -57
  370. package/dist/plugins/workers/plugin.d.ts.map +1 -1
  371. package/dist/plugins/workers/plugin.js +385 -123
  372. package/dist/plugins/workers/plugin.js.map +1 -1
  373. package/dist/plugins/workers/state.d.ts +69 -0
  374. package/dist/plugins/workers/state.d.ts.map +1 -0
  375. package/dist/plugins/workers/state.js +52 -0
  376. package/dist/plugins/workers/state.js.map +1 -0
  377. package/dist/plugins/workers/worker.d.ts +15 -4
  378. package/dist/plugins/workers/worker.d.ts.map +1 -1
  379. package/dist/testing/conformance.d.ts +106 -0
  380. package/dist/testing/conformance.d.ts.map +1 -0
  381. package/dist/testing/conformance.js +1463 -0
  382. package/dist/testing/conformance.js.map +1 -0
  383. package/dist/testing/node-platform.d.ts.map +1 -1
  384. package/dist/testing/node-platform.js +5 -0
  385. package/dist/testing/node-platform.js.map +1 -1
  386. package/dist/testing/test-harness.d.ts +11 -1
  387. package/dist/testing/test-harness.d.ts.map +1 -1
  388. package/dist/testing/test-harness.js +17 -37
  389. package/dist/testing/test-harness.js.map +1 -1
  390. package/dist/testing/wait-helpers.d.ts.map +1 -1
  391. package/dist/testing/wait-helpers.js +1 -3
  392. package/dist/testing/wait-helpers.js.map +1 -1
  393. package/dist/transport/adapter/client-adapter.d.ts +6 -0
  394. package/dist/transport/adapter/client-adapter.d.ts.map +1 -1
  395. package/dist/transport/adapter/client-adapter.js +50 -3
  396. package/dist/transport/adapter/client-adapter.js.map +1 -1
  397. package/dist/transport/adapter/heartbeat.d.ts +16 -0
  398. package/dist/transport/adapter/heartbeat.d.ts.map +1 -0
  399. package/dist/transport/adapter/heartbeat.js +41 -0
  400. package/dist/transport/adapter/heartbeat.js.map +1 -0
  401. package/dist/transport/adapter/server-adapter.d.ts.map +1 -1
  402. package/dist/transport/adapter/server-adapter.js +6 -0
  403. package/dist/transport/adapter/server-adapter.js.map +1 -1
  404. package/dist/transport/http/app.d.ts +3 -30
  405. package/dist/transport/http/app.d.ts.map +1 -1
  406. package/dist/transport/http/app.js +3 -7
  407. package/dist/transport/http/app.js.map +1 -1
  408. package/dist/transport/http/context.d.ts +40 -0
  409. package/dist/transport/http/context.d.ts.map +1 -0
  410. package/dist/transport/http/context.js +17 -0
  411. package/dist/transport/http/context.js.map +1 -0
  412. package/dist/transport/http/fetch-guard.d.ts +49 -0
  413. package/dist/transport/http/fetch-guard.d.ts.map +1 -0
  414. package/dist/transport/http/fetch-guard.js +312 -0
  415. package/dist/transport/http/fetch-guard.js.map +1 -0
  416. package/dist/transport/http/index.d.ts +1 -1
  417. package/dist/transport/http/index.d.ts.map +1 -1
  418. package/dist/transport/http/middleware/bearer-auth.d.ts +1 -1
  419. package/dist/transport/http/middleware/bearer-auth.d.ts.map +1 -1
  420. package/dist/transport/http/middleware/error-handler.d.ts +2 -2
  421. package/dist/transport/http/middleware/error-handler.d.ts.map +1 -1
  422. package/dist/transport/http/path-containment.d.ts +20 -0
  423. package/dist/transport/http/path-containment.d.ts.map +1 -0
  424. package/dist/transport/http/path-containment.js +31 -0
  425. package/dist/transport/http/path-containment.js.map +1 -0
  426. package/dist/transport/http/responses.d.ts +30 -0
  427. package/dist/transport/http/responses.d.ts.map +1 -0
  428. package/dist/transport/http/responses.js +5 -0
  429. package/dist/transport/http/responses.js.map +1 -0
  430. package/dist/transport/http/routes/files.d.ts +1 -1
  431. package/dist/transport/http/routes/files.d.ts.map +1 -1
  432. package/dist/transport/http/routes/files.js +54 -155
  433. package/dist/transport/http/routes/files.js.map +1 -1
  434. package/dist/transport/http/routes/resources.d.ts +1 -1
  435. package/dist/transport/http/routes/resources.d.ts.map +1 -1
  436. package/dist/transport/http/routes/resources.js +92 -57
  437. package/dist/transport/http/routes/resources.js.map +1 -1
  438. package/dist/transport/http/routes/rpc.d.ts +6 -1
  439. package/dist/transport/http/routes/rpc.d.ts.map +1 -1
  440. package/dist/transport/http/routes/rpc.js +51 -21
  441. package/dist/transport/http/routes/rpc.js.map +1 -1
  442. package/dist/transport/http/routes/upload.d.ts +1 -1
  443. package/dist/transport/http/routes/upload.d.ts.map +1 -1
  444. package/dist/transport/http/routes/upload.js +256 -169
  445. package/dist/transport/http/routes/upload.js.map +1 -1
  446. package/dist/user-config.d.ts +34 -7
  447. package/dist/user-config.d.ts.map +1 -1
  448. package/dist/user-config.js +71 -0
  449. package/dist/user-config.js.map +1 -1
  450. package/package.json +17 -7
  451. package/src/bootstrap.ts +119 -35
  452. package/src/bun-platform/fs.ts +1 -0
  453. package/src/bun-platform/index.ts +18 -1
  454. package/src/bun-platform/server-lifecycle.ts +218 -0
  455. package/src/bun-platform/shell.ts +362 -0
  456. package/src/config.ts +130 -0
  457. package/src/core/agents/agent.ts +210 -92
  458. package/src/core/agents/config.ts +8 -0
  459. package/src/core/agents/retry.ts +16 -17
  460. package/src/core/agents/state.ts +2 -0
  461. package/src/core/events/base-event-store.ts +24 -16
  462. package/src/core/events/file.ts +198 -22
  463. package/src/core/file-store/containment.ts +75 -0
  464. package/src/core/file-store/file-store.ts +106 -25
  465. package/src/core/file-store/types.ts +14 -1
  466. package/src/core/image/image-processor.ts +3 -1
  467. package/src/core/image/types.ts +2 -0
  468. package/src/core/image/vips-resizer.ts +102 -37
  469. package/src/core/llm/anthropic.ts +63 -79
  470. package/src/core/llm/logger.ts +175 -37
  471. package/src/core/llm/openrouter.ts +57 -72
  472. package/src/core/llm/provider-request.ts +40 -0
  473. package/src/core/llm/provider.ts +28 -0
  474. package/src/core/plugins/plugin-builder.ts +106 -14
  475. package/src/core/preset/preset-builder.ts +12 -22
  476. package/src/core/sessions/context.ts +18 -0
  477. package/src/core/sessions/runtime-activity.ts +78 -0
  478. package/src/core/sessions/schema.ts +29 -3
  479. package/src/core/sessions/session-manager.ts +560 -140
  480. package/src/core/sessions/session-store.ts +38 -0
  481. package/src/core/sessions/session.ts +187 -47
  482. package/src/core/sessions/state.ts +11 -0
  483. package/src/core/system.ts +15 -0
  484. package/src/core/wake-key.ts +65 -0
  485. package/src/index.ts +32 -12
  486. package/src/lib/archive/archive-inspection.fixtures.ts +138 -0
  487. package/src/lib/archive/archive-inspection.ts +359 -0
  488. package/src/lib/archive/index.ts +17 -0
  489. package/src/lib/logger/console.ts +7 -57
  490. package/src/lib/logger/file.ts +28 -50
  491. package/src/lib/logger/index.ts +3 -1
  492. package/src/lib/logger/jsonl.ts +36 -0
  493. package/src/lib/logger/logger.ts +48 -0
  494. package/src/lib/logger/session-log.ts +42 -0
  495. package/src/lib/utils/concurrency.ts +37 -8
  496. package/src/lib/utils/fs-batch.ts +49 -0
  497. package/src/lib/utils/result.ts +7 -31
  498. package/src/lib/utils/sleep.ts +23 -0
  499. package/src/platform/fs-revision.ts +21 -0
  500. package/src/platform/fs.ts +109 -1
  501. package/src/platform/git.ts +67 -0
  502. package/src/platform/index.ts +51 -1
  503. package/src/platform/llm-call-log.ts +86 -0
  504. package/src/platform/scheduler.ts +71 -0
  505. package/src/platform/session-log.ts +39 -0
  506. package/src/platform/shell.ts +89 -0
  507. package/src/plugins/agent-status/plugin.ts +1 -0
  508. package/src/plugins/agents/plugin.ts +134 -81
  509. package/src/plugins/filesystem/listing.ts +70 -12
  510. package/src/plugins/filesystem/plugin.ts +8 -6
  511. package/src/plugins/git-status/index.ts +1 -0
  512. package/src/plugins/git-status/plugin.ts +211 -77
  513. package/src/plugins/llm-debug/plugin.ts +1 -0
  514. package/src/plugins/logs/plugin.ts +9 -0
  515. package/src/plugins/mailbox/plugin.ts +46 -64
  516. package/src/plugins/mailbox/query.ts +15 -7
  517. package/src/plugins/mailbox/state.ts +1 -0
  518. package/src/plugins/resources/filename.ts +19 -0
  519. package/src/plugins/resources/plugin.ts +111 -59
  520. package/src/plugins/result-eviction/plugin.ts +6 -1
  521. package/src/plugins/services/plugin.ts +254 -98
  522. package/src/plugins/services/prompt.ts +1 -6
  523. package/src/plugins/services/schema.ts +17 -3
  524. package/src/plugins/services/service.ts +702 -252
  525. package/src/plugins/session-lifecycle/plugin.ts +56 -41
  526. package/src/plugins/session-state/plugin.ts +242 -88
  527. package/src/plugins/session-stats/plugin.ts +1 -0
  528. package/src/plugins/shell/executor.ts +144 -226
  529. package/src/plugins/shell/index.ts +1 -4
  530. package/src/plugins/shell/plugin.ts +7 -9
  531. package/src/plugins/skills/discovery.ts +10 -8
  532. package/src/plugins/skills/index.ts +1 -1
  533. package/src/plugins/skills/plugin.ts +19 -27
  534. package/src/plugins/skills/prompts.ts +3 -3
  535. package/src/plugins/skills/schema.ts +5 -2
  536. package/src/plugins/uploads/plugin.ts +991 -273
  537. package/src/plugins/uploads/preprocessor.ts +25 -0
  538. package/src/plugins/uploads/preprocessors/image-classifier.ts +56 -25
  539. package/src/plugins/uploads/preprocessors/markitdown-preprocessor.ts +41 -15
  540. package/src/plugins/uploads/preprocessors/pdf-preprocessor.ts +32 -16
  541. package/src/plugins/uploads/preprocessors/zip-preprocessor.ts +67 -38
  542. package/src/plugins/uploads/schema.ts +30 -3
  543. package/src/plugins/uploads/state.ts +12 -0
  544. package/src/plugins/user-chat/plugin.ts +196 -40
  545. package/src/plugins/workers/context.ts +102 -39
  546. package/src/plugins/workers/definition.ts +7 -0
  547. package/src/plugins/workers/index.ts +4 -4
  548. package/src/plugins/workers/plugin.ts +461 -151
  549. package/src/plugins/workers/state.ts +64 -0
  550. package/src/plugins/workers/worker.ts +15 -4
  551. package/src/testing/conformance.ts +1751 -0
  552. package/src/testing/node-platform.ts +5 -0
  553. package/src/testing/test-harness.ts +21 -38
  554. package/src/testing/wait-helpers.ts +1 -4
  555. package/src/transport/adapter/client-adapter.ts +60 -3
  556. package/src/transport/adapter/heartbeat.ts +50 -0
  557. package/src/transport/adapter/server-adapter.ts +6 -0
  558. package/src/transport/http/app.ts +7 -35
  559. package/src/transport/http/context.ts +46 -0
  560. package/src/transport/http/fetch-guard.ts +318 -0
  561. package/src/transport/http/index.ts +1 -1
  562. package/src/transport/http/middleware/bearer-auth.ts +1 -1
  563. package/src/transport/http/middleware/error-handler.ts +1 -1
  564. package/src/transport/http/path-containment.ts +44 -0
  565. package/src/transport/http/responses.ts +20 -0
  566. package/src/transport/http/routes/files.ts +69 -159
  567. package/src/transport/http/routes/resources.ts +124 -93
  568. package/src/transport/http/routes/rpc.ts +63 -26
  569. package/src/transport/http/routes/upload.ts +327 -258
  570. package/src/user-config.ts +93 -7
  571. package/dist/config.test.d.ts +0 -2
  572. package/dist/config.test.d.ts.map +0 -1
  573. package/dist/config.test.js +0 -155
  574. package/dist/config.test.js.map +0 -1
  575. package/dist/core/agent-loop.integration.test.d.ts +0 -2
  576. package/dist/core/agent-loop.integration.test.d.ts.map +0 -1
  577. package/dist/core/agent-loop.integration.test.js +0 -414
  578. package/dist/core/agent-loop.integration.test.js.map +0 -1
  579. package/dist/core/agents/agent-config.test.d.ts +0 -2
  580. package/dist/core/agents/agent-config.test.d.ts.map +0 -1
  581. package/dist/core/agents/agent-config.test.js +0 -194
  582. package/dist/core/agents/agent-config.test.js.map +0 -1
  583. package/dist/core/agents/agent-shutdown.test.d.ts +0 -6
  584. package/dist/core/agents/agent-shutdown.test.d.ts.map +0 -1
  585. package/dist/core/agents/agent-shutdown.test.js +0 -180
  586. package/dist/core/agents/agent-shutdown.test.js.map +0 -1
  587. package/dist/core/agents/agent.test.d.ts +0 -2
  588. package/dist/core/agents/agent.test.d.ts.map +0 -1
  589. package/dist/core/agents/agent.test.js +0 -313
  590. package/dist/core/agents/agent.test.js.map +0 -1
  591. package/dist/core/agents/communicator.d.ts +0 -3
  592. package/dist/core/agents/communicator.d.ts.map +0 -1
  593. package/dist/core/agents/communicator.js +0 -13
  594. package/dist/core/agents/communicator.js.map +0 -1
  595. package/dist/core/agents/handler-events.test.d.ts +0 -5
  596. package/dist/core/agents/handler-events.test.d.ts.map +0 -1
  597. package/dist/core/agents/handler-events.test.js +0 -115
  598. package/dist/core/agents/handler-events.test.js.map +0 -1
  599. package/dist/core/agents/response-sanitizer.test.d.ts +0 -2
  600. package/dist/core/agents/response-sanitizer.test.d.ts.map +0 -1
  601. package/dist/core/agents/response-sanitizer.test.js +0 -101
  602. package/dist/core/agents/response-sanitizer.test.js.map +0 -1
  603. package/dist/core/error-recovery.integration.test.d.ts +0 -2
  604. package/dist/core/error-recovery.integration.test.d.ts.map +0 -1
  605. package/dist/core/error-recovery.integration.test.js +0 -288
  606. package/dist/core/error-recovery.integration.test.js.map +0 -1
  607. package/dist/core/event-sourcing.integration.test.d.ts +0 -2
  608. package/dist/core/event-sourcing.integration.test.d.ts.map +0 -1
  609. package/dist/core/event-sourcing.integration.test.js +0 -154
  610. package/dist/core/event-sourcing.integration.test.js.map +0 -1
  611. package/dist/core/events/file.test.d.ts +0 -2
  612. package/dist/core/events/file.test.d.ts.map +0 -1
  613. package/dist/core/events/file.test.js +0 -284
  614. package/dist/core/events/file.test.js.map +0 -1
  615. package/dist/core/events/memory.test.d.ts +0 -2
  616. package/dist/core/events/memory.test.d.ts.map +0 -1
  617. package/dist/core/events/memory.test.js +0 -502
  618. package/dist/core/events/memory.test.js.map +0 -1
  619. package/dist/core/image/image-processor.test.d.ts +0 -2
  620. package/dist/core/image/image-processor.test.d.ts.map +0 -1
  621. package/dist/core/image/image-processor.test.js +0 -171
  622. package/dist/core/image/image-processor.test.js.map +0 -1
  623. package/dist/core/image/vips-resizer.test.d.ts +0 -2
  624. package/dist/core/image/vips-resizer.test.d.ts.map +0 -1
  625. package/dist/core/image/vips-resizer.test.js +0 -336
  626. package/dist/core/image/vips-resizer.test.js.map +0 -1
  627. package/dist/core/llm/anthropic.test.d.ts +0 -2
  628. package/dist/core/llm/anthropic.test.d.ts.map +0 -1
  629. package/dist/core/llm/anthropic.test.js +0 -464
  630. package/dist/core/llm/anthropic.test.js.map +0 -1
  631. package/dist/core/llm/cache-breakpoints.test.d.ts +0 -2
  632. package/dist/core/llm/cache-breakpoints.test.d.ts.map +0 -1
  633. package/dist/core/llm/cache-breakpoints.test.js +0 -69
  634. package/dist/core/llm/cache-breakpoints.test.js.map +0 -1
  635. package/dist/core/llm/cache-live.test.d.ts +0 -16
  636. package/dist/core/llm/cache-live.test.d.ts.map +0 -1
  637. package/dist/core/llm/cache-live.test.js +0 -137
  638. package/dist/core/llm/cache-live.test.js.map +0 -1
  639. package/dist/core/llm/logger.test.d.ts +0 -7
  640. package/dist/core/llm/logger.test.d.ts.map +0 -1
  641. package/dist/core/llm/logger.test.js +0 -228
  642. package/dist/core/llm/logger.test.js.map +0 -1
  643. package/dist/core/llm/message-sanitization.test.d.ts +0 -2
  644. package/dist/core/llm/message-sanitization.test.d.ts.map +0 -1
  645. package/dist/core/llm/message-sanitization.test.js +0 -181
  646. package/dist/core/llm/message-sanitization.test.js.map +0 -1
  647. package/dist/core/llm/mock.test.d.ts +0 -2
  648. package/dist/core/llm/mock.test.d.ts.map +0 -1
  649. package/dist/core/llm/mock.test.js +0 -318
  650. package/dist/core/llm/mock.test.js.map +0 -1
  651. package/dist/core/llm/model-shortlist-live.test.d.ts +0 -35
  652. package/dist/core/llm/model-shortlist-live.test.d.ts.map +0 -1
  653. package/dist/core/llm/model-shortlist-live.test.js +0 -452
  654. package/dist/core/llm/model-shortlist-live.test.js.map +0 -1
  655. package/dist/core/llm/openrouter-mapping.test.d.ts +0 -2
  656. package/dist/core/llm/openrouter-mapping.test.d.ts.map +0 -1
  657. package/dist/core/llm/openrouter-mapping.test.js +0 -190
  658. package/dist/core/llm/openrouter-mapping.test.js.map +0 -1
  659. package/dist/core/llm/openrouter.test.d.ts +0 -2
  660. package/dist/core/llm/openrouter.test.d.ts.map +0 -1
  661. package/dist/core/llm/openrouter.test.js +0 -377
  662. package/dist/core/llm/openrouter.test.js.map +0 -1
  663. package/dist/core/llm/provider-integration.test.d.ts +0 -12
  664. package/dist/core/llm/provider-integration.test.d.ts.map +0 -1
  665. package/dist/core/llm/provider-integration.test.js +0 -350
  666. package/dist/core/llm/provider-integration.test.js.map +0 -1
  667. package/dist/core/llm/reasoning-details.test.d.ts +0 -2
  668. package/dist/core/llm/reasoning-details.test.d.ts.map +0 -1
  669. package/dist/core/llm/reasoning-details.test.js +0 -144
  670. package/dist/core/llm/reasoning-details.test.js.map +0 -1
  671. package/dist/core/llm/routing-provider.test.d.ts +0 -2
  672. package/dist/core/llm/routing-provider.test.d.ts.map +0 -1
  673. package/dist/core/llm/routing-provider.test.js +0 -94
  674. package/dist/core/llm/routing-provider.test.js.map +0 -1
  675. package/dist/core/llm/snapshot-middleware.test.d.ts +0 -2
  676. package/dist/core/llm/snapshot-middleware.test.d.ts.map +0 -1
  677. package/dist/core/llm/snapshot-middleware.test.js +0 -144
  678. package/dist/core/llm/snapshot-middleware.test.js.map +0 -1
  679. package/dist/core/llm/thinking-blocks.test.d.ts +0 -2
  680. package/dist/core/llm/thinking-blocks.test.d.ts.map +0 -1
  681. package/dist/core/llm/thinking-blocks.test.js +0 -291
  682. package/dist/core/llm/thinking-blocks.test.js.map +0 -1
  683. package/dist/core/multi-agent.integration.test.d.ts +0 -2
  684. package/dist/core/multi-agent.integration.test.d.ts.map +0 -1
  685. package/dist/core/multi-agent.integration.test.js +0 -298
  686. package/dist/core/multi-agent.integration.test.js.map +0 -1
  687. package/dist/core/plugin-hooks.integration.test.d.ts +0 -2
  688. package/dist/core/plugin-hooks.integration.test.d.ts.map +0 -1
  689. package/dist/core/plugin-hooks.integration.test.js +0 -458
  690. package/dist/core/plugin-hooks.integration.test.js.map +0 -1
  691. package/dist/core/reasoning-round-trip.integration.test.d.ts +0 -2
  692. package/dist/core/reasoning-round-trip.integration.test.d.ts.map +0 -1
  693. package/dist/core/reasoning-round-trip.integration.test.js +0 -150
  694. package/dist/core/reasoning-round-trip.integration.test.js.map +0 -1
  695. package/dist/core/session-lifecycle.integration.test.d.ts +0 -2
  696. package/dist/core/session-lifecycle.integration.test.d.ts.map +0 -1
  697. package/dist/core/session-lifecycle.integration.test.js +0 -159
  698. package/dist/core/session-lifecycle.integration.test.js.map +0 -1
  699. package/dist/core/sessions/fork-utils.test.d.ts +0 -2
  700. package/dist/core/sessions/fork-utils.test.d.ts.map +0 -1
  701. package/dist/core/sessions/fork-utils.test.js +0 -129
  702. package/dist/core/sessions/fork-utils.test.js.map +0 -1
  703. package/dist/core/sessions/session-overrides.integration.test.d.ts +0 -2
  704. package/dist/core/sessions/session-overrides.integration.test.d.ts.map +0 -1
  705. package/dist/core/sessions/session-overrides.integration.test.js +0 -154
  706. package/dist/core/sessions/session-overrides.integration.test.js.map +0 -1
  707. package/dist/core/sessions/session.test.d.ts +0 -2
  708. package/dist/core/sessions/session.test.d.ts.map +0 -1
  709. package/dist/core/sessions/session.test.js +0 -1100
  710. package/dist/core/sessions/session.test.js.map +0 -1
  711. package/dist/core/tools/executor.test.d.ts +0 -2
  712. package/dist/core/tools/executor.test.d.ts.map +0 -1
  713. package/dist/core/tools/executor.test.js +0 -144
  714. package/dist/core/tools/executor.test.js.map +0 -1
  715. package/dist/lib/logger/console.test.d.ts +0 -2
  716. package/dist/lib/logger/console.test.d.ts.map +0 -1
  717. package/dist/lib/logger/console.test.js +0 -258
  718. package/dist/lib/logger/console.test.js.map +0 -1
  719. package/dist/lib/never.d.ts +0 -2
  720. package/dist/lib/never.d.ts.map +0 -1
  721. package/dist/lib/never.js +0 -4
  722. package/dist/lib/never.js.map +0 -1
  723. package/dist/lib/utils/concurrency.test.d.ts +0 -2
  724. package/dist/lib/utils/concurrency.test.d.ts.map +0 -1
  725. package/dist/lib/utils/concurrency.test.js +0 -135
  726. package/dist/lib/utils/concurrency.test.js.map +0 -1
  727. package/dist/plugins/agent-status/agent-status.test.d.ts +0 -2
  728. package/dist/plugins/agent-status/agent-status.test.d.ts.map +0 -1
  729. package/dist/plugins/agent-status/agent-status.test.js +0 -136
  730. package/dist/plugins/agent-status/agent-status.test.js.map +0 -1
  731. package/dist/plugins/agents/agents.integration.test.d.ts +0 -2
  732. package/dist/plugins/agents/agents.integration.test.d.ts.map +0 -1
  733. package/dist/plugins/agents/agents.integration.test.js +0 -830
  734. package/dist/plugins/agents/agents.integration.test.js.map +0 -1
  735. package/dist/plugins/agents/supervision.integration.test.d.ts +0 -2
  736. package/dist/plugins/agents/supervision.integration.test.d.ts.map +0 -1
  737. package/dist/plugins/agents/supervision.integration.test.js +0 -215
  738. package/dist/plugins/agents/supervision.integration.test.js.map +0 -1
  739. package/dist/plugins/context-compact/compaction-live.test.d.ts +0 -17
  740. package/dist/plugins/context-compact/compaction-live.test.d.ts.map +0 -1
  741. package/dist/plugins/context-compact/compaction-live.test.js +0 -177
  742. package/dist/plugins/context-compact/compaction-live.test.js.map +0 -1
  743. package/dist/plugins/context-compact/context-compact.integration.test.d.ts +0 -2
  744. package/dist/plugins/context-compact/context-compact.integration.test.d.ts.map +0 -1
  745. package/dist/plugins/context-compact/context-compact.integration.test.js +0 -364
  746. package/dist/plugins/context-compact/context-compact.integration.test.js.map +0 -1
  747. package/dist/plugins/context-compact/context-compactor.test.d.ts +0 -2
  748. package/dist/plugins/context-compact/context-compactor.test.d.ts.map +0 -1
  749. package/dist/plugins/context-compact/context-compactor.test.js +0 -858
  750. package/dist/plugins/context-compact/context-compactor.test.js.map +0 -1
  751. package/dist/plugins/context-compact/history-offloader.test.d.ts +0 -2
  752. package/dist/plugins/context-compact/history-offloader.test.d.ts.map +0 -1
  753. package/dist/plugins/context-compact/history-offloader.test.js +0 -77
  754. package/dist/plugins/context-compact/history-offloader.test.js.map +0 -1
  755. package/dist/plugins/filesystem/filesystem.integration.test.d.ts +0 -2
  756. package/dist/plugins/filesystem/filesystem.integration.test.d.ts.map +0 -1
  757. package/dist/plugins/filesystem/filesystem.integration.test.js +0 -447
  758. package/dist/plugins/filesystem/filesystem.integration.test.js.map +0 -1
  759. package/dist/plugins/filesystem/schema.d.ts +0 -7
  760. package/dist/plugins/filesystem/schema.d.ts.map +0 -1
  761. package/dist/plugins/filesystem/schema.js +0 -2
  762. package/dist/plugins/filesystem/schema.js.map +0 -1
  763. package/dist/plugins/limits-guard/limit-guard.test.d.ts +0 -2
  764. package/dist/plugins/limits-guard/limit-guard.test.d.ts.map +0 -1
  765. package/dist/plugins/limits-guard/limit-guard.test.js +0 -194
  766. package/dist/plugins/limits-guard/limit-guard.test.js.map +0 -1
  767. package/dist/plugins/limits-guard/limits-guard.integration.test.d.ts +0 -2
  768. package/dist/plugins/limits-guard/limits-guard.integration.test.d.ts.map +0 -1
  769. package/dist/plugins/limits-guard/limits-guard.integration.test.js +0 -887
  770. package/dist/plugins/limits-guard/limits-guard.integration.test.js.map +0 -1
  771. package/dist/plugins/llm-debug/llm-debug.integration.test.d.ts +0 -2
  772. package/dist/plugins/llm-debug/llm-debug.integration.test.d.ts.map +0 -1
  773. package/dist/plugins/llm-debug/llm-debug.integration.test.js +0 -157
  774. package/dist/plugins/llm-debug/llm-debug.integration.test.js.map +0 -1
  775. package/dist/plugins/mailbox/mailbox.integration.test.d.ts +0 -2
  776. package/dist/plugins/mailbox/mailbox.integration.test.d.ts.map +0 -1
  777. package/dist/plugins/mailbox/mailbox.integration.test.js +0 -678
  778. package/dist/plugins/mailbox/mailbox.integration.test.js.map +0 -1
  779. package/dist/plugins/resources/resources.integration.test.d.ts +0 -2
  780. package/dist/plugins/resources/resources.integration.test.d.ts.map +0 -1
  781. package/dist/plugins/resources/resources.integration.test.js +0 -62
  782. package/dist/plugins/resources/resources.integration.test.js.map +0 -1
  783. package/dist/plugins/result-eviction/result-eviction.integration.test.d.ts +0 -2
  784. package/dist/plugins/result-eviction/result-eviction.integration.test.d.ts.map +0 -1
  785. package/dist/plugins/result-eviction/result-eviction.integration.test.js +0 -294
  786. package/dist/plugins/result-eviction/result-eviction.integration.test.js.map +0 -1
  787. package/dist/plugins/services/pid-registry.test.d.ts +0 -2
  788. package/dist/plugins/services/pid-registry.test.d.ts.map +0 -1
  789. package/dist/plugins/services/pid-registry.test.js +0 -161
  790. package/dist/plugins/services/pid-registry.test.js.map +0 -1
  791. package/dist/plugins/services/services.integration.test.d.ts +0 -2
  792. package/dist/plugins/services/services.integration.test.d.ts.map +0 -1
  793. package/dist/plugins/services/services.integration.test.js +0 -1057
  794. package/dist/plugins/services/services.integration.test.js.map +0 -1
  795. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.d.ts +0 -2
  796. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.d.ts.map +0 -1
  797. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js +0 -498
  798. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js.map +0 -1
  799. package/dist/plugins/session-stats/plugin.test.d.ts +0 -2
  800. package/dist/plugins/session-stats/plugin.test.d.ts.map +0 -1
  801. package/dist/plugins/session-stats/plugin.test.js +0 -145
  802. package/dist/plugins/session-stats/plugin.test.js.map +0 -1
  803. package/dist/plugins/shell/shell.integration.test.d.ts +0 -2
  804. package/dist/plugins/shell/shell.integration.test.d.ts.map +0 -1
  805. package/dist/plugins/shell/shell.integration.test.js +0 -234
  806. package/dist/plugins/shell/shell.integration.test.js.map +0 -1
  807. package/dist/plugins/shell/shell.test.d.ts +0 -2
  808. package/dist/plugins/shell/shell.test.d.ts.map +0 -1
  809. package/dist/plugins/shell/shell.test.js +0 -236
  810. package/dist/plugins/shell/shell.test.js.map +0 -1
  811. package/dist/plugins/skills/discovery.test.d.ts +0 -2
  812. package/dist/plugins/skills/discovery.test.d.ts.map +0 -1
  813. package/dist/plugins/skills/discovery.test.js +0 -312
  814. package/dist/plugins/skills/discovery.test.js.map +0 -1
  815. package/dist/plugins/skills/skills.integration.test.d.ts +0 -2
  816. package/dist/plugins/skills/skills.integration.test.d.ts.map +0 -1
  817. package/dist/plugins/skills/skills.integration.test.js +0 -475
  818. package/dist/plugins/skills/skills.integration.test.js.map +0 -1
  819. package/dist/plugins/todo/todo.integration.test.d.ts +0 -2
  820. package/dist/plugins/todo/todo.integration.test.d.ts.map +0 -1
  821. package/dist/plugins/todo/todo.integration.test.js +0 -605
  822. package/dist/plugins/todo/todo.integration.test.js.map +0 -1
  823. package/dist/plugins/uploads/preprocessors/image-classifier.test.d.ts +0 -2
  824. package/dist/plugins/uploads/preprocessors/image-classifier.test.d.ts.map +0 -1
  825. package/dist/plugins/uploads/preprocessors/image-classifier.test.js +0 -113
  826. package/dist/plugins/uploads/preprocessors/image-classifier.test.js.map +0 -1
  827. package/dist/plugins/uploads/uploads.integration.test.d.ts +0 -2
  828. package/dist/plugins/uploads/uploads.integration.test.d.ts.map +0 -1
  829. package/dist/plugins/uploads/uploads.integration.test.js +0 -639
  830. package/dist/plugins/uploads/uploads.integration.test.js.map +0 -1
  831. package/dist/plugins/user-chat/user-chat.integration.test.d.ts +0 -2
  832. package/dist/plugins/user-chat/user-chat.integration.test.d.ts.map +0 -1
  833. package/dist/plugins/user-chat/user-chat.integration.test.js +0 -754
  834. package/dist/plugins/user-chat/user-chat.integration.test.js.map +0 -1
  835. package/dist/plugins/workers/workers.integration.test.d.ts +0 -2
  836. package/dist/plugins/workers/workers.integration.test.d.ts.map +0 -1
  837. package/dist/plugins/workers/workers.integration.test.js +0 -629
  838. package/dist/plugins/workers/workers.integration.test.js.map +0 -1
  839. package/dist/prompts/macros.test.d.ts +0 -2
  840. package/dist/prompts/macros.test.d.ts.map +0 -1
  841. package/dist/prompts/macros.test.js +0 -80
  842. package/dist/prompts/macros.test.js.map +0 -1
  843. package/dist/testing/test-harness.test.d.ts +0 -2
  844. package/dist/testing/test-harness.test.d.ts.map +0 -1
  845. package/dist/testing/test-harness.test.js +0 -92
  846. package/dist/testing/test-harness.test.js.map +0 -1
  847. package/dist/transport/http/routes/rpc.integration.test.d.ts +0 -2
  848. package/dist/transport/http/routes/rpc.integration.test.d.ts.map +0 -1
  849. package/dist/transport/http/routes/rpc.integration.test.js +0 -189
  850. package/dist/transport/http/routes/rpc.integration.test.js.map +0 -1
  851. package/dist/transport/http/routes/rpc.test.d.ts +0 -2
  852. package/dist/transport/http/routes/rpc.test.d.ts.map +0 -1
  853. package/dist/transport/http/routes/rpc.test.js +0 -316
  854. package/dist/transport/http/routes/rpc.test.js.map +0 -1
  855. package/src/config.test.ts +0 -174
  856. package/src/core/agent-loop.integration.test.ts +0 -503
  857. package/src/core/agents/agent-config.test.ts +0 -240
  858. package/src/core/agents/agent-shutdown.test.ts +0 -236
  859. package/src/core/agents/agent.test.ts +0 -387
  860. package/src/core/agents/communicator.ts +0 -16
  861. package/src/core/agents/handler-events.test.ts +0 -167
  862. package/src/core/agents/response-sanitizer.test.ts +0 -137
  863. package/src/core/error-recovery.integration.test.ts +0 -343
  864. package/src/core/event-sourcing.integration.test.ts +0 -191
  865. package/src/core/events/file.test.ts +0 -436
  866. package/src/core/events/memory.test.ts +0 -741
  867. package/src/core/image/image-processor.test.ts +0 -218
  868. package/src/core/image/vips-resizer.test.ts +0 -388
  869. package/src/core/llm/anthropic.test.ts +0 -546
  870. package/src/core/llm/cache-breakpoints.test.ts +0 -85
  871. package/src/core/llm/cache-live.test.ts +0 -155
  872. package/src/core/llm/logger.test.ts +0 -270
  873. package/src/core/llm/message-sanitization.test.ts +0 -217
  874. package/src/core/llm/mock.test.ts +0 -402
  875. package/src/core/llm/model-shortlist-live.test.ts +0 -577
  876. package/src/core/llm/openrouter-mapping.test.ts +0 -234
  877. package/src/core/llm/openrouter.test.ts +0 -499
  878. package/src/core/llm/provider-integration.test.ts +0 -408
  879. package/src/core/llm/reasoning-details.test.ts +0 -177
  880. package/src/core/llm/routing-provider.test.ts +0 -113
  881. package/src/core/llm/snapshot-middleware.test.ts +0 -168
  882. package/src/core/llm/thinking-blocks.test.ts +0 -345
  883. package/src/core/multi-agent.integration.test.ts +0 -340
  884. package/src/core/plugin-hooks.integration.test.ts +0 -568
  885. package/src/core/reasoning-round-trip.integration.test.ts +0 -188
  886. package/src/core/session-lifecycle.integration.test.ts +0 -202
  887. package/src/core/sessions/fork-utils.test.ts +0 -158
  888. package/src/core/sessions/session-overrides.integration.test.ts +0 -210
  889. package/src/core/sessions/session.test.ts +0 -1736
  890. package/src/core/tools/executor.test.ts +0 -161
  891. package/src/lib/logger/console.test.ts +0 -348
  892. package/src/lib/never.ts +0 -3
  893. package/src/lib/utils/concurrency.test.ts +0 -169
  894. package/src/plugins/agent-status/agent-status.test.ts +0 -164
  895. package/src/plugins/agents/agents.integration.test.ts +0 -976
  896. package/src/plugins/agents/supervision.integration.test.ts +0 -249
  897. package/src/plugins/context-compact/compaction-live.test.ts +0 -221
  898. package/src/plugins/context-compact/context-compact.integration.test.ts +0 -418
  899. package/src/plugins/context-compact/context-compactor.test.ts +0 -1036
  900. package/src/plugins/context-compact/history-offloader.test.ts +0 -100
  901. package/src/plugins/filesystem/filesystem.integration.test.ts +0 -529
  902. package/src/plugins/filesystem/schema.ts +0 -6
  903. package/src/plugins/limits-guard/limit-guard.test.ts +0 -253
  904. package/src/plugins/limits-guard/limits-guard.integration.test.ts +0 -1012
  905. package/src/plugins/llm-debug/llm-debug.integration.test.ts +0 -192
  906. package/src/plugins/mailbox/mailbox.integration.test.ts +0 -794
  907. package/src/plugins/resources/resources.integration.test.ts +0 -68
  908. package/src/plugins/result-eviction/result-eviction.integration.test.ts +0 -332
  909. package/src/plugins/services/pid-registry.test.ts +0 -180
  910. package/src/plugins/services/services.integration.test.ts +0 -1223
  911. package/src/plugins/session-lifecycle/session-lifecycle.integration.test.ts +0 -639
  912. package/src/plugins/session-stats/plugin.test.ts +0 -160
  913. package/src/plugins/shell/shell.integration.test.ts +0 -273
  914. package/src/plugins/shell/shell.test.ts +0 -314
  915. package/src/plugins/skills/discovery.test.ts +0 -395
  916. package/src/plugins/skills/skills.integration.test.ts +0 -580
  917. package/src/plugins/todo/todo.integration.test.ts +0 -687
  918. package/src/plugins/uploads/preprocessors/image-classifier.test.ts +0 -142
  919. package/src/plugins/uploads/uploads.integration.test.ts +0 -831
  920. package/src/plugins/user-chat/user-chat.integration.test.ts +0 -893
  921. package/src/plugins/workers/workers.integration.test.ts +0 -778
  922. package/src/prompts/macros.test.ts +0 -107
  923. package/src/testing/test-harness.test.ts +0 -114
  924. package/src/transport/http/routes/rpc.integration.test.ts +0 -243
  925. package/src/transport/http/routes/rpc.test.ts +0 -372
@@ -1,18 +1,19 @@
1
1
  import z from 'zod/v4';
2
- import { ValidationErrors } from '../../core/errors.js';
2
+ import { createDomainError, ValidationErrors } from '../../core/errors.js';
3
3
  import { definePlugin } from '../../core/plugins/plugin-builder.js';
4
4
  import { getEntryAgentId } from '../../core/sessions/state.js';
5
+ import { ArchiveBudget } from '../../lib/archive/index.js';
5
6
  import { Err, Ok } from '../../lib/utils/result.js';
6
- import { generateUploadId, UploadId } from './schema.js';
7
+ import { generateUploadId, parseUploadId, UploadId, uploadIdSchema } from './schema.js';
7
8
  import { uploadEvents } from './state.js';
8
9
  // ============================================================================
9
10
  // Notification schemas
10
11
  // ============================================================================
12
+ // No producer sets extractedContent: it can be megabytes and clients read it from the state projection.
11
13
  const statusChangedSchema = z.object({
12
14
  sessionId: z.string(),
13
15
  uploadId: z.string(),
14
16
  status: z.enum(['processing', 'ready', 'failed']),
15
- extractedContent: z.string().optional(),
16
17
  error: z.string().optional(),
17
18
  });
18
19
  // ============================================================================
@@ -36,16 +37,17 @@ const ALLOWED_MIME_TYPES = [
36
37
  'application/vnd.oasis.opendocument.text',
37
38
  ];
38
39
  const PROCESSING_TIMEOUT_MS = 120_000; // 120 seconds
40
+ const PROCESSING_ABORT_GRACE_MS = 1_000;
41
+ const CLOSE_DRAIN_TIMEOUT_MS = 30_000;
42
+ const TERMINAL_APPEND_ATTEMPTS = 3;
43
+ const TERMINAL_APPEND_RETRY_DELAY_MS = 25;
44
+ /** Store faults are internal: the client gets this fixed text, never the driver's own message. */
45
+ const uploadStorageError = () => createDomainError('upload_storage_error', 'Failed to write file', 500);
39
46
  // ============================================================================
40
47
  // Helpers
41
48
  // ============================================================================
42
49
  function isAllowedMimeType(mimeType) {
43
- return ALLOWED_MIME_TYPES.some((allowed) => allowed.endsWith('/')
44
- ? mimeType.startsWith(allowed)
45
- : mimeType === allowed);
46
- }
47
- function sleep(ms) {
48
- return new Promise((resolve) => setTimeout(resolve, ms));
50
+ return ALLOWED_MIME_TYPES.some((allowed) => (allowed.endsWith('/') ? mimeType.startsWith(allowed) : mimeType === allowed));
49
51
  }
50
52
  function formatUploadsForLLM(uploads, sessionRoot) {
51
53
  const blocks = uploads.map((u) => {
@@ -55,65 +57,498 @@ function formatUploadsForLLM(uploads, sessionRoot) {
55
57
  });
56
58
  return blocks.join('\n');
57
59
  }
60
+ function withoutKeys(values, uploadIds) {
61
+ if (uploadIds.every((uploadId) => values[uploadId] === undefined))
62
+ return values;
63
+ const next = { ...values };
64
+ for (const uploadId of uploadIds)
65
+ delete next[uploadId];
66
+ return next;
67
+ }
68
+ /**
69
+ * True when the value names one entry inside the upload dir and cannot walk anywhere.
70
+ *
71
+ * Deliberately duplicates the download route's boundary check: this is where the bytes
72
+ * are written, and prepareUpload is reachable from callers that never touch HTTP.
73
+ */
74
+ function isBasename(value) {
75
+ if (value === '' || value === '.' || value === '..')
76
+ return false;
77
+ return !/[/\\\0]/.test(value);
78
+ }
58
79
  /**
59
- * Run preprocessor (with timeout) and persist final upload metadata to disk.
60
- * Returns the resolved status + extracted/derived data for the caller to emit.
80
+ * The single spelling of a filename this plugin stores and reports.
81
+ *
82
+ * Unicode lets the same name be two different byte strings: macOS hands over the decomposed form
83
+ * (`á` as `a` + U+0301), while a model asked to repeat that name writes the composed one. The two
84
+ * do not compare equal, so every `cp`, `read_file` and shell redirect the agent builds from the
85
+ * name it was given misses the file that is actually on disk. Composing on the way in makes the
86
+ * name the agent produces the name that is there.
61
87
  */
62
- async function runPreprocessAndPersist(args) {
88
+ function toStoredFilename(filename) {
89
+ return filename.normalize('NFC');
90
+ }
91
+ function validateUploadInput(input) {
92
+ if (!isBasename(input.filename))
93
+ return 'Invalid filename';
94
+ if (input.size > MAX_FILE_SIZE)
95
+ return `File too large: max ${MAX_FILE_SIZE / (1024 * 1024)}MB`;
96
+ if (!isAllowedMimeType(input.mimeType))
97
+ return `Unsupported file type: ${input.mimeType}`;
98
+ return undefined;
99
+ }
100
+ function reserveUploadLifecycle(pluginContext, uploadId) {
101
+ const controller = new AbortController();
102
+ if (pluginContext.closing)
103
+ controller.abort(new Error('Session is closing'));
104
+ let resolveProcessingCompletion = () => { };
105
+ const processingCompletion = new Promise((resolve) => {
106
+ resolveProcessingCompletion = resolve;
107
+ });
108
+ let resolveCompletion = () => { };
109
+ const completion = new Promise((resolve) => {
110
+ resolveCompletion = resolve;
111
+ });
112
+ let processingSettled = false;
113
+ let terminalHolds = 0;
114
+ let settled = false;
115
+ let releaseRuntimeLease;
116
+ let operation;
117
+ const settle = () => {
118
+ if (!processingSettled || terminalHolds !== 0 || settled)
119
+ return;
120
+ settled = true;
121
+ if (pluginContext.activeUploads.get(uploadId) === operation)
122
+ pluginContext.activeUploads.delete(uploadId);
123
+ releaseRuntimeLease?.();
124
+ releaseRuntimeLease = undefined;
125
+ resolveCompletion();
126
+ };
127
+ const finishProcessing = () => {
128
+ if (processingSettled)
129
+ return;
130
+ processingSettled = true;
131
+ resolveProcessingCompletion();
132
+ settle();
133
+ };
134
+ operation = {
135
+ controller,
136
+ processingCompletion,
137
+ completion,
138
+ holdTerminalMutation() {
139
+ if (settled)
140
+ return () => { };
141
+ terminalHolds++;
142
+ let released = false;
143
+ return () => {
144
+ if (released)
145
+ return;
146
+ released = true;
147
+ terminalHolds--;
148
+ settle();
149
+ };
150
+ },
151
+ };
152
+ pluginContext.activeUploads.set(uploadId, operation);
153
+ return {
154
+ controller,
155
+ start(run) {
156
+ let result;
157
+ try {
158
+ result = run(controller);
159
+ }
160
+ catch (error) {
161
+ finishProcessing();
162
+ return Promise.reject(error);
163
+ }
164
+ result.then(finishProcessing, finishProcessing);
165
+ return result;
166
+ },
167
+ attachRuntimeLease(release) {
168
+ if (settled) {
169
+ release();
170
+ return;
171
+ }
172
+ if (releaseRuntimeLease) {
173
+ release();
174
+ throw new Error(`Upload ${uploadId} already has a runtime lease`);
175
+ }
176
+ releaseRuntimeLease = release;
177
+ },
178
+ abandon: finishProcessing,
179
+ };
180
+ }
181
+ function runTrackedMutation(args) {
182
+ if (args.pluginContext.closing)
183
+ return Promise.reject(new Error('Session is closing'));
184
+ const releaseRuntimeLease = args.runtimeActivity.acquire(args.reason);
185
+ let resolveCompletion = () => { };
186
+ const completion = new Promise((resolve) => {
187
+ resolveCompletion = resolve;
188
+ });
189
+ args.pluginContext.activeMutations.set(completion, args.reason);
190
+ return (async () => {
191
+ try {
192
+ return await args.run();
193
+ }
194
+ finally {
195
+ args.pluginContext.activeMutations.delete(completion);
196
+ releaseRuntimeLease();
197
+ resolveCompletion();
198
+ }
199
+ })();
200
+ }
201
+ async function prepareUpload(args) {
202
+ const filename = toStoredFilename(args.input.filename);
203
+ const validationError = validateUploadInput({ ...args.input, filename });
204
+ if (validationError)
205
+ return Err(ValidationErrors.invalid(validationError));
206
+ const uploadId = generateUploadId();
207
+ const uploadIdStr = String(uploadId);
208
+ const lifecycle = reserveUploadLifecycle(args.pluginContext, uploadIdStr);
209
+ let prepared;
210
+ try {
211
+ if (lifecycle.controller.signal.aborted)
212
+ return Err(ValidationErrors.invalid('Session is closing'));
213
+ const uploadStore = args.dataFileStore.scoped(`sessions/${args.input.sessionId}/uploads/${uploadId}`);
214
+ const writeResult = await uploadStore.write(filename, args.input.fileBuffer);
215
+ if (!writeResult.ok) {
216
+ args.logger.error('Could not write upload file', undefined, { uploadId: uploadIdStr, filename, error: writeResult.error });
217
+ return Err(uploadStorageError());
218
+ }
219
+ if (args.pluginContext.closing) {
220
+ await uploadStore.remove(filename);
221
+ return Err(ValidationErrors.invalid('Session is closing'));
222
+ }
223
+ prepared = {
224
+ uploadId,
225
+ uploadIdStr,
226
+ uploadStore,
227
+ filename,
228
+ filePath: writeResult.value.path,
229
+ createdAt: Date.now(),
230
+ lifecycle,
231
+ };
232
+ return Ok(prepared);
233
+ }
234
+ catch (error) {
235
+ // An embedder-supplied store throws its own driver text (bucket, endpoint, request id) — log it, never echo it.
236
+ args.logger.error('Could not write upload file', error instanceof Error ? error : undefined, { uploadId: uploadIdStr, filename });
237
+ return Err(uploadStorageError());
238
+ }
239
+ finally {
240
+ // The lifecycle is registered before any fs work, so every failure path must settle it —
241
+ // an orphan never resolves its completion and onSessionClose waits on that.
242
+ if (!prepared)
243
+ lifecycle.abandon();
244
+ }
245
+ }
246
+ async function runPreprocessor(args) {
63
247
  const preprocessor = args.preprocessorRegistry?.getForMimeType(args.mimeType);
64
- let status = 'ready';
65
- let extractedContent;
66
- let derivedPaths;
67
- let errorMessage;
68
- if (preprocessor) {
69
- const processPromise = preprocessor.process(args.filePath, args.mimeType, {
70
- files: args.uploadStore,
248
+ if (!preprocessor)
249
+ return { status: 'ready' };
250
+ let timedOut = false;
251
+ const processPromise = (async () => {
252
+ try {
253
+ return {
254
+ kind: 'completed',
255
+ result: await preprocessor.process(args.filePath, args.mimeType, {
256
+ files: args.uploadStore,
257
+ signal: args.controller.signal,
258
+ inferenceContext: args.inferenceContext,
259
+ archiveBudget: new ArchiveBudget(args.archiveLimits),
260
+ }),
261
+ };
262
+ }
263
+ catch (error) {
264
+ return { kind: 'threw', error };
265
+ }
266
+ })();
267
+ let resolveAbort;
268
+ const aborted = new Promise((resolve) => {
269
+ if (args.controller.signal.aborted) {
270
+ resolve({ kind: 'aborted' });
271
+ return;
272
+ }
273
+ resolveAbort = () => resolve({ kind: 'aborted' });
274
+ args.controller.signal.addEventListener('abort', resolveAbort, {
275
+ once: true,
71
276
  });
72
- const timeoutPromise = sleep(PROCESSING_TIMEOUT_MS).then(() => ({
73
- ok: false,
74
- error: new Error('Processing timeout'),
75
- }));
76
- const result = await Promise.race([processPromise, timeoutPromise]);
77
- if (result.ok) {
78
- extractedContent = result.value.extractedContent;
79
- derivedPaths = result.value.derivedPaths;
277
+ });
278
+ const timeoutId = setTimeout(() => {
279
+ timedOut = true;
280
+ args.controller.abort(new Error('Processing timeout'));
281
+ }, args.processingTimeoutMs ?? PROCESSING_TIMEOUT_MS);
282
+ let outcome;
283
+ let graceTimeoutId;
284
+ try {
285
+ const first = await Promise.race([processPromise, aborted]);
286
+ if (first.kind === 'aborted') {
287
+ const grace = new Promise((resolve) => {
288
+ graceTimeoutId = setTimeout(() => resolve({ kind: 'grace-expired' }), args.processingAbortGraceMs ?? PROCESSING_ABORT_GRACE_MS);
289
+ });
290
+ const afterAbort = await Promise.race([processPromise, grace]);
291
+ if (afterAbort.kind !== 'grace-expired')
292
+ outcome = afterAbort;
80
293
  }
81
294
  else {
82
- status = 'failed';
83
- errorMessage = result.error.message;
295
+ outcome = first;
84
296
  }
85
297
  }
86
- const metadata = {
87
- uploadId: UploadId(args.uploadId),
298
+ finally {
299
+ clearTimeout(timeoutId);
300
+ if (graceTimeoutId)
301
+ clearTimeout(graceTimeoutId);
302
+ if (resolveAbort)
303
+ args.controller.signal.removeEventListener('abort', resolveAbort);
304
+ }
305
+ if (args.controller.signal.aborted) {
306
+ return {
307
+ status: 'failed',
308
+ error: timedOut ? 'Processing timeout' : 'Processing cancelled',
309
+ };
310
+ }
311
+ if (outcome?.kind === 'threw') {
312
+ return {
313
+ status: 'failed',
314
+ error: outcome.error instanceof Error ? outcome.error.message : String(outcome.error),
315
+ };
316
+ }
317
+ if (!outcome)
318
+ return { status: 'failed', error: 'Processing failed' };
319
+ if (!outcome.result.ok)
320
+ return { status: 'failed', error: outcome.result.error.message };
321
+ return {
322
+ status: 'ready',
323
+ extractedContent: outcome.result.value.extractedContent,
324
+ derivedPaths: outcome.result.value.derivedPaths,
325
+ };
326
+ }
327
+ async function writeMetadata(uploadStore, metadata) {
328
+ const result = await uploadStore.write('meta.json', JSON.stringify(metadata, null, 2));
329
+ if (!result.ok)
330
+ throw new Error(result.error);
331
+ }
332
+ function createFinalMetadata(args) {
333
+ return {
334
+ uploadId: args.prepared.uploadId,
88
335
  sessionId: args.sessionId,
89
- filename: args.filename,
90
- mimeType: args.mimeType,
91
- size: args.size,
92
- path: args.filePath,
93
- status,
94
- extractedContent,
95
- derivedPaths,
96
- createdAt: args.createdAt,
336
+ filename: args.upload.filename,
337
+ mimeType: args.upload.mimeType,
338
+ size: args.upload.size,
339
+ path: args.prepared.filePath,
340
+ status: args.result.status,
341
+ extractedContent: args.result.extractedContent,
342
+ derivedPaths: args.result.derivedPaths,
343
+ error: args.result.error,
344
+ createdAt: args.prepared.createdAt,
97
345
  completedAt: Date.now(),
98
346
  };
99
- await args.uploadStore.write('meta.json', JSON.stringify(metadata, null, 2));
100
- return { status, extractedContent, derivedPaths, error: errorMessage };
347
+ }
348
+ function createUploadResultEvent(upload, uploadId, result) {
349
+ return uploadEvents.create('attachment_uploaded', {
350
+ uploadId,
351
+ filename: upload.filename,
352
+ mimeType: upload.mimeType,
353
+ size: upload.size,
354
+ status: result.status,
355
+ extractedContent: result.extractedContent,
356
+ derivedPaths: result.derivedPaths,
357
+ error: result.error,
358
+ });
359
+ }
360
+ async function withUploadLock(pluginContext, uploadId, run, options) {
361
+ if (pluginContext.closing && !options?.allowDuringClose)
362
+ throw new Error('Session is closing');
363
+ const previous = pluginContext.operationQueues.get(uploadId) ?? Promise.resolve();
364
+ let release = () => { };
365
+ const current = new Promise((resolve) => {
366
+ release = resolve;
367
+ });
368
+ pluginContext.operationQueues.set(uploadId, current);
369
+ await previous;
370
+ try {
371
+ if (pluginContext.closing && !options?.allowDuringClose)
372
+ throw new Error('Session is closing');
373
+ return await run();
374
+ }
375
+ finally {
376
+ release();
377
+ if (pluginContext.operationQueues.get(uploadId) === current)
378
+ pluginContext.operationQueues.delete(uploadId);
379
+ }
380
+ }
381
+ /** Resolves true when everything settled, false when the bound expired first. */
382
+ async function settleWithTimeout(pending, timeoutMs) {
383
+ if (pending.length === 0)
384
+ return true;
385
+ let timeoutId;
386
+ const expired = new Promise((resolve) => {
387
+ timeoutId = setTimeout(() => resolve(false), timeoutMs);
388
+ });
389
+ try {
390
+ return await Promise.race([Promise.allSettled(pending).then(() => true), expired]);
391
+ }
392
+ finally {
393
+ if (timeoutId !== undefined)
394
+ clearTimeout(timeoutId);
395
+ }
396
+ }
397
+ /**
398
+ * Appends the terminal event, retrying a transient store failure.
399
+ *
400
+ * Losing the append means the projection never gains the attachment, so it is never
401
+ * auto-dequeued to the agent while the caller was already told the upload is ready.
402
+ * Metadata makes it recoverable, but only when the runtime is next built — with idle
403
+ * eviction off (the default) that is a process restart, so retrying here is the fix.
404
+ */
405
+ async function appendTerminalEvent(args) {
406
+ for (let attempt = 1; attempt <= TERMINAL_APPEND_ATTEMPTS; attempt++) {
407
+ try {
408
+ await args.append();
409
+ return true;
410
+ }
411
+ catch (error) {
412
+ const giveUp = attempt === TERMINAL_APPEND_ATTEMPTS || args.pluginContext.closing;
413
+ args.logger.error(giveUp ? 'Could not persist upload terminal event' : 'Retrying upload terminal event append', error instanceof Error ? error : undefined, { uploadId: args.uploadId, filename: args.filename, attempt });
414
+ if (giveUp)
415
+ return false;
416
+ await new Promise((resolve) => setTimeout(resolve, TERMINAL_APPEND_RETRY_DELAY_MS * attempt));
417
+ }
418
+ }
419
+ return false;
420
+ }
421
+ async function processUpload(args) {
422
+ const result = await runPreprocessor({
423
+ uploadStore: args.prepared.uploadStore,
424
+ filePath: args.prepared.filePath,
425
+ mimeType: args.upload.mimeType,
426
+ preprocessorRegistry: args.config.preprocessorRegistry,
427
+ processingTimeoutMs: args.config.processingTimeoutMs,
428
+ processingAbortGraceMs: args.config.processingAbortGraceMs,
429
+ archiveLimits: args.config.archiveLimits,
430
+ controller: args.prepared.lifecycle.controller,
431
+ inferenceContext: args.inferenceContext,
432
+ });
433
+ await withUploadLock(args.pluginContext, args.prepared.uploadIdStr, async () => {
434
+ if (args.pluginContext.deletedUploads.has(args.prepared.uploadIdStr))
435
+ return;
436
+ const metadata = createFinalMetadata({
437
+ prepared: args.prepared,
438
+ sessionId: args.sessionId,
439
+ upload: args.upload,
440
+ result,
441
+ });
442
+ await writeMetadata(args.prepared.uploadStore, metadata);
443
+ if (args.pluginContext.closing)
444
+ return;
445
+ const appended = await appendTerminalEvent({
446
+ pluginContext: args.pluginContext,
447
+ append: () => args.onTerminal(result),
448
+ logger: args.logger,
449
+ uploadId: args.prepared.uploadIdStr,
450
+ filename: args.upload.filename,
451
+ });
452
+ if (appended) {
453
+ metadata.terminalEventPersisted = true;
454
+ try {
455
+ await writeMetadata(args.prepared.uploadStore, metadata);
456
+ }
457
+ catch (error) {
458
+ // Only the flag write failed; the event is in the log, so onSessionReady reconciles the metadata.
459
+ args.logger.error('Could not record the upload terminal event flag', error instanceof Error ? error : undefined, { uploadId: args.prepared.uploadIdStr, filename: args.upload.filename });
460
+ }
461
+ }
462
+ // The client has no polling fallback, so it is told either way — even when the event never landed.
463
+ await args.onPersisted(result);
464
+ }, { allowDuringClose: true });
465
+ return result;
466
+ }
467
+ function startAsyncUpload(args) {
468
+ // start() rejects synchronously when `run` throws before its first await — that rejection needs a handler.
469
+ void args.prepared.lifecycle.start(async () => {
470
+ try {
471
+ await processUpload({
472
+ pluginContext: args.pluginContext,
473
+ prepared: args.prepared,
474
+ sessionId: args.sessionId,
475
+ upload: args.upload,
476
+ config: args.config,
477
+ logger: args.logger,
478
+ inferenceContext: args.inferenceContext,
479
+ onTerminal: async (result) => {
480
+ await args.emitEvent(createUploadResultEvent(args.upload, args.prepared.uploadId, result));
481
+ },
482
+ onPersisted: async (result) => {
483
+ if (result.status === 'ready' && args.entryAgentId)
484
+ args.scheduleAgent(args.entryAgentId);
485
+ args.notify('uploadStatusChanged', {
486
+ sessionId: String(args.sessionId),
487
+ uploadId: args.prepared.uploadIdStr,
488
+ status: result.status,
489
+ error: result.error,
490
+ });
491
+ },
492
+ });
493
+ }
494
+ catch (error) {
495
+ args.logger.error('Async upload processing crashed', error instanceof Error ? error : undefined, {
496
+ uploadId: args.prepared.uploadIdStr,
497
+ filename: args.upload.filename,
498
+ });
499
+ }
500
+ }).catch((error) => {
501
+ args.logger.error('Async upload never started', error instanceof Error ? error : undefined, {
502
+ uploadId: args.prepared.uploadIdStr,
503
+ filename: args.upload.filename,
504
+ });
505
+ });
506
+ }
507
+ async function removeUploadFiles(uploadStore, path = '') {
508
+ const listResult = await uploadStore.list(path, { maxDepth: 1 });
509
+ if (!listResult.ok)
510
+ return listResult;
511
+ for (const entry of listResult.value) {
512
+ const entryPath = path ? `${path}/${entry.name}` : entry.name;
513
+ if (entry.type === 'directory') {
514
+ const nested = await removeUploadFiles(uploadStore, entryPath);
515
+ if (!nested.ok)
516
+ return nested;
517
+ continue;
518
+ }
519
+ if (entryPath === 'meta.json')
520
+ continue;
521
+ if (entry.type !== 'file' && entry.type !== 'symlink')
522
+ return Err(`Unsupported upload entry: ${entryPath}`);
523
+ const removed = await uploadStore.remove(entryPath);
524
+ if (!removed.ok)
525
+ return removed;
526
+ }
527
+ return Ok(undefined);
101
528
  }
102
529
  // ============================================================================
103
530
  // Plugin
104
531
  // ============================================================================
105
532
  export const uploadsPlugin = definePlugin('uploads')
533
+ .order(70)
106
534
  .pluginConfig()
107
535
  .events([uploadEvents])
108
536
  .notification('uploadStatusChanged', { schema: statusChangedSchema })
109
537
  .state({
110
538
  key: 'uploads',
111
- initial: () => ({ pending: [] }),
539
+ initial: () => ({ pending: [], terminal: {} }),
112
540
  reduce: (state, event) => {
113
541
  switch (event.type) {
114
542
  case 'attachment_uploaded': {
543
+ const pending = state.pending.filter((upload) => String(upload.uploadId) !== String(event.uploadId));
544
+ if (event.status === 'processing')
545
+ return { ...state, pending };
546
+ const terminal = {
547
+ ...state.terminal,
548
+ [String(event.uploadId)]: event.status,
549
+ };
115
550
  if (event.status !== 'ready')
116
- return state;
551
+ return { ...state, pending, terminal };
117
552
  const upload = {
118
553
  uploadId: event.uploadId,
119
554
  filename: event.filename,
@@ -123,13 +558,22 @@ export const uploadsPlugin = definePlugin('uploads')
123
558
  extractedContent: event.extractedContent,
124
559
  derivedPaths: event.derivedPaths,
125
560
  };
126
- return { ...state, pending: [...state.pending, upload] };
561
+ return { ...state, pending: [...pending, upload], terminal };
127
562
  }
128
563
  case 'attachments_consumed': {
129
564
  const consumedIds = new Set(event.uploadIds.map(String));
130
565
  return {
131
566
  ...state,
132
567
  pending: state.pending.filter((u) => !consumedIds.has(String(u.uploadId))),
568
+ terminal: withoutKeys(state.terminal, [...consumedIds]),
569
+ };
570
+ }
571
+ case 'attachment_deletion_completed': {
572
+ const uploadId = String(event.uploadId);
573
+ return {
574
+ ...state,
575
+ pending: state.pending.filter((upload) => String(upload.uploadId) !== uploadId),
576
+ terminal: withoutKeys(state.terminal, [uploadId]),
133
577
  };
134
578
  }
135
579
  default:
@@ -137,6 +581,13 @@ export const uploadsPlugin = definePlugin('uploads')
137
581
  }
138
582
  },
139
583
  })
584
+ .context(async () => ({
585
+ activeUploads: new Map(),
586
+ activeMutations: new Map(),
587
+ deletedUploads: new Set(),
588
+ operationQueues: new Map(),
589
+ closing: false,
590
+ }))
140
591
  .dequeue({
141
592
  hasPendingMessages: (ctx) => {
142
593
  const uploads = ctx.pluginState;
@@ -148,29 +599,41 @@ export const uploadsPlugin = definePlugin('uploads')
148
599
  return null;
149
600
  const sessionRoot = ctx.files.getRoots().session;
150
601
  return {
151
- messages: [{
602
+ messages: [
603
+ {
152
604
  role: 'user',
153
605
  content: formatUploadsForLLM(uploads.pending, sessionRoot),
154
- }],
606
+ },
607
+ ],
155
608
  token: uploads.pending.map((u) => u.uploadId),
156
609
  };
157
610
  },
158
611
  markConsumed: async (ctx, token) => {
159
- await ctx.emitEvent(uploadEvents.create('attachments_consumed', {
160
- agentId: ctx.agentId,
161
- uploadIds: token.map(UploadId),
162
- }));
163
- // Also mark as used on disk
164
- const { dataFileStore } = ctx.pluginConfig;
165
- for (const uploadIdStr of token) {
166
- const uploadStore = dataFileStore.scoped(`sessions/${ctx.sessionId}/uploads/${uploadIdStr}`);
167
- const metaResult = await uploadStore.read('meta.json');
168
- if (metaResult.ok) {
169
- const meta = JSON.parse(metaResult.value);
170
- meta.usedInMessageId = 'auto-dequeued';
171
- await uploadStore.write('meta.json', JSON.stringify(meta, null, 2));
172
- }
173
- }
612
+ await runTrackedMutation({
613
+ pluginContext: ctx.pluginContext,
614
+ runtimeActivity: ctx.runtimeActivity,
615
+ reason: 'uploads:consume',
616
+ run: async () => {
617
+ await ctx.emitEvent(uploadEvents.create('attachments_consumed', {
618
+ agentId: ctx.agentId,
619
+ uploadIds: token.map(UploadId),
620
+ }));
621
+ const { dataFileStore } = ctx.pluginConfig;
622
+ for (const uploadIdStr of token) {
623
+ await withUploadLock(ctx.pluginContext, uploadIdStr, async () => {
624
+ const uploadStore = dataFileStore.scoped(`sessions/${ctx.sessionId}/uploads/${uploadIdStr}`);
625
+ const metaResult = await uploadStore.read('meta.json');
626
+ if (!metaResult.ok)
627
+ return;
628
+ const meta = JSON.parse(metaResult.value);
629
+ if (meta.status === 'deleted')
630
+ return;
631
+ meta.usedInMessageId = 'auto-dequeued';
632
+ await writeMetadata(uploadStore, meta);
633
+ }, { allowDuringClose: true });
634
+ }
635
+ },
636
+ });
174
637
  },
175
638
  })
176
639
  .method('listPending', {
@@ -228,33 +691,72 @@ export const uploadsPlugin = definePlugin('uploads')
228
691
  .method('delete', {
229
692
  input: z.object({
230
693
  sessionId: z.string(),
231
- uploadId: z.string(),
694
+ uploadId: uploadIdSchema,
232
695
  }),
233
696
  output: z.object({}),
234
697
  handler: async (ctx, input) => {
235
698
  const { dataFileStore } = ctx.pluginConfig;
236
699
  const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${input.uploadId}`);
237
- const metaResult = await uploadStore.read('meta.json');
238
- if (!metaResult.ok) {
239
- return Err(ValidationErrors.invalid(`Upload not found: ${input.uploadId}`));
240
- }
241
- const meta = JSON.parse(metaResult.value);
242
- if (meta.sessionId !== input.sessionId) {
243
- return Err(ValidationErrors.invalid('Upload does not belong to this session'));
700
+ try {
701
+ return await runTrackedMutation({
702
+ pluginContext: ctx.pluginContext,
703
+ runtimeActivity: ctx.runtimeActivity,
704
+ reason: `upload:${input.uploadId}:delete`,
705
+ run: async () => {
706
+ let releaseTerminalHold;
707
+ try {
708
+ const reservation = await withUploadLock(ctx.pluginContext, input.uploadId, async () => {
709
+ const metaResult = await uploadStore.read('meta.json');
710
+ if (!metaResult.ok)
711
+ return Err(ValidationErrors.invalid(`Upload not found: ${input.uploadId}`));
712
+ const meta = JSON.parse(metaResult.value);
713
+ if (meta.sessionId !== input.sessionId) {
714
+ return Err(ValidationErrors.invalid('Upload does not belong to this session'));
715
+ }
716
+ if (meta.usedInMessageId) {
717
+ return Err(ValidationErrors.invalid('Cannot delete an upload that has been sent in a message'));
718
+ }
719
+ ctx.pluginContext.deletedUploads.add(input.uploadId);
720
+ const active = ctx.pluginContext.activeUploads.get(input.uploadId);
721
+ releaseTerminalHold = active?.holdTerminalMutation();
722
+ active?.controller.abort(new Error('Upload deleted'));
723
+ return Ok(active?.processingCompletion);
724
+ }, { allowDuringClose: true });
725
+ if (!reservation.ok)
726
+ return reservation;
727
+ await reservation.value;
728
+ return await withUploadLock(ctx.pluginContext, input.uploadId, async () => {
729
+ const metaResult = await uploadStore.read('meta.json');
730
+ if (!metaResult.ok)
731
+ return Err(ValidationErrors.invalid(`Upload not found: ${input.uploadId}`));
732
+ const meta = JSON.parse(metaResult.value);
733
+ meta.status = 'deleted';
734
+ await writeMetadata(uploadStore, meta);
735
+ const cleanup = await removeUploadFiles(uploadStore);
736
+ if (!cleanup.ok)
737
+ return Err(ValidationErrors.invalid(`Could not remove upload: ${input.uploadId}`));
738
+ await ctx.emitEvent(uploadEvents.create('attachment_deletion_completed', {
739
+ uploadId: input.uploadId,
740
+ }));
741
+ return Ok({});
742
+ }, { allowDuringClose: true });
743
+ }
744
+ finally {
745
+ ctx.pluginContext.deletedUploads.delete(input.uploadId);
746
+ releaseTerminalHold?.();
747
+ }
748
+ },
749
+ });
244
750
  }
245
- if (meta.usedInMessageId) {
246
- return Err(ValidationErrors.invalid('Cannot delete an upload that has been sent in a message'));
751
+ catch (error) {
752
+ return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not delete upload'));
247
753
  }
248
- // Mark as deleted
249
- meta.status = 'deleted';
250
- await uploadStore.write('meta.json', JSON.stringify(meta, null, 2));
251
- return Ok({});
252
754
  },
253
755
  })
254
756
  .method('loadAttachments', {
255
757
  input: z.object({
256
758
  sessionId: z.string(),
257
- uploadIds: z.array(z.string()),
759
+ uploadIds: z.array(uploadIdSchema),
258
760
  }),
259
761
  output: z.object({
260
762
  attachments: z.array(z.unknown()),
@@ -291,22 +793,63 @@ export const uploadsPlugin = definePlugin('uploads')
291
793
  .method('markUsed', {
292
794
  input: z.object({
293
795
  sessionId: z.string(),
294
- uploadIds: z.array(z.string()),
796
+ // The id lands in a path, so it is validated here rather than inside the loop below.
797
+ uploadIds: z.array(uploadIdSchema),
295
798
  messageId: z.string(),
296
799
  }),
297
800
  output: z.object({}),
298
801
  handler: async (ctx, input) => {
299
802
  const { dataFileStore } = ctx.pluginConfig;
300
- for (const uploadIdStr of input.uploadIds) {
301
- const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${uploadIdStr}`);
302
- const metaResult = await uploadStore.read('meta.json');
303
- if (metaResult.ok) {
304
- const meta = JSON.parse(metaResult.value);
305
- meta.usedInMessageId = input.messageId;
306
- await uploadStore.write('meta.json', JSON.stringify(meta, null, 2));
307
- }
803
+ try {
804
+ return await runTrackedMutation({
805
+ pluginContext: ctx.pluginContext,
806
+ runtimeActivity: ctx.runtimeActivity,
807
+ reason: 'uploads:mark-used',
808
+ run: async () => {
809
+ const successfulIds = [];
810
+ const emitSuccessfulConsumption = async () => {
811
+ if (successfulIds.length === 0)
812
+ return;
813
+ await ctx.emitEvent(uploadEvents.create('attachments_consumed', {
814
+ agentId: getEntryAgentId(ctx.sessionState) ?? 'uploads.markUsed',
815
+ uploadIds: successfulIds.map(UploadId),
816
+ }));
817
+ };
818
+ for (const uploadIdStr of input.uploadIds) {
819
+ let result;
820
+ try {
821
+ result = await withUploadLock(ctx.pluginContext, uploadIdStr, async () => {
822
+ const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${uploadIdStr}`);
823
+ const metaResult = await uploadStore.read('meta.json');
824
+ if (!metaResult.ok || ctx.pluginContext.deletedUploads.has(uploadIdStr)) {
825
+ return Err(ValidationErrors.invalid(`Upload not found: ${uploadIdStr}`));
826
+ }
827
+ const meta = JSON.parse(metaResult.value);
828
+ if (meta.status === 'deleted')
829
+ return Err(ValidationErrors.invalid(`Upload not found: ${uploadIdStr}`));
830
+ meta.usedInMessageId = input.messageId;
831
+ await writeMetadata(uploadStore, meta);
832
+ return Ok({});
833
+ }, { allowDuringClose: true });
834
+ }
835
+ catch (error) {
836
+ await emitSuccessfulConsumption();
837
+ throw error;
838
+ }
839
+ if (!result.ok) {
840
+ await emitSuccessfulConsumption();
841
+ return result;
842
+ }
843
+ successfulIds.push(uploadIdStr);
844
+ }
845
+ await emitSuccessfulConsumption();
846
+ return Ok({});
847
+ },
848
+ });
849
+ }
850
+ catch (error) {
851
+ return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not mark upload as used'));
308
852
  }
309
- return Ok({});
310
853
  },
311
854
  })
312
855
  .method('upload', {
@@ -323,49 +866,55 @@ export const uploadsPlugin = definePlugin('uploads')
323
866
  extractedContent: z.string().optional(),
324
867
  }),
325
868
  handler: async (ctx, input) => {
326
- const { dataFileStore, preprocessorRegistry } = ctx.pluginConfig;
327
- if (input.size > MAX_FILE_SIZE) {
328
- return Err(ValidationErrors.invalid(`File too large: max ${MAX_FILE_SIZE / (1024 * 1024)}MB`));
329
- }
330
- if (!isAllowedMimeType(input.mimeType)) {
331
- return Err(ValidationErrors.invalid(`Unsupported file type: ${input.mimeType}`));
332
- }
333
- const uploadId = generateUploadId();
334
- const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${uploadId}`);
335
- const writeResult = await uploadStore.write(input.filename, input.fileBuffer);
336
- if (!writeResult.ok) {
337
- return Err(ValidationErrors.invalid('Failed to write file'));
338
- }
339
- const result = await runPreprocessAndPersist({
340
- uploadId: String(uploadId),
341
- sessionId: ctx.sessionId,
342
- uploadStore,
343
- filePath: writeResult.value.path,
344
- filename: input.filename,
345
- mimeType: input.mimeType,
346
- size: input.size,
347
- createdAt: Date.now(),
348
- preprocessorRegistry,
869
+ const preparedResult = await prepareUpload({
870
+ pluginContext: ctx.pluginContext,
871
+ dataFileStore: ctx.pluginConfig.dataFileStore,
872
+ logger: ctx.logger,
873
+ input,
349
874
  });
350
- await ctx.emitEvent(uploadEvents.create('attachment_uploaded', {
351
- uploadId,
352
- filename: input.filename,
875
+ if (!preparedResult.ok)
876
+ return Err(preparedResult.error);
877
+ const prepared = preparedResult.value;
878
+ // Built from the stored name, not from `input`: the metadata and the event tell the agent
879
+ // where the file is, and a spelling that differs from the directory entry is unusable.
880
+ const upload = {
881
+ filename: prepared.filename,
353
882
  mimeType: input.mimeType,
354
883
  size: input.size,
355
- status: result.status,
356
- extractedContent: result.extractedContent,
357
- derivedPaths: result.derivedPaths,
358
- error: result.error,
359
- }));
884
+ };
360
885
  const entryAgentId = getEntryAgentId(ctx.sessionState);
361
- if (result.status === 'ready' && entryAgentId) {
362
- ctx.scheduleAgent(entryAgentId);
886
+ try {
887
+ const result = await prepared.lifecycle.start(() => processUpload({
888
+ pluginContext: ctx.pluginContext,
889
+ prepared,
890
+ sessionId: ctx.sessionId,
891
+ upload,
892
+ config: ctx.pluginConfig,
893
+ logger: ctx.logger,
894
+ inferenceContext: entryAgentId
895
+ ? {
896
+ sessionId: String(ctx.sessionId),
897
+ agentId: String(entryAgentId),
898
+ fileStore: ctx.files,
899
+ }
900
+ : undefined,
901
+ onTerminal: async (finalResult) => {
902
+ await ctx.emitEvent(createUploadResultEvent(upload, prepared.uploadId, finalResult));
903
+ },
904
+ onPersisted: async (finalResult) => {
905
+ if (finalResult.status === 'ready' && entryAgentId)
906
+ ctx.scheduleAgent(entryAgentId);
907
+ },
908
+ }));
909
+ return Ok({
910
+ uploadId: prepared.uploadIdStr,
911
+ status: result.status,
912
+ extractedContent: result.extractedContent,
913
+ });
914
+ }
915
+ catch (error) {
916
+ return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Upload processing failed'));
363
917
  }
364
- return Ok({
365
- uploadId: String(uploadId),
366
- status: result.status,
367
- extractedContent: result.extractedContent,
368
- });
369
918
  },
370
919
  })
371
920
  .method('uploadAsync', {
@@ -381,119 +930,236 @@ export const uploadsPlugin = definePlugin('uploads')
381
930
  status: z.enum(['processing']),
382
931
  }),
383
932
  handler: async (ctx, input) => {
384
- const { dataFileStore, preprocessorRegistry } = ctx.pluginConfig;
385
- if (input.size > MAX_FILE_SIZE) {
386
- return Err(ValidationErrors.invalid(`File too large: max ${MAX_FILE_SIZE / (1024 * 1024)}MB`));
387
- }
388
- if (!isAllowedMimeType(input.mimeType)) {
389
- return Err(ValidationErrors.invalid(`Unsupported file type: ${input.mimeType}`));
390
- }
391
- const uploadId = generateUploadId();
392
- const uploadIdStr = String(uploadId);
393
- const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${uploadId}`);
394
- const writeResult = await uploadStore.write(input.filename, input.fileBuffer);
395
- if (!writeResult.ok) {
396
- return Err(ValidationErrors.invalid('Failed to write file'));
397
- }
398
- const filePath = writeResult.value.path;
399
- const createdAt = Date.now();
400
- // Persist initial 'processing' metadata so listPending sees it before preprocessor finishes.
401
- const processingMeta = {
402
- uploadId,
403
- sessionId: ctx.sessionId,
404
- filename: input.filename,
933
+ const preparedResult = await prepareUpload({
934
+ pluginContext: ctx.pluginContext,
935
+ dataFileStore: ctx.pluginConfig.dataFileStore,
936
+ logger: ctx.logger,
937
+ input,
938
+ });
939
+ if (!preparedResult.ok)
940
+ return Err(preparedResult.error);
941
+ const prepared = preparedResult.value;
942
+ // Built from the stored name, not from `input`: the metadata and the event tell the agent
943
+ // where the file is, and a spelling that differs from the directory entry is unusable.
944
+ const upload = {
945
+ filename: prepared.filename,
405
946
  mimeType: input.mimeType,
406
947
  size: input.size,
407
- path: filePath,
408
- status: 'processing',
409
- createdAt,
410
- completedAt: createdAt,
411
948
  };
412
- await uploadStore.write('meta.json', JSON.stringify(processingMeta, null, 2));
413
- await ctx.emitEvent(uploadEvents.create('attachment_uploaded', {
414
- uploadId,
415
- filename: input.filename,
416
- mimeType: input.mimeType,
417
- size: input.size,
949
+ const processingMeta = {
950
+ uploadId: prepared.uploadId,
951
+ sessionId: ctx.sessionId,
952
+ filename: upload.filename,
953
+ mimeType: upload.mimeType,
954
+ size: upload.size,
955
+ path: prepared.filePath,
418
956
  status: 'processing',
419
- }));
420
- ctx.notify('uploadStatusChanged', {
421
- sessionId: input.sessionId,
422
- uploadId: uploadIdStr,
957
+ createdAt: prepared.createdAt,
958
+ };
959
+ try {
960
+ await writeMetadata(prepared.uploadStore, processingMeta);
961
+ await ctx.emitEvent(uploadEvents.create('attachment_uploaded', {
962
+ uploadId: prepared.uploadId,
963
+ filename: upload.filename,
964
+ mimeType: upload.mimeType,
965
+ size: upload.size,
966
+ status: 'processing',
967
+ }));
968
+ }
969
+ catch (error) {
970
+ prepared.lifecycle.abandon();
971
+ return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not start upload'));
972
+ }
973
+ try {
974
+ const releaseRuntimeLease = ctx.runtimeActivity.acquire(`upload:${prepared.uploadIdStr}:processing`);
975
+ prepared.lifecycle.attachRuntimeLease(releaseRuntimeLease);
976
+ }
977
+ catch (error) {
978
+ prepared.lifecycle.abandon();
979
+ return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not start upload'));
980
+ }
981
+ // Everything from here to startAsyncUpload must be guarded: the lifecycle
982
+ // only settles once lifecycle.start() runs, so a throw before it leaks the
983
+ // runtime lease AND hangs onSessionClose, which awaits this completion.
984
+ // ctx.notify is the realistic trigger — it reaches embedder-supplied
985
+ // onUserOutput, which in standalone/sandbox writes to sockets.
986
+ try {
987
+ ctx.notify('uploadStatusChanged', {
988
+ sessionId: input.sessionId,
989
+ uploadId: prepared.uploadIdStr,
990
+ status: 'processing',
991
+ });
992
+ const sessionId = ctx.sessionId;
993
+ const sessionIdString = String(sessionId);
994
+ const entryAgentId = getEntryAgentId(ctx.sessionState);
995
+ const inferenceContext = entryAgentId
996
+ ? {
997
+ sessionId: sessionIdString,
998
+ agentId: String(entryAgentId),
999
+ fileStore: ctx.files,
1000
+ }
1001
+ : undefined;
1002
+ startAsyncUpload({
1003
+ pluginContext: ctx.pluginContext,
1004
+ prepared,
1005
+ sessionId,
1006
+ upload,
1007
+ config: ctx.pluginConfig,
1008
+ inferenceContext,
1009
+ emitEvent: ctx.emitEvent,
1010
+ notify: ctx.notify,
1011
+ logger: ctx.logger,
1012
+ entryAgentId,
1013
+ scheduleAgent: ctx.scheduleAgent,
1014
+ });
1015
+ }
1016
+ catch (error) {
1017
+ prepared.lifecycle.abandon();
1018
+ return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not start upload'));
1019
+ }
1020
+ return Ok({
1021
+ uploadId: prepared.uploadIdStr,
423
1022
  status: 'processing',
424
1023
  });
425
- // Capture refs from ctx before the handler returns — `notify`/`emitEvent`
426
- // closures stay valid for the lifetime of the session, which in roj
427
- // outlives any single handler call.
428
- const { emitEvent, notify, logger, scheduleAgent } = ctx;
429
- const sessionId = ctx.sessionId;
430
- const entryAgentId = getEntryAgentId(ctx.sessionState);
431
- void (async () => {
432
- try {
433
- const result = await runPreprocessAndPersist({
434
- uploadId: uploadIdStr,
435
- sessionId,
436
- uploadStore,
437
- filePath,
438
- filename: input.filename,
439
- mimeType: input.mimeType,
440
- size: input.size,
441
- createdAt,
442
- preprocessorRegistry,
443
- });
444
- await emitEvent(uploadEvents.create('attachment_uploaded', {
445
- uploadId,
446
- filename: input.filename,
447
- mimeType: input.mimeType,
448
- size: input.size,
449
- status: result.status,
450
- extractedContent: result.extractedContent,
451
- derivedPaths: result.derivedPaths,
452
- error: result.error,
453
- }));
454
- if (result.status === 'ready' && entryAgentId) {
455
- scheduleAgent(entryAgentId);
456
- }
457
- notify('uploadStatusChanged', {
458
- sessionId: input.sessionId,
459
- uploadId: uploadIdStr,
460
- status: result.status,
461
- extractedContent: result.extractedContent,
462
- error: result.error,
463
- });
1024
+ },
1025
+ })
1026
+ .sessionHook('onSessionReady', async (ctx) => {
1027
+ const uploadsPath = `sessions/${ctx.sessionId}/uploads`;
1028
+ const listResult = await ctx.pluginConfig.dataFileStore.list(uploadsPath);
1029
+ if (!listResult.ok)
1030
+ return;
1031
+ const seenUploadIds = new Set();
1032
+ for (const entry of listResult.value) {
1033
+ if (entry.type !== 'directory')
1034
+ continue;
1035
+ seenUploadIds.add(entry.name);
1036
+ const uploadStore = ctx.pluginConfig.dataFileStore.scoped(`${uploadsPath}/${entry.name}`);
1037
+ try {
1038
+ const metaResult = await uploadStore.read('meta.json');
1039
+ if (!metaResult.ok)
1040
+ continue;
1041
+ const metadata = JSON.parse(metaResult.value);
1042
+ if (String(metadata.sessionId) !== String(ctx.sessionId))
1043
+ continue;
1044
+ // The agent can write into the upload dir, and this id becomes a path handed back to it.
1045
+ const parsedUploadId = parseUploadId(String(metadata.uploadId));
1046
+ if (!parsedUploadId.ok) {
1047
+ ctx.logger.warn('Skipping upload metadata with an invalid upload id', { entry: entry.name });
1048
+ continue;
464
1049
  }
465
- catch (err) {
466
- const message = err instanceof Error ? err.message : String(err);
467
- logger.error('Async upload processing crashed', err instanceof Error ? err : undefined, {
468
- uploadId: uploadIdStr,
469
- filename: input.filename,
470
- });
471
- try {
472
- await emitEvent(uploadEvents.create('attachment_uploaded', {
473
- uploadId,
474
- filename: input.filename,
475
- mimeType: input.mimeType,
476
- size: input.size,
477
- status: 'failed',
478
- error: message,
1050
+ const uploadId = String(parsedUploadId.value);
1051
+ seenUploadIds.add(uploadId);
1052
+ const projection = ctx.pluginState.terminal[uploadId];
1053
+ if (metadata.usedInMessageId) {
1054
+ if (projection) {
1055
+ await ctx.emitEvent(uploadEvents.create('attachments_consumed', {
1056
+ agentId: 'uploads.recovery',
1057
+ uploadIds: [metadata.uploadId],
479
1058
  }));
480
1059
  }
481
- catch {
482
- // Even event emission failed — best-effort; nothing useful left to do.
1060
+ continue;
1061
+ }
1062
+ if (metadata.status === 'deleted') {
1063
+ if (projection) {
1064
+ await ctx.emitEvent(uploadEvents.create('attachment_deletion_completed', { uploadId: metadata.uploadId }));
483
1065
  }
484
- notify('uploadStatusChanged', {
485
- sessionId: input.sessionId,
486
- uploadId: uploadIdStr,
487
- status: 'failed',
488
- error: message,
489
- });
1066
+ continue;
490
1067
  }
491
- })();
492
- return Ok({
493
- uploadId: uploadIdStr,
494
- status: 'processing',
1068
+ if (projection) {
1069
+ if (metadata.status !== projection || !metadata.terminalEventPersisted) {
1070
+ metadata.status = projection;
1071
+ // createFinalMetadata already wrote result.error before the terminal event.
1072
+ metadata.error = projection === 'failed' ? metadata.error ?? 'Processing failed' : undefined;
1073
+ if (projection === 'ready') {
1074
+ const pending = ctx.pluginState.pending.find((upload) => String(upload.uploadId) === uploadId);
1075
+ if (!pending)
1076
+ throw new Error('Ready terminal event has no pending projection');
1077
+ metadata.extractedContent = pending.extractedContent;
1078
+ metadata.derivedPaths = pending.derivedPaths;
1079
+ }
1080
+ else {
1081
+ metadata.extractedContent = undefined;
1082
+ metadata.derivedPaths = undefined;
1083
+ }
1084
+ metadata.completedAt ??= Date.now();
1085
+ metadata.terminalEventPersisted = true;
1086
+ await writeMetadata(uploadStore, metadata);
1087
+ }
1088
+ continue;
1089
+ }
1090
+ if (metadata.terminalEventPersisted)
1091
+ continue;
1092
+ if (metadata.status === 'processing') {
1093
+ metadata.status = 'failed';
1094
+ metadata.error = 'Processing interrupted by restart';
1095
+ metadata.completedAt = Date.now();
1096
+ await writeMetadata(uploadStore, metadata);
1097
+ }
1098
+ const result = metadata.status === 'ready'
1099
+ ? {
1100
+ status: 'ready',
1101
+ extractedContent: metadata.extractedContent,
1102
+ derivedPaths: metadata.derivedPaths,
1103
+ }
1104
+ : { status: 'failed', error: metadata.error };
1105
+ const recoveredUpload = {
1106
+ filename: metadata.filename,
1107
+ mimeType: metadata.mimeType,
1108
+ size: metadata.size,
1109
+ };
1110
+ await ctx.emitEvent(createUploadResultEvent(recoveredUpload, metadata.uploadId, result));
1111
+ metadata.terminalEventPersisted = true;
1112
+ await writeMetadata(uploadStore, metadata);
1113
+ ctx.notify('uploadStatusChanged', {
1114
+ sessionId: String(ctx.sessionId),
1115
+ uploadId,
1116
+ status: result.status,
1117
+ error: result.error,
1118
+ });
1119
+ }
1120
+ catch (error) {
1121
+ ctx.logger.warn('Could not recover interrupted upload', {
1122
+ uploadId: entry.name,
1123
+ error: error instanceof Error ? error.message : String(error),
1124
+ });
1125
+ }
1126
+ }
1127
+ // A fork copies the event log but not the uploads dir, so every inherited upload looks
1128
+ // orphaned here — sweeping them would delete attachments the fork still shows.
1129
+ if (ctx.sessionState.forkedFrom)
1130
+ return;
1131
+ for (const uploadId of Object.keys(ctx.pluginState.terminal)) {
1132
+ if (seenUploadIds.has(uploadId))
1133
+ continue;
1134
+ await ctx.emitEvent(uploadEvents.create('attachment_deletion_completed', { uploadId: UploadId(uploadId) }));
1135
+ }
1136
+ })
1137
+ .sessionHook('onSessionClose', async (ctx) => {
1138
+ ctx.pluginContext.closing = true;
1139
+ const active = [...ctx.pluginContext.activeUploads.values()];
1140
+ for (const upload of active)
1141
+ upload.controller.abort(new Error('Session is closing'));
1142
+ const configuredDrainTimeoutMs = ctx.pluginConfig.closeDrainTimeoutMs;
1143
+ const settled = await settleWithTimeout([...active.map((upload) => upload.completion), ...ctx.pluginContext.activeMutations.keys()], configuredDrainTimeoutMs !== undefined && configuredDrainTimeoutMs > 0 ? configuredDrainTimeoutMs : CLOSE_DRAIN_TIMEOUT_MS);
1144
+ if (!settled) {
1145
+ // Releasing the manager's map entry is not the same as freeing the session: a wedged lifecycle
1146
+ // still holds emitEvent/notify/scheduleAgent closures, so the runtime graph outlives disposal
1147
+ // until it settles — bounded here only so disposal itself never hangs on it.
1148
+ ctx.logger.warn('Upload lifecycles did not settle before close', {
1149
+ sessionId: String(ctx.sessionId),
1150
+ reason: ctx.reason,
1151
+ pendingUploadIds: [...ctx.pluginContext.activeUploads.keys()],
1152
+ pendingMutations: [...ctx.pluginContext.activeMutations.values()],
495
1153
  });
496
- },
1154
+ }
1155
+ ctx.pluginContext.activeUploads.clear();
1156
+ ctx.pluginContext.activeMutations.clear();
1157
+ if (settled) {
1158
+ // Work that outlived the drain still reads these: clearing them un-guards a deleted upload
1159
+ // against its own processUpload and drops the lock that keeps the two ordered.
1160
+ ctx.pluginContext.deletedUploads.clear();
1161
+ ctx.pluginContext.operationQueues.clear();
1162
+ }
497
1163
  })
498
1164
  .build();
499
1165
  //# sourceMappingURL=plugin.js.map