@sleepy-ai/cli 0.1.5 → 0.1.7

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 (799) hide show
  1. package/{src/skill/compose/LICENSE-superpowers → LICENSE} +3 -6
  2. package/README.md +139 -6
  3. package/package.json +35 -199
  4. package/postinstall.mjs +102 -0
  5. package/.sleepycode-test-fixtures-50158/sleepycode-test-eqsxv84ythu/.watcher-b67v64k2mnv +0 -1
  6. package/Dockerfile +0 -18
  7. package/drizzle.config.ts +0 -10
  8. package/git +0 -0
  9. package/migration/20260127222353_familiar_lady_ursula/migration.sql +0 -90
  10. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +0 -796
  11. package/migration/20260211171708_add_project_commands/migration.sql +0 -1
  12. package/migration/20260211171708_add_project_commands/snapshot.json +0 -806
  13. package/migration/20260213144116_wakeful_the_professor/migration.sql +0 -11
  14. package/migration/20260213144116_wakeful_the_professor/snapshot.json +0 -897
  15. package/migration/20260225215848_workspace/migration.sql +0 -7
  16. package/migration/20260225215848_workspace/snapshot.json +0 -959
  17. package/migration/20260227213759_add_session_workspace_id/migration.sql +0 -2
  18. package/migration/20260227213759_add_session_workspace_id/snapshot.json +0 -983
  19. package/migration/20260228203230_blue_harpoon/migration.sql +0 -17
  20. package/migration/20260228203230_blue_harpoon/snapshot.json +0 -1102
  21. package/migration/20260303231226_add_workspace_fields/migration.sql +0 -5
  22. package/migration/20260303231226_add_workspace_fields/snapshot.json +0 -1013
  23. package/migration/20260309230000_move_org_to_state/migration.sql +0 -3
  24. package/migration/20260309230000_move_org_to_state/snapshot.json +0 -1156
  25. package/migration/20260312043431_session_message_cursor/migration.sql +0 -4
  26. package/migration/20260312043431_session_message_cursor/snapshot.json +0 -1168
  27. package/migration/20260323234822_events/migration.sql +0 -13
  28. package/migration/20260323234822_events/snapshot.json +0 -1271
  29. package/migration/20260410174513_workspace-name/migration.sql +0 -16
  30. package/migration/20260410174513_workspace-name/snapshot.json +0 -1271
  31. package/migration/20260413175956_chief_energizer/migration.sql +0 -13
  32. package/migration/20260413175956_chief_energizer/snapshot.json +0 -1399
  33. package/migration/20260422160000_context_inheritance/migration.sql +0 -3
  34. package/migration/20260422170000_task_registry/migration.sql +0 -18
  35. package/migration/20260423145421_remove_session_entry/migration.sql +0 -4
  36. package/migration/20260515000000_actor_rename/migration.sql +0 -7
  37. package/migration/20260515010000_memory_fts/migration.sql +0 -33
  38. package/migration/20260515020000_user_task/migration.sql +0 -29
  39. package/migration/20260519000000_last_checkpoint_message_id/migration.sql +0 -1
  40. package/migration/20260521000000_message_agent_id/migration.sql +0 -2
  41. package/migration/20260521000100_actor_registry_v6/migration.sql +0 -25
  42. package/migration/20260521010000_memory_fts_v6/migration.sql +0 -33
  43. package/migration/20260521020000_memory_fts_triggers/migration.sql +0 -17
  44. package/migration/20260526000000_agent_id_main/migration.sql +0 -14
  45. package/migration/20260527000000_actor_lifecycle/migration.sql +0 -8
  46. package/migration/20260527000100_inbox/migration.sql +0 -12
  47. package/migration/20260529000000_task_todo_redesign/migration.sql +0 -16
  48. package/migration/20260603000000_task_in_progress_owner/migration.sql +0 -1
  49. package/migration/20260603000000_workflow_run/migration.sql +0 -17
  50. package/migration/20260604000000_workflow_script_sha/migration.sql +0 -1
  51. package/migration/20260608000000_claude_import/migration.sql +0 -7
  52. package/migration/20260608010000_claude_import_message_ids/migration.sql +0 -1
  53. package/migration/20260609000000_history_fts/migration.sql +0 -29
  54. package/migration/20260609230000_workflow_agent_timeout/migration.sql +0 -1
  55. package/migration/20260612000000_external_import/migration.sql +0 -16
  56. package/parsers-config.ts +0 -290
  57. package/src/account/account.sql.ts +0 -39
  58. package/src/account/account.ts +0 -456
  59. package/src/account/repo.ts +0 -166
  60. package/src/account/schema.ts +0 -99
  61. package/src/account/url.ts +0 -8
  62. package/src/acp/README.md +0 -174
  63. package/src/acp/agent.ts +0 -1787
  64. package/src/acp/session.ts +0 -116
  65. package/src/acp/types.ts +0 -24
  66. package/src/actor/actor.sql.ts +0 -38
  67. package/src/actor/events.ts +0 -67
  68. package/src/actor/index.ts +0 -2
  69. package/src/actor/registry.ts +0 -412
  70. package/src/actor/return-header.ts +0 -24
  71. package/src/actor/schema.ts +0 -47
  72. package/src/actor/spawn-ref.ts +0 -16
  73. package/src/actor/spawn.ts +0 -756
  74. package/src/actor/turn.ts +0 -49
  75. package/src/actor/waiter.ts +0 -166
  76. package/src/agent/agent.ts +0 -574
  77. package/src/agent/config.ts +0 -5
  78. package/src/agent/generate.txt +0 -75
  79. package/src/agent/prompt/checkpoint-writer.txt +0 -167
  80. package/src/agent/prompt/compaction.txt +0 -9
  81. package/src/agent/prompt/distill.txt +0 -199
  82. package/src/agent/prompt/dream.txt +0 -155
  83. package/src/agent/prompt/explore.txt +0 -18
  84. package/src/agent/prompt/summary.txt +0 -11
  85. package/src/agent/prompt/title.txt +0 -44
  86. package/src/audio.d.ts +0 -9
  87. package/src/auth/index.ts +0 -97
  88. package/src/bus/bus-event.ts +0 -33
  89. package/src/bus/global.ts +0 -12
  90. package/src/bus/index.ts +0 -193
  91. package/src/cli/bootstrap.ts +0 -33
  92. package/src/cli/cmd/account.ts +0 -258
  93. package/src/cli/cmd/acp.ts +0 -70
  94. package/src/cli/cmd/agent.ts +0 -248
  95. package/src/cli/cmd/cmd.ts +0 -7
  96. package/src/cli/cmd/db.ts +0 -120
  97. package/src/cli/cmd/debug/agent.ts +0 -192
  98. package/src/cli/cmd/debug/config.ts +0 -17
  99. package/src/cli/cmd/debug/file.ts +0 -100
  100. package/src/cli/cmd/debug/index.ts +0 -48
  101. package/src/cli/cmd/debug/lsp.ts +0 -61
  102. package/src/cli/cmd/debug/ripgrep.ts +0 -105
  103. package/src/cli/cmd/debug/scrap.ts +0 -16
  104. package/src/cli/cmd/debug/skill.ts +0 -23
  105. package/src/cli/cmd/debug/snapshot.ts +0 -53
  106. package/src/cli/cmd/doctor.ts +0 -116
  107. package/src/cli/cmd/export.ts +0 -306
  108. package/src/cli/cmd/generate.ts +0 -50
  109. package/src/cli/cmd/github.ts +0 -1647
  110. package/src/cli/cmd/import.ts +0 -208
  111. package/src/cli/cmd/init.ts +0 -97
  112. package/src/cli/cmd/login.ts +0 -402
  113. package/src/cli/cmd/mcp.ts +0 -812
  114. package/src/cli/cmd/models.ts +0 -88
  115. package/src/cli/cmd/plug.ts +0 -233
  116. package/src/cli/cmd/pr.ts +0 -138
  117. package/src/cli/cmd/providers.ts +0 -713
  118. package/src/cli/cmd/run-completion.ts +0 -77
  119. package/src/cli/cmd/run.ts +0 -694
  120. package/src/cli/cmd/serve.ts +0 -30
  121. package/src/cli/cmd/session.ts +0 -181
  122. package/src/cli/cmd/stats.ts +0 -413
  123. package/src/cli/cmd/tui/app.tsx +0 -1181
  124. package/src/cli/cmd/tui/asset/TEN_VAD_LICENSE +0 -12
  125. package/src/cli/cmd/tui/asset/charge.wav +0 -0
  126. package/src/cli/cmd/tui/asset/pulse-a.wav +0 -0
  127. package/src/cli/cmd/tui/asset/pulse-b.wav +0 -0
  128. package/src/cli/cmd/tui/asset/pulse-c.wav +0 -0
  129. package/src/cli/cmd/tui/asset/ten_vad.wasm +0 -0
  130. package/src/cli/cmd/tui/asset/ten_vad_loader.js +0 -30
  131. package/src/cli/cmd/tui/attach.ts +0 -84
  132. package/src/cli/cmd/tui/component/background-image.tsx +0 -150
  133. package/src/cli/cmd/tui/component/banner-session-expired.tsx +0 -48
  134. package/src/cli/cmd/tui/component/bg-pulse.tsx +0 -130
  135. package/src/cli/cmd/tui/component/border.tsx +0 -21
  136. package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
  137. package/src/cli/cmd/tui/component/dialog-agreement.tsx +0 -111
  138. package/src/cli/cmd/tui/component/dialog-command.tsx +0 -219
  139. package/src/cli/cmd/tui/component/dialog-console-org.tsx +0 -103
  140. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +0 -157
  141. package/src/cli/cmd/tui/component/dialog-image-list.tsx +0 -111
  142. package/src/cli/cmd/tui/component/dialog-logo-design.tsx +0 -37
  143. package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
  144. package/src/cli/cmd/tui/component/dialog-model.tsx +0 -299
  145. package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -449
  146. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +0 -101
  147. package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -269
  148. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
  149. package/src/cli/cmd/tui/component/dialog-skill.tsx +0 -42
  150. package/src/cli/cmd/tui/component/dialog-sleepy-login.tsx +0 -288
  151. package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
  152. package/src/cli/cmd/tui/component/dialog-status.tsx +0 -170
  153. package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
  154. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
  155. package/src/cli/cmd/tui/component/dialog-token-plan.tsx +0 -77
  156. package/src/cli/cmd/tui/component/dialog-variant.tsx +0 -39
  157. package/src/cli/cmd/tui/component/dialog-workflows.tsx +0 -51
  158. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +0 -289
  159. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +0 -81
  160. package/src/cli/cmd/tui/component/dialog-worktree.tsx +0 -90
  161. package/src/cli/cmd/tui/component/error-component.tsx +0 -92
  162. package/src/cli/cmd/tui/component/logo.tsx +0 -961
  163. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +0 -14
  164. package/src/cli/cmd/tui/component/prompt/autocomplete-detect.ts +0 -34
  165. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -668
  166. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  167. package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -90
  168. package/src/cli/cmd/tui/component/prompt/history.tsx +0 -108
  169. package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1869
  170. package/src/cli/cmd/tui/component/prompt/offset.ts +0 -45
  171. package/src/cli/cmd/tui/component/prompt/part.ts +0 -36
  172. package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
  173. package/src/cli/cmd/tui/component/spinner.tsx +0 -24
  174. package/src/cli/cmd/tui/component/starry-background.tsx +0 -305
  175. package/src/cli/cmd/tui/component/startup-loading.tsx +0 -67
  176. package/src/cli/cmd/tui/component/task-item.tsx +0 -63
  177. package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
  178. package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
  179. package/src/cli/cmd/tui/component/workflow-tree.tsx +0 -177
  180. package/src/cli/cmd/tui/config/cwd.ts +0 -5
  181. package/src/cli/cmd/tui/config/tui-migrate.ts +0 -151
  182. package/src/cli/cmd/tui/config/tui-schema.ts +0 -38
  183. package/src/cli/cmd/tui/config/tui.ts +0 -219
  184. package/src/cli/cmd/tui/context/args.tsx +0 -16
  185. package/src/cli/cmd/tui/context/directory.ts +0 -15
  186. package/src/cli/cmd/tui/context/event.ts +0 -45
  187. package/src/cli/cmd/tui/context/exit.tsx +0 -65
  188. package/src/cli/cmd/tui/context/helper.tsx +0 -25
  189. package/src/cli/cmd/tui/context/keybind.tsx +0 -105
  190. package/src/cli/cmd/tui/context/kv.tsx +0 -86
  191. package/src/cli/cmd/tui/context/language.tsx +0 -91
  192. package/src/cli/cmd/tui/context/local.tsx +0 -469
  193. package/src/cli/cmd/tui/context/plugin-keybinds.ts +0 -41
  194. package/src/cli/cmd/tui/context/project.tsx +0 -109
  195. package/src/cli/cmd/tui/context/prompt.tsx +0 -18
  196. package/src/cli/cmd/tui/context/route.tsx +0 -68
  197. package/src/cli/cmd/tui/context/sdk.tsx +0 -150
  198. package/src/cli/cmd/tui/context/sync.tsx +0 -884
  199. package/src/cli/cmd/tui/context/theme/aura.json +0 -69
  200. package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
  201. package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
  202. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -230
  203. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -230
  204. package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
  205. package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -225
  206. package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
  207. package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
  208. package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
  209. package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
  210. package/src/cli/cmd/tui/context/theme/github.json +0 -233
  211. package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -242
  212. package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
  213. package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -234
  214. package/src/cli/cmd/tui/context/theme/material.json +0 -235
  215. package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
  216. package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
  217. package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
  218. package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
  219. package/src/cli/cmd/tui/context/theme/nord.json +0 -223
  220. package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
  221. package/src/cli/cmd/tui/context/theme/orng.json +0 -249
  222. package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
  223. package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
  224. package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
  225. package/src/cli/cmd/tui/context/theme/sleepycode.json +0 -245
  226. package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
  227. package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
  228. package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
  229. package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
  230. package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
  231. package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
  232. package/src/cli/cmd/tui/context/theme.tsx +0 -1298
  233. package/src/cli/cmd/tui/context/thinking.ts +0 -48
  234. package/src/cli/cmd/tui/context/tui-config.tsx +0 -9
  235. package/src/cli/cmd/tui/event.ts +0 -62
  236. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +0 -93
  237. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +0 -193
  238. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +0 -54
  239. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +0 -267
  240. package/src/cli/cmd/tui/feature-plugins/sidebar/cwd.tsx +0 -45
  241. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +0 -62
  242. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +0 -93
  243. package/src/cli/cmd/tui/feature-plugins/sidebar/goal.tsx +0 -84
  244. package/src/cli/cmd/tui/feature-plugins/sidebar/instructions.tsx +0 -54
  245. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +0 -66
  246. package/src/cli/cmd/tui/feature-plugins/sidebar/mascot.tsx +0 -69
  247. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +0 -98
  248. package/src/cli/cmd/tui/feature-plugins/sidebar/task.tsx +0 -95
  249. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +0 -51
  250. package/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts +0 -31
  251. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +0 -274
  252. package/src/cli/cmd/tui/i18n/en.ts +0 -465
  253. package/src/cli/cmd/tui/i18n/es.ts +0 -508
  254. package/src/cli/cmd/tui/i18n/fr.ts +0 -515
  255. package/src/cli/cmd/tui/i18n/ja.ts +0 -463
  256. package/src/cli/cmd/tui/i18n/locales.ts +0 -82
  257. package/src/cli/cmd/tui/i18n/ru.ts +0 -527
  258. package/src/cli/cmd/tui/i18n/slash-command.ts +0 -11
  259. package/src/cli/cmd/tui/i18n/zh.ts +0 -454
  260. package/src/cli/cmd/tui/i18n/zht.ts +0 -430
  261. package/src/cli/cmd/tui/layer.ts +0 -6
  262. package/src/cli/cmd/tui/plugin/api.tsx +0 -402
  263. package/src/cli/cmd/tui/plugin/index.ts +0 -3
  264. package/src/cli/cmd/tui/plugin/internal.ts +0 -37
  265. package/src/cli/cmd/tui/plugin/runtime.ts +0 -1057
  266. package/src/cli/cmd/tui/plugin/slots.tsx +0 -60
  267. package/src/cli/cmd/tui/routes/home.tsx +0 -165
  268. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -76
  269. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -116
  270. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -47
  271. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
  272. package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
  273. package/src/cli/cmd/tui/routes/session/index.tsx +0 -3073
  274. package/src/cli/cmd/tui/routes/session/permission.tsx +0 -697
  275. package/src/cli/cmd/tui/routes/session/question.tsx +0 -488
  276. package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -97
  277. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +0 -143
  278. package/src/cli/cmd/tui/thread.ts +0 -323
  279. package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -61
  280. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -95
  281. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -223
  282. package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -42
  283. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -123
  284. package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -467
  285. package/src/cli/cmd/tui/ui/dialog.tsx +0 -207
  286. package/src/cli/cmd/tui/ui/link.tsx +0 -28
  287. package/src/cli/cmd/tui/ui/spinner.ts +0 -378
  288. package/src/cli/cmd/tui/ui/toast.tsx +0 -102
  289. package/src/cli/cmd/tui/util/clipboard.ts +0 -203
  290. package/src/cli/cmd/tui/util/editor.ts +0 -36
  291. package/src/cli/cmd/tui/util/image-protocol.ts +0 -35
  292. package/src/cli/cmd/tui/util/index.ts +0 -6
  293. package/src/cli/cmd/tui/util/model.ts +0 -23
  294. package/src/cli/cmd/tui/util/pinyin.ts +0 -20
  295. package/src/cli/cmd/tui/util/provider-origin.ts +0 -7
  296. package/src/cli/cmd/tui/util/revert-diff.ts +0 -18
  297. package/src/cli/cmd/tui/util/scroll.ts +0 -23
  298. package/src/cli/cmd/tui/util/selection.ts +0 -23
  299. package/src/cli/cmd/tui/util/signal.ts +0 -41
  300. package/src/cli/cmd/tui/util/sound.ts +0 -154
  301. package/src/cli/cmd/tui/util/system-locale.ts +0 -209
  302. package/src/cli/cmd/tui/util/terminal.ts +0 -110
  303. package/src/cli/cmd/tui/util/transcript.ts +0 -112
  304. package/src/cli/cmd/tui/util/vad.ts +0 -229
  305. package/src/cli/cmd/tui/util/voice.ts +0 -450
  306. package/src/cli/cmd/tui/win32.ts +0 -130
  307. package/src/cli/cmd/tui/worker.ts +0 -104
  308. package/src/cli/cmd/uninstall.ts +0 -351
  309. package/src/cli/cmd/upgrade.ts +0 -79
  310. package/src/cli/cmd/web.ts +0 -96
  311. package/src/cli/effect/prompt.ts +0 -25
  312. package/src/cli/error.ts +0 -82
  313. package/src/cli/heap.ts +0 -59
  314. package/src/cli/i18n.ts +0 -15
  315. package/src/cli/logo.ts +0 -53
  316. package/src/cli/network.ts +0 -68
  317. package/src/cli/ui.ts +0 -133
  318. package/src/cli/upgrade.ts +0 -41
  319. package/src/command/index.ts +0 -276
  320. package/src/command/template/initialize.txt +0 -66
  321. package/src/command/template/review.txt +0 -101
  322. package/src/config/agent.ts +0 -197
  323. package/src/config/command.ts +0 -69
  324. package/src/config/compose.ts +0 -26
  325. package/src/config/config.ts +0 -1047
  326. package/src/config/console-state.ts +0 -16
  327. package/src/config/entry-name.ts +0 -16
  328. package/src/config/error.ts +0 -21
  329. package/src/config/formatter.ts +0 -17
  330. package/src/config/history.ts +0 -21
  331. package/src/config/index.ts +0 -17
  332. package/src/config/keybinds.ts +0 -127
  333. package/src/config/layout.ts +0 -10
  334. package/src/config/lsp.ts +0 -45
  335. package/src/config/managed.ts +0 -70
  336. package/src/config/markdown.ts +0 -97
  337. package/src/config/mcp.ts +0 -172
  338. package/src/config/model-id.ts +0 -14
  339. package/src/config/parse.ts +0 -44
  340. package/src/config/paths.ts +0 -85
  341. package/src/config/permission.ts +0 -76
  342. package/src/config/plugin.ts +0 -88
  343. package/src/config/provider.ts +0 -118
  344. package/src/config/server.ts +0 -20
  345. package/src/config/skills.ts +0 -16
  346. package/src/config/variable.ts +0 -90
  347. package/src/control-plane/adaptors/index.ts +0 -52
  348. package/src/control-plane/adaptors/worktree.ts +0 -47
  349. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  350. package/src/control-plane/schema.ts +0 -19
  351. package/src/control-plane/sse.ts +0 -66
  352. package/src/control-plane/types.ts +0 -34
  353. package/src/control-plane/util.ts +0 -37
  354. package/src/control-plane/workspace-context.ts +0 -26
  355. package/src/control-plane/workspace.sql.ts +0 -17
  356. package/src/control-plane/workspace.ts +0 -615
  357. package/src/effect/app-runtime.ts +0 -146
  358. package/src/effect/bootstrap-runtime.ts +0 -33
  359. package/src/effect/bridge.ts +0 -48
  360. package/src/effect/cross-spawn-spawner.ts +0 -514
  361. package/src/effect/index.ts +0 -5
  362. package/src/effect/instance-ref.ts +0 -11
  363. package/src/effect/instance-registry.ts +0 -12
  364. package/src/effect/instance-state.ts +0 -81
  365. package/src/effect/logger.ts +0 -73
  366. package/src/effect/memo-map.ts +0 -3
  367. package/src/effect/observability.ts +0 -107
  368. package/src/effect/run-service.ts +0 -52
  369. package/src/effect/runner.ts +0 -210
  370. package/src/effect/runtime.ts +0 -19
  371. package/src/env/index.ts +0 -37
  372. package/src/file/ignore.ts +0 -81
  373. package/src/file/index.ts +0 -664
  374. package/src/file/protected.ts +0 -59
  375. package/src/file/ripgrep.ts +0 -485
  376. package/src/file/watcher.ts +0 -163
  377. package/src/flag/flag.ts +0 -185
  378. package/src/format/formatter.ts +0 -403
  379. package/src/format/index.ts +0 -203
  380. package/src/git/index.ts +0 -260
  381. package/src/global/index.ts +0 -54
  382. package/src/history/backfill.ts +0 -162
  383. package/src/history/extract.ts +0 -67
  384. package/src/history/fts-query.ts +0 -15
  385. package/src/history/fts.sql.ts +0 -20
  386. package/src/history/index.ts +0 -10
  387. package/src/history/resolve.ts +0 -65
  388. package/src/history/service.ts +0 -258
  389. package/src/history/writer.ts +0 -112
  390. package/src/id/id.ts +0 -87
  391. package/src/ide/index.ts +0 -73
  392. package/src/inbox/inbox-ref.ts +0 -38
  393. package/src/inbox/inbox.sql.ts +0 -26
  394. package/src/inbox/inbox.ts +0 -223
  395. package/src/inbox/index.ts +0 -3
  396. package/src/inbox/render.ts +0 -40
  397. package/src/index.ts +0 -268
  398. package/src/installation/index.ts +0 -362
  399. package/src/installation/version.ts +0 -8
  400. package/src/lsp/client.ts +0 -249
  401. package/src/lsp/diagnostic.ts +0 -29
  402. package/src/lsp/index.ts +0 -3
  403. package/src/lsp/language.ts +0 -120
  404. package/src/lsp/launch.ts +0 -21
  405. package/src/lsp/lsp.ts +0 -519
  406. package/src/lsp/server.ts +0 -1956
  407. package/src/mcp/auth.ts +0 -144
  408. package/src/mcp/index.ts +0 -939
  409. package/src/mcp/oauth-callback.ts +0 -236
  410. package/src/mcp/oauth-provider.ts +0 -214
  411. package/src/memory/fts-query.ts +0 -37
  412. package/src/memory/fts.sql.ts +0 -19
  413. package/src/memory/index.ts +0 -1
  414. package/src/memory/paths.ts +0 -116
  415. package/src/memory/reconcile.ts +0 -144
  416. package/src/memory/service.ts +0 -144
  417. package/src/metrics/client.ts +0 -40
  418. package/src/metrics/event.ts +0 -43
  419. package/src/metrics/index.ts +0 -5
  420. package/src/metrics/installation.ts +0 -18
  421. package/src/metrics/subscriber.ts +0 -58
  422. package/src/metrics/util.ts +0 -9
  423. package/src/node.ts +0 -6
  424. package/src/npm/config.ts +0 -0
  425. package/src/npm/index.ts +0 -293
  426. package/src/npmcli-config.d.ts +0 -43
  427. package/src/patch/index.ts +0 -680
  428. package/src/permission/arity.ts +0 -163
  429. package/src/permission/evaluate.ts +0 -15
  430. package/src/permission/index.ts +0 -382
  431. package/src/permission/schema.ts +0 -17
  432. package/src/plugin/checkpoint-splitover.ts +0 -60
  433. package/src/plugin/cloudflare.ts +0 -76
  434. package/src/plugin/codex.ts +0 -611
  435. package/src/plugin/github-copilot/copilot.ts +0 -368
  436. package/src/plugin/github-copilot/models.ts +0 -153
  437. package/src/plugin/index.ts +0 -637
  438. package/src/plugin/install.ts +0 -439
  439. package/src/plugin/loader.ts +0 -216
  440. package/src/plugin/matcher.ts +0 -33
  441. package/src/plugin/meta.ts +0 -188
  442. package/src/plugin/shared.ts +0 -323
  443. package/src/plugin/sleepy.ts +0 -244
  444. package/src/plugin/subagent-progress-checker.ts +0 -147
  445. package/src/project/bootstrap.ts +0 -59
  446. package/src/project/index.ts +0 -2
  447. package/src/project/instance.ts +0 -215
  448. package/src/project/project-id.ts +0 -48
  449. package/src/project/project.sql.ts +0 -16
  450. package/src/project/project.ts +0 -522
  451. package/src/project/schema.ts +0 -15
  452. package/src/project/vcs.ts +0 -227
  453. package/src/project/workspace-trust.ts +0 -67
  454. package/src/provider/auth.ts +0 -234
  455. package/src/provider/error.ts +0 -216
  456. package/src/provider/index.ts +0 -5
  457. package/src/provider/models-snapshot.d.ts +0 -2
  458. package/src/provider/models-snapshot.js +0 -3
  459. package/src/provider/models.ts +0 -180
  460. package/src/provider/provider.ts +0 -2098
  461. package/src/provider/schema.ts +0 -36
  462. package/src/provider/sdk/copilot/README.md +0 -5
  463. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
  464. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
  465. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
  466. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
  467. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -815
  468. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
  469. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
  470. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
  471. package/src/provider/sdk/copilot/copilot-provider.ts +0 -100
  472. package/src/provider/sdk/copilot/index.ts +0 -2
  473. package/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
  474. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
  475. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
  476. package/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
  477. package/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
  478. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
  479. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1770
  480. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
  481. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
  482. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
  483. package/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
  484. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
  485. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
  486. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
  487. package/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
  488. package/src/provider/session-check.ts +0 -163
  489. package/src/provider/transform.ts +0 -1376
  490. package/src/pty/index.ts +0 -364
  491. package/src/pty/pty.bun.ts +0 -26
  492. package/src/pty/pty.node.ts +0 -27
  493. package/src/pty/pty.ts +0 -25
  494. package/src/pty/schema.ts +0 -17
  495. package/src/question/index.ts +0 -252
  496. package/src/question/schema.ts +0 -17
  497. package/src/server/adapter.bun.ts +0 -40
  498. package/src/server/adapter.node.ts +0 -66
  499. package/src/server/adapter.ts +0 -21
  500. package/src/server/auth.ts +0 -16
  501. package/src/server/error.ts +0 -53
  502. package/src/server/event.ts +0 -7
  503. package/src/server/fence.ts +0 -81
  504. package/src/server/mdns.ts +0 -60
  505. package/src/server/middleware.ts +0 -94
  506. package/src/server/projectors.ts +0 -28
  507. package/src/server/proxy.ts +0 -171
  508. package/src/server/pty-ticket.ts +0 -42
  509. package/src/server/rate-limit.ts +0 -38
  510. package/src/server/routes/control/index.ts +0 -160
  511. package/src/server/routes/control/workspace.ts +0 -203
  512. package/src/server/routes/global.ts +0 -367
  513. package/src/server/routes/instance/bash-interactive.ts +0 -82
  514. package/src/server/routes/instance/config.ts +0 -89
  515. package/src/server/routes/instance/event.ts +0 -108
  516. package/src/server/routes/instance/experimental.ts +0 -408
  517. package/src/server/routes/instance/file.ts +0 -190
  518. package/src/server/routes/instance/httpapi/config.ts +0 -51
  519. package/src/server/routes/instance/httpapi/permission.ts +0 -72
  520. package/src/server/routes/instance/httpapi/project.ts +0 -62
  521. package/src/server/routes/instance/httpapi/provider.ts +0 -142
  522. package/src/server/routes/instance/httpapi/question.ts +0 -121
  523. package/src/server/routes/instance/httpapi/server.ts +0 -153
  524. package/src/server/routes/instance/index.ts +0 -301
  525. package/src/server/routes/instance/mcp.ts +0 -260
  526. package/src/server/routes/instance/middleware.ts +0 -44
  527. package/src/server/routes/instance/permission.ts +0 -73
  528. package/src/server/routes/instance/project.ts +0 -122
  529. package/src/server/routes/instance/provider.ts +0 -158
  530. package/src/server/routes/instance/pty.ts +0 -302
  531. package/src/server/routes/instance/question.ts +0 -162
  532. package/src/server/routes/instance/session.ts +0 -1328
  533. package/src/server/routes/instance/sync.ts +0 -143
  534. package/src/server/routes/instance/trace.ts +0 -59
  535. package/src/server/routes/instance/tui.ts +0 -384
  536. package/src/server/routes/instance/workflows.ts +0 -142
  537. package/src/server/routes/ui.ts +0 -37
  538. package/src/server/server.ts +0 -146
  539. package/src/server/workspace.ts +0 -122
  540. package/src/session/auto-dream.ts +0 -123
  541. package/src/session/boundary.ts +0 -77
  542. package/src/session/budgeted-read.ts +0 -118
  543. package/src/session/checkpoint-align.ts +0 -29
  544. package/src/session/checkpoint-context.ts +0 -36
  545. package/src/session/checkpoint-paths.ts +0 -86
  546. package/src/session/checkpoint-progress-reconcile.ts +0 -111
  547. package/src/session/checkpoint-retry.ts +0 -192
  548. package/src/session/checkpoint-templates.ts +0 -114
  549. package/src/session/checkpoint-validator.ts +0 -259
  550. package/src/session/checkpoint.ts +0 -1560
  551. package/src/session/classify.ts +0 -117
  552. package/src/session/claude-import.ts +0 -381
  553. package/src/session/codex-import.ts +0 -416
  554. package/src/session/compaction.ts +0 -545
  555. package/src/session/external-import.sql.ts +0 -18
  556. package/src/session/external-import.ts +0 -136
  557. package/src/session/goal.ts +0 -232
  558. package/src/session/index.ts +0 -1
  559. package/src/session/instruction.ts +0 -276
  560. package/src/session/last-message-info.ts +0 -32
  561. package/src/session/llm-request-prefix.ts +0 -82
  562. package/src/session/llm.ts +0 -737
  563. package/src/session/max-mode.ts +0 -410
  564. package/src/session/message-v2.ts +0 -1138
  565. package/src/session/message.ts +0 -191
  566. package/src/session/opencode-import.ts +0 -281
  567. package/src/session/overflow.ts +0 -53
  568. package/src/session/prefix-capture-ref.ts +0 -48
  569. package/src/session/processor.ts +0 -983
  570. package/src/session/projectors.ts +0 -137
  571. package/src/session/prompt/anthropic.txt +0 -154
  572. package/src/session/prompt/beast.txt +0 -155
  573. package/src/session/prompt/build-switch.txt +0 -5
  574. package/src/session/prompt/codex.txt +0 -79
  575. package/src/session/prompt/compose.txt +0 -119
  576. package/src/session/prompt/copilot-gpt-5.txt +0 -143
  577. package/src/session/prompt/deepseek.txt +0 -131
  578. package/src/session/prompt/default.old.txt +0 -151
  579. package/src/session/prompt/default.txt +0 -172
  580. package/src/session/prompt/gemini.txt +0 -155
  581. package/src/session/prompt/glm.txt +0 -51
  582. package/src/session/prompt/gpt.txt +0 -107
  583. package/src/session/prompt/kimi.txt +0 -95
  584. package/src/session/prompt/max-steps.txt +0 -16
  585. package/src/session/prompt/minimax.txt +0 -140
  586. package/src/session/prompt/text-loop-recovery.ts +0 -40
  587. package/src/session/prompt/text-ngram-detection.ts +0 -87
  588. package/src/session/prompt/trinity.txt +0 -97
  589. package/src/session/prompt.ts +0 -3878
  590. package/src/session/prune.ts +0 -481
  591. package/src/session/retry.ts +0 -178
  592. package/src/session/revert.ts +0 -161
  593. package/src/session/run-state.ts +0 -135
  594. package/src/session/schema.ts +0 -36
  595. package/src/session/session.sql.ts +0 -110
  596. package/src/session/session.ts +0 -908
  597. package/src/session/status.ts +0 -89
  598. package/src/session/summary.ts +0 -163
  599. package/src/session/system.ts +0 -96
  600. package/src/session/todo.ts +0 -77
  601. package/src/session/trajectory.ts +0 -98
  602. package/src/share/index.ts +0 -2
  603. package/src/share/session.ts +0 -57
  604. package/src/share/share-next.ts +0 -381
  605. package/src/share/share.sql.ts +0 -13
  606. package/src/shell/shell.ts +0 -124
  607. package/src/skill/builtin/.bundle/self-extend/SKILL.md +0 -131
  608. package/src/skill/builtin/.bundle/self-extend/reference/hook-api.md +0 -242
  609. package/src/skill/builtin/.bundle/self-extend/reference/skill-api.md +0 -114
  610. package/src/skill/builtin/.bundle/self-extend/reference/tool-api.md +0 -115
  611. package/src/skill/builtin/.bundle/self-extend/reference/tui-api.md +0 -258
  612. package/src/skill/builtin/bundle.macro.ts +0 -30
  613. package/src/skill/builtin/extract.ts +0 -41
  614. package/src/skill/compose/.bundle/ask/SKILL.md +0 -58
  615. package/src/skill/compose/.bundle/brainstorm/SKILL.md +0 -200
  616. package/src/skill/compose/.bundle/brainstorm/scripts/frame-template.html +0 -214
  617. package/src/skill/compose/.bundle/brainstorm/scripts/helper.js +0 -88
  618. package/src/skill/compose/.bundle/brainstorm/scripts/server.cjs +0 -354
  619. package/src/skill/compose/.bundle/brainstorm/scripts/start-server.sh +0 -148
  620. package/src/skill/compose/.bundle/brainstorm/scripts/stop-server.sh +0 -56
  621. package/src/skill/compose/.bundle/brainstorm/spec-document-reviewer-prompt.md +0 -50
  622. package/src/skill/compose/.bundle/brainstorm/visual-companion.md +0 -258
  623. package/src/skill/compose/.bundle/debug/CREATION-LOG.md +0 -119
  624. package/src/skill/compose/.bundle/debug/SKILL.md +0 -297
  625. package/src/skill/compose/.bundle/debug/condition-based-waiting-example.ts +0 -158
  626. package/src/skill/compose/.bundle/debug/condition-based-waiting.md +0 -106
  627. package/src/skill/compose/.bundle/debug/defense-in-depth.md +0 -122
  628. package/src/skill/compose/.bundle/debug/find-polluter.sh +0 -63
  629. package/src/skill/compose/.bundle/debug/root-cause-tracing.md +0 -144
  630. package/src/skill/compose/.bundle/debug/test-academic.md +0 -14
  631. package/src/skill/compose/.bundle/debug/test-pressure-1.md +0 -58
  632. package/src/skill/compose/.bundle/debug/test-pressure-2.md +0 -68
  633. package/src/skill/compose/.bundle/debug/test-pressure-3.md +0 -69
  634. package/src/skill/compose/.bundle/execute/SKILL.md +0 -71
  635. package/src/skill/compose/.bundle/feedback/SKILL.md +0 -214
  636. package/src/skill/compose/.bundle/merge/SKILL.md +0 -252
  637. package/src/skill/compose/.bundle/new-skill/SKILL.md +0 -211
  638. package/src/skill/compose/.bundle/parallel/SKILL.md +0 -168
  639. package/src/skill/compose/.bundle/plan/SKILL.md +0 -183
  640. package/src/skill/compose/.bundle/plan/plan-document-reviewer-prompt.md +0 -50
  641. package/src/skill/compose/.bundle/report/SKILL.md +0 -180
  642. package/src/skill/compose/.bundle/review/SKILL.md +0 -104
  643. package/src/skill/compose/.bundle/review/code-reviewer.md +0 -178
  644. package/src/skill/compose/.bundle/subagent/SKILL.md +0 -325
  645. package/src/skill/compose/.bundle/subagent/code-quality-reviewer-prompt.md +0 -26
  646. package/src/skill/compose/.bundle/subagent/implementer-prompt.md +0 -128
  647. package/src/skill/compose/.bundle/subagent/spec-reviewer-prompt.md +0 -118
  648. package/src/skill/compose/.bundle/tdd/SKILL.md +0 -360
  649. package/src/skill/compose/.bundle/tdd/testing-anti-patterns.md +0 -299
  650. package/src/skill/compose/.bundle/verify/SKILL.md +0 -140
  651. package/src/skill/compose/.bundle/worktree/SKILL.md +0 -234
  652. package/src/skill/compose/LICENSE-karpathy +0 -28
  653. package/src/skill/compose/bundle.macro.ts +0 -30
  654. package/src/skill/compose/extract.ts +0 -85
  655. package/src/skill/discovery.ts +0 -114
  656. package/src/skill/index.ts +0 -328
  657. package/src/snapshot/index.ts +0 -777
  658. package/src/sql.d.ts +0 -4
  659. package/src/storage/db.bun.ts +0 -8
  660. package/src/storage/db.node.ts +0 -8
  661. package/src/storage/db.ts +0 -172
  662. package/src/storage/index.ts +0 -26
  663. package/src/storage/json-migration.ts +0 -426
  664. package/src/storage/read-sqlite.bun.ts +0 -11
  665. package/src/storage/read-sqlite.node.ts +0 -13
  666. package/src/storage/read-sqlite.ts +0 -10
  667. package/src/storage/schema.sql.ts +0 -10
  668. package/src/storage/schema.ts +0 -7
  669. package/src/storage/storage.ts +0 -331
  670. package/src/sync/README.md +0 -179
  671. package/src/sync/event.sql.ts +0 -16
  672. package/src/sync/index.ts +0 -278
  673. package/src/sync/schema.ts +0 -14
  674. package/src/task/events.ts +0 -28
  675. package/src/task/gate-state.ts +0 -54
  676. package/src/task/gate.ts +0 -116
  677. package/src/task/index.ts +0 -1
  678. package/src/task/registry.ts +0 -394
  679. package/src/task/schema.ts +0 -43
  680. package/src/task/task.sql.ts +0 -50
  681. package/src/team/events.ts +0 -22
  682. package/src/team/index.ts +0 -113
  683. package/src/team/schema.ts +0 -31
  684. package/src/temporary.ts +0 -33
  685. package/src/tool/actor.shell.txt +0 -72
  686. package/src/tool/actor.ts +0 -804
  687. package/src/tool/actor.txt +0 -103
  688. package/src/tool/apply_patch.ts +0 -308
  689. package/src/tool/apply_patch.txt +0 -33
  690. package/src/tool/bash-interactive.ts +0 -183
  691. package/src/tool/bash.ts +0 -704
  692. package/src/tool/bash.txt +0 -123
  693. package/src/tool/change-directory.ts +0 -91
  694. package/src/tool/codesearch.ts +0 -63
  695. package/src/tool/codesearch.txt +0 -12
  696. package/src/tool/edit.ts +0 -693
  697. package/src/tool/edit.txt +0 -10
  698. package/src/tool/external-directory.ts +0 -132
  699. package/src/tool/glob.ts +0 -100
  700. package/src/tool/glob.txt +0 -6
  701. package/src/tool/grep.ts +0 -145
  702. package/src/tool/grep.txt +0 -8
  703. package/src/tool/history.ts +0 -146
  704. package/src/tool/history.txt +0 -17
  705. package/src/tool/index.ts +0 -4
  706. package/src/tool/invalid.ts +0 -20
  707. package/src/tool/invocation-style.ts +0 -17
  708. package/src/tool/lsp.ts +0 -91
  709. package/src/tool/lsp.txt +0 -19
  710. package/src/tool/mcp-exa.ts +0 -78
  711. package/src/tool/memory-path-guard.ts +0 -162
  712. package/src/tool/memory.ts +0 -81
  713. package/src/tool/memory.txt +0 -69
  714. package/src/tool/multiedit.ts +0 -54
  715. package/src/tool/multiedit.txt +0 -41
  716. package/src/tool/notebook-edit.ts +0 -225
  717. package/src/tool/notebook-edit.txt +0 -10
  718. package/src/tool/plan-enter.txt +0 -16
  719. package/src/tool/plan-exit.txt +0 -14
  720. package/src/tool/plan.ts +0 -179
  721. package/src/tool/question.ts +0 -67
  722. package/src/tool/question.txt +0 -10
  723. package/src/tool/read-state.ts +0 -44
  724. package/src/tool/read.ts +0 -327
  725. package/src/tool/read.txt +0 -14
  726. package/src/tool/recoverable.ts +0 -35
  727. package/src/tool/registry.ts +0 -429
  728. package/src/tool/schema.ts +0 -17
  729. package/src/tool/session-cwd.ts +0 -35
  730. package/src/tool/shell-tokenize.ts +0 -374
  731. package/src/tool/shell-wrap.ts +0 -235
  732. package/src/tool/skill.ts +0 -76
  733. package/src/tool/skill.txt +0 -5
  734. package/src/tool/task.shell.txt +0 -57
  735. package/src/tool/task.ts +0 -456
  736. package/src/tool/task.txt +0 -56
  737. package/src/tool/tool.ts +0 -166
  738. package/src/tool/truncate.ts +0 -201
  739. package/src/tool/truncation-dir.ts +0 -4
  740. package/src/tool/webfetch.ts +0 -208
  741. package/src/tool/webfetch.txt +0 -13
  742. package/src/tool/websearch/index.ts +0 -104
  743. package/src/tool/websearch/sleepy.ts +0 -118
  744. package/src/tool/websearch/websearch.txt +0 -14
  745. package/src/tool/workflow.ts +0 -357
  746. package/src/tool/workflow.txt +0 -25
  747. package/src/tool/write.ts +0 -88
  748. package/src/tool/write.txt +0 -10
  749. package/src/util/abort.ts +0 -35
  750. package/src/util/archive.ts +0 -15
  751. package/src/util/color.ts +0 -17
  752. package/src/util/data-url.ts +0 -9
  753. package/src/util/defer.ts +0 -10
  754. package/src/util/effect-http-client.ts +0 -11
  755. package/src/util/effect-zod.ts +0 -367
  756. package/src/util/env-info.ts +0 -62
  757. package/src/util/error.ts +0 -78
  758. package/src/util/filesystem.ts +0 -243
  759. package/src/util/fn.ts +0 -21
  760. package/src/util/format.ts +0 -20
  761. package/src/util/iife.ts +0 -3
  762. package/src/util/index.ts +0 -14
  763. package/src/util/keybind.ts +0 -101
  764. package/src/util/lazy.ts +0 -18
  765. package/src/util/local-context.ts +0 -23
  766. package/src/util/locale.ts +0 -79
  767. package/src/util/lock.ts +0 -96
  768. package/src/util/log.ts +0 -234
  769. package/src/util/media.ts +0 -26
  770. package/src/util/network.ts +0 -9
  771. package/src/util/process.ts +0 -174
  772. package/src/util/provider-priority.ts +0 -48
  773. package/src/util/queue.ts +0 -60
  774. package/src/util/record.ts +0 -3
  775. package/src/util/rpc.ts +0 -64
  776. package/src/util/schema.ts +0 -53
  777. package/src/util/scrap.ts +0 -10
  778. package/src/util/signal.ts +0 -12
  779. package/src/util/sleepy-process.ts +0 -24
  780. package/src/util/ssrf.ts +0 -116
  781. package/src/util/timeout.ts +0 -14
  782. package/src/util/token.ts +0 -5
  783. package/src/util/tool-compat.ts +0 -144
  784. package/src/util/update-schema.ts +0 -13
  785. package/src/util/which.ts +0 -14
  786. package/src/util/wildcard.ts +0 -57
  787. package/src/workflow/builtin/compose.js +0 -749
  788. package/src/workflow/builtin/deep-research.js +0 -398
  789. package/src/workflow/builtin.ts +0 -59
  790. package/src/workflow/events.ts +0 -72
  791. package/src/workflow/meta.ts +0 -335
  792. package/src/workflow/persistence.ts +0 -312
  793. package/src/workflow/resolve.ts +0 -45
  794. package/src/workflow/runtime-ref.ts +0 -18
  795. package/src/workflow/runtime.ts +0 -1447
  796. package/src/workflow/sandbox.ts +0 -286
  797. package/src/workflow/workflow.sql.ts +0 -31
  798. package/src/workflow/workspace.ts +0 -69
  799. package/src/worktree/index.ts +0 -629
