@toolkit-cli/toolkode 1.3.7

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 (676) 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/opencode +179 -0
  6. package/bin/toolkode +17 -0
  7. package/bin/toolkode.cjs +190 -0
  8. package/bunfig.toml +7 -0
  9. package/drizzle.config.ts +10 -0
  10. package/git +0 -0
  11. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  12. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  13. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  14. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  15. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  16. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  17. package/migration/20260225215848_workspace/migration.sql +7 -0
  18. package/migration/20260225215848_workspace/snapshot.json +959 -0
  19. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  20. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  21. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  22. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  23. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  24. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  25. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  26. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  27. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  28. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  29. package/migration/20260323234822_events/migration.sql +13 -0
  30. package/migration/20260323234822_events/snapshot.json +1271 -0
  31. package/package.json +160 -0
  32. package/parsers-config.ts +290 -0
  33. package/script/build-node.ts +54 -0
  34. package/script/build.ts +276 -0
  35. package/script/check-migrations.ts +16 -0
  36. package/script/postinstall.mjs +131 -0
  37. package/script/publish.ts +181 -0
  38. package/script/schema.ts +63 -0
  39. package/script/seed-e2e.ts +60 -0
  40. package/script/upgrade-opentui.ts +64 -0
  41. package/specs/effect-migration.md +293 -0
  42. package/specs/tui-plugins.md +389 -0
  43. package/src/account/account.sql.ts +39 -0
  44. package/src/account/index.ts +397 -0
  45. package/src/account/repo.ts +163 -0
  46. package/src/account/schema.ts +91 -0
  47. package/src/acp/README.md +174 -0
  48. package/src/acp/agent.ts +1743 -0
  49. package/src/acp/session.ts +116 -0
  50. package/src/acp/types.ts +24 -0
  51. package/src/agent/agent.ts +418 -0
  52. package/src/agent/generate.txt +75 -0
  53. package/src/agent/prompt/compaction.txt +14 -0
  54. package/src/agent/prompt/explore.txt +18 -0
  55. package/src/agent/prompt/summary.txt +11 -0
  56. package/src/agent/prompt/title.txt +44 -0
  57. package/src/auth/index.ts +115 -0
  58. package/src/bun/index.ts +128 -0
  59. package/src/bun/registry.ts +50 -0
  60. package/src/bus/bus-event.ts +40 -0
  61. package/src/bus/global.ts +10 -0
  62. package/src/bus/index.ts +184 -0
  63. package/src/channel/index.ts +231 -0
  64. package/src/cli/bootstrap.ts +17 -0
  65. package/src/cli/cmd/account.ts +257 -0
  66. package/src/cli/cmd/acp.ts +70 -0
  67. package/src/cli/cmd/agent.ts +245 -0
  68. package/src/cli/cmd/cmd.ts +7 -0
  69. package/src/cli/cmd/db.ts +119 -0
  70. package/src/cli/cmd/debug/agent.ts +167 -0
  71. package/src/cli/cmd/debug/config.ts +16 -0
  72. package/src/cli/cmd/debug/file.ts +97 -0
  73. package/src/cli/cmd/debug/index.ts +48 -0
  74. package/src/cli/cmd/debug/lsp.ts +53 -0
  75. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  76. package/src/cli/cmd/debug/scrap.ts +16 -0
  77. package/src/cli/cmd/debug/skill.ts +16 -0
  78. package/src/cli/cmd/debug/snapshot.ts +52 -0
  79. package/src/cli/cmd/export.ts +89 -0
  80. package/src/cli/cmd/generate.ts +38 -0
  81. package/src/cli/cmd/github.ts +1646 -0
  82. package/src/cli/cmd/import.ts +207 -0
  83. package/src/cli/cmd/mcp.ts +754 -0
  84. package/src/cli/cmd/models.ts +78 -0
  85. package/src/cli/cmd/plug.ts +231 -0
  86. package/src/cli/cmd/pr.ts +127 -0
  87. package/src/cli/cmd/providers.ts +482 -0
  88. package/src/cli/cmd/run.ts +738 -0
  89. package/src/cli/cmd/serve.ts +42 -0
  90. package/src/cli/cmd/session.ts +159 -0
  91. package/src/cli/cmd/stats.ts +410 -0
  92. package/src/cli/cmd/tui/app.tsx +1255 -0
  93. package/src/cli/cmd/tui/attach.ts +88 -0
  94. package/src/cli/cmd/tui/component/border.tsx +21 -0
  95. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  96. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  97. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  98. package/src/cli/cmd/tui/component/dialog-model.tsx +264 -0
  99. package/src/cli/cmd/tui/component/dialog-provider.tsx +334 -0
  100. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  101. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  102. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  103. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  104. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  105. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  106. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  107. package/src/cli/cmd/tui/component/dialog-variant.tsx +29 -0
  108. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  109. package/src/cli/cmd/tui/component/error-component.tsx +91 -0
  110. package/src/cli/cmd/tui/component/logo.tsx +86 -0
  111. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  112. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
  113. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  114. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  115. package/src/cli/cmd/tui/component/prompt/index.tsx +1353 -0
  116. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  117. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  118. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  119. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  120. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  121. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  122. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  123. package/src/cli/cmd/tui/context/args.tsx +15 -0
  124. package/src/cli/cmd/tui/context/directory.ts +13 -0
  125. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  126. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  127. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  128. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  129. package/src/cli/cmd/tui/context/local.tsx +406 -0
  130. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  131. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  132. package/src/cli/cmd/tui/context/route.tsx +52 -0
  133. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  134. package/src/cli/cmd/tui/context/sync.tsx +504 -0
  135. package/src/cli/cmd/tui/context/theme/amber.json +245 -0
  136. package/src/cli/cmd/tui/context/theme/amiga.json +245 -0
  137. package/src/cli/cmd/tui/context/theme/atari.json +245 -0
  138. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  139. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  140. package/src/cli/cmd/tui/context/theme/borland.json +245 -0
  141. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  142. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  143. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  144. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  145. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  146. package/src/cli/cmd/tui/context/theme/commodore.json +245 -0
  147. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  148. package/src/cli/cmd/tui/context/theme/dos-edit.json +245 -0
  149. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  150. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  151. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  152. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  153. package/src/cli/cmd/tui/context/theme/gnu.json +245 -0
  154. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  155. package/src/cli/cmd/tui/context/theme/hacker.json +245 -0
  156. package/src/cli/cmd/tui/context/theme/irix.json +245 -0
  157. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  158. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  159. package/src/cli/cmd/tui/context/theme/mac84.json +245 -0
  160. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  161. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  162. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  163. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  164. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  165. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  166. package/src/cli/cmd/tui/context/theme/norton.json +245 -0
  167. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  168. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  169. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  170. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  171. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  172. package/src/cli/cmd/tui/context/theme/pine.json +245 -0
  173. package/src/cli/cmd/tui/context/theme/retrowave.json +245 -0
  174. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  175. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  176. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  177. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  178. package/src/cli/cmd/tui/context/theme/toolkode.json +245 -0
  179. package/src/cli/cmd/tui/context/theme/tron.json +245 -0
  180. package/src/cli/cmd/tui/context/theme/ubuntu.json +245 -0
  181. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  182. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  183. package/src/cli/cmd/tui/context/theme/vt100.json +245 -0
  184. package/src/cli/cmd/tui/context/theme/xcode.json +245 -0
  185. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  186. package/src/cli/cmd/tui/context/theme.tsx +1288 -0
  187. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  188. package/src/cli/cmd/tui/event.ts +49 -0
  189. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  190. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  191. package/src/cli/cmd/tui/feature-plugins/sidebar/agents-panel.tsx +95 -0
  192. package/src/cli/cmd/tui/feature-plugins/sidebar/btw-panel.tsx +105 -0
  193. package/src/cli/cmd/tui/feature-plugins/sidebar/commands-panel.tsx +40 -0
  194. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  195. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  196. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  197. package/src/cli/cmd/tui/feature-plugins/sidebar/git-panel.tsx +36 -0
  198. package/src/cli/cmd/tui/feature-plugins/sidebar/loop-panel.tsx +124 -0
  199. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  200. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  201. package/src/cli/cmd/tui/feature-plugins/sidebar/session-panel.tsx +48 -0
  202. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  203. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  204. package/src/cli/cmd/tui/plugin/api.tsx +420 -0
  205. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  206. package/src/cli/cmd/tui/plugin/internal.ts +37 -0
  207. package/src/cli/cmd/tui/plugin/runtime.ts +967 -0
  208. package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
  209. package/src/cli/cmd/tui/routes/home.tsx +173 -0
  210. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  211. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  212. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  213. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  214. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  215. package/src/cli/cmd/tui/routes/session/index.tsx +2229 -0
  216. package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
  217. package/src/cli/cmd/tui/routes/session/question.tsx +467 -0
  218. package/src/cli/cmd/tui/routes/session/sidebar.tsx +72 -0
  219. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
  220. package/src/cli/cmd/tui/thread.ts +232 -0
  221. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  222. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  223. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +208 -0
  224. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  225. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +106 -0
  226. package/src/cli/cmd/tui/ui/dialog-select.tsx +402 -0
  227. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  228. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  229. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  230. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  231. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  232. package/src/cli/cmd/tui/util/editor.ts +37 -0
  233. package/src/cli/cmd/tui/util/selection.ts +25 -0
  234. package/src/cli/cmd/tui/util/signal.ts +7 -0
  235. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  236. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  237. package/src/cli/cmd/tui/win32.ts +129 -0
  238. package/src/cli/cmd/tui/worker.ts +204 -0
  239. package/src/cli/cmd/uninstall.ts +353 -0
  240. package/src/cli/cmd/upgrade.ts +73 -0
  241. package/src/cli/cmd/web.ts +81 -0
  242. package/src/cli/effect/prompt.ts +25 -0
  243. package/src/cli/error.ts +46 -0
  244. package/src/cli/logo.ts +7 -0
  245. package/src/cli/network.ts +60 -0
  246. package/src/cli/ui.ts +116 -0
  247. package/src/cli/upgrade.ts +31 -0
  248. package/src/command/index.ts +195 -0
  249. package/src/command/template/initialize.txt +10 -0
  250. package/src/command/template/review.txt +101 -0
  251. package/src/config/config.ts +1693 -0
  252. package/src/config/markdown.ts +99 -0
  253. package/src/config/migrate-tui-config.ts +155 -0
  254. package/src/config/paths.ts +174 -0
  255. package/src/config/tui-schema.ts +36 -0
  256. package/src/config/tui.ts +212 -0
  257. package/src/control-plane/adaptors/index.ts +20 -0
  258. package/src/control-plane/adaptors/worktree.ts +38 -0
  259. package/src/control-plane/schema.ts +17 -0
  260. package/src/control-plane/sse.ts +66 -0
  261. package/src/control-plane/types.ts +21 -0
  262. package/src/control-plane/workspace.sql.ts +17 -0
  263. package/src/control-plane/workspace.ts +154 -0
  264. package/src/cron/index.ts +241 -0
  265. package/src/cron/parse.ts +189 -0
  266. package/src/effect/cross-spawn-spawner.ts +479 -0
  267. package/src/effect/instance-registry.ts +12 -0
  268. package/src/effect/instance-state.ts +47 -0
  269. package/src/effect/run-service.ts +19 -0
  270. package/src/env/index.ts +28 -0
  271. package/src/file/ignore.ts +82 -0
  272. package/src/file/index.ts +693 -0
  273. package/src/file/protected.ts +59 -0
  274. package/src/file/ripgrep.ts +376 -0
  275. package/src/file/time.ts +128 -0
  276. package/src/file/watcher.ts +171 -0
  277. package/src/filesystem/index.ts +226 -0
  278. package/src/flag/flag.ts +157 -0
  279. package/src/format/formatter.ts +396 -0
  280. package/src/format/index.ts +199 -0
  281. package/src/global/index.ts +54 -0
  282. package/src/hooks/index.ts +302 -0
  283. package/src/id/id.ts +85 -0
  284. package/src/ide/index.ts +74 -0
  285. package/src/index.ts +243 -0
  286. package/src/installation/index.ts +363 -0
  287. package/src/lsp/client.ts +252 -0
  288. package/src/lsp/index.ts +558 -0
  289. package/src/lsp/language.ts +120 -0
  290. package/src/lsp/launch.ts +21 -0
  291. package/src/lsp/server.ts +2093 -0
  292. package/src/mcp/auth.ts +181 -0
  293. package/src/mcp/index.ts +926 -0
  294. package/src/mcp/oauth-callback.ts +215 -0
  295. package/src/mcp/oauth-provider.ts +185 -0
  296. package/src/node.ts +1 -0
  297. package/src/patch/index.ts +680 -0
  298. package/src/permission/arity.ts +163 -0
  299. package/src/permission/evaluate.ts +15 -0
  300. package/src/permission/index.ts +322 -0
  301. package/src/permission/schema.ts +17 -0
  302. package/src/plugin/codex.ts +628 -0
  303. package/src/plugin/copilot.ts +343 -0
  304. package/src/plugin/index.ts +331 -0
  305. package/src/plugin/install.ts +384 -0
  306. package/src/plugin/meta.ts +165 -0
  307. package/src/plugin/shared.ts +172 -0
  308. package/src/project/bootstrap.ts +31 -0
  309. package/src/project/instance.ts +167 -0
  310. package/src/project/project.sql.ts +16 -0
  311. package/src/project/project.ts +519 -0
  312. package/src/project/schema.ts +16 -0
  313. package/src/project/state.ts +70 -0
  314. package/src/project/vcs.ts +124 -0
  315. package/src/provider/auth.ts +252 -0
  316. package/src/provider/error.ts +197 -0
  317. package/src/provider/models.ts +138 -0
  318. package/src/provider/provider.ts +1593 -0
  319. package/src/provider/schema.ts +39 -0
  320. package/src/provider/sdk/copilot/README.md +5 -0
  321. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  322. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  323. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  324. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  325. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  326. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  327. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  328. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  329. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  330. package/src/provider/sdk/copilot/index.ts +2 -0
  331. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  332. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  333. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  334. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  335. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  336. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  337. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  338. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  339. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  340. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  341. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  342. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  343. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  344. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  345. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  346. package/src/provider/toolkit-manifest.ts +110 -0
  347. package/src/provider/transform.ts +1045 -0
  348. package/src/pty/index.ts +397 -0
  349. package/src/pty/schema.ts +17 -0
  350. package/src/question/index.ts +221 -0
  351. package/src/question/schema.ts +17 -0
  352. package/src/server/error.ts +36 -0
  353. package/src/server/event.ts +7 -0
  354. package/src/server/instance.ts +285 -0
  355. package/src/server/mdns.ts +60 -0
  356. package/src/server/middleware.ts +29 -0
  357. package/src/server/projectors.ts +28 -0
  358. package/src/server/router.ts +99 -0
  359. package/src/server/routes/config.ts +92 -0
  360. package/src/server/routes/event.ts +83 -0
  361. package/src/server/routes/experimental.ts +271 -0
  362. package/src/server/routes/file.ts +197 -0
  363. package/src/server/routes/global.ts +339 -0
  364. package/src/server/routes/mcp.ts +225 -0
  365. package/src/server/routes/permission.ts +69 -0
  366. package/src/server/routes/project.ts +118 -0
  367. package/src/server/routes/provider.ts +171 -0
  368. package/src/server/routes/pty.ts +211 -0
  369. package/src/server/routes/question.ts +99 -0
  370. package/src/server/routes/session.ts +1031 -0
  371. package/src/server/routes/tui.ts +379 -0
  372. package/src/server/routes/workspace.ts +94 -0
  373. package/src/server/server.ts +312 -0
  374. package/src/session/compaction.ts +424 -0
  375. package/src/session/index.ts +882 -0
  376. package/src/session/instruction.ts +321 -0
  377. package/src/session/llm.ts +341 -0
  378. package/src/session/message-v2.ts +1030 -0
  379. package/src/session/message.ts +191 -0
  380. package/src/session/overflow.ts +22 -0
  381. package/src/session/processor.ts +554 -0
  382. package/src/session/projectors.ts +135 -0
  383. package/src/session/prompt/anthropic.txt +105 -0
  384. package/src/session/prompt/beast.txt +147 -0
  385. package/src/session/prompt/build-switch.txt +5 -0
  386. package/src/session/prompt/codex.txt +79 -0
  387. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  388. package/src/session/prompt/default.txt +108 -0
  389. package/src/session/prompt/gemini.txt +155 -0
  390. package/src/session/prompt/gpt.txt +107 -0
  391. package/src/session/prompt/max-steps.txt +16 -0
  392. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  393. package/src/session/prompt/plan.txt +26 -0
  394. package/src/session/prompt/trinity.txt +97 -0
  395. package/src/session/prompt.ts +2058 -0
  396. package/src/session/retry.ts +106 -0
  397. package/src/session/revert.ts +138 -0
  398. package/src/session/schema.ts +38 -0
  399. package/src/session/session.sql.ts +103 -0
  400. package/src/session/status.ts +102 -0
  401. package/src/session/summary.ts +170 -0
  402. package/src/session/system.ts +74 -0
  403. package/src/session/todo.ts +57 -0
  404. package/src/share/share-next.ts +288 -0
  405. package/src/share/share.sql.ts +13 -0
  406. package/src/shell/shell.ts +73 -0
  407. package/src/skill/discovery.ts +116 -0
  408. package/src/skill/index.ts +284 -0
  409. package/src/skills-marketplace/index.ts +305 -0
  410. package/src/snapshot/index.ts +489 -0
  411. package/src/sql.d.ts +4 -0
  412. package/src/storage/db.bun.ts +8 -0
  413. package/src/storage/db.node.ts +8 -0
  414. package/src/storage/db.ts +177 -0
  415. package/src/storage/json-migration.ts +425 -0
  416. package/src/storage/schema.sql.ts +10 -0
  417. package/src/storage/schema.ts +5 -0
  418. package/src/storage/storage.ts +217 -0
  419. package/src/sync/README.md +179 -0
  420. package/src/sync/event.sql.ts +16 -0
  421. package/src/sync/index.ts +263 -0
  422. package/src/sync/schema.ts +14 -0
  423. package/src/team/index.ts +428 -0
  424. package/src/tool/apply_patch.ts +281 -0
  425. package/src/tool/apply_patch.txt +33 -0
  426. package/src/tool/bash.ts +271 -0
  427. package/src/tool/bash.txt +115 -0
  428. package/src/tool/batch.ts +183 -0
  429. package/src/tool/batch.txt +24 -0
  430. package/src/tool/codesearch.ts +132 -0
  431. package/src/tool/codesearch.txt +12 -0
  432. package/src/tool/cron-create.ts +54 -0
  433. package/src/tool/cron-create.txt +16 -0
  434. package/src/tool/cron-delete.ts +29 -0
  435. package/src/tool/cron-delete.txt +1 -0
  436. package/src/tool/cron-list.ts +41 -0
  437. package/src/tool/cron-list.txt +1 -0
  438. package/src/tool/edit.ts +667 -0
  439. package/src/tool/edit.txt +10 -0
  440. package/src/tool/external-directory.ts +32 -0
  441. package/src/tool/glob.ts +78 -0
  442. package/src/tool/glob.txt +6 -0
  443. package/src/tool/grep.ts +156 -0
  444. package/src/tool/grep.txt +8 -0
  445. package/src/tool/invalid.ts +17 -0
  446. package/src/tool/ls.ts +121 -0
  447. package/src/tool/ls.txt +1 -0
  448. package/src/tool/lsp.ts +97 -0
  449. package/src/tool/lsp.txt +19 -0
  450. package/src/tool/multiedit.ts +46 -0
  451. package/src/tool/multiedit.txt +41 -0
  452. package/src/tool/plan-enter.txt +14 -0
  453. package/src/tool/plan-exit.txt +13 -0
  454. package/src/tool/plan.ts +131 -0
  455. package/src/tool/question.ts +33 -0
  456. package/src/tool/question.txt +10 -0
  457. package/src/tool/read.ts +293 -0
  458. package/src/tool/read.txt +14 -0
  459. package/src/tool/registry.ts +232 -0
  460. package/src/tool/schema.ts +17 -0
  461. package/src/tool/send-message.ts +59 -0
  462. package/src/tool/send-message.txt +7 -0
  463. package/src/tool/skill.ts +105 -0
  464. package/src/tool/task.ts +230 -0
  465. package/src/tool/task.txt +62 -0
  466. package/src/tool/team.ts +235 -0
  467. package/src/tool/team.txt +22 -0
  468. package/src/tool/todo.ts +31 -0
  469. package/src/tool/todowrite.txt +167 -0
  470. package/src/tool/tool.ts +90 -0
  471. package/src/tool/truncate.ts +144 -0
  472. package/src/tool/truncation-dir.ts +4 -0
  473. package/src/tool/webfetch.ts +206 -0
  474. package/src/tool/webfetch.txt +13 -0
  475. package/src/tool/websearch.ts +150 -0
  476. package/src/tool/websearch.txt +14 -0
  477. package/src/tool/write.ts +84 -0
  478. package/src/tool/write.txt +8 -0
  479. package/src/util/abort.ts +35 -0
  480. package/src/util/archive.ts +17 -0
  481. package/src/util/color.ts +19 -0
  482. package/src/util/context.ts +25 -0
  483. package/src/util/data-url.ts +9 -0
  484. package/src/util/defer.ts +12 -0
  485. package/src/util/effect-http-client.ts +11 -0
  486. package/src/util/effect-zod.ts +98 -0
  487. package/src/util/error.ts +77 -0
  488. package/src/util/filesystem.ts +203 -0
  489. package/src/util/flock.ts +333 -0
  490. package/src/util/fn.ts +21 -0
  491. package/src/util/format.ts +20 -0
  492. package/src/util/git.ts +35 -0
  493. package/src/util/glob.ts +34 -0
  494. package/src/util/hash.ts +7 -0
  495. package/src/util/iife.ts +3 -0
  496. package/src/util/keybind.ts +103 -0
  497. package/src/util/lazy.ts +23 -0
  498. package/src/util/locale.ts +81 -0
  499. package/src/util/lock.ts +98 -0
  500. package/src/util/log.ts +182 -0
  501. package/src/util/network.ts +9 -0
  502. package/src/util/process.ts +172 -0
  503. package/src/util/queue.ts +32 -0
  504. package/src/util/record.ts +3 -0
  505. package/src/util/rpc.ts +66 -0
  506. package/src/util/schema.ts +53 -0
  507. package/src/util/scrap.ts +10 -0
  508. package/src/util/signal.ts +12 -0
  509. package/src/util/timeout.ts +14 -0
  510. package/src/util/token.ts +7 -0
  511. package/src/util/update-schema.ts +13 -0
  512. package/src/util/which.ts +14 -0
  513. package/src/util/wildcard.ts +59 -0
  514. package/src/worktree/index.ts +638 -0
  515. package/sst-env.d.ts +10 -0
  516. package/test/AGENTS.md +81 -0
  517. package/test/account/repo.test.ts +326 -0
  518. package/test/account/service.test.ts +282 -0
  519. package/test/acp/agent-interface.test.ts +51 -0
  520. package/test/acp/event-subscription.test.ts +685 -0
  521. package/test/agent/agent.test.ts +717 -0
  522. package/test/auth/auth.test.ts +58 -0
  523. package/test/bun.test.ts +53 -0
  524. package/test/bus/bus-effect.test.ts +164 -0
  525. package/test/bus/bus-integration.test.ts +87 -0
  526. package/test/bus/bus.test.ts +219 -0
  527. package/test/cli/account.test.ts +26 -0
  528. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  529. package/test/cli/github-action.test.ts +198 -0
  530. package/test/cli/github-remote.test.ts +80 -0
  531. package/test/cli/import.test.ts +54 -0
  532. package/test/cli/plugin-auth-picker.test.ts +120 -0
  533. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  534. package/test/cli/tui/plugin-add.test.ts +61 -0
  535. package/test/cli/tui/plugin-install.test.ts +95 -0
  536. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  537. package/test/cli/tui/plugin-loader-entrypoint.test.ts +189 -0
  538. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  539. package/test/cli/tui/plugin-loader.test.ts +563 -0
  540. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  541. package/test/cli/tui/theme-store.test.ts +51 -0
  542. package/test/cli/tui/thread.test.ts +128 -0
  543. package/test/cli/tui/transcript.test.ts +322 -0
  544. package/test/config/agent-color.test.ts +71 -0
  545. package/test/config/config.test.ts +2187 -0
  546. package/test/config/fixtures/empty-frontmatter.md +4 -0
  547. package/test/config/fixtures/frontmatter.md +28 -0
  548. package/test/config/fixtures/markdown-header.md +11 -0
  549. package/test/config/fixtures/no-frontmatter.md +1 -0
  550. package/test/config/fixtures/weird-model-id.md +13 -0
  551. package/test/config/markdown.test.ts +228 -0
  552. package/test/config/tui.test.ts +667 -0
  553. package/test/control-plane/sse.test.ts +56 -0
  554. package/test/effect/cross-spawn-spawner.test.ts +402 -0
  555. package/test/effect/instance-state.test.ts +384 -0
  556. package/test/effect/run-service.test.ts +46 -0
  557. package/test/file/fsmonitor.test.ts +62 -0
  558. package/test/file/ignore.test.ts +10 -0
  559. package/test/file/index.test.ts +946 -0
  560. package/test/file/path-traversal.test.ts +198 -0
  561. package/test/file/ripgrep.test.ts +54 -0
  562. package/test/file/time.test.ts +354 -0
  563. package/test/file/watcher.test.ts +247 -0
  564. package/test/filesystem/filesystem.test.ts +319 -0
  565. package/test/fixture/db.ts +11 -0
  566. package/test/fixture/fixture.test.ts +26 -0
  567. package/test/fixture/fixture.ts +141 -0
  568. package/test/fixture/flock-worker.ts +72 -0
  569. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  570. package/test/fixture/plug-worker.ts +93 -0
  571. package/test/fixture/plugin-meta-worker.ts +26 -0
  572. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  573. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  574. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  575. package/test/fixture/skills/index.json +6 -0
  576. package/test/fixture/tui-plugin.ts +335 -0
  577. package/test/fixture/tui-runtime.ts +34 -0
  578. package/test/format/format.test.ts +179 -0
  579. package/test/ide/ide.test.ts +82 -0
  580. package/test/installation/installation.test.ts +151 -0
  581. package/test/keybind.test.ts +421 -0
  582. package/test/lib/effect.ts +37 -0
  583. package/test/lib/filesystem.ts +10 -0
  584. package/test/lsp/client.test.ts +95 -0
  585. package/test/lsp/index.test.ts +55 -0
  586. package/test/lsp/launch.test.ts +22 -0
  587. package/test/lsp/lifecycle.test.ts +147 -0
  588. package/test/mcp/headers.test.ts +153 -0
  589. package/test/mcp/lifecycle.test.ts +750 -0
  590. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  591. package/test/mcp/oauth-browser.test.ts +249 -0
  592. package/test/memory/abort-leak.test.ts +137 -0
  593. package/test/patch/patch.test.ts +348 -0
  594. package/test/permission/arity.test.ts +33 -0
  595. package/test/permission/next.test.ts +1148 -0
  596. package/test/permission-task.test.ts +323 -0
  597. package/test/plugin/auth-override.test.ts +74 -0
  598. package/test/plugin/codex.test.ts +123 -0
  599. package/test/plugin/install-concurrency.test.ts +134 -0
  600. package/test/plugin/install.test.ts +504 -0
  601. package/test/plugin/loader-shared.test.ts +625 -0
  602. package/test/plugin/meta.test.ts +137 -0
  603. package/test/plugin/trigger.test.ts +111 -0
  604. package/test/preload.ts +90 -0
  605. package/test/project/migrate-global.test.ts +140 -0
  606. package/test/project/project.test.ts +459 -0
  607. package/test/project/state.test.ts +115 -0
  608. package/test/project/vcs.test.ts +116 -0
  609. package/test/project/worktree-remove.test.ts +96 -0
  610. package/test/project/worktree.test.ts +173 -0
  611. package/test/provider/amazon-bedrock.test.ts +447 -0
  612. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  613. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  614. package/test/provider/gitlab-duo.test.ts +412 -0
  615. package/test/provider/provider.test.ts +2284 -0
  616. package/test/provider/transform.test.ts +2758 -0
  617. package/test/pty/pty-output-isolation.test.ts +141 -0
  618. package/test/pty/pty-session.test.ts +92 -0
  619. package/test/question/question.test.ts +453 -0
  620. package/test/server/global-session-list.test.ts +89 -0
  621. package/test/server/project-init-git.test.ts +121 -0
  622. package/test/server/session-list.test.ts +90 -0
  623. package/test/server/session-messages.test.ts +132 -0
  624. package/test/server/session-select.test.ts +78 -0
  625. package/test/session/compaction.test.ts +1094 -0
  626. package/test/session/instruction.test.ts +170 -0
  627. package/test/session/llm.test.ts +882 -0
  628. package/test/session/message-v2.test.ts +957 -0
  629. package/test/session/messages-pagination.test.ts +115 -0
  630. package/test/session/processor-effect.test.ts +838 -0
  631. package/test/session/prompt.test.ts +518 -0
  632. package/test/session/retry.test.ts +232 -0
  633. package/test/session/revert-compact.test.ts +286 -0
  634. package/test/session/session.test.ts +142 -0
  635. package/test/session/structured-output-integration.test.ts +233 -0
  636. package/test/session/structured-output.test.ts +391 -0
  637. package/test/session/system.test.ts +59 -0
  638. package/test/share/share-next.test.ts +76 -0
  639. package/test/skill/discovery.test.ts +116 -0
  640. package/test/skill/skill.test.ts +392 -0
  641. package/test/snapshot/snapshot.test.ts +1235 -0
  642. package/test/storage/db.test.ts +14 -0
  643. package/test/storage/json-migration.test.ts +849 -0
  644. package/test/sync/index.test.ts +191 -0
  645. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  646. package/test/tool/apply_patch.test.ts +567 -0
  647. package/test/tool/bash.test.ts +403 -0
  648. package/test/tool/edit.test.ts +681 -0
  649. package/test/tool/external-directory.test.ts +128 -0
  650. package/test/tool/fixtures/large-image.png +0 -0
  651. package/test/tool/fixtures/models-api.json +38413 -0
  652. package/test/tool/grep.test.ts +111 -0
  653. package/test/tool/question.test.ts +108 -0
  654. package/test/tool/read.test.ts +509 -0
  655. package/test/tool/registry.test.ts +126 -0
  656. package/test/tool/skill.test.ts +167 -0
  657. package/test/tool/task.test.ts +49 -0
  658. package/test/tool/truncation.test.ts +161 -0
  659. package/test/tool/webfetch.test.ts +101 -0
  660. package/test/tool/write.test.ts +353 -0
  661. package/test/util/data-url.test.ts +14 -0
  662. package/test/util/effect-zod.test.ts +61 -0
  663. package/test/util/error.test.ts +38 -0
  664. package/test/util/filesystem.test.ts +558 -0
  665. package/test/util/flock.test.ts +383 -0
  666. package/test/util/format.test.ts +59 -0
  667. package/test/util/glob.test.ts +164 -0
  668. package/test/util/iife.test.ts +36 -0
  669. package/test/util/lazy.test.ts +50 -0
  670. package/test/util/lock.test.ts +72 -0
  671. package/test/util/module.test.ts +59 -0
  672. package/test/util/process.test.ts +128 -0
  673. package/test/util/timeout.test.ts +21 -0
  674. package/test/util/which.test.ts +100 -0
  675. package/test/util/wildcard.test.ts +90 -0
  676. package/tsconfig.json +23 -0
