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.
- package/README.md +115 -101
- package/bin/cli.js +13678 -4
- package/bin/cli.ts +1143 -260
- package/package.json +27 -17
- package/packages/daemon/dist/__tests__/support.d.ts +7 -0
- package/packages/daemon/dist/__tests__/support.js +33 -0
- package/packages/daemon/dist/agent-explain.d.ts +45 -0
- package/packages/daemon/dist/agent-explain.js +210 -0
- package/packages/daemon/dist/attach.d.ts +3 -0
- package/packages/daemon/dist/attach.js +14 -0
- package/packages/daemon/dist/bin.d.ts +2 -0
- package/packages/daemon/dist/bin.js +3 -0
- package/packages/daemon/dist/canonical.d.ts +2 -0
- package/packages/daemon/dist/canonical.js +1 -0
- package/packages/daemon/dist/cli.d.ts +1 -0
- package/packages/daemon/dist/cli.js +425 -0
- package/packages/daemon/dist/command-center/actions/contract.d.ts +1 -0
- package/packages/daemon/dist/command-center/actions/contract.js +2 -0
- package/packages/daemon/dist/command-center/actions/dispatcher.d.ts +27 -0
- package/packages/daemon/dist/command-center/actions/dispatcher.js +109 -0
- package/packages/daemon/dist/command-center/actions/errors.d.ts +38 -0
- package/packages/daemon/dist/command-center/actions/errors.js +56 -0
- package/packages/daemon/dist/command-center/actions/handlers/_project-context.d.ts +12 -0
- package/packages/daemon/dist/command-center/actions/handlers/_project-context.js +12 -0
- package/packages/daemon/dist/command-center/actions/handlers/_resolve-project.d.ts +42 -0
- package/packages/daemon/dist/command-center/actions/handlers/_resolve-project.js +48 -0
- package/packages/daemon/dist/command-center/actions/handlers/app-set-remote-access.d.ts +23 -0
- package/packages/daemon/dist/command-center/actions/handlers/app-set-remote-access.js +67 -0
- package/packages/daemon/dist/command-center/actions/handlers/config-actions.d.ts +12 -0
- package/packages/daemon/dist/command-center/actions/handlers/config-actions.js +69 -0
- package/packages/daemon/dist/command-center/actions/handlers/daemon-shutdown.d.ts +6 -0
- package/packages/daemon/dist/command-center/actions/handlers/daemon-shutdown.js +24 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-activate.d.ts +7 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-activate.js +19 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-launch.d.ts +18 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-launch.js +58 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-open-terminal.d.ts +32 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-open-terminal.js +69 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-restart.d.ts +17 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-restart.js +27 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-stop.d.ts +24 -0
- package/packages/daemon/dist/command-center/actions/handlers/project-stop.js +38 -0
- package/packages/daemon/dist/command-center/actions/handlers/terminal-respawn.d.ts +24 -0
- package/packages/daemon/dist/command-center/actions/handlers/terminal-respawn.js +88 -0
- package/packages/daemon/dist/command-center/actions/handlers/terminal-stop.d.ts +14 -0
- package/packages/daemon/dist/command-center/actions/handlers/terminal-stop.js +22 -0
- package/packages/daemon/dist/command-center/actions/registry.d.ts +35 -0
- package/packages/daemon/dist/command-center/actions/registry.js +109 -0
- package/packages/daemon/dist/command-center/discovery.d.ts +23 -0
- package/packages/daemon/dist/command-center/discovery.js +67 -0
- package/packages/daemon/dist/command-center/index.d.ts +10 -0
- package/packages/daemon/dist/command-center/index.js +21 -0
- package/packages/daemon/dist/command-center/schemas.d.ts +76 -0
- package/packages/daemon/dist/command-center/schemas.js +68 -0
- package/packages/daemon/dist/command-center/server.d.ts +26 -0
- package/packages/daemon/dist/command-center/server.js +1103 -0
- package/packages/daemon/dist/command-center/ws-events.d.ts +59 -0
- package/packages/daemon/dist/command-center/ws-events.js +277 -0
- package/packages/daemon/dist/config.d.ts +19 -0
- package/packages/daemon/dist/config.js +494 -0
- package/packages/daemon/dist/detect.d.ts +15 -0
- package/packages/daemon/dist/detect.js +228 -0
- package/packages/daemon/dist/doctor.d.ts +21 -0
- package/packages/daemon/dist/doctor.js +154 -0
- package/packages/daemon/dist/embed.d.ts +1 -0
- package/packages/daemon/dist/embed.js +1 -0
- package/packages/daemon/dist/index.d.ts +3 -0
- package/packages/daemon/dist/index.js +2 -0
- package/packages/daemon/dist/init.d.ts +4 -0
- package/packages/daemon/dist/init.js +174 -0
- package/packages/daemon/dist/inspect.d.ts +54 -0
- package/packages/daemon/dist/inspect.js +118 -0
- package/packages/daemon/dist/launch.d.ts +23 -0
- package/packages/daemon/dist/launch.js +250 -0
- package/packages/daemon/dist/lib/active-projects.d.ts +12 -0
- package/packages/daemon/dist/lib/active-projects.js +35 -0
- package/packages/daemon/dist/lib/agent-discovery.d.ts +43 -0
- package/packages/daemon/dist/lib/agent-discovery.js +85 -0
- package/packages/daemon/dist/lib/app-config.d.ts +147 -0
- package/packages/daemon/dist/lib/app-config.js +191 -0
- package/packages/daemon/dist/lib/app-settings.d.ts +9 -0
- package/packages/daemon/dist/lib/app-settings.js +44 -0
- package/packages/daemon/dist/lib/auth/auth-service.d.ts +33 -0
- package/packages/daemon/dist/lib/auth/auth-service.js +172 -0
- package/packages/daemon/dist/lib/auth/middleware.d.ts +16 -0
- package/packages/daemon/dist/lib/auth/middleware.js +43 -0
- package/packages/daemon/dist/lib/auth/types.d.ts +1 -0
- package/packages/daemon/dist/lib/auth/types.js +2 -0
- package/packages/daemon/dist/lib/auth-token.d.ts +1 -0
- package/packages/daemon/dist/lib/auth-token.js +4 -0
- package/packages/daemon/dist/lib/authorship.d.ts +62 -0
- package/packages/daemon/dist/lib/authorship.js +209 -0
- package/packages/daemon/dist/lib/canonical-daemon.d.ts +21 -0
- package/packages/daemon/dist/lib/canonical-daemon.js +111 -0
- package/packages/daemon/dist/lib/cli-action-bridge.d.ts +27 -0
- package/packages/daemon/dist/lib/cli-action-bridge.js +161 -0
- package/packages/daemon/dist/lib/daemon-embed.d.ts +31 -0
- package/packages/daemon/dist/lib/daemon-embed.js +643 -0
- package/packages/daemon/dist/lib/daemon-watchdog.d.ts +16 -0
- package/packages/daemon/dist/lib/daemon-watchdog.js +98 -0
- package/packages/daemon/dist/lib/daemon.d.ts +8 -0
- package/packages/daemon/dist/lib/daemon.js +79 -0
- package/packages/daemon/dist/lib/dot-path.d.ts +2 -0
- package/packages/daemon/dist/lib/dot-path.js +17 -0
- package/packages/daemon/dist/lib/errors.d.ts +37 -0
- package/packages/daemon/dist/lib/errors.js +50 -0
- package/packages/daemon/dist/lib/filesystem-browser.d.ts +85 -0
- package/packages/daemon/dist/lib/filesystem-browser.js +257 -0
- package/packages/daemon/dist/lib/launch-plan.d.ts +6 -0
- package/packages/daemon/dist/lib/launch-plan.js +76 -0
- package/packages/daemon/dist/lib/log.d.ts +40 -0
- package/packages/daemon/dist/lib/log.js +86 -0
- package/packages/daemon/dist/lib/output.d.ts +9 -0
- package/packages/daemon/dist/lib/output.js +65 -0
- package/packages/daemon/dist/lib/project-init-runner.d.ts +46 -0
- package/packages/daemon/dist/lib/project-init-runner.js +117 -0
- package/packages/daemon/dist/lib/project-inspect.d.ts +28 -0
- package/packages/daemon/dist/lib/project-inspect.js +67 -0
- package/packages/daemon/dist/lib/project-onboard.d.ts +58 -0
- package/packages/daemon/dist/lib/project-onboard.js +108 -0
- package/packages/daemon/dist/lib/project-probe.d.ts +35 -0
- package/packages/daemon/dist/lib/project-probe.js +61 -0
- package/packages/daemon/dist/lib/project-registry.d.ts +76 -0
- package/packages/daemon/dist/lib/project-registry.js +230 -0
- package/packages/daemon/dist/lib/session-monitor.d.ts +15 -0
- package/packages/daemon/dist/lib/session-monitor.js +103 -0
- package/packages/daemon/dist/lib/session-options.d.ts +13 -0
- package/packages/daemon/dist/lib/session-options.js +85 -0
- package/packages/daemon/dist/lib/shell.d.ts +5 -0
- package/{src/lib/shell.ts → packages/daemon/dist/lib/shell.js} +3 -3
- package/packages/daemon/dist/lib/sizes.d.ts +16 -0
- package/packages/daemon/dist/lib/sizes.js +36 -0
- package/packages/daemon/dist/lib/skill-sync.d.ts +60 -0
- package/packages/daemon/dist/lib/skill-sync.js +128 -0
- package/packages/daemon/dist/lib/slugify.d.ts +4 -0
- package/packages/daemon/dist/lib/slugify.js +10 -0
- package/packages/daemon/dist/lib/terminals-store.d.ts +27 -0
- package/packages/daemon/dist/lib/terminals-store.js +111 -0
- package/packages/daemon/dist/lib/update-check.d.ts +118 -0
- package/packages/daemon/dist/lib/update-check.js +261 -0
- package/packages/daemon/dist/lib/update.d.ts +65 -0
- package/packages/daemon/dist/lib/update.js +141 -0
- package/packages/daemon/dist/lib/workspace-registry.d.ts +84 -0
- package/packages/daemon/dist/lib/workspace-registry.js +176 -0
- package/packages/daemon/dist/lib/worktree.d.ts +76 -0
- package/packages/daemon/dist/lib/worktree.js +207 -0
- package/packages/daemon/dist/lib/yaml-io.d.ts +10 -0
- package/packages/daemon/dist/lib/yaml-io.js +24 -0
- package/packages/daemon/dist/ls.d.ts +3 -0
- package/packages/daemon/dist/ls.js +35 -0
- package/packages/daemon/dist/restart.d.ts +4 -0
- package/packages/daemon/dist/restart.js +16 -0
- package/packages/daemon/dist/restore.d.ts +86 -0
- package/packages/daemon/dist/restore.js +406 -0
- package/packages/daemon/dist/schemas/domain.d.ts +1 -0
- package/packages/daemon/dist/schemas/domain.js +2 -0
- package/packages/daemon/dist/schemas/filesystem.d.ts +29 -0
- package/packages/daemon/dist/schemas/filesystem.js +29 -0
- package/packages/daemon/dist/schemas/ide-config.d.ts +1 -0
- package/packages/daemon/dist/schemas/ide-config.js +2 -0
- package/packages/daemon/dist/schemas/index.d.ts +12 -0
- package/packages/daemon/dist/schemas/index.js +6 -0
- package/packages/daemon/dist/schemas/inspect.d.ts +53 -0
- package/packages/daemon/dist/schemas/inspect.js +57 -0
- package/packages/daemon/dist/schemas/registry.d.ts +35 -0
- package/packages/daemon/dist/schemas/registry.js +43 -0
- package/packages/daemon/dist/schemas/ws-events.d.ts +72 -0
- package/packages/daemon/dist/schemas/ws-events.js +110 -0
- package/packages/daemon/dist/send.d.ts +27 -0
- package/packages/daemon/dist/send.js +141 -0
- package/packages/daemon/dist/server/index.d.ts +10 -0
- package/packages/daemon/dist/server/index.js +55 -0
- package/packages/daemon/dist/server/pty-bridge.d.ts +174 -0
- package/packages/daemon/dist/server/pty-bridge.js +422 -0
- package/packages/daemon/dist/server/standalone.d.ts +1 -0
- package/packages/daemon/dist/server/standalone.js +14 -0
- package/packages/daemon/dist/server/ws-route.d.ts +74 -0
- package/packages/daemon/dist/server/ws-route.js +379 -0
- package/packages/daemon/dist/status.d.ts +3 -0
- package/packages/daemon/dist/status.js +46 -0
- package/packages/daemon/dist/stop.d.ts +3 -0
- package/packages/daemon/dist/stop.js +21 -0
- package/packages/daemon/dist/terminal/NodePtyAdapter.d.ts +44 -0
- package/packages/daemon/dist/terminal/NodePtyAdapter.js +253 -0
- package/packages/daemon/dist/terminal/PtyAdapter.d.ts +124 -0
- package/packages/daemon/dist/terminal/PtyAdapter.js +43 -0
- package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.d.ts +77 -0
- package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +155 -0
- package/packages/daemon/dist/types.d.ts +2 -0
- package/packages/daemon/dist/types.js +1 -0
- package/packages/daemon/dist/validate.d.ts +4 -0
- package/packages/daemon/dist/validate.js +184 -0
- package/packages/daemon/dist/widgets/changes/index.d.ts +1 -0
- package/packages/daemon/dist/widgets/changes/index.jsx +575 -0
- package/packages/daemon/dist/widgets/config/index.d.ts +1 -0
- package/packages/daemon/dist/widgets/config/index.jsx +395 -0
- package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +10 -0
- package/packages/daemon/dist/widgets/explorer/breadcrumbs.jsx +51 -0
- package/packages/daemon/dist/widgets/explorer/footer.d.ts +6 -0
- package/packages/daemon/dist/widgets/explorer/footer.jsx +11 -0
- package/packages/daemon/dist/widgets/explorer/index.d.ts +1 -0
- package/packages/daemon/dist/widgets/explorer/index.jsx +393 -0
- package/packages/daemon/dist/widgets/explorer/tree-model.d.ts +13 -0
- package/packages/daemon/dist/widgets/explorer/tree-model.js +69 -0
- package/packages/daemon/dist/widgets/explorer/tree.d.ts +13 -0
- package/packages/daemon/dist/widgets/explorer/tree.jsx +108 -0
- package/packages/daemon/dist/widgets/lib/config-model.d.ts +22 -0
- package/packages/daemon/dist/widgets/lib/config-model.js +99 -0
- package/packages/daemon/dist/widgets/lib/files.d.ts +11 -0
- package/packages/daemon/dist/widgets/lib/files.js +76 -0
- package/packages/daemon/dist/widgets/lib/git.d.ts +11 -0
- package/packages/daemon/dist/widgets/lib/git.js +70 -0
- package/packages/daemon/dist/widgets/lib/grammar.d.ts +79 -0
- package/packages/daemon/dist/widgets/lib/grammar.js +95 -0
- package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +19 -0
- package/packages/daemon/dist/widgets/lib/help-overlay.jsx +69 -0
- package/packages/daemon/dist/widgets/lib/pane-comms.d.ts +28 -0
- package/packages/daemon/dist/widgets/lib/pane-comms.js +189 -0
- package/packages/daemon/dist/widgets/lib/theme.d.ts +44 -0
- package/packages/daemon/dist/widgets/lib/theme.js +153 -0
- package/packages/daemon/dist/widgets/lib/watcher.d.ts +9 -0
- package/packages/daemon/dist/widgets/lib/watcher.js +92 -0
- package/packages/daemon/dist/widgets/preview/index.d.ts +1 -0
- package/packages/daemon/dist/widgets/preview/index.jsx +371 -0
- package/packages/daemon/dist/widgets/resolve.d.ts +20 -0
- package/packages/daemon/dist/widgets/resolve.js +100 -0
- package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +9 -0
- package/packages/daemon/dist/widgets/setup/agent-naming.jsx +81 -0
- package/packages/daemon/dist/widgets/setup/config-tree.d.ts +13 -0
- package/packages/daemon/dist/widgets/setup/config-tree.jsx +203 -0
- package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +14 -0
- package/{src/widgets/setup/detect-panel.tsx → packages/daemon/dist/widgets/setup/detect-panel.jsx} +16 -40
- package/packages/daemon/dist/widgets/setup/field-editor.d.ts +12 -0
- package/packages/daemon/dist/widgets/setup/field-editor.jsx +163 -0
- package/packages/daemon/dist/widgets/setup/footer.d.ts +17 -0
- package/packages/daemon/dist/widgets/setup/footer.jsx +64 -0
- package/packages/daemon/dist/widgets/setup/index.d.ts +1 -0
- package/packages/daemon/dist/widgets/setup/index.jsx +261 -0
- package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +9 -0
- package/packages/daemon/dist/widgets/setup/layout-picker.jsx +69 -0
- package/packages/daemon/dist/widgets/setup/setup-model.d.ts +15 -0
- package/packages/daemon/dist/widgets/setup/setup-model.js +157 -0
- package/packages/daemon/dist/widgets/sidebar/index.d.ts +1 -0
- package/packages/daemon/dist/widgets/sidebar/index.jsx +462 -0
- package/scripts/build-cli.mjs +101 -0
- package/scripts/build-tui.mjs +58 -0
- package/scripts/capture-codex-traffic.mjs +195 -0
- package/scripts/check-src-additions.sh +74 -0
- package/scripts/coverage-summary.mjs +68 -0
- package/scripts/install-git-hooks.js +53 -0
- package/scripts/merge-update-manifests.mjs +58 -0
- package/scripts/merge-update-manifests.test.mjs +82 -0
- package/scripts/pack-check-run.mjs +83 -0
- package/scripts/postinstall.js +28 -6
- package/scripts/prepublish-check.mjs +44 -0
- package/scripts/stress-test.ts +141 -0
- package/scripts/verify-endpoints.sh +90 -0
- package/skill/SKILL.md +203 -236
- package/dashboard/out/404/index.html +0 -1
- package/dashboard/out/404.html +0 -1
- package/dashboard/out/__next.__PAGE__.txt +0 -9
- package/dashboard/out/__next._full.txt +0 -21
- package/dashboard/out/__next._head.txt +0 -5
- package/dashboard/out/__next._index.txt +0 -7
- package/dashboard/out/__next._tree.txt +0 -4
- package/dashboard/out/_next/static/Tl1KoIH7mHCyjo5KtXqQ0/_buildManifest.js +0 -11
- package/dashboard/out/_next/static/Tl1KoIH7mHCyjo5KtXqQ0/_clientMiddlewareManifest.json +0 -1
- package/dashboard/out/_next/static/Tl1KoIH7mHCyjo5KtXqQ0/_ssgManifest.js +0 -1
- package/dashboard/out/_next/static/chunks/048e2449530877e2.js +0 -1
- package/dashboard/out/_next/static/chunks/053c57bda6abb959.js +0 -1
- package/dashboard/out/_next/static/chunks/056760c6c2dd9172.js +0 -1
- package/dashboard/out/_next/static/chunks/06ab47357b6d3e35.js +0 -1
- package/dashboard/out/_next/static/chunks/0921cbe5dfd6738e.js +0 -1
- package/dashboard/out/_next/static/chunks/0b2599bf468cf5ea.js +0 -1
- package/dashboard/out/_next/static/chunks/0c012eac08ef2c4f.js +0 -1
- package/dashboard/out/_next/static/chunks/0c29e87fcdcffac3.js +0 -1
- package/dashboard/out/_next/static/chunks/0e1e98854430c693.js +0 -1
- package/dashboard/out/_next/static/chunks/0e24498976a88976.js +0 -1
- package/dashboard/out/_next/static/chunks/0e5bf11ff0b9ab90.js +0 -1
- package/dashboard/out/_next/static/chunks/0eae0a45ab8416c8.js +0 -1
- package/dashboard/out/_next/static/chunks/104797400d9d38ad.js +0 -1
- package/dashboard/out/_next/static/chunks/10cfcfce475f9d2d.js +0 -1
- package/dashboard/out/_next/static/chunks/11bac38e7cfb4ecc.js +0 -1
- package/dashboard/out/_next/static/chunks/120c5f6501ec3529.js +0 -1
- package/dashboard/out/_next/static/chunks/121fceb2b1dcd79f.js +0 -1
- package/dashboard/out/_next/static/chunks/13726b63e1be6b98.js +0 -1
- package/dashboard/out/_next/static/chunks/140c6ce631514660.js +0 -1
- package/dashboard/out/_next/static/chunks/1435ee3a51174c97.js +0 -1
- package/dashboard/out/_next/static/chunks/14dc927d2e2cca91.js +0 -1
- package/dashboard/out/_next/static/chunks/179c3031b292d98e.js +0 -1
- package/dashboard/out/_next/static/chunks/181e88957c47e5c1.js +0 -1
- package/dashboard/out/_next/static/chunks/18f70ba23873c432.js +0 -1
- package/dashboard/out/_next/static/chunks/1b58303b027fd135.js +0 -1
- package/dashboard/out/_next/static/chunks/1b5f49a358dcc71e.js +0 -1
- package/dashboard/out/_next/static/chunks/1bba44b81585099a.js +0 -1
- package/dashboard/out/_next/static/chunks/1bea8dee19a3e9ae.js +0 -1
- package/dashboard/out/_next/static/chunks/1cfad21646fc250e.js +0 -1
- package/dashboard/out/_next/static/chunks/1d053dc9e1e428ef.js +0 -1
- package/dashboard/out/_next/static/chunks/1e7d7639c6f0ee5e.js +0 -1
- package/dashboard/out/_next/static/chunks/1f42504ca81463f1.js +0 -1
- package/dashboard/out/_next/static/chunks/20c92047399305b4.js +0 -1
- package/dashboard/out/_next/static/chunks/214560e9514a9a4d.js +0 -1
- package/dashboard/out/_next/static/chunks/21d4ef106914e997.js +0 -1
- package/dashboard/out/_next/static/chunks/2586e83fc3dc2588.js +0 -1
- package/dashboard/out/_next/static/chunks/267136a09e94f5c2.js +0 -1
- package/dashboard/out/_next/static/chunks/271c50b329917c10.js +0 -1
- package/dashboard/out/_next/static/chunks/27fccecefffd88b5.js +0 -1
- package/dashboard/out/_next/static/chunks/28f058ed331315b6.js +0 -1
- package/dashboard/out/_next/static/chunks/28f704f2cc5713eb.js +0 -1
- package/dashboard/out/_next/static/chunks/290f67a6cd150a98.js +0 -1
- package/dashboard/out/_next/static/chunks/2ad12f4dcc30d30a.js +0 -1
- package/dashboard/out/_next/static/chunks/2b650b172078ab92.js +0 -1
- package/dashboard/out/_next/static/chunks/2bd91076019a6453.js +0 -1
- package/dashboard/out/_next/static/chunks/2e1278563a8e26be.js +0 -1
- package/dashboard/out/_next/static/chunks/309eb70780a3d67d.js +0 -1
- package/dashboard/out/_next/static/chunks/30f71950992e2307.js +0 -1
- package/dashboard/out/_next/static/chunks/31191873c2075784.js +0 -1
- package/dashboard/out/_next/static/chunks/31a790921a0c035e.js +0 -1
- package/dashboard/out/_next/static/chunks/34f8d7a5a06e8e32.js +0 -1
- package/dashboard/out/_next/static/chunks/36b3716436d9bc9d.js +0 -1
- package/dashboard/out/_next/static/chunks/3770b7d0ff004de3.js +0 -1
- package/dashboard/out/_next/static/chunks/381ab3bd4cffbf4d.js +0 -1
- package/dashboard/out/_next/static/chunks/38aa4eada5dcd1a4.js +0 -1
- package/dashboard/out/_next/static/chunks/39723132b78cb77e.js +0 -1
- package/dashboard/out/_next/static/chunks/399efdcc3fe56e09.js +0 -1
- package/dashboard/out/_next/static/chunks/3a8431a9221b0706.js +0 -1
- package/dashboard/out/_next/static/chunks/3a9b40810ce88cbf.js +0 -1
- package/dashboard/out/_next/static/chunks/3b322e3e9476ecdf.js +0 -1
- package/dashboard/out/_next/static/chunks/3bb14befeeb08c28.js +0 -1
- package/dashboard/out/_next/static/chunks/3c99637ebd462b20.js +0 -1
- package/dashboard/out/_next/static/chunks/3cbb9217d56459d8.js +0 -1
- package/dashboard/out/_next/static/chunks/3d438e0929a762f7.js +0 -1
- package/dashboard/out/_next/static/chunks/3e6d4834e0752f76.js +0 -2
- package/dashboard/out/_next/static/chunks/3e6f8cedaad4e89b.js +0 -1
- package/dashboard/out/_next/static/chunks/3e701259ce1b53e3.js +0 -1
- package/dashboard/out/_next/static/chunks/41562f7ab7c033eb.js +0 -1
- package/dashboard/out/_next/static/chunks/418fadd8a94e2fd7.js +0 -1
- package/dashboard/out/_next/static/chunks/42345437bb922e9c.js +0 -1
- package/dashboard/out/_next/static/chunks/434b6bd78c97b668.js +0 -1
- package/dashboard/out/_next/static/chunks/434febe897d33e2d.js +0 -1
- package/dashboard/out/_next/static/chunks/45d28707dd8ce1c1.js +0 -1
- package/dashboard/out/_next/static/chunks/4620a640e88cb1b7.js +0 -1
- package/dashboard/out/_next/static/chunks/468f8eb604502ea4.js +0 -1
- package/dashboard/out/_next/static/chunks/495f5cedf8afe823.js +0 -1
- package/dashboard/out/_next/static/chunks/49a89d1704d27740.js +0 -1
- package/dashboard/out/_next/static/chunks/49e5c37d15f63def.js +0 -1
- package/dashboard/out/_next/static/chunks/4a46ad186a8587ee.css +0 -2
- package/dashboard/out/_next/static/chunks/4a5c728574935d50.js +0 -1
- package/dashboard/out/_next/static/chunks/4b27ebd4d9304d8f.js +0 -1
- package/dashboard/out/_next/static/chunks/4b965aa320a9b358.js +0 -1
- package/dashboard/out/_next/static/chunks/4bb2da90bbe5794f.js +0 -1
- package/dashboard/out/_next/static/chunks/4d2f1c6f631e4b85.js +0 -1
- package/dashboard/out/_next/static/chunks/4d696d5a64dab42e.js +0 -1
- package/dashboard/out/_next/static/chunks/4f660dadb3f1e9e2.js +0 -1
- package/dashboard/out/_next/static/chunks/507b50d47f949333.js +0 -1
- package/dashboard/out/_next/static/chunks/50b1b7063bb044e2.js +0 -1
- package/dashboard/out/_next/static/chunks/50c0c2e3c4f02508.js +0 -1
- package/dashboard/out/_next/static/chunks/50d7ba9803e65b02.js +0 -1
- package/dashboard/out/_next/static/chunks/51535a6d4c98c7a4.js +0 -1
- package/dashboard/out/_next/static/chunks/526de6cc16c7dd2f.js +0 -1
- package/dashboard/out/_next/static/chunks/5307604f8632b3fd.js +0 -1
- package/dashboard/out/_next/static/chunks/5480bd722260f231.js +0 -1
- package/dashboard/out/_next/static/chunks/54c61c82e5f1dca5.js +0 -1
- package/dashboard/out/_next/static/chunks/555de6ac6bb9034c.js +0 -1
- package/dashboard/out/_next/static/chunks/562b89ee76a4b90c.js +0 -1
- package/dashboard/out/_next/static/chunks/5747f9a32edff429.js +0 -1
- package/dashboard/out/_next/static/chunks/57ee141430b4c05c.js +0 -1
- package/dashboard/out/_next/static/chunks/584c066a0837dd9a.js +0 -1
- package/dashboard/out/_next/static/chunks/5856aed4670a6253.js +0 -1
- package/dashboard/out/_next/static/chunks/58fe563414726511.js +0 -1
- package/dashboard/out/_next/static/chunks/5948196b2729b18c.js +0 -1
- package/dashboard/out/_next/static/chunks/5ab77c69e7306f97.js +0 -1
- package/dashboard/out/_next/static/chunks/5b3fa4c99d61611c.js +0 -1
- package/dashboard/out/_next/static/chunks/5cc87abaef616661.js +0 -1
- package/dashboard/out/_next/static/chunks/5dfa7913f99bb966.js +0 -1
- package/dashboard/out/_next/static/chunks/5e49b008432770f1.js +0 -1
- package/dashboard/out/_next/static/chunks/5e5a33dd81c45c47.js +0 -1
- package/dashboard/out/_next/static/chunks/5ee61bed638eb8a8.js +0 -1
- package/dashboard/out/_next/static/chunks/5eed557b9f45ae4a.js +0 -1
- package/dashboard/out/_next/static/chunks/60446823a7d30c1b.js +0 -1
- package/dashboard/out/_next/static/chunks/605150c490861442.js +0 -1
- package/dashboard/out/_next/static/chunks/609056d51c0bf332.js +0 -1
- package/dashboard/out/_next/static/chunks/60ec91dfeb50071b.js +0 -1
- package/dashboard/out/_next/static/chunks/61bc7be0bba6a33f.js +0 -1
- package/dashboard/out/_next/static/chunks/61f4a5c9ce23e55e.js +0 -1
- package/dashboard/out/_next/static/chunks/63aae3585be04e65.js +0 -1
- package/dashboard/out/_next/static/chunks/64e0e1ee7ef4f01b.js +0 -1
- package/dashboard/out/_next/static/chunks/66e9b4c26ff1aaaf.js +0 -1
- package/dashboard/out/_next/static/chunks/67068847aa1b5e98.js +0 -1
- package/dashboard/out/_next/static/chunks/68d7de1f2e202c26.js +0 -1
- package/dashboard/out/_next/static/chunks/698ca3ea1937ba4f.js +0 -1
- package/dashboard/out/_next/static/chunks/69eb97c24b5b6b9d.js +0 -1
- package/dashboard/out/_next/static/chunks/6b1ac098c228a479.js +0 -1
- package/dashboard/out/_next/static/chunks/6bb8020cb796bcc1.js +0 -1
- package/dashboard/out/_next/static/chunks/6cf8b1db17f2a155.js +0 -1
- package/dashboard/out/_next/static/chunks/6e7613643906ea88.js +0 -1
- package/dashboard/out/_next/static/chunks/6ea27ceb2e6feae5.js +0 -1
- package/dashboard/out/_next/static/chunks/6f95a8dbcf23dded.js +0 -1
- package/dashboard/out/_next/static/chunks/6fd44e759d82f64e.js +0 -1
- package/dashboard/out/_next/static/chunks/716ec29c268c38f6.js +0 -1
- package/dashboard/out/_next/static/chunks/717b399dfcf7c02c.js +0 -1
- package/dashboard/out/_next/static/chunks/72badd90e1c91348.js +0 -1
- package/dashboard/out/_next/static/chunks/72f9296dbb7ae3c4.js +0 -1
- package/dashboard/out/_next/static/chunks/73aa31e1144d8191.js +0 -1
- package/dashboard/out/_next/static/chunks/73f98ded8fe2e879.js +0 -1
- package/dashboard/out/_next/static/chunks/740a2e8ace15e13b.js +0 -1
- package/dashboard/out/_next/static/chunks/74e57c16e7304b6b.js +0 -1
- package/dashboard/out/_next/static/chunks/7628dead4d113df2.js +0 -1
- package/dashboard/out/_next/static/chunks/7717c88c07ed96e6.js +0 -1
- package/dashboard/out/_next/static/chunks/775581f7956d9a27.js +0 -1
- package/dashboard/out/_next/static/chunks/77d3b6661befdb98.js +0 -1
- package/dashboard/out/_next/static/chunks/78435c0e1c77fea7.js +0 -1
- package/dashboard/out/_next/static/chunks/791336748b9c9393.js +0 -1
- package/dashboard/out/_next/static/chunks/79ea68dfae65ef2f.js +0 -1
- package/dashboard/out/_next/static/chunks/7bae777a472eb13c.js +0 -1
- package/dashboard/out/_next/static/chunks/7fd847f47237a35c.js +0 -1
- package/dashboard/out/_next/static/chunks/804d718be0370058.css +0 -1
- package/dashboard/out/_next/static/chunks/807cb3c1923995ab.js +0 -1
- package/dashboard/out/_next/static/chunks/8286b39407d94447.js +0 -1
- package/dashboard/out/_next/static/chunks/84e4305b8dc830ed.js +0 -1
- package/dashboard/out/_next/static/chunks/850f99dbfabb53e0.js +0 -1
- package/dashboard/out/_next/static/chunks/85594127e66b6b7f.js +0 -1
- package/dashboard/out/_next/static/chunks/858c11169c183c9c.js +0 -1
- package/dashboard/out/_next/static/chunks/85e27005dfe4c3e3.js +0 -1
- package/dashboard/out/_next/static/chunks/8707bd3d8ddc1f1c.js +0 -1
- package/dashboard/out/_next/static/chunks/8851e5d7ef894973.js +0 -1
- package/dashboard/out/_next/static/chunks/88ba3782cc614989.js +0 -1
- package/dashboard/out/_next/static/chunks/8a0c9a181784c2ef.js +0 -1
- package/dashboard/out/_next/static/chunks/8ad01ae355a6e7f6.js +0 -1
- package/dashboard/out/_next/static/chunks/8d2ed93b0d0a4b4f.js +0 -1
- package/dashboard/out/_next/static/chunks/8dc352ebd5310d3c.js +0 -1
- package/dashboard/out/_next/static/chunks/8dd1d722c8c1a22c.js +0 -1
- package/dashboard/out/_next/static/chunks/8e60fe307e3e4986.js +0 -1
- package/dashboard/out/_next/static/chunks/8eea4dd71e25f32a.js +0 -1
- package/dashboard/out/_next/static/chunks/8f468a65bac34eaa.js +0 -1
- package/dashboard/out/_next/static/chunks/9057e5382d6a5059.js +0 -1
- package/dashboard/out/_next/static/chunks/931d056437805ecb.js +0 -1
- package/dashboard/out/_next/static/chunks/954eb11648dd8cff.js +0 -1
- package/dashboard/out/_next/static/chunks/955034d876d14bb3.js +0 -1
- package/dashboard/out/_next/static/chunks/966c4895773249f6.js +0 -1
- package/dashboard/out/_next/static/chunks/96aff410a6ab106c.js +0 -1
- package/dashboard/out/_next/static/chunks/99b396c821c1dd2d.js +0 -1
- package/dashboard/out/_next/static/chunks/9a255fd2cf4922ae.js +0 -1
- package/dashboard/out/_next/static/chunks/9b1f6aa73bc864b2.js +0 -1
- package/dashboard/out/_next/static/chunks/9b9ce582c533aa79.js +0 -1
- package/dashboard/out/_next/static/chunks/9c19bd3c773c6611.js +0 -1
- package/dashboard/out/_next/static/chunks/9ceeaa847eb15ca2.js +0 -1
- package/dashboard/out/_next/static/chunks/9d2e6e7b8b65f845.js +0 -1
- package/dashboard/out/_next/static/chunks/9de23802066128ff.js +0 -1
- package/dashboard/out/_next/static/chunks/9e327f94b3701170.js +0 -1
- package/dashboard/out/_next/static/chunks/9e7818421c2d455a.js +0 -1
- package/dashboard/out/_next/static/chunks/9e935acbcf2fb519.js +0 -1
- package/dashboard/out/_next/static/chunks/9ed9f68340343b51.js +0 -1
- package/dashboard/out/_next/static/chunks/9f94a0242455fc20.js +0 -1
- package/dashboard/out/_next/static/chunks/9fff46b5aaecf47e.js +0 -1
- package/dashboard/out/_next/static/chunks/a06d267a5c557657.js +0 -1
- package/dashboard/out/_next/static/chunks/a0eb8c81c54722fe.js +0 -1
- package/dashboard/out/_next/static/chunks/a16808e83106427d.js +0 -1
- package/dashboard/out/_next/static/chunks/a2215e2bc72739ef.js +0 -1
- package/dashboard/out/_next/static/chunks/a2de9e6696eb9c17.js +0 -1
- package/dashboard/out/_next/static/chunks/a33420b8c14090d6.js +0 -1
- package/dashboard/out/_next/static/chunks/a360bffef39d818b.js +0 -1
- package/dashboard/out/_next/static/chunks/a42852fdf5287cf5.js +0 -1
- package/dashboard/out/_next/static/chunks/a4efc88488322b67.js +0 -1
- package/dashboard/out/_next/static/chunks/a696a91d14101bdb.js +0 -1
- package/dashboard/out/_next/static/chunks/a6dad97d9634a72d.js +0 -1
- package/dashboard/out/_next/static/chunks/a6dad97d9634a72d.js.map +0 -1
- package/dashboard/out/_next/static/chunks/a705c7261712bd8e.js +0 -1
- package/dashboard/out/_next/static/chunks/a7d1e4b067ace180.js +0 -1
- package/dashboard/out/_next/static/chunks/a8590c121a7efaa5.js +0 -1
- package/dashboard/out/_next/static/chunks/a95953a83a6df6cf.js +0 -1
- package/dashboard/out/_next/static/chunks/a9d0a757d3b4afed.js +0 -1
- package/dashboard/out/_next/static/chunks/a9dc35514619c872.js +0 -1
- package/dashboard/out/_next/static/chunks/aae0253c73fba984.js +0 -193
- package/dashboard/out/_next/static/chunks/ab1821552066a74d.js +0 -1
- package/dashboard/out/_next/static/chunks/ac96bc0eccebe15a.js +0 -1
- package/dashboard/out/_next/static/chunks/acc03cfaaf6bb30f.js +0 -1
- package/dashboard/out/_next/static/chunks/acc298ae059b9fe4.js +0 -1
- package/dashboard/out/_next/static/chunks/ad076b6e996d250c.js +0 -1
- package/dashboard/out/_next/static/chunks/ae53b64af7ba16d5.js +0 -1
- package/dashboard/out/_next/static/chunks/ae5da7fa4bc5fa95.js +0 -1
- package/dashboard/out/_next/static/chunks/aefcf0eb033a155e.js +0 -1
- package/dashboard/out/_next/static/chunks/b12d43100ee10526.js +0 -1
- package/dashboard/out/_next/static/chunks/b137952dd5566a41.js +0 -1
- package/dashboard/out/_next/static/chunks/b2832d8a2d332a47.js +0 -1
- package/dashboard/out/_next/static/chunks/b417aa09c15f63c6.js +0 -1
- package/dashboard/out/_next/static/chunks/b48e152ba7bf6caf.js +0 -1
- package/dashboard/out/_next/static/chunks/b4cbef8e4714bf92.js +0 -1
- package/dashboard/out/_next/static/chunks/b568ff8383a37f53.js +0 -1
- package/dashboard/out/_next/static/chunks/b6e1495dd89a39e3.js +0 -1
- package/dashboard/out/_next/static/chunks/b73a21bd67528440.js +0 -1
- package/dashboard/out/_next/static/chunks/b77ee62bc71dcec7.js +0 -1
- package/dashboard/out/_next/static/chunks/b7f454615c11d4eb.js +0 -1
- package/dashboard/out/_next/static/chunks/b818ea32a839590f.js +0 -1
- package/dashboard/out/_next/static/chunks/b901ce78c1c65d66.js +0 -1
- package/dashboard/out/_next/static/chunks/b93d647b803be9fa.js +0 -1
- package/dashboard/out/_next/static/chunks/b960b09ff1ecd6bb.js +0 -1
- package/dashboard/out/_next/static/chunks/ba717c7779a778fc.js +0 -1
- package/dashboard/out/_next/static/chunks/bacb0ae7458d18b6.js +0 -1
- package/dashboard/out/_next/static/chunks/bb51f68b27346852.js +0 -1
- package/dashboard/out/_next/static/chunks/bb67f22e1ab632ae.js +0 -1
- package/dashboard/out/_next/static/chunks/bbae97ec58d1bb9d.js +0 -1
- package/dashboard/out/_next/static/chunks/bbaf38ee90307765.js +0 -1
- package/dashboard/out/_next/static/chunks/be01598fe1fce5db.js +0 -1
- package/dashboard/out/_next/static/chunks/bf4cbbc095987fec.js +0 -1
- package/dashboard/out/_next/static/chunks/c18f823797d57446.js +0 -1
- package/dashboard/out/_next/static/chunks/c1bcece34ec5b295.js +0 -1
- package/dashboard/out/_next/static/chunks/c1ccbc3a51adaf0a.js +0 -1
- package/dashboard/out/_next/static/chunks/c1f54a628f27791c.js +0 -1
- package/dashboard/out/_next/static/chunks/c21916bf5d232151.js +0 -1
- package/dashboard/out/_next/static/chunks/c27e570f0fc2f78b.js +0 -1
- package/dashboard/out/_next/static/chunks/c322b8005eebc0d8.js +0 -1
- package/dashboard/out/_next/static/chunks/c3900ac2b009b764.js +0 -1
- package/dashboard/out/_next/static/chunks/c3c597c8eb77ad6b.js +0 -1
- package/dashboard/out/_next/static/chunks/c58d45c1d88e208c.js +0 -1
- package/dashboard/out/_next/static/chunks/c63875998d7a655f.js +0 -1
- package/dashboard/out/_next/static/chunks/c6eaa7c8e7209c8e.js +0 -1
- package/dashboard/out/_next/static/chunks/c6f820f71162a59a.js +0 -1
- package/dashboard/out/_next/static/chunks/c7ce0941d1dfe7ed.js +0 -1
- package/dashboard/out/_next/static/chunks/c877259f784ec8e5.js +0 -1
- package/dashboard/out/_next/static/chunks/cb8057e86aaa1c68.js +0 -1
- package/dashboard/out/_next/static/chunks/ccb4e306ee4859b1.js +0 -1
- package/dashboard/out/_next/static/chunks/ccde62b0fa8112eb.js +0 -1
- package/dashboard/out/_next/static/chunks/cd421f0971c63c31.js +0 -1
- package/dashboard/out/_next/static/chunks/cde0317494d4adb7.js +0 -1
- package/dashboard/out/_next/static/chunks/cfae64434b92b8b6.js +0 -1
- package/dashboard/out/_next/static/chunks/cfc0e19310ec0a36.js +0 -1
- package/dashboard/out/_next/static/chunks/d0f344c7d0bcb59a.js +0 -1
- package/dashboard/out/_next/static/chunks/d1871c73e74e1f50.js +0 -1
- package/dashboard/out/_next/static/chunks/d18f658850711061.js +0 -1
- package/dashboard/out/_next/static/chunks/d25d9c48880697f6.js +0 -1
- package/dashboard/out/_next/static/chunks/d3f0a72bbc3060b0.js +0 -1
- package/dashboard/out/_next/static/chunks/d5fca429bc89060b.js +0 -1
- package/dashboard/out/_next/static/chunks/d6a6c75c9a4bef49.js +0 -1
- package/dashboard/out/_next/static/chunks/d6a8f93aa8f1e345.js +0 -1
- package/dashboard/out/_next/static/chunks/d7deab676b19ba0e.js +0 -1
- package/dashboard/out/_next/static/chunks/d80030dde88cec85.js +0 -1
- package/dashboard/out/_next/static/chunks/da84f9774c076ff1.js +0 -1
- package/dashboard/out/_next/static/chunks/da98ecc6b9b0ceb2.js +0 -1
- package/dashboard/out/_next/static/chunks/db550aabce60b912.js +0 -4
- package/dashboard/out/_next/static/chunks/dc87f77fcc029482.js +0 -1
- package/dashboard/out/_next/static/chunks/dd05f30acdb63a53.js +0 -1
- package/dashboard/out/_next/static/chunks/dea150155499b903.js +0 -1
- package/dashboard/out/_next/static/chunks/df4358754ff1d5e8.js +0 -1
- package/dashboard/out/_next/static/chunks/df8b21ff12d23b0e.js +0 -1
- package/dashboard/out/_next/static/chunks/df8fc68d77debf2c.js +0 -1
- package/dashboard/out/_next/static/chunks/df9ed1ffec90589f.js +0 -1
- package/dashboard/out/_next/static/chunks/dfa65ece1598f934.js +0 -1
- package/dashboard/out/_next/static/chunks/dfaab5cdd41a20d9.js +0 -1
- package/dashboard/out/_next/static/chunks/e00c03c942b5ae3e.js +0 -1
- package/dashboard/out/_next/static/chunks/e02d27cf679bece1.js +0 -1
- package/dashboard/out/_next/static/chunks/e0b747585f78513a.js +0 -1
- package/dashboard/out/_next/static/chunks/e19b71ca23fd7df0.js +0 -1
- package/dashboard/out/_next/static/chunks/e21cbe89d0cda120.js +0 -1
- package/dashboard/out/_next/static/chunks/e2a0386930edd34f.js +0 -1
- package/dashboard/out/_next/static/chunks/e375de19a36e2f38.js +0 -1
- package/dashboard/out/_next/static/chunks/e5241b4a6496a9e0.js +0 -1
- package/dashboard/out/_next/static/chunks/e569dd07c96eb4dd.js +0 -1
- package/dashboard/out/_next/static/chunks/e61c3ea91233f948.js +0 -1
- package/dashboard/out/_next/static/chunks/e73b42c5d34c4c28.js +0 -1
- package/dashboard/out/_next/static/chunks/e7c5b2105f651e87.js +0 -1
- package/dashboard/out/_next/static/chunks/e878e58a69bb01b6.js +0 -1
- package/dashboard/out/_next/static/chunks/e8f090a4dc571062.js +0 -1
- package/dashboard/out/_next/static/chunks/e98dc34dac91cdad.js +0 -1
- package/dashboard/out/_next/static/chunks/ea67cf7a0c5e7233.js +0 -1
- package/dashboard/out/_next/static/chunks/eacc98a1e3512fa9.js +0 -1
- package/dashboard/out/_next/static/chunks/eae9cd74d9bbed8b.js +0 -1
- package/dashboard/out/_next/static/chunks/eb187fd345852b34.js +0 -1
- package/dashboard/out/_next/static/chunks/ecea46df7a40b427.js +0 -1
- package/dashboard/out/_next/static/chunks/ecf48b83f4245d9c.js +0 -1
- package/dashboard/out/_next/static/chunks/eda41732bf966c6d.js +0 -1
- package/dashboard/out/_next/static/chunks/ee174a433049f43a.js +0 -1
- package/dashboard/out/_next/static/chunks/ee66b2d3b76de72e.js +0 -1
- package/dashboard/out/_next/static/chunks/ef058d3041c3d8ab.js +0 -1
- package/dashboard/out/_next/static/chunks/f3544b800b0d4969.js +0 -1
- package/dashboard/out/_next/static/chunks/f3b280e604b97509.js +0 -1
- package/dashboard/out/_next/static/chunks/f42e1d3693232993.js +0 -1
- package/dashboard/out/_next/static/chunks/f5a5fdd80166d304.js +0 -1
- package/dashboard/out/_next/static/chunks/f60adc5dd6169316.js +0 -1
- package/dashboard/out/_next/static/chunks/f6255bc2e6c68ce8.js +0 -1
- package/dashboard/out/_next/static/chunks/f6bb677d2d8840db.js +0 -1
- package/dashboard/out/_next/static/chunks/f6f3bf016239c33a.js +0 -1
- package/dashboard/out/_next/static/chunks/f6f57a46cd074bfe.js +0 -1
- package/dashboard/out/_next/static/chunks/f7feb9c1199e8cf6.js +0 -1
- package/dashboard/out/_next/static/chunks/f831087bf964cc3d.js +0 -1
- package/dashboard/out/_next/static/chunks/fa5a2f7244935aa2.js +0 -1
- package/dashboard/out/_next/static/chunks/fa73f36df4c38fa0.js +0 -1
- package/dashboard/out/_next/static/chunks/faa1461722d6a88d.js +0 -1
- package/dashboard/out/_next/static/chunks/fbf706b9a0edb7e6.js +0 -1
- package/dashboard/out/_next/static/chunks/fc2b8c8b7446bbc2.js +0 -1
- package/dashboard/out/_next/static/chunks/fd516f3930c128c1.js +0 -1
- package/dashboard/out/_next/static/chunks/fd663e7ff8de4887.js +0 -1
- package/dashboard/out/_next/static/chunks/fe5b656cc9bdd60e.js +0 -1
- package/dashboard/out/_next/static/chunks/turbopack-2f5dec4a537c0ee7.js +0 -4
- package/dashboard/out/_next/static/media/02263ebadd758ea4-s.8da66e7f.woff2 +0 -0
- package/dashboard/out/_next/static/media/2fe89d53234c61d4-s.cb53f04b.woff2 +0 -0
- package/dashboard/out/_next/static/media/59b15b4bcd7b1eb5-s.afa48be3.woff2 +0 -0
- package/dashboard/out/_next/static/media/5e05ae5b48faa55e-s.6b5ea6af.woff2 +0 -0
- package/dashboard/out/_next/static/media/68757d6cddeff913-s.e6cd1753.woff2 +0 -0
- package/dashboard/out/_next/static/media/99e609270109b47d-s.p.64b9304e.woff2 +0 -0
- package/dashboard/out/_next/static/media/a7afbb44bec2bb18-s.1dcddae6.woff2 +0 -0
- package/dashboard/out/_next/static/media/e390973e931a41c5-s.a82ecf4e.woff2 +0 -0
- package/dashboard/out/_next/static/media/e532fa1b9921e1cd-s.764b43eb.woff2 +0 -0
- package/dashboard/out/_next/static/media/effe91970fc4db64-s.p.19510058.woff2 +0 -0
- package/dashboard/out/_not-found/__next._full.txt +0 -15
- package/dashboard/out/_not-found/__next._head.txt +0 -5
- package/dashboard/out/_not-found/__next._index.txt +0 -7
- package/dashboard/out/_not-found/__next._not-found.__PAGE__.txt +0 -5
- package/dashboard/out/_not-found/__next._not-found.txt +0 -4
- package/dashboard/out/_not-found/__next._tree.txt +0 -2
- package/dashboard/out/_not-found/index.html +0 -1
- package/dashboard/out/_not-found/index.txt +0 -15
- package/dashboard/out/index.html +0 -1
- package/dashboard/out/index.txt +0 -21
- package/dashboard/out/project/__fallback/__next._full.txt +0 -19
- package/dashboard/out/project/__fallback/__next._head.txt +0 -5
- package/dashboard/out/project/__fallback/__next._index.txt +0 -7
- package/dashboard/out/project/__fallback/__next._tree.txt +0 -5
- package/dashboard/out/project/__fallback/__next.project.$d$name.__PAGE__.txt +0 -7
- package/dashboard/out/project/__fallback/__next.project.$d$name.txt +0 -4
- package/dashboard/out/project/__fallback/__next.project.txt +0 -4
- package/dashboard/out/project/__fallback/index.html +0 -1
- package/dashboard/out/project/__fallback/index.txt +0 -19
- package/src/__tests__/support.ts +0 -186
- package/src/attach.ts +0 -20
- package/src/cli.test.ts +0 -314
- package/src/command-center/discovery.test.ts +0 -265
- package/src/command-center/discovery.ts +0 -296
- package/src/command-center/index.ts +0 -31
- package/src/command-center/schemas.ts +0 -57
- package/src/command-center/server.test.ts +0 -814
- package/src/command-center/server.ts +0 -1427
- package/src/command-center/static.test.ts +0 -105
- package/src/command-center/static.ts +0 -122
- package/src/config-cli.test.ts +0 -86
- package/src/config.test.ts +0 -205
- package/src/config.ts +0 -313
- package/src/detect.test.ts +0 -142
- package/src/detect.ts +0 -248
- package/src/dispatch.ts +0 -102
- package/src/doctor.test.ts +0 -119
- package/src/doctor.ts +0 -167
- package/src/init.test.ts +0 -101
- package/src/init.ts +0 -211
- package/src/inspect.test.ts +0 -67
- package/src/inspect.ts +0 -212
- package/src/integration.test.ts.skip +0 -276
- package/src/js-yaml.d.ts +0 -10
- package/src/launch.test.ts +0 -149
- package/src/launch.ts +0 -583
- package/src/lib/auth/auth-service.test.ts +0 -178
- package/src/lib/auth/auth-service.ts +0 -227
- package/src/lib/auth/middleware.test.ts +0 -119
- package/src/lib/auth/middleware.ts +0 -56
- package/src/lib/auth/types.ts +0 -13
- package/src/lib/authorship.test.ts +0 -242
- package/src/lib/authorship.ts +0 -280
- package/src/lib/cast/recorder.test.ts +0 -127
- package/src/lib/cast/recorder.ts +0 -148
- package/src/lib/daemon-watchdog.ts +0 -102
- package/src/lib/daemon.ts +0 -431
- package/src/lib/dot-path.test.ts +0 -66
- package/src/lib/dot-path.ts +0 -17
- package/src/lib/errors.ts +0 -44
- package/src/lib/event-log.test.ts +0 -189
- package/src/lib/event-log.ts +0 -237
- package/src/lib/github-pr.ts +0 -182
- package/src/lib/hq/client.test.ts +0 -154
- package/src/lib/hq/client.ts +0 -142
- package/src/lib/hq/mdns.test.ts +0 -54
- package/src/lib/hq/mdns.ts +0 -88
- package/src/lib/hq/registry.test.ts +0 -237
- package/src/lib/hq/registry.ts +0 -145
- package/src/lib/hq/types.ts +0 -44
- package/src/lib/ipc/socket-protocol.test.ts +0 -126
- package/src/lib/ipc/socket-protocol.ts +0 -324
- package/src/lib/launch-plan.test.ts +0 -119
- package/src/lib/launch-plan.ts +0 -90
- package/src/lib/log.test.ts +0 -39
- package/src/lib/log.ts +0 -71
- package/src/lib/metrics.test.ts +0 -213
- package/src/lib/metrics.ts +0 -345
- package/src/lib/orchestrator.test.ts +0 -2358
- package/src/lib/orchestrator.ts +0 -1799
- package/src/lib/output.ts +0 -76
- package/src/lib/plan-store.test.ts +0 -148
- package/src/lib/plan-store.ts +0 -131
- package/src/lib/research.test.ts +0 -208
- package/src/lib/research.ts +0 -475
- package/src/lib/round-trip.test.ts +0 -341
- package/src/lib/session-monitor.test.ts +0 -152
- package/src/lib/session-monitor.ts +0 -282
- package/src/lib/session-options.test.ts +0 -146
- package/src/lib/session-options.ts +0 -100
- package/src/lib/shell.test.ts +0 -20
- package/src/lib/sizes.test.ts +0 -73
- package/src/lib/sizes.ts +0 -36
- package/src/lib/skill-registry.test.ts +0 -134
- package/src/lib/skill-registry.ts +0 -86
- package/src/lib/slugify.test.ts +0 -35
- package/src/lib/slugify.ts +0 -10
- package/src/lib/task-store.ts +0 -352
- package/src/lib/tmux.test.ts +0 -500
- package/src/lib/tmux.ts +0 -372
- package/src/lib/token-tracker.test.ts +0 -102
- package/src/lib/token-tracker.ts +0 -70
- package/src/lib/tunnels/cloudflare.test.ts +0 -73
- package/src/lib/tunnels/cloudflare.ts +0 -254
- package/src/lib/tunnels/manager.test.ts +0 -63
- package/src/lib/tunnels/manager.ts +0 -132
- package/src/lib/tunnels/ngrok.test.ts +0 -94
- package/src/lib/tunnels/ngrok.ts +0 -261
- package/src/lib/tunnels/tailscale.test.ts +0 -103
- package/src/lib/tunnels/tailscale.ts +0 -1048
- package/src/lib/tunnels/types.ts +0 -44
- package/src/lib/validation.test.ts +0 -289
- package/src/lib/validation.ts +0 -123
- package/src/lib/webhook.ts +0 -51
- package/src/lib/workflow-store.ts +0 -209
- package/src/lib/ws-v3/protocol.test.ts +0 -127
- package/src/lib/ws-v3/protocol.ts +0 -160
- package/src/lib/yaml-io.test.ts +0 -125
- package/src/lib/yaml-io.ts +0 -27
- package/src/ls.test.ts +0 -76
- package/src/ls.ts +0 -40
- package/src/metrics-cli.ts +0 -177
- package/src/notify.ts +0 -85
- package/src/orchestrator-status.ts +0 -228
- package/src/plan.ts +0 -117
- package/src/postinstall.test.ts +0 -79
- package/src/remote.test.ts +0 -115
- package/src/remote.ts +0 -148
- package/src/restart.ts +0 -24
- package/src/schemas/domain.ts +0 -422
- package/src/schemas/ide-config.ts +0 -148
- package/src/schemas/index.ts +0 -53
- package/src/send.test.ts +0 -70
- package/src/send.ts +0 -181
- package/src/skill.test.ts +0 -114
- package/src/skill.ts +0 -138
- package/src/status.ts +0 -66
- package/src/stop.test.ts +0 -151
- package/src/stop.ts +0 -37
- package/src/task.test.ts +0 -1466
- package/src/task.ts +0 -1143
- package/src/tunnel.test.ts +0 -80
- package/src/tunnel.ts +0 -133
- package/src/types.ts +0 -15
- package/src/ui/index.ts +0 -32
- package/src/ui/terminal/index.ts +0 -9
- package/src/ui/types.ts +0 -91
- package/src/ui/web/base.css +0 -80
- package/src/ui/web/components/Box.tsx +0 -59
- package/src/ui/web/components/Input.tsx +0 -32
- package/src/ui/web/components/ScrollBox.tsx +0 -60
- package/src/ui/web/components/Text.tsx +0 -28
- package/src/ui/web/hooks.ts +0 -106
- package/src/ui/web/index.ts +0 -27
- package/src/ui/web/render.ts +0 -77
- package/src/ui/web/utils/color.test.ts +0 -93
- package/src/ui/web/utils/color.ts +0 -27
- package/src/validate.test.ts +0 -385
- package/src/validate.ts +0 -217
- package/src/widgets/changes/index.tsx +0 -576
- package/src/widgets/config/index.tsx +0 -440
- package/src/widgets/costs/index.tsx +0 -216
- package/src/widgets/explorer/breadcrumbs.tsx +0 -77
- package/src/widgets/explorer/footer.tsx +0 -20
- package/src/widgets/explorer/header.tsx +0 -23
- package/src/widgets/explorer/index.tsx +0 -408
- package/src/widgets/explorer/tree-model.test.ts +0 -214
- package/src/widgets/explorer/tree-model.ts +0 -103
- package/src/widgets/explorer/tree.tsx +0 -156
- package/src/widgets/lib/config-model.ts +0 -116
- package/src/widgets/lib/files.test.ts +0 -103
- package/src/widgets/lib/files.ts +0 -88
- package/src/widgets/lib/git.test.ts +0 -151
- package/src/widgets/lib/git.ts +0 -88
- package/src/widgets/lib/pane-comms.test.ts +0 -178
- package/src/widgets/lib/pane-comms.ts +0 -207
- package/src/widgets/lib/theme.ts +0 -151
- package/src/widgets/lib/watcher.ts +0 -50
- package/src/widgets/mission-control/activity-feed.tsx +0 -59
- package/src/widgets/mission-control/agent-panel.tsx +0 -132
- package/src/widgets/mission-control/command-bar.tsx +0 -63
- package/src/widgets/mission-control/index.tsx +0 -751
- package/src/widgets/mission-control/task-panel.tsx +0 -238
- package/src/widgets/preview/index.tsx +0 -415
- package/src/widgets/resolve.ts +0 -40
- package/src/widgets/setup/agent-naming.tsx +0 -112
- package/src/widgets/setup/config-tree.tsx +0 -238
- package/src/widgets/setup/field-editor.tsx +0 -265
- package/src/widgets/setup/footer.tsx +0 -107
- package/src/widgets/setup/index.tsx +0 -340
- package/src/widgets/setup/layout-picker.tsx +0 -96
- package/src/widgets/setup/orchestrator-panel.tsx +0 -200
- package/src/widgets/setup/review-panel.tsx +0 -140
- package/src/widgets/setup/setup-model.test.ts +0 -303
- package/src/widgets/setup/setup-model.ts +0 -188
- package/src/widgets/tasks/index.tsx +0 -129
- package/src/widgets/tasks/task-detail.tsx +0 -309
- package/src/widgets/tasks/task-form.tsx +0 -193
- package/src/widgets/tasks/task-list.tsx +0 -205
- package/src/widgets/tasks/task-model.test.ts +0 -473
- package/src/widgets/tasks/task-model.ts +0 -157
package/src/lib/orchestrator.ts
DELETED
|
@@ -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
|
-
}
|