tmux-ide 2.1.4 → 2.6.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 (805) hide show
  1. package/README.md +115 -101
  2. package/bin/cli.js +13678 -4
  3. package/bin/cli.ts +1143 -260
  4. package/package.json +27 -17
  5. package/packages/daemon/dist/__tests__/support.d.ts +7 -0
  6. package/packages/daemon/dist/__tests__/support.js +33 -0
  7. package/packages/daemon/dist/agent-explain.d.ts +45 -0
  8. package/packages/daemon/dist/agent-explain.js +210 -0
  9. package/packages/daemon/dist/attach.d.ts +3 -0
  10. package/packages/daemon/dist/attach.js +14 -0
  11. package/packages/daemon/dist/bin.d.ts +2 -0
  12. package/packages/daemon/dist/bin.js +3 -0
  13. package/packages/daemon/dist/canonical.d.ts +2 -0
  14. package/packages/daemon/dist/canonical.js +1 -0
  15. package/packages/daemon/dist/cli.d.ts +1 -0
  16. package/packages/daemon/dist/cli.js +425 -0
  17. package/packages/daemon/dist/command-center/actions/contract.d.ts +1 -0
  18. package/packages/daemon/dist/command-center/actions/contract.js +2 -0
  19. package/packages/daemon/dist/command-center/actions/dispatcher.d.ts +27 -0
  20. package/packages/daemon/dist/command-center/actions/dispatcher.js +109 -0
  21. package/packages/daemon/dist/command-center/actions/errors.d.ts +38 -0
  22. package/packages/daemon/dist/command-center/actions/errors.js +56 -0
  23. package/packages/daemon/dist/command-center/actions/handlers/_project-context.d.ts +12 -0
  24. package/packages/daemon/dist/command-center/actions/handlers/_project-context.js +12 -0
  25. package/packages/daemon/dist/command-center/actions/handlers/_resolve-project.d.ts +42 -0
  26. package/packages/daemon/dist/command-center/actions/handlers/_resolve-project.js +48 -0
  27. package/packages/daemon/dist/command-center/actions/handlers/app-set-remote-access.d.ts +23 -0
  28. package/packages/daemon/dist/command-center/actions/handlers/app-set-remote-access.js +67 -0
  29. package/packages/daemon/dist/command-center/actions/handlers/config-actions.d.ts +12 -0
  30. package/packages/daemon/dist/command-center/actions/handlers/config-actions.js +69 -0
  31. package/packages/daemon/dist/command-center/actions/handlers/daemon-shutdown.d.ts +6 -0
  32. package/packages/daemon/dist/command-center/actions/handlers/daemon-shutdown.js +24 -0
  33. package/packages/daemon/dist/command-center/actions/handlers/project-activate.d.ts +7 -0
  34. package/packages/daemon/dist/command-center/actions/handlers/project-activate.js +19 -0
  35. package/packages/daemon/dist/command-center/actions/handlers/project-launch.d.ts +18 -0
  36. package/packages/daemon/dist/command-center/actions/handlers/project-launch.js +58 -0
  37. package/packages/daemon/dist/command-center/actions/handlers/project-open-terminal.d.ts +32 -0
  38. package/packages/daemon/dist/command-center/actions/handlers/project-open-terminal.js +69 -0
  39. package/packages/daemon/dist/command-center/actions/handlers/project-restart.d.ts +17 -0
  40. package/packages/daemon/dist/command-center/actions/handlers/project-restart.js +27 -0
  41. package/packages/daemon/dist/command-center/actions/handlers/project-stop.d.ts +24 -0
  42. package/packages/daemon/dist/command-center/actions/handlers/project-stop.js +38 -0
  43. package/packages/daemon/dist/command-center/actions/handlers/terminal-respawn.d.ts +24 -0
  44. package/packages/daemon/dist/command-center/actions/handlers/terminal-respawn.js +88 -0
  45. package/packages/daemon/dist/command-center/actions/handlers/terminal-stop.d.ts +14 -0
  46. package/packages/daemon/dist/command-center/actions/handlers/terminal-stop.js +22 -0
  47. package/packages/daemon/dist/command-center/actions/registry.d.ts +35 -0
  48. package/packages/daemon/dist/command-center/actions/registry.js +109 -0
  49. package/packages/daemon/dist/command-center/discovery.d.ts +23 -0
  50. package/packages/daemon/dist/command-center/discovery.js +67 -0
  51. package/packages/daemon/dist/command-center/index.d.ts +10 -0
  52. package/packages/daemon/dist/command-center/index.js +21 -0
  53. package/packages/daemon/dist/command-center/schemas.d.ts +76 -0
  54. package/packages/daemon/dist/command-center/schemas.js +68 -0
  55. package/packages/daemon/dist/command-center/server.d.ts +26 -0
  56. package/packages/daemon/dist/command-center/server.js +1103 -0
  57. package/packages/daemon/dist/command-center/ws-events.d.ts +59 -0
  58. package/packages/daemon/dist/command-center/ws-events.js +277 -0
  59. package/packages/daemon/dist/config.d.ts +19 -0
  60. package/packages/daemon/dist/config.js +494 -0
  61. package/packages/daemon/dist/detect.d.ts +15 -0
  62. package/packages/daemon/dist/detect.js +228 -0
  63. package/packages/daemon/dist/doctor.d.ts +21 -0
  64. package/packages/daemon/dist/doctor.js +154 -0
  65. package/packages/daemon/dist/embed.d.ts +1 -0
  66. package/packages/daemon/dist/embed.js +1 -0
  67. package/packages/daemon/dist/index.d.ts +3 -0
  68. package/packages/daemon/dist/index.js +2 -0
  69. package/packages/daemon/dist/init.d.ts +4 -0
  70. package/packages/daemon/dist/init.js +174 -0
  71. package/packages/daemon/dist/inspect.d.ts +54 -0
  72. package/packages/daemon/dist/inspect.js +118 -0
  73. package/packages/daemon/dist/launch.d.ts +23 -0
  74. package/packages/daemon/dist/launch.js +250 -0
  75. package/packages/daemon/dist/lib/active-projects.d.ts +12 -0
  76. package/packages/daemon/dist/lib/active-projects.js +35 -0
  77. package/packages/daemon/dist/lib/agent-discovery.d.ts +43 -0
  78. package/packages/daemon/dist/lib/agent-discovery.js +85 -0
  79. package/packages/daemon/dist/lib/app-config.d.ts +147 -0
  80. package/packages/daemon/dist/lib/app-config.js +191 -0
  81. package/packages/daemon/dist/lib/app-settings.d.ts +9 -0
  82. package/packages/daemon/dist/lib/app-settings.js +44 -0
  83. package/packages/daemon/dist/lib/auth/auth-service.d.ts +33 -0
  84. package/packages/daemon/dist/lib/auth/auth-service.js +172 -0
  85. package/packages/daemon/dist/lib/auth/middleware.d.ts +16 -0
  86. package/packages/daemon/dist/lib/auth/middleware.js +43 -0
  87. package/packages/daemon/dist/lib/auth/types.d.ts +1 -0
  88. package/packages/daemon/dist/lib/auth/types.js +2 -0
  89. package/packages/daemon/dist/lib/auth-token.d.ts +1 -0
  90. package/packages/daemon/dist/lib/auth-token.js +4 -0
  91. package/packages/daemon/dist/lib/authorship.d.ts +62 -0
  92. package/packages/daemon/dist/lib/authorship.js +209 -0
  93. package/packages/daemon/dist/lib/canonical-daemon.d.ts +21 -0
  94. package/packages/daemon/dist/lib/canonical-daemon.js +111 -0
  95. package/packages/daemon/dist/lib/cli-action-bridge.d.ts +27 -0
  96. package/packages/daemon/dist/lib/cli-action-bridge.js +161 -0
  97. package/packages/daemon/dist/lib/daemon-embed.d.ts +31 -0
  98. package/packages/daemon/dist/lib/daemon-embed.js +643 -0
  99. package/packages/daemon/dist/lib/daemon-watchdog.d.ts +16 -0
  100. package/packages/daemon/dist/lib/daemon-watchdog.js +98 -0
  101. package/packages/daemon/dist/lib/daemon.d.ts +8 -0
  102. package/packages/daemon/dist/lib/daemon.js +79 -0
  103. package/packages/daemon/dist/lib/dot-path.d.ts +2 -0
  104. package/packages/daemon/dist/lib/dot-path.js +17 -0
  105. package/packages/daemon/dist/lib/errors.d.ts +37 -0
  106. package/packages/daemon/dist/lib/errors.js +50 -0
  107. package/packages/daemon/dist/lib/filesystem-browser.d.ts +85 -0
  108. package/packages/daemon/dist/lib/filesystem-browser.js +257 -0
  109. package/packages/daemon/dist/lib/launch-plan.d.ts +6 -0
  110. package/packages/daemon/dist/lib/launch-plan.js +76 -0
  111. package/packages/daemon/dist/lib/log.d.ts +40 -0
  112. package/packages/daemon/dist/lib/log.js +86 -0
  113. package/packages/daemon/dist/lib/output.d.ts +9 -0
  114. package/packages/daemon/dist/lib/output.js +65 -0
  115. package/packages/daemon/dist/lib/project-init-runner.d.ts +46 -0
  116. package/packages/daemon/dist/lib/project-init-runner.js +117 -0
  117. package/packages/daemon/dist/lib/project-inspect.d.ts +28 -0
  118. package/packages/daemon/dist/lib/project-inspect.js +67 -0
  119. package/packages/daemon/dist/lib/project-onboard.d.ts +58 -0
  120. package/packages/daemon/dist/lib/project-onboard.js +108 -0
  121. package/packages/daemon/dist/lib/project-probe.d.ts +35 -0
  122. package/packages/daemon/dist/lib/project-probe.js +61 -0
  123. package/packages/daemon/dist/lib/project-registry.d.ts +76 -0
  124. package/packages/daemon/dist/lib/project-registry.js +230 -0
  125. package/packages/daemon/dist/lib/session-monitor.d.ts +15 -0
  126. package/packages/daemon/dist/lib/session-monitor.js +103 -0
  127. package/packages/daemon/dist/lib/session-options.d.ts +13 -0
  128. package/packages/daemon/dist/lib/session-options.js +85 -0
  129. package/packages/daemon/dist/lib/shell.d.ts +5 -0
  130. package/{src/lib/shell.ts → packages/daemon/dist/lib/shell.js} +3 -3
  131. package/packages/daemon/dist/lib/sizes.d.ts +16 -0
  132. package/packages/daemon/dist/lib/sizes.js +36 -0
  133. package/packages/daemon/dist/lib/skill-sync.d.ts +60 -0
  134. package/packages/daemon/dist/lib/skill-sync.js +128 -0
  135. package/packages/daemon/dist/lib/slugify.d.ts +4 -0
  136. package/packages/daemon/dist/lib/slugify.js +10 -0
  137. package/packages/daemon/dist/lib/terminals-store.d.ts +27 -0
  138. package/packages/daemon/dist/lib/terminals-store.js +111 -0
  139. package/packages/daemon/dist/lib/update-check.d.ts +118 -0
  140. package/packages/daemon/dist/lib/update-check.js +261 -0
  141. package/packages/daemon/dist/lib/update.d.ts +65 -0
  142. package/packages/daemon/dist/lib/update.js +141 -0
  143. package/packages/daemon/dist/lib/workspace-registry.d.ts +84 -0
  144. package/packages/daemon/dist/lib/workspace-registry.js +176 -0
  145. package/packages/daemon/dist/lib/worktree.d.ts +76 -0
  146. package/packages/daemon/dist/lib/worktree.js +207 -0
  147. package/packages/daemon/dist/lib/yaml-io.d.ts +10 -0
  148. package/packages/daemon/dist/lib/yaml-io.js +24 -0
  149. package/packages/daemon/dist/ls.d.ts +3 -0
  150. package/packages/daemon/dist/ls.js +35 -0
  151. package/packages/daemon/dist/restart.d.ts +4 -0
  152. package/packages/daemon/dist/restart.js +16 -0
  153. package/packages/daemon/dist/restore.d.ts +86 -0
  154. package/packages/daemon/dist/restore.js +406 -0
  155. package/packages/daemon/dist/schemas/domain.d.ts +1 -0
  156. package/packages/daemon/dist/schemas/domain.js +2 -0
  157. package/packages/daemon/dist/schemas/filesystem.d.ts +29 -0
  158. package/packages/daemon/dist/schemas/filesystem.js +29 -0
  159. package/packages/daemon/dist/schemas/ide-config.d.ts +1 -0
  160. package/packages/daemon/dist/schemas/ide-config.js +2 -0
  161. package/packages/daemon/dist/schemas/index.d.ts +12 -0
  162. package/packages/daemon/dist/schemas/index.js +6 -0
  163. package/packages/daemon/dist/schemas/inspect.d.ts +53 -0
  164. package/packages/daemon/dist/schemas/inspect.js +57 -0
  165. package/packages/daemon/dist/schemas/registry.d.ts +35 -0
  166. package/packages/daemon/dist/schemas/registry.js +43 -0
  167. package/packages/daemon/dist/schemas/ws-events.d.ts +72 -0
  168. package/packages/daemon/dist/schemas/ws-events.js +110 -0
  169. package/packages/daemon/dist/send.d.ts +27 -0
  170. package/packages/daemon/dist/send.js +141 -0
  171. package/packages/daemon/dist/server/index.d.ts +10 -0
  172. package/packages/daemon/dist/server/index.js +55 -0
  173. package/packages/daemon/dist/server/pty-bridge.d.ts +174 -0
  174. package/packages/daemon/dist/server/pty-bridge.js +422 -0
  175. package/packages/daemon/dist/server/standalone.d.ts +1 -0
  176. package/packages/daemon/dist/server/standalone.js +14 -0
  177. package/packages/daemon/dist/server/ws-route.d.ts +74 -0
  178. package/packages/daemon/dist/server/ws-route.js +379 -0
  179. package/packages/daemon/dist/status.d.ts +3 -0
  180. package/packages/daemon/dist/status.js +46 -0
  181. package/packages/daemon/dist/stop.d.ts +3 -0
  182. package/packages/daemon/dist/stop.js +21 -0
  183. package/packages/daemon/dist/terminal/NodePtyAdapter.d.ts +44 -0
  184. package/packages/daemon/dist/terminal/NodePtyAdapter.js +253 -0
  185. package/packages/daemon/dist/terminal/PtyAdapter.d.ts +124 -0
  186. package/packages/daemon/dist/terminal/PtyAdapter.js +43 -0
  187. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.d.ts +77 -0
  188. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +155 -0
  189. package/packages/daemon/dist/types.d.ts +2 -0
  190. package/packages/daemon/dist/types.js +1 -0
  191. package/packages/daemon/dist/validate.d.ts +4 -0
  192. package/packages/daemon/dist/validate.js +184 -0
  193. package/packages/daemon/dist/widgets/changes/index.d.ts +1 -0
  194. package/packages/daemon/dist/widgets/changes/index.jsx +575 -0
  195. package/packages/daemon/dist/widgets/config/index.d.ts +1 -0
  196. package/packages/daemon/dist/widgets/config/index.jsx +395 -0
  197. package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +10 -0
  198. package/packages/daemon/dist/widgets/explorer/breadcrumbs.jsx +51 -0
  199. package/packages/daemon/dist/widgets/explorer/footer.d.ts +6 -0
  200. package/packages/daemon/dist/widgets/explorer/footer.jsx +11 -0
  201. package/packages/daemon/dist/widgets/explorer/index.d.ts +1 -0
  202. package/packages/daemon/dist/widgets/explorer/index.jsx +393 -0
  203. package/packages/daemon/dist/widgets/explorer/tree-model.d.ts +13 -0
  204. package/packages/daemon/dist/widgets/explorer/tree-model.js +69 -0
  205. package/packages/daemon/dist/widgets/explorer/tree.d.ts +13 -0
  206. package/packages/daemon/dist/widgets/explorer/tree.jsx +108 -0
  207. package/packages/daemon/dist/widgets/lib/config-model.d.ts +22 -0
  208. package/packages/daemon/dist/widgets/lib/config-model.js +99 -0
  209. package/packages/daemon/dist/widgets/lib/files.d.ts +11 -0
  210. package/packages/daemon/dist/widgets/lib/files.js +76 -0
  211. package/packages/daemon/dist/widgets/lib/git.d.ts +11 -0
  212. package/packages/daemon/dist/widgets/lib/git.js +70 -0
  213. package/packages/daemon/dist/widgets/lib/grammar.d.ts +79 -0
  214. package/packages/daemon/dist/widgets/lib/grammar.js +95 -0
  215. package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +19 -0
  216. package/packages/daemon/dist/widgets/lib/help-overlay.jsx +69 -0
  217. package/packages/daemon/dist/widgets/lib/pane-comms.d.ts +28 -0
  218. package/packages/daemon/dist/widgets/lib/pane-comms.js +189 -0
  219. package/packages/daemon/dist/widgets/lib/theme.d.ts +44 -0
  220. package/packages/daemon/dist/widgets/lib/theme.js +153 -0
  221. package/packages/daemon/dist/widgets/lib/watcher.d.ts +9 -0
  222. package/packages/daemon/dist/widgets/lib/watcher.js +92 -0
  223. package/packages/daemon/dist/widgets/preview/index.d.ts +1 -0
  224. package/packages/daemon/dist/widgets/preview/index.jsx +371 -0
  225. package/packages/daemon/dist/widgets/resolve.d.ts +20 -0
  226. package/packages/daemon/dist/widgets/resolve.js +100 -0
  227. package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +9 -0
  228. package/packages/daemon/dist/widgets/setup/agent-naming.jsx +81 -0
  229. package/packages/daemon/dist/widgets/setup/config-tree.d.ts +13 -0
  230. package/packages/daemon/dist/widgets/setup/config-tree.jsx +203 -0
  231. package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +14 -0
  232. package/{src/widgets/setup/detect-panel.tsx → packages/daemon/dist/widgets/setup/detect-panel.jsx} +16 -40
  233. package/packages/daemon/dist/widgets/setup/field-editor.d.ts +12 -0
  234. package/packages/daemon/dist/widgets/setup/field-editor.jsx +163 -0
  235. package/packages/daemon/dist/widgets/setup/footer.d.ts +17 -0
  236. package/packages/daemon/dist/widgets/setup/footer.jsx +64 -0
  237. package/packages/daemon/dist/widgets/setup/index.d.ts +1 -0
  238. package/packages/daemon/dist/widgets/setup/index.jsx +261 -0
  239. package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +9 -0
  240. package/packages/daemon/dist/widgets/setup/layout-picker.jsx +69 -0
  241. package/packages/daemon/dist/widgets/setup/setup-model.d.ts +15 -0
  242. package/packages/daemon/dist/widgets/setup/setup-model.js +157 -0
  243. package/packages/daemon/dist/widgets/sidebar/index.d.ts +1 -0
  244. package/packages/daemon/dist/widgets/sidebar/index.jsx +462 -0
  245. package/scripts/build-cli.mjs +101 -0
  246. package/scripts/build-tui.mjs +58 -0
  247. package/scripts/capture-codex-traffic.mjs +195 -0
  248. package/scripts/check-src-additions.sh +74 -0
  249. package/scripts/coverage-summary.mjs +68 -0
  250. package/scripts/install-git-hooks.js +53 -0
  251. package/scripts/merge-update-manifests.mjs +58 -0
  252. package/scripts/merge-update-manifests.test.mjs +82 -0
  253. package/scripts/pack-check-run.mjs +83 -0
  254. package/scripts/postinstall.js +28 -6
  255. package/scripts/prepublish-check.mjs +44 -0
  256. package/scripts/stress-test.ts +141 -0
  257. package/scripts/verify-endpoints.sh +90 -0
  258. package/skill/SKILL.md +203 -236
  259. package/dashboard/out/404/index.html +0 -1
  260. package/dashboard/out/404.html +0 -1
  261. package/dashboard/out/__next.__PAGE__.txt +0 -9
  262. package/dashboard/out/__next._full.txt +0 -21
  263. package/dashboard/out/__next._head.txt +0 -5
  264. package/dashboard/out/__next._index.txt +0 -7
  265. package/dashboard/out/__next._tree.txt +0 -4
  266. package/dashboard/out/_next/static/Tl1KoIH7mHCyjo5KtXqQ0/_buildManifest.js +0 -11
  267. package/dashboard/out/_next/static/Tl1KoIH7mHCyjo5KtXqQ0/_clientMiddlewareManifest.json +0 -1
  268. package/dashboard/out/_next/static/Tl1KoIH7mHCyjo5KtXqQ0/_ssgManifest.js +0 -1
  269. package/dashboard/out/_next/static/chunks/048e2449530877e2.js +0 -1
  270. package/dashboard/out/_next/static/chunks/053c57bda6abb959.js +0 -1
  271. package/dashboard/out/_next/static/chunks/056760c6c2dd9172.js +0 -1
  272. package/dashboard/out/_next/static/chunks/06ab47357b6d3e35.js +0 -1
  273. package/dashboard/out/_next/static/chunks/0921cbe5dfd6738e.js +0 -1
  274. package/dashboard/out/_next/static/chunks/0b2599bf468cf5ea.js +0 -1
  275. package/dashboard/out/_next/static/chunks/0c012eac08ef2c4f.js +0 -1
  276. package/dashboard/out/_next/static/chunks/0c29e87fcdcffac3.js +0 -1
  277. package/dashboard/out/_next/static/chunks/0e1e98854430c693.js +0 -1
  278. package/dashboard/out/_next/static/chunks/0e24498976a88976.js +0 -1
  279. package/dashboard/out/_next/static/chunks/0e5bf11ff0b9ab90.js +0 -1
  280. package/dashboard/out/_next/static/chunks/0eae0a45ab8416c8.js +0 -1
  281. package/dashboard/out/_next/static/chunks/104797400d9d38ad.js +0 -1
  282. package/dashboard/out/_next/static/chunks/10cfcfce475f9d2d.js +0 -1
  283. package/dashboard/out/_next/static/chunks/11bac38e7cfb4ecc.js +0 -1
  284. package/dashboard/out/_next/static/chunks/120c5f6501ec3529.js +0 -1
  285. package/dashboard/out/_next/static/chunks/121fceb2b1dcd79f.js +0 -1
  286. package/dashboard/out/_next/static/chunks/13726b63e1be6b98.js +0 -1
  287. package/dashboard/out/_next/static/chunks/140c6ce631514660.js +0 -1
  288. package/dashboard/out/_next/static/chunks/1435ee3a51174c97.js +0 -1
  289. package/dashboard/out/_next/static/chunks/14dc927d2e2cca91.js +0 -1
  290. package/dashboard/out/_next/static/chunks/179c3031b292d98e.js +0 -1
  291. package/dashboard/out/_next/static/chunks/181e88957c47e5c1.js +0 -1
  292. package/dashboard/out/_next/static/chunks/18f70ba23873c432.js +0 -1
  293. package/dashboard/out/_next/static/chunks/1b58303b027fd135.js +0 -1
  294. package/dashboard/out/_next/static/chunks/1b5f49a358dcc71e.js +0 -1
  295. package/dashboard/out/_next/static/chunks/1bba44b81585099a.js +0 -1
  296. package/dashboard/out/_next/static/chunks/1bea8dee19a3e9ae.js +0 -1
  297. package/dashboard/out/_next/static/chunks/1cfad21646fc250e.js +0 -1
  298. package/dashboard/out/_next/static/chunks/1d053dc9e1e428ef.js +0 -1
  299. package/dashboard/out/_next/static/chunks/1e7d7639c6f0ee5e.js +0 -1
  300. package/dashboard/out/_next/static/chunks/1f42504ca81463f1.js +0 -1
  301. package/dashboard/out/_next/static/chunks/20c92047399305b4.js +0 -1
  302. package/dashboard/out/_next/static/chunks/214560e9514a9a4d.js +0 -1
  303. package/dashboard/out/_next/static/chunks/21d4ef106914e997.js +0 -1
  304. package/dashboard/out/_next/static/chunks/2586e83fc3dc2588.js +0 -1
  305. package/dashboard/out/_next/static/chunks/267136a09e94f5c2.js +0 -1
  306. package/dashboard/out/_next/static/chunks/271c50b329917c10.js +0 -1
  307. package/dashboard/out/_next/static/chunks/27fccecefffd88b5.js +0 -1
  308. package/dashboard/out/_next/static/chunks/28f058ed331315b6.js +0 -1
  309. package/dashboard/out/_next/static/chunks/28f704f2cc5713eb.js +0 -1
  310. package/dashboard/out/_next/static/chunks/290f67a6cd150a98.js +0 -1
  311. package/dashboard/out/_next/static/chunks/2ad12f4dcc30d30a.js +0 -1
  312. package/dashboard/out/_next/static/chunks/2b650b172078ab92.js +0 -1
  313. package/dashboard/out/_next/static/chunks/2bd91076019a6453.js +0 -1
  314. package/dashboard/out/_next/static/chunks/2e1278563a8e26be.js +0 -1
  315. package/dashboard/out/_next/static/chunks/309eb70780a3d67d.js +0 -1
  316. package/dashboard/out/_next/static/chunks/30f71950992e2307.js +0 -1
  317. package/dashboard/out/_next/static/chunks/31191873c2075784.js +0 -1
  318. package/dashboard/out/_next/static/chunks/31a790921a0c035e.js +0 -1
  319. package/dashboard/out/_next/static/chunks/34f8d7a5a06e8e32.js +0 -1
  320. package/dashboard/out/_next/static/chunks/36b3716436d9bc9d.js +0 -1
  321. package/dashboard/out/_next/static/chunks/3770b7d0ff004de3.js +0 -1
  322. package/dashboard/out/_next/static/chunks/381ab3bd4cffbf4d.js +0 -1
  323. package/dashboard/out/_next/static/chunks/38aa4eada5dcd1a4.js +0 -1
  324. package/dashboard/out/_next/static/chunks/39723132b78cb77e.js +0 -1
  325. package/dashboard/out/_next/static/chunks/399efdcc3fe56e09.js +0 -1
  326. package/dashboard/out/_next/static/chunks/3a8431a9221b0706.js +0 -1
  327. package/dashboard/out/_next/static/chunks/3a9b40810ce88cbf.js +0 -1
  328. package/dashboard/out/_next/static/chunks/3b322e3e9476ecdf.js +0 -1
  329. package/dashboard/out/_next/static/chunks/3bb14befeeb08c28.js +0 -1
  330. package/dashboard/out/_next/static/chunks/3c99637ebd462b20.js +0 -1
  331. package/dashboard/out/_next/static/chunks/3cbb9217d56459d8.js +0 -1
  332. package/dashboard/out/_next/static/chunks/3d438e0929a762f7.js +0 -1
  333. package/dashboard/out/_next/static/chunks/3e6d4834e0752f76.js +0 -2
  334. package/dashboard/out/_next/static/chunks/3e6f8cedaad4e89b.js +0 -1
  335. package/dashboard/out/_next/static/chunks/3e701259ce1b53e3.js +0 -1
  336. package/dashboard/out/_next/static/chunks/41562f7ab7c033eb.js +0 -1
  337. package/dashboard/out/_next/static/chunks/418fadd8a94e2fd7.js +0 -1
  338. package/dashboard/out/_next/static/chunks/42345437bb922e9c.js +0 -1
  339. package/dashboard/out/_next/static/chunks/434b6bd78c97b668.js +0 -1
  340. package/dashboard/out/_next/static/chunks/434febe897d33e2d.js +0 -1
  341. package/dashboard/out/_next/static/chunks/45d28707dd8ce1c1.js +0 -1
  342. package/dashboard/out/_next/static/chunks/4620a640e88cb1b7.js +0 -1
  343. package/dashboard/out/_next/static/chunks/468f8eb604502ea4.js +0 -1
  344. package/dashboard/out/_next/static/chunks/495f5cedf8afe823.js +0 -1
  345. package/dashboard/out/_next/static/chunks/49a89d1704d27740.js +0 -1
  346. package/dashboard/out/_next/static/chunks/49e5c37d15f63def.js +0 -1
  347. package/dashboard/out/_next/static/chunks/4a46ad186a8587ee.css +0 -2
  348. package/dashboard/out/_next/static/chunks/4a5c728574935d50.js +0 -1
  349. package/dashboard/out/_next/static/chunks/4b27ebd4d9304d8f.js +0 -1
  350. package/dashboard/out/_next/static/chunks/4b965aa320a9b358.js +0 -1
  351. package/dashboard/out/_next/static/chunks/4bb2da90bbe5794f.js +0 -1
  352. package/dashboard/out/_next/static/chunks/4d2f1c6f631e4b85.js +0 -1
  353. package/dashboard/out/_next/static/chunks/4d696d5a64dab42e.js +0 -1
  354. package/dashboard/out/_next/static/chunks/4f660dadb3f1e9e2.js +0 -1
  355. package/dashboard/out/_next/static/chunks/507b50d47f949333.js +0 -1
  356. package/dashboard/out/_next/static/chunks/50b1b7063bb044e2.js +0 -1
  357. package/dashboard/out/_next/static/chunks/50c0c2e3c4f02508.js +0 -1
  358. package/dashboard/out/_next/static/chunks/50d7ba9803e65b02.js +0 -1
  359. package/dashboard/out/_next/static/chunks/51535a6d4c98c7a4.js +0 -1
  360. package/dashboard/out/_next/static/chunks/526de6cc16c7dd2f.js +0 -1
  361. package/dashboard/out/_next/static/chunks/5307604f8632b3fd.js +0 -1
  362. package/dashboard/out/_next/static/chunks/5480bd722260f231.js +0 -1
  363. package/dashboard/out/_next/static/chunks/54c61c82e5f1dca5.js +0 -1
  364. package/dashboard/out/_next/static/chunks/555de6ac6bb9034c.js +0 -1
  365. package/dashboard/out/_next/static/chunks/562b89ee76a4b90c.js +0 -1
  366. package/dashboard/out/_next/static/chunks/5747f9a32edff429.js +0 -1
  367. package/dashboard/out/_next/static/chunks/57ee141430b4c05c.js +0 -1
  368. package/dashboard/out/_next/static/chunks/584c066a0837dd9a.js +0 -1
  369. package/dashboard/out/_next/static/chunks/5856aed4670a6253.js +0 -1
  370. package/dashboard/out/_next/static/chunks/58fe563414726511.js +0 -1
  371. package/dashboard/out/_next/static/chunks/5948196b2729b18c.js +0 -1
  372. package/dashboard/out/_next/static/chunks/5ab77c69e7306f97.js +0 -1
  373. package/dashboard/out/_next/static/chunks/5b3fa4c99d61611c.js +0 -1
  374. package/dashboard/out/_next/static/chunks/5cc87abaef616661.js +0 -1
  375. package/dashboard/out/_next/static/chunks/5dfa7913f99bb966.js +0 -1
  376. package/dashboard/out/_next/static/chunks/5e49b008432770f1.js +0 -1
  377. package/dashboard/out/_next/static/chunks/5e5a33dd81c45c47.js +0 -1
  378. package/dashboard/out/_next/static/chunks/5ee61bed638eb8a8.js +0 -1
  379. package/dashboard/out/_next/static/chunks/5eed557b9f45ae4a.js +0 -1
  380. package/dashboard/out/_next/static/chunks/60446823a7d30c1b.js +0 -1
  381. package/dashboard/out/_next/static/chunks/605150c490861442.js +0 -1
  382. package/dashboard/out/_next/static/chunks/609056d51c0bf332.js +0 -1
  383. package/dashboard/out/_next/static/chunks/60ec91dfeb50071b.js +0 -1
  384. package/dashboard/out/_next/static/chunks/61bc7be0bba6a33f.js +0 -1
  385. package/dashboard/out/_next/static/chunks/61f4a5c9ce23e55e.js +0 -1
  386. package/dashboard/out/_next/static/chunks/63aae3585be04e65.js +0 -1
  387. package/dashboard/out/_next/static/chunks/64e0e1ee7ef4f01b.js +0 -1
  388. package/dashboard/out/_next/static/chunks/66e9b4c26ff1aaaf.js +0 -1
  389. package/dashboard/out/_next/static/chunks/67068847aa1b5e98.js +0 -1
  390. package/dashboard/out/_next/static/chunks/68d7de1f2e202c26.js +0 -1
  391. package/dashboard/out/_next/static/chunks/698ca3ea1937ba4f.js +0 -1
  392. package/dashboard/out/_next/static/chunks/69eb97c24b5b6b9d.js +0 -1
  393. package/dashboard/out/_next/static/chunks/6b1ac098c228a479.js +0 -1
  394. package/dashboard/out/_next/static/chunks/6bb8020cb796bcc1.js +0 -1
  395. package/dashboard/out/_next/static/chunks/6cf8b1db17f2a155.js +0 -1
  396. package/dashboard/out/_next/static/chunks/6e7613643906ea88.js +0 -1
  397. package/dashboard/out/_next/static/chunks/6ea27ceb2e6feae5.js +0 -1
  398. package/dashboard/out/_next/static/chunks/6f95a8dbcf23dded.js +0 -1
  399. package/dashboard/out/_next/static/chunks/6fd44e759d82f64e.js +0 -1
  400. package/dashboard/out/_next/static/chunks/716ec29c268c38f6.js +0 -1
  401. package/dashboard/out/_next/static/chunks/717b399dfcf7c02c.js +0 -1
  402. package/dashboard/out/_next/static/chunks/72badd90e1c91348.js +0 -1
  403. package/dashboard/out/_next/static/chunks/72f9296dbb7ae3c4.js +0 -1
  404. package/dashboard/out/_next/static/chunks/73aa31e1144d8191.js +0 -1
  405. package/dashboard/out/_next/static/chunks/73f98ded8fe2e879.js +0 -1
  406. package/dashboard/out/_next/static/chunks/740a2e8ace15e13b.js +0 -1
  407. package/dashboard/out/_next/static/chunks/74e57c16e7304b6b.js +0 -1
  408. package/dashboard/out/_next/static/chunks/7628dead4d113df2.js +0 -1
  409. package/dashboard/out/_next/static/chunks/7717c88c07ed96e6.js +0 -1
  410. package/dashboard/out/_next/static/chunks/775581f7956d9a27.js +0 -1
  411. package/dashboard/out/_next/static/chunks/77d3b6661befdb98.js +0 -1
  412. package/dashboard/out/_next/static/chunks/78435c0e1c77fea7.js +0 -1
  413. package/dashboard/out/_next/static/chunks/791336748b9c9393.js +0 -1
  414. package/dashboard/out/_next/static/chunks/79ea68dfae65ef2f.js +0 -1
  415. package/dashboard/out/_next/static/chunks/7bae777a472eb13c.js +0 -1
  416. package/dashboard/out/_next/static/chunks/7fd847f47237a35c.js +0 -1
  417. package/dashboard/out/_next/static/chunks/804d718be0370058.css +0 -1
  418. package/dashboard/out/_next/static/chunks/807cb3c1923995ab.js +0 -1
  419. package/dashboard/out/_next/static/chunks/8286b39407d94447.js +0 -1
  420. package/dashboard/out/_next/static/chunks/84e4305b8dc830ed.js +0 -1
  421. package/dashboard/out/_next/static/chunks/850f99dbfabb53e0.js +0 -1
  422. package/dashboard/out/_next/static/chunks/85594127e66b6b7f.js +0 -1
  423. package/dashboard/out/_next/static/chunks/858c11169c183c9c.js +0 -1
  424. package/dashboard/out/_next/static/chunks/85e27005dfe4c3e3.js +0 -1
  425. package/dashboard/out/_next/static/chunks/8707bd3d8ddc1f1c.js +0 -1
  426. package/dashboard/out/_next/static/chunks/8851e5d7ef894973.js +0 -1
  427. package/dashboard/out/_next/static/chunks/88ba3782cc614989.js +0 -1
  428. package/dashboard/out/_next/static/chunks/8a0c9a181784c2ef.js +0 -1
  429. package/dashboard/out/_next/static/chunks/8ad01ae355a6e7f6.js +0 -1
  430. package/dashboard/out/_next/static/chunks/8d2ed93b0d0a4b4f.js +0 -1
  431. package/dashboard/out/_next/static/chunks/8dc352ebd5310d3c.js +0 -1
  432. package/dashboard/out/_next/static/chunks/8dd1d722c8c1a22c.js +0 -1
  433. package/dashboard/out/_next/static/chunks/8e60fe307e3e4986.js +0 -1
  434. package/dashboard/out/_next/static/chunks/8eea4dd71e25f32a.js +0 -1
  435. package/dashboard/out/_next/static/chunks/8f468a65bac34eaa.js +0 -1
  436. package/dashboard/out/_next/static/chunks/9057e5382d6a5059.js +0 -1
  437. package/dashboard/out/_next/static/chunks/931d056437805ecb.js +0 -1
  438. package/dashboard/out/_next/static/chunks/954eb11648dd8cff.js +0 -1
  439. package/dashboard/out/_next/static/chunks/955034d876d14bb3.js +0 -1
  440. package/dashboard/out/_next/static/chunks/966c4895773249f6.js +0 -1
  441. package/dashboard/out/_next/static/chunks/96aff410a6ab106c.js +0 -1
  442. package/dashboard/out/_next/static/chunks/99b396c821c1dd2d.js +0 -1
  443. package/dashboard/out/_next/static/chunks/9a255fd2cf4922ae.js +0 -1
  444. package/dashboard/out/_next/static/chunks/9b1f6aa73bc864b2.js +0 -1
  445. package/dashboard/out/_next/static/chunks/9b9ce582c533aa79.js +0 -1
  446. package/dashboard/out/_next/static/chunks/9c19bd3c773c6611.js +0 -1
  447. package/dashboard/out/_next/static/chunks/9ceeaa847eb15ca2.js +0 -1
  448. package/dashboard/out/_next/static/chunks/9d2e6e7b8b65f845.js +0 -1
  449. package/dashboard/out/_next/static/chunks/9de23802066128ff.js +0 -1
  450. package/dashboard/out/_next/static/chunks/9e327f94b3701170.js +0 -1
  451. package/dashboard/out/_next/static/chunks/9e7818421c2d455a.js +0 -1
  452. package/dashboard/out/_next/static/chunks/9e935acbcf2fb519.js +0 -1
  453. package/dashboard/out/_next/static/chunks/9ed9f68340343b51.js +0 -1
  454. package/dashboard/out/_next/static/chunks/9f94a0242455fc20.js +0 -1
  455. package/dashboard/out/_next/static/chunks/9fff46b5aaecf47e.js +0 -1
  456. package/dashboard/out/_next/static/chunks/a06d267a5c557657.js +0 -1
  457. package/dashboard/out/_next/static/chunks/a0eb8c81c54722fe.js +0 -1
  458. package/dashboard/out/_next/static/chunks/a16808e83106427d.js +0 -1
  459. package/dashboard/out/_next/static/chunks/a2215e2bc72739ef.js +0 -1
  460. package/dashboard/out/_next/static/chunks/a2de9e6696eb9c17.js +0 -1
  461. package/dashboard/out/_next/static/chunks/a33420b8c14090d6.js +0 -1
  462. package/dashboard/out/_next/static/chunks/a360bffef39d818b.js +0 -1
  463. package/dashboard/out/_next/static/chunks/a42852fdf5287cf5.js +0 -1
  464. package/dashboard/out/_next/static/chunks/a4efc88488322b67.js +0 -1
  465. package/dashboard/out/_next/static/chunks/a696a91d14101bdb.js +0 -1
  466. package/dashboard/out/_next/static/chunks/a6dad97d9634a72d.js +0 -1
  467. package/dashboard/out/_next/static/chunks/a6dad97d9634a72d.js.map +0 -1
  468. package/dashboard/out/_next/static/chunks/a705c7261712bd8e.js +0 -1
  469. package/dashboard/out/_next/static/chunks/a7d1e4b067ace180.js +0 -1
  470. package/dashboard/out/_next/static/chunks/a8590c121a7efaa5.js +0 -1
  471. package/dashboard/out/_next/static/chunks/a95953a83a6df6cf.js +0 -1
  472. package/dashboard/out/_next/static/chunks/a9d0a757d3b4afed.js +0 -1
  473. package/dashboard/out/_next/static/chunks/a9dc35514619c872.js +0 -1
  474. package/dashboard/out/_next/static/chunks/aae0253c73fba984.js +0 -193
  475. package/dashboard/out/_next/static/chunks/ab1821552066a74d.js +0 -1
  476. package/dashboard/out/_next/static/chunks/ac96bc0eccebe15a.js +0 -1
  477. package/dashboard/out/_next/static/chunks/acc03cfaaf6bb30f.js +0 -1
  478. package/dashboard/out/_next/static/chunks/acc298ae059b9fe4.js +0 -1
  479. package/dashboard/out/_next/static/chunks/ad076b6e996d250c.js +0 -1
  480. package/dashboard/out/_next/static/chunks/ae53b64af7ba16d5.js +0 -1
  481. package/dashboard/out/_next/static/chunks/ae5da7fa4bc5fa95.js +0 -1
  482. package/dashboard/out/_next/static/chunks/aefcf0eb033a155e.js +0 -1
  483. package/dashboard/out/_next/static/chunks/b12d43100ee10526.js +0 -1
  484. package/dashboard/out/_next/static/chunks/b137952dd5566a41.js +0 -1
  485. package/dashboard/out/_next/static/chunks/b2832d8a2d332a47.js +0 -1
  486. package/dashboard/out/_next/static/chunks/b417aa09c15f63c6.js +0 -1
  487. package/dashboard/out/_next/static/chunks/b48e152ba7bf6caf.js +0 -1
  488. package/dashboard/out/_next/static/chunks/b4cbef8e4714bf92.js +0 -1
  489. package/dashboard/out/_next/static/chunks/b568ff8383a37f53.js +0 -1
  490. package/dashboard/out/_next/static/chunks/b6e1495dd89a39e3.js +0 -1
  491. package/dashboard/out/_next/static/chunks/b73a21bd67528440.js +0 -1
  492. package/dashboard/out/_next/static/chunks/b77ee62bc71dcec7.js +0 -1
  493. package/dashboard/out/_next/static/chunks/b7f454615c11d4eb.js +0 -1
  494. package/dashboard/out/_next/static/chunks/b818ea32a839590f.js +0 -1
  495. package/dashboard/out/_next/static/chunks/b901ce78c1c65d66.js +0 -1
  496. package/dashboard/out/_next/static/chunks/b93d647b803be9fa.js +0 -1
  497. package/dashboard/out/_next/static/chunks/b960b09ff1ecd6bb.js +0 -1
  498. package/dashboard/out/_next/static/chunks/ba717c7779a778fc.js +0 -1
  499. package/dashboard/out/_next/static/chunks/bacb0ae7458d18b6.js +0 -1
  500. package/dashboard/out/_next/static/chunks/bb51f68b27346852.js +0 -1
  501. package/dashboard/out/_next/static/chunks/bb67f22e1ab632ae.js +0 -1
  502. package/dashboard/out/_next/static/chunks/bbae97ec58d1bb9d.js +0 -1
  503. package/dashboard/out/_next/static/chunks/bbaf38ee90307765.js +0 -1
  504. package/dashboard/out/_next/static/chunks/be01598fe1fce5db.js +0 -1
  505. package/dashboard/out/_next/static/chunks/bf4cbbc095987fec.js +0 -1
  506. package/dashboard/out/_next/static/chunks/c18f823797d57446.js +0 -1
  507. package/dashboard/out/_next/static/chunks/c1bcece34ec5b295.js +0 -1
  508. package/dashboard/out/_next/static/chunks/c1ccbc3a51adaf0a.js +0 -1
  509. package/dashboard/out/_next/static/chunks/c1f54a628f27791c.js +0 -1
  510. package/dashboard/out/_next/static/chunks/c21916bf5d232151.js +0 -1
  511. package/dashboard/out/_next/static/chunks/c27e570f0fc2f78b.js +0 -1
  512. package/dashboard/out/_next/static/chunks/c322b8005eebc0d8.js +0 -1
  513. package/dashboard/out/_next/static/chunks/c3900ac2b009b764.js +0 -1
  514. package/dashboard/out/_next/static/chunks/c3c597c8eb77ad6b.js +0 -1
  515. package/dashboard/out/_next/static/chunks/c58d45c1d88e208c.js +0 -1
  516. package/dashboard/out/_next/static/chunks/c63875998d7a655f.js +0 -1
  517. package/dashboard/out/_next/static/chunks/c6eaa7c8e7209c8e.js +0 -1
  518. package/dashboard/out/_next/static/chunks/c6f820f71162a59a.js +0 -1
  519. package/dashboard/out/_next/static/chunks/c7ce0941d1dfe7ed.js +0 -1
  520. package/dashboard/out/_next/static/chunks/c877259f784ec8e5.js +0 -1
  521. package/dashboard/out/_next/static/chunks/cb8057e86aaa1c68.js +0 -1
  522. package/dashboard/out/_next/static/chunks/ccb4e306ee4859b1.js +0 -1
  523. package/dashboard/out/_next/static/chunks/ccde62b0fa8112eb.js +0 -1
  524. package/dashboard/out/_next/static/chunks/cd421f0971c63c31.js +0 -1
  525. package/dashboard/out/_next/static/chunks/cde0317494d4adb7.js +0 -1
  526. package/dashboard/out/_next/static/chunks/cfae64434b92b8b6.js +0 -1
  527. package/dashboard/out/_next/static/chunks/cfc0e19310ec0a36.js +0 -1
  528. package/dashboard/out/_next/static/chunks/d0f344c7d0bcb59a.js +0 -1
  529. package/dashboard/out/_next/static/chunks/d1871c73e74e1f50.js +0 -1
  530. package/dashboard/out/_next/static/chunks/d18f658850711061.js +0 -1
  531. package/dashboard/out/_next/static/chunks/d25d9c48880697f6.js +0 -1
  532. package/dashboard/out/_next/static/chunks/d3f0a72bbc3060b0.js +0 -1
  533. package/dashboard/out/_next/static/chunks/d5fca429bc89060b.js +0 -1
  534. package/dashboard/out/_next/static/chunks/d6a6c75c9a4bef49.js +0 -1
  535. package/dashboard/out/_next/static/chunks/d6a8f93aa8f1e345.js +0 -1
  536. package/dashboard/out/_next/static/chunks/d7deab676b19ba0e.js +0 -1
  537. package/dashboard/out/_next/static/chunks/d80030dde88cec85.js +0 -1
  538. package/dashboard/out/_next/static/chunks/da84f9774c076ff1.js +0 -1
  539. package/dashboard/out/_next/static/chunks/da98ecc6b9b0ceb2.js +0 -1
  540. package/dashboard/out/_next/static/chunks/db550aabce60b912.js +0 -4
  541. package/dashboard/out/_next/static/chunks/dc87f77fcc029482.js +0 -1
  542. package/dashboard/out/_next/static/chunks/dd05f30acdb63a53.js +0 -1
  543. package/dashboard/out/_next/static/chunks/dea150155499b903.js +0 -1
  544. package/dashboard/out/_next/static/chunks/df4358754ff1d5e8.js +0 -1
  545. package/dashboard/out/_next/static/chunks/df8b21ff12d23b0e.js +0 -1
  546. package/dashboard/out/_next/static/chunks/df8fc68d77debf2c.js +0 -1
  547. package/dashboard/out/_next/static/chunks/df9ed1ffec90589f.js +0 -1
  548. package/dashboard/out/_next/static/chunks/dfa65ece1598f934.js +0 -1
  549. package/dashboard/out/_next/static/chunks/dfaab5cdd41a20d9.js +0 -1
  550. package/dashboard/out/_next/static/chunks/e00c03c942b5ae3e.js +0 -1
  551. package/dashboard/out/_next/static/chunks/e02d27cf679bece1.js +0 -1
  552. package/dashboard/out/_next/static/chunks/e0b747585f78513a.js +0 -1
  553. package/dashboard/out/_next/static/chunks/e19b71ca23fd7df0.js +0 -1
  554. package/dashboard/out/_next/static/chunks/e21cbe89d0cda120.js +0 -1
  555. package/dashboard/out/_next/static/chunks/e2a0386930edd34f.js +0 -1
  556. package/dashboard/out/_next/static/chunks/e375de19a36e2f38.js +0 -1
  557. package/dashboard/out/_next/static/chunks/e5241b4a6496a9e0.js +0 -1
  558. package/dashboard/out/_next/static/chunks/e569dd07c96eb4dd.js +0 -1
  559. package/dashboard/out/_next/static/chunks/e61c3ea91233f948.js +0 -1
  560. package/dashboard/out/_next/static/chunks/e73b42c5d34c4c28.js +0 -1
  561. package/dashboard/out/_next/static/chunks/e7c5b2105f651e87.js +0 -1
  562. package/dashboard/out/_next/static/chunks/e878e58a69bb01b6.js +0 -1
  563. package/dashboard/out/_next/static/chunks/e8f090a4dc571062.js +0 -1
  564. package/dashboard/out/_next/static/chunks/e98dc34dac91cdad.js +0 -1
  565. package/dashboard/out/_next/static/chunks/ea67cf7a0c5e7233.js +0 -1
  566. package/dashboard/out/_next/static/chunks/eacc98a1e3512fa9.js +0 -1
  567. package/dashboard/out/_next/static/chunks/eae9cd74d9bbed8b.js +0 -1
  568. package/dashboard/out/_next/static/chunks/eb187fd345852b34.js +0 -1
  569. package/dashboard/out/_next/static/chunks/ecea46df7a40b427.js +0 -1
  570. package/dashboard/out/_next/static/chunks/ecf48b83f4245d9c.js +0 -1
  571. package/dashboard/out/_next/static/chunks/eda41732bf966c6d.js +0 -1
  572. package/dashboard/out/_next/static/chunks/ee174a433049f43a.js +0 -1
  573. package/dashboard/out/_next/static/chunks/ee66b2d3b76de72e.js +0 -1
  574. package/dashboard/out/_next/static/chunks/ef058d3041c3d8ab.js +0 -1
  575. package/dashboard/out/_next/static/chunks/f3544b800b0d4969.js +0 -1
  576. package/dashboard/out/_next/static/chunks/f3b280e604b97509.js +0 -1
  577. package/dashboard/out/_next/static/chunks/f42e1d3693232993.js +0 -1
  578. package/dashboard/out/_next/static/chunks/f5a5fdd80166d304.js +0 -1
  579. package/dashboard/out/_next/static/chunks/f60adc5dd6169316.js +0 -1
  580. package/dashboard/out/_next/static/chunks/f6255bc2e6c68ce8.js +0 -1
  581. package/dashboard/out/_next/static/chunks/f6bb677d2d8840db.js +0 -1
  582. package/dashboard/out/_next/static/chunks/f6f3bf016239c33a.js +0 -1
  583. package/dashboard/out/_next/static/chunks/f6f57a46cd074bfe.js +0 -1
  584. package/dashboard/out/_next/static/chunks/f7feb9c1199e8cf6.js +0 -1
  585. package/dashboard/out/_next/static/chunks/f831087bf964cc3d.js +0 -1
  586. package/dashboard/out/_next/static/chunks/fa5a2f7244935aa2.js +0 -1
  587. package/dashboard/out/_next/static/chunks/fa73f36df4c38fa0.js +0 -1
  588. package/dashboard/out/_next/static/chunks/faa1461722d6a88d.js +0 -1
  589. package/dashboard/out/_next/static/chunks/fbf706b9a0edb7e6.js +0 -1
  590. package/dashboard/out/_next/static/chunks/fc2b8c8b7446bbc2.js +0 -1
  591. package/dashboard/out/_next/static/chunks/fd516f3930c128c1.js +0 -1
  592. package/dashboard/out/_next/static/chunks/fd663e7ff8de4887.js +0 -1
  593. package/dashboard/out/_next/static/chunks/fe5b656cc9bdd60e.js +0 -1
  594. package/dashboard/out/_next/static/chunks/turbopack-2f5dec4a537c0ee7.js +0 -4
  595. package/dashboard/out/_next/static/media/02263ebadd758ea4-s.8da66e7f.woff2 +0 -0
  596. package/dashboard/out/_next/static/media/2fe89d53234c61d4-s.cb53f04b.woff2 +0 -0
  597. package/dashboard/out/_next/static/media/59b15b4bcd7b1eb5-s.afa48be3.woff2 +0 -0
  598. package/dashboard/out/_next/static/media/5e05ae5b48faa55e-s.6b5ea6af.woff2 +0 -0
  599. package/dashboard/out/_next/static/media/68757d6cddeff913-s.e6cd1753.woff2 +0 -0
  600. package/dashboard/out/_next/static/media/99e609270109b47d-s.p.64b9304e.woff2 +0 -0
  601. package/dashboard/out/_next/static/media/a7afbb44bec2bb18-s.1dcddae6.woff2 +0 -0
  602. package/dashboard/out/_next/static/media/e390973e931a41c5-s.a82ecf4e.woff2 +0 -0
  603. package/dashboard/out/_next/static/media/e532fa1b9921e1cd-s.764b43eb.woff2 +0 -0
  604. package/dashboard/out/_next/static/media/effe91970fc4db64-s.p.19510058.woff2 +0 -0
  605. package/dashboard/out/_not-found/__next._full.txt +0 -15
  606. package/dashboard/out/_not-found/__next._head.txt +0 -5
  607. package/dashboard/out/_not-found/__next._index.txt +0 -7
  608. package/dashboard/out/_not-found/__next._not-found.__PAGE__.txt +0 -5
  609. package/dashboard/out/_not-found/__next._not-found.txt +0 -4
  610. package/dashboard/out/_not-found/__next._tree.txt +0 -2
  611. package/dashboard/out/_not-found/index.html +0 -1
  612. package/dashboard/out/_not-found/index.txt +0 -15
  613. package/dashboard/out/index.html +0 -1
  614. package/dashboard/out/index.txt +0 -21
  615. package/dashboard/out/project/__fallback/__next._full.txt +0 -19
  616. package/dashboard/out/project/__fallback/__next._head.txt +0 -5
  617. package/dashboard/out/project/__fallback/__next._index.txt +0 -7
  618. package/dashboard/out/project/__fallback/__next._tree.txt +0 -5
  619. package/dashboard/out/project/__fallback/__next.project.$d$name.__PAGE__.txt +0 -7
  620. package/dashboard/out/project/__fallback/__next.project.$d$name.txt +0 -4
  621. package/dashboard/out/project/__fallback/__next.project.txt +0 -4
  622. package/dashboard/out/project/__fallback/index.html +0 -1
  623. package/dashboard/out/project/__fallback/index.txt +0 -19
  624. package/src/__tests__/support.ts +0 -186
  625. package/src/attach.ts +0 -20
  626. package/src/cli.test.ts +0 -314
  627. package/src/command-center/discovery.test.ts +0 -265
  628. package/src/command-center/discovery.ts +0 -296
  629. package/src/command-center/index.ts +0 -31
  630. package/src/command-center/schemas.ts +0 -57
  631. package/src/command-center/server.test.ts +0 -814
  632. package/src/command-center/server.ts +0 -1427
  633. package/src/command-center/static.test.ts +0 -105
  634. package/src/command-center/static.ts +0 -122
  635. package/src/config-cli.test.ts +0 -86
  636. package/src/config.test.ts +0 -205
  637. package/src/config.ts +0 -313
  638. package/src/detect.test.ts +0 -142
  639. package/src/detect.ts +0 -248
  640. package/src/dispatch.ts +0 -102
  641. package/src/doctor.test.ts +0 -119
  642. package/src/doctor.ts +0 -167
  643. package/src/init.test.ts +0 -101
  644. package/src/init.ts +0 -211
  645. package/src/inspect.test.ts +0 -67
  646. package/src/inspect.ts +0 -212
  647. package/src/integration.test.ts.skip +0 -276
  648. package/src/js-yaml.d.ts +0 -10
  649. package/src/launch.test.ts +0 -149
  650. package/src/launch.ts +0 -583
  651. package/src/lib/auth/auth-service.test.ts +0 -178
  652. package/src/lib/auth/auth-service.ts +0 -227
  653. package/src/lib/auth/middleware.test.ts +0 -119
  654. package/src/lib/auth/middleware.ts +0 -56
  655. package/src/lib/auth/types.ts +0 -13
  656. package/src/lib/authorship.test.ts +0 -242
  657. package/src/lib/authorship.ts +0 -280
  658. package/src/lib/cast/recorder.test.ts +0 -127
  659. package/src/lib/cast/recorder.ts +0 -148
  660. package/src/lib/daemon-watchdog.ts +0 -102
  661. package/src/lib/daemon.ts +0 -431
  662. package/src/lib/dot-path.test.ts +0 -66
  663. package/src/lib/dot-path.ts +0 -17
  664. package/src/lib/errors.ts +0 -44
  665. package/src/lib/event-log.test.ts +0 -189
  666. package/src/lib/event-log.ts +0 -237
  667. package/src/lib/github-pr.ts +0 -182
  668. package/src/lib/hq/client.test.ts +0 -154
  669. package/src/lib/hq/client.ts +0 -142
  670. package/src/lib/hq/mdns.test.ts +0 -54
  671. package/src/lib/hq/mdns.ts +0 -88
  672. package/src/lib/hq/registry.test.ts +0 -237
  673. package/src/lib/hq/registry.ts +0 -145
  674. package/src/lib/hq/types.ts +0 -44
  675. package/src/lib/ipc/socket-protocol.test.ts +0 -126
  676. package/src/lib/ipc/socket-protocol.ts +0 -324
  677. package/src/lib/launch-plan.test.ts +0 -119
  678. package/src/lib/launch-plan.ts +0 -90
  679. package/src/lib/log.test.ts +0 -39
  680. package/src/lib/log.ts +0 -71
  681. package/src/lib/metrics.test.ts +0 -213
  682. package/src/lib/metrics.ts +0 -345
  683. package/src/lib/orchestrator.test.ts +0 -2358
  684. package/src/lib/orchestrator.ts +0 -1799
  685. package/src/lib/output.ts +0 -76
  686. package/src/lib/plan-store.test.ts +0 -148
  687. package/src/lib/plan-store.ts +0 -131
  688. package/src/lib/research.test.ts +0 -208
  689. package/src/lib/research.ts +0 -475
  690. package/src/lib/round-trip.test.ts +0 -341
  691. package/src/lib/session-monitor.test.ts +0 -152
  692. package/src/lib/session-monitor.ts +0 -282
  693. package/src/lib/session-options.test.ts +0 -146
  694. package/src/lib/session-options.ts +0 -100
  695. package/src/lib/shell.test.ts +0 -20
  696. package/src/lib/sizes.test.ts +0 -73
  697. package/src/lib/sizes.ts +0 -36
  698. package/src/lib/skill-registry.test.ts +0 -134
  699. package/src/lib/skill-registry.ts +0 -86
  700. package/src/lib/slugify.test.ts +0 -35
  701. package/src/lib/slugify.ts +0 -10
  702. package/src/lib/task-store.ts +0 -352
  703. package/src/lib/tmux.test.ts +0 -500
  704. package/src/lib/tmux.ts +0 -372
  705. package/src/lib/token-tracker.test.ts +0 -102
  706. package/src/lib/token-tracker.ts +0 -70
  707. package/src/lib/tunnels/cloudflare.test.ts +0 -73
  708. package/src/lib/tunnels/cloudflare.ts +0 -254
  709. package/src/lib/tunnels/manager.test.ts +0 -63
  710. package/src/lib/tunnels/manager.ts +0 -132
  711. package/src/lib/tunnels/ngrok.test.ts +0 -94
  712. package/src/lib/tunnels/ngrok.ts +0 -261
  713. package/src/lib/tunnels/tailscale.test.ts +0 -103
  714. package/src/lib/tunnels/tailscale.ts +0 -1048
  715. package/src/lib/tunnels/types.ts +0 -44
  716. package/src/lib/validation.test.ts +0 -289
  717. package/src/lib/validation.ts +0 -123
  718. package/src/lib/webhook.ts +0 -51
  719. package/src/lib/workflow-store.ts +0 -209
  720. package/src/lib/ws-v3/protocol.test.ts +0 -127
  721. package/src/lib/ws-v3/protocol.ts +0 -160
  722. package/src/lib/yaml-io.test.ts +0 -125
  723. package/src/lib/yaml-io.ts +0 -27
  724. package/src/ls.test.ts +0 -76
  725. package/src/ls.ts +0 -40
  726. package/src/metrics-cli.ts +0 -177
  727. package/src/notify.ts +0 -85
  728. package/src/orchestrator-status.ts +0 -228
  729. package/src/plan.ts +0 -117
  730. package/src/postinstall.test.ts +0 -79
  731. package/src/remote.test.ts +0 -115
  732. package/src/remote.ts +0 -148
  733. package/src/restart.ts +0 -24
  734. package/src/schemas/domain.ts +0 -422
  735. package/src/schemas/ide-config.ts +0 -148
  736. package/src/schemas/index.ts +0 -53
  737. package/src/send.test.ts +0 -70
  738. package/src/send.ts +0 -181
  739. package/src/skill.test.ts +0 -114
  740. package/src/skill.ts +0 -138
  741. package/src/status.ts +0 -66
  742. package/src/stop.test.ts +0 -151
  743. package/src/stop.ts +0 -37
  744. package/src/task.test.ts +0 -1466
  745. package/src/task.ts +0 -1143
  746. package/src/tunnel.test.ts +0 -80
  747. package/src/tunnel.ts +0 -133
  748. package/src/types.ts +0 -15
  749. package/src/ui/index.ts +0 -32
  750. package/src/ui/terminal/index.ts +0 -9
  751. package/src/ui/types.ts +0 -91
  752. package/src/ui/web/base.css +0 -80
  753. package/src/ui/web/components/Box.tsx +0 -59
  754. package/src/ui/web/components/Input.tsx +0 -32
  755. package/src/ui/web/components/ScrollBox.tsx +0 -60
  756. package/src/ui/web/components/Text.tsx +0 -28
  757. package/src/ui/web/hooks.ts +0 -106
  758. package/src/ui/web/index.ts +0 -27
  759. package/src/ui/web/render.ts +0 -77
  760. package/src/ui/web/utils/color.test.ts +0 -93
  761. package/src/ui/web/utils/color.ts +0 -27
  762. package/src/validate.test.ts +0 -385
  763. package/src/validate.ts +0 -217
  764. package/src/widgets/changes/index.tsx +0 -576
  765. package/src/widgets/config/index.tsx +0 -440
  766. package/src/widgets/costs/index.tsx +0 -216
  767. package/src/widgets/explorer/breadcrumbs.tsx +0 -77
  768. package/src/widgets/explorer/footer.tsx +0 -20
  769. package/src/widgets/explorer/header.tsx +0 -23
  770. package/src/widgets/explorer/index.tsx +0 -408
  771. package/src/widgets/explorer/tree-model.test.ts +0 -214
  772. package/src/widgets/explorer/tree-model.ts +0 -103
  773. package/src/widgets/explorer/tree.tsx +0 -156
  774. package/src/widgets/lib/config-model.ts +0 -116
  775. package/src/widgets/lib/files.test.ts +0 -103
  776. package/src/widgets/lib/files.ts +0 -88
  777. package/src/widgets/lib/git.test.ts +0 -151
  778. package/src/widgets/lib/git.ts +0 -88
  779. package/src/widgets/lib/pane-comms.test.ts +0 -178
  780. package/src/widgets/lib/pane-comms.ts +0 -207
  781. package/src/widgets/lib/theme.ts +0 -151
  782. package/src/widgets/lib/watcher.ts +0 -50
  783. package/src/widgets/mission-control/activity-feed.tsx +0 -59
  784. package/src/widgets/mission-control/agent-panel.tsx +0 -132
  785. package/src/widgets/mission-control/command-bar.tsx +0 -63
  786. package/src/widgets/mission-control/index.tsx +0 -751
  787. package/src/widgets/mission-control/task-panel.tsx +0 -238
  788. package/src/widgets/preview/index.tsx +0 -415
  789. package/src/widgets/resolve.ts +0 -40
  790. package/src/widgets/setup/agent-naming.tsx +0 -112
  791. package/src/widgets/setup/config-tree.tsx +0 -238
  792. package/src/widgets/setup/field-editor.tsx +0 -265
  793. package/src/widgets/setup/footer.tsx +0 -107
  794. package/src/widgets/setup/index.tsx +0 -340
  795. package/src/widgets/setup/layout-picker.tsx +0 -96
  796. package/src/widgets/setup/orchestrator-panel.tsx +0 -200
  797. package/src/widgets/setup/review-panel.tsx +0 -140
  798. package/src/widgets/setup/setup-model.test.ts +0 -303
  799. package/src/widgets/setup/setup-model.ts +0 -188
  800. package/src/widgets/tasks/index.tsx +0 -129
  801. package/src/widgets/tasks/task-detail.tsx +0 -309
  802. package/src/widgets/tasks/task-form.tsx +0 -193
  803. package/src/widgets/tasks/task-list.tsx +0 -205
  804. package/src/widgets/tasks/task-model.test.ts +0 -473
  805. package/src/widgets/tasks/task-model.ts +0 -157
