orynacode-ai 1.16.2

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 (938) hide show
  1. package/AGENTS.md +131 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/orynacode +199 -0
  6. package/bunfig.toml +7 -0
  7. package/git +0 -0
  8. package/migration/20260511173437_session-metadata/migration.sql +1 -0
  9. package/migration/20260511173437_session-metadata/snapshot.json +1500 -0
  10. package/package.json +155 -0
  11. package/parsers-config.ts +386 -0
  12. package/script/bench-search.ts +115 -0
  13. package/script/bench-test-suite.ts +52 -0
  14. package/script/build.ts +244 -0
  15. package/script/generate.ts +14 -0
  16. package/script/httpapi-exercise.ts +1 -0
  17. package/script/postinstall.mjs +189 -0
  18. package/script/profile-test-files.ts +42 -0
  19. package/script/publish.ts +213 -0
  20. package/script/run-workspace-server +106 -0
  21. package/script/schema.ts +77 -0
  22. package/script/time.ts +6 -0
  23. package/script/trace-imports.ts +153 -0
  24. package/specs/effect/error-boundaries-plan.md +235 -0
  25. package/specs/effect/errors.md +207 -0
  26. package/specs/effect/facades.md +218 -0
  27. package/specs/effect/guide.md +247 -0
  28. package/specs/effect/instance-context.md +13 -0
  29. package/specs/effect/loose-ends.md +30 -0
  30. package/specs/effect/migration.md +62 -0
  31. package/specs/effect/routes.md +61 -0
  32. package/specs/effect/schema.md +88 -0
  33. package/specs/effect/server-package.md +58 -0
  34. package/specs/effect/todo.md +241 -0
  35. package/specs/effect/tools.md +88 -0
  36. package/specs/openapi-translation-cleanup.md +204 -0
  37. package/specs/tui-plugins.md +544 -0
  38. package/specs/v2/api.ts +67 -0
  39. package/specs/v2/message-shape.md +136 -0
  40. package/specs/v2/notifications.md +13 -0
  41. package/specs/v2/tui-command-shim.md +67 -0
  42. package/src/account/account.ts +459 -0
  43. package/src/account/repo.ts +170 -0
  44. package/src/account/schema.ts +99 -0
  45. package/src/account/url.ts +8 -0
  46. package/src/acp/agent.ts +95 -0
  47. package/src/acp/config-option.ts +203 -0
  48. package/src/acp/content.ts +250 -0
  49. package/src/acp/directory.ts +210 -0
  50. package/src/acp/error.ts +90 -0
  51. package/src/acp/event.ts +344 -0
  52. package/src/acp/permission.ts +145 -0
  53. package/src/acp/profile.ts +42 -0
  54. package/src/acp/service.ts +1062 -0
  55. package/src/acp/session.ts +231 -0
  56. package/src/acp/tool.ts +317 -0
  57. package/src/acp/usage.ts +239 -0
  58. package/src/agent/agent.ts +433 -0
  59. package/src/agent/generate.txt +75 -0
  60. package/src/agent/prompt/compaction.txt +9 -0
  61. package/src/agent/prompt/explore.txt +18 -0
  62. package/src/agent/prompt/summary.txt +11 -0
  63. package/src/agent/prompt/title.txt +44 -0
  64. package/src/agent/subagent-permissions.ts +35 -0
  65. package/src/audio.d.ts +14 -0
  66. package/src/auth/index.ts +96 -0
  67. package/src/background/job.ts +36 -0
  68. package/src/bus/global.ts +22 -0
  69. package/src/cli/bootstrap.ts +11 -0
  70. package/src/cli/cmd/account.ts +264 -0
  71. package/src/cli/cmd/acp.ts +76 -0
  72. package/src/cli/cmd/agent.ts +259 -0
  73. package/src/cli/cmd/cmd.ts +7 -0
  74. package/src/cli/cmd/db.ts +62 -0
  75. package/src/cli/cmd/debug/agent.handler.ts +193 -0
  76. package/src/cli/cmd/debug/agent.ts +27 -0
  77. package/src/cli/cmd/debug/config.ts +14 -0
  78. package/src/cli/cmd/debug/file.ts +87 -0
  79. package/src/cli/cmd/debug/index.ts +87 -0
  80. package/src/cli/cmd/debug/lsp.ts +51 -0
  81. package/src/cli/cmd/debug/ripgrep.ts +99 -0
  82. package/src/cli/cmd/debug/scrap.ts +18 -0
  83. package/src/cli/cmd/debug/skill.ts +15 -0
  84. package/src/cli/cmd/debug/snapshot.ts +50 -0
  85. package/src/cli/cmd/debug/startup.ts +11 -0
  86. package/src/cli/cmd/debug/v2.ts +46 -0
  87. package/src/cli/cmd/export.ts +292 -0
  88. package/src/cli/cmd/generate.ts +54 -0
  89. package/src/cli/cmd/github.handler.ts +1593 -0
  90. package/src/cli/cmd/github.shared.ts +30 -0
  91. package/src/cli/cmd/github.ts +42 -0
  92. package/src/cli/cmd/import.ts +224 -0
  93. package/src/cli/cmd/mcp.ts +846 -0
  94. package/src/cli/cmd/models.ts +66 -0
  95. package/src/cli/cmd/plug.ts +230 -0
  96. package/src/cli/cmd/pr.ts +115 -0
  97. package/src/cli/cmd/prompt-display.ts +48 -0
  98. package/src/cli/cmd/providers.ts +506 -0
  99. package/src/cli/cmd/run/demo.ts +1274 -0
  100. package/src/cli/cmd/run/entry.body.ts +194 -0
  101. package/src/cli/cmd/run/footer.command.tsx +899 -0
  102. package/src/cli/cmd/run/footer.menu.tsx +306 -0
  103. package/src/cli/cmd/run/footer.permission.tsx +475 -0
  104. package/src/cli/cmd/run/footer.prompt.tsx +1207 -0
  105. package/src/cli/cmd/run/footer.question.tsx +579 -0
  106. package/src/cli/cmd/run/footer.subagent.tsx +171 -0
  107. package/src/cli/cmd/run/footer.ts +1092 -0
  108. package/src/cli/cmd/run/footer.view.tsx +935 -0
  109. package/src/cli/cmd/run/otel.ts +117 -0
  110. package/src/cli/cmd/run/permission.shared.ts +256 -0
  111. package/src/cli/cmd/run/prompt.shared.ts +147 -0
  112. package/src/cli/cmd/run/question.shared.ts +340 -0
  113. package/src/cli/cmd/run/runtime.boot.ts +210 -0
  114. package/src/cli/cmd/run/runtime.lifecycle.ts +369 -0
  115. package/src/cli/cmd/run/runtime.queue.ts +347 -0
  116. package/src/cli/cmd/run/runtime.shared.ts +17 -0
  117. package/src/cli/cmd/run/runtime.stdin.ts +37 -0
  118. package/src/cli/cmd/run/runtime.ts +879 -0
  119. package/src/cli/cmd/run/scrollback.shared.ts +92 -0
  120. package/src/cli/cmd/run/scrollback.surface.ts +435 -0
  121. package/src/cli/cmd/run/scrollback.writer.tsx +335 -0
  122. package/src/cli/cmd/run/session-data.ts +1113 -0
  123. package/src/cli/cmd/run/session-replay.ts +301 -0
  124. package/src/cli/cmd/run/session.shared.ts +196 -0
  125. package/src/cli/cmd/run/splash.ts +310 -0
  126. package/src/cli/cmd/run/stream.transport.ts +1465 -0
  127. package/src/cli/cmd/run/stream.ts +175 -0
  128. package/src/cli/cmd/run/subagent-data.ts +844 -0
  129. package/src/cli/cmd/run/theme.ts +603 -0
  130. package/src/cli/cmd/run/tool.ts +1489 -0
  131. package/src/cli/cmd/run/trace.ts +94 -0
  132. package/src/cli/cmd/run/types.ts +342 -0
  133. package/src/cli/cmd/run/variant.shared.ts +215 -0
  134. package/src/cli/cmd/run.ts +879 -0
  135. package/src/cli/cmd/serve.ts +24 -0
  136. package/src/cli/cmd/session.ts +147 -0
  137. package/src/cli/cmd/stats.ts +393 -0
  138. package/src/cli/cmd/tui/app.tsx +1113 -0
  139. package/src/cli/cmd/tui/attach.ts +103 -0
  140. package/src/cli/cmd/tui/attention.ts +262 -0
  141. package/src/cli/cmd/tui/component/bg-pulse-render.ts +436 -0
  142. package/src/cli/cmd/tui/component/bg-pulse.tsx +99 -0
  143. package/src/cli/cmd/tui/component/border.tsx +21 -0
  144. package/src/cli/cmd/tui/component/command-palette.tsx +79 -0
  145. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  146. package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
  147. package/src/cli/cmd/tui/component/dialog-mcp.tsx +85 -0
  148. package/src/cli/cmd/tui/component/dialog-model.tsx +185 -0
  149. package/src/cli/cmd/tui/component/dialog-move-session.tsx +240 -0
  150. package/src/cli/cmd/tui/component/dialog-provider.tsx +687 -0
  151. package/src/cli/cmd/tui/component/dialog-retry-action.tsx +160 -0
  152. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +99 -0
  153. package/src/cli/cmd/tui/component/dialog-session-list.tsx +318 -0
  154. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  155. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  156. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  157. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  158. package/src/cli/cmd/tui/component/dialog-tag.tsx +47 -0
  159. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  160. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  161. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +313 -0
  162. package/src/cli/cmd/tui/component/dialog-workspace-file-changes.tsx +144 -0
  163. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +112 -0
  164. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +69 -0
  165. package/src/cli/cmd/tui/component/error-component.tsx +81 -0
  166. package/src/cli/cmd/tui/component/logo.tsx +885 -0
  167. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  168. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +799 -0
  169. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  170. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  171. package/src/cli/cmd/tui/component/prompt/history.tsx +117 -0
  172. package/src/cli/cmd/tui/component/prompt/index.tsx +1725 -0
  173. package/src/cli/cmd/tui/component/prompt/move.tsx +192 -0
  174. package/src/cli/cmd/tui/component/prompt/part.ts +31 -0
  175. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  176. package/src/cli/cmd/tui/component/prompt/traits.ts +35 -0
  177. package/src/cli/cmd/tui/component/prompt/workspace.tsx +137 -0
  178. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  179. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  180. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  181. package/src/cli/cmd/tui/component/use-connected.tsx +9 -0
  182. package/src/cli/cmd/tui/component/workspace-label.tsx +19 -0
  183. package/src/cli/cmd/tui/config/cwd.ts +5 -0
  184. package/src/cli/cmd/tui/config/keybind.ts +467 -0
  185. package/src/cli/cmd/tui/config/tui-migrate.ts +154 -0
  186. package/src/cli/cmd/tui/config/tui-schema.ts +88 -0
  187. package/src/cli/cmd/tui/config/tui.ts +308 -0
  188. package/src/cli/cmd/tui/context/agent.tsx +11 -0
  189. package/src/cli/cmd/tui/context/aggregate-failures.ts +51 -0
  190. package/src/cli/cmd/tui/context/args.tsx +15 -0
  191. package/src/cli/cmd/tui/context/directory.ts +15 -0
  192. package/src/cli/cmd/tui/context/editor-zed.ts +287 -0
  193. package/src/cli/cmd/tui/context/editor.ts +469 -0
  194. package/src/cli/cmd/tui/context/event.ts +38 -0
  195. package/src/cli/cmd/tui/context/exit.tsx +42 -0
  196. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  197. package/src/cli/cmd/tui/context/kv.tsx +76 -0
  198. package/src/cli/cmd/tui/context/local.tsx +510 -0
  199. package/src/cli/cmd/tui/context/path-format.tsx +39 -0
  200. package/src/cli/cmd/tui/context/project.tsx +111 -0
  201. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  202. package/src/cli/cmd/tui/context/route.tsx +52 -0
  203. package/src/cli/cmd/tui/context/sdk.tsx +142 -0
  204. package/src/cli/cmd/tui/context/sync-v2.tsx +447 -0
  205. package/src/cli/cmd/tui/context/sync.tsx +628 -0
  206. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  207. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  208. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  209. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +230 -0
  210. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +230 -0
  211. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  212. package/src/cli/cmd/tui/context/theme/cobalt2.json +225 -0
  213. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  214. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  215. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  216. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  217. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  218. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  219. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  220. package/src/cli/cmd/tui/context/theme/lucent-orng.json +234 -0
  221. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  222. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  223. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  224. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  225. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  226. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  227. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  228. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  229. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  230. package/src/cli/cmd/tui/context/theme/oryna.json +95 -0
  231. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  232. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  233. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  234. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  235. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  236. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  237. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  238. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  239. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  240. package/src/cli/cmd/tui/context/theme.tsx +1341 -0
  241. package/src/cli/cmd/tui/context/thinking.ts +67 -0
  242. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  243. package/src/cli/cmd/tui/event.ts +53 -0
  244. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +98 -0
  245. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +288 -0
  246. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +59 -0
  247. package/src/cli/cmd/tui/feature-plugins/session/dialog.tsx +356 -0
  248. package/src/cli/cmd/tui/feature-plugins/session/index.tsx +32 -0
  249. package/src/cli/cmd/tui/feature-plugins/session/preview-pane.tsx +288 -0
  250. package/src/cli/cmd/tui/feature-plugins/session/util.tsx +54 -0
  251. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +65 -0
  252. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +70 -0
  253. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +96 -0
  254. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +65 -0
  255. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +97 -0
  256. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +49 -0
  257. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer-file-tree-utils.ts +232 -0
  258. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer-file-tree.tsx +162 -0
  259. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer-ui.tsx +103 -0
  260. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer.tsx +1058 -0
  261. package/src/cli/cmd/tui/feature-plugins/system/notifications.ts +94 -0
  262. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +269 -0
  263. package/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx +1184 -0
  264. package/src/cli/cmd/tui/feature-plugins/system/which-key.tsx +608 -0
  265. package/src/cli/cmd/tui/keymap.tsx +283 -0
  266. package/src/cli/cmd/tui/layer.ts +6 -0
  267. package/src/cli/cmd/tui/plugin/api.tsx +390 -0
  268. package/src/cli/cmd/tui/plugin/command-shim.ts +109 -0
  269. package/src/cli/cmd/tui/plugin/internal.ts +42 -0
  270. package/src/cli/cmd/tui/plugin/runtime.ts +1131 -0
  271. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  272. package/src/cli/cmd/tui/routes/home/session-destination.tsx +39 -0
  273. package/src/cli/cmd/tui/routes/home.tsx +149 -0
  274. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
  275. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +108 -0
  276. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  277. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  278. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  279. package/src/cli/cmd/tui/routes/session/index.tsx +2629 -0
  280. package/src/cli/cmd/tui/routes/session/permission.tsx +729 -0
  281. package/src/cli/cmd/tui/routes/session/question.tsx +514 -0
  282. package/src/cli/cmd/tui/routes/session/sidebar.tsx +102 -0
  283. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +132 -0
  284. package/src/cli/cmd/tui/thread.ts +264 -0
  285. package/src/cli/cmd/tui/ui/dialog-alert.tsx +66 -0
  286. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +108 -0
  287. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +217 -0
  288. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  289. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +126 -0
  290. package/src/cli/cmd/tui/ui/dialog-select.tsx +712 -0
  291. package/src/cli/cmd/tui/ui/dialog.tsx +218 -0
  292. package/src/cli/cmd/tui/ui/link.tsx +34 -0
  293. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  294. package/src/cli/cmd/tui/ui/toast.tsx +102 -0
  295. package/src/cli/cmd/tui/util/audio.ts +58 -0
  296. package/src/cli/cmd/tui/util/clipboard.ts +181 -0
  297. package/src/cli/cmd/tui/util/collapse-tool-output.ts +19 -0
  298. package/src/cli/cmd/tui/util/editor.ts +43 -0
  299. package/src/cli/cmd/tui/util/layout.ts +25 -0
  300. package/src/cli/cmd/tui/util/model.ts +23 -0
  301. package/src/cli/cmd/tui/util/provider-origin.ts +7 -0
  302. package/src/cli/cmd/tui/util/revert-diff.ts +18 -0
  303. package/src/cli/cmd/tui/util/scroll.ts +25 -0
  304. package/src/cli/cmd/tui/util/selection.ts +73 -0
  305. package/src/cli/cmd/tui/util/signal.ts +41 -0
  306. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  307. package/src/cli/cmd/tui/validate-session.ts +29 -0
  308. package/src/cli/cmd/tui/win32.ts +130 -0
  309. package/src/cli/cmd/tui/worker.ts +99 -0
  310. package/src/cli/cmd/uninstall.ts +353 -0
  311. package/src/cli/cmd/upgrade.ts +74 -0
  312. package/src/cli/cmd/web.ts +84 -0
  313. package/src/cli/effect/prompt.ts +37 -0
  314. package/src/cli/effect-cmd.ts +96 -0
  315. package/src/cli/error.ts +118 -0
  316. package/src/cli/heap.ts +59 -0
  317. package/src/cli/logo.ts +21 -0
  318. package/src/cli/network.ts +64 -0
  319. package/src/cli/ui.ts +132 -0
  320. package/src/cli/upgrade.ts +53 -0
  321. package/src/command/index.ts +181 -0
  322. package/src/command/template/initialize.txt +66 -0
  323. package/src/command/template/review.txt +101 -0
  324. package/src/config/agent.ts +68 -0
  325. package/src/config/command.ts +45 -0
  326. package/src/config/config.ts +679 -0
  327. package/src/config/entry-name.ts +19 -0
  328. package/src/config/managed.ts +77 -0
  329. package/src/config/markdown.ts +36 -0
  330. package/src/config/parse.ts +79 -0
  331. package/src/config/paths.ts +45 -0
  332. package/src/config/plugin.ts +79 -0
  333. package/src/config/reference.ts +48 -0
  334. package/src/config/variable.ts +91 -0
  335. package/src/control-plane/adapters/index.ts +41 -0
  336. package/src/control-plane/adapters/worktree.ts +96 -0
  337. package/src/control-plane/dev/README.md +19 -0
  338. package/src/control-plane/dev/debug-workspace-plugin.ts +73 -0
  339. package/src/control-plane/types.ts +59 -0
  340. package/src/control-plane/util.ts +39 -0
  341. package/src/control-plane/workspace-adapter-runtime.ts +51 -0
  342. package/src/control-plane/workspace-context.ts +26 -0
  343. package/src/control-plane/workspace.ts +1075 -0
  344. package/src/effect/app-runtime.ts +133 -0
  345. package/src/effect/bootstrap-runtime.ts +23 -0
  346. package/src/effect/bridge.ts +84 -0
  347. package/src/effect/config-service.ts +67 -0
  348. package/src/effect/instance-ref.ts +11 -0
  349. package/src/effect/instance-registry.ts +12 -0
  350. package/src/effect/instance-state.ts +72 -0
  351. package/src/effect/promise.ts +17 -0
  352. package/src/effect/run-service.ts +47 -0
  353. package/src/effect/runner.ts +217 -0
  354. package/src/effect/runtime-flags.ts +76 -0
  355. package/src/env/index.ts +40 -0
  356. package/src/event-v2-bridge.ts +76 -0
  357. package/src/format/formatter.ts +404 -0
  358. package/src/format/index.ts +212 -0
  359. package/src/git/index.ts +347 -0
  360. package/src/id/id.ts +80 -0
  361. package/src/ide/index.ts +70 -0
  362. package/src/image/image.ts +177 -0
  363. package/src/index.ts +208 -0
  364. package/src/installation/index.ts +349 -0
  365. package/src/lsp/client.ts +686 -0
  366. package/src/lsp/diagnostic.ts +29 -0
  367. package/src/lsp/language.ts +121 -0
  368. package/src/lsp/launch.ts +21 -0
  369. package/src/lsp/lsp.ts +517 -0
  370. package/src/lsp/server.ts +2064 -0
  371. package/src/markdown.d.ts +4 -0
  372. package/src/mcp/auth.ts +171 -0
  373. package/src/mcp/index.ts +982 -0
  374. package/src/mcp/oauth-callback.ts +232 -0
  375. package/src/mcp/oauth-provider.ts +217 -0
  376. package/src/node.ts +5 -0
  377. package/src/oryna/agent.ts +112 -0
  378. package/src/oryna/reply-service.ts +8 -0
  379. package/src/patch/index.ts +689 -0
  380. package/src/permission/arity.ts +163 -0
  381. package/src/permission/evaluate.ts +1 -0
  382. package/src/permission/index.ts +230 -0
  383. package/src/plugin/azure.ts +26 -0
  384. package/src/plugin/cloudflare.ts +76 -0
  385. package/src/plugin/digitalocean.ts +391 -0
  386. package/src/plugin/github-copilot/copilot.ts +417 -0
  387. package/src/plugin/github-copilot/models.ts +246 -0
  388. package/src/plugin/index.ts +323 -0
  389. package/src/plugin/install.ts +439 -0
  390. package/src/plugin/loader.ts +237 -0
  391. package/src/plugin/meta.ts +188 -0
  392. package/src/plugin/openai/README.md +31 -0
  393. package/src/plugin/openai/codex.ts +647 -0
  394. package/src/plugin/openai/ws-pool.ts +290 -0
  395. package/src/plugin/openai/ws.ts +381 -0
  396. package/src/plugin/oryna.ts +349 -0
  397. package/src/plugin/shared.ts +323 -0
  398. package/src/plugin/xai.ts +742 -0
  399. package/src/project/bootstrap-service.ts +9 -0
  400. package/src/project/bootstrap.ts +80 -0
  401. package/src/project/instance-context.ts +24 -0
  402. package/src/project/instance-layer.ts +11 -0
  403. package/src/project/instance-runtime.ts +16 -0
  404. package/src/project/instance-store.ts +207 -0
  405. package/src/project/project.ts +520 -0
  406. package/src/project/vcs.ts +435 -0
  407. package/src/provider/auth.ts +230 -0
  408. package/src/provider/error.ts +188 -0
  409. package/src/provider/model-status.ts +8 -0
  410. package/src/provider/provider.ts +2009 -0
  411. package/src/provider/transform.ts +1363 -0
  412. package/src/pty-preparation.ts +30 -0
  413. package/src/question/index.ts +229 -0
  414. package/src/question/schema.ts +10 -0
  415. package/src/reference/reference.ts +239 -0
  416. package/src/reference/repository-cache.ts +320 -0
  417. package/src/server/auth.ts +48 -0
  418. package/src/server/cors.ts +34 -0
  419. package/src/server/event.ts +13 -0
  420. package/src/server/global-lifecycle.ts +37 -0
  421. package/src/server/init-projectors.ts +3 -0
  422. package/src/server/mdns.ts +60 -0
  423. package/src/server/projectors.ts +1 -0
  424. package/src/server/proxy-util.ts +48 -0
  425. package/src/server/routes/instance/httpapi/AGENTS.md +39 -0
  426. package/src/server/routes/instance/httpapi/api.ts +78 -0
  427. package/src/server/routes/instance/httpapi/errors.ts +193 -0
  428. package/src/server/routes/instance/httpapi/groups/config.ts +65 -0
  429. package/src/server/routes/instance/httpapi/groups/control-plane.ts +35 -0
  430. package/src/server/routes/instance/httpapi/groups/control.ts +76 -0
  431. package/src/server/routes/instance/httpapi/groups/event.ts +29 -0
  432. package/src/server/routes/instance/httpapi/groups/experimental.ts +260 -0
  433. package/src/server/routes/instance/httpapi/groups/file.ts +172 -0
  434. package/src/server/routes/instance/httpapi/groups/global.ts +138 -0
  435. package/src/server/routes/instance/httpapi/groups/instance.ts +206 -0
  436. package/src/server/routes/instance/httpapi/groups/mcp.ts +156 -0
  437. package/src/server/routes/instance/httpapi/groups/metadata.ts +18 -0
  438. package/src/server/routes/instance/httpapi/groups/permission.ts +61 -0
  439. package/src/server/routes/instance/httpapi/groups/project-copy.ts +88 -0
  440. package/src/server/routes/instance/httpapi/groups/project.ts +93 -0
  441. package/src/server/routes/instance/httpapi/groups/provider.ts +101 -0
  442. package/src/server/routes/instance/httpapi/groups/pty.ts +172 -0
  443. package/src/server/routes/instance/httpapi/groups/query.ts +12 -0
  444. package/src/server/routes/instance/httpapi/groups/question.ts +74 -0
  445. package/src/server/routes/instance/httpapi/groups/session.ts +462 -0
  446. package/src/server/routes/instance/httpapi/groups/sync.ts +113 -0
  447. package/src/server/routes/instance/httpapi/groups/tui.ts +208 -0
  448. package/src/server/routes/instance/httpapi/groups/workspace.ts +141 -0
  449. package/src/server/routes/instance/httpapi/handlers/config.ts +151 -0
  450. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +37 -0
  451. package/src/server/routes/instance/httpapi/handlers/control.ts +40 -0
  452. package/src/server/routes/instance/httpapi/handlers/event.ts +102 -0
  453. package/src/server/routes/instance/httpapi/handlers/experimental.ts +187 -0
  454. package/src/server/routes/instance/httpapi/handlers/file.ts +103 -0
  455. package/src/server/routes/instance/httpapi/handlers/global.ts +157 -0
  456. package/src/server/routes/instance/httpapi/handlers/instance.ts +110 -0
  457. package/src/server/routes/instance/httpapi/handlers/mcp.ts +111 -0
  458. package/src/server/routes/instance/httpapi/handlers/permission.ts +41 -0
  459. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +157 -0
  460. package/src/server/routes/instance/httpapi/handlers/project.ts +63 -0
  461. package/src/server/routes/instance/httpapi/handlers/provider.ts +160 -0
  462. package/src/server/routes/instance/httpapi/handlers/pty.ts +258 -0
  463. package/src/server/routes/instance/httpapi/handlers/question.ts +54 -0
  464. package/src/server/routes/instance/httpapi/handlers/session-errors.ts +21 -0
  465. package/src/server/routes/instance/httpapi/handlers/session.ts +442 -0
  466. package/src/server/routes/instance/httpapi/handlers/sync.ts +95 -0
  467. package/src/server/routes/instance/httpapi/handlers/tui.ts +131 -0
  468. package/src/server/routes/instance/httpapi/handlers/workspace.ts +102 -0
  469. package/src/server/routes/instance/httpapi/lifecycle.ts +57 -0
  470. package/src/server/routes/instance/httpapi/middleware/authorization.ts +147 -0
  471. package/src/server/routes/instance/httpapi/middleware/compression.ts +64 -0
  472. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +29 -0
  473. package/src/server/routes/instance/httpapi/middleware/error.ts +36 -0
  474. package/src/server/routes/instance/httpapi/middleware/fence.ts +25 -0
  475. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +43 -0
  476. package/src/server/routes/instance/httpapi/middleware/proxy.ts +108 -0
  477. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +42 -0
  478. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +250 -0
  479. package/src/server/routes/instance/httpapi/public.ts +535 -0
  480. package/src/server/routes/instance/httpapi/server.ts +277 -0
  481. package/src/server/routes/instance/httpapi/websocket-tracker.ts +57 -0
  482. package/src/server/server.ts +218 -0
  483. package/src/server/shared/fence.ts +68 -0
  484. package/src/server/shared/pty-ticket.ts +15 -0
  485. package/src/server/shared/public-ui.ts +12 -0
  486. package/src/server/shared/tui-control.ts +28 -0
  487. package/src/server/shared/ui.ts +108 -0
  488. package/src/server/shared/workspace-routing.ts +38 -0
  489. package/src/session/compaction.ts +609 -0
  490. package/src/session/instruction.ts +237 -0
  491. package/src/session/llm/AGENTS.md +90 -0
  492. package/src/session/llm/ai-sdk.ts +288 -0
  493. package/src/session/llm/native-request.ts +196 -0
  494. package/src/session/llm/native-runtime.ts +195 -0
  495. package/src/session/llm/request.ts +215 -0
  496. package/src/session/llm.ts +402 -0
  497. package/src/session/message-error.ts +14 -0
  498. package/src/session/message-v2.ts +745 -0
  499. package/src/session/message.ts +148 -0
  500. package/src/session/overflow.ts +34 -0
  501. package/src/session/processor.ts +1063 -0
  502. package/src/session/prompt/anthropic.txt +105 -0
  503. package/src/session/prompt/beast.txt +147 -0
  504. package/src/session/prompt/build-switch.txt +5 -0
  505. package/src/session/prompt/codex.txt +79 -0
  506. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  507. package/src/session/prompt/default.txt +95 -0
  508. package/src/session/prompt/gemini.txt +155 -0
  509. package/src/session/prompt/gpt.txt +107 -0
  510. package/src/session/prompt/kimi.txt +95 -0
  511. package/src/session/prompt/max-steps.txt +16 -0
  512. package/src/session/prompt/plan-mode.txt +70 -0
  513. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  514. package/src/session/prompt/plan.txt +26 -0
  515. package/src/session/prompt/reference.ts +72 -0
  516. package/src/session/prompt/trinity.txt +97 -0
  517. package/src/session/prompt.ts +1755 -0
  518. package/src/session/reminders.ts +92 -0
  519. package/src/session/retry.ts +201 -0
  520. package/src/session/revert.ts +153 -0
  521. package/src/session/run-state.ts +153 -0
  522. package/src/session/schema.ts +26 -0
  523. package/src/session/session.ts +1116 -0
  524. package/src/session/status.ts +94 -0
  525. package/src/session/summary.ts +162 -0
  526. package/src/session/system.ts +84 -0
  527. package/src/session/todo.ts +87 -0
  528. package/src/session/tools.ts +211 -0
  529. package/src/share/session.ts +58 -0
  530. package/src/share/share-next.ts +379 -0
  531. package/src/shell/shell.ts +215 -0
  532. package/src/skill/discovery.ts +115 -0
  533. package/src/skill/index.ts +357 -0
  534. package/src/snapshot/index.ts +759 -0
  535. package/src/sql.d.ts +4 -0
  536. package/src/storage/schema.ts +5 -0
  537. package/src/storage/storage.ts +329 -0
  538. package/src/sync/README.md +179 -0
  539. package/src/sync/schema.ts +11 -0
  540. package/src/temporary.ts +33 -0
  541. package/src/tool/apply_patch.ts +313 -0
  542. package/src/tool/apply_patch.txt +33 -0
  543. package/src/tool/edit.ts +737 -0
  544. package/src/tool/edit.txt +10 -0
  545. package/src/tool/external-directory.ts +49 -0
  546. package/src/tool/glob.ts +84 -0
  547. package/src/tool/glob.txt +6 -0
  548. package/src/tool/grep.ts +140 -0
  549. package/src/tool/grep.txt +8 -0
  550. package/src/tool/invalid.ts +21 -0
  551. package/src/tool/json-schema.ts +164 -0
  552. package/src/tool/lsp.ts +113 -0
  553. package/src/tool/lsp.txt +24 -0
  554. package/src/tool/mcp-websearch.ts +96 -0
  555. package/src/tool/plan-enter.txt +14 -0
  556. package/src/tool/plan-exit.txt +13 -0
  557. package/src/tool/plan.ts +79 -0
  558. package/src/tool/question.ts +44 -0
  559. package/src/tool/question.txt +10 -0
  560. package/src/tool/read.ts +392 -0
  561. package/src/tool/read.txt +14 -0
  562. package/src/tool/registry.ts +475 -0
  563. package/src/tool/reply.ts +29 -0
  564. package/src/tool/schema.ts +14 -0
  565. package/src/tool/shell/id.ts +19 -0
  566. package/src/tool/shell/prompt.ts +307 -0
  567. package/src/tool/shell/shell.txt +21 -0
  568. package/src/tool/shell.ts +660 -0
  569. package/src/tool/skill.ts +72 -0
  570. package/src/tool/skill.txt +5 -0
  571. package/src/tool/task.ts +338 -0
  572. package/src/tool/task.txt +19 -0
  573. package/src/tool/todo.ts +57 -0
  574. package/src/tool/todowrite.txt +44 -0
  575. package/src/tool/tool.ts +183 -0
  576. package/src/tool/truncate.ts +160 -0
  577. package/src/tool/truncation-dir.ts +4 -0
  578. package/src/tool/webfetch.ts +192 -0
  579. package/src/tool/webfetch.txt +13 -0
  580. package/src/tool/websearch.ts +143 -0
  581. package/src/tool/websearch.txt +14 -0
  582. package/src/tool/write.ts +104 -0
  583. package/src/tool/write.txt +8 -0
  584. package/src/util/archive.ts +17 -0
  585. package/src/util/bom.ts +27 -0
  586. package/src/util/data-url.ts +9 -0
  587. package/src/util/defer.ts +10 -0
  588. package/src/util/effect-http-client.ts +11 -0
  589. package/src/util/error.ts +88 -0
  590. package/src/util/filesystem.ts +251 -0
  591. package/src/util/format.ts +20 -0
  592. package/src/util/iife.ts +3 -0
  593. package/src/util/lan-scan.ts +90 -0
  594. package/src/util/lazy.ts +20 -0
  595. package/src/util/local-context.ts +25 -0
  596. package/src/util/locale.ts +86 -0
  597. package/src/util/media.ts +26 -0
  598. package/src/util/process.ts +176 -0
  599. package/src/util/proxy-env.ts +72 -0
  600. package/src/util/queue.ts +32 -0
  601. package/src/util/record.ts +3 -0
  602. package/src/util/repository.ts +232 -0
  603. package/src/util/rpc.ts +66 -0
  604. package/src/util/signal.ts +12 -0
  605. package/src/util/timeout.ts +13 -0
  606. package/src/util/token.ts +1 -0
  607. package/src/util/wildcard.ts +59 -0
  608. package/src/worktree/index.ts +645 -0
  609. package/sst-env.d.ts +10 -0
  610. package/test/AGENTS.md +204 -0
  611. package/test/EFFECT_TEST_MIGRATION.md +169 -0
  612. package/test/account/repo.test.ts +353 -0
  613. package/test/account/service.test.ts +453 -0
  614. package/test/acp/config-option.test.ts +229 -0
  615. package/test/acp/content.test.ts +201 -0
  616. package/test/acp/directory.test.ts +186 -0
  617. package/test/acp/error.test.ts +67 -0
  618. package/test/acp/event.test.ts +711 -0
  619. package/test/acp/permission.test.ts +273 -0
  620. package/test/acp/service-session.test.ts +1174 -0
  621. package/test/acp/session.test.ts +200 -0
  622. package/test/acp/tool.test.ts +210 -0
  623. package/test/acp/usage.test.ts +315 -0
  624. package/test/agent/agent.test.ts +710 -0
  625. package/test/agent/plan-mode-subagent-bypass.test.ts +213 -0
  626. package/test/agent/plugin-agent-regression.test.ts +62 -0
  627. package/test/auth/auth.test.ts +77 -0
  628. package/test/background/job.test.ts +243 -0
  629. package/test/cli/account.test.ts +30 -0
  630. package/test/cli/acp/acp-test-client.ts +97 -0
  631. package/test/cli/acp/config-options.test.ts +103 -0
  632. package/test/cli/acp/helpers.ts +96 -0
  633. package/test/cli/acp/initialize-auth.test.ts +61 -0
  634. package/test/cli/acp/lifecycle.test.ts +118 -0
  635. package/test/cli/acp/prompt-content.test.ts +97 -0
  636. package/test/cli/acp/skills.test.ts +38 -0
  637. package/test/cli/cmd/tui/aggregate-failures.test.ts +93 -0
  638. package/test/cli/cmd/tui/attention.test.ts +484 -0
  639. package/test/cli/cmd/tui/dialog-workspace-create.test.ts +28 -0
  640. package/test/cli/cmd/tui/model-options.test.ts +30 -0
  641. package/test/cli/cmd/tui/notifications.test.ts +267 -0
  642. package/test/cli/cmd/tui/prompt-history.test.ts +44 -0
  643. package/test/cli/cmd/tui/prompt-part.test.ts +77 -0
  644. package/test/cli/cmd/tui/prompt-traits.test.ts +29 -0
  645. package/test/cli/cmd/tui/provider-options.test.ts +29 -0
  646. package/test/cli/cmd/tui/sync-fixture.tsx +64 -0
  647. package/test/cli/cmd/tui/sync-live-hydration.test.tsx +278 -0
  648. package/test/cli/cmd/tui/sync-undefined-messages.test.tsx +47 -0
  649. package/test/cli/cmd/tui/sync.test.tsx +70 -0
  650. package/test/cli/effect-cmd-instance-als.test.ts +39 -0
  651. package/test/cli/error.test.ts +95 -0
  652. package/test/cli/github-action.test.ts +199 -0
  653. package/test/cli/github-remote.test.ts +90 -0
  654. package/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +631 -0
  655. package/test/cli/help/help-snapshots.test.ts +137 -0
  656. package/test/cli/import.test.ts +54 -0
  657. package/test/cli/mcp-add.test.ts +74 -0
  658. package/test/cli/plugin-auth-picker.test.ts +120 -0
  659. package/test/cli/run/entry.body.test.ts +536 -0
  660. package/test/cli/run/footer.menu.test.ts +43 -0
  661. package/test/cli/run/footer.view.test.tsx +927 -0
  662. package/test/cli/run/permission.shared.test.ts +144 -0
  663. package/test/cli/run/prompt.shared.test.ts +133 -0
  664. package/test/cli/run/question.shared.test.ts +115 -0
  665. package/test/cli/run/run-process.test.ts +84 -0
  666. package/test/cli/run/runtime.boot.test.ts +282 -0
  667. package/test/cli/run/runtime.queue.test.ts +465 -0
  668. package/test/cli/run/runtime.stdin.test.ts +71 -0
  669. package/test/cli/run/scrollback.surface.test.ts +1048 -0
  670. package/test/cli/run/session-data.test.ts +595 -0
  671. package/test/cli/run/session-replay.test.ts +456 -0
  672. package/test/cli/run/session.shared.test.ts +247 -0
  673. package/test/cli/run/stream.test.ts +56 -0
  674. package/test/cli/run/stream.transport.test.ts +2363 -0
  675. package/test/cli/run/subagent-data.test.ts +456 -0
  676. package/test/cli/run/theme.test.ts +152 -0
  677. package/test/cli/run/variant.shared.test.ts +217 -0
  678. package/test/cli/serve/serve-process.test.ts +61 -0
  679. package/test/cli/smokes/read-only.test.ts +115 -0
  680. package/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap +72 -0
  681. package/test/cli/tui/app-lifecycle.test.ts +261 -0
  682. package/test/cli/tui/dialog-prompt.test.tsx +146 -0
  683. package/test/cli/tui/diff-viewer-file-tree-utils.test.ts +323 -0
  684. package/test/cli/tui/diff-viewer-file-tree.test.tsx +197 -0
  685. package/test/cli/tui/diff-viewer.test.tsx +230 -0
  686. package/test/cli/tui/editor-context-zed.test.ts +384 -0
  687. package/test/cli/tui/editor-context.test.tsx +288 -0
  688. package/test/cli/tui/inline-tool-wrap-snapshot.test.tsx +232 -0
  689. package/test/cli/tui/keymap.test.tsx +136 -0
  690. package/test/cli/tui/plugin-add.test.ts +110 -0
  691. package/test/cli/tui/plugin-install.test.ts +87 -0
  692. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  693. package/test/cli/tui/plugin-loader-entrypoint.test.ts +485 -0
  694. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  695. package/test/cli/tui/plugin-loader.test.ts +1332 -0
  696. package/test/cli/tui/plugin-toggle.test.ts +264 -0
  697. package/test/cli/tui/prompt-submit-race.test.ts +98 -0
  698. package/test/cli/tui/revert-diff.test.ts +35 -0
  699. package/test/cli/tui/slot-replace.test.tsx +50 -0
  700. package/test/cli/tui/sync-v2.test.tsx +558 -0
  701. package/test/cli/tui/theme-store.test.ts +76 -0
  702. package/test/cli/tui/thinking.test.ts +36 -0
  703. package/test/cli/tui/thread.test.ts +28 -0
  704. package/test/cli/tui/transcript.test.ts +426 -0
  705. package/test/cli/tui/use-event.test.tsx +145 -0
  706. package/test/config/agent-color.test.ts +47 -0
  707. package/test/config/config.test.ts +1991 -0
  708. package/test/config/entry-name.test.ts +57 -0
  709. package/test/config/fixtures/empty-frontmatter.md +4 -0
  710. package/test/config/fixtures/frontmatter.md +28 -0
  711. package/test/config/fixtures/markdown-header.md +11 -0
  712. package/test/config/fixtures/no-frontmatter.md +1 -0
  713. package/test/config/fixtures/weird-model-id.md +13 -0
  714. package/test/config/lsp.test.ts +69 -0
  715. package/test/config/markdown.test.ts +228 -0
  716. package/test/config/plugin.test.ts +0 -0
  717. package/test/config/tui.test.ts +878 -0
  718. package/test/control-plane/adapters.test.ts +71 -0
  719. package/test/control-plane/workspace.test.ts +1704 -0
  720. package/test/effect/app-runtime-logger.test.ts +105 -0
  721. package/test/effect/config-service.test.ts +65 -0
  722. package/test/effect/instance-state.test.ts +391 -0
  723. package/test/effect/run-service.test.ts +89 -0
  724. package/test/effect/runner.test.ts +514 -0
  725. package/test/effect/runtime-flags.test.ts +373 -0
  726. package/test/fake/account.ts +9 -0
  727. package/test/fake/auth.ts +8 -0
  728. package/test/fake/npm.ts +8 -0
  729. package/test/fake/provider.ts +82 -0
  730. package/test/fake/skill.ts +8 -0
  731. package/test/filesystem/filesystem.test.ts +319 -0
  732. package/test/fixture/agent-plugin.constants.ts +6 -0
  733. package/test/fixture/agent-plugin.ts +12 -0
  734. package/test/fixture/config.ts +23 -0
  735. package/test/fixture/db.ts +11 -0
  736. package/test/fixture/fixture.test.ts +26 -0
  737. package/test/fixture/fixture.ts +224 -0
  738. package/test/fixture/flag.ts +20 -0
  739. package/test/fixture/flock-worker.ts +72 -0
  740. package/test/fixture/lsp/fake-lsp-server.js +249 -0
  741. package/test/fixture/plug-worker.ts +93 -0
  742. package/test/fixture/plugin-meta-worker.ts +19 -0
  743. package/test/fixture/plugin.ts +10 -0
  744. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  745. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  746. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  747. package/test/fixture/skills/index.json +6 -0
  748. package/test/fixture/tui-plugin.ts +355 -0
  749. package/test/fixture/tui-runtime.ts +64 -0
  750. package/test/fixture/tui-sdk.ts +82 -0
  751. package/test/fixture/workspace.ts +30 -0
  752. package/test/fixtures/recordings/session/native-anthropic-tool-loop.json +49 -0
  753. package/test/fixtures/recordings/session/native-openai-oauth-tool-loop.json +45 -0
  754. package/test/fixtures/recordings/session/native-zen-tool-loop.json +49 -0
  755. package/test/format/format.test.ts +228 -0
  756. package/test/git/git.test.ts +178 -0
  757. package/test/ide/ide.test.ts +82 -0
  758. package/test/image/fixtures/picture-5mb-base64.png +0 -0
  759. package/test/image/image.test.ts +123 -0
  760. package/test/installation/installation.test.ts +230 -0
  761. package/test/lib/cli-process.ts +459 -0
  762. package/test/lib/effect.ts +177 -0
  763. package/test/lib/filesystem.ts +10 -0
  764. package/test/lib/llm-server.ts +771 -0
  765. package/test/lib/snapshot.ts +73 -0
  766. package/test/lib/test-provider.ts +37 -0
  767. package/test/lib/websocket.ts +46 -0
  768. package/test/lsp/client.test.ts +493 -0
  769. package/test/lsp/index.test.ts +232 -0
  770. package/test/lsp/launch.test.ts +22 -0
  771. package/test/lsp/lifecycle.test.ts +160 -0
  772. package/test/mcp/auth.test.ts +78 -0
  773. package/test/mcp/headers.test.ts +126 -0
  774. package/test/mcp/lifecycle.test.ts +888 -0
  775. package/test/mcp/oauth-auto-connect.test.ts +236 -0
  776. package/test/mcp/oauth-browser.test.ts +228 -0
  777. package/test/mcp/oauth-callback.test.ts +34 -0
  778. package/test/mcp/oauth-provider.test.ts +61 -0
  779. package/test/patch/patch.test.ts +383 -0
  780. package/test/permission/arity.test.ts +33 -0
  781. package/test/permission/next.test.ts +1176 -0
  782. package/test/permission-task.test.ts +318 -0
  783. package/test/plugin/auth-override.test.ts +105 -0
  784. package/test/plugin/cloudflare.test.ts +68 -0
  785. package/test/plugin/codex.test.ts +247 -0
  786. package/test/plugin/github-copilot-models.test.ts +332 -0
  787. package/test/plugin/install-concurrency.test.ts +140 -0
  788. package/test/plugin/install.test.ts +570 -0
  789. package/test/plugin/loader-shared.test.ts +1303 -0
  790. package/test/plugin/meta.test.ts +137 -0
  791. package/test/plugin/openai-rollout.test.ts +17 -0
  792. package/test/plugin/openai-ws.test.ts +877 -0
  793. package/test/plugin/shared.test.ts +88 -0
  794. package/test/plugin/trigger.test.ts +120 -0
  795. package/test/plugin/workspace-adapter.test.ts +137 -0
  796. package/test/plugin/xai.test.ts +634 -0
  797. package/test/preload.ts +95 -0
  798. package/test/project/instance-bootstrap.test.ts +110 -0
  799. package/test/project/instance.test.ts +245 -0
  800. package/test/project/migrate-global.test.ts +170 -0
  801. package/test/project/project-directory.test.ts +169 -0
  802. package/test/project/project.test.ts +818 -0
  803. package/test/project/vcs.test.ts +336 -0
  804. package/test/project/worktree-remove.test.ts +126 -0
  805. package/test/project/worktree.test.ts +320 -0
  806. package/test/provider/amazon-bedrock.test.ts +360 -0
  807. package/test/provider/cf-ai-gateway-e2e.test.ts +132 -0
  808. package/test/provider/digitalocean.test.ts +123 -0
  809. package/test/provider/gitlab-duo.test.ts +412 -0
  810. package/test/provider/header-timeout.test.ts +233 -0
  811. package/test/provider/model-status.test.ts +61 -0
  812. package/test/provider/provider.test.ts +1793 -0
  813. package/test/provider/transform.test.ts +3937 -0
  814. package/test/pty/pty-shell.test.ts +102 -0
  815. package/test/question/question.test.ts +465 -0
  816. package/test/reference/reference.test.ts +310 -0
  817. package/test/server/AGENTS.md +15 -0
  818. package/test/server/auth.test.ts +59 -0
  819. package/test/server/global-bus.ts +31 -0
  820. package/test/server/global-session-list.test.ts +107 -0
  821. package/test/server/httpapi-authorization.test.ts +174 -0
  822. package/test/server/httpapi-compression.test.ts +154 -0
  823. package/test/server/httpapi-config.test.ts +113 -0
  824. package/test/server/httpapi-control-plane.test.ts +63 -0
  825. package/test/server/httpapi-cors-vary.test.ts +66 -0
  826. package/test/server/httpapi-cors.test.ts +122 -0
  827. package/test/server/httpapi-error-middleware.test.ts +96 -0
  828. package/test/server/httpapi-event.test.ts +97 -0
  829. package/test/server/httpapi-exercise/assertions.ts +64 -0
  830. package/test/server/httpapi-exercise/backend.ts +144 -0
  831. package/test/server/httpapi-exercise/dsl.ts +210 -0
  832. package/test/server/httpapi-exercise/environment.ts +40 -0
  833. package/test/server/httpapi-exercise/index.ts +1535 -0
  834. package/test/server/httpapi-exercise/report.ts +66 -0
  835. package/test/server/httpapi-exercise/routing.ts +96 -0
  836. package/test/server/httpapi-exercise/runner.ts +267 -0
  837. package/test/server/httpapi-exercise/runtime.ts +52 -0
  838. package/test/server/httpapi-exercise/types.ts +123 -0
  839. package/test/server/httpapi-experimental.test.ts +300 -0
  840. package/test/server/httpapi-file.test.ts +76 -0
  841. package/test/server/httpapi-global.test.ts +66 -0
  842. package/test/server/httpapi-instance-context.test.ts +347 -0
  843. package/test/server/httpapi-instance-route-auth.test.ts +84 -0
  844. package/test/server/httpapi-instance.test.ts +265 -0
  845. package/test/server/httpapi-layer.ts +33 -0
  846. package/test/server/httpapi-listen.test.ts +415 -0
  847. package/test/server/httpapi-mcp-oauth.test.ts +73 -0
  848. package/test/server/httpapi-mcp.test.ts +226 -0
  849. package/test/server/httpapi-mdns.test.ts +82 -0
  850. package/test/server/httpapi-promptasync-context.test.ts +222 -0
  851. package/test/server/httpapi-provider.test.ts +403 -0
  852. package/test/server/httpapi-pty.test.ts +275 -0
  853. package/test/server/httpapi-public-openapi.test.ts +297 -0
  854. package/test/server/httpapi-query-schema-drift.test.ts +330 -0
  855. package/test/server/httpapi-schema-error-body.test.ts +165 -0
  856. package/test/server/httpapi-sdk.test.ts +909 -0
  857. package/test/server/httpapi-session.test.ts +1013 -0
  858. package/test/server/httpapi-sync.test.ts +154 -0
  859. package/test/server/httpapi-ui.test.ts +456 -0
  860. package/test/server/httpapi-v2-location.test.ts +85 -0
  861. package/test/server/httpapi-workspace-routing.test.ts +554 -0
  862. package/test/server/httpapi-workspace.test.ts +515 -0
  863. package/test/server/negative-tokens-regression.test.ts +83 -0
  864. package/test/server/project-copy.test.ts +101 -0
  865. package/test/server/project-init-git.test.ts +117 -0
  866. package/test/server/proxy-util.test.ts +113 -0
  867. package/test/server/sdk-error-shape.test.ts +84 -0
  868. package/test/server/sdk-v1-smoke.test.ts +60 -0
  869. package/test/server/session-actions.test.ts +112 -0
  870. package/test/server/session-diff-missing-patch.test.ts +99 -0
  871. package/test/server/session-list.test.ts +314 -0
  872. package/test/server/session-messages.test.ts +182 -0
  873. package/test/server/session-select.test.ts +69 -0
  874. package/test/server/workspace-proxy.test.ts +181 -0
  875. package/test/server/workspace-routing.test.ts +94 -0
  876. package/test/server/worktree-endpoint-repro.test.ts +307 -0
  877. package/test/session/compaction.test.ts +1835 -0
  878. package/test/session/instruction.test.ts +256 -0
  879. package/test/session/llm-native-recorded.test.ts +433 -0
  880. package/test/session/llm-native.test.ts +760 -0
  881. package/test/session/llm.test.ts +1932 -0
  882. package/test/session/message-v2.test.ts +1661 -0
  883. package/test/session/messages-pagination.test.ts +1059 -0
  884. package/test/session/processor-effect.test.ts +1101 -0
  885. package/test/session/prompt.test.ts +2318 -0
  886. package/test/session/retry.test.ts +439 -0
  887. package/test/session/revert-compact.test.ts +642 -0
  888. package/test/session/schema-decoding.test.ts +313 -0
  889. package/test/session/session-schema.test.ts +78 -0
  890. package/test/session/session.test.ts +251 -0
  891. package/test/session/snapshot-tool-race.test.ts +280 -0
  892. package/test/session/structured-output-integration.test.ts +235 -0
  893. package/test/session/structured-output.test.ts +387 -0
  894. package/test/session/system.test.ts +84 -0
  895. package/test/share/share-next.test.ts +344 -0
  896. package/test/shell/shell.test.ts +99 -0
  897. package/test/skill/discovery.test.ts +139 -0
  898. package/test/skill/skill.test.ts +571 -0
  899. package/test/snapshot/snapshot.test.ts +1121 -0
  900. package/test/storage/storage.test.ts +296 -0
  901. package/test/storage/workspace-time-migration.test.ts +50 -0
  902. package/test/tool/__snapshots__/parameters.test.ts.snap +484 -0
  903. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  904. package/test/tool/apply_patch.test.ts +533 -0
  905. package/test/tool/edit.test.ts +578 -0
  906. package/test/tool/external-directory.test.ts +155 -0
  907. package/test/tool/fixtures/large-image.png +0 -0
  908. package/test/tool/fixtures/models-api.json +117299 -0
  909. package/test/tool/glob.test.ts +188 -0
  910. package/test/tool/grep.test.ts +266 -0
  911. package/test/tool/lsp.test.ts +181 -0
  912. package/test/tool/parameters.test.ts +293 -0
  913. package/test/tool/question.test.ts +138 -0
  914. package/test/tool/read.test.ts +654 -0
  915. package/test/tool/registry.test.ts +539 -0
  916. package/test/tool/shell.test.ts +1238 -0
  917. package/test/tool/skill.test.ts +132 -0
  918. package/test/tool/task.test.ts +901 -0
  919. package/test/tool/tool-define.test.ts +153 -0
  920. package/test/tool/truncation.test.ts +266 -0
  921. package/test/tool/webfetch.test.ts +113 -0
  922. package/test/tool/websearch.test.ts +99 -0
  923. package/test/tool/write.test.ts +276 -0
  924. package/test/util/data-url.test.ts +14 -0
  925. package/test/util/error.test.ts +64 -0
  926. package/test/util/filesystem.test.ts +656 -0
  927. package/test/util/format.test.ts +59 -0
  928. package/test/util/glob.test.ts +164 -0
  929. package/test/util/iife.test.ts +36 -0
  930. package/test/util/lazy.test.ts +50 -0
  931. package/test/util/log.test.ts +77 -0
  932. package/test/util/module.test.ts +59 -0
  933. package/test/util/process.test.ts +128 -0
  934. package/test/util/repository.test.ts +93 -0
  935. package/test/util/timeout.test.ts +21 -0
  936. package/test/util/wildcard.test.ts +90 -0
  937. package/test/v2/session-message-updater.test.ts +270 -0
  938. package/tsconfig.json +17 -0
