@stonerzju/opencode 1.2.16-offline.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. package/AGENTS.md +10 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/opencode +179 -0
  6. package/bunfig.toml +7 -0
  7. package/drizzle.config.ts +10 -0
  8. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  9. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  10. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  11. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  12. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  13. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  14. package/migration/20260225215848_workspace/migration.sql +7 -0
  15. package/migration/20260225215848_workspace/snapshot.json +959 -0
  16. package/package.json +140 -0
  17. package/package.json.bak +140 -0
  18. package/parsers-config.ts +254 -0
  19. package/script/build.ts +224 -0
  20. package/script/check-migrations.ts +16 -0
  21. package/script/postinstall.mjs +131 -0
  22. package/script/publish.ts +181 -0
  23. package/script/schema.ts +63 -0
  24. package/script/seed-e2e.ts +50 -0
  25. package/src/acp/README.md +174 -0
  26. package/src/acp/agent.ts +1741 -0
  27. package/src/acp/session.ts +116 -0
  28. package/src/acp/types.ts +23 -0
  29. package/src/agent/agent.ts +339 -0
  30. package/src/agent/generate.txt +75 -0
  31. package/src/agent/prompt/compaction.txt +14 -0
  32. package/src/agent/prompt/explore.txt +18 -0
  33. package/src/agent/prompt/summary.txt +11 -0
  34. package/src/agent/prompt/title.txt +44 -0
  35. package/src/auth/index.ts +68 -0
  36. package/src/bun/index.ts +131 -0
  37. package/src/bun/registry.ts +50 -0
  38. package/src/bus/bus-event.ts +43 -0
  39. package/src/bus/global.ts +10 -0
  40. package/src/bus/index.ts +105 -0
  41. package/src/cli/bootstrap.ts +17 -0
  42. package/src/cli/cmd/acp.ts +70 -0
  43. package/src/cli/cmd/agent.ts +257 -0
  44. package/src/cli/cmd/auth.ts +449 -0
  45. package/src/cli/cmd/cmd.ts +7 -0
  46. package/src/cli/cmd/db.ts +118 -0
  47. package/src/cli/cmd/debug/agent.ts +167 -0
  48. package/src/cli/cmd/debug/config.ts +16 -0
  49. package/src/cli/cmd/debug/file.ts +97 -0
  50. package/src/cli/cmd/debug/index.ts +48 -0
  51. package/src/cli/cmd/debug/lsp.ts +52 -0
  52. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  53. package/src/cli/cmd/debug/scrap.ts +16 -0
  54. package/src/cli/cmd/debug/skill.ts +16 -0
  55. package/src/cli/cmd/debug/snapshot.ts +52 -0
  56. package/src/cli/cmd/export.ts +88 -0
  57. package/src/cli/cmd/generate.ts +38 -0
  58. package/src/cli/cmd/github.ts +1631 -0
  59. package/src/cli/cmd/import.ts +170 -0
  60. package/src/cli/cmd/mcp.ts +754 -0
  61. package/src/cli/cmd/models.ts +77 -0
  62. package/src/cli/cmd/pr.ts +112 -0
  63. package/src/cli/cmd/run.ts +625 -0
  64. package/src/cli/cmd/serve.ts +31 -0
  65. package/src/cli/cmd/session.ts +156 -0
  66. package/src/cli/cmd/stats.ts +410 -0
  67. package/src/cli/cmd/tui/app.tsx +845 -0
  68. package/src/cli/cmd/tui/attach.ts +88 -0
  69. package/src/cli/cmd/tui/component/border.tsx +21 -0
  70. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  71. package/src/cli/cmd/tui/component/dialog-command.tsx +147 -0
  72. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  73. package/src/cli/cmd/tui/component/dialog-model.tsx +165 -0
  74. package/src/cli/cmd/tui/component/dialog-provider.tsx +259 -0
  75. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  76. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  77. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  78. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  79. package/src/cli/cmd/tui/component/dialog-status.tsx +167 -0
  80. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  81. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  82. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  83. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
  84. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  85. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  86. package/src/cli/cmd/tui/component/prompt/index.tsx +1155 -0
  87. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  88. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  89. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  90. package/src/cli/cmd/tui/component/tips.tsx +152 -0
  91. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  92. package/src/cli/cmd/tui/context/args.tsx +15 -0
  93. package/src/cli/cmd/tui/context/directory.ts +13 -0
  94. package/src/cli/cmd/tui/context/exit.tsx +53 -0
  95. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  96. package/src/cli/cmd/tui/context/keybind.tsx +102 -0
  97. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  98. package/src/cli/cmd/tui/context/local.tsx +406 -0
  99. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  100. package/src/cli/cmd/tui/context/route.tsx +46 -0
  101. package/src/cli/cmd/tui/context/sdk.tsx +101 -0
  102. package/src/cli/cmd/tui/context/sync.tsx +488 -0
  103. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  104. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  105. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  106. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  107. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  108. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  109. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  110. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  111. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  112. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  113. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  114. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  115. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  116. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  117. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  118. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  119. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  120. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  121. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  122. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  123. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  124. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  125. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  126. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  127. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  128. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  129. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  130. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  131. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  132. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  133. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  134. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  135. package/src/cli/cmd/tui/context/theme.tsx +1152 -0
  136. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  137. package/src/cli/cmd/tui/event.ts +48 -0
  138. package/src/cli/cmd/tui/routes/home.tsx +145 -0
  139. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  140. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  141. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  142. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  143. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  144. package/src/cli/cmd/tui/routes/session/header.tsx +135 -0
  145. package/src/cli/cmd/tui/routes/session/index.tsx +2219 -0
  146. package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
  147. package/src/cli/cmd/tui/routes/session/question.tsx +466 -0
  148. package/src/cli/cmd/tui/routes/session/sidebar.tsx +321 -0
  149. package/src/cli/cmd/tui/thread.ts +199 -0
  150. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  151. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +85 -0
  152. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +207 -0
  153. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  154. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +80 -0
  155. package/src/cli/cmd/tui/ui/dialog-select.tsx +401 -0
  156. package/src/cli/cmd/tui/ui/dialog.tsx +182 -0
  157. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  158. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  159. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  160. package/src/cli/cmd/tui/util/clipboard.ts +164 -0
  161. package/src/cli/cmd/tui/util/editor.ts +33 -0
  162. package/src/cli/cmd/tui/util/selection.ts +25 -0
  163. package/src/cli/cmd/tui/util/signal.ts +7 -0
  164. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  165. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  166. package/src/cli/cmd/tui/win32.ts +129 -0
  167. package/src/cli/cmd/tui/worker.ts +157 -0
  168. package/src/cli/cmd/uninstall.ts +356 -0
  169. package/src/cli/cmd/upgrade.ts +73 -0
  170. package/src/cli/cmd/web.ts +81 -0
  171. package/src/cli/cmd/workspace-serve.ts +16 -0
  172. package/src/cli/error.ts +57 -0
  173. package/src/cli/logo.ts +6 -0
  174. package/src/cli/network.ts +60 -0
  175. package/src/cli/ui.ts +116 -0
  176. package/src/cli/upgrade.ts +25 -0
  177. package/src/command/index.ts +150 -0
  178. package/src/command/template/initialize.txt +10 -0
  179. package/src/command/template/review.txt +101 -0
  180. package/src/config/config.ts +1408 -0
  181. package/src/config/markdown.ts +99 -0
  182. package/src/config/migrate-tui-config.ts +155 -0
  183. package/src/config/paths.ts +174 -0
  184. package/src/config/tui-schema.ts +34 -0
  185. package/src/config/tui.ts +118 -0
  186. package/src/control/control.sql.ts +22 -0
  187. package/src/control/index.ts +67 -0
  188. package/src/control-plane/adaptors/index.ts +10 -0
  189. package/src/control-plane/adaptors/types.ts +7 -0
  190. package/src/control-plane/adaptors/worktree.ts +26 -0
  191. package/src/control-plane/config.ts +10 -0
  192. package/src/control-plane/session-proxy-middleware.ts +46 -0
  193. package/src/control-plane/sse.ts +66 -0
  194. package/src/control-plane/workspace-server/routes.ts +33 -0
  195. package/src/control-plane/workspace-server/server.ts +24 -0
  196. package/src/control-plane/workspace.sql.ts +12 -0
  197. package/src/control-plane/workspace.ts +160 -0
  198. package/src/env/index.ts +28 -0
  199. package/src/file/ignore.ts +82 -0
  200. package/src/file/index.ts +646 -0
  201. package/src/file/ripgrep.ts +372 -0
  202. package/src/file/time.ts +71 -0
  203. package/src/file/watcher.ts +128 -0
  204. package/src/flag/flag.ts +109 -0
  205. package/src/format/formatter.ts +395 -0
  206. package/src/format/index.ts +140 -0
  207. package/src/global/index.ts +54 -0
  208. package/src/id/id.ts +84 -0
  209. package/src/ide/index.ts +76 -0
  210. package/src/index.ts +210 -0
  211. package/src/installation/index.ts +266 -0
  212. package/src/lsp/client.ts +251 -0
  213. package/src/lsp/index.ts +485 -0
  214. package/src/lsp/language.ts +120 -0
  215. package/src/lsp/server.ts +2142 -0
  216. package/src/mcp/auth.ts +130 -0
  217. package/src/mcp/index.ts +937 -0
  218. package/src/mcp/oauth-callback.ts +200 -0
  219. package/src/mcp/oauth-provider.ts +176 -0
  220. package/src/patch/index.ts +680 -0
  221. package/src/permission/arity.ts +163 -0
  222. package/src/permission/index.ts +210 -0
  223. package/src/permission/next.ts +286 -0
  224. package/src/plugin/codex.ts +624 -0
  225. package/src/plugin/copilot.ts +327 -0
  226. package/src/plugin/index.ts +143 -0
  227. package/src/project/bootstrap.ts +33 -0
  228. package/src/project/instance.ts +114 -0
  229. package/src/project/project.sql.ts +15 -0
  230. package/src/project/project.ts +441 -0
  231. package/src/project/state.ts +70 -0
  232. package/src/project/vcs.ts +76 -0
  233. package/src/provider/auth.ts +147 -0
  234. package/src/provider/error.ts +189 -0
  235. package/src/provider/models.ts +146 -0
  236. package/src/provider/provider.ts +1338 -0
  237. package/src/provider/sdk/copilot/README.md +5 -0
  238. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +164 -0
  239. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  240. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +17 -0
  241. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  242. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +780 -0
  243. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  244. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  245. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +87 -0
  246. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  247. package/src/provider/sdk/copilot/index.ts +2 -0
  248. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  249. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +303 -0
  250. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  251. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  252. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  253. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +207 -0
  254. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1732 -0
  255. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +177 -0
  256. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  257. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +88 -0
  258. package/src/provider/sdk/copilot/responses/tool/file-search.ts +128 -0
  259. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +115 -0
  260. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +65 -0
  261. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +104 -0
  262. package/src/provider/sdk/copilot/responses/tool/web-search.ts +103 -0
  263. package/src/provider/transform.ts +955 -0
  264. package/src/pty/index.ts +324 -0
  265. package/src/question/index.ts +171 -0
  266. package/src/scheduler/index.ts +61 -0
  267. package/src/server/error.ts +36 -0
  268. package/src/server/event.ts +7 -0
  269. package/src/server/mdns.ts +60 -0
  270. package/src/server/routes/config.ts +92 -0
  271. package/src/server/routes/experimental.ts +270 -0
  272. package/src/server/routes/file.ts +197 -0
  273. package/src/server/routes/global.ts +185 -0
  274. package/src/server/routes/mcp.ts +225 -0
  275. package/src/server/routes/permission.ts +68 -0
  276. package/src/server/routes/project.ts +82 -0
  277. package/src/server/routes/provider.ts +165 -0
  278. package/src/server/routes/pty.ts +200 -0
  279. package/src/server/routes/question.ts +98 -0
  280. package/src/server/routes/session.ts +974 -0
  281. package/src/server/routes/tui.ts +379 -0
  282. package/src/server/routes/workspace.ts +104 -0
  283. package/src/server/server.ts +623 -0
  284. package/src/session/compaction.ts +261 -0
  285. package/src/session/index.ts +877 -0
  286. package/src/session/instruction.ts +192 -0
  287. package/src/session/llm.ts +279 -0
  288. package/src/session/message-v2.ts +899 -0
  289. package/src/session/message.ts +189 -0
  290. package/src/session/processor.ts +421 -0
  291. package/src/session/prompt/anthropic-20250930.txt +166 -0
  292. package/src/session/prompt/anthropic.txt +105 -0
  293. package/src/session/prompt/beast.txt +147 -0
  294. package/src/session/prompt/build-switch.txt +5 -0
  295. package/src/session/prompt/codex_header.txt +79 -0
  296. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  297. package/src/session/prompt/gemini.txt +155 -0
  298. package/src/session/prompt/max-steps.txt +16 -0
  299. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  300. package/src/session/prompt/plan.txt +26 -0
  301. package/src/session/prompt/qwen.txt +109 -0
  302. package/src/session/prompt/trinity.txt +97 -0
  303. package/src/session/prompt.ts +1959 -0
  304. package/src/session/retry.ts +101 -0
  305. package/src/session/revert.ts +138 -0
  306. package/src/session/session.sql.ts +88 -0
  307. package/src/session/status.ts +76 -0
  308. package/src/session/summary.ts +161 -0
  309. package/src/session/system.ts +54 -0
  310. package/src/session/todo.ts +56 -0
  311. package/src/share/share-next.ts +210 -0
  312. package/src/share/share.sql.ts +13 -0
  313. package/src/shell/shell.ts +68 -0
  314. package/src/skill/discovery.ts +98 -0
  315. package/src/skill/index.ts +1 -0
  316. package/src/skill/skill.ts +189 -0
  317. package/src/snapshot/index.ts +297 -0
  318. package/src/sql.d.ts +4 -0
  319. package/src/storage/db.ts +155 -0
  320. package/src/storage/json-migration.ts +425 -0
  321. package/src/storage/schema.sql.ts +10 -0
  322. package/src/storage/schema.ts +5 -0
  323. package/src/storage/storage.ts +220 -0
  324. package/src/tool/apply_patch.ts +281 -0
  325. package/src/tool/apply_patch.txt +33 -0
  326. package/src/tool/bash.ts +274 -0
  327. package/src/tool/bash.txt +115 -0
  328. package/src/tool/batch.ts +181 -0
  329. package/src/tool/batch.txt +24 -0
  330. package/src/tool/codesearch.ts +132 -0
  331. package/src/tool/codesearch.txt +12 -0
  332. package/src/tool/edit.ts +654 -0
  333. package/src/tool/edit.txt +10 -0
  334. package/src/tool/external-directory.ts +32 -0
  335. package/src/tool/glob.ts +78 -0
  336. package/src/tool/glob.txt +6 -0
  337. package/src/tool/grep.ts +156 -0
  338. package/src/tool/grep.txt +8 -0
  339. package/src/tool/invalid.ts +17 -0
  340. package/src/tool/ls.ts +121 -0
  341. package/src/tool/ls.txt +1 -0
  342. package/src/tool/lsp.ts +97 -0
  343. package/src/tool/lsp.txt +19 -0
  344. package/src/tool/multiedit.ts +46 -0
  345. package/src/tool/multiedit.txt +41 -0
  346. package/src/tool/plan-enter.txt +14 -0
  347. package/src/tool/plan-exit.txt +13 -0
  348. package/src/tool/plan.ts +131 -0
  349. package/src/tool/question.ts +33 -0
  350. package/src/tool/question.txt +10 -0
  351. package/src/tool/read.ts +293 -0
  352. package/src/tool/read.txt +14 -0
  353. package/src/tool/registry.ts +173 -0
  354. package/src/tool/skill.ts +123 -0
  355. package/src/tool/task.ts +165 -0
  356. package/src/tool/task.txt +60 -0
  357. package/src/tool/todo.ts +53 -0
  358. package/src/tool/todoread.txt +14 -0
  359. package/src/tool/todowrite.txt +167 -0
  360. package/src/tool/tool.ts +89 -0
  361. package/src/tool/truncation.ts +107 -0
  362. package/src/tool/webfetch.ts +206 -0
  363. package/src/tool/webfetch.txt +13 -0
  364. package/src/tool/websearch.ts +150 -0
  365. package/src/tool/websearch.txt +14 -0
  366. package/src/tool/write.ts +84 -0
  367. package/src/tool/write.txt +8 -0
  368. package/src/util/abort.ts +35 -0
  369. package/src/util/archive.ts +16 -0
  370. package/src/util/color.ts +19 -0
  371. package/src/util/context.ts +25 -0
  372. package/src/util/defer.ts +12 -0
  373. package/src/util/eventloop.ts +20 -0
  374. package/src/util/filesystem.ts +189 -0
  375. package/src/util/fn.ts +11 -0
  376. package/src/util/format.ts +20 -0
  377. package/src/util/git.ts +35 -0
  378. package/src/util/glob.ts +34 -0
  379. package/src/util/iife.ts +3 -0
  380. package/src/util/keybind.ts +103 -0
  381. package/src/util/lazy.ts +23 -0
  382. package/src/util/locale.ts +81 -0
  383. package/src/util/lock.ts +98 -0
  384. package/src/util/log.ts +182 -0
  385. package/src/util/process.ts +126 -0
  386. package/src/util/proxied.ts +3 -0
  387. package/src/util/queue.ts +32 -0
  388. package/src/util/rpc.ts +66 -0
  389. package/src/util/scrap.ts +10 -0
  390. package/src/util/signal.ts +12 -0
  391. package/src/util/timeout.ts +14 -0
  392. package/src/util/token.ts +7 -0
  393. package/src/util/wildcard.ts +59 -0
  394. package/src/worktree/index.ts +643 -0
  395. package/sst-env.d.ts +10 -0
  396. package/test/AGENTS.md +81 -0
  397. package/test/acp/agent-interface.test.ts +51 -0
  398. package/test/acp/event-subscription.test.ts +683 -0
  399. package/test/agent/agent.test.ts +689 -0
  400. package/test/bun.test.ts +53 -0
  401. package/test/cli/github-action.test.ts +197 -0
  402. package/test/cli/github-remote.test.ts +80 -0
  403. package/test/cli/import.test.ts +38 -0
  404. package/test/cli/plugin-auth-picker.test.ts +120 -0
  405. package/test/cli/tui/transcript.test.ts +322 -0
  406. package/test/config/agent-color.test.ts +71 -0
  407. package/test/config/config.test.ts +1886 -0
  408. package/test/config/fixtures/empty-frontmatter.md +4 -0
  409. package/test/config/fixtures/frontmatter.md +28 -0
  410. package/test/config/fixtures/markdown-header.md +11 -0
  411. package/test/config/fixtures/no-frontmatter.md +1 -0
  412. package/test/config/fixtures/weird-model-id.md +13 -0
  413. package/test/config/markdown.test.ts +228 -0
  414. package/test/config/tui.test.ts +510 -0
  415. package/test/control-plane/session-proxy-middleware.test.ts +147 -0
  416. package/test/control-plane/sse.test.ts +56 -0
  417. package/test/control-plane/workspace-server-sse.test.ts +65 -0
  418. package/test/control-plane/workspace-sync.test.ts +97 -0
  419. package/test/file/ignore.test.ts +10 -0
  420. package/test/file/index.test.ts +394 -0
  421. package/test/file/path-traversal.test.ts +198 -0
  422. package/test/file/ripgrep.test.ts +39 -0
  423. package/test/file/time.test.ts +361 -0
  424. package/test/fixture/db.ts +11 -0
  425. package/test/fixture/fixture.ts +45 -0
  426. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  427. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  428. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  429. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  430. package/test/fixture/skills/index.json +6 -0
  431. package/test/ide/ide.test.ts +82 -0
  432. package/test/keybind.test.ts +421 -0
  433. package/test/lsp/client.test.ts +95 -0
  434. package/test/mcp/headers.test.ts +153 -0
  435. package/test/mcp/oauth-browser.test.ts +249 -0
  436. package/test/memory/abort-leak.test.ts +136 -0
  437. package/test/patch/patch.test.ts +348 -0
  438. package/test/permission/arity.test.ts +33 -0
  439. package/test/permission/next.test.ts +689 -0
  440. package/test/permission-task.test.ts +319 -0
  441. package/test/plugin/auth-override.test.ts +44 -0
  442. package/test/plugin/codex.test.ts +123 -0
  443. package/test/preload.ts +80 -0
  444. package/test/project/project.test.ts +348 -0
  445. package/test/project/worktree-remove.test.ts +65 -0
  446. package/test/provider/amazon-bedrock.test.ts +446 -0
  447. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  448. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  449. package/test/provider/gitlab-duo.test.ts +262 -0
  450. package/test/provider/provider.test.ts +2220 -0
  451. package/test/provider/transform.test.ts +2353 -0
  452. package/test/pty/pty-output-isolation.test.ts +140 -0
  453. package/test/question/question.test.ts +300 -0
  454. package/test/scheduler.test.ts +73 -0
  455. package/test/server/global-session-list.test.ts +89 -0
  456. package/test/server/session-list.test.ts +90 -0
  457. package/test/server/session-select.test.ts +78 -0
  458. package/test/session/compaction.test.ts +423 -0
  459. package/test/session/instruction.test.ts +170 -0
  460. package/test/session/llm.test.ts +667 -0
  461. package/test/session/message-v2.test.ts +924 -0
  462. package/test/session/prompt.test.ts +211 -0
  463. package/test/session/retry.test.ts +188 -0
  464. package/test/session/revert-compact.test.ts +285 -0
  465. package/test/session/session.test.ts +71 -0
  466. package/test/session/structured-output-integration.test.ts +233 -0
  467. package/test/session/structured-output.test.ts +385 -0
  468. package/test/skill/discovery.test.ts +110 -0
  469. package/test/skill/skill.test.ts +388 -0
  470. package/test/snapshot/snapshot.test.ts +1180 -0
  471. package/test/storage/json-migration.test.ts +846 -0
  472. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  473. package/test/tool/apply_patch.test.ts +566 -0
  474. package/test/tool/bash.test.ts +402 -0
  475. package/test/tool/edit.test.ts +496 -0
  476. package/test/tool/external-directory.test.ts +127 -0
  477. package/test/tool/fixtures/large-image.png +0 -0
  478. package/test/tool/fixtures/models-api.json +38413 -0
  479. package/test/tool/grep.test.ts +110 -0
  480. package/test/tool/question.test.ts +107 -0
  481. package/test/tool/read.test.ts +504 -0
  482. package/test/tool/registry.test.ts +122 -0
  483. package/test/tool/skill.test.ts +112 -0
  484. package/test/tool/truncation.test.ts +160 -0
  485. package/test/tool/webfetch.test.ts +100 -0
  486. package/test/tool/write.test.ts +348 -0
  487. package/test/util/filesystem.test.ts +443 -0
  488. package/test/util/format.test.ts +59 -0
  489. package/test/util/glob.test.ts +164 -0
  490. package/test/util/iife.test.ts +36 -0
  491. package/test/util/lazy.test.ts +50 -0
  492. package/test/util/lock.test.ts +72 -0
  493. package/test/util/process.test.ts +59 -0
  494. package/test/util/timeout.test.ts +21 -0
  495. package/test/util/wildcard.test.ts +90 -0
  496. package/tsconfig.json +16 -0
