koro-ai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (670) hide show
  1. package/AGENTS.md +69 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/koro +16 -0
  6. package/bin/opencode +179 -0
  7. package/bunfig.toml +7 -0
  8. package/drizzle.config.ts +10 -0
  9. package/git +0 -0
  10. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  11. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  12. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  13. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  14. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  15. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  16. package/migration/20260225215848_workspace/migration.sql +7 -0
  17. package/migration/20260225215848_workspace/snapshot.json +959 -0
  18. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  19. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  20. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  21. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  22. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  23. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  24. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  25. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  26. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  27. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  28. package/migration/20260323234822_events/migration.sql +13 -0
  29. package/migration/20260323234822_events/snapshot.json +1271 -0
  30. package/package.json +163 -0
  31. package/parsers-config.ts +290 -0
  32. package/script/build-node.ts +56 -0
  33. package/script/build.ts +276 -0
  34. package/script/check-migrations.ts +16 -0
  35. package/script/postinstall.mjs +131 -0
  36. package/script/publish.ts +181 -0
  37. package/script/schema.ts +63 -0
  38. package/script/seed-e2e.ts +60 -0
  39. package/script/upgrade-opentui.ts +64 -0
  40. package/specs/effect-migration.md +294 -0
  41. package/specs/tui-plugins.md +436 -0
  42. package/src/account/account.sql.ts +39 -0
  43. package/src/account/index.ts +424 -0
  44. package/src/account/repo.ts +163 -0
  45. package/src/account/schema.ts +91 -0
  46. package/src/acp/README.md +174 -0
  47. package/src/acp/agent.ts +1763 -0
  48. package/src/acp/session.ts +116 -0
  49. package/src/acp/types.ts +24 -0
  50. package/src/agent/agent.ts +476 -0
  51. package/src/agent/generate.txt +75 -0
  52. package/src/agent/prompt/compaction.txt +15 -0
  53. package/src/agent/prompt/explore.txt +18 -0
  54. package/src/agent/prompt/summary.txt +11 -0
  55. package/src/agent/prompt/title.txt +44 -0
  56. package/src/auth/index.ts +109 -0
  57. package/src/bus/bus-event.ts +40 -0
  58. package/src/bus/global.ts +10 -0
  59. package/src/bus/index.ts +185 -0
  60. package/src/cli/bootstrap.ts +17 -0
  61. package/src/cli/cmd/account.ts +257 -0
  62. package/src/cli/cmd/acp.ts +70 -0
  63. package/src/cli/cmd/agent.ts +245 -0
  64. package/src/cli/cmd/cmd.ts +7 -0
  65. package/src/cli/cmd/db.ts +119 -0
  66. package/src/cli/cmd/debug/agent.ts +167 -0
  67. package/src/cli/cmd/debug/config.ts +16 -0
  68. package/src/cli/cmd/debug/file.ts +97 -0
  69. package/src/cli/cmd/debug/index.ts +48 -0
  70. package/src/cli/cmd/debug/lsp.ts +53 -0
  71. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  72. package/src/cli/cmd/debug/scrap.ts +16 -0
  73. package/src/cli/cmd/debug/skill.ts +16 -0
  74. package/src/cli/cmd/debug/snapshot.ts +52 -0
  75. package/src/cli/cmd/export.ts +89 -0
  76. package/src/cli/cmd/generate.ts +38 -0
  77. package/src/cli/cmd/github.ts +1646 -0
  78. package/src/cli/cmd/import.ts +207 -0
  79. package/src/cli/cmd/mcp.ts +754 -0
  80. package/src/cli/cmd/models.ts +78 -0
  81. package/src/cli/cmd/plug.ts +233 -0
  82. package/src/cli/cmd/pr.ts +127 -0
  83. package/src/cli/cmd/providers.ts +478 -0
  84. package/src/cli/cmd/run.ts +676 -0
  85. package/src/cli/cmd/serve.ts +24 -0
  86. package/src/cli/cmd/session.ts +159 -0
  87. package/src/cli/cmd/stats.ts +410 -0
  88. package/src/cli/cmd/tui/app.tsx +919 -0
  89. package/src/cli/cmd/tui/attach.ts +88 -0
  90. package/src/cli/cmd/tui/component/border.tsx +21 -0
  91. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  92. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  93. package/src/cli/cmd/tui/component/dialog-import-share.tsx +118 -0
  94. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  95. package/src/cli/cmd/tui/component/dialog-model.tsx +179 -0
  96. package/src/cli/cmd/tui/component/dialog-provider.tsx +329 -0
  97. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  98. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  99. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  100. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  101. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  102. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  103. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  104. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  105. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  106. package/src/cli/cmd/tui/component/error-component.tsx +92 -0
  107. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  108. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  109. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +672 -0
  110. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  111. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  112. package/src/cli/cmd/tui/component/prompt/index.tsx +1310 -0
  113. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  114. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  115. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  116. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  117. package/src/cli/cmd/tui/component/task-panel.tsx +44 -0
  118. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  119. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  120. package/src/cli/cmd/tui/component/token-bar.tsx +60 -0
  121. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  122. package/src/cli/cmd/tui/context/args.tsx +15 -0
  123. package/src/cli/cmd/tui/context/directory.ts +13 -0
  124. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  125. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  126. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  127. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  128. package/src/cli/cmd/tui/context/local.tsx +412 -0
  129. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  130. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  131. package/src/cli/cmd/tui/context/route.tsx +52 -0
  132. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  133. package/src/cli/cmd/tui/context/sync.tsx +504 -0
  134. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  135. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  136. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  137. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  138. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  139. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  140. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  141. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  142. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  143. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  144. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  145. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  146. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  147. package/src/cli/cmd/tui/context/theme/gtr.json +245 -0
  148. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  149. package/src/cli/cmd/tui/context/theme/koro.json +241 -0
  150. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  151. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  152. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  153. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  154. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  155. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  156. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  157. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  158. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  159. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  160. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  161. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  162. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  163. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  164. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  165. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  166. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  167. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  168. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  169. package/src/cli/cmd/tui/context/theme.tsx +1240 -0
  170. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  171. package/src/cli/cmd/tui/event.ts +49 -0
  172. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
  173. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  174. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  175. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  176. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  177. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  178. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  179. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  180. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  181. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  182. package/src/cli/cmd/tui/plugin/api.tsx +430 -0
  183. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  184. package/src/cli/cmd/tui/plugin/internal.ts +27 -0
  185. package/src/cli/cmd/tui/plugin/runtime.ts +1033 -0
  186. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  187. package/src/cli/cmd/tui/routes/home.tsx +84 -0
  188. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  189. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  190. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  191. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  192. package/src/cli/cmd/tui/routes/session/footer.tsx +93 -0
  193. package/src/cli/cmd/tui/routes/session/index.tsx +2270 -0
  194. package/src/cli/cmd/tui/routes/session/permission.tsx +691 -0
  195. package/src/cli/cmd/tui/routes/session/question.tsx +468 -0
  196. package/src/cli/cmd/tui/routes/session/sidebar.tsx +74 -0
  197. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
  198. package/src/cli/cmd/tui/thread.ts +232 -0
  199. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  200. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  201. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +211 -0
  202. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  203. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +130 -0
  204. package/src/cli/cmd/tui/ui/dialog-select.tsx +409 -0
  205. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  206. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  207. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  208. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  209. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  210. package/src/cli/cmd/tui/util/editor.ts +37 -0
  211. package/src/cli/cmd/tui/util/model.ts +23 -0
  212. package/src/cli/cmd/tui/util/scroll.ts +23 -0
  213. package/src/cli/cmd/tui/util/selection.ts +25 -0
  214. package/src/cli/cmd/tui/util/signal.ts +7 -0
  215. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  216. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  217. package/src/cli/cmd/tui/win32.ts +129 -0
  218. package/src/cli/cmd/tui/worker.ts +175 -0
  219. package/src/cli/cmd/uninstall.ts +353 -0
  220. package/src/cli/cmd/upgrade.ts +73 -0
  221. package/src/cli/cmd/web.ts +81 -0
  222. package/src/cli/effect/prompt.ts +25 -0
  223. package/src/cli/error.ts +46 -0
  224. package/src/cli/heap.ts +59 -0
  225. package/src/cli/logo.ts +14 -0
  226. package/src/cli/network.ts +60 -0
  227. package/src/cli/ui.ts +133 -0
  228. package/src/cli/upgrade.ts +31 -0
  229. package/src/command/index.ts +195 -0
  230. package/src/command/template/initialize.txt +66 -0
  231. package/src/command/template/review.txt +101 -0
  232. package/src/config/config.ts +1591 -0
  233. package/src/config/markdown.ts +99 -0
  234. package/src/config/paths.ts +181 -0
  235. package/src/config/tui-migrate.ts +155 -0
  236. package/src/config/tui-schema.ts +36 -0
  237. package/src/config/tui.ts +171 -0
  238. package/src/control-plane/adaptors/index.ts +20 -0
  239. package/src/control-plane/adaptors/worktree.ts +38 -0
  240. package/src/control-plane/schema.ts +17 -0
  241. package/src/control-plane/sse.ts +66 -0
  242. package/src/control-plane/types.ts +21 -0
  243. package/src/control-plane/workspace.sql.ts +17 -0
  244. package/src/control-plane/workspace.ts +154 -0
  245. package/src/effect/cross-spawn-spawner.ts +502 -0
  246. package/src/effect/instance-ref.ts +6 -0
  247. package/src/effect/instance-registry.ts +12 -0
  248. package/src/effect/instance-state.ts +82 -0
  249. package/src/effect/run-service.ts +33 -0
  250. package/src/effect/runner.ts +216 -0
  251. package/src/env/index.ts +28 -0
  252. package/src/file/ignore.ts +82 -0
  253. package/src/file/index.ts +686 -0
  254. package/src/file/protected.ts +59 -0
  255. package/src/file/ripgrep.ts +376 -0
  256. package/src/file/time.ts +133 -0
  257. package/src/file/watcher.ts +171 -0
  258. package/src/filesystem/index.ts +226 -0
  259. package/src/flag/flag.ts +155 -0
  260. package/src/format/formatter.ts +413 -0
  261. package/src/format/index.ts +203 -0
  262. package/src/git/index.ts +303 -0
  263. package/src/global/index.ts +161 -0
  264. package/src/id/id.ts +85 -0
  265. package/src/ide/index.ts +74 -0
  266. package/src/index.ts +240 -0
  267. package/src/installation/index.ts +355 -0
  268. package/src/installation/meta.ts +7 -0
  269. package/src/lsp/client.ts +252 -0
  270. package/src/lsp/index.ts +558 -0
  271. package/src/lsp/language.ts +120 -0
  272. package/src/lsp/launch.ts +21 -0
  273. package/src/lsp/server.ts +1958 -0
  274. package/src/mcp/auth.ts +173 -0
  275. package/src/mcp/index.ts +921 -0
  276. package/src/mcp/oauth-callback.ts +215 -0
  277. package/src/mcp/oauth-provider.ts +185 -0
  278. package/src/memory/index.ts +117 -0
  279. package/src/node.ts +1 -0
  280. package/src/npm/index.ts +180 -0
  281. package/src/orchestrator/agent-registry.ts +38 -0
  282. package/src/orchestrator/conflict.ts +25 -0
  283. package/src/orchestrator/context-manager.ts +22 -0
  284. package/src/orchestrator/index.ts +9 -0
  285. package/src/orchestrator/scheduler.ts +30 -0
  286. package/src/orchestrator/state-tracker.ts +71 -0
  287. package/src/orchestrator/task-manager.ts +69 -0
  288. package/src/patch/index.ts +680 -0
  289. package/src/permission/arity.ts +163 -0
  290. package/src/permission/evaluate.ts +15 -0
  291. package/src/permission/index.ts +325 -0
  292. package/src/permission/schema.ts +17 -0
  293. package/src/plugin/codex.ts +596 -0
  294. package/src/plugin/github-copilot/copilot.ts +353 -0
  295. package/src/plugin/github-copilot/models.ts +144 -0
  296. package/src/plugin/index.ts +281 -0
  297. package/src/plugin/install.ts +439 -0
  298. package/src/plugin/loader.ts +174 -0
  299. package/src/plugin/meta.ts +188 -0
  300. package/src/plugin/shared.ts +307 -0
  301. package/src/project/bootstrap.ts +31 -0
  302. package/src/project/instance.ts +175 -0
  303. package/src/project/project.sql.ts +16 -0
  304. package/src/project/project.ts +519 -0
  305. package/src/project/schema.ts +16 -0
  306. package/src/project/state.ts +70 -0
  307. package/src/project/vcs.ts +240 -0
  308. package/src/provider/auth.ts +253 -0
  309. package/src/provider/error.ts +197 -0
  310. package/src/provider/models-snapshot.ts +60410 -0
  311. package/src/provider/models.ts +162 -0
  312. package/src/provider/provider.ts +1677 -0
  313. package/src/provider/schema.ts +38 -0
  314. package/src/provider/sdk/copilot/README.md +5 -0
  315. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  316. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  317. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  318. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  319. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  320. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  321. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  322. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  323. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  324. package/src/provider/sdk/copilot/index.ts +2 -0
  325. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  326. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  327. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  328. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  329. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  330. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  331. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  332. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  333. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  334. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  335. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  336. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  337. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  338. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  339. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  340. package/src/provider/transform.ts +1046 -0
  341. package/src/pty/index.ts +401 -0
  342. package/src/pty/schema.ts +17 -0
  343. package/src/question/index.ts +224 -0
  344. package/src/question/schema.ts +17 -0
  345. package/src/server/error.ts +36 -0
  346. package/src/server/event.ts +7 -0
  347. package/src/server/instance.ts +314 -0
  348. package/src/server/mdns.ts +60 -0
  349. package/src/server/middleware.ts +33 -0
  350. package/src/server/projectors.ts +28 -0
  351. package/src/server/router.ts +99 -0
  352. package/src/server/routes/config.ts +92 -0
  353. package/src/server/routes/event.ts +83 -0
  354. package/src/server/routes/experimental.ts +271 -0
  355. package/src/server/routes/file.ts +197 -0
  356. package/src/server/routes/global.ts +312 -0
  357. package/src/server/routes/mcp.ts +225 -0
  358. package/src/server/routes/permission.ts +69 -0
  359. package/src/server/routes/project.ts +118 -0
  360. package/src/server/routes/provider.ts +171 -0
  361. package/src/server/routes/pty.ts +211 -0
  362. package/src/server/routes/question.ts +99 -0
  363. package/src/server/routes/session.ts +1031 -0
  364. package/src/server/routes/tui.ts +379 -0
  365. package/src/server/routes/workspace.ts +94 -0
  366. package/src/server/server.ts +312 -0
  367. package/src/session/compaction.ts +428 -0
  368. package/src/session/index.ts +887 -0
  369. package/src/session/instruction.ts +258 -0
  370. package/src/session/llm.ts +370 -0
  371. package/src/session/message-v2.ts +1031 -0
  372. package/src/session/message.ts +191 -0
  373. package/src/session/overflow.ts +22 -0
  374. package/src/session/processor.ts +523 -0
  375. package/src/session/projectors.ts +135 -0
  376. package/src/session/prompt/anthropic.txt +105 -0
  377. package/src/session/prompt/beast.txt +147 -0
  378. package/src/session/prompt/build-switch.txt +5 -0
  379. package/src/session/prompt/codex.txt +79 -0
  380. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  381. package/src/session/prompt/default.txt +105 -0
  382. package/src/session/prompt/gemini.txt +155 -0
  383. package/src/session/prompt/gpt.txt +107 -0
  384. package/src/session/prompt/kimi.txt +114 -0
  385. package/src/session/prompt/max-steps.txt +16 -0
  386. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  387. package/src/session/prompt/plan.txt +26 -0
  388. package/src/session/prompt/trinity.txt +97 -0
  389. package/src/session/prompt.ts +1908 -0
  390. package/src/session/retry.ts +106 -0
  391. package/src/session/revert.ts +176 -0
  392. package/src/session/schema.ts +38 -0
  393. package/src/session/session.sql.ts +103 -0
  394. package/src/session/status.ts +102 -0
  395. package/src/session/summary.ts +177 -0
  396. package/src/session/system.ts +76 -0
  397. package/src/session/todo.ts +95 -0
  398. package/src/share/share-next.ts +369 -0
  399. package/src/share/share.sql.ts +13 -0
  400. package/src/shell/shell.ts +110 -0
  401. package/src/skill/discovery.ts +116 -0
  402. package/src/skill/index.ts +277 -0
  403. package/src/snapshot/index.ts +571 -0
  404. package/src/sql.d.ts +4 -0
  405. package/src/storage/db.bun.ts +8 -0
  406. package/src/storage/db.node.ts +8 -0
  407. package/src/storage/db.ts +174 -0
  408. package/src/storage/json-migration.ts +425 -0
  409. package/src/storage/schema.sql.ts +10 -0
  410. package/src/storage/schema.ts +5 -0
  411. package/src/storage/storage.ts +353 -0
  412. package/src/sync/README.md +179 -0
  413. package/src/sync/event.sql.ts +16 -0
  414. package/src/sync/index.ts +263 -0
  415. package/src/sync/schema.ts +14 -0
  416. package/src/token/index.ts +77 -0
  417. package/src/tool/apply_patch.ts +281 -0
  418. package/src/tool/apply_patch.txt +33 -0
  419. package/src/tool/bash.ts +496 -0
  420. package/src/tool/bash.txt +117 -0
  421. package/src/tool/batch.ts +183 -0
  422. package/src/tool/batch.txt +24 -0
  423. package/src/tool/codesearch.ts +132 -0
  424. package/src/tool/codesearch.txt +12 -0
  425. package/src/tool/edit.ts +667 -0
  426. package/src/tool/edit.txt +10 -0
  427. package/src/tool/external-directory.ts +37 -0
  428. package/src/tool/glob.ts +78 -0
  429. package/src/tool/glob.txt +6 -0
  430. package/src/tool/grep.ts +156 -0
  431. package/src/tool/grep.txt +8 -0
  432. package/src/tool/invalid.ts +17 -0
  433. package/src/tool/ls.ts +121 -0
  434. package/src/tool/ls.txt +1 -0
  435. package/src/tool/lsp.ts +97 -0
  436. package/src/tool/lsp.txt +19 -0
  437. package/src/tool/multiedit.ts +46 -0
  438. package/src/tool/multiedit.txt +41 -0
  439. package/src/tool/plan-enter.txt +14 -0
  440. package/src/tool/plan-exit.txt +13 -0
  441. package/src/tool/plan.ts +131 -0
  442. package/src/tool/question.ts +46 -0
  443. package/src/tool/question.txt +10 -0
  444. package/src/tool/read.ts +296 -0
  445. package/src/tool/read.txt +14 -0
  446. package/src/tool/registry.ts +248 -0
  447. package/src/tool/schema.ts +17 -0
  448. package/src/tool/skill.ts +105 -0
  449. package/src/tool/task.ts +166 -0
  450. package/src/tool/task.txt +60 -0
  451. package/src/tool/todo.ts +48 -0
  452. package/src/tool/todowrite.txt +167 -0
  453. package/src/tool/tool.ts +112 -0
  454. package/src/tool/truncate.ts +144 -0
  455. package/src/tool/truncation-dir.ts +4 -0
  456. package/src/tool/webfetch.ts +206 -0
  457. package/src/tool/webfetch.txt +13 -0
  458. package/src/tool/websearch.ts +150 -0
  459. package/src/tool/websearch.txt +14 -0
  460. package/src/tool/write.ts +84 -0
  461. package/src/tool/write.txt +8 -0
  462. package/src/util/abort.ts +35 -0
  463. package/src/util/archive.ts +17 -0
  464. package/src/util/color.ts +19 -0
  465. package/src/util/context.ts +25 -0
  466. package/src/util/data-url.ts +9 -0
  467. package/src/util/defer.ts +12 -0
  468. package/src/util/effect-http-client.ts +11 -0
  469. package/src/util/effect-zod.ts +98 -0
  470. package/src/util/error.ts +77 -0
  471. package/src/util/filesystem.ts +245 -0
  472. package/src/util/flock.ts +333 -0
  473. package/src/util/fn.ts +21 -0
  474. package/src/util/format.ts +20 -0
  475. package/src/util/glob.ts +34 -0
  476. package/src/util/hash.ts +7 -0
  477. package/src/util/iife.ts +3 -0
  478. package/src/util/keybind.ts +103 -0
  479. package/src/util/lazy.ts +23 -0
  480. package/src/util/locale.ts +81 -0
  481. package/src/util/lock.ts +98 -0
  482. package/src/util/log.ts +182 -0
  483. package/src/util/network.ts +9 -0
  484. package/src/util/process.ts +176 -0
  485. package/src/util/queue.ts +32 -0
  486. package/src/util/record.ts +3 -0
  487. package/src/util/rpc.ts +66 -0
  488. package/src/util/schema.ts +53 -0
  489. package/src/util/scrap.ts +10 -0
  490. package/src/util/signal.ts +12 -0
  491. package/src/util/timeout.ts +14 -0
  492. package/src/util/token.ts +7 -0
  493. package/src/util/update-schema.ts +13 -0
  494. package/src/util/which.ts +14 -0
  495. package/src/util/wildcard.ts +59 -0
  496. package/src/worktree/index.ts +612 -0
  497. package/sst-env.d.ts +10 -0
  498. package/test/AGENTS.md +81 -0
  499. package/test/account/repo.test.ts +326 -0
  500. package/test/account/service.test.ts +393 -0
  501. package/test/acp/agent-interface.test.ts +51 -0
  502. package/test/acp/event-subscription.test.ts +685 -0
  503. package/test/agent/agent.test.ts +717 -0
  504. package/test/auth/auth.test.ts +58 -0
  505. package/test/bus/bus-effect.test.ts +164 -0
  506. package/test/bus/bus-integration.test.ts +87 -0
  507. package/test/bus/bus.test.ts +219 -0
  508. package/test/cli/account.test.ts +26 -0
  509. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  510. package/test/cli/github-action.test.ts +198 -0
  511. package/test/cli/github-remote.test.ts +80 -0
  512. package/test/cli/import.test.ts +54 -0
  513. package/test/cli/plugin-auth-picker.test.ts +120 -0
  514. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  515. package/test/cli/tui/plugin-add.test.ts +107 -0
  516. package/test/cli/tui/plugin-install.test.ts +89 -0
  517. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  518. package/test/cli/tui/plugin-loader-entrypoint.test.ts +492 -0
  519. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  520. package/test/cli/tui/plugin-loader.test.ts +752 -0
  521. package/test/cli/tui/plugin-toggle.test.ts +159 -0
  522. package/test/cli/tui/slot-replace.test.tsx +47 -0
  523. package/test/cli/tui/theme-store.test.ts +51 -0
  524. package/test/cli/tui/thread.test.ts +128 -0
  525. package/test/cli/tui/transcript.test.ts +426 -0
  526. package/test/config/agent-color.test.ts +71 -0
  527. package/test/config/config.test.ts +2348 -0
  528. package/test/config/fixtures/empty-frontmatter.md +4 -0
  529. package/test/config/fixtures/frontmatter.md +28 -0
  530. package/test/config/fixtures/markdown-header.md +11 -0
  531. package/test/config/fixtures/no-frontmatter.md +1 -0
  532. package/test/config/fixtures/weird-model-id.md +13 -0
  533. package/test/config/markdown.test.ts +228 -0
  534. package/test/config/tui.test.ts +752 -0
  535. package/test/control-plane/sse.test.ts +56 -0
  536. package/test/effect/cross-spawn-spawner.test.ts +412 -0
  537. package/test/effect/instance-state.test.ts +482 -0
  538. package/test/effect/run-service.test.ts +46 -0
  539. package/test/effect/runner.test.ts +523 -0
  540. package/test/fake/provider.ts +81 -0
  541. package/test/file/fsmonitor.test.ts +62 -0
  542. package/test/file/ignore.test.ts +10 -0
  543. package/test/file/index.test.ts +946 -0
  544. package/test/file/path-traversal.test.ts +198 -0
  545. package/test/file/ripgrep.test.ts +54 -0
  546. package/test/file/time.test.ts +445 -0
  547. package/test/file/watcher.test.ts +247 -0
  548. package/test/filesystem/filesystem.test.ts +319 -0
  549. package/test/fixture/db.ts +11 -0
  550. package/test/fixture/fixture.test.ts +26 -0
  551. package/test/fixture/fixture.ts +172 -0
  552. package/test/fixture/flock-worker.ts +72 -0
  553. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  554. package/test/fixture/plug-worker.ts +93 -0
  555. package/test/fixture/plugin-meta-worker.ts +26 -0
  556. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  557. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  558. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  559. package/test/fixture/skills/index.json +6 -0
  560. package/test/fixture/tui-plugin.ts +337 -0
  561. package/test/fixture/tui-runtime.ts +27 -0
  562. package/test/format/format.test.ts +171 -0
  563. package/test/git/git.test.ts +128 -0
  564. package/test/ide/ide.test.ts +82 -0
  565. package/test/installation/installation.test.ts +151 -0
  566. package/test/keybind.test.ts +421 -0
  567. package/test/lib/effect.ts +53 -0
  568. package/test/lib/filesystem.ts +10 -0
  569. package/test/lib/llm-server.ts +795 -0
  570. package/test/lsp/client.test.ts +95 -0
  571. package/test/lsp/index.test.ts +55 -0
  572. package/test/lsp/launch.test.ts +22 -0
  573. package/test/lsp/lifecycle.test.ts +147 -0
  574. package/test/mcp/headers.test.ts +153 -0
  575. package/test/mcp/lifecycle.test.ts +750 -0
  576. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  577. package/test/mcp/oauth-browser.test.ts +249 -0
  578. package/test/memory/abort-leak.test.ts +137 -0
  579. package/test/patch/patch.test.ts +348 -0
  580. package/test/permission/arity.test.ts +33 -0
  581. package/test/permission/next.test.ts +1148 -0
  582. package/test/permission-task.test.ts +323 -0
  583. package/test/plugin/auth-override.test.ts +74 -0
  584. package/test/plugin/codex.test.ts +123 -0
  585. package/test/plugin/github-copilot-models.test.ts +117 -0
  586. package/test/plugin/install-concurrency.test.ts +140 -0
  587. package/test/plugin/install.test.ts +570 -0
  588. package/test/plugin/loader-shared.test.ts +1136 -0
  589. package/test/plugin/meta.test.ts +137 -0
  590. package/test/plugin/trigger.test.ts +111 -0
  591. package/test/preload.ts +90 -0
  592. package/test/project/migrate-global.test.ts +140 -0
  593. package/test/project/project.test.ts +459 -0
  594. package/test/project/state.test.ts +115 -0
  595. package/test/project/vcs.test.ts +228 -0
  596. package/test/project/worktree-remove.test.ts +96 -0
  597. package/test/project/worktree.test.ts +173 -0
  598. package/test/provider/amazon-bedrock.test.ts +447 -0
  599. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  600. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  601. package/test/provider/gitlab-duo.test.ts +412 -0
  602. package/test/provider/provider.test.ts +2284 -0
  603. package/test/provider/transform.test.ts +2839 -0
  604. package/test/pty/pty-output-isolation.test.ts +141 -0
  605. package/test/pty/pty-session.test.ts +92 -0
  606. package/test/pty/pty-shell.test.ts +59 -0
  607. package/test/question/question.test.ts +453 -0
  608. package/test/server/global-session-list.test.ts +89 -0
  609. package/test/server/project-init-git.test.ts +121 -0
  610. package/test/server/session-actions.test.ts +83 -0
  611. package/test/server/session-list.test.ts +98 -0
  612. package/test/server/session-messages.test.ts +159 -0
  613. package/test/server/session-select.test.ts +84 -0
  614. package/test/session/compaction.test.ts +1212 -0
  615. package/test/session/instruction.test.ts +286 -0
  616. package/test/session/llm.test.ts +1098 -0
  617. package/test/session/message-v2.test.ts +957 -0
  618. package/test/session/messages-pagination.test.ts +885 -0
  619. package/test/session/processor-effect.test.ts +747 -0
  620. package/test/session/prompt-effect.test.ts +1241 -0
  621. package/test/session/prompt.test.ts +518 -0
  622. package/test/session/retry.test.ts +232 -0
  623. package/test/session/revert-compact.test.ts +621 -0
  624. package/test/session/session.test.ts +142 -0
  625. package/test/session/snapshot-tool-race.test.ts +242 -0
  626. package/test/session/structured-output-integration.test.ts +233 -0
  627. package/test/session/structured-output.test.ts +391 -0
  628. package/test/session/system.test.ts +59 -0
  629. package/test/share/share-next.test.ts +333 -0
  630. package/test/shell/shell.test.ts +73 -0
  631. package/test/skill/discovery.test.ts +116 -0
  632. package/test/skill/skill.test.ts +392 -0
  633. package/test/snapshot/snapshot.test.ts +1312 -0
  634. package/test/storage/db.test.ts +14 -0
  635. package/test/storage/json-migration.test.ts +849 -0
  636. package/test/storage/storage.test.ts +295 -0
  637. package/test/sync/index.test.ts +191 -0
  638. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  639. package/test/tool/apply_patch.test.ts +567 -0
  640. package/test/tool/bash.test.ts +1099 -0
  641. package/test/tool/edit.test.ts +681 -0
  642. package/test/tool/external-directory.test.ts +198 -0
  643. package/test/tool/fixtures/large-image.png +0 -0
  644. package/test/tool/fixtures/models-api.json +65179 -0
  645. package/test/tool/grep.test.ts +111 -0
  646. package/test/tool/question.test.ts +126 -0
  647. package/test/tool/read.test.ts +546 -0
  648. package/test/tool/registry.test.ts +126 -0
  649. package/test/tool/skill.test.ts +167 -0
  650. package/test/tool/task.test.ts +49 -0
  651. package/test/tool/tool-define.test.ts +101 -0
  652. package/test/tool/truncation.test.ts +161 -0
  653. package/test/tool/webfetch.test.ts +101 -0
  654. package/test/tool/write.test.ts +353 -0
  655. package/test/util/data-url.test.ts +14 -0
  656. package/test/util/effect-zod.test.ts +61 -0
  657. package/test/util/error.test.ts +38 -0
  658. package/test/util/filesystem.test.ts +656 -0
  659. package/test/util/flock.test.ts +383 -0
  660. package/test/util/format.test.ts +59 -0
  661. package/test/util/glob.test.ts +164 -0
  662. package/test/util/iife.test.ts +36 -0
  663. package/test/util/lazy.test.ts +50 -0
  664. package/test/util/lock.test.ts +72 -0
  665. package/test/util/module.test.ts +59 -0
  666. package/test/util/process.test.ts +128 -0
  667. package/test/util/timeout.test.ts +21 -0
  668. package/test/util/which.test.ts +100 -0
  669. package/test/util/wildcard.test.ts +90 -0
  670. package/tsconfig.json +23 -0
