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