opencode-v2 1.1.53

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 (439) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +84 -0
  5. package/bunfig.toml +5 -0
  6. package/package.json +126 -0
  7. package/parsers-config.ts +253 -0
  8. package/script/build.ts +193 -0
  9. package/script/postinstall.mjs +125 -0
  10. package/script/publish.ts +181 -0
  11. package/script/schema.ts +47 -0
  12. package/script/seed-e2e.ts +50 -0
  13. package/src/acp/README.md +164 -0
  14. package/src/acp/agent.ts +1676 -0
  15. package/src/acp/session.ts +117 -0
  16. package/src/acp/types.ts +23 -0
  17. package/src/agent/agent.ts +414 -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 +44 -0
  23. package/src/auth/index.ts +70 -0
  24. package/src/bun/index.ts +137 -0
  25. package/src/bun/registry.ts +48 -0
  26. package/src/bus/bus-event.ts +43 -0
  27. package/src/bus/global.ts +10 -0
  28. package/src/bus/index.ts +105 -0
  29. package/src/cli/bootstrap.ts +17 -0
  30. package/src/cli/cmd/acp.ts +70 -0
  31. package/src/cli/cmd/agent.ts +257 -0
  32. package/src/cli/cmd/auth.ts +400 -0
  33. package/src/cli/cmd/cmd.ts +7 -0
  34. package/src/cli/cmd/debug/agent.ts +167 -0
  35. package/src/cli/cmd/debug/config.ts +16 -0
  36. package/src/cli/cmd/debug/file.ts +97 -0
  37. package/src/cli/cmd/debug/index.ts +48 -0
  38. package/src/cli/cmd/debug/lsp.ts +52 -0
  39. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  40. package/src/cli/cmd/debug/scrap.ts +16 -0
  41. package/src/cli/cmd/debug/skill.ts +16 -0
  42. package/src/cli/cmd/debug/snapshot.ts +52 -0
  43. package/src/cli/cmd/export.ts +88 -0
  44. package/src/cli/cmd/generate.ts +38 -0
  45. package/src/cli/cmd/github.ts +1540 -0
  46. package/src/cli/cmd/import.ts +147 -0
  47. package/src/cli/cmd/mcp.ts +755 -0
  48. package/src/cli/cmd/models.ts +77 -0
  49. package/src/cli/cmd/pr.ts +112 -0
  50. package/src/cli/cmd/run.ts +617 -0
  51. package/src/cli/cmd/serve.ts +20 -0
  52. package/src/cli/cmd/session.ts +135 -0
  53. package/src/cli/cmd/stats.ts +426 -0
  54. package/src/cli/cmd/tui/app.tsx +801 -0
  55. package/src/cli/cmd/tui/attach.ts +52 -0
  56. package/src/cli/cmd/tui/component/border.tsx +21 -0
  57. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  58. package/src/cli/cmd/tui/component/dialog-command.tsx +148 -0
  59. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  60. package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
  61. package/src/cli/cmd/tui/component/dialog-provider.tsx +266 -0
  62. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  63. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  64. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  65. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  66. package/src/cli/cmd/tui/component/dialog-status.tsx +177 -0
  67. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  68. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  69. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  70. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +666 -0
  71. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  72. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  73. package/src/cli/cmd/tui/component/prompt/index.tsx +1132 -0
  74. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  75. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  76. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  77. package/src/cli/cmd/tui/component/tips.tsx +153 -0
  78. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  79. package/src/cli/cmd/tui/context/args.tsx +15 -0
  80. package/src/cli/cmd/tui/context/directory.ts +13 -0
  81. package/src/cli/cmd/tui/context/exit.tsx +52 -0
  82. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  83. package/src/cli/cmd/tui/context/keybind.tsx +100 -0
  84. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  85. package/src/cli/cmd/tui/context/local.tsx +409 -0
  86. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  87. package/src/cli/cmd/tui/context/route.tsx +46 -0
  88. package/src/cli/cmd/tui/context/sdk.tsx +101 -0
  89. package/src/cli/cmd/tui/context/sync.tsx +470 -0
  90. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  91. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  92. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  95. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  96. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  97. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  98. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  99. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  100. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  101. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  102. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  103. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  104. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  105. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  106. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  107. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  108. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  109. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  110. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  111. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  112. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  113. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  114. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  115. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  116. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  117. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  118. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  119. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  120. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  121. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  122. package/src/cli/cmd/tui/context/theme.tsx +1152 -0
  123. package/src/cli/cmd/tui/event.ts +48 -0
  124. package/src/cli/cmd/tui/routes/home.tsx +140 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  126. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  128. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  129. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  130. package/src/cli/cmd/tui/routes/session/header.tsx +142 -0
  131. package/src/cli/cmd/tui/routes/session/index.tsx +2126 -0
  132. package/src/cli/cmd/tui/routes/session/permission.tsx +508 -0
  133. package/src/cli/cmd/tui/routes/session/question.tsx +466 -0
  134. package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
  135. package/src/cli/cmd/tui/thread.ts +175 -0
  136. package/src/cli/cmd/tui/ui/dialog-alert.tsx +68 -0
  137. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +93 -0
  138. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +215 -0
  139. package/src/cli/cmd/tui/ui/dialog-help.tsx +49 -0
  140. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +88 -0
  141. package/src/cli/cmd/tui/ui/dialog-select.tsx +399 -0
  142. package/src/cli/cmd/tui/ui/dialog.tsx +167 -0
  143. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  144. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  145. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  146. package/src/cli/cmd/tui/util/clipboard.ts +159 -0
  147. package/src/cli/cmd/tui/util/editor.ts +32 -0
  148. package/src/cli/cmd/tui/util/signal.ts +7 -0
  149. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  150. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  151. package/src/cli/cmd/tui/worker.ts +152 -0
  152. package/src/cli/cmd/uninstall.ts +357 -0
  153. package/src/cli/cmd/upgrade.ts +73 -0
  154. package/src/cli/cmd/web.ts +81 -0
  155. package/src/cli/error.ts +57 -0
  156. package/src/cli/logo.ts +6 -0
  157. package/src/cli/network.ts +60 -0
  158. package/src/cli/ui.ts +113 -0
  159. package/src/cli/upgrade.ts +25 -0
  160. package/src/command/index.ts +150 -0
  161. package/src/command/template/initialize.txt +10 -0
  162. package/src/command/template/review.txt +99 -0
  163. package/src/config/config.ts +1477 -0
  164. package/src/config/markdown.ts +98 -0
  165. package/src/env/index.ts +28 -0
  166. package/src/file/ignore.ts +83 -0
  167. package/src/file/index.ts +583 -0
  168. package/src/file/ripgrep.ts +375 -0
  169. package/src/file/time.ts +69 -0
  170. package/src/file/watcher.ts +127 -0
  171. package/src/flag/flag.ts +97 -0
  172. package/src/format/formatter.ts +366 -0
  173. package/src/format/index.ts +137 -0
  174. package/src/global/index.ts +55 -0
  175. package/src/id/id.ts +83 -0
  176. package/src/ide/index.ts +76 -0
  177. package/src/index.ts +159 -0
  178. package/src/installation/index.ts +246 -0
  179. package/src/lsp/client.ts +252 -0
  180. package/src/lsp/index.ts +485 -0
  181. package/src/lsp/language.ts +119 -0
  182. package/src/lsp/server.ts +2046 -0
  183. package/src/mcp/auth.ts +132 -0
  184. package/src/mcp/index.ts +934 -0
  185. package/src/mcp/oauth-callback.ts +200 -0
  186. package/src/mcp/oauth-provider.ts +154 -0
  187. package/src/patch/index.ts +680 -0
  188. package/src/permission/arity.ts +163 -0
  189. package/src/permission/index.ts +210 -0
  190. package/src/permission/next.ts +280 -0
  191. package/src/plugin/codex.ts +624 -0
  192. package/src/plugin/copilot.ts +327 -0
  193. package/src/plugin/index.ts +138 -0
  194. package/src/project/bootstrap.ts +35 -0
  195. package/src/project/instance.ts +114 -0
  196. package/src/project/project.ts +371 -0
  197. package/src/project/state.ts +70 -0
  198. package/src/project/vcs.ts +76 -0
  199. package/src/provider/auth.ts +147 -0
  200. package/src/provider/models.ts +133 -0
  201. package/src/provider/provider.ts +1262 -0
  202. package/src/provider/sdk/copilot/README.md +5 -0
  203. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +164 -0
  204. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  205. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +17 -0
  206. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  207. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +780 -0
  208. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  209. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  210. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +87 -0
  211. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  212. package/src/provider/sdk/copilot/index.ts +2 -0
  213. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  214. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +303 -0
  215. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  216. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  217. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  218. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +207 -0
  219. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1732 -0
  220. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +177 -0
  221. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  222. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +88 -0
  223. package/src/provider/sdk/copilot/responses/tool/file-search.ts +128 -0
  224. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +115 -0
  225. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +65 -0
  226. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +104 -0
  227. package/src/provider/sdk/copilot/responses/tool/web-search.ts +103 -0
  228. package/src/provider/transform.ts +828 -0
  229. package/src/pty/index.ts +250 -0
  230. package/src/question/index.ts +171 -0
  231. package/src/scheduler/index.ts +61 -0
  232. package/src/server/error.ts +36 -0
  233. package/src/server/event.ts +7 -0
  234. package/src/server/mdns.ts +60 -0
  235. package/src/server/routes/config.ts +92 -0
  236. package/src/server/routes/experimental.ts +208 -0
  237. package/src/server/routes/file.ts +197 -0
  238. package/src/server/routes/global.ts +183 -0
  239. package/src/server/routes/mcp.ts +225 -0
  240. package/src/server/routes/permission.ts +68 -0
  241. package/src/server/routes/project.ts +82 -0
  242. package/src/server/routes/provider.ts +165 -0
  243. package/src/server/routes/pty.ts +169 -0
  244. package/src/server/routes/question.ts +98 -0
  245. package/src/server/routes/session.ts +939 -0
  246. package/src/server/routes/tui.ts +379 -0
  247. package/src/server/server.ts +613 -0
  248. package/src/session/compaction.ts +226 -0
  249. package/src/session/index.ts +524 -0
  250. package/src/session/instruction.ts +197 -0
  251. package/src/session/llm.ts +289 -0
  252. package/src/session/message-v2.ts +802 -0
  253. package/src/session/message.ts +189 -0
  254. package/src/session/processor.ts +407 -0
  255. package/src/session/prompt/agent.txt +43 -0
  256. package/src/session/prompt/anthropic-20250930.txt +166 -0
  257. package/src/session/prompt/anthropic.txt +105 -0
  258. package/src/session/prompt/beast.txt +147 -0
  259. package/src/session/prompt/build-switch.txt +5 -0
  260. package/src/session/prompt/codex_header.txt +79 -0
  261. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  262. package/src/session/prompt/gemini.txt +155 -0
  263. package/src/session/prompt/max-steps.txt +16 -0
  264. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  265. package/src/session/prompt/plan.txt +26 -0
  266. package/src/session/prompt/qwen.txt +109 -0
  267. package/src/session/prompt/research.txt +81 -0
  268. package/src/session/prompt/trinity.txt +97 -0
  269. package/src/session/prompt.ts +1952 -0
  270. package/src/session/retry.ts +97 -0
  271. package/src/session/revert.ts +121 -0
  272. package/src/session/status.ts +76 -0
  273. package/src/session/summary.ts +217 -0
  274. package/src/session/system.ts +54 -0
  275. package/src/session/todo.ts +37 -0
  276. package/src/share/share-next.ts +200 -0
  277. package/src/share/share.ts +92 -0
  278. package/src/shell/shell.ts +67 -0
  279. package/src/skill/discovery.ts +97 -0
  280. package/src/skill/index.ts +1 -0
  281. package/src/skill/skill.ts +188 -0
  282. package/src/snapshot/index.ts +255 -0
  283. package/src/storage/storage.ts +227 -0
  284. package/src/tool/agent-enter.txt +1 -0
  285. package/src/tool/agent-exit.txt +1 -0
  286. package/src/tool/agent.ts +237 -0
  287. package/src/tool/apply_patch.ts +281 -0
  288. package/src/tool/apply_patch.txt +33 -0
  289. package/src/tool/bash.ts +269 -0
  290. package/src/tool/bash.txt +115 -0
  291. package/src/tool/batch.ts +175 -0
  292. package/src/tool/batch.txt +24 -0
  293. package/src/tool/chat-enter.txt +15 -0
  294. package/src/tool/chat-exit.txt +7 -0
  295. package/src/tool/chat.ts +217 -0
  296. package/src/tool/codesearch.ts +132 -0
  297. package/src/tool/codesearch.txt +12 -0
  298. package/src/tool/edit.ts +655 -0
  299. package/src/tool/edit.txt +10 -0
  300. package/src/tool/external-directory.ts +32 -0
  301. package/src/tool/glob.ts +78 -0
  302. package/src/tool/glob.txt +6 -0
  303. package/src/tool/grep.ts +147 -0
  304. package/src/tool/grep.txt +8 -0
  305. package/src/tool/invalid.ts +17 -0
  306. package/src/tool/ls.ts +121 -0
  307. package/src/tool/ls.txt +1 -0
  308. package/src/tool/lsp.ts +96 -0
  309. package/src/tool/lsp.txt +19 -0
  310. package/src/tool/multiedit.ts +46 -0
  311. package/src/tool/multiedit.txt +41 -0
  312. package/src/tool/plan-enter.txt +14 -0
  313. package/src/tool/plan-exit.txt +13 -0
  314. package/src/tool/plan.ts +130 -0
  315. package/src/tool/question.ts +33 -0
  316. package/src/tool/question.txt +10 -0
  317. package/src/tool/read.ts +211 -0
  318. package/src/tool/read.txt +12 -0
  319. package/src/tool/registry.ts +167 -0
  320. package/src/tool/research-enter.txt +1 -0
  321. package/src/tool/research-exit.txt +1 -0
  322. package/src/tool/research.ts +134 -0
  323. package/src/tool/skill.ts +123 -0
  324. package/src/tool/task.ts +165 -0
  325. package/src/tool/task.txt +60 -0
  326. package/src/tool/todo.ts +53 -0
  327. package/src/tool/todoread.txt +14 -0
  328. package/src/tool/todowrite.txt +167 -0
  329. package/src/tool/tool.ts +89 -0
  330. package/src/tool/truncation.ts +106 -0
  331. package/src/tool/webfetch.ts +186 -0
  332. package/src/tool/webfetch.txt +13 -0
  333. package/src/tool/websearch.ts +150 -0
  334. package/src/tool/websearch.txt +14 -0
  335. package/src/tool/write.ts +85 -0
  336. package/src/tool/write.txt +8 -0
  337. package/src/util/abort.ts +35 -0
  338. package/src/util/archive.ts +16 -0
  339. package/src/util/color.ts +19 -0
  340. package/src/util/context.ts +25 -0
  341. package/src/util/defer.ts +12 -0
  342. package/src/util/eventloop.ts +20 -0
  343. package/src/util/filesystem.ts +93 -0
  344. package/src/util/fn.ts +11 -0
  345. package/src/util/format.ts +20 -0
  346. package/src/util/iife.ts +3 -0
  347. package/src/util/keybind.ts +103 -0
  348. package/src/util/lazy.ts +18 -0
  349. package/src/util/locale.ts +81 -0
  350. package/src/util/lock.ts +98 -0
  351. package/src/util/log.ts +180 -0
  352. package/src/util/proxied.ts +3 -0
  353. package/src/util/queue.ts +32 -0
  354. package/src/util/rpc.ts +66 -0
  355. package/src/util/scrap.ts +10 -0
  356. package/src/util/signal.ts +12 -0
  357. package/src/util/timeout.ts +14 -0
  358. package/src/util/token.ts +7 -0
  359. package/src/util/wildcard.ts +56 -0
  360. package/src/worktree/index.ts +574 -0
  361. package/sst-env.d.ts +9 -0
  362. package/test/acp/agent-interface.test.ts +51 -0
  363. package/test/acp/event-subscription.test.ts +436 -0
  364. package/test/agent/agent.test.ts +675 -0
  365. package/test/bun.test.ts +53 -0
  366. package/test/cli/github-action.test.ts +161 -0
  367. package/test/cli/github-remote.test.ts +80 -0
  368. package/test/cli/import.test.ts +38 -0
  369. package/test/cli/tui/transcript.test.ts +322 -0
  370. package/test/config/agent-color.test.ts +71 -0
  371. package/test/config/config.test.ts +1802 -0
  372. package/test/config/fixtures/empty-frontmatter.md +4 -0
  373. package/test/config/fixtures/frontmatter.md +28 -0
  374. package/test/config/fixtures/markdown-header.md +11 -0
  375. package/test/config/fixtures/no-frontmatter.md +1 -0
  376. package/test/config/fixtures/weird-model-id.md +13 -0
  377. package/test/config/markdown.test.ts +228 -0
  378. package/test/file/ignore.test.ts +10 -0
  379. package/test/file/path-traversal.test.ts +198 -0
  380. package/test/file/ripgrep.test.ts +39 -0
  381. package/test/fixture/fixture.ts +45 -0
  382. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  383. package/test/ide/ide.test.ts +82 -0
  384. package/test/keybind.test.ts +421 -0
  385. package/test/lsp/client.test.ts +95 -0
  386. package/test/mcp/headers.test.ts +153 -0
  387. package/test/mcp/oauth-browser.test.ts +249 -0
  388. package/test/memory/abort-leak.test.ts +136 -0
  389. package/test/patch/patch.test.ts +348 -0
  390. package/test/permission/arity.test.ts +33 -0
  391. package/test/permission/next.test.ts +690 -0
  392. package/test/permission-task.test.ts +319 -0
  393. package/test/plugin/auth-override.test.ts +44 -0
  394. package/test/plugin/codex.test.ts +123 -0
  395. package/test/preload.ts +63 -0
  396. package/test/project/project.test.ts +120 -0
  397. package/test/provider/amazon-bedrock.test.ts +445 -0
  398. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  399. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  400. package/test/provider/gitlab-duo.test.ts +262 -0
  401. package/test/provider/provider.test.ts +2129 -0
  402. package/test/provider/transform.test.ts +2022 -0
  403. package/test/question/question.test.ts +300 -0
  404. package/test/scheduler.test.ts +73 -0
  405. package/test/server/session-list.test.ts +39 -0
  406. package/test/server/session-select.test.ts +78 -0
  407. package/test/session/compaction.test.ts +293 -0
  408. package/test/session/instruction.test.ts +170 -0
  409. package/test/session/llm.test.ts +691 -0
  410. package/test/session/message-v2.test.ts +786 -0
  411. package/test/session/prompt-missing-file.test.ts +53 -0
  412. package/test/session/prompt-special-chars.test.ts +56 -0
  413. package/test/session/prompt-variant.test.ts +60 -0
  414. package/test/session/retry.test.ts +179 -0
  415. package/test/session/revert-compact.test.ts +285 -0
  416. package/test/session/session.test.ts +71 -0
  417. package/test/skill/discovery.test.ts +60 -0
  418. package/test/skill/skill.test.ts +388 -0
  419. package/test/snapshot/snapshot.test.ts +1040 -0
  420. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  421. package/test/tool/apply_patch.test.ts +559 -0
  422. package/test/tool/bash.test.ts +399 -0
  423. package/test/tool/external-directory.test.ts +127 -0
  424. package/test/tool/fixtures/large-image.png +0 -0
  425. package/test/tool/fixtures/models-api.json +38413 -0
  426. package/test/tool/grep.test.ts +110 -0
  427. package/test/tool/question.test.ts +107 -0
  428. package/test/tool/read.test.ts +358 -0
  429. package/test/tool/registry.test.ts +122 -0
  430. package/test/tool/skill.test.ts +112 -0
  431. package/test/tool/truncation.test.ts +159 -0
  432. package/test/util/filesystem.test.ts +39 -0
  433. package/test/util/format.test.ts +59 -0
  434. package/test/util/iife.test.ts +36 -0
  435. package/test/util/lazy.test.ts +50 -0
  436. package/test/util/lock.test.ts +72 -0
  437. package/test/util/timeout.test.ts +21 -0
  438. package/test/util/wildcard.test.ts +75 -0
  439. package/tsconfig.json +16 -0
