woozlit 2.3.0 → 2.3.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 (1118) hide show
  1. package/{src/skill/compose/LICENSE-superpowers → LICENSE} +21 -26
  2. package/package.json +18 -191
  3. package/AGENTS.md +0 -134
  4. package/Dockerfile +0 -18
  5. package/README.md +0 -15
  6. package/bunfig.toml +0 -7
  7. package/drizzle.config.ts +0 -10
  8. package/git +0 -0
  9. package/migration/20260127222353_familiar_lady_ursula/migration.sql +0 -90
  10. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +0 -796
  11. package/migration/20260211171708_add_project_commands/migration.sql +0 -1
  12. package/migration/20260211171708_add_project_commands/snapshot.json +0 -806
  13. package/migration/20260213144116_wakeful_the_professor/migration.sql +0 -11
  14. package/migration/20260213144116_wakeful_the_professor/snapshot.json +0 -897
  15. package/migration/20260225215848_workspace/migration.sql +0 -7
  16. package/migration/20260225215848_workspace/snapshot.json +0 -959
  17. package/migration/20260227213759_add_session_workspace_id/migration.sql +0 -2
  18. package/migration/20260227213759_add_session_workspace_id/snapshot.json +0 -983
  19. package/migration/20260228203230_blue_harpoon/migration.sql +0 -17
  20. package/migration/20260228203230_blue_harpoon/snapshot.json +0 -1102
  21. package/migration/20260303231226_add_workspace_fields/migration.sql +0 -5
  22. package/migration/20260303231226_add_workspace_fields/snapshot.json +0 -1013
  23. package/migration/20260309230000_move_org_to_state/migration.sql +0 -3
  24. package/migration/20260309230000_move_org_to_state/snapshot.json +0 -1156
  25. package/migration/20260312043431_session_message_cursor/migration.sql +0 -4
  26. package/migration/20260312043431_session_message_cursor/snapshot.json +0 -1168
  27. package/migration/20260323234822_events/migration.sql +0 -13
  28. package/migration/20260323234822_events/snapshot.json +0 -1271
  29. package/migration/20260410174513_workspace-name/migration.sql +0 -16
  30. package/migration/20260410174513_workspace-name/snapshot.json +0 -1271
  31. package/migration/20260413175956_chief_energizer/migration.sql +0 -13
  32. package/migration/20260413175956_chief_energizer/snapshot.json +0 -1399
  33. package/migration/20260422160000_context_inheritance/migration.sql +0 -3
  34. package/migration/20260422170000_task_registry/migration.sql +0 -18
  35. package/migration/20260423145421_remove_session_entry/migration.sql +0 -4
  36. package/migration/20260515000000_actor_rename/migration.sql +0 -7
  37. package/migration/20260515010000_memory_fts/migration.sql +0 -33
  38. package/migration/20260515020000_user_task/migration.sql +0 -29
  39. package/migration/20260519000000_last_checkpoint_message_id/migration.sql +0 -1
  40. package/migration/20260521000000_message_agent_id/migration.sql +0 -2
  41. package/migration/20260521000100_actor_registry_v6/migration.sql +0 -25
  42. package/migration/20260521010000_memory_fts_v6/migration.sql +0 -33
  43. package/migration/20260521020000_memory_fts_triggers/migration.sql +0 -17
  44. package/migration/20260526000000_agent_id_main/migration.sql +0 -14
  45. package/migration/20260527000000_actor_lifecycle/migration.sql +0 -8
  46. package/migration/20260527000100_inbox/migration.sql +0 -12
  47. package/migration/20260529000000_task_todo_redesign/migration.sql +0 -16
  48. package/migration/20260603000000_task_in_progress_owner/migration.sql +0 -1
  49. package/migration/20260603000000_workflow_run/migration.sql +0 -17
  50. package/migration/20260604000000_workflow_script_sha/migration.sql +0 -1
  51. package/migration/20260608000000_claude_import/migration.sql +0 -7
  52. package/migration/20260608010000_claude_import_message_ids/migration.sql +0 -1
  53. package/migration/20260609000000_history_fts/migration.sql +0 -29
  54. package/migration/20260609230000_workflow_agent_timeout/migration.sql +0 -1
  55. package/parsers-config.ts +0 -290
  56. package/script/build.ts +0 -269
  57. package/script/check-migrations.ts +0 -16
  58. package/script/fix-node-pty.ts +0 -28
  59. package/script/generate.ts +0 -23
  60. package/script/publish.ts +0 -75
  61. package/script/run-workspace-server +0 -106
  62. package/script/schema.ts +0 -63
  63. package/script/time.ts +0 -6
  64. package/script/trace-imports.ts +0 -153
  65. package/script/upgrade-opentui.ts +0 -64
  66. package/src/account/account.sql.ts +0 -39
  67. package/src/account/account.ts +0 -456
  68. package/src/account/repo.ts +0 -166
  69. package/src/account/schema.ts +0 -99
  70. package/src/account/url.ts +0 -8
  71. package/src/acp/README.md +0 -174
  72. package/src/acp/agent.ts +0 -1783
  73. package/src/acp/session.ts +0 -116
  74. package/src/acp/types.ts +0 -24
  75. package/src/actor/actor.sql.ts +0 -38
  76. package/src/actor/events.ts +0 -67
  77. package/src/actor/index.ts +0 -2
  78. package/src/actor/registry.ts +0 -412
  79. package/src/actor/return-header.ts +0 -24
  80. package/src/actor/schema.ts +0 -47
  81. package/src/actor/spawn-ref.ts +0 -16
  82. package/src/actor/spawn.ts +0 -741
  83. package/src/actor/turn.ts +0 -49
  84. package/src/actor/waiter.ts +0 -166
  85. package/src/agent/agent.ts +0 -554
  86. package/src/agent/config.ts +0 -5
  87. package/src/agent/generate.txt +0 -75
  88. package/src/agent/prompt/checkpoint-writer.txt +0 -167
  89. package/src/agent/prompt/compaction.txt +0 -9
  90. package/src/agent/prompt/distill.txt +0 -199
  91. package/src/agent/prompt/dream.txt +0 -155
  92. package/src/agent/prompt/explore.txt +0 -18
  93. package/src/agent/prompt/summary.txt +0 -11
  94. package/src/agent/prompt/title.txt +0 -44
  95. package/src/audio.d.ts +0 -9
  96. package/src/auth/index.ts +0 -106
  97. package/src/bus/bus-event.ts +0 -33
  98. package/src/bus/global.ts +0 -12
  99. package/src/bus/index.ts +0 -193
  100. package/src/cli/bootstrap.ts +0 -33
  101. package/src/cli/cmd/account.ts +0 -258
  102. package/src/cli/cmd/acp.ts +0 -70
  103. package/src/cli/cmd/agent.ts +0 -248
  104. package/src/cli/cmd/cmd.ts +0 -7
  105. package/src/cli/cmd/db.ts +0 -120
  106. package/src/cli/cmd/debug/agent.ts +0 -192
  107. package/src/cli/cmd/debug/config.ts +0 -17
  108. package/src/cli/cmd/debug/file.ts +0 -100
  109. package/src/cli/cmd/debug/index.ts +0 -48
  110. package/src/cli/cmd/debug/lsp.ts +0 -61
  111. package/src/cli/cmd/debug/ripgrep.ts +0 -105
  112. package/src/cli/cmd/debug/scrap.ts +0 -16
  113. package/src/cli/cmd/debug/skill.ts +0 -23
  114. package/src/cli/cmd/debug/snapshot.ts +0 -53
  115. package/src/cli/cmd/export.ts +0 -306
  116. package/src/cli/cmd/generate.ts +0 -50
  117. package/src/cli/cmd/github.ts +0 -1647
  118. package/src/cli/cmd/import.ts +0 -208
  119. package/src/cli/cmd/mcp.ts +0 -812
  120. package/src/cli/cmd/models.ts +0 -88
  121. package/src/cli/cmd/plug.ts +0 -233
  122. package/src/cli/cmd/pr.ts +0 -138
  123. package/src/cli/cmd/providers.ts +0 -689
  124. package/src/cli/cmd/run-completion.ts +0 -77
  125. package/src/cli/cmd/run.ts +0 -694
  126. package/src/cli/cmd/serve.ts +0 -21
  127. package/src/cli/cmd/session.ts +0 -181
  128. package/src/cli/cmd/stats.ts +0 -413
  129. package/src/cli/cmd/tui/app.tsx +0 -1349
  130. package/src/cli/cmd/tui/asset/TEN_VAD_LICENSE +0 -12
  131. package/src/cli/cmd/tui/asset/charge.wav +0 -0
  132. package/src/cli/cmd/tui/asset/pulse-a.wav +0 -0
  133. package/src/cli/cmd/tui/asset/pulse-b.wav +0 -0
  134. package/src/cli/cmd/tui/asset/pulse-c.wav +0 -0
  135. package/src/cli/cmd/tui/asset/ten_vad.wasm +0 -0
  136. package/src/cli/cmd/tui/asset/ten_vad_loader.js +0 -30
  137. package/src/cli/cmd/tui/attach.ts +0 -83
  138. package/src/cli/cmd/tui/component/background-image.tsx +0 -150
  139. package/src/cli/cmd/tui/component/bg-pulse.tsx +0 -130
  140. package/src/cli/cmd/tui/component/border.tsx +0 -21
  141. package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
  142. package/src/cli/cmd/tui/component/dialog-command.tsx +0 -208
  143. package/src/cli/cmd/tui/component/dialog-console-org.tsx +0 -103
  144. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +0 -157
  145. package/src/cli/cmd/tui/component/dialog-image-list.tsx +0 -111
  146. package/src/cli/cmd/tui/component/dialog-logo-design.tsx +0 -37
  147. package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
  148. package/src/cli/cmd/tui/component/dialog-model.tsx +0 -260
  149. package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -454
  150. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +0 -101
  151. package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -269
  152. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
  153. package/src/cli/cmd/tui/component/dialog-skill.tsx +0 -42
  154. package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
  155. package/src/cli/cmd/tui/component/dialog-status.tsx +0 -170
  156. package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
  157. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
  158. package/src/cli/cmd/tui/component/dialog-variant.tsx +0 -39
  159. package/src/cli/cmd/tui/component/dialog-woozlit-login.tsx +0 -206
  160. package/src/cli/cmd/tui/component/dialog-workflows.tsx +0 -62
  161. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +0 -289
  162. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +0 -81
  163. package/src/cli/cmd/tui/component/dialog-worktree.tsx +0 -90
  164. package/src/cli/cmd/tui/component/error-component.tsx +0 -92
  165. package/src/cli/cmd/tui/component/logo.tsx +0 -961
  166. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +0 -14
  167. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -684
  168. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  169. package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -90
  170. package/src/cli/cmd/tui/component/prompt/history.tsx +0 -108
  171. package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1829
  172. package/src/cli/cmd/tui/component/prompt/part.ts +0 -16
  173. package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
  174. package/src/cli/cmd/tui/component/spinner.tsx +0 -24
  175. package/src/cli/cmd/tui/component/starry-background.tsx +0 -305
  176. package/src/cli/cmd/tui/component/startup-loading.tsx +0 -67
  177. package/src/cli/cmd/tui/component/task-item.tsx +0 -63
  178. package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
  179. package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
  180. package/src/cli/cmd/tui/config/cwd.ts +0 -5
  181. package/src/cli/cmd/tui/config/tui-migrate.ts +0 -151
  182. package/src/cli/cmd/tui/config/tui-schema.ts +0 -38
  183. package/src/cli/cmd/tui/config/tui.ts +0 -219
  184. package/src/cli/cmd/tui/context/args.tsx +0 -16
  185. package/src/cli/cmd/tui/context/directory.ts +0 -15
  186. package/src/cli/cmd/tui/context/event.ts +0 -45
  187. package/src/cli/cmd/tui/context/exit.tsx +0 -65
  188. package/src/cli/cmd/tui/context/helper.tsx +0 -25
  189. package/src/cli/cmd/tui/context/keybind.tsx +0 -105
  190. package/src/cli/cmd/tui/context/kv.tsx +0 -76
  191. package/src/cli/cmd/tui/context/language.tsx +0 -91
  192. package/src/cli/cmd/tui/context/local.tsx +0 -455
  193. package/src/cli/cmd/tui/context/plugin-keybinds.ts +0 -41
  194. package/src/cli/cmd/tui/context/project.tsx +0 -109
  195. package/src/cli/cmd/tui/context/prompt.tsx +0 -18
  196. package/src/cli/cmd/tui/context/route.tsx +0 -61
  197. package/src/cli/cmd/tui/context/sdk.tsx +0 -150
  198. package/src/cli/cmd/tui/context/sync.tsx +0 -828
  199. package/src/cli/cmd/tui/context/theme/aura.json +0 -69
  200. package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
  201. package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
  202. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -230
  203. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -230
  204. package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
  205. package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -225
  206. package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
  207. package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
  208. package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
  209. package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
  210. package/src/cli/cmd/tui/context/theme/github.json +0 -233
  211. package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -242
  212. package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
  213. package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -232
  214. package/src/cli/cmd/tui/context/theme/material.json +0 -235
  215. package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
  216. package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
  217. package/src/cli/cmd/tui/context/theme/mimocode.json +0 -245
  218. package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
  219. package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
  220. package/src/cli/cmd/tui/context/theme/nord.json +0 -223
  221. package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
  222. package/src/cli/cmd/tui/context/theme/orng.json +0 -247
  223. package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
  224. package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
  225. package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
  226. package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
  227. package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
  228. package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
  229. package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
  230. package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
  231. package/src/cli/cmd/tui/context/theme/woozlit.json +0 -245
  232. package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
  233. package/src/cli/cmd/tui/context/theme.tsx +0 -1298
  234. package/src/cli/cmd/tui/context/thinking.ts +0 -48
  235. package/src/cli/cmd/tui/context/tui-config.tsx +0 -9
  236. package/src/cli/cmd/tui/event.ts +0 -56
  237. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +0 -93
  238. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +0 -193
  239. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +0 -54
  240. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +0 -169
  241. package/src/cli/cmd/tui/feature-plugins/sidebar/cwd.tsx +0 -45
  242. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +0 -62
  243. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +0 -93
  244. package/src/cli/cmd/tui/feature-plugins/sidebar/goal.tsx +0 -84
  245. package/src/cli/cmd/tui/feature-plugins/sidebar/instructions.tsx +0 -54
  246. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +0 -66
  247. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +0 -98
  248. package/src/cli/cmd/tui/feature-plugins/sidebar/task.tsx +0 -95
  249. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +0 -51
  250. package/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts +0 -31
  251. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +0 -274
  252. package/src/cli/cmd/tui/i18n/en.ts +0 -397
  253. package/src/cli/cmd/tui/i18n/es.ts +0 -433
  254. package/src/cli/cmd/tui/i18n/fr.ts +0 -440
  255. package/src/cli/cmd/tui/i18n/ja.ts +0 -392
  256. package/src/cli/cmd/tui/i18n/locales.ts +0 -82
  257. package/src/cli/cmd/tui/i18n/ru.ts +0 -452
  258. package/src/cli/cmd/tui/i18n/zh.ts +0 -390
  259. package/src/cli/cmd/tui/i18n/zht.ts +0 -360
  260. package/src/cli/cmd/tui/layer.ts +0 -6
  261. package/src/cli/cmd/tui/plugin/api.tsx +0 -405
  262. package/src/cli/cmd/tui/plugin/index.ts +0 -3
  263. package/src/cli/cmd/tui/plugin/internal.ts +0 -35
  264. package/src/cli/cmd/tui/plugin/runtime.ts +0 -1030
  265. package/src/cli/cmd/tui/plugin/slots.tsx +0 -60
  266. package/src/cli/cmd/tui/routes/home.tsx +0 -172
  267. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -76
  268. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -116
  269. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -47
  270. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
  271. package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
  272. package/src/cli/cmd/tui/routes/session/index.tsx +0 -2532
  273. package/src/cli/cmd/tui/routes/session/permission.tsx +0 -691
  274. package/src/cli/cmd/tui/routes/session/question.tsx +0 -488
  275. package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -97
  276. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +0 -142
  277. package/src/cli/cmd/tui/thread.ts +0 -246
  278. package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -61
  279. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -95
  280. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -223
  281. package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -42
  282. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -123
  283. package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -452
  284. package/src/cli/cmd/tui/ui/dialog.tsx +0 -207
  285. package/src/cli/cmd/tui/ui/link.tsx +0 -28
  286. package/src/cli/cmd/tui/ui/spinner.ts +0 -378
  287. package/src/cli/cmd/tui/ui/toast.tsx +0 -102
  288. package/src/cli/cmd/tui/util/clipboard.ts +0 -203
  289. package/src/cli/cmd/tui/util/editor.ts +0 -35
  290. package/src/cli/cmd/tui/util/image-protocol.ts +0 -35
  291. package/src/cli/cmd/tui/util/index.ts +0 -6
  292. package/src/cli/cmd/tui/util/model.ts +0 -23
  293. package/src/cli/cmd/tui/util/provider-origin.ts +0 -7
  294. package/src/cli/cmd/tui/util/revert-diff.ts +0 -18
  295. package/src/cli/cmd/tui/util/scroll.ts +0 -23
  296. package/src/cli/cmd/tui/util/selection.ts +0 -23
  297. package/src/cli/cmd/tui/util/signal.ts +0 -41
  298. package/src/cli/cmd/tui/util/sound.ts +0 -154
  299. package/src/cli/cmd/tui/util/system-locale.ts +0 -209
  300. package/src/cli/cmd/tui/util/terminal.ts +0 -110
  301. package/src/cli/cmd/tui/util/transcript.ts +0 -112
  302. package/src/cli/cmd/tui/util/vad.ts +0 -229
  303. package/src/cli/cmd/tui/util/voice.ts +0 -360
  304. package/src/cli/cmd/tui/win32.ts +0 -130
  305. package/src/cli/cmd/tui/worker.ts +0 -104
  306. package/src/cli/cmd/uninstall.ts +0 -351
  307. package/src/cli/cmd/upgrade.ts +0 -79
  308. package/src/cli/cmd/web.ts +0 -81
  309. package/src/cli/effect/prompt.ts +0 -25
  310. package/src/cli/error.ts +0 -82
  311. package/src/cli/heap.ts +0 -59
  312. package/src/cli/i18n.ts +0 -15
  313. package/src/cli/logo.ts +0 -59
  314. package/src/cli/network.ts +0 -62
  315. package/src/cli/ui.ts +0 -133
  316. package/src/cli/upgrade.ts +0 -41
  317. package/src/command/index.ts +0 -276
  318. package/src/command/template/initialize.txt +0 -66
  319. package/src/command/template/review.txt +0 -101
  320. package/src/config/agent.ts +0 -197
  321. package/src/config/command.ts +0 -69
  322. package/src/config/config.ts +0 -1024
  323. package/src/config/console-state.ts +0 -16
  324. package/src/config/entry-name.ts +0 -16
  325. package/src/config/error.ts +0 -21
  326. package/src/config/formatter.ts +0 -17
  327. package/src/config/history.ts +0 -21
  328. package/src/config/index.ts +0 -16
  329. package/src/config/keybinds.ts +0 -127
  330. package/src/config/layout.ts +0 -10
  331. package/src/config/lsp.ts +0 -45
  332. package/src/config/managed.ts +0 -70
  333. package/src/config/markdown.ts +0 -97
  334. package/src/config/mcp.ts +0 -172
  335. package/src/config/model-id.ts +0 -14
  336. package/src/config/parse.ts +0 -44
  337. package/src/config/paths.ts +0 -73
  338. package/src/config/permission.ts +0 -76
  339. package/src/config/plugin.ts +0 -88
  340. package/src/config/provider.ts +0 -118
  341. package/src/config/server.ts +0 -20
  342. package/src/config/skills.ts +0 -16
  343. package/src/config/variable.ts +0 -90
  344. package/src/control-plane/adaptors/index.ts +0 -52
  345. package/src/control-plane/adaptors/worktree.ts +0 -47
  346. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  347. package/src/control-plane/schema.ts +0 -19
  348. package/src/control-plane/sse.ts +0 -66
  349. package/src/control-plane/types.ts +0 -34
  350. package/src/control-plane/util.ts +0 -37
  351. package/src/control-plane/workspace-context.ts +0 -26
  352. package/src/control-plane/workspace.sql.ts +0 -17
  353. package/src/control-plane/workspace.ts +0 -615
  354. package/src/effect/app-runtime.ts +0 -146
  355. package/src/effect/bootstrap-runtime.ts +0 -33
  356. package/src/effect/bridge.ts +0 -48
  357. package/src/effect/cross-spawn-spawner.ts +0 -514
  358. package/src/effect/index.ts +0 -5
  359. package/src/effect/instance-ref.ts +0 -11
  360. package/src/effect/instance-registry.ts +0 -12
  361. package/src/effect/instance-state.ts +0 -81
  362. package/src/effect/logger.ts +0 -73
  363. package/src/effect/memo-map.ts +0 -3
  364. package/src/effect/observability.ts +0 -107
  365. package/src/effect/run-service.ts +0 -52
  366. package/src/effect/runner.ts +0 -210
  367. package/src/effect/runtime.ts +0 -19
  368. package/src/env/index.ts +0 -37
  369. package/src/file/ignore.ts +0 -81
  370. package/src/file/index.ts +0 -664
  371. package/src/file/protected.ts +0 -59
  372. package/src/file/ripgrep.ts +0 -485
  373. package/src/file/watcher.ts +0 -163
  374. package/src/flag/flag.ts +0 -172
  375. package/src/format/formatter.ts +0 -403
  376. package/src/format/index.ts +0 -203
  377. package/src/git/index.ts +0 -260
  378. package/src/global/index.ts +0 -54
  379. package/src/history/backfill.ts +0 -162
  380. package/src/history/extract.ts +0 -67
  381. package/src/history/fts-query.ts +0 -15
  382. package/src/history/fts.sql.ts +0 -20
  383. package/src/history/index.ts +0 -10
  384. package/src/history/resolve.ts +0 -65
  385. package/src/history/service.ts +0 -258
  386. package/src/history/writer.ts +0 -112
  387. package/src/id/id.ts +0 -87
  388. package/src/ide/index.ts +0 -73
  389. package/src/inbox/inbox-ref.ts +0 -38
  390. package/src/inbox/inbox.sql.ts +0 -26
  391. package/src/inbox/inbox.ts +0 -223
  392. package/src/inbox/index.ts +0 -3
  393. package/src/inbox/render.ts +0 -40
  394. package/src/index.ts +0 -260
  395. package/src/installation/index.ts +0 -351
  396. package/src/installation/version.ts +0 -8
  397. package/src/lsp/client.ts +0 -249
  398. package/src/lsp/diagnostic.ts +0 -29
  399. package/src/lsp/index.ts +0 -3
  400. package/src/lsp/language.ts +0 -120
  401. package/src/lsp/launch.ts +0 -21
  402. package/src/lsp/lsp.ts +0 -519
  403. package/src/lsp/server.ts +0 -1956
  404. package/src/mcp/auth.ts +0 -144
  405. package/src/mcp/index.ts +0 -944
  406. package/src/mcp/oauth-callback.ts +0 -232
  407. package/src/mcp/oauth-provider.ts +0 -214
  408. package/src/memory/fts-query.ts +0 -37
  409. package/src/memory/fts.sql.ts +0 -19
  410. package/src/memory/index.ts +0 -1
  411. package/src/memory/paths.ts +0 -116
  412. package/src/memory/reconcile.ts +0 -144
  413. package/src/memory/service.ts +0 -144
  414. package/src/metrics/client.ts +0 -35
  415. package/src/metrics/event.ts +0 -43
  416. package/src/metrics/index.ts +0 -5
  417. package/src/metrics/installation.ts +0 -18
  418. package/src/metrics/subscriber.ts +0 -58
  419. package/src/metrics/util.ts +0 -9
  420. package/src/node.ts +0 -6
  421. package/src/npm/config.ts +0 -0
  422. package/src/npm/index.ts +0 -293
  423. package/src/npmcli-config.d.ts +0 -43
  424. package/src/patch/index.ts +0 -680
  425. package/src/permission/arity.ts +0 -163
  426. package/src/permission/evaluate.ts +0 -15
  427. package/src/permission/index.ts +0 -379
  428. package/src/permission/schema.ts +0 -17
  429. package/src/plugin/checkpoint-splitover.ts +0 -60
  430. package/src/plugin/cloudflare.ts +0 -76
  431. package/src/plugin/codex.ts +0 -607
  432. package/src/plugin/github-copilot/copilot.ts +0 -368
  433. package/src/plugin/github-copilot/models.ts +0 -153
  434. package/src/plugin/index.ts +0 -500
  435. package/src/plugin/install.ts +0 -439
  436. package/src/plugin/loader.ts +0 -216
  437. package/src/plugin/matcher.ts +0 -33
  438. package/src/plugin/meta.ts +0 -188
  439. package/src/plugin/shared.ts +0 -323
  440. package/src/plugin/subagent-progress-checker.ts +0 -147
  441. package/src/plugin/woozlit-free.ts +0 -164
  442. package/src/plugin/woozlit.ts +0 -440
  443. package/src/project/bootstrap.ts +0 -59
  444. package/src/project/index.ts +0 -2
  445. package/src/project/instance.ts +0 -190
  446. package/src/project/project-id.ts +0 -48
  447. package/src/project/project.sql.ts +0 -16
  448. package/src/project/project.ts +0 -501
  449. package/src/project/schema.ts +0 -15
  450. package/src/project/vcs.ts +0 -227
  451. package/src/provider/auth.ts +0 -234
  452. package/src/provider/error.ts +0 -216
  453. package/src/provider/index.ts +0 -5
  454. package/src/provider/models.ts +0 -180
  455. package/src/provider/provider.ts +0 -1788
  456. package/src/provider/schema.ts +0 -36
  457. package/src/provider/sdk/copilot/README.md +0 -5
  458. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
  459. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
  460. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
  461. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
  462. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -815
  463. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
  464. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
  465. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
  466. package/src/provider/sdk/copilot/copilot-provider.ts +0 -100
  467. package/src/provider/sdk/copilot/index.ts +0 -2
  468. package/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
  469. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
  470. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
  471. package/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
  472. package/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
  473. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
  474. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1770
  475. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
  476. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
  477. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
  478. package/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
  479. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
  480. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
  481. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
  482. package/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
  483. package/src/provider/transform.ts +0 -1350
  484. package/src/pty/index.ts +0 -364
  485. package/src/pty/pty.bun.ts +0 -26
  486. package/src/pty/pty.node.ts +0 -27
  487. package/src/pty/pty.ts +0 -25
  488. package/src/pty/schema.ts +0 -17
  489. package/src/question/index.ts +0 -252
  490. package/src/question/schema.ts +0 -17
  491. package/src/server/adapter.bun.ts +0 -40
  492. package/src/server/adapter.node.ts +0 -66
  493. package/src/server/adapter.ts +0 -21
  494. package/src/server/error.ts +0 -53
  495. package/src/server/event.ts +0 -7
  496. package/src/server/fence.ts +0 -81
  497. package/src/server/mdns.ts +0 -60
  498. package/src/server/middleware.ts +0 -92
  499. package/src/server/projectors.ts +0 -28
  500. package/src/server/proxy.ts +0 -171
  501. package/src/server/routes/control/index.ts +0 -218
  502. package/src/server/routes/control/workspace.ts +0 -203
  503. package/src/server/routes/global.ts +0 -287
  504. package/src/server/routes/instance/bash-interactive.ts +0 -82
  505. package/src/server/routes/instance/config.ts +0 -89
  506. package/src/server/routes/instance/event.ts +0 -88
  507. package/src/server/routes/instance/experimental.ts +0 -408
  508. package/src/server/routes/instance/file.ts +0 -190
  509. package/src/server/routes/instance/httpapi/config.ts +0 -51
  510. package/src/server/routes/instance/httpapi/permission.ts +0 -72
  511. package/src/server/routes/instance/httpapi/project.ts +0 -62
  512. package/src/server/routes/instance/httpapi/provider.ts +0 -150
  513. package/src/server/routes/instance/httpapi/question.ts +0 -121
  514. package/src/server/routes/instance/httpapi/server.ts +0 -136
  515. package/src/server/routes/instance/index.ts +0 -301
  516. package/src/server/routes/instance/mcp.ts +0 -260
  517. package/src/server/routes/instance/middleware.ts +0 -35
  518. package/src/server/routes/instance/permission.ts +0 -73
  519. package/src/server/routes/instance/project.ts +0 -122
  520. package/src/server/routes/instance/provider.ts +0 -158
  521. package/src/server/routes/instance/pty.ts +0 -247
  522. package/src/server/routes/instance/question.ts +0 -162
  523. package/src/server/routes/instance/session.ts +0 -1296
  524. package/src/server/routes/instance/sync.ts +0 -143
  525. package/src/server/routes/instance/trace.ts +0 -59
  526. package/src/server/routes/instance/tui.ts +0 -384
  527. package/src/server/routes/instance/workflows.ts +0 -72
  528. package/src/server/routes/ui.ts +0 -55
  529. package/src/server/server.ts +0 -136
  530. package/src/server/workspace.ts +0 -122
  531. package/src/session/auto-dream.ts +0 -123
  532. package/src/session/boundary.ts +0 -77
  533. package/src/session/budgeted-read.ts +0 -118
  534. package/src/session/checkpoint-align.ts +0 -29
  535. package/src/session/checkpoint-context.ts +0 -36
  536. package/src/session/checkpoint-paths.ts +0 -86
  537. package/src/session/checkpoint-progress-reconcile.ts +0 -111
  538. package/src/session/checkpoint-retry.ts +0 -192
  539. package/src/session/checkpoint-templates.ts +0 -114
  540. package/src/session/checkpoint-validator.ts +0 -259
  541. package/src/session/checkpoint.ts +0 -1478
  542. package/src/session/classify.ts +0 -92
  543. package/src/session/claude-import.sql.ts +0 -13
  544. package/src/session/claude-import.ts +0 -379
  545. package/src/session/compaction.ts +0 -543
  546. package/src/session/goal.ts +0 -232
  547. package/src/session/index.ts +0 -1
  548. package/src/session/instruction.ts +0 -314
  549. package/src/session/last-message-info.ts +0 -32
  550. package/src/session/llm-request-prefix.ts +0 -82
  551. package/src/session/llm.ts +0 -735
  552. package/src/session/max-mode.ts +0 -397
  553. package/src/session/message-v2.ts +0 -1136
  554. package/src/session/message.ts +0 -191
  555. package/src/session/overflow.ts +0 -53
  556. package/src/session/prefix-capture-ref.ts +0 -48
  557. package/src/session/processor.ts +0 -972
  558. package/src/session/projectors.ts +0 -137
  559. package/src/session/prompt/anthropic.txt +0 -154
  560. package/src/session/prompt/beast.txt +0 -155
  561. package/src/session/prompt/build-switch.txt +0 -5
  562. package/src/session/prompt/codex.txt +0 -79
  563. package/src/session/prompt/compose.txt +0 -115
  564. package/src/session/prompt/copilot-gpt-5.txt +0 -143
  565. package/src/session/prompt/default.txt +0 -151
  566. package/src/session/prompt/gemini.txt +0 -155
  567. package/src/session/prompt/gpt.txt +0 -107
  568. package/src/session/prompt/kimi.txt +0 -95
  569. package/src/session/prompt/max-steps.txt +0 -16
  570. package/src/session/prompt/trinity.txt +0 -97
  571. package/src/session/prompt.ts +0 -3362
  572. package/src/session/prune.ts +0 -481
  573. package/src/session/retry.ts +0 -166
  574. package/src/session/revert.ts +0 -161
  575. package/src/session/run-state.ts +0 -135
  576. package/src/session/schema.ts +0 -36
  577. package/src/session/session.sql.ts +0 -110
  578. package/src/session/session.ts +0 -908
  579. package/src/session/status.ts +0 -89
  580. package/src/session/summary.ts +0 -163
  581. package/src/session/system.ts +0 -86
  582. package/src/session/todo.ts +0 -77
  583. package/src/share/index.ts +0 -2
  584. package/src/share/session.ts +0 -57
  585. package/src/share/share-next.ts +0 -381
  586. package/src/share/share.sql.ts +0 -13
  587. package/src/shell/shell.ts +0 -110
  588. package/src/skill/compose/.bundle/ask/SKILL.md +0 -58
  589. package/src/skill/compose/.bundle/brainstorm/SKILL.md +0 -220
  590. package/src/skill/compose/.bundle/brainstorm/scripts/frame-template.html +0 -214
  591. package/src/skill/compose/.bundle/brainstorm/scripts/helper.js +0 -88
  592. package/src/skill/compose/.bundle/brainstorm/scripts/server.cjs +0 -354
  593. package/src/skill/compose/.bundle/brainstorm/scripts/start-server.sh +0 -148
  594. package/src/skill/compose/.bundle/brainstorm/scripts/stop-server.sh +0 -56
  595. package/src/skill/compose/.bundle/brainstorm/spec-document-reviewer-prompt.md +0 -50
  596. package/src/skill/compose/.bundle/brainstorm/visual-companion.md +0 -287
  597. package/src/skill/compose/.bundle/debug/CREATION-LOG.md +0 -119
  598. package/src/skill/compose/.bundle/debug/SKILL.md +0 -297
  599. package/src/skill/compose/.bundle/debug/condition-based-waiting-example.ts +0 -158
  600. package/src/skill/compose/.bundle/debug/condition-based-waiting.md +0 -115
  601. package/src/skill/compose/.bundle/debug/defense-in-depth.md +0 -122
  602. package/src/skill/compose/.bundle/debug/find-polluter.sh +0 -63
  603. package/src/skill/compose/.bundle/debug/root-cause-tracing.md +0 -169
  604. package/src/skill/compose/.bundle/debug/test-academic.md +0 -14
  605. package/src/skill/compose/.bundle/debug/test-pressure-1.md +0 -58
  606. package/src/skill/compose/.bundle/debug/test-pressure-2.md +0 -68
  607. package/src/skill/compose/.bundle/debug/test-pressure-3.md +0 -69
  608. package/src/skill/compose/.bundle/execute/SKILL.md +0 -71
  609. package/src/skill/compose/.bundle/feedback/SKILL.md +0 -214
  610. package/src/skill/compose/.bundle/merge/SKILL.md +0 -252
  611. package/src/skill/compose/.bundle/new-skill/SKILL.md +0 -656
  612. package/src/skill/compose/.bundle/new-skill/anthropic-best-practices.md +0 -1150
  613. package/src/skill/compose/.bundle/new-skill/examples/CLAUDE_MD_TESTING.md +0 -189
  614. package/src/skill/compose/.bundle/new-skill/graphviz-conventions.dot +0 -172
  615. package/src/skill/compose/.bundle/new-skill/persuasion-principles.md +0 -187
  616. package/src/skill/compose/.bundle/new-skill/render-graphs.js +0 -168
  617. package/src/skill/compose/.bundle/new-skill/testing-skills-with-subagents.md +0 -384
  618. package/src/skill/compose/.bundle/parallel/SKILL.md +0 -182
  619. package/src/skill/compose/.bundle/plan/SKILL.md +0 -161
  620. package/src/skill/compose/.bundle/plan/plan-document-reviewer-prompt.md +0 -50
  621. package/src/skill/compose/.bundle/report/SKILL.md +0 -180
  622. package/src/skill/compose/.bundle/review/SKILL.md +0 -104
  623. package/src/skill/compose/.bundle/review/code-reviewer.md +0 -171
  624. package/src/skill/compose/.bundle/subagent/SKILL.md +0 -344
  625. package/src/skill/compose/.bundle/subagent/code-quality-reviewer-prompt.md +0 -24
  626. package/src/skill/compose/.bundle/subagent/implementer-prompt.md +0 -126
  627. package/src/skill/compose/.bundle/subagent/spec-reviewer-prompt.md +0 -112
  628. package/src/skill/compose/.bundle/tdd/SKILL.md +0 -372
  629. package/src/skill/compose/.bundle/tdd/testing-anti-patterns.md +0 -299
  630. package/src/skill/compose/.bundle/verify/SKILL.md +0 -140
  631. package/src/skill/compose/.bundle/worktree/SKILL.md +0 -234
  632. package/src/skill/compose/LICENSE-karpathy +0 -28
  633. package/src/skill/compose/bundle.macro.ts +0 -30
  634. package/src/skill/compose/extract.ts +0 -85
  635. package/src/skill/discovery.ts +0 -116
  636. package/src/skill/index.ts +0 -311
  637. package/src/snapshot/index.ts +0 -777
  638. package/src/sql.d.ts +0 -4
  639. package/src/storage/db.bun.ts +0 -8
  640. package/src/storage/db.node.ts +0 -8
  641. package/src/storage/db.ts +0 -172
  642. package/src/storage/index.ts +0 -26
  643. package/src/storage/json-migration.ts +0 -426
  644. package/src/storage/schema.sql.ts +0 -10
  645. package/src/storage/schema.ts +0 -7
  646. package/src/storage/storage.ts +0 -331
  647. package/src/sync/README.md +0 -179
  648. package/src/sync/event.sql.ts +0 -16
  649. package/src/sync/index.ts +0 -278
  650. package/src/sync/schema.ts +0 -14
  651. package/src/task/events.ts +0 -28
  652. package/src/task/gate-state.ts +0 -54
  653. package/src/task/gate.ts +0 -116
  654. package/src/task/index.ts +0 -1
  655. package/src/task/registry.ts +0 -387
  656. package/src/task/schema.ts +0 -43
  657. package/src/task/task.sql.ts +0 -50
  658. package/src/team/events.ts +0 -22
  659. package/src/team/index.ts +0 -113
  660. package/src/team/schema.ts +0 -31
  661. package/src/temporary.ts +0 -33
  662. package/src/tool/actor.shell.txt +0 -72
  663. package/src/tool/actor.ts +0 -803
  664. package/src/tool/actor.txt +0 -103
  665. package/src/tool/apply_patch.ts +0 -308
  666. package/src/tool/apply_patch.txt +0 -33
  667. package/src/tool/bash-interactive.ts +0 -183
  668. package/src/tool/bash.ts +0 -696
  669. package/src/tool/bash.txt +0 -123
  670. package/src/tool/change-directory.ts +0 -91
  671. package/src/tool/codesearch.ts +0 -63
  672. package/src/tool/codesearch.txt +0 -12
  673. package/src/tool/edit.ts +0 -685
  674. package/src/tool/edit.txt +0 -10
  675. package/src/tool/external-directory.ts +0 -132
  676. package/src/tool/glob.ts +0 -100
  677. package/src/tool/glob.txt +0 -6
  678. package/src/tool/grep.ts +0 -145
  679. package/src/tool/grep.txt +0 -8
  680. package/src/tool/history.ts +0 -146
  681. package/src/tool/history.txt +0 -17
  682. package/src/tool/index.ts +0 -4
  683. package/src/tool/invalid.ts +0 -20
  684. package/src/tool/invocation-style.ts +0 -17
  685. package/src/tool/lsp.ts +0 -91
  686. package/src/tool/lsp.txt +0 -19
  687. package/src/tool/mcp-exa.ts +0 -78
  688. package/src/tool/memory-path-guard.ts +0 -162
  689. package/src/tool/memory.ts +0 -81
  690. package/src/tool/memory.txt +0 -69
  691. package/src/tool/multiedit.ts +0 -61
  692. package/src/tool/multiedit.txt +0 -41
  693. package/src/tool/plan-enter.txt +0 -14
  694. package/src/tool/plan-exit.txt +0 -13
  695. package/src/tool/plan.ts +0 -90
  696. package/src/tool/question.ts +0 -67
  697. package/src/tool/question.txt +0 -10
  698. package/src/tool/read.ts +0 -327
  699. package/src/tool/read.txt +0 -14
  700. package/src/tool/registry.ts +0 -415
  701. package/src/tool/schema.ts +0 -17
  702. package/src/tool/session-cwd.ts +0 -35
  703. package/src/tool/shell-tokenize.ts +0 -346
  704. package/src/tool/shell-wrap.ts +0 -190
  705. package/src/tool/skill.ts +0 -76
  706. package/src/tool/skill.txt +0 -5
  707. package/src/tool/task.shell.txt +0 -57
  708. package/src/tool/task.ts +0 -456
  709. package/src/tool/task.txt +0 -56
  710. package/src/tool/tool.ts +0 -153
  711. package/src/tool/truncate.ts +0 -201
  712. package/src/tool/truncation-dir.ts +0 -4
  713. package/src/tool/webfetch.ts +0 -199
  714. package/src/tool/webfetch.txt +0 -13
  715. package/src/tool/websearch/index.ts +0 -111
  716. package/src/tool/websearch/mimo.ts +0 -120
  717. package/src/tool/websearch/websearch.txt +0 -14
  718. package/src/tool/workflow.ts +0 -164
  719. package/src/tool/workflow.txt +0 -25
  720. package/src/tool/write.ts +0 -88
  721. package/src/tool/write.txt +0 -9
  722. package/src/util/abort.ts +0 -35
  723. package/src/util/archive.ts +0 -15
  724. package/src/util/color.ts +0 -17
  725. package/src/util/data-url.ts +0 -9
  726. package/src/util/defer.ts +0 -10
  727. package/src/util/effect-http-client.ts +0 -11
  728. package/src/util/effect-zod.ts +0 -367
  729. package/src/util/error.ts +0 -78
  730. package/src/util/filesystem.ts +0 -243
  731. package/src/util/fn.ts +0 -21
  732. package/src/util/format.ts +0 -20
  733. package/src/util/iife.ts +0 -3
  734. package/src/util/index.ts +0 -12
  735. package/src/util/keybind.ts +0 -101
  736. package/src/util/lazy.ts +0 -18
  737. package/src/util/local-context.ts +0 -23
  738. package/src/util/locale.ts +0 -79
  739. package/src/util/lock.ts +0 -96
  740. package/src/util/log.ts +0 -197
  741. package/src/util/media.ts +0 -26
  742. package/src/util/mimo-process.ts +0 -24
  743. package/src/util/network.ts +0 -9
  744. package/src/util/process.ts +0 -174
  745. package/src/util/queue.ts +0 -32
  746. package/src/util/record.ts +0 -3
  747. package/src/util/rpc.ts +0 -64
  748. package/src/util/schema.ts +0 -53
  749. package/src/util/scrap.ts +0 -10
  750. package/src/util/signal.ts +0 -12
  751. package/src/util/timeout.ts +0 -14
  752. package/src/util/token.ts +0 -5
  753. package/src/util/update-schema.ts +0 -13
  754. package/src/util/which.ts +0 -14
  755. package/src/util/wildcard.ts +0 -57
  756. package/src/workflow/builtin/deep-research.js +0 -391
  757. package/src/workflow/builtin.ts +0 -54
  758. package/src/workflow/events.ts +0 -72
  759. package/src/workflow/meta.ts +0 -335
  760. package/src/workflow/persistence.ts +0 -312
  761. package/src/workflow/resolve.ts +0 -45
  762. package/src/workflow/runtime-ref.ts +0 -18
  763. package/src/workflow/runtime.ts +0 -1234
  764. package/src/workflow/sandbox.ts +0 -280
  765. package/src/workflow/workflow.sql.ts +0 -31
  766. package/src/workflow/workspace.ts +0 -69
  767. package/src/worktree/index.ts +0 -614
  768. package/sst-env.d.ts +0 -10
  769. package/test/AGENTS.md +0 -133
  770. package/test/account/repo.test.ts +0 -352
  771. package/test/account/service.test.ts +0 -456
  772. package/test/acp/agent-interface.test.ts +0 -51
  773. package/test/acp/event-subscription.test.ts +0 -725
  774. package/test/actor/cancel-cascade.test.ts +0 -432
  775. package/test/actor/no-completion-listener.test.ts +0 -41
  776. package/test/actor/poststop-progress-write-permission.repro.test.ts +0 -414
  777. package/test/actor/registry-render.test.ts +0 -113
  778. package/test/actor/registry-status.test.ts +0 -111
  779. package/test/actor/registry.test.ts +0 -619
  780. package/test/actor/return-header.test.ts +0 -40
  781. package/test/actor/spawn-lifecycle.test.ts +0 -346
  782. package/test/actor/spawn-no-deadlock.test.ts +0 -340
  783. package/test/actor/spawn-notification.test.ts +0 -393
  784. package/test/actor/spawn-task-autostart.test.ts +0 -530
  785. package/test/actor/spawn.test.ts +0 -1072
  786. package/test/actor/status-event-payload.test.ts +0 -132
  787. package/test/actor/terminology.test.ts +0 -39
  788. package/test/actor/turn.test.ts +0 -125
  789. package/test/actor/waiter.test.ts +0 -246
  790. package/test/agent/agent.test.ts +0 -874
  791. package/test/agent/allowlist.test.ts +0 -45
  792. package/test/auth/auth.test.ts +0 -86
  793. package/test/bus/bus-effect.test.ts +0 -162
  794. package/test/bus/bus-integration.test.ts +0 -87
  795. package/test/bus/bus.test.ts +0 -219
  796. package/test/cli/account.test.ts +0 -26
  797. package/test/cli/cmd/tui/prompt-part.test.ts +0 -47
  798. package/test/cli/error.test.ts +0 -18
  799. package/test/cli/github-action.test.ts +0 -198
  800. package/test/cli/github-remote.test.ts +0 -80
  801. package/test/cli/import.test.ts +0 -54
  802. package/test/cli/plugin-auth-picker.test.ts +0 -120
  803. package/test/cli/run-completion.test.ts +0 -131
  804. package/test/cli/tui/keybind-plugin.test.ts +0 -90
  805. package/test/cli/tui/plugin-add.test.ts +0 -111
  806. package/test/cli/tui/plugin-install.test.ts +0 -87
  807. package/test/cli/tui/plugin-lifecycle.test.ts +0 -224
  808. package/test/cli/tui/plugin-loader-entrypoint.test.ts +0 -484
  809. package/test/cli/tui/plugin-loader-pure.test.ts +0 -71
  810. package/test/cli/tui/plugin-loader.test.ts +0 -816
  811. package/test/cli/tui/plugin-toggle.test.ts +0 -157
  812. package/test/cli/tui/revert-diff.test.ts +0 -35
  813. package/test/cli/tui/route-agent-id.test.ts +0 -26
  814. package/test/cli/tui/sidebar-tps.test.ts +0 -63
  815. package/test/cli/tui/slot-replace.test.tsx +0 -47
  816. package/test/cli/tui/sync-bucket.test.ts +0 -29
  817. package/test/cli/tui/theme-store.test.ts +0 -51
  818. package/test/cli/tui/thread.test.ts +0 -121
  819. package/test/cli/tui/transcript.test.ts +0 -426
  820. package/test/cli/tui/use-event.test.tsx +0 -175
  821. package/test/cli/tui/voice.test.ts +0 -269
  822. package/test/command/deep-research-command.test.ts +0 -16
  823. package/test/config/agent-color.test.ts +0 -77
  824. package/test/config/checkpoint-fork.test.ts +0 -21
  825. package/test/config/config.test.ts +0 -2577
  826. package/test/config/fixtures/empty-frontmatter.md +0 -4
  827. package/test/config/fixtures/frontmatter.md +0 -28
  828. package/test/config/fixtures/markdown-header.md +0 -11
  829. package/test/config/fixtures/no-frontmatter.md +0 -1
  830. package/test/config/fixtures/weird-model-id.md +0 -13
  831. package/test/config/lsp.test.ts +0 -87
  832. package/test/config/markdown.test.ts +0 -228
  833. package/test/config/plugin.test.ts +0 -0
  834. package/test/config/tui.test.ts +0 -627
  835. package/test/control-plane/adaptors.test.ts +0 -71
  836. package/test/control-plane/sse.test.ts +0 -56
  837. package/test/effect/app-runtime-logger.test.ts +0 -92
  838. package/test/effect/cross-spawn-spawner.test.ts +0 -411
  839. package/test/effect/instance-state.test.ts +0 -482
  840. package/test/effect/observability.test.ts +0 -46
  841. package/test/effect/run-service.test.ts +0 -46
  842. package/test/effect/runner-warn-log.test.ts +0 -111
  843. package/test/effect/runner.test.ts +0 -494
  844. package/test/fake/provider.ts +0 -90
  845. package/test/file/fsmonitor.test.ts +0 -68
  846. package/test/file/ignore.test.ts +0 -10
  847. package/test/file/index.test.ts +0 -956
  848. package/test/file/path-traversal.test.ts +0 -204
  849. package/test/file/ripgrep.test.ts +0 -214
  850. package/test/file/watcher.test.ts +0 -249
  851. package/test/filesystem/filesystem.test.ts +0 -319
  852. package/test/fixture/db.ts +0 -11
  853. package/test/fixture/fixture.test.ts +0 -58
  854. package/test/fixture/fixture.ts +0 -190
  855. package/test/fixture/flock-worker.ts +0 -72
  856. package/test/fixture/lsp/fake-lsp-server.js +0 -75
  857. package/test/fixture/plug-worker.ts +0 -93
  858. package/test/fixture/plugin-meta-worker.ts +0 -19
  859. package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
  860. package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
  861. package/test/fixture/skills/cloudflare/SKILL.md +0 -211
  862. package/test/fixture/skills/index.json +0 -6
  863. package/test/fixture/tui-plugin.ts +0 -329
  864. package/test/fixture/tui-runtime.ts +0 -31
  865. package/test/format/format.test.ts +0 -244
  866. package/test/git/git.test.ts +0 -128
  867. package/test/global/fixture/global-paths-worker.ts +0 -17
  868. package/test/global/mimocode-home.test.ts +0 -143
  869. package/test/history/backfill.test.ts +0 -149
  870. package/test/history/extract.test.ts +0 -106
  871. package/test/history/fts-query.test.ts +0 -30
  872. package/test/history/resolve.test.ts +0 -130
  873. package/test/history/service.test.ts +0 -210
  874. package/test/history/writer.test.ts +0 -163
  875. package/test/ide/ide.test.ts +0 -82
  876. package/test/inbox/drain-in-loop.test.ts +0 -230
  877. package/test/inbox/fork-agent-compat.test.ts +0 -387
  878. package/test/inbox/gc-on-init.test.ts +0 -167
  879. package/test/inbox/send-no-block.test.ts +0 -120
  880. package/test/inbox/sender-cancel-independence.test.ts +0 -160
  881. package/test/inbox/wake-matrix.test.ts +0 -141
  882. package/test/installation/installation.test.ts +0 -226
  883. package/test/keybind.test.ts +0 -421
  884. package/test/lib/effect.ts +0 -53
  885. package/test/lib/filesystem.ts +0 -10
  886. package/test/lib/llm-server.ts +0 -770
  887. package/test/lib/scripted-llm-server.ts +0 -245
  888. package/test/lsp/client.test.ts +0 -98
  889. package/test/lsp/index.test.ts +0 -109
  890. package/test/lsp/launch.test.ts +0 -22
  891. package/test/lsp/lifecycle.test.ts +0 -184
  892. package/test/mcp/headers.test.ts +0 -178
  893. package/test/mcp/lifecycle.test.ts +0 -824
  894. package/test/mcp/oauth-auto-connect.test.ts +0 -281
  895. package/test/mcp/oauth-browser.test.ts +0 -268
  896. package/test/mcp/oauth-callback.test.ts +0 -34
  897. package/test/memory/abort-leak-webfetch.ts +0 -49
  898. package/test/memory/abort-leak.test.ts +0 -127
  899. package/test/memory/cc-frontmatter.test.ts +0 -85
  900. package/test/memory/cc-paths.test.ts +0 -60
  901. package/test/memory/cc-reconcile.test.ts +0 -239
  902. package/test/memory/cc-search.test.ts +0 -151
  903. package/test/memory/fts-query.test.ts +0 -48
  904. package/test/memory/fts-rowid-stability.test.ts +0 -271
  905. package/test/memory/paths.test.ts +0 -210
  906. package/test/memory/reconcile.test.ts +0 -115
  907. package/test/memory/service.test.ts +0 -169
  908. package/test/npm.test.ts +0 -18
  909. package/test/patch/patch.test.ts +0 -348
  910. package/test/permission/abort.test.ts +0 -116
  911. package/test/permission/arity.test.ts +0 -33
  912. package/test/permission/disabled.test.ts +0 -51
  913. package/test/permission/next.test.ts +0 -1080
  914. package/test/permission/non-interactive.test.ts +0 -55
  915. package/test/permission-task.test.ts +0 -326
  916. package/test/plugin/actor-hooks.test.ts +0 -1471
  917. package/test/plugin/auth-override.test.ts +0 -79
  918. package/test/plugin/checkpoint-splitover.test.ts +0 -434
  919. package/test/plugin/cloudflare.test.ts +0 -68
  920. package/test/plugin/codex.test.ts +0 -123
  921. package/test/plugin/github-copilot-models.test.ts +0 -163
  922. package/test/plugin/install-concurrency.test.ts +0 -140
  923. package/test/plugin/install.test.ts +0 -570
  924. package/test/plugin/loader-shared.test.ts +0 -1169
  925. package/test/plugin/matcher.test.ts +0 -97
  926. package/test/plugin/meta.test.ts +0 -137
  927. package/test/plugin/shared.test.ts +0 -88
  928. package/test/plugin/subagent-progress-checker.test.ts +0 -227
  929. package/test/plugin/trigger.test.ts +0 -116
  930. package/test/plugin/woozlit.test.ts +0 -68
  931. package/test/plugin/workspace-adaptor.test.ts +0 -109
  932. package/test/preload.ts +0 -102
  933. package/test/project/migrate-global.test.ts +0 -150
  934. package/test/project/project-id.test.ts +0 -64
  935. package/test/project/project.test.ts +0 -481
  936. package/test/project/vcs.test.ts +0 -286
  937. package/test/project/worktree-remove.test.ts +0 -126
  938. package/test/project/worktree.test.ts +0 -214
  939. package/test/provider/amazon-bedrock.test.ts +0 -462
  940. package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
  941. package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
  942. package/test/provider/error.test.ts +0 -160
  943. package/test/provider/gitlab-duo.test.ts +0 -413
  944. package/test/provider/model-groups.test.ts +0 -389
  945. package/test/provider/provider-chunk-timeout.test.ts +0 -23
  946. package/test/provider/provider.test.ts +0 -2648
  947. package/test/provider/transform.test.ts +0 -3379
  948. package/test/pty/pty-output-isolation.test.ts +0 -146
  949. package/test/pty/pty-session.test.ts +0 -102
  950. package/test/pty/pty-shell.test.ts +0 -69
  951. package/test/question/question.test.ts +0 -464
  952. package/test/server/global-session-list.test.ts +0 -105
  953. package/test/server/project-init-git.test.ts +0 -122
  954. package/test/server/session-actions.test.ts +0 -49
  955. package/test/server/session-list.test.ts +0 -110
  956. package/test/server/session-messages.test.ts +0 -220
  957. package/test/server/session-prompt-busy.test.ts +0 -146
  958. package/test/server/session-select.test.ts +0 -100
  959. package/test/server/session-task-route.test.ts +0 -165
  960. package/test/server/summarize-route-main-slice.test.ts +0 -99
  961. package/test/server/trace-attributes.test.ts +0 -76
  962. package/test/server/workflows-route.test.ts +0 -279
  963. package/test/session/bootstrap-skip-system.test.ts +0 -121
  964. package/test/session/boundary.test.ts +0 -33
  965. package/test/session/budgeted-read.test.ts +0 -74
  966. package/test/session/checkpoint-align.test.ts +0 -58
  967. package/test/session/checkpoint-boundary.test.ts +0 -186
  968. package/test/session/checkpoint-child-session.test.ts +0 -508
  969. package/test/session/checkpoint-context.test.ts +0 -141
  970. package/test/session/checkpoint-drain.test.ts +0 -188
  971. package/test/session/checkpoint-extract-titles.test.ts +0 -58
  972. package/test/session/checkpoint-fork-mode.test.ts +0 -576
  973. package/test/session/checkpoint-main-slice.test.ts +0 -259
  974. package/test/session/checkpoint-paths.test.ts +0 -78
  975. package/test/session/checkpoint-permission.test.ts +0 -136
  976. package/test/session/checkpoint-progress-reconcile.test.ts +0 -219
  977. package/test/session/checkpoint-rebuild-unify.test.ts +0 -143
  978. package/test/session/checkpoint-rebuild-v3.test.ts +0 -248
  979. package/test/session/checkpoint-render-verify.test.ts +0 -512
  980. package/test/session/checkpoint-retry.test.ts +0 -150
  981. package/test/session/checkpoint-splitover-integration.test.ts +0 -533
  982. package/test/session/checkpoint-templates.test.ts +0 -51
  983. package/test/session/checkpoint-thresholds.test.ts +0 -120
  984. package/test/session/checkpoint-validator.test.ts +0 -189
  985. package/test/session/classify-integration.test.ts +0 -476
  986. package/test/session/classify.test.ts +0 -335
  987. package/test/session/compaction-agent-scope.test.ts +0 -164
  988. package/test/session/context-inheritance.test.ts +0 -46
  989. package/test/session/fork-prefix-invariant.test.ts +0 -116
  990. package/test/session/goal.test.ts +0 -106
  991. package/test/session/instruction.test.ts +0 -387
  992. package/test/session/invalid-output-continuation.test.ts +0 -150
  993. package/test/session/last-message-info.test.ts +0 -47
  994. package/test/session/length-tool-safety.test.ts +0 -121
  995. package/test/session/llm-request-prefix.test.ts +0 -197
  996. package/test/session/llm-retry.test.ts +0 -59
  997. package/test/session/llm-system-prompt.test.ts +0 -479
  998. package/test/session/llm.test.ts +0 -1272
  999. package/test/session/main-lifecycle.test.ts +0 -51
  1000. package/test/session/main-runloop-history-invariant.test.ts +0 -182
  1001. package/test/session/max-mode-econnreset.test.ts +0 -229
  1002. package/test/session/max-mode.test.ts +0 -54
  1003. package/test/session/message-v2-filter.test.ts +0 -197
  1004. package/test/session/message-v2.test.ts +0 -1119
  1005. package/test/session/messages-default-main.test.ts +0 -105
  1006. package/test/session/messages-pagination.test.ts +0 -888
  1007. package/test/session/overflow.test.ts +0 -576
  1008. package/test/session/processor-effect.test.ts +0 -853
  1009. package/test/session/prompt-effect.test.ts +0 -1574
  1010. package/test/session/prompt-rebuild-loop.test.ts +0 -108
  1011. package/test/session/prompt-rebuild-reset.test.ts +0 -67
  1012. package/test/session/prompt-sweep.test.ts +0 -145
  1013. package/test/session/prompt-task-gate.test.ts +0 -127
  1014. package/test/session/prompt.test.ts +0 -703
  1015. package/test/session/prune-main-slice.test.ts +0 -272
  1016. package/test/session/prune-skip-system.test.ts +0 -346
  1017. package/test/session/prune.test.ts +0 -419
  1018. package/test/session/rebuild-microcompact.test.ts +0 -318
  1019. package/test/session/recall-reminder.test.ts +0 -37
  1020. package/test/session/retry.test.ts +0 -410
  1021. package/test/session/revert-compact.test.ts +0 -639
  1022. package/test/session/run-state-tuple-key.test.ts +0 -152
  1023. package/test/session/session-create-registers-main.test.ts +0 -70
  1024. package/test/session/session.test.ts +0 -181
  1025. package/test/session/snapshot-tool-race.test.ts +0 -301
  1026. package/test/session/structured-output-integration.test.ts +0 -264
  1027. package/test/session/structured-output-retry.test.ts +0 -127
  1028. package/test/session/structured-output.test.ts +0 -397
  1029. package/test/session/summary-main-slice.test.ts +0 -170
  1030. package/test/session/system.test.ts +0 -72
  1031. package/test/share/share-next.test.ts +0 -332
  1032. package/test/shell/shell.test.ts +0 -73
  1033. package/test/skill/compose-review.test.ts +0 -141
  1034. package/test/skill/discovery.test.ts +0 -116
  1035. package/test/skill/skill.test.ts +0 -465
  1036. package/test/snapshot/snapshot.test.ts +0 -1531
  1037. package/test/storage/db.test.ts +0 -16
  1038. package/test/storage/json-migration.test.ts +0 -831
  1039. package/test/storage/storage.test.ts +0 -293
  1040. package/test/sync/index.test.ts +0 -237
  1041. package/test/task/gate-state.test.ts +0 -66
  1042. package/test/task/gate.test.ts +0 -167
  1043. package/test/task/registry.test.ts +0 -152
  1044. package/test/task/state-machine.test.ts +0 -292
  1045. package/test/team/migrate-to-inbox.test.ts +0 -124
  1046. package/test/team/team.test.ts +0 -75
  1047. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  1048. package/test/tool/actor-cancel.test.ts +0 -206
  1049. package/test/tool/actor-recover.test.ts +0 -50
  1050. package/test/tool/actor-send.test.ts +0 -200
  1051. package/test/tool/actor-status.test.ts +0 -296
  1052. package/test/tool/actor-wait.test.ts +0 -193
  1053. package/test/tool/actor.shell.test.ts +0 -250
  1054. package/test/tool/actor.test.ts +0 -748
  1055. package/test/tool/apply_patch.test.ts +0 -626
  1056. package/test/tool/bash.test.ts +0 -1195
  1057. package/test/tool/describe-workflow.test.ts +0 -12
  1058. package/test/tool/edit.test.ts +0 -691
  1059. package/test/tool/external-directory.test.ts +0 -207
  1060. package/test/tool/fixtures/large-image.png +0 -0
  1061. package/test/tool/fixtures/models-api.json +0 -65179
  1062. package/test/tool/glob.test.ts +0 -81
  1063. package/test/tool/grep.test.ts +0 -114
  1064. package/test/tool/history.test.ts +0 -144
  1065. package/test/tool/invocation-style.test.ts +0 -30
  1066. package/test/tool/memory-edit-ask-skip.test.ts +0 -62
  1067. package/test/tool/memory-path-guard.test.ts +0 -594
  1068. package/test/tool/memory.test.ts +0 -71
  1069. package/test/tool/question.test.ts +0 -167
  1070. package/test/tool/read.test.ts +0 -483
  1071. package/test/tool/registry-invocation-style.test.ts +0 -121
  1072. package/test/tool/registry.test.ts +0 -164
  1073. package/test/tool/shell-tokenize.test.ts +0 -273
  1074. package/test/tool/shell-wrap-missing-script.test.ts +0 -128
  1075. package/test/tool/shell-wrap.test.ts +0 -257
  1076. package/test/tool/skill.test.ts +0 -99
  1077. package/test/tool/task-recover.test.ts +0 -36
  1078. package/test/tool/task.shell.test.ts +0 -234
  1079. package/test/tool/task.test.ts +0 -296
  1080. package/test/tool/tool-def-shell-shape.test.ts +0 -23
  1081. package/test/tool/tool-define.test.ts +0 -59
  1082. package/test/tool/truncation.test.ts +0 -253
  1083. package/test/tool/webfetch.test.ts +0 -103
  1084. package/test/tool/whitelist.test.ts +0 -373
  1085. package/test/tool/write.test.ts +0 -244
  1086. package/test/util/data-url.test.ts +0 -14
  1087. package/test/util/effect-zod.test.ts +0 -869
  1088. package/test/util/error.test.ts +0 -38
  1089. package/test/util/filesystem.test.ts +0 -656
  1090. package/test/util/format.test.ts +0 -59
  1091. package/test/util/glob.test.ts +0 -164
  1092. package/test/util/iife.test.ts +0 -36
  1093. package/test/util/lazy.test.ts +0 -50
  1094. package/test/util/lock.test.ts +0 -72
  1095. package/test/util/log.test.ts +0 -44
  1096. package/test/util/module.test.ts +0 -59
  1097. package/test/util/process.test.ts +0 -128
  1098. package/test/util/timeout.test.ts +0 -21
  1099. package/test/util/which.test.ts +0 -100
  1100. package/test/util/wildcard.test.ts +0 -90
  1101. package/test/workflow/builtin.test.ts +0 -22
  1102. package/test/workflow/deep-research-cluster.test.ts +0 -47
  1103. package/test/workflow/lib.ts +0 -243
  1104. package/test/workflow/meta.test.ts +0 -142
  1105. package/test/workflow/model-routing.test.ts +0 -68
  1106. package/test/workflow/persistence.test.ts +0 -229
  1107. package/test/workflow/resolve.test.ts +0 -37
  1108. package/test/workflow/runtime-nested.test.ts +0 -419
  1109. package/test/workflow/runtime-worktree.test.ts +0 -261
  1110. package/test/workflow/runtime.test.ts +0 -1078
  1111. package/test/workflow/sandbox.test.ts +0 -259
  1112. package/test/workflow/tool.test.ts +0 -473
  1113. package/test/workflow/verify-wow.test.ts +0 -144
  1114. package/test/workflow/workspace.test.ts +0 -88
  1115. package/test/workspace/workspace-restore.test.ts +0 -281
  1116. package/test/worktree/index.test.ts +0 -30
  1117. package/tsconfig.json +0 -24
  1118. /package/{script/postinstall.mjs → postinstall.mjs} +0 -0
