@roj-ai/sdk 0.1.36 → 0.2.1

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 (922) 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 +23 -13
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +22 -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/plugin.d.ts +34 -7
  233. package/dist/plugins/git-status/plugin.d.ts.map +1 -1
  234. package/dist/plugins/git-status/plugin.js +168 -64
  235. package/dist/plugins/git-status/plugin.js.map +1 -1
  236. package/dist/plugins/llm-debug/plugin.d.ts.map +1 -1
  237. package/dist/plugins/llm-debug/plugin.js +1 -0
  238. package/dist/plugins/llm-debug/plugin.js.map +1 -1
  239. package/dist/plugins/logs/plugin.d.ts +1 -4
  240. package/dist/plugins/logs/plugin.d.ts.map +1 -1
  241. package/dist/plugins/logs/plugin.js +7 -0
  242. package/dist/plugins/logs/plugin.js.map +1 -1
  243. package/dist/plugins/mailbox/helpers.d.ts +2 -2
  244. package/dist/plugins/mailbox/helpers.js +1 -1
  245. package/dist/plugins/mailbox/plugin.d.ts.map +1 -1
  246. package/dist/plugins/mailbox/plugin.js +38 -43
  247. package/dist/plugins/mailbox/plugin.js.map +1 -1
  248. package/dist/plugins/mailbox/query.d.ts +7 -2
  249. package/dist/plugins/mailbox/query.d.ts.map +1 -1
  250. package/dist/plugins/mailbox/query.js +14 -7
  251. package/dist/plugins/mailbox/query.js.map +1 -1
  252. package/dist/plugins/mailbox/state.d.ts +5 -4
  253. package/dist/plugins/mailbox/state.d.ts.map +1 -1
  254. package/dist/plugins/mailbox/state.js +6 -5
  255. package/dist/plugins/mailbox/state.js.map +1 -1
  256. package/dist/plugins/resources/filename.d.ts +4 -0
  257. package/dist/plugins/resources/filename.d.ts.map +1 -0
  258. package/dist/plugins/resources/filename.js +18 -0
  259. package/dist/plugins/resources/filename.js.map +1 -0
  260. package/dist/plugins/resources/plugin.d.ts +3 -0
  261. package/dist/plugins/resources/plugin.d.ts.map +1 -1
  262. package/dist/plugins/resources/plugin.js +97 -60
  263. package/dist/plugins/resources/plugin.js.map +1 -1
  264. package/dist/plugins/resources/state.d.ts +1 -1
  265. package/dist/plugins/resources/state.js +1 -1
  266. package/dist/plugins/result-eviction/plugin.d.ts.map +1 -1
  267. package/dist/plugins/result-eviction/plugin.js +6 -1
  268. package/dist/plugins/result-eviction/plugin.js.map +1 -1
  269. package/dist/plugins/services/plugin.d.ts +4 -1
  270. package/dist/plugins/services/plugin.d.ts.map +1 -1
  271. package/dist/plugins/services/plugin.js +251 -100
  272. package/dist/plugins/services/plugin.js.map +1 -1
  273. package/dist/plugins/services/prompt.d.ts.map +1 -1
  274. package/dist/plugins/services/prompt.js +1 -4
  275. package/dist/plugins/services/prompt.js.map +1 -1
  276. package/dist/plugins/services/schema.d.ts +15 -5
  277. package/dist/plugins/services/schema.d.ts.map +1 -1
  278. package/dist/plugins/services/service.d.ts +100 -18
  279. package/dist/plugins/services/service.d.ts.map +1 -1
  280. package/dist/plugins/services/service.js +680 -230
  281. package/dist/plugins/services/service.js.map +1 -1
  282. package/dist/plugins/session-lifecycle/plugin.d.ts +21 -1
  283. package/dist/plugins/session-lifecycle/plugin.d.ts.map +1 -1
  284. package/dist/plugins/session-lifecycle/plugin.js +53 -40
  285. package/dist/plugins/session-lifecycle/plugin.js.map +1 -1
  286. package/dist/plugins/session-state/plugin.d.ts +21 -13
  287. package/dist/plugins/session-state/plugin.d.ts.map +1 -1
  288. package/dist/plugins/session-state/plugin.js +214 -79
  289. package/dist/plugins/session-state/plugin.js.map +1 -1
  290. package/dist/plugins/session-stats/plugin.d.ts.map +1 -1
  291. package/dist/plugins/session-stats/plugin.js +1 -0
  292. package/dist/plugins/session-stats/plugin.js.map +1 -1
  293. package/dist/plugins/shell/executor.d.ts +17 -15
  294. package/dist/plugins/shell/executor.d.ts.map +1 -1
  295. package/dist/plugins/shell/executor.js +130 -195
  296. package/dist/plugins/shell/executor.js.map +1 -1
  297. package/dist/plugins/shell/index.d.ts +2 -2
  298. package/dist/plugins/shell/index.d.ts.map +1 -1
  299. package/dist/plugins/shell/index.js +2 -2
  300. package/dist/plugins/shell/index.js.map +1 -1
  301. package/dist/plugins/shell/plugin.d.ts +5 -4
  302. package/dist/plugins/shell/plugin.d.ts.map +1 -1
  303. package/dist/plugins/shell/plugin.js +4 -8
  304. package/dist/plugins/shell/plugin.js.map +1 -1
  305. package/dist/plugins/skills/discovery.d.ts.map +1 -1
  306. package/dist/plugins/skills/discovery.js +10 -8
  307. package/dist/plugins/skills/discovery.js.map +1 -1
  308. package/dist/plugins/skills/index.d.ts +1 -1
  309. package/dist/plugins/skills/index.d.ts.map +1 -1
  310. package/dist/plugins/skills/plugin.d.ts +1 -1
  311. package/dist/plugins/skills/plugin.d.ts.map +1 -1
  312. package/dist/plugins/skills/plugin.js +23 -30
  313. package/dist/plugins/skills/plugin.js.map +1 -1
  314. package/dist/plugins/skills/prompts.d.ts +3 -3
  315. package/dist/plugins/skills/prompts.d.ts.map +1 -1
  316. package/dist/plugins/skills/prompts.js.map +1 -1
  317. package/dist/plugins/skills/schema.d.ts +4 -2
  318. package/dist/plugins/skills/schema.d.ts.map +1 -1
  319. package/dist/plugins/todo/schema.d.ts +1 -1
  320. package/dist/plugins/uploads/plugin.d.ts +11 -4
  321. package/dist/plugins/uploads/plugin.d.ts.map +1 -1
  322. package/dist/plugins/uploads/plugin.js +891 -225
  323. package/dist/plugins/uploads/plugin.js.map +1 -1
  324. package/dist/plugins/uploads/preprocessor.d.ts +14 -0
  325. package/dist/plugins/uploads/preprocessor.d.ts.map +1 -1
  326. package/dist/plugins/uploads/preprocessor.js +11 -0
  327. package/dist/plugins/uploads/preprocessor.js.map +1 -1
  328. package/dist/plugins/uploads/preprocessors/image-classifier.d.ts +1 -1
  329. package/dist/plugins/uploads/preprocessors/image-classifier.d.ts.map +1 -1
  330. package/dist/plugins/uploads/preprocessors/image-classifier.js +51 -26
  331. package/dist/plugins/uploads/preprocessors/image-classifier.js.map +1 -1
  332. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts +2 -2
  333. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts.map +1 -1
  334. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js +41 -16
  335. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js.map +1 -1
  336. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts +1 -1
  337. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts.map +1 -1
  338. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js +29 -14
  339. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js.map +1 -1
  340. package/dist/plugins/uploads/preprocessors/zip-preprocessor.d.ts +1 -1
  341. package/dist/plugins/uploads/preprocessors/zip-preprocessor.d.ts.map +1 -1
  342. package/dist/plugins/uploads/preprocessors/zip-preprocessor.js +62 -34
  343. package/dist/plugins/uploads/preprocessors/zip-preprocessor.js.map +1 -1
  344. package/dist/plugins/uploads/schema.d.ts +25 -2
  345. package/dist/plugins/uploads/schema.d.ts.map +1 -1
  346. package/dist/plugins/uploads/schema.js +24 -3
  347. package/dist/plugins/uploads/schema.js.map +1 -1
  348. package/dist/plugins/uploads/state.d.ts +13 -1
  349. package/dist/plugins/uploads/state.d.ts.map +1 -1
  350. package/dist/plugins/uploads/state.js +4 -1
  351. package/dist/plugins/uploads/state.js.map +1 -1
  352. package/dist/plugins/user-chat/plugin.d.ts +12 -5
  353. package/dist/plugins/user-chat/plugin.d.ts.map +1 -1
  354. package/dist/plugins/user-chat/plugin.js +141 -32
  355. package/dist/plugins/user-chat/plugin.js.map +1 -1
  356. package/dist/plugins/workers/context.d.ts +35 -13
  357. package/dist/plugins/workers/context.d.ts.map +1 -1
  358. package/dist/plugins/workers/context.js +82 -32
  359. package/dist/plugins/workers/context.js.map +1 -1
  360. package/dist/plugins/workers/definition.d.ts +7 -0
  361. package/dist/plugins/workers/definition.d.ts.map +1 -1
  362. package/dist/plugins/workers/definition.js.map +1 -1
  363. package/dist/plugins/workers/index.d.ts +4 -4
  364. package/dist/plugins/workers/index.d.ts.map +1 -1
  365. package/dist/plugins/workers/index.js +2 -2
  366. package/dist/plugins/workers/index.js.map +1 -1
  367. package/dist/plugins/workers/plugin.d.ts +10 -57
  368. package/dist/plugins/workers/plugin.d.ts.map +1 -1
  369. package/dist/plugins/workers/plugin.js +385 -123
  370. package/dist/plugins/workers/plugin.js.map +1 -1
  371. package/dist/plugins/workers/state.d.ts +69 -0
  372. package/dist/plugins/workers/state.d.ts.map +1 -0
  373. package/dist/plugins/workers/state.js +52 -0
  374. package/dist/plugins/workers/state.js.map +1 -0
  375. package/dist/plugins/workers/worker.d.ts +15 -4
  376. package/dist/plugins/workers/worker.d.ts.map +1 -1
  377. package/dist/testing/conformance.d.ts +106 -0
  378. package/dist/testing/conformance.d.ts.map +1 -0
  379. package/dist/testing/conformance.js +1463 -0
  380. package/dist/testing/conformance.js.map +1 -0
  381. package/dist/testing/node-platform.d.ts.map +1 -1
  382. package/dist/testing/node-platform.js +5 -0
  383. package/dist/testing/node-platform.js.map +1 -1
  384. package/dist/testing/test-harness.d.ts +11 -1
  385. package/dist/testing/test-harness.d.ts.map +1 -1
  386. package/dist/testing/test-harness.js +17 -37
  387. package/dist/testing/test-harness.js.map +1 -1
  388. package/dist/testing/wait-helpers.d.ts.map +1 -1
  389. package/dist/testing/wait-helpers.js +1 -3
  390. package/dist/testing/wait-helpers.js.map +1 -1
  391. package/dist/transport/adapter/client-adapter.d.ts +6 -0
  392. package/dist/transport/adapter/client-adapter.d.ts.map +1 -1
  393. package/dist/transport/adapter/client-adapter.js +50 -3
  394. package/dist/transport/adapter/client-adapter.js.map +1 -1
  395. package/dist/transport/adapter/heartbeat.d.ts +16 -0
  396. package/dist/transport/adapter/heartbeat.d.ts.map +1 -0
  397. package/dist/transport/adapter/heartbeat.js +41 -0
  398. package/dist/transport/adapter/heartbeat.js.map +1 -0
  399. package/dist/transport/adapter/server-adapter.d.ts.map +1 -1
  400. package/dist/transport/adapter/server-adapter.js +6 -0
  401. package/dist/transport/adapter/server-adapter.js.map +1 -1
  402. package/dist/transport/http/app.d.ts +3 -30
  403. package/dist/transport/http/app.d.ts.map +1 -1
  404. package/dist/transport/http/app.js +3 -7
  405. package/dist/transport/http/app.js.map +1 -1
  406. package/dist/transport/http/context.d.ts +40 -0
  407. package/dist/transport/http/context.d.ts.map +1 -0
  408. package/dist/transport/http/context.js +17 -0
  409. package/dist/transport/http/context.js.map +1 -0
  410. package/dist/transport/http/fetch-guard.d.ts +49 -0
  411. package/dist/transport/http/fetch-guard.d.ts.map +1 -0
  412. package/dist/transport/http/fetch-guard.js +312 -0
  413. package/dist/transport/http/fetch-guard.js.map +1 -0
  414. package/dist/transport/http/index.d.ts +1 -1
  415. package/dist/transport/http/index.d.ts.map +1 -1
  416. package/dist/transport/http/middleware/bearer-auth.d.ts +1 -1
  417. package/dist/transport/http/middleware/bearer-auth.d.ts.map +1 -1
  418. package/dist/transport/http/middleware/error-handler.d.ts +2 -2
  419. package/dist/transport/http/middleware/error-handler.d.ts.map +1 -1
  420. package/dist/transport/http/path-containment.d.ts +20 -0
  421. package/dist/transport/http/path-containment.d.ts.map +1 -0
  422. package/dist/transport/http/path-containment.js +31 -0
  423. package/dist/transport/http/path-containment.js.map +1 -0
  424. package/dist/transport/http/responses.d.ts +30 -0
  425. package/dist/transport/http/responses.d.ts.map +1 -0
  426. package/dist/transport/http/responses.js +5 -0
  427. package/dist/transport/http/responses.js.map +1 -0
  428. package/dist/transport/http/routes/files.d.ts +1 -1
  429. package/dist/transport/http/routes/files.d.ts.map +1 -1
  430. package/dist/transport/http/routes/files.js +54 -155
  431. package/dist/transport/http/routes/files.js.map +1 -1
  432. package/dist/transport/http/routes/resources.d.ts +1 -1
  433. package/dist/transport/http/routes/resources.d.ts.map +1 -1
  434. package/dist/transport/http/routes/resources.js +92 -57
  435. package/dist/transport/http/routes/resources.js.map +1 -1
  436. package/dist/transport/http/routes/rpc.d.ts +6 -1
  437. package/dist/transport/http/routes/rpc.d.ts.map +1 -1
  438. package/dist/transport/http/routes/rpc.js +51 -21
  439. package/dist/transport/http/routes/rpc.js.map +1 -1
  440. package/dist/transport/http/routes/upload.d.ts +1 -1
  441. package/dist/transport/http/routes/upload.d.ts.map +1 -1
  442. package/dist/transport/http/routes/upload.js +256 -169
  443. package/dist/transport/http/routes/upload.js.map +1 -1
  444. package/dist/user-config.d.ts +34 -7
  445. package/dist/user-config.d.ts.map +1 -1
  446. package/dist/user-config.js +71 -0
  447. package/dist/user-config.js.map +1 -1
  448. package/package.json +17 -7
  449. package/src/bootstrap.ts +119 -35
  450. package/src/bun-platform/fs.ts +1 -0
  451. package/src/bun-platform/index.ts +18 -1
  452. package/src/bun-platform/server-lifecycle.ts +218 -0
  453. package/src/bun-platform/shell.ts +362 -0
  454. package/src/config.ts +130 -0
  455. package/src/core/agents/agent.ts +210 -92
  456. package/src/core/agents/config.ts +8 -0
  457. package/src/core/agents/retry.ts +16 -17
  458. package/src/core/agents/state.ts +2 -0
  459. package/src/core/events/base-event-store.ts +24 -16
  460. package/src/core/events/file.ts +198 -22
  461. package/src/core/file-store/containment.ts +75 -0
  462. package/src/core/file-store/file-store.ts +106 -25
  463. package/src/core/file-store/types.ts +14 -1
  464. package/src/core/image/image-processor.ts +3 -1
  465. package/src/core/image/types.ts +2 -0
  466. package/src/core/image/vips-resizer.ts +102 -37
  467. package/src/core/llm/anthropic.ts +63 -79
  468. package/src/core/llm/logger.ts +175 -37
  469. package/src/core/llm/openrouter.ts +57 -72
  470. package/src/core/llm/provider-request.ts +40 -0
  471. package/src/core/llm/provider.ts +28 -0
  472. package/src/core/plugins/plugin-builder.ts +106 -14
  473. package/src/core/preset/preset-builder.ts +12 -22
  474. package/src/core/sessions/context.ts +18 -0
  475. package/src/core/sessions/runtime-activity.ts +78 -0
  476. package/src/core/sessions/schema.ts +29 -3
  477. package/src/core/sessions/session-manager.ts +560 -140
  478. package/src/core/sessions/session-store.ts +38 -0
  479. package/src/core/sessions/session.ts +187 -47
  480. package/src/core/sessions/state.ts +11 -0
  481. package/src/core/system.ts +15 -0
  482. package/src/core/wake-key.ts +65 -0
  483. package/src/index.ts +30 -12
  484. package/src/lib/archive/archive-inspection.fixtures.ts +138 -0
  485. package/src/lib/archive/archive-inspection.ts +359 -0
  486. package/src/lib/archive/index.ts +17 -0
  487. package/src/lib/logger/console.ts +7 -57
  488. package/src/lib/logger/file.ts +28 -50
  489. package/src/lib/logger/index.ts +3 -1
  490. package/src/lib/logger/jsonl.ts +36 -0
  491. package/src/lib/logger/logger.ts +48 -0
  492. package/src/lib/logger/session-log.ts +42 -0
  493. package/src/lib/utils/concurrency.ts +37 -8
  494. package/src/lib/utils/fs-batch.ts +49 -0
  495. package/src/lib/utils/result.ts +7 -31
  496. package/src/lib/utils/sleep.ts +23 -0
  497. package/src/platform/fs-revision.ts +21 -0
  498. package/src/platform/fs.ts +109 -1
  499. package/src/platform/git.ts +67 -0
  500. package/src/platform/index.ts +51 -1
  501. package/src/platform/llm-call-log.ts +86 -0
  502. package/src/platform/scheduler.ts +71 -0
  503. package/src/platform/session-log.ts +39 -0
  504. package/src/platform/shell.ts +89 -0
  505. package/src/plugins/agent-status/plugin.ts +1 -0
  506. package/src/plugins/agents/plugin.ts +134 -81
  507. package/src/plugins/filesystem/listing.ts +70 -12
  508. package/src/plugins/filesystem/plugin.ts +8 -6
  509. package/src/plugins/git-status/plugin.ts +204 -77
  510. package/src/plugins/llm-debug/plugin.ts +1 -0
  511. package/src/plugins/logs/plugin.ts +9 -0
  512. package/src/plugins/mailbox/plugin.ts +46 -64
  513. package/src/plugins/mailbox/query.ts +15 -7
  514. package/src/plugins/mailbox/state.ts +1 -0
  515. package/src/plugins/resources/filename.ts +19 -0
  516. package/src/plugins/resources/plugin.ts +111 -59
  517. package/src/plugins/result-eviction/plugin.ts +6 -1
  518. package/src/plugins/services/plugin.ts +254 -98
  519. package/src/plugins/services/prompt.ts +1 -6
  520. package/src/plugins/services/schema.ts +17 -3
  521. package/src/plugins/services/service.ts +702 -252
  522. package/src/plugins/session-lifecycle/plugin.ts +56 -41
  523. package/src/plugins/session-state/plugin.ts +242 -88
  524. package/src/plugins/session-stats/plugin.ts +1 -0
  525. package/src/plugins/shell/executor.ts +144 -226
  526. package/src/plugins/shell/index.ts +1 -4
  527. package/src/plugins/shell/plugin.ts +7 -9
  528. package/src/plugins/skills/discovery.ts +10 -8
  529. package/src/plugins/skills/index.ts +1 -1
  530. package/src/plugins/skills/plugin.ts +19 -27
  531. package/src/plugins/skills/prompts.ts +3 -3
  532. package/src/plugins/skills/schema.ts +5 -2
  533. package/src/plugins/uploads/plugin.ts +991 -273
  534. package/src/plugins/uploads/preprocessor.ts +25 -0
  535. package/src/plugins/uploads/preprocessors/image-classifier.ts +56 -25
  536. package/src/plugins/uploads/preprocessors/markitdown-preprocessor.ts +41 -15
  537. package/src/plugins/uploads/preprocessors/pdf-preprocessor.ts +32 -16
  538. package/src/plugins/uploads/preprocessors/zip-preprocessor.ts +67 -38
  539. package/src/plugins/uploads/schema.ts +30 -3
  540. package/src/plugins/uploads/state.ts +12 -0
  541. package/src/plugins/user-chat/plugin.ts +196 -40
  542. package/src/plugins/workers/context.ts +102 -39
  543. package/src/plugins/workers/definition.ts +7 -0
  544. package/src/plugins/workers/index.ts +4 -4
  545. package/src/plugins/workers/plugin.ts +461 -151
  546. package/src/plugins/workers/state.ts +64 -0
  547. package/src/plugins/workers/worker.ts +15 -4
  548. package/src/testing/conformance.ts +1751 -0
  549. package/src/testing/node-platform.ts +5 -0
  550. package/src/testing/test-harness.ts +21 -38
  551. package/src/testing/wait-helpers.ts +1 -4
  552. package/src/transport/adapter/client-adapter.ts +60 -3
  553. package/src/transport/adapter/heartbeat.ts +50 -0
  554. package/src/transport/adapter/server-adapter.ts +6 -0
  555. package/src/transport/http/app.ts +7 -35
  556. package/src/transport/http/context.ts +46 -0
  557. package/src/transport/http/fetch-guard.ts +318 -0
  558. package/src/transport/http/index.ts +1 -1
  559. package/src/transport/http/middleware/bearer-auth.ts +1 -1
  560. package/src/transport/http/middleware/error-handler.ts +1 -1
  561. package/src/transport/http/path-containment.ts +44 -0
  562. package/src/transport/http/responses.ts +20 -0
  563. package/src/transport/http/routes/files.ts +69 -159
  564. package/src/transport/http/routes/resources.ts +124 -93
  565. package/src/transport/http/routes/rpc.ts +63 -26
  566. package/src/transport/http/routes/upload.ts +327 -258
  567. package/src/user-config.ts +93 -7
  568. package/dist/config.test.d.ts +0 -2
  569. package/dist/config.test.d.ts.map +0 -1
  570. package/dist/config.test.js +0 -155
  571. package/dist/config.test.js.map +0 -1
  572. package/dist/core/agent-loop.integration.test.d.ts +0 -2
  573. package/dist/core/agent-loop.integration.test.d.ts.map +0 -1
  574. package/dist/core/agent-loop.integration.test.js +0 -414
  575. package/dist/core/agent-loop.integration.test.js.map +0 -1
  576. package/dist/core/agents/agent-config.test.d.ts +0 -2
  577. package/dist/core/agents/agent-config.test.d.ts.map +0 -1
  578. package/dist/core/agents/agent-config.test.js +0 -194
  579. package/dist/core/agents/agent-config.test.js.map +0 -1
  580. package/dist/core/agents/agent-shutdown.test.d.ts +0 -6
  581. package/dist/core/agents/agent-shutdown.test.d.ts.map +0 -1
  582. package/dist/core/agents/agent-shutdown.test.js +0 -180
  583. package/dist/core/agents/agent-shutdown.test.js.map +0 -1
  584. package/dist/core/agents/agent.test.d.ts +0 -2
  585. package/dist/core/agents/agent.test.d.ts.map +0 -1
  586. package/dist/core/agents/agent.test.js +0 -313
  587. package/dist/core/agents/agent.test.js.map +0 -1
  588. package/dist/core/agents/communicator.d.ts +0 -3
  589. package/dist/core/agents/communicator.d.ts.map +0 -1
  590. package/dist/core/agents/communicator.js +0 -13
  591. package/dist/core/agents/communicator.js.map +0 -1
  592. package/dist/core/agents/handler-events.test.d.ts +0 -5
  593. package/dist/core/agents/handler-events.test.d.ts.map +0 -1
  594. package/dist/core/agents/handler-events.test.js +0 -115
  595. package/dist/core/agents/handler-events.test.js.map +0 -1
  596. package/dist/core/agents/response-sanitizer.test.d.ts +0 -2
  597. package/dist/core/agents/response-sanitizer.test.d.ts.map +0 -1
  598. package/dist/core/agents/response-sanitizer.test.js +0 -101
  599. package/dist/core/agents/response-sanitizer.test.js.map +0 -1
  600. package/dist/core/error-recovery.integration.test.d.ts +0 -2
  601. package/dist/core/error-recovery.integration.test.d.ts.map +0 -1
  602. package/dist/core/error-recovery.integration.test.js +0 -288
  603. package/dist/core/error-recovery.integration.test.js.map +0 -1
  604. package/dist/core/event-sourcing.integration.test.d.ts +0 -2
  605. package/dist/core/event-sourcing.integration.test.d.ts.map +0 -1
  606. package/dist/core/event-sourcing.integration.test.js +0 -154
  607. package/dist/core/event-sourcing.integration.test.js.map +0 -1
  608. package/dist/core/events/file.test.d.ts +0 -2
  609. package/dist/core/events/file.test.d.ts.map +0 -1
  610. package/dist/core/events/file.test.js +0 -284
  611. package/dist/core/events/file.test.js.map +0 -1
  612. package/dist/core/events/memory.test.d.ts +0 -2
  613. package/dist/core/events/memory.test.d.ts.map +0 -1
  614. package/dist/core/events/memory.test.js +0 -502
  615. package/dist/core/events/memory.test.js.map +0 -1
  616. package/dist/core/image/image-processor.test.d.ts +0 -2
  617. package/dist/core/image/image-processor.test.d.ts.map +0 -1
  618. package/dist/core/image/image-processor.test.js +0 -171
  619. package/dist/core/image/image-processor.test.js.map +0 -1
  620. package/dist/core/image/vips-resizer.test.d.ts +0 -2
  621. package/dist/core/image/vips-resizer.test.d.ts.map +0 -1
  622. package/dist/core/image/vips-resizer.test.js +0 -336
  623. package/dist/core/image/vips-resizer.test.js.map +0 -1
  624. package/dist/core/llm/anthropic.test.d.ts +0 -2
  625. package/dist/core/llm/anthropic.test.d.ts.map +0 -1
  626. package/dist/core/llm/anthropic.test.js +0 -464
  627. package/dist/core/llm/anthropic.test.js.map +0 -1
  628. package/dist/core/llm/cache-breakpoints.test.d.ts +0 -2
  629. package/dist/core/llm/cache-breakpoints.test.d.ts.map +0 -1
  630. package/dist/core/llm/cache-breakpoints.test.js +0 -69
  631. package/dist/core/llm/cache-breakpoints.test.js.map +0 -1
  632. package/dist/core/llm/cache-live.test.d.ts +0 -16
  633. package/dist/core/llm/cache-live.test.d.ts.map +0 -1
  634. package/dist/core/llm/cache-live.test.js +0 -137
  635. package/dist/core/llm/cache-live.test.js.map +0 -1
  636. package/dist/core/llm/logger.test.d.ts +0 -7
  637. package/dist/core/llm/logger.test.d.ts.map +0 -1
  638. package/dist/core/llm/logger.test.js +0 -228
  639. package/dist/core/llm/logger.test.js.map +0 -1
  640. package/dist/core/llm/message-sanitization.test.d.ts +0 -2
  641. package/dist/core/llm/message-sanitization.test.d.ts.map +0 -1
  642. package/dist/core/llm/message-sanitization.test.js +0 -181
  643. package/dist/core/llm/message-sanitization.test.js.map +0 -1
  644. package/dist/core/llm/mock.test.d.ts +0 -2
  645. package/dist/core/llm/mock.test.d.ts.map +0 -1
  646. package/dist/core/llm/mock.test.js +0 -318
  647. package/dist/core/llm/mock.test.js.map +0 -1
  648. package/dist/core/llm/model-shortlist-live.test.d.ts +0 -35
  649. package/dist/core/llm/model-shortlist-live.test.d.ts.map +0 -1
  650. package/dist/core/llm/model-shortlist-live.test.js +0 -452
  651. package/dist/core/llm/model-shortlist-live.test.js.map +0 -1
  652. package/dist/core/llm/openrouter-mapping.test.d.ts +0 -2
  653. package/dist/core/llm/openrouter-mapping.test.d.ts.map +0 -1
  654. package/dist/core/llm/openrouter-mapping.test.js +0 -190
  655. package/dist/core/llm/openrouter-mapping.test.js.map +0 -1
  656. package/dist/core/llm/openrouter.test.d.ts +0 -2
  657. package/dist/core/llm/openrouter.test.d.ts.map +0 -1
  658. package/dist/core/llm/openrouter.test.js +0 -377
  659. package/dist/core/llm/openrouter.test.js.map +0 -1
  660. package/dist/core/llm/provider-integration.test.d.ts +0 -12
  661. package/dist/core/llm/provider-integration.test.d.ts.map +0 -1
  662. package/dist/core/llm/provider-integration.test.js +0 -350
  663. package/dist/core/llm/provider-integration.test.js.map +0 -1
  664. package/dist/core/llm/reasoning-details.test.d.ts +0 -2
  665. package/dist/core/llm/reasoning-details.test.d.ts.map +0 -1
  666. package/dist/core/llm/reasoning-details.test.js +0 -144
  667. package/dist/core/llm/reasoning-details.test.js.map +0 -1
  668. package/dist/core/llm/routing-provider.test.d.ts +0 -2
  669. package/dist/core/llm/routing-provider.test.d.ts.map +0 -1
  670. package/dist/core/llm/routing-provider.test.js +0 -94
  671. package/dist/core/llm/routing-provider.test.js.map +0 -1
  672. package/dist/core/llm/snapshot-middleware.test.d.ts +0 -2
  673. package/dist/core/llm/snapshot-middleware.test.d.ts.map +0 -1
  674. package/dist/core/llm/snapshot-middleware.test.js +0 -144
  675. package/dist/core/llm/snapshot-middleware.test.js.map +0 -1
  676. package/dist/core/llm/thinking-blocks.test.d.ts +0 -2
  677. package/dist/core/llm/thinking-blocks.test.d.ts.map +0 -1
  678. package/dist/core/llm/thinking-blocks.test.js +0 -291
  679. package/dist/core/llm/thinking-blocks.test.js.map +0 -1
  680. package/dist/core/multi-agent.integration.test.d.ts +0 -2
  681. package/dist/core/multi-agent.integration.test.d.ts.map +0 -1
  682. package/dist/core/multi-agent.integration.test.js +0 -298
  683. package/dist/core/multi-agent.integration.test.js.map +0 -1
  684. package/dist/core/plugin-hooks.integration.test.d.ts +0 -2
  685. package/dist/core/plugin-hooks.integration.test.d.ts.map +0 -1
  686. package/dist/core/plugin-hooks.integration.test.js +0 -458
  687. package/dist/core/plugin-hooks.integration.test.js.map +0 -1
  688. package/dist/core/reasoning-round-trip.integration.test.d.ts +0 -2
  689. package/dist/core/reasoning-round-trip.integration.test.d.ts.map +0 -1
  690. package/dist/core/reasoning-round-trip.integration.test.js +0 -150
  691. package/dist/core/reasoning-round-trip.integration.test.js.map +0 -1
  692. package/dist/core/session-lifecycle.integration.test.d.ts +0 -2
  693. package/dist/core/session-lifecycle.integration.test.d.ts.map +0 -1
  694. package/dist/core/session-lifecycle.integration.test.js +0 -159
  695. package/dist/core/session-lifecycle.integration.test.js.map +0 -1
  696. package/dist/core/sessions/fork-utils.test.d.ts +0 -2
  697. package/dist/core/sessions/fork-utils.test.d.ts.map +0 -1
  698. package/dist/core/sessions/fork-utils.test.js +0 -129
  699. package/dist/core/sessions/fork-utils.test.js.map +0 -1
  700. package/dist/core/sessions/session-overrides.integration.test.d.ts +0 -2
  701. package/dist/core/sessions/session-overrides.integration.test.d.ts.map +0 -1
  702. package/dist/core/sessions/session-overrides.integration.test.js +0 -154
  703. package/dist/core/sessions/session-overrides.integration.test.js.map +0 -1
  704. package/dist/core/sessions/session.test.d.ts +0 -2
  705. package/dist/core/sessions/session.test.d.ts.map +0 -1
  706. package/dist/core/sessions/session.test.js +0 -1100
  707. package/dist/core/sessions/session.test.js.map +0 -1
  708. package/dist/core/tools/executor.test.d.ts +0 -2
  709. package/dist/core/tools/executor.test.d.ts.map +0 -1
  710. package/dist/core/tools/executor.test.js +0 -144
  711. package/dist/core/tools/executor.test.js.map +0 -1
  712. package/dist/lib/logger/console.test.d.ts +0 -2
  713. package/dist/lib/logger/console.test.d.ts.map +0 -1
  714. package/dist/lib/logger/console.test.js +0 -258
  715. package/dist/lib/logger/console.test.js.map +0 -1
  716. package/dist/lib/never.d.ts +0 -2
  717. package/dist/lib/never.d.ts.map +0 -1
  718. package/dist/lib/never.js +0 -4
  719. package/dist/lib/never.js.map +0 -1
  720. package/dist/lib/utils/concurrency.test.d.ts +0 -2
  721. package/dist/lib/utils/concurrency.test.d.ts.map +0 -1
  722. package/dist/lib/utils/concurrency.test.js +0 -135
  723. package/dist/lib/utils/concurrency.test.js.map +0 -1
  724. package/dist/plugins/agent-status/agent-status.test.d.ts +0 -2
  725. package/dist/plugins/agent-status/agent-status.test.d.ts.map +0 -1
  726. package/dist/plugins/agent-status/agent-status.test.js +0 -136
  727. package/dist/plugins/agent-status/agent-status.test.js.map +0 -1
  728. package/dist/plugins/agents/agents.integration.test.d.ts +0 -2
  729. package/dist/plugins/agents/agents.integration.test.d.ts.map +0 -1
  730. package/dist/plugins/agents/agents.integration.test.js +0 -830
  731. package/dist/plugins/agents/agents.integration.test.js.map +0 -1
  732. package/dist/plugins/agents/supervision.integration.test.d.ts +0 -2
  733. package/dist/plugins/agents/supervision.integration.test.d.ts.map +0 -1
  734. package/dist/plugins/agents/supervision.integration.test.js +0 -215
  735. package/dist/plugins/agents/supervision.integration.test.js.map +0 -1
  736. package/dist/plugins/context-compact/compaction-live.test.d.ts +0 -17
  737. package/dist/plugins/context-compact/compaction-live.test.d.ts.map +0 -1
  738. package/dist/plugins/context-compact/compaction-live.test.js +0 -177
  739. package/dist/plugins/context-compact/compaction-live.test.js.map +0 -1
  740. package/dist/plugins/context-compact/context-compact.integration.test.d.ts +0 -2
  741. package/dist/plugins/context-compact/context-compact.integration.test.d.ts.map +0 -1
  742. package/dist/plugins/context-compact/context-compact.integration.test.js +0 -364
  743. package/dist/plugins/context-compact/context-compact.integration.test.js.map +0 -1
  744. package/dist/plugins/context-compact/context-compactor.test.d.ts +0 -2
  745. package/dist/plugins/context-compact/context-compactor.test.d.ts.map +0 -1
  746. package/dist/plugins/context-compact/context-compactor.test.js +0 -858
  747. package/dist/plugins/context-compact/context-compactor.test.js.map +0 -1
  748. package/dist/plugins/context-compact/history-offloader.test.d.ts +0 -2
  749. package/dist/plugins/context-compact/history-offloader.test.d.ts.map +0 -1
  750. package/dist/plugins/context-compact/history-offloader.test.js +0 -77
  751. package/dist/plugins/context-compact/history-offloader.test.js.map +0 -1
  752. package/dist/plugins/filesystem/filesystem.integration.test.d.ts +0 -2
  753. package/dist/plugins/filesystem/filesystem.integration.test.d.ts.map +0 -1
  754. package/dist/plugins/filesystem/filesystem.integration.test.js +0 -447
  755. package/dist/plugins/filesystem/filesystem.integration.test.js.map +0 -1
  756. package/dist/plugins/filesystem/schema.d.ts +0 -7
  757. package/dist/plugins/filesystem/schema.d.ts.map +0 -1
  758. package/dist/plugins/filesystem/schema.js +0 -2
  759. package/dist/plugins/filesystem/schema.js.map +0 -1
  760. package/dist/plugins/limits-guard/limit-guard.test.d.ts +0 -2
  761. package/dist/plugins/limits-guard/limit-guard.test.d.ts.map +0 -1
  762. package/dist/plugins/limits-guard/limit-guard.test.js +0 -194
  763. package/dist/plugins/limits-guard/limit-guard.test.js.map +0 -1
  764. package/dist/plugins/limits-guard/limits-guard.integration.test.d.ts +0 -2
  765. package/dist/plugins/limits-guard/limits-guard.integration.test.d.ts.map +0 -1
  766. package/dist/plugins/limits-guard/limits-guard.integration.test.js +0 -887
  767. package/dist/plugins/limits-guard/limits-guard.integration.test.js.map +0 -1
  768. package/dist/plugins/llm-debug/llm-debug.integration.test.d.ts +0 -2
  769. package/dist/plugins/llm-debug/llm-debug.integration.test.d.ts.map +0 -1
  770. package/dist/plugins/llm-debug/llm-debug.integration.test.js +0 -157
  771. package/dist/plugins/llm-debug/llm-debug.integration.test.js.map +0 -1
  772. package/dist/plugins/mailbox/mailbox.integration.test.d.ts +0 -2
  773. package/dist/plugins/mailbox/mailbox.integration.test.d.ts.map +0 -1
  774. package/dist/plugins/mailbox/mailbox.integration.test.js +0 -678
  775. package/dist/plugins/mailbox/mailbox.integration.test.js.map +0 -1
  776. package/dist/plugins/resources/resources.integration.test.d.ts +0 -2
  777. package/dist/plugins/resources/resources.integration.test.d.ts.map +0 -1
  778. package/dist/plugins/resources/resources.integration.test.js +0 -62
  779. package/dist/plugins/resources/resources.integration.test.js.map +0 -1
  780. package/dist/plugins/result-eviction/result-eviction.integration.test.d.ts +0 -2
  781. package/dist/plugins/result-eviction/result-eviction.integration.test.d.ts.map +0 -1
  782. package/dist/plugins/result-eviction/result-eviction.integration.test.js +0 -294
  783. package/dist/plugins/result-eviction/result-eviction.integration.test.js.map +0 -1
  784. package/dist/plugins/services/pid-registry.test.d.ts +0 -2
  785. package/dist/plugins/services/pid-registry.test.d.ts.map +0 -1
  786. package/dist/plugins/services/pid-registry.test.js +0 -161
  787. package/dist/plugins/services/pid-registry.test.js.map +0 -1
  788. package/dist/plugins/services/services.integration.test.d.ts +0 -2
  789. package/dist/plugins/services/services.integration.test.d.ts.map +0 -1
  790. package/dist/plugins/services/services.integration.test.js +0 -1057
  791. package/dist/plugins/services/services.integration.test.js.map +0 -1
  792. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.d.ts +0 -2
  793. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.d.ts.map +0 -1
  794. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js +0 -498
  795. package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js.map +0 -1
  796. package/dist/plugins/session-stats/plugin.test.d.ts +0 -2
  797. package/dist/plugins/session-stats/plugin.test.d.ts.map +0 -1
  798. package/dist/plugins/session-stats/plugin.test.js +0 -145
  799. package/dist/plugins/session-stats/plugin.test.js.map +0 -1
  800. package/dist/plugins/shell/shell.integration.test.d.ts +0 -2
  801. package/dist/plugins/shell/shell.integration.test.d.ts.map +0 -1
  802. package/dist/plugins/shell/shell.integration.test.js +0 -234
  803. package/dist/plugins/shell/shell.integration.test.js.map +0 -1
  804. package/dist/plugins/shell/shell.test.d.ts +0 -2
  805. package/dist/plugins/shell/shell.test.d.ts.map +0 -1
  806. package/dist/plugins/shell/shell.test.js +0 -236
  807. package/dist/plugins/shell/shell.test.js.map +0 -1
  808. package/dist/plugins/skills/discovery.test.d.ts +0 -2
  809. package/dist/plugins/skills/discovery.test.d.ts.map +0 -1
  810. package/dist/plugins/skills/discovery.test.js +0 -312
  811. package/dist/plugins/skills/discovery.test.js.map +0 -1
  812. package/dist/plugins/skills/skills.integration.test.d.ts +0 -2
  813. package/dist/plugins/skills/skills.integration.test.d.ts.map +0 -1
  814. package/dist/plugins/skills/skills.integration.test.js +0 -475
  815. package/dist/plugins/skills/skills.integration.test.js.map +0 -1
  816. package/dist/plugins/todo/todo.integration.test.d.ts +0 -2
  817. package/dist/plugins/todo/todo.integration.test.d.ts.map +0 -1
  818. package/dist/plugins/todo/todo.integration.test.js +0 -605
  819. package/dist/plugins/todo/todo.integration.test.js.map +0 -1
  820. package/dist/plugins/uploads/preprocessors/image-classifier.test.d.ts +0 -2
  821. package/dist/plugins/uploads/preprocessors/image-classifier.test.d.ts.map +0 -1
  822. package/dist/plugins/uploads/preprocessors/image-classifier.test.js +0 -113
  823. package/dist/plugins/uploads/preprocessors/image-classifier.test.js.map +0 -1
  824. package/dist/plugins/uploads/uploads.integration.test.d.ts +0 -2
  825. package/dist/plugins/uploads/uploads.integration.test.d.ts.map +0 -1
  826. package/dist/plugins/uploads/uploads.integration.test.js +0 -639
  827. package/dist/plugins/uploads/uploads.integration.test.js.map +0 -1
  828. package/dist/plugins/user-chat/user-chat.integration.test.d.ts +0 -2
  829. package/dist/plugins/user-chat/user-chat.integration.test.d.ts.map +0 -1
  830. package/dist/plugins/user-chat/user-chat.integration.test.js +0 -754
  831. package/dist/plugins/user-chat/user-chat.integration.test.js.map +0 -1
  832. package/dist/plugins/workers/workers.integration.test.d.ts +0 -2
  833. package/dist/plugins/workers/workers.integration.test.d.ts.map +0 -1
  834. package/dist/plugins/workers/workers.integration.test.js +0 -629
  835. package/dist/plugins/workers/workers.integration.test.js.map +0 -1
  836. package/dist/prompts/macros.test.d.ts +0 -2
  837. package/dist/prompts/macros.test.d.ts.map +0 -1
  838. package/dist/prompts/macros.test.js +0 -80
  839. package/dist/prompts/macros.test.js.map +0 -1
  840. package/dist/testing/test-harness.test.d.ts +0 -2
  841. package/dist/testing/test-harness.test.d.ts.map +0 -1
  842. package/dist/testing/test-harness.test.js +0 -92
  843. package/dist/testing/test-harness.test.js.map +0 -1
  844. package/dist/transport/http/routes/rpc.integration.test.d.ts +0 -2
  845. package/dist/transport/http/routes/rpc.integration.test.d.ts.map +0 -1
  846. package/dist/transport/http/routes/rpc.integration.test.js +0 -189
  847. package/dist/transport/http/routes/rpc.integration.test.js.map +0 -1
  848. package/dist/transport/http/routes/rpc.test.d.ts +0 -2
  849. package/dist/transport/http/routes/rpc.test.d.ts.map +0 -1
  850. package/dist/transport/http/routes/rpc.test.js +0 -316
  851. package/dist/transport/http/routes/rpc.test.js.map +0 -1
  852. package/src/config.test.ts +0 -174
  853. package/src/core/agent-loop.integration.test.ts +0 -503
  854. package/src/core/agents/agent-config.test.ts +0 -240
  855. package/src/core/agents/agent-shutdown.test.ts +0 -236
  856. package/src/core/agents/agent.test.ts +0 -387
  857. package/src/core/agents/communicator.ts +0 -16
  858. package/src/core/agents/handler-events.test.ts +0 -167
  859. package/src/core/agents/response-sanitizer.test.ts +0 -137
  860. package/src/core/error-recovery.integration.test.ts +0 -343
  861. package/src/core/event-sourcing.integration.test.ts +0 -191
  862. package/src/core/events/file.test.ts +0 -436
  863. package/src/core/events/memory.test.ts +0 -741
  864. package/src/core/image/image-processor.test.ts +0 -218
  865. package/src/core/image/vips-resizer.test.ts +0 -388
  866. package/src/core/llm/anthropic.test.ts +0 -546
  867. package/src/core/llm/cache-breakpoints.test.ts +0 -85
  868. package/src/core/llm/cache-live.test.ts +0 -155
  869. package/src/core/llm/logger.test.ts +0 -270
  870. package/src/core/llm/message-sanitization.test.ts +0 -217
  871. package/src/core/llm/mock.test.ts +0 -402
  872. package/src/core/llm/model-shortlist-live.test.ts +0 -577
  873. package/src/core/llm/openrouter-mapping.test.ts +0 -234
  874. package/src/core/llm/openrouter.test.ts +0 -499
  875. package/src/core/llm/provider-integration.test.ts +0 -408
  876. package/src/core/llm/reasoning-details.test.ts +0 -177
  877. package/src/core/llm/routing-provider.test.ts +0 -113
  878. package/src/core/llm/snapshot-middleware.test.ts +0 -168
  879. package/src/core/llm/thinking-blocks.test.ts +0 -345
  880. package/src/core/multi-agent.integration.test.ts +0 -340
  881. package/src/core/plugin-hooks.integration.test.ts +0 -568
  882. package/src/core/reasoning-round-trip.integration.test.ts +0 -188
  883. package/src/core/session-lifecycle.integration.test.ts +0 -202
  884. package/src/core/sessions/fork-utils.test.ts +0 -158
  885. package/src/core/sessions/session-overrides.integration.test.ts +0 -210
  886. package/src/core/sessions/session.test.ts +0 -1736
  887. package/src/core/tools/executor.test.ts +0 -161
  888. package/src/lib/logger/console.test.ts +0 -348
  889. package/src/lib/never.ts +0 -3
  890. package/src/lib/utils/concurrency.test.ts +0 -169
  891. package/src/plugins/agent-status/agent-status.test.ts +0 -164
  892. package/src/plugins/agents/agents.integration.test.ts +0 -976
  893. package/src/plugins/agents/supervision.integration.test.ts +0 -249
  894. package/src/plugins/context-compact/compaction-live.test.ts +0 -221
  895. package/src/plugins/context-compact/context-compact.integration.test.ts +0 -418
  896. package/src/plugins/context-compact/context-compactor.test.ts +0 -1036
  897. package/src/plugins/context-compact/history-offloader.test.ts +0 -100
  898. package/src/plugins/filesystem/filesystem.integration.test.ts +0 -529
  899. package/src/plugins/filesystem/schema.ts +0 -6
  900. package/src/plugins/limits-guard/limit-guard.test.ts +0 -253
  901. package/src/plugins/limits-guard/limits-guard.integration.test.ts +0 -1012
  902. package/src/plugins/llm-debug/llm-debug.integration.test.ts +0 -192
  903. package/src/plugins/mailbox/mailbox.integration.test.ts +0 -794
  904. package/src/plugins/resources/resources.integration.test.ts +0 -68
  905. package/src/plugins/result-eviction/result-eviction.integration.test.ts +0 -332
  906. package/src/plugins/services/pid-registry.test.ts +0 -180
  907. package/src/plugins/services/services.integration.test.ts +0 -1223
  908. package/src/plugins/session-lifecycle/session-lifecycle.integration.test.ts +0 -639
  909. package/src/plugins/session-stats/plugin.test.ts +0 -160
  910. package/src/plugins/shell/shell.integration.test.ts +0 -273
  911. package/src/plugins/shell/shell.test.ts +0 -314
  912. package/src/plugins/skills/discovery.test.ts +0 -395
  913. package/src/plugins/skills/skills.integration.test.ts +0 -580
  914. package/src/plugins/todo/todo.integration.test.ts +0 -687
  915. package/src/plugins/uploads/preprocessors/image-classifier.test.ts +0 -142
  916. package/src/plugins/uploads/uploads.integration.test.ts +0 -831
  917. package/src/plugins/user-chat/user-chat.integration.test.ts +0 -893
  918. package/src/plugins/workers/workers.integration.test.ts +0 -778
  919. package/src/prompts/macros.test.ts +0 -107
  920. package/src/testing/test-harness.test.ts +0 -114
  921. package/src/transport/http/routes/rpc.integration.test.ts +0 -243
  922. package/src/transport/http/routes/rpc.test.ts +0 -372
