@tinfoilsh/tinfoil-terminal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +88 -0
  5. package/bunfig.toml +7 -0
  6. package/package.json +134 -0
  7. package/parsers-config.ts +253 -0
  8. package/script/build.ts +176 -0
  9. package/script/postinstall.mjs +122 -0
  10. package/script/publish-registries.ts +187 -0
  11. package/script/publish.ts +70 -0
  12. package/script/schema.ts +47 -0
  13. package/src/acp/README.md +164 -0
  14. package/src/acp/agent.ts +1124 -0
  15. package/src/acp/session.ts +101 -0
  16. package/src/acp/types.ts +22 -0
  17. package/src/agent/agent.ts +284 -0
  18. package/src/agent/generate.txt +75 -0
  19. package/src/agent/prompt/compaction.txt +12 -0
  20. package/src/agent/prompt/explore.txt +18 -0
  21. package/src/agent/prompt/summary.txt +11 -0
  22. package/src/agent/prompt/title.txt +43 -0
  23. package/src/auth/index.ts +73 -0
  24. package/src/bun/index.ts +130 -0
  25. package/src/bus/bus-event.ts +43 -0
  26. package/src/bus/global.ts +10 -0
  27. package/src/bus/index.ts +105 -0
  28. package/src/cli/bootstrap.ts +17 -0
  29. package/src/cli/cmd/acp.ts +69 -0
  30. package/src/cli/cmd/agent.ts +257 -0
  31. package/src/cli/cmd/auth.ts +400 -0
  32. package/src/cli/cmd/cmd.ts +7 -0
  33. package/src/cli/cmd/debug/agent.ts +29 -0
  34. package/src/cli/cmd/debug/config.ts +16 -0
  35. package/src/cli/cmd/debug/file.ts +97 -0
  36. package/src/cli/cmd/debug/index.ts +48 -0
  37. package/src/cli/cmd/debug/lsp.ts +52 -0
  38. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  39. package/src/cli/cmd/debug/scrap.ts +16 -0
  40. package/src/cli/cmd/debug/skill.ts +16 -0
  41. package/src/cli/cmd/debug/snapshot.ts +52 -0
  42. package/src/cli/cmd/export.ts +88 -0
  43. package/src/cli/cmd/generate.ts +38 -0
  44. package/src/cli/cmd/github.ts +1546 -0
  45. package/src/cli/cmd/import.ts +98 -0
  46. package/src/cli/cmd/mcp.ts +671 -0
  47. package/src/cli/cmd/models.ts +77 -0
  48. package/src/cli/cmd/pr.ts +112 -0
  49. package/src/cli/cmd/run.ts +395 -0
  50. package/src/cli/cmd/serve.ts +16 -0
  51. package/src/cli/cmd/session.ts +135 -0
  52. package/src/cli/cmd/stats.ts +402 -0
  53. package/src/cli/cmd/tui/app.tsx +743 -0
  54. package/src/cli/cmd/tui/attach.ts +31 -0
  55. package/src/cli/cmd/tui/component/border.tsx +21 -0
  56. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  57. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  58. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  59. package/src/cli/cmd/tui/component/dialog-model.tsx +168 -0
  60. package/src/cli/cmd/tui/component/dialog-provider.tsx +47 -0
  61. package/src/cli/cmd/tui/component/dialog-session-list.tsx +115 -0
  62. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  63. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  64. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  65. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  66. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  67. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  68. package/src/cli/cmd/tui/component/logo.tsx +33 -0
  69. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +718 -0
  70. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  71. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  72. package/src/cli/cmd/tui/component/prompt/index.tsx +1083 -0
  73. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  74. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  75. package/src/cli/cmd/tui/component/tips.ts +103 -0
  76. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  77. package/src/cli/cmd/tui/context/args.tsx +14 -0
  78. package/src/cli/cmd/tui/context/directory.ts +13 -0
  79. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  80. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  81. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  82. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  83. package/src/cli/cmd/tui/context/local.tsx +393 -0
  84. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  85. package/src/cli/cmd/tui/context/route.tsx +46 -0
  86. package/src/cli/cmd/tui/context/sdk.tsx +94 -0
  87. package/src/cli/cmd/tui/context/sync.tsx +427 -0
  88. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  89. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  90. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  91. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  93. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  94. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  95. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  96. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  97. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  98. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  99. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  100. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  101. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  102. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  103. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  104. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  105. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  106. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  107. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  108. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  109. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  110. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  111. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  112. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  113. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  114. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  115. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  116. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  117. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  118. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  119. package/src/cli/cmd/tui/context/theme.tsx +1150 -0
  120. package/src/cli/cmd/tui/event.ts +46 -0
  121. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  122. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  123. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  124. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  126. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  127. package/src/cli/cmd/tui/routes/session/header.tsx +105 -0
  128. package/src/cli/cmd/tui/routes/session/index.tsx +1897 -0
  129. package/src/cli/cmd/tui/routes/session/permission.tsx +416 -0
  130. package/src/cli/cmd/tui/routes/session/question.tsx +368 -0
  131. package/src/cli/cmd/tui/routes/session/sidebar.tsx +279 -0
  132. package/src/cli/cmd/tui/spawn.ts +46 -0
  133. package/src/cli/cmd/tui/thread.ts +174 -0
  134. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  135. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  136. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
  137. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  138. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  139. package/src/cli/cmd/tui/ui/dialog-select.tsx +345 -0
  140. package/src/cli/cmd/tui/ui/dialog.tsx +172 -0
  141. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  142. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  143. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  144. package/src/cli/cmd/tui/util/clipboard.ts +128 -0
  145. package/src/cli/cmd/tui/util/editor.ts +32 -0
  146. package/src/cli/cmd/tui/util/signal.ts +7 -0
  147. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  148. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  149. package/src/cli/cmd/tui/worker.ts +95 -0
  150. package/src/cli/cmd/uninstall.ts +344 -0
  151. package/src/cli/cmd/upgrade.ts +67 -0
  152. package/src/cli/cmd/web.ts +73 -0
  153. package/src/cli/error.ts +57 -0
  154. package/src/cli/network.ts +53 -0
  155. package/src/cli/ui.ts +87 -0
  156. package/src/cli/upgrade.ts +25 -0
  157. package/src/command/index.ts +131 -0
  158. package/src/command/template/initialize.txt +10 -0
  159. package/src/command/template/review.txt +97 -0
  160. package/src/config/config.ts +1226 -0
  161. package/src/config/markdown.ts +41 -0
  162. package/src/env/index.ts +26 -0
  163. package/src/file/ignore.ts +83 -0
  164. package/src/file/index.ts +411 -0
  165. package/src/file/ripgrep.ts +409 -0
  166. package/src/file/time.ts +64 -0
  167. package/src/file/watcher.ts +118 -0
  168. package/src/flag/flag.ts +51 -0
  169. package/src/format/formatter.ts +359 -0
  170. package/src/format/index.ts +137 -0
  171. package/src/global/index.ts +55 -0
  172. package/src/id/id.ts +83 -0
  173. package/src/ide/index.ts +76 -0
  174. package/src/index.ts +161 -0
  175. package/src/installation/index.ts +205 -0
  176. package/src/lsp/client.ts +252 -0
  177. package/src/lsp/index.ts +485 -0
  178. package/src/lsp/language.ts +119 -0
  179. package/src/lsp/server.ts +2032 -0
  180. package/src/mcp/auth.ts +135 -0
  181. package/src/mcp/index.ts +874 -0
  182. package/src/mcp/oauth-callback.ts +200 -0
  183. package/src/mcp/oauth-provider.ts +154 -0
  184. package/src/patch/index.ts +622 -0
  185. package/src/permission/arity.ts +163 -0
  186. package/src/permission/index.ts +210 -0
  187. package/src/permission/next.ts +269 -0
  188. package/src/plugin/codex.ts +524 -0
  189. package/src/plugin/index.ts +118 -0
  190. package/src/project/bootstrap.ts +31 -0
  191. package/src/project/instance.ts +78 -0
  192. package/src/project/project.ts +316 -0
  193. package/src/project/state.ts +65 -0
  194. package/src/project/vcs.ts +76 -0
  195. package/src/provider/auth.ts +147 -0
  196. package/src/provider/models-macro.ts +11 -0
  197. package/src/provider/models.ts +86 -0
  198. package/src/provider/provider.ts +429 -0
  199. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  200. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  201. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  202. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  203. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  204. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  205. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  206. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  207. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  208. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  209. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  210. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  211. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  212. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  213. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  214. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  215. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  216. package/src/provider/transform.ts +654 -0
  217. package/src/pty/index.ts +229 -0
  218. package/src/question/index.ts +170 -0
  219. package/src/server/error.ts +36 -0
  220. package/src/server/mdns.ts +57 -0
  221. package/src/server/project.ts +79 -0
  222. package/src/server/question.ts +95 -0
  223. package/src/server/server.ts +2878 -0
  224. package/src/server/tui.ts +71 -0
  225. package/src/session/compaction.ts +225 -0
  226. package/src/session/index.ts +476 -0
  227. package/src/session/llm.ts +235 -0
  228. package/src/session/message-v2.ts +683 -0
  229. package/src/session/message.ts +189 -0
  230. package/src/session/processor.ts +406 -0
  231. package/src/session/prompt/anthropic-20250930.txt +166 -0
  232. package/src/session/prompt/anthropic.txt +105 -0
  233. package/src/session/prompt/anthropic_spoof.txt +1 -0
  234. package/src/session/prompt/beast.txt +147 -0
  235. package/src/session/prompt/build-switch.txt +5 -0
  236. package/src/session/prompt/codex.txt +318 -0
  237. package/src/session/prompt/codex_header.txt +318 -0
  238. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  239. package/src/session/prompt/gemini.txt +155 -0
  240. package/src/session/prompt/max-steps.txt +16 -0
  241. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  242. package/src/session/prompt/plan.txt +26 -0
  243. package/src/session/prompt/qwen.txt +109 -0
  244. package/src/session/prompt.ts +1652 -0
  245. package/src/session/retry.ts +90 -0
  246. package/src/session/revert.ts +108 -0
  247. package/src/session/status.ts +76 -0
  248. package/src/session/summary.ts +194 -0
  249. package/src/session/system.ts +138 -0
  250. package/src/session/todo.ts +37 -0
  251. package/src/share/share-next.ts +194 -0
  252. package/src/share/share.ts +87 -0
  253. package/src/shell/shell.ts +67 -0
  254. package/src/skill/index.ts +1 -0
  255. package/src/skill/skill.ts +127 -0
  256. package/src/snapshot/index.ts +199 -0
  257. package/src/storage/storage.ts +226 -0
  258. package/src/tool/bash.ts +258 -0
  259. package/src/tool/bash.txt +115 -0
  260. package/src/tool/batch.ts +175 -0
  261. package/src/tool/batch.txt +24 -0
  262. package/src/tool/codesearch.ts +132 -0
  263. package/src/tool/codesearch.txt +12 -0
  264. package/src/tool/edit.ts +645 -0
  265. package/src/tool/edit.txt +10 -0
  266. package/src/tool/external-directory.ts +33 -0
  267. package/src/tool/glob.ts +77 -0
  268. package/src/tool/glob.txt +6 -0
  269. package/src/tool/grep.ts +136 -0
  270. package/src/tool/grep.txt +8 -0
  271. package/src/tool/invalid.ts +17 -0
  272. package/src/tool/ls.ts +121 -0
  273. package/src/tool/ls.txt +1 -0
  274. package/src/tool/lsp.ts +96 -0
  275. package/src/tool/lsp.txt +19 -0
  276. package/src/tool/multiedit.ts +46 -0
  277. package/src/tool/multiedit.txt +41 -0
  278. package/src/tool/patch.ts +201 -0
  279. package/src/tool/patch.txt +1 -0
  280. package/src/tool/question.ts +33 -0
  281. package/src/tool/question.txt +10 -0
  282. package/src/tool/read.ts +200 -0
  283. package/src/tool/read.txt +12 -0
  284. package/src/tool/registry.ts +141 -0
  285. package/src/tool/skill.ts +75 -0
  286. package/src/tool/task.ts +181 -0
  287. package/src/tool/task.txt +60 -0
  288. package/src/tool/todo.ts +53 -0
  289. package/src/tool/todoread.txt +14 -0
  290. package/src/tool/todowrite.txt +167 -0
  291. package/src/tool/tool.ts +88 -0
  292. package/src/tool/truncation.ts +98 -0
  293. package/src/tool/webfetch.ts +182 -0
  294. package/src/tool/webfetch.txt +13 -0
  295. package/src/tool/websearch.ts +144 -0
  296. package/src/tool/websearch.txt +11 -0
  297. package/src/tool/write.ts +80 -0
  298. package/src/tool/write.txt +8 -0
  299. package/src/util/archive.ts +16 -0
  300. package/src/util/color.ts +19 -0
  301. package/src/util/context.ts +25 -0
  302. package/src/util/defer.ts +12 -0
  303. package/src/util/eventloop.ts +20 -0
  304. package/src/util/filesystem.ts +83 -0
  305. package/src/util/fn.ts +11 -0
  306. package/src/util/iife.ts +3 -0
  307. package/src/util/keybind.ts +102 -0
  308. package/src/util/lazy.ts +18 -0
  309. package/src/util/locale.ts +81 -0
  310. package/src/util/lock.ts +98 -0
  311. package/src/util/log.ts +180 -0
  312. package/src/util/queue.ts +32 -0
  313. package/src/util/rpc.ts +66 -0
  314. package/src/util/scrap.ts +10 -0
  315. package/src/util/signal.ts +12 -0
  316. package/src/util/timeout.ts +14 -0
  317. package/src/util/token.ts +7 -0
  318. package/src/util/wildcard.ts +54 -0
  319. package/src/worktree/index.ts +217 -0
  320. package/sst-env.d.ts +9 -0
  321. package/test/agent/agent.test.ts +511 -0
  322. package/test/bun.test.ts +53 -0
  323. package/test/cli/github-action.test.ts +129 -0
  324. package/test/cli/github-remote.test.ts +80 -0
  325. package/test/cli/tui/transcript.test.ts +297 -0
  326. package/test/config/agent-color.test.ts +66 -0
  327. package/test/config/config.test.ts +1235 -0
  328. package/test/config/markdown.test.ts +89 -0
  329. package/test/file/ignore.test.ts +10 -0
  330. package/test/file/path-traversal.test.ts +115 -0
  331. package/test/fixture/fixture.ts +45 -0
  332. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  333. package/test/ide/ide.test.ts +82 -0
  334. package/test/keybind.test.ts +421 -0
  335. package/test/lsp/client.test.ts +95 -0
  336. package/test/mcp/headers.test.ts +153 -0
  337. package/test/patch/patch.test.ts +348 -0
  338. package/test/permission/arity.test.ts +33 -0
  339. package/test/permission/next.test.ts +652 -0
  340. package/test/permission-task.test.ts +319 -0
  341. package/test/plugin/codex.test.ts +123 -0
  342. package/test/preload.ts +65 -0
  343. package/test/project/project.test.ts +120 -0
  344. package/test/provider/amazon-bedrock.test.ts +205 -0
  345. package/test/provider/provider.test.ts +2127 -0
  346. package/test/provider/transform.test.ts +1155 -0
  347. package/test/question/question.test.ts +300 -0
  348. package/test/server/session-select.test.ts +78 -0
  349. package/test/session/compaction.test.ts +251 -0
  350. package/test/session/message-v2.test.ts +570 -0
  351. package/test/session/retry.test.ts +131 -0
  352. package/test/session/revert-compact.test.ts +285 -0
  353. package/test/session/session.test.ts +71 -0
  354. package/test/skill/skill.test.ts +185 -0
  355. package/test/snapshot/snapshot.test.ts +939 -0
  356. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  357. package/test/tool/bash.test.ts +320 -0
  358. package/test/tool/external-directory.test.ts +126 -0
  359. package/test/tool/fixtures/large-image.png +0 -0
  360. package/test/tool/fixtures/models-api.json +33453 -0
  361. package/test/tool/grep.test.ts +109 -0
  362. package/test/tool/patch.test.ts +261 -0
  363. package/test/tool/read.test.ts +303 -0
  364. package/test/tool/truncation.test.ts +159 -0
  365. package/test/util/iife.test.ts +36 -0
  366. package/test/util/lazy.test.ts +50 -0
  367. package/test/util/timeout.test.ts +21 -0
  368. package/test/util/wildcard.test.ts +55 -0
  369. package/tsconfig.json +16 -0