@@ -0,0 +1,189 @@
1
+ import z from "zod"
2
+ import { NamedError } from "@opencode-ai/util/error"
3
+
4
+ export namespace Message {
5
+ export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
6
+ export const AuthError = NamedError.create(
7
+ "ProviderAuthError",
8
+ z.object({
9
+ providerID: z.string(),
10
+ message: z.string(),
11
+ }),
12
+ )
13
+
14
+ export const ToolCall = z
15
+ .object({
16
+ state: z.literal("call"),
17
+ step: z.number().optional(),
18
+ toolCallId: z.string(),
19
+ toolName: z.string(),
20
+ args: z.custom<Required<unknown>>(),
21
+ })
22
+ .meta({
23
+ ref: "ToolCall",
24
+ })
25
+ export type ToolCall = z.infer<typeof ToolCall>
26
+
27
+ export const ToolPartialCall = z
28
+ .object({
29
+ state: z.literal("partial-call"),
30
+ step: z.number().optional(),
31
+ toolCallId: z.string(),
32
+ toolName: z.string(),
33
+ args: z.custom<Required<unknown>>(),
34
+ })
35
+ .meta({
36
+ ref: "ToolPartialCall",
37
+ })
38
+ export type ToolPartialCall = z.infer<typeof ToolPartialCall>
39
+
40
+ export const ToolResult = z
41
+ .object({
42
+ state: z.literal("result"),
43
+ step: z.number().optional(),
44
+ toolCallId: z.string(),
45
+ toolName: z.string(),
46
+ args: z.custom<Required<unknown>>(),
47
+ result: z.string(),
48
+ })
49
+ .meta({
50
+ ref: "ToolResult",
51
+ })
52
+ export type ToolResult = z.infer<typeof ToolResult>
53
+
54
+ export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
55
+ ref: "ToolInvocation",
56
+ })
57
+ export type ToolInvocation = z.infer<typeof ToolInvocation>
58
+
59
+ export const TextPart = z
60
+ .object({
61
+ type: z.literal("text"),
62
+ text: z.string(),
63
+ })
64
+ .meta({
65
+ ref: "TextPart",
66
+ })
67
+ export type TextPart = z.infer<typeof TextPart>
68
+
69
+ export const ReasoningPart = z
70
+ .object({
71
+ type: z.literal("reasoning"),
72
+ text: z.string(),
73
+ providerMetadata: z.record(z.string(), z.any()).optional(),
74
+ })
75
+ .meta({
76
+ ref: "ReasoningPart",
77
+ })
78
+ export type ReasoningPart = z.infer<typeof ReasoningPart>
79
+
80
+ export const ToolInvocationPart = z
81
+ .object({
82
+ type: z.literal("tool-invocation"),
83
+ toolInvocation: ToolInvocation,
84
+ })
85
+ .meta({
86
+ ref: "ToolInvocationPart",
87
+ })
88
+ export type ToolInvocationPart = z.infer<typeof ToolInvocationPart>
89
+
90
+ export const SourceUrlPart = z
91
+ .object({
92
+ type: z.literal("source-url"),
93
+ sourceId: z.string(),
94
+ url: z.string(),
95
+ title: z.string().optional(),
96
+ providerMetadata: z.record(z.string(), z.any()).optional(),
97
+ })
98
+ .meta({
99
+ ref: "SourceUrlPart",
100
+ })
101
+ export type SourceUrlPart = z.infer<typeof SourceUrlPart>
102
+
103
+ export const FilePart = z
104
+ .object({
105
+ type: z.literal("file"),
106
+ mediaType: z.string(),
107
+ filename: z.string().optional(),
108
+ url: z.string(),
109
+ })
110
+ .meta({
111
+ ref: "FilePart",
112
+ })
113
+ export type FilePart = z.infer<typeof FilePart>
114
+
115
+ export const StepStartPart = z
116
+ .object({
117
+ type: z.literal("step-start"),
118
+ })
119
+ .meta({
120
+ ref: "StepStartPart",
121
+ })
122
+ export type StepStartPart = z.infer<typeof StepStartPart>
123
+
124
+ export const MessagePart = z
125
+ .discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
126
+ .meta({
127
+ ref: "MessagePart",
128
+ })
129
+ export type MessagePart = z.infer<typeof MessagePart>
130
+
131
+ export const Info = z
132
+ .object({
133
+ id: z.string(),
134
+ role: z.enum(["user", "assistant"]),
135
+ parts: z.array(MessagePart),
136
+ metadata: z
137
+ .object({
138
+ time: z.object({
139
+ created: z.number(),
140
+ completed: z.number().optional(),
141
+ }),
142
+ error: z
143
+ .discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
144
+ .optional(),
145
+ sessionID: z.string(),
146
+ tool: z.record(
147
+ z.string(),
148
+ z
149
+ .object({
150
+ title: z.string(),
151
+ snapshot: z.string().optional(),
152
+ time: z.object({
153
+ start: z.number(),
154
+ end: z.number(),
155
+ }),
156
+ })
157
+ .catchall(z.any()),
158
+ ),
159
+ assistant: z
160
+ .object({
161
+ system: z.string().array(),
162
+ modelID: z.string(),
163
+ providerID: z.string(),
164
+ path: z.object({
165
+ cwd: z.string(),
166
+ root: z.string(),
167
+ }),
168
+ cost: z.number(),
169
+ summary: z.boolean().optional(),
170
+ tokens: z.object({
171
+ input: z.number(),
172
+ output: z.number(),
173
+ reasoning: z.number(),
174
+ cache: z.object({
175
+ read: z.number(),
176
+ write: z.number(),
177
+ }),
178
+ }),
179
+ })
180
+ .optional(),
181
+ snapshot: z.string().optional(),
182
+ })
183
+ .meta({ ref: "MessageMetadata" }),
184
+ })
185
+ .meta({
186
+ ref: "Message",
187
+ })
188
+ export type Info = z.infer<typeof Info>
189
+ }
@@ -0,0 +1,407 @@
1
+ import { MessageV2 } from "./message-v2"
2
+ import { Log } from "@/util/log"
3
+ import { Identifier } from "@/id/id"
4
+ import { Session } from "."
5
+ import { Agent } from "@/agent/agent"
6
+ import { Snapshot } from "@/snapshot"
7
+ import { SessionSummary } from "./summary"
8
+ import { Bus } from "@/bus"
9
+ import { SessionRetry } from "./retry"
10
+ import { SessionStatus } from "./status"
11
+ import { Plugin } from "@/plugin"
12
+ import type { Provider } from "@/provider/provider"
13
+ import { LLM } from "./llm"
14
+ import { Config } from "@/config/config"
15
+ import { SessionCompaction } from "./compaction"
16
+ import { PermissionNext } from "@/permission/next"
17
+ import { Question } from "@/question"
18
+
19
+ export namespace SessionProcessor {
20
+ const DOOM_LOOP_THRESHOLD = 3
21
+ const log = Log.create({ service: "session.processor" })
22
+
23
+ export type Info = Awaited<ReturnType<typeof create>>
24
+ export type Result = Awaited<ReturnType<Info["process"]>>
25
+
26
+ export function create(input: {
27
+ assistantMessage: MessageV2.Assistant
28
+ sessionID: string
29
+ model: Provider.Model
30
+ abort: AbortSignal
31
+ }) {
32
+ const toolcalls: Record<string, MessageV2.ToolPart> = {}
33
+ let snapshot: string | undefined
34
+ let blocked = false
35
+ let attempt = 0
36
+ let needsCompaction = false
37
+
38
+ const result = {
39
+ get message() {
40
+ return input.assistantMessage
41
+ },
42
+ partFromToolCall(toolCallID: string) {
43
+ return toolcalls[toolCallID]
44
+ },
45
+ async process(streamInput: LLM.StreamInput) {
46
+ log.info("process")
47
+ needsCompaction = false
48
+ const shouldBreak = (await Config.get()).experimental?.continue_loop_on_deny !== true
49
+ while (true) {
50
+ try {
51
+ let currentText: MessageV2.TextPart | undefined
52
+ let reasoningMap: Record<string, MessageV2.ReasoningPart> = {}
53
+ const stream = await LLM.stream(streamInput)
54
+
55
+ for await (const value of stream.fullStream) {
56
+ input.abort.throwIfAborted()
57
+ switch (value.type) {
58
+ case "start":
59
+ SessionStatus.set(input.sessionID, { type: "busy" })
60
+ break
61
+
62
+ case "reasoning-start":
63
+ if (value.id in reasoningMap) {
64
+ continue
65
+ }
66
+ reasoningMap[value.id] = {
67
+ id: Identifier.ascending("part"),
68
+ messageID: input.assistantMessage.id,
69
+ sessionID: input.assistantMessage.sessionID,
70
+ type: "reasoning",
71
+ text: "",
72
+ time: {
73
+ start: Date.now(),
74
+ },
75
+ metadata: value.providerMetadata,
76
+ }
77
+ break
78
+
79
+ case "reasoning-delta":
80
+ if (value.id in reasoningMap) {
81
+ const part = reasoningMap[value.id]
82
+ part.text += value.text
83
+ if (value.providerMetadata) part.metadata = value.providerMetadata
84
+ if (part.text) await Session.updatePart({ part, delta: value.text })
85
+ }
86
+ break
87
+
88
+ case "reasoning-end":
89
+ if (value.id in reasoningMap) {
90
+ const part = reasoningMap[value.id]
91
+ part.text = part.text.trimEnd()
92
+
93
+ part.time = {
94
+ ...part.time,
95
+ end: Date.now(),
96
+ }
97
+ if (value.providerMetadata) part.metadata = value.providerMetadata
98
+ await Session.updatePart(part)
99
+ delete reasoningMap[value.id]
100
+ }
101
+ break
102
+
103
+ case "tool-input-start":
104
+ const part = await Session.updatePart({
105
+ id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
106
+ messageID: input.assistantMessage.id,
107
+ sessionID: input.assistantMessage.sessionID,
108
+ type: "tool",
109
+ tool: value.toolName,
110
+ callID: value.id,
111
+ state: {
112
+ status: "pending",
113
+ input: {},
114
+ raw: "",
115
+ },
116
+ })
117
+ toolcalls[value.id] = part as MessageV2.ToolPart
118
+ break
119
+
120
+ case "tool-input-delta":
121
+ break
122
+
123
+ case "tool-input-end":
124
+ break
125
+
126
+ case "tool-call": {
127
+ const match = toolcalls[value.toolCallId]
128
+ if (match) {
129
+ const part = await Session.updatePart({
130
+ ...match,
131
+ tool: value.toolName,
132
+ state: {
133
+ status: "running",
134
+ input: value.input,
135
+ time: {
136
+ start: Date.now(),
137
+ },
138
+ },
139
+ metadata: value.providerMetadata,
140
+ })
141
+ toolcalls[value.toolCallId] = part as MessageV2.ToolPart
142
+
143
+ const parts = await MessageV2.parts(input.assistantMessage.id)
144
+ const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
145
+
146
+ if (
147
+ lastThree.length === DOOM_LOOP_THRESHOLD &&
148
+ lastThree.every(
149
+ (p) =>
150
+ p.type === "tool" &&
151
+ p.tool === value.toolName &&
152
+ p.state.status !== "pending" &&
153
+ JSON.stringify(p.state.input) === JSON.stringify(value.input),
154
+ )
155
+ ) {
156
+ const agent = await Agent.get(input.assistantMessage.agent)
157
+ await PermissionNext.ask({
158
+ permission: "doom_loop",
159
+ patterns: [value.toolName],
160
+ sessionID: input.assistantMessage.sessionID,
161
+ metadata: {
162
+ tool: value.toolName,
163
+ input: value.input,
164
+ },
165
+ always: [value.toolName],
166
+ ruleset: agent.permission,
167
+ })
168
+ }
169
+ }
170
+ break
171
+ }
172
+ case "tool-result": {
173
+ const match = toolcalls[value.toolCallId]
174
+ if (match && match.state.status === "running") {
175
+ await Session.updatePart({
176
+ ...match,
177
+ state: {
178
+ status: "completed",
179
+ input: value.input ?? match.state.input,
180
+ output: value.output.output,
181
+ metadata: value.output.metadata,
182
+ title: value.output.title,
183
+ time: {
184
+ start: match.state.time.start,
185
+ end: Date.now(),
186
+ },
187
+ attachments: value.output.attachments,
188
+ },
189
+ })
190
+
191
+ delete toolcalls[value.toolCallId]
192
+ }
193
+ break
194
+ }
195
+
196
+ case "tool-error": {
197
+ const match = toolcalls[value.toolCallId]
198
+ if (match && match.state.status === "running") {
199
+ await Session.updatePart({
200
+ ...match,
201
+ state: {
202
+ status: "error",
203
+ input: value.input ?? match.state.input,
204
+ error: (value.error as any).toString(),
205
+ time: {
206
+ start: match.state.time.start,
207
+ end: Date.now(),
208
+ },
209
+ },
210
+ })
211
+
212
+ if (
213
+ value.error instanceof PermissionNext.RejectedError ||
214
+ value.error instanceof Question.RejectedError
215
+ ) {
216
+ blocked = shouldBreak
217
+ }
218
+ delete toolcalls[value.toolCallId]
219
+ }
220
+ break
221
+ }
222
+ case "error":
223
+ throw value.error
224
+
225
+ case "start-step":
226
+ snapshot = await Snapshot.track()
227
+ await Session.updatePart({
228
+ id: Identifier.ascending("part"),
229
+ messageID: input.assistantMessage.id,
230
+ sessionID: input.sessionID,
231
+ snapshot,
232
+ type: "step-start",
233
+ })
234
+ break
235
+
236
+ case "finish-step":
237
+ const usage = Session.getUsage({
238
+ model: input.model,
239
+ usage: value.usage,
240
+ metadata: value.providerMetadata,
241
+ })
242
+ input.assistantMessage.finish = value.finishReason
243
+ input.assistantMessage.cost += usage.cost
244
+ input.assistantMessage.tokens = usage.tokens
245
+ await Session.updatePart({
246
+ id: Identifier.ascending("part"),
247
+ reason: value.finishReason,
248
+ snapshot: await Snapshot.track(),
249
+ messageID: input.assistantMessage.id,
250
+ sessionID: input.assistantMessage.sessionID,
251
+ type: "step-finish",
252
+ tokens: usage.tokens,
253
+ cost: usage.cost,
254
+ })
255
+ await Session.updateMessage(input.assistantMessage)
256
+ if (snapshot) {
257
+ const patch = await Snapshot.patch(snapshot)
258
+ if (patch.files.length) {
259
+ await Session.updatePart({
260
+ id: Identifier.ascending("part"),
261
+ messageID: input.assistantMessage.id,
262
+ sessionID: input.sessionID,
263
+ type: "patch",
264
+ hash: patch.hash,
265
+ files: patch.files,
266
+ })
267
+ }
268
+ snapshot = undefined
269
+ }
270
+ SessionSummary.summarize({
271
+ sessionID: input.sessionID,
272
+ messageID: input.assistantMessage.parentID,
273
+ })
274
+ if (await SessionCompaction.isOverflow({ tokens: usage.tokens, model: input.model })) {
275
+ needsCompaction = true
276
+ }
277
+ break
278
+
279
+ case "text-start":
280
+ currentText = {
281
+ id: Identifier.ascending("part"),
282
+ messageID: input.assistantMessage.id,
283
+ sessionID: input.assistantMessage.sessionID,
284
+ type: "text",
285
+ text: "",
286
+ time: {
287
+ start: Date.now(),
288
+ },
289
+ metadata: value.providerMetadata,
290
+ }
291
+ break
292
+
293
+ case "text-delta":
294
+ if (currentText) {
295
+ currentText.text += value.text
296
+ if (value.providerMetadata) currentText.metadata = value.providerMetadata
297
+ if (currentText.text)
298
+ await Session.updatePart({
299
+ part: currentText,
300
+ delta: value.text,
301
+ })
302
+ }
303
+ break
304
+
305
+ case "text-end":
306
+ if (currentText) {
307
+ currentText.text = currentText.text.trimEnd()
308
+ const textOutput = await Plugin.trigger(
309
+ "experimental.text.complete",
310
+ {
311
+ sessionID: input.sessionID,
312
+ messageID: input.assistantMessage.id,
313
+ partID: currentText.id,
314
+ },
315
+ { text: currentText.text },
316
+ )
317
+ currentText.text = textOutput.text
318
+ currentText.time = {
319
+ start: Date.now(),
320
+ end: Date.now(),
321
+ }
322
+ if (value.providerMetadata) currentText.metadata = value.providerMetadata
323
+ await Session.updatePart(currentText)
324
+ }
325
+ currentText = undefined
326
+ break
327
+
328
+ case "finish":
329
+ break
330
+
331
+ default:
332
+ log.info("unhandled", {
333
+ ...value,
334
+ })
335
+ continue
336
+ }
337
+ if (needsCompaction) break
338
+ }
339
+ } catch (e: any) {
340
+ log.error("process", {
341
+ error: e,
342
+ stack: JSON.stringify(e.stack),
343
+ })
344
+ const error = MessageV2.fromError(e, { providerID: input.model.providerID })
345
+ const retry = SessionRetry.retryable(error)
346
+ if (retry !== undefined) {
347
+ attempt++
348
+ const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
349
+ SessionStatus.set(input.sessionID, {
350
+ type: "retry",
351
+ attempt,
352
+ message: retry,
353
+ next: Date.now() + delay,
354
+ })
355
+ await SessionRetry.sleep(delay, input.abort).catch(() => {})
356
+ continue
357
+ }
358
+ input.assistantMessage.error = error
359
+ Bus.publish(Session.Event.Error, {
360
+ sessionID: input.assistantMessage.sessionID,
361
+ error: input.assistantMessage.error,
362
+ })
363
+ SessionStatus.set(input.sessionID, { type: "idle" })
364
+ }
365
+ if (snapshot) {
366
+ const patch = await Snapshot.patch(snapshot)
367
+ if (patch.files.length) {
368
+ await Session.updatePart({
369
+ id: Identifier.ascending("part"),
370
+ messageID: input.assistantMessage.id,
371
+ sessionID: input.sessionID,
372
+ type: "patch",
373
+ hash: patch.hash,
374
+ files: patch.files,
375
+ })
376
+ }
377
+ snapshot = undefined
378
+ }
379
+ const p = await MessageV2.parts(input.assistantMessage.id)
380
+ for (const part of p) {
381
+ if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
382
+ await Session.updatePart({
383
+ ...part,
384
+ state: {
385
+ ...part.state,
386
+ status: "error",
387
+ error: "Tool execution aborted",
388
+ time: {
389
+ start: Date.now(),
390
+ end: Date.now(),
391
+ },
392
+ },
393
+ })
394
+ }
395
+ }
396
+ input.assistantMessage.time.completed = Date.now()
397
+ await Session.updateMessage(input.assistantMessage)
398
+ if (needsCompaction) return "compact"
399
+ if (blocked) return "stop"
400
+ if (input.assistantMessage.error) return "stop"
401
+ return "continue"
402
+ }
403
+ },
404
+ }
405
+ return result
406
+ }
407
+ }
@@ -0,0 +1,43 @@
1
+ ## Agent Mode - Unified Planning and Execution
2
+
3
+ You are in Agent mode. This mode combines planning and execution in a single workflow.
4
+
5
+ ### Current Phase: ${phase}
6
+
7
+ ${phase === "planning"
8
+ ? `## Planning Phase
9
+
10
+ You are in READ-ONLY mode. DO NOT make any file edits, run bash commands that modify files, or otherwise make changes to the system.
11
+
12
+ Your workflow:
13
+ 1. **Understand the Request**: Read the user's goal/problem carefully
14
+ 2. **Ask Clarifying Questions**: Use the question tool to understand any ambiguities
15
+ 3. **Research**: Use explore agents, read files, and search to understand the codebase
16
+ 4. **Create a Plan**: Write a detailed plan to ${plan}
17
+ 5. **Present for Approval**: Call agent_approve_plan tool to request user approval
18
+
19
+ Only proceed to execution after explicit user approval.`
20
+ : `## Execution Phase
21
+
22
+ You now have FULL FILE SYSTEM ACCESS. You can edit files, run bash commands, and make changes.
23
+
24
+ Your workflow:
25
+ 1. **Create Todo List**: Break the approved plan into tasks using todowrite
26
+ 2. **Execute Tasks**: Work through the todo list systematically
27
+ 3. **Use Tools**: You have full access to all tools including edit, write, bash, glob, grep
28
+ 4. **Complete the Goal**: Execute the plan until all tasks are done
29
+ 5. **Call agent_exit**: When done, call agent_exit to finish
30
+
31
+ ${plan_approved ? `## Approved Plan
32
+ A plan has been approved at ${plan}. Execute this plan.` : ""}`
33
+ }
34
+
35
+ ### Plan File
36
+ ${exists ? `A plan file exists at ${plan}. You can read and edit it.` : `No plan file exists yet. Create your plan at ${plan} using the write tool.`}
37
+
38
+ ### Important Notes
39
+ - In planning phase: You may ONLY read, search, explore, and plan. NO modifications.
40
+ - In execution phase: You have full access to edit, write, bash, and all tools.
41
+ - Always ask clarifying questions when uncertain about user intent.
42
+ - Use question tool to get user feedback on plans before requesting approval.
43
+ - Call agent_approve_plan when the plan is ready for execution.