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,245 @@
1
+ import { chmod, mkdir, readFile, stat as statFile, writeFile } from "fs/promises"
2
+ import { createWriteStream, existsSync, statSync } from "fs"
3
+ import { lookup } from "mime-types"
4
+ import { realpathSync } from "fs"
5
+ import { dirname, join, relative, resolve as pathResolve, win32 } from "path"
6
+ import { Readable } from "stream"
7
+ import { pipeline } from "stream/promises"
8
+ import { Glob } from "./glob"
9
+
10
+ export namespace Filesystem {
11
+ // Fast sync version for metadata checks
12
+ export async function exists(p: string): Promise<boolean> {
13
+ return existsSync(p)
14
+ }
15
+
16
+ export async function isDir(p: string): Promise<boolean> {
17
+ try {
18
+ return statSync(p).isDirectory()
19
+ } catch {
20
+ return false
21
+ }
22
+ }
23
+
24
+ export function stat(p: string): ReturnType<typeof statSync> | undefined {
25
+ return statSync(p, { throwIfNoEntry: false }) ?? undefined
26
+ }
27
+
28
+ export async function statAsync(p: string): Promise<ReturnType<typeof statSync> | undefined> {
29
+ return statFile(p).catch((e) => {
30
+ if (isEnoent(e)) return undefined
31
+ throw e
32
+ })
33
+ }
34
+
35
+ export async function size(p: string): Promise<number> {
36
+ const s = stat(p)?.size ?? 0
37
+ return typeof s === "bigint" ? Number(s) : s
38
+ }
39
+
40
+ export async function readText(p: string): Promise<string> {
41
+ return readFile(p, "utf-8")
42
+ }
43
+
44
+ export async function readJson<T = any>(p: string): Promise<T> {
45
+ return JSON.parse(await readFile(p, "utf-8"))
46
+ }
47
+
48
+ export async function readBytes(p: string): Promise<Buffer> {
49
+ return readFile(p)
50
+ }
51
+
52
+ export async function readArrayBuffer(p: string): Promise<ArrayBuffer> {
53
+ const buf = await readFile(p)
54
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer
55
+ }
56
+
57
+ function isEnoent(e: unknown): e is { code: "ENOENT" } {
58
+ return typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "ENOENT"
59
+ }
60
+
61
+ export async function write(p: string, content: string | Buffer | Uint8Array, mode?: number): Promise<void> {
62
+ try {
63
+ if (mode) {
64
+ await writeFile(p, content, { mode })
65
+ } else {
66
+ await writeFile(p, content)
67
+ }
68
+ } catch (e) {
69
+ if (isEnoent(e)) {
70
+ await mkdir(dirname(p), { recursive: true })
71
+ if (mode) {
72
+ await writeFile(p, content, { mode })
73
+ } else {
74
+ await writeFile(p, content)
75
+ }
76
+ return
77
+ }
78
+ throw e
79
+ }
80
+ }
81
+
82
+ export async function writeJson(p: string, data: unknown, mode?: number): Promise<void> {
83
+ return write(p, JSON.stringify(data, null, 2), mode)
84
+ }
85
+
86
+ export async function writeStream(
87
+ p: string,
88
+ stream: ReadableStream<Uint8Array> | Readable,
89
+ mode?: number,
90
+ ): Promise<void> {
91
+ const dir = dirname(p)
92
+ if (!existsSync(dir)) {
93
+ await mkdir(dir, { recursive: true })
94
+ }
95
+
96
+ const nodeStream = stream instanceof ReadableStream ? Readable.fromWeb(stream as any) : stream
97
+ const writeStream = createWriteStream(p)
98
+ await pipeline(nodeStream, writeStream)
99
+
100
+ if (mode) {
101
+ await chmod(p, mode)
102
+ }
103
+ }
104
+
105
+ export function mimeType(p: string): string {
106
+ return lookup(p) || "application/octet-stream"
107
+ }
108
+
109
+ /**
110
+ * On Windows, normalize a path to its canonical casing using the filesystem.
111
+ * This is needed because Windows paths are case-insensitive but LSP servers
112
+ * may return paths with different casing than what we send them.
113
+ */
114
+ export function normalizePath(p: string): string {
115
+ if (process.platform !== "win32") return p
116
+ const resolved = win32.normalize(win32.resolve(windowsPath(p)))
117
+ try {
118
+ return realpathSync.native(resolved)
119
+ } catch {
120
+ return resolved
121
+ }
122
+ }
123
+
124
+ export function normalizePathPattern(p: string): string {
125
+ if (process.platform !== "win32") return p
126
+ if (p === "*") return p
127
+ const match = p.match(/^(.*)[\\/]\*$/)
128
+ if (!match) return normalizePath(p)
129
+ const dir = /^[A-Za-z]:$/.test(match[1]) ? match[1] + "\\" : match[1]
130
+ return join(normalizePath(dir), "*")
131
+ }
132
+
133
+ // We cannot rely on path.resolve() here because git.exe may come from Git Bash, Cygwin, or MSYS2, so we need to translate these paths at the boundary.
134
+ // Also resolves symlinks so that callers using the result as a cache key
135
+ // always get the same canonical path for a given physical directory.
136
+ export function resolve(p: string): string {
137
+ const resolved = pathResolve(windowsPath(p))
138
+ try {
139
+ return normalizePath(realpathSync(resolved))
140
+ } catch (e) {
141
+ if (isEnoent(e)) return normalizePath(resolved)
142
+ throw e
143
+ }
144
+ }
145
+
146
+ export function windowsPath(p: string): string {
147
+ if (process.platform !== "win32") return p
148
+ return (
149
+ p
150
+ .replace(/^\/([a-zA-Z]):(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
151
+ // Git Bash for Windows paths are typically /<drive>/...
152
+ .replace(/^\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
153
+ // Cygwin git paths are typically /cygdrive/<drive>/...
154
+ .replace(/^\/cygdrive\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
155
+ // WSL paths are typically /mnt/<drive>/...
156
+ .replace(/^\/mnt\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
157
+ )
158
+ }
159
+ export function overlaps(a: string, b: string) {
160
+ const relA = relative(a, b)
161
+ const relB = relative(b, a)
162
+ return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..")
163
+ }
164
+
165
+ export function contains(parent: string, child: string) {
166
+ return !relative(parent, child).startsWith("..")
167
+ }
168
+
169
+ export async function findUp(
170
+ target: string,
171
+ start: string,
172
+ stop?: string,
173
+ options?: { rootFirst?: boolean },
174
+ ): Promise<string[]>
175
+ export async function findUp(
176
+ target: string[],
177
+ start: string,
178
+ stop?: string,
179
+ options?: { rootFirst?: boolean },
180
+ ): Promise<string[]>
181
+ export async function findUp(
182
+ target: string | string[],
183
+ start: string,
184
+ stop?: string,
185
+ options?: { rootFirst?: boolean },
186
+ ) {
187
+ const dirs = [start]
188
+ let current = start
189
+ while (true) {
190
+ if (stop === current) break
191
+ const parent = dirname(current)
192
+ if (parent === current) break
193
+ dirs.push(parent)
194
+ current = parent
195
+ }
196
+
197
+ const targets = Array.isArray(target) ? target : [target]
198
+ const result = []
199
+ for (const dir of options?.rootFirst ? dirs.toReversed() : dirs) {
200
+ for (const item of targets) {
201
+ const search = join(dir, item)
202
+ if (await exists(search)) result.push(search)
203
+ }
204
+ }
205
+ return result
206
+ }
207
+
208
+ export async function* up(options: { targets: string[]; start: string; stop?: string }) {
209
+ const { targets, start, stop } = options
210
+ let current = start
211
+ while (true) {
212
+ for (const target of targets) {
213
+ const search = join(current, target)
214
+ if (await exists(search)) yield search
215
+ }
216
+ if (stop === current) break
217
+ const parent = dirname(current)
218
+ if (parent === current) break
219
+ current = parent
220
+ }
221
+ }
222
+
223
+ export async function globUp(pattern: string, start: string, stop?: string) {
224
+ let current = start
225
+ const result = []
226
+ while (true) {
227
+ try {
228
+ const matches = await Glob.scan(pattern, {
229
+ cwd: current,
230
+ absolute: true,
231
+ include: "file",
232
+ dot: true,
233
+ })
234
+ result.push(...matches)
235
+ } catch {
236
+ // Skip invalid glob patterns
237
+ }
238
+ if (stop === current) break
239
+ const parent = dirname(current)
240
+ if (parent === current) break
241
+ current = parent
242
+ }
243
+ return result
244
+ }
245
+ }
@@ -0,0 +1,333 @@
1
+ import path from "path"
2
+ import os from "os"
3
+ import { randomBytes, randomUUID } from "crypto"
4
+ import { mkdir, readFile, rm, stat, utimes, writeFile } from "fs/promises"
5
+ import { Global } from "@/global"
6
+ import { Hash } from "@/util/hash"
7
+
8
+ export namespace Flock {
9
+ const root = path.join(Global.Path.state, "locks")
10
+ // Defaults for callers that do not provide timing options.
11
+ const defaultOpts = {
12
+ staleMs: 60_000,
13
+ timeoutMs: 5 * 60_000,
14
+ baseDelayMs: 100,
15
+ maxDelayMs: 2_000,
16
+ }
17
+
18
+ export interface WaitEvent {
19
+ key: string
20
+ attempt: number
21
+ delay: number
22
+ waited: number
23
+ }
24
+
25
+ export type Wait = (input: WaitEvent) => void | Promise<void>
26
+
27
+ export interface Options {
28
+ dir?: string
29
+ signal?: AbortSignal
30
+ staleMs?: number
31
+ timeoutMs?: number
32
+ baseDelayMs?: number
33
+ maxDelayMs?: number
34
+ onWait?: Wait
35
+ }
36
+
37
+ type Opts = {
38
+ staleMs: number
39
+ timeoutMs: number
40
+ baseDelayMs: number
41
+ maxDelayMs: number
42
+ }
43
+
44
+ type Owned = {
45
+ acquired: true
46
+ startHeartbeat: (intervalMs?: number) => void
47
+ release: () => Promise<void>
48
+ }
49
+
50
+ export interface Lease {
51
+ release: () => Promise<void>
52
+ [Symbol.asyncDispose]: () => Promise<void>
53
+ }
54
+
55
+ function code(err: unknown) {
56
+ if (typeof err !== "object" || err === null || !("code" in err)) return
57
+ const value = err.code
58
+ if (typeof value !== "string") return
59
+ return value
60
+ }
61
+
62
+ function sleep(ms: number, signal?: AbortSignal) {
63
+ return new Promise<void>((resolve, reject) => {
64
+ if (signal?.aborted) {
65
+ reject(signal.reason ?? new Error("Aborted"))
66
+ return
67
+ }
68
+
69
+ let timer: NodeJS.Timeout | undefined
70
+
71
+ const done = () => {
72
+ signal?.removeEventListener("abort", abort)
73
+ resolve()
74
+ }
75
+
76
+ const abort = () => {
77
+ if (timer) {
78
+ clearTimeout(timer)
79
+ }
80
+ signal?.removeEventListener("abort", abort)
81
+ reject(signal?.reason ?? new Error("Aborted"))
82
+ }
83
+
84
+ signal?.addEventListener("abort", abort, { once: true })
85
+ timer = setTimeout(done, ms)
86
+ })
87
+ }
88
+
89
+ function jitter(ms: number) {
90
+ const j = Math.floor(ms * 0.3)
91
+ const d = Math.floor(Math.random() * (2 * j + 1)) - j
92
+ return Math.max(0, ms + d)
93
+ }
94
+
95
+ function mono() {
96
+ return performance.now()
97
+ }
98
+
99
+ function wall() {
100
+ return performance.timeOrigin + mono()
101
+ }
102
+
103
+ async function stats(file: string) {
104
+ try {
105
+ return await stat(file)
106
+ } catch (err) {
107
+ const errCode = code(err)
108
+ if (errCode === "ENOENT" || errCode === "ENOTDIR") return
109
+ throw err
110
+ }
111
+ }
112
+
113
+ async function stale(lockDir: string, heartbeatPath: string, metaPath: string, staleMs: number) {
114
+ // Stale detection allows automatic recovery after crashed owners.
115
+ const now = wall()
116
+ const heartbeat = await stats(heartbeatPath)
117
+ if (heartbeat) {
118
+ return now - heartbeat.mtimeMs > staleMs
119
+ }
120
+
121
+ const meta = await stats(metaPath)
122
+ if (meta) {
123
+ return now - meta.mtimeMs > staleMs
124
+ }
125
+
126
+ const dir = await stats(lockDir)
127
+ if (!dir) {
128
+ return false
129
+ }
130
+
131
+ return now - dir.mtimeMs > staleMs
132
+ }
133
+
134
+ async function tryAcquireLockDir(lockDir: string, opts: Opts): Promise<Owned | { acquired: false }> {
135
+ const token = randomUUID?.() ?? randomBytes(16).toString("hex")
136
+ const metaPath = path.join(lockDir, "meta.json")
137
+ const heartbeatPath = path.join(lockDir, "heartbeat")
138
+
139
+ try {
140
+ await mkdir(lockDir, { mode: 0o700 })
141
+ } catch (err) {
142
+ if (code(err) !== "EEXIST") {
143
+ throw err
144
+ }
145
+
146
+ if (!(await stale(lockDir, heartbeatPath, metaPath, opts.staleMs))) {
147
+ return { acquired: false }
148
+ }
149
+
150
+ const breakerPath = lockDir + ".breaker"
151
+ try {
152
+ await mkdir(breakerPath, { mode: 0o700 })
153
+ } catch (claimErr) {
154
+ const errCode = code(claimErr)
155
+ if (errCode === "EEXIST") {
156
+ const breaker = await stats(breakerPath)
157
+ if (breaker && wall() - breaker.mtimeMs > opts.staleMs) {
158
+ await rm(breakerPath, { recursive: true, force: true }).catch(() => undefined)
159
+ }
160
+ return { acquired: false }
161
+ }
162
+
163
+ if (errCode === "ENOENT" || errCode === "ENOTDIR") {
164
+ return { acquired: false }
165
+ }
166
+
167
+ throw claimErr
168
+ }
169
+
170
+ try {
171
+ // Breaker ownership ensures only one contender performs stale cleanup.
172
+ if (!(await stale(lockDir, heartbeatPath, metaPath, opts.staleMs))) {
173
+ return { acquired: false }
174
+ }
175
+
176
+ await rm(lockDir, { recursive: true, force: true })
177
+
178
+ try {
179
+ await mkdir(lockDir, { mode: 0o700 })
180
+ } catch (retryErr) {
181
+ const errCode = code(retryErr)
182
+ if (errCode === "EEXIST" || errCode === "ENOTEMPTY") {
183
+ return { acquired: false }
184
+ }
185
+ throw retryErr
186
+ }
187
+ } finally {
188
+ await rm(breakerPath, { recursive: true, force: true }).catch(() => undefined)
189
+ }
190
+ }
191
+
192
+ const meta = {
193
+ token,
194
+ pid: process.pid,
195
+ hostname: os.hostname(),
196
+ createdAt: new Date().toISOString(),
197
+ }
198
+
199
+ await writeFile(heartbeatPath, "", { flag: "wx" }).catch(async () => {
200
+ await rm(lockDir, { recursive: true, force: true })
201
+ throw new Error("Lock acquired but heartbeat already existed (possible compromise).")
202
+ })
203
+
204
+ await writeFile(metaPath, JSON.stringify(meta, null, 2), { flag: "wx" }).catch(async () => {
205
+ await rm(lockDir, { recursive: true, force: true })
206
+ throw new Error("Lock acquired but meta.json already existed (possible compromise).")
207
+ })
208
+
209
+ let timer: NodeJS.Timeout | undefined
210
+
211
+ const startHeartbeat = (intervalMs = Math.max(100, Math.floor(opts.staleMs / 3))) => {
212
+ if (timer) return
213
+ // Heartbeat prevents long critical sections from being evicted as stale.
214
+ timer = setInterval(() => {
215
+ const t = new Date()
216
+ void utimes(heartbeatPath, t, t).catch(() => undefined)
217
+ }, intervalMs)
218
+ timer.unref?.()
219
+ }
220
+
221
+ const release = async () => {
222
+ if (timer) {
223
+ clearInterval(timer)
224
+ timer = undefined
225
+ }
226
+
227
+ const current = await readFile(metaPath, "utf8")
228
+ .then((raw) => {
229
+ const parsed = JSON.parse(raw)
230
+ if (!parsed || typeof parsed !== "object") return {}
231
+ return {
232
+ token: "token" in parsed && typeof parsed.token === "string" ? parsed.token : undefined,
233
+ }
234
+ })
235
+ .catch((err) => {
236
+ const errCode = code(err)
237
+ if (errCode === "ENOENT" || errCode === "ENOTDIR") {
238
+ throw new Error("Refusing to release: lock is compromised (metadata missing).")
239
+ }
240
+ if (err instanceof SyntaxError) {
241
+ throw new Error("Refusing to release: lock is compromised (metadata invalid).")
242
+ }
243
+ throw err
244
+ })
245
+ // Token check prevents deleting a lock that was re-acquired by another process.
246
+ if (current.token !== token) {
247
+ throw new Error("Refusing to release: lock token mismatch (not the owner).")
248
+ }
249
+
250
+ await rm(lockDir, { recursive: true, force: true })
251
+ }
252
+
253
+ return {
254
+ acquired: true,
255
+ startHeartbeat,
256
+ release,
257
+ }
258
+ }
259
+
260
+ async function acquireLockDir(
261
+ lockDir: string,
262
+ input: { key: string; onWait?: Wait; signal?: AbortSignal },
263
+ opts: Opts,
264
+ ) {
265
+ const stop = mono() + opts.timeoutMs
266
+ let attempt = 0
267
+ let waited = 0
268
+ let delay = opts.baseDelayMs
269
+
270
+ while (true) {
271
+ input.signal?.throwIfAborted()
272
+
273
+ const res = await tryAcquireLockDir(lockDir, opts)
274
+ if (res.acquired) {
275
+ return res
276
+ }
277
+
278
+ if (mono() > stop) {
279
+ throw new Error(`Timed out waiting for lock: ${input.key}`)
280
+ }
281
+
282
+ attempt += 1
283
+ const ms = jitter(delay)
284
+ await input.onWait?.({
285
+ key: input.key,
286
+ attempt,
287
+ delay: ms,
288
+ waited,
289
+ })
290
+ await sleep(ms, input.signal)
291
+ waited += ms
292
+ delay = Math.min(opts.maxDelayMs, Math.floor(delay * 1.7))
293
+ }
294
+ }
295
+
296
+ export async function acquire(key: string, input: Options = {}): Promise<Lease> {
297
+ input.signal?.throwIfAborted()
298
+ const cfg: Opts = {
299
+ staleMs: input.staleMs ?? defaultOpts.staleMs,
300
+ timeoutMs: input.timeoutMs ?? defaultOpts.timeoutMs,
301
+ baseDelayMs: input.baseDelayMs ?? defaultOpts.baseDelayMs,
302
+ maxDelayMs: input.maxDelayMs ?? defaultOpts.maxDelayMs,
303
+ }
304
+ const dir = input.dir ?? root
305
+
306
+ await mkdir(dir, { recursive: true })
307
+ const lockfile = path.join(dir, Hash.fast(key) + ".lock")
308
+ const lock = await acquireLockDir(
309
+ lockfile,
310
+ {
311
+ key,
312
+ onWait: input.onWait,
313
+ signal: input.signal,
314
+ },
315
+ cfg,
316
+ )
317
+ lock.startHeartbeat()
318
+
319
+ const release = () => lock.release()
320
+ return {
321
+ release,
322
+ [Symbol.asyncDispose]() {
323
+ return release()
324
+ },
325
+ }
326
+ }
327
+
328
+ export async function withLock<T>(key: string, fn: () => Promise<T>, input: Options = {}) {
329
+ await using _ = await acquire(key, input)
330
+ input.signal?.throwIfAborted()
331
+ return await fn()
332
+ }
333
+ }
package/src/util/fn.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { z } from "zod"
2
+
3
+ export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.infer<T>) => Result) {
4
+ const result = (input: z.infer<T>) => {
5
+ let parsed
6
+ try {
7
+ parsed = schema.parse(input)
8
+ } catch (e) {
9
+ console.trace("schema validation failure stack trace:")
10
+ if (e instanceof z.ZodError) {
11
+ console.error("schema validation issues:", JSON.stringify(e.issues, null, 2))
12
+ }
13
+ throw e
14
+ }
15
+
16
+ return cb(parsed)
17
+ }
18
+ result.force = (input: z.infer<T>) => cb(input)
19
+ result.schema = schema
20
+ return result
21
+ }
@@ -0,0 +1,20 @@
1
+ export function formatDuration(secs: number) {
2
+ if (secs <= 0) return ""
3
+ if (secs < 60) return `${secs}s`
4
+ if (secs < 3600) {
5
+ const mins = Math.floor(secs / 60)
6
+ const remaining = secs % 60
7
+ return remaining > 0 ? `${mins}m ${remaining}s` : `${mins}m`
8
+ }
9
+ if (secs < 86400) {
10
+ const hours = Math.floor(secs / 3600)
11
+ const remaining = Math.floor((secs % 3600) / 60)
12
+ return remaining > 0 ? `${hours}h ${remaining}m` : `${hours}h`
13
+ }
14
+ if (secs < 604800) {
15
+ const days = Math.floor(secs / 86400)
16
+ return days === 1 ? "~1 day" : `~${days} days`
17
+ }
18
+ const weeks = Math.floor(secs / 604800)
19
+ return weeks === 1 ? "~1 week" : `~${weeks} weeks`
20
+ }
@@ -0,0 +1,34 @@
1
+ import { glob, globSync, type GlobOptions } from "glob"
2
+ import { minimatch } from "minimatch"
3
+
4
+ export namespace Glob {
5
+ export interface Options {
6
+ cwd?: string
7
+ absolute?: boolean
8
+ include?: "file" | "all"
9
+ dot?: boolean
10
+ symlink?: boolean
11
+ }
12
+
13
+ function toGlobOptions(options: Options): GlobOptions {
14
+ return {
15
+ cwd: options.cwd,
16
+ absolute: options.absolute,
17
+ dot: options.dot,
18
+ follow: options.symlink ?? false,
19
+ nodir: options.include !== "all",
20
+ }
21
+ }
22
+
23
+ export async function scan(pattern: string, options: Options = {}): Promise<string[]> {
24
+ return glob(pattern, toGlobOptions(options)) as Promise<string[]>
25
+ }
26
+
27
+ export function scanSync(pattern: string, options: Options = {}): string[] {
28
+ return globSync(pattern, toGlobOptions(options)) as string[]
29
+ }
30
+
31
+ export function match(pattern: string, filepath: string): boolean {
32
+ return minimatch(filepath, pattern, { dot: true })
33
+ }
34
+ }
@@ -0,0 +1,7 @@
1
+ import { createHash } from "crypto"
2
+
3
+ export namespace Hash {
4
+ export function fast(input: string | Buffer): string {
5
+ return createHash("sha1").update(input).digest("hex")
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ export function iife<T>(fn: () => T) {
2
+ return fn()
3
+ }