@@ -0,0 +1,511 @@
1
+ import { test, expect } from "bun:test"
2
+ import { tmpdir } from "../fixture/fixture"
3
+ import { Instance } from "../../src/project/instance"
4
+ import { Agent } from "../../src/agent/agent"
5
+ import { PermissionNext } from "../../src/permission/next"
6
+
7
+ // Helper to evaluate permission for a tool with wildcard pattern
8
+ function evalPerm(agent: Agent.Info | undefined, permission: string): PermissionNext.Action | undefined {
9
+ if (!agent) return undefined
10
+ return PermissionNext.evaluate(permission, "*", agent.permission).action
11
+ }
12
+
13
+ test("returns default native agents when no config", async () => {
14
+ await using tmp = await tmpdir()
15
+ await Instance.provide({
16
+ directory: tmp.path,
17
+ fn: async () => {
18
+ const agents = await Agent.list()
19
+ const names = agents.map((a) => a.name)
20
+ expect(names).toContain("build")
21
+ expect(names).toContain("plan")
22
+ expect(names).toContain("general")
23
+ expect(names).toContain("explore")
24
+ expect(names).toContain("compaction")
25
+ expect(names).toContain("title")
26
+ expect(names).toContain("summary")
27
+ },
28
+ })
29
+ })
30
+
31
+ test("build agent has correct default properties", async () => {
32
+ await using tmp = await tmpdir()
33
+ await Instance.provide({
34
+ directory: tmp.path,
35
+ fn: async () => {
36
+ const build = await Agent.get("build")
37
+ expect(build).toBeDefined()
38
+ expect(build?.mode).toBe("primary")
39
+ expect(build?.native).toBe(true)
40
+ expect(evalPerm(build, "edit")).toBe("allow")
41
+ expect(evalPerm(build, "bash")).toBe("allow")
42
+ },
43
+ })
44
+ })
45
+
46
+ test("plan agent denies edits except .opencode/plan/*", async () => {
47
+ await using tmp = await tmpdir()
48
+ await Instance.provide({
49
+ directory: tmp.path,
50
+ fn: async () => {
51
+ const plan = await Agent.get("plan")
52
+ expect(plan).toBeDefined()
53
+ // Wildcard is denied
54
+ expect(evalPerm(plan, "edit")).toBe("deny")
55
+ // But specific path is allowed
56
+ expect(PermissionNext.evaluate("edit", ".opencode/plan/foo.md", plan!.permission).action).toBe("allow")
57
+ },
58
+ })
59
+ })
60
+
61
+ test("explore agent denies edit and write", async () => {
62
+ await using tmp = await tmpdir()
63
+ await Instance.provide({
64
+ directory: tmp.path,
65
+ fn: async () => {
66
+ const explore = await Agent.get("explore")
67
+ expect(explore).toBeDefined()
68
+ expect(explore?.mode).toBe("subagent")
69
+ expect(evalPerm(explore, "edit")).toBe("deny")
70
+ expect(evalPerm(explore, "write")).toBe("deny")
71
+ expect(evalPerm(explore, "todoread")).toBe("deny")
72
+ expect(evalPerm(explore, "todowrite")).toBe("deny")
73
+ },
74
+ })
75
+ })
76
+
77
+ test("general agent denies todo tools", async () => {
78
+ await using tmp = await tmpdir()
79
+ await Instance.provide({
80
+ directory: tmp.path,
81
+ fn: async () => {
82
+ const general = await Agent.get("general")
83
+ expect(general).toBeDefined()
84
+ expect(general?.mode).toBe("subagent")
85
+ expect(general?.hidden).toBeUndefined()
86
+ expect(evalPerm(general, "todoread")).toBe("deny")
87
+ expect(evalPerm(general, "todowrite")).toBe("deny")
88
+ },
89
+ })
90
+ })
91
+
92
+ test("compaction agent denies all permissions", async () => {
93
+ await using tmp = await tmpdir()
94
+ await Instance.provide({
95
+ directory: tmp.path,
96
+ fn: async () => {
97
+ const compaction = await Agent.get("compaction")
98
+ expect(compaction).toBeDefined()
99
+ expect(compaction?.hidden).toBe(true)
100
+ expect(evalPerm(compaction, "bash")).toBe("deny")
101
+ expect(evalPerm(compaction, "edit")).toBe("deny")
102
+ expect(evalPerm(compaction, "read")).toBe("deny")
103
+ },
104
+ })
105
+ })
106
+
107
+ test("custom agent from config creates new agent", async () => {
108
+ await using tmp = await tmpdir({
109
+ config: {
110
+ agent: {
111
+ my_custom_agent: {
112
+ model: "openai/gpt-4",
113
+ description: "My custom agent",
114
+ temperature: 0.5,
115
+ top_p: 0.9,
116
+ },
117
+ },
118
+ },
119
+ })
120
+ await Instance.provide({
121
+ directory: tmp.path,
122
+ fn: async () => {
123
+ const custom = await Agent.get("my_custom_agent")
124
+ expect(custom).toBeDefined()
125
+ expect(custom?.model?.providerID).toBe("openai")
126
+ expect(custom?.model?.modelID).toBe("gpt-4")
127
+ expect(custom?.description).toBe("My custom agent")
128
+ expect(custom?.temperature).toBe(0.5)
129
+ expect(custom?.topP).toBe(0.9)
130
+ expect(custom?.native).toBe(false)
131
+ expect(custom?.mode).toBe("all")
132
+ },
133
+ })
134
+ })
135
+
136
+ test("custom agent config overrides native agent properties", async () => {
137
+ await using tmp = await tmpdir({
138
+ config: {
139
+ agent: {
140
+ build: {
141
+ model: "anthropic/claude-3",
142
+ description: "Custom build agent",
143
+ temperature: 0.7,
144
+ color: "#FF0000",
145
+ },
146
+ },
147
+ },
148
+ })
149
+ await Instance.provide({
150
+ directory: tmp.path,
151
+ fn: async () => {
152
+ const build = await Agent.get("build")
153
+ expect(build).toBeDefined()
154
+ expect(build?.model?.providerID).toBe("anthropic")
155
+ expect(build?.model?.modelID).toBe("claude-3")
156
+ expect(build?.description).toBe("Custom build agent")
157
+ expect(build?.temperature).toBe(0.7)
158
+ expect(build?.color).toBe("#FF0000")
159
+ expect(build?.native).toBe(true)
160
+ },
161
+ })
162
+ })
163
+
164
+ test("agent disable removes agent from list", async () => {
165
+ await using tmp = await tmpdir({
166
+ config: {
167
+ agent: {
168
+ explore: { disable: true },
169
+ },
170
+ },
171
+ })
172
+ await Instance.provide({
173
+ directory: tmp.path,
174
+ fn: async () => {
175
+ const explore = await Agent.get("explore")
176
+ expect(explore).toBeUndefined()
177
+ const agents = await Agent.list()
178
+ const names = agents.map((a) => a.name)
179
+ expect(names).not.toContain("explore")
180
+ },
181
+ })
182
+ })
183
+
184
+ test("agent permission config merges with defaults", async () => {
185
+ await using tmp = await tmpdir({
186
+ config: {
187
+ agent: {
188
+ build: {
189
+ permission: {
190
+ bash: {
191
+ "rm -rf *": "deny",
192
+ },
193
+ },
194
+ },
195
+ },
196
+ },
197
+ })
198
+ await Instance.provide({
199
+ directory: tmp.path,
200
+ fn: async () => {
201
+ const build = await Agent.get("build")
202
+ expect(build).toBeDefined()
203
+ // Specific pattern is denied
204
+ expect(PermissionNext.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
205
+ // Edit still allowed
206
+ expect(evalPerm(build, "edit")).toBe("allow")
207
+ },
208
+ })
209
+ })
210
+
211
+ test("global permission config applies to all agents", async () => {
212
+ await using tmp = await tmpdir({
213
+ config: {
214
+ permission: {
215
+ bash: "deny",
216
+ },
217
+ },
218
+ })
219
+ await Instance.provide({
220
+ directory: tmp.path,
221
+ fn: async () => {
222
+ const build = await Agent.get("build")
223
+ expect(build).toBeDefined()
224
+ expect(evalPerm(build, "bash")).toBe("deny")
225
+ },
226
+ })
227
+ })
228
+
229
+ test("agent steps/maxSteps config sets steps property", async () => {
230
+ await using tmp = await tmpdir({
231
+ config: {
232
+ agent: {
233
+ build: { steps: 50 },
234
+ plan: { maxSteps: 100 },
235
+ },
236
+ },
237
+ })
238
+ await Instance.provide({
239
+ directory: tmp.path,
240
+ fn: async () => {
241
+ const build = await Agent.get("build")
242
+ const plan = await Agent.get("plan")
243
+ expect(build?.steps).toBe(50)
244
+ expect(plan?.steps).toBe(100)
245
+ },
246
+ })
247
+ })
248
+
249
+ test("agent mode can be overridden", async () => {
250
+ await using tmp = await tmpdir({
251
+ config: {
252
+ agent: {
253
+ explore: { mode: "primary" },
254
+ },
255
+ },
256
+ })
257
+ await Instance.provide({
258
+ directory: tmp.path,
259
+ fn: async () => {
260
+ const explore = await Agent.get("explore")
261
+ expect(explore?.mode).toBe("primary")
262
+ },
263
+ })
264
+ })
265
+
266
+ test("agent name can be overridden", async () => {
267
+ await using tmp = await tmpdir({
268
+ config: {
269
+ agent: {
270
+ build: { name: "Builder" },
271
+ },
272
+ },
273
+ })
274
+ await Instance.provide({
275
+ directory: tmp.path,
276
+ fn: async () => {
277
+ const build = await Agent.get("build")
278
+ expect(build?.name).toBe("Builder")
279
+ },
280
+ })
281
+ })
282
+
283
+ test("agent prompt can be set from config", async () => {
284
+ await using tmp = await tmpdir({
285
+ config: {
286
+ agent: {
287
+ build: { prompt: "Custom system prompt" },
288
+ },
289
+ },
290
+ })
291
+ await Instance.provide({
292
+ directory: tmp.path,
293
+ fn: async () => {
294
+ const build = await Agent.get("build")
295
+ expect(build?.prompt).toBe("Custom system prompt")
296
+ },
297
+ })
298
+ })
299
+
300
+ test("unknown agent properties are placed into options", async () => {
301
+ await using tmp = await tmpdir({
302
+ config: {
303
+ agent: {
304
+ build: {
305
+ random_property: "hello",
306
+ another_random: 123,
307
+ },
308
+ },
309
+ },
310
+ })
311
+ await Instance.provide({
312
+ directory: tmp.path,
313
+ fn: async () => {
314
+ const build = await Agent.get("build")
315
+ expect(build?.options.random_property).toBe("hello")
316
+ expect(build?.options.another_random).toBe(123)
317
+ },
318
+ })
319
+ })
320
+
321
+ test("agent options merge correctly", async () => {
322
+ await using tmp = await tmpdir({
323
+ config: {
324
+ agent: {
325
+ build: {
326
+ options: {
327
+ custom_option: true,
328
+ another_option: "value",
329
+ },
330
+ },
331
+ },
332
+ },
333
+ })
334
+ await Instance.provide({
335
+ directory: tmp.path,
336
+ fn: async () => {
337
+ const build = await Agent.get("build")
338
+ expect(build?.options.custom_option).toBe(true)
339
+ expect(build?.options.another_option).toBe("value")
340
+ },
341
+ })
342
+ })
343
+
344
+ test("multiple custom agents can be defined", async () => {
345
+ await using tmp = await tmpdir({
346
+ config: {
347
+ agent: {
348
+ agent_a: {
349
+ description: "Agent A",
350
+ mode: "subagent",
351
+ },
352
+ agent_b: {
353
+ description: "Agent B",
354
+ mode: "primary",
355
+ },
356
+ },
357
+ },
358
+ })
359
+ await Instance.provide({
360
+ directory: tmp.path,
361
+ fn: async () => {
362
+ const agentA = await Agent.get("agent_a")
363
+ const agentB = await Agent.get("agent_b")
364
+ expect(agentA?.description).toBe("Agent A")
365
+ expect(agentA?.mode).toBe("subagent")
366
+ expect(agentB?.description).toBe("Agent B")
367
+ expect(agentB?.mode).toBe("primary")
368
+ },
369
+ })
370
+ })
371
+
372
+ test("Agent.get returns undefined for non-existent agent", async () => {
373
+ await using tmp = await tmpdir()
374
+ await Instance.provide({
375
+ directory: tmp.path,
376
+ fn: async () => {
377
+ const nonExistent = await Agent.get("does_not_exist")
378
+ expect(nonExistent).toBeUndefined()
379
+ },
380
+ })
381
+ })
382
+
383
+ test("default permission includes doom_loop and external_directory as ask", async () => {
384
+ await using tmp = await tmpdir()
385
+ await Instance.provide({
386
+ directory: tmp.path,
387
+ fn: async () => {
388
+ const build = await Agent.get("build")
389
+ expect(evalPerm(build, "doom_loop")).toBe("ask")
390
+ expect(evalPerm(build, "external_directory")).toBe("ask")
391
+ },
392
+ })
393
+ })
394
+
395
+ test("webfetch is allowed by default", async () => {
396
+ await using tmp = await tmpdir()
397
+ await Instance.provide({
398
+ directory: tmp.path,
399
+ fn: async () => {
400
+ const build = await Agent.get("build")
401
+ expect(evalPerm(build, "webfetch")).toBe("allow")
402
+ },
403
+ })
404
+ })
405
+
406
+ test("legacy tools config converts to permissions", async () => {
407
+ await using tmp = await tmpdir({
408
+ config: {
409
+ agent: {
410
+ build: {
411
+ tools: {
412
+ bash: false,
413
+ read: false,
414
+ },
415
+ },
416
+ },
417
+ },
418
+ })
419
+ await Instance.provide({
420
+ directory: tmp.path,
421
+ fn: async () => {
422
+ const build = await Agent.get("build")
423
+ expect(evalPerm(build, "bash")).toBe("deny")
424
+ expect(evalPerm(build, "read")).toBe("deny")
425
+ },
426
+ })
427
+ })
428
+
429
+ test("legacy tools config maps write/edit/patch/multiedit to edit permission", async () => {
430
+ await using tmp = await tmpdir({
431
+ config: {
432
+ agent: {
433
+ build: {
434
+ tools: {
435
+ write: false,
436
+ },
437
+ },
438
+ },
439
+ },
440
+ })
441
+ await Instance.provide({
442
+ directory: tmp.path,
443
+ fn: async () => {
444
+ const build = await Agent.get("build")
445
+ expect(evalPerm(build, "edit")).toBe("deny")
446
+ },
447
+ })
448
+ })
449
+
450
+ test("Truncate.DIR is allowed even when user denies external_directory globally", async () => {
451
+ const { Truncate } = await import("../../src/tool/truncation")
452
+ await using tmp = await tmpdir({
453
+ config: {
454
+ permission: {
455
+ external_directory: "deny",
456
+ },
457
+ },
458
+ })
459
+ await Instance.provide({
460
+ directory: tmp.path,
461
+ fn: async () => {
462
+ const build = await Agent.get("build")
463
+ expect(PermissionNext.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("allow")
464
+ expect(PermissionNext.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny")
465
+ },
466
+ })
467
+ })
468
+
469
+ test("Truncate.DIR is allowed even when user denies external_directory per-agent", async () => {
470
+ const { Truncate } = await import("../../src/tool/truncation")
471
+ await using tmp = await tmpdir({
472
+ config: {
473
+ agent: {
474
+ build: {
475
+ permission: {
476
+ external_directory: "deny",
477
+ },
478
+ },
479
+ },
480
+ },
481
+ })
482
+ await Instance.provide({
483
+ directory: tmp.path,
484
+ fn: async () => {
485
+ const build = await Agent.get("build")
486
+ expect(PermissionNext.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("allow")
487
+ expect(PermissionNext.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny")
488
+ },
489
+ })
490
+ })
491
+
492
+ test("explicit Truncate.DIR deny is respected", async () => {
493
+ const { Truncate } = await import("../../src/tool/truncation")
494
+ await using tmp = await tmpdir({
495
+ config: {
496
+ permission: {
497
+ external_directory: {
498
+ "*": "deny",
499
+ [Truncate.DIR]: "deny",
500
+ },
501
+ },
502
+ },
503
+ })
504
+ await Instance.provide({
505
+ directory: tmp.path,
506
+ fn: async () => {
507
+ const build = await Agent.get("build")
508
+ expect(PermissionNext.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny")
509
+ },
510
+ })
511
+ })
@@ -0,0 +1,53 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import fs from "fs/promises"
3
+ import path from "path"
4
+
5
+ describe("BunProc registry configuration", () => {
6
+ test("should not contain hardcoded registry parameters", async () => {
7
+ // Read the bun/index.ts file
8
+ const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
9
+ const content = await fs.readFile(bunIndexPath, "utf-8")
10
+
11
+ // Verify that no hardcoded registry is present
12
+ expect(content).not.toContain("--registry=")
13
+ expect(content).not.toContain("hasNpmRcConfig")
14
+ expect(content).not.toContain("NpmRc")
15
+ })
16
+
17
+ test("should use Bun's default registry resolution", async () => {
18
+ // Read the bun/index.ts file
19
+ const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
20
+ const content = await fs.readFile(bunIndexPath, "utf-8")
21
+
22
+ // Verify that it uses Bun's default resolution
23
+ expect(content).toContain("Bun's default registry resolution")
24
+ expect(content).toContain("Bun will use them automatically")
25
+ expect(content).toContain("No need to pass --registry flag")
26
+ })
27
+
28
+ test("should have correct command structure without registry", async () => {
29
+ // Read the bun/index.ts file
30
+ const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
31
+ const content = await fs.readFile(bunIndexPath, "utf-8")
32
+
33
+ // Extract the install function
34
+ const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m)
35
+ expect(installFunctionMatch).toBeTruthy()
36
+
37
+ if (installFunctionMatch) {
38
+ const installFunction = installFunctionMatch[0]
39
+
40
+ // Verify expected arguments are present
41
+ expect(installFunction).toContain('"add"')
42
+ expect(installFunction).toContain('"--force"')
43
+ expect(installFunction).toContain('"--exact"')
44
+ expect(installFunction).toContain('"--cwd"')
45
+ expect(installFunction).toContain("Global.Path.cache")
46
+ expect(installFunction).toContain('pkg + "@" + version')
47
+
48
+ // Verify no registry argument is added
49
+ expect(installFunction).not.toContain('"--registry"')
50
+ expect(installFunction).not.toContain('args.push("--registry')
51
+ }
52
+ })
53
+ })
@@ -0,0 +1,129 @@
1
+ import { test, expect, describe } from "bun:test"
2
+ import { extractResponseText } from "../../src/cli/cmd/github"
3
+ import type { MessageV2 } from "../../src/session/message-v2"
4
+
5
+ // Helper to create minimal valid parts
6
+ function createTextPart(text: string): MessageV2.Part {
7
+ return {
8
+ id: "1",
9
+ sessionID: "s",
10
+ messageID: "m",
11
+ type: "text" as const,
12
+ text,
13
+ }
14
+ }
15
+
16
+ function createReasoningPart(text: string): MessageV2.Part {
17
+ return {
18
+ id: "1",
19
+ sessionID: "s",
20
+ messageID: "m",
21
+ type: "reasoning" as const,
22
+ text,
23
+ time: { start: 0 },
24
+ }
25
+ }
26
+
27
+ function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): MessageV2.Part {
28
+ if (status === "completed") {
29
+ return {
30
+ id: "1",
31
+ sessionID: "s",
32
+ messageID: "m",
33
+ type: "tool" as const,
34
+ callID: "c1",
35
+ tool,
36
+ state: {
37
+ status: "completed",
38
+ input: {},
39
+ output: "",
40
+ title,
41
+ metadata: {},
42
+ time: { start: 0, end: 1 },
43
+ },
44
+ }
45
+ }
46
+ return {
47
+ id: "1",
48
+ sessionID: "s",
49
+ messageID: "m",
50
+ type: "tool" as const,
51
+ callID: "c1",
52
+ tool,
53
+ state: {
54
+ status: "running",
55
+ input: {},
56
+ time: { start: 0 },
57
+ },
58
+ }
59
+ }
60
+
61
+ function createStepStartPart(): MessageV2.Part {
62
+ return {
63
+ id: "1",
64
+ sessionID: "s",
65
+ messageID: "m",
66
+ type: "step-start" as const,
67
+ }
68
+ }
69
+
70
+ describe("extractResponseText", () => {
71
+ test("returns text from text part", () => {
72
+ const parts = [createTextPart("Hello world")]
73
+ expect(extractResponseText(parts)).toBe("Hello world")
74
+ })
75
+
76
+ test("returns last text part when multiple exist", () => {
77
+ const parts = [createTextPart("First"), createTextPart("Last")]
78
+ expect(extractResponseText(parts)).toBe("Last")
79
+ })
80
+
81
+ test("returns text even when tool parts follow", () => {
82
+ const parts = [createTextPart("I'll help with that."), createToolPart("todowrite", "3 todos")]
83
+ expect(extractResponseText(parts)).toBe("I'll help with that.")
84
+ })
85
+
86
+ test("returns null for reasoning-only response (signals summary needed)", () => {
87
+ const parts = [createReasoningPart("Let me think about this...")]
88
+ expect(extractResponseText(parts)).toBeNull()
89
+ })
90
+
91
+ test("returns null for tool-only response (signals summary needed)", () => {
92
+ // This is the exact scenario from the bug report - todowrite with no text
93
+ const parts = [createToolPart("todowrite", "8 todos")]
94
+ expect(extractResponseText(parts)).toBeNull()
95
+ })
96
+
97
+ test("returns null for multiple completed tools", () => {
98
+ const parts = [
99
+ createToolPart("read", "src/file.ts"),
100
+ createToolPart("edit", "src/file.ts"),
101
+ createToolPart("bash", "bun test"),
102
+ ]
103
+ expect(extractResponseText(parts)).toBeNull()
104
+ })
105
+
106
+ test("ignores running tool parts (throws since no completed tools)", () => {
107
+ const parts = [createToolPart("bash", "", "running")]
108
+ expect(() => extractResponseText(parts)).toThrow("Failed to parse response")
109
+ })
110
+
111
+ test("throws with part types on empty array", () => {
112
+ expect(() => extractResponseText([])).toThrow("Part types found: [none]")
113
+ })
114
+
115
+ test("throws with part types on unhandled parts", () => {
116
+ const parts = [createStepStartPart()]
117
+ expect(() => extractResponseText(parts)).toThrow("Part types found: [step-start]")
118
+ })
119
+
120
+ test("prefers text over reasoning when both present", () => {
121
+ const parts = [createReasoningPart("Internal thinking..."), createTextPart("Final answer")]
122
+ expect(extractResponseText(parts)).toBe("Final answer")
123
+ })
124
+
125
+ test("prefers text over tools when both present", () => {
126
+ const parts = [createToolPart("read", "src/file.ts"), createTextPart("Here's what I found")]
127
+ expect(extractResponseText(parts)).toBe("Here's what I found")
128
+ })
129
+ })