@@ -0,0 +1,1463 @@
1
+ /**
2
+ * Executable contract for `Platform`.
3
+ *
4
+ * The ports are interfaces with doc comments, and until now the only thing that
5
+ * said whether an implementation honoured them was the SDK's own test suite —
6
+ * which a host that cannot run the SDK cannot run either. This is the contract
7
+ * on its own: a list of checks, each asserting one clause the doc comments
8
+ * state, runnable against any `Platform`.
9
+ *
10
+ * The checks are data, not `test()` calls. `runPlatformConformance` maps them
11
+ * onto `bun:test`; anything else can execute one directly with
12
+ * `runConformanceCheck` — which is how the suite is itself proven able to fail.
13
+ *
14
+ * Optional ports are probed, never assumed, and a port that is not exercised is
15
+ * reported with the reason. A host that answers nothing does not pass quietly.
16
+ */
17
+ import { describe, expect, test } from 'bun:test';
18
+ import { sleep } from '../lib/utils/sleep.js';
19
+ import { isLiveScheduler } from '../platform/index.js';
20
+ /**
21
+ * What a `git` fixture repository must contain, however it is built:
22
+ * branch `feature`, one commit ahead of `main`, HEAD's subject line `second`,
23
+ * a tracked `note.txt` modified in the worktree, and an untracked `scratch.txt`.
24
+ */
25
+ export const GIT_FIXTURE = {
26
+ branch: 'feature',
27
+ base: 'main',
28
+ headSubject: 'second',
29
+ baseSubject: 'first',
30
+ modified: 'note.txt',
31
+ untracked: 'scratch.txt',
32
+ };
33
+ /** Report order, and the order sections are declared in. */
34
+ export const CONFORMANCE_PORTS = [
35
+ 'fs',
36
+ 'fs.symlinks',
37
+ 'fs.walk',
38
+ 'fs.readFiles',
39
+ 'fs.writeFiles',
40
+ 'fs.rmFiles',
41
+ 'fs.scopeReads',
42
+ 'scheduler',
43
+ 'scheduler.live',
44
+ 'shell',
45
+ 'shell.paths',
46
+ 'shell.host',
47
+ 'git',
48
+ 'fsRevision',
49
+ 'fsRevision.numbered',
50
+ 'sessionLog',
51
+ 'llmCallLog',
52
+ ];
53
+ /** The five optional `FileSystem` members, as the port names that report them. */
54
+ const OPTIONAL_FS_VERBS = [
55
+ 'walk',
56
+ 'readFiles',
57
+ 'writeFiles',
58
+ 'rmFiles',
59
+ 'scopeReads',
60
+ ];
61
+ // ============================================================================
62
+ // Helpers
63
+ // ============================================================================
64
+ function joinPath(base, segments) {
65
+ const tail = segments.filter((segment) => segment.length > 0).join('/');
66
+ if (tail.length === 0)
67
+ return base;
68
+ return base.endsWith('/') ? `${base}${tail}` : `${base}/${tail}`;
69
+ }
70
+ function baseName(path) {
71
+ const cut = path.lastIndexOf('/');
72
+ return cut === -1 ? path : path.slice(cut + 1);
73
+ }
74
+ function relativeTo(base, path) {
75
+ return path.startsWith(base) ? path.slice(base.length).replace(/^\/+/, '') : path;
76
+ }
77
+ /** The rejection, or a failure of its own when the promise resolved. */
78
+ async function rejection(promise, what) {
79
+ try {
80
+ await promise;
81
+ }
82
+ catch (error) {
83
+ return error;
84
+ }
85
+ throw new Error(`expected ${what} to reject, but it resolved`);
86
+ }
87
+ /** The code `readFile` threw, the way `ReadFilesEntry.error` names it. */
88
+ function errorCode(error) {
89
+ if (error instanceof Error && 'code' in error && typeof error.code === 'string')
90
+ return error.code;
91
+ return undefined;
92
+ }
93
+ async function waitUntil(predicate, timeoutMs, what) {
94
+ const deadline = Date.now() + timeoutMs;
95
+ while (Date.now() < deadline) {
96
+ if (predicate())
97
+ return;
98
+ await sleep(5);
99
+ }
100
+ throw new Error(`${what} did not happen within ${timeoutMs}ms`);
101
+ }
102
+ const textOf = (bytes) => Buffer.from(bytes).toString('utf-8');
103
+ function walkShapes(base, entries) {
104
+ return entries
105
+ .map((entry) => ({ path: relativeTo(base, entry.path), type: entry.type, size: entry.size }))
106
+ .sort((a, b) => a.path.localeCompare(b.path));
107
+ }
108
+ /**
109
+ * The readdir-and-stat walk `FileSystem.walk` replaces, following the same
110
+ * rules — classifying from the dirent so a link is a link, but sizing from
111
+ * `stat`, which follows it. A broken link is listed at 0, not dropped.
112
+ */
113
+ async function walkWithReaddir(fs, dir, options) {
114
+ const excluded = new Set(options?.exclude ?? []);
115
+ const out = [];
116
+ const descend = async (current, depthLeft) => {
117
+ for (const dirent of await fs.readdir(current, { withFileTypes: true })) {
118
+ if (options?.limit !== undefined && out.length >= options.limit)
119
+ return;
120
+ if (excluded.has(dirent.name))
121
+ continue;
122
+ if (options?.excludeHidden && dirent.name.startsWith('.'))
123
+ continue;
124
+ const path = joinPath(current, [dirent.name]);
125
+ const type = dirent.isSymbolicLink()
126
+ ? 'symlink'
127
+ : dirent.isDirectory()
128
+ ? 'directory'
129
+ : dirent.isFile()
130
+ ? 'file'
131
+ : 'other';
132
+ const stats = await fs.stat(path).then((value) => value, () => undefined);
133
+ const size = type === 'directory' ? 0 : stats?.size ?? 0;
134
+ const mtime = stats?.mtimeMs ?? 0;
135
+ out.push({ path, type, size, mtime });
136
+ if (type === 'directory' && depthLeft > 1)
137
+ await descend(path, depthLeft - 1);
138
+ }
139
+ };
140
+ await descend(dir, options?.depth ?? Number.POSITIVE_INFINITY);
141
+ return out;
142
+ }
143
+ /** The per-path `readFile` loop `FileSystem.readFiles` replaces. */
144
+ async function readFilesWithLoop(fs, paths) {
145
+ return Promise.all(paths.map(async (path) => {
146
+ try {
147
+ return { path, content: await fs.readFile(path) };
148
+ }
149
+ catch (error) {
150
+ return { path, error: errorCode(error) ?? String(error) };
151
+ }
152
+ }));
153
+ }
154
+ /**
155
+ * A small tree the walk checks read: two levels, a hidden file, a hidden
156
+ * directory with a child, and a directory the `exclude` check names.
157
+ */
158
+ async function buildWalkTree(ctx) {
159
+ const { platform, path } = ctx;
160
+ const fs = platform.fs;
161
+ await fs.mkdir(path('sub', 'deep'), { recursive: true });
162
+ await fs.mkdir(path('skipme'), { recursive: true });
163
+ await fs.mkdir(path('.hiddendir'), { recursive: true });
164
+ await fs.writeFile(path('a.txt'), 'aaa');
165
+ await fs.writeFile(path('.hidden.txt'), 'h');
166
+ await fs.writeFile(path('sub', 'b.txt'), 'bb');
167
+ await fs.writeFile(path('sub', 'deep', 'c.txt'), 'cccc');
168
+ await fs.writeFile(path('skipme', 'd.txt'), 'd');
169
+ await fs.writeFile(path('.hiddendir', 'inside.txt'), 'i');
170
+ }
171
+ async function defaultSymlink(instance, targetPath, linkPath) {
172
+ await instance.platform.process.execFile('ln', ['-s', targetPath, linkPath], {});
173
+ }
174
+ async function defaultBuildGitRepo(instance, dir) {
175
+ const { platform } = instance;
176
+ const identity = ['-c', 'user.email=conformance@roj.local', '-c', 'user.name=Roj Conformance', '-c', 'commit.gpgsign=false'];
177
+ const git = async (...args) => {
178
+ await platform.process.execFile('git', [...identity, ...args], { cwd: dir });
179
+ };
180
+ await platform.fs.mkdir(dir, { recursive: true });
181
+ await git('init', '-q', '-b', GIT_FIXTURE.base);
182
+ await platform.fs.writeFile(joinPath(dir, [GIT_FIXTURE.modified]), 'one\n');
183
+ await git('add', GIT_FIXTURE.modified);
184
+ await git('commit', '-q', '-m', GIT_FIXTURE.baseSubject);
185
+ await git('checkout', '-q', '-b', GIT_FIXTURE.branch);
186
+ await platform.fs.writeFile(joinPath(dir, [GIT_FIXTURE.modified]), 'two\n');
187
+ await git('commit', '-q', '-a', '-m', `${GIT_FIXTURE.headSubject}\n\nbody paragraph\n`);
188
+ await platform.fs.writeFile(joinPath(dir, [GIT_FIXTURE.modified]), 'three\n');
189
+ await platform.fs.writeFile(joinPath(dir, [GIT_FIXTURE.untracked]), 'scratch\n');
190
+ }
191
+ function contextFor(target, instance) {
192
+ let repo;
193
+ return {
194
+ platform: instance.platform,
195
+ root: instance.root,
196
+ path: (...segments) => joinPath(instance.root, segments),
197
+ symlink: (targetPath, linkPath) => (target.symlink ?? defaultSymlink)(instance, targetPath, linkPath),
198
+ gitRepo: () => {
199
+ if (!repo) {
200
+ const dir = joinPath(instance.root, ['fixture-repo']);
201
+ const build = target.buildGitRepo ?? defaultBuildGitRepo;
202
+ repo = build(instance, dir).then(() => dir, (error) => {
203
+ throw new Error(`could not build the fixture repository: ${error instanceof Error ? error.message : String(error)}. `
204
+ + 'A host that cannot run `git` supplies ConformanceTarget.buildGitRepo.');
205
+ });
206
+ }
207
+ return repo;
208
+ },
209
+ };
210
+ }
211
+ // ============================================================================
212
+ // fs — the required surface
213
+ // ============================================================================
214
+ const fsChecks = [
215
+ {
216
+ port: 'fs',
217
+ name: 'writeFile and readFile round-trip text and bytes',
218
+ async run({ platform, path }) {
219
+ const fs = platform.fs;
220
+ const file = path('note.txt');
221
+ await fs.writeFile(file, 'héllo\n');
222
+ expect(await fs.readFile(file, 'utf-8')).toBe('héllo\n');
223
+ expect(textOf(await fs.readFile(file))).toBe('héllo\n');
224
+ const binary = path('bytes.bin');
225
+ await fs.writeFile(binary, new Uint8Array([0, 1, 254, 255]));
226
+ expect([...Buffer.from(await fs.readFile(binary))]).toEqual([0, 1, 254, 255]);
227
+ await fs.writeFile(file, 'replaced');
228
+ expect(await fs.readFile(file, 'utf-8')).toBe('replaced');
229
+ },
230
+ },
231
+ {
232
+ port: 'fs',
233
+ name: 'readFile rejects for a missing path',
234
+ async run({ platform, path }) {
235
+ await rejection(platform.fs.readFile(path('missing.txt')), 'readFile of a missing path');
236
+ },
237
+ },
238
+ {
239
+ port: 'fs',
240
+ name: 'appendFile creates a file and adds to the end of one',
241
+ async run({ platform, path }) {
242
+ const fs = platform.fs;
243
+ const file = path('log.txt');
244
+ await fs.appendFile(file, 'one\n');
245
+ await fs.appendFile(file, 'two\n');
246
+ expect(await fs.readFile(file, 'utf-8')).toBe('one\ntwo\n');
247
+ },
248
+ },
249
+ {
250
+ port: 'fs',
251
+ name: 'mkdir creates one level, and the whole chain when recursive',
252
+ async run({ platform, path }) {
253
+ const fs = platform.fs;
254
+ await fs.mkdir(path('one'));
255
+ expect(await fs.exists(path('one'))).toBe(true);
256
+ await rejection(fs.mkdir(path('deep', 'chain')), 'mkdir of a path whose parent is missing');
257
+ await fs.mkdir(path('deep', 'chain'), { recursive: true });
258
+ expect(await fs.exists(path('deep', 'chain'))).toBe(true);
259
+ await rejection(fs.mkdir(path('one')), 'mkdir of an existing directory');
260
+ await fs.mkdir(path('one'), { recursive: true });
261
+ },
262
+ },
263
+ {
264
+ port: 'fs',
265
+ name: 'readdir lists names, and classifies them with withFileTypes',
266
+ async run({ platform, path }) {
267
+ const fs = platform.fs;
268
+ await fs.mkdir(path('dir'), { recursive: true });
269
+ await fs.writeFile(path('a.txt'), 'a');
270
+ await fs.writeFile(path('b.txt'), 'b');
271
+ expect((await fs.readdir(path())).sort()).toEqual(['a.txt', 'b.txt', 'dir']);
272
+ const dirents = await fs.readdir(path(), { withFileTypes: true });
273
+ const classified = dirents
274
+ .map((dirent) => [dirent.name, dirent.isDirectory(), dirent.isFile()])
275
+ .sort();
276
+ expect(classified).toEqual([
277
+ ['a.txt', false, true],
278
+ ['b.txt', false, true],
279
+ ['dir', true, false],
280
+ ]);
281
+ await rejection(fs.readdir(path('missing')), 'readdir of a missing directory');
282
+ },
283
+ },
284
+ {
285
+ port: 'fs',
286
+ name: 'stat reports size, kind and a modification time',
287
+ async run({ platform, path }) {
288
+ const fs = platform.fs;
289
+ await fs.writeFile(path('a.txt'), 'abcde');
290
+ await fs.mkdir(path('dir'), { recursive: true });
291
+ const file = await fs.stat(path('a.txt'));
292
+ expect(file.size).toBe(5);
293
+ expect(file.isFile()).toBe(true);
294
+ expect(file.isDirectory()).toBe(false);
295
+ expect(Number.isFinite(file.mtimeMs)).toBe(true);
296
+ const dir = await fs.stat(path('dir'));
297
+ expect(dir.isDirectory()).toBe(true);
298
+ await rejection(fs.stat(path('missing')), 'stat of a missing path');
299
+ },
300
+ },
301
+ {
302
+ port: 'fs',
303
+ name: 'lstat answers as stat does for a path that is not a link',
304
+ async run({ platform, path }) {
305
+ const fs = platform.fs;
306
+ await fs.writeFile(path('a.txt'), 'abcde');
307
+ await fs.mkdir(path('dir'), { recursive: true });
308
+ const file = await fs.lstat(path('a.txt'));
309
+ expect(file.size).toBe(5);
310
+ expect(file.isFile()).toBe(true);
311
+ expect(file.isSymbolicLink()).toBe(false);
312
+ expect((await fs.lstat(path('dir'))).isDirectory()).toBe(true);
313
+ await rejection(fs.lstat(path('missing')), 'lstat of a missing path');
314
+ },
315
+ },
316
+ {
317
+ port: 'fs',
318
+ needs: ['fs.symlinks'],
319
+ name: 'lstat reports the link itself, and answers for one whose target is gone',
320
+ async run({ platform, path, symlink }) {
321
+ const fs = platform.fs;
322
+ await fs.writeFile(path('target.txt'), 'target');
323
+ await symlink(path('target.txt'), path('link.txt'));
324
+ await symlink(path('nowhere.txt'), path('broken.link'));
325
+ expect((await fs.lstat(path('link.txt'))).isSymbolicLink()).toBe(true);
326
+ expect((await fs.lstat(path('link.txt'))).isFile()).toBe(false);
327
+ expect((await fs.stat(path('link.txt'))).isFile()).toBe(true);
328
+ // The pair a caller separates "not there" from "there but unresolvable" on.
329
+ expect((await fs.lstat(path('broken.link'))).isSymbolicLink()).toBe(true);
330
+ await rejection(fs.realpath(path('broken.link')), 'realpath of a broken link');
331
+ },
332
+ },
333
+ {
334
+ port: 'fs',
335
+ name: 'access resolves for a reachable path and rejects for a missing one',
336
+ async run({ platform, path }) {
337
+ const fs = platform.fs;
338
+ await fs.writeFile(path('a.txt'), 'a');
339
+ await fs.access(path('a.txt'));
340
+ await fs.access(path());
341
+ await rejection(fs.access(path('missing')), 'access of a missing path');
342
+ },
343
+ },
344
+ {
345
+ port: 'fs',
346
+ name: 'exists never throws for a missing path',
347
+ async run({ platform, path }) {
348
+ const fs = platform.fs;
349
+ await fs.writeFile(path('a.txt'), 'a');
350
+ await fs.mkdir(path('dir'), { recursive: true });
351
+ expect(await fs.exists(path('a.txt'))).toBe(true);
352
+ expect(await fs.exists(path('dir'))).toBe(true);
353
+ expect(await fs.exists(path('missing.txt'))).toBe(false);
354
+ // The parent is missing too — the corner that makes a stat-and-rethrow fail.
355
+ expect(await fs.exists(path('no', 'such', 'parent', 'file.txt'))).toBe(false);
356
+ },
357
+ },
358
+ {
359
+ port: 'fs',
360
+ name: 'unlink removes one file and rejects for a missing one',
361
+ async run({ platform, path }) {
362
+ const fs = platform.fs;
363
+ await fs.writeFile(path('a.txt'), 'a');
364
+ await fs.unlink(path('a.txt'));
365
+ expect(await fs.exists(path('a.txt'))).toBe(false);
366
+ await rejection(fs.unlink(path('a.txt')), 'unlink of a missing path');
367
+ },
368
+ },
369
+ {
370
+ port: 'fs',
371
+ name: 'rm with force is a no-op on a missing path, and rejects without it',
372
+ async run({ platform, path }) {
373
+ const fs = platform.fs;
374
+ await fs.rm(path('missing.txt'), { force: true });
375
+ await fs.rm(path('missing', 'deep'), { recursive: true, force: true });
376
+ await rejection(fs.rm(path('missing.txt')), 'rm of a missing path without force');
377
+ },
378
+ },
379
+ {
380
+ port: 'fs',
381
+ name: 'rm removes a populated directory only when recursive',
382
+ async run({ platform, path }) {
383
+ const fs = platform.fs;
384
+ await fs.mkdir(path('tree', 'nested'), { recursive: true });
385
+ await fs.writeFile(path('tree', 'nested', 'leaf.txt'), 'leaf');
386
+ await rejection(fs.rm(path('tree')), 'rm of a populated directory without recursive');
387
+ expect(await fs.exists(path('tree', 'nested', 'leaf.txt'))).toBe(true);
388
+ await fs.rm(path('tree'), { recursive: true });
389
+ expect(await fs.exists(path('tree'))).toBe(false);
390
+ },
391
+ },
392
+ {
393
+ port: 'fs',
394
+ name: 'cp copies a file, and a subtree only when recursive',
395
+ async run({ platform, path }) {
396
+ const fs = platform.fs;
397
+ await fs.mkdir(path('src', 'nested'), { recursive: true });
398
+ await fs.writeFile(path('src', 'top.txt'), 'top');
399
+ await fs.writeFile(path('src', 'nested', 'leaf.txt'), 'leaf');
400
+ await fs.cp(path('src', 'top.txt'), path('copy.txt'));
401
+ expect(await fs.readFile(path('copy.txt'), 'utf-8')).toBe('top');
402
+ await rejection(fs.cp(path('src'), path('flat')), 'cp of a directory without recursive');
403
+ await fs.cp(path('src'), path('deep'), { recursive: true });
404
+ expect(await fs.readFile(path('deep', 'top.txt'), 'utf-8')).toBe('top');
405
+ expect(await fs.readFile(path('deep', 'nested', 'leaf.txt'), 'utf-8')).toBe('leaf');
406
+ },
407
+ },
408
+ {
409
+ port: 'fs',
410
+ name: 'realpath returns a canonical path it can be asked for again',
411
+ async run({ platform, path }) {
412
+ const fs = platform.fs;
413
+ await fs.writeFile(path('a.txt'), 'a');
414
+ const canonical = await fs.realpath(path('a.txt'));
415
+ expect(baseName(canonical)).toBe('a.txt');
416
+ expect(await fs.exists(canonical)).toBe(true);
417
+ expect(await fs.realpath(canonical)).toBe(canonical);
418
+ await rejection(fs.realpath(path('missing.txt')), 'realpath of a missing path');
419
+ },
420
+ },
421
+ {
422
+ port: 'fs',
423
+ needs: ['fs.symlinks'],
424
+ name: 'realpath resolves a symlink to its target',
425
+ async run({ platform, path, symlink }) {
426
+ const fs = platform.fs;
427
+ await fs.writeFile(path('target.txt'), 'target');
428
+ await symlink(path('target.txt'), path('link.txt'));
429
+ expect(await fs.realpath(path('link.txt'))).toBe(await fs.realpath(path('target.txt')));
430
+ },
431
+ },
432
+ {
433
+ port: 'fs',
434
+ name: 'open returns the positional-read handle subset',
435
+ async run({ platform, path }) {
436
+ const fs = platform.fs;
437
+ const content = 'abcdefghijklmnop';
438
+ await fs.writeFile(path('a.txt'), content);
439
+ const handle = await fs.open(path('a.txt'));
440
+ try {
441
+ expect((await handle.stat()).size).toBe(content.length);
442
+ const buffer = Buffer.alloc(8, 0);
443
+ const read = await handle.read(buffer, 2, 4, 6);
444
+ expect(read.bytesRead).toBe(4);
445
+ expect(Buffer.from(read.buffer).subarray(2, 6).toString('utf-8')).toBe(content.slice(6, 10));
446
+ // A second read at a different position: the handle carries no cursor of its own.
447
+ const again = await handle.read(Buffer.alloc(3, 0), 0, 3, 0);
448
+ expect(Buffer.from(again.buffer).subarray(0, 3).toString('utf-8')).toBe(content.slice(0, 3));
449
+ expect((await handle.read(Buffer.alloc(4, 0), 0, 4, content.length)).bytesRead).toBe(0);
450
+ }
451
+ finally {
452
+ await handle.close();
453
+ }
454
+ },
455
+ },
456
+ {
457
+ port: 'fs',
458
+ name: 'tmpDir names a directory that exists',
459
+ async run({ platform }) {
460
+ expect(platform.tmpDir.length).toBeGreaterThan(0);
461
+ expect(await platform.fs.exists(platform.tmpDir)).toBe(true);
462
+ },
463
+ },
464
+ ];
465
+ // ============================================================================
466
+ // fs — the optional verbs, each against the loop it replaces
467
+ // ============================================================================
468
+ const walkChecks = [
469
+ {
470
+ port: 'fs.walk',
471
+ name: 'agrees with the readdir-and-stat walk it replaces',
472
+ async run(ctx) {
473
+ const fs = ctx.platform.fs;
474
+ await buildWalkTree(ctx);
475
+ const walked = await fs.walk?.(ctx.root);
476
+ const looped = await walkWithReaddir(fs, ctx.root);
477
+ expect(walkShapes(ctx.root, walked ?? [])).toEqual(walkShapes(ctx.root, looped));
478
+ for (const entry of walked ?? []) {
479
+ expect(entry.path.startsWith(ctx.root)).toBe(true);
480
+ expect(Number.isFinite(entry.mtime)).toBe(true);
481
+ // "0 for a directory" — everything else is whatever stat reports.
482
+ if (entry.type === 'directory')
483
+ expect(entry.size).toBe(0);
484
+ }
485
+ },
486
+ },
487
+ {
488
+ port: 'fs.walk',
489
+ name: 'honours depth',
490
+ async run(ctx) {
491
+ const fs = ctx.platform.fs;
492
+ await buildWalkTree(ctx);
493
+ const shallow = walkShapes(ctx.root, (await fs.walk?.(ctx.root, { depth: 1 })) ?? []);
494
+ expect(shallow).toEqual(walkShapes(ctx.root, await walkWithReaddir(fs, ctx.root, { depth: 1 })));
495
+ expect(shallow.map((entry) => entry.path)).not.toContain('sub/b.txt');
496
+ const twoDeep = walkShapes(ctx.root, (await fs.walk?.(ctx.root, { depth: 2 })) ?? []);
497
+ expect(twoDeep.map((entry) => entry.path)).toContain('sub/b.txt');
498
+ expect(twoDeep.map((entry) => entry.path)).not.toContain('sub/deep/c.txt');
499
+ },
500
+ },
501
+ {
502
+ port: 'fs.walk',
503
+ name: 'honours limit, in traversal order',
504
+ async run(ctx) {
505
+ const fs = ctx.platform.fs;
506
+ await buildWalkTree(ctx);
507
+ const all = (await fs.walk?.(ctx.root)) ?? [];
508
+ expect(all.length).toBeGreaterThan(3);
509
+ const limited = (await fs.walk?.(ctx.root, { limit: 3 })) ?? [];
510
+ expect(limited.length).toBe(3);
511
+ // "Entries to return at most, in traversal order" — so a prefix, not a sample.
512
+ expect(limited.map((entry) => entry.path)).toEqual(all.slice(0, 3).map((entry) => entry.path));
513
+ expect(((await fs.walk?.(ctx.root, { limit: all.length + 10 })) ?? []).length).toBe(all.length);
514
+ expect(((await fs.walk?.(ctx.root, { limit: 0 })) ?? []).length).toBe(0);
515
+ },
516
+ },
517
+ {
518
+ port: 'fs.walk',
519
+ name: 'honours exclude, and does not descend into an excluded directory',
520
+ async run(ctx) {
521
+ const fs = ctx.platform.fs;
522
+ await buildWalkTree(ctx);
523
+ const walked = walkShapes(ctx.root, (await fs.walk?.(ctx.root, { exclude: ['skipme'] })) ?? []);
524
+ expect(walked).toEqual(walkShapes(ctx.root, await walkWithReaddir(fs, ctx.root, { exclude: ['skipme'] })));
525
+ expect(walked.map((entry) => entry.path)).not.toContain('skipme');
526
+ expect(walked.map((entry) => entry.path)).not.toContain('skipme/d.txt');
527
+ expect(walked.map((entry) => entry.path)).toContain('a.txt');
528
+ },
529
+ },
530
+ {
531
+ port: 'fs.walk',
532
+ name: 'honours excludeHidden, and does not descend into a hidden directory',
533
+ async run(ctx) {
534
+ const fs = ctx.platform.fs;
535
+ await buildWalkTree(ctx);
536
+ const walked = walkShapes(ctx.root, (await fs.walk?.(ctx.root, { excludeHidden: true })) ?? []);
537
+ expect(walked).toEqual(walkShapes(ctx.root, await walkWithReaddir(fs, ctx.root, { excludeHidden: true })));
538
+ for (const entry of walked) {
539
+ expect(entry.path.split('/').some((segment) => segment.startsWith('.'))).toBe(false);
540
+ }
541
+ expect(walked.map((entry) => entry.path)).toContain('a.txt');
542
+ },
543
+ },
544
+ {
545
+ port: 'fs.walk',
546
+ needs: ['fs.symlinks'],
547
+ name: 'reports a symlink as one, carrying the size stat gives its target',
548
+ async run(ctx) {
549
+ const fs = ctx.platform.fs;
550
+ await fs.writeFile(ctx.path('target.txt'), 'abc');
551
+ await ctx.symlink(ctx.path('target.txt'), ctx.path('good.link'));
552
+ await ctx.symlink(ctx.path('nowhere.txt'), ctx.path('broken.link'));
553
+ const walked = walkShapes(ctx.root, (await fs.walk?.(ctx.root)) ?? []);
554
+ // The loop this replaces stats through the link, so a walk that reported
555
+ // the link's own size would disagree with it for the same tree.
556
+ expect(walked).toEqual([
557
+ { path: 'broken.link', type: 'symlink', size: 0 },
558
+ { path: 'good.link', type: 'symlink', size: 3 },
559
+ { path: 'target.txt', type: 'file', size: 3 },
560
+ ]);
561
+ },
562
+ },
563
+ {
564
+ port: 'fs.walk',
565
+ name: 'rejects for a missing directory, as the readdir it replaces would',
566
+ async run(ctx) {
567
+ await rejection(Promise.resolve(ctx.platform.fs.walk?.(ctx.path('missing'))), 'walk of a missing directory');
568
+ },
569
+ },
570
+ ];
571
+ const readFilesChecks = [
572
+ {
573
+ port: 'fs.readFiles',
574
+ name: 'agrees with the readFile loop it replaces, in the order asked',
575
+ async run({ platform, path }) {
576
+ const fs = platform.fs;
577
+ await fs.mkdir(path('sub'), { recursive: true });
578
+ await fs.writeFile(path('a.txt'), 'aaa');
579
+ await fs.writeFile(path('sub', 'b.txt'), 'bb');
580
+ await fs.writeFile(path('c.txt'), '');
581
+ // Deliberately not sorted: the contract fixes the order asked, not a sort.
582
+ const paths = [path('sub', 'b.txt'), path('c.txt'), path('a.txt')];
583
+ const batched = (await fs.readFiles?.(paths)) ?? [];
584
+ const looped = await readFilesWithLoop(fs, paths);
585
+ expect(batched.map((entry) => entry.path)).toEqual(paths);
586
+ expect(batched.map((entry) => (entry.content ? textOf(entry.content) : undefined)))
587
+ .toEqual(looped.map((entry) => (entry.content ? textOf(entry.content) : undefined)));
588
+ expect(batched.map((entry) => entry.error)).toEqual([undefined, undefined, undefined]);
589
+ },
590
+ },
591
+ {
592
+ port: 'fs.readFiles',
593
+ name: 'reports a missing path per entry instead of throwing',
594
+ async run({ platform, path }) {
595
+ const fs = platform.fs;
596
+ await fs.writeFile(path('a.txt'), 'aaa');
597
+ await fs.writeFile(path('b.txt'), 'bbb');
598
+ const paths = [path('a.txt'), path('missing.txt'), path('b.txt')];
599
+ const batched = (await fs.readFiles?.(paths)) ?? [];
600
+ expect(batched.map((entry) => entry.path)).toEqual(paths);
601
+ expect(batched[0]?.content && textOf(batched[0].content)).toBe('aaa');
602
+ expect(batched[2]?.content && textOf(batched[2].content)).toBe('bbb');
603
+ const missing = batched[1];
604
+ expect(missing?.content).toBeUndefined();
605
+ expect(typeof missing?.error).toBe('string');
606
+ expect(missing?.error?.length).toBeGreaterThan(0);
607
+ // "The code `readFile` would have thrown for this path."
608
+ const thrown = errorCode(await rejection(fs.readFile(path('missing.txt')), 'readFile of a missing path'));
609
+ if (thrown !== undefined)
610
+ expect(missing?.error).toBe(thrown);
611
+ },
612
+ },
613
+ {
614
+ port: 'fs.readFiles',
615
+ name: 'reads a directory the same way the loop does',
616
+ async run({ platform, path }) {
617
+ const fs = platform.fs;
618
+ await fs.mkdir(path('dir'), { recursive: true });
619
+ const batched = (await fs.readFiles?.([path('dir')])) ?? [];
620
+ const looped = await readFilesWithLoop(fs, [path('dir')]);
621
+ expect(batched.length).toBe(1);
622
+ expect(batched[0]?.error !== undefined).toBe(looped[0]?.error !== undefined);
623
+ },
624
+ },
625
+ ];
626
+ const writeFilesChecks = [
627
+ {
628
+ port: 'fs.writeFiles',
629
+ name: 'agrees with the writeFile loop it replaces',
630
+ async run({ platform, path }) {
631
+ const fs = platform.fs;
632
+ await fs.writeFile(path('c.txt'), 'stale');
633
+ await fs.writeFiles?.([
634
+ { path: path('a.txt'), content: 'aaa' },
635
+ { path: path('b.txt'), content: new Uint8Array([1, 2, 3]) },
636
+ { path: path('c.txt'), content: 'fresh' },
637
+ ]);
638
+ expect(await fs.readFile(path('a.txt'), 'utf-8')).toBe('aaa');
639
+ expect([...Buffer.from(await fs.readFile(path('b.txt')))]).toEqual([1, 2, 3]);
640
+ expect(await fs.readFile(path('c.txt'), 'utf-8')).toBe('fresh');
641
+ },
642
+ },
643
+ {
644
+ port: 'fs.writeFiles',
645
+ name: 'creates parents only when asked to',
646
+ async run({ platform, path }) {
647
+ const fs = platform.fs;
648
+ await rejection(Promise.resolve(fs.writeFiles?.([{ path: path('missing', 'a.txt'), content: 'a' }])), 'writeFiles into a missing directory without createParents');
649
+ expect(await fs.exists(path('missing'))).toBe(false);
650
+ await fs.writeFiles?.([{ path: path('made', 'deep', 'a.txt'), content: 'a' }], { createParents: true });
651
+ expect(await fs.readFile(path('made', 'deep', 'a.txt'), 'utf-8')).toBe('a');
652
+ },
653
+ },
654
+ ];
655
+ const rmFilesChecks = [
656
+ {
657
+ port: 'fs.rmFiles',
658
+ name: 'removes every path it is given',
659
+ async run({ platform, path }) {
660
+ const fs = platform.fs;
661
+ await fs.writeFile(path('a.txt'), 'a');
662
+ await fs.writeFile(path('b.txt'), 'b');
663
+ await fs.writeFile(path('c.txt'), 'c');
664
+ await fs.rmFiles?.([path('a.txt'), path('b.txt')]);
665
+ expect(await fs.exists(path('a.txt'))).toBe(false);
666
+ expect(await fs.exists(path('b.txt'))).toBe(false);
667
+ expect(await fs.exists(path('c.txt'))).toBe(true);
668
+ },
669
+ },
670
+ {
671
+ port: 'fs.rmFiles',
672
+ name: 'raises what rm raises for a missing path, and force silences it the same way',
673
+ async run({ platform, path }) {
674
+ const fs = platform.fs;
675
+ await fs.writeFile(path('a.txt'), 'a');
676
+ await rejection(Promise.resolve(fs.rmFiles?.([path('missing.txt')])), 'rmFiles of a missing path without force');
677
+ await fs.rmFiles?.([path('missing.txt'), path('a.txt')], { force: true });
678
+ expect(await fs.exists(path('a.txt'))).toBe(false);
679
+ },
680
+ },
681
+ {
682
+ port: 'fs.rmFiles',
683
+ name: 'removes a populated directory only when recursive',
684
+ async run({ platform, path }) {
685
+ const fs = platform.fs;
686
+ await fs.mkdir(path('tree', 'nested'), { recursive: true });
687
+ await fs.writeFile(path('tree', 'nested', 'leaf.txt'), 'leaf');
688
+ await rejection(Promise.resolve(fs.rmFiles?.([path('tree')])), 'rmFiles of a populated directory without recursive');
689
+ expect(await fs.exists(path('tree', 'nested', 'leaf.txt'))).toBe(true);
690
+ await fs.rmFiles?.([path('tree')], { recursive: true });
691
+ expect(await fs.exists(path('tree'))).toBe(false);
692
+ },
693
+ },
694
+ ];
695
+ const scopeReadsChecks = [
696
+ {
697
+ port: 'fs.scopeReads',
698
+ name: 'returns what the block returns, and the same reads it would answer outside',
699
+ async run({ platform, path }) {
700
+ const fs = platform.fs;
701
+ await fs.mkdir(path('sub'), { recursive: true });
702
+ await fs.writeFile(path('a.txt'), 'aaa');
703
+ await fs.writeFile(path('sub', 'b.txt'), 'bb');
704
+ const outside = [await fs.readFile(path('a.txt'), 'utf-8'), (await fs.readdir(path())).sort().join(',')];
705
+ const inside = await fs.scopeReads?.(async () => [
706
+ await fs.readFile(path('a.txt'), 'utf-8'),
707
+ (await fs.readdir(path())).sort().join(','),
708
+ ]);
709
+ expect(inside).toEqual(outside);
710
+ },
711
+ },
712
+ {
713
+ port: 'fs.scopeReads',
714
+ name: 'propagates the block rejection rather than swallowing it',
715
+ async run({ platform }) {
716
+ const marker = new Error('block failed');
717
+ const caught = await rejection(Promise.resolve(platform.fs.scopeReads?.(async () => {
718
+ throw marker;
719
+ })), 'scopeReads over a block that throws');
720
+ expect(caught).toBe(marker);
721
+ },
722
+ },
723
+ {
724
+ port: 'fs.scopeReads',
725
+ name: 'a write inside the scope is visible to a read after it',
726
+ async run({ platform, path }) {
727
+ const fs = platform.fs;
728
+ await fs.scopeReads?.(async () => {
729
+ await fs.writeFile(path('a.txt'), 'written');
730
+ });
731
+ expect(await fs.readFile(path('a.txt'), 'utf-8')).toBe('written');
732
+ },
733
+ },
734
+ ];
735
+ // ============================================================================
736
+ // scheduler
737
+ // ============================================================================
738
+ /** Keys a wake was delivered for, in the order they arrived. */
739
+ function collectWakes(platform) {
740
+ const fired = [];
741
+ if (isLiveScheduler(platform.scheduler))
742
+ platform.scheduler.onWake((key) => void fired.push(key));
743
+ return fired;
744
+ }
745
+ const schedulerChecks = [
746
+ {
747
+ port: 'scheduler',
748
+ name: 'wake and cancel resolve, and cancel of an unarmed key is a no-op',
749
+ async run({ platform }) {
750
+ await platform.scheduler.cancel('never-armed');
751
+ await platform.scheduler.wake('armed', 20);
752
+ await platform.scheduler.cancel('armed');
753
+ await platform.scheduler.cancel('armed');
754
+ },
755
+ },
756
+ {
757
+ port: 'scheduler.live',
758
+ name: 'a wake fires after its delay, once, with its own key',
759
+ async run({ platform }) {
760
+ const fired = collectWakes(platform);
761
+ await platform.scheduler.wake('alpha', 40);
762
+ expect(fired).toEqual([]);
763
+ await waitUntil(() => fired.length > 0, 2000, 'the wake for alpha');
764
+ await sleep(80);
765
+ expect(fired).toEqual(['alpha']);
766
+ },
767
+ },
768
+ {
769
+ port: 'scheduler.live',
770
+ name: 'a second wake for the same key replaces the first rather than adding one',
771
+ async run({ platform }) {
772
+ const fired = collectWakes(platform);
773
+ await platform.scheduler.wake('alpha', 20);
774
+ await platform.scheduler.wake('alpha', 400);
775
+ // Past the first deadline by a wide margin: the replaced wake must not fire.
776
+ await sleep(150);
777
+ expect(fired).toEqual([]);
778
+ await waitUntil(() => fired.length > 0, 3000, 'the replacing wake for alpha');
779
+ await sleep(80);
780
+ expect(fired).toEqual(['alpha']);
781
+ },
782
+ },
783
+ {
784
+ port: 'scheduler.live',
785
+ name: 'cancel before the delay stops it',
786
+ async run({ platform }) {
787
+ const fired = collectWakes(platform);
788
+ await platform.scheduler.wake('alpha', 60);
789
+ await platform.scheduler.wake('beta', 60);
790
+ await platform.scheduler.cancel('alpha');
791
+ await waitUntil(() => fired.length > 0, 2000, 'the wake for beta');
792
+ await sleep(120);
793
+ expect(fired).toEqual(['beta']);
794
+ },
795
+ },
796
+ {
797
+ port: 'scheduler.live',
798
+ name: 'a cancel-then-wake pair issued without awaiting lands in that order',
799
+ async run({ platform }) {
800
+ const fired = collectWakes(platform);
801
+ await platform.scheduler.wake('alpha', 500);
802
+ // Exactly how callers issue it: neither promise awaited before the next call.
803
+ const cancelled = platform.scheduler.cancel('alpha');
804
+ const rearmed = platform.scheduler.wake('alpha', 40);
805
+ await Promise.all([cancelled, rearmed]);
806
+ await waitUntil(() => fired.length > 0, 2000, 'the re-armed wake for alpha');
807
+ await sleep(80);
808
+ expect(fired).toEqual(['alpha']);
809
+ },
810
+ },
811
+ {
812
+ port: 'scheduler.live',
813
+ name: 'onWake replaces any previous handler',
814
+ async run({ platform }) {
815
+ const scheduler = platform.scheduler;
816
+ if (!isLiveScheduler(scheduler))
817
+ throw new Error('scheduler.live checks need a LiveScheduler');
818
+ const first = [];
819
+ const second = [];
820
+ scheduler.onWake((key) => void first.push(key));
821
+ scheduler.onWake((key) => void second.push(key));
822
+ await scheduler.wake('alpha', 20);
823
+ await waitUntil(() => second.length > 0, 2000, 'the wake for alpha');
824
+ await sleep(60);
825
+ expect(second).toEqual(['alpha']);
826
+ expect(first).toEqual([]);
827
+ },
828
+ },
829
+ ];
830
+ // ============================================================================
831
+ // shell
832
+ // ============================================================================
833
+ const shellRun = (platform, options) => {
834
+ if (!platform.shell)
835
+ throw new Error('shell checks need platform.shell');
836
+ return platform.shell.run(options);
837
+ };
838
+ const shellChecks = [
839
+ {
840
+ port: 'shell',
841
+ name: 'declares one of the three confinements',
842
+ async run({ platform }) {
843
+ const declared = ['paths', 'host', 'none'];
844
+ expect(declared).toContain(platform.shell?.confinement);
845
+ },
846
+ },
847
+ {
848
+ port: 'shell',
849
+ name: 'a non-zero exit resolves rather than rejects',
850
+ async run({ platform, root }) {
851
+ const result = await shellRun(platform, { command: 'exit 3', cwd: root, timeoutMs: 10_000 });
852
+ expect(result.exitCode).toBe(3);
853
+ expect(result.timedOut).toBe(false);
854
+ },
855
+ },
856
+ {
857
+ port: 'shell',
858
+ name: 'buffers stdout and stderr separately',
859
+ async run({ platform, root }) {
860
+ const result = await shellRun(platform, {
861
+ command: 'printf out; printf err 1>&2',
862
+ cwd: root,
863
+ timeoutMs: 10_000,
864
+ });
865
+ expect(result.exitCode).toBe(0);
866
+ expect(result.stdout).toContain('out');
867
+ expect(result.stderr).toContain('err');
868
+ expect(result.stdout).not.toContain('err');
869
+ },
870
+ },
871
+ {
872
+ port: 'shell',
873
+ name: 'stdin reaches the command',
874
+ async run({ platform, root }) {
875
+ const result = await shellRun(platform, {
876
+ command: 'cat',
877
+ cwd: root,
878
+ stdin: 'conformance-stdin\n',
879
+ timeoutMs: 10_000,
880
+ });
881
+ expect(result.exitCode).toBe(0);
882
+ expect(result.stdout).toContain('conformance-stdin');
883
+ },
884
+ },
885
+ {
886
+ port: 'shell',
887
+ name: 'cwd is where the command runs',
888
+ async run({ platform, root }) {
889
+ const result = await shellRun(platform, { command: 'pwd', cwd: root, timeoutMs: 10_000 });
890
+ expect([root, await platform.fs.realpath(root)]).toContain(result.stdout.trim());
891
+ },
892
+ },
893
+ {
894
+ port: 'shell',
895
+ name: 'env reaches the command',
896
+ async run({ platform, root }) {
897
+ const result = await shellRun(platform, {
898
+ command: 'printf %s "$ROJ_CONFORMANCE"',
899
+ cwd: root,
900
+ env: { ROJ_CONFORMANCE: 'present' },
901
+ timeoutMs: 10_000,
902
+ });
903
+ expect(result.stdout).toContain('present');
904
+ },
905
+ },
906
+ {
907
+ port: 'shell',
908
+ name: 'a timeout terminates the command and reports timedOut',
909
+ async run({ platform, root }) {
910
+ const started = Date.now();
911
+ const result = await shellRun(platform, { command: 'sleep 30', cwd: root, timeoutMs: 300 });
912
+ expect(result.timedOut).toBe(true);
913
+ expect(Date.now() - started).toBeLessThan(20_000);
914
+ expect(result.exitCode).not.toBe(0);
915
+ },
916
+ },
917
+ {
918
+ port: 'shell.paths',
919
+ name: 'a granted path is reachable and one outside the grants is not',
920
+ async run({ platform, path }) {
921
+ const fs = platform.fs;
922
+ await fs.mkdir(path('granted'), { recursive: true });
923
+ await fs.mkdir(path('ungranted'), { recursive: true });
924
+ await fs.writeFile(path('granted', 'inside.txt'), 'inside');
925
+ await fs.writeFile(path('ungranted', 'secret.txt'), 'secret');
926
+ const seen = path('visible');
927
+ const grants = [{ path: seen, source: path('granted'), mode: 'rw' }];
928
+ const reachable = await shellRun(platform, {
929
+ command: 'cat inside.txt',
930
+ cwd: seen,
931
+ grants,
932
+ timeoutMs: 20_000,
933
+ });
934
+ expect(reachable.exitCode).toBe(0);
935
+ expect(reachable.stdout).toContain('inside');
936
+ const blocked = await shellRun(platform, {
937
+ command: `cat ${path('ungranted', 'secret.txt')}`,
938
+ cwd: seen,
939
+ grants,
940
+ timeoutMs: 20_000,
941
+ });
942
+ expect(blocked.exitCode).not.toBe(0);
943
+ expect(blocked.stdout).not.toContain('secret');
944
+ },
945
+ },
946
+ {
947
+ port: 'shell.paths',
948
+ name: 'a read-only grant is readable and not writable',
949
+ async run({ platform, path }) {
950
+ const fs = platform.fs;
951
+ await fs.mkdir(path('granted'), { recursive: true });
952
+ await fs.writeFile(path('granted', 'inside.txt'), 'inside');
953
+ const seen = path('visible');
954
+ const grants = [{ path: seen, source: path('granted'), mode: 'ro' }];
955
+ const read = await shellRun(platform, { command: 'cat inside.txt', cwd: seen, grants, timeoutMs: 20_000 });
956
+ expect(read.exitCode).toBe(0);
957
+ const write = await shellRun(platform, {
958
+ command: 'printf x > written.txt',
959
+ cwd: seen,
960
+ grants,
961
+ timeoutMs: 20_000,
962
+ });
963
+ expect(write.exitCode).not.toBe(0);
964
+ expect(await fs.exists(path('granted', 'written.txt'))).toBe(false);
965
+ },
966
+ },
967
+ {
968
+ port: 'shell.host',
969
+ name: 'needs no grants — the filesystem holds nothing but this session',
970
+ async run({ platform, path, root }) {
971
+ await platform.fs.writeFile(path('inside.txt'), 'inside');
972
+ const result = await shellRun(platform, { command: 'cat inside.txt', cwd: root, timeoutMs: 20_000 });
973
+ expect(result.exitCode).toBe(0);
974
+ expect(result.stdout).toContain('inside');
975
+ },
976
+ },
977
+ ];
978
+ // ============================================================================
979
+ // git
980
+ // ============================================================================
981
+ const gitChecks = [
982
+ {
983
+ port: 'git',
984
+ name: 'status reports the non-clean paths, and nothing else',
985
+ async run({ platform, gitRepo }) {
986
+ const dir = await gitRepo();
987
+ const status = (await platform.git?.status({ dir })) ?? [];
988
+ expect([...status].sort((a, b) => a.path.localeCompare(b.path))).toEqual([
989
+ { path: GIT_FIXTURE.modified, index: ' ', worktree: 'M' },
990
+ { path: GIT_FIXTURE.untracked, index: ' ', worktree: '?' },
991
+ ]);
992
+ },
993
+ },
994
+ {
995
+ port: 'git',
996
+ name: 'log returns commits newest first, in milliseconds, and honours depth',
997
+ async run({ platform, gitRepo }) {
998
+ const dir = await gitRepo();
999
+ const head = (await platform.git?.log({ dir, depth: 1 })) ?? [];
1000
+ expect(head.length).toBe(1);
1001
+ expect(head[0]?.message.split('\n')[0]).toBe(GIT_FIXTURE.headSubject);
1002
+ expect(head[0]?.oid.length).toBeGreaterThan(0);
1003
+ // Milliseconds, "like every other SDK timestamp" — seconds would be ~1.7e9.
1004
+ expect(head[0]?.committedAt).toBeGreaterThan(1e12);
1005
+ const all = (await platform.git?.log({ dir })) ?? [];
1006
+ expect(all.length).toBeGreaterThanOrEqual(2);
1007
+ expect(all[0]?.message.split('\n')[0]).toBe(GIT_FIXTURE.headSubject);
1008
+ expect(all[all.length - 1]?.message.split('\n')[0]).toBe(GIT_FIXTURE.baseSubject);
1009
+ expect(new Set(all.map((commit) => commit.oid)).size).toBe(all.length);
1010
+ const base = (await platform.git?.log({ dir, ref: GIT_FIXTURE.base, depth: 1 })) ?? [];
1011
+ expect(base[0]?.message.split('\n')[0]).toBe(GIT_FIXTURE.baseSubject);
1012
+ },
1013
+ },
1014
+ {
1015
+ port: 'git',
1016
+ name: 'countAhead counts what is in ref and not in base',
1017
+ async run({ platform, gitRepo }) {
1018
+ const dir = await gitRepo();
1019
+ expect(await platform.git?.countAhead({ dir, base: GIT_FIXTURE.base })).toBe(1);
1020
+ expect(await platform.git?.countAhead({ dir, base: GIT_FIXTURE.branch })).toBe(0);
1021
+ expect(await platform.git?.countAhead({ dir, base: GIT_FIXTURE.base, ref: GIT_FIXTURE.base })).toBe(0);
1022
+ },
1023
+ },
1024
+ {
1025
+ port: 'git',
1026
+ name: 'defaultBranch answers a branch name or undefined',
1027
+ async run({ platform, gitRepo }) {
1028
+ const dir = await gitRepo();
1029
+ const branch = await platform.git?.defaultBranch({ dir });
1030
+ expect(branch === undefined || (typeof branch === 'string' && branch.length > 0)).toBe(true);
1031
+ },
1032
+ },
1033
+ {
1034
+ port: 'git',
1035
+ name: 'rejects outside a repository, so the caller reads it as no git state',
1036
+ async run({ platform, path }) {
1037
+ const dir = path('not-a-repo');
1038
+ await platform.fs.mkdir(dir, { recursive: true });
1039
+ await rejection(Promise.resolve(platform.git?.status({ dir })), 'status outside a repository');
1040
+ await rejection(Promise.resolve(platform.git?.log({ dir })), 'log outside a repository');
1041
+ await rejection(Promise.resolve(platform.git?.countAhead({ dir, base: GIT_FIXTURE.base })), 'countAhead outside a repository');
1042
+ // `defaultBranch` says undefined means unknown, so outside a repo it may answer either way.
1043
+ const branch = await platform.git?.defaultBranch({ dir }).catch(() => undefined);
1044
+ expect(branch).toBeUndefined();
1045
+ },
1046
+ },
1047
+ ];
1048
+ // ============================================================================
1049
+ // fsRevision
1050
+ // ============================================================================
1051
+ const fsRevisionChecks = [
1052
+ {
1053
+ port: 'fsRevision',
1054
+ name: 'current never rejects — a host that cannot answer returns undefined',
1055
+ async run({ platform, path }) {
1056
+ const first = await platform.fsRevision?.current();
1057
+ expect(first === undefined || typeof first === 'number').toBe(true);
1058
+ await platform.fs.writeFile(path('a.txt'), 'a');
1059
+ const second = await platform.fsRevision?.current();
1060
+ expect(second === undefined || typeof second === 'number').toBe(true);
1061
+ },
1062
+ },
1063
+ {
1064
+ port: 'fsRevision.numbered',
1065
+ name: 'the number moves after a write',
1066
+ async run({ platform, path }) {
1067
+ const before = await platform.fsRevision?.current();
1068
+ await platform.fs.writeFile(path('a.txt'), 'a');
1069
+ const after = await platform.fsRevision?.current();
1070
+ expect(typeof after).toBe('number');
1071
+ expect(after).not.toBe(before);
1072
+ },
1073
+ },
1074
+ {
1075
+ port: 'fsRevision.numbered',
1076
+ name: 'the number stands still without one',
1077
+ async run({ platform, path }) {
1078
+ await platform.fs.writeFile(path('a.txt'), 'a');
1079
+ const first = await platform.fsRevision?.current();
1080
+ const second = await platform.fsRevision?.current();
1081
+ await platform.fs.readFile(path('a.txt'));
1082
+ const third = await platform.fsRevision?.current();
1083
+ expect(second).toBe(first);
1084
+ expect(third).toBe(first);
1085
+ },
1086
+ },
1087
+ ];
1088
+ // ============================================================================
1089
+ // sessionLog
1090
+ // ============================================================================
1091
+ const SESSION_A = 'conformance-session-a';
1092
+ const SESSION_B = 'conformance-session-b';
1093
+ const sessionLogChecks = [
1094
+ {
1095
+ port: 'sessionLog',
1096
+ name: 'append returns nothing and round-trips the lines in order',
1097
+ async run({ platform }) {
1098
+ const store = platform.sessionLog;
1099
+ if (!store)
1100
+ throw new Error('sessionLog checks need platform.sessionLog');
1101
+ expect(store.append(SESSION_A, '{"n":1}')).toBeUndefined();
1102
+ store.append(SESSION_A, '{"n":2}');
1103
+ store.append(SESSION_A, '{"n":3}');
1104
+ const page = await store.read(SESSION_A, 0);
1105
+ expect(page.lines).toEqual(['{"n":1}', '{"n":2}', '{"n":3}']);
1106
+ },
1107
+ },
1108
+ {
1109
+ port: 'sessionLog',
1110
+ name: 'the cursor returns what followed it, and only that',
1111
+ async run({ platform }) {
1112
+ const store = platform.sessionLog;
1113
+ if (!store)
1114
+ throw new Error('sessionLog checks need platform.sessionLog');
1115
+ store.append(SESSION_A, '{"n":1}');
1116
+ store.append(SESSION_A, '{"n":2}');
1117
+ const first = await store.read(SESSION_A, 0);
1118
+ expect(first.lines.length).toBe(2);
1119
+ // Nothing new: the same cursor comes back with nothing behind it.
1120
+ expect((await store.read(SESSION_A, first.offset)).lines).toEqual([]);
1121
+ expect((await store.read(SESSION_A, first.offset)).offset).toBe(first.offset);
1122
+ store.append(SESSION_A, '{"n":3}');
1123
+ const second = await store.read(SESSION_A, first.offset);
1124
+ expect(second.lines).toEqual(['{"n":3}']);
1125
+ // Re-reading the same cursor answers the same — a cursor is a position, not a queue.
1126
+ expect((await store.read(SESSION_A, first.offset)).lines).toEqual(['{"n":3}']);
1127
+ expect((await store.read(SESSION_A, second.offset)).lines).toEqual([]);
1128
+ },
1129
+ },
1130
+ {
1131
+ port: 'sessionLog',
1132
+ name: 'a session with nothing in it reads empty',
1133
+ async run({ platform }) {
1134
+ const store = platform.sessionLog;
1135
+ if (!store)
1136
+ throw new Error('sessionLog checks need platform.sessionLog');
1137
+ expect((await store.read('conformance-never-written', 0)).lines).toEqual([]);
1138
+ },
1139
+ },
1140
+ {
1141
+ port: 'sessionLog',
1142
+ name: 'one session does not see another',
1143
+ async run({ platform }) {
1144
+ const store = platform.sessionLog;
1145
+ if (!store)
1146
+ throw new Error('sessionLog checks need platform.sessionLog');
1147
+ store.append(SESSION_A, '{"who":"a"}');
1148
+ store.append(SESSION_B, '{"who":"b"}');
1149
+ expect((await store.read(SESSION_A, 0)).lines).toEqual(['{"who":"a"}']);
1150
+ expect((await store.read(SESSION_B, 0)).lines).toEqual(['{"who":"b"}']);
1151
+ },
1152
+ },
1153
+ {
1154
+ port: 'sessionLog',
1155
+ name: 'delete drops one session and reports how many went',
1156
+ async run({ platform }) {
1157
+ const store = platform.sessionLog;
1158
+ if (!store)
1159
+ throw new Error('sessionLog checks need platform.sessionLog');
1160
+ store.append(SESSION_A, '{"n":1}');
1161
+ store.append(SESSION_A, '{"n":2}');
1162
+ store.append(SESSION_B, '{"n":1}');
1163
+ expect(await store.delete(SESSION_A)).toBe(2);
1164
+ expect((await store.read(SESSION_A, 0)).lines).toEqual([]);
1165
+ expect((await store.read(SESSION_B, 0)).lines).toEqual(['{"n":1}']);
1166
+ expect(await store.delete('conformance-never-written')).toBe(0);
1167
+ },
1168
+ },
1169
+ ];
1170
+ // ============================================================================
1171
+ // llmCallLog
1172
+ // ============================================================================
1173
+ /** Fixed-width and ordered, the way a UUIDv7 is — so `list` can be checked on it. */
1174
+ const callId = (n) => `0198e9c0-0000-7000-8000-${String(n).padStart(12, '0')}`;
1175
+ function callRow(n) {
1176
+ return {
1177
+ callId: callId(n),
1178
+ agentId: 'conformance-agent',
1179
+ createdAt: 1_700_000_000_000 + n,
1180
+ status: 'running',
1181
+ model: 'test-model',
1182
+ request: JSON.stringify({ n }),
1183
+ };
1184
+ }
1185
+ const llmCallLogChecks = [
1186
+ {
1187
+ port: 'llmCallLog',
1188
+ name: 'create and get round-trip the row',
1189
+ async run({ platform }) {
1190
+ const store = platform.llmCallLog;
1191
+ if (!store)
1192
+ throw new Error('llmCallLog checks need platform.llmCallLog');
1193
+ if (store.maxBlobBytes !== undefined)
1194
+ expect(store.maxBlobBytes).toBeGreaterThan(0);
1195
+ const row = callRow(1);
1196
+ await store.create(SESSION_A, row);
1197
+ expect(await store.get(SESSION_A, row.callId)).toEqual(row);
1198
+ expect(await store.get(SESSION_A, callId(999))).toBeNull();
1199
+ expect(await store.get(SESSION_B, row.callId)).toBeNull();
1200
+ },
1201
+ },
1202
+ {
1203
+ port: 'llmCallLog',
1204
+ name: 'complete attaches the outcome and leaves the request alone',
1205
+ async run({ platform }) {
1206
+ const store = platform.llmCallLog;
1207
+ if (!store)
1208
+ throw new Error('llmCallLog checks need platform.llmCallLog');
1209
+ const row = callRow(1);
1210
+ await store.create(SESSION_A, row);
1211
+ await store.complete(SESSION_A, row.callId, {
1212
+ status: 'success',
1213
+ completedAt: 1_700_000_005_000,
1214
+ durationMs: 5000,
1215
+ providerRequestId: 'req-1',
1216
+ response: '{"text":"hi"}',
1217
+ metrics: '{"tokens":7}',
1218
+ });
1219
+ const stored = await store.get(SESSION_A, row.callId);
1220
+ expect(stored?.status).toBe('success');
1221
+ expect(stored?.completedAt).toBe(1_700_000_005_000);
1222
+ expect(stored?.durationMs).toBe(5000);
1223
+ expect(stored?.providerRequestId).toBe('req-1');
1224
+ expect(stored?.response).toBe('{"text":"hi"}');
1225
+ expect(stored?.metrics).toBe('{"tokens":7}');
1226
+ expect(stored?.request).toBe(row.request);
1227
+ expect(stored?.error).toBeUndefined();
1228
+ },
1229
+ },
1230
+ {
1231
+ port: 'llmCallLog',
1232
+ name: 'complete on an id the store does not hold is a no-op, not a throw',
1233
+ async run({ platform }) {
1234
+ const store = platform.llmCallLog;
1235
+ if (!store)
1236
+ throw new Error('llmCallLog checks need platform.llmCallLog');
1237
+ await store.create(SESSION_A, callRow(1));
1238
+ await store.complete(SESSION_A, callId(999), {
1239
+ status: 'error',
1240
+ completedAt: 1_700_000_005_000,
1241
+ durationMs: 5000,
1242
+ error: '{"message":"reaped"}',
1243
+ });
1244
+ expect(await store.get(SESSION_A, callId(999))).toBeNull();
1245
+ expect((await store.list(SESSION_A, { limit: 10, offset: 0 })).total).toBe(1);
1246
+ },
1247
+ },
1248
+ {
1249
+ port: 'llmCallLog',
1250
+ name: 'list pages newest first by callId, with a total that ignores limit and offset',
1251
+ async run({ platform }) {
1252
+ const store = platform.llmCallLog;
1253
+ if (!store)
1254
+ throw new Error('llmCallLog checks need platform.llmCallLog');
1255
+ for (const n of [1, 2, 3])
1256
+ await store.create(SESSION_A, callRow(n));
1257
+ await store.create(SESSION_B, callRow(9));
1258
+ const firstPage = await store.list(SESSION_A, { limit: 2, offset: 0 });
1259
+ expect(firstPage.total).toBe(3);
1260
+ expect(firstPage.calls.map((call) => call.callId)).toEqual([callId(3), callId(2)]);
1261
+ const secondPage = await store.list(SESSION_A, { limit: 2, offset: 2 });
1262
+ expect(secondPage.total).toBe(3);
1263
+ expect(secondPage.calls.map((call) => call.callId)).toEqual([callId(1)]);
1264
+ expect((await store.list(SESSION_A, { limit: 10, offset: 10 })).calls).toEqual([]);
1265
+ expect((await store.list(SESSION_B, { limit: 10, offset: 0 })).total).toBe(1);
1266
+ expect((await store.list('conformance-never-written', { limit: 10, offset: 0 })).total).toBe(0);
1267
+ },
1268
+ },
1269
+ {
1270
+ port: 'llmCallLog',
1271
+ name: 'delete drops one session and reports how many went',
1272
+ async run({ platform }) {
1273
+ const store = platform.llmCallLog;
1274
+ if (!store)
1275
+ throw new Error('llmCallLog checks need platform.llmCallLog');
1276
+ for (const n of [1, 2])
1277
+ await store.create(SESSION_A, callRow(n));
1278
+ await store.create(SESSION_B, callRow(9));
1279
+ expect(await store.delete(SESSION_A)).toBe(2);
1280
+ expect((await store.list(SESSION_A, { limit: 10, offset: 0 })).total).toBe(0);
1281
+ expect(await store.get(SESSION_A, callId(1))).toBeNull();
1282
+ expect((await store.list(SESSION_B, { limit: 10, offset: 0 })).total).toBe(1);
1283
+ expect(await store.delete('conformance-never-written')).toBe(0);
1284
+ },
1285
+ },
1286
+ ];
1287
+ /** Every clause the ports state, as one list. */
1288
+ export const platformConformanceChecks = [
1289
+ ...fsChecks,
1290
+ ...walkChecks,
1291
+ ...readFilesChecks,
1292
+ ...writeFilesChecks,
1293
+ ...rmFilesChecks,
1294
+ ...scopeReadsChecks,
1295
+ ...schedulerChecks,
1296
+ ...shellChecks,
1297
+ ...gitChecks,
1298
+ ...fsRevisionChecks,
1299
+ ...sessionLogChecks,
1300
+ ...llmCallLogChecks,
1301
+ ];
1302
+ // ============================================================================
1303
+ // Probing what a host answers
1304
+ // ============================================================================
1305
+ /** Which ports the target answers, and why it does not answer the rest. */
1306
+ export async function probePlatformPorts(target) {
1307
+ const instance = await target.create();
1308
+ try {
1309
+ return await probeInstance(target, instance);
1310
+ }
1311
+ finally {
1312
+ await instance.dispose?.();
1313
+ }
1314
+ }
1315
+ async function probeInstance(target, instance) {
1316
+ const { platform } = instance;
1317
+ const support = [];
1318
+ const add = (port, answered, note) => {
1319
+ support.push(answered ? { port, answered } : { port, answered, note: note ?? 'port absent' });
1320
+ };
1321
+ add('fs', true);
1322
+ add('fs.symlinks', await canSymlink(target, instance), 'the host cannot make a symlink and the target declares no `symlink`');
1323
+ for (const verb of OPTIONAL_FS_VERBS) {
1324
+ add(`fs.${verb}`, typeof platform.fs[verb] === 'function');
1325
+ }
1326
+ add('scheduler', true);
1327
+ add('scheduler.live', isLiveScheduler(platform.scheduler), 'the scheduler does not deliver its own wakes');
1328
+ const confinement = platform.shell?.confinement;
1329
+ add('shell', platform.shell !== undefined);
1330
+ const confines = await probeConfinement(instance);
1331
+ add('shell.paths', confines.ok, confines.note);
1332
+ add('shell.host', confinement === 'host', `confinement is ${confinement ?? 'absent'}`);
1333
+ const buildable = platform.git !== undefined && await canBuildGitRepo(target, platform);
1334
+ add('git', buildable, platform.git === undefined ? undefined : 'no way to build a fixture repository — declare ConformanceTarget.buildGitRepo');
1335
+ add('fsRevision', platform.fsRevision !== undefined);
1336
+ const numbered = await probeRevision(platform);
1337
+ add('fsRevision.numbered', numbered.ok, numbered.note);
1338
+ add('sessionLog', platform.sessionLog !== undefined);
1339
+ add('llmCallLog', platform.llmCallLog !== undefined);
1340
+ return support;
1341
+ }
1342
+ async function canSymlink(target, instance) {
1343
+ const probePath = joinPath(instance.root, ['.conformance-symlink-probe']);
1344
+ try {
1345
+ await (target.symlink ?? defaultSymlink)(instance, instance.root, probePath);
1346
+ return await instance.platform.fs.exists(probePath);
1347
+ }
1348
+ catch {
1349
+ return false;
1350
+ }
1351
+ }
1352
+ async function canBuildGitRepo(target, platform) {
1353
+ if (target.buildGitRepo)
1354
+ return true;
1355
+ try {
1356
+ await platform.process.execFile('git', ['--version'], {});
1357
+ return true;
1358
+ }
1359
+ catch {
1360
+ return false;
1361
+ }
1362
+ }
1363
+ /** First line of an error, short enough to carry in a test name. */
1364
+ function messageOf(error) {
1365
+ const text = (error instanceof Error ? error.message : String(error)).split('\n')[0] ?? '';
1366
+ return text.length > 120 ? `${text.slice(0, 117)}...` : text;
1367
+ }
1368
+ /**
1369
+ * Whether the host can run a confined command at all — not whether it confines
1370
+ * correctly, which is what the `shell.paths` checks are for.
1371
+ *
1372
+ * Resolving is the signal, whatever the exit code: a runner that ran and answered
1373
+ * wrongly must fail those checks, and only one that cannot run at all is skipped.
1374
+ */
1375
+ async function probeConfinement(instance) {
1376
+ const { platform } = instance;
1377
+ const shell = platform.shell;
1378
+ if (!shell)
1379
+ return { ok: false, note: 'port absent' };
1380
+ if (shell.confinement !== 'paths')
1381
+ return { ok: false, note: `confinement is ${shell.confinement}` };
1382
+ const dir = joinPath(instance.root, ['.conformance-confinement-probe']);
1383
+ try {
1384
+ await platform.fs.mkdir(dir, { recursive: true });
1385
+ await shell.run({ command: 'exit 0', cwd: dir, grants: [{ path: dir, mode: 'rw' }], timeoutMs: 30_000 });
1386
+ return { ok: true };
1387
+ }
1388
+ catch (error) {
1389
+ return { ok: false, note: `declares \`paths\` confinement but cannot run a confined command: ${messageOf(error)}` };
1390
+ }
1391
+ }
1392
+ async function probeRevision(platform) {
1393
+ if (!platform.fsRevision)
1394
+ return { ok: false, note: 'port absent' };
1395
+ try {
1396
+ const current = await platform.fsRevision.current();
1397
+ return typeof current === 'number' ? { ok: true } : { ok: false, note: 'current() answers undefined' };
1398
+ }
1399
+ catch {
1400
+ return { ok: false, note: 'current() rejected' };
1401
+ }
1402
+ }
1403
+ // ============================================================================
1404
+ // Running
1405
+ // ============================================================================
1406
+ /** Run one check against a fresh instance of the target. Throws what it asserts. */
1407
+ export async function runConformanceCheck(target, check) {
1408
+ const instance = await target.create();
1409
+ try {
1410
+ await check.run(contextFor(target, instance));
1411
+ }
1412
+ finally {
1413
+ await instance.dispose?.();
1414
+ }
1415
+ }
1416
+ /** The checks a host answering `answered` should be held to. */
1417
+ export function checksFor(answered) {
1418
+ const ports = new Set(answered);
1419
+ return platformConformanceChecks.filter((check) => ports.has(check.port) && (check.needs ?? []).every((port) => ports.has(port)));
1420
+ }
1421
+ function describeSkip(support) {
1422
+ return support.note ? `${support.port} (${support.note})` : support.port;
1423
+ }
1424
+ /**
1425
+ * Declare the suite against `target`, one section per port.
1426
+ *
1427
+ * Ports the host does not answer are declared skipped rather than dropped, and
1428
+ * the two `ports:` tests carry the exercised and skipped sets in their names —
1429
+ * so a host that answers almost nothing says so in the run's output.
1430
+ */
1431
+ export function runPlatformConformance(target) {
1432
+ describe(`platform conformance: ${target.name}`, async () => {
1433
+ const support = await probePlatformPorts(target);
1434
+ const exercised = support.filter((entry) => entry.answered).map((entry) => entry.port);
1435
+ const answered = new Set(exercised);
1436
+ const skipped = support.filter((entry) => !entry.answered);
1437
+ console.log(`[conformance] ${target.name}\n`
1438
+ + ` exercised: ${exercised.join(', ') || 'none'}\n`
1439
+ + ` skipped: ${skipped.map(describeSkip).join(', ') || 'none'}`);
1440
+ for (const port of CONFORMANCE_PORTS) {
1441
+ const checks = platformConformanceChecks.filter((check) => check.port === port);
1442
+ if (checks.length === 0)
1443
+ continue;
1444
+ describe(port, () => {
1445
+ for (const check of checks) {
1446
+ const missing = [port, ...(check.needs ?? [])].filter((needed) => !answered.has(needed));
1447
+ const declareTest = missing.length === 0 ? test : test.skip;
1448
+ const name = missing.length === 0 ? check.name : `${check.name} — needs ${missing.join(', ')}`;
1449
+ declareTest(name, () => runConformanceCheck(target, check));
1450
+ }
1451
+ });
1452
+ }
1453
+ test(`ports exercised: ${exercised.join(', ') || 'none'}`, async () => {
1454
+ const again = await probePlatformPorts(target);
1455
+ expect(again.filter((entry) => entry.answered).map((entry) => entry.port)).toEqual(exercised);
1456
+ });
1457
+ test(`ports skipped: ${skipped.map(describeSkip).join(', ') || 'none'}`, () => {
1458
+ // A skip with no reason is a silent one, which is the thing this suite must not allow.
1459
+ expect(skipped.filter((entry) => entry.note === undefined)).toEqual([]);
1460
+ });
1461
+ });
1462
+ }
1463
+ //# sourceMappingURL=conformance.js.map