@@ -1,1447 +0,0 @@
1
- import { Context, Deferred, Effect, Exit, Fiber, Layer, Scope } from "effect"
2
- import os from "node:os"
3
- import { createHash } from "node:crypto"
4
- import { spawnRef } from "@/actor/spawn-ref"
5
- import { workflowRef } from "./runtime-ref"
6
- import { Config } from "@/config"
7
- import { EffectBridge } from "@/effect"
8
- import { Bus } from "@/bus"
9
- import { Inbox } from "@/inbox"
10
- import { Worktree } from "@/worktree"
11
- import { Provider } from "@/provider"
12
- import { InstanceRef } from "@/effect/instance-ref"
13
- import { Instance } from "@/project/instance"
14
- import { Identifier } from "@/id/id"
15
- import type { SessionID } from "@/session/schema"
16
- import type { ProviderID, ModelID } from "@/provider/schema"
17
- import { parseMeta } from "./meta"
18
- import { evalScript, type HostFn } from "./sandbox"
19
- import { makeFileHooks, resolveInWorkspace } from "./workspace"
20
- import { isInlineScript, resolveWorkflowScript } from "./resolve"
21
- import { WorkflowAgentFailed, WorkflowChildFailed, WorkflowFinished, WorkflowLog, WorkflowPhase, WorkflowStarted } from "./events"
22
- import { WorkflowPersistence, journalKeyBase } from "./persistence"
23
- import type { RunSummary } from "./persistence"
24
- import { Log, Lock } from "@/util"
25
-
26
- const log = Log.create({ service: "workflow.runtime" })
27
-
28
- /** Default wall-clock budget for a whole workflow script (12h research default). */
29
- const SCRIPT_DEADLINE_MS = 12 * 60 * 60 * 1000
30
- /** Unique sentinel for the per-agent timeout race: a timeout winner can never
31
- * collide with an agent deliverable (those are object | string | null). */
32
- const STRAGGLER_TIMEOUT = Symbol("straggler-timeout")
33
- /** Hard ceiling on total agents a single run may spawn (lifecycle cap). */
34
- const MAX_LIFECYCLE_AGENTS = 1000
35
- /** Default soft cap on concurrent agents when the caller does not specify one. */
36
- const DEFAULT_MAX_CONCURRENT = 16
37
- /** Marker prefix on errors from STRUCTURAL workflow faults (cycle, over-depth,
38
- * unknown name) — workflow-wiring bugs that must fail the whole tree loud rather
39
- * than degrade to the never-throw null that a child's RUNTIME failure yields. The
40
- * workflow() hook re-propagates any child outcome whose error carries this marker,
41
- * so the fault surfaces at the root run the user launched. */
42
- const WORKFLOW_STRUCTURAL_ERROR = "WorkflowStructuralError"
43
-
44
- type RunStatus = "running" | "completed" | "failed" | "cancelled"
45
-
46
- export type RunOutcome =
47
- | { status: "completed"; result: unknown }
48
- | { status: "failed"; error: string }
49
- | { status: "cancelled" }
50
-
51
- /** One ordered transcript line. The runtime appends these synchronously from the
52
- * guest's phase()/log() hooks (in-process QuickJS, same thread), so the array is
53
- * the authoritative program-order record — no bus delivery race, no cross-event
54
- * reordering. Consumers (the workflow tool's sync path) read it instead of
55
- * subscribing to WorkflowPhase/WorkflowLog. */
56
- export type WorkflowTranscriptEntry = { kind: "phase" | "log"; text: string }
57
-
58
- // Observability-only structure tree for one run, recorded in the agent()/phase()/
59
- // workflow() host hooks. Each agent/workflow node is attributed to the phase
60
- // current at call time (known synchronously inside the hook — no timing guess).
61
- // This NEVER touches journal keys / occ counts / resume: adding or removing it
62
- // changes no run outcome. parallel/pipeline batch grouping is intentionally not
63
- // recorded (pure-guest helpers, no AsyncLocalStorage in QuickJS) — agents are
64
- // siblings under their phase.
65
- export type WorkflowNode =
66
- | { type: "phase"; id: string; title: string }
67
- | {
68
- type: "agent"
69
- id: string
70
- phaseId?: string
71
- label?: string
72
- agentType: string
73
- /** The prompt the guest passed to agent() — the call's primary parameter. */
74
- prompt: string
75
- /** Resolved-ref model the call requested (undefined = run default). */
76
- model?: string
77
- /** Tool allowlist the call passed, if any. */
78
- tools?: string[]
79
- /** Whether the call requested structured output (a schema was passed). */
80
- schema?: boolean
81
- /** Whether the call ran in an isolated worktree. */
82
- isolation?: boolean
83
- /** The spawned child actor id (filled once spawned; absent for cache hits / over-cap). */
84
- actorID?: string
85
- /** Wall-clock duration in ms (filled when the call settles). */
86
- durationMs?: number
87
- /** Short summary of the agent's deliverable (the value agent() resolved to),
88
- * so the tree shows what it actually produced — not just that it finished. */
89
- resultSummary?: string
90
- status: "running" | "succeeded" | "failed"
91
- }
92
- | {
93
- type: "workflow"
94
- id: string
95
- phaseId?: string
96
- childRunID: string
97
- name: string
98
- /** The args the guest passed to workflow() (JSON), for parity with agent params. */
99
- args?: unknown
100
- status: "running" | "completed" | "failed" | "cancelled"
101
- }
102
- export type WorkflowStructure = { nodes: WorkflowNode[] }
103
-
104
- // Short, display-only summary of an agent's deliverable for the structure tree.
105
- // A deliverable is a string (prose finalText) or a structured object; a worktree
106
- // deliverable is wrapped as { _worktree, result }. We flatten to one line and cap
107
- // length — purely observability, never fed to the model. Capped generously so the
108
- // card shows a substantial chunk of the response (the full trace is one ↗ away).
109
- const RESULT_SUMMARY_MAX = 600
110
- function summarizeAgentResult(result: unknown): string | undefined {
111
- if (result === null || result === undefined) return undefined
112
- const unwrapped =
113
- typeof result === "object" && result !== null && "_worktree" in result
114
- ? (result as { result?: unknown }).result ?? result
115
- : result
116
- // JSON.stringify can throw (cycles, BigInt). This runs on the host settle path
117
- // inside markAgentNode, so a throw would escape into the run — guard it: a result
118
- // we can't summarize just yields no summary, never breaks the agent.
119
- let text: string
120
- try {
121
- text = typeof unwrapped === "string" ? unwrapped : JSON.stringify(unwrapped, null, 2)
122
- } catch {
123
- return undefined
124
- }
125
- if (!text) return undefined
126
- // Preserve line breaks (collapse only runs of spaces/tabs) so a multi-paragraph
127
- // response renders as readable text in the card, then cap total length.
128
- const trimmed = text.replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim()
129
- if (!trimmed) return undefined
130
- return trimmed.length > RESULT_SUMMARY_MAX ? trimmed.slice(0, RESULT_SUMMARY_MAX - 1) + "…" : trimmed
131
- }
132
-
133
- interface RunEntry {
134
- runID: string
135
- sessionID: SessionID
136
- status: RunStatus
137
- deferred: Deferred.Deferred<RunOutcome>
138
- fiber: Fiber.Fiber<void> | undefined
139
- childActorIDs: Set<string>
140
- worktrees: Set<string> // worktree directories pending disposition, for cancel cleanup
141
- childRunIDs: Set<string> // child workflow runIDs, for recursive cancel/reclaim
142
- name: string
143
- running: number
144
- succeeded: number
145
- failed: number
146
- agentCount: number
147
- capWarned: boolean
148
- // Model refs already warned about this run, so an unresolvable ref (e.g. a
149
- // workflow using "lite" with no model_groups.lite configured) logs ONCE per
150
- // run instead of once per agent spawn. Per-run, not layer-global, so a later
151
- // run re-warns. See resolveAgentModel.
152
- warnedModelRefs: Set<string>
153
- currentPhase: string | undefined
154
- // Ordered phase/log transcript, appended synchronously by the guest hooks. The
155
- // sync workflow-tool path reads this (via the `transcript` accessor) rather than
156
- // subscribing to the bus, which removes the subscribe-after-start head race, the
157
- // two-PubSub reordering, the post-wait tail race, and the subscription leak.
158
- transcript: WorkflowTranscriptEntry[]
159
- // Observability-only structure nodes (phase/agent/workflow), program-ordered.
160
- structure: WorkflowNode[]
161
- // Id of the phase node current at the time of the next agent()/workflow() call.
162
- currentPhaseId: string | undefined
163
- }
164
-
165
- interface StartInput {
166
- script: string
167
- sessionID: SessionID
168
- parentActorID: string
169
- args?: unknown
170
- model?: { providerID: ProviderID; modelID: ModelID }
171
- maxConcurrentAgents?: number
172
- // Hard ceiling on total agents this run may spawn (lifecycle cap). Defaults to
173
- // MAX_LIFECYCLE_AGENTS (1000). Over-cap agent() calls return null (graceful
174
- // degradation, never-throw), NOT throw — so a fan-out that wants more agents
175
- // than the cap degrades to the cap-limited subset instead of aborting the run.
176
- // Lowerable for tests; tunable in prod.
177
- maxLifecycleAgents?: number
178
- /** Per-agent wall-clock timeout (ms). When an individual agent() call's spawned
179
- * child produces no terminal outcome within this window, it is gracefully
180
- * cancelled and agent() resolves to null (the never-throw failure sentinel), so
181
- * one hung agent (e.g. an LLM TTFT wall) cannot stall a parallel/pipeline barrier
182
- * indefinitely. Default undefined = OFF (only the global scriptDeadlineMs bounds a
183
- * run). A per-call agent(prompt,{timeoutMs}) overrides this. */
184
- agentTimeoutMs?: number
185
- scriptDeadlineMs?: number
186
- // Internal (resume-only): when true, launch ignores any persisted journal and
187
- // truncates the stale `.jsonl` before the run appends. resume() sets this on the
188
- // script-change path (stored script_sha != current script's sha, MR104 P1-2) so
189
- // an EDITED script never replays results journaled against the OLD body. start()
190
- // never sets it (a fresh runID has no prior journal — nothing to invalidate).
191
- freshJournal?: boolean
192
- /** Root dir the guest's file primitives (readFile/writeFile/glob/exists) are
193
- * jailed to. Defaults to the caller's worktree. A child workflow inherits the
194
- * parent's workspace unless its workflow() opts override it. */
195
- workspace?: string
196
- /** Resolved names of ancestor workflows (root = empty). A workflow() whose
197
- * resolved child name is already here is a cycle → throw. */
198
- lineage?: readonly string[]
199
- /** Current nesting depth (root run = 0). */
200
- depth?: number
201
- /** Max nesting depth before workflow() throws. Defaults to config (8). */
202
- maxDepth?: number
203
- /** When the run reaches a terminal state, send an actor_notification to the
204
- * parent's inbox (the legacy fire-and-forget contract). Defaults to true. The
205
- * workflow tool's SYNC path sets this false: it blocks on wait() and returns the
206
- * result as its own tool output, so a parent inbox notification would surface a
207
- * DUPLICATE completion (and duplicate error text) on the next turn. */
208
- notifyOnTerminal?: boolean
209
- }
210
-
211
- /** Options the guest may pass to `agent(prompt, opts?)`. */
212
- interface AgentOpts {
213
- agentType?: string
214
- tools?: readonly string[]
215
- /** A model reference resolved host-side via Provider.resolveModelRef: either a
216
- * "provider/model" literal or a configured tier/group name (e.g. "lite").
217
- * Omitted → the run's default model. Unknown group → falls back to the run
218
- * default (never throws to the guest). */
219
- model?: string
220
- schema?: Record<string, unknown>
221
- isolation?: "worktree"
222
- label?: string
223
- phase?: string
224
- /** Per-call override of the run's agentTimeoutMs (ms). */
225
- timeoutMs?: number
226
- }
227
-
228
- export interface Interface {
229
- readonly start: (input: StartInput) => Effect.Effect<{ runID: string }>
230
- readonly status: (input: {
231
- runID: string
232
- }) => Effect.Effect<{
233
- status: RunStatus | "unknown"
234
- agentCount: number
235
- running: number
236
- succeeded: number
237
- failed: number
238
- currentPhase?: string
239
- }>
240
- readonly wait: (input: { runID: string; timeoutMs?: number }) => Effect.Effect<RunOutcome>
241
- readonly transcript: (input: { runID: string }) => Effect.Effect<readonly WorkflowTranscriptEntry[]>
242
- readonly structure: (input: { runID: string }) => Effect.Effect<WorkflowStructure>
243
- readonly cancel: (input: { runID: string }) => Effect.Effect<void>
244
- readonly list: (input?: { sessionID?: SessionID }) => Effect.Effect<RunSummary[]>
245
- readonly resume: (input: { runID: string; agentTimeoutMs?: number }) => Effect.Effect<{ runID: string; resumed: boolean }>
246
- }
247
-
248
- export class Service extends Context.Service<Service, Interface>()("@opencode/WorkflowRuntime") {}
249
-
250
- /** A plain promise-based semaphore: at most `max` concurrent `run` callbacks. */
251
- function makeSemaphore(max: number) {
252
- let active = 0
253
- const queue: Array<() => void> = []
254
- const release = () => {
255
- active--
256
- const next = queue.shift()
257
- if (next) next()
258
- }
259
- return {
260
- run<T>(fn: () => Promise<T>): Promise<T> {
261
- return new Promise<T>((resolve, reject) => {
262
- const attempt = () => {
263
- active++
264
- fn().then(
265
- (value) => {
266
- release()
267
- resolve(value)
268
- },
269
- (err) => {
270
- release()
271
- reject(err)
272
- },
273
- )
274
- }
275
- if (active < max) attempt()
276
- else queue.push(attempt)
277
- })
278
- },
279
- }
280
- }
281
-
282
- function cpuCount(): number {
283
- const n = os.cpus().length
284
- return n > 0 ? n : 4
285
- }
286
-
287
- export const layer = Layer.effect(
288
- Service,
289
- Effect.gen(function* () {
290
- const bus = yield* Bus.Service
291
- const inbox = yield* Inbox.Service
292
- const worktree = yield* Worktree.Service
293
- const provider = yield* Provider.Service
294
- // Resolve the Config service handle at layer scope (a legitimate layer dep,
295
- // satisfied by Config.defaultLayer) so the requirement is discharged here and
296
- // does NOT leak into start/resume's effect signatures. Only config.get() runs
297
- // lazily below — it reads the per-instance ALS context and returns Effect<Info>
298
- // with no requirement, so it stays out of the public method types.
299
- const config = yield* Config.Service
300
- const scope = yield* Scope.Scope
301
- const runs = new Map<string, RunEntry>()
302
-
303
- // Resolve a guest-supplied model ref (a "provider/model" literal OR a
304
- // tier/group name like "lite") to a concrete {providerID, modelID} via the
305
- // Provider service — host-side, inside the runtime's own Layer scope (so it
306
- // survives resume(), which re-reads the script with no fresh StartInput).
307
- // NEVER throws to the guest: an unknown group (resolveModelRef throwing
308
- // ModelGroupNotFoundError) falls back to the run default, matching agent()'s
309
- // never-throw contract. undefined ref → the run default unchanged.
310
- const resolveAgentModel = (
311
- ref: string | undefined,
312
- fallback: { providerID: ProviderID; modelID: ModelID } | undefined,
313
- warned: Set<string>,
314
- ): Effect.Effect<{ providerID: ProviderID; modelID: ModelID } | undefined> =>
315
- ref === undefined
316
- ? Effect.succeed(fallback)
317
- : provider.resolveModelRef(ref).pipe(
318
- Effect.map((m) => ({ providerID: m.providerID, modelID: m.id })),
319
- Effect.catchCause(() =>
320
- Effect.sync(() => {
321
- // Leave a breadcrumb so a bad ref isn't pure silence: an unknown
322
- // group/tier, a typo, or an out-of-tree script passing the old
323
- // {providerID, modelID} object (not a string) all land here and
324
- // silently use the run default. Warn ONCE per unique ref per run
325
- // (a fan-out like deep-research would otherwise log on every
326
- // agent spawn). For a non-string ref, log its sorted keys (e.g.
327
- // "modelID,providerID") so the operator sees it's the legacy
328
- // object shape — keys are schema names, no user data.
329
- const shown =
330
- typeof ref === "string"
331
- ? ref
332
- : `{${Object.keys(ref as object)
333
- .sort()
334
- .join(",")}}`
335
- if (!warned.has(shown)) {
336
- warned.add(shown)
337
- log.warn("workflow agent model ref did not resolve — using run default", { ref: shown })
338
- }
339
- return fallback
340
- }),
341
- ),
342
- )
343
-
344
- // Process-wide concurrency ceiling: ONE semaphore shared by every run
345
- // (including nested children), so tree-wide concurrent agents can never
346
- // exceed it regardless of nesting depth. It is a PURE process/config property,
347
- // sized SOLELY from config.workflow.maxConcurrentAgents (falling back to the
348
- // min(16, 2×cores) default) — NEVER seeded or raised by any per-launch
349
- // maxConcurrentAgents input. A per-run input only ever NARROWS that run's own
350
- // semaphore (clamped ≤ global, below); it can neither raise the global nor bind
351
- // a later run to an earlier run's cap. Resolved LAZILY on the first launch
352
- // (config.get reads the per-instance ALS context, live inside launch but NOT at
353
- // layer-build time) and memoized at service scope so every subsequent launch() —
354
- // including nested children — shares the same semaphore. `cfg`/`globalMax`/
355
- // `globalSem` are reused by later tasks (T12 maxDepth, T14 maxLifecycleAgents).
356
- let cfg: Config.Info | undefined
357
- let globalMax = 0
358
- let globalSem: ReturnType<typeof makeSemaphore> | undefined
359
- const ensureGlobal = Effect.fn("WorkflowRuntime.ensureGlobal")(function* () {
360
- if (globalSem) return globalSem
361
- // Resolve config once (this is the only suspension point). Cached on `cfg`
362
- // for reuse by later per-run reads (maxDepth, maxLifecycleAgents).
363
- cfg ??= yield* config.get()
364
- globalMax = Math.max(
365
- 1,
366
- cfg.workflow?.maxConcurrentAgents ?? Math.min(DEFAULT_MAX_CONCURRENT, 2 * Math.max(1, cpuCount())),
367
- )
368
- // Assign synchronously with ??= so two concurrent first-launches that both
369
- // passed the guard above (the `config.get()` await is a suspension point)
370
- // converge on ONE semaphore instead of transiently doubling the ceiling.
371
- // Frozen for the process lifetime: a later config change to
372
- // maxConcurrentAgents does NOT rebuild it (acceptable while workflow is
373
- // experimental — the global ceiling is a process/config property).
374
- globalSem ??= makeSemaphore(globalMax)
375
- return globalSem
376
- })
377
-
378
- // Debounced counter flush: coalesce high-rate running/succeeded/failed updates
379
- // to at most one DB write per ~250ms per run. flushNow is the synchronous final
380
- // flush on terminal. All best-effort.
381
- const flushTimers = new Map<string, ReturnType<typeof setTimeout>>()
382
- const flushNow = (entry: RunEntry) => {
383
- const t = flushTimers.get(entry.runID)
384
- if (t) {
385
- clearTimeout(t)
386
- flushTimers.delete(entry.runID)
387
- }
388
- return WorkflowPersistence.flushCounters({
389
- runID: entry.runID,
390
- running: entry.running,
391
- succeeded: entry.succeeded,
392
- failed: entry.failed,
393
- }).pipe(Effect.ignore)
394
- }
395
- const scheduleFlush = (entry: RunEntry) => {
396
- if (flushTimers.has(entry.runID)) return
397
- flushTimers.set(
398
- entry.runID,
399
- setTimeout(() => {
400
- flushTimers.delete(entry.runID)
401
- Effect.runFork(
402
- WorkflowPersistence.flushCounters({
403
- runID: entry.runID,
404
- running: entry.running,
405
- succeeded: entry.succeeded,
406
- failed: entry.failed,
407
- }).pipe(Effect.ignore),
408
- )
409
- }, 250),
410
- )
411
- }
412
-
413
- // Best-effort cleanup for a NON-SUCCESS terminal (cancel, deadline, script
414
- // failure): graceful-cancel any in-flight child agents and remove every
415
- // worktree the run still owns, then clear the set. NEVER throws — a reclaim
416
- // failure must not mask the original terminal cause. NOT called on success:
417
- // kept (success+changed) worktrees are the deliverable and must survive.
418
- const reclaim = (entry: RunEntry) =>
419
- Effect.gen(function* () {
420
- const actor = spawnRef.current
421
- if (actor) {
422
- yield* Effect.forEach(
423
- [...entry.childActorIDs],
424
- (childID) => actor.cancel(entry.sessionID, childID, "graceful").pipe(Effect.ignore),
425
- { concurrency: "unbounded", discard: true },
426
- )
427
- }
428
- yield* Effect.forEach(
429
- [...entry.worktrees],
430
- (directory) => worktree.remove({ directory }).pipe(Effect.ignore),
431
- { concurrency: "unbounded", discard: true },
432
- )
433
- entry.worktrees.clear()
434
- // Recurse into child workflow RUNS (populated by workflow()). Cancelling the
435
- // orchestrator tears down the whole tree — a child still "running" here is
436
- // cancelled via cancelEntry (mutually recursive with reclaim).
437
- // SAFETY: childRunIDs edges are parent→child only (added solely at the
438
- // workflow() call site with a freshly-minted child runID), so the graph is a
439
- // tree and this recursion is finite. The status-flip guard alone does NOT stop
440
- // a cycle (a node's flip is post-order, after its reclaim returns), so
441
- // acyclicity is load-bearing — the workflow() cycle guard (Task 12) is what
442
- // keeps it true as the call graph grows.
443
- yield* Effect.forEach(
444
- [...entry.childRunIDs],
445
- (childRunID) =>
446
- Effect.gen(function* () {
447
- const child = runs.get(childRunID)
448
- if (child && child.status === "running") yield* cancelEntry(child)
449
- }).pipe(Effect.ignore),
450
- { concurrency: "unbounded", discard: true },
451
- )
452
- })
453
-
454
- const cancelEntry = (entry: RunEntry): Effect.Effect<void> =>
455
- Effect.gen(function* () {
456
- if (entry.status !== "running") return
457
- yield* reclaim(entry)
458
- yield* flushNow(entry)
459
- yield* WorkflowPersistence.recordTerminal({ runID: entry.runID, status: "cancelled" }).pipe(Effect.ignore)
460
- if (entry.fiber) yield* Fiber.interrupt(entry.fiber)
461
- entry.status = "cancelled"
462
- yield* Deferred.succeed(entry.deferred, { status: "cancelled" })
463
- yield* bus.publish(WorkflowFinished, { sessionID: entry.sessionID, runID: entry.runID, status: "cancelled" })
464
- })
465
-
466
- const waitFor = (childRunID: string) =>
467
- Effect.gen(function* () {
468
- const child = runs.get(childRunID)
469
- if (!child) return { status: "failed" as const, error: "child run missing" }
470
- return yield* Deferred.await(child.deferred)
471
- })
472
-
473
- const launch = Effect.fn("WorkflowRuntime.launch")(function* (input: StartInput, runID: string, name: string) {
474
- // The guest body is the script with the `meta` literal blanked out (parseMeta
475
- // preserves line numbers). start already validated meta and resume only loads
476
- // a previously-validated script, so this parse is purely to extract the body;
477
- // it never gates here. Fall back to the raw script if parse somehow fails.
478
- const parsed = parseMeta(input.script)
479
- const body = parsed.ok ? parsed.body : input.script
480
- // Resolve the workspace root ONCE at launch (the Instance ALS context is
481
- // live here — the bridge below captures it). Default = the caller's
482
- // worktree. Captured in the closure so the file hooks read it synchronously
483
- // and never touch ALS from inside the forked work fiber.
484
- const workspaceRoot = input.workspace ?? Instance.worktree
485
- const fileHooks = makeFileHooks(workspaceRoot)
486
- const deferred = yield* Deferred.make<RunOutcome>()
487
- const entry: RunEntry = {
488
- runID,
489
- sessionID: input.sessionID,
490
- status: "running",
491
- deferred,
492
- fiber: undefined,
493
- childActorIDs: new Set<string>(),
494
- worktrees: new Set<string>(),
495
- childRunIDs: new Set<string>(),
496
- name,
497
- running: 0,
498
- succeeded: 0,
499
- failed: 0,
500
- agentCount: 0,
501
- capWarned: false,
502
- warnedModelRefs: new Set<string>(),
503
- currentPhase: undefined,
504
- transcript: [],
505
- structure: [],
506
- currentPhaseId: undefined,
507
- }
508
- runs.set(runID, entry)
509
- // Stamp a sha256 of the FULL script body (the exact bytes writeScript persists
510
- // and resume's readScript reads back), so resume can detect a between-cycle
511
- // edit by comparing this to the current file's sha — apples-to-apples, MR104
512
- // P1-2. recordStart re-stamps it on every (re)launch, so a changed-script
513
- // relaunch overwrites the stale sha and a subsequent resume replays correctly.
514
- const scriptSha = createHash("sha256").update(input.script).digest("hex")
515
- yield* WorkflowPersistence.recordStart({
516
- runID,
517
- sessionID: input.sessionID,
518
- name,
519
- parentActorID: input.parentActorID,
520
- args: input.args,
521
- scriptSha,
522
- agentTimeoutMs: input.agentTimeoutMs,
523
- }).pipe(Effect.ignore)
524
- yield* WorkflowPersistence.writeScript(runID, input.script).pipe(Effect.ignore)
525
-
526
- // Replay journal: prior agent() results (empty on a fresh run). On resume,
527
- // a cache hit returns instantly with no spawn; misses spawn + append. The
528
- // occ counter disambiguates byte-identical calls into distinct slots.
529
- // freshJournal (resume's script-change path) truncates the stale `.jsonl`
530
- // FIRST so loadJournal returns empty AND the run's appends don't interleave
531
- // with results journaled against the old script body — a later resume would
532
- // otherwise read both and replay the wrong results.
533
- if (input.freshJournal) yield* WorkflowPersistence.clearJournal(runID).pipe(Effect.ignore)
534
- const journal = yield* WorkflowPersistence.loadJournal(runID)
535
- const occ = new Map<string, number>()
536
- const pass = journal.pass
537
-
538
- // Capture the bridge BEFORE forking so it snapshots the caller's
539
- // Instance/Workspace context — the quickjs Promise boundary in agent()
540
- // would otherwise lose it.
541
- const bridge = yield* EffectBridge.make()
542
-
543
- // Resolve the process-wide ceiling NOW (under the live Instance context) so
544
- // its semaphore object exists before any spawn site closes over it. Sized
545
- // PURELY from config (memoized after the first launch); a per-launch
546
- // maxConcurrentAgents never seeds or raises it — it only narrows this run's
547
- // own semaphore below.
548
- const globalSemLocal = yield* ensureGlobal()
549
- // Nesting safety (T12): carried through every run. lineage = resolved names of
550
- // ancestor workflows (root = empty); depth = this run's level (root = 0). A
551
- // workflow() whose child name is already in lineage is a cycle, and a child
552
- // beyond maxDepth is over-deep — both throw at the call site (workflowHook).
553
- // maxDepth precedence: explicit per-run input > config > module default 8.
554
- const lineage = input.lineage ?? []
555
- const depth = input.depth ?? 0
556
- const maxDepth = input.maxDepth ?? cfg?.workflow?.maxDepth ?? 8
557
- // Per-run soft cap: defaults to the global ceiling, clamped to ≤ global so a
558
- // child can shrink its own concurrency but never exceed the process ceiling.
559
- // The 2×cores clamp is GONE — the global semaphore is the real throttle.
560
- const requested = input.maxConcurrentAgents ?? globalMax
561
- const max = Math.max(1, Math.min(requested, globalMax))
562
- const sem = makeSemaphore(max)
563
- // Lifecycle cap (total agents over the run's life). Resolved once here so
564
- // both spawn paths (shared + isolated) share it; over-cap calls return null.
565
- const lifecycleCap = input.maxLifecycleAgents ?? cfg?.workflow?.maxLifecycleAgents ?? MAX_LIFECYCLE_AGENTS
566
- // Over-cap → null (see maxLifecycleAgents doc): warn ONCE per run so the
567
- // dropped work is visible without spamming a log line per over-cap call.
568
- const warnCapOnce = () => {
569
- if (entry.capWarned) return
570
- entry.capWarned = true
571
- log.warn("workflow lifecycle agent cap reached — over-cap agents return null", {
572
- runID,
573
- cap: lifecycleCap,
574
- })
575
- }
576
- // Per-agent wall-clock timeout. Run-level default (OFF unless set); a per-call
577
- // opts.timeoutMs overrides it. Resolved per agent() call since opts is per-call.
578
- const runAgentTimeoutMs = input.agentTimeoutMs
579
- // Race a child's outcome-await against the effective per-agent timeout. On a
580
- // TRUE timeout: gracefully cancel that one child (the lever reclaim uses) and
581
- // yield null — the never-throw sentinel the guest already tolerates, so a hung
582
- // agent can't stall a parallel/pipeline barrier. A genuine null deliverable
583
- // (agent failed fast) is NOT a timeout → no cancel. No timeout configured
584
- // (undefined / <=0) ⇒ await unbounded (current behavior, only scriptDeadline bounds).
585
- const awaitWithTimeout = <A>(
586
- actorID: string,
587
- opts: AgentOpts,
588
- await_: Effect.Effect<A | null>,
589
- // Optional side-channel: set when the timeout branch wins, so the caller
590
- // can distinguish a TRUE timeout (reason="timeout") from a fast actor-error
591
- // null. Never throws; called once at most. Pure observability.
592
- onTimeout?: () => void,
593
- ) => {
594
- const ms = opts.timeoutMs ?? runAgentTimeoutMs
595
- if (!ms || ms <= 0) return await_
596
- return Effect.raceFirst(
597
- await_,
598
- Effect.sleep(`${ms} millis`).pipe(Effect.as(STRAGGLER_TIMEOUT as unknown as A | null)),
599
- ).pipe(
600
- Effect.flatMap((r) =>
601
- r === (STRAGGLER_TIMEOUT as unknown)
602
- ? (spawnRef.current
603
- ? spawnRef.current.cancel(input.sessionID, actorID, "graceful").pipe(Effect.ignore)
604
- : Effect.void
605
- ).pipe(
606
- Effect.tap(() =>
607
- Effect.sync(() => {
608
- try {
609
- onTimeout?.()
610
- } catch {
611
- /* observability must never escape */
612
- }
613
- }),
614
- ),
615
- Effect.as(null),
616
- )
617
- : Effect.succeed(r),
618
- ),
619
- )
620
- }
621
-
622
- // Publish a WorkflowAgentFailed event for an agent() call that resolved to
623
- // null. Pure observability — counters and the agent() return value are
624
- // unaffected. Wrapped in try/catch so a bus problem can never break a run.
625
- type FailReason = "over-cap" | "spawn-reject" | "timeout" | "actor-error" | "no-deliverable"
626
- const publishAgentFailed = (
627
- o: AgentOpts,
628
- reason: FailReason,
629
- info: { actorID?: string; errorMessage?: string } = {},
630
- ) => {
631
- try {
632
- Effect.runFork(
633
- bus
634
- .publish(WorkflowAgentFailed, {
635
- sessionID: input.sessionID,
636
- runID,
637
- actorID: info.actorID,
638
- agentType: o.agentType ?? "general",
639
- label: o.label,
640
- phase: o.phase ?? entry.currentPhase,
641
- reason,
642
- errorMessage: info.errorMessage,
643
- })
644
- .pipe(Effect.ignore),
645
- )
646
- } catch {
647
- /* observability must never escape */
648
- }
649
- }
650
-
651
- yield* bus.publish(WorkflowStarted, { sessionID: input.sessionID, runID, name })
652
-
653
- // Observability-only spawn description from label/phase: "[Phase] label",
654
- // or just one of them, or undefined (then spawn falls back to agentType —
655
- // see spawn.ts `input.description ?? input.agentType`). label/phase NEVER
656
- // touch currentPhase/counters/schema — they are purely the per-agent tag
657
- // the actor registry stores and the /workflows view surfaces.
658
- const spawnDescription = (o: AgentOpts) =>
659
- o.label ? (o.phase ? `[${o.phase}] ${o.label}` : o.label) : o.phase ? `[${o.phase}]` : undefined
660
-
661
- // Shared-tree spawn (default): the existing behavior. SUBAGENT mode — the
662
- // worker shares the run's parent session (cheaper, no per-agent session).
663
- // Safe since lastAssistant is agent-scoped (fix 59597264): each subagent's
664
- // result is extracted by its own agentID, so concurrent same-session
665
- // subagents don't cross-contaminate. context:"none" keeps each worker free
666
- // of parent history (parallel fan-out is the use case). NEVER throw to the
667
- // guest for spawn/turn failures — resolve to null so the script continues.
668
- const spawnShared = async (
669
- actor: NonNullable<typeof spawnRef.current>,
670
- prompt: string,
671
- o: AgentOpts,
672
- resolvedModel: { providerID: ProviderID; modelID: ModelID } | undefined,
673
- onActorID?: (id: string) => void,
674
- ) => {
675
- // COUNTER INVARIANT: running++ exactly once BEFORE the spawn attempt, and
676
- // running-- + (succeeded XOR failed)++ exactly once AFTER it settles. The
677
- // bookkeeping lives OUTSIDE the bridge so it still runs when the bridge
678
- // result is the spawn-reject sentinel (null). Counters settle on whether a
679
- // DELIVERABLE was produced (value !== null) — the exact thing the guest
680
- // observes. An agent whose turn errored finishes with status:"success" but
681
- // no finalText/structured, so its deliverable is null and the guest sees a
682
- // failure; the counter must agree. A spawn reject also yields null → failed.
683
- entry.running++
684
- scheduleFlush(entry)
685
- // Failure-reason refs: defaults to "actor-error" (the broad catch-all) and
686
- // is narrowed at known branch points. Read once at the end, on null return,
687
- // to publish WorkflowAgentFailed. agent()'s null contract is unchanged.
688
- let reason: FailReason = "actor-error"
689
- let actorID: string | undefined
690
- let errorMessage: string | undefined
691
- const value = await bridge
692
- .promise(
693
- Effect.gen(function* () {
694
- const spawned = yield* actor.spawn({
695
- mode: "subagent",
696
- sessionID: input.sessionID,
697
- agentType: o.agentType ?? "general",
698
- description: spawnDescription(o),
699
- task: prompt,
700
- context: "none",
701
- tools: o.tools ? [...o.tools] : "INHERIT",
702
- background: true,
703
- parentActorID: input.parentActorID,
704
- model: resolvedModel,
705
- // Register the child in the reclaim set the instant the actor
706
- // exists — synchronously inside the spawn Effect, BEFORE its work
707
- // fiber detaches. A cancel racing this spawn would otherwise miss
708
- // it (the child runs detached in the actor scope, so interrupting
709
- // the workflow fiber can't stop it) and leak an orphan. MR104 #2.
710
- onActorID: (id) => {
711
- entry.childActorIDs.add(id)
712
- onActorID?.(id)
713
- },
714
- ...(o.schema ? { format: { type: "json_schema" as const, schema: o.schema, retryCount: 2 } } : {}),
715
- })
716
- actorID = spawned.actorID
717
- // Bound the outcome-await by the per-agent timeout: a hung child times
718
- // out to null (and is cancelled) rather than stalling the barrier. The
719
- // deliverable is computed inside the awaited Effect so the timeout wraps
720
- // the whole await→extract. schema requested ⇒ structured ?? null (never
721
- // prose finalText: prose breaks `r.fields`-style scripts + our pipeline).
722
- const deliverable = yield* awaitWithTimeout(
723
- spawned.actorID,
724
- o,
725
- Deferred.await(spawned.outcome).pipe(
726
- Effect.map((outcome) => {
727
- if (outcome.status !== "success") {
728
- reason = "actor-error"
729
- errorMessage = (outcome as { error?: string }).error
730
- return null
731
- }
732
- const v = o.schema
733
- ? (outcome.structured ?? null)
734
- : (outcome.structured ?? outcome.finalText ?? null)
735
- if (v === null) reason = "no-deliverable"
736
- return v
737
- }),
738
- ),
739
- () => {
740
- reason = "timeout"
741
- },
742
- )
743
- entry.childActorIDs.delete(spawned.actorID)
744
- return deliverable
745
- }),
746
- )
747
- .catch((e) => {
748
- reason = "spawn-reject"
749
- errorMessage = e instanceof Error ? e.message : String(e)
750
- return null
751
- })
752
- entry.running--
753
- if (value !== null) entry.succeeded++
754
- else {
755
- entry.failed++
756
- publishAgentFailed(o, reason, { actorID, errorMessage })
757
- }
758
- scheduleFlush(entry)
759
- return value
760
- }
761
-
762
- // Isolated spawn: fresh worktree, file tools rebound to it via Instance.provide.
763
- const spawnIsolated = async (
764
- actor: NonNullable<typeof spawnRef.current>,
765
- prompt: string,
766
- o: AgentOpts,
767
- resolvedModel: { providerID: ProviderID; modelID: ModelID } | undefined,
768
- onActorID?: (id: string) => void,
769
- ) => {
770
- // Failure-reason refs (parallel to spawnShared); see there for rationale.
771
- let reason: FailReason = "actor-error"
772
- let actorIDOut: string | undefined
773
- let errorMessage: string | undefined
774
- // 1) Create + fully populate a worktree (createFromInfo awaits boot).
775
- const info = await bridge
776
- .promise(
777
- Effect.gen(function* () {
778
- const i = yield* worktree.makeWorktreeInfo()
779
- yield* worktree.createFromInfo(i)
780
- return i
781
- }),
782
- )
783
- .catch((e) => {
784
- errorMessage = e instanceof Error ? e.message : String(e)
785
- return null
786
- })
787
- if (!info) {
788
- publishAgentFailed(o, "spawn-reject", { errorMessage })
789
- return null
790
- }
791
- // Register the worktree for cleanup the moment it exists on disk — BEFORE
792
- // the spawn attempt. If spawn rejects or the agent fails, cancel-cleanup
793
- // (and the disposition below) can still reclaim it; nothing orphans.
794
- entry.worktrees.add(info.directory)
795
- const base = await bridge.promise(worktree.head(info.directory)).catch(() => "")
796
- // 2) A bridge bound to the worktree's InstanceContext: provide InstanceRef =
797
- // worktree ctx so Effect-side reads resolve there; the Instance.provide
798
- // wrap below covers raw-ALS tool reads (the load-bearing part). The outer
799
- // Instance.provide is what reroutes the agent's file tools; wtBridge is
800
- // defense-in-depth for any Effect-side InstanceRef read during dispatch.
801
- const wtCtx = await Instance.provide({
802
- directory: info.directory,
803
- fn: () => Promise.resolve(Instance.current),
804
- })
805
- const wtBridge = await bridge.promise(EffectBridge.make().pipe(Effect.provideService(InstanceRef, wtCtx)))
806
- // 3) Spawn + await INSIDE Instance.provide({worktree}) — AsyncLocalStorage
807
- // propagates the worktree dir across the actor's forked work fiber, so the
808
- // agent's read/write/bash resolve to the worktree, not the parent tree.
809
- // COUNTER INVARIANT (isolated path): running++ here, BEFORE the spawn
810
- // attempt, so it pairs with the settle below regardless of spawn-reject
811
- // (spawned === null). The settle (running-- + succeeded/failed++) runs once
812
- // on every disposition path after `succeeded` is known.
813
- entry.running++
814
- scheduleFlush(entry)
815
- const spawned = await Instance.provide({
816
- directory: info.directory,
817
- fn: () =>
818
- wtBridge
819
- .promise(
820
- Effect.gen(function* () {
821
- const s = yield* actor.spawn({
822
- mode: "subagent",
823
- sessionID: input.sessionID,
824
- agentType: o.agentType ?? "general",
825
- description: spawnDescription(o),
826
- task: prompt,
827
- context: "none",
828
- tools: o.tools ? [...o.tools] : "INHERIT",
829
- background: true,
830
- parentActorID: input.parentActorID,
831
- model: resolvedModel,
832
- // Same MR104 #2 fix as spawnShared: register the child in the
833
- // reclaim set synchronously inside the spawn Effect, before its
834
- // work fiber detaches, so a racing cancel never orphans it.
835
- onActorID: (id) => {
836
- entry.childActorIDs.add(id)
837
- onActorID?.(id)
838
- },
839
- ...(o.schema ? { format: { type: "json_schema" as const, schema: o.schema, retryCount: 2 } } : {}),
840
- })
841
- actorIDOut = s.actorID
842
- // Bound the await by the per-agent timeout. On timeout the helper
843
- // cancels the child and yields null; we surface that as a null
844
- // `spawned` so the disposition below takes the same path as a
845
- // spawn-reject/failure (worktree reclaimed, value null, failed++) —
846
- // a hung isolated agent can't stall the barrier or leak a worktree.
847
- const outcome = yield* awaitWithTimeout(s.actorID, o, Deferred.await(s.outcome), () => {
848
- reason = "timeout"
849
- })
850
- entry.childActorIDs.delete(s.actorID)
851
- if (outcome === null) return null
852
- if (outcome.status !== "success") {
853
- reason = "actor-error"
854
- errorMessage = (outcome as { error?: string }).error
855
- }
856
- return { actorID: s.actorID, outcome }
857
- }),
858
- )
859
- .catch((e) => {
860
- reason = "spawn-reject"
861
- errorMessage = e instanceof Error ? e.message : String(e)
862
- return null
863
- }),
864
- }).catch((e) => {
865
- reason = "spawn-reject"
866
- errorMessage = e instanceof Error ? e.message : String(e)
867
- return null
868
- })
869
- // 4) Disposition. KEEP the worktree only when the agent SUCCEEDED and left
870
- // changes (the deliverable, surfaced via _worktree). In every other case
871
- // — pristine (untouched), spawn rejected, or agent failed/cancelled —
872
- // remove it so nothing leaks on disk. Guard: an empty base means head()
873
- // failed at create time; treat as CHANGED (never trust an unreliable
874
- // pristine check to authorize a delete).
875
- const succeeded = !!spawned && spawned.outcome.status === "success"
876
- // Settle the counter once here — after `succeeded` is known and before any
877
- // disposition branch — so it runs exactly once on every path (spawn-reject
878
- // → spawned===null → failed++, keep, remove). Pairs with the running++ above.
879
- // We REUSE the existing `succeeded` discriminant (read-only; the worktree
880
- // disposition below owns it) rather than the returned deliverable: in the
881
- // isolated path a successful agent's work is its worktree, so a status
882
- // success is a success even when it returned no text.
883
- entry.running--
884
- if (succeeded) entry.succeeded++
885
- else {
886
- entry.failed++
887
- publishAgentFailed(o, reason, { actorID: actorIDOut, errorMessage })
888
- }
889
- scheduleFlush(entry)
890
- // The success deliverable. When a schema was requested it MUST be the
891
- // validated structured object — never prose finalText (see the shared-spawn
892
- // path above for why: prose breaks `r.fields`-style scripts + our pipeline
893
- // null-injection). schema requested ⇒ structured ?? null.
894
- const value =
895
- spawned && spawned.outcome.status === "success"
896
- ? o.schema
897
- ? (spawned.outcome.structured ?? null)
898
- : (spawned.outcome.structured ?? spawned.outcome.finalText ?? null)
899
- : null
900
- const pristine =
901
- base !== "" && (await bridge.promise(worktree.isPristine(info.directory, base)).catch(() => false))
902
- const keep = succeeded && !pristine
903
- if (!keep) {
904
- await bridge.promise(worktree.remove({ directory: info.directory })).catch(() => undefined)
905
- entry.worktrees.delete(info.directory)
906
- return succeeded ? value : null
907
- }
908
- // keep: the worktree stays on disk and tracked until an integrate step or
909
- // cancel reclaims it; surface its branch so the script can act on it.
910
- const wt = { branch: info.branch, directory: info.directory, changed: true }
911
- if (value && typeof value === "object" && !Array.isArray(value)) return { ...(value as object), _worktree: wt }
912
- return { _worktree: wt, result: value }
913
- }
914
-
915
- // Per-call start times (host wall-clock) for the observability nodes' durationMs.
916
- // Host-side only — never read by the guest, so it doesn't affect determinism/replay.
917
- const nodeStart = new Map<string, number>()
918
- const agentImpl = (prompt: unknown, opts?: unknown, nodeId?: string) => {
919
- const o = (opts ?? {}) as AgentOpts
920
- const promptStr = String(prompt)
921
- // Flip the observability node (if any) recorded by the `agent` wrapper for
922
- // THIS call. Called at the same points that bump succeeded/failed counters.
923
- // `result` is the agent's deliverable (string / structured object / null);
924
- // we stash a short summary on the node so the tree shows what it produced.
925
- const markAgentNode = (status: "succeeded" | "failed", result?: unknown, actorID?: string) => {
926
- if (!nodeId) return
927
- const node = entry.structure.find((n) => n.id === nodeId)
928
- if (node && node.type === "agent") {
929
- node.status = status
930
- const start = nodeStart.get(nodeId)
931
- if (start !== undefined) node.durationMs = Date.now() - start
932
- if (actorID && node.actorID === undefined) node.actorID = actorID
933
- const summary = summarizeAgentResult(result)
934
- if (summary !== undefined) node.resultSummary = summary
935
- }
936
- }
937
- // Fill the node's actorID the instant the child actor is minted (BEFORE it
938
- // settles), so the TUI can offer "open this agent" while it's still running.
939
- const setActorID = (actorID: string) => {
940
- if (!nodeId) return
941
- const node = entry.structure.find((n) => n.id === nodeId)
942
- if (node && node.type === "agent" && node.actorID === undefined) node.actorID = actorID
943
- }
944
- // Isolated agents are never journaled in v1 (their deliverable is a
945
- // worktree the journal can't reconstruct) — always spawn.
946
- if (o.isolation !== "worktree") {
947
- const base = journalKeyBase(promptStr, {
948
- agentType: o.agentType,
949
- model: o.model,
950
- schema: o.schema,
951
- phase: o.phase,
952
- })
953
- const n = occ.get(base) ?? 0
954
- occ.set(base, n + 1)
955
- const key = base + ":" + n
956
- if (journal.results.has(key)) {
957
- // Cache hit: no spawn, no agentCount increment (would hit the 1000
958
- // cap on replays alone). Outcome counter DOES climb so the live view
959
- // reflects reality as replay proceeds.
960
- entry.succeeded++
961
- markAgentNode("succeeded", journal.results.get(key))
962
- scheduleFlush(entry)
963
- return Promise.resolve(journal.results.get(key))
964
- }
965
- return (async () => {
966
- // Spawn UNDER the semaphore (governs concurrency). The journal append
967
- // happens AFTER the slot is released, so file IO never holds a slot.
968
- const result = await sem.run(async () =>
969
- globalSemLocal.run(async () => {
970
- if (entry.agentCount >= lifecycleCap) {
971
- warnCapOnce()
972
- publishAgentFailed(o, "over-cap")
973
- return null
974
- }
975
- entry.agentCount++
976
- const actor = spawnRef.current
977
- if (!actor) throw new Error("Actor service unavailable")
978
- // Resolve the guest's model ref host-side AFTER the journal key was
979
- // computed above (the key hashes the raw `o.model` ref, NOT the
980
- // resolved struct, so resume keys stay stable across config changes).
981
- // Never-throws: an unknown group falls back to input.model.
982
- const resolvedModel = await bridge.promise(resolveAgentModel(o.model, input.model, entry.warnedModelRefs))
983
- return spawnShared(actor, promptStr, o, resolvedModel, setActorID)
984
- }),
985
- )
986
- markAgentNode(result === null ? "failed" : "succeeded", result)
987
- // Cache successful results only (null = failure/spawn-reject/killed →
988
- // not journaled → re-runs on resume, self-heal). SYNCHRONOUS append so
989
- // the result is durable the instant it resolves: a mid-run process exit
990
- // / SIGKILL / deadline leaves a journal with every completed agent, which
991
- // is the whole point of resume. A sync write (unlike an awaited async
992
- // Effect.promise(fs)) does NOT starve the quickjs sandbox pump — verified.
993
- // Effect.ignore'd so a write failure can't break the agent.
994
- if (result !== null) {
995
- await Effect.runPromise(
996
- WorkflowPersistence.appendJournalSync(runID, [{ t: "agent", key, result, pass }]).pipe(Effect.ignore),
997
- )
998
- }
999
- return result
1000
- })()
1001
- }
1002
- return sem.run(async () =>
1003
- globalSemLocal.run(async () => {
1004
- if (entry.agentCount >= lifecycleCap) {
1005
- warnCapOnce()
1006
- publishAgentFailed(o, "over-cap")
1007
- markAgentNode("failed")
1008
- return null
1009
- }
1010
- entry.agentCount++
1011
- const actor = spawnRef.current
1012
- if (!actor) throw new Error("Actor service unavailable")
1013
- // Resolve the guest's model ref host-side (isolated agents aren't
1014
- // journaled, so there's no key to keep stable here). Never-throws.
1015
- const resolvedModel = await bridge.promise(resolveAgentModel(o.model, input.model, entry.warnedModelRefs))
1016
- const value = await spawnIsolated(actor, promptStr, o, resolvedModel, setActorID)
1017
- markAgentNode(value === null ? "failed" : "succeeded", value)
1018
- return value
1019
- }),
1020
- )
1021
- }
1022
-
1023
- // Observability: record a structure node at agent() call time, attributed to
1024
- // the current phase. Status starts "running" and is flipped to succeeded/failed
1025
- // at the SAME sites that bump the succeeded/failed counters (markAgentNode),
1026
- // keyed by this node id. We deliberately do NOT wrap or tap the returned host
1027
- // promise: the sandbox's asyncify-free sync-promise bridge counts pending jobs
1028
- // to drive its pump, so an extra Promise.resolve().then() perturbs settle timing
1029
- // and corrupts counters. The hook returns the host promise untouched.
1030
- const agent: HostFn = (prompt: unknown, opts?: unknown) => {
1031
- const o = (opts ?? {}) as AgentOpts
1032
- const nodeId = "a" + entry.structure.length
1033
- entry.structure.push({
1034
- type: "agent",
1035
- id: nodeId,
1036
- phaseId: entry.currentPhaseId,
1037
- label: o.label,
1038
- agentType: o.agentType ?? "general",
1039
- prompt: String(prompt),
1040
- ...(o.model !== undefined ? { model: o.model } : {}),
1041
- ...(o.tools ? { tools: [...o.tools] } : {}),
1042
- ...(o.schema ? { schema: true } : {}),
1043
- ...(o.isolation === "worktree" ? { isolation: true } : {}),
1044
- status: "running",
1045
- })
1046
- nodeStart.set(nodeId, Date.now())
1047
- return agentImpl(prompt, opts, nodeId)
1048
- }
1049
-
1050
- const phase: HostFn = (title: unknown) => {
1051
- entry.currentPhase = String(title)
1052
- entry.transcript.push({ kind: "phase", text: String(title) })
1053
- const phaseId = "p" + entry.structure.length
1054
- entry.structure.push({ type: "phase", id: phaseId, title: String(title) })
1055
- entry.currentPhaseId = phaseId
1056
- Effect.runFork(WorkflowPersistence.recordPhase({ runID, phase: String(title) }).pipe(Effect.ignore))
1057
- Effect.runFork(WorkflowPersistence.appendJournal(runID, { t: "phase", title: String(title), pass }).pipe(Effect.ignore))
1058
- Effect.runFork(bus.publish(WorkflowPhase, { sessionID: input.sessionID, runID, title: String(title) }))
1059
- return undefined
1060
- }
1061
-
1062
- const logHook: HostFn = (message: unknown) => {
1063
- entry.transcript.push({ kind: "log", text: String(message) })
1064
- Effect.runFork(WorkflowPersistence.appendJournal(runID, { t: "log", msg: String(message), pass }).pipe(Effect.ignore))
1065
- Effect.runFork(bus.publish(WorkflowLog, { sessionID: input.sessionID, runID, message: String(message) }))
1066
- return undefined
1067
- }
1068
-
1069
- // workflow(nameOrScript, args?, opts?) — schedule a CHILD workflow as its
1070
- // own independent sub-run, awaited inline. Mirrors agent()→Actor.spawn one
1071
- // level up: mint a deterministic child runID (stable across resume so the
1072
- // parent journal can find the child), resolve name→script, launch it, await
1073
- // its RunOutcome. A child that fails resolves to null (never-throw, like
1074
- // agent()) so parallel/pipeline over children degrade gracefully. An unknown
1075
- // name THROWS (Effect.die → the guest call rejects → the run fails loud).
1076
- const workflowOcc = new Map<string, number>()
1077
- const workflowHook: HostFn = (nameOrScript: unknown, childArgs?: unknown, opts?: unknown) => {
1078
- const spec = String(nameOrScript)
1079
- const o = (opts ?? {}) as { workspace?: string; maxConcurrentAgents?: number }
1080
- // Content key over the SEMANTIC inputs that reach the child (spec + args).
1081
- // occ disambiguates byte-identical workflow() calls into distinct slots.
1082
- const base = createHash("sha256")
1083
- .update(JSON.stringify({ spec, args: childArgs ?? null }))
1084
- .digest("hex")
1085
- const n = workflowOcc.get(base) ?? 0
1086
- workflowOcc.set(base, n + 1)
1087
- const key = base + ":" + n
1088
- // Parent-journal hit: a completed child replays its result with NO relaunch
1089
- // (the two-level resume short-circuit — parent journal skips the whole child
1090
- // sub-run; the child's own journal would handle agent-level skip if it were
1091
- // re-run). Counts as a succeeded outcome so the live view reflects replay
1092
- // progress. The "wf:" prefix keeps this slot namespace disjoint from agent() keys.
1093
- if (journal.results.has("wf:" + key)) {
1094
- entry.succeeded++
1095
- scheduleFlush(entry)
1096
- return Promise.resolve(journal.results.get("wf:" + key))
1097
- }
1098
- const childRunID = "wf_" + createHash("sha256").update(runID + key).digest("hex")
1099
- return bridge.promise(
1100
- Effect.gen(function* () {
1101
- const childScript = isInlineScript(spec)
1102
- ? spec
1103
- : yield* Effect.promise(() => resolveWorkflowScript(spec, workspaceRoot, Instance.worktree))
1104
- if (childScript === null)
1105
- return yield* Effect.die(new Error(`${WORKFLOW_STRUCTURAL_ERROR}: unknown workflow: ${JSON.stringify(spec)}`))
1106
- // Nesting guards (T12) — LAUNCH path only (a journal HIT early-returned
1107
- // above without deriving childName/childRunID, and a cached child already
1108
- // completed in a prior pass, so re-validating would be wrong). The child's
1109
- // lineage name is its resolved saved name, or a content-hash label for an
1110
- // inline body so distinct inline children don't collide AND an inline body
1111
- // that re-invokes itself is still caught as a cycle. Over-depth and cycle
1112
- // are SCRIPT-LOGIC errors → Effect.die (fail loud), same posture as the
1113
- // unknown-name die above. The guest await rejects → the orchestrator script
1114
- // throws → the parent run fails with this message.
1115
- // NOTE: saved names key on the name alone (args-independent), so saved
1116
- // A→A with different args IS a cycle; an inline body keys on its content
1117
- // hash WHICH INCLUDES args, so inline A→A with different args is NOT a
1118
- // cycle and is bounded only by maxDepth.
1119
- const childName = isInlineScript(spec) ? "inline:" + base.slice(0, 12) : spec
1120
- if (depth + 1 > maxDepth) {
1121
- return yield* Effect.die(new Error(`${WORKFLOW_STRUCTURAL_ERROR}: workflow nesting exceeds maxDepth (${maxDepth})`))
1122
- }
1123
- if (lineage.includes(childName)) {
1124
- return yield* Effect.die(
1125
- new Error(`${WORKFLOW_STRUCTURAL_ERROR}: workflow cycle detected: ${childName} is already an ancestor`),
1126
- )
1127
- }
1128
- entry.childRunIDs.add(childRunID)
1129
- const wfNodeId = "w" + entry.structure.length
1130
- entry.structure.push({
1131
- type: "workflow",
1132
- id: wfNodeId,
1133
- phaseId: entry.currentPhaseId,
1134
- childRunID,
1135
- name: isInlineScript(spec) ? "inline" : spec,
1136
- ...(childArgs !== undefined ? { args: childArgs } : {}),
1137
- status: "running",
1138
- })
1139
- // The child is an independent sub-run: it gets its own per-run lifecycle
1140
- // cap + per-agent timeout (defaults), deliberately NOT inherited from the
1141
- // parent. Tree-wide concurrency is bounded by the global semaphore,
1142
- // not by propagating these per-run knobs.
1143
- yield* launch(
1144
- {
1145
- script: childScript,
1146
- sessionID: input.sessionID,
1147
- parentActorID: input.parentActorID,
1148
- args: childArgs,
1149
- model: input.model,
1150
- // A child may narrow its workspace to a subdir but never widen it
1151
- // beyond the parent's root — resolveInWorkspace throws on escape
1152
- // (a script-logic error → fail loud), same posture as the jail itself.
1153
- workspace: o.workspace ? resolveInWorkspace(workspaceRoot, String(o.workspace)) : workspaceRoot,
1154
- maxConcurrentAgents: o.maxConcurrentAgents,
1155
- scriptDeadlineMs: input.scriptDeadlineMs,
1156
- // Extend the nesting context for the child (T12): append this child to
1157
- // the ancestor lineage, increment depth, carry the same cap down.
1158
- lineage: [...lineage, childName],
1159
- depth: depth + 1,
1160
- maxDepth,
1161
- // A child is awaited inline here (waitFor below) and its outcome is
1162
- // consumed by the parent script — never deliver a separate inbox
1163
- // notification to the parent actor, regardless of the root run's
1164
- // sync/async mode. Only top-level async runs notify.
1165
- notifyOnTerminal: false,
1166
- },
1167
- childRunID,
1168
- isInlineScript(spec) ? "inline" : spec,
1169
- )
1170
- const childOutcome = yield* waitFor(childRunID)
1171
- const wfNode = entry.structure.find((n) => n.id === wfNodeId)
1172
- if (wfNode && wfNode.type === "workflow") wfNode.status = childOutcome.status
1173
- // Structural faults (cycle / depth / unknown-name) are workflow-wiring
1174
- // BUGS, not runtime conditions — propagate them loud instead of degrading
1175
- // to null like a child's runtime failure, so the fault surfaces at the root
1176
- // run. Each ancestor re-dies in turn; slice from the marker so the message
1177
- // doesn't accrete a "workflow script rejected:" prefix at every level.
1178
- if (childOutcome.status === "failed" && childOutcome.error.includes(WORKFLOW_STRUCTURAL_ERROR)) {
1179
- const idx = childOutcome.error.indexOf(WORKFLOW_STRUCTURAL_ERROR)
1180
- return yield* Effect.die(new Error(childOutcome.error.slice(idx)))
1181
- }
1182
- // Runtime failure (NOT structural — that path re-died above): the child's
1183
- // agents failed, it hit its deadline, or it was cancelled. workflow() still
1184
- // returns null (never-throw); this event records WHY for triage. Mirrors
1185
- // WorkflowAgentFailed. Fire-and-forget so a bus problem can't break the run.
1186
- if (childOutcome.status !== "completed") {
1187
- yield* bus
1188
- .publish(WorkflowChildFailed, {
1189
- sessionID: input.sessionID,
1190
- runID,
1191
- childRunID,
1192
- name: isInlineScript(spec) ? "inline" : spec,
1193
- status: childOutcome.status, // "failed" | "cancelled"
1194
- ...(childOutcome.status === "failed" ? { error: childOutcome.error } : {}),
1195
- })
1196
- .pipe(Effect.ignore)
1197
- }
1198
- const value = childOutcome.status === "completed" ? (childOutcome.result ?? null) : null
1199
- // Journal ONLY a successful child (null = failure → not cached → re-runs
1200
- // on resume, self-heal — same contract as agent()). Synchronous append so
1201
- // it survives a mid-run kill.
1202
- if (value !== null) {
1203
- yield* WorkflowPersistence.appendJournalSync(runID, [
1204
- { t: "agent", key: "wf:" + key, result: value, pass },
1205
- ]).pipe(Effect.ignore)
1206
- }
1207
- return value
1208
- }),
1209
- )
1210
- }
1211
-
1212
- const hooks: Record<string, HostFn> = {
1213
- agent,
1214
- phase,
1215
- log: logHook,
1216
- workflow: workflowHook,
1217
- readFile: fileHooks.readFile,
1218
- writeFile: fileHooks.writeFile,
1219
- glob: fileHooks.glob,
1220
- exists: fileHooks.exists,
1221
- }
1222
-
1223
- const work = Effect.gen(function* () {
1224
- // Object-form tryPromise: bare tryPromise wraps any rejection as an
1225
- // UnknownError whose .message is the useless "An error occurred in
1226
- // Effect.tryPromise" (the real error lands in .cause), so the failed-run
1227
- // error field / WorkflowFinished.error below would be opaque. Catching to
1228
- // the raw Error makes result.failure the sandbox Error itself, whose
1229
- // .message already carries the guest {name,message,stack} (vm.dump
1230
- // preserves it through the sandbox throw site) — a script-logic crash is
1231
- // then diagnosable from the run's error alone, no repro needed.
1232
- // Per-run PRNG seed = first 4 bytes of sha1(runID). runID is unique-per-run
1233
- // and persisted, so resume of the SAME run derives the SAME seed → guest
1234
- // Math.random replays identically (the replay invariant). Two UNRELATED runs
1235
- // of the same script get DIFFERENT runIDs → different seeds → different
1236
- // sequences, so sampling-style scripts get fresh coverage instead of
1237
- // repeating the same picks. Bun's lifetime-classify verification sample
1238
- // is the motivating use case.
1239
- const seed = createHash("sha1").update(runID).digest().readUInt32BE(0)
1240
- const result = yield* Effect.tryPromise({
1241
- try: () => evalScript(body, hooks, { deadlineMs: input.scriptDeadlineMs ?? SCRIPT_DEADLINE_MS, args: input.args, seed }),
1242
- catch: (e) => (e instanceof Error ? e : new Error(String(e))),
1243
- }).pipe(Effect.result)
1244
-
1245
- if (result._tag === "Success") {
1246
- entry.status = "completed"
1247
- yield* flushNow(entry)
1248
- yield* WorkflowPersistence.recordTerminal({ runID, status: "completed" }).pipe(Effect.ignore)
1249
- yield* Deferred.succeed(deferred, { status: "completed", result: result.success })
1250
- yield* bus.publish(WorkflowFinished, { sessionID: input.sessionID, runID, status: "completed" })
1251
- // Notify the parent so its next turn drains a completion message, the
1252
- // same way background actors notify on terminal (see actor/spawn.ts
1253
- // forkWork.notify). Fire-and-forget: a notify failure (e.g. parent row
1254
- // gone) must never fail the run, and wait-ers are already unblocked
1255
- // above by Deferred.succeed. Skipped when notifyOnTerminal === false (the
1256
- // tool's sync path returns the result inline; a notify would duplicate it).
1257
- if (input.notifyOnTerminal !== false)
1258
- yield* inbox
1259
- .send({
1260
- receiverSessionID: input.sessionID,
1261
- receiverActorID: input.parentActorID,
1262
- senderSessionID: input.sessionID,
1263
- senderActorID: "workflow",
1264
- type: "actor_notification",
1265
- content: `Workflow completed. run_id: ${runID}\n` + JSON.stringify(result.success ?? null).slice(0, 4000),
1266
- })
1267
- .pipe(Effect.ignore)
1268
- return
1269
- }
1270
- // Non-success terminal: reclaim in-flight agents + worktrees so a
1271
- // deadline-fire / script throw leaves a clean slate for a convergent
1272
- // re-run. Success path does NOT reclaim — kept worktrees are the deliverable.
1273
- yield* reclaim(entry)
1274
- const error = result.failure instanceof Error ? result.failure.message : String(result.failure)
1275
- entry.status = "failed"
1276
- log.warn("workflow run failed", { runID, error })
1277
- yield* flushNow(entry)
1278
- yield* WorkflowPersistence.recordTerminal({ runID, status: "failed", error }).pipe(Effect.ignore)
1279
- yield* Deferred.succeed(deferred, { status: "failed", error })
1280
- yield* bus.publish(WorkflowFinished, { sessionID: input.sessionID, runID, status: "failed", error })
1281
- if (input.notifyOnTerminal !== false)
1282
- yield* inbox
1283
- .send({
1284
- receiverSessionID: input.sessionID,
1285
- receiverActorID: input.parentActorID,
1286
- senderSessionID: input.sessionID,
1287
- senderActorID: "workflow",
1288
- type: "actor_notification",
1289
- content: `Workflow failed. run_id: ${runID}\nerror: ${error}`,
1290
- })
1291
- .pipe(Effect.ignore)
1292
- })
1293
-
1294
- entry.fiber = yield* work.pipe(Effect.forkIn(scope))
1295
- return { runID }
1296
- })
1297
-
1298
- const start = Effect.fn("WorkflowRuntime.start")(function* (input: StartInput) {
1299
- const parsed = parseMeta(input.script)
1300
- if (!parsed.ok) return yield* Effect.die(parsed.error)
1301
- const runID = Identifier.descending("workflow")
1302
- return yield* launch(input, runID, parsed.meta.name)
1303
- })
1304
-
1305
- const status = Effect.fn("WorkflowRuntime.status")(function* (input: { runID: string }) {
1306
- const entry = runs.get(input.runID)
1307
- if (!entry) return { status: "unknown" as const, agentCount: 0, running: 0, succeeded: 0, failed: 0 }
1308
- return {
1309
- status: entry.status,
1310
- agentCount: entry.agentCount,
1311
- running: entry.running,
1312
- succeeded: entry.succeeded,
1313
- failed: entry.failed,
1314
- ...(entry.currentPhase !== undefined ? { currentPhase: entry.currentPhase } : {}),
1315
- }
1316
- })
1317
-
1318
- const transcript = Effect.fn("WorkflowRuntime.transcript")(function* (input: { runID: string }) {
1319
- const entry = runs.get(input.runID)
1320
- return entry ? entry.transcript.slice() : []
1321
- })
1322
-
1323
- const structure = Effect.fn("WorkflowRuntime.structure")(function* (input: { runID: string }) {
1324
- const entry = runs.get(input.runID)
1325
- return { nodes: entry ? entry.structure.slice() : [] } satisfies WorkflowStructure
1326
- })
1327
-
1328
- const wait = Effect.fn("WorkflowRuntime.wait")(function* (input: { runID: string; timeoutMs?: number }) {
1329
- const entry = runs.get(input.runID)
1330
- if (!entry) return { status: "failed" as const, error: `unknown runID ${input.runID}` }
1331
- if (input.timeoutMs === undefined) return yield* Deferred.await(entry.deferred)
1332
- const raced = yield* Deferred.await(entry.deferred).pipe(
1333
- Effect.timeout(input.timeoutMs),
1334
- Effect.catchTag("TimeoutError", () => Effect.succeed(null)),
1335
- )
1336
- if (raced === null) return { status: "failed" as const, error: "workflow wait timed out" }
1337
- return raced
1338
- })
1339
-
1340
- const cancel = Effect.fn("WorkflowRuntime.cancel")(function* (input: { runID: string }) {
1341
- const entry = runs.get(input.runID)
1342
- if (!entry) return
1343
- yield* cancelEntry(entry)
1344
- })
1345
-
1346
- const list = Effect.fn("WorkflowRuntime.list")(function* (input?: { sessionID?: SessionID }) {
1347
- return yield* WorkflowPersistence.list(input)
1348
- })
1349
-
1350
- // Re-launch a persisted run under the SAME runID via the shared launch path.
1351
- // recordStart's onConflictDoUpdate flips the existing row back to "running" and
1352
- // runs.set overwrites the stale terminal entry (its old fiber is already done).
1353
- // model/concurrency/deadline are not persisted in v1 — launch applies defaults.
1354
- const resume = Effect.fn("WorkflowRuntime.resume")(function* (input: { runID: string; agentTimeoutMs?: number }) {
1355
- // SERIALIZE same-runID resume with the repo's in-process reader/writer lock
1356
- // (util/lock.ts: a module-global Map mutex). The live-guard below is a
1357
- // check-then-act (read runs.get → decide → launch) and is NOT atomic on its
1358
- // own: two concurrent resume(sameRunID) of a completed run would BOTH read
1359
- // status !== "running", BOTH pass the guard, and BOTH launch() — and launch
1360
- // does runs.set(runID, entry), so the second clobbers the first (orphaned
1361
- // fiber, raced counter flush) and both append to the same .jsonl journal.
1362
- // Holding the write lock across the guard THROUGH launch closes that window:
1363
- // the first waiter launches and flips the entry to "running" before releasing,
1364
- // so the second waiter sees status "running" at the guard and bails. We do NOT
1365
- // hold it for the whole run (launch forks the work fiber and returns once the
1366
- // entry is "running") — only the resume decision + entry creation is serialized.
1367
- // LIMITATION: this is in-process only. Two SEPARATE processes resuming the same
1368
- // runID against the same DB (e.g. two server instances) are NOT covered — there
1369
- // is no shared/file-lock infra in this repo to reuse, and cross-process resume
1370
- // is out of scope for MR104 P2-1.
1371
- // Acquire as a JS Promise<Disposable> (Lock.write is promise-based; there is no
1372
- // existing Effect-context consumer to mirror, so we bridge via Effect.promise),
1373
- // and release in Effect.ensuring so it ALWAYS releases — even if load /
1374
- // readScript / launch throws — otherwise a failed resume would deadlock every
1375
- // future resume of this runID.
1376
- const lock = yield* Effect.promise(() => Lock.write("workflow-resume:" + input.runID))
1377
- return yield* Effect.gen(function* () {
1378
- // Refuse to resume a run that is still LIVE in this process: launch would
1379
- // runs.set() over the live entry, orphaning the running fiber (double parent
1380
- // notify, raced counter flush, unreclaimable by cancel). The DB row is NOT the
1381
- // signal — a process-exited run still reads "running" there and IS resumable;
1382
- // a live `runs` entry means a fiber is actually executing here.
1383
- const live = runs.get(input.runID)
1384
- if (live && live.status === "running") return { runID: input.runID, resumed: false }
1385
- const row = yield* WorkflowPersistence.load(input.runID)
1386
- if (!row) return { runID: input.runID, resumed: false }
1387
- // readScript is Effect.promise — a missing file rejects as a DEFECT, which
1388
- // Effect.exit captures (Effect.result/option/catchAll do not catch defects in
1389
- // this effect version). Treat a missing or empty script as not-resumable.
1390
- const read = yield* WorkflowPersistence.readScript(input.runID).pipe(Effect.exit)
1391
- const script = Exit.isSuccess(read) ? read.value : ""
1392
- if (!script) return { runID: input.runID, resumed: false }
1393
- // Script-change invalidation (MR104 P1-2): the journal keys results by
1394
- // {prompt,agentType,model,schema,phase}+occ, NOT by the script body — so a
1395
- // between-cycle edit would replay OLD results onto NEW code paths (silent
1396
- // divergence). Compare the persisted sha (stamped at the prior launch) to the
1397
- // CURRENT script's sha; on any mismatch — including a null stored sha (a run
1398
- // recorded before this column existed → "unknown" → treat as changed) — pass
1399
- // freshJournal so launch truncates the stale journal and runs from scratch,
1400
- // re-stamping the new sha for the next resume. A match → normal replay.
1401
- const currentSha = createHash("sha256").update(script).digest("hex")
1402
- const freshJournal = row.scriptSha !== currentSha
1403
- yield* launch(
1404
- {
1405
- script,
1406
- sessionID: row.sessionID,
1407
- parentActorID: row.parentActorID ?? "main",
1408
- args: row.args,
1409
- freshJournal,
1410
- // Per-agent timeout: caller's explicit override > persisted value > undefined (off).
1411
- // The row's agent_timeout_ms was stamped at the original launch (or last resume
1412
- // that supplied an explicit override), so a UI-side resume that doesn't know
1413
- // the original launch params (e.g. TUI's /workflows resume) inherits the
1414
- // original timeout instead of silently dropping to unbounded — which used to
1415
- // let a wedged sleepy TTFT stall the resumed run forever.
1416
- agentTimeoutMs: input.agentTimeoutMs ?? row.agentTimeoutMs,
1417
- },
1418
- input.runID,
1419
- row.name,
1420
- )
1421
- return { runID: input.runID, resumed: true }
1422
- }).pipe(Effect.ensuring(Effect.sync(() => lock[Symbol.dispose]())))
1423
- })
1424
-
1425
- const impl = Service.of({ start, status, wait, transcript, structure, cancel, list, resume })
1426
- // Late-bind the impl so the `workflow` tool can resolve it without forcing a
1427
- // WorkflowRuntime.Service requirement onto ToolRegistry.layer. See
1428
- // runtime-ref.ts for rationale.
1429
- workflowRef.current = impl
1430
- yield* Effect.addFinalizer(() =>
1431
- Effect.sync(() => {
1432
- if (workflowRef.current === impl) workflowRef.current = undefined
1433
- }),
1434
- )
1435
- return impl
1436
- }),
1437
- )
1438
-
1439
- export const defaultLayer = layer.pipe(
1440
- Layer.provide(Bus.defaultLayer),
1441
- Layer.provide(Inbox.defaultLayer),
1442
- Layer.provide(Worktree.defaultLayer),
1443
- Layer.provide(Provider.defaultLayer),
1444
- Layer.provide(Config.defaultLayer),
1445
- )
1446
-
1447
- export * as WorkflowRuntime from "./runtime"