tulingcode 0.1.0

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 (1119) hide show
  1. package/AGENTS.md +134 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/tuling +179 -0
  5. package/bunfig.toml +7 -0
  6. package/drizzle.config.ts +10 -0
  7. package/git +0 -0
  8. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  9. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  10. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  11. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  12. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  13. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  14. package/migration/20260225215848_workspace/migration.sql +7 -0
  15. package/migration/20260225215848_workspace/snapshot.json +959 -0
  16. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  17. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  18. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  19. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  20. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  21. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  22. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  23. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  24. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  25. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  26. package/migration/20260323234822_events/migration.sql +13 -0
  27. package/migration/20260323234822_events/snapshot.json +1271 -0
  28. package/migration/20260410174513_workspace-name/migration.sql +16 -0
  29. package/migration/20260410174513_workspace-name/snapshot.json +1271 -0
  30. package/migration/20260413175956_chief_energizer/migration.sql +13 -0
  31. package/migration/20260413175956_chief_energizer/snapshot.json +1399 -0
  32. package/migration/20260422160000_context_inheritance/migration.sql +3 -0
  33. package/migration/20260422170000_task_registry/migration.sql +18 -0
  34. package/migration/20260423145421_remove_session_entry/migration.sql +4 -0
  35. package/migration/20260515000000_actor_rename/migration.sql +7 -0
  36. package/migration/20260515010000_memory_fts/migration.sql +33 -0
  37. package/migration/20260515020000_user_task/migration.sql +29 -0
  38. package/migration/20260519000000_last_checkpoint_message_id/migration.sql +1 -0
  39. package/migration/20260521000000_message_agent_id/migration.sql +2 -0
  40. package/migration/20260521000100_actor_registry_v6/migration.sql +25 -0
  41. package/migration/20260521010000_memory_fts_v6/migration.sql +33 -0
  42. package/migration/20260521020000_memory_fts_triggers/migration.sql +17 -0
  43. package/migration/20260526000000_agent_id_main/migration.sql +14 -0
  44. package/migration/20260527000000_actor_lifecycle/migration.sql +8 -0
  45. package/migration/20260527000100_inbox/migration.sql +12 -0
  46. package/migration/20260529000000_task_todo_redesign/migration.sql +16 -0
  47. package/migration/20260603000000_task_in_progress_owner/migration.sql +1 -0
  48. package/migration/20260603000000_workflow_run/migration.sql +17 -0
  49. package/migration/20260604000000_workflow_script_sha/migration.sql +1 -0
  50. package/migration/20260608000000_claude_import/migration.sql +7 -0
  51. package/migration/20260608010000_claude_import_message_ids/migration.sql +1 -0
  52. package/migration/20260609000000_history_fts/migration.sql +29 -0
  53. package/migration/20260609230000_workflow_agent_timeout/migration.sql +1 -0
  54. package/package.json +196 -0
  55. package/parsers-config.ts +290 -0
  56. package/script/build.ts +267 -0
  57. package/script/check-migrations.ts +16 -0
  58. package/script/fix-node-pty.ts +28 -0
  59. package/script/generate.ts +23 -0
  60. package/script/postinstall.mjs +102 -0
  61. package/script/publish.ts +60 -0
  62. package/script/run-workspace-server +106 -0
  63. package/script/schema.ts +63 -0
  64. package/script/time.ts +6 -0
  65. package/script/trace-imports.ts +153 -0
  66. package/script/upgrade-opentui.ts +64 -0
  67. package/src/account/account.sql.ts +39 -0
  68. package/src/account/account.ts +456 -0
  69. package/src/account/repo.ts +166 -0
  70. package/src/account/schema.ts +99 -0
  71. package/src/account/url.ts +8 -0
  72. package/src/acp/README.md +174 -0
  73. package/src/acp/agent.ts +1783 -0
  74. package/src/acp/session.ts +116 -0
  75. package/src/acp/types.ts +24 -0
  76. package/src/actor/actor.sql.ts +38 -0
  77. package/src/actor/events.ts +67 -0
  78. package/src/actor/index.ts +2 -0
  79. package/src/actor/registry.ts +412 -0
  80. package/src/actor/return-header.ts +24 -0
  81. package/src/actor/schema.ts +47 -0
  82. package/src/actor/spawn-ref.ts +16 -0
  83. package/src/actor/spawn.ts +741 -0
  84. package/src/actor/turn.ts +49 -0
  85. package/src/actor/waiter.ts +166 -0
  86. package/src/agent/agent.ts +554 -0
  87. package/src/agent/config.ts +5 -0
  88. package/src/agent/generate.txt +75 -0
  89. package/src/agent/prompt/checkpoint-writer.txt +167 -0
  90. package/src/agent/prompt/compaction.txt +9 -0
  91. package/src/agent/prompt/distill.txt +199 -0
  92. package/src/agent/prompt/dream.txt +155 -0
  93. package/src/agent/prompt/explore.txt +18 -0
  94. package/src/agent/prompt/summary.txt +11 -0
  95. package/src/agent/prompt/title.txt +44 -0
  96. package/src/audio.d.ts +9 -0
  97. package/src/auth/index.ts +97 -0
  98. package/src/bus/bus-event.ts +33 -0
  99. package/src/bus/global.ts +12 -0
  100. package/src/bus/index.ts +193 -0
  101. package/src/cli/bootstrap.ts +33 -0
  102. package/src/cli/cmd/account.ts +258 -0
  103. package/src/cli/cmd/acp.ts +70 -0
  104. package/src/cli/cmd/agent.ts +248 -0
  105. package/src/cli/cmd/cmd.ts +7 -0
  106. package/src/cli/cmd/db.ts +120 -0
  107. package/src/cli/cmd/debug/agent.ts +192 -0
  108. package/src/cli/cmd/debug/config.ts +17 -0
  109. package/src/cli/cmd/debug/file.ts +100 -0
  110. package/src/cli/cmd/debug/index.ts +48 -0
  111. package/src/cli/cmd/debug/lsp.ts +61 -0
  112. package/src/cli/cmd/debug/ripgrep.ts +105 -0
  113. package/src/cli/cmd/debug/scrap.ts +16 -0
  114. package/src/cli/cmd/debug/skill.ts +23 -0
  115. package/src/cli/cmd/debug/snapshot.ts +53 -0
  116. package/src/cli/cmd/export.ts +306 -0
  117. package/src/cli/cmd/generate.ts +50 -0
  118. package/src/cli/cmd/github.ts +1647 -0
  119. package/src/cli/cmd/import.ts +208 -0
  120. package/src/cli/cmd/mcp.ts +812 -0
  121. package/src/cli/cmd/models.ts +88 -0
  122. package/src/cli/cmd/plug.ts +233 -0
  123. package/src/cli/cmd/pr.ts +138 -0
  124. package/src/cli/cmd/providers.ts +705 -0
  125. package/src/cli/cmd/run-completion.ts +77 -0
  126. package/src/cli/cmd/run.ts +694 -0
  127. package/src/cli/cmd/serve.ts +21 -0
  128. package/src/cli/cmd/session.ts +181 -0
  129. package/src/cli/cmd/stats.ts +413 -0
  130. package/src/cli/cmd/tui/app.tsx +1130 -0
  131. package/src/cli/cmd/tui/asset/TEN_VAD_LICENSE +12 -0
  132. package/src/cli/cmd/tui/asset/charge.wav +0 -0
  133. package/src/cli/cmd/tui/asset/pulse-a.wav +0 -0
  134. package/src/cli/cmd/tui/asset/pulse-b.wav +0 -0
  135. package/src/cli/cmd/tui/asset/pulse-c.wav +0 -0
  136. package/src/cli/cmd/tui/asset/ten_vad.wasm +0 -0
  137. package/src/cli/cmd/tui/asset/ten_vad_loader.js +30 -0
  138. package/src/cli/cmd/tui/attach.ts +83 -0
  139. package/src/cli/cmd/tui/component/background-image.tsx +150 -0
  140. package/src/cli/cmd/tui/component/bg-pulse.tsx +130 -0
  141. package/src/cli/cmd/tui/component/border.tsx +21 -0
  142. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  143. package/src/cli/cmd/tui/component/dialog-command.tsx +208 -0
  144. package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
  145. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +157 -0
  146. package/src/cli/cmd/tui/component/dialog-image-list.tsx +111 -0
  147. package/src/cli/cmd/tui/component/dialog-logo-design.tsx +37 -0
  148. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  149. package/src/cli/cmd/tui/component/dialog-mimo-login.tsx +224 -0
  150. package/src/cli/cmd/tui/component/dialog-model.tsx +253 -0
  151. package/src/cli/cmd/tui/component/dialog-provider.tsx +490 -0
  152. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +101 -0
  153. package/src/cli/cmd/tui/component/dialog-session-list.tsx +269 -0
  154. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  155. package/src/cli/cmd/tui/component/dialog-skill.tsx +42 -0
  156. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  157. package/src/cli/cmd/tui/component/dialog-status.tsx +170 -0
  158. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  159. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  160. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  161. package/src/cli/cmd/tui/component/dialog-workflows.tsx +62 -0
  162. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +289 -0
  163. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +81 -0
  164. package/src/cli/cmd/tui/component/dialog-worktree.tsx +90 -0
  165. package/src/cli/cmd/tui/component/error-component.tsx +92 -0
  166. package/src/cli/cmd/tui/component/logo.tsx +961 -0
  167. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  168. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +684 -0
  169. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  170. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  171. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  172. package/src/cli/cmd/tui/component/prompt/index.tsx +1812 -0
  173. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  174. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  175. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  176. package/src/cli/cmd/tui/component/starry-background.tsx +305 -0
  177. package/src/cli/cmd/tui/component/startup-loading.tsx +67 -0
  178. package/src/cli/cmd/tui/component/task-item.tsx +63 -0
  179. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  180. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  181. package/src/cli/cmd/tui/config/cwd.ts +5 -0
  182. package/src/cli/cmd/tui/config/tui-migrate.ts +151 -0
  183. package/src/cli/cmd/tui/config/tui-schema.ts +38 -0
  184. package/src/cli/cmd/tui/config/tui.ts +219 -0
  185. package/src/cli/cmd/tui/context/args.tsx +16 -0
  186. package/src/cli/cmd/tui/context/directory.ts +15 -0
  187. package/src/cli/cmd/tui/context/event.ts +45 -0
  188. package/src/cli/cmd/tui/context/exit.tsx +65 -0
  189. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  190. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  191. package/src/cli/cmd/tui/context/kv.tsx +76 -0
  192. package/src/cli/cmd/tui/context/language.tsx +91 -0
  193. package/src/cli/cmd/tui/context/local.tsx +455 -0
  194. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  195. package/src/cli/cmd/tui/context/project.tsx +109 -0
  196. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  197. package/src/cli/cmd/tui/context/route.tsx +61 -0
  198. package/src/cli/cmd/tui/context/sdk.tsx +150 -0
  199. package/src/cli/cmd/tui/context/sync.tsx +828 -0
  200. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  201. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  202. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  203. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +230 -0
  204. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +230 -0
  205. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  206. package/src/cli/cmd/tui/context/theme/cobalt2.json +225 -0
  207. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  208. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  209. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  210. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  211. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  212. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  213. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  214. package/src/cli/cmd/tui/context/theme/lucent-orng.json +234 -0
  215. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  216. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  217. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  218. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  219. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  220. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  221. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  222. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  223. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  224. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  225. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  226. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  227. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  228. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  229. package/src/cli/cmd/tui/context/theme/tulingcode.json +245 -0
  230. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  231. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  232. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  233. package/src/cli/cmd/tui/context/theme.tsx +1298 -0
  234. package/src/cli/cmd/tui/context/thinking.ts +48 -0
  235. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  236. package/src/cli/cmd/tui/event.ts +56 -0
  237. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
  238. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +193 -0
  239. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +54 -0
  240. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +114 -0
  241. package/src/cli/cmd/tui/feature-plugins/sidebar/cwd.tsx +45 -0
  242. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  243. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  244. package/src/cli/cmd/tui/feature-plugins/sidebar/goal.tsx +84 -0
  245. package/src/cli/cmd/tui/feature-plugins/sidebar/instructions.tsx +54 -0
  246. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  247. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +98 -0
  248. package/src/cli/cmd/tui/feature-plugins/sidebar/task.tsx +95 -0
  249. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +51 -0
  250. package/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts +31 -0
  251. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +274 -0
  252. package/src/cli/cmd/tui/i18n/en.ts +397 -0
  253. package/src/cli/cmd/tui/i18n/es.ts +433 -0
  254. package/src/cli/cmd/tui/i18n/fr.ts +440 -0
  255. package/src/cli/cmd/tui/i18n/ja.ts +392 -0
  256. package/src/cli/cmd/tui/i18n/locales.ts +82 -0
  257. package/src/cli/cmd/tui/i18n/ru.ts +452 -0
  258. package/src/cli/cmd/tui/i18n/zh.ts +390 -0
  259. package/src/cli/cmd/tui/i18n/zht.ts +360 -0
  260. package/src/cli/cmd/tui/layer.ts +6 -0
  261. package/src/cli/cmd/tui/plugin/api.tsx +402 -0
  262. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  263. package/src/cli/cmd/tui/plugin/internal.ts +35 -0
  264. package/src/cli/cmd/tui/plugin/runtime.ts +1030 -0
  265. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  266. package/src/cli/cmd/tui/routes/home.tsx +165 -0
  267. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
  268. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +116 -0
  269. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +47 -0
  270. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  271. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  272. package/src/cli/cmd/tui/routes/session/index.tsx +2532 -0
  273. package/src/cli/cmd/tui/routes/session/permission.tsx +691 -0
  274. package/src/cli/cmd/tui/routes/session/question.tsx +488 -0
  275. package/src/cli/cmd/tui/routes/session/sidebar.tsx +97 -0
  276. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +142 -0
  277. package/src/cli/cmd/tui/thread.ts +246 -0
  278. package/src/cli/cmd/tui/ui/dialog-alert.tsx +61 -0
  279. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +95 -0
  280. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +223 -0
  281. package/src/cli/cmd/tui/ui/dialog-help.tsx +42 -0
  282. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +123 -0
  283. package/src/cli/cmd/tui/ui/dialog-select.tsx +452 -0
  284. package/src/cli/cmd/tui/ui/dialog.tsx +207 -0
  285. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  286. package/src/cli/cmd/tui/ui/spinner.ts +378 -0
  287. package/src/cli/cmd/tui/ui/toast.tsx +102 -0
  288. package/src/cli/cmd/tui/util/clipboard.ts +203 -0
  289. package/src/cli/cmd/tui/util/editor.ts +35 -0
  290. package/src/cli/cmd/tui/util/image-protocol.ts +35 -0
  291. package/src/cli/cmd/tui/util/index.ts +6 -0
  292. package/src/cli/cmd/tui/util/model.ts +23 -0
  293. package/src/cli/cmd/tui/util/provider-origin.ts +7 -0
  294. package/src/cli/cmd/tui/util/revert-diff.ts +18 -0
  295. package/src/cli/cmd/tui/util/scroll.ts +23 -0
  296. package/src/cli/cmd/tui/util/selection.ts +23 -0
  297. package/src/cli/cmd/tui/util/signal.ts +41 -0
  298. package/src/cli/cmd/tui/util/sound.ts +154 -0
  299. package/src/cli/cmd/tui/util/system-locale.ts +209 -0
  300. package/src/cli/cmd/tui/util/terminal.ts +110 -0
  301. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  302. package/src/cli/cmd/tui/util/vad.ts +229 -0
  303. package/src/cli/cmd/tui/util/voice.ts +360 -0
  304. package/src/cli/cmd/tui/win32.ts +130 -0
  305. package/src/cli/cmd/tui/worker.ts +104 -0
  306. package/src/cli/cmd/uninstall.ts +351 -0
  307. package/src/cli/cmd/upgrade.ts +79 -0
  308. package/src/cli/cmd/web.ts +81 -0
  309. package/src/cli/effect/prompt.ts +25 -0
  310. package/src/cli/error.ts +82 -0
  311. package/src/cli/heap.ts +59 -0
  312. package/src/cli/i18n.ts +15 -0
  313. package/src/cli/logo.ts +53 -0
  314. package/src/cli/network.ts +62 -0
  315. package/src/cli/ui.ts +133 -0
  316. package/src/cli/upgrade.ts +41 -0
  317. package/src/command/index.ts +276 -0
  318. package/src/command/template/initialize.txt +66 -0
  319. package/src/command/template/review.txt +101 -0
  320. package/src/config/agent.ts +197 -0
  321. package/src/config/command.ts +69 -0
  322. package/src/config/config.ts +1024 -0
  323. package/src/config/console-state.ts +16 -0
  324. package/src/config/entry-name.ts +16 -0
  325. package/src/config/error.ts +21 -0
  326. package/src/config/formatter.ts +17 -0
  327. package/src/config/history.ts +21 -0
  328. package/src/config/index.ts +16 -0
  329. package/src/config/keybinds.ts +127 -0
  330. package/src/config/layout.ts +10 -0
  331. package/src/config/lsp.ts +45 -0
  332. package/src/config/managed.ts +70 -0
  333. package/src/config/markdown.ts +97 -0
  334. package/src/config/mcp.ts +172 -0
  335. package/src/config/model-id.ts +14 -0
  336. package/src/config/parse.ts +44 -0
  337. package/src/config/paths.ts +73 -0
  338. package/src/config/permission.ts +76 -0
  339. package/src/config/plugin.ts +88 -0
  340. package/src/config/provider.ts +118 -0
  341. package/src/config/server.ts +20 -0
  342. package/src/config/skills.ts +16 -0
  343. package/src/config/variable.ts +90 -0
  344. package/src/control-plane/adaptors/index.ts +52 -0
  345. package/src/control-plane/adaptors/worktree.ts +47 -0
  346. package/src/control-plane/dev/debug-workspace-plugin.ts +73 -0
  347. package/src/control-plane/schema.ts +19 -0
  348. package/src/control-plane/sse.ts +66 -0
  349. package/src/control-plane/types.ts +34 -0
  350. package/src/control-plane/util.ts +37 -0
  351. package/src/control-plane/workspace-context.ts +26 -0
  352. package/src/control-plane/workspace.sql.ts +17 -0
  353. package/src/control-plane/workspace.ts +615 -0
  354. package/src/effect/app-runtime.ts +146 -0
  355. package/src/effect/bootstrap-runtime.ts +33 -0
  356. package/src/effect/bridge.ts +48 -0
  357. package/src/effect/cross-spawn-spawner.ts +514 -0
  358. package/src/effect/index.ts +5 -0
  359. package/src/effect/instance-ref.ts +11 -0
  360. package/src/effect/instance-registry.ts +12 -0
  361. package/src/effect/instance-state.ts +81 -0
  362. package/src/effect/logger.ts +73 -0
  363. package/src/effect/memo-map.ts +3 -0
  364. package/src/effect/observability.ts +107 -0
  365. package/src/effect/run-service.ts +52 -0
  366. package/src/effect/runner.ts +210 -0
  367. package/src/effect/runtime.ts +19 -0
  368. package/src/env/index.ts +37 -0
  369. package/src/file/ignore.ts +81 -0
  370. package/src/file/index.ts +664 -0
  371. package/src/file/protected.ts +59 -0
  372. package/src/file/ripgrep.ts +485 -0
  373. package/src/file/watcher.ts +163 -0
  374. package/src/flag/flag.ts +164 -0
  375. package/src/format/formatter.ts +403 -0
  376. package/src/format/index.ts +203 -0
  377. package/src/git/index.ts +260 -0
  378. package/src/global/index.ts +54 -0
  379. package/src/history/backfill.ts +162 -0
  380. package/src/history/extract.ts +67 -0
  381. package/src/history/fts-query.ts +15 -0
  382. package/src/history/fts.sql.ts +20 -0
  383. package/src/history/index.ts +10 -0
  384. package/src/history/resolve.ts +65 -0
  385. package/src/history/service.ts +258 -0
  386. package/src/history/writer.ts +112 -0
  387. package/src/id/id.ts +87 -0
  388. package/src/ide/index.ts +73 -0
  389. package/src/inbox/inbox-ref.ts +38 -0
  390. package/src/inbox/inbox.sql.ts +26 -0
  391. package/src/inbox/inbox.ts +223 -0
  392. package/src/inbox/index.ts +3 -0
  393. package/src/inbox/render.ts +40 -0
  394. package/src/index.ts +260 -0
  395. package/src/installation/index.ts +351 -0
  396. package/src/installation/version.ts +8 -0
  397. package/src/lsp/client.ts +249 -0
  398. package/src/lsp/diagnostic.ts +29 -0
  399. package/src/lsp/index.ts +3 -0
  400. package/src/lsp/language.ts +120 -0
  401. package/src/lsp/launch.ts +21 -0
  402. package/src/lsp/lsp.ts +519 -0
  403. package/src/lsp/server.ts +1956 -0
  404. package/src/mcp/auth.ts +144 -0
  405. package/src/mcp/index.ts +944 -0
  406. package/src/mcp/oauth-callback.ts +232 -0
  407. package/src/mcp/oauth-provider.ts +214 -0
  408. package/src/memory/fts-query.ts +37 -0
  409. package/src/memory/fts.sql.ts +19 -0
  410. package/src/memory/index.ts +1 -0
  411. package/src/memory/paths.ts +116 -0
  412. package/src/memory/reconcile.ts +144 -0
  413. package/src/memory/service.ts +144 -0
  414. package/src/metrics/client.ts +40 -0
  415. package/src/metrics/event.ts +43 -0
  416. package/src/metrics/index.ts +5 -0
  417. package/src/metrics/installation.ts +18 -0
  418. package/src/metrics/subscriber.ts +58 -0
  419. package/src/metrics/util.ts +9 -0
  420. package/src/node.ts +6 -0
  421. package/src/npm/config.ts +0 -0
  422. package/src/npm/index.ts +293 -0
  423. package/src/npmcli-config.d.ts +43 -0
  424. package/src/patch/index.ts +680 -0
  425. package/src/permission/arity.ts +163 -0
  426. package/src/permission/evaluate.ts +15 -0
  427. package/src/permission/index.ts +379 -0
  428. package/src/permission/schema.ts +17 -0
  429. package/src/plugin/checkpoint-splitover.ts +60 -0
  430. package/src/plugin/cloud-ai.ts +329 -0
  431. package/src/plugin/cloudflare.ts +76 -0
  432. package/src/plugin/codex.ts +607 -0
  433. package/src/plugin/github-copilot/copilot.ts +368 -0
  434. package/src/plugin/github-copilot/models.ts +153 -0
  435. package/src/plugin/index.ts +493 -0
  436. package/src/plugin/install.ts +439 -0
  437. package/src/plugin/loader.ts +216 -0
  438. package/src/plugin/matcher.ts +33 -0
  439. package/src/plugin/meta.ts +188 -0
  440. package/src/plugin/mimo-free.ts +153 -0
  441. package/src/plugin/mimo.ts +124 -0
  442. package/src/plugin/shared.ts +323 -0
  443. package/src/plugin/subagent-progress-checker.ts +147 -0
  444. package/src/project/bootstrap.ts +59 -0
  445. package/src/project/index.ts +2 -0
  446. package/src/project/instance.ts +190 -0
  447. package/src/project/project-id.ts +48 -0
  448. package/src/project/project.sql.ts +16 -0
  449. package/src/project/project.ts +501 -0
  450. package/src/project/schema.ts +15 -0
  451. package/src/project/vcs.ts +227 -0
  452. package/src/provider/auth.ts +234 -0
  453. package/src/provider/error.ts +216 -0
  454. package/src/provider/index.ts +5 -0
  455. package/src/provider/models.ts +180 -0
  456. package/src/provider/provider.ts +1782 -0
  457. package/src/provider/schema.ts +36 -0
  458. package/src/provider/sdk/copilot/README.md +5 -0
  459. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  460. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  461. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  462. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  463. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  464. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  465. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  466. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  467. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  468. package/src/provider/sdk/copilot/index.ts +2 -0
  469. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  470. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  471. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  472. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  473. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  474. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  475. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1770 -0
  476. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  477. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  478. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  479. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  480. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  481. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  482. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  483. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  484. package/src/provider/transform.ts +1322 -0
  485. package/src/pty/index.ts +364 -0
  486. package/src/pty/pty.bun.ts +26 -0
  487. package/src/pty/pty.node.ts +27 -0
  488. package/src/pty/pty.ts +25 -0
  489. package/src/pty/schema.ts +17 -0
  490. package/src/question/index.ts +252 -0
  491. package/src/question/schema.ts +17 -0
  492. package/src/server/adapter.bun.ts +40 -0
  493. package/src/server/adapter.node.ts +66 -0
  494. package/src/server/adapter.ts +21 -0
  495. package/src/server/error.ts +53 -0
  496. package/src/server/event.ts +7 -0
  497. package/src/server/fence.ts +81 -0
  498. package/src/server/mdns.ts +60 -0
  499. package/src/server/middleware.ts +92 -0
  500. package/src/server/projectors.ts +28 -0
  501. package/src/server/proxy.ts +171 -0
  502. package/src/server/routes/control/index.ts +160 -0
  503. package/src/server/routes/control/workspace.ts +203 -0
  504. package/src/server/routes/global.ts +287 -0
  505. package/src/server/routes/instance/bash-interactive.ts +82 -0
  506. package/src/server/routes/instance/config.ts +89 -0
  507. package/src/server/routes/instance/event.ts +88 -0
  508. package/src/server/routes/instance/experimental.ts +408 -0
  509. package/src/server/routes/instance/file.ts +190 -0
  510. package/src/server/routes/instance/httpapi/config.ts +51 -0
  511. package/src/server/routes/instance/httpapi/permission.ts +72 -0
  512. package/src/server/routes/instance/httpapi/project.ts +62 -0
  513. package/src/server/routes/instance/httpapi/provider.ts +142 -0
  514. package/src/server/routes/instance/httpapi/question.ts +121 -0
  515. package/src/server/routes/instance/httpapi/server.ts +136 -0
  516. package/src/server/routes/instance/index.ts +301 -0
  517. package/src/server/routes/instance/mcp.ts +260 -0
  518. package/src/server/routes/instance/middleware.ts +35 -0
  519. package/src/server/routes/instance/permission.ts +73 -0
  520. package/src/server/routes/instance/project.ts +122 -0
  521. package/src/server/routes/instance/provider.ts +158 -0
  522. package/src/server/routes/instance/pty.ts +247 -0
  523. package/src/server/routes/instance/question.ts +162 -0
  524. package/src/server/routes/instance/session.ts +1296 -0
  525. package/src/server/routes/instance/sync.ts +143 -0
  526. package/src/server/routes/instance/trace.ts +59 -0
  527. package/src/server/routes/instance/tui.ts +384 -0
  528. package/src/server/routes/instance/workflows.ts +72 -0
  529. package/src/server/routes/ui.ts +55 -0
  530. package/src/server/server.ts +136 -0
  531. package/src/server/workspace.ts +122 -0
  532. package/src/session/auto-dream.ts +123 -0
  533. package/src/session/boundary.ts +77 -0
  534. package/src/session/budgeted-read.ts +118 -0
  535. package/src/session/checkpoint-align.ts +29 -0
  536. package/src/session/checkpoint-context.ts +36 -0
  537. package/src/session/checkpoint-paths.ts +86 -0
  538. package/src/session/checkpoint-progress-reconcile.ts +111 -0
  539. package/src/session/checkpoint-retry.ts +192 -0
  540. package/src/session/checkpoint-templates.ts +114 -0
  541. package/src/session/checkpoint-validator.ts +259 -0
  542. package/src/session/checkpoint.ts +1478 -0
  543. package/src/session/classify.ts +92 -0
  544. package/src/session/claude-import.sql.ts +13 -0
  545. package/src/session/claude-import.ts +379 -0
  546. package/src/session/compaction.ts +543 -0
  547. package/src/session/goal.ts +232 -0
  548. package/src/session/index.ts +1 -0
  549. package/src/session/instruction.ts +276 -0
  550. package/src/session/last-message-info.ts +32 -0
  551. package/src/session/llm-request-prefix.ts +82 -0
  552. package/src/session/llm.ts +735 -0
  553. package/src/session/max-mode.ts +397 -0
  554. package/src/session/message-v2.ts +1136 -0
  555. package/src/session/message.ts +191 -0
  556. package/src/session/overflow.ts +53 -0
  557. package/src/session/prefix-capture-ref.ts +48 -0
  558. package/src/session/processor.ts +962 -0
  559. package/src/session/projectors.ts +137 -0
  560. package/src/session/prompt/anthropic.txt +154 -0
  561. package/src/session/prompt/beast.txt +155 -0
  562. package/src/session/prompt/build-switch.txt +5 -0
  563. package/src/session/prompt/codex.txt +79 -0
  564. package/src/session/prompt/compose.txt +115 -0
  565. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  566. package/src/session/prompt/default.txt +151 -0
  567. package/src/session/prompt/gemini.txt +155 -0
  568. package/src/session/prompt/gpt.txt +107 -0
  569. package/src/session/prompt/kimi.txt +95 -0
  570. package/src/session/prompt/max-steps.txt +16 -0
  571. package/src/session/prompt/trinity.txt +97 -0
  572. package/src/session/prompt.ts +3355 -0
  573. package/src/session/prune.ts +481 -0
  574. package/src/session/retry.ts +166 -0
  575. package/src/session/revert.ts +161 -0
  576. package/src/session/run-state.ts +135 -0
  577. package/src/session/schema.ts +36 -0
  578. package/src/session/session.sql.ts +110 -0
  579. package/src/session/session.ts +908 -0
  580. package/src/session/status.ts +89 -0
  581. package/src/session/summary.ts +163 -0
  582. package/src/session/system.ts +86 -0
  583. package/src/session/todo.ts +77 -0
  584. package/src/share/index.ts +2 -0
  585. package/src/share/session.ts +57 -0
  586. package/src/share/share-next.ts +381 -0
  587. package/src/share/share.sql.ts +13 -0
  588. package/src/shell/shell.ts +110 -0
  589. package/src/skill/compose/.bundle/ask/SKILL.md +58 -0
  590. package/src/skill/compose/.bundle/brainstorm/SKILL.md +220 -0
  591. package/src/skill/compose/.bundle/brainstorm/scripts/frame-template.html +214 -0
  592. package/src/skill/compose/.bundle/brainstorm/scripts/helper.js +88 -0
  593. package/src/skill/compose/.bundle/brainstorm/scripts/server.cjs +354 -0
  594. package/src/skill/compose/.bundle/brainstorm/scripts/start-server.sh +148 -0
  595. package/src/skill/compose/.bundle/brainstorm/scripts/stop-server.sh +56 -0
  596. package/src/skill/compose/.bundle/brainstorm/spec-document-reviewer-prompt.md +50 -0
  597. package/src/skill/compose/.bundle/brainstorm/visual-companion.md +287 -0
  598. package/src/skill/compose/.bundle/debug/CREATION-LOG.md +119 -0
  599. package/src/skill/compose/.bundle/debug/SKILL.md +297 -0
  600. package/src/skill/compose/.bundle/debug/condition-based-waiting-example.ts +158 -0
  601. package/src/skill/compose/.bundle/debug/condition-based-waiting.md +115 -0
  602. package/src/skill/compose/.bundle/debug/defense-in-depth.md +122 -0
  603. package/src/skill/compose/.bundle/debug/find-polluter.sh +63 -0
  604. package/src/skill/compose/.bundle/debug/root-cause-tracing.md +169 -0
  605. package/src/skill/compose/.bundle/debug/test-academic.md +14 -0
  606. package/src/skill/compose/.bundle/debug/test-pressure-1.md +58 -0
  607. package/src/skill/compose/.bundle/debug/test-pressure-2.md +68 -0
  608. package/src/skill/compose/.bundle/debug/test-pressure-3.md +69 -0
  609. package/src/skill/compose/.bundle/execute/SKILL.md +71 -0
  610. package/src/skill/compose/.bundle/feedback/SKILL.md +214 -0
  611. package/src/skill/compose/.bundle/merge/SKILL.md +252 -0
  612. package/src/skill/compose/.bundle/new-skill/SKILL.md +656 -0
  613. package/src/skill/compose/.bundle/new-skill/anthropic-best-practices.md +1150 -0
  614. package/src/skill/compose/.bundle/new-skill/examples/CLAUDE_MD_TESTING.md +189 -0
  615. package/src/skill/compose/.bundle/new-skill/graphviz-conventions.dot +172 -0
  616. package/src/skill/compose/.bundle/new-skill/persuasion-principles.md +187 -0
  617. package/src/skill/compose/.bundle/new-skill/render-graphs.js +168 -0
  618. package/src/skill/compose/.bundle/new-skill/testing-skills-with-subagents.md +384 -0
  619. package/src/skill/compose/.bundle/parallel/SKILL.md +182 -0
  620. package/src/skill/compose/.bundle/plan/SKILL.md +161 -0
  621. package/src/skill/compose/.bundle/plan/plan-document-reviewer-prompt.md +50 -0
  622. package/src/skill/compose/.bundle/report/SKILL.md +180 -0
  623. package/src/skill/compose/.bundle/review/SKILL.md +104 -0
  624. package/src/skill/compose/.bundle/review/code-reviewer.md +171 -0
  625. package/src/skill/compose/.bundle/subagent/SKILL.md +344 -0
  626. package/src/skill/compose/.bundle/subagent/code-quality-reviewer-prompt.md +24 -0
  627. package/src/skill/compose/.bundle/subagent/implementer-prompt.md +126 -0
  628. package/src/skill/compose/.bundle/subagent/spec-reviewer-prompt.md +112 -0
  629. package/src/skill/compose/.bundle/tdd/SKILL.md +372 -0
  630. package/src/skill/compose/.bundle/tdd/testing-anti-patterns.md +299 -0
  631. package/src/skill/compose/.bundle/verify/SKILL.md +140 -0
  632. package/src/skill/compose/.bundle/worktree/SKILL.md +234 -0
  633. package/src/skill/compose/LICENSE-karpathy +28 -0
  634. package/src/skill/compose/LICENSE-superpowers +26 -0
  635. package/src/skill/compose/bundle.macro.ts +30 -0
  636. package/src/skill/compose/extract.ts +85 -0
  637. package/src/skill/discovery.ts +116 -0
  638. package/src/skill/index.ts +311 -0
  639. package/src/snapshot/index.ts +777 -0
  640. package/src/sql.d.ts +4 -0
  641. package/src/storage/db.bun.ts +8 -0
  642. package/src/storage/db.node.ts +8 -0
  643. package/src/storage/db.ts +172 -0
  644. package/src/storage/index.ts +26 -0
  645. package/src/storage/json-migration.ts +426 -0
  646. package/src/storage/schema.sql.ts +10 -0
  647. package/src/storage/schema.ts +7 -0
  648. package/src/storage/storage.ts +331 -0
  649. package/src/sync/README.md +179 -0
  650. package/src/sync/event.sql.ts +16 -0
  651. package/src/sync/index.ts +278 -0
  652. package/src/sync/schema.ts +14 -0
  653. package/src/task/events.ts +28 -0
  654. package/src/task/gate-state.ts +54 -0
  655. package/src/task/gate.ts +116 -0
  656. package/src/task/index.ts +1 -0
  657. package/src/task/registry.ts +387 -0
  658. package/src/task/schema.ts +43 -0
  659. package/src/task/task.sql.ts +50 -0
  660. package/src/team/events.ts +22 -0
  661. package/src/team/index.ts +113 -0
  662. package/src/team/schema.ts +31 -0
  663. package/src/temporary.ts +33 -0
  664. package/src/tool/actor.shell.txt +72 -0
  665. package/src/tool/actor.ts +803 -0
  666. package/src/tool/actor.txt +103 -0
  667. package/src/tool/apply_patch.ts +308 -0
  668. package/src/tool/apply_patch.txt +33 -0
  669. package/src/tool/bash-interactive.ts +183 -0
  670. package/src/tool/bash.ts +696 -0
  671. package/src/tool/bash.txt +123 -0
  672. package/src/tool/change-directory.ts +91 -0
  673. package/src/tool/codesearch.ts +63 -0
  674. package/src/tool/codesearch.txt +12 -0
  675. package/src/tool/edit.ts +685 -0
  676. package/src/tool/edit.txt +10 -0
  677. package/src/tool/external-directory.ts +132 -0
  678. package/src/tool/glob.ts +100 -0
  679. package/src/tool/glob.txt +6 -0
  680. package/src/tool/grep.ts +145 -0
  681. package/src/tool/grep.txt +8 -0
  682. package/src/tool/history.ts +146 -0
  683. package/src/tool/history.txt +17 -0
  684. package/src/tool/index.ts +4 -0
  685. package/src/tool/invalid.ts +20 -0
  686. package/src/tool/invocation-style.ts +17 -0
  687. package/src/tool/lsp.ts +91 -0
  688. package/src/tool/lsp.txt +19 -0
  689. package/src/tool/mcp-exa.ts +78 -0
  690. package/src/tool/memory-path-guard.ts +162 -0
  691. package/src/tool/memory.ts +81 -0
  692. package/src/tool/memory.txt +69 -0
  693. package/src/tool/multiedit.ts +61 -0
  694. package/src/tool/multiedit.txt +41 -0
  695. package/src/tool/plan-enter.txt +14 -0
  696. package/src/tool/plan-exit.txt +13 -0
  697. package/src/tool/plan.ts +90 -0
  698. package/src/tool/question.ts +67 -0
  699. package/src/tool/question.txt +10 -0
  700. package/src/tool/read.ts +327 -0
  701. package/src/tool/read.txt +14 -0
  702. package/src/tool/registry.ts +413 -0
  703. package/src/tool/schema.ts +17 -0
  704. package/src/tool/session-cwd.ts +35 -0
  705. package/src/tool/shell-tokenize.ts +346 -0
  706. package/src/tool/shell-wrap.ts +190 -0
  707. package/src/tool/skill.ts +76 -0
  708. package/src/tool/skill.txt +5 -0
  709. package/src/tool/task.shell.txt +57 -0
  710. package/src/tool/task.ts +456 -0
  711. package/src/tool/task.txt +56 -0
  712. package/src/tool/tool.ts +153 -0
  713. package/src/tool/truncate.ts +201 -0
  714. package/src/tool/truncation-dir.ts +4 -0
  715. package/src/tool/webfetch.ts +199 -0
  716. package/src/tool/webfetch.txt +13 -0
  717. package/src/tool/websearch/index.ts +104 -0
  718. package/src/tool/websearch/mimo.ts +118 -0
  719. package/src/tool/websearch/websearch.txt +14 -0
  720. package/src/tool/workflow.ts +164 -0
  721. package/src/tool/workflow.txt +25 -0
  722. package/src/tool/write.ts +88 -0
  723. package/src/tool/write.txt +9 -0
  724. package/src/util/abort.ts +35 -0
  725. package/src/util/archive.ts +15 -0
  726. package/src/util/color.ts +17 -0
  727. package/src/util/data-url.ts +9 -0
  728. package/src/util/defer.ts +10 -0
  729. package/src/util/effect-http-client.ts +11 -0
  730. package/src/util/effect-zod.ts +367 -0
  731. package/src/util/error.ts +78 -0
  732. package/src/util/filesystem.ts +243 -0
  733. package/src/util/fn.ts +21 -0
  734. package/src/util/format.ts +20 -0
  735. package/src/util/iife.ts +3 -0
  736. package/src/util/index.ts +12 -0
  737. package/src/util/keybind.ts +101 -0
  738. package/src/util/lazy.ts +18 -0
  739. package/src/util/local-context.ts +23 -0
  740. package/src/util/locale.ts +79 -0
  741. package/src/util/lock.ts +96 -0
  742. package/src/util/log.ts +197 -0
  743. package/src/util/media.ts +26 -0
  744. package/src/util/mimo-process.ts +24 -0
  745. package/src/util/network.ts +9 -0
  746. package/src/util/process.ts +174 -0
  747. package/src/util/queue.ts +32 -0
  748. package/src/util/record.ts +3 -0
  749. package/src/util/rpc.ts +64 -0
  750. package/src/util/schema.ts +53 -0
  751. package/src/util/scrap.ts +10 -0
  752. package/src/util/signal.ts +12 -0
  753. package/src/util/timeout.ts +14 -0
  754. package/src/util/token.ts +5 -0
  755. package/src/util/update-schema.ts +13 -0
  756. package/src/util/which.ts +14 -0
  757. package/src/util/wildcard.ts +57 -0
  758. package/src/workflow/builtin/deep-research.js +391 -0
  759. package/src/workflow/builtin.ts +54 -0
  760. package/src/workflow/events.ts +72 -0
  761. package/src/workflow/meta.ts +335 -0
  762. package/src/workflow/persistence.ts +312 -0
  763. package/src/workflow/resolve.ts +45 -0
  764. package/src/workflow/runtime-ref.ts +18 -0
  765. package/src/workflow/runtime.ts +1234 -0
  766. package/src/workflow/sandbox.ts +280 -0
  767. package/src/workflow/workflow.sql.ts +31 -0
  768. package/src/workflow/workspace.ts +69 -0
  769. package/src/worktree/index.ts +614 -0
  770. package/sst-env.d.ts +10 -0
  771. package/test/AGENTS.md +133 -0
  772. package/test/account/repo.test.ts +352 -0
  773. package/test/account/service.test.ts +456 -0
  774. package/test/acp/agent-interface.test.ts +51 -0
  775. package/test/acp/event-subscription.test.ts +725 -0
  776. package/test/actor/cancel-cascade.test.ts +432 -0
  777. package/test/actor/no-completion-listener.test.ts +41 -0
  778. package/test/actor/poststop-progress-write-permission.repro.test.ts +414 -0
  779. package/test/actor/registry-render.test.ts +113 -0
  780. package/test/actor/registry-status.test.ts +111 -0
  781. package/test/actor/registry.test.ts +619 -0
  782. package/test/actor/return-header.test.ts +40 -0
  783. package/test/actor/spawn-lifecycle.test.ts +346 -0
  784. package/test/actor/spawn-no-deadlock.test.ts +340 -0
  785. package/test/actor/spawn-notification.test.ts +393 -0
  786. package/test/actor/spawn-task-autostart.test.ts +530 -0
  787. package/test/actor/spawn.test.ts +1072 -0
  788. package/test/actor/status-event-payload.test.ts +132 -0
  789. package/test/actor/terminology.test.ts +39 -0
  790. package/test/actor/turn.test.ts +125 -0
  791. package/test/actor/waiter.test.ts +246 -0
  792. package/test/agent/agent.test.ts +874 -0
  793. package/test/agent/allowlist.test.ts +45 -0
  794. package/test/auth/auth.test.ts +86 -0
  795. package/test/bus/bus-effect.test.ts +162 -0
  796. package/test/bus/bus-integration.test.ts +87 -0
  797. package/test/bus/bus.test.ts +219 -0
  798. package/test/cli/account.test.ts +26 -0
  799. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  800. package/test/cli/error.test.ts +18 -0
  801. package/test/cli/github-action.test.ts +198 -0
  802. package/test/cli/github-remote.test.ts +80 -0
  803. package/test/cli/import.test.ts +54 -0
  804. package/test/cli/plugin-auth-picker.test.ts +120 -0
  805. package/test/cli/run-completion.test.ts +131 -0
  806. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  807. package/test/cli/tui/plugin-add.test.ts +111 -0
  808. package/test/cli/tui/plugin-install.test.ts +87 -0
  809. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  810. package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
  811. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  812. package/test/cli/tui/plugin-loader.test.ts +816 -0
  813. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  814. package/test/cli/tui/revert-diff.test.ts +35 -0
  815. package/test/cli/tui/route-agent-id.test.ts +26 -0
  816. package/test/cli/tui/sidebar-tps.test.ts +63 -0
  817. package/test/cli/tui/slot-replace.test.tsx +47 -0
  818. package/test/cli/tui/sync-bucket.test.ts +29 -0
  819. package/test/cli/tui/theme-store.test.ts +51 -0
  820. package/test/cli/tui/thread.test.ts +121 -0
  821. package/test/cli/tui/transcript.test.ts +426 -0
  822. package/test/cli/tui/use-event.test.tsx +175 -0
  823. package/test/cli/tui/voice.test.ts +269 -0
  824. package/test/command/deep-research-command.test.ts +16 -0
  825. package/test/config/agent-color.test.ts +77 -0
  826. package/test/config/checkpoint-fork.test.ts +21 -0
  827. package/test/config/config.test.ts +2577 -0
  828. package/test/config/fixtures/empty-frontmatter.md +4 -0
  829. package/test/config/fixtures/frontmatter.md +28 -0
  830. package/test/config/fixtures/markdown-header.md +11 -0
  831. package/test/config/fixtures/no-frontmatter.md +1 -0
  832. package/test/config/fixtures/weird-model-id.md +13 -0
  833. package/test/config/lsp.test.ts +87 -0
  834. package/test/config/markdown.test.ts +228 -0
  835. package/test/config/plugin.test.ts +0 -0
  836. package/test/config/tui.test.ts +627 -0
  837. package/test/control-plane/adaptors.test.ts +71 -0
  838. package/test/control-plane/sse.test.ts +56 -0
  839. package/test/effect/app-runtime-logger.test.ts +92 -0
  840. package/test/effect/cross-spawn-spawner.test.ts +411 -0
  841. package/test/effect/instance-state.test.ts +482 -0
  842. package/test/effect/observability.test.ts +46 -0
  843. package/test/effect/run-service.test.ts +46 -0
  844. package/test/effect/runner-warn-log.test.ts +111 -0
  845. package/test/effect/runner.test.ts +494 -0
  846. package/test/fake/provider.ts +90 -0
  847. package/test/file/fsmonitor.test.ts +68 -0
  848. package/test/file/ignore.test.ts +10 -0
  849. package/test/file/index.test.ts +956 -0
  850. package/test/file/path-traversal.test.ts +204 -0
  851. package/test/file/ripgrep.test.ts +214 -0
  852. package/test/file/watcher.test.ts +249 -0
  853. package/test/filesystem/filesystem.test.ts +319 -0
  854. package/test/fixture/db.ts +11 -0
  855. package/test/fixture/fixture.test.ts +58 -0
  856. package/test/fixture/fixture.ts +190 -0
  857. package/test/fixture/flock-worker.ts +72 -0
  858. package/test/fixture/lsp/fake-lsp-server.js +75 -0
  859. package/test/fixture/plug-worker.ts +93 -0
  860. package/test/fixture/plugin-meta-worker.ts +19 -0
  861. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  862. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  863. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  864. package/test/fixture/skills/index.json +6 -0
  865. package/test/fixture/tui-plugin.ts +328 -0
  866. package/test/fixture/tui-runtime.ts +31 -0
  867. package/test/format/format.test.ts +244 -0
  868. package/test/git/git.test.ts +128 -0
  869. package/test/global/fixture/global-paths-worker.ts +17 -0
  870. package/test/global/mimocode-home.test.ts +143 -0
  871. package/test/history/backfill.test.ts +149 -0
  872. package/test/history/extract.test.ts +106 -0
  873. package/test/history/fts-query.test.ts +30 -0
  874. package/test/history/resolve.test.ts +130 -0
  875. package/test/history/service.test.ts +210 -0
  876. package/test/history/writer.test.ts +163 -0
  877. package/test/ide/ide.test.ts +82 -0
  878. package/test/inbox/drain-in-loop.test.ts +230 -0
  879. package/test/inbox/fork-agent-compat.test.ts +387 -0
  880. package/test/inbox/gc-on-init.test.ts +167 -0
  881. package/test/inbox/send-no-block.test.ts +120 -0
  882. package/test/inbox/sender-cancel-independence.test.ts +160 -0
  883. package/test/inbox/wake-matrix.test.ts +141 -0
  884. package/test/installation/installation.test.ts +226 -0
  885. package/test/keybind.test.ts +421 -0
  886. package/test/lib/effect.ts +53 -0
  887. package/test/lib/filesystem.ts +10 -0
  888. package/test/lib/llm-server.ts +770 -0
  889. package/test/lib/scripted-llm-server.ts +245 -0
  890. package/test/lsp/client.test.ts +98 -0
  891. package/test/lsp/index.test.ts +109 -0
  892. package/test/lsp/launch.test.ts +22 -0
  893. package/test/lsp/lifecycle.test.ts +184 -0
  894. package/test/mcp/headers.test.ts +178 -0
  895. package/test/mcp/lifecycle.test.ts +824 -0
  896. package/test/mcp/oauth-auto-connect.test.ts +281 -0
  897. package/test/mcp/oauth-browser.test.ts +268 -0
  898. package/test/mcp/oauth-callback.test.ts +34 -0
  899. package/test/memory/abort-leak-webfetch.ts +49 -0
  900. package/test/memory/abort-leak.test.ts +127 -0
  901. package/test/memory/cc-frontmatter.test.ts +85 -0
  902. package/test/memory/cc-paths.test.ts +60 -0
  903. package/test/memory/cc-reconcile.test.ts +239 -0
  904. package/test/memory/cc-search.test.ts +151 -0
  905. package/test/memory/fts-query.test.ts +48 -0
  906. package/test/memory/fts-rowid-stability.test.ts +271 -0
  907. package/test/memory/paths.test.ts +210 -0
  908. package/test/memory/reconcile.test.ts +115 -0
  909. package/test/memory/service.test.ts +169 -0
  910. package/test/npm.test.ts +18 -0
  911. package/test/patch/patch.test.ts +348 -0
  912. package/test/permission/abort.test.ts +116 -0
  913. package/test/permission/arity.test.ts +33 -0
  914. package/test/permission/disabled.test.ts +51 -0
  915. package/test/permission/next.test.ts +1080 -0
  916. package/test/permission/non-interactive.test.ts +55 -0
  917. package/test/permission-task.test.ts +326 -0
  918. package/test/plugin/actor-hooks.test.ts +1471 -0
  919. package/test/plugin/auth-override.test.ts +79 -0
  920. package/test/plugin/checkpoint-splitover.test.ts +434 -0
  921. package/test/plugin/cloudflare.test.ts +68 -0
  922. package/test/plugin/codex.test.ts +123 -0
  923. package/test/plugin/github-copilot-models.test.ts +163 -0
  924. package/test/plugin/install-concurrency.test.ts +140 -0
  925. package/test/plugin/install.test.ts +570 -0
  926. package/test/plugin/loader-shared.test.ts +1169 -0
  927. package/test/plugin/matcher.test.ts +97 -0
  928. package/test/plugin/meta.test.ts +137 -0
  929. package/test/plugin/mimo.test.ts +257 -0
  930. package/test/plugin/shared.test.ts +88 -0
  931. package/test/plugin/subagent-progress-checker.test.ts +227 -0
  932. package/test/plugin/trigger.test.ts +116 -0
  933. package/test/plugin/workspace-adaptor.test.ts +109 -0
  934. package/test/preload.ts +102 -0
  935. package/test/project/migrate-global.test.ts +150 -0
  936. package/test/project/project-id.test.ts +64 -0
  937. package/test/project/project.test.ts +481 -0
  938. package/test/project/vcs.test.ts +286 -0
  939. package/test/project/worktree-remove.test.ts +126 -0
  940. package/test/project/worktree.test.ts +214 -0
  941. package/test/provider/amazon-bedrock.test.ts +462 -0
  942. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  943. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  944. package/test/provider/error.test.ts +160 -0
  945. package/test/provider/gitlab-duo.test.ts +413 -0
  946. package/test/provider/model-groups.test.ts +389 -0
  947. package/test/provider/provider-chunk-timeout.test.ts +23 -0
  948. package/test/provider/provider.test.ts +2648 -0
  949. package/test/provider/transform.test.ts +3379 -0
  950. package/test/pty/pty-output-isolation.test.ts +146 -0
  951. package/test/pty/pty-session.test.ts +102 -0
  952. package/test/pty/pty-shell.test.ts +69 -0
  953. package/test/question/question.test.ts +464 -0
  954. package/test/server/global-session-list.test.ts +105 -0
  955. package/test/server/project-init-git.test.ts +122 -0
  956. package/test/server/session-actions.test.ts +49 -0
  957. package/test/server/session-list.test.ts +110 -0
  958. package/test/server/session-messages.test.ts +220 -0
  959. package/test/server/session-prompt-busy.test.ts +146 -0
  960. package/test/server/session-select.test.ts +100 -0
  961. package/test/server/session-task-route.test.ts +165 -0
  962. package/test/server/summarize-route-main-slice.test.ts +99 -0
  963. package/test/server/trace-attributes.test.ts +76 -0
  964. package/test/server/workflows-route.test.ts +279 -0
  965. package/test/session/bootstrap-skip-system.test.ts +121 -0
  966. package/test/session/boundary.test.ts +33 -0
  967. package/test/session/budgeted-read.test.ts +74 -0
  968. package/test/session/checkpoint-align.test.ts +58 -0
  969. package/test/session/checkpoint-boundary.test.ts +186 -0
  970. package/test/session/checkpoint-child-session.test.ts +508 -0
  971. package/test/session/checkpoint-context.test.ts +141 -0
  972. package/test/session/checkpoint-drain.test.ts +188 -0
  973. package/test/session/checkpoint-extract-titles.test.ts +58 -0
  974. package/test/session/checkpoint-fork-mode.test.ts +576 -0
  975. package/test/session/checkpoint-main-slice.test.ts +259 -0
  976. package/test/session/checkpoint-paths.test.ts +78 -0
  977. package/test/session/checkpoint-permission.test.ts +136 -0
  978. package/test/session/checkpoint-progress-reconcile.test.ts +219 -0
  979. package/test/session/checkpoint-rebuild-unify.test.ts +143 -0
  980. package/test/session/checkpoint-rebuild-v3.test.ts +248 -0
  981. package/test/session/checkpoint-render-verify.test.ts +512 -0
  982. package/test/session/checkpoint-retry.test.ts +150 -0
  983. package/test/session/checkpoint-splitover-integration.test.ts +533 -0
  984. package/test/session/checkpoint-templates.test.ts +51 -0
  985. package/test/session/checkpoint-thresholds.test.ts +120 -0
  986. package/test/session/checkpoint-validator.test.ts +189 -0
  987. package/test/session/classify-integration.test.ts +476 -0
  988. package/test/session/classify.test.ts +335 -0
  989. package/test/session/compaction-agent-scope.test.ts +164 -0
  990. package/test/session/context-inheritance.test.ts +46 -0
  991. package/test/session/fork-prefix-invariant.test.ts +116 -0
  992. package/test/session/goal.test.ts +106 -0
  993. package/test/session/instruction.test.ts +387 -0
  994. package/test/session/invalid-output-continuation.test.ts +150 -0
  995. package/test/session/last-message-info.test.ts +47 -0
  996. package/test/session/length-tool-safety.test.ts +121 -0
  997. package/test/session/llm-request-prefix.test.ts +197 -0
  998. package/test/session/llm-retry.test.ts +59 -0
  999. package/test/session/llm-system-prompt.test.ts +479 -0
  1000. package/test/session/llm.test.ts +1272 -0
  1001. package/test/session/main-lifecycle.test.ts +51 -0
  1002. package/test/session/main-runloop-history-invariant.test.ts +182 -0
  1003. package/test/session/max-mode-econnreset.test.ts +229 -0
  1004. package/test/session/max-mode.test.ts +54 -0
  1005. package/test/session/message-v2-filter.test.ts +197 -0
  1006. package/test/session/message-v2.test.ts +1119 -0
  1007. package/test/session/messages-default-main.test.ts +105 -0
  1008. package/test/session/messages-pagination.test.ts +888 -0
  1009. package/test/session/overflow.test.ts +576 -0
  1010. package/test/session/processor-effect.test.ts +853 -0
  1011. package/test/session/prompt-effect.test.ts +1574 -0
  1012. package/test/session/prompt-rebuild-loop.test.ts +108 -0
  1013. package/test/session/prompt-rebuild-reset.test.ts +67 -0
  1014. package/test/session/prompt-sweep.test.ts +145 -0
  1015. package/test/session/prompt-task-gate.test.ts +127 -0
  1016. package/test/session/prompt.test.ts +703 -0
  1017. package/test/session/prune-main-slice.test.ts +272 -0
  1018. package/test/session/prune-skip-system.test.ts +346 -0
  1019. package/test/session/prune.test.ts +419 -0
  1020. package/test/session/rebuild-microcompact.test.ts +318 -0
  1021. package/test/session/recall-reminder.test.ts +37 -0
  1022. package/test/session/retry.test.ts +410 -0
  1023. package/test/session/revert-compact.test.ts +639 -0
  1024. package/test/session/run-state-tuple-key.test.ts +152 -0
  1025. package/test/session/session-create-registers-main.test.ts +70 -0
  1026. package/test/session/session.test.ts +181 -0
  1027. package/test/session/snapshot-tool-race.test.ts +301 -0
  1028. package/test/session/structured-output-integration.test.ts +264 -0
  1029. package/test/session/structured-output-retry.test.ts +127 -0
  1030. package/test/session/structured-output.test.ts +397 -0
  1031. package/test/session/summary-main-slice.test.ts +170 -0
  1032. package/test/session/system.test.ts +72 -0
  1033. package/test/share/share-next.test.ts +332 -0
  1034. package/test/shell/shell.test.ts +73 -0
  1035. package/test/skill/compose-review.test.ts +141 -0
  1036. package/test/skill/discovery.test.ts +116 -0
  1037. package/test/skill/skill.test.ts +465 -0
  1038. package/test/snapshot/snapshot.test.ts +1531 -0
  1039. package/test/storage/db.test.ts +16 -0
  1040. package/test/storage/json-migration.test.ts +831 -0
  1041. package/test/storage/storage.test.ts +293 -0
  1042. package/test/sync/index.test.ts +237 -0
  1043. package/test/task/gate-state.test.ts +66 -0
  1044. package/test/task/gate.test.ts +167 -0
  1045. package/test/task/registry.test.ts +152 -0
  1046. package/test/task/state-machine.test.ts +292 -0
  1047. package/test/team/migrate-to-inbox.test.ts +124 -0
  1048. package/test/team/team.test.ts +75 -0
  1049. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  1050. package/test/tool/actor-cancel.test.ts +206 -0
  1051. package/test/tool/actor-recover.test.ts +50 -0
  1052. package/test/tool/actor-send.test.ts +200 -0
  1053. package/test/tool/actor-status.test.ts +296 -0
  1054. package/test/tool/actor-wait.test.ts +193 -0
  1055. package/test/tool/actor.shell.test.ts +250 -0
  1056. package/test/tool/actor.test.ts +748 -0
  1057. package/test/tool/apply_patch.test.ts +626 -0
  1058. package/test/tool/bash.test.ts +1195 -0
  1059. package/test/tool/describe-workflow.test.ts +12 -0
  1060. package/test/tool/edit.test.ts +691 -0
  1061. package/test/tool/external-directory.test.ts +207 -0
  1062. package/test/tool/fixtures/large-image.png +0 -0
  1063. package/test/tool/fixtures/models-api.json +65179 -0
  1064. package/test/tool/glob.test.ts +81 -0
  1065. package/test/tool/grep.test.ts +114 -0
  1066. package/test/tool/history.test.ts +144 -0
  1067. package/test/tool/invocation-style.test.ts +30 -0
  1068. package/test/tool/memory-edit-ask-skip.test.ts +62 -0
  1069. package/test/tool/memory-path-guard.test.ts +594 -0
  1070. package/test/tool/memory.test.ts +71 -0
  1071. package/test/tool/question.test.ts +167 -0
  1072. package/test/tool/read.test.ts +483 -0
  1073. package/test/tool/registry-invocation-style.test.ts +121 -0
  1074. package/test/tool/registry.test.ts +164 -0
  1075. package/test/tool/shell-tokenize.test.ts +273 -0
  1076. package/test/tool/shell-wrap-missing-script.test.ts +128 -0
  1077. package/test/tool/shell-wrap.test.ts +257 -0
  1078. package/test/tool/skill.test.ts +99 -0
  1079. package/test/tool/task-recover.test.ts +36 -0
  1080. package/test/tool/task.shell.test.ts +234 -0
  1081. package/test/tool/task.test.ts +296 -0
  1082. package/test/tool/tool-def-shell-shape.test.ts +23 -0
  1083. package/test/tool/tool-define.test.ts +59 -0
  1084. package/test/tool/truncation.test.ts +253 -0
  1085. package/test/tool/webfetch.test.ts +103 -0
  1086. package/test/tool/whitelist.test.ts +373 -0
  1087. package/test/tool/write.test.ts +244 -0
  1088. package/test/util/data-url.test.ts +14 -0
  1089. package/test/util/effect-zod.test.ts +869 -0
  1090. package/test/util/error.test.ts +38 -0
  1091. package/test/util/filesystem.test.ts +656 -0
  1092. package/test/util/format.test.ts +59 -0
  1093. package/test/util/glob.test.ts +164 -0
  1094. package/test/util/iife.test.ts +36 -0
  1095. package/test/util/lazy.test.ts +50 -0
  1096. package/test/util/lock.test.ts +72 -0
  1097. package/test/util/log.test.ts +44 -0
  1098. package/test/util/module.test.ts +59 -0
  1099. package/test/util/process.test.ts +128 -0
  1100. package/test/util/timeout.test.ts +21 -0
  1101. package/test/util/which.test.ts +100 -0
  1102. package/test/util/wildcard.test.ts +90 -0
  1103. package/test/workflow/builtin.test.ts +22 -0
  1104. package/test/workflow/deep-research-cluster.test.ts +47 -0
  1105. package/test/workflow/lib.ts +243 -0
  1106. package/test/workflow/meta.test.ts +142 -0
  1107. package/test/workflow/model-routing.test.ts +68 -0
  1108. package/test/workflow/persistence.test.ts +229 -0
  1109. package/test/workflow/resolve.test.ts +37 -0
  1110. package/test/workflow/runtime-nested.test.ts +419 -0
  1111. package/test/workflow/runtime-worktree.test.ts +261 -0
  1112. package/test/workflow/runtime.test.ts +1078 -0
  1113. package/test/workflow/sandbox.test.ts +259 -0
  1114. package/test/workflow/tool.test.ts +473 -0
  1115. package/test/workflow/verify-wow.test.ts +144 -0
  1116. package/test/workflow/workspace.test.ts +88 -0
  1117. package/test/workspace/workspace-restore.test.ts +281 -0
  1118. package/test/worktree/index.test.ts +30 -0
  1119. package/tsconfig.json +24 -0