@@ -1,1799 +0,0 @@
1
- /**
2
- * Autonomous task orchestration engine.
3
- *
4
- * Runs as a background polling loop that coordinates agent workloads across
5
- * tmux panes. Core capabilities:
6
- *
7
- * - **Dispatch** — assigns unblocked todo tasks to idle agents, respecting
8
- * dependency ordering, concurrency limits, and claim locks.
9
- * - **Stall detection** — nudges agents that exceed the configured timeout
10
- * without producing output.
11
- * - **Completion handling** — records cost/time metrics, runs after-hooks,
12
- * and notifies the master pane.
13
- * - **Retry with backoff** — re-dispatches failed tasks up to maxRetries,
14
- * using nextRetryAt to schedule exponential backoff.
15
- * - **Reconciliation** — detects crashed or vanished agent panes and
16
- * releases their in-progress tasks back to the queue.
17
- * - **Graceful shutdown** — on SIGTERM/SIGINT, releases all in-progress
18
- * tasks and persists orchestrator state for resume on next startup.
19
- * - **Hot reload** — watches ide.yml for config changes and applies them
20
- * without restarting the loop.
21
- *
22
- * @module orchestrator
23
- */
24
- import { execSync } from "node:child_process";
25
- import {
26
- existsSync,
27
- readFileSync,
28
- writeFileSync,
29
- appendFileSync,
30
- renameSync,
31
- mkdirSync,
32
- readdirSync,
33
- watch,
34
- type FSWatcher,
35
- } from "node:fs";
36
- import { join } from "node:path";
37
- import yaml from "js-yaml";
38
- import {
39
- loadMission,
40
- saveMission,
41
- loadGoal,
42
- loadGoals,
43
- loadTasks,
44
- saveTask,
45
- saveGoal,
46
- nextTaskId,
47
- type Task,
48
- type Goal,
49
- type Mission,
50
- type Milestone,
51
- } from "./task-store.ts";
52
- import { slugify } from "./slugify.ts";
53
- import { recordTaskTime } from "./token-tracker.ts";
54
- import {
55
- listSessionPanes,
56
- sendCommand,
57
- captureLastLine,
58
- type PaneInfo,
59
- } from "../widgets/lib/pane-comms.ts";
60
- import { appendEvent, readEvents, type OrchestratorEvent } from "./event-log.ts";
61
- import { loadSkill } from "./skill-registry.ts";
62
- import { isGhAvailable, createMissionPr } from "./github-pr.ts";
63
- import { computeAndSaveMetrics, appendMissionHistory } from "./metrics.ts";
64
- import {
65
- loadValidationContract,
66
- loadValidationState,
67
- saveValidationState,
68
- checkCoverage,
69
- } from "./validation.ts";
70
- import {
71
- dispatchResearch,
72
- evaluateTriggers,
73
- loadResearchState,
74
- processResearchCompletion,
75
- saveResearchState,
76
- type ResearchConfigShape,
77
- type ResearchState,
78
- } from "./research.ts";
79
-
80
- export interface OrchestratorConfig {
81
- session: string;
82
- dir: string;
83
- autoDispatch: boolean;
84
- stallTimeout: number;
85
- pollInterval: number;
86
- masterPane: string | null;
87
- beforeRun: string | null;
88
- afterRun: string | null;
89
- maxConcurrentAgents: number;
90
- dispatchMode: "tasks" | "goals" | "missions";
91
- paneSpecialties: Map<string, string[]>;
92
- services: Record<string, { command: string; port?: number; healthcheck?: string }>;
93
- research?: ResearchConfigShape;
94
- }
95
-
96
- export interface OrchestratorState {
97
- lastActivity: Map<string, number>;
98
- previousTasks: Map<string, string>;
99
- claimedTasks: Set<string>;
100
- taskClaimTimes: Map<string, number>;
101
- }
102
-
103
- export function runHook(command: string, cwd: string): { ok: true } | { ok: false; error: string } {
104
- try {
105
- execSync(command, { cwd, timeout: 60000, stdio: "pipe" });
106
- return { ok: true };
107
- } catch (err: unknown) {
108
- const message = err instanceof Error ? err.message : String(err);
109
- return { ok: false, error: message };
110
- }
111
- }
112
-
113
- // Agent detection: Claude Code reports its version (e.g. "2.1.80") as currentCommand,
114
- // not "claude". Detect agents by checking both the command name and the pane title.
115
- const SHELL_COMMANDS = new Set(["zsh", "bash", "sh", "fish"]);
116
- const AGENT_PREFIXES = ["claude", "codex"];
117
- const SPINNERS = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⠂⠒⠢⠆⠐⠠⠄◐◓◑◒✳|/\\-] /;
118
- const VERSION_PATTERN = /^\d+\.\d+/;
119
-
120
- // Strip spinner/status prefix from pane title to get stable name
121
- export function normalizePaneTitle(title: string): string {
122
- return title.replace(SPINNERS, "").trim();
123
- }
124
-
125
- // French names for agents — fun, memorable, and stable across spinner changes
126
- const AGENT_NAMES = [
127
- "François",
128
- "Amélie",
129
- "Louis",
130
- "Camille",
131
- "Marcel",
132
- "Colette",
133
- "Henri",
134
- "Margaux",
135
- "René",
136
- "Léonie",
137
- "Étienne",
138
- "Fleur",
139
- "Gaston",
140
- "Isabelle",
141
- "Jacques",
142
- "Lucienne",
143
- "Nicolas",
144
- "Odette",
145
- "Pierre",
146
- "Rosalie",
147
- ];
148
-
149
- // Get a stable, memorable identifier for an agent pane
150
- export function agentIdentifier(pane: PaneInfo): string {
151
- if (pane.name) return pane.name;
152
- return AGENT_NAMES[pane.index % AGENT_NAMES.length] ?? `Agent ${pane.index}`;
153
- }
154
-
155
- export function isAgentPane(pane: PaneInfo): boolean {
156
- const cmd = pane.currentCommand.toLowerCase();
157
- if (AGENT_PREFIXES.some((p) => cmd.startsWith(p))) return true;
158
- // Claude Code shows its version string as the command (e.g. "2.1.80")
159
- if (VERSION_PATTERN.test(cmd)) return true;
160
- // Title-based detection — check for Claude Code or French agent names
161
- if (/claude\s*code/i.test(pane.title)) return true;
162
- const name = normalizePaneTitle(pane.title);
163
- if (AGENT_NAMES.includes(name)) return true;
164
- return false;
165
- }
166
-
167
- export function isAgentBusy(pane: PaneInfo): boolean {
168
- // Spinner chars in pane title indicate the agent is actively working
169
- return SPINNERS.test(pane.title);
170
- }
171
-
172
- /**
173
- * Check if a pane is at the agent prompt (❯) by capturing the last line.
174
- * This is more reliable than title-based spinner detection since pane titles
175
- * can show stale spinners after the agent finishes.
176
- */
177
- function isAtAgentPrompt(paneId: string): boolean {
178
- const lastLine = captureLastLine(paneId);
179
- return lastLine.includes("❯");
180
- }
181
-
182
- export function isIdleForDispatch(pane: PaneInfo): boolean {
183
- const cmd = pane.currentCommand.toLowerCase();
184
- // Running a shell → idle
185
- if (SHELL_COMMANDS.has(cmd)) return true;
186
- // Agent pane: check spinner first (fast), then fall back to prompt detection (reliable)
187
- if (isAgentPane(pane)) {
188
- if (!isAgentBusy(pane)) return true;
189
- // Spinner may be stale — check if agent is actually at the prompt
190
- return isAtAgentPrompt(pane.id);
191
- }
192
- return false;
193
- }
194
-
195
- /**
196
- * Load a library file excerpt. Returns content truncated to ~500 chars if needed.
197
- */
198
- function loadLibraryExcerpt(filePath: string, maxChars = 500): string | null {
199
- if (!existsSync(filePath)) return null;
200
- try {
201
- const content = readFileSync(filePath, "utf-8").trim();
202
- if (!content) return null;
203
- if (content.length <= maxChars) return content;
204
- return content.slice(0, maxChars) + `\n... (see full file at ${filePath})`;
205
- } catch {
206
- return null;
207
- }
208
- }
209
-
210
- export function buildTaskPrompt(dir: string, task: Task, config?: OrchestratorConfig): string {
211
- let prompt = "";
212
-
213
- // 1. Mission narrative
214
- const mission = loadMission(dir);
215
- if (mission) {
216
- prompt += `## Mission: ${mission.title}\n`;
217
- if (mission.description) prompt += `${mission.description}\n`;
218
- prompt += "\n";
219
- }
220
-
221
- // 2. Milestone context
222
- if (task.milestone && mission) {
223
- const milestone = mission.milestones.find((m) => m.id === task.milestone);
224
- if (milestone) {
225
- prompt += `## Milestone: ${milestone.title} (${milestone.id})\n`;
226
- if (milestone.description) prompt += `${milestone.description}\n`;
227
- prompt += "\n";
228
- }
229
- }
230
-
231
- // 3. AGENTS.md — project boundaries
232
- const agentsMdPath = join(dir, "AGENTS.md");
233
- const agentsContent = loadLibraryExcerpt(agentsMdPath, 1000);
234
- if (agentsContent) {
235
- prompt += `## Agent Guidelines\n${agentsContent}\n\n`;
236
- }
237
-
238
- // 4. Skill context
239
- {
240
- const skillName = task.specialty ?? "general-worker";
241
- const skill = loadSkill(dir, skillName);
242
- if (skill?.body) {
243
- prompt += `## Your Role: ${skill.name}\n`;
244
- prompt += `${skill.body}\n\n`;
245
- }
246
- }
247
-
248
- // 5. Goal context
249
- if (task.goal) {
250
- const goal = loadGoal(dir, task.goal);
251
- if (goal) {
252
- prompt += `## Goal: ${goal.title}\n`;
253
- if (goal.acceptance) prompt += `Acceptance: ${goal.acceptance}\n`;
254
- prompt += "\n";
255
- }
256
- }
257
-
258
- // 6. Task details
259
- prompt += `## Your Task: ${task.title}\n`;
260
- if (task.description) prompt += `${task.description}\n`;
261
- prompt += `\nPriority: P${task.priority}\n`;
262
- if (task.tags?.length) prompt += `Tags: ${task.tags.join(", ")}\n`;
263
- if (task.fulfills?.length) prompt += `Fulfills assertions: ${task.fulfills.join(", ")}\n`;
264
- prompt += "\n";
265
-
266
- // 7. Recent completions (same milestone, last 5)
267
- if (task.milestone) {
268
- const allTasks = loadTasks(dir);
269
- const recentDone = allTasks
270
- .filter((t) => t.milestone === task.milestone && t.status === "done" && t.id !== task.id)
271
- .slice(-5);
272
- if (recentDone.length > 0) {
273
- prompt += `## Recent Completions (${task.milestone})\n`;
274
- for (const t of recentDone) {
275
- prompt += `- ${t.id}: ${t.title}`;
276
- if (t.salientSummary) prompt += ` — ${t.salientSummary}`;
277
- prompt += "\n";
278
- }
279
- prompt += "\n";
280
- }
281
- }
282
-
283
- // 8. Library references
284
- const libraryDir = join(dir, ".tmux-ide", "library");
285
- if (existsSync(libraryDir)) {
286
- // Always include architecture.md
287
- const archExcerpt = loadLibraryExcerpt(join(libraryDir, "architecture.md"));
288
- if (archExcerpt) {
289
- prompt += `## Architecture\n${archExcerpt}\n\n`;
290
- }
291
-
292
- // Match library files by task tags
293
- if (task.tags?.length) {
294
- try {
295
- const files = readdirSync(libraryDir).filter(
296
- (f) => f.endsWith(".md") && f !== "architecture.md",
297
- );
298
- const tagSet = new Set(task.tags.map((t) => t.toLowerCase()));
299
- for (const file of files) {
300
- const stem = file.replace(/\.md$/, "").toLowerCase();
301
- if (tagSet.has(stem)) {
302
- const excerpt = loadLibraryExcerpt(join(libraryDir, file));
303
- if (excerpt) {
304
- prompt += `## Reference: ${file}\n${excerpt}\n\n`;
305
- }
306
- }
307
- }
308
- } catch {
309
- // Library scan failed — skip
310
- }
311
- }
312
- }
313
-
314
- // 9. Services
315
- if (config?.services && Object.keys(config.services).length > 0) {
316
- prompt += `## Available Services\n`;
317
- for (const [name, svc] of Object.entries(config.services)) {
318
- prompt += `- **${name}**: \`${svc.command}\``;
319
- if (svc.port) prompt += ` (port ${svc.port})`;
320
- if (svc.healthcheck) prompt += ` — healthcheck: \`${svc.healthcheck}\``;
321
- prompt += "\n";
322
- }
323
- prompt += "\n";
324
- }
325
-
326
- // 10. Workspace + completion instructions
327
- prompt += `Workspace: ${dir}\n\n`;
328
- prompt += `When done:\n`;
329
- prompt += ` tmux-ide task done ${task.id} --proof "short summary of what you accomplished"\n`;
330
-
331
- return prompt.trim();
332
- }
333
-
334
- // --- Milestone gating ---
335
-
336
- /**
337
- * Get the current (first non-done) milestone from the mission, sorted by order.
338
- */
339
- export function getCurrentMilestone(dir: string): Milestone | null {
340
- const mission = loadMission(dir);
341
- if (!mission || mission.milestones.length === 0) return null;
342
- const sorted = [...mission.milestones].sort((a, b) => a.order - b.order);
343
- return sorted.find((m) => m.status !== "done") ?? null;
344
- }
345
-
346
- /**
347
- * Check that all milestones with lower order than the given one are 'done'.
348
- */
349
- export function isMilestoneReady(dir: string, milestoneId: string): boolean {
350
- const mission = loadMission(dir);
351
- if (!mission) return false;
352
- const target = mission.milestones.find((m) => m.id === milestoneId);
353
- if (!target) return false;
354
- return mission.milestones.filter((m) => m.order < target.order).every((m) => m.status === "done");
355
- }
356
-
357
- /**
358
- * Check if a task's milestone allows dispatch.
359
- * Tasks without a milestone are always eligible (backward compat).
360
- * Tasks with a milestone require it to be 'active' and all predecessors done.
361
- */
362
- function isTaskMilestoneEligible(dir: string, task: Task, mission: Mission | null): boolean {
363
- if (!task.milestone) return true;
364
- if (!mission) return false;
365
- const milestone = mission.milestones.find((m) => m.id === task.milestone);
366
- if (!milestone) return false;
367
- if (milestone.status !== "active") return false;
368
- return isMilestoneReady(dir, milestone.id);
369
- }
370
-
371
- /**
372
- * Check active milestones for completion. When all tasks for a milestone are done,
373
- * transition to 'validating' (if a validation contract exists) or straight to 'done'.
374
- */
375
- export function checkMilestoneCompletion(
376
- config: OrchestratorConfig,
377
- state: OrchestratorState,
378
- tasks: Task[],
379
- panes: PaneInfo[],
380
- ): void {
381
- const mission = loadMission(config.dir);
382
- if (!mission || mission.milestones.length === 0) return;
383
-
384
- let changed = false;
385
- const sorted = [...mission.milestones].sort((a, b) => a.order - b.order);
386
-
387
- for (const milestone of sorted) {
388
- if (milestone.status !== "active") continue;
389
-
390
- const milestoneTasks = tasks.filter((t) => t.milestone === milestone.id);
391
- if (milestoneTasks.length === 0) continue;
392
- const allDone = milestoneTasks.every((t) => t.status === "done");
393
- if (!allDone) continue;
394
-
395
- // Check if a validation contract exists — if so, go through validation
396
- const contract = loadValidationContract(config.dir);
397
- if (contract) {
398
- const dispatched = dispatchValidation(config, state, milestone, milestoneTasks, panes);
399
- if (!dispatched) continue;
400
-
401
- milestone.status = "validating";
402
- milestone.updated = new Date().toISOString();
403
- changed = true;
404
-
405
- appendEvent(config.dir, {
406
- timestamp: new Date().toISOString(),
407
- type: "milestone_validating",
408
- milestoneId: milestone.id,
409
- title: milestone.title,
410
- message: `Milestone "${milestone.title}" (${milestone.id}) — all tasks done, dispatching validation`,
411
- });
412
- } else {
413
- // No contract — skip straight to done
414
- milestone.status = "done";
415
- milestone.updated = new Date().toISOString();
416
- changed = true;
417
-
418
- appendEvent(config.dir, {
419
- timestamp: new Date().toISOString(),
420
- type: "milestone_complete",
421
- milestoneId: milestone.id,
422
- title: milestone.title,
423
- message: `Milestone "${milestone.title}" (${milestone.id}) completed`,
424
- });
425
-
426
- activateNextMilestone(mission, milestone.id);
427
- }
428
- }
429
-
430
- if (changed) {
431
- mission.updated = new Date().toISOString();
432
- saveMission(config.dir, mission);
433
- }
434
- }
435
-
436
- function activateNextMilestone(mission: Mission, completedMilestoneId: string): boolean {
437
- const sorted = [...mission.milestones].sort((a, b) => a.order - b.order);
438
- const completed = sorted.find((milestone) => milestone.id === completedMilestoneId);
439
- if (!completed || completed.status !== "done") return false;
440
-
441
- const next = sorted.find(
442
- (milestone) => milestone.order > completed.order && milestone.status === "locked",
443
- );
444
- if (!next) return false;
445
-
446
- next.status = "active";
447
- next.updated = new Date().toISOString();
448
- return true;
449
- }
450
-
451
- /**
452
- * Build a validation prompt for a milestone.
453
- */
454
- export function buildValidationPrompt(
455
- dir: string,
456
- milestone: Milestone,
457
- completedTasks: Task[],
458
- ): string {
459
- const contract = loadValidationContract(dir);
460
- let prompt = `# Validation: ${milestone.title} (${milestone.id})\n\n`;
461
-
462
- if (contract) {
463
- prompt += `## Validation Contract\n\n${contract}\n\n`;
464
- }
465
-
466
- prompt += `## Completed Tasks\n\n`;
467
- for (const task of completedTasks) {
468
- prompt += `### ${task.id}: ${task.title}\n`;
469
- if (task.proof) prompt += `Proof: ${JSON.stringify(task.proof)}\n`;
470
- if (task.fulfills.length > 0) prompt += `Fulfills assertions: ${task.fulfills.join(", ")}\n`;
471
- if (task.salientSummary) prompt += `Summary: ${task.salientSummary}\n`;
472
- prompt += "\n";
473
- }
474
-
475
- prompt += `## Instructions\n\n`;
476
- prompt += `Verify each assertion in the validation contract. For each assertion:\n`;
477
- prompt += `1. Check that the completed work actually satisfies the requirement\n`;
478
- prompt += `2. Run any relevant tests or checks\n`;
479
- prompt += `3. Report your finding:\n`;
480
- prompt += ` tmux-ide validate assert <ASSERT_ID> --status passing --evidence "description"\n`;
481
- prompt += ` tmux-ide validate assert <ASSERT_ID> --status failing --evidence "what's wrong"\n\n`;
482
- prompt += `When done verifying all assertions, the orchestrator will automatically detect results.\n`;
483
-
484
- return prompt.trim();
485
- }
486
-
487
- /**
488
- * Dispatch validation to a validator pane after milestone completion.
489
- */
490
- export function dispatchValidation(
491
- config: OrchestratorConfig,
492
- state: OrchestratorState,
493
- milestone: Milestone,
494
- completedTasks: Task[],
495
- panes: PaneInfo[],
496
- ): boolean {
497
- // Find validator pane, fall back to any idle non-lead agent
498
- const validatorPane =
499
- panes.find((p) => p.role === "validator" && isIdleForDispatch(p)) ??
500
- panes.find((p) => {
501
- if (p.role === "lead") return false;
502
- if (config.masterPane && normalizePaneTitle(p.title) === config.masterPane) return false;
503
- return isIdleForDispatch(p);
504
- });
505
-
506
- if (!validatorPane) {
507
- appendEvent(config.dir, {
508
- timestamp: new Date().toISOString(),
509
- type: "error",
510
- message: `No idle validator pane for milestone "${milestone.title}" — validation pending`,
511
- });
512
- return false;
513
- }
514
-
515
- // Initialize validation state with pending entries for all fulfills
516
- const valState = loadValidationState(config.dir) ?? { assertions: {}, lastVerified: null };
517
- for (const task of completedTasks) {
518
- for (const assertionId of task.fulfills) {
519
- if (!valState.assertions[assertionId]) {
520
- valState.assertions[assertionId] = {
521
- status: "pending",
522
- verifiedBy: null,
523
- verifiedAt: null,
524
- evidence: null,
525
- blockedBy: null,
526
- };
527
- }
528
- }
529
- }
530
- saveValidationState(config.dir, valState);
531
-
532
- // Build and write prompt
533
- const prompt = buildValidationPrompt(config.dir, milestone, completedTasks);
534
- const dispatchDir = join(config.dir, ".tasks", "dispatch");
535
- if (!existsSync(dispatchDir)) mkdirSync(dispatchDir, { recursive: true });
536
- const dispatchFile = join(dispatchDir, `validate-${milestone.id}.md`);
537
- writeFileSync(dispatchFile, prompt);
538
-
539
- try {
540
- sendCommand(
541
- config.session,
542
- validatorPane.id,
543
- `Read and execute: .tasks/dispatch/validate-${milestone.id}.md`,
544
- );
545
- } catch (error: unknown) {
546
- const message = error instanceof Error ? error.message : String(error);
547
- appendEvent(config.dir, {
548
- timestamp: new Date().toISOString(),
549
- type: "error",
550
- message: `Failed to dispatch validation for milestone "${milestone.title}": ${message}`,
551
- });
552
- return false;
553
- }
554
-
555
- saveValidationState(config.dir, valState);
556
-
557
- appendEvent(config.dir, {
558
- timestamp: new Date().toISOString(),
559
- type: "validation_dispatch",
560
- milestoneId: milestone.id,
561
- title: milestone.title,
562
- target: validatorPane.title,
563
- message: `Dispatched validation for "${milestone.title}" to ${validatorPane.title}`,
564
- });
565
-
566
- state.lastActivity.set(validatorPane.id, Date.now());
567
- return true;
568
- }
569
-
570
- /**
571
- * Check validation results for milestones in 'validating' state.
572
- * If all assertions pass → mark done, activate next.
573
- * If any fail → create remediation tasks, set milestone back to 'active'.
574
- */
575
- export function checkValidationResults(config: OrchestratorConfig, tasks: Task[]): void {
576
- const mission = loadMission(config.dir);
577
- if (!mission) return;
578
-
579
- const valState = loadValidationState(config.dir);
580
- if (!valState) return;
581
-
582
- let changed = false;
583
- const sorted = [...mission.milestones].sort((a, b) => a.order - b.order);
584
-
585
- for (const milestone of sorted) {
586
- if (milestone.status !== "validating") continue;
587
-
588
- // Collect assertion IDs relevant to this milestone's tasks
589
- const milestoneTasks = tasks.filter((t) => t.milestone === milestone.id);
590
- const milestoneAssertionIds = new Set(milestoneTasks.flatMap((t) => t.fulfills));
591
-
592
- if (milestoneAssertionIds.size === 0) {
593
- milestone.status = "done";
594
- milestone.updated = new Date().toISOString();
595
- changed = true;
596
-
597
- appendEvent(config.dir, {
598
- timestamp: new Date().toISOString(),
599
- type: "milestone_complete",
600
- milestoneId: milestone.id,
601
- title: milestone.title,
602
- message: `Milestone "${milestone.title}" (${milestone.id}) — no assertions to validate`,
603
- });
604
-
605
- activateNextMilestone(mission, milestone.id);
606
- continue;
607
- }
608
-
609
- // Check if all milestone assertions have been verified (no longer pending)
610
- const allVerified = [...milestoneAssertionIds].every((id) => {
611
- const entry = valState.assertions[id];
612
- return entry && entry.status !== "pending";
613
- });
614
- if (!allVerified) continue;
615
-
616
- const allPassing = [...milestoneAssertionIds].every(
617
- (id) => valState.assertions[id]?.status === "passing",
618
- );
619
-
620
- if (allPassing) {
621
- milestone.status = "done";
622
- milestone.updated = new Date().toISOString();
623
- changed = true;
624
-
625
- appendEvent(config.dir, {
626
- timestamp: new Date().toISOString(),
627
- type: "milestone_complete",
628
- milestoneId: milestone.id,
629
- title: milestone.title,
630
- message: `Milestone "${milestone.title}" (${milestone.id}) — validation passed`,
631
- });
632
-
633
- activateNextMilestone(mission, milestone.id);
634
- } else {
635
- // Create remediation tasks for failing assertions
636
- const failedIds = [...milestoneAssertionIds].filter(
637
- (id) => valState.assertions[id]?.status === "failing",
638
- );
639
-
640
- for (const assertionId of failedIds) {
641
- const entry = valState.assertions[assertionId];
642
- if (!entry || entry.status !== "failing") continue;
643
-
644
- const id = nextTaskId(config.dir);
645
- const now = new Date().toISOString();
646
- const task: Task = {
647
- id,
648
- title: `Remediate: assertion ${assertionId} failing`,
649
- description: `Assertion ${assertionId} failed validation.\nEvidence: ${entry.evidence ?? "none"}\n\nFix the issue and ensure the assertion passes.`,
650
- goal: null,
651
- status: "todo",
652
- assignee: null,
653
- priority: 1,
654
- created: now,
655
- updated: now,
656
- tags: ["remediation"],
657
- proof: null,
658
- depends_on: [],
659
- retryCount: 0,
660
- maxRetries: 5,
661
- lastError: null,
662
- nextRetryAt: null,
663
- milestone: milestone.id,
664
- specialty: null,
665
- fulfills: [assertionId],
666
- discoveredIssues: [],
667
- salientSummary: null,
668
- };
669
- saveTask(config.dir, task);
670
-
671
- // Reset the assertion back to pending for re-verification
672
- entry.status = "pending";
673
- entry.verifiedBy = null;
674
- entry.verifiedAt = null;
675
- entry.evidence = null;
676
- entry.blockedBy = null;
677
-
678
- appendEvent(config.dir, {
679
- timestamp: new Date().toISOString(),
680
- type: "remediation",
681
- taskId: id,
682
- assertionId,
683
- message: `Created remediation task ${id} for failing assertion ${assertionId}`,
684
- });
685
- }
686
-
687
- saveValidationState(config.dir, valState);
688
-
689
- // Set milestone back to active so remediation tasks can be dispatched
690
- milestone.status = "active";
691
- milestone.updated = new Date().toISOString();
692
- changed = true;
693
-
694
- appendEvent(config.dir, {
695
- timestamp: new Date().toISOString(),
696
- type: "validation_failed",
697
- milestoneId: milestone.id,
698
- title: milestone.title,
699
- failedCount: failedIds.length,
700
- message: `Milestone "${milestone.title}" (${milestone.id}) — ${failedIds.length} assertion(s) failed, remediation tasks created`,
701
- });
702
- }
703
- }
704
-
705
- if (changed) {
706
- mission.updated = new Date().toISOString();
707
- saveMission(config.dir, mission);
708
- }
709
- }
710
-
711
- // --- Planning dispatch ---
712
-
713
- const PLANNING_CLAIM = "__planning__";
714
-
715
- /**
716
- * Build the planning prompt for the lead/planner agent.
717
- */
718
- export function buildPlanningPrompt(dir: string): string {
719
- const mission = loadMission(dir);
720
- let prompt = `# Mission Planning\n\n`;
721
-
722
- if (mission) {
723
- prompt += `## Mission: ${mission.title}\n`;
724
- if (mission.description) prompt += `${mission.description}\n`;
725
- prompt += "\n";
726
- }
727
-
728
- // AGENTS.md
729
- const agentsMdPath = join(dir, "AGENTS.md");
730
- const agentsContent = loadLibraryExcerpt(agentsMdPath, 1000);
731
- if (agentsContent) {
732
- prompt += `## Agent Guidelines\n${agentsContent}\n\n`;
733
- }
734
-
735
- prompt += `## Instructions\n\n`;
736
- prompt += `You are the mission planner. Analyze the mission scope and create an execution plan.\n\n`;
737
- prompt += `1. Design milestones (sequential execution phases):\n`;
738
- prompt += ` tmux-ide milestone create "title" --sequence N [-d "description"]\n\n`;
739
- prompt += `2. Create a validation contract at .tasks/validation-contract.md with assertion IDs:\n`;
740
- prompt += ` Each assertion is a testable claim (e.g., ASSERT01: "Auth endpoint returns 200")\n\n`;
741
- prompt += `3. Create tasks linked to milestones:\n`;
742
- prompt += ` tmux-ide task create "title" --milestone M1 --specialty frontend --fulfills "ASSERT01,ASSERT02" [-d "description"]\n\n`;
743
- prompt += `4. Ensure every assertion in the contract is claimed by at least one task's --fulfills\n\n`;
744
- prompt += `5. When planning is complete, signal:\n`;
745
- prompt += ` tmux-ide mission plan-complete\n`;
746
-
747
- return prompt.trim();
748
- }
749
-
750
- /**
751
- * Dispatch the planning phase to the lead/planner pane.
752
- * Only runs once per mission (guarded by __planning__ claim).
753
- */
754
- export function dispatchPlanning(
755
- config: OrchestratorConfig,
756
- state: OrchestratorState,
757
- panes: PaneInfo[],
758
- ): void {
759
- if (state.claimedTasks.has(PLANNING_CLAIM)) return;
760
-
761
- // Find the lead/planner pane
762
- const plannerPane =
763
- panes.find((p) => p.role === "lead") ??
764
- panes.find((p) => config.masterPane && normalizePaneTitle(p.title) === config.masterPane) ??
765
- panes.find((p) => isIdleForDispatch(p));
766
-
767
- if (!plannerPane) {
768
- appendEvent(config.dir, {
769
- timestamp: new Date().toISOString(),
770
- type: "error",
771
- message: "Unable to dispatch mission planning: no planner pane available",
772
- });
773
- return;
774
- }
775
-
776
- state.claimedTasks.add(PLANNING_CLAIM);
777
-
778
- const prompt = buildPlanningPrompt(config.dir);
779
- const dispatchDir = join(config.dir, ".tasks", "dispatch");
780
- if (!existsSync(dispatchDir)) mkdirSync(dispatchDir, { recursive: true });
781
- const dispatchFile = join(dispatchDir, "planning.md");
782
- writeFileSync(dispatchFile, prompt);
783
-
784
- sendCommand(config.session, plannerPane.id, `Read and execute: .tasks/dispatch/planning.md`);
785
-
786
- appendEvent(config.dir, {
787
- timestamp: new Date().toISOString(),
788
- type: "planning",
789
- target: plannerPane.title,
790
- message: `Dispatched mission planning to ${plannerPane.title}`,
791
- });
792
-
793
- state.lastActivity.set(plannerPane.id, Date.now());
794
- }
795
-
796
- // --- Mission completion ---
797
-
798
- /**
799
- * Handle mission completion: all milestones done and all validations passing.
800
- */
801
- export function handleMissionComplete(
802
- config: OrchestratorConfig,
803
- mission: Mission,
804
- panes: PaneInfo[],
805
- ): void {
806
- if (!mission.milestones.every((milestone) => milestone.status === "done")) return;
807
-
808
- mission.status = "complete";
809
- mission.updated = new Date().toISOString();
810
- saveMission(config.dir, mission);
811
-
812
- // Try to create a mission-level PR
813
- let prResult: { url: string; number: number } | null = null;
814
- if (mission.branch && isGhAvailable()) {
815
- prResult = createMissionPr(mission, config.dir);
816
- }
817
-
818
- // Build completion summary
819
- const totalMilestones = mission.milestones.length;
820
- const tasks = loadTasks(config.dir);
821
- const doneTasks = tasks.filter((t) => t.status === "done").length;
822
-
823
- let summary = `# Mission Complete: ${mission.title}\n\n`;
824
- summary += `Milestones: ${totalMilestones} completed\n`;
825
- summary += `Tasks: ${doneTasks}/${tasks.length} done\n`;
826
- if (prResult) summary += `PR: ${prResult.url}\n`;
827
- summary += `\nMission "${mission.title}" has been delivered.`;
828
-
829
- // Write dispatch file and notify master
830
- const dispatchDir = join(config.dir, ".tasks", "dispatch");
831
- if (!existsSync(dispatchDir)) mkdirSync(dispatchDir, { recursive: true });
832
- writeFileSync(join(dispatchDir, "mission-complete.md"), summary);
833
-
834
- if (config.masterPane) {
835
- const masterPane =
836
- panes.find((p) => p.role === "lead") ?? panes.find((p) => p.title === config.masterPane);
837
- if (masterPane) {
838
- sendCommand(
839
- config.session,
840
- masterPane.id,
841
- `Mission complete: "${mission.title}". Run: tmux-ide dispatch mission-complete`,
842
- );
843
- }
844
- }
845
-
846
- // Record mission in history for cross-mission comparison
847
- appendMissionHistory(config.dir, mission, tasks);
848
-
849
- appendEvent(config.dir, {
850
- timestamp: new Date().toISOString(),
851
- type: "mission_complete",
852
- title: mission.title,
853
- milestoneCount: totalMilestones,
854
- taskCount: doneTasks,
855
- ...(prResult ? { prNumber: prResult.number } : {}),
856
- message: `Mission "${mission.title}" completed — ${totalMilestones} milestones, ${doneTasks} tasks${prResult ? `, PR #${prResult.number}` : ""}`,
857
- });
858
- }
859
-
860
- export function dispatch(
861
- config: OrchestratorConfig,
862
- state: OrchestratorState,
863
- tasks: Task[],
864
- panes: PaneInfo[],
865
- ): void {
866
- // Coverage check: warn about unclaimed assertions (once per session)
867
- if (config.dispatchMode === "missions" && !state.claimedTasks.has("__coverage_warned__")) {
868
- const { unclaimed } = checkCoverage(config.dir);
869
- if (unclaimed.length > 0) {
870
- state.claimedTasks.add("__coverage_warned__");
871
- appendEvent(config.dir, {
872
- timestamp: new Date().toISOString(),
873
- type: "error",
874
- message: `Coverage gap: ${unclaimed.length} assertion(s) not claimed by any task: ${unclaimed.join(", ")}`,
875
- });
876
- }
877
- }
878
-
879
- // Find idle agent panes (not the master pane, not already assigned a task)
880
- const idleAgents = panes.filter((p) => {
881
- // Master pane check: use role when available, fall back to title
882
- if (p.role === "lead") return false;
883
- if (!p.role && config.masterPane && normalizePaneTitle(p.title) === config.masterPane)
884
- return false;
885
- if (!isIdleForDispatch(p)) return false;
886
- // Don't dispatch to a pane that already has an in-progress task assigned
887
- const name = agentIdentifier(p);
888
- const hasActiveTask = tasks.some(
889
- (t) => t.assignee === name && (t.status === "in-progress" || t.status === "review"),
890
- );
891
- if (hasActiveTask) return false;
892
- return true;
893
- });
894
-
895
- // Concurrency control: don't exceed max concurrent agents
896
- const maxConcurrent = config.maxConcurrentAgents ?? 10;
897
- const inProgressCount = tasks.filter((t) => t.status === "in-progress").length;
898
- const availableSlots = Math.max(0, maxConcurrent - inProgressCount);
899
- if (availableSlots === 0) return;
900
-
901
- // Find unassigned todo tasks that haven't been claimed, sorted by priority
902
- // Also include retry-eligible tasks (nextRetryAt in the past)
903
- const now = Date.now();
904
- const mission = config.dispatchMode === "missions" ? loadMission(config.dir) : null;
905
- const todoTasks = tasks
906
- .filter((t) => {
907
- // Skip if already claimed
908
- if (state.claimedTasks.has(t.id)) return false;
909
- // Regular todo tasks (include pre-assigned tasks — assignee is a preference hint)
910
- if (t.status === "todo") {
911
- // Skip if waiting for scheduled retry (nextRetryAt is in the future)
912
- if (t.nextRetryAt && new Date(t.nextRetryAt).getTime() > now) return false;
913
- // Check dependencies: all depends_on tasks must be done
914
- if (t.depends_on && t.depends_on.length > 0) {
915
- const allDepsDone = t.depends_on.every((depId) => {
916
- const dep = tasks.find((d) => d.id === depId);
917
- return dep?.status === "done";
918
- });
919
- if (!allDepsDone) return false;
920
- }
921
- // Milestone gating (missions mode only)
922
- if (
923
- config.dispatchMode === "missions" &&
924
- !isTaskMilestoneEligible(config.dir, t, mission)
925
- ) {
926
- return false;
927
- }
928
- return true;
929
- }
930
- // Retry-eligible tasks (failed with nextRetryAt in the past)
931
- if (t.nextRetryAt && t.retryCount < (t.maxRetries ?? 5)) {
932
- const retryTime = new Date(t.nextRetryAt).getTime();
933
- if (retryTime <= now) return true;
934
- }
935
- return false;
936
- })
937
- .sort((a, b) => a.priority - b.priority);
938
-
939
- const assignedAgents = new Set<string>();
940
- let dispatched = 0;
941
- for (const task of todoTasks) {
942
- if (dispatched >= availableSlots) break;
943
- const agent = findBestAgent(config, idleAgents, task, assignedAgents);
944
- if (!agent) continue; // no suitable agent — skip (specialist task waits)
945
-
946
- // Verify the target pane still exists before dispatch
947
- const currentPanes = listSessionPanes(config.session);
948
- if (!currentPanes.find((p) => p.id === agent.id)) {
949
- // Agent pane vanished — skip
950
- continue;
951
- }
952
-
953
- const agentName = agentIdentifier(agent);
954
-
955
- // Persist task as in-progress FIRST — prevents double-dispatch on crash
956
- task.status = "in-progress";
957
- task.assignee = agentName;
958
- task.nextRetryAt = null; // Clear retry schedule on dispatch
959
- task.updated = new Date().toISOString();
960
- saveTask(config.dir, task);
961
-
962
- // Claim lock: prevent double-dispatch across poll ticks
963
- state.claimedTasks.add(task.id);
964
-
965
- // Run before_run hook in project directory — abort dispatch for this task on failure
966
- if (config.beforeRun) {
967
- const result = runHook(config.beforeRun, config.dir);
968
- if (!result.ok) {
969
- task.status = "todo";
970
- task.assignee = null;
971
- task.updated = new Date().toISOString();
972
- saveTask(config.dir, task);
973
- state.claimedTasks.delete(task.id);
974
- continue;
975
- }
976
- }
977
-
978
- // Record claim time for cost tracking
979
- state.taskClaimTimes.set(task.id, Date.now());
980
-
981
- // Write the full prompt to a dispatch file and send a short command
982
- // telling the agent to read it. This avoids the paste preview problem
983
- // entirely — short commands (<200 chars) never trigger it.
984
- const prompt = buildTaskPrompt(config.dir, task, config);
985
- const dispatchDir = join(config.dir, ".tasks", "dispatch");
986
- if (!existsSync(dispatchDir)) mkdirSync(dispatchDir, { recursive: true });
987
- // Validate task ID to prevent path traversal
988
- if (!/^\d{3,}$/.test(task.id)) {
989
- task.status = "todo";
990
- task.assignee = null;
991
- task.updated = new Date().toISOString();
992
- saveTask(config.dir, task);
993
- state.claimedTasks.delete(task.id);
994
- continue;
995
- }
996
- const dispatchFile = join(dispatchDir, `${task.id}.md`);
997
- writeFileSync(dispatchFile, prompt);
998
-
999
- const shortCmd = `tmux-ide dispatch ${task.id}`;
1000
- const sent = sendCommand(config.session, agent.id, shortCmd);
1001
-
1002
- if (!sent) {
1003
- // Roll back: reset task to todo, remove claim
1004
- task.status = "todo";
1005
- task.assignee = null;
1006
- task.updated = new Date().toISOString();
1007
- saveTask(config.dir, task);
1008
- state.claimedTasks.delete(task.id);
1009
- state.taskClaimTimes.delete(task.id);
1010
-
1011
- appendEvent(config.dir, {
1012
- timestamp: new Date().toISOString(),
1013
- type: "error",
1014
- taskId: task.id,
1015
- agent: agent.title,
1016
- message: `Failed to send command to ${agent.title} for task "${task.title}" — rolled back`,
1017
- });
1018
- continue;
1019
- }
1020
-
1021
- // Log dispatch event
1022
- appendEvent(config.dir, {
1023
- timestamp: new Date().toISOString(),
1024
- type: "dispatch",
1025
- taskId: task.id,
1026
- agent: agent.title,
1027
- message: `Dispatched "${task.title}" to ${agent.title}`,
1028
- });
1029
-
1030
- // Track agent assignment to prevent double-assignment
1031
- assignedAgents.add(agent.id);
1032
- dispatched++;
1033
-
1034
- // Track activity
1035
- state.lastActivity.set(agent.id, Date.now());
1036
- }
1037
- }
1038
-
1039
- // --- Skill-based agent matching ---
1040
-
1041
- function matchesSpecialty(specialty: string | null, agentSpecialties: string[]): boolean {
1042
- if (!specialty) return true; // no specialty = any agent
1043
- if (agentSpecialties.length === 0) return true; // no agent specialty = takes anything
1044
- const tags = specialty.split(",").map((s) => s.trim().toLowerCase());
1045
- return tags.some((tag) => agentSpecialties.includes(tag));
1046
- }
1047
-
1048
- /**
1049
- * Find the best available agent for a task based on specialty matching.
1050
- *
1051
- * Priority: specialist match > generalist (no specialties) > null.
1052
- * Tasks with no specialty go to the first available agent.
1053
- */
1054
- export function findBestAgent(
1055
- config: OrchestratorConfig,
1056
- idleAgents: PaneInfo[],
1057
- task: Task,
1058
- excludeIds: Set<string>,
1059
- ): PaneInfo | null {
1060
- const candidates = idleAgents.filter((p) => !excludeIds.has(p.id));
1061
- if (candidates.length === 0) return null;
1062
-
1063
- // Pre-assigned agent preference: if the task has an assignee (set at creation time),
1064
- // prefer that agent if it's idle. This treats assignee as a preference hint.
1065
- if (task.assignee) {
1066
- const preferred = candidates.find((p) => agentIdentifier(p) === task.assignee);
1067
- if (preferred) return preferred;
1068
- // Preferred agent not idle — fall through to normal matching
1069
- }
1070
-
1071
- // No specialty required — any idle agent works
1072
- if (!task.specialty) return candidates[0] ?? null;
1073
-
1074
- // Prefer agents whose specialties match the task
1075
- const specialist = candidates.find((p) => {
1076
- const specs = getPaneSpecialties(config, p);
1077
- return specs.length > 0 && matchesSpecialty(task.specialty, specs);
1078
- });
1079
- if (specialist) return specialist;
1080
-
1081
- // Fall back to generalist agents (no specialties defined)
1082
- const generalist = candidates.find((p) => {
1083
- const specs = getPaneSpecialties(config, p);
1084
- return specs.length === 0;
1085
- });
1086
- return generalist ?? null;
1087
- }
1088
-
1089
- // --- Goal-level dispatch (planner agents) ---
1090
-
1091
- export function getPaneSpecialties(config: OrchestratorConfig, pane: PaneInfo): string[] {
1092
- const key = pane.name ?? pane.title;
1093
- return config.paneSpecialties.get(key) ?? [];
1094
- }
1095
-
1096
- function isPlannerPane(config: OrchestratorConfig, pane: PaneInfo): boolean {
1097
- const key = pane.name ?? pane.title;
1098
- if (config.paneSpecialties.has(key)) return true;
1099
- if (pane.role === "lead") return false;
1100
- return isAgentPane(pane);
1101
- }
1102
-
1103
- export function buildGoalPrompt(dir: string, goal: Goal, planner: PaneInfo): string {
1104
- const mission = loadMission(dir);
1105
- const name = agentIdentifier(planner);
1106
- const specialtyLabel = goal.specialty ?? "general";
1107
-
1108
- let prompt = `You are ${name}, a ${specialtyLabel} planner.\n\n`;
1109
-
1110
- if (mission) {
1111
- prompt += `## Mission: ${mission.title}\n`;
1112
- if (mission.description) prompt += `${mission.description}\n`;
1113
- prompt += "\n";
1114
-
1115
- // Milestone context
1116
- if (goal.milestone) {
1117
- const milestone = mission.milestones.find((m) => m.id === goal.milestone);
1118
- if (milestone) {
1119
- prompt += `## Milestone: ${milestone.title} (${milestone.id})\n`;
1120
- if (milestone.description) prompt += `${milestone.description}\n`;
1121
- prompt += "\n";
1122
- }
1123
- }
1124
- }
1125
-
1126
- // AGENTS.md — project boundaries
1127
- const agentsMdPath = join(dir, "AGENTS.md");
1128
- const agentsContent = loadLibraryExcerpt(agentsMdPath, 1000);
1129
- if (agentsContent) {
1130
- prompt += `## Agent Guidelines\n${agentsContent}\n\n`;
1131
- }
1132
-
1133
- prompt += `Your Goal: ${goal.title}\n`;
1134
- if (goal.acceptance) prompt += `Acceptance Criteria: ${goal.acceptance}\n`;
1135
- prompt += `Priority: P${goal.priority}\n\n`;
1136
-
1137
- prompt += `Instructions:\n`;
1138
- prompt += `1. Read plans/MISSION.md for architectural context (if it exists)\n`;
1139
- prompt += `2. Create your plan file: plans/goal-${goal.id}-${slugify(goal.title, 30)}.md\n`;
1140
- prompt += ` Include: analysis, approach, task breakdown, risks\n`;
1141
- prompt += `3. Create tasks for your goal:\n`;
1142
- prompt += ` tmux-ide task create "task title" --goal ${goal.id} --priority N\n`;
1143
- prompt += `4. Execute tasks using Claude Code subagents for parallel work\n`;
1144
- prompt += `5. Update your plan file as tasks complete\n`;
1145
- prompt += `6. Review all completed work for quality\n`;
1146
- prompt += `7. When done: tmux-ide goal done ${goal.id} --proof "summary of what was accomplished"\n\n`;
1147
- prompt += `You own this goal end-to-end. Plan it, execute it, deliver it.\n`;
1148
-
1149
- return prompt.trim();
1150
- }
1151
-
1152
- export function dispatchGoals(
1153
- config: OrchestratorConfig,
1154
- state: OrchestratorState,
1155
- goals: Goal[],
1156
- tasks: Task[],
1157
- panes: PaneInfo[],
1158
- ): void {
1159
- // Find idle planner panes (not the master pane, not already assigned)
1160
- const idlePlanners = panes.filter((p) => {
1161
- if (p.index === 0) return false;
1162
- if (p.role === "lead") return false;
1163
- if (!p.role && config.masterPane && normalizePaneTitle(p.title) === config.masterPane)
1164
- return false;
1165
- if (!isPlannerPane(config, p)) return false;
1166
- if (!isIdleForDispatch(p)) return false;
1167
- const name = agentIdentifier(p);
1168
- const hasActiveGoal = goals.some((g) => g.assignee === name && g.status === "in-progress");
1169
- if (hasActiveGoal) return false;
1170
- return true;
1171
- });
1172
-
1173
- // Find unassigned goals sorted by priority
1174
- const todoGoals = goals
1175
- .filter((g) => g.status === "todo" && !g.assignee)
1176
- .sort((a, b) => a.priority - b.priority);
1177
-
1178
- for (const planner of idlePlanners) {
1179
- const plannerSpecs = getPaneSpecialties(config, planner);
1180
-
1181
- // Find best matching goal
1182
- const goal = todoGoals.find((g) => matchesSpecialty(g.specialty, plannerSpecs));
1183
- if (!goal) continue;
1184
-
1185
- // Remove from candidates
1186
- todoGoals.splice(todoGoals.indexOf(goal), 1);
1187
-
1188
- // Assign goal to planner
1189
- goal.assignee = agentIdentifier(planner);
1190
- goal.status = "in-progress";
1191
- goal.updated = new Date().toISOString();
1192
- saveGoal(config.dir, goal);
1193
-
1194
- // Write goal prompt to file and send short command
1195
- const prompt = buildGoalPrompt(config.dir, goal, planner);
1196
- const goalDispatchDir = join(config.dir, ".tasks", "dispatch");
1197
- if (!existsSync(goalDispatchDir)) mkdirSync(goalDispatchDir, { recursive: true });
1198
- const goalDispatchFile = join(goalDispatchDir, `goal-${goal.id}.md`);
1199
- writeFileSync(goalDispatchFile, prompt);
1200
- sendCommand(
1201
- config.session,
1202
- planner.id,
1203
- `Read and execute the goal in .tasks/dispatch/goal-${goal.id}.md`,
1204
- );
1205
-
1206
- // Log event
1207
- appendEvent(config.dir, {
1208
- timestamp: new Date().toISOString(),
1209
- type: "dispatch",
1210
- taskId: goal.id,
1211
- agent: planner.title,
1212
- message: `Dispatched goal "${goal.title}" to ${planner.title}`,
1213
- });
1214
-
1215
- state.lastActivity.set(planner.id, Date.now());
1216
- }
1217
- }
1218
-
1219
- export function detectStalls(
1220
- config: OrchestratorConfig,
1221
- state: OrchestratorState,
1222
- tasks: Task[],
1223
- panes: PaneInfo[],
1224
- researchState?: ResearchState,
1225
- ): void {
1226
- const now = Date.now();
1227
- for (const task of tasks.filter((t) => t.status === "in-progress" && t.assignee)) {
1228
- if (
1229
- task.tags.includes("research") &&
1230
- (!researchState?.activeResearchTaskId || researchState.activeResearchTaskId !== task.id)
1231
- ) {
1232
- continue;
1233
- }
1234
-
1235
- const agentPane = panes.find((p) => agentIdentifier(p) === task.assignee);
1236
- if (!agentPane) continue;
1237
-
1238
- const lastSeen = state.lastActivity.get(agentPane.id) ?? now;
1239
- const elapsed = now - lastSeen;
1240
-
1241
- if (elapsed > config.stallTimeout) {
1242
- sendCommand(
1243
- config.session,
1244
- agentPane.id,
1245
- `You've been working on "${task.title}" for ${Math.floor(elapsed / 60000)} minutes. ` +
1246
- `If done, run: tmux-ide task done ${task.id} --proof "describe what you did". ` +
1247
- `If stuck, run: tmux-ide task update ${task.id} --status review`,
1248
- );
1249
- appendEvent(config.dir, {
1250
- timestamp: new Date().toISOString(),
1251
- type: "stall",
1252
- taskId: task.id,
1253
- agent: task.assignee!,
1254
- message: `Stall detected: "${task.title}" (${Math.floor(elapsed / 60000)}m)`,
1255
- });
1256
- state.lastActivity.set(agentPane.id, now);
1257
- }
1258
- }
1259
- }
1260
-
1261
- export function detectCompletions(
1262
- config: OrchestratorConfig,
1263
- state: OrchestratorState,
1264
- tasks: Task[],
1265
- panes: PaneInfo[],
1266
- researchState?: ResearchState,
1267
- ): void {
1268
- for (const task of tasks) {
1269
- const prev = state.previousTasks.get(task.id);
1270
-
1271
- // Task was explicitly marked as review while in-progress — treat as failure, schedule retry
1272
- if (prev === "in-progress" && task.status === "review" && task.lastError) {
1273
- state.taskClaimTimes.delete(task.id);
1274
- const retried = scheduleRetry(config.dir, state, task, task.lastError);
1275
-
1276
- // Notify master if retries exhausted
1277
- if (!retried && config.masterPane) {
1278
- const masterPaneInfo =
1279
- panes.find((p) => p.role === "lead") ?? panes.find((p) => p.title === config.masterPane);
1280
- if (masterPaneInfo) {
1281
- const retryDispDir = join(config.dir, ".tasks", "dispatch");
1282
- if (!existsSync(retryDispDir)) mkdirSync(retryDispDir, { recursive: true });
1283
- const msgFile = join(retryDispDir, `retry-exhausted-${task.id}.md`);
1284
- const msg = `# Task Failed: ${task.title}\n\nFailed after ${task.maxRetries ?? 5} retries.\nLast error: ${task.lastError}\n\nManual intervention required.`;
1285
- writeFileSync(msgFile, msg);
1286
- // Keep under 200 chars to avoid paste preview in Claude Code TUI
1287
- sendCommand(
1288
- config.session,
1289
- masterPaneInfo.id,
1290
- `Task ${task.id} failed after retries. Run: tmux-ide dispatch retry-exhausted-${task.id}`,
1291
- );
1292
- }
1293
- }
1294
- }
1295
-
1296
- if (prev === "in-progress" && task.status === "done") {
1297
- // Record elapsed time for cost tracking
1298
- const claimTime = state.taskClaimTimes.get(task.id);
1299
- const elapsedMs = claimTime ? Date.now() - claimTime : undefined;
1300
- if (claimTime && task.assignee) {
1301
- recordTaskTime(config.dir, task.assignee, task.id, elapsedMs!);
1302
- }
1303
- state.taskClaimTimes.delete(task.id);
1304
-
1305
- // Clear claim lock
1306
- state.claimedTasks.delete(task.id);
1307
-
1308
- appendEvent(config.dir, {
1309
- timestamp: new Date().toISOString(),
1310
- type: "completion",
1311
- taskId: task.id,
1312
- agent: task.assignee ?? undefined,
1313
- durationMs: elapsedMs,
1314
- message: `Completed "${task.title}" by ${task.assignee ?? "unknown"}${task.salientSummary ? ` — ${task.salientSummary}` : ""}`,
1315
- } as OrchestratorEvent & { durationMs?: number });
1316
-
1317
- if (task.tags.includes("research") && researchState) {
1318
- processResearchCompletion(config, researchState, task);
1319
- }
1320
-
1321
- // Append salient summary to knowledge library
1322
- if (task.salientSummary) {
1323
- try {
1324
- const libraryDir = join(config.dir, ".tmux-ide", "library");
1325
- if (!existsSync(libraryDir)) mkdirSync(libraryDir, { recursive: true });
1326
- const learningsPath = join(libraryDir, "learnings.md");
1327
- const entry = `## Task ${task.id}: ${task.title}\n${task.salientSummary}\n---\n\n`;
1328
- appendFileSync(learningsPath, entry);
1329
- } catch (err: unknown) {
1330
- const msg = err instanceof Error ? err.message : String(err);
1331
- console.error(`[orchestrator] Failed to write learnings.md: ${msg}`);
1332
- appendEvent(config.dir, {
1333
- timestamp: new Date().toISOString(),
1334
- type: "error",
1335
- taskId: task.id,
1336
- message: `Failed to append salientSummary to learnings.md: ${msg}`,
1337
- });
1338
- }
1339
- }
1340
-
1341
- // Structured handoff: auto-create follow-up tasks for discovered issues
1342
- if (task.discoveredIssues && task.discoveredIssues.length > 0) {
1343
- for (const issue of task.discoveredIssues) {
1344
- const followUpId = nextTaskId(config.dir);
1345
- const now = new Date().toISOString();
1346
- const followUp: Task = {
1347
- id: followUpId,
1348
- title: issue.length > 80 ? issue.slice(0, 77) + "..." : issue,
1349
- description: `Discovered during task ${task.id} ("${task.title}"):\n\n${issue}`,
1350
- goal: task.goal,
1351
- status: "todo",
1352
- assignee: null,
1353
- priority: 2,
1354
- created: now,
1355
- updated: now,
1356
- tags: ["discovered-issue"],
1357
- proof: null,
1358
- depends_on: [],
1359
- retryCount: 0,
1360
- maxRetries: 5,
1361
- lastError: null,
1362
- nextRetryAt: null,
1363
- milestone: task.milestone,
1364
- specialty: null,
1365
- fulfills: [],
1366
- discoveredIssues: [],
1367
- salientSummary: null,
1368
- };
1369
- saveTask(config.dir, followUp);
1370
-
1371
- appendEvent(config.dir, {
1372
- timestamp: new Date().toISOString(),
1373
- type: "discovered_issue",
1374
- taskId: followUpId,
1375
- sourceTaskId: task.id,
1376
- issue,
1377
- message: `Auto-created follow-up task ${followUpId} from ${task.id}: ${issue}`,
1378
- });
1379
- }
1380
- }
1381
-
1382
- // Run after_run hook (failure is logged but ignored)
1383
- if (config.afterRun) {
1384
- runHook(config.afterRun, config.dir);
1385
- }
1386
-
1387
- if (config.masterPane) {
1388
- const masterPaneInfo =
1389
- panes.find((p) => p.role === "lead") ?? panes.find((p) => p.title === config.masterPane);
1390
- if (masterPaneInfo) {
1391
- const proofStr = task.proof ? JSON.stringify(task.proof, null, 2) : "no proof provided";
1392
- const dispDir = join(config.dir, ".tasks", "dispatch");
1393
- if (!existsSync(dispDir)) mkdirSync(dispDir, { recursive: true });
1394
- const msgFile = join(dispDir, `completed-${task.id}.md`);
1395
- const msg = `# Task Completed: ${task.title}\n\nBy: ${task.assignee}\nProof:\n${proofStr}\n\nReview and approve, or request changes.`;
1396
- writeFileSync(msgFile, msg);
1397
- // Keep under 200 chars to avoid paste preview in Claude Code TUI
1398
- const shortTitle = task.title.length > 60 ? task.title.slice(0, 57) + "..." : task.title;
1399
- sendCommand(
1400
- config.session,
1401
- masterPaneInfo.id,
1402
- `Task done: "${shortTitle}" by ${task.assignee}. Run: tmux-ide dispatch completed-${task.id}`,
1403
- );
1404
- }
1405
- }
1406
- }
1407
- }
1408
- }
1409
-
1410
- /**
1411
- * Schedule a failed task for retry with exponential backoff.
1412
- *
1413
- * Backoff formula: 10s * 2^attempt, capped at 300s (5 minutes).
1414
- * Max 5 retries by default (configurable per task via maxRetries).
1415
- *
1416
- * Returns true if retry was scheduled, false if max retries exceeded.
1417
- */
1418
- export function scheduleRetry(
1419
- dir: string,
1420
- state: OrchestratorState,
1421
- task: Task,
1422
- reason: string,
1423
- ): boolean {
1424
- const maxRetries = task.maxRetries ?? 5;
1425
-
1426
- if (task.retryCount >= maxRetries) {
1427
- // Max retries exceeded — mark for review
1428
- task.status = "review";
1429
- task.lastError = reason;
1430
- task.assignee = null;
1431
- task.updated = new Date().toISOString();
1432
- saveTask(dir, task);
1433
- state.claimedTasks.delete(task.id);
1434
-
1435
- appendEvent(dir, {
1436
- timestamp: new Date().toISOString(),
1437
- type: "error",
1438
- taskId: task.id,
1439
- message: `Max retries (${maxRetries}) exceeded for "${task.title}": ${reason}`,
1440
- });
1441
-
1442
- return false;
1443
- }
1444
-
1445
- // Exponential backoff: 10s * 2^attempt, capped at 300s
1446
- const backoffMs = Math.min(10_000 * Math.pow(2, task.retryCount), 300_000);
1447
- const nextRetryAt = new Date(Date.now() + backoffMs).toISOString();
1448
-
1449
- task.retryCount += 1;
1450
- task.lastError = reason;
1451
- task.nextRetryAt = nextRetryAt;
1452
- task.status = "todo";
1453
- task.assignee = null;
1454
- task.updated = new Date().toISOString();
1455
- saveTask(dir, task);
1456
- state.claimedTasks.delete(task.id);
1457
-
1458
- appendEvent(dir, {
1459
- timestamp: new Date().toISOString(),
1460
- type: "retry",
1461
- taskId: task.id,
1462
- message: `Retry ${task.retryCount}/${maxRetries} scheduled for "${task.title}" in ${backoffMs / 1000}s: ${reason}`,
1463
- });
1464
-
1465
- return true;
1466
- }
1467
-
1468
- export function reconcile(
1469
- config: OrchestratorConfig,
1470
- state: OrchestratorState,
1471
- tasks: Task[],
1472
- panes: PaneInfo[],
1473
- ): void {
1474
- const agentIds = new Set(panes.map((p) => agentIdentifier(p)));
1475
-
1476
- for (const task of tasks.filter((t) => t.status === "in-progress" && t.assignee)) {
1477
- // Check if the assigned agent's pane still exists
1478
- if (!agentIds.has(task.assignee!)) {
1479
- const agent = task.assignee!;
1480
- const reason = `Agent "${agent}" vanished (pane closed or crashed)`;
1481
-
1482
- // Schedule retry with backoff instead of immediate re-queue
1483
- scheduleRetry(config.dir, state, task, reason);
1484
-
1485
- appendEvent(config.dir, {
1486
- timestamp: new Date().toISOString(),
1487
- type: "reconcile",
1488
- taskId: task.id,
1489
- agent,
1490
- message: `Agent "${agent}" vanished, scheduled retry for "${task.title}"`,
1491
- });
1492
- }
1493
- }
1494
- }
1495
-
1496
- // --- Orchestrator state persistence ---
1497
-
1498
- interface PersistedState {
1499
- claimedTasks: string[];
1500
- taskClaimTimes: Record<string, number>;
1501
- lastActivity?: Record<string, number>;
1502
- }
1503
-
1504
- function stateFilePath(dir: string): string {
1505
- return join(dir, ".tasks", "orchestrator-state.json");
1506
- }
1507
-
1508
- export function saveOrchestratorState(dir: string, state: OrchestratorState): void {
1509
- const tasksDir = join(dir, ".tasks");
1510
- if (!existsSync(tasksDir)) mkdirSync(tasksDir, { recursive: true });
1511
- const data: PersistedState = {
1512
- claimedTasks: [...state.claimedTasks],
1513
- taskClaimTimes: Object.fromEntries(state.taskClaimTimes),
1514
- lastActivity: Object.fromEntries(state.lastActivity),
1515
- };
1516
- // Atomic write: write to temp file, then rename
1517
- const filePath = stateFilePath(dir);
1518
- const tmpPath = filePath + ".tmp";
1519
- writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n");
1520
- renameSync(tmpPath, filePath);
1521
- }
1522
-
1523
- export function loadOrchestratorState(dir: string, state: OrchestratorState): void {
1524
- const path = stateFilePath(dir);
1525
- if (!existsSync(path)) return;
1526
- try {
1527
- const data = JSON.parse(readFileSync(path, "utf-8")) as PersistedState;
1528
- if (Array.isArray(data.claimedTasks)) {
1529
- for (const id of data.claimedTasks) state.claimedTasks.add(id);
1530
- }
1531
- if (data.taskClaimTimes && typeof data.taskClaimTimes === "object") {
1532
- for (const [id, time] of Object.entries(data.taskClaimTimes)) {
1533
- state.taskClaimTimes.set(id, time);
1534
- }
1535
- }
1536
- if (data.lastActivity && typeof data.lastActivity === "object") {
1537
- for (const [id, time] of Object.entries(data.lastActivity)) {
1538
- state.lastActivity.set(id, time);
1539
- }
1540
- }
1541
- } catch {
1542
- // Corrupted state file — start fresh
1543
- }
1544
- }
1545
-
1546
- /**
1547
- * Sync claimedTasks with the actual task store on startup.
1548
- * Removes stale claims (task no longer in-progress) and adds missing claims
1549
- * (task is in-progress but not in claimedTasks). Prevents stale claims from
1550
- * blocking dispatch after a crash or manual state reset.
1551
- */
1552
- export function syncClaims(dir: string, state: OrchestratorState): void {
1553
- const tasks = loadTasks(dir);
1554
- const inProgressIds = new Set(tasks.filter((t) => t.status === "in-progress").map((t) => t.id));
1555
- // Remove stale claims
1556
- for (const id of state.claimedTasks) {
1557
- if (!inProgressIds.has(id)) {
1558
- state.claimedTasks.delete(id);
1559
- }
1560
- }
1561
- // Add missing claims
1562
- for (const id of inProgressIds) {
1563
- state.claimedTasks.add(id);
1564
- }
1565
- }
1566
-
1567
- export function gracefulShutdown(config: OrchestratorConfig, state: OrchestratorState): void {
1568
- // Release all in-progress tasks back to todo
1569
- const tasks = loadTasks(config.dir);
1570
- for (const task of tasks) {
1571
- if (task.status === "in-progress" && task.assignee) {
1572
- task.assignee = null;
1573
- task.status = "todo";
1574
- task.updated = new Date().toISOString();
1575
- saveTask(config.dir, task);
1576
- }
1577
- }
1578
-
1579
- // Emit session_end and save metrics
1580
- appendEvent(config.dir, {
1581
- timestamp: new Date().toISOString(),
1582
- type: "session_end",
1583
- message: `Orchestrator shutting down`,
1584
- });
1585
-
1586
- try {
1587
- computeAndSaveMetrics(config.dir);
1588
- } catch {
1589
- // Metrics save failure should not prevent shutdown
1590
- }
1591
-
1592
- // Persist state for resume
1593
- saveOrchestratorState(config.dir, state);
1594
- }
1595
-
1596
- /** Apply hot-reloadable fields from a partial config onto a live config. */
1597
- export function reloadConfig(
1598
- target: OrchestratorConfig,
1599
- patch: Partial<
1600
- Pick<
1601
- OrchestratorConfig,
1602
- | "pollInterval"
1603
- | "stallTimeout"
1604
- | "maxConcurrentAgents"
1605
- | "autoDispatch"
1606
- | "beforeRun"
1607
- | "afterRun"
1608
- | "research"
1609
- >
1610
- >,
1611
- ): void {
1612
- if (patch.pollInterval !== undefined) target.pollInterval = patch.pollInterval;
1613
- if (patch.stallTimeout !== undefined) target.stallTimeout = patch.stallTimeout;
1614
- if (patch.maxConcurrentAgents !== undefined)
1615
- target.maxConcurrentAgents = patch.maxConcurrentAgents;
1616
- if (patch.autoDispatch !== undefined) target.autoDispatch = patch.autoDispatch;
1617
- if (patch.beforeRun !== undefined) target.beforeRun = patch.beforeRun;
1618
- if (patch.afterRun !== undefined) target.afterRun = patch.afterRun;
1619
- if (patch.research !== undefined) target.research = patch.research;
1620
- }
1621
-
1622
- export function createOrchestrator(initialConfig: OrchestratorConfig): () => void {
1623
- // Mutable config — reloadConfig updates fields between ticks
1624
- const config: OrchestratorConfig = { ...initialConfig };
1625
-
1626
- const state: OrchestratorState = {
1627
- lastActivity: new Map(),
1628
- previousTasks: new Map(),
1629
- claimedTasks: new Set(),
1630
- taskClaimTimes: new Map(),
1631
- };
1632
- const researchState = loadResearchState(config.dir);
1633
-
1634
- // Load persisted state from previous run
1635
- loadOrchestratorState(config.dir, state);
1636
-
1637
- // Sync claims with actual task store to clear stale claims after crash
1638
- syncClaims(config.dir, state);
1639
-
1640
- // Initialize previous state
1641
- for (const task of loadTasks(config.dir)) {
1642
- state.previousTasks.set(task.id, task.status);
1643
- }
1644
-
1645
- // Emit session_start
1646
- appendEvent(config.dir, {
1647
- timestamp: new Date().toISOString(),
1648
- type: "session_start",
1649
- message: `Orchestrator started — mode: ${config.dispatchMode}, max agents: ${config.maxConcurrentAgents}`,
1650
- });
1651
-
1652
- let tickCount = 0;
1653
-
1654
- function tick(): void {
1655
- tickCount++;
1656
- let tasks = loadTasks(config.dir);
1657
- const panes = listSessionPanes(config.session);
1658
-
1659
- // 1. Reconcile: detect crashed agents, unassign their tasks
1660
- reconcile(config, state, tasks, panes);
1661
-
1662
- // 2. Auto-dispatch: assign tasks or goals to idle agents
1663
- if (config.autoDispatch) {
1664
- if (config.dispatchMode === "goals") {
1665
- const goals = loadGoals(config.dir);
1666
- dispatchGoals(config, state, goals, tasks, panes);
1667
- } else if (config.dispatchMode === "missions") {
1668
- // Mission lifecycle: planning → active → complete
1669
- const mission = loadMission(config.dir);
1670
- if (mission?.status === "planning" && mission.milestones.length === 0) {
1671
- dispatchPlanning(config, state, panes);
1672
- } else if (mission?.status === "active") {
1673
- dispatch(config, state, tasks, panes);
1674
- }
1675
- } else {
1676
- dispatch(config, state, tasks, panes);
1677
- }
1678
- }
1679
-
1680
- // 3. Detect stalls: nudge agents that haven't produced output
1681
- detectStalls(config, state, tasks, panes, researchState);
1682
-
1683
- // 4. Detect completions: notify master, run hooks, cleanup
1684
- detectCompletions(config, state, tasks, panes, researchState);
1685
- tasks = loadTasks(config.dir);
1686
-
1687
- if (config.dispatchMode === "missions" && config.research?.enabled) {
1688
- const triggers = evaluateTriggers(
1689
- config,
1690
- state,
1691
- researchState,
1692
- tasks,
1693
- readEvents(config.dir),
1694
- );
1695
- if (triggers.length > 0) {
1696
- dispatchResearch(config, state, researchState, tasks, panes, triggers[0]);
1697
- tasks = loadTasks(config.dir);
1698
- }
1699
- }
1700
-
1701
- // 5. Milestone completion + validation flow + mission completion
1702
- if (config.dispatchMode === "missions") {
1703
- checkMilestoneCompletion(config, state, tasks, panes);
1704
- checkValidationResults(config, tasks);
1705
-
1706
- // Check if mission is complete: all milestones done
1707
- const mission = loadMission(config.dir);
1708
- if (mission?.status === "active" && mission.milestones.length > 0) {
1709
- const allMilestonesDone = mission.milestones.every((m) => m.status === "done");
1710
- if (allMilestonesDone) {
1711
- handleMissionComplete(config, mission, panes);
1712
- }
1713
- }
1714
- }
1715
-
1716
- // Agent heartbeat every 6th tick (~30s at default 5s poll)
1717
- if (tickCount % 6 === 0) {
1718
- const agentPanes = panes.filter((p) => {
1719
- if (p.role === "lead") return false;
1720
- return isAgentPane(p);
1721
- });
1722
- if (agentPanes.length > 0) {
1723
- const statuses = agentPanes
1724
- .map((p) => `${agentIdentifier(p)}=${isAgentBusy(p) ? "busy" : "idle"}`)
1725
- .join(", ");
1726
- appendEvent(config.dir, {
1727
- timestamp: new Date().toISOString(),
1728
- type: "agent_heartbeat",
1729
- message: `agents: ${statuses}`,
1730
- });
1731
- }
1732
- }
1733
-
1734
- // Update previous state
1735
- for (const task of tasks) {
1736
- state.previousTasks.set(task.id, task.status);
1737
- }
1738
- }
1739
-
1740
- let interval = setInterval(tick, config.pollInterval);
1741
-
1742
- // Watch ide.yml for config changes — apply new settings without restart
1743
- let watcher: FSWatcher | null = null;
1744
- const configPath = join(config.dir, "ide.yml");
1745
- if (existsSync(configPath)) {
1746
- let debounce: ReturnType<typeof setTimeout> | null = null;
1747
- watcher = watch(configPath, () => {
1748
- if (debounce) clearTimeout(debounce);
1749
- debounce = setTimeout(() => {
1750
- try {
1751
- const raw = readFileSync(configPath, "utf-8");
1752
- const parsed = yaml.load(raw) as Record<string, unknown>;
1753
- const orch = (parsed.orchestrator ?? {}) as Record<string, unknown>;
1754
-
1755
- const oldPollInterval = config.pollInterval;
1756
-
1757
- reloadConfig(config, {
1758
- pollInterval: (orch.poll_interval as number | undefined) ?? config.pollInterval,
1759
- stallTimeout: (orch.stall_timeout as number | undefined) ?? config.stallTimeout,
1760
- maxConcurrentAgents:
1761
- (orch.max_concurrent_agents as number | undefined) ?? config.maxConcurrentAgents,
1762
- autoDispatch: (orch.auto_dispatch as boolean | undefined) ?? config.autoDispatch,
1763
- beforeRun: (orch.before_run as string | undefined) ?? config.beforeRun,
1764
- afterRun: (orch.after_run as string | undefined) ?? config.afterRun,
1765
- research: (orch.research as ResearchConfigShape | undefined) ?? config.research,
1766
- });
1767
-
1768
- // Restart interval if pollInterval changed
1769
- if (config.pollInterval !== oldPollInterval) {
1770
- clearInterval(interval);
1771
- interval = setInterval(tick, config.pollInterval);
1772
- }
1773
- } catch {
1774
- // Config file might be mid-write or invalid — ignore
1775
- }
1776
- }, 300);
1777
- });
1778
- }
1779
-
1780
- // Graceful shutdown on SIGTERM/SIGINT
1781
- function onSignal() {
1782
- clearInterval(interval);
1783
- if (watcher) watcher.close();
1784
- gracefulShutdown(config, state);
1785
- saveResearchState(config.dir, researchState);
1786
- process.exit(0);
1787
- }
1788
-
1789
- process.on("SIGTERM", onSignal);
1790
- process.on("SIGINT", onSignal);
1791
-
1792
- return () => {
1793
- clearInterval(interval);
1794
- if (watcher) watcher.close();
1795
- saveResearchState(config.dir, researchState);
1796
- process.removeListener("SIGTERM", onSignal);
1797
- process.removeListener("SIGINT", onSignal);
1798
- };
1799
- }