@@ -0,0 +1,198 @@
1
+ import { test, expect, describe } from "bun:test"
2
+ import path from "path"
3
+ import fs from "fs/promises"
4
+ import { Filesystem } from "../../src/util/filesystem"
5
+ import { File } from "../../src/file"
6
+ import { Instance } from "../../src/project/instance"
7
+ import { tmpdir } from "../fixture/fixture"
8
+
9
+ describe("Filesystem.contains", () => {
10
+ test("allows paths within project", () => {
11
+ expect(Filesystem.contains("/project", "/project/src")).toBe(true)
12
+ expect(Filesystem.contains("/project", "/project/src/file.ts")).toBe(true)
13
+ expect(Filesystem.contains("/project", "/project")).toBe(true)
14
+ })
15
+
16
+ test("blocks ../ traversal", () => {
17
+ expect(Filesystem.contains("/project", "/project/../etc")).toBe(false)
18
+ expect(Filesystem.contains("/project", "/project/src/../../etc")).toBe(false)
19
+ expect(Filesystem.contains("/project", "/etc/passwd")).toBe(false)
20
+ })
21
+
22
+ test("blocks absolute paths outside project", () => {
23
+ expect(Filesystem.contains("/project", "/etc/passwd")).toBe(false)
24
+ expect(Filesystem.contains("/project", "/tmp/file")).toBe(false)
25
+ expect(Filesystem.contains("/home/user/project", "/home/user/other")).toBe(false)
26
+ })
27
+
28
+ test("handles prefix collision edge cases", () => {
29
+ expect(Filesystem.contains("/project", "/project-other/file")).toBe(false)
30
+ expect(Filesystem.contains("/project", "/projectfile")).toBe(false)
31
+ })
32
+ })
33
+
34
+ /*
35
+ * Integration tests for File.read() and File.list() path traversal protection.
36
+ *
37
+ * These tests verify the HTTP API code path is protected. The HTTP endpoints
38
+ * in server.ts (GET /file/content, GET /file) call File.read()/File.list()
39
+ * directly - they do NOT go through ReadTool or the agent permission layer.
40
+ *
41
+ * This is a SEPARATE code path from ReadTool, which has its own checks.
42
+ */
43
+ describe("File.read path traversal protection", () => {
44
+ test("rejects ../ traversal attempting to read /etc/passwd", async () => {
45
+ await using tmp = await tmpdir({
46
+ init: async (dir) => {
47
+ await Bun.write(path.join(dir, "allowed.txt"), "allowed content")
48
+ },
49
+ })
50
+
51
+ await Instance.provide({
52
+ directory: tmp.path,
53
+ fn: async () => {
54
+ await expect(File.read("../../../etc/passwd")).rejects.toThrow("Access denied: path escapes project directory")
55
+ },
56
+ })
57
+ })
58
+
59
+ test("rejects deeply nested traversal", async () => {
60
+ await using tmp = await tmpdir()
61
+
62
+ await Instance.provide({
63
+ directory: tmp.path,
64
+ fn: async () => {
65
+ await expect(File.read("src/nested/../../../../../../../etc/passwd")).rejects.toThrow(
66
+ "Access denied: path escapes project directory",
67
+ )
68
+ },
69
+ })
70
+ })
71
+
72
+ test("allows valid paths within project", async () => {
73
+ await using tmp = await tmpdir({
74
+ init: async (dir) => {
75
+ await Bun.write(path.join(dir, "valid.txt"), "valid content")
76
+ },
77
+ })
78
+
79
+ await Instance.provide({
80
+ directory: tmp.path,
81
+ fn: async () => {
82
+ const result = await File.read("valid.txt")
83
+ expect(result.content).toBe("valid content")
84
+ },
85
+ })
86
+ })
87
+ })
88
+
89
+ describe("File.list path traversal protection", () => {
90
+ test("rejects ../ traversal attempting to list /etc", async () => {
91
+ await using tmp = await tmpdir()
92
+
93
+ await Instance.provide({
94
+ directory: tmp.path,
95
+ fn: async () => {
96
+ await expect(File.list("../../../etc")).rejects.toThrow("Access denied: path escapes project directory")
97
+ },
98
+ })
99
+ })
100
+
101
+ test("allows valid subdirectory listing", async () => {
102
+ await using tmp = await tmpdir({
103
+ init: async (dir) => {
104
+ await Bun.write(path.join(dir, "subdir", "file.txt"), "content")
105
+ },
106
+ })
107
+
108
+ await Instance.provide({
109
+ directory: tmp.path,
110
+ fn: async () => {
111
+ const result = await File.list("subdir")
112
+ expect(Array.isArray(result)).toBe(true)
113
+ },
114
+ })
115
+ })
116
+ })
117
+
118
+ describe("Instance.containsPath", () => {
119
+ test("returns true for path inside directory", async () => {
120
+ await using tmp = await tmpdir({ git: true })
121
+
122
+ await Instance.provide({
123
+ directory: tmp.path,
124
+ fn: () => {
125
+ expect(Instance.containsPath(path.join(tmp.path, "foo.txt"))).toBe(true)
126
+ expect(Instance.containsPath(path.join(tmp.path, "src", "file.ts"))).toBe(true)
127
+ },
128
+ })
129
+ })
130
+
131
+ test("returns true for path inside worktree but outside directory (monorepo subdirectory scenario)", async () => {
132
+ await using tmp = await tmpdir({ git: true })
133
+ const subdir = path.join(tmp.path, "packages", "lib")
134
+ await fs.mkdir(subdir, { recursive: true })
135
+
136
+ await Instance.provide({
137
+ directory: subdir,
138
+ fn: () => {
139
+ // .opencode at worktree root, but we're running from packages/lib
140
+ expect(Instance.containsPath(path.join(tmp.path, ".opencode", "state"))).toBe(true)
141
+ // sibling package should also be accessible
142
+ expect(Instance.containsPath(path.join(tmp.path, "packages", "other", "file.ts"))).toBe(true)
143
+ // worktree root itself
144
+ expect(Instance.containsPath(tmp.path)).toBe(true)
145
+ },
146
+ })
147
+ })
148
+
149
+ test("returns false for path outside both directory and worktree", async () => {
150
+ await using tmp = await tmpdir({ git: true })
151
+
152
+ await Instance.provide({
153
+ directory: tmp.path,
154
+ fn: () => {
155
+ expect(Instance.containsPath("/etc/passwd")).toBe(false)
156
+ expect(Instance.containsPath("/tmp/other-project")).toBe(false)
157
+ },
158
+ })
159
+ })
160
+
161
+ test("returns false for path with .. escaping worktree", async () => {
162
+ await using tmp = await tmpdir({ git: true })
163
+
164
+ await Instance.provide({
165
+ directory: tmp.path,
166
+ fn: () => {
167
+ expect(Instance.containsPath(path.join(tmp.path, "..", "escape.txt"))).toBe(false)
168
+ },
169
+ })
170
+ })
171
+
172
+ test("handles directory === worktree (running from repo root)", async () => {
173
+ await using tmp = await tmpdir({ git: true })
174
+
175
+ await Instance.provide({
176
+ directory: tmp.path,
177
+ fn: () => {
178
+ expect(Instance.directory).toBe(Instance.worktree)
179
+ expect(Instance.containsPath(path.join(tmp.path, "file.txt"))).toBe(true)
180
+ expect(Instance.containsPath("/etc/passwd")).toBe(false)
181
+ },
182
+ })
183
+ })
184
+
185
+ test("non-git project does not allow arbitrary paths via worktree='/'", async () => {
186
+ await using tmp = await tmpdir() // no git: true
187
+
188
+ await Instance.provide({
189
+ directory: tmp.path,
190
+ fn: () => {
191
+ // worktree is "/" for non-git projects, but containsPath should NOT allow all paths
192
+ expect(Instance.containsPath(path.join(tmp.path, "file.txt"))).toBe(true)
193
+ expect(Instance.containsPath("/etc/passwd")).toBe(false)
194
+ expect(Instance.containsPath("/tmp/other")).toBe(false)
195
+ },
196
+ })
197
+ })
198
+ })
@@ -0,0 +1,54 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import fs from "fs/promises"
3
+ import path from "path"
4
+ import { tmpdir } from "../fixture/fixture"
5
+ import { Ripgrep } from "../../src/file/ripgrep"
6
+
7
+ describe("file.ripgrep", () => {
8
+ test("defaults to include hidden", async () => {
9
+ await using tmp = await tmpdir({
10
+ init: async (dir) => {
11
+ await Bun.write(path.join(dir, "visible.txt"), "hello")
12
+ await fs.mkdir(path.join(dir, ".opencode"), { recursive: true })
13
+ await Bun.write(path.join(dir, ".opencode", "thing.json"), "{}")
14
+ },
15
+ })
16
+
17
+ const files = await Array.fromAsync(Ripgrep.files({ cwd: tmp.path }))
18
+ const hasVisible = files.includes("visible.txt")
19
+ const hasHidden = files.includes(path.join(".opencode", "thing.json"))
20
+ expect(hasVisible).toBe(true)
21
+ expect(hasHidden).toBe(true)
22
+ })
23
+
24
+ test("hidden false excludes hidden", async () => {
25
+ await using tmp = await tmpdir({
26
+ init: async (dir) => {
27
+ await Bun.write(path.join(dir, "visible.txt"), "hello")
28
+ await fs.mkdir(path.join(dir, ".opencode"), { recursive: true })
29
+ await Bun.write(path.join(dir, ".opencode", "thing.json"), "{}")
30
+ },
31
+ })
32
+
33
+ const files = await Array.fromAsync(Ripgrep.files({ cwd: tmp.path, hidden: false }))
34
+ const hasVisible = files.includes("visible.txt")
35
+ const hasHidden = files.includes(path.join(".opencode", "thing.json"))
36
+ expect(hasVisible).toBe(true)
37
+ expect(hasHidden).toBe(false)
38
+ })
39
+
40
+ test("search returns empty when nothing matches", async () => {
41
+ await using tmp = await tmpdir({
42
+ init: async (dir) => {
43
+ await Bun.write(path.join(dir, "match.ts"), "const value = 'other'\n")
44
+ },
45
+ })
46
+
47
+ const hits = await Ripgrep.search({
48
+ cwd: tmp.path,
49
+ pattern: "needle",
50
+ })
51
+
52
+ expect(hits).toEqual([])
53
+ })
54
+ })
@@ -0,0 +1,354 @@
1
+ import { describe, test, expect, afterEach } from "bun:test"
2
+ import path from "path"
3
+ import fs from "fs/promises"
4
+ import { FileTime } from "../../src/file/time"
5
+ import { Instance } from "../../src/project/instance"
6
+ import { SessionID } from "../../src/session/schema"
7
+ import { Filesystem } from "../../src/util/filesystem"
8
+ import { tmpdir } from "../fixture/fixture"
9
+
10
+ afterEach(async () => {
11
+ await Instance.disposeAll()
12
+ })
13
+
14
+ async function touch(file: string, time: number) {
15
+ const date = new Date(time)
16
+ await fs.utimes(file, date, date)
17
+ }
18
+
19
+ function gate() {
20
+ let open!: () => void
21
+ const wait = new Promise<void>((resolve) => {
22
+ open = resolve
23
+ })
24
+ return { open, wait }
25
+ }
26
+
27
+ describe("file/time", () => {
28
+ const sessionID = SessionID.make("ses_00000000000000000000000001")
29
+
30
+ describe("read() and get()", () => {
31
+ test("stores read timestamp", async () => {
32
+ await using tmp = await tmpdir()
33
+ const filepath = path.join(tmp.path, "file.txt")
34
+ await fs.writeFile(filepath, "content", "utf-8")
35
+
36
+ await Instance.provide({
37
+ directory: tmp.path,
38
+ fn: async () => {
39
+ const before = await FileTime.get(sessionID, filepath)
40
+ expect(before).toBeUndefined()
41
+
42
+ await FileTime.read(sessionID, filepath)
43
+
44
+ const after = await FileTime.get(sessionID, filepath)
45
+ expect(after).toBeInstanceOf(Date)
46
+ expect(after!.getTime()).toBeGreaterThan(0)
47
+ },
48
+ })
49
+ })
50
+
51
+ test("tracks separate timestamps per session", async () => {
52
+ await using tmp = await tmpdir()
53
+ const filepath = path.join(tmp.path, "file.txt")
54
+ await fs.writeFile(filepath, "content", "utf-8")
55
+
56
+ await Instance.provide({
57
+ directory: tmp.path,
58
+ fn: async () => {
59
+ await FileTime.read(SessionID.make("ses_00000000000000000000000002"), filepath)
60
+ await FileTime.read(SessionID.make("ses_00000000000000000000000003"), filepath)
61
+
62
+ const time1 = await FileTime.get(SessionID.make("ses_00000000000000000000000002"), filepath)
63
+ const time2 = await FileTime.get(SessionID.make("ses_00000000000000000000000003"), filepath)
64
+
65
+ expect(time1).toBeDefined()
66
+ expect(time2).toBeDefined()
67
+ },
68
+ })
69
+ })
70
+
71
+ test("updates timestamp on subsequent reads", async () => {
72
+ await using tmp = await tmpdir()
73
+ const filepath = path.join(tmp.path, "file.txt")
74
+ await fs.writeFile(filepath, "content", "utf-8")
75
+
76
+ await Instance.provide({
77
+ directory: tmp.path,
78
+ fn: async () => {
79
+ await FileTime.read(sessionID, filepath)
80
+ const first = await FileTime.get(sessionID, filepath)
81
+
82
+ await FileTime.read(sessionID, filepath)
83
+ const second = await FileTime.get(sessionID, filepath)
84
+
85
+ expect(second!.getTime()).toBeGreaterThanOrEqual(first!.getTime())
86
+ },
87
+ })
88
+ })
89
+
90
+ test("isolates reads by directory", async () => {
91
+ await using one = await tmpdir()
92
+ await using two = await tmpdir()
93
+ await using shared = await tmpdir()
94
+ const filepath = path.join(shared.path, "file.txt")
95
+ await fs.writeFile(filepath, "content", "utf-8")
96
+
97
+ await Instance.provide({
98
+ directory: one.path,
99
+ fn: async () => {
100
+ await FileTime.read(sessionID, filepath)
101
+ },
102
+ })
103
+
104
+ await Instance.provide({
105
+ directory: two.path,
106
+ fn: async () => {
107
+ expect(await FileTime.get(sessionID, filepath)).toBeUndefined()
108
+ },
109
+ })
110
+ })
111
+ })
112
+
113
+ describe("assert()", () => {
114
+ test("passes when file has not been modified", async () => {
115
+ await using tmp = await tmpdir()
116
+ const filepath = path.join(tmp.path, "file.txt")
117
+ await fs.writeFile(filepath, "content", "utf-8")
118
+ await touch(filepath, 1_000)
119
+
120
+ await Instance.provide({
121
+ directory: tmp.path,
122
+ fn: async () => {
123
+ await FileTime.read(sessionID, filepath)
124
+ await FileTime.assert(sessionID, filepath)
125
+ },
126
+ })
127
+ })
128
+
129
+ test("throws when file was not read first", async () => {
130
+ await using tmp = await tmpdir()
131
+ const filepath = path.join(tmp.path, "file.txt")
132
+ await fs.writeFile(filepath, "content", "utf-8")
133
+
134
+ await Instance.provide({
135
+ directory: tmp.path,
136
+ fn: async () => {
137
+ await expect(FileTime.assert(sessionID, filepath)).rejects.toThrow("You must read file")
138
+ },
139
+ })
140
+ })
141
+
142
+ test("throws when file was modified after read", async () => {
143
+ await using tmp = await tmpdir()
144
+ const filepath = path.join(tmp.path, "file.txt")
145
+ await fs.writeFile(filepath, "content", "utf-8")
146
+ await touch(filepath, 1_000)
147
+
148
+ await Instance.provide({
149
+ directory: tmp.path,
150
+ fn: async () => {
151
+ await FileTime.read(sessionID, filepath)
152
+ await fs.writeFile(filepath, "modified content", "utf-8")
153
+ await touch(filepath, 2_000)
154
+ await expect(FileTime.assert(sessionID, filepath)).rejects.toThrow("modified since it was last read")
155
+ },
156
+ })
157
+ })
158
+
159
+ test("includes timestamps in error message", async () => {
160
+ await using tmp = await tmpdir()
161
+ const filepath = path.join(tmp.path, "file.txt")
162
+ await fs.writeFile(filepath, "content", "utf-8")
163
+ await touch(filepath, 1_000)
164
+
165
+ await Instance.provide({
166
+ directory: tmp.path,
167
+ fn: async () => {
168
+ await FileTime.read(sessionID, filepath)
169
+ await fs.writeFile(filepath, "modified", "utf-8")
170
+ await touch(filepath, 2_000)
171
+
172
+ let error: Error | undefined
173
+ try {
174
+ await FileTime.assert(sessionID, filepath)
175
+ } catch (e) {
176
+ error = e as Error
177
+ }
178
+ expect(error).toBeDefined()
179
+ expect(error!.message).toContain("Last modification:")
180
+ expect(error!.message).toContain("Last read:")
181
+ },
182
+ })
183
+ })
184
+ })
185
+
186
+ describe("withLock()", () => {
187
+ test("executes function within lock", async () => {
188
+ await using tmp = await tmpdir()
189
+ const filepath = path.join(tmp.path, "file.txt")
190
+
191
+ await Instance.provide({
192
+ directory: tmp.path,
193
+ fn: async () => {
194
+ let executed = false
195
+ await FileTime.withLock(filepath, async () => {
196
+ executed = true
197
+ return "result"
198
+ })
199
+ expect(executed).toBe(true)
200
+ },
201
+ })
202
+ })
203
+
204
+ test("returns function result", async () => {
205
+ await using tmp = await tmpdir()
206
+ const filepath = path.join(tmp.path, "file.txt")
207
+
208
+ await Instance.provide({
209
+ directory: tmp.path,
210
+ fn: async () => {
211
+ const result = await FileTime.withLock(filepath, async () => {
212
+ return "success"
213
+ })
214
+ expect(result).toBe("success")
215
+ },
216
+ })
217
+ })
218
+
219
+ test("serializes concurrent operations on same file", async () => {
220
+ await using tmp = await tmpdir()
221
+ const filepath = path.join(tmp.path, "file.txt")
222
+
223
+ await Instance.provide({
224
+ directory: tmp.path,
225
+ fn: async () => {
226
+ const order: number[] = []
227
+ const hold = gate()
228
+ const ready = gate()
229
+
230
+ const op1 = FileTime.withLock(filepath, async () => {
231
+ order.push(1)
232
+ ready.open()
233
+ await hold.wait
234
+ order.push(2)
235
+ })
236
+
237
+ await ready.wait
238
+
239
+ const op2 = FileTime.withLock(filepath, async () => {
240
+ order.push(3)
241
+ order.push(4)
242
+ })
243
+
244
+ hold.open()
245
+
246
+ await Promise.all([op1, op2])
247
+ expect(order).toEqual([1, 2, 3, 4])
248
+ },
249
+ })
250
+ })
251
+
252
+ test("allows concurrent operations on different files", async () => {
253
+ await using tmp = await tmpdir()
254
+ const filepath1 = path.join(tmp.path, "file1.txt")
255
+ const filepath2 = path.join(tmp.path, "file2.txt")
256
+
257
+ await Instance.provide({
258
+ directory: tmp.path,
259
+ fn: async () => {
260
+ let started1 = false
261
+ let started2 = false
262
+ const hold = gate()
263
+ const ready = gate()
264
+
265
+ const op1 = FileTime.withLock(filepath1, async () => {
266
+ started1 = true
267
+ ready.open()
268
+ await hold.wait
269
+ expect(started2).toBe(true)
270
+ })
271
+
272
+ await ready.wait
273
+
274
+ const op2 = FileTime.withLock(filepath2, async () => {
275
+ started2 = true
276
+ hold.open()
277
+ })
278
+
279
+ await Promise.all([op1, op2])
280
+ expect(started1).toBe(true)
281
+ expect(started2).toBe(true)
282
+ },
283
+ })
284
+ })
285
+
286
+ test("releases lock even if function throws", async () => {
287
+ await using tmp = await tmpdir()
288
+ const filepath = path.join(tmp.path, "file.txt")
289
+
290
+ await Instance.provide({
291
+ directory: tmp.path,
292
+ fn: async () => {
293
+ await expect(
294
+ FileTime.withLock(filepath, async () => {
295
+ throw new Error("Test error")
296
+ }),
297
+ ).rejects.toThrow("Test error")
298
+
299
+ let executed = false
300
+ await FileTime.withLock(filepath, async () => {
301
+ executed = true
302
+ })
303
+ expect(executed).toBe(true)
304
+ },
305
+ })
306
+ })
307
+ })
308
+
309
+ describe("stat() Filesystem.stat pattern", () => {
310
+ test("reads file modification time via Filesystem.stat()", async () => {
311
+ await using tmp = await tmpdir()
312
+ const filepath = path.join(tmp.path, "file.txt")
313
+ await fs.writeFile(filepath, "content", "utf-8")
314
+ await touch(filepath, 1_000)
315
+
316
+ await Instance.provide({
317
+ directory: tmp.path,
318
+ fn: async () => {
319
+ await FileTime.read(sessionID, filepath)
320
+
321
+ const stats = Filesystem.stat(filepath)
322
+ expect(stats?.mtime).toBeInstanceOf(Date)
323
+ expect(stats!.mtime.getTime()).toBeGreaterThan(0)
324
+
325
+ await FileTime.assert(sessionID, filepath)
326
+ },
327
+ })
328
+ })
329
+
330
+ test("detects modification via stat mtime", async () => {
331
+ await using tmp = await tmpdir()
332
+ const filepath = path.join(tmp.path, "file.txt")
333
+ await fs.writeFile(filepath, "original", "utf-8")
334
+ await touch(filepath, 1_000)
335
+
336
+ await Instance.provide({
337
+ directory: tmp.path,
338
+ fn: async () => {
339
+ await FileTime.read(sessionID, filepath)
340
+
341
+ const originalStat = Filesystem.stat(filepath)
342
+
343
+ await fs.writeFile(filepath, "modified", "utf-8")
344
+ await touch(filepath, 2_000)
345
+
346
+ const newStat = Filesystem.stat(filepath)
347
+ expect(newStat!.mtime.getTime()).toBeGreaterThan(originalStat!.mtime.getTime())
348
+
349
+ await expect(FileTime.assert(sessionID, filepath)).rejects.toThrow()
350
+ },
351
+ })
352
+ })
353
+ })
354
+ })