@@ -0,0 +1,425 @@
1
+ import { Database } from "bun:sqlite"
2
+ import { drizzle } from "drizzle-orm/bun-sqlite"
3
+ import { Global } from "../global"
4
+ import { Log } from "../util/log"
5
+ import { ProjectTable } from "../project/project.sql"
6
+ import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql"
7
+ import { SessionShareTable } from "../share/share.sql"
8
+ import path from "path"
9
+ import { existsSync } from "fs"
10
+ import { Filesystem } from "../util/filesystem"
11
+ import { Glob } from "../util/glob"
12
+
13
+ export namespace JsonMigration {
14
+ const log = Log.create({ service: "json-migration" })
15
+
16
+ export type Progress = {
17
+ current: number
18
+ total: number
19
+ label: string
20
+ }
21
+
22
+ type Options = {
23
+ progress?: (event: Progress) => void
24
+ }
25
+
26
+ export async function run(sqlite: Database, options?: Options) {
27
+ const storageDir = path.join(Global.Path.data, "storage")
28
+
29
+ if (!existsSync(storageDir)) {
30
+ log.info("storage directory does not exist, skipping migration")
31
+ return {
32
+ projects: 0,
33
+ sessions: 0,
34
+ messages: 0,
35
+ parts: 0,
36
+ todos: 0,
37
+ permissions: 0,
38
+ shares: 0,
39
+ errors: [] as string[],
40
+ }
41
+ }
42
+
43
+ log.info("starting json to sqlite migration", { storageDir })
44
+ const start = performance.now()
45
+
46
+ const db = drizzle({ client: sqlite })
47
+
48
+ // Optimize SQLite for bulk inserts
49
+ sqlite.exec("PRAGMA journal_mode = WAL")
50
+ sqlite.exec("PRAGMA synchronous = OFF")
51
+ sqlite.exec("PRAGMA cache_size = 10000")
52
+ sqlite.exec("PRAGMA temp_store = MEMORY")
53
+ const stats = {
54
+ projects: 0,
55
+ sessions: 0,
56
+ messages: 0,
57
+ parts: 0,
58
+ todos: 0,
59
+ permissions: 0,
60
+ shares: 0,
61
+ errors: [] as string[],
62
+ }
63
+ const orphans = {
64
+ sessions: 0,
65
+ todos: 0,
66
+ permissions: 0,
67
+ shares: 0,
68
+ }
69
+ const errs = stats.errors
70
+
71
+ const batchSize = 1000
72
+ const now = Date.now()
73
+
74
+ async function list(pattern: string) {
75
+ return Glob.scan(pattern, { cwd: storageDir, absolute: true })
76
+ }
77
+
78
+ async function read(files: string[], start: number, end: number) {
79
+ const count = end - start
80
+ const tasks = new Array(count)
81
+ for (let i = 0; i < count; i++) {
82
+ tasks[i] = Filesystem.readJson(files[start + i])
83
+ }
84
+ const results = await Promise.allSettled(tasks)
85
+ const items = new Array(count)
86
+ for (let i = 0; i < results.length; i++) {
87
+ const result = results[i]
88
+ if (result.status === "fulfilled") {
89
+ items[i] = result.value
90
+ continue
91
+ }
92
+ errs.push(`failed to read ${files[start + i]}: ${result.reason}`)
93
+ }
94
+ return items
95
+ }
96
+
97
+ function insert(values: any[], table: any, label: string) {
98
+ if (values.length === 0) return 0
99
+ try {
100
+ db.insert(table).values(values).onConflictDoNothing().run()
101
+ return values.length
102
+ } catch (e) {
103
+ errs.push(`failed to migrate ${label} batch: ${e}`)
104
+ return 0
105
+ }
106
+ }
107
+
108
+ // Pre-scan all files upfront to avoid repeated glob operations
109
+ log.info("scanning files...")
110
+ const [projectFiles, sessionFiles, messageFiles, partFiles, todoFiles, permFiles, shareFiles] = await Promise.all([
111
+ list("project/*.json"),
112
+ list("session/*/*.json"),
113
+ list("message/*/*.json"),
114
+ list("part/*/*.json"),
115
+ list("todo/*.json"),
116
+ list("permission/*.json"),
117
+ list("session_share/*.json"),
118
+ ])
119
+
120
+ log.info("file scan complete", {
121
+ projects: projectFiles.length,
122
+ sessions: sessionFiles.length,
123
+ messages: messageFiles.length,
124
+ parts: partFiles.length,
125
+ todos: todoFiles.length,
126
+ permissions: permFiles.length,
127
+ shares: shareFiles.length,
128
+ })
129
+
130
+ const total = Math.max(
131
+ 1,
132
+ projectFiles.length +
133
+ sessionFiles.length +
134
+ messageFiles.length +
135
+ partFiles.length +
136
+ todoFiles.length +
137
+ permFiles.length +
138
+ shareFiles.length,
139
+ )
140
+ const progress = options?.progress
141
+ let current = 0
142
+ const step = (label: string, count: number) => {
143
+ current = Math.min(total, current + count)
144
+ progress?.({ current, total, label })
145
+ }
146
+
147
+ progress?.({ current, total, label: "starting" })
148
+
149
+ sqlite.exec("BEGIN TRANSACTION")
150
+
151
+ // Migrate projects first (no FK deps)
152
+ // Derive all IDs from file paths, not JSON content
153
+ const projectIds = new Set<string>()
154
+ const projectValues = [] as any[]
155
+ for (let i = 0; i < projectFiles.length; i += batchSize) {
156
+ const end = Math.min(i + batchSize, projectFiles.length)
157
+ const batch = await read(projectFiles, i, end)
158
+ projectValues.length = 0
159
+ for (let j = 0; j < batch.length; j++) {
160
+ const data = batch[j]
161
+ if (!data) continue
162
+ const id = path.basename(projectFiles[i + j], ".json")
163
+ projectIds.add(id)
164
+ projectValues.push({
165
+ id,
166
+ worktree: data.worktree ?? "/",
167
+ vcs: data.vcs,
168
+ name: data.name ?? undefined,
169
+ icon_url: data.icon?.url,
170
+ icon_color: data.icon?.color,
171
+ time_created: data.time?.created ?? now,
172
+ time_updated: data.time?.updated ?? now,
173
+ time_initialized: data.time?.initialized,
174
+ sandboxes: data.sandboxes ?? [],
175
+ commands: data.commands,
176
+ })
177
+ }
178
+ stats.projects += insert(projectValues, ProjectTable, "project")
179
+ step("projects", end - i)
180
+ }
181
+ log.info("migrated projects", { count: stats.projects, duration: Math.round(performance.now() - start) })
182
+
183
+ // Migrate sessions (depends on projects)
184
+ // Derive all IDs from directory/file paths, not JSON content, since earlier
185
+ // migrations may have moved sessions to new directories without updating the JSON
186
+ const sessionProjects = sessionFiles.map((file) => path.basename(path.dirname(file)))
187
+ const sessionIds = new Set<string>()
188
+ const sessionValues = [] as any[]
189
+ for (let i = 0; i < sessionFiles.length; i += batchSize) {
190
+ const end = Math.min(i + batchSize, sessionFiles.length)
191
+ const batch = await read(sessionFiles, i, end)
192
+ sessionValues.length = 0
193
+ for (let j = 0; j < batch.length; j++) {
194
+ const data = batch[j]
195
+ if (!data) continue
196
+ const id = path.basename(sessionFiles[i + j], ".json")
197
+ const projectID = sessionProjects[i + j]
198
+ if (!projectIds.has(projectID)) {
199
+ orphans.sessions++
200
+ continue
201
+ }
202
+ sessionIds.add(id)
203
+ sessionValues.push({
204
+ id,
205
+ project_id: projectID,
206
+ parent_id: data.parentID ?? null,
207
+ slug: data.slug ?? "",
208
+ directory: data.directory ?? "",
209
+ title: data.title ?? "",
210
+ version: data.version ?? "",
211
+ share_url: data.share?.url ?? null,
212
+ summary_additions: data.summary?.additions ?? null,
213
+ summary_deletions: data.summary?.deletions ?? null,
214
+ summary_files: data.summary?.files ?? null,
215
+ summary_diffs: data.summary?.diffs ?? null,
216
+ revert: data.revert ?? null,
217
+ permission: data.permission ?? null,
218
+ time_created: data.time?.created ?? now,
219
+ time_updated: data.time?.updated ?? now,
220
+ time_compacting: data.time?.compacting ?? null,
221
+ time_archived: data.time?.archived ?? null,
222
+ })
223
+ }
224
+ stats.sessions += insert(sessionValues, SessionTable, "session")
225
+ step("sessions", end - i)
226
+ }
227
+ log.info("migrated sessions", { count: stats.sessions })
228
+ if (orphans.sessions > 0) {
229
+ log.warn("skipped orphaned sessions", { count: orphans.sessions })
230
+ }
231
+
232
+ // Migrate messages using pre-scanned file map
233
+ const allMessageFiles = [] as string[]
234
+ const allMessageSessions = [] as string[]
235
+ const messageSessions = new Map<string, string>()
236
+ for (const file of messageFiles) {
237
+ const sessionID = path.basename(path.dirname(file))
238
+ if (!sessionIds.has(sessionID)) continue
239
+ allMessageFiles.push(file)
240
+ allMessageSessions.push(sessionID)
241
+ }
242
+
243
+ for (let i = 0; i < allMessageFiles.length; i += batchSize) {
244
+ const end = Math.min(i + batchSize, allMessageFiles.length)
245
+ const batch = await read(allMessageFiles, i, end)
246
+ const values = new Array(batch.length)
247
+ let count = 0
248
+ for (let j = 0; j < batch.length; j++) {
249
+ const data = batch[j]
250
+ if (!data) continue
251
+ const file = allMessageFiles[i + j]
252
+ const id = path.basename(file, ".json")
253
+ const sessionID = allMessageSessions[i + j]
254
+ messageSessions.set(id, sessionID)
255
+ const rest = data
256
+ delete rest.id
257
+ delete rest.sessionID
258
+ values[count++] = {
259
+ id,
260
+ session_id: sessionID,
261
+ time_created: data.time?.created ?? now,
262
+ time_updated: data.time?.updated ?? now,
263
+ data: rest,
264
+ }
265
+ }
266
+ values.length = count
267
+ stats.messages += insert(values, MessageTable, "message")
268
+ step("messages", end - i)
269
+ }
270
+ log.info("migrated messages", { count: stats.messages })
271
+
272
+ // Migrate parts using pre-scanned file map
273
+ for (let i = 0; i < partFiles.length; i += batchSize) {
274
+ const end = Math.min(i + batchSize, partFiles.length)
275
+ const batch = await read(partFiles, i, end)
276
+ const values = new Array(batch.length)
277
+ let count = 0
278
+ for (let j = 0; j < batch.length; j++) {
279
+ const data = batch[j]
280
+ if (!data) continue
281
+ const file = partFiles[i + j]
282
+ const id = path.basename(file, ".json")
283
+ const messageID = path.basename(path.dirname(file))
284
+ const sessionID = messageSessions.get(messageID)
285
+ if (!sessionID) {
286
+ errs.push(`part missing message session: ${file}`)
287
+ continue
288
+ }
289
+ if (!sessionIds.has(sessionID)) continue
290
+ const rest = data
291
+ delete rest.id
292
+ delete rest.messageID
293
+ delete rest.sessionID
294
+ values[count++] = {
295
+ id,
296
+ message_id: messageID,
297
+ session_id: sessionID,
298
+ time_created: data.time?.created ?? now,
299
+ time_updated: data.time?.updated ?? now,
300
+ data: rest,
301
+ }
302
+ }
303
+ values.length = count
304
+ stats.parts += insert(values, PartTable, "part")
305
+ step("parts", end - i)
306
+ }
307
+ log.info("migrated parts", { count: stats.parts })
308
+
309
+ // Migrate todos
310
+ const todoSessions = todoFiles.map((file) => path.basename(file, ".json"))
311
+ for (let i = 0; i < todoFiles.length; i += batchSize) {
312
+ const end = Math.min(i + batchSize, todoFiles.length)
313
+ const batch = await read(todoFiles, i, end)
314
+ const values = [] as any[]
315
+ for (let j = 0; j < batch.length; j++) {
316
+ const data = batch[j]
317
+ if (!data) continue
318
+ const sessionID = todoSessions[i + j]
319
+ if (!sessionIds.has(sessionID)) {
320
+ orphans.todos++
321
+ continue
322
+ }
323
+ if (!Array.isArray(data)) {
324
+ errs.push(`todo not an array: ${todoFiles[i + j]}`)
325
+ continue
326
+ }
327
+ for (let position = 0; position < data.length; position++) {
328
+ const todo = data[position]
329
+ if (!todo?.content || !todo?.status || !todo?.priority) continue
330
+ values.push({
331
+ session_id: sessionID,
332
+ content: todo.content,
333
+ status: todo.status,
334
+ priority: todo.priority,
335
+ position,
336
+ time_created: now,
337
+ time_updated: now,
338
+ })
339
+ }
340
+ }
341
+ stats.todos += insert(values, TodoTable, "todo")
342
+ step("todos", end - i)
343
+ }
344
+ log.info("migrated todos", { count: stats.todos })
345
+ if (orphans.todos > 0) {
346
+ log.warn("skipped orphaned todos", { count: orphans.todos })
347
+ }
348
+
349
+ // Migrate permissions
350
+ const permProjects = permFiles.map((file) => path.basename(file, ".json"))
351
+ const permValues = [] as any[]
352
+ for (let i = 0; i < permFiles.length; i += batchSize) {
353
+ const end = Math.min(i + batchSize, permFiles.length)
354
+ const batch = await read(permFiles, i, end)
355
+ permValues.length = 0
356
+ for (let j = 0; j < batch.length; j++) {
357
+ const data = batch[j]
358
+ if (!data) continue
359
+ const projectID = permProjects[i + j]
360
+ if (!projectIds.has(projectID)) {
361
+ orphans.permissions++
362
+ continue
363
+ }
364
+ permValues.push({ project_id: projectID, data })
365
+ }
366
+ stats.permissions += insert(permValues, PermissionTable, "permission")
367
+ step("permissions", end - i)
368
+ }
369
+ log.info("migrated permissions", { count: stats.permissions })
370
+ if (orphans.permissions > 0) {
371
+ log.warn("skipped orphaned permissions", { count: orphans.permissions })
372
+ }
373
+
374
+ // Migrate session shares
375
+ const shareSessions = shareFiles.map((file) => path.basename(file, ".json"))
376
+ const shareValues = [] as any[]
377
+ for (let i = 0; i < shareFiles.length; i += batchSize) {
378
+ const end = Math.min(i + batchSize, shareFiles.length)
379
+ const batch = await read(shareFiles, i, end)
380
+ shareValues.length = 0
381
+ for (let j = 0; j < batch.length; j++) {
382
+ const data = batch[j]
383
+ if (!data) continue
384
+ const sessionID = shareSessions[i + j]
385
+ if (!sessionIds.has(sessionID)) {
386
+ orphans.shares++
387
+ continue
388
+ }
389
+ if (!data?.id || !data?.secret || !data?.url) {
390
+ errs.push(`session_share missing id/secret/url: ${shareFiles[i + j]}`)
391
+ continue
392
+ }
393
+ shareValues.push({ session_id: sessionID, id: data.id, secret: data.secret, url: data.url })
394
+ }
395
+ stats.shares += insert(shareValues, SessionShareTable, "session_share")
396
+ step("shares", end - i)
397
+ }
398
+ log.info("migrated session shares", { count: stats.shares })
399
+ if (orphans.shares > 0) {
400
+ log.warn("skipped orphaned session shares", { count: orphans.shares })
401
+ }
402
+
403
+ sqlite.exec("COMMIT")
404
+
405
+ log.info("json migration complete", {
406
+ projects: stats.projects,
407
+ sessions: stats.sessions,
408
+ messages: stats.messages,
409
+ parts: stats.parts,
410
+ todos: stats.todos,
411
+ permissions: stats.permissions,
412
+ shares: stats.shares,
413
+ errorCount: stats.errors.length,
414
+ duration: Math.round(performance.now() - start),
415
+ })
416
+
417
+ if (stats.errors.length > 0) {
418
+ log.warn("migration errors", { errors: stats.errors.slice(0, 20) })
419
+ }
420
+
421
+ progress?.({ current: total, total, label: "complete" })
422
+
423
+ return stats
424
+ }
425
+ }
@@ -0,0 +1,10 @@
1
+ import { integer } from "drizzle-orm/sqlite-core"
2
+
3
+ export const Timestamps = {
4
+ time_created: integer()
5
+ .notNull()
6
+ .$default(() => Date.now()),
7
+ time_updated: integer()
8
+ .notNull()
9
+ .$onUpdate(() => Date.now()),
10
+ }
@@ -0,0 +1,5 @@
1
+ export { ControlAccountTable } from "../control/control.sql"
2
+ export { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql"
3
+ export { SessionShareTable } from "../share/share.sql"
4
+ export { ProjectTable } from "../project/project.sql"
5
+ export { WorkspaceTable } from "../control-plane/workspace.sql"
@@ -0,0 +1,220 @@
1
+ import { Log } from "../util/log"
2
+ import path from "path"
3
+ import fs from "fs/promises"
4
+ import { Global } from "../global"
5
+ import { Filesystem } from "../util/filesystem"
6
+ import { lazy } from "../util/lazy"
7
+ import { Lock } from "../util/lock"
8
+ import { $ } from "bun"
9
+ import { NamedError } from "@opencode-ai/util/error"
10
+ import z from "zod"
11
+ import { Glob } from "../util/glob"
12
+
13
+ export namespace Storage {
14
+ const log = Log.create({ service: "storage" })
15
+
16
+ type Migration = (dir: string) => Promise<void>
17
+
18
+ export const NotFoundError = NamedError.create(
19
+ "NotFoundError",
20
+ z.object({
21
+ message: z.string(),
22
+ }),
23
+ )
24
+
25
+ const MIGRATIONS: Migration[] = [
26
+ async (dir) => {
27
+ const project = path.resolve(dir, "../project")
28
+ if (!(await Filesystem.isDir(project))) return
29
+ const projectDirs = await Glob.scan("*", {
30
+ cwd: project,
31
+ include: "all",
32
+ })
33
+ for (const projectDir of projectDirs) {
34
+ const fullPath = path.join(project, projectDir)
35
+ if (!(await Filesystem.isDir(fullPath))) continue
36
+ log.info(`migrating project ${projectDir}`)
37
+ let projectID = projectDir
38
+ const fullProjectDir = path.join(project, projectDir)
39
+ let worktree = "/"
40
+
41
+ if (projectID !== "global") {
42
+ for (const msgFile of await Glob.scan("storage/session/message/*/*.json", {
43
+ cwd: path.join(project, projectDir),
44
+ absolute: true,
45
+ })) {
46
+ const json = await Filesystem.readJson<any>(msgFile)
47
+ worktree = json.path?.root
48
+ if (worktree) break
49
+ }
50
+ if (!worktree) continue
51
+ if (!(await Filesystem.isDir(worktree))) continue
52
+ const [id] = await $`git rev-list --max-parents=0 --all`
53
+ .quiet()
54
+ .nothrow()
55
+ .cwd(worktree)
56
+ .text()
57
+ .then((x) =>
58
+ x
59
+ .split("\n")
60
+ .filter(Boolean)
61
+ .map((x) => x.trim())
62
+ .toSorted(),
63
+ )
64
+ if (!id) continue
65
+ projectID = id
66
+
67
+ await Filesystem.writeJson(path.join(dir, "project", projectID + ".json"), {
68
+ id,
69
+ vcs: "git",
70
+ worktree,
71
+ time: {
72
+ created: Date.now(),
73
+ initialized: Date.now(),
74
+ },
75
+ })
76
+
77
+ log.info(`migrating sessions for project ${projectID}`)
78
+ for (const sessionFile of await Glob.scan("storage/session/info/*.json", {
79
+ cwd: fullProjectDir,
80
+ absolute: true,
81
+ })) {
82
+ const dest = path.join(dir, "session", projectID, path.basename(sessionFile))
83
+ log.info("copying", {
84
+ sessionFile,
85
+ dest,
86
+ })
87
+ const session = await Filesystem.readJson<any>(sessionFile)
88
+ await Filesystem.writeJson(dest, session)
89
+ log.info(`migrating messages for session ${session.id}`)
90
+ for (const msgFile of await Glob.scan(`storage/session/message/${session.id}/*.json`, {
91
+ cwd: fullProjectDir,
92
+ absolute: true,
93
+ })) {
94
+ const dest = path.join(dir, "message", session.id, path.basename(msgFile))
95
+ log.info("copying", {
96
+ msgFile,
97
+ dest,
98
+ })
99
+ const message = await Filesystem.readJson<any>(msgFile)
100
+ await Filesystem.writeJson(dest, message)
101
+
102
+ log.info(`migrating parts for message ${message.id}`)
103
+ for (const partFile of await Glob.scan(`storage/session/part/${session.id}/${message.id}/*.json`, {
104
+ cwd: fullProjectDir,
105
+ absolute: true,
106
+ })) {
107
+ const dest = path.join(dir, "part", message.id, path.basename(partFile))
108
+ const part = await Filesystem.readJson(partFile)
109
+ log.info("copying", {
110
+ partFile,
111
+ dest,
112
+ })
113
+ await Filesystem.writeJson(dest, part)
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ },
120
+ async (dir) => {
121
+ for (const item of await Glob.scan("session/*/*.json", {
122
+ cwd: dir,
123
+ absolute: true,
124
+ })) {
125
+ const session = await Filesystem.readJson<any>(item)
126
+ if (!session.projectID) continue
127
+ if (!session.summary?.diffs) continue
128
+ const { diffs } = session.summary
129
+ await Filesystem.write(path.join(dir, "session_diff", session.id + ".json"), JSON.stringify(diffs))
130
+ await Filesystem.writeJson(path.join(dir, "session", session.projectID, session.id + ".json"), {
131
+ ...session,
132
+ summary: {
133
+ additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
134
+ deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
135
+ },
136
+ })
137
+ }
138
+ },
139
+ ]
140
+
141
+ const state = lazy(async () => {
142
+ const dir = path.join(Global.Path.data, "storage")
143
+ const migration = await Filesystem.readJson<string>(path.join(dir, "migration"))
144
+ .then((x) => parseInt(x))
145
+ .catch(() => 0)
146
+ for (let index = migration; index < MIGRATIONS.length; index++) {
147
+ log.info("running migration", { index })
148
+ const migration = MIGRATIONS[index]
149
+ await migration(dir).catch(() => log.error("failed to run migration", { index }))
150
+ await Filesystem.write(path.join(dir, "migration"), (index + 1).toString())
151
+ }
152
+ return {
153
+ dir,
154
+ }
155
+ })
156
+
157
+ export async function remove(key: string[]) {
158
+ const dir = await state().then((x) => x.dir)
159
+ const target = path.join(dir, ...key) + ".json"
160
+ return withErrorHandling(async () => {
161
+ await fs.unlink(target).catch(() => {})
162
+ })
163
+ }
164
+
165
+ export async function read<T>(key: string[]) {
166
+ const dir = await state().then((x) => x.dir)
167
+ const target = path.join(dir, ...key) + ".json"
168
+ return withErrorHandling(async () => {
169
+ using _ = await Lock.read(target)
170
+ const result = await Filesystem.readJson<T>(target)
171
+ return result as T
172
+ })
173
+ }
174
+
175
+ export async function update<T>(key: string[], fn: (draft: T) => void) {
176
+ const dir = await state().then((x) => x.dir)
177
+ const target = path.join(dir, ...key) + ".json"
178
+ return withErrorHandling(async () => {
179
+ using _ = await Lock.write(target)
180
+ const content = await Filesystem.readJson<T>(target)
181
+ fn(content as T)
182
+ await Filesystem.writeJson(target, content)
183
+ return content
184
+ })
185
+ }
186
+
187
+ export async function write<T>(key: string[], content: T) {
188
+ const dir = await state().then((x) => x.dir)
189
+ const target = path.join(dir, ...key) + ".json"
190
+ return withErrorHandling(async () => {
191
+ using _ = await Lock.write(target)
192
+ await Filesystem.writeJson(target, content)
193
+ })
194
+ }
195
+
196
+ async function withErrorHandling<T>(body: () => Promise<T>) {
197
+ return body().catch((e) => {
198
+ if (!(e instanceof Error)) throw e
199
+ const errnoException = e as NodeJS.ErrnoException
200
+ if (errnoException.code === "ENOENT") {
201
+ throw new NotFoundError({ message: `Resource not found: ${errnoException.path}` })
202
+ }
203
+ throw e
204
+ })
205
+ }
206
+
207
+ export async function list(prefix: string[]) {
208
+ const dir = await state().then((x) => x.dir)
209
+ try {
210
+ const result = await Glob.scan("**/*", {
211
+ cwd: path.join(dir, ...prefix),
212
+ include: "file",
213
+ }).then((results) => results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)]))
214
+ result.sort()
215
+ return result
216
+ } catch {
217
+ return []
218
+ }
219
+ }
220
+ }