@@ -1,3362 +0,0 @@
1
- import path from "path"
2
- import os from "os"
3
- import z from "zod"
4
- import { SessionID, MessageID, PartID } from "./schema"
5
- import { MessageV2 } from "./message-v2"
6
- import { classifyAssistantStep } from "./classify"
7
- import { Log } from "../util"
8
- import { SessionRevert } from "./revert"
9
- import * as Session from "./session"
10
- import { Agent } from "../agent/agent"
11
- import { SYSTEM_SPAWNED_AGENT_TYPES } from "@/agent/config"
12
- import { Provider } from "../provider"
13
- import { ModelID, ProviderID } from "../provider/schema"
14
- import { type Tool as AITool, type ModelMessage, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai"
15
- import type { JSONSchema7 } from "@ai-sdk/provider"
16
- import { SessionPrune } from "./prune"
17
- import { SessionCheckpoint } from "./checkpoint"
18
- import { SessionCompaction } from "./compaction"
19
- import { computeLastMessageInfo } from "./last-message-info"
20
- import { pressureLevel, isOverflow as overflowCheck } from "./overflow"
21
- import { Config } from "@/config"
22
- import { Global } from "@/global"
23
- import { Bus } from "../bus"
24
- import { ProviderTransform } from "../provider"
25
- import { SystemPrompt } from "./system"
26
- import { Instruction } from "./instruction"
27
- import { TuiEvent } from "@/cli/cmd/tui/event"
28
- import { Plugin } from "../plugin"
29
- import BUILD_SWITCH from "../session/prompt/build-switch.txt"
30
- import MAX_STEPS from "../session/prompt/max-steps.txt"
31
- import PROMPT_COMPOSE from "../session/prompt/compose.txt"
32
- import { composeSkillsBlock } from "@/skill/compose/extract"
33
- import { ToolRegistry } from "../tool"
34
- import { MCP } from "../mcp"
35
- import { LSP } from "../lsp"
36
- import { Flag } from "../flag/flag"
37
- import { ulid } from "ulid"
38
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
39
- import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
40
- import * as Stream from "effect/Stream"
41
- import { Command } from "../command"
42
- import { pathToFileURL, fileURLToPath } from "url"
43
- import { ConfigMarkdown } from "../config"
44
- import { SessionSummary } from "./summary"
45
- import { NamedError } from "@woozlit/shared/util/error"
46
- import { SessionProcessor } from "./processor"
47
- import { buildLLMRequestPrefix } from "./llm-request-prefix"
48
- import { prefixCaptureRef } from "./prefix-capture-ref"
49
- import { spawnRef } from "@/actor/spawn-ref"
50
- import { Inbox } from "@/inbox"
51
- import { sessionPromptRef } from "@/inbox/inbox-ref"
52
- import { Tool } from "@/tool"
53
- import { Permission } from "@/permission"
54
- import { SessionStatus } from "./status"
55
- import { LLM } from "./llm"
56
- import { MaxMode } from "./max-mode"
57
- import { Shell } from "@/shell/shell"
58
- import { AppFileSystem } from "@woozlit/shared/filesystem"
59
- import { Truncate } from "@/tool"
60
- import { decodeDataUrl } from "@/util/data-url"
61
- import { Process } from "@/util"
62
- import { Cause, Effect, Exit, Layer, Option, Scope, Context } from "effect"
63
- import { EffectLogger } from "@/effect"
64
- import { InstanceState } from "@/effect"
65
- import { ActorTool, type ActorPromptOps } from "@/tool/actor"
66
- import { SessionRunState } from "./run-state"
67
- import { Goal } from "./goal"
68
- import { TaskGate, MAX_TASK_GATE_MAIN_REACT } from "@/task/gate"
69
- import { TaskGateState } from "@/task/gate-state"
70
- import { TaskRegistry } from "@/task/registry"
71
- import { EffectBridge } from "@/effect"
72
- import { Team } from "@/team"
73
- import { ActorRegistry } from "@/actor/registry"
74
- import { Metrics } from "@/metrics"
75
- import { resolveInvocationStyle, type ToolStyleConfig } from "../tool/invocation-style"
76
- import { shouldAutoDream, shouldAutoDistill, DREAM_TASK, DISTILL_TASK, AUTO_DREAM_TITLE, AUTO_DISTILL_TITLE } from "./auto-dream"
77
-
78
- // @ts-ignore
79
- globalThis.AI_SDK_LOG_WARNINGS = false
80
-
81
- // Recall-reminder hints, rendered in each tool's configured invocation style so
82
- // shell-mode sessions never see a JSON-shaped example (which primes models to
83
- // emit JSON and crash the shell parser). `memory` has no shell form, so it is
84
- // always JSON. Exported for unit testing.
85
- export function recallHintLines(toolCfg: ToolStyleConfig | undefined): string[] {
86
- const taskHint =
87
- resolveInvocationStyle(toolCfg, "task") === "shell" ? "- task list" : `- task({ operation: "list" })`
88
- const actorHint =
89
- resolveInvocationStyle(toolCfg, "actor") === "shell"
90
- ? "- actor status <actor_id>"
91
- : `- actor({ operation: "status", actor_id: "<id>" })`
92
- // memory has no shell form (no shell.parse) → always JSON.
93
- return [`- memory({ operation: "search", query: "<keyword>" })`, taskHint, actorHint]
94
- }
95
-
96
- /**
97
- * Cap on goal-driven main-loop re-entries per turn — the safety valve against
98
- * a never-satisfiable condition burning tokens forever. Higher than spawned
99
- * actors' MAX_PRE_REACT (=3) because main-session goals are usually larger.
100
- * TODO: lift to WOOZLIT_CODE.json config (e.g. session.maxGoalReact).
101
- */
102
- const MAX_GOAL_REACT = 12
103
-
104
- /**
105
- * Number of consecutive finished assistant steps with an identical action
106
- * signature that trips the repeated-step nudge. Three in a row is a strong
107
- * signal the model is stuck repeating itself rather than making progress.
108
- */
109
- const REPEATED_STEP_THRESHOLD = 3
110
-
111
- /**
112
- * Deterministic JSON serialization with sorted object keys, so that two
113
- * semantically-identical tool inputs produce the same string regardless of the
114
- * order the model happened to emit the keys in. `JSON.stringify` preserves
115
- * insertion order, and models routinely re-emit the same arguments with keys in
116
- * a different order (e.g. {url,format} vs {format,url}) — without this the
117
- * signatures would differ and the repeated-step check would miss real loops.
118
- */
119
- function stableStringify(value: unknown): string {
120
- if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null"
121
- if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]"
122
- const keys = Object.keys(value as Record<string, unknown>).sort()
123
- return (
124
- "{" +
125
- keys.map((k) => JSON.stringify(k) + ":" + stableStringify((value as Record<string, unknown>)[k])).join(",") +
126
- "}"
127
- )
128
- }
129
-
130
- /**
131
- * Stable signature for an assistant step's *action* — the tool calls it made
132
- * (name + key-order-independent input). Text and reasoning are excluded on
133
- * purpose: in a ReAct loop the model narrates each step in slightly different
134
- * words while taking the exact same action, and some models emit their
135
- * reasoning as plain text parts — counting either would mask the repeated
136
- * action we want to catch. Returns undefined when a step makes no tool calls
137
- * (e.g. a pure-text turn), since there is no repeated *action* to compare.
138
- */
139
- function stepSignature(parts: MessageV2.Part[]): string | undefined {
140
- const segments: string[] = []
141
- for (const part of parts) {
142
- if (part.type === "tool") {
143
- segments.push("tool:" + part.tool + ":" + stableStringify(part.state.input ?? {}))
144
- }
145
- }
146
- if (segments.length === 0) return undefined
147
- return segments.join("\n")
148
- }
149
-
150
- const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format.
151
-
152
- IMPORTANT:
153
- - You MUST call this tool exactly once at the end of your response
154
- - The input must be valid JSON matching the required schema
155
- - Complete all necessary research and tool calls BEFORE calling this tool
156
- - This tool provides your final answer - no further actions are taken after calling it`
157
-
158
- 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.`
159
-
160
- const PREDICT_SYSTEM = `You predict the single most likely next message a user will send to a coding assistant, based on the conversation so far. Output only that next message as one short, natural first-person request (what the user would type). No preamble, no quotes, no explanation, no markdown. Keep it under 100 characters.`
161
-
162
- const PREDICT_NUDGE = `Based on the conversation above, write the user's most likely next message:`
163
-
164
- const OUTPUT_LENGTH_CONTINUATION_LIMIT = Flag.WOOZLIT_CODE_OUTPUT_LENGTH_CONTINUATION_LIMIT
165
- const INVALID_OUTPUT_CONTINUATION_LIMIT = Flag.WOOZLIT_CODE_INVALID_OUTPUT_CONTINUATION_LIMIT
166
-
167
- const log = Log.create({ service: "session.prompt" })
168
- const elog = EffectLogger.create({ service: "session.prompt" })
169
-
170
- export interface Interface {
171
- readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
172
- readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts>
173
- readonly loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts>
174
- readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts>
175
- readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts>
176
- readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
177
- readonly sweepOrphanAssistants: (sessionID: SessionID) => Effect.Effect<void>
178
- readonly predict: (input: { sessionID: SessionID }) => Effect.Effect<string>
179
- }
180
-
181
- export class Service extends Context.Service<Service, Interface>()("@opencode/SessionPrompt") {}
182
-
183
- export const layer = Layer.effect(
184
- Service,
185
- Effect.gen(function* () {
186
- const bus = yield* Bus.Service
187
- const status = yield* SessionStatus.Service
188
- const sessions = yield* Session.Service
189
- const agents = yield* Agent.Service
190
- const provider = yield* Provider.Service
191
- const processor = yield* SessionProcessor.Service
192
- const prune = yield* SessionPrune.Service
193
- const checkpoint = yield* SessionCheckpoint.Service
194
- const compaction = yield* SessionCompaction.Service
195
- const config = yield* Config.Service
196
- const plugin = yield* Plugin.Service
197
- const commands = yield* Command.Service
198
- const permission = yield* Permission.Service
199
- const fsys = yield* AppFileSystem.Service
200
- const mcp = yield* MCP.Service
201
- const lsp = yield* LSP.Service
202
- const registry = yield* ToolRegistry.Service
203
- const truncate = yield* Truncate.Service
204
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
205
- const scope = yield* Scope.Scope
206
- const instruction = yield* Instruction.Service
207
- const state = yield* SessionRunState.Service
208
- const goal = yield* Goal.Service
209
- const taskGateState = yield* TaskGateState.Service
210
- const taskRegistry = yield* TaskRegistry.Service
211
- const revert = yield* SessionRevert.Service
212
- const summary = yield* SessionSummary.Service
213
- const sys = yield* SystemPrompt.Service
214
- const llm = yield* LLM.Service
215
- const actorRegistry = yield* ActorRegistry.Service
216
- const inbox = yield* Inbox.Service
217
-
218
- // Track sessions that have already shown the "loaded instructions" toast so we
219
- // surface it once per primary session rather than on every run-loop turn.
220
- const instructionsNotified = new Set<SessionID>()
221
-
222
- // Late-bind prefix-capture helper so SessionCheckpoint.tryStartCheckpointWriter
223
- // can call buildLLMRequestPrefix without forming a layer cycle
224
- // (ToolRegistry → SessionCheckpoint → ToolRegistry). See prefix-capture-ref.ts.
225
- // The closure resolves Agent.Info and Provider.Model internally so checkpoint.ts
226
- // only needs to pass string IDs.
227
- const capture: typeof prefixCaptureRef.current = (input) =>
228
- Effect.gen(function* () {
229
- const empty = { system: [] as string[], tools: {} as Record<string, AITool>, inheritedMessages: [] as ModelMessage[], parentPermission: [] as Permission.Ruleset }
230
- const ag = yield* agents.get(input.agentName).pipe(Effect.catch(() => Effect.succeed(undefined)))
231
- if (!ag) return empty
232
- const model = yield* provider
233
- .getModel(input.providerID as ProviderID, input.modelID as ModelID)
234
- .pipe(Effect.catch(() => Effect.succeed(undefined)))
235
- if (!model) return empty
236
- const [skills, env, instructions] = yield* Effect.all([
237
- sys.skills(ag),
238
- Effect.sync(() => sys.environment(model)),
239
- instruction.system().pipe(Effect.orDie),
240
- ])
241
- // (checkpoint-writer never requests json_schema output, so STRUCTURED_OUTPUT_SYSTEM_PROMPT
242
- // is not included; parent's runLoop adds it conditionally based on user.format)
243
- const additions = [...env, ...(skills ? [skills] : []), ...instructions.content]
244
- const prefix = yield* buildLLMRequestPrefix({
245
- sessionID: input.sessionID,
246
- agent: ag,
247
- model,
248
- msgs: input.msgs as Parameters<typeof buildLLMRequestPrefix>[0]["msgs"],
249
- additions,
250
- }).pipe(
251
- Effect.provideService(LLM.Service, llm),
252
- Effect.provideService(ToolRegistry.Service, registry),
253
- Effect.catch(() => Effect.succeed(empty)),
254
- )
255
- return { ...prefix, parentPermission: ag.permission }
256
- })
257
- prefixCaptureRef.current = capture
258
- yield* Effect.addFinalizer(() =>
259
- Effect.sync(() => {
260
- if (prefixCaptureRef.current === capture) prefixCaptureRef.current = undefined
261
- }),
262
- )
263
-
264
- const runner = Effect.fn("SessionPrompt.runner")(function* () {
265
- return yield* EffectBridge.make()
266
- })
267
- const ops = Effect.fn("SessionPrompt.ops")(function* () {
268
- const run = yield* runner()
269
- return {
270
- cancel: (sessionID: SessionID) => run.fork(cancel(sessionID)),
271
- resolvePromptParts: (template: string) => resolvePromptParts(template),
272
- prompt: (input: PromptInput) => prompt(input),
273
- } satisfies ActorPromptOps
274
- })
275
-
276
- const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
277
- yield* elog.info("cancel", { sessionID })
278
- yield* state.cancel(sessionID)
279
- })
280
-
281
- const resolvePromptParts = Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) {
282
- const ctx = yield* InstanceState.context
283
- const parts: PromptInput["parts"] = [{ type: "text", text: template }]
284
- const files = ConfigMarkdown.files(template)
285
- const seen = new Set<string>()
286
- yield* Effect.forEach(
287
- files,
288
- Effect.fnUntraced(function* (match) {
289
- const name = match[1]
290
- if (seen.has(name)) return
291
- seen.add(name)
292
- const filepath = name.startsWith("~/")
293
- ? path.join(os.homedir(), name.slice(2))
294
- : path.resolve(ctx.worktree, name)
295
-
296
- const info = yield* fsys.stat(filepath).pipe(Effect.option)
297
- if (Option.isNone(info)) {
298
- const found = yield* agents.get(name)
299
- if (found) parts.push({ type: "agent", name: found.name })
300
- return
301
- }
302
- const stat = info.value
303
- parts.push({
304
- type: "file",
305
- url: pathToFileURL(filepath).href,
306
- filename: name,
307
- mime: stat.type === "Directory" ? "application/x-directory" : "text/plain",
308
- })
309
- }),
310
- { concurrency: "unbounded", discard: true },
311
- )
312
- return parts
313
- })
314
-
315
- const title = Effect.fn("SessionPrompt.ensureTitle")(function* (input: {
316
- session: Session.Info
317
- history: MessageV2.WithParts[]
318
- providerID: ProviderID
319
- modelID: ModelID
320
- }) {
321
- if (input.session.parentID) return
322
- if (!Session.isDefaultTitle(input.session.title)) return
323
-
324
- const real = (m: MessageV2.WithParts) =>
325
- m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)
326
- const idx = input.history.findIndex(real)
327
- if (idx === -1) return
328
- if (input.history.filter(real).length !== 1) return
329
-
330
- const context = input.history.slice(0, idx + 1)
331
- const firstUser = context[idx]
332
- if (!firstUser || firstUser.info.role !== "user") return
333
- const firstInfo = firstUser.info
334
-
335
- const subtasks = firstUser.parts.filter((p): p is MessageV2.SubtaskPart => p.type === "subtask")
336
- const onlySubtasks = subtasks.length > 0 && firstUser.parts.every((p) => p.type === "subtask")
337
-
338
- const ag = yield* agents.get("title")
339
- if (!ag) return
340
- const mdl = ag.modelRef
341
- ? yield* provider.resolveModelRef(ag.modelRef, input.providerID)
342
- : ag.model
343
- ? yield* provider.getModel(ag.model.providerID, ag.model.modelID)
344
- : ((yield* provider.getSmallModel(input.providerID)) ??
345
- (yield* provider.getModel(input.providerID, input.modelID)))
346
- const msgs = onlySubtasks
347
- ? [{ role: "user" as const, content: subtasks.map((p) => p.prompt).join("\n") }]
348
- : yield* MessageV2.toModelMessagesEffect(context, mdl)
349
- const text = yield* llm
350
- .stream({
351
- agent: ag,
352
- user: firstInfo,
353
- system: [],
354
- small: true,
355
- tools: {},
356
- model: mdl,
357
- sessionID: input.session.id,
358
- retries: 2,
359
- messages: [{ role: "user", content: "Generate a title for this conversation:\n" }, ...msgs],
360
- })
361
- .pipe(
362
- Stream.filter((e): e is Extract<LLM.Event, { type: "text-delta" }> => e.type === "text-delta"),
363
- Stream.map((e) => e.text),
364
- Stream.mkString,
365
- Effect.orDie,
366
- )
367
- const cleaned = text
368
- .replace(/<think>[\s\S]*?<\/think>\s*/g, "")
369
- .split("\n")
370
- .map((line) => line.trim())
371
- .find((line) => line.length > 0)
372
- if (!cleaned) return
373
- const t = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
374
- yield* sessions
375
- .setTitle({ sessionID: input.session.id, title: t })
376
- .pipe(Effect.catchCause((cause) => elog.error("failed to generate title", { error: Cause.squash(cause) })))
377
- })
378
-
379
- const predict = Effect.fn("SessionPrompt.predict")(function* (input: { sessionID: SessionID }) {
380
- const cfg = yield* config.get()
381
- if (cfg.experimental?.predict_next_prompt === false) return ""
382
-
383
- const history = yield* sessions.messages({ sessionID: input.sessionID, agentID: "main" })
384
- const real = (m: MessageV2.WithParts) =>
385
- m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)
386
- const userIdx = history.findLastIndex(real)
387
- if (userIdx === -1) return ""
388
- const lastUser = history[userIdx]
389
- if (lastUser.info.role !== "user") return ""
390
-
391
- // Only the assistant turn that actually answered this user message counts.
392
- // Bail if that turn is still running (an incomplete assistant after it),
393
- // so we never pair the newest prompt with a stale/older result.
394
- const assistants = history
395
- .slice(userIdx + 1)
396
- .filter((m): m is MessageV2.WithParts & { info: MessageV2.Assistant } => m.info.role === "assistant")
397
- if (assistants.length === 0) return ""
398
- if (assistants.some((m) => m.info.time.completed === undefined)) return ""
399
- const lastAssistant = assistants[assistants.length - 1]
400
-
401
- const base = yield* agents.get("title")
402
- if (!base) return ""
403
- // Reuse the lightweight title agent's settings but swap its prompt for the
404
- // prediction prompt — its default ("output ONLY a thread title") would
405
- // otherwise be prepended ahead of PREDICT_SYSTEM and win.
406
- const ag = { ...base, prompt: PREDICT_SYSTEM }
407
- const mdl = ag.modelRef
408
- ? yield* provider.resolveModelRef(ag.modelRef, lastAssistant.info.providerID)
409
- : ag.model
410
- ? yield* provider.getModel(ag.model.providerID, ag.model.modelID)
411
- : ((yield* provider.getSmallModel(lastAssistant.info.providerID)) ??
412
- (yield* provider.getModel(lastAssistant.info.providerID, lastAssistant.info.modelID)))
413
-
414
- const msgs = yield* MessageV2.toModelMessagesEffect([lastUser, lastAssistant], mdl, { stripMedia: true })
415
- const text = yield* llm
416
- .stream({
417
- agent: ag,
418
- user: lastUser.info,
419
- system: [],
420
- small: true,
421
- tools: {},
422
- model: mdl,
423
- sessionID: input.sessionID,
424
- retries: 1,
425
- messages: [...msgs, { role: "user", content: PREDICT_NUDGE }],
426
- })
427
- .pipe(
428
- Stream.filter((e): e is Extract<LLM.Event, { type: "text-delta" }> => e.type === "text-delta"),
429
- Stream.map((e) => e.text),
430
- Stream.mkString,
431
- Effect.orElseSucceed(() => ""),
432
- )
433
- const cleaned = text
434
- .replace(/<think>[\s\S]*?<\/think>\s*/g, "")
435
- .split("\n")
436
- .map((line) => line.trim())
437
- .find((line) => line.length > 0)
438
- if (!cleaned) return ""
439
- const stripped = cleaned.replace(quoteTrimRegex, "")
440
- return stripped.length > 120 ? stripped.substring(0, 117) + "..." : stripped
441
- })
442
-
443
- const insertReminders = Effect.fn("SessionPrompt.insertReminders")(function* (input: {
444
- messages: MessageV2.WithParts[]
445
- agent: Agent.Info
446
- session: Session.Info
447
- }) {
448
- const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
449
- if (!userMessage) return input.messages
450
-
451
- const composeModeMsg = input.messages.find(
452
- (msg) => msg.info.role === "user" && msg.info.agent === "compose",
453
- )
454
- if (composeModeMsg) {
455
- const composeModeBlock = composeSkillsBlock()
456
- composeModeMsg.parts.unshift({
457
- id: PartID.ascending(),
458
- messageID: composeModeMsg.info.id,
459
- sessionID: composeModeMsg.info.sessionID,
460
- type: "text",
461
- text: PROMPT_COMPOSE + (composeModeBlock ? "\n\n" + composeModeBlock : ""),
462
- synthetic: true,
463
- })
464
- }
465
-
466
- const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
467
- if (input.agent.name !== "plan" && assistantMessage?.info.agent === "plan") {
468
- const plan = Session.plan(input.session)
469
- if (!(yield* fsys.existsSafe(plan))) return input.messages
470
- const part = yield* sessions.updatePart({
471
- id: PartID.ascending(),
472
- messageID: userMessage.info.id,
473
- sessionID: userMessage.info.sessionID,
474
- type: "text",
475
- text: `${BUILD_SWITCH}\n\nA plan file exists at ${plan}. You should execute on the plan defined within it`,
476
- synthetic: true,
477
- })
478
- userMessage.parts.push(part)
479
- return input.messages
480
- }
481
-
482
- if (input.agent.name !== "plan" || assistantMessage?.info.agent === "plan") return input.messages
483
-
484
- const plan = Session.plan(input.session)
485
- const exists = yield* fsys.existsSafe(plan)
486
- if (!exists) yield* fsys.ensureDir(path.dirname(plan)).pipe(Effect.catch(Effect.die))
487
- const part = yield* sessions.updatePart({
488
- id: PartID.ascending(),
489
- messageID: userMessage.info.id,
490
- sessionID: userMessage.info.sessionID,
491
- type: "text",
492
- text: `<system-reminder>
493
- Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received.
494
-
495
- ## Plan File Info:
496
- ${exists ? `A plan file already exists at ${plan}. You can read it and make incremental edits using the edit tool.` : `No plan file exists yet. You should create your plan at ${plan} using the write tool.`}
497
- You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
498
-
499
- ## Plan Workflow
500
-
501
- ### Phase 1: Initial Understanding
502
- Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the explore subagent type.
503
-
504
- 1. Focus on understanding the user's request and the code associated with their request
505
-
506
- 2. **Launch up to 3 explore agents IN PARALLEL** (single message, multiple tool calls) to efficiently explore the codebase.
507
- - Use 1 agent when the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change.
508
- - Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.
509
- - Quality over quantity - 3 agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)
510
- - If using multiple agents: Provide each agent with a specific search focus or area to explore. Example: One agent searches for existing implementations, another explores related components, a third investigates testing patterns
511
-
512
- 3. After exploring the code, use the question tool to clarify ambiguities in the user request up front.
513
-
514
- ### Phase 2: Design
515
- Goal: Design an implementation approach.
516
-
517
- Launch general agent(s) to design the implementation based on the user's intent and your exploration results from Phase 1.
518
-
519
- You can launch up to 1 agent(s) in parallel.
520
-
521
- **Guidelines:**
522
- - **Default**: Launch at least 1 Plan agent for most tasks - it helps validate your understanding and consider alternatives
523
- - **Skip agents**: Only for truly trivial tasks (typo fixes, single-line changes, simple renames)
524
-
525
- Examples of when to use multiple agents:
526
- - The task touches multiple parts of the codebase
527
- - It's a large refactor or architectural change
528
- - There are many edge cases to consider
529
- - You'd benefit from exploring different approaches
530
-
531
- Example perspectives by task type:
532
- - New feature: simplicity vs performance vs maintainability
533
- - Bug fix: root cause vs workaround vs prevention
534
- - Refactoring: minimal change vs clean architecture
535
-
536
- In the agent prompt:
537
- - Provide comprehensive background context from Phase 1 exploration including filenames and code path traces
538
- - Describe requirements and constraints
539
- - Request a detailed implementation plan
540
-
541
- ### Phase 3: Review
542
- Goal: Review the plan(s) from Phase 2 and ensure alignment with the user's intentions.
543
- 1. Read the critical files identified by agents to deepen your understanding
544
- 2. Ensure that the plans align with the user's original request
545
- 3. Use question tool to clarify any remaining questions with the user
546
-
547
- ### Phase 4: Final Plan
548
- Goal: Write your final plan to the plan file (the only file you can edit).
549
- - Include only your recommended approach, not all alternatives
550
- - Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
551
- - Include the paths of critical files to be modified
552
- - Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)
553
-
554
- ### Phase 5: Call plan_exit tool
555
- At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call plan_exit to indicate to the user that you are done planning.
556
- This is critical - your turn should only end with either asking the user a question or calling plan_exit. Do not stop unless it's for these 2 reasons.
557
-
558
- **Important:** Use question tool to clarify requirements/approach, use plan_exit to request plan approval. Do NOT use question tool to ask "Is this plan okay?" - that's what plan_exit does.
559
-
560
- NOTE: At any point in time through this workflow you should feel free to ask the user questions or clarifications. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.
561
- </system-reminder>`,
562
- synthetic: true,
563
- })
564
- userMessage.parts.push(part)
565
- return input.messages
566
- })
567
-
568
- const resolveTools = Effect.fn("SessionPrompt.resolveTools")(function* (input: {
569
- agent: Agent.Info
570
- model: Provider.Model
571
- session: Session.Info
572
- tools?: Record<string, boolean>
573
- processor: Pick<SessionProcessor.Handle, "message" | "updateToolCall" | "completeToolCall">
574
- bypassAgentCheck: boolean
575
- messages: MessageV2.WithParts[]
576
- agentID?: string
577
- task_id?: string
578
- }) {
579
- using _ = log.time("resolveTools")
580
- const tools: Record<string, AITool> = {}
581
- const run = yield* runner()
582
- const promptOps = yield* ops()
583
-
584
- // Per-tool runtime whitelist: when the LLM call is being made on behalf
585
- // of a registered actor (subagent or peer), look up the actor row and,
586
- // if `actor.tools` is an array, reject calls to tools not in the
587
- // whitelist. `INHERIT` and a missing actor row both mean full access.
588
- const whitelistFor = Effect.fn("SessionPrompt.whitelistFor")(function* () {
589
- if (!input.agentID) return undefined
590
- const actor = yield* actorRegistry.get(input.session.id, input.agentID)
591
- if (!actor || !Array.isArray(actor.tools)) return undefined
592
- return new Set(actor.tools)
593
- })
594
- const whitelist = yield* whitelistFor()
595
- const rejectionFor = (toolID: string) => ({
596
- title: "Tool not permitted",
597
- output: `The "${toolID}" tool is not in this actor's whitelist. Allowed tools: ${
598
- whitelist ? [...whitelist].join(", ") : "(none)"
599
- }.`,
600
- metadata: { rejected: true, reason: "tool-whitelist" as const },
601
- })
602
-
603
- const context = (args: any, options: ToolExecutionOptions): Tool.Context => ({
604
- sessionID: input.session.id,
605
- abort: options.abortSignal!,
606
- messageID: input.processor.message.id,
607
- callID: options.toolCallId,
608
- extra: { model: input.model, bypassAgentCheck: input.bypassAgentCheck, promptOps },
609
- agent: input.agent.name,
610
- actorID: input.agentID,
611
- taskId: input.task_id,
612
- messages: input.messages,
613
- metadata: (val) =>
614
- input.processor.updateToolCall(options.toolCallId, (match) => {
615
- if (!["running", "pending"].includes(match.state.status)) return match
616
- return {
617
- ...match,
618
- state: {
619
- title: val.title,
620
- metadata: val.metadata,
621
- status: "running",
622
- input: args,
623
- time: { start: Date.now() },
624
- },
625
- }
626
- }),
627
- ask: (req) =>
628
- permission
629
- .ask(
630
- {
631
- ...req,
632
- sessionID: input.session.id,
633
- tool: { messageID: input.processor.message.id, callID: options.toolCallId },
634
- ruleset: Permission.merge(input.agent.permission, input.session.permission ?? []),
635
- // System-spawned background agents (checkpoint-writer, dream, distill)
636
- // have no human to answer a permission prompt — fail clean, don't hang.
637
- interactive: !SYSTEM_SPAWNED_AGENT_TYPES.has(input.agent.name),
638
- },
639
- options.abortSignal,
640
- )
641
- .pipe(Effect.orDie),
642
- })
643
-
644
- for (const item of yield* registry.tools({
645
- modelID: ModelID.make(input.model.api.id),
646
- providerID: input.model.providerID,
647
- agent: input.agent,
648
- })) {
649
- const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
650
- tools[item.id] = tool({
651
- description: item.description,
652
- inputSchema: jsonSchema(schema),
653
- execute(args, options) {
654
- return run.promise(
655
- Effect.gen(function* () {
656
- const startTs = Date.now()
657
- const callID = options?.toolCallId ?? "?"
658
- log.debug("tool execute start", {
659
- tool: item.id,
660
- callID,
661
- sessionID: input.session.id,
662
- })
663
- const ctx = context(args, options)
664
- if (whitelist && !whitelist.has(item.id)) {
665
- const output = rejectionFor(item.id)
666
- log.debug("tool execute rejected", {
667
- tool: item.id,
668
- callID,
669
- durationMs: Date.now() - startTs,
670
- })
671
- yield* input.processor.completeToolCall(options.toolCallId, output)
672
- return output
673
- }
674
- yield* plugin.trigger(
675
- "tool.execute.before",
676
- { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID },
677
- { args },
678
- )
679
- const result = yield* item.execute(args, ctx)
680
- log.debug("tool execute done", {
681
- tool: item.id,
682
- callID,
683
- durationMs: Date.now() - startTs,
684
- ok: true,
685
- })
686
- const output = {
687
- ...result,
688
- attachments: result.attachments?.map((attachment) => ({
689
- ...attachment,
690
- id: PartID.ascending(),
691
- sessionID: ctx.sessionID,
692
- messageID: input.processor.message.id,
693
- })),
694
- }
695
- yield* plugin.trigger(
696
- "tool.execute.after",
697
- { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, args },
698
- output,
699
- )
700
- yield* bus
701
- .publish(Metrics.ToolCall, {
702
- sessionID: ctx.sessionID,
703
- tool_name: item.id,
704
- input_bytes: Metrics.jsonByteLength(args),
705
- output_bytes: Buffer.byteLength(output.output ?? "", "utf8"),
706
- tool_call_id: options.toolCallId,
707
- tool_call_status: "success",
708
- })
709
- .pipe(Effect.ignore)
710
- if (options.abortSignal?.aborted) {
711
- yield* input.processor.completeToolCall(options.toolCallId, output)
712
- }
713
- return output
714
- }),
715
- )
716
- },
717
- })
718
- }
719
-
720
- for (const [key, item] of Object.entries(yield* mcp.tools())) {
721
- const execute = item.execute
722
- if (!execute) continue
723
-
724
- const schema = yield* Effect.promise(() => Promise.resolve(asSchema(item.inputSchema).jsonSchema))
725
- const transformed = ProviderTransform.schema(input.model, schema)
726
- item.inputSchema = jsonSchema(transformed)
727
- item.execute = (args, opts) =>
728
- run.promise(
729
- Effect.gen(function* () {
730
- const startTs = Date.now()
731
- const callID = opts?.toolCallId ?? "?"
732
- log.debug("tool execute start (mcp)", {
733
- tool: key,
734
- callID,
735
- sessionID: input.session.id,
736
- })
737
- const ctx = context(args, opts)
738
- if (whitelist && !whitelist.has(key)) {
739
- const rejection = rejectionFor(key)
740
- const output = {
741
- title: rejection.title,
742
- metadata: rejection.metadata,
743
- output: rejection.output,
744
- attachments: [],
745
- content: [{ type: "text" as const, text: rejection.output }],
746
- }
747
- log.debug("tool execute rejected (mcp)", {
748
- tool: key,
749
- callID,
750
- durationMs: Date.now() - startTs,
751
- })
752
- yield* input.processor.completeToolCall(opts.toolCallId, output)
753
- return output
754
- }
755
- yield* plugin.trigger(
756
- "tool.execute.before",
757
- { tool: key, sessionID: ctx.sessionID, callID: opts.toolCallId },
758
- { args },
759
- )
760
- yield* ctx.ask({ permission: key, metadata: {}, patterns: ["*"], always: ["*"] })
761
- const result: Awaited<ReturnType<NonNullable<typeof execute>>> = yield* Effect.promise(() =>
762
- execute(args, opts),
763
- )
764
- log.debug("tool execute done (mcp)", {
765
- tool: key,
766
- callID,
767
- durationMs: Date.now() - startTs,
768
- ok: true,
769
- })
770
- yield* plugin.trigger(
771
- "tool.execute.after",
772
- { tool: key, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
773
- result,
774
- )
775
-
776
- const textParts: string[] = []
777
- yield* bus
778
- .publish(Metrics.ToolCall, {
779
- sessionID: ctx.sessionID,
780
- tool_name: key,
781
- input_bytes: Metrics.jsonByteLength(args),
782
- output_bytes: Metrics.jsonByteLength(result.content ?? ""),
783
- tool_call_id: opts.toolCallId,
784
- tool_call_status: "success",
785
- })
786
- .pipe(Effect.ignore)
787
- const attachments: Omit<MessageV2.FilePart, "id" | "sessionID" | "messageID">[] = []
788
- for (const contentItem of result.content) {
789
- if (contentItem.type === "text") textParts.push(contentItem.text)
790
- else if (contentItem.type === "image") {
791
- attachments.push({
792
- type: "file",
793
- mime: contentItem.mimeType,
794
- url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
795
- })
796
- } else if (contentItem.type === "resource") {
797
- const { resource } = contentItem
798
- if (resource.text) textParts.push(resource.text)
799
- if (resource.blob) {
800
- attachments.push({
801
- type: "file",
802
- mime: resource.mimeType ?? "application/octet-stream",
803
- url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
804
- filename: resource.uri,
805
- })
806
- }
807
- }
808
- }
809
-
810
- const truncated = yield* truncate.output(textParts.join("\n\n"), {}, input.agent)
811
- const metadata = {
812
- ...result.metadata,
813
- truncated: truncated.truncated,
814
- ...(truncated.truncated && { outputPath: truncated.outputPath }),
815
- }
816
-
817
- const output = {
818
- title: "",
819
- metadata,
820
- output: truncated.content,
821
- attachments: attachments.map((attachment) => ({
822
- ...attachment,
823
- id: PartID.ascending(),
824
- sessionID: ctx.sessionID,
825
- messageID: input.processor.message.id,
826
- })),
827
- content: result.content,
828
- }
829
- if (opts.abortSignal?.aborted) {
830
- yield* input.processor.completeToolCall(opts.toolCallId, output)
831
- }
832
- return output
833
- }),
834
- )
835
- tools[key] = item
836
- }
837
-
838
- return tools
839
- })
840
-
841
- const handleSubtask = Effect.fn("SessionPrompt.handleSubtask")(function* (input: {
842
- task: MessageV2.SubtaskPart
843
- model: Provider.Model
844
- lastUser: MessageV2.User
845
- sessionID: SessionID
846
- session: Session.Info
847
- msgs: MessageV2.WithParts[]
848
- }) {
849
- const { task, model, lastUser, sessionID, session, msgs } = input
850
- const ctx = yield* InstanceState.context
851
- const promptOps = yield* ops()
852
- const { actor: actorTool } = yield* registry.named()
853
- const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model
854
- const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({
855
- id: MessageID.ascending(),
856
- role: "assistant",
857
- parentID: lastUser.id,
858
- sessionID,
859
- agentID: lastUser.agentID,
860
- mode: task.agent,
861
- agent: task.agent,
862
- variant: lastUser.model.variant,
863
- path: { cwd: ctx.directory, root: ctx.worktree },
864
- cost: 0,
865
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
866
- modelID: taskModel.id,
867
- providerID: taskModel.providerID,
868
- time: { created: Date.now() },
869
- })
870
- const taskArgs = {
871
- operation: {
872
- action: "run" as const,
873
- prompt: task.prompt,
874
- description: task.description,
875
- subagent_type: task.agent,
876
- command: task.command,
877
- },
878
- }
879
- let part: MessageV2.ToolPart = yield* sessions.updatePart({
880
- id: PartID.ascending(),
881
- messageID: assistantMessage.id,
882
- sessionID: assistantMessage.sessionID,
883
- type: "tool",
884
- callID: ulid(),
885
- tool: ActorTool.id,
886
- state: {
887
- status: "running",
888
- input: taskArgs,
889
- time: { start: Date.now() },
890
- },
891
- })
892
- yield* plugin.trigger(
893
- "tool.execute.before",
894
- { tool: ActorTool.id, sessionID, callID: part.id },
895
- { args: taskArgs },
896
- )
897
-
898
- const taskAgent = yield* agents.get(task.agent)
899
- if (!taskAgent) {
900
- const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
901
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
902
- const error = new NamedError.Unknown({ message: `Agent not found: "${task.agent}".${hint}` })
903
- yield* bus.publish(Session.Event.Error, { sessionID, error: error.toObject() })
904
- throw error
905
- }
906
-
907
- let error: Error | undefined
908
- const taskAbort = new AbortController()
909
- const result = yield* actorTool
910
- .execute(taskArgs, {
911
- agent: task.agent,
912
- messageID: assistantMessage.id,
913
- sessionID,
914
- abort: taskAbort.signal,
915
- callID: part.callID,
916
- extra: { bypassAgentCheck: true, promptOps },
917
- messages: msgs,
918
- metadata: (val: { title?: string; metadata?: Record<string, any> }) =>
919
- Effect.gen(function* () {
920
- part = yield* sessions.updatePart({
921
- ...part,
922
- type: "tool",
923
- state: { ...part.state, ...val },
924
- } satisfies MessageV2.ToolPart)
925
- }),
926
- ask: (req: any) =>
927
- permission
928
- .ask({
929
- ...req,
930
- sessionID,
931
- ruleset: Permission.merge(taskAgent.permission, session.permission ?? []),
932
- })
933
- .pipe(Effect.orDie),
934
- })
935
- .pipe(
936
- Effect.catchCause((cause) => {
937
- const defect = Cause.squash(cause)
938
- error = defect instanceof Error ? defect : new Error(String(defect))
939
- log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
940
- return Effect.void
941
- }),
942
- Effect.onInterrupt(() =>
943
- Effect.gen(function* () {
944
- taskAbort.abort()
945
- assistantMessage.finish = "tool-calls"
946
- assistantMessage.time.completed = Date.now()
947
- yield* sessions.updateMessage(assistantMessage)
948
- if (part.state.status === "running") {
949
- yield* sessions.updatePart({
950
- ...part,
951
- state: {
952
- status: "error",
953
- error: "Cancelled",
954
- time: { start: part.state.time.start, end: Date.now() },
955
- metadata: part.state.metadata,
956
- input: part.state.input,
957
- },
958
- } satisfies MessageV2.ToolPart)
959
- }
960
- }),
961
- ),
962
- )
963
-
964
- const attachments = result?.attachments?.map((attachment) => ({
965
- ...attachment,
966
- id: PartID.ascending(),
967
- sessionID,
968
- messageID: assistantMessage.id,
969
- }))
970
-
971
- yield* plugin.trigger(
972
- "tool.execute.after",
973
- { tool: ActorTool.id, sessionID, callID: part.id, args: taskArgs },
974
- result,
975
- )
976
-
977
- assistantMessage.finish = "tool-calls"
978
- assistantMessage.time.completed = Date.now()
979
- yield* sessions.updateMessage(assistantMessage)
980
-
981
- if (result && part.state.status === "running") {
982
- yield* sessions.updatePart({
983
- ...part,
984
- state: {
985
- status: "completed",
986
- input: part.state.input,
987
- title: result.title,
988
- metadata: result.metadata,
989
- output: result.output,
990
- attachments,
991
- time: { ...part.state.time, end: Date.now() },
992
- },
993
- } satisfies MessageV2.ToolPart)
994
- }
995
-
996
- if (!result) {
997
- yield* sessions.updatePart({
998
- ...part,
999
- state: {
1000
- status: "error",
1001
- error: error ? `Tool execution failed: ${error.message}` : "Tool execution failed",
1002
- time: {
1003
- start: part.state.status === "running" ? part.state.time.start : Date.now(),
1004
- end: Date.now(),
1005
- },
1006
- metadata: part.state.status === "pending" ? undefined : part.state.metadata,
1007
- input: part.state.input,
1008
- },
1009
- } satisfies MessageV2.ToolPart)
1010
- }
1011
-
1012
- if (!task.command) return
1013
-
1014
- const summaryUserMsg: MessageV2.User = {
1015
- id: MessageID.ascending(),
1016
- sessionID,
1017
- role: "user",
1018
- agentID: lastUser.agentID,
1019
- time: { created: Date.now() },
1020
- agent: lastUser.agent,
1021
- model: lastUser.model,
1022
- }
1023
- yield* sessions.updateMessage(summaryUserMsg)
1024
- yield* sessions.updatePart({
1025
- id: PartID.ascending(),
1026
- messageID: summaryUserMsg.id,
1027
- sessionID,
1028
- type: "text",
1029
- text: "Summarize the actor tool output above and continue with your task.",
1030
- synthetic: true,
1031
- } satisfies MessageV2.TextPart)
1032
- })
1033
-
1034
- const shellImpl = Effect.fn("SessionPrompt.shellImpl")(function* (input: ShellInput) {
1035
- const ctx = yield* InstanceState.context
1036
- const run = yield* runner()
1037
- const session = yield* sessions.get(input.sessionID)
1038
- if (session.revert) {
1039
- yield* revert.cleanup(session)
1040
- }
1041
- const agent = yield* agents.get(input.agent)
1042
- if (!agent) {
1043
- const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
1044
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
1045
- const error = new NamedError.Unknown({ message: `Agent not found: "${input.agent}".${hint}` })
1046
- yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
1047
- throw error
1048
- }
1049
- const inputModel = input.modelRef
1050
- ? yield* provider
1051
- .resolveModelRef(input.modelRef)
1052
- .pipe(Effect.map((m) => ({ providerID: m.providerID, modelID: m.id })))
1053
- : input.model
1054
- const agentModel = agent.modelRef
1055
- ? yield* provider
1056
- .resolveModelRef(agent.modelRef)
1057
- .pipe(Effect.map((m) => ({ providerID: m.providerID, modelID: m.id })))
1058
- : agent.model
1059
- const model = inputModel ?? agentModel ?? (yield* lastModel(input.sessionID))
1060
- const userMsg: MessageV2.User = {
1061
- id: input.messageID ?? MessageID.ascending(),
1062
- sessionID: input.sessionID,
1063
- time: { created: Date.now() },
1064
- role: "user",
1065
- agent: input.agent,
1066
- model: { providerID: model.providerID, modelID: model.modelID },
1067
- }
1068
- yield* sessions.updateMessage(userMsg)
1069
- const userPart: MessageV2.Part = {
1070
- type: "text",
1071
- id: PartID.ascending(),
1072
- messageID: userMsg.id,
1073
- sessionID: input.sessionID,
1074
- text: "The following tool was executed by the user",
1075
- synthetic: true,
1076
- }
1077
- yield* sessions.updatePart(userPart)
1078
-
1079
- const msg: MessageV2.Assistant = {
1080
- id: MessageID.ascending(),
1081
- sessionID: input.sessionID,
1082
- parentID: userMsg.id,
1083
- agentID: userMsg.agentID,
1084
- mode: input.agent,
1085
- agent: input.agent,
1086
- cost: 0,
1087
- path: { cwd: ctx.directory, root: ctx.worktree },
1088
- time: { created: Date.now() },
1089
- role: "assistant",
1090
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
1091
- modelID: model.modelID,
1092
- providerID: model.providerID,
1093
- }
1094
- yield* sessions.updateMessage(msg)
1095
- const part: MessageV2.ToolPart = {
1096
- type: "tool",
1097
- id: PartID.ascending(),
1098
- messageID: msg.id,
1099
- sessionID: input.sessionID,
1100
- tool: "bash",
1101
- callID: ulid(),
1102
- state: {
1103
- status: "running",
1104
- time: { start: Date.now() },
1105
- input: { command: input.command },
1106
- },
1107
- }
1108
- yield* sessions.updatePart(part)
1109
-
1110
- const sh = Shell.preferred()
1111
- const shellName = (
1112
- process.platform === "win32" ? path.win32.basename(sh, ".exe") : path.basename(sh)
1113
- ).toLowerCase()
1114
- const invocations: Record<string, { args: string[] }> = {
1115
- nu: { args: ["-c", input.command] },
1116
- fish: { args: ["-c", input.command] },
1117
- zsh: {
1118
- args: [
1119
- "-l",
1120
- "-c",
1121
- `
1122
- __oc_cwd=$PWD
1123
- [[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true
1124
- [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true
1125
- cd "$__oc_cwd"
1126
- eval ${JSON.stringify(input.command)}
1127
- `,
1128
- ],
1129
- },
1130
- bash: {
1131
- args: [
1132
- "-l",
1133
- "-c",
1134
- `
1135
- __oc_cwd=$PWD
1136
- shopt -s expand_aliases
1137
- [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
1138
- cd "$__oc_cwd"
1139
- eval ${JSON.stringify(input.command)}
1140
- `,
1141
- ],
1142
- },
1143
- cmd: { args: ["/c", input.command] },
1144
- powershell: { args: ["-NoProfile", "-Command", input.command] },
1145
- pwsh: { args: ["-NoProfile", "-Command", input.command] },
1146
- "": { args: ["-c", input.command] },
1147
- }
1148
-
1149
- const args = (invocations[shellName] ?? invocations[""]).args
1150
- const cwd = ctx.directory
1151
- const shellEnv = yield* plugin.trigger(
1152
- "shell.env",
1153
- { cwd, sessionID: input.sessionID, callID: part.callID },
1154
- { env: {} },
1155
- )
1156
-
1157
- const cmd = ChildProcess.make(sh, args, {
1158
- cwd,
1159
- extendEnv: true,
1160
- env: { ...shellEnv.env, TERM: "dumb" },
1161
- stdin: "ignore",
1162
- forceKillAfter: "3 seconds",
1163
- })
1164
-
1165
- let output = ""
1166
- let aborted = false
1167
-
1168
- const finish = Effect.uninterruptible(
1169
- Effect.gen(function* () {
1170
- if (aborted) {
1171
- output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n")
1172
- }
1173
- if (!msg.time.completed) {
1174
- msg.time.completed = Date.now()
1175
- yield* sessions.updateMessage(msg)
1176
- }
1177
- if (part.state.status === "running") {
1178
- part.state = {
1179
- status: "completed",
1180
- time: { ...part.state.time, end: Date.now() },
1181
- input: part.state.input,
1182
- title: "",
1183
- metadata: { output, description: "" },
1184
- output,
1185
- }
1186
- yield* sessions.updatePart(part)
1187
- }
1188
- }),
1189
- )
1190
-
1191
- const exit = yield* Effect.gen(function* () {
1192
- const handle = yield* spawner.spawn(cmd)
1193
- yield* Stream.runForEach(Stream.decodeText(handle.all), (chunk) =>
1194
- Effect.sync(() => {
1195
- output += chunk
1196
- if (part.state.status === "running") {
1197
- part.state.metadata = { output, description: "" }
1198
- void run.fork(sessions.updatePart(part))
1199
- }
1200
- }),
1201
- )
1202
- yield* handle.exitCode
1203
- }).pipe(
1204
- Effect.scoped,
1205
- Effect.onInterrupt(() =>
1206
- Effect.sync(() => {
1207
- aborted = true
1208
- }),
1209
- ),
1210
- Effect.orDie,
1211
- Effect.ensuring(finish),
1212
- Effect.exit,
1213
- )
1214
-
1215
- if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
1216
- return yield* Effect.failCause(exit.cause)
1217
- }
1218
-
1219
- return { info: msg, parts: [part] }
1220
- })
1221
-
1222
- const getModel = Effect.fn("SessionPrompt.getModel")(function* (
1223
- providerID: ProviderID,
1224
- modelID: ModelID,
1225
- sessionID: SessionID,
1226
- ) {
1227
- const exit = yield* provider.getModel(providerID, modelID).pipe(Effect.exit)
1228
- if (Exit.isSuccess(exit)) return exit.value
1229
- const err = Cause.squash(exit.cause)
1230
- if (Provider.ModelNotFoundError.isInstance(err)) {
1231
- const hint = err.data.suggestions?.length ? ` Did you mean: ${err.data.suggestions.join(", ")}?` : ""
1232
- yield* bus.publish(Session.Event.Error, {
1233
- sessionID,
1234
- error: new NamedError.Unknown({
1235
- message: `Model not found: ${err.data.providerID}/${err.data.modelID}.${hint}`,
1236
- }).toObject(),
1237
- })
1238
- }
1239
- return yield* Effect.failCause(exit.cause)
1240
- })
1241
-
1242
- const lastModel = Effect.fnUntraced(function* (sessionID: SessionID) {
1243
- const match = yield* sessions.findMessage(
1244
- sessionID,
1245
- (m) => m.info.role === "user" && !!m.info.model,
1246
- { agentID: "*" },
1247
- )
1248
- if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model
1249
- return yield* provider.defaultModel()
1250
- })
1251
-
1252
- const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
1253
- const agentName = input.agent || (yield* agents.defaultAgent())
1254
- const ag = yield* agents.get(agentName)
1255
- if (!ag) {
1256
- const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
1257
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
1258
- const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
1259
- yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
1260
- throw error
1261
- }
1262
-
1263
- const inputModel = input.modelRef
1264
- ? yield* provider
1265
- .resolveModelRef(input.modelRef)
1266
- .pipe(Effect.map((m) => ({ providerID: m.providerID, modelID: m.id })))
1267
- : input.model
1268
- const agentModel = ag.modelRef
1269
- ? yield* provider
1270
- .resolveModelRef(ag.modelRef)
1271
- .pipe(Effect.map((m) => ({ providerID: m.providerID, modelID: m.id })))
1272
- : ag.model
1273
- const model = inputModel ?? agentModel ?? (yield* lastModel(input.sessionID))
1274
- const same = agentModel && model.providerID === agentModel.providerID && model.modelID === agentModel.modelID
1275
- const full =
1276
- !input.variant && ag.variant && same
1277
- ? yield* provider.getModel(model.providerID, model.modelID).pipe(Effect.catchDefect(() => Effect.void))
1278
- : undefined
1279
- const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined)
1280
-
1281
- const info: MessageV2.User = {
1282
- id: input.messageID ?? MessageID.ascending(),
1283
- role: "user",
1284
- sessionID: input.sessionID,
1285
- agentID: input.agentID,
1286
- time: { created: Date.now() },
1287
- tools: input.tools,
1288
- agent: ag.name,
1289
- model: {
1290
- providerID: model.providerID,
1291
- modelID: model.modelID,
1292
- variant,
1293
- },
1294
- system: input.system,
1295
- format: input.format,
1296
- provenance: input.provenance,
1297
- }
1298
-
1299
- yield* Effect.addFinalizer(() => instruction.clear(info.id))
1300
-
1301
- type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
1302
- const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
1303
- ...part,
1304
- id: part.id ? PartID.make(part.id) : PartID.ascending(),
1305
- })
1306
-
1307
- const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect<Draft<MessageV2.Part>[]> = Effect.fn(
1308
- "SessionPrompt.resolveUserPart",
1309
- )(function* (part) {
1310
- if (part.type === "file") {
1311
- if (part.source?.type === "resource") {
1312
- const { clientName, uri } = part.source
1313
- log.info("mcp resource", { clientName, uri, mime: part.mime })
1314
- const pieces: Draft<MessageV2.Part>[] = [
1315
- {
1316
- messageID: info.id,
1317
- sessionID: input.sessionID,
1318
- type: "text",
1319
- synthetic: true,
1320
- text: `Reading MCP resource: ${part.filename} (${uri})`,
1321
- },
1322
- ]
1323
- const exit = yield* mcp.readResource(clientName, uri).pipe(Effect.exit)
1324
- if (Exit.isSuccess(exit)) {
1325
- const content = exit.value
1326
- if (!content) throw new Error(`Resource not found: ${clientName}/${uri}`)
1327
- const items = Array.isArray(content.contents) ? content.contents : [content.contents]
1328
- for (const c of items) {
1329
- if ("text" in c && c.text) {
1330
- pieces.push({
1331
- messageID: info.id,
1332
- sessionID: input.sessionID,
1333
- type: "text",
1334
- synthetic: true,
1335
- text: c.text,
1336
- })
1337
- } else if ("blob" in c && c.blob) {
1338
- const mime = "mimeType" in c ? c.mimeType : part.mime
1339
- pieces.push({
1340
- messageID: info.id,
1341
- sessionID: input.sessionID,
1342
- type: "text",
1343
- synthetic: true,
1344
- text: `[Binary content: ${mime}]`,
1345
- })
1346
- }
1347
- }
1348
- pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
1349
- } else {
1350
- const error = Cause.squash(exit.cause)
1351
- log.error("failed to read MCP resource", { error, clientName, uri })
1352
- const message = error instanceof Error ? error.message : String(error)
1353
- pieces.push({
1354
- messageID: info.id,
1355
- sessionID: input.sessionID,
1356
- type: "text",
1357
- synthetic: true,
1358
- text: `Failed to read MCP resource ${part.filename}: ${message}`,
1359
- })
1360
- }
1361
- return pieces
1362
- }
1363
- const url = new URL(part.url)
1364
- switch (url.protocol) {
1365
- case "data:":
1366
- if (part.mime === "text/plain") {
1367
- return [
1368
- {
1369
- messageID: info.id,
1370
- sessionID: input.sessionID,
1371
- type: "text",
1372
- synthetic: true,
1373
- text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
1374
- },
1375
- {
1376
- messageID: info.id,
1377
- sessionID: input.sessionID,
1378
- type: "text",
1379
- synthetic: true,
1380
- text: decodeDataUrl(part.url),
1381
- },
1382
- { ...part, messageID: info.id, sessionID: input.sessionID },
1383
- ]
1384
- }
1385
- break
1386
- case "file:": {
1387
- log.info("file", { mime: part.mime })
1388
- const filepath = fileURLToPath(part.url)
1389
- if (yield* fsys.isDir(filepath)) part.mime = "application/x-directory"
1390
-
1391
- const { read } = yield* registry.named()
1392
- const execRead = (args: Parameters<typeof read.execute>[0], extra?: Tool.Context["extra"]) => {
1393
- const controller = new AbortController()
1394
- return read
1395
- .execute(args, {
1396
- sessionID: input.sessionID,
1397
- abort: controller.signal,
1398
- agent: input.agent!,
1399
- messageID: info.id,
1400
- extra: { bypassCwdCheck: true, ...extra },
1401
- messages: [],
1402
- metadata: () => Effect.void,
1403
- ask: () => Effect.void,
1404
- })
1405
- .pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort())))
1406
- }
1407
-
1408
- if (part.mime === "text/plain") {
1409
- let offset: number | undefined
1410
- let limit: number | undefined
1411
- const range = { start: url.searchParams.get("start"), end: url.searchParams.get("end") }
1412
- if (range.start != null) {
1413
- const filePathURI = part.url.split("?")[0]
1414
- let start = parseInt(range.start)
1415
- let end = range.end ? parseInt(range.end) : undefined
1416
- if (start === end) {
1417
- const symbols = yield* lsp.documentSymbol(filePathURI).pipe(Effect.catch(() => Effect.succeed([])))
1418
- for (const symbol of symbols) {
1419
- let r: LSP.Range | undefined
1420
- if ("range" in symbol) r = symbol.range
1421
- else if ("location" in symbol) r = symbol.location.range
1422
- if (r?.start?.line && r?.start?.line === start) {
1423
- start = r.start.line
1424
- end = r?.end?.line ?? start
1425
- break
1426
- }
1427
- }
1428
- }
1429
- offset = Math.max(start, 1)
1430
- if (end) limit = end - (offset - 1)
1431
- }
1432
- const args = { filePath: filepath, offset, limit }
1433
- const pieces: Draft<MessageV2.Part>[] = [
1434
- {
1435
- messageID: info.id,
1436
- sessionID: input.sessionID,
1437
- type: "text",
1438
- synthetic: true,
1439
- text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
1440
- },
1441
- ]
1442
- const exit = yield* provider.getModel(info.model.providerID, info.model.modelID).pipe(
1443
- Effect.flatMap((mdl) => execRead(args, { model: mdl })),
1444
- Effect.exit,
1445
- )
1446
- if (Exit.isSuccess(exit)) {
1447
- const result = exit.value
1448
- pieces.push({
1449
- messageID: info.id,
1450
- sessionID: input.sessionID,
1451
- type: "text",
1452
- synthetic: true,
1453
- text: result.output,
1454
- })
1455
- if (result.attachments?.length) {
1456
- pieces.push(
1457
- ...result.attachments.map((a) => ({
1458
- ...a,
1459
- synthetic: true,
1460
- filename: a.filename ?? part.filename,
1461
- messageID: info.id,
1462
- sessionID: input.sessionID,
1463
- })),
1464
- )
1465
- } else {
1466
- pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
1467
- }
1468
- } else {
1469
- const error = Cause.squash(exit.cause)
1470
- log.error("failed to read file", { error })
1471
- const message = error instanceof Error ? error.message : String(error)
1472
- yield* bus.publish(Session.Event.Error, {
1473
- sessionID: input.sessionID,
1474
- error: new NamedError.Unknown({ message }).toObject(),
1475
- })
1476
- pieces.push({
1477
- messageID: info.id,
1478
- sessionID: input.sessionID,
1479
- type: "text",
1480
- synthetic: true,
1481
- text: `Read tool failed to read ${filepath} with the following error: ${message}`,
1482
- })
1483
- }
1484
- return pieces
1485
- }
1486
-
1487
- if (part.mime === "application/x-directory") {
1488
- const args = { filePath: filepath }
1489
- const exit = yield* execRead(args).pipe(Effect.exit)
1490
- if (Exit.isFailure(exit)) {
1491
- const error = Cause.squash(exit.cause)
1492
- log.error("failed to read directory", { error })
1493
- const message = error instanceof Error ? error.message : String(error)
1494
- yield* bus.publish(Session.Event.Error, {
1495
- sessionID: input.sessionID,
1496
- error: new NamedError.Unknown({ message }).toObject(),
1497
- })
1498
- return [
1499
- {
1500
- messageID: info.id,
1501
- sessionID: input.sessionID,
1502
- type: "text",
1503
- synthetic: true,
1504
- text: `Read tool failed to read ${filepath} with the following error: ${message}`,
1505
- },
1506
- ]
1507
- }
1508
- return [
1509
- {
1510
- messageID: info.id,
1511
- sessionID: input.sessionID,
1512
- type: "text",
1513
- synthetic: true,
1514
- text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
1515
- },
1516
- {
1517
- messageID: info.id,
1518
- sessionID: input.sessionID,
1519
- type: "text",
1520
- synthetic: true,
1521
- text: exit.value.output,
1522
- },
1523
- { ...part, messageID: info.id, sessionID: input.sessionID },
1524
- ]
1525
- }
1526
-
1527
- return [
1528
- {
1529
- messageID: info.id,
1530
- sessionID: input.sessionID,
1531
- type: "text",
1532
- synthetic: true,
1533
- text: `Called the Read tool with the following input: {"filePath":"${filepath}"}`,
1534
- },
1535
- {
1536
- id: part.id,
1537
- messageID: info.id,
1538
- sessionID: input.sessionID,
1539
- type: "file",
1540
- url:
1541
- `data:${part.mime};base64,` +
1542
- Buffer.from(yield* fsys.readFile(filepath).pipe(Effect.catch(Effect.die))).toString("base64"),
1543
- mime: part.mime,
1544
- filename: part.filename!,
1545
- source: part.source,
1546
- },
1547
- ]
1548
- }
1549
- }
1550
- }
1551
-
1552
- if (part.type === "agent") {
1553
- const perm = Permission.evaluate("task", part.name, ag.permission)
1554
- const hint = perm.action === "deny" ? " . Invoked by user; guaranteed to exist." : ""
1555
- return [
1556
- { ...part, messageID: info.id, sessionID: input.sessionID },
1557
- {
1558
- messageID: info.id,
1559
- sessionID: input.sessionID,
1560
- type: "text",
1561
- synthetic: true,
1562
- text:
1563
- " Use the above message and context to generate a prompt and call the actor tool with subagent: " +
1564
- part.name +
1565
- hint,
1566
- },
1567
- ]
1568
- }
1569
-
1570
- return [{ ...part, messageID: info.id, sessionID: input.sessionID }]
1571
- })
1572
-
1573
- const parts = yield* Effect.forEach(input.parts, resolvePart, { concurrency: "unbounded" }).pipe(
1574
- Effect.map((x) => x.flat().map(assign)),
1575
- )
1576
-
1577
- yield* plugin.trigger(
1578
- "chat.message",
1579
- {
1580
- sessionID: input.sessionID,
1581
- agent: input.agent,
1582
- model: input.model,
1583
- messageID: input.messageID,
1584
- variant: input.variant,
1585
- },
1586
- { message: info, parts },
1587
- )
1588
-
1589
- const parsed = MessageV2.Info.safeParse(info)
1590
- if (!parsed.success) {
1591
- log.error("invalid user message before save", {
1592
- sessionID: input.sessionID,
1593
- messageID: info.id,
1594
- agent: info.agent,
1595
- model: info.model,
1596
- issues: parsed.error.issues,
1597
- })
1598
- }
1599
- parts.forEach((part, index) => {
1600
- const p = MessageV2.Part.safeParse(part)
1601
- if (p.success) return
1602
- log.error("invalid user part before save", {
1603
- sessionID: input.sessionID,
1604
- messageID: info.id,
1605
- partID: part.id,
1606
- partType: part.type,
1607
- index,
1608
- issues: p.error.issues,
1609
- part,
1610
- })
1611
- })
1612
-
1613
- yield* sessions.updateMessage(info)
1614
- for (const part of parts) yield* sessions.updatePart(part)
1615
-
1616
- return { info, parts }
1617
- }, Effect.scoped)
1618
-
1619
- const sweepOrphanAssistants = Effect.fn("SessionPrompt.sweepOrphanAssistants")(function* (sessionID: SessionID) {
1620
- const msgs = yield* sessions.messages({ sessionID, agentID: "*" })
1621
- const now = Date.now()
1622
- // 1 hour — must exceed Task 1's chunkMs (300s) plus Task 2's
1623
- // PERSISTENT_RETRY worst-case backoff (10 attempts × 5 min cap =
1624
- // 50 min) so a still-active in-flight request is never falsely
1625
- // swept while its retry chain is making progress.
1626
- const ORPHAN_AGE_MS = 3_600_000
1627
- for (const m of msgs) {
1628
- if (m.info.role !== "assistant") continue
1629
- if (m.info.time?.completed) continue
1630
- const created = m.info.time?.created ?? 0
1631
- if (now - created < ORPHAN_AGE_MS) continue
1632
- m.info.time = { ...m.info.time, completed: now }
1633
- m.info.error =
1634
- m.info.error ??
1635
- new MessageV2.AbortedError({
1636
- message: "Abandoned: previous request interrupted before completion",
1637
- }).toObject()
1638
- yield* sessions.updateMessage(m.info).pipe(
1639
- Effect.catchCause((cause) =>
1640
- elog.warn("orphan-update-failed", {
1641
- sessionID,
1642
- messageID: m.info.id,
1643
- cause,
1644
- }),
1645
- ),
1646
- )
1647
- yield* elog.info("orphan-assistant-cleared", {
1648
- sessionID,
1649
- messageID: m.info.id,
1650
- })
1651
- }
1652
- })
1653
-
1654
- const prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.prompt")(
1655
- function* (input: PromptInput) {
1656
- const session = yield* sessions.get(input.sessionID)
1657
- if (input.source !== "spawn" && input.source !== "hook") {
1658
- yield* revert.cleanup(session)
1659
- yield* sweepOrphanAssistants(input.sessionID)
1660
- }
1661
- const message = yield* createUserMessage(input)
1662
- yield* sessions.touch(input.sessionID)
1663
-
1664
- const permissions: Permission.Ruleset = []
1665
- for (const [t, enabled] of Object.entries(input.tools ?? {})) {
1666
- permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
1667
- }
1668
- if (permissions.length > 0) {
1669
- session.permission = permissions
1670
- yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
1671
- }
1672
-
1673
- if (input.noReply === true) return message
1674
- return yield* loop({ sessionID: input.sessionID, agentID: input.agentID ?? "main", task_id: input.task_id })
1675
- },
1676
- )
1677
-
1678
- const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID, agentID?: string) {
1679
- if (agentID !== undefined) {
1680
- // Agent-scoped: return THIS agent's newest message (assistant preferred).
1681
- // Critical for concurrent same-session subagents — a session-wide lookup
1682
- // collapses concurrent actors' return values onto whichever finished last.
1683
- // messages() yields oldest-first/newest-last, so findLast picks the newest
1684
- // assistant and the last element is the newest message overall.
1685
- const own = yield* sessions.messages({ sessionID, agentID })
1686
- const lastAsst = own.findLast((m) => m.info.role === "assistant")
1687
- if (lastAsst) return lastAsst
1688
- if (own.length > 0) return own[own.length - 1]
1689
- // fall through to session-wide if this agent has no messages yet
1690
- }
1691
- const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user", { agentID: "*" })
1692
- if (Option.isSome(match)) return match.value
1693
- const msgs = yield* sessions.messages({ sessionID, limit: 1, agentID: "*" })
1694
- if (msgs.length > 0) return msgs[0]
1695
- throw new Error("Impossible")
1696
- })
1697
-
1698
- const runLoop: (sessionID: SessionID, agentID?: string, task_id?: string) => Effect.Effect<MessageV2.WithParts> = Effect.fn(
1699
- "SessionPrompt.run",
1700
- )(
1701
- function* (sessionID: SessionID, agentID?: string, task_id?: string) {
1702
- const ctx = yield* InstanceState.context
1703
- const slog = elog.with({ sessionID })
1704
- let structured: unknown | undefined
1705
- let step = 0
1706
- const session = yield* sessions.get(sessionID)
1707
- let lastFinishedForPrune: MessageV2.Assistant | undefined
1708
- let lastModelForPrune: Provider.Model | undefined
1709
- let outputLengthContinuations = 0
1710
- // Shared local counter for "model finished but produced nothing usable"
1711
- // (think-only / empty). T04's generic-invalid retries reuse this same
1712
- // counter — do not add a second one. Local to runLoop so a fresh user
1713
- // turn resets it (no cross-message pollution), same as outputLengthContinuations.
1714
- let invalidContinuations = 0
1715
- // structured-output 专用 retry:上限来自 lastUser.format.retryCount(默认 2),
1716
- // 与 invalidContinuations(generic invalid)分离,互不污染。局部于 runLoop,
1717
- // 新一轮用户 turn 自动归零。
1718
- let structuredRetries = 0
1719
- const agentMetrics = { tokens_in: 0, tokens_out: 0, files_changed: 0 }
1720
- const publishAgentRequest = (phase: string, taskType: string) =>
1721
- bus
1722
- .publish(Metrics.AgentRequest, {
1723
- sessionID,
1724
- phase,
1725
- task_type: taskType,
1726
- surface: Flag.WOOZLIT_CODE_CLIENT,
1727
- total_tokens_in: agentMetrics.tokens_in,
1728
- total_tokens_out: agentMetrics.tokens_out,
1729
- files_changed: agentMetrics.files_changed,
1730
- validation_status: "skipped",
1731
- })
1732
- .pipe(Effect.ignore)
1733
- // Trim freed space but `lastFinished.tokens` still reflects pre-trim state.
1734
- // Skip one overflow check so the model can respond on the trimmed context;
1735
- // its new assistant message will carry accurate tokens for the next check.
1736
- let skipOverflowCheck = false
1737
-
1738
- // Contract (T05): on finish="length", inject a continuation nudge ONLY for
1739
- // plain text. If any non-providerExecuted client tool part exists we bail
1740
- // (return false) and let classify route the normal tool-observation re-loop.
1741
- // This guarantees "no output-length continuation when a tool is involved" —
1742
- // it does NOT guarantee a stream-time-truncated tool never executed, since
1743
- // the AI SDK runs tools mid-stream before the finish reason is known.
1744
- const autoContinueOutputLength = Effect.fn("SessionPrompt.autoContinueOutputLength")(function* (input: {
1745
- lastUser: MessageV2.User
1746
- assistant: MessageV2.Assistant
1747
- }) {
1748
- if (input.assistant.finish !== "length" || input.assistant.error || input.assistant.summary) return false
1749
- if (
1750
- MessageV2.parts(input.assistant.id).some((part) => part.type === "tool" && !part.metadata?.providerExecuted)
1751
- ) {
1752
- return false
1753
- }
1754
- if (outputLengthContinuations >= OUTPUT_LENGTH_CONTINUATION_LIMIT) {
1755
- input.assistant.error = new MessageV2.OutputLengthError({}).toObject()
1756
- yield* sessions.updateMessage(input.assistant)
1757
- yield* bus.publish(Session.Event.Error, {
1758
- sessionID: input.assistant.sessionID,
1759
- error: input.assistant.error,
1760
- })
1761
- return false
1762
- }
1763
-
1764
- outputLengthContinuations++
1765
- yield* slog.info("auto-continuing output length", { attempt: outputLengthContinuations })
1766
- const msg = yield* sessions.updateMessage({
1767
- id: MessageID.ascending(),
1768
- role: "user" as const,
1769
- sessionID: input.lastUser.sessionID,
1770
- agentID: input.lastUser.agentID,
1771
- agent: input.lastUser.agent,
1772
- model: input.lastUser.model,
1773
- tools: input.lastUser.tools,
1774
- format: input.lastUser.format,
1775
- time: { created: Date.now() },
1776
- })
1777
- yield* sessions.updatePart({
1778
- id: PartID.ascending(),
1779
- messageID: msg.id,
1780
- sessionID: msg.sessionID,
1781
- type: "text",
1782
- synthetic: true,
1783
- text: [
1784
- "<system-reminder>",
1785
- "The previous assistant response hit the model output token limit before completing.",
1786
- "Continue the same task from the exact point where it stopped.",
1787
- "Do not restart, recap, or repeat prior reasoning. Keep reasoning concise, prefer concrete tool calls or final output, and only stop when the user's task is complete or genuinely blocked.",
1788
- "</system-reminder>",
1789
- ].join("\n"),
1790
- } satisfies MessageV2.TextPart)
1791
- return true
1792
- })
1793
-
1794
- // Task stop-condition gate (main agent only). Before honoring a stop,
1795
- // list non-terminal tasks in the session: if any remain, inject a
1796
- // nudge as a synthetic user turn and re-enter (return true) so the
1797
- // model closes them with `task done` / `task abandon`. ReAct cap +
1798
- // counter mirror the goal gate; cap-exceeded allows stop with a
1799
- // warn log (no reportedStatus on main). owner=undefined picks up
1800
- // tasks orphaned by subagent gates that hit their own cap. Runs
1801
- // BEFORE goalGate because task state is cheaper to settle and a
1802
- // pending-task board pollutes any goal verdict.
1803
- const taskGate = Effect.fn("SessionPrompt.taskGate")(function* (lastUser: MessageV2.User) {
1804
- if ((agentID ?? "main") !== "main") return false
1805
- // If the main agent has the `task` tool stripped (Permission.disabled),
1806
- // a nudge to call `task done` is unsatisfiable and would re-loop to
1807
- // cap. Skip the gate entirely. Mirrors the canWrite skip in
1808
- // actor/spawn.ts (Permission.disabled(["write"], ...) check on
1809
- // forkAgentInfo). Per-session resolution means this checks the
1810
- // agent's static permission only (good enough for v1; session-
1811
- // level overrides re-enabling task on a denied agent are
1812
- // pathological and out of scope).
1813
- const mainAgent = yield* agents.get("main").pipe(Effect.orElseSucceed(() => undefined))
1814
- if (mainAgent && Permission.disabled(["task"], mainAgent.permission).has("task")) return false
1815
- // Per-message `tools` is the second tool-strip layer (llm.ts:720
1816
- // `input.user.tools?.[k] !== false` filter), separate from
1817
- // Permission.disabled. A slash command pinning a narrow toolset for
1818
- // its turn can drop `task` even when permission allows it; nudging
1819
- // is then unsatisfiable. Same skip rationale, narrower window.
1820
- if (lastUser.tools?.["task"] === false) return false
1821
-
1822
- const count = yield* taskGateState.get(sessionID)
1823
- // runLoop is annotated `R = never`; TaskGate.decide raises a
1824
- // TaskRegistry.Service requirement that we close locally with the
1825
- // layer-resolved binding so it doesn't leak into runLoop's R-set.
1826
- const decision = yield* TaskGate.decide({
1827
- session_id: sessionID,
1828
- owner: undefined,
1829
- reactCount: count,
1830
- maxReact: MAX_TASK_GATE_MAIN_REACT,
1831
- mode: "main",
1832
- }).pipe(Effect.provideService(TaskRegistry.Service, taskRegistry))
1833
- if (!decision.needReentry) {
1834
- if (decision.capExceeded) {
1835
- yield* slog.warn("task gate hit cap; allowing stop", {
1836
- sessionID,
1837
- incompleteTasks: decision.incompleteTasks,
1838
- })
1839
- }
1840
- yield* taskGateState.clear(sessionID)
1841
- return false
1842
- }
1843
- yield* taskGateState.bump(sessionID)
1844
- const reentry = yield* sessions.updateMessage({
1845
- id: MessageID.ascending(),
1846
- role: "user" as const,
1847
- sessionID,
1848
- agentID: lastUser.agentID,
1849
- agent: lastUser.agent,
1850
- model: lastUser.model,
1851
- tools: lastUser.tools,
1852
- format: lastUser.format,
1853
- time: { created: Date.now() },
1854
- })
1855
- yield* sessions.updatePart({
1856
- id: PartID.ascending(),
1857
- messageID: reentry.id,
1858
- sessionID,
1859
- type: "text",
1860
- synthetic: true,
1861
- text: decision.reentryText,
1862
- } satisfies MessageV2.TextPart)
1863
- return true
1864
- })
1865
-
1866
- // Goal stop-condition gate (main agent only). Before honoring a stop,
1867
- // an independent judge model reads the transcript and decides whether
1868
- // the active goal is satisfied. Not satisfied → inject the judge's
1869
- // reason as a synthetic user turn and signal the caller to keep working
1870
- // (return true). This is the main-loop analogue of actor.preStop ReAct
1871
- // re-entry, which only fires for spawned actors. fail-open on any judge
1872
- // error so a flaky judge can never trap the user.
1873
- const goalGate = Effect.fn("SessionPrompt.goalGate")(function* (lastUser: MessageV2.User) {
1874
- if ((agentID ?? "main") !== "main") return false
1875
- const active = yield* goal.get(sessionID)
1876
- if (!active) return false
1877
-
1878
- const transcriptMsgs = yield* MessageV2.filterCompactedEffect(sessionID, {
1879
- contextFrom: session.contextFrom,
1880
- contextWatermark: session.contextWatermark,
1881
- agentID: "main",
1882
- })
1883
- // Anchor the verdict to the assistant turn the judge just evaluated, so
1884
- // the TUI can render a per-turn marker the user can trace back to.
1885
- const judgedMessageID = transcriptMsgs.findLast((m) => m.info.role === "assistant")?.info.id
1886
- const verdict = yield* goal
1887
- .evaluate({
1888
- condition: active.condition,
1889
- msgs: transcriptMsgs,
1890
- model: lastUser.model,
1891
- })
1892
- .pipe(
1893
- Effect.catch((err) =>
1894
- Effect.gen(function* () {
1895
- yield* slog.warn("goal judge failed; allowing stop", { error: String(err) })
1896
- return { ok: true, reason: "judge error", judgeFailed: true } as Goal.Verdict & {
1897
- judgeFailed: true
1898
- }
1899
- }),
1900
- ),
1901
- )
1902
-
1903
- if (verdict.ok || verdict.impossible) {
1904
- yield* slog.info("goal satisfied; allowing stop", {
1905
- sessionID,
1906
- impossible: verdict.impossible === true,
1907
- })
1908
- // Publish the final verdict (goal cleared) so the TUI can render the
1909
- // ✓/⊘ result line before the indicator disappears. goal.clear also
1910
- // publishes goal:undefined, but the TUI keeps lastVerdict sticky.
1911
- yield* bus.publish(Goal.Event.Updated, {
1912
- sessionID,
1913
- goal: undefined,
1914
- lastVerdict: {
1915
- ...verdict,
1916
- attempt: active.react,
1917
- messageID: judgedMessageID,
1918
- error: "judgeFailed" in verdict ? true : undefined,
1919
- },
1920
- })
1921
- yield* goal.clear(sessionID)
1922
- return false
1923
- }
1924
-
1925
- const count = yield* goal.bumpReact(sessionID)
1926
- if (count > MAX_GOAL_REACT) {
1927
- yield* slog.warn("goal hit MAX_GOAL_REACT cap; allowing stop", {
1928
- sessionID,
1929
- condition: active.condition,
1930
- count,
1931
- })
1932
- yield* bus.publish(Goal.Event.Updated, {
1933
- sessionID,
1934
- goal: undefined,
1935
- lastVerdict: { ...verdict, attempt: count, messageID: judgedMessageID },
1936
- })
1937
- yield* goal.clear(sessionID)
1938
- return false
1939
- }
1940
-
1941
- yield* slog.info("goal not satisfied; re-entering", { sessionID, attempt: count })
1942
- yield* bus.publish(Goal.Event.Updated, {
1943
- sessionID,
1944
- goal: { condition: active.condition },
1945
- lastVerdict: { ...verdict, attempt: count, messageID: judgedMessageID },
1946
- })
1947
- const reentry = yield* sessions.updateMessage({
1948
- id: MessageID.ascending(),
1949
- role: "user" as const,
1950
- sessionID,
1951
- agentID: lastUser.agentID,
1952
- agent: lastUser.agent,
1953
- model: lastUser.model,
1954
- tools: lastUser.tools,
1955
- format: lastUser.format,
1956
- time: { created: Date.now() },
1957
- })
1958
- yield* sessions.updatePart({
1959
- id: PartID.ascending(),
1960
- messageID: reentry.id,
1961
- sessionID,
1962
- type: "text",
1963
- synthetic: true,
1964
- text: [
1965
- "<system-reminder>",
1966
- `Your goal is not yet satisfied: "${active.condition}".`,
1967
- "A judge reviewed the transcript and reported what is still missing:",
1968
- verdict.reason,
1969
- "Keep working toward the goal. Do not stop until it is genuinely met or impossible.",
1970
- "</system-reminder>",
1971
- ].join("\n"),
1972
- } satisfies MessageV2.TextPart)
1973
- return true
1974
- })
1975
-
1976
- // think-only (reasoning only) / empty (nothing at all) steps finish with
1977
- // a non-tool stop but carry no usable answer. Without intervention the loop
1978
- // breaks and hands the user an assistant with no final text. Nudge the model
1979
- // to produce a final answer or call a real tool; give up (write a terminal
1980
- // error) once the shared counter is exhausted so we never loop forever.
1981
- const autoContinueInvalidOutput = Effect.fn("SessionPrompt.autoContinueInvalidOutput")(function* (input: {
1982
- lastUser: MessageV2.User
1983
- assistant: MessageV2.Assistant
1984
- reason: string
1985
- }) {
1986
- if (input.assistant.error || input.assistant.summary || input.assistant.structured !== undefined) return false
1987
- if (invalidContinuations >= INVALID_OUTPUT_CONTINUATION_LIMIT) {
1988
- input.assistant.error = new MessageV2.InvalidOutputError({ message: input.reason }).toObject()
1989
- yield* sessions.updateMessage(input.assistant)
1990
- yield* bus.publish(Session.Event.Error, {
1991
- sessionID: input.assistant.sessionID,
1992
- error: input.assistant.error,
1993
- })
1994
- return false
1995
- }
1996
-
1997
- invalidContinuations++
1998
- yield* slog.info("auto-continuing invalid output", { attempt: invalidContinuations, reason: input.reason })
1999
- const msg = yield* sessions.updateMessage({
2000
- id: MessageID.ascending(),
2001
- role: "user" as const,
2002
- sessionID: input.lastUser.sessionID,
2003
- agentID: input.lastUser.agentID,
2004
- agent: input.lastUser.agent,
2005
- model: input.lastUser.model,
2006
- tools: input.lastUser.tools,
2007
- format: input.lastUser.format,
2008
- time: { created: Date.now() },
2009
- })
2010
- yield* sessions.updatePart({
2011
- id: PartID.ascending(),
2012
- messageID: msg.id,
2013
- sessionID: msg.sessionID,
2014
- type: "text",
2015
- synthetic: true,
2016
- text: [
2017
- "<system-reminder>",
2018
- "Your previous response contained no usable answer (it had only reasoning, or was empty).",
2019
- "Provide a final answer to the user now, or call a valid tool to make progress on the task.",
2020
- "Do not respond with only reasoning/thinking.",
2021
- "</system-reminder>",
2022
- ].join("\n"),
2023
- } satisfies MessageV2.TextPart)
2024
- return true
2025
- })
2026
-
2027
- // json_schema mode but the model never produced structured output (plain
2028
- // text stop, empty, think-only, or any other non-tool terminal). Retry up
2029
- // to lastUser.format.retryCount with a repair nudge; on exhaustion write a
2030
- // StructuredOutputError carrying the *real* retry count. Separate from
2031
- // invalidContinuations: structured retries are bounded by the per-request
2032
- // retryCount, not the generic invalid-output limit.
2033
- const autoRetryStructuredOutput = Effect.fn("SessionPrompt.autoRetryStructuredOutput")(function* (input: {
2034
- lastUser: MessageV2.User
2035
- assistant: MessageV2.Assistant
2036
- }) {
2037
- if (input.assistant.error || input.assistant.summary || input.assistant.structured !== undefined) return false
2038
- const limit = input.lastUser.format?.type === "json_schema" ? input.lastUser.format.retryCount : 0
2039
- if (structuredRetries >= limit) {
2040
- input.assistant.error = new MessageV2.StructuredOutputError({
2041
- message: "Model did not produce structured output",
2042
- retries: structuredRetries,
2043
- }).toObject()
2044
- yield* sessions.updateMessage(input.assistant)
2045
- yield* bus.publish(Session.Event.Error, {
2046
- sessionID: input.assistant.sessionID,
2047
- error: input.assistant.error,
2048
- })
2049
- return false
2050
- }
2051
-
2052
- structuredRetries++
2053
- yield* slog.info("retrying structured output", { attempt: structuredRetries })
2054
- const msg = yield* sessions.updateMessage({
2055
- id: MessageID.ascending(),
2056
- role: "user" as const,
2057
- sessionID: input.lastUser.sessionID,
2058
- agentID: input.lastUser.agentID,
2059
- agent: input.lastUser.agent,
2060
- model: input.lastUser.model,
2061
- tools: input.lastUser.tools,
2062
- // Must carry format so the next iteration re-registers the StructuredOutput tool.
2063
- format: input.lastUser.format,
2064
- time: { created: Date.now() },
2065
- })
2066
- yield* sessions.updatePart({
2067
- id: PartID.ascending(),
2068
- messageID: msg.id,
2069
- sessionID: msg.sessionID,
2070
- type: "text",
2071
- synthetic: true,
2072
- text: [
2073
- "<system-reminder>",
2074
- "Your previous response did not produce valid structured output via the StructuredOutput tool",
2075
- "(it was plain text, empty, or only reasoning).",
2076
- "You MUST call the StructuredOutput tool now, passing JSON that matches the requested schema.",
2077
- "Do not reply with plain text and do not respond with only reasoning/thinking.",
2078
- "</system-reminder>",
2079
- ].join("\n"),
2080
- } satisfies MessageV2.TextPart)
2081
- return true
2082
- })
2083
-
2084
- // content-filter is terminal on first occurrence: re-sending the same
2085
- // turn would just get filtered again, so there is no nudge / counter.
2086
- // Write a user-visible error (rendered via the session.error toast) and
2087
- // let the caller break.
2088
- const writeContentFilterError = Effect.fn("SessionPrompt.writeContentFilterError")(function* (input: {
2089
- assistant: MessageV2.Assistant
2090
- }) {
2091
- if (input.assistant.error) return
2092
- input.assistant.error = new MessageV2.ContentFilterError({
2093
- message: "The response was withheld by the model provider's content safety filter.",
2094
- }).toObject()
2095
- yield* sessions.updateMessage(input.assistant)
2096
- yield* bus.publish(Session.Event.Error, {
2097
- sessionID: input.assistant.sessionID,
2098
- error: input.assistant.error,
2099
- })
2100
- })
2101
-
2102
- // A `failed` classification (model "error" finish, or an error already set
2103
- // by the stream-error path) is terminal. If the step already carries an
2104
- // error (e.g. APIError written when the stream threw, processor.ts:581),
2105
- // keep it; otherwise write a ModelError so the loop never breaks silently
2106
- // without a user-visible failure.
2107
- const writeModelError = Effect.fn("SessionPrompt.writeModelError")(function* (input: {
2108
- assistant: MessageV2.Assistant
2109
- reason: string
2110
- }) {
2111
- if (input.assistant.error) return
2112
- input.assistant.error = new MessageV2.ModelError({ message: input.reason }).toObject()
2113
- yield* sessions.updateMessage(input.assistant)
2114
- yield* bus.publish(Session.Event.Error, {
2115
- sessionID: input.assistant.sessionID,
2116
- error: input.assistant.error,
2117
- })
2118
- })
2119
-
2120
- while (true) {
2121
- // F55: only main agent sets session status to busy; subagent runners
2122
- // must not touch session-level status (Runner.onBusy is Effect.void
2123
- // for non-main actors per F47).
2124
- if (!agentID || agentID === "main") yield* status.set(sessionID, { type: "busy" })
2125
- yield* inbox.drain(sessionID, agentID ?? "main").pipe(Effect.ignore)
2126
- yield* slog.info("loop", { step })
2127
-
2128
- // F37: filter by agentID so subagent slices stay isolated from the
2129
- // main agent's slice within the same session. Without this, an actor
2130
- // (explore/general/etc) spawned via WOOZLIT_CODE's shared-sessionID
2131
- // design would see the parent's full conversation here and drift
2132
- // off-task. agentID === "main" => main agent slice (agent_id = 'main'
2133
- // in DB), agentID === "explore-1" => only explore-1's slice.
2134
- let msgs = yield* MessageV2.filterCompactedEffect(sessionID, {
2135
- contextFrom: session.contextFrom,
2136
- contextWatermark: session.contextWatermark,
2137
- agentID: agentID ?? "main",
2138
- })
2139
-
2140
- let lastUser: MessageV2.User | undefined
2141
- let lastAssistant: MessageV2.Assistant | undefined
2142
- let lastFinished: MessageV2.Assistant | undefined
2143
- let tasks: MessageV2.SubtaskPart[] = []
2144
- for (let i = msgs.length - 1; i >= 0; i--) {
2145
- const msg = msgs[i]
2146
- if (!lastUser && msg.info.role === "user") lastUser = msg.info
2147
- if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info
2148
- if (!lastFinished && msg.info.role === "assistant" && msg.info.finish) lastFinished = msg.info
2149
- if (lastUser && lastFinished) break
2150
- const task = msg.parts.filter((part): part is MessageV2.SubtaskPart => part.type === "subtask")
2151
- if (task && !lastFinished) tasks.push(...task)
2152
- }
2153
-
2154
- if (!lastUser) throw new Error("No user message found in stream. This should never happen.")
2155
-
2156
- // Per-user-message active recall reminder. Once the session has
2157
- // any memory artifacts (memory dir populated OR tasks recorded),
2158
- // append a brief recall protocol so the agent's reflex to query
2159
- // memory.search / task / actor / Read stays warm across many
2160
- // post-rebuild turns. Cost ~120 tokens per turn, conditional on
2161
- // hasMemoryOrTasks.
2162
- const lastUserMsgForRecall = msgs.findLast((m) => m.info.role === "user")
2163
- if (lastUserMsgForRecall) {
2164
- const hasRecallTarget = yield* checkpoint
2165
- .hasMemoryOrTasks(sessionID)
2166
- .pipe(Effect.catch(() => Effect.succeed(false)))
2167
- if (hasRecallTarget) {
2168
- const sessMemDir = path.join(Global.Path.data, "memory", "sessions", sessionID)
2169
- const hints = recallHintLines((yield* config.get()).tool)
2170
- lastUserMsgForRecall.parts.push({
2171
- id: PartID.ascending(),
2172
- messageID: lastUserMsgForRecall.info.id,
2173
- sessionID,
2174
- type: "text" as const,
2175
- synthetic: true,
2176
- text: [
2177
- "<system-reminder>",
2178
- `This session has memory at ${sessMemDir}/. Recall content`,
2179
- "not in your context with:",
2180
- hints[0],
2181
- `- Read(file_path="${sessMemDir}/...")`,
2182
- hints[1],
2183
- hints[2],
2184
- "",
2185
- "Don't ask the user about something memory may already record.",
2186
- "</system-reminder>",
2187
- ].join("\n"),
2188
- })
2189
- }
2190
- }
2191
-
2192
- const lastAssistantMsg = msgs.findLast(
2193
- (msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id,
2194
- )
2195
- // Some providers return "stop" even when the assistant message contains tool calls.
2196
- // Keep the loop running so tool results can be sent back to the model.
2197
- // Skip provider-executed tool parts — those were fully handled within the
2198
- // provider's stream (e.g. DWS Agent Platform) and don't need a re-loop.
2199
- const hasToolCalls =
2200
- lastAssistantMsg?.parts.some((part) => part.type === "tool" && !part.metadata?.providerExecuted) ?? false
2201
-
2202
- if (
2203
- lastAssistant?.finish === "length" &&
2204
- !hasToolCalls &&
2205
- lastUser.id < lastAssistant.id &&
2206
- (yield* autoContinueOutputLength({ lastUser, assistant: lastAssistant }))
2207
- ) {
2208
- continue
2209
- }
2210
-
2211
- if (lastAssistant) {
2212
- const classification = classifyAssistantStep({
2213
- phase: "existing-assistant",
2214
- lastUser,
2215
- assistant: lastAssistant,
2216
- parts: lastAssistantMsg?.parts ?? [],
2217
- })
2218
- if (classification.type === "filtered") {
2219
- yield* writeContentFilterError({ assistant: lastAssistant })
2220
- yield* slog.info("exiting loop", { classification: classification.type })
2221
- break
2222
- }
2223
- if (classification.type === "failed") {
2224
- yield* writeModelError({ assistant: lastAssistant, reason: classification.reason })
2225
- yield* slog.info("exiting loop", { classification: classification.type, reason: classification.reason })
2226
- break
2227
- }
2228
- if (classification.type === "think-only" || classification.type === "invalid") {
2229
- const reason = classification.type === "invalid" ? classification.reason : "think-only"
2230
- if (yield* autoContinueInvalidOutput({ lastUser, assistant: lastAssistant, reason })) continue
2231
- yield* slog.info("exiting loop", { classification: classification.type })
2232
- break
2233
- }
2234
- if (classification.type === "final" && classification.degraded)
2235
- yield* slog.warn("degraded final on abnormal finish", { finish: lastAssistant.finish })
2236
- if (classification.type !== "continue") {
2237
- if (yield* taskGate(lastUser)) continue
2238
- if (yield* goalGate(lastUser)) continue
2239
- yield* slog.info("exiting loop", { classification: classification.type })
2240
- break
2241
- }
2242
- }
2243
-
2244
- step++
2245
- if (step === 1)
2246
- yield* title({
2247
- session,
2248
- modelID: lastUser.model.modelID,
2249
- providerID: lastUser.model.providerID,
2250
- history: msgs,
2251
- }).pipe(Effect.ignore, Effect.forkIn(scope))
2252
-
2253
- if (step === 1 && !session.parentID) {
2254
- const cfg = yield* config.get()
2255
- const dreamTrigger = yield* shouldAutoDream(cfg).pipe(Effect.catch(() => Effect.succeed(false)))
2256
- const distillTrigger = yield* shouldAutoDistill(cfg).pipe(Effect.catch(() => Effect.succeed(false)))
2257
- const mdl = { providerID: lastUser.model.providerID, modelID: lastUser.model.modelID }
2258
- // AppRuntime is imported dynamically (not at module top level) to keep
2259
- // the session layer out of the app-runtime module-init cycle
2260
- // (prompt → app-runtime → AppLayer → SessionPrompt). Only loaded when a
2261
- // trigger actually fires. Detached fire-and-forget on the full runtime.
2262
- if (dreamTrigger || distillTrigger) {
2263
- const { AppRuntime } = yield* Effect.promise(() => import("@/effect/app-runtime"))
2264
- if (dreamTrigger) {
2265
- AppRuntime.runPromise(
2266
- Session.Service.use((svc) =>
2267
- Effect.gen(function* () {
2268
- const s = yield* svc.create({ title: AUTO_DREAM_TITLE })
2269
- const sp = yield* Service
2270
- yield* sp.prompt({ sessionID: s.id, agent: "dream", model: mdl, parts: [{ type: "text", text: DREAM_TASK }] })
2271
- }),
2272
- ),
2273
- ).catch((err) => log.error("auto-dream prompt failed", { error: String(err) }))
2274
- }
2275
- if (distillTrigger) {
2276
- AppRuntime.runPromise(
2277
- Session.Service.use((svc) =>
2278
- Effect.gen(function* () {
2279
- const s = yield* svc.create({ title: AUTO_DISTILL_TITLE })
2280
- const sp = yield* Service
2281
- yield* sp.prompt({ sessionID: s.id, agent: "distill", model: mdl, parts: [{ type: "text", text: DISTILL_TASK }] })
2282
- }),
2283
- ),
2284
- ).catch((err) => log.error("auto-distill prompt failed", { error: String(err) }))
2285
- }
2286
- }
2287
- }
2288
-
2289
- const model = yield* getModel(lastUser.model.providerID, lastUser.model.modelID, sessionID)
2290
- lastModelForPrune = model
2291
- lastFinishedForPrune = lastFinished
2292
- const task = tasks.pop()
2293
-
2294
- if (task?.type === "subtask") {
2295
- yield* handleSubtask({ task, model, lastUser, sessionID, session, msgs })
2296
- continue
2297
- }
2298
-
2299
- // Detect compaction boundary: if the last user message has a compaction
2300
- // part, route to compact.process() instead of the normal LLM flow.
2301
- const lastUserMsgForCompaction = msgs.findLast((m) => m.info.role === "user")
2302
- if (lastUserMsgForCompaction?.parts.some((p) => p.type === "compaction")) {
2303
- const compactionPart = lastUserMsgForCompaction.parts.find(
2304
- (p): p is MessageV2.CompactionPart => p.type === "compaction",
2305
- )
2306
- const allMsgs = yield* sessions.messages({ sessionID })
2307
- const result = yield* compaction.process({
2308
- parentID: lastUser.id,
2309
- messages: allMsgs,
2310
- sessionID,
2311
- auto: compactionPart?.auto ?? false,
2312
- overflow: compactionPart?.overflow,
2313
- agentID: lastUser.agentID,
2314
- })
2315
- if (result === "stop") break
2316
- continue
2317
- }
2318
-
2319
- // Memory flush nudge at high context pressure
2320
- if (lastFinished && lastFinished.summary !== true && model) {
2321
- const cfg = yield* config.get()
2322
- const pressure = pressureLevel({ cfg, tokens: lastFinished.tokens, model })
2323
- if (pressure >= 2) {
2324
- // Inject nudge as a synthetic text part on the last user message
2325
- const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
2326
- if (
2327
- lastUserMsg &&
2328
- !lastUserMsg.parts.some((p) => p.type === "text" && p.text?.includes("Context is filling up"))
2329
- ) {
2330
- lastUserMsg.parts.push({
2331
- id: PartID.ascending(),
2332
- messageID: lastUserMsg.info.id,
2333
- sessionID,
2334
- type: "text",
2335
- synthetic: true,
2336
- text: [
2337
- "<system-reminder>",
2338
- `Context is filling up (${pressure >= 3 ? ">85%" : ">70%"}).`,
2339
- "If you have important learnings or decisions from this session,",
2340
- "consider writing them to memory now before context may be reset.",
2341
- "</system-reminder>",
2342
- ].join("\n"),
2343
- })
2344
- }
2345
- }
2346
- }
2347
-
2348
- // Repeated-step nudge: if the last REPEATED_STEP_THRESHOLD finished
2349
- // assistant steps made an identical tool call, the model is likely
2350
- // stuck looping. Inject a reminder on the last user message asking it
2351
- // to change approach. Mirrors the memory-flush nudge above (synthetic
2352
- // text part, deduped per build).
2353
- if (lastFinished) {
2354
- const recentSignatures: string[] = []
2355
- for (let i = msgs.length - 1; i >= 0 && recentSignatures.length < REPEATED_STEP_THRESHOLD; i--) {
2356
- const m = msgs[i]
2357
- if (m.info.role !== "assistant" || !m.info.finish) continue
2358
- const sig = stepSignature(m.parts)
2359
- if (sig === undefined) break
2360
- recentSignatures.push(sig)
2361
- }
2362
- const repeating =
2363
- recentSignatures.length === REPEATED_STEP_THRESHOLD &&
2364
- recentSignatures.every((sig) => sig === recentSignatures[0])
2365
- if (repeating) {
2366
- const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
2367
- if (
2368
- lastUserMsg &&
2369
- !lastUserMsg.parts.some(
2370
- (p) => p.type === "text" && p.text?.includes("repeating the same action"),
2371
- )
2372
- ) {
2373
- lastUserMsg.parts.push({
2374
- id: PartID.ascending(),
2375
- messageID: lastUserMsg.info.id,
2376
- sessionID,
2377
- type: "text",
2378
- synthetic: true,
2379
- text: [
2380
- "<system-reminder>",
2381
- `Your last ${REPEATED_STEP_THRESHOLD} steps have been identical — you appear to be`,
2382
- "repeating the same action without making progress. Stop and reconsider:",
2383
- "the current approach is not working. Try a different strategy, use a",
2384
- "different tool, or if you are blocked, explain the blocker to the user",
2385
- "instead of repeating the same step again.",
2386
- "</system-reminder>",
2387
- ].join("\n"),
2388
- })
2389
- }
2390
- }
2391
- }
2392
-
2393
- // Resolve the agent for this iteration once. Both the management
2394
- // hooks below (fireCheckpoints, overflow handler) and the existing
2395
- // agent-not-found check later in the iteration reuse this binding.
2396
- // Bounded computation agents (native + hidden — currently title,
2397
- // summary, checkpoint-writer) are exempt from context management;
2398
- // see docs/superpowers/specs/2026-04-28-bounded-computation-agents-design.md
2399
- const agent = yield* agents.get(lastUser.agent)
2400
- const isBoundedComputation =
2401
- agent?.native === true && agent?.hidden === true
2402
-
2403
- // Fire background checkpoint writers for any newly-crossed thresholds
2404
- // based on the latest completed assistant message's tokens. Must run
2405
- // BEFORE the overflow/maxThreshold check below so maxCrossed flag is
2406
- // set in time to trigger rebuild on this same iteration.
2407
- if (!skipOverflowCheck && !isBoundedComputation && lastFinished && lastFinished.tokens) {
2408
- const fireOps = yield* ops()
2409
- yield* prune
2410
- .fireCheckpoints({
2411
- sessionID,
2412
- model,
2413
- tokens: lastFinished.tokens,
2414
- promptOps: fireOps,
2415
- agentID: lastUser.agentID,
2416
- })
2417
- .pipe(Effect.ignore)
2418
- }
2419
-
2420
- if (
2421
- !skipOverflowCheck &&
2422
- !isBoundedComputation &&
2423
- lastFinished &&
2424
- lastFinished.summary !== true &&
2425
- (overflowCheck({ cfg: yield* config.get(), tokens: lastFinished.tokens, model }) ||
2426
- (yield* prune.maxThresholdCrossed(sessionID)))
2427
- ) {
2428
- // Subagent overflow → per-actor compaction (lossy LLM summarization
2429
- // scoped to the actor's (sessionID, agent_id) slice). Subagents
2430
- // don't have checkpoints, so checkpoint+discard does not apply.
2431
- // Gate must exclude agentID="main" — F49+F50 made main carry
2432
- // agentID="main", so a bare `if (lastUser.agentID)` would route
2433
- // main to this subagent path and skip the checkpoint rebuild
2434
- // below. See checkpoint.ts:715 for the matching gate.
2435
- if (lastUser.agentID && lastUser.agentID !== "main") {
2436
- yield* compaction
2437
- .create({
2438
- sessionID,
2439
- agent: lastUser.agent,
2440
- model: { providerID: model.providerID, modelID: model.id },
2441
- auto: true,
2442
- agentID: lastUser.agentID,
2443
- })
2444
- .pipe(Effect.ignore)
2445
- // After inserting the boundary, the actor's filterCompactedEffect
2446
- // slice begins at the boundary marker — context is freed for the
2447
- // next iteration's stream. Skip the next overflow check so the
2448
- // model can respond on the trimmed context.
2449
- skipOverflowCheck = true
2450
- continue
2451
- }
2452
-
2453
- // Main-agent overflow: insert a checkpoint boundary marker (never
2454
- // deletes DB messages) so the next iteration rebuilds from the
2455
- // freshest checkpoint. Fall back to compaction only when no boundary
2456
- // can be produced.
2457
- const hasCP = yield* checkpoint.hasCheckpoint(sessionID).pipe(Effect.catch(() => Effect.succeed(false)))
2458
- if (hasCP) {
2459
- // Wait for any running writer so the freshest checkpoint is available
2460
- yield* checkpoint.waitForWriter(sessionID).pipe(Effect.ignore)
2461
-
2462
- const boundary = yield* checkpoint
2463
- .lastBoundary(sessionID)
2464
- .pipe(Effect.catch(() => Effect.succeed(undefined)))
2465
- const boundaryMsg = boundary ? msgs.find((m) => m.info.id === boundary) : undefined
2466
- const inserted = boundary
2467
- ? yield* checkpoint
2468
- .insertRebuildBoundary({
2469
- sessionID,
2470
- boundary,
2471
- lastMessageInfo: computeLastMessageInfo(msgs.map((m) => m.info)),
2472
- agentID: lastUser.agentID,
2473
- agent: lastUser.agent,
2474
- model: { providerID: model.providerID, modelID: model.id },
2475
- boundaryCreatedAt: boundaryMsg?.info.time.created,
2476
- })
2477
- .pipe(Effect.catch(() => Effect.succeed(false)))
2478
- : false
2479
-
2480
- if (inserted) {
2481
- yield* prune.resetThresholds(sessionID)
2482
- skipOverflowCheck = true
2483
- continue
2484
- }
2485
- }
2486
-
2487
- // F39: no checkpoint — fall back to compaction (LLM-driven lossy summary).
2488
- // Better than mechanical trim: preserves semantic content via summary.
2489
- yield* compaction
2490
- .create({
2491
- sessionID,
2492
- agent: lastUser.agent,
2493
- model: { providerID: model.providerID, modelID: model.id },
2494
- auto: true,
2495
- agentID: lastUser.agentID,
2496
- })
2497
- .pipe(Effect.ignore)
2498
- skipOverflowCheck = true
2499
- continue
2500
- }
2501
- skipOverflowCheck = false
2502
-
2503
- // `agent` resolved at iteration start; reuse here for the
2504
- // agent-not-found user-visible error.
2505
- if (!agent) {
2506
- const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
2507
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
2508
- const error = new NamedError.Unknown({ message: `Agent not found: "${lastUser.agent}".${hint}` })
2509
- yield* bus.publish(Session.Event.Error, { sessionID, error: error.toObject() })
2510
- throw error
2511
- }
2512
- const maxSteps = agent.steps ?? Infinity
2513
- const isLastStep = step >= maxSteps
2514
- msgs = yield* insertReminders({ messages: msgs, agent, session })
2515
-
2516
- const msg: MessageV2.Assistant = {
2517
- id: MessageID.ascending(),
2518
- parentID: lastUser.id,
2519
- role: "assistant",
2520
- agentID: lastUser.agentID,
2521
- mode: agent.name,
2522
- agent: agent.name,
2523
- variant: lastUser.model.variant,
2524
- path: { cwd: ctx.directory, root: ctx.worktree },
2525
- cost: 0,
2526
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
2527
- modelID: model.id,
2528
- providerID: model.providerID,
2529
- time: { created: Date.now() },
2530
- sessionID,
2531
- }
2532
- yield* sessions.updateMessage(msg)
2533
- const handle = yield* processor.create({
2534
- assistantMessage: msg,
2535
- sessionID,
2536
- model,
2537
- agentMetrics,
2538
- })
2539
-
2540
- const outcome: "break" | "continue" = yield* Effect.gen(function* () {
2541
- const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
2542
- const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false
2543
-
2544
- const tools = yield* resolveTools({
2545
- agent,
2546
- session,
2547
- model,
2548
- tools: lastUser.tools,
2549
- processor: handle,
2550
- bypassAgentCheck,
2551
- messages: msgs,
2552
- agentID: lastUser.agentID,
2553
- task_id,
2554
- })
2555
-
2556
- if (lastUser.format?.type === "json_schema") {
2557
- tools["StructuredOutput"] = createStructuredOutputTool({
2558
- schema: lastUser.format.schema,
2559
- onSuccess(output) {
2560
- structured = output
2561
- },
2562
- })
2563
- }
2564
-
2565
- if (step === 1)
2566
- yield* summary.summarize({ sessionID, messageID: lastUser.id }).pipe(Effect.ignore, Effect.forkIn(scope))
2567
-
2568
- if (step > 1 && lastFinished) {
2569
- for (const m of msgs) {
2570
- if (m.info.role !== "user" || m.info.id <= lastFinished.id) continue
2571
- for (const p of m.parts) {
2572
- if (p.type !== "text" || p.ignored || p.synthetic) continue
2573
- if (!p.text.trim()) continue
2574
- p.text = [
2575
- "<system-reminder>",
2576
- "The user sent the following message:",
2577
- p.text,
2578
- "",
2579
- "Please address this message and continue with your tasks.",
2580
- "</system-reminder>",
2581
- ].join("\n")
2582
- }
2583
- }
2584
- }
2585
-
2586
- yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
2587
-
2588
- const format = lastUser.format ?? { type: "text" as const }
2589
-
2590
- // Determine if this iteration is for a fork agent (contextMode === "full").
2591
- // Fork agents use the frozen ForkContext snapshot captured at spawn time
2592
- // (system + inheritedMessages) rather than recomputing from their own
2593
- // agent identity — which would diverge from the parent and break the
2594
- // prefix cache.
2595
- const actorRecord = lastUser.agentID
2596
- ? yield* actorRegistry.get(sessionID, lastUser.agentID).pipe(
2597
- Effect.orElseSucceed(() => undefined),
2598
- )
2599
- : undefined
2600
- // v9 registers main as `mode: "main"` with `contextMode: "full"`.
2601
- // Only spawned actors (subagent/peer) carry a frozen ForkContext;
2602
- // main is the captor, never the captured.
2603
- const isForkAgent =
2604
- actorRecord?.contextMode === "full" &&
2605
- (actorRecord.mode === "subagent" || actorRecord.mode === "peer")
2606
-
2607
- // Fork path: read frozen ForkContext from Actor service (late-bound via
2608
- // spawnRef to break the Actor → SessionPrompt → Actor layer cycle).
2609
- // If forkCtx is missing (race / cleanup bug / spawn skipped), fail the
2610
- // actor so the next prune turn can spawn a fresh fork.
2611
- if (isForkAgent) {
2612
- const forkCtxEffect = spawnRef.current?.getForkContext(lastUser.agentID!)
2613
- const forkCtx = forkCtxEffect ? yield* forkCtxEffect : undefined
2614
- if (!forkCtx) {
2615
- yield* slog.warn("fork agent runLoop: missing forkContext, failing actor", {
2616
- sessionID,
2617
- agentID: lastUser.agentID,
2618
- })
2619
- yield* actorRegistry
2620
- .updateStatus(sessionID, lastUser.agentID!, { status: "idle", lastOutcome: "failure", lastError: "missing fork context" })
2621
- .pipe(Effect.ignore)
2622
- return "break" as const
2623
- }
2624
- const ownNew = msgs.filter(
2625
- (m) => m.info.id > forkCtx.watermarkMsgID && m.info.agentID === lastUser.agentID,
2626
- )
2627
- const ownNewModelMsgs = yield* MessageV2.toModelMessagesEffect(ownNew, model)
2628
- const prebuiltSystem = forkCtx.system
2629
- const modelMsgs: ModelMessage[] = [...forkCtx.inheritedMessages, ...ownNewModelMsgs]
2630
- // additions is empty for fork agents: system is taken verbatim from
2631
- // forkCtx.system. Passed as `system` to handle.process for logging/replay.
2632
- const additions: string[] = []
2633
- // Note: fork uses `tools` from resolveTools (not `forkCtx.tools`) — runtime
2634
- // tool dispatch needs execute closures, which `forkCtx.tools` does not carry.
2635
- // Schema parity with parent is currently a consequence of checkpoint-writer
2636
- // having no toolAllowlist (Task 2.6 + agent.test.ts guard). See ForkContext.tools
2637
- // JSDoc in packages/opencode/src/actor/spawn.ts for the full contract.
2638
- const result = yield* handle.process({
2639
- user: lastUser,
2640
- agent,
2641
- // Fork inherits the parent agent's permission (captured at spawn into
2642
- // ForkContext). This drives llm.ts resolveTools/disabled() to the SAME
2643
- // visible tool set as the parent → prompt-cache parity on the inherited
2644
- // prefix. Scope: this affects tool VISIBILITY only; the per-call ask
2645
- // ruleset (built separately in resolveTools' ask closure) is unchanged.
2646
- // Parity is exact modulo non-default `session.permission`: the parent's
2647
- // visibility ruleset is merge(parent.permission, session.permission)
2648
- // while the fork's is merge(writer.permission, parentPermission) — so a
2649
- // session-level rule pins the parent but not the fork. Still a strict
2650
- // improvement over the old bespoke "*":"deny" block (which always
2651
- // diverged). The `?? session.permission` is defense-in-depth only:
2652
- // parentPermission is a required field (empty `[]` on a missed capture,
2653
- // which `??` does NOT override), so the fallback fires solely if a future
2654
- // refactor makes the field optional.
2655
- permission: forkCtx.parentPermission ?? session.permission,
2656
- sessionID,
2657
- parentSessionID: session.parentID,
2658
- system: additions,
2659
- prebuiltSystem,
2660
- messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])],
2661
- tools,
2662
- model,
2663
- toolChoice: format.type === "json_schema" ? "required" : undefined,
2664
- agentID: lastUser.agentID,
2665
- })
2666
-
2667
- if (
2668
- result === "continue" &&
2669
- (yield* autoContinueOutputLength({ lastUser, assistant: handle.message }))
2670
- ) {
2671
- return "continue" as const
2672
- }
2673
-
2674
- if (structured !== undefined) {
2675
- handle.message.structured = structured
2676
- handle.message.finish = handle.message.finish ?? "stop"
2677
- yield* sessions.updateMessage(handle.message)
2678
- return "break" as const
2679
- }
2680
-
2681
- const forkClassification = classifyAssistantStep({
2682
- phase: "after-process",
2683
- lastUser,
2684
- assistant: handle.message,
2685
- parts: MessageV2.parts(handle.message.id),
2686
- processResult: result,
2687
- })
2688
- if (forkClassification.type === "filtered") {
2689
- yield* writeContentFilterError({ assistant: handle.message })
2690
- return "break" as const
2691
- }
2692
- if (forkClassification.type === "failed") {
2693
- yield* writeModelError({ assistant: handle.message, reason: forkClassification.reason })
2694
- return "break" as const
2695
- }
2696
- if (forkClassification.type !== "continue" && !handle.message.error && format.type === "json_schema") {
2697
- if (yield* autoRetryStructuredOutput({ lastUser, assistant: handle.message }))
2698
- return "continue" as const
2699
- return "break" as const
2700
- }
2701
-
2702
- if (
2703
- (forkClassification.type === "think-only" || forkClassification.type === "invalid") &&
2704
- format.type !== "json_schema"
2705
- ) {
2706
- const reason =
2707
- forkClassification.type === "invalid" ? forkClassification.reason : "think-only"
2708
- if (yield* autoContinueInvalidOutput({ lastUser, assistant: handle.message, reason }))
2709
- return "continue" as const
2710
- return "break" as const
2711
- }
2712
-
2713
- if (forkClassification.type === "final" && forkClassification.degraded)
2714
- yield* slog.warn("degraded final on abnormal finish", { finish: handle.message.finish })
2715
- if (result === "stop") return "break" as const
2716
- // Fork agents are always subagents (lastUser.agentID is set); use
2717
- // per-actor compaction on overflow (same as non-fork subagent path).
2718
- if (!isBoundedComputation && result === "overflow") {
2719
- yield* compaction
2720
- .create({
2721
- sessionID,
2722
- agent: lastUser.agent,
2723
- model: { providerID: model.providerID, modelID: model.id },
2724
- auto: true,
2725
- overflow: true,
2726
- agentID: lastUser.agentID,
2727
- })
2728
- .pipe(Effect.ignore)
2729
- }
2730
- return "continue" as const
2731
- }
2732
-
2733
- const [skills, env, instructions] = yield* Effect.all([
2734
- sys.skills(agent),
2735
- Effect.sync(() => sys.environment(model)),
2736
- instruction.system().pipe(Effect.orDie),
2737
- ])
2738
- // Surface which instruction files (CLAUDE.md, AGENTS.md, ...) were loaded.
2739
- // Only for primary sessions (subagents would be noisy) and once per session.
2740
- if (!session.parentID && !instructionsNotified.has(sessionID)) {
2741
- instructionsNotified.add(sessionID)
2742
- const worktree = (yield* InstanceState.context).worktree
2743
- const files = Array.from(instructions.paths, (p) => Instruction.display(p, worktree))
2744
- if (files.length > 0) {
2745
- yield* bus.publish(TuiEvent.InstructionsLoaded, { files }).pipe(Effect.ignore)
2746
- }
2747
- }
2748
- const additions = [
2749
- ...env,
2750
- ...(skills ? [skills] : []),
2751
- ...instructions.content,
2752
- ...(format.type === "json_schema" ? [STRUCTURED_OUTPUT_SYSTEM_PROMPT] : []),
2753
- ]
2754
- // Note: `buildLLMRequestPrefix` also returns a `tools` field, but we
2755
- // intentionally don't use it here — the `tools` variable from `resolveTools`
2756
- // (set earlier via `handle.process({tools: ...})`) carries `execute` closures
2757
- // the AI SDK needs for runtime tool dispatch, while `buildLLMRequestPrefix`
2758
- // produces schema-only tools. Schema bytes match between both paths (both call
2759
- // registry.tools with identical args), so prefix cache parity holds.
2760
- // Main runLoop: no watermark — LLM must see the full msgs list,
2761
- // including this turn's intermediate assistant turns (tool reads,
2762
- // task creates, etc.) so each step doesn't replay from the bare
2763
- // user prompt. The watermark is for fork capture only (frozen
2764
- // snapshot of parent-view at spawn time).
2765
- const { system: prebuiltSystem, inheritedMessages: modelMsgs } =
2766
- yield* buildLLMRequestPrefix({
2767
- sessionID,
2768
- agent,
2769
- model,
2770
- msgs,
2771
- additions,
2772
- }).pipe(
2773
- Effect.provideService(LLM.Service, llm),
2774
- Effect.provideService(ToolRegistry.Service, registry),
2775
- )
2776
- const maxModeCfg = (yield* config.get()).experimental?.maxMode
2777
- const useMaxMode =
2778
- agent.name === MaxMode.MAX_MODE_AGENT && maxModeCfg !== undefined && format.type !== "json_schema"
2779
-
2780
- const processArgs = {
2781
- user: lastUser,
2782
- agent,
2783
- permission: session.permission,
2784
- sessionID,
2785
- parentSessionID: session.parentID,
2786
- // system: additions is preserved for non-LLM consumers of StreamInput (e.g.,
2787
- // MessageV2.User.system for logging/replay); llm.stream itself uses prebuiltSystem.
2788
- system: additions,
2789
- prebuiltSystem,
2790
- messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])],
2791
- tools,
2792
- model,
2793
- toolChoice: format.type === "json_schema" ? ("required" as const) : undefined,
2794
- agentID: lastUser.agentID,
2795
- }
2796
-
2797
- const result = useMaxMode
2798
- ? yield* MaxMode.runMaxStep({
2799
- // runMaxStep reuses the identical per-step args as handle.process,
2800
- // plus the orchestration handles it needs.
2801
- ...processArgs,
2802
- handle,
2803
- llm,
2804
- candidates: maxModeCfg?.candidates,
2805
- setStatus: (message) =>
2806
- status.set(sessionID, message ? { type: "busy", message } : { type: "busy" }),
2807
- })
2808
- : yield* handle.process(processArgs)
2809
-
2810
- if (
2811
- result === "continue" &&
2812
- (yield* autoContinueOutputLength({ lastUser, assistant: handle.message }))
2813
- ) {
2814
- return "continue" as const
2815
- }
2816
-
2817
- if (structured !== undefined) {
2818
- handle.message.structured = structured
2819
- handle.message.finish = handle.message.finish ?? "stop"
2820
- yield* sessions.updateMessage(handle.message)
2821
- return "break" as const
2822
- }
2823
-
2824
- const classification = classifyAssistantStep({
2825
- phase: "after-process",
2826
- lastUser,
2827
- assistant: handle.message,
2828
- parts: MessageV2.parts(handle.message.id),
2829
- processResult: result,
2830
- })
2831
- slog.info("classified assistant step", {
2832
- type: classification.type,
2833
- reason: "reason" in classification ? classification.reason : undefined,
2834
- partsCount: MessageV2.parts(handle.message.id).length,
2835
- finishReason: handle.message.finish,
2836
- result,
2837
- })
2838
- if (classification.type === "filtered") {
2839
- yield* writeContentFilterError({ assistant: handle.message })
2840
- return "break" as const
2841
- }
2842
- if (classification.type === "failed") {
2843
- yield* writeModelError({ assistant: handle.message, reason: classification.reason })
2844
- return "break" as const
2845
- }
2846
- if (classification.type !== "continue" && !handle.message.error && format.type === "json_schema") {
2847
- if (yield* autoRetryStructuredOutput({ lastUser, assistant: handle.message })) return "continue" as const
2848
- return "break" as const
2849
- }
2850
-
2851
- if (
2852
- (classification.type === "think-only" || classification.type === "invalid") &&
2853
- format.type !== "json_schema"
2854
- ) {
2855
- const reason = classification.type === "invalid" ? classification.reason : "think-only"
2856
- if (yield* autoContinueInvalidOutput({ lastUser, assistant: handle.message, reason }))
2857
- return "continue" as const
2858
- return "break" as const
2859
- }
2860
-
2861
- if (classification.type === "final" && classification.degraded)
2862
- yield* slog.warn("degraded final on abnormal finish", { finish: handle.message.finish })
2863
- if (result === "stop") return "break" as const
2864
- if (!isBoundedComputation && result === "overflow") {
2865
- // Subagent overflow → per-actor compaction. Insert a boundary
2866
- // tagged with the subagent's agent_id; the next runLoop iteration
2867
- // will see a trimmed context (filterCompactedEffect stops at
2868
- // the boundary).
2869
- // Gate must exclude "main" — see comment at the matching gate
2870
- // earlier in this file (~line 1716) and at checkpoint.ts:715.
2871
- if (lastUser.agentID && lastUser.agentID !== "main") {
2872
- yield* compaction
2873
- .create({
2874
- sessionID,
2875
- agent: lastUser.agent,
2876
- model: { providerID: model.providerID, modelID: model.id },
2877
- auto: true,
2878
- overflow: true,
2879
- agentID: lastUser.agentID,
2880
- })
2881
- .pipe(Effect.ignore)
2882
- return "continue" as const
2883
- }
2884
-
2885
- // Main-agent provider-signalled overflow: insert a checkpoint
2886
- // boundary marker (never deletes). Prefer rebuild over compaction:
2887
- // if a writer is running or finished, wait (bounded) and rebuild
2888
- // from it. Fall back to compaction only when no boundary exists.
2889
- const writerRunning = yield* checkpoint.isWriterRunning(sessionID)
2890
- .pipe(Effect.catch(() => Effect.succeed(false)))
2891
- const hasCP = yield* checkpoint.hasCheckpoint(sessionID)
2892
- .pipe(Effect.catch(() => Effect.succeed(false)))
2893
-
2894
- if (writerRunning || hasCP) {
2895
- yield* checkpoint.waitForWriter(sessionID).pipe(Effect.ignore)
2896
- const boundary2 = yield* checkpoint.lastBoundary(sessionID)
2897
- .pipe(Effect.catch(() => Effect.succeed(undefined)))
2898
- const boundary2Msg = boundary2 ? msgs.find((m) => m.info.id === boundary2) : undefined
2899
- const inserted2 = boundary2
2900
- ? yield* checkpoint
2901
- .insertRebuildBoundary({
2902
- sessionID,
2903
- boundary: boundary2,
2904
- lastMessageInfo: computeLastMessageInfo(msgs.map((m) => m.info)),
2905
- agentID: lastUser.agentID,
2906
- agent: lastUser.agent,
2907
- model: { providerID: model.providerID, modelID: model.id },
2908
- boundaryCreatedAt: boundary2Msg?.info.time.created,
2909
- })
2910
- .pipe(Effect.catch(() => Effect.succeed(false)))
2911
- : false
2912
-
2913
- if (inserted2) {
2914
- yield* prune.resetThresholds(sessionID)
2915
- return "continue" as const
2916
- }
2917
- }
2918
-
2919
- // F39: no checkpoint — fall back to compaction (LLM-driven lossy summary).
2920
- yield* compaction
2921
- .create({
2922
- sessionID,
2923
- agent: lastUser.agent,
2924
- model: { providerID: model.providerID, modelID: model.id },
2925
- auto: true,
2926
- overflow: true,
2927
- agentID: lastUser.agentID,
2928
- })
2929
- .pipe(Effect.ignore)
2930
- }
2931
- return "continue" as const
2932
- }).pipe(Effect.ensuring(instruction.clear(handle.message.id)))
2933
- if (outcome === "break") {
2934
- if (yield* taskGate(lastUser)) continue
2935
- if (yield* goalGate(lastUser)) continue
2936
- break
2937
- }
2938
- continue
2939
- }
2940
-
2941
- const promptOps = yield* ops()
2942
- if (lastModelForPrune && lastFinishedForPrune) {
2943
- yield* prune
2944
- .prune({
2945
- sessionID,
2946
- model: lastModelForPrune,
2947
- tokens: lastFinishedForPrune.tokens,
2948
- lastAssistantTime: lastFinishedForPrune.time.completed,
2949
- promptOps,
2950
- })
2951
- .pipe(Effect.ignore, Effect.forkIn(scope))
2952
- }
2953
- const final = yield* lastAssistant(sessionID, agentID)
2954
- const finalIsError = final.info.role === "assistant" && !!final.info.error
2955
- const lastUserForMetrics = yield* sessions.findMessage(
2956
- sessionID,
2957
- (m) => m.info.role === "user",
2958
- { agentID: "*" },
2959
- )
2960
- yield* publishAgentRequest(
2961
- finalIsError ? "error" : "completed",
2962
- Option.isSome(lastUserForMetrics) ? lastUserForMetrics.value.info.agent : final.info.agent,
2963
- )
2964
- return final
2965
- },
2966
- )
2967
-
2968
- const loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts> = Effect.fn(
2969
- "SessionPrompt.loop",
2970
- )(function* (input: z.infer<typeof LoopInput>) {
2971
- const agentID = input.agentID ?? "main"
2972
- return yield* state.ensureRunning(
2973
- input.sessionID,
2974
- agentID,
2975
- lastAssistant(input.sessionID, agentID),
2976
- runLoop(input.sessionID, agentID, input.task_id),
2977
- )
2978
- })
2979
-
2980
- const shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.shell")(
2981
- function* (input: ShellInput) {
2982
- return yield* state.startShell(input.sessionID, lastAssistant(input.sessionID), shellImpl(input))
2983
- },
2984
- )
2985
-
2986
- const command = Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
2987
- yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
2988
- const cmd = yield* commands.get(input.command)
2989
- if (!cmd) {
2990
- const available = (yield* commands.list()).map((c) => c.name)
2991
- const hint = available.length ? ` Available commands: ${available.join(", ")}` : ""
2992
- const error = new NamedError.Unknown({ message: `Command not found: "${input.command}".${hint}` })
2993
- yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
2994
- throw error
2995
- }
2996
- const agentName = cmd.agent ?? input.agent ?? (yield* agents.defaultAgent())
2997
-
2998
- // /goal — set or clear a session-level stop-condition goal. The condition
2999
- // text itself becomes the prompt for this turn (the working agent starts
3000
- // pursuing it immediately); the main runLoop then refuses to stop until
3001
- // the judge says it's satisfied. See session/goal.ts.
3002
- if (input.command === Command.Default.GOAL) {
3003
- const condition = input.arguments.trim()
3004
- if (condition === "" || condition === "clear" || condition === "reset") {
3005
- yield* goal.clear(input.sessionID)
3006
- return yield* prompt({
3007
- sessionID: input.sessionID,
3008
- messageID: input.messageID,
3009
- agent: agentName,
3010
- parts: [{ type: "text", text: "Goal cleared.", synthetic: true }],
3011
- noReply: true,
3012
- })
3013
- }
3014
- yield* goal.set(input.sessionID, condition)
3015
- }
3016
-
3017
- const raw = input.arguments.match(argsRegex) ?? []
3018
- const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
3019
- const templateCommand = yield* Effect.promise(async () => cmd.template)
3020
-
3021
- let template: string
3022
- if (cmd.source === "skill") {
3023
- template = input.arguments
3024
- } else {
3025
- const placeholders = templateCommand.match(placeholderRegex) ?? []
3026
- let last = 0
3027
- for (const item of placeholders) {
3028
- const value = Number(item.slice(1))
3029
- if (value > last) last = value
3030
- }
3031
-
3032
- const withArgs = templateCommand.replaceAll(placeholderRegex, (_, index) => {
3033
- const position = Number(index)
3034
- const argIndex = position - 1
3035
- if (argIndex >= args.length) return ""
3036
- if (position === last) return args.slice(argIndex).join(" ")
3037
- return args[argIndex]
3038
- })
3039
- const usesArgumentsPlaceholder = templateCommand.includes("$ARGUMENTS")
3040
- template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
3041
-
3042
- if (placeholders.length === 0 && !usesArgumentsPlaceholder && input.arguments.trim()) {
3043
- template = template + "\n\n" + input.arguments
3044
- }
3045
- }
3046
-
3047
- const shellMatches = ConfigMarkdown.shell(template)
3048
- if (shellMatches.length > 0) {
3049
- const sh = Shell.preferred()
3050
- const results = yield* Effect.promise(() =>
3051
- Promise.all(
3052
- shellMatches.map(async ([, cmd]) => (await Process.text([cmd], { shell: sh, nothrow: true })).text),
3053
- ),
3054
- )
3055
- let index = 0
3056
- template = template.replace(bashRegex, () => results[index++])
3057
- }
3058
- template = template.trim()
3059
-
3060
- const taskModel = yield* Effect.gen(function* () {
3061
- if (cmd.model) return Provider.parseModel(cmd.model)
3062
- if (cmd.agent) {
3063
- const cmdAgent = yield* agents.get(cmd.agent)
3064
- if (cmdAgent?.model) return cmdAgent.model
3065
- }
3066
- if (input.model) return Provider.parseModel(input.model)
3067
- return yield* lastModel(input.sessionID)
3068
- })
3069
-
3070
- yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID)
3071
-
3072
- const agent = yield* agents.get(agentName)
3073
- if (!agent) {
3074
- const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
3075
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
3076
- const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
3077
- yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
3078
- throw error
3079
- }
3080
-
3081
- const templateParts = yield* resolvePromptParts(template)
3082
- const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true
3083
-
3084
- let parts: PromptInput["parts"]
3085
- if (isSubtask) {
3086
- const promptText = cmd.source === "skill"
3087
- ? templateCommand + (input.arguments.trim() ? "\n\n" + input.arguments : "")
3088
- : (templateParts.find((y): y is typeof y & { type: "text"; text: string } => y.type === "text"))?.text ?? ""
3089
- parts = [
3090
- {
3091
- type: "subtask" as const,
3092
- agent: agent.name,
3093
- description: cmd.description ?? "",
3094
- command: input.command,
3095
- model: { providerID: taskModel.providerID, modelID: taskModel.modelID },
3096
- prompt: promptText,
3097
- },
3098
- ]
3099
- } else if (cmd.source === "skill") {
3100
- const visibleText = input.arguments.trim()
3101
- ? `/${input.command} ${input.arguments}`
3102
- : `/${input.command}`
3103
- const skillPart = {
3104
- type: "text" as const,
3105
- text: `<skill_content name="${input.command}">\n${templateCommand}\n</skill_content>`,
3106
- synthetic: true,
3107
- }
3108
- const attachments = templateParts.filter((p): p is Exclude<typeof p, { type: "text" }> => p.type !== "text")
3109
- parts = [{ type: "text" as const, text: visibleText }, skillPart, ...attachments, ...(input.parts ?? [])]
3110
- } else {
3111
- parts = [...templateParts, ...(input.parts ?? [])]
3112
- }
3113
-
3114
- const userAgent = isSubtask ? (input.agent ?? (yield* agents.defaultAgent())) : agentName
3115
- const userModel = isSubtask
3116
- ? input.model
3117
- ? Provider.parseModel(input.model)
3118
- : yield* lastModel(input.sessionID)
3119
- : taskModel
3120
-
3121
- yield* plugin.trigger(
3122
- "command.execute.before",
3123
- { command: input.command, sessionID: input.sessionID, arguments: input.arguments },
3124
- { parts },
3125
- )
3126
-
3127
- const result = yield* prompt({
3128
- sessionID: input.sessionID,
3129
- messageID: input.messageID,
3130
- model: userModel,
3131
- agent: userAgent,
3132
- parts,
3133
- variant: input.variant,
3134
- })
3135
- yield* bus.publish(Command.Event.Executed, {
3136
- name: input.command,
3137
- sessionID: input.sessionID,
3138
- arguments: input.arguments,
3139
- messageID: result.info.id,
3140
- })
3141
- return result
3142
- })
3143
-
3144
- const impl = Service.of({
3145
- cancel,
3146
- prompt,
3147
- loop,
3148
- shell,
3149
- command,
3150
- resolvePromptParts,
3151
- sweepOrphanAssistants,
3152
- predict,
3153
- })
3154
- sessionPromptRef.current = { loop: impl.loop }
3155
- yield* Effect.addFinalizer(() =>
3156
- Effect.sync(() => {
3157
- if (sessionPromptRef.current?.loop === impl.loop) sessionPromptRef.current = undefined
3158
- }),
3159
- )
3160
- return impl
3161
- }),
3162
- )
3163
-
3164
- export const defaultLayer = Layer.suspend(() =>
3165
- layer.pipe(
3166
- Layer.provide(SessionRunState.defaultLayer),
3167
- Layer.provide(SessionStatus.defaultLayer),
3168
- Layer.provide(SessionPrune.defaultLayer),
3169
- Layer.provide(SessionCheckpoint.defaultLayer),
3170
- Layer.provide(SessionCompaction.defaultLayer),
3171
- Layer.provide(SessionProcessor.defaultLayer),
3172
- Layer.provide(Command.defaultLayer),
3173
- Layer.provide(Permission.defaultLayer),
3174
- Layer.provide(MCP.defaultLayer),
3175
- Layer.provide(LSP.defaultLayer),
3176
- Layer.provide(ToolRegistry.defaultLayer),
3177
- Layer.provide(Truncate.defaultLayer),
3178
- Layer.provide(Provider.defaultLayer),
3179
- Layer.provide(Instruction.defaultLayer),
3180
- Layer.provide(AppFileSystem.defaultLayer),
3181
- Layer.provide(Plugin.defaultLayer),
3182
- Layer.provide(Session.defaultLayer),
3183
- Layer.provide(SessionRevert.defaultLayer),
3184
- Layer.provide(
3185
- Layer.mergeAll(
3186
- Config.defaultLayer,
3187
- SessionSummary.defaultLayer,
3188
- Team.defaultLayer,
3189
- ActorRegistry.defaultLayer,
3190
- Agent.defaultLayer,
3191
- SystemPrompt.defaultLayer,
3192
- LLM.defaultLayer,
3193
- Bus.layer,
3194
- CrossSpawnSpawner.defaultLayer,
3195
- Inbox.defaultLayer,
3196
- Goal.defaultLayer,
3197
- TaskGateState.defaultLayer,
3198
- TaskRegistry.defaultLayer,
3199
- ),
3200
- ),
3201
- ),
3202
- )
3203
- export const PromptInput = z.object({
3204
- sessionID: SessionID.zod,
3205
- messageID: MessageID.zod.optional(),
3206
- model: z
3207
- .object({
3208
- providerID: ProviderID.zod,
3209
- modelID: ModelID.zod,
3210
- })
3211
- .optional(),
3212
- modelRef: z
3213
- .string()
3214
- .optional()
3215
- .describe(
3216
- "Model group/tier name (e.g. ultra/standard/lite) or a literal provider/model. Resolved provider-aware. Takes precedence over `model` when both are set.",
3217
- ),
3218
- agent: z.string().optional(),
3219
- agentID: z.string().optional(),
3220
- task_id: z.string().optional()
3221
- .describe("If the spawning caller bound this prompt to a specific user-task (T4 etc), pass its TID. Propagates to Tool.Context.taskId so memory-path-guard allows writes to tasks/<task_id>/*.md."),
3222
- source: z.enum(["user", "spawn", "hook"]).optional(),
3223
- provenance: MessageV2.Provenance.optional(),
3224
- noReply: z.boolean().optional(),
3225
- tools: z
3226
- .record(z.string(), z.boolean())
3227
- .optional()
3228
- .describe("@deprecated tools and permissions have been merged, you can set permissions on the session itself now"),
3229
- format: MessageV2.Format.optional(),
3230
- system: z.string().optional(),
3231
- variant: z.string().optional(),
3232
- parts: z.array(
3233
- z.discriminatedUnion("type", [
3234
- MessageV2.TextPart.omit({
3235
- messageID: true,
3236
- sessionID: true,
3237
- })
3238
- .partial({
3239
- id: true,
3240
- })
3241
- .meta({
3242
- ref: "TextPartInput",
3243
- }),
3244
- MessageV2.FilePart.omit({
3245
- messageID: true,
3246
- sessionID: true,
3247
- })
3248
- .partial({
3249
- id: true,
3250
- })
3251
- .meta({
3252
- ref: "FilePartInput",
3253
- }),
3254
- MessageV2.AgentPart.omit({
3255
- messageID: true,
3256
- sessionID: true,
3257
- })
3258
- .partial({
3259
- id: true,
3260
- })
3261
- .meta({
3262
- ref: "AgentPartInput",
3263
- }),
3264
- MessageV2.SubtaskPart.omit({
3265
- messageID: true,
3266
- sessionID: true,
3267
- })
3268
- .partial({
3269
- id: true,
3270
- })
3271
- .meta({
3272
- ref: "SubtaskPartInput",
3273
- }),
3274
- ]),
3275
- ),
3276
- })
3277
- export type PromptInput = z.infer<typeof PromptInput>
3278
-
3279
- export const LoopInput = z.object({
3280
- sessionID: SessionID.zod,
3281
- agentID: z.string().optional(),
3282
- task_id: z.string().optional(),
3283
- })
3284
-
3285
- export const ShellInput = z.object({
3286
- sessionID: SessionID.zod,
3287
- messageID: MessageID.zod.optional(),
3288
- agent: z.string(),
3289
- model: z
3290
- .object({
3291
- providerID: ProviderID.zod,
3292
- modelID: ModelID.zod,
3293
- })
3294
- .optional(),
3295
- modelRef: z
3296
- .string()
3297
- .optional()
3298
- .describe(
3299
- "Model group/tier name (e.g. ultra/standard/lite) or a literal provider/model. Resolved provider-aware. Takes precedence over `model` when both are set.",
3300
- ),
3301
- command: z.string(),
3302
- })
3303
- export type ShellInput = z.infer<typeof ShellInput>
3304
-
3305
- export const CommandInput = z.object({
3306
- messageID: MessageID.zod.optional(),
3307
- sessionID: SessionID.zod,
3308
- agent: z.string().optional(),
3309
- model: z.string().optional(),
3310
- arguments: z.string(),
3311
- command: z.string(),
3312
- variant: z.string().optional(),
3313
- parts: z
3314
- .array(
3315
- z.discriminatedUnion("type", [
3316
- MessageV2.FilePart.omit({
3317
- messageID: true,
3318
- sessionID: true,
3319
- }).partial({
3320
- id: true,
3321
- }),
3322
- ]),
3323
- )
3324
- .optional(),
3325
- })
3326
- export type CommandInput = z.infer<typeof CommandInput>
3327
-
3328
- /** @internal Exported for testing */
3329
- export function createStructuredOutputTool(input: {
3330
- schema: Record<string, any>
3331
- onSuccess: (output: unknown) => void
3332
- }): AITool {
3333
- // Remove $schema property if present (not needed for tool input)
3334
- const { $schema: _, ...toolSchema } = input.schema
3335
-
3336
- return tool({
3337
- description: STRUCTURED_OUTPUT_DESCRIPTION,
3338
- inputSchema: jsonSchema(toolSchema as JSONSchema7),
3339
- async execute(args) {
3340
- // AI SDK validates args against inputSchema before calling execute()
3341
- input.onSuccess(args)
3342
- return {
3343
- output: "Structured output captured successfully.",
3344
- title: "Structured Output",
3345
- metadata: { valid: true },
3346
- }
3347
- },
3348
- toModelOutput({ output }) {
3349
- return {
3350
- type: "text",
3351
- value: output.output,
3352
- }
3353
- },
3354
- })
3355
- }
3356
- const bashRegex = /!`([^`]+)`/g
3357
- // Match [Image N] as single token, quoted strings, or non-space sequences
3358
- const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
3359
- const placeholderRegex = /\$(\d+)/g
3360
- const quoteTrimRegex = /^["']|["']$/g
3361
-
3362
- export * as SessionPrompt from "./prompt"