@@ -0,0 +1,1478 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import { Global } from "@/global"
4
+ import { Bus } from "@/bus"
5
+ import { Config } from "@/config"
6
+ import { Memory } from "@/memory"
7
+ import { MemoryFtsTable } from "@/memory/fts.sql"
8
+ import { TaskRegistry } from "@/task/registry"
9
+ import { ActorRegistry } from "@/actor/registry"
10
+ import type { AgentOutcome, ForkContext } from "@/actor/spawn"
11
+ import { spawnRef } from "@/actor/spawn-ref"
12
+ import { prefixCaptureRef } from "./prefix-capture-ref"
13
+ import { Database, and, eq, or } from "@/storage"
14
+ import { Instance } from "@/project/instance"
15
+ import { ProjectID } from "@/project/schema"
16
+ import { SessionTable } from "./session.sql"
17
+ import * as Session from "./session"
18
+ import { MessageV2 } from "./message-v2"
19
+ import { SessionID, MessageID, PartID } from "./schema"
20
+ import { Log, Token } from "../util"
21
+ import { Effect, Layer, Deferred, Context, Scope } from "effect"
22
+ import { makeRuntime } from "@/effect/run-service"
23
+ import type { ActorPromptOps } from "@/tool/actor"
24
+ import type { ProviderID, ModelID } from "../provider/schema"
25
+ import PROMPT_CHECKPOINT_WRITER from "@/agent/prompt/checkpoint-writer.txt"
26
+ import { WriterCachePerf } from "@/actor/events"
27
+ import {
28
+ metaDir,
29
+ checkpointPath,
30
+ memoryPath,
31
+ notesPath,
32
+ globalMemoryPath,
33
+ migrateProjectMemory,
34
+ } from "./checkpoint-paths"
35
+ import { readBudgeted, readBudgetedSectionAware } from "./budgeted-read"
36
+ import type { LastMessageInfo } from "./last-message-info"
37
+ import { CHECKPOINT_TEMPLATE, MEMORY_TEMPLATE, NOTES_TEMPLATE, CHECKPOINT_SECTION_BUDGETS } from "./checkpoint-templates"
38
+ import { adjustBoundaryForApiInvariants } from "./boundary"
39
+ import { alignToNonToolResultUser } from "./checkpoint-align"
40
+ import { loadPriorDiscoveredTitles } from "./checkpoint-retry"
41
+ import * as CheckpointContext from "./checkpoint-context"
42
+ import { buildProgressDiff } from "./checkpoint-progress-reconcile"
43
+
44
+ const log = Log.create({ service: "session.checkpoint" })
45
+
46
+ function truncate(s: string, max: number): string {
47
+ return s.length <= max ? s : s.slice(0, max - 60) + "\n... (truncated, full body at file)"
48
+ }
49
+
50
+ function autonomousLoopReminder(): string {
51
+ return [
52
+ "<system-reminder>",
53
+ "You are mid-loop in an autonomous task. Continue your work loop:",
54
+ "respond to the tool results below and proceed to the next iteration.",
55
+ "</system-reminder>",
56
+ ].join("\n")
57
+ }
58
+
59
+ function stopReminder(focusTaskID: string | undefined): string {
60
+ const taskHint = focusTaskID
61
+ ? `Consult this session's tasks/${focusTaskID}/progress.md head section.`
62
+ : "Consult the most recently active task's progress.md head section."
63
+ return [
64
+ "<system-reminder>",
65
+ "The previous assistant turn ended with a stop. Before stopping again,",
66
+ taskHint,
67
+ "Compare the Task spec to the latest Progress entries. If the task is",
68
+ "incomplete, proceed to the next concrete step. Only stop when the spec",
69
+ "is genuinely satisfied or you need user input you cannot infer.",
70
+ "</system-reminder>",
71
+ ].join("\n")
72
+ }
73
+
74
+ function toolResultContinueReminder(): string {
75
+ return [
76
+ "<system-reminder>",
77
+ "Tool results above are real history from the autonomous loop. Process",
78
+ "them and continue to the next iteration. Do not pause to summarize.",
79
+ "</system-reminder>",
80
+ ].join("\n")
81
+ }
82
+
83
+ async function ensureCheckpointTemplate(checkpointFile: string): Promise<void> {
84
+ if (!(await Bun.file(checkpointFile).exists())) {
85
+ await fs.mkdir(path.dirname(checkpointFile), { recursive: true })
86
+ await Bun.write(checkpointFile, CHECKPOINT_TEMPLATE)
87
+ }
88
+ }
89
+
90
+ async function ensureMemoryTemplate(memoryFile: string): Promise<void> {
91
+ if (!(await Bun.file(memoryFile).exists())) {
92
+ await fs.mkdir(path.dirname(memoryFile), { recursive: true })
93
+ await Bun.write(memoryFile, MEMORY_TEMPLATE)
94
+ }
95
+ }
96
+
97
+ async function ensureNotesTemplate(notesFile: string): Promise<void> {
98
+ if (!(await Bun.file(notesFile).exists())) {
99
+ await fs.mkdir(path.dirname(notesFile), { recursive: true })
100
+ await Bun.write(notesFile, NOTES_TEMPLATE)
101
+ }
102
+ }
103
+
104
+ // Tail preservation budget (token-budgeted boundary).
105
+ // Session-memory compact: minimum guarantees the LLM has enough
106
+ // recent-context anchor (avoids the agent-Read-loop failure mode from
107
+ // v4 → v5 spec rationale); maximum is a SOFT ceiling on backward
108
+ // expansion — i.e. when the natural tail is below the floor we expand
109
+ // backward UP TO maxTokens, but if the natural tail already exceeds
110
+ // maxTokens we leave it alone. Single-message-granularity cap would
111
+ // break tool_use/result pairing.
112
+ //
113
+ // 20K is the empirical sweet spot — observed compact output is ~20K,
114
+ // not the 40K nominal default. The 40K appears in source as fallback,
115
+ // but the upstream config likely tunes it lower in production.
116
+ const TAIL_MIN_TOKENS = 10_000
117
+ const TAIL_MAX_TOKENS = 20_000
118
+ const TAIL_MIN_TEXT_BLOCK_MESSAGES = 5
119
+
120
+ // Rebuild-time microcompact (see
121
+ // docs/superpowers/specs/2026-06-03-rebuild-tail-microcompact-design.md).
122
+ //
123
+ // After computing the boundary, msgs strictly newer than the boundary
124
+ // survive into the rebuild context. Their tool_use parts are kept (so the
125
+ // LLM still sees what action was taken), but for tools in this whitelist
126
+ // the tool_result content is replaced with a placeholder. Result is either
127
+ // large-and-regeneratable (read/bash/grep/glob/webfetch/websearch) or
128
+ // essentially a "done" confirmation (edit/write/multiedit). Tools NOT here
129
+ // carry state the LLM references later (actor/task/question/skill/memory).
130
+ const COMPACTABLE_TOOL_NAMES = new Set<string>([
131
+ "read",
132
+ "bash",
133
+ "grep",
134
+ "glob",
135
+ "webfetch",
136
+ "websearch",
137
+ "edit",
138
+ "write",
139
+ "multiedit",
140
+ "apply_patch",
141
+ "codesearch",
142
+ ])
143
+
144
+ function estimateMessageTokens(m: { parts: Array<{ type: string; [k: string]: unknown }> }): number {
145
+ // Same estimator used elsewhere in checkpoint.ts (Token.estimate over JSON).
146
+ // Sum across all parts of the message.
147
+ let sum = 0
148
+ for (const p of m.parts) {
149
+ // JSON.stringify throws on circular structures. Parser-produced parts are
150
+ // plain POJOs, but a plugin-injected part could contain a cycle. Fall back
151
+ // to a conservative NON-ZERO estimate so a bad part is never counted as
152
+ // "free" — a 0 here would let the tail-boundary algorithm swallow the part
153
+ // for nothing and skew the budget. The constant overstates a typical part,
154
+ // which is the safe direction (boundary walks back, never forward).
155
+ try {
156
+ sum += Token.estimate(JSON.stringify(p))
157
+ } catch {
158
+ sum += 1000
159
+ }
160
+ }
161
+ return sum
162
+ }
163
+
164
+ function hasTextBlocks(m: { parts: Array<{ type: string }> }): boolean {
165
+ return m.parts.some((p) => p.type === "text" || p.type === "reasoning")
166
+ }
167
+
168
+ /**
169
+ * Token-budgeted, role-aware boundary choice for the preserved tail.
170
+ *
171
+ * Returns the ID of the FIRST message to preserve (boundary message ID;
172
+ * everything strictly before this ID is summarized into checkpoint.md and
173
+ * discarded from the rebuild context).
174
+ *
175
+ * Algorithm (token-budgeted boundary):
176
+ *
177
+ * 1. Start at the last finished assistant index minus 1, take it+successors
178
+ * as the candidate tail (preserves spec 2 starting point so reasonable
179
+ * tails are unchanged).
180
+ * 2. If tail tokens already >= TAIL_MAX_TOKENS: leave boundary as-is and
181
+ * return. Do NOT pull boundary forward — message-granularity truncation
182
+ * would split tool_use/tool_result pairs (downstream
183
+ * adjustBoundaryForApiInvariants would just walk back, net no-op + risk
184
+ * of thinking-block breaks). The cap is a SOFT ceiling on backward
185
+ * expansion, not a hard upper bound on tail size. If a single
186
+ * assistant turn legitimately produces 60K of tool_result, the tail
187
+ * will be 60K and that's fine.
188
+ * 3. Else if tail tokens < TAIL_MIN_TOKENS or text-block messages < min:
189
+ * walk backward (earlier) one message at a time until both minimums
190
+ * met OR TAIL_MAX_TOKENS hit OR no more messages.
191
+ *
192
+ * The downstream adjustBoundaryForApiInvariants call (in
193
+ * tryStartCheckpointWriter) handles tool_use/tool_result pairing and
194
+ * thinking-block atomicity — this function does NOT need to.
195
+ *
196
+ * Edge cases:
197
+ * - msgs.length === 0: return "" (matches old behavior).
198
+ * - No finished assistant: return msgs[0].info.id (degenerate; caller should
199
+ * not be invoking trim here, but stay safe).
200
+ * - lastAsstIdx === 0: return msgs[0].info.id (degenerate tail).
201
+ */
202
+ export function computeBoundary(
203
+ msgs: ReadonlyArray<{ info: { id: string; role: "user" | "assistant"; finish?: string }; parts: Array<{ type: string; [k: string]: unknown }> }>,
204
+ ): string {
205
+ if (msgs.length === 0) return ""
206
+ const lastAsstIdx = msgs.findLastIndex(
207
+ (m) => m.info.role === "assistant" && m.info.finish !== undefined,
208
+ )
209
+ if (lastAsstIdx <= 0) return msgs[lastAsstIdx >= 0 ? lastAsstIdx : 0].info.id
210
+
211
+ // Token estimate per message (computed once).
212
+ const tokens = msgs.map((m) => estimateMessageTokens(m))
213
+
214
+ // Spec 2 starting point: lastAsstIdx - 1.
215
+ let startIdx = lastAsstIdx - 1
216
+ let tailSum = 0
217
+ let textBlockCount = 0
218
+ for (let i = startIdx; i < msgs.length; i++) {
219
+ tailSum += tokens[i]
220
+ if (hasTextBlocks(msgs[i])) textBlockCount += 1
221
+ }
222
+
223
+ // Natural tail already >= cap: leave it alone (soft ceiling; do NOT pull
224
+ // boundary forward — see jsdoc rationale).
225
+ if (tailSum >= TAIL_MAX_TOKENS) {
226
+ return msgs[startIdx].info.id
227
+ }
228
+
229
+ // Tail too small — pull boundary earlier (include more history)
230
+ // until both floors met, capped at TAIL_MAX_TOKENS.
231
+ while (
232
+ startIdx > 0 &&
233
+ tailSum < TAIL_MAX_TOKENS &&
234
+ (tailSum < TAIL_MIN_TOKENS || textBlockCount < TAIL_MIN_TEXT_BLOCK_MESSAGES)
235
+ ) {
236
+ startIdx -= 1
237
+ tailSum += tokens[startIdx]
238
+ if (hasTextBlocks(msgs[startIdx])) textBlockCount += 1
239
+ }
240
+ return msgs[startIdx].info.id
241
+ }
242
+
243
+ function renderSectionBudgets(budgets: Record<string, number>): string {
244
+ const entries = Object.entries(budgets)
245
+ if (entries.length === 0) {
246
+ throw new Error("CHECKPOINT_SECTION_BUDGETS is empty — F43 substitution would produce an empty prompt block")
247
+ }
248
+ const cols = 3
249
+ const lines: string[] = ["Section budgets (~tokens):"]
250
+ for (let i = 0; i < entries.length; i += cols) {
251
+ const row = entries
252
+ .slice(i, i + cols)
253
+ .map(([k, v]) => `${k}: ${v}`)
254
+ .join(" ")
255
+ lines.push(` ${row}`)
256
+ }
257
+ return lines.join("\n")
258
+ }
259
+
260
+ /**
261
+ * Composes the full writer prompt for the checkpoint subagent.
262
+ *
263
+ * The body wraps PROMPT_CHECKPOINT_WRITER with an ABSOLUTE-PATHS preamble
264
+ * that pins CHECKPOINT_PATH/MEMORY_PATH/TASK_MEM_DIR to the current session's
265
+ * dirs — without this, the model frequently invents legacy `/data/checkpoints/`
266
+ * style paths from training-data lookalikes.
267
+ */
268
+ function composeWriterPrompt(input: {
269
+ checkpointFile: string
270
+ memoryFile: string
271
+ taskMemDir: string
272
+ notesFile: string
273
+ rangeDesc: string
274
+ progressDiff: string // Spec ② Chain 2: empty string when nothing to reconcile
275
+ }): string {
276
+ return [
277
+ "<system-reminder>",
278
+ "You are now operating in checkpoint-writer mode. Ignore the general coding-assistant framing in the system prompt above. The read, write, edit, glob, grep, and task tools are available; do not invoke others.",
279
+ "",
280
+ "========================================================================",
281
+ "ABSOLUTE PATHS — USE THESE VERBATIM. NEVER COMPUTE, INFER, OR MODIFY.",
282
+ "========================================================================",
283
+ "",
284
+ `CHECKPOINT_PATH = ${input.checkpointFile}`,
285
+ `MEMORY_PATH = ${input.memoryFile}`,
286
+ `TASK_MEM_DIR = ${input.taskMemDir}`,
287
+ `NOTES_PATH = ${input.notesFile}`,
288
+ "",
289
+ "When using the Write tool, the first arg MUST be one of these literal",
290
+ "absolute paths (or for task narrative, TASK_MEM_DIR + '/' + task_id +",
291
+ "'/progress.md' or '/notes.md'). Do NOT abbreviate. Do NOT change",
292
+ "parent directories. Do NOT insert paths from memory of similar projects.",
293
+ "If you find yourself typing '/data/checkpoints/' as a parent, STOP — that",
294
+ "is the legacy v2 layout and is wrong. The current parent for the",
295
+ "checkpoint file is the directory portion of CHECKPOINT_PATH above.",
296
+ "========================================================================",
297
+ "",
298
+ input.progressDiff,
299
+ "",
300
+ PROMPT_CHECKPOINT_WRITER.replace("{{SECTION_BUDGETS}}", renderSectionBudgets(CHECKPOINT_SECTION_BUDGETS)),
301
+ "</system-reminder>",
302
+ "",
303
+ `Write the next checkpoint for this session.`,
304
+ "",
305
+ input.rangeDesc,
306
+ "",
307
+ "Use the `task` tool for ALL task state ops (create / start / progress / done / abandon / approve / rename / block / unblock / batch_create). Use the Write tool for the checkpoint, memory, and task narrative files at the CHECKPOINT_PATH / MEMORY_PATH / TASK_MEM_DIR locations declared above. After all writes and tool calls, stop immediately.",
308
+ ].join("\n")
309
+ }
310
+
311
+ function aggregateWriterCacheMetrics(
312
+ sessions: Session.Interface,
313
+ sessionID: SessionID,
314
+ actorID: string,
315
+ ) {
316
+ return Effect.gen(function* () {
317
+ const msgs = yield* sessions.messages({ sessionID, agentID: "*" })
318
+ let totalInput = 0
319
+ let cacheRead = 0
320
+ let cacheWrite = 0
321
+ let assistantCount = 0
322
+ for (const m of msgs) {
323
+ if (m.info.role !== "assistant") continue
324
+ if (m.info.agentID !== actorID) continue
325
+ // Count every assistant LLM call in the slice, even ones without
326
+ // billing-token data (errors / mid-stream interrupts). Token sums
327
+ // skip the no-data rows; the call count includes them so downstream
328
+ // consumers can distinguish "low cache hit" from "few calls".
329
+ assistantCount += 1
330
+ const t = m.info.tokens
331
+ if (!t) continue
332
+ totalInput += (t.input ?? 0) + (t.cache?.read ?? 0) + (t.cache?.write ?? 0)
333
+ cacheRead += t.cache?.read ?? 0
334
+ cacheWrite += t.cache?.write ?? 0
335
+ }
336
+ const billable = totalInput - cacheRead - cacheWrite
337
+ const denom = cacheRead + Math.max(billable, 0)
338
+ const hitRate = denom > 0 ? cacheRead / denom : 0
339
+ return {
340
+ total_input_tokens: totalInput,
341
+ cache_read_tokens: cacheRead,
342
+ cache_write_tokens: cacheWrite,
343
+ cache_hit_rate: hitRate,
344
+ num_llm_calls: assistantCount,
345
+ }
346
+ })
347
+ }
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // Service interface
351
+ // ---------------------------------------------------------------------------
352
+
353
+ export type TryStartCheckpointWriterInput = {
354
+ sessionID: SessionID
355
+ model: { providerID: string; modelID: string }
356
+ promptOps: ActorPromptOps
357
+ }
358
+
359
+ /**
360
+ * Outcome of a tryStartCheckpointWriter call:
361
+ * - "started": no writer was running for this session, a fresh one was forked.
362
+ * - "queued": a writer is already running. The new request is held in the
363
+ * 1-slot pending queue and will fire once the current writer
364
+ * settles. If a pending request already exists it's evicted —
365
+ * newest wins because its range is a strict superset of the
366
+ * older pending range, so the older one would just duplicate
367
+ * work. (F40)
368
+ * - "skipped": the request was rejected outright — empty session, system-
369
+ * spawned subagent, or Actor service unavailable. No writer
370
+ * will fire for this request now or later.
371
+ */
372
+ export type TryStartCheckpointWriterResult = "started" | "queued" | "skipped"
373
+
374
+ export interface Interface {
375
+ readonly tryStartCheckpointWriter: (
376
+ input: TryStartCheckpointWriterInput,
377
+ ) => Effect.Effect<TryStartCheckpointWriterResult>
378
+
379
+ readonly waitForWriter: (sessionID: SessionID) => Effect.Effect<WriterOutcome | "no-writer">
380
+
381
+ /**
382
+ * Await all in-flight writers across sessions up to `timeoutMs`. Used by
383
+ * the CLI shutdown path so headless `mimo run` invocations don't exit
384
+ * while a forked checkpoint writer is still waiting on its LLM round-trip.
385
+ * Returns the count of writers that completed vs. still pending when the
386
+ * timeout fired.
387
+ */
388
+ readonly drainWriters: (input?: { timeoutMs?: number }) => Effect.Effect<{
389
+ drained: number
390
+ timedOut: number
391
+ }>
392
+
393
+ readonly hasCheckpoint: (sessionID: SessionID) => Effect.Effect<boolean>
394
+
395
+ /**
396
+ * Returns true when the session has any memory artifacts:
397
+ * either a populated `<data>/memory/sessions/<sid>/` directory, or
398
+ * any tasks recorded in the task registry. Used by the per-user-message
399
+ * recall reminder so it fires whenever there is anything to recall —
400
+ * not only when classic v2 checkpoints exist.
401
+ */
402
+ readonly hasMemoryOrTasks: (sessionID: SessionID) => Effect.Effect<boolean>
403
+
404
+ /** Returns the content of the latest checkpoint file, or undefined if none exists. */
405
+ readonly loadLatest: (sessionID: SessionID) => Effect.Effect<string | undefined>
406
+
407
+ /** Returns the content of the last N checkpoint files, ordered oldest to newest. */
408
+ readonly loadCheckpoints: (sessionID: SessionID, count: number) => Effect.Effect<string[]>
409
+
410
+ /** Returns a human-readable index overview for injection into rebuild context. */
411
+ readonly renderIndex: (sessionID: SessionID) => Effect.Effect<string>
412
+
413
+ /**
414
+ * Returns the rebuild-time context that should be injected after trim.
415
+ * Format:
416
+ * <system-reminder>Verify-before-act note...</system-reminder>
417
+ * ## Accumulated learnings (chronological)
418
+ * ### From checkpoint #1 (<topic>)
419
+ * <Learning body>
420
+ * ...
421
+ * ## Current snapshot (as of checkpoint #N)
422
+ * <Snapshot body>
423
+ *
424
+ * Stale Snapshots from older checkpoints are intentionally dropped. Returns
425
+ * an empty string if no checkpoints exist. When checkpoints exist but all
426
+ * Learning sections are empty, emits "(no prior learnings)" placeholder;
427
+ * when the latest checkpoint has no Snapshot section, emits
428
+ * "(latest checkpoint has no Snapshot section)" placeholder — the full
429
+ * structure is always produced so the verify-before-act reminder is
430
+ * consistently visible.
431
+ */
432
+ readonly renderRebuildContext: (
433
+ sessionID: SessionID,
434
+ opts?: { lastMessageInfo?: LastMessageInfo; agentID?: string },
435
+ ) => Effect.Effect<string>
436
+
437
+ readonly lastBoundary: (sessionID: SessionID) => Effect.Effect<MessageID | undefined>
438
+
439
+ readonly isWriterRunning: (sessionID: SessionID) => Effect.Effect<boolean>
440
+
441
+ /**
442
+ * Insert a synthetic checkpoint-boundary user message (boundary marker +
443
+ * index overview + rebuild context + active-actors text) just after the
444
+ * given boundary. Inserts nothing and returns false when rebuild context is
445
+ * empty. Never deletes DB messages.
446
+ */
447
+ readonly insertRebuildBoundary: (input: {
448
+ sessionID: SessionID
449
+ boundary: MessageID
450
+ lastMessageInfo?: LastMessageInfo
451
+ agentID?: string
452
+ agent: string
453
+ model: { providerID: string; modelID: string }
454
+ boundaryCreatedAt?: number
455
+ }) => Effect.Effect<boolean>
456
+ }
457
+
458
+ export class Service extends Context.Service<Service, Interface>()("@tulingcode/SessionCheckpoint") {}
459
+
460
+ // ---------------------------------------------------------------------------
461
+ // Writer state per session
462
+ // ---------------------------------------------------------------------------
463
+
464
+ export type WriterOutcome = "success" | "failure"
465
+
466
+ interface WriterState {
467
+ // Holds the AgentOutcome Deferred returned by Actor.spawn so callers can
468
+ // await writer settlement (waitForWriter / drainWriters). The public
469
+ // WriterOutcome translation happens in waitForWriter.
470
+ writing: Deferred.Deferred<AgentOutcome>
471
+ // F40: 1-slot pending queue. When set, holds the input for a writer that
472
+ // should fire as soon as `writing` settles. Newer requests evict older
473
+ // pending values — the newest range is always a strict superset of the
474
+ // older one, so older pending checkpoints would only duplicate work.
475
+ pending?: TryStartCheckpointWriterInput
476
+ }
477
+
478
+ // ---------------------------------------------------------------------------
479
+ // Layer implementation
480
+ // ---------------------------------------------------------------------------
481
+
482
+ export const layer: Layer.Layer<
483
+ Service,
484
+ never,
485
+ Session.Service | Bus.Service | Config.Service | Memory.Service | TaskRegistry.Service | ActorRegistry.Service
486
+ > = Layer.effect(
487
+ Service,
488
+ Effect.gen(function* () {
489
+ const session = yield* Session.Service
490
+ const config = yield* Config.Service
491
+ const memory = yield* Memory.Service
492
+ const taskRegistry = yield* TaskRegistry.Service
493
+ const actorRegistry = yield* ActorRegistry.Service
494
+ const bus = yield* Bus.Service
495
+ const scope = yield* Scope.Scope
496
+
497
+ // Plain Map in the layer closure — same approach as compaction.ts
498
+ const writers = new Map<SessionID, WriterState>()
499
+
500
+ const tryStartCheckpointWriter: (
501
+ input: TryStartCheckpointWriterInput,
502
+ ) => Effect.Effect<TryStartCheckpointWriterResult> = Effect.fn("SessionCheckpoint.tryStartCheckpointWriter")(function* (
503
+ input: TryStartCheckpointWriterInput,
504
+ ) {
505
+ // F40: writer1 still running. Evict any prior pending and queue this
506
+ // request — newest wins because its range is a strict superset of the
507
+ // older pending range, so older pending checkpoints would only
508
+ // duplicate the work.
509
+ const existing = writers.get(input.sessionID)
510
+ if (existing) {
511
+ if (existing.pending) {
512
+ log.info("writer pending evicted (newer range arrived)", { sessionID: input.sessionID })
513
+ } else {
514
+ log.info("writer already running, queueing", { sessionID: input.sessionID })
515
+ }
516
+ existing.pending = input
517
+ return "queued" as const
518
+ }
519
+
520
+ // Defensive: skip if called for a system-spawned session. With Task 27's
521
+ // writer-as-subagent migration this becomes mostly impossible, but the
522
+ // guard stays so future paths that fold a system-spawn actor into the
523
+ // main loop don't accidentally re-enter the writer.
524
+ if (yield* actorRegistry.isSystemSpawned(input.sessionID, "main")) {
525
+ log.info("tryStartCheckpointWriter skipping system-spawned session")
526
+ return "skipped" as const
527
+ }
528
+
529
+ // Mirror parent runLoop's view (prompt.ts:2036-2040) so the writer's
530
+ // ForkContext is byte-equal at the watermark moment. Reading the
531
+ // unfiltered session stream would let computeBoundary land on a
532
+ // subagent/prior-writer assistant turn and misalign the prefix cache.
533
+ const sessionInfo = yield* session.get(input.sessionID)
534
+ const msgs = yield* MessageV2.filterCompactedEffect(input.sessionID, {
535
+ contextFrom: sessionInfo.contextFrom,
536
+ contextWatermark: sessionInfo.contextWatermark,
537
+ agentID: "main",
538
+ })
539
+ if (msgs.length === 0) {
540
+ log.info("no messages, skipping checkpoint", { sessionID: input.sessionID })
541
+ return "skipped" as const
542
+ }
543
+
544
+ // Compute boundary for last_checkpoint_message_id bookkeeping. Layer 6
545
+ // (Task 16): role-aware adjustment to ensure tool_use/tool_result pairs
546
+ // and same-message.id thinking blocks aren't split. OpenCode's ToolPart
547
+ // carries both use (input) and result (output) on the SAME message, so
548
+ // we project each ToolPart to both a tool_use and a tool_result block —
549
+ // pairing is intrinsically satisfied today and the algorithm acts as a
550
+ // no-op. Wiring is in place so future tool_result extraction
551
+ // (separate user message) will walk the boundary correctly
552
+ // without further changes here.
553
+ const candidateID = computeBoundary(msgs)
554
+ const candidateIdx = msgs.findIndex((m) => m.info.id === candidateID)
555
+ const adjustedIdx = adjustBoundaryForApiInvariants(
556
+ msgs.map((m) => ({
557
+ role: m.info.role,
558
+ id: m.info.id,
559
+ content: m.parts.flatMap((p) =>
560
+ p.type === "tool"
561
+ ? [
562
+ { type: "tool_use", id: p.callID },
563
+ { type: "tool_result", tool_use_id: p.callID },
564
+ ]
565
+ : [],
566
+ ),
567
+ })),
568
+ Math.max(candidateIdx, 0),
569
+ )
570
+ const endMessageID = msgs[adjustedIdx]?.info.id ?? candidateID
571
+
572
+ // v5 paths: single checkpoint.md per session, single memory.md per
573
+ // project (carries across sessions in the same repo), task narrative
574
+ // under <sid>/tasks/<id>/. Resolve projectID once HERE — Instance.current
575
+ // is ALS-bound and lost once the writer subagent fiber detaches.
576
+ const projectID =
577
+ (yield* Effect.try({
578
+ try: () => Instance.current?.project?.id as ProjectID | undefined,
579
+ catch: () => undefined,
580
+ }).pipe(Effect.orElseSucceed(() => undefined))) ?? ProjectID.global
581
+ const sessMemDir = metaDir(input.sessionID)
582
+ const projectMemDir = path.join(Global.Path.data, "memory", "projects", projectID)
583
+ const checkpointFile = checkpointPath(input.sessionID)
584
+ const memoryFile = memoryPath(projectID)
585
+ const taskMemDir = path.join(sessMemDir, "tasks")
586
+ const notesFile = notesPath(input.sessionID)
587
+
588
+ // Ensure dirs exist before writer fires
589
+ yield* Effect.promise(() => fs.mkdir(sessMemDir, { recursive: true }))
590
+ yield* Effect.promise(() => fs.mkdir(taskMemDir, { recursive: true }))
591
+ yield* Effect.promise(() => fs.mkdir(projectMemDir, { recursive: true }))
592
+
593
+ // Migrate legacy lowercase memory.md → MEMORY.md before templating/reading.
594
+ yield* Effect.promise(() => migrateProjectMemory(projectID))
595
+
596
+ // Bootstrap checkpoint.md, memory.md, and notes.md from templates if missing.
597
+ // Self-contained helpers also mkdir parent so they're safe in isolation.
598
+ yield* Effect.promise(() => ensureCheckpointTemplate(checkpointFile))
599
+ yield* Effect.promise(() => ensureMemoryTemplate(memoryFile))
600
+ yield* Effect.promise(() => ensureNotesTemplate(notesFile))
601
+
602
+ // v5: single-file checkpoint, check if prior content exists
603
+ const checkpointExists = yield* Effect.promise(() => Bun.file(checkpointFile).exists())
604
+ const memoryExists = yield* Effect.promise(() => Bun.file(memoryFile).exists())
605
+ const rangeDesc = checkpointExists
606
+ ? [
607
+ `Previous checkpoint: ${checkpointFile}`,
608
+ memoryExists ? `Previous memory: ${memoryFile}` : "",
609
+ "Read BOTH the prior checkpoint (to dedupe Discovered/Dead-end titles AND to carry forward Live Resources, Execution-context frames, and Session-metadata fields that are still alive) AND the prior memory (project memory) before writing yours.",
610
+ ]
611
+ .filter((s) => s.length > 0)
612
+ .join("\n")
613
+ : "This is the first checkpoint of this session. No prior checkpoint exists; MEMORY.md and the task narrative directory likely don't exist yet either."
614
+
615
+ const progressDiff = yield* Effect.promise(() => buildProgressDiff(input.sessionID))
616
+ const promptText = composeWriterPrompt({ checkpointFile, memoryFile, taskMemDir, notesFile, rangeDesc, progressDiff })
617
+
618
+ // v6: spawn writer as subagent — shared sessionID, automatic
619
+ // ActorRegistry registration, automatic tool whitelist enforcement
620
+ // via permission system. Replaces the legacy session.create + manual
621
+ // forkDetach + WriterState tracking that lived here pre-Task-27.
622
+ //
623
+ // Resolved via spawnRef rather than `yield* Actor.Service` to break the
624
+ // (Actor → SessionPrompt → SessionCheckpoint → Actor) layer cycle.
625
+ const actor = spawnRef.current
626
+ if (!actor) {
627
+ log.warn("tryStartCheckpointWriter skipping — Actor service unavailable", { sessionID: input.sessionID })
628
+ return "skipped" as const
629
+ }
630
+
631
+ // Axis B: branch forkContext shape on config.checkpoint.fork.
632
+ // - true → preserve existing prefix-cache parent-fork behavior
633
+ // (parent agent's system + tools, full slice up to watermark).
634
+ // - false → cold-start: writer's own system + tools, delta slice since
635
+ // last_checkpoint_message_id (aligned past tool_use/tool_result).
636
+ // See spec 2026-06-09-checkpoint-writer-child-session-and-no-fork-fallback-design.md §3.
637
+ //
638
+ // Default-behavior change at this PR: previously the writer always forked
639
+ // the parent's full prefix (effectively fork: true). The default is now
640
+ // false (no-fork delta-only). Users on cache-breakpoint providers
641
+ // (Anthropic) who want to retain the prefix-cache benefit must set
642
+ // `checkpoint.fork: true` in their config. See the spec at
643
+ // docs/superpowers/specs/2026-06-09-checkpoint-writer-child-session-and-no-fork-fallback-design.md §4.5.
644
+ const cfg = yield* config.get()
645
+ const forkMode = cfg.checkpoint?.fork ?? false
646
+
647
+ const parentRow = yield* Effect.sync(() =>
648
+ Database.use((d) =>
649
+ d.select({ last: SessionTable.last_checkpoint_message_id })
650
+ .from(SessionTable)
651
+ .where(eq(SessionTable.id, input.sessionID))
652
+ .get(),
653
+ ),
654
+ ).pipe(Effect.catch(() => Effect.succeed(undefined as { last: MessageID | null } | undefined)))
655
+ const lastCheckpointMessageID = parentRow?.last ?? undefined
656
+
657
+ // Hoisted watermark + delta computation: must run BEFORE session.create
658
+ // so an empty-delta fork:false call short-circuits to "skipped" without
659
+ // creating a child session or invoking actor.spawn. Pre-fix, the
660
+ // empty-delta path fell through to spawn → runLoop's
661
+ // `isForkAgent && !forkCtx → break` → settle watcher resolved success →
662
+ // parent's last_checkpoint_message_id advanced silently (stale checkpoint).
663
+ const watermarkIdx = msgs.findIndex((m) => m.info.id === endMessageID)
664
+ if (watermarkIdx < 0) {
665
+ log.warn("tryStartCheckpointWriter: watermark message not found, skipping", {
666
+ sessionID: input.sessionID,
667
+ endMessageID,
668
+ })
669
+ return "skipped" as const
670
+ }
671
+
672
+ // For fork:false only: precompute the aligned delta and bail if empty.
673
+ // fork:true uses msgs.slice(0, watermarkIdx + 1) which is never empty
674
+ // given msgs.length > 0 and watermarkIdx >= 0.
675
+ const lastIdx = lastCheckpointMessageID
676
+ ? msgs.findIndex((m) => m.info.id === lastCheckpointMessageID)
677
+ : -1
678
+ const rawDeltaStart = lastIdx >= 0 ? lastIdx + 1 : 0
679
+ const alignedStart = alignToNonToolResultUser(
680
+ msgs.map((m) => ({ info: { role: m.info.role }, parts: m.parts })),
681
+ rawDeltaStart,
682
+ )
683
+ const delta = forkMode ? [] : msgs.slice(alignedStart, watermarkIdx + 1)
684
+ if (!forkMode && delta.length === 0) {
685
+ // Empty delta under fork:false signals either (a) a degenerate
686
+ // session, or (b) a bug elsewhere advanced last_checkpoint_message_id
687
+ // past the watermark. Either way, spawning a writer would be a
688
+ // silent no-op that would still advance the watermark on settle —
689
+ // skip visibly so it's observable in logs.
690
+ log.warn("tryStartCheckpointWriter: empty delta under fork:false, skipping", {
691
+ sessionID: input.sessionID,
692
+ endMessageID,
693
+ lastCheckpointMessageID,
694
+ })
695
+ return "skipped" as const
696
+ }
697
+
698
+ // Capture parent's view at the watermark for prefix-cache alignment.
699
+ // See docs/superpowers/specs/2026-05-26-fork-agent-prefix-cache-design.md
700
+ //
701
+ // prefixCaptureRef is populated by SessionPrompt.layer to break the
702
+ // (ToolRegistry → SessionCheckpoint → ToolRegistry) layer cycle.
703
+ const buildPrefix = prefixCaptureRef.current
704
+ if (!buildPrefix) {
705
+ log.warn("tryStartCheckpointWriter: prefixCaptureRef not set, spawning without forkContext", {
706
+ sessionID: input.sessionID,
707
+ })
708
+ }
709
+ const forkCtx: ForkContext | undefined = yield* (buildPrefix
710
+ ? Effect.gen(function* () {
711
+ const watermarkMsg = msgs[watermarkIdx]
712
+ const parentAgentName = (watermarkMsg.info as { agent?: string }).agent
713
+ // NOTE: parentAgentName guard is scoped to the forkMode:true branch only —
714
+ // fork:false is agent-name-independent (always passes "checkpoint-writer"
715
+ // to buildPrefix), so a missing parent agent field must not gate it.
716
+
717
+ if (forkMode) {
718
+ if (!parentAgentName) {
719
+ log.warn(
720
+ "tryStartCheckpointWriter: watermark has no agent, fork:true requires parent agent — falling back to no forkContext",
721
+ { sessionID: input.sessionID, endMessageID },
722
+ )
723
+ return undefined as ForkContext | undefined
724
+ }
725
+ // fork:true — preserve existing prefix-cache parent-fork behavior.
726
+ // Build system + tools + inheritedMessages snapshot via capture ref
727
+ // using the parent agent's identity and the full slice up to watermark.
728
+ // The closure inside SessionPrompt.layer resolves Agent.Info and Provider.Model.
729
+ const msgsAtWatermark = msgs.slice(0, watermarkIdx + 1)
730
+ const prefix = yield* buildPrefix({
731
+ sessionID: input.sessionID,
732
+ agentName: parentAgentName,
733
+ providerID: input.model.providerID,
734
+ modelID: input.model.modelID,
735
+ msgs: msgsAtWatermark,
736
+ })
737
+ return {
738
+ system: prefix.system,
739
+ tools: prefix.tools,
740
+ inheritedMessages: prefix.inheritedMessages,
741
+ parentPermission: prefix.parentPermission,
742
+ watermarkMsgID: endMessageID as MessageID,
743
+ model: {
744
+ providerID: input.model.providerID as ProviderID,
745
+ modelID: input.model.modelID as ModelID,
746
+ },
747
+ } satisfies ForkContext
748
+ }
749
+
750
+ // fork:false — cold-start: use the writer's own system + tools and
751
+ // a delta slice since the last checkpoint. capture() resolves the
752
+ // checkpoint-writer agent's own definition when agentName is
753
+ // "checkpoint-writer", so a single call returns:
754
+ // - system + tools = writer's (because agentName === "checkpoint-writer"),
755
+ // - inheritedMessages = the delta we pass in, converted to ModelMessage[],
756
+ // - parentPermission = writer's own permission (used for tool-availability filter).
757
+ // Earlier draft considered two buildPrefix calls (one with msgs:[] for
758
+ // system+tools, one with msgs:delta for messages); rejected because
759
+ // buildLLMRequestPrefix `Effect.die`s if msgs has no user message.
760
+ // Delta is precomputed (and the empty-delta case is short-circuited)
761
+ // above, before session.create.
762
+ const writerPrefix = yield* buildPrefix({
763
+ sessionID: input.sessionID,
764
+ agentName: "checkpoint-writer",
765
+ providerID: input.model.providerID,
766
+ modelID: input.model.modelID,
767
+ msgs: delta,
768
+ })
769
+
770
+ return {
771
+ system: writerPrefix.system,
772
+ tools: writerPrefix.tools,
773
+ inheritedMessages: writerPrefix.inheritedMessages,
774
+ parentPermission: writerPrefix.parentPermission,
775
+ watermarkMsgID: endMessageID as MessageID,
776
+ model: {
777
+ providerID: input.model.providerID as ProviderID,
778
+ modelID: input.model.modelID as ModelID,
779
+ },
780
+ } satisfies ForkContext
781
+ })
782
+ : Effect.succeed(undefined as ForkContext | undefined))
783
+
784
+ // Axis A: writer always runs in a fresh child session. This isolates the
785
+ // writer's messages and actor registration from the parent so:
786
+ // - parent's message table sees zero new rows,
787
+ // - parent's `sync.data.actor[parent]` does not include the writer,
788
+ // - Ctrl+X subagent cycle / SubagentFooter / DialogSubagent / etc. are
789
+ // all naturally clean (they all key on sessionID).
790
+ // The writer's checkpoint.md / memory.md / progress paths are absolute and
791
+ // computed from input.sessionID (parent) above, so file writes still target
792
+ // the parent's artifacts. Settle watcher below also targets parent.
793
+ // See spec 2026-06-09-checkpoint-writer-child-session-and-no-fork-fallback-design.md §2.
794
+ const writerChildSession = yield* session.create({
795
+ parentID: input.sessionID,
796
+ title: `checkpoint-writer: ${rangeDesc}`,
797
+ })
798
+
799
+ // Estimate delta tokens for observability. forkCtx.inheritedMessages is
800
+ // ModelMessage[]; an exact count requires the tokenizer, but a rough
801
+ // length heuristic is sufficient for the log line.
802
+ const deltaApproxBytes = JSON.stringify(forkCtx?.inheritedMessages ?? []).length
803
+ log.info("tryStartCheckpointWriter spawning", {
804
+ sessionID: input.sessionID,
805
+ childSessionID: writerChildSession.id,
806
+ mode: forkMode ? "fork" : "no-fork",
807
+ deltaApproxBytes,
808
+ rangeDesc,
809
+ })
810
+
811
+ const result = yield* actor.spawn({
812
+ mode: "subagent",
813
+ sessionID: writerChildSession.id,
814
+ // Axis A: writer runs under child session, but its checkpoint.md /
815
+ // memory.md / progress paths AND CheckpointContext entries are keyed
816
+ // on the PARENT. The splitover plugin reads these via actor.preStop
817
+ // and must see parentSessionID to re-derive the right paths — without
818
+ // it, checkpointPath(child) returns an empty file and the plugin
819
+ // emits a false topic-missing reflection that loops the writer up to
820
+ // MAX_PRE_REACT.
821
+ parentSessionID: input.sessionID,
822
+ agentType: "checkpoint-writer",
823
+ description: `checkpoint writer for session ${input.sessionID} covering ${rangeDesc}`,
824
+ task: promptText,
825
+ context: "full",
826
+ tools: ["read", "write", "edit", "apply_patch", "glob", "grep", "task"],
827
+ model: {
828
+ providerID: input.model.providerID as ProviderID,
829
+ modelID: input.model.modelID as ModelID,
830
+ },
831
+ background: true,
832
+ forkContext: forkCtx,
833
+ })
834
+
835
+ const actorID = result.actorID
836
+
837
+ // Capture priorTitles (from checkpoint.md as it stood at the watermark)
838
+ // and register the per-actor context entry BEFORE the writer's first
839
+ // turn so the splitover plugin's preStop hook can read it. The set
840
+ // runs in microseconds; the writer's first LLM round-trip takes
841
+ // seconds — no race in practice. See spec §6.1.
842
+ const priorTitles = yield* Effect.promise(() => loadPriorDiscoveredTitles(input.sessionID))
843
+ CheckpointContext.set(input.sessionID, actorID, {
844
+ priorTitles,
845
+ expectedRevisions: [],
846
+ })
847
+
848
+ writers.set(input.sessionID, { writing: result.outcome })
849
+
850
+ // Bookkeeping: the parent's last_checkpoint_message_id advances when the
851
+ // writer settles. Fork into the layer's scope so the watcher survives
852
+ // tryStartCheckpointWriter returning (background: true semantics) but is still tied
853
+ // to the layer's lifetime — no orphan fiber on shutdown.
854
+ yield* Effect.gen(function* () {
855
+ const outcome = yield* Deferred.await(result.outcome)
856
+ yield* Effect.sync(() =>
857
+ Database.use((d) =>
858
+ d.update(SessionTable)
859
+ .set({ last_checkpoint_message_id: endMessageID as MessageID })
860
+ .where(eq(SessionTable.id, input.sessionID))
861
+ .run(),
862
+ ),
863
+ )
864
+
865
+ // F40: capture pending before deleting the slot so a queued writer
866
+ // (held while writer1 was running) can fire as a fresh writer.
867
+ const pending = writers.get(input.sessionID)?.pending
868
+ writers.delete(input.sessionID)
869
+
870
+ // F44: aggregate writer slice tokens and emit cache-perf metric so
871
+ // prefix-cache reuse is empirically observable. Degrades to zeros if
872
+ // aggregation fails (e.g. session messages unavailable post-shutdown).
873
+ const stats = yield* aggregateWriterCacheMetrics(session, input.sessionID, result.actorID).pipe(
874
+ Effect.catch(() =>
875
+ Effect.succeed({
876
+ total_input_tokens: 0,
877
+ cache_read_tokens: 0,
878
+ cache_write_tokens: 0,
879
+ cache_hit_rate: 0,
880
+ num_llm_calls: 0,
881
+ }),
882
+ ),
883
+ )
884
+ yield* bus
885
+ .publish(WriterCachePerf, {
886
+ sessionID: input.sessionID,
887
+ writerActorID: result.actorID,
888
+ status: outcome.status === "success" ? ("completed" as const) : ("failed" as const),
889
+ ...stats,
890
+ })
891
+ .pipe(Effect.ignore)
892
+
893
+ // F40: drain pending. If a queued request exists, fire a fresh writer
894
+ // for it. Errors are swallowed — the queued writer's failure should
895
+ // not interrupt the original writer's settlement watcher.
896
+ if (pending) {
897
+ log.info("draining pending writer", { sessionID: input.sessionID })
898
+ yield* tryStartCheckpointWriter(pending).pipe(Effect.ignore)
899
+ }
900
+ }).pipe(
901
+ Effect.ensuring(
902
+ Effect.sync(() => CheckpointContext.remove(input.sessionID, actorID)),
903
+ ),
904
+ Effect.forkIn(scope),
905
+ )
906
+
907
+ return "started" as const
908
+ })
909
+
910
+ const waitForWriter = Effect.fn("SessionCheckpoint.waitForWriter")(function* (sessionID: SessionID) {
911
+ const state = writers.get(sessionID)
912
+ if (!state) return "no-writer" as const
913
+
914
+ // v2 writers manage 3 file types and frequently take 60-180s; pad to
915
+ // 5min so a long-but-honest writer is not mistaken for a failure by
916
+ // the prune retry watcher. AgentOutcome → WriterOutcome translation:
917
+ // success → "success", failure / cancelled → "failure".
918
+ const outcome = yield* Deferred.await(state.writing).pipe(
919
+ Effect.timeout(300_000),
920
+ Effect.catch(() => Effect.succeed<AgentOutcome>({ status: "failure", error: "timeout" })),
921
+ )
922
+ return outcome.status === "success" ? ("success" as const) : ("failure" as const)
923
+ })
924
+
925
+ const drainWriters = Effect.fn("SessionCheckpoint.drainWriters")(function* (input?: { timeoutMs?: number }) {
926
+ const timeoutMs = input?.timeoutMs ?? 120_000
927
+ const pending = [...writers.values()]
928
+ if (pending.length === 0) return { drained: 0, timedOut: 0 }
929
+ log.info("draining checkpoint writers before shutdown", {
930
+ count: pending.length,
931
+ timeoutMs,
932
+ })
933
+
934
+ // Deferred.await ignores fiber interruption during shutdown because
935
+ // it resolves via Deferred.succeed in the detached writer. We only
936
+ // need a collective upper bound so a stuck writer doesn't block exit.
937
+ yield* Effect.all(
938
+ pending.map((state) => Deferred.await(state.writing)),
939
+ { concurrency: "unbounded" },
940
+ ).pipe(
941
+ Effect.timeout(timeoutMs),
942
+ Effect.catch(() => Effect.succeed(undefined)),
943
+ )
944
+
945
+ // Writers delete themselves from the map on success/failure, so anything
946
+ // still present after the timeout is a writer that didn't settle in time.
947
+ const timedOut = writers.size
948
+ const drained = pending.length - timedOut
949
+ if (timedOut > 0) log.warn("drain timed out, writers still pending", { drained, timedOut })
950
+ else log.info("drain complete", { drained })
951
+ return { drained, timedOut }
952
+ })
953
+
954
+ const hasCheckpoint = Effect.fn("SessionCheckpoint.hasCheckpoint")(function* (sessionID: SessionID) {
955
+ return yield* Effect.promise(() => Bun.file(checkpointPath(sessionID)).exists())
956
+ })
957
+
958
+ const hasMemoryOrTasks = Effect.fn("SessionCheckpoint.hasMemoryOrTasks")(function* (sessionID: SessionID) {
959
+ const memoryRoot = yield* memory.root()
960
+ const sessMemDir = path.join(memoryRoot, "sessions", sessionID)
961
+ const memEntries = yield* Effect.promise(() =>
962
+ fs.readdir(sessMemDir).catch(() => [] as string[]),
963
+ )
964
+ if (memEntries.length > 0) return true
965
+ const tasks = yield* taskRegistry.list({ session_id: sessionID, include_terminal: true })
966
+ return tasks.length > 0
967
+ })
968
+
969
+ const loadLatest = Effect.fn("SessionCheckpoint.loadLatest")(function* (sessionID: SessionID) {
970
+ const content = yield* Effect.promise(() =>
971
+ Bun.file(checkpointPath(sessionID)).text().catch(() => ""),
972
+ )
973
+ return content || undefined
974
+ })
975
+
976
+ const loadCheckpoints = Effect.fn("SessionCheckpoint.loadCheckpoints")(function* (
977
+ sessionID: SessionID,
978
+ _count: number,
979
+ ) {
980
+ const content = yield* Effect.promise(() =>
981
+ Bun.file(checkpointPath(sessionID)).text().catch(() => ""),
982
+ )
983
+ return content ? [content] : []
984
+ })
985
+
986
+ const renderIndex = Effect.fn("SessionCheckpoint.renderIndex")(function* (sessionID: SessionID) {
987
+ const snapFile = checkpointPath(sessionID)
988
+ const exists = yield* Effect.promise(() => Bun.file(snapFile).exists())
989
+ if (!exists) return "No checkpoints yet for this session."
990
+
991
+ const content = yield* Effect.promise(() => Bun.file(snapFile).text().catch(() => ""))
992
+ const topicMatch = content.match(/^Topic:\s*(.+)$/m)
993
+ const topic = topicMatch ? topicMatch[1].trim() : "(unknown)"
994
+
995
+ const dir = metaDir(sessionID)
996
+ const lines: string[] = []
997
+ lines.push("## Checkpoint")
998
+ lines.push("")
999
+ lines.push(`Directory: ${dir}/`)
1000
+ lines.push("")
1001
+ lines.push(`Current checkpoint (${topic}): checkpoint.md [shown below]`)
1002
+ lines.push("")
1003
+ lines.push(`Use read("${snapFile}") to access the full checkpoint.`)
1004
+
1005
+ return lines.join("\n")
1006
+ })
1007
+
1008
+ const renderRebuildContext = Effect.fn("SessionCheckpoint.renderRebuildContext")(function* (
1009
+ sessionID: SessionID,
1010
+ opts?: { lastMessageInfo?: LastMessageInfo; agentID?: string },
1011
+ ) {
1012
+ // renderRebuildContext is for the user-facing main agent's context rebuild.
1013
+ // Subagent-mode actors (system-spawned writers, model-spawned subagents)
1014
+ // share the parent's session but don't have their own checkpoint state to
1015
+ // render — return empty so the rebuild path is a no-op for them.
1016
+ // Note: agentID === "main" must pass through. After F49+F50 the main
1017
+ // agent's lastUser.agentID is "main" (DB row→info reconstruction in
1018
+ // message-v2.ts populates info.agentID from agent_id column), and the
1019
+ // runLoop calls this with that value. Treating "main" as subagent here
1020
+ // would skip rebuild → fall through to F39 compaction → context loss.
1021
+ if (opts?.agentID && opts.agentID !== "main") return ""
1022
+
1023
+ const inFlight = writers.get(sessionID)
1024
+ if (inFlight) {
1025
+ log.info("rebuild waiting for in-flight writer", { sessionID })
1026
+ yield* Effect.race(
1027
+ Deferred.await(inFlight.writing).pipe(Effect.as("done" as const)),
1028
+ Effect.sleep("60 seconds").pipe(
1029
+ Effect.tap(() => Effect.sync(() => log.warn("writer wait timeout — using on-disk checkpoint", { sessionID }))),
1030
+ Effect.as("timeout" as const),
1031
+ ),
1032
+ ).pipe(Effect.catch(() => Effect.succeed("error" as const)))
1033
+ }
1034
+
1035
+ const cfg = yield* config.get()
1036
+ const caps = cfg.checkpoint?.push_caps ?? {}
1037
+ const memoryRoot = yield* memory.root()
1038
+
1039
+ const sessMemDir = path.join(memoryRoot, "sessions", sessionID)
1040
+
1041
+ // Resolve current project ID once. Used by Section 7 (project memory
1042
+ // read) and Section 8 (FTS scope filter). ALS-bound — must be resolved
1043
+ // before any deferred work.
1044
+ const currentProjectID = yield* Effect.try({
1045
+ try: () => Instance.current?.project?.id as ProjectID | undefined,
1046
+ catch: () => undefined as ProjectID | undefined,
1047
+ }).pipe(Effect.catch(() => Effect.succeed<ProjectID | undefined>(undefined)))
1048
+ const projectID = currentProjectID ?? ProjectID.global
1049
+
1050
+ // Section data: tasks (SQL), session checkpoint (file), project memory (file).
1051
+ const tasks = yield* taskRegistry.list({ session_id: sessionID, include_terminal: true })
1052
+
1053
+ const checkpointResult = yield* Effect.promise(() =>
1054
+ readBudgetedSectionAware(checkpointPath(sessionID), caps.checkpoint ?? 11_000),
1055
+ )
1056
+ const checkpointText = checkpointResult?.text ?? ""
1057
+
1058
+ yield* Effect.promise(() => migrateProjectMemory(projectID))
1059
+ const memoryResult = yield* Effect.promise(() =>
1060
+ readBudgetedSectionAware(memoryPath(projectID), caps.memory ?? 10_000),
1061
+ )
1062
+ const memoryText = memoryResult?.text ?? ""
1063
+
1064
+ const notesResult = yield* Effect.promise(() =>
1065
+ readBudgeted(notesPath(sessionID), caps.notes ?? 6000),
1066
+ )
1067
+ const notesText = notesResult?.text ?? ""
1068
+
1069
+ const globalResult = yield* Effect.promise(() =>
1070
+ readBudgetedSectionAware(globalMemoryPath(), caps.global ?? 6000),
1071
+ )
1072
+ const globalText = globalResult?.text ?? ""
1073
+
1074
+ const actors = yield* actorRegistry.listActive()
1075
+
1076
+ // Bail early if absolutely nothing to push: no tasks, no memory content, no live actors.
1077
+ if (
1078
+ tasks.length === 0 &&
1079
+ !checkpointText.trim() &&
1080
+ !memoryText.trim() &&
1081
+ !globalText.trim() &&
1082
+ actors.length === 0
1083
+ ) {
1084
+ return ""
1085
+ }
1086
+
1087
+ const lines: string[] = []
1088
+
1089
+ // F17: Explicit "already loaded" header. Anchors the active recall
1090
+ // protocol's "look for this header" instruction in buildMemoryInstructions.
1091
+ lines.push(
1092
+ "The following blocks are auto-loaded from your session memory. They are already in your context — do not Read them as whole files. Use Grep for specific facts instead.",
1093
+ )
1094
+ lines.push("")
1095
+
1096
+ // Section 3: tasks ledger (hierarchical with subtasks).
1097
+ lines.push("## Tasks ledger")
1098
+ if (tasks.length === 0) {
1099
+ lines.push("(none)")
1100
+ } else {
1101
+ const topLevel = tasks.filter((t) => !t.parent_task_id)
1102
+ const byParent = new Map<string, typeof tasks>()
1103
+ for (const t of tasks) {
1104
+ if (!t.parent_task_id) continue
1105
+ const bucket = byParent.get(t.parent_task_id) ?? []
1106
+ bucket.push(t)
1107
+ byParent.set(t.parent_task_id, bucket)
1108
+ }
1109
+ const statusIcon = (s: string) =>
1110
+ ({ open: "🔵", in_progress: "🔄", blocked: "🟡", done: "✅", abandoned: "❌" })[s] ?? s
1111
+ const ledgerLines: string[] = []
1112
+ for (const t of topLevel) {
1113
+ ledgerLines.push(`- ${t.id} ${t.status} — ${t.summary}`)
1114
+ const subs = byParent.get(t.id) ?? []
1115
+ if (subs.length === 0) continue
1116
+ const sublist = subs
1117
+ .map((s) => `${statusIcon(s.status)}${s.id}`)
1118
+ .join(" / ")
1119
+ ledgerLines.push(` Subtasks: ${sublist}`)
1120
+ }
1121
+ lines.push(truncate(ledgerLines.join("\n"), caps.tasks_ledger ?? 2000))
1122
+ }
1123
+ lines.push("")
1124
+
1125
+ // Section 5: session checkpoint (full body, capped).
1126
+ if (checkpointText.trim()) {
1127
+ lines.push("## Session checkpoint")
1128
+ lines.push(checkpointText.trim())
1129
+ lines.push("")
1130
+ }
1131
+
1132
+ // Section 6: active actors ledger (one line per running actor).
1133
+ if (actors.length > 0) {
1134
+ lines.push("## Active actors")
1135
+ let actorBudget = caps.actor_ledger ?? 500
1136
+ for (const a of actors) {
1137
+ const line = `- ${a.actorID} — ${a.status}, "${a.description}" (agent=${a.agent})`
1138
+ const cost = Token.estimate(line)
1139
+ if (actorBudget - cost < 0) break
1140
+ lines.push(line)
1141
+ actorBudget -= cost
1142
+ }
1143
+ lines.push("")
1144
+ }
1145
+
1146
+ // Section 7: project memory (full body, capped).
1147
+ if (memoryText.trim()) {
1148
+ lines.push("## Project memory")
1149
+ lines.push(memoryText.trim())
1150
+ lines.push("")
1151
+ }
1152
+
1153
+ // Section 7.4: global memory (full body, capped). User-level cross-project
1154
+ // preferences. Placed after project memory (more actionable) and before
1155
+ // session notes (more volatile).
1156
+ if (globalText.trim()) {
1157
+ lines.push("## Global memory")
1158
+ lines.push(globalText.trim())
1159
+ lines.push("")
1160
+ }
1161
+
1162
+ // F14 Section 7.5: session notes (full body, capped). Skip if empty.
1163
+ if (notesText.trim()) {
1164
+ lines.push("## Session notes")
1165
+ lines.push(notesText.trim())
1166
+ lines.push("")
1167
+ }
1168
+
1169
+ // Section 8: memory keys index (paths only, omit already-pushed).
1170
+ // SQL-scoped to the current session/project + global so other
1171
+ // sessions' files are not leaked. Falls back to skipping the projects
1172
+ // scope when the current project is the global/non-git fallback.
1173
+ // Reconcile first so files written off-tool (e.g. by the checkpoint
1174
+ // writer subagent) are visible in the FTS index here.
1175
+ yield* memory.reconcile().pipe(Effect.ignore)
1176
+ const pushedPaths = new Set(
1177
+ [
1178
+ memoryPath(projectID),
1179
+ checkpointPath(sessionID),
1180
+ globalMemoryPath(),
1181
+ ].filter((p) => p.length > 0),
1182
+ )
1183
+
1184
+ const scopeFilter =
1185
+ currentProjectID && currentProjectID !== ProjectID.global
1186
+ ? or(
1187
+ eq(MemoryFtsTable.scope, "global"),
1188
+ and(eq(MemoryFtsTable.scope, "sessions"), eq(MemoryFtsTable.scope_id, sessionID as string)),
1189
+ and(eq(MemoryFtsTable.scope, "projects"), eq(MemoryFtsTable.scope_id, currentProjectID)),
1190
+ )
1191
+ : or(
1192
+ eq(MemoryFtsTable.scope, "global"),
1193
+ and(eq(MemoryFtsTable.scope, "sessions"), eq(MemoryFtsTable.scope_id, sessionID as string)),
1194
+ )
1195
+ const scopedPaths = yield* Effect.sync(() =>
1196
+ Database.use((db) =>
1197
+ db.select({ path: MemoryFtsTable.path }).from(MemoryFtsTable).where(scopeFilter).all(),
1198
+ ),
1199
+ )
1200
+ const keyEntries = scopedPaths
1201
+ .map((r) => r.path)
1202
+ .filter((p) => !pushedPaths.has(p) && !p.includes(`${path.sep}checkpoint${path.sep}learning-`))
1203
+ .map((p) => p.replace(memoryRoot + path.sep, ""))
1204
+ if (keyEntries.length > 0) {
1205
+ lines.push("## Memory keys index")
1206
+ let kBudget = caps.memory_titles ?? 500
1207
+ for (const entry of keyEntries) {
1208
+ const cost = Token.estimate(entry)
1209
+ if (kBudget - cost < 0) break
1210
+ lines.push(`- ${entry}`)
1211
+ kBudget -= cost
1212
+ }
1213
+ lines.push("")
1214
+ }
1215
+
1216
+ // Section 10: explicit seam framing for LLM continuity post-rebuild.
1217
+ // Compaction-summary pattern: tells the model
1218
+ // that preserved messages below are real history, not pseudo-content,
1219
+ // so it resumes mid-loop instead of asking "what would you like me
1220
+ // to do".
1221
+ lines.push("")
1222
+ lines.push(
1223
+ "This session is being continued from a previous conversation that hit a checkpoint. The session checkpoint and project memory above cover the earlier portion of the conversation.",
1224
+ )
1225
+ lines.push("")
1226
+ lines.push(
1227
+ "Recent messages are preserved verbatim below — the assistant turn (and any tool results) you'll see is real history, not pseudo-content. Continue your task by responding to the most recent state.",
1228
+ )
1229
+ lines.push("")
1230
+ lines.push(
1231
+ "Resume directly. Do not acknowledge this memory dump, do not recap, do not preface with \"I'll continue\" or similar. Pick up the last task as if the break never happened.",
1232
+ )
1233
+
1234
+ // Section 11: tail-aware system reminder. Picks the appropriate nudge
1235
+ // based on how the preserved tail ends: tool-calls → continue loop,
1236
+ // stop → check task spec before stopping again, tool → process results,
1237
+ // user → no addendum needed.
1238
+ const info = opts?.lastMessageInfo
1239
+ if (info) {
1240
+ const reminder = (() => {
1241
+ switch (info.role) {
1242
+ case "assistant":
1243
+ if (info.finish === "tool-calls") return autonomousLoopReminder()
1244
+ return stopReminder(undefined)
1245
+ case "tool":
1246
+ return toolResultContinueReminder()
1247
+ case "user":
1248
+ return ""
1249
+ }
1250
+ })()
1251
+ if (reminder) {
1252
+ lines.push("")
1253
+ lines.push(reminder)
1254
+ }
1255
+ }
1256
+
1257
+ return lines.join("\n")
1258
+ })
1259
+
1260
+ const lastBoundary = Effect.fn("SessionCheckpoint.lastBoundary")(function* (sessionID: SessionID) {
1261
+ const row = yield* Effect.sync(() =>
1262
+ Database.use((db) =>
1263
+ db.select({ last_checkpoint_message_id: SessionTable.last_checkpoint_message_id })
1264
+ .from(SessionTable)
1265
+ .where(eq(SessionTable.id, sessionID))
1266
+ .get(),
1267
+ ),
1268
+ )
1269
+ return row?.last_checkpoint_message_id as MessageID | undefined
1270
+ })
1271
+
1272
+ const isWriterRunning = Effect.fn("SessionCheckpoint.isWriterRunning")(function* (sessionID: SessionID) {
1273
+ return writers.has(sessionID)
1274
+ })
1275
+
1276
+ const insertRebuildBoundary = Effect.fn("SessionCheckpoint.insertRebuildBoundary")(function* (input: {
1277
+ sessionID: SessionID
1278
+ boundary: MessageID
1279
+ lastMessageInfo?: LastMessageInfo
1280
+ agentID?: string
1281
+ agent: string
1282
+ model: { providerID: string; modelID: string }
1283
+ boundaryCreatedAt?: number
1284
+ }) {
1285
+ const rebuildContext = yield* renderRebuildContext(input.sessionID, {
1286
+ lastMessageInfo: input.lastMessageInfo,
1287
+ agentID: input.agentID,
1288
+ }).pipe(Effect.catch(() => Effect.succeed("")))
1289
+ if (!rebuildContext) return false
1290
+
1291
+ const indexText = yield* renderIndex(input.sessionID).pipe(Effect.catch(() => Effect.succeed("")))
1292
+
1293
+ const syntheticTime = (input.boundaryCreatedAt ?? Date.now()) + 1
1294
+ const msg = yield* session.updateMessage({
1295
+ id: MessageID.ascending(),
1296
+ role: "user" as const,
1297
+ model: { providerID: input.model.providerID as ProviderID, modelID: input.model.modelID as ModelID },
1298
+ sessionID: input.sessionID,
1299
+ agent: input.agent,
1300
+ time: { created: syntheticTime },
1301
+ })
1302
+
1303
+ yield* session.updatePart({
1304
+ id: PartID.ascending(),
1305
+ messageID: msg.id,
1306
+ sessionID: input.sessionID,
1307
+ type: "checkpoint",
1308
+ checkpointDir: "",
1309
+ checkpointNumber: 0,
1310
+ coveredUpTo: input.boundary,
1311
+ })
1312
+
1313
+ if (indexText) {
1314
+ yield* session.updatePart({
1315
+ id: PartID.ascending(),
1316
+ messageID: msg.id,
1317
+ sessionID: input.sessionID,
1318
+ type: "text",
1319
+ synthetic: true,
1320
+ text: indexText,
1321
+ })
1322
+ }
1323
+
1324
+ yield* session.updatePart({
1325
+ id: PartID.ascending(),
1326
+ messageID: msg.id,
1327
+ sessionID: input.sessionID,
1328
+ type: "text",
1329
+ synthetic: true,
1330
+ text: rebuildContext,
1331
+ })
1332
+
1333
+ const actorsText = yield* actorRegistry
1334
+ .renderForAgent(input.sessionID)
1335
+ .pipe(Effect.catch(() => Effect.succeed("")))
1336
+ if (actorsText) {
1337
+ yield* session.updatePart({
1338
+ id: PartID.ascending(),
1339
+ messageID: msg.id,
1340
+ sessionID: input.sessionID,
1341
+ type: "text",
1342
+ synthetic: true,
1343
+ text: actorsText,
1344
+ })
1345
+ }
1346
+
1347
+ // Microcompact: messages strictly newer than the boundary will survive
1348
+ // into the rebuild context. Clear tool_result content for compactable
1349
+ // tools so the first uncached request after rebuild is smaller. tool_use
1350
+ // is preserved — LLM still sees what action was taken; result body
1351
+ // becomes "[Old tool result content cleared]" via the converter at
1352
+ // message-v2.ts (ToolStateCompleted → output).
1353
+ // See docs/superpowers/specs/2026-06-03-rebuild-tail-microcompact-design.md.
1354
+ //
1355
+ // boundaryTime resolution (fail-closed):
1356
+ // 1. Prefer explicit input.boundaryCreatedAt (production callers
1357
+ // compute it but may pass undefined if the boundary message is no
1358
+ // longer in their filterCompactedEffect slice).
1359
+ // 2. Else look up input.boundary in allMsgs (full DB, includes
1360
+ // pre-marker history).
1361
+ // 3. Else SKIP — the previous fallback of 0 would clear EVERY
1362
+ // completed compactable tool result in the entire session,
1363
+ // corrupting future checkpoint writer input. Log a warning.
1364
+ const allMsgs = yield* session.messages({ sessionID: input.sessionID, agentID: "*" })
1365
+ const boundaryTime =
1366
+ input.boundaryCreatedAt ??
1367
+ allMsgs.find((m) => m.info.id === input.boundary)?.info.time.created
1368
+ if (boundaryTime === undefined) {
1369
+ log.warn("microcompact skipped: no boundary timestamp available", {
1370
+ sessionID: input.sessionID,
1371
+ boundary: input.boundary,
1372
+ })
1373
+ return true
1374
+ }
1375
+ let cleared = 0
1376
+ for (const m of allMsgs) {
1377
+ if (m.info.id === msg.id) continue
1378
+ if (m.info.time.created <= boundaryTime) continue
1379
+ for (const part of m.parts) {
1380
+ if (part.type !== "tool") continue
1381
+ if (!COMPACTABLE_TOOL_NAMES.has(part.tool)) continue
1382
+ if (part.state.status !== "completed") continue
1383
+ if (part.state.time.compacted) continue
1384
+ part.state.time.compacted = Date.now()
1385
+ yield* session.updatePart(part)
1386
+ cleared += 1
1387
+ }
1388
+ }
1389
+ if (cleared > 0) {
1390
+ log.info("rebuild microcompact", { sessionID: input.sessionID, cleared })
1391
+ }
1392
+
1393
+ return true
1394
+ })
1395
+
1396
+ return Service.of({
1397
+ tryStartCheckpointWriter,
1398
+ waitForWriter,
1399
+ drainWriters,
1400
+ hasCheckpoint,
1401
+ hasMemoryOrTasks,
1402
+ loadLatest,
1403
+ loadCheckpoints,
1404
+ renderIndex,
1405
+ renderRebuildContext,
1406
+ lastBoundary,
1407
+ isWriterRunning,
1408
+ insertRebuildBoundary,
1409
+ })
1410
+ }),
1411
+ )
1412
+
1413
+ // ---------------------------------------------------------------------------
1414
+ // Default layer
1415
+ // ---------------------------------------------------------------------------
1416
+
1417
+ // `defaultLayer` no longer requires `Actor.Service`: SessionCheckpoint reaches
1418
+ // the Actor implementation through the late-bound `spawnRef` (see
1419
+ // `actor/spawn-ref.ts`). This deliberately breaks the otherwise-unresolvable
1420
+ // layer cycle Actor → SessionPrompt → SessionCheckpoint → Actor. The AppLayer
1421
+ // constructs `Actor.defaultLayer` separately; its initialiser populates
1422
+ // `spawnRef`, which `tryStartCheckpointWriter` reads at call time.
1423
+ export const defaultLayer = Layer.suspend(() =>
1424
+ layer.pipe(
1425
+ Layer.provide(Session.defaultLayer),
1426
+ Layer.provide(Bus.layer),
1427
+ Layer.provide(Config.defaultLayer),
1428
+ Layer.provide(Memory.defaultLayer),
1429
+ Layer.provide(TaskRegistry.defaultLayer),
1430
+ Layer.provide(ActorRegistry.defaultLayer),
1431
+ ),
1432
+ )
1433
+
1434
+ // ---------------------------------------------------------------------------
1435
+ // Convenience wrappers
1436
+ // ---------------------------------------------------------------------------
1437
+
1438
+ const { runPromise } = makeRuntime(Service, defaultLayer)
1439
+
1440
+ export async function hasCheckpoint(input: { sessionID: SessionID }) {
1441
+ return runPromise((svc) => svc.hasCheckpoint(input.sessionID))
1442
+ }
1443
+
1444
+ export async function loadLatest(input: { sessionID: SessionID }) {
1445
+ return runPromise((svc) => svc.loadLatest(input.sessionID))
1446
+ }
1447
+
1448
+ export async function loadCheckpoints(input: { sessionID: SessionID; count: number }) {
1449
+ return runPromise((svc) => svc.loadCheckpoints(input.sessionID, input.count))
1450
+ }
1451
+
1452
+ export async function renderIndex(input: { sessionID: SessionID }) {
1453
+ return runPromise((svc) => svc.renderIndex(input.sessionID))
1454
+ }
1455
+
1456
+ export async function renderRebuildContext(input: {
1457
+ sessionID: SessionID
1458
+ lastMessageInfo?: LastMessageInfo
1459
+ agentID?: string
1460
+ }) {
1461
+ return runPromise((svc) =>
1462
+ svc.renderRebuildContext(input.sessionID, { lastMessageInfo: input.lastMessageInfo, agentID: input.agentID }),
1463
+ )
1464
+ }
1465
+
1466
+ export async function lastBoundary(input: { sessionID: SessionID }) {
1467
+ return runPromise((svc) => svc.lastBoundary(input.sessionID))
1468
+ }
1469
+
1470
+ export async function isWriterRunning(input: { sessionID: SessionID }) {
1471
+ return runPromise((svc) => svc.isWriterRunning(input.sessionID))
1472
+ }
1473
+
1474
+ export * as SessionCheckpoint from "./checkpoint"
1475
+
1476
+ // Test-only re-export so test code can call composeWriterPrompt without
1477
+ // triggering the full SessionCheckpoint Service stack.
1478
+ export { composeWriterPrompt as composeWriterPromptForTest }