@@ -0,0 +1,1755 @@
1
+ import { PermissionV1 } from "@opencode-ai/core/v1/permission"
2
+ import path from "path"
3
+ import { SessionV1 } from "@opencode-ai/core/v1/session"
4
+ import os from "os"
5
+ import { SessionID, MessageID, PartID } from "./schema"
6
+ import { MessageV2 } from "./message-v2"
7
+ import { Log } from "@opencode-ai/core/util/log"
8
+ import { SessionRevert } from "./revert"
9
+ import { Session } from "./session"
10
+ import { Agent } from "../agent/agent"
11
+ import { Provider } from "@/provider/provider"
12
+
13
+ import { type Tool as AITool, tool, jsonSchema } from "ai"
14
+ import type { JSONSchema7 } from "@ai-sdk/provider"
15
+ import { SessionCompaction } from "./compaction"
16
+ import { SystemPrompt } from "./system"
17
+ import { Instruction } from "./instruction"
18
+ import { Plugin } from "../plugin"
19
+ import MAX_STEPS from "../session/prompt/max-steps.txt"
20
+ import { ToolRegistry } from "@/tool/registry"
21
+ import { MCP } from "../mcp"
22
+ import { LSP } from "@/lsp/lsp"
23
+ import { ulid } from "ulid"
24
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
25
+ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
26
+ import * as Stream from "effect/Stream"
27
+ import { Command } from "../command"
28
+ import { pathToFileURL, fileURLToPath } from "url"
29
+ import { Config } from "@/config/config"
30
+ import { ConfigMarkdown } from "@/config/markdown"
31
+ import { SessionSummary } from "./summary"
32
+ import { NamedError } from "@opencode-ai/core/util/error"
33
+ import { SessionProcessor } from "./processor"
34
+ import { Tool } from "@/tool/tool"
35
+ import { Permission } from "@/permission"
36
+ import { SessionStatus } from "./status"
37
+ import { LLM } from "./llm"
38
+ import { Shell } from "@/shell/shell"
39
+ import { ShellID } from "@/tool/shell/id"
40
+ import { FSUtil } from "@opencode-ai/core/fs-util"
41
+ import { Truncate } from "@/tool/truncate"
42
+ import { Image } from "@/image/image"
43
+ import { decodeDataUrl } from "@/util/data-url"
44
+ import { Process } from "@/util/process"
45
+ import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
46
+ import * as EffectLogger from "@opencode-ai/core/effect/logger"
47
+ import { InstanceState } from "@/effect/instance-state"
48
+ import { TaskTool, type TaskPromptOps } from "@/tool/task"
49
+ import { SessionRunState } from "./run-state"
50
+ import { RuntimeFlags } from "@/effect/runtime-flags"
51
+ import { EventV2Bridge } from "@/event-v2-bridge"
52
+ import { Database } from "@opencode-ai/core/database/database"
53
+ import { SessionEvent } from "@opencode-ai/core/session/event"
54
+ import { SessionMessage } from "@opencode-ai/core/session/message"
55
+ import { ModelV2 } from "@opencode-ai/core/model"
56
+ import { ProviderV2 } from "@opencode-ai/core/provider"
57
+ import { AgentAttachment, FileAttachment, Prompt, ReferenceAttachment, Source } from "@opencode-ai/core/session/prompt"
58
+ import { Reference } from "@/reference/reference"
59
+ import * as DateTime from "effect/DateTime"
60
+ import { eq } from "drizzle-orm"
61
+ import { SessionTable } from "@opencode-ai/core/session/sql"
62
+ import { referencePromptMetadata, referenceTextPart } from "./prompt/reference"
63
+ import { SessionReminders } from "./reminders"
64
+ import { SessionTools } from "./tools"
65
+ import { LLMEvent } from "@opencode-ai/llm"
66
+
67
+ // @ts-ignore
68
+ globalThis.AI_SDK_LOG_WARNINGS = false
69
+
70
+ const decodeMessageInfo = Schema.decodeUnknownExit(SessionV1.Info)
71
+ const decodeMessagePart = Schema.decodeUnknownExit(SessionV1.Part)
72
+
73
+ const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format.
74
+
75
+ IMPORTANT:
76
+ - You MUST call this tool exactly once at the end of your response
77
+ - The input must be valid JSON matching the required schema
78
+ - Complete all necessary research and tool calls BEFORE calling this tool
79
+ - This tool provides your final answer - no further actions are taken after calling it`
80
+
81
+ const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.`
82
+
83
+ const log = Log.create({ service: "session.prompt" })
84
+ const elog = EffectLogger.create({ service: "session.prompt" })
85
+
86
+ function isOrphanedInterruptedTool(part: SessionV1.ToolPart) {
87
+ // cleanup() marks abandoned tool_use blocks this way after retries/aborts.
88
+ // They are not pending work and must not trigger an assistant-prefill request.
89
+ return part.state.status === "error" && part.state.metadata?.interrupted === true
90
+ }
91
+
92
+ export interface Interface {
93
+ readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
94
+ readonly prompt: (input: PromptInput) => Effect.Effect<SessionV1.WithParts, Image.Error>
95
+ readonly loop: (input: LoopInput) => Effect.Effect<SessionV1.WithParts>
96
+ readonly shell: (input: ShellInput) => Effect.Effect<SessionV1.WithParts, Session.BusyError>
97
+ readonly command: (input: CommandInput) => Effect.Effect<SessionV1.WithParts, Image.Error>
98
+ readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
99
+ }
100
+
101
+ export class Service extends Context.Service<Service, Interface>()("@opencode/SessionPrompt") {}
102
+
103
+ export const layer = Layer.effect(
104
+ Service,
105
+ Effect.gen(function* () {
106
+ const status = yield* SessionStatus.Service
107
+ const sessions = yield* Session.Service
108
+ const agents = yield* Agent.Service
109
+ const provider = yield* Provider.Service
110
+ const processor = yield* SessionProcessor.Service
111
+ const compaction = yield* SessionCompaction.Service
112
+ const plugin = yield* Plugin.Service
113
+ const commands = yield* Command.Service
114
+ const config = yield* Config.Service
115
+ const permission = yield* Permission.Service
116
+ const fsys = yield* FSUtil.Service
117
+ const mcp = yield* MCP.Service
118
+ const lsp = yield* LSP.Service
119
+ const registry = yield* ToolRegistry.Service
120
+ const truncate = yield* Truncate.Service
121
+ const image = yield* Image.Service
122
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
123
+ const scope = yield* Scope.Scope
124
+ const instruction = yield* Instruction.Service
125
+ const state = yield* SessionRunState.Service
126
+ const revert = yield* SessionRevert.Service
127
+ const summary = yield* SessionSummary.Service
128
+ const sys = yield* SystemPrompt.Service
129
+ const llm = yield* LLM.Service
130
+ const references = yield* Reference.Service
131
+ const events = yield* EventV2Bridge.Service
132
+ const flags = yield* RuntimeFlags.Service
133
+ const database = yield* Database.Service
134
+ const { db } = database
135
+ const ops = Effect.fn("SessionPrompt.ops")(function* () {
136
+ return {
137
+ cancel: (sessionID: SessionID) => cancel(sessionID),
138
+ resolvePromptParts: (template: string) => resolvePromptParts(template),
139
+ prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)),
140
+ } satisfies TaskPromptOps
141
+ })
142
+
143
+ const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
144
+ yield* elog.info("cancel", { sessionID })
145
+ yield* state.cancel(sessionID)
146
+ })
147
+
148
+ const resolveReferenceParts = Effect.fnUntraced(function* (template: string) {
149
+ const parts: Types.DeepMutable<PromptInput["parts"]> = []
150
+ const seen = new Set<string>()
151
+ yield* Effect.forEach(
152
+ ConfigMarkdown.files(template),
153
+ Effect.fnUntraced(function* (match) {
154
+ const name = match[1]
155
+ if (!name) return
156
+ const alias = name.split("/")[0]
157
+ if (!alias || seen.has(alias)) return
158
+ const reference = yield* references.get(alias)
159
+ if (!reference) return
160
+ seen.add(alias)
161
+
162
+ const start = match.index ?? 0
163
+ const source = { value: match[0], start, end: start + match[0].length }
164
+ if (reference.kind === "invalid") {
165
+ parts.push(referenceTextPart({ reference, source }))
166
+ return
167
+ }
168
+
169
+ yield* references.ensure(reference.path)
170
+ parts.push({
171
+ type: "file",
172
+ url: pathToFileURL(reference.path).href,
173
+ filename: alias,
174
+ mime: "application/x-directory",
175
+ source: { type: "file", text: source, path: alias },
176
+ })
177
+ }),
178
+ { concurrency: 1, discard: true },
179
+ )
180
+ return parts
181
+ })
182
+
183
+ const resolvePromptParts = Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) {
184
+ const ctx = yield* InstanceState.context
185
+ const parts: Types.DeepMutable<PromptInput["parts"]> = [
186
+ { type: "text", text: template },
187
+ ...(yield* resolveReferenceParts(template)),
188
+ ]
189
+ const files = ConfigMarkdown.files(template)
190
+ const seen = new Set<string>()
191
+ yield* Effect.forEach(
192
+ files,
193
+ Effect.fnUntraced(function* (match) {
194
+ const name = match[1]
195
+ if (!name) return
196
+ if (seen.has(name)) return
197
+ seen.add(name)
198
+
199
+ const slash = name.indexOf("/")
200
+ const alias = slash === -1 ? name : name.slice(0, slash)
201
+ if (yield* references.get(alias)) return
202
+
203
+ const filepath = name.startsWith("~/")
204
+ ? path.join(os.homedir(), name.slice(2))
205
+ : path.resolve(ctx.worktree, name)
206
+
207
+ const info = yield* fsys.stat(filepath).pipe(Effect.option)
208
+ if (Option.isNone(info)) {
209
+ const found = yield* agents.get(name)
210
+ if (found) parts.push({ type: "agent", name: found.name })
211
+ return
212
+ }
213
+ const stat = info.value
214
+ parts.push({
215
+ type: "file",
216
+ url: pathToFileURL(filepath).href,
217
+ filename: name,
218
+ mime: stat.type === "Directory" ? "application/x-directory" : "text/plain",
219
+ })
220
+ }),
221
+ { concurrency: "unbounded", discard: true },
222
+ )
223
+ return parts
224
+ })
225
+
226
+ const title = Effect.fn("SessionPrompt.ensureTitle")(function* (input: {
227
+ session: Session.Info
228
+ history: SessionV1.WithParts[]
229
+ providerID: ProviderV2.ID
230
+ modelID: ModelV2.ID
231
+ }) {
232
+ if (input.session.parentID) return
233
+ if (!Session.isDefaultTitle(input.session.title)) return
234
+
235
+ const real = (m: SessionV1.WithParts) =>
236
+ m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)
237
+ const idx = input.history.findIndex(real)
238
+ if (idx === -1) return
239
+ if (input.history.filter(real).length !== 1) return
240
+
241
+ const context = input.history.slice(0, idx + 1)
242
+ const firstUser = context[idx]
243
+ if (!firstUser || firstUser.info.role !== "user") return
244
+ const firstInfo = firstUser.info
245
+
246
+ const subtasks = firstUser.parts.filter((p): p is SessionV1.SubtaskPart => p.type === "subtask")
247
+ const onlySubtasks = subtasks.length > 0 && firstUser.parts.every((p) => p.type === "subtask")
248
+
249
+ const ag = yield* agents.get("title")
250
+ if (!ag) return
251
+ const mdl = ag.model
252
+ ? yield* provider.getModel(ag.model.providerID, ag.model.modelID)
253
+ : ((yield* provider.getSmallModel(input.providerID)) ??
254
+ (yield* provider.getModel(input.providerID, input.modelID)))
255
+ const msgs = onlySubtasks
256
+ ? [{ role: "user" as const, content: subtasks.map((p) => p.prompt).join("\n") }]
257
+ : yield* MessageV2.toModelMessagesEffect(context, mdl)
258
+ const text = yield* llm
259
+ .stream({
260
+ agent: ag,
261
+ user: firstInfo,
262
+ system: [],
263
+ small: true,
264
+ tools: {},
265
+ model: mdl,
266
+ sessionID: input.session.id,
267
+ retries: 2,
268
+ messages: [{ role: "user", content: "Generate a title for this conversation:\n" }, ...msgs],
269
+ })
270
+ .pipe(
271
+ Stream.filter(LLMEvent.is.textDelta),
272
+ Stream.map((e) => e.text),
273
+ Stream.mkString,
274
+ Effect.orDie,
275
+ )
276
+ const cleaned = text
277
+ .replace(/<think>[\s\S]*?<\/think>\s*/g, "")
278
+ .split("\n")
279
+ .map((line) => line.trim())
280
+ .find((line) => line.length > 0)
281
+ if (!cleaned) return
282
+ const t = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
283
+ yield* sessions
284
+ .setTitle({ sessionID: input.session.id, title: t })
285
+ .pipe(Effect.catchCause((cause) => elog.error("failed to generate title", { error: Cause.squash(cause) })))
286
+ })
287
+
288
+ const handleSubtask = Effect.fn("SessionPrompt.handleSubtask")(function* (input: {
289
+ task: SessionV1.SubtaskPart
290
+ model: Provider.Model
291
+ lastUser: SessionV1.User
292
+ sessionID: SessionID
293
+ session: Session.Info
294
+ msgs: SessionV1.WithParts[]
295
+ }) {
296
+ const { task, model, lastUser, sessionID, session, msgs } = input
297
+ const ctx = yield* InstanceState.context
298
+ const promptOps = yield* ops()
299
+ const { task: taskTool } = yield* registry.named()
300
+ const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model
301
+ const assistantMessage: SessionV1.Assistant = yield* sessions.updateMessage({
302
+ id: MessageID.ascending(),
303
+ role: "assistant",
304
+ parentID: lastUser.id,
305
+ sessionID,
306
+ mode: task.agent,
307
+ agent: task.agent,
308
+ variant: lastUser.model.variant,
309
+ path: { cwd: ctx.directory, root: ctx.worktree },
310
+ cost: 0,
311
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
312
+ modelID: taskModel.id,
313
+ providerID: taskModel.providerID,
314
+ time: { created: Date.now() },
315
+ })
316
+ let part: SessionV1.ToolPart = yield* sessions.updatePart({
317
+ id: PartID.ascending(),
318
+ messageID: assistantMessage.id,
319
+ sessionID: assistantMessage.sessionID,
320
+ type: "tool",
321
+ callID: ulid(),
322
+ tool: TaskTool.id,
323
+ state: {
324
+ status: "running",
325
+ input: {
326
+ prompt: task.prompt,
327
+ description: task.description,
328
+ subagent_type: task.agent,
329
+ command: task.command,
330
+ },
331
+ time: { start: Date.now() },
332
+ },
333
+ })
334
+ const taskArgs = {
335
+ prompt: task.prompt,
336
+ description: task.description,
337
+ subagent_type: task.agent,
338
+ command: task.command,
339
+ }
340
+ yield* plugin.trigger(
341
+ "tool.execute.before",
342
+ { tool: TaskTool.id, sessionID, callID: part.id },
343
+ { args: taskArgs },
344
+ )
345
+
346
+ const taskAgent = yield* agents.get(task.agent)
347
+ if (!taskAgent) {
348
+ const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
349
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
350
+ const error = new NamedError.Unknown({ message: `Agent not found: "${task.agent}".${hint}` })
351
+ yield* events.publish(Session.Event.Error, { sessionID, error: error.toObject() })
352
+ throw error
353
+ }
354
+
355
+ let error: Error | undefined
356
+ const taskAbort = new AbortController()
357
+ const result = yield* taskTool
358
+ .execute(taskArgs, {
359
+ agent: task.agent,
360
+ messageID: assistantMessage.id,
361
+ sessionID,
362
+ abort: taskAbort.signal,
363
+ callID: part.callID,
364
+ extra: { bypassAgentCheck: true, promptOps },
365
+ messages: msgs,
366
+ metadata: (val: { title?: string; metadata?: Record<string, any> }) =>
367
+ Effect.gen(function* () {
368
+ part = yield* sessions.updatePart({
369
+ ...part,
370
+ type: "tool",
371
+ state: { ...part.state, ...val },
372
+ } satisfies SessionV1.ToolPart)
373
+ }),
374
+ ask: (req: any) =>
375
+ permission
376
+ .ask({
377
+ ...req,
378
+ sessionID,
379
+ ruleset: Permission.merge(taskAgent.permission, session.permission ?? []),
380
+ })
381
+ .pipe(Effect.orDie),
382
+ })
383
+ .pipe(
384
+ Effect.catchCause((cause) => {
385
+ const defect = Cause.squash(cause)
386
+ error = defect instanceof Error ? defect : new Error(String(defect))
387
+ log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
388
+ return Effect.void
389
+ }),
390
+ Effect.onInterrupt(() =>
391
+ Effect.gen(function* () {
392
+ taskAbort.abort()
393
+ assistantMessage.finish = "tool-calls"
394
+ assistantMessage.time.completed = Date.now()
395
+ yield* sessions.updateMessage(assistantMessage)
396
+ if (part.state.status === "running") {
397
+ yield* sessions.updatePart({
398
+ ...part,
399
+ state: {
400
+ status: "error",
401
+ error: "Cancelled",
402
+ time: { start: part.state.time.start, end: Date.now() },
403
+ metadata: part.state.metadata,
404
+ input: part.state.input,
405
+ },
406
+ } satisfies SessionV1.ToolPart)
407
+ }
408
+ }),
409
+ ),
410
+ )
411
+
412
+ const attachments = result?.attachments?.map((attachment) => ({
413
+ ...attachment,
414
+ id: PartID.ascending(),
415
+ sessionID,
416
+ messageID: assistantMessage.id,
417
+ }))
418
+
419
+ yield* plugin.trigger(
420
+ "tool.execute.after",
421
+ { tool: TaskTool.id, sessionID, callID: part.id, args: taskArgs },
422
+ result,
423
+ )
424
+
425
+ assistantMessage.finish = "tool-calls"
426
+ assistantMessage.time.completed = Date.now()
427
+ yield* sessions.updateMessage(assistantMessage)
428
+
429
+ if (result && part.state.status === "running") {
430
+ yield* sessions.updatePart({
431
+ ...part,
432
+ state: {
433
+ status: "completed",
434
+ input: part.state.input,
435
+ title: result.title,
436
+ metadata: result.metadata,
437
+ output: result.output,
438
+ attachments,
439
+ time: { ...part.state.time, end: Date.now() },
440
+ },
441
+ } satisfies SessionV1.ToolPart)
442
+ }
443
+
444
+ if (!result) {
445
+ yield* sessions.updatePart({
446
+ ...part,
447
+ state: {
448
+ status: "error",
449
+ error: error ? `Tool execution failed: ${error.message}` : "Tool execution failed",
450
+ time: {
451
+ start: part.state.status === "running" ? part.state.time.start : Date.now(),
452
+ end: Date.now(),
453
+ },
454
+ metadata: part.state.status === "pending" ? undefined : part.state.metadata,
455
+ input: part.state.input,
456
+ },
457
+ } satisfies SessionV1.ToolPart)
458
+ }
459
+
460
+ if (!task.command) return
461
+
462
+ const summaryUserMsg: SessionV1.User = {
463
+ id: MessageID.ascending(),
464
+ sessionID,
465
+ role: "user",
466
+ time: { created: Date.now() },
467
+ agent: lastUser.agent,
468
+ model: lastUser.model,
469
+ }
470
+ yield* sessions.updateMessage(summaryUserMsg)
471
+ yield* sessions.updatePart({
472
+ id: PartID.ascending(),
473
+ messageID: summaryUserMsg.id,
474
+ sessionID,
475
+ type: "text",
476
+ text: "Summarize the task tool output above and continue with your task.",
477
+ synthetic: true,
478
+ } satisfies SessionV1.TextPart)
479
+ })
480
+
481
+ const shellImpl = Effect.fn("SessionPrompt.shellImpl")(function* (input: ShellInput, ready?: Latch.Latch) {
482
+ return yield* Effect.uninterruptibleMask((restore) =>
483
+ Effect.gen(function* () {
484
+ const markReady = ready ? ready.open.pipe(Effect.asVoid) : Effect.void
485
+ const { msg, part, cwd } = yield* Effect.gen(function* () {
486
+ const ctx = yield* InstanceState.context
487
+ const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
488
+ if (session.revert) {
489
+ yield* revert.cleanup(session)
490
+ }
491
+ const agent = yield* agents.get(input.agent)
492
+ if (!agent) {
493
+ const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
494
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
495
+ const error = new NamedError.Unknown({ message: `Agent not found: "${input.agent}".${hint}` })
496
+ yield* events.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
497
+ throw error
498
+ }
499
+ const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID))
500
+ const userMsg: SessionV1.User = {
501
+ id: input.messageID ?? MessageID.ascending(),
502
+ sessionID: input.sessionID,
503
+ time: { created: Date.now() },
504
+ role: "user",
505
+ agent: input.agent,
506
+ model: { providerID: model.providerID, modelID: model.modelID },
507
+ }
508
+ yield* sessions.updateMessage(userMsg)
509
+ const userPart: SessionV1.Part = {
510
+ type: "text",
511
+ id: PartID.ascending(),
512
+ messageID: userMsg.id,
513
+ sessionID: input.sessionID,
514
+ text: "The following tool was executed by the user",
515
+ synthetic: true,
516
+ }
517
+ yield* sessions.updatePart(userPart)
518
+
519
+ const msg: SessionV1.Assistant = {
520
+ id: MessageID.ascending(),
521
+ sessionID: input.sessionID,
522
+ parentID: userMsg.id,
523
+ mode: input.agent,
524
+ agent: input.agent,
525
+ cost: 0,
526
+ path: { cwd: ctx.directory, root: ctx.worktree },
527
+ time: { created: Date.now() },
528
+ role: "assistant",
529
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
530
+ modelID: model.modelID,
531
+ providerID: model.providerID,
532
+ }
533
+ yield* sessions.updateMessage(msg)
534
+ const started = Date.now()
535
+ const part: SessionV1.ToolPart = {
536
+ type: "tool",
537
+ id: PartID.ascending(),
538
+ messageID: msg.id,
539
+ sessionID: input.sessionID,
540
+ tool: ShellID.ToolID,
541
+ callID: ulid(),
542
+ state: {
543
+ status: "running",
544
+ time: { start: started },
545
+ input: { command: input.command },
546
+ },
547
+ }
548
+ yield* sessions.updatePart(part)
549
+ if (flags.experimentalEventSystem) {
550
+ yield* events.publish(SessionEvent.Shell.Started, {
551
+ sessionID: input.sessionID,
552
+ messageID: SessionMessage.ID.create(),
553
+ timestamp: DateTime.makeUnsafe(started),
554
+ callID: part.callID,
555
+ command: input.command,
556
+ })
557
+ }
558
+ return { msg, part, cwd: ctx.directory }
559
+ }).pipe(Effect.ensuring(markReady))
560
+
561
+ const cfg = yield* config.get()
562
+ const sh = Shell.preferred(cfg.shell)
563
+ const args = Shell.args(sh, input.command, cwd)
564
+ let output = ""
565
+ let aborted = false
566
+
567
+ const finish = Effect.uninterruptible(
568
+ Effect.gen(function* () {
569
+ if (aborted) {
570
+ output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n")
571
+ }
572
+ const completed = Date.now()
573
+ if (flags.experimentalEventSystem) {
574
+ yield* events.publish(SessionEvent.Shell.Ended, {
575
+ sessionID: input.sessionID,
576
+ timestamp: DateTime.makeUnsafe(completed),
577
+ callID: part.callID,
578
+ output,
579
+ })
580
+ }
581
+ if (!msg.time.completed) {
582
+ msg.time.completed = completed
583
+ yield* sessions.updateMessage(msg)
584
+ }
585
+ if (part.state.status === "running") {
586
+ part.state = {
587
+ status: "completed",
588
+ time: { ...part.state.time, end: completed },
589
+ input: part.state.input,
590
+ title: "",
591
+ metadata: { output, description: "" },
592
+ output,
593
+ }
594
+ yield* sessions.updatePart(part)
595
+ }
596
+ }),
597
+ )
598
+
599
+ const exit = yield* restore(
600
+ Effect.gen(function* () {
601
+ const shellEnv = yield* plugin.trigger(
602
+ "shell.env",
603
+ { cwd, sessionID: input.sessionID, callID: part.callID },
604
+ { env: {} },
605
+ )
606
+ const cmd = ChildProcess.make(sh, args, {
607
+ cwd,
608
+ extendEnv: true,
609
+ env: { ...shellEnv.env, TERM: "dumb" },
610
+ stdin: "ignore",
611
+ forceKillAfter: "3 seconds",
612
+ })
613
+ const handle = yield* spawner.spawn(cmd)
614
+ yield* Stream.runForEach(Stream.decodeText(handle.all), (chunk) =>
615
+ Effect.gen(function* () {
616
+ output += chunk
617
+ if (part.state.status === "running") {
618
+ part.state.metadata = { output, description: "" }
619
+ yield* sessions.updatePart(part)
620
+ }
621
+ }),
622
+ )
623
+ yield* handle.exitCode
624
+ }).pipe(Effect.scoped, Effect.orDie),
625
+ ).pipe(Effect.exit)
626
+
627
+ if (Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause) && !Cause.hasDies(exit.cause)) {
628
+ aborted = true
629
+ }
630
+ yield* finish
631
+
632
+ if (Exit.isFailure(exit) && !aborted && !Cause.hasInterruptsOnly(exit.cause)) {
633
+ return yield* Effect.failCause(exit.cause)
634
+ }
635
+
636
+ return { info: msg, parts: [part] }
637
+ }),
638
+ )
639
+ })
640
+
641
+ const getModel = Effect.fn("SessionPrompt.getModel")(function* (
642
+ providerID: ProviderV2.ID,
643
+ modelID: ModelV2.ID,
644
+ sessionID: SessionID,
645
+ ) {
646
+ const exit = yield* provider.getModel(providerID, modelID).pipe(Effect.exit)
647
+ if (Exit.isSuccess(exit)) return exit.value
648
+ const err = Cause.squash(exit.cause)
649
+ if (Provider.ModelNotFoundError.isInstance(err)) {
650
+ const hint = err.suggestions?.length ? ` Did you mean: ${err.suggestions.join(", ")}?` : ""
651
+ yield* events.publish(Session.Event.Error, {
652
+ sessionID,
653
+ error: new NamedError.Unknown({
654
+ message: `Model not found: ${err.providerID}/${err.modelID}.${hint}`,
655
+ }).toObject(),
656
+ })
657
+ }
658
+ return yield* Effect.die(err)
659
+ })
660
+
661
+ const currentModel = Effect.fnUntraced(function* (sessionID: SessionID) {
662
+ const current = yield* db
663
+ .select({ model: SessionTable.model })
664
+ .from(SessionTable)
665
+ .where(eq(SessionTable.id, sessionID))
666
+ .get()
667
+ .pipe(Effect.orDie)
668
+ if (current?.model) {
669
+ return {
670
+ providerID: ProviderV2.ID.make(current.model.providerID),
671
+ modelID: ModelV2.ID.make(current.model.id),
672
+ ...(current.model.variant && current.model.variant !== "default" ? { variant: current.model.variant } : {}),
673
+ }
674
+ }
675
+ const match = yield* sessions
676
+ .findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model)
677
+ .pipe(Effect.orDie)
678
+ if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model
679
+ return yield* provider.defaultModel().pipe(Effect.orDie)
680
+ })
681
+
682
+ const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
683
+ const agentName = input.agent
684
+ const ag = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo()
685
+ if (!ag) {
686
+ const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
687
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
688
+ const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
689
+ yield* events.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
690
+ throw error
691
+ }
692
+
693
+ const current = yield* db
694
+ .select({ agent: SessionTable.agent, model: SessionTable.model })
695
+ .from(SessionTable)
696
+ .where(eq(SessionTable.id, input.sessionID))
697
+ .get()
698
+ .pipe(Effect.orDie)
699
+ const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID))
700
+ const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID
701
+ const full =
702
+ !input.variant && ag.variant && same
703
+ ? yield* provider
704
+ .getModel(model.providerID, model.modelID)
705
+ .pipe(Effect.catchIf(Provider.ModelNotFoundError.isInstance, () => Effect.succeed(undefined)))
706
+ : undefined
707
+ const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined)
708
+
709
+ const info: SessionV1.User = {
710
+ id: input.messageID ?? MessageID.ascending(),
711
+ role: "user",
712
+ sessionID: input.sessionID,
713
+ time: { created: Date.now() },
714
+ tools: input.tools,
715
+ agent: ag.name,
716
+ model: {
717
+ providerID: model.providerID,
718
+ modelID: model.modelID,
719
+ variant,
720
+ },
721
+ system: input.system,
722
+ format: input.format,
723
+ }
724
+
725
+ if (current?.agent !== info.agent) {
726
+ yield* events.publish(SessionEvent.AgentSwitched, {
727
+ sessionID: input.sessionID,
728
+ messageID: SessionMessage.ID.create(),
729
+ timestamp: DateTime.makeUnsafe(info.time.created),
730
+ agent: info.agent,
731
+ })
732
+ }
733
+ if (
734
+ current?.model?.providerID !== info.model.providerID ||
735
+ current.model.id !== info.model.modelID ||
736
+ (current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant
737
+ ) {
738
+ yield* events.publish(SessionEvent.ModelSwitched, {
739
+ sessionID: input.sessionID,
740
+ messageID: SessionMessage.ID.create(),
741
+ timestamp: DateTime.makeUnsafe(info.time.created),
742
+ model: {
743
+ id: ModelV2.ID.make(info.model.modelID),
744
+ providerID: ProviderV2.ID.make(info.model.providerID),
745
+ variant: ModelV2.VariantID.make(info.model.variant ?? "default"),
746
+ },
747
+ })
748
+ }
749
+
750
+ yield* Effect.addFinalizer(() => instruction.clear(info.id))
751
+
752
+ type Draft<T> = T extends SessionV1.Part ? Omit<T, "id"> & { id?: string } : never
753
+ const assign = (part: Draft<SessionV1.Part>): SessionV1.Part => ({
754
+ ...part,
755
+ id: part.id ? PartID.make(part.id) : PartID.ascending(),
756
+ })
757
+
758
+ const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect<Draft<SessionV1.Part>[]> = Effect.fn(
759
+ "SessionPrompt.resolveUserPart",
760
+ )(function* (part) {
761
+ if (part.type === "file") {
762
+ if (part.source?.type === "resource") {
763
+ const { clientName, uri } = part.source
764
+ log.info("mcp resource", { clientName, uri, mime: part.mime })
765
+ const pieces: Draft<SessionV1.Part>[] = [
766
+ {
767
+ messageID: info.id,
768
+ sessionID: input.sessionID,
769
+ type: "text",
770
+ synthetic: true,
771
+ text: `Reading MCP resource: ${part.filename} (${uri})`,
772
+ },
773
+ ]
774
+ const exit = yield* mcp.readResource(clientName, uri).pipe(Effect.exit)
775
+ if (Exit.isSuccess(exit)) {
776
+ const content = exit.value
777
+ if (!content) throw new Error(`Resource not found: ${clientName}/${uri}`)
778
+ const items = Array.isArray(content.contents) ? content.contents : [content.contents]
779
+ for (const c of items) {
780
+ if ("text" in c && c.text) {
781
+ pieces.push({
782
+ messageID: info.id,
783
+ sessionID: input.sessionID,
784
+ type: "text",
785
+ synthetic: true,
786
+ text: c.text,
787
+ })
788
+ } else if ("blob" in c && c.blob) {
789
+ const mime = "mimeType" in c ? c.mimeType : part.mime
790
+ pieces.push({
791
+ messageID: info.id,
792
+ sessionID: input.sessionID,
793
+ type: "text",
794
+ synthetic: true,
795
+ text: `[Binary content: ${mime}]`,
796
+ })
797
+ }
798
+ }
799
+ pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
800
+ } else {
801
+ const error = Cause.squash(exit.cause)
802
+ log.error("failed to read MCP resource", { error, clientName, uri })
803
+ const message = error instanceof Error ? error.message : String(error)
804
+ pieces.push({
805
+ messageID: info.id,
806
+ sessionID: input.sessionID,
807
+ type: "text",
808
+ synthetic: true,
809
+ text: `Failed to read MCP resource ${part.filename}: ${message}`,
810
+ })
811
+ }
812
+ return pieces
813
+ }
814
+ const url = new URL(part.url)
815
+ switch (url.protocol) {
816
+ case "data:":
817
+ if (part.mime === "text/plain") {
818
+ return [
819
+ {
820
+ messageID: info.id,
821
+ sessionID: input.sessionID,
822
+ type: "text",
823
+ synthetic: true,
824
+ text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
825
+ },
826
+ {
827
+ messageID: info.id,
828
+ sessionID: input.sessionID,
829
+ type: "text",
830
+ synthetic: true,
831
+ text: decodeDataUrl(part.url),
832
+ },
833
+ { ...part, messageID: info.id, sessionID: input.sessionID },
834
+ ]
835
+ }
836
+ break
837
+ case "file:": {
838
+ log.info("file", { mime: part.mime })
839
+ const filepath = fileURLToPath(part.url)
840
+ const mime = (yield* fsys.isDir(filepath)) ? "application/x-directory" : part.mime
841
+
842
+ const { read } = yield* registry.named()
843
+ const execRead = (args: Parameters<typeof read.execute>[0], extra?: Tool.Context["extra"]) => {
844
+ const controller = new AbortController()
845
+ return read
846
+ .execute(args, {
847
+ sessionID: input.sessionID,
848
+ abort: controller.signal,
849
+ agent: input.agent!,
850
+ messageID: info.id,
851
+ extra: { bypassCwdCheck: true, ...extra },
852
+ messages: [],
853
+ metadata: () => Effect.void,
854
+ ask: () => Effect.void,
855
+ })
856
+ .pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort())))
857
+ }
858
+
859
+ if (mime === "text/plain") {
860
+ let offset: number | undefined
861
+ let limit: number | undefined
862
+ const range = { start: url.searchParams.get("start"), end: url.searchParams.get("end") }
863
+ if (range.start != null) {
864
+ const filePathURI = part.url.split("?")[0]
865
+ let start = parseInt(range.start)
866
+ let end = range.end ? parseInt(range.end) : undefined
867
+ if (start === end) {
868
+ const symbols = yield* lsp.documentSymbol(filePathURI).pipe(Effect.catch(() => Effect.succeed([])))
869
+ for (const symbol of symbols) {
870
+ let r: LSP.Range | undefined
871
+ if ("range" in symbol) r = symbol.range
872
+ else if ("location" in symbol) r = symbol.location.range
873
+ if (r?.start?.line && r?.start?.line === start) {
874
+ start = r.start.line
875
+ end = r?.end?.line ?? start
876
+ break
877
+ }
878
+ }
879
+ }
880
+ offset = Math.max(start, 1)
881
+ if (end) limit = end - (offset - 1)
882
+ }
883
+ const args = { filePath: filepath, offset, limit }
884
+ const pieces: Draft<SessionV1.Part>[] = [
885
+ {
886
+ messageID: info.id,
887
+ sessionID: input.sessionID,
888
+ type: "text",
889
+ synthetic: true,
890
+ text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
891
+ },
892
+ ]
893
+ const exit = yield* provider.getModel(info.model.providerID, info.model.modelID).pipe(
894
+ Effect.flatMap((mdl) => execRead(args, { model: mdl })),
895
+ Effect.exit,
896
+ )
897
+ if (Exit.isSuccess(exit)) {
898
+ const result = exit.value
899
+ pieces.push({
900
+ messageID: info.id,
901
+ sessionID: input.sessionID,
902
+ type: "text",
903
+ synthetic: true,
904
+ text: result.output,
905
+ })
906
+ if (result.attachments?.length) {
907
+ pieces.push(
908
+ ...result.attachments.map((a) => ({
909
+ ...a,
910
+ synthetic: true,
911
+ filename: a.filename ?? part.filename,
912
+ messageID: info.id,
913
+ sessionID: input.sessionID,
914
+ })),
915
+ )
916
+ } else {
917
+ pieces.push({ ...part, mime, messageID: info.id, sessionID: input.sessionID })
918
+ }
919
+ } else {
920
+ const error = Cause.squash(exit.cause)
921
+ log.error("failed to read file", { error })
922
+ const message = error instanceof Error ? error.message : String(error)
923
+ yield* events.publish(Session.Event.Error, {
924
+ sessionID: input.sessionID,
925
+ error: new NamedError.Unknown({ message }).toObject(),
926
+ })
927
+ pieces.push({
928
+ messageID: info.id,
929
+ sessionID: input.sessionID,
930
+ type: "text",
931
+ synthetic: true,
932
+ text: `Read tool failed to read ${filepath} with the following error: ${message}`,
933
+ })
934
+ }
935
+ return pieces
936
+ }
937
+
938
+ if (mime === "application/x-directory") {
939
+ const args = { filePath: filepath }
940
+ const exit = yield* execRead(args).pipe(Effect.exit)
941
+ if (Exit.isFailure(exit)) {
942
+ const error = Cause.squash(exit.cause)
943
+ log.error("failed to read directory", { error })
944
+ const message = error instanceof Error ? error.message : String(error)
945
+ yield* events.publish(Session.Event.Error, {
946
+ sessionID: input.sessionID,
947
+ error: new NamedError.Unknown({ message }).toObject(),
948
+ })
949
+ return [
950
+ {
951
+ messageID: info.id,
952
+ sessionID: input.sessionID,
953
+ type: "text",
954
+ synthetic: true,
955
+ text: `Read tool failed to read ${filepath} with the following error: ${message}`,
956
+ },
957
+ ]
958
+ }
959
+ return [
960
+ {
961
+ messageID: info.id,
962
+ sessionID: input.sessionID,
963
+ type: "text",
964
+ synthetic: true,
965
+ text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
966
+ },
967
+ {
968
+ messageID: info.id,
969
+ sessionID: input.sessionID,
970
+ type: "text",
971
+ synthetic: true,
972
+ text: exit.value.output,
973
+ },
974
+ { ...part, mime, messageID: info.id, sessionID: input.sessionID },
975
+ ]
976
+ }
977
+
978
+ return [
979
+ {
980
+ messageID: info.id,
981
+ sessionID: input.sessionID,
982
+ type: "text",
983
+ synthetic: true,
984
+ text: `Called the Read tool with the following input: {"filePath":"${filepath}"}`,
985
+ },
986
+ {
987
+ id: part.id,
988
+ messageID: info.id,
989
+ sessionID: input.sessionID,
990
+ type: "file",
991
+ url:
992
+ `data:${mime};base64,` +
993
+ Buffer.from(yield* fsys.readFile(filepath).pipe(Effect.catch(Effect.die))).toString("base64"),
994
+ mime,
995
+ filename: part.filename!,
996
+ source: part.source,
997
+ },
998
+ ]
999
+ }
1000
+ }
1001
+ }
1002
+
1003
+ if (part.type === "agent") {
1004
+ const perm = Permission.evaluate("task", part.name, ag.permission)
1005
+ const hint = perm.action === "deny" ? " . Invoked by user; guaranteed to exist." : ""
1006
+ return [
1007
+ { ...part, messageID: info.id, sessionID: input.sessionID },
1008
+ {
1009
+ messageID: info.id,
1010
+ sessionID: input.sessionID,
1011
+ type: "text",
1012
+ synthetic: true,
1013
+ text:
1014
+ " Use the above message and context to generate a prompt and call the task tool with subagent: " +
1015
+ part.name +
1016
+ hint,
1017
+ },
1018
+ ]
1019
+ }
1020
+
1021
+ return [{ ...part, messageID: info.id, sessionID: input.sessionID }]
1022
+ })
1023
+
1024
+ const submittedParts: Types.DeepMutable<PromptInput["parts"]> = [...input.parts]
1025
+ const attachedReferences = new Set(
1026
+ input.parts.flatMap((part) =>
1027
+ part.type === "file" && part.mime === "application/x-directory" ? [part.url] : [],
1028
+ ),
1029
+ )
1030
+ for (const part of input.parts) {
1031
+ if (part.type !== "text" || part.synthetic) continue
1032
+ for (const reference of yield* resolveReferenceParts(part.text)) {
1033
+ if (reference.type === "file" && attachedReferences.has(reference.url)) continue
1034
+ if (reference.type === "file") attachedReferences.add(reference.url)
1035
+ submittedParts.push(reference)
1036
+ }
1037
+ }
1038
+
1039
+ const resolvedParts = yield* Effect.forEach(submittedParts, resolvePart, { concurrency: "unbounded" }).pipe(
1040
+ Effect.map((x) => x.flat().map(assign)),
1041
+ )
1042
+
1043
+ yield* plugin.trigger(
1044
+ "chat.message",
1045
+ {
1046
+ sessionID: input.sessionID,
1047
+ agent: input.agent,
1048
+ model: input.model,
1049
+ messageID: input.messageID,
1050
+ variant: input.variant,
1051
+ },
1052
+ { message: info, parts: resolvedParts },
1053
+ )
1054
+
1055
+ const parts = yield* Effect.forEach(resolvedParts, (part) =>
1056
+ part.type === "file" && part.mime.startsWith("image/")
1057
+ ? image.normalize(part).pipe(
1058
+ Effect.catchIf(
1059
+ (error) => error instanceof Image.ResizerUnavailableError,
1060
+ () => Effect.succeed(part),
1061
+ ),
1062
+ )
1063
+ : Effect.succeed(part),
1064
+ )
1065
+
1066
+ const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" })
1067
+ if (Exit.isFailure(parsed)) {
1068
+ log.error("invalid user message before save", {
1069
+ sessionID: input.sessionID,
1070
+ messageID: info.id,
1071
+ agent: info.agent,
1072
+ model: info.model,
1073
+ cause: Cause.pretty(parsed.cause),
1074
+ })
1075
+ }
1076
+ parts.forEach((part, index) => {
1077
+ const p = decodeMessagePart(part, { errors: "all", propertyOrder: "original" })
1078
+ if (Exit.isSuccess(p)) return
1079
+ log.error("invalid user part before save", {
1080
+ sessionID: input.sessionID,
1081
+ messageID: info.id,
1082
+ partID: part.id,
1083
+ partType: part.type,
1084
+ index,
1085
+ cause: Cause.pretty(p.cause),
1086
+ part,
1087
+ })
1088
+ })
1089
+
1090
+ yield* sessions.updateMessage(info)
1091
+ for (const part of parts) yield* sessions.updatePart(part)
1092
+ const nextPrompt = parts.reduce(
1093
+ (result, part) => {
1094
+ if (part.type === "text") {
1095
+ if (part.synthetic) result.synthetic.push(part.text)
1096
+ else result.text.push(part.text)
1097
+ const reference = referencePromptMetadata(part.metadata?.reference)
1098
+ if (reference) {
1099
+ result.references.push(
1100
+ new ReferenceAttachment({
1101
+ name: reference.name,
1102
+ kind: reference.kind,
1103
+ uri: reference.path ? pathToFileURL(reference.path).href : undefined,
1104
+ repository: reference.repository,
1105
+ branch: reference.branch,
1106
+ target: reference.target,
1107
+ targetUri: reference.targetPath ? pathToFileURL(reference.targetPath).href : undefined,
1108
+ problem: reference.problem,
1109
+ source: new Source({
1110
+ start: reference.source.start,
1111
+ end: reference.source.end,
1112
+ text: reference.source.value,
1113
+ }),
1114
+ }),
1115
+ )
1116
+ }
1117
+ }
1118
+ if (part.type === "file") {
1119
+ result.files.push(
1120
+ new FileAttachment({
1121
+ uri: part.url,
1122
+ mime: part.mime,
1123
+ name: part.filename,
1124
+ source: part.source
1125
+ ? new Source({
1126
+ start: part.source.text.start,
1127
+ end: part.source.text.end,
1128
+ text: part.source.text.value,
1129
+ })
1130
+ : undefined,
1131
+ }),
1132
+ )
1133
+ }
1134
+ if (part.type === "agent") {
1135
+ result.agents.push(
1136
+ new AgentAttachment({
1137
+ name: part.name,
1138
+ source: part.source
1139
+ ? new Source({
1140
+ start: part.source.start,
1141
+ end: part.source.end,
1142
+ text: part.source.value,
1143
+ })
1144
+ : undefined,
1145
+ }),
1146
+ )
1147
+ }
1148
+ return result
1149
+ },
1150
+ {
1151
+ text: [] as string[],
1152
+ files: [] as FileAttachment[],
1153
+ agents: [] as AgentAttachment[],
1154
+ references: [] as ReferenceAttachment[],
1155
+ synthetic: [] as string[],
1156
+ },
1157
+ )
1158
+ // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
1159
+ if (flags.experimentalEventSystem) {
1160
+ yield* events.publish(SessionEvent.Prompted, {
1161
+ sessionID: input.sessionID,
1162
+ messageID: SessionMessage.ID.create(),
1163
+ timestamp: DateTime.makeUnsafe(info.time.created),
1164
+ delivery: "steer",
1165
+ prompt: new Prompt({
1166
+ text: nextPrompt.text.join("\n"),
1167
+ files: nextPrompt.files,
1168
+ agents: nextPrompt.agents,
1169
+ references: nextPrompt.references,
1170
+ }),
1171
+ })
1172
+ }
1173
+ for (const text of nextPrompt.synthetic) {
1174
+ // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
1175
+ if (flags.experimentalEventSystem) {
1176
+ yield* events.publish(SessionEvent.Synthetic, {
1177
+ sessionID: input.sessionID,
1178
+ messageID: SessionMessage.ID.create(),
1179
+ timestamp: DateTime.makeUnsafe(info.time.created),
1180
+ text,
1181
+ })
1182
+ }
1183
+ }
1184
+
1185
+ return { info, parts }
1186
+ }, Effect.scoped)
1187
+
1188
+ const prompt: (input: PromptInput) => Effect.Effect<SessionV1.WithParts, Image.Error> = Effect.fn(
1189
+ "SessionPrompt.prompt",
1190
+ )(function* (input: PromptInput) {
1191
+ const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
1192
+ yield* revert.cleanup(session)
1193
+ const message = yield* createUserMessage(input)
1194
+ yield* sessions.touch(input.sessionID)
1195
+
1196
+ const permissions: PermissionV1.Rule[] = []
1197
+ for (const [t, enabled] of Object.entries(input.tools ?? {})) {
1198
+ permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
1199
+ }
1200
+ if (permissions.length > 0) {
1201
+ session.permission = permissions
1202
+ yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
1203
+ }
1204
+
1205
+ if (input.noReply === true) return message
1206
+ return yield* loop({ sessionID: input.sessionID })
1207
+ })
1208
+
1209
+ const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) {
1210
+ const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user").pipe(Effect.orDie)
1211
+ if (Option.isSome(match)) return match.value
1212
+ const msgs = yield* sessions.messages({ sessionID, limit: 1 }).pipe(Effect.orDie)
1213
+ if (msgs.length > 0) return msgs[0]
1214
+ throw new Error("Impossible")
1215
+ })
1216
+
1217
+ const runLoop: (sessionID: SessionID) => Effect.Effect<SessionV1.WithParts> = Effect.fn("SessionPrompt.run")(
1218
+ function* (sessionID: SessionID) {
1219
+ const ctx = yield* InstanceState.context
1220
+ const slog = elog.with({ sessionID })
1221
+ let structured: unknown
1222
+ let step = 0
1223
+ const session = yield* sessions.get(sessionID).pipe(Effect.orDie)
1224
+
1225
+ while (true) {
1226
+ yield* status.set(sessionID, { type: "busy" })
1227
+ yield* slog.info("loop", { step })
1228
+
1229
+ let msgs = yield* MessageV2.filterCompactedEffect(sessionID).pipe(
1230
+ Effect.provideService(Database.Service, database),
1231
+ )
1232
+
1233
+ const { user: lastUser, assistant: lastAssistant, finished: lastFinished, tasks } = MessageV2.latest(msgs)
1234
+
1235
+ if (!lastUser) throw new Error("No user message found in stream. This should never happen.")
1236
+
1237
+ const lastAssistantMsg = msgs.findLast(
1238
+ (msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id,
1239
+ )
1240
+ // Some providers return "stop" even when the assistant message contains
1241
+ // tool calls. Keep the loop running so tool results can be sent back to
1242
+ // the model, but ignore cleanup-marked interrupted orphans.
1243
+ const hasToolCalls =
1244
+ lastAssistantMsg?.parts.some(
1245
+ (part) => part.type === "tool" && !part.metadata?.providerExecuted && !isOrphanedInterruptedTool(part),
1246
+ ) ?? false
1247
+
1248
+ if (
1249
+ lastAssistant?.finish &&
1250
+ !["tool-calls"].includes(lastAssistant.finish) &&
1251
+ !hasToolCalls &&
1252
+ lastUser.id < lastAssistant.id
1253
+ ) {
1254
+ const orphan = lastAssistantMsg?.parts.find(
1255
+ (part): part is SessionV1.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part),
1256
+ )
1257
+ if (orphan) {
1258
+ yield* slog.warn("loop exit with orphaned interrupted tool", {
1259
+ messageID: lastAssistant.id,
1260
+ tool: orphan.tool,
1261
+ callID: orphan.callID,
1262
+ })
1263
+ }
1264
+ yield* slog.info("exiting loop")
1265
+ break
1266
+ }
1267
+
1268
+ step++
1269
+ if (step === 1)
1270
+ yield* title({
1271
+ session,
1272
+ modelID: lastUser.model.modelID,
1273
+ providerID: lastUser.model.providerID,
1274
+ history: msgs,
1275
+ }).pipe(Effect.ignore, Effect.forkIn(scope))
1276
+
1277
+ const model = yield* getModel(lastUser.model.providerID, lastUser.model.modelID, sessionID)
1278
+ const task = tasks.pop()
1279
+
1280
+ if (task?.type === "subtask") {
1281
+ yield* handleSubtask({ task, model, lastUser, sessionID, session, msgs })
1282
+ continue
1283
+ }
1284
+
1285
+ if (task?.type === "compaction") {
1286
+ const result = yield* compaction.process({
1287
+ messages: msgs,
1288
+ parentID: lastUser.id,
1289
+ sessionID,
1290
+ auto: task.auto,
1291
+ overflow: task.overflow,
1292
+ })
1293
+ if (result === "stop") break
1294
+ continue
1295
+ }
1296
+
1297
+ if (
1298
+ lastFinished &&
1299
+ lastFinished.summary !== true &&
1300
+ (yield* compaction.isOverflow({ tokens: lastFinished.tokens, model }))
1301
+ ) {
1302
+ yield* compaction.create({ sessionID, agent: lastUser.agent, model: lastUser.model, auto: true })
1303
+ continue
1304
+ }
1305
+
1306
+ const agent = yield* agents.get(lastUser.agent)
1307
+ if (!agent) {
1308
+ const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
1309
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
1310
+ const error = new NamedError.Unknown({ message: `Agent not found: "${lastUser.agent}".${hint}` })
1311
+ yield* events.publish(Session.Event.Error, { sessionID, error: error.toObject() })
1312
+ throw error
1313
+ }
1314
+ const maxSteps = agent.steps ?? Infinity
1315
+ const isLastStep = step >= maxSteps
1316
+ msgs = yield* SessionReminders.apply({ messages: msgs, agent, session }).pipe(
1317
+ Effect.provideService(RuntimeFlags.Service, flags),
1318
+ Effect.provideService(FSUtil.Service, fsys),
1319
+ Effect.provideService(Session.Service, sessions),
1320
+ )
1321
+
1322
+ const msg: SessionV1.Assistant = {
1323
+ id: MessageID.ascending(),
1324
+ parentID: lastUser.id,
1325
+ role: "assistant",
1326
+ mode: agent.name,
1327
+ agent: agent.name,
1328
+ variant: lastUser.model.variant,
1329
+ path: { cwd: ctx.directory, root: ctx.worktree },
1330
+ cost: 0,
1331
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
1332
+ modelID: model.id,
1333
+ providerID: model.providerID,
1334
+ time: { created: Date.now() },
1335
+ sessionID,
1336
+ }
1337
+ yield* sessions.updateMessage(msg)
1338
+
1339
+ const finalizeInterruptedAssistant = Effect.gen(function* () {
1340
+ if (msg.time.completed) return
1341
+ msg.error ??= MessageV2.fromError(new DOMException("Aborted", "AbortError"), {
1342
+ providerID: msg.providerID,
1343
+ aborted: true,
1344
+ })
1345
+ msg.time.completed = Date.now()
1346
+ yield* sessions.updateMessage(msg)
1347
+ })
1348
+
1349
+ const handle = yield* processor
1350
+ .create({
1351
+ assistantMessage: msg,
1352
+ sessionID,
1353
+ model,
1354
+ })
1355
+ .pipe(Effect.onInterrupt(() => finalizeInterruptedAssistant))
1356
+
1357
+ const outcome: "break" | "continue" = yield* Effect.gen(function* () {
1358
+ const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
1359
+ const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false
1360
+ const promptOps = yield* ops()
1361
+
1362
+ const tools = yield* SessionTools.resolve({
1363
+ agent,
1364
+ session,
1365
+ model,
1366
+ processor: handle,
1367
+ bypassAgentCheck,
1368
+ messages: msgs,
1369
+ promptOps,
1370
+ }).pipe(
1371
+ Effect.provideService(Plugin.Service, plugin),
1372
+ Effect.provideService(Permission.Service, permission),
1373
+ Effect.provideService(ToolRegistry.Service, registry),
1374
+ Effect.provideService(MCP.Service, mcp),
1375
+ Effect.provideService(Truncate.Service, truncate),
1376
+ )
1377
+
1378
+ if (lastUser.format?.type === "json_schema") {
1379
+ tools["StructuredOutput"] = createStructuredOutputTool({
1380
+ schema: lastUser.format.schema,
1381
+ onSuccess(output) {
1382
+ structured = output
1383
+ },
1384
+ })
1385
+ }
1386
+
1387
+ if (step === 1)
1388
+ yield* summary.summarize({ sessionID, messageID: lastUser.id }).pipe(Effect.ignore, Effect.forkIn(scope))
1389
+
1390
+ if (step > 1 && lastFinished) {
1391
+ for (const m of msgs) {
1392
+ if (m.info.role !== "user" || m.info.id <= lastFinished.id) continue
1393
+ for (const p of m.parts) {
1394
+ if (p.type !== "text" || p.ignored || p.synthetic) continue
1395
+ if (!p.text.trim()) continue
1396
+ p.text = [
1397
+ "<system-reminder>",
1398
+ "The user sent the following message:",
1399
+ p.text,
1400
+ "",
1401
+ "Please address this message and continue with your tasks.",
1402
+ "</system-reminder>",
1403
+ ].join("\n")
1404
+ }
1405
+ }
1406
+ }
1407
+
1408
+ yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
1409
+
1410
+ const [skills, env, instructions, modelMsgs] = yield* Effect.all([
1411
+ sys.skills(agent),
1412
+ sys.environment(model),
1413
+ instruction.system().pipe(Effect.orDie),
1414
+ MessageV2.toModelMessagesEffect(msgs, model),
1415
+ ])
1416
+ const system = [...env, ...instructions, ...(skills ? [skills] : [])]
1417
+ const format = lastUser.format ?? { type: "text" as const }
1418
+ if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
1419
+ const result = yield* handle.process({
1420
+ user: lastUser,
1421
+ agent,
1422
+ permission: session.permission,
1423
+ sessionID,
1424
+ parentSessionID: session.parentID,
1425
+ system,
1426
+ messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])],
1427
+ tools,
1428
+ model,
1429
+ toolChoice: format.type === "json_schema" ? "required" : undefined,
1430
+ })
1431
+
1432
+ if (structured !== undefined) {
1433
+ handle.message.structured = structured
1434
+ handle.message.finish = handle.message.finish ?? "stop"
1435
+ yield* sessions.updateMessage(handle.message)
1436
+ return "break" as const
1437
+ }
1438
+
1439
+ const finished = handle.message.finish && !["tool-calls", "unknown"].includes(handle.message.finish)
1440
+ if (finished && !handle.message.error) {
1441
+ if (format.type === "json_schema") {
1442
+ handle.message.error = new SessionV1.StructuredOutputError({
1443
+ message: "Model did not produce structured output",
1444
+ retries: 0,
1445
+ }).toObject()
1446
+ yield* sessions.updateMessage(handle.message)
1447
+ return "break" as const
1448
+ }
1449
+ }
1450
+
1451
+ if (result === "stop") return "break" as const
1452
+ if (result === "compact") {
1453
+ yield* compaction.create({
1454
+ sessionID,
1455
+ agent: lastUser.agent,
1456
+ model: lastUser.model,
1457
+ auto: true,
1458
+ overflow: !handle.message.finish,
1459
+ })
1460
+ }
1461
+ return "continue" as const
1462
+ }).pipe(
1463
+ Effect.ensuring(instruction.clear(handle.message.id)),
1464
+ Effect.onInterrupt(() => finalizeInterruptedAssistant),
1465
+ )
1466
+ if (outcome === "break") break
1467
+ continue
1468
+ }
1469
+
1470
+ yield* compaction.prune({ sessionID }).pipe(Effect.ignore, Effect.forkIn(scope))
1471
+ return yield* lastAssistant(sessionID)
1472
+ },
1473
+ )
1474
+
1475
+ const loop: (input: LoopInput) => Effect.Effect<SessionV1.WithParts> = Effect.fn("SessionPrompt.loop")(function* (
1476
+ input: LoopInput,
1477
+ ) {
1478
+ return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID))
1479
+ })
1480
+
1481
+ const shell: (input: ShellInput) => Effect.Effect<SessionV1.WithParts, Session.BusyError> = Effect.fn(
1482
+ "SessionPrompt.shell",
1483
+ )(function* (input: ShellInput) {
1484
+ const ready = yield* Latch.make()
1485
+ return yield* state.startShell(input.sessionID, lastAssistant(input.sessionID), shellImpl(input, ready), ready)
1486
+ })
1487
+
1488
+ const command = Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
1489
+ yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
1490
+ const cmd = yield* commands.get(input.command)
1491
+ if (!cmd) {
1492
+ const available = (yield* commands.list()).map((c) => c.name)
1493
+ const hint = available.length ? ` Available commands: ${available.join(", ")}` : ""
1494
+ const error = new NamedError.Unknown({ message: `Command not found: "${input.command}".${hint}` })
1495
+ yield* events.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
1496
+ throw error
1497
+ }
1498
+ const agentName = cmd.agent ?? input.agent
1499
+
1500
+ const raw = input.arguments.match(argsRegex) ?? []
1501
+ const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
1502
+ const templateCommand = yield* Effect.promise(async () => cmd.template)
1503
+
1504
+ const placeholders = templateCommand.match(placeholderRegex) ?? []
1505
+ let last = 0
1506
+ for (const item of placeholders) {
1507
+ const value = Number(item.slice(1))
1508
+ if (value > last) last = value
1509
+ }
1510
+
1511
+ const withArgs = templateCommand.replaceAll(placeholderRegex, (_, index) => {
1512
+ const position = Number(index)
1513
+ const argIndex = position - 1
1514
+ if (argIndex >= args.length) return ""
1515
+ if (position === last) return args.slice(argIndex).join(" ")
1516
+ return args[argIndex]
1517
+ })
1518
+ const usesArgumentsPlaceholder = templateCommand.includes("$ARGUMENTS")
1519
+ let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
1520
+
1521
+ if (placeholders.length === 0 && !usesArgumentsPlaceholder && input.arguments.trim()) {
1522
+ template = template + "\n\n" + input.arguments
1523
+ }
1524
+
1525
+ const shellMatches = ConfigMarkdown.shell(template)
1526
+ if (shellMatches.length > 0) {
1527
+ const cfg = yield* config.get()
1528
+ const sh = Shell.preferred(cfg.shell)
1529
+ const results = yield* Effect.promise(() =>
1530
+ Promise.all(
1531
+ shellMatches.map(async ([, cmd]) => (await Process.text([cmd], { shell: sh, nothrow: true })).text),
1532
+ ),
1533
+ )
1534
+ let index = 0
1535
+ template = template.replace(bashRegex, () => results[index++])
1536
+ }
1537
+ template = template.trim()
1538
+
1539
+ const taskModel = yield* Effect.gen(function* () {
1540
+ if (cmd.model) return Provider.parseModel(cmd.model)
1541
+ if (cmd.agent) {
1542
+ const cmdAgent = yield* agents.get(cmd.agent)
1543
+ if (cmdAgent?.model) return cmdAgent.model
1544
+ }
1545
+ if (input.model) return Provider.parseModel(input.model)
1546
+ return yield* currentModel(input.sessionID)
1547
+ })
1548
+
1549
+ yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID)
1550
+
1551
+ const agent = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo()
1552
+ if (!agent) {
1553
+ const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
1554
+ const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
1555
+ const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
1556
+ yield* events.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
1557
+ throw error
1558
+ }
1559
+
1560
+ const templateParts = yield* resolvePromptParts(template)
1561
+ const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true
1562
+ const parts = isSubtask
1563
+ ? [
1564
+ {
1565
+ type: "subtask" as const,
1566
+ agent: agent.name,
1567
+ description: cmd.description ?? "",
1568
+ command: input.command,
1569
+ model: { providerID: taskModel.providerID, modelID: taskModel.modelID },
1570
+ prompt: templateParts.find((y) => y.type === "text")?.text ?? "",
1571
+ },
1572
+ ]
1573
+ : [...templateParts, ...(input.parts ?? [])]
1574
+
1575
+ const userAgent = isSubtask ? (input.agent ?? (yield* agents.defaultInfo()).name) : agent.name
1576
+ const userModel = isSubtask
1577
+ ? input.model
1578
+ ? Provider.parseModel(input.model)
1579
+ : yield* currentModel(input.sessionID)
1580
+ : taskModel
1581
+
1582
+ yield* plugin.trigger(
1583
+ "command.execute.before",
1584
+ { command: input.command, sessionID: input.sessionID, arguments: input.arguments },
1585
+ { parts },
1586
+ )
1587
+
1588
+ const result = yield* prompt({
1589
+ sessionID: input.sessionID,
1590
+ messageID: input.messageID,
1591
+ model: userModel,
1592
+ agent: userAgent,
1593
+ parts,
1594
+ variant: input.variant,
1595
+ })
1596
+ yield* events.publish(Command.Event.Executed, {
1597
+ name: input.command,
1598
+ sessionID: input.sessionID,
1599
+ arguments: input.arguments,
1600
+ messageID: result.info.id,
1601
+ })
1602
+ return result
1603
+ })
1604
+
1605
+ return Service.of({
1606
+ cancel,
1607
+ prompt,
1608
+ loop,
1609
+ shell,
1610
+ command,
1611
+ resolvePromptParts,
1612
+ })
1613
+ }),
1614
+ )
1615
+
1616
+ export const defaultLayer = Layer.suspend(() =>
1617
+ layer.pipe(
1618
+ Layer.provide(SessionRunState.defaultLayer),
1619
+ Layer.provide(SessionStatus.defaultLayer),
1620
+ Layer.provide(SessionCompaction.defaultLayer),
1621
+ Layer.provide(SessionProcessor.defaultLayer),
1622
+ Layer.provide(Command.defaultLayer),
1623
+ Layer.provide(Permission.defaultLayer),
1624
+ Layer.provide(MCP.defaultLayer),
1625
+ Layer.provide(LSP.defaultLayer),
1626
+ Layer.provide(ToolRegistry.defaultLayer),
1627
+ Layer.provide(Truncate.defaultLayer),
1628
+ Layer.provide(Provider.defaultLayer),
1629
+ Layer.provide(Config.defaultLayer),
1630
+ Layer.provide(Instruction.defaultLayer),
1631
+ Layer.provide(FSUtil.defaultLayer),
1632
+ Layer.provide(Plugin.defaultLayer),
1633
+ Layer.provide(Session.defaultLayer),
1634
+ Layer.provide(SessionRevert.defaultLayer),
1635
+ Layer.provide(SessionSummary.defaultLayer),
1636
+ Layer.provide(Image.defaultLayer),
1637
+ Layer.provide(
1638
+ Layer.mergeAll(
1639
+ Agent.defaultLayer,
1640
+ Database.defaultLayer,
1641
+ SystemPrompt.defaultLayer,
1642
+ LLM.defaultLayer,
1643
+ Reference.defaultLayer,
1644
+ CrossSpawnSpawner.defaultLayer,
1645
+ RuntimeFlags.defaultLayer,
1646
+ EventV2Bridge.defaultLayer,
1647
+ ),
1648
+ ),
1649
+ ),
1650
+ )
1651
+ const ModelRef = Schema.Struct({
1652
+ providerID: ProviderV2.ID,
1653
+ modelID: ModelV2.ID,
1654
+ })
1655
+
1656
+ export const PromptInput = Schema.Struct({
1657
+ sessionID: SessionID,
1658
+ messageID: Schema.optional(MessageID),
1659
+ model: Schema.optional(ModelRef),
1660
+ agent: Schema.optional(Schema.String),
1661
+ noReply: Schema.optional(Schema.Boolean),
1662
+ tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({
1663
+ description:
1664
+ "@deprecated tools and permissions have been merged, you can set permissions on the session itself now",
1665
+ }),
1666
+ format: Schema.optional(SessionV1.Format),
1667
+ system: Schema.optional(Schema.String),
1668
+ variant: Schema.optional(Schema.String),
1669
+ parts: Schema.Array(
1670
+ Schema.Union([
1671
+ SessionV1.TextPartInput,
1672
+ SessionV1.FilePartInput,
1673
+ SessionV1.AgentPartInput,
1674
+ SessionV1.SubtaskPartInput,
1675
+ ]).annotate({ discriminator: "type" }),
1676
+ ),
1677
+ })
1678
+ export type PromptInput = Schema.Schema.Type<typeof PromptInput>
1679
+
1680
+ export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
1681
+ sessionID: SessionID,
1682
+ }) {}
1683
+
1684
+ export const ShellInput = Schema.Struct({
1685
+ sessionID: SessionID,
1686
+ messageID: Schema.optional(MessageID),
1687
+ agent: Schema.String,
1688
+ model: Schema.optional(ModelRef),
1689
+ command: Schema.String,
1690
+ })
1691
+ export type ShellInput = Schema.Schema.Type<typeof ShellInput>
1692
+
1693
+ export const CommandInput = Schema.Struct({
1694
+ messageID: Schema.optional(MessageID),
1695
+ sessionID: SessionID,
1696
+ agent: Schema.optional(Schema.String),
1697
+ model: Schema.optional(Schema.String),
1698
+ arguments: Schema.String,
1699
+ command: Schema.String,
1700
+ variant: Schema.optional(Schema.String),
1701
+ // Inlined (no identifier annotation) to keep the original SDK output — the
1702
+ // PromptInput call site below references FilePartInput by ref via the
1703
+ // Schema export in message-v2.ts.
1704
+ parts: Schema.optional(
1705
+ Schema.Array(
1706
+ Schema.Union([
1707
+ Schema.Struct({
1708
+ id: Schema.optional(PartID),
1709
+ type: Schema.Literal("file"),
1710
+ mime: Schema.String,
1711
+ filename: Schema.optional(Schema.String),
1712
+ url: Schema.String,
1713
+ source: Schema.optional(SessionV1.FilePartSource),
1714
+ }),
1715
+ ]).annotate({ discriminator: "type" }),
1716
+ ),
1717
+ ),
1718
+ })
1719
+ export type CommandInput = Schema.Schema.Type<typeof CommandInput>
1720
+
1721
+ /** @internal Exported for testing */
1722
+ export function createStructuredOutputTool(input: {
1723
+ schema: Record<string, any>
1724
+ onSuccess: (output: unknown) => void
1725
+ }): AITool {
1726
+ // Remove $schema property if present (not needed for tool input)
1727
+ const { $schema: _, ...toolSchema } = input.schema
1728
+
1729
+ return tool({
1730
+ description: STRUCTURED_OUTPUT_DESCRIPTION,
1731
+ inputSchema: jsonSchema(toolSchema as JSONSchema7),
1732
+ async execute(args) {
1733
+ // AI SDK validates args against inputSchema before calling execute()
1734
+ input.onSuccess(args)
1735
+ return {
1736
+ output: "Structured output captured successfully.",
1737
+ title: "Structured Output",
1738
+ metadata: { valid: true },
1739
+ }
1740
+ },
1741
+ toModelOutput({ output }) {
1742
+ return {
1743
+ type: "text",
1744
+ value: output.output,
1745
+ }
1746
+ },
1747
+ })
1748
+ }
1749
+ const bashRegex = /!`([^`]+)`/g
1750
+ // Match [Image N] as single token, quoted strings, or non-space sequences
1751
+ const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
1752
+ const placeholderRegex = /\$(\d+)/g
1753
+ const quoteTrimRegex = /^["']|["']$/g
1754
+
1755
+ export * as SessionPrompt from "./prompt"