@@ -0,0 +1,885 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import path from "path"
3
+ import { Instance } from "../../src/project/instance"
4
+ import { Session } from "../../src/session"
5
+ import { MessageV2 } from "../../src/session/message-v2"
6
+ import { MessageID, PartID, type SessionID } from "../../src/session/schema"
7
+ import { ModelID, ProviderID } from "../../src/provider/schema"
8
+ import { Log } from "../../src/util/log"
9
+
10
+ const root = path.join(__dirname, "../..")
11
+ Log.init({ print: false })
12
+
13
+ async function fill(sessionID: SessionID, count: number, time = (i: number) => Date.now() + i) {
14
+ const ids = [] as MessageID[]
15
+ for (let i = 0; i < count; i++) {
16
+ const id = MessageID.ascending()
17
+ ids.push(id)
18
+ await Session.updateMessage({
19
+ id,
20
+ sessionID,
21
+ role: "user",
22
+ time: { created: time(i) },
23
+ agent: "test",
24
+ model: { providerID: "test", modelID: "test" },
25
+ tools: {},
26
+ mode: "",
27
+ } as unknown as MessageV2.Info)
28
+ await Session.updatePart({
29
+ id: PartID.ascending(),
30
+ sessionID,
31
+ messageID: id,
32
+ type: "text",
33
+ text: `m${i}`,
34
+ })
35
+ }
36
+ return ids
37
+ }
38
+
39
+ async function addUser(sessionID: SessionID, text?: string) {
40
+ const id = MessageID.ascending()
41
+ await Session.updateMessage({
42
+ id,
43
+ sessionID,
44
+ role: "user",
45
+ time: { created: Date.now() },
46
+ agent: "test",
47
+ model: { providerID: "test", modelID: "test" },
48
+ tools: {},
49
+ mode: "",
50
+ } as unknown as MessageV2.Info)
51
+ if (text) {
52
+ await Session.updatePart({
53
+ id: PartID.ascending(),
54
+ sessionID,
55
+ messageID: id,
56
+ type: "text",
57
+ text,
58
+ })
59
+ }
60
+ return id
61
+ }
62
+
63
+ async function addAssistant(
64
+ sessionID: SessionID,
65
+ parentID: MessageID,
66
+ opts?: { summary?: boolean; finish?: string; error?: MessageV2.Assistant["error"] },
67
+ ) {
68
+ const id = MessageID.ascending()
69
+ await Session.updateMessage({
70
+ id,
71
+ sessionID,
72
+ role: "assistant",
73
+ time: { created: Date.now() },
74
+ parentID,
75
+ modelID: ModelID.make("test"),
76
+ providerID: ProviderID.make("test"),
77
+ mode: "",
78
+ agent: "default",
79
+ path: { cwd: "/", root: "/" },
80
+ cost: 0,
81
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
82
+ summary: opts?.summary,
83
+ finish: opts?.finish,
84
+ error: opts?.error,
85
+ } as unknown as MessageV2.Info)
86
+ return id
87
+ }
88
+
89
+ async function addCompactionPart(sessionID: SessionID, messageID: MessageID) {
90
+ await Session.updatePart({
91
+ id: PartID.ascending(),
92
+ sessionID,
93
+ messageID,
94
+ type: "compaction",
95
+ auto: true,
96
+ } as any)
97
+ }
98
+
99
+ describe("MessageV2.page", () => {
100
+ test("returns sync result", async () => {
101
+ await Instance.provide({
102
+ directory: root,
103
+ fn: async () => {
104
+ const session = await Session.create({})
105
+ await fill(session.id, 2)
106
+
107
+ const result = MessageV2.page({ sessionID: session.id, limit: 10 })
108
+ expect(result).toBeDefined()
109
+ expect(result.items).toBeArray()
110
+
111
+ await Session.remove(session.id)
112
+ },
113
+ })
114
+ })
115
+
116
+ test("pages backward with opaque cursors", async () => {
117
+ await Instance.provide({
118
+ directory: root,
119
+ fn: async () => {
120
+ const session = await Session.create({})
121
+ const ids = await fill(session.id, 6)
122
+
123
+ const a = MessageV2.page({ sessionID: session.id, limit: 2 })
124
+ expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2))
125
+ expect(a.items.every((item) => item.parts.length === 1)).toBe(true)
126
+ expect(a.more).toBe(true)
127
+ expect(a.cursor).toBeTruthy()
128
+
129
+ const b = MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! })
130
+ expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2))
131
+ expect(b.more).toBe(true)
132
+ expect(b.cursor).toBeTruthy()
133
+
134
+ const c = MessageV2.page({ sessionID: session.id, limit: 2, before: b.cursor! })
135
+ expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2))
136
+ expect(c.more).toBe(false)
137
+ expect(c.cursor).toBeUndefined()
138
+
139
+ await Session.remove(session.id)
140
+ },
141
+ })
142
+ })
143
+
144
+ test("returns items in chronological order within a page", async () => {
145
+ await Instance.provide({
146
+ directory: root,
147
+ fn: async () => {
148
+ const session = await Session.create({})
149
+ const ids = await fill(session.id, 4)
150
+
151
+ const result = MessageV2.page({ sessionID: session.id, limit: 4 })
152
+ expect(result.items.map((item) => item.info.id)).toEqual(ids)
153
+
154
+ await Session.remove(session.id)
155
+ },
156
+ })
157
+ })
158
+
159
+ test("returns empty items for session with no messages", async () => {
160
+ await Instance.provide({
161
+ directory: root,
162
+ fn: async () => {
163
+ const session = await Session.create({})
164
+
165
+ const result = MessageV2.page({ sessionID: session.id, limit: 10 })
166
+ expect(result.items).toEqual([])
167
+ expect(result.more).toBe(false)
168
+ expect(result.cursor).toBeUndefined()
169
+
170
+ await Session.remove(session.id)
171
+ },
172
+ })
173
+ })
174
+
175
+ test("throws NotFoundError for non-existent session", async () => {
176
+ await Instance.provide({
177
+ directory: root,
178
+ fn: async () => {
179
+ const fake = "non-existent-session" as SessionID
180
+ expect(() => MessageV2.page({ sessionID: fake, limit: 10 })).toThrow("NotFoundError")
181
+ },
182
+ })
183
+ })
184
+
185
+ test("handles exact limit boundary", async () => {
186
+ await Instance.provide({
187
+ directory: root,
188
+ fn: async () => {
189
+ const session = await Session.create({})
190
+ const ids = await fill(session.id, 3)
191
+
192
+ const result = MessageV2.page({ sessionID: session.id, limit: 3 })
193
+ expect(result.items.map((item) => item.info.id)).toEqual(ids)
194
+ expect(result.more).toBe(false)
195
+ expect(result.cursor).toBeUndefined()
196
+
197
+ await Session.remove(session.id)
198
+ },
199
+ })
200
+ })
201
+
202
+ test("limit of 1 returns single newest message", async () => {
203
+ await Instance.provide({
204
+ directory: root,
205
+ fn: async () => {
206
+ const session = await Session.create({})
207
+ const ids = await fill(session.id, 5)
208
+
209
+ const result = MessageV2.page({ sessionID: session.id, limit: 1 })
210
+ expect(result.items).toHaveLength(1)
211
+ expect(result.items[0].info.id).toBe(ids[ids.length - 1])
212
+ expect(result.more).toBe(true)
213
+
214
+ await Session.remove(session.id)
215
+ },
216
+ })
217
+ })
218
+
219
+ test("hydrates multiple parts per message", async () => {
220
+ await Instance.provide({
221
+ directory: root,
222
+ fn: async () => {
223
+ const session = await Session.create({})
224
+ const [id] = await fill(session.id, 1)
225
+
226
+ await Session.updatePart({
227
+ id: PartID.ascending(),
228
+ sessionID: session.id,
229
+ messageID: id,
230
+ type: "text",
231
+ text: "extra",
232
+ })
233
+
234
+ const result = MessageV2.page({ sessionID: session.id, limit: 10 })
235
+ expect(result.items).toHaveLength(1)
236
+ expect(result.items[0].parts).toHaveLength(2)
237
+
238
+ await Session.remove(session.id)
239
+ },
240
+ })
241
+ })
242
+
243
+ test("accepts cursors from fractional timestamps", async () => {
244
+ await Instance.provide({
245
+ directory: root,
246
+ fn: async () => {
247
+ const session = await Session.create({})
248
+ const ids = await fill(session.id, 4, (i) => 1000.5 + i)
249
+
250
+ const a = MessageV2.page({ sessionID: session.id, limit: 2 })
251
+ const b = MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! })
252
+
253
+ expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2))
254
+ expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2))
255
+
256
+ await Session.remove(session.id)
257
+ },
258
+ })
259
+ })
260
+
261
+ test("messages with same timestamp are ordered by id", async () => {
262
+ await Instance.provide({
263
+ directory: root,
264
+ fn: async () => {
265
+ const session = await Session.create({})
266
+ const ids = await fill(session.id, 4, () => 1000)
267
+
268
+ const a = MessageV2.page({ sessionID: session.id, limit: 2 })
269
+ expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2))
270
+ expect(a.more).toBe(true)
271
+
272
+ const b = MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! })
273
+ expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2))
274
+ expect(b.more).toBe(false)
275
+
276
+ await Session.remove(session.id)
277
+ },
278
+ })
279
+ })
280
+
281
+ test("does not return messages from other sessions", async () => {
282
+ await Instance.provide({
283
+ directory: root,
284
+ fn: async () => {
285
+ const a = await Session.create({})
286
+ const b = await Session.create({})
287
+ await fill(a.id, 3)
288
+ await fill(b.id, 2)
289
+
290
+ const resultA = MessageV2.page({ sessionID: a.id, limit: 10 })
291
+ const resultB = MessageV2.page({ sessionID: b.id, limit: 10 })
292
+ expect(resultA.items).toHaveLength(3)
293
+ expect(resultB.items).toHaveLength(2)
294
+ expect(resultA.items.every((item) => item.info.sessionID === a.id)).toBe(true)
295
+ expect(resultB.items.every((item) => item.info.sessionID === b.id)).toBe(true)
296
+
297
+ await Session.remove(a.id)
298
+ await Session.remove(b.id)
299
+ },
300
+ })
301
+ })
302
+
303
+ test("large limit returns all messages without cursor", async () => {
304
+ await Instance.provide({
305
+ directory: root,
306
+ fn: async () => {
307
+ const session = await Session.create({})
308
+ const ids = await fill(session.id, 10)
309
+
310
+ const result = MessageV2.page({ sessionID: session.id, limit: 100 })
311
+ expect(result.items).toHaveLength(10)
312
+ expect(result.items.map((item) => item.info.id)).toEqual(ids)
313
+ expect(result.more).toBe(false)
314
+ expect(result.cursor).toBeUndefined()
315
+
316
+ await Session.remove(session.id)
317
+ },
318
+ })
319
+ })
320
+ })
321
+
322
+ describe("MessageV2.stream", () => {
323
+ test("yields items newest first", async () => {
324
+ await Instance.provide({
325
+ directory: root,
326
+ fn: async () => {
327
+ const session = await Session.create({})
328
+ const ids = await fill(session.id, 5)
329
+
330
+ const items = Array.from(MessageV2.stream(session.id))
331
+ expect(items.map((item) => item.info.id)).toEqual(ids.slice().reverse())
332
+
333
+ await Session.remove(session.id)
334
+ },
335
+ })
336
+ })
337
+
338
+ test("yields nothing for empty session", async () => {
339
+ await Instance.provide({
340
+ directory: root,
341
+ fn: async () => {
342
+ const session = await Session.create({})
343
+
344
+ const items = Array.from(MessageV2.stream(session.id))
345
+ expect(items).toHaveLength(0)
346
+
347
+ await Session.remove(session.id)
348
+ },
349
+ })
350
+ })
351
+
352
+ test("yields single message", async () => {
353
+ await Instance.provide({
354
+ directory: root,
355
+ fn: async () => {
356
+ const session = await Session.create({})
357
+ const ids = await fill(session.id, 1)
358
+
359
+ const items = Array.from(MessageV2.stream(session.id))
360
+ expect(items).toHaveLength(1)
361
+ expect(items[0].info.id).toBe(ids[0])
362
+
363
+ await Session.remove(session.id)
364
+ },
365
+ })
366
+ })
367
+
368
+ test("hydrates parts for each yielded message", async () => {
369
+ await Instance.provide({
370
+ directory: root,
371
+ fn: async () => {
372
+ const session = await Session.create({})
373
+ await fill(session.id, 3)
374
+
375
+ const items = Array.from(MessageV2.stream(session.id))
376
+ for (const item of items) {
377
+ expect(item.parts).toHaveLength(1)
378
+ expect(item.parts[0].type).toBe("text")
379
+ }
380
+
381
+ await Session.remove(session.id)
382
+ },
383
+ })
384
+ })
385
+
386
+ test("handles sets exceeding internal page size", async () => {
387
+ await Instance.provide({
388
+ directory: root,
389
+ fn: async () => {
390
+ const session = await Session.create({})
391
+ const ids = await fill(session.id, 60)
392
+
393
+ const items = Array.from(MessageV2.stream(session.id))
394
+ expect(items).toHaveLength(60)
395
+ expect(items[0].info.id).toBe(ids[ids.length - 1])
396
+ expect(items[59].info.id).toBe(ids[0])
397
+
398
+ await Session.remove(session.id)
399
+ },
400
+ })
401
+ })
402
+
403
+ test("is a sync generator", async () => {
404
+ await Instance.provide({
405
+ directory: root,
406
+ fn: async () => {
407
+ const session = await Session.create({})
408
+ await fill(session.id, 1)
409
+
410
+ const gen = MessageV2.stream(session.id)
411
+ const first = gen.next()
412
+ // sync generator returns { value, done } directly, not a Promise
413
+ expect(first).toHaveProperty("value")
414
+ expect(first).toHaveProperty("done")
415
+ expect(first.done).toBe(false)
416
+
417
+ await Session.remove(session.id)
418
+ },
419
+ })
420
+ })
421
+ })
422
+
423
+ describe("MessageV2.parts", () => {
424
+ test("returns parts for a message", async () => {
425
+ await Instance.provide({
426
+ directory: root,
427
+ fn: async () => {
428
+ const session = await Session.create({})
429
+ const [id] = await fill(session.id, 1)
430
+
431
+ const result = MessageV2.parts(id)
432
+ expect(result).toHaveLength(1)
433
+ expect(result[0].type).toBe("text")
434
+ expect((result[0] as MessageV2.TextPart).text).toBe("m0")
435
+
436
+ await Session.remove(session.id)
437
+ },
438
+ })
439
+ })
440
+
441
+ test("returns empty array for message with no parts", async () => {
442
+ await Instance.provide({
443
+ directory: root,
444
+ fn: async () => {
445
+ const session = await Session.create({})
446
+ const id = await addUser(session.id)
447
+
448
+ const result = MessageV2.parts(id)
449
+ expect(result).toEqual([])
450
+
451
+ await Session.remove(session.id)
452
+ },
453
+ })
454
+ })
455
+
456
+ test("returns multiple parts in order", async () => {
457
+ await Instance.provide({
458
+ directory: root,
459
+ fn: async () => {
460
+ const session = await Session.create({})
461
+ const [id] = await fill(session.id, 1)
462
+
463
+ await Session.updatePart({
464
+ id: PartID.ascending(),
465
+ sessionID: session.id,
466
+ messageID: id,
467
+ type: "text",
468
+ text: "second",
469
+ })
470
+ await Session.updatePart({
471
+ id: PartID.ascending(),
472
+ sessionID: session.id,
473
+ messageID: id,
474
+ type: "text",
475
+ text: "third",
476
+ })
477
+
478
+ const result = MessageV2.parts(id)
479
+ expect(result).toHaveLength(3)
480
+ expect((result[0] as MessageV2.TextPart).text).toBe("m0")
481
+ expect((result[1] as MessageV2.TextPart).text).toBe("second")
482
+ expect((result[2] as MessageV2.TextPart).text).toBe("third")
483
+
484
+ await Session.remove(session.id)
485
+ },
486
+ })
487
+ })
488
+
489
+ test("returns empty for non-existent message id", async () => {
490
+ await Instance.provide({
491
+ directory: root,
492
+ fn: async () => {
493
+ await Session.create({})
494
+ const result = MessageV2.parts(MessageID.ascending())
495
+ expect(result).toEqual([])
496
+ },
497
+ })
498
+ })
499
+
500
+ test("parts contain sessionID and messageID", async () => {
501
+ await Instance.provide({
502
+ directory: root,
503
+ fn: async () => {
504
+ const session = await Session.create({})
505
+ const [id] = await fill(session.id, 1)
506
+
507
+ const result = MessageV2.parts(id)
508
+ expect(result[0].sessionID).toBe(session.id)
509
+ expect(result[0].messageID).toBe(id)
510
+
511
+ await Session.remove(session.id)
512
+ },
513
+ })
514
+ })
515
+ })
516
+
517
+ describe("MessageV2.get", () => {
518
+ test("returns message with hydrated parts", async () => {
519
+ await Instance.provide({
520
+ directory: root,
521
+ fn: async () => {
522
+ const session = await Session.create({})
523
+ const [id] = await fill(session.id, 1)
524
+
525
+ const result = MessageV2.get({ sessionID: session.id, messageID: id })
526
+ expect(result.info.id).toBe(id)
527
+ expect(result.info.sessionID).toBe(session.id)
528
+ expect(result.info.role).toBe("user")
529
+ expect(result.parts).toHaveLength(1)
530
+ expect((result.parts[0] as MessageV2.TextPart).text).toBe("m0")
531
+
532
+ await Session.remove(session.id)
533
+ },
534
+ })
535
+ })
536
+
537
+ test("throws NotFoundError for non-existent message", async () => {
538
+ await Instance.provide({
539
+ directory: root,
540
+ fn: async () => {
541
+ const session = await Session.create({})
542
+
543
+ expect(() => MessageV2.get({ sessionID: session.id, messageID: MessageID.ascending() })).toThrow(
544
+ "NotFoundError",
545
+ )
546
+
547
+ await Session.remove(session.id)
548
+ },
549
+ })
550
+ })
551
+
552
+ test("scopes by session id", async () => {
553
+ await Instance.provide({
554
+ directory: root,
555
+ fn: async () => {
556
+ const a = await Session.create({})
557
+ const b = await Session.create({})
558
+ const [id] = await fill(a.id, 1)
559
+
560
+ expect(() => MessageV2.get({ sessionID: b.id, messageID: id })).toThrow("NotFoundError")
561
+ const result = MessageV2.get({ sessionID: a.id, messageID: id })
562
+ expect(result.info.id).toBe(id)
563
+
564
+ await Session.remove(a.id)
565
+ await Session.remove(b.id)
566
+ },
567
+ })
568
+ })
569
+
570
+ test("returns message with multiple parts", async () => {
571
+ await Instance.provide({
572
+ directory: root,
573
+ fn: async () => {
574
+ const session = await Session.create({})
575
+ const [id] = await fill(session.id, 1)
576
+
577
+ await Session.updatePart({
578
+ id: PartID.ascending(),
579
+ sessionID: session.id,
580
+ messageID: id,
581
+ type: "text",
582
+ text: "extra",
583
+ })
584
+
585
+ const result = MessageV2.get({ sessionID: session.id, messageID: id })
586
+ expect(result.parts).toHaveLength(2)
587
+
588
+ await Session.remove(session.id)
589
+ },
590
+ })
591
+ })
592
+
593
+ test("returns assistant message with correct role", async () => {
594
+ await Instance.provide({
595
+ directory: root,
596
+ fn: async () => {
597
+ const session = await Session.create({})
598
+ const uid = await addUser(session.id, "hello")
599
+ const aid = await addAssistant(session.id, uid)
600
+
601
+ await Session.updatePart({
602
+ id: PartID.ascending(),
603
+ sessionID: session.id,
604
+ messageID: aid,
605
+ type: "text",
606
+ text: "response",
607
+ })
608
+
609
+ const result = MessageV2.get({ sessionID: session.id, messageID: aid })
610
+ expect(result.info.role).toBe("assistant")
611
+ expect(result.parts).toHaveLength(1)
612
+ expect((result.parts[0] as MessageV2.TextPart).text).toBe("response")
613
+
614
+ await Session.remove(session.id)
615
+ },
616
+ })
617
+ })
618
+
619
+ test("returns message with zero parts", async () => {
620
+ await Instance.provide({
621
+ directory: root,
622
+ fn: async () => {
623
+ const session = await Session.create({})
624
+ const id = await addUser(session.id)
625
+
626
+ const result = MessageV2.get({ sessionID: session.id, messageID: id })
627
+ expect(result.info.id).toBe(id)
628
+ expect(result.parts).toEqual([])
629
+
630
+ await Session.remove(session.id)
631
+ },
632
+ })
633
+ })
634
+ })
635
+
636
+ describe("MessageV2.filterCompacted", () => {
637
+ test("returns all messages when no compaction", async () => {
638
+ await Instance.provide({
639
+ directory: root,
640
+ fn: async () => {
641
+ const session = await Session.create({})
642
+ const ids = await fill(session.id, 5)
643
+
644
+ const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
645
+ expect(result).toHaveLength(5)
646
+ // reversed from newest-first to chronological
647
+ expect(result.map((item) => item.info.id)).toEqual(ids)
648
+
649
+ await Session.remove(session.id)
650
+ },
651
+ })
652
+ })
653
+
654
+ test("stops at compaction boundary and returns chronological order", async () => {
655
+ await Instance.provide({
656
+ directory: root,
657
+ fn: async () => {
658
+ const session = await Session.create({})
659
+
660
+ // Chronological: u1(+compaction part), a1(summary, parentID=u1), u2, a2
661
+ // Stream (newest first): a2, u2, a1(adds u1 to completed), u1(in completed + compaction) -> break
662
+ const u1 = await addUser(session.id, "first question")
663
+ const a1 = await addAssistant(session.id, u1, { summary: true, finish: "end_turn" })
664
+ await Session.updatePart({
665
+ id: PartID.ascending(),
666
+ sessionID: session.id,
667
+ messageID: a1,
668
+ type: "text",
669
+ text: "summary",
670
+ })
671
+ await addCompactionPart(session.id, u1)
672
+
673
+ const u2 = await addUser(session.id, "new question")
674
+ const a2 = await addAssistant(session.id, u2)
675
+ await Session.updatePart({
676
+ id: PartID.ascending(),
677
+ sessionID: session.id,
678
+ messageID: a2,
679
+ type: "text",
680
+ text: "new response",
681
+ })
682
+
683
+ const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
684
+ // Includes compaction boundary: u1, a1, u2, a2
685
+ expect(result[0].info.id).toBe(u1)
686
+ expect(result.length).toBe(4)
687
+
688
+ await Session.remove(session.id)
689
+ },
690
+ })
691
+ })
692
+
693
+ test("handles empty iterable", () => {
694
+ const result = MessageV2.filterCompacted([])
695
+ expect(result).toEqual([])
696
+ })
697
+
698
+ test("does not break on compaction part without matching summary", async () => {
699
+ await Instance.provide({
700
+ directory: root,
701
+ fn: async () => {
702
+ const session = await Session.create({})
703
+
704
+ const u1 = await addUser(session.id, "hello")
705
+ await addCompactionPart(session.id, u1)
706
+ const u2 = await addUser(session.id, "world")
707
+
708
+ const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
709
+ expect(result).toHaveLength(2)
710
+
711
+ await Session.remove(session.id)
712
+ },
713
+ })
714
+ })
715
+
716
+ test("skips assistant with error even if marked as summary", async () => {
717
+ await Instance.provide({
718
+ directory: root,
719
+ fn: async () => {
720
+ const session = await Session.create({})
721
+
722
+ const u1 = await addUser(session.id, "hello")
723
+ await addCompactionPart(session.id, u1)
724
+
725
+ const error = new MessageV2.APIError({
726
+ message: "boom",
727
+ isRetryable: true,
728
+ }).toObject() as MessageV2.Assistant["error"]
729
+ await addAssistant(session.id, u1, { summary: true, finish: "end_turn", error })
730
+ const u2 = await addUser(session.id, "retry")
731
+
732
+ const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
733
+ // Error assistant doesn't add to completed, so compaction boundary never triggers
734
+ expect(result).toHaveLength(3)
735
+
736
+ await Session.remove(session.id)
737
+ },
738
+ })
739
+ })
740
+
741
+ test("skips assistant without finish even if marked as summary", async () => {
742
+ await Instance.provide({
743
+ directory: root,
744
+ fn: async () => {
745
+ const session = await Session.create({})
746
+
747
+ const u1 = await addUser(session.id, "hello")
748
+ await addCompactionPart(session.id, u1)
749
+
750
+ // summary=true but no finish
751
+ await addAssistant(session.id, u1, { summary: true })
752
+ const u2 = await addUser(session.id, "next")
753
+
754
+ const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
755
+ expect(result).toHaveLength(3)
756
+
757
+ await Session.remove(session.id)
758
+ },
759
+ })
760
+ })
761
+
762
+ test("works with array input", () => {
763
+ // filterCompacted accepts any Iterable, not just generators
764
+ const id = MessageID.ascending()
765
+ const items: MessageV2.WithParts[] = [
766
+ {
767
+ info: {
768
+ id,
769
+ sessionID: "s1",
770
+ role: "user",
771
+ time: { created: 1 },
772
+ agent: "test",
773
+ model: { providerID: "test", modelID: "test" },
774
+ } as unknown as MessageV2.Info,
775
+ parts: [{ type: "text", text: "hello" }] as unknown as MessageV2.Part[],
776
+ },
777
+ ]
778
+ const result = MessageV2.filterCompacted(items)
779
+ expect(result).toHaveLength(1)
780
+ expect(result[0].info.id).toBe(id)
781
+ })
782
+ })
783
+
784
+ describe("MessageV2.cursor", () => {
785
+ test("encode/decode roundtrip", () => {
786
+ const input = { id: MessageID.ascending(), time: 1234567890 }
787
+ const encoded = MessageV2.cursor.encode(input)
788
+ const decoded = MessageV2.cursor.decode(encoded)
789
+ expect(decoded.id).toBe(input.id)
790
+ expect(decoded.time).toBe(input.time)
791
+ })
792
+
793
+ test("encode/decode with fractional time", () => {
794
+ const input = { id: MessageID.ascending(), time: 1234567890.5 }
795
+ const encoded = MessageV2.cursor.encode(input)
796
+ const decoded = MessageV2.cursor.decode(encoded)
797
+ expect(decoded.time).toBe(1234567890.5)
798
+ })
799
+
800
+ test("encoded cursor is base64url", () => {
801
+ const encoded = MessageV2.cursor.encode({ id: MessageID.ascending(), time: 0 })
802
+ expect(encoded).toMatch(/^[A-Za-z0-9_-]+$/)
803
+ })
804
+ })
805
+
806
+ describe("MessageV2 consistency", () => {
807
+ test("page hydration matches get for each message", async () => {
808
+ await Instance.provide({
809
+ directory: root,
810
+ fn: async () => {
811
+ const session = await Session.create({})
812
+ await fill(session.id, 3)
813
+
814
+ const paged = MessageV2.page({ sessionID: session.id, limit: 10 })
815
+ for (const item of paged.items) {
816
+ const got = MessageV2.get({ sessionID: session.id, messageID: item.info.id as MessageID })
817
+ expect(got.info).toEqual(item.info)
818
+ expect(got.parts).toEqual(item.parts)
819
+ }
820
+
821
+ await Session.remove(session.id)
822
+ },
823
+ })
824
+ })
825
+
826
+ test("parts from get match standalone parts call", async () => {
827
+ await Instance.provide({
828
+ directory: root,
829
+ fn: async () => {
830
+ const session = await Session.create({})
831
+ const [id] = await fill(session.id, 1)
832
+
833
+ const got = MessageV2.get({ sessionID: session.id, messageID: id })
834
+ const standalone = MessageV2.parts(id)
835
+ expect(got.parts).toEqual(standalone)
836
+
837
+ await Session.remove(session.id)
838
+ },
839
+ })
840
+ })
841
+
842
+ test("stream collects same messages as exhaustive page iteration", async () => {
843
+ await Instance.provide({
844
+ directory: root,
845
+ fn: async () => {
846
+ const session = await Session.create({})
847
+ await fill(session.id, 7)
848
+
849
+ const streamed = Array.from(MessageV2.stream(session.id))
850
+
851
+ const paged = [] as MessageV2.WithParts[]
852
+ let cursor: string | undefined
853
+ while (true) {
854
+ const result = MessageV2.page({ sessionID: session.id, limit: 3, before: cursor })
855
+ for (let i = result.items.length - 1; i >= 0; i--) {
856
+ paged.push(result.items[i])
857
+ }
858
+ if (!result.more || !result.cursor) break
859
+ cursor = result.cursor
860
+ }
861
+
862
+ expect(streamed.map((m) => m.info.id)).toEqual(paged.map((m) => m.info.id))
863
+
864
+ await Session.remove(session.id)
865
+ },
866
+ })
867
+ })
868
+
869
+ test("filterCompacted of full stream returns same as Array.from when no compaction", async () => {
870
+ await Instance.provide({
871
+ directory: root,
872
+ fn: async () => {
873
+ const session = await Session.create({})
874
+ const ids = await fill(session.id, 4)
875
+
876
+ const filtered = MessageV2.filterCompacted(MessageV2.stream(session.id))
877
+ const all = Array.from(MessageV2.stream(session.id)).reverse()
878
+
879
+ expect(filtered.map((m) => m.info.id)).toEqual(all.map((m) => m.info.id))
880
+
881
+ await Session.remove(session.id)
882
+ },
883
+ })
884
+ })
885
+ })