night-orch 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/cli/commands/cleanup.d.ts +14 -0
- package/dist/cli/commands/cleanup.d.ts.map +1 -0
- package/dist/cli/commands/cleanup.js +62 -0
- package/dist/cli/commands/cleanup.js.map +1 -0
- package/dist/cli/commands/continue.d.ts +9 -0
- package/dist/cli/commands/continue.d.ts.map +1 -0
- package/dist/cli/commands/continue.js +70 -0
- package/dist/cli/commands/continue.js.map +1 -0
- package/dist/cli/commands/cost-override.d.ts +11 -0
- package/dist/cli/commands/cost-override.d.ts.map +1 -0
- package/dist/cli/commands/cost-override.js +76 -0
- package/dist/cli/commands/cost-override.js.map +1 -0
- package/dist/cli/commands/daily-cost-override.d.ts +11 -0
- package/dist/cli/commands/daily-cost-override.d.ts.map +1 -0
- package/dist/cli/commands/daily-cost-override.js +71 -0
- package/dist/cli/commands/daily-cost-override.js.map +1 -0
- package/dist/cli/commands/delete-entry.d.ts +12 -0
- package/dist/cli/commands/delete-entry.d.ts.map +1 -0
- package/dist/cli/commands/delete-entry.js +68 -0
- package/dist/cli/commands/delete-entry.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +227 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +73 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/labels-init.d.ts +9 -0
- package/dist/cli/commands/labels-init.d.ts.map +1 -0
- package/dist/cli/commands/labels-init.js +52 -0
- package/dist/cli/commands/labels-init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +9 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +52 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/monitoring-init.d.ts +2 -0
- package/dist/cli/commands/monitoring-init.d.ts.map +1 -0
- package/dist/cli/commands/monitoring-init.js +43 -0
- package/dist/cli/commands/monitoring-init.js.map +1 -0
- package/dist/cli/commands/notify-test.d.ts +9 -0
- package/dist/cli/commands/notify-test.d.ts.map +1 -0
- package/dist/cli/commands/notify-test.js +41 -0
- package/dist/cli/commands/notify-test.js.map +1 -0
- package/dist/cli/commands/rebase.d.ts +9 -0
- package/dist/cli/commands/rebase.d.ts.map +1 -0
- package/dist/cli/commands/rebase.js +63 -0
- package/dist/cli/commands/rebase.js.map +1 -0
- package/dist/cli/commands/retry.d.ts +14 -0
- package/dist/cli/commands/retry.d.ts.map +1 -0
- package/dist/cli/commands/retry.js +52 -0
- package/dist/cli/commands/retry.js.map +1 -0
- package/dist/cli/commands/run-once.d.ts +9 -0
- package/dist/cli/commands/run-once.d.ts.map +1 -0
- package/dist/cli/commands/run-once.js +62 -0
- package/dist/cli/commands/run-once.js.map +1 -0
- package/dist/cli/commands/run.d.ts +9 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +142 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +14 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +51 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/settings.d.ts +10 -0
- package/dist/cli/commands/settings.d.ts.map +1 -0
- package/dist/cli/commands/settings.js +100 -0
- package/dist/cli/commands/settings.js.map +1 -0
- package/dist/cli/commands/status.d.ts +8 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +153 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +9 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +60 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/update.d.ts +4 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +29 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +9 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +56 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/commands/web.d.ts +16 -0
- package/dist/cli/commands/web.d.ts.map +1 -0
- package/dist/cli/commands/web.js +206 -0
- package/dist/cli/commands/web.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +213 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init/detector.d.ts +7 -0
- package/dist/cli/init/detector.d.ts.map +1 -0
- package/dist/cli/init/detector.js +40 -0
- package/dist/cli/init/detector.js.map +1 -0
- package/dist/cli/init/templates.d.ts +10 -0
- package/dist/cli/init/templates.d.ts.map +1 -0
- package/dist/cli/init/templates.js +30 -0
- package/dist/cli/init/templates.js.map +1 -0
- package/dist/cli/tui/actions-bar.d.ts +21 -0
- package/dist/cli/tui/actions-bar.d.ts.map +1 -0
- package/dist/cli/tui/actions-bar.js +91 -0
- package/dist/cli/tui/actions-bar.js.map +1 -0
- package/dist/cli/tui/active-runs.d.ts +9 -0
- package/dist/cli/tui/active-runs.d.ts.map +1 -0
- package/dist/cli/tui/active-runs.js +23 -0
- package/dist/cli/tui/active-runs.js.map +1 -0
- package/dist/cli/tui/agent-stream.d.ts +10 -0
- package/dist/cli/tui/agent-stream.d.ts.map +1 -0
- package/dist/cli/tui/agent-stream.js +83 -0
- package/dist/cli/tui/agent-stream.js.map +1 -0
- package/dist/cli/tui/app.d.ts +40 -0
- package/dist/cli/tui/app.d.ts.map +1 -0
- package/dist/cli/tui/app.js +995 -0
- package/dist/cli/tui/app.js.map +1 -0
- package/dist/cli/tui/constants.d.ts +15 -0
- package/dist/cli/tui/constants.d.ts.map +1 -0
- package/dist/cli/tui/constants.js +102 -0
- package/dist/cli/tui/constants.js.map +1 -0
- package/dist/cli/tui/cost-bar.d.ts +10 -0
- package/dist/cli/tui/cost-bar.d.ts.map +1 -0
- package/dist/cli/tui/cost-bar.js +17 -0
- package/dist/cli/tui/cost-bar.js.map +1 -0
- package/dist/cli/tui/data.d.ts +56 -0
- package/dist/cli/tui/data.d.ts.map +1 -0
- package/dist/cli/tui/data.js +296 -0
- package/dist/cli/tui/data.js.map +1 -0
- package/dist/cli/tui/format.d.ts +11 -0
- package/dist/cli/tui/format.d.ts.map +1 -0
- package/dist/cli/tui/format.js +83 -0
- package/dist/cli/tui/format.js.map +1 -0
- package/dist/cli/tui/header.d.ts +16 -0
- package/dist/cli/tui/header.d.ts.map +1 -0
- package/dist/cli/tui/header.js +22 -0
- package/dist/cli/tui/header.js.map +1 -0
- package/dist/cli/tui/logs-view.d.ts +19 -0
- package/dist/cli/tui/logs-view.d.ts.map +1 -0
- package/dist/cli/tui/logs-view.js +63 -0
- package/dist/cli/tui/logs-view.js.map +1 -0
- package/dist/cli/tui/merge-queue-panel.d.ts +9 -0
- package/dist/cli/tui/merge-queue-panel.d.ts.map +1 -0
- package/dist/cli/tui/merge-queue-panel.js +14 -0
- package/dist/cli/tui/merge-queue-panel.js.map +1 -0
- package/dist/cli/tui/projects-view.d.ts +25 -0
- package/dist/cli/tui/projects-view.d.ts.map +1 -0
- package/dist/cli/tui/projects-view.js +145 -0
- package/dist/cli/tui/projects-view.js.map +1 -0
- package/dist/cli/tui/recent-runs.d.ts +9 -0
- package/dist/cli/tui/recent-runs.d.ts.map +1 -0
- package/dist/cli/tui/recent-runs.js +20 -0
- package/dist/cli/tui/recent-runs.js.map +1 -0
- package/dist/cli/tui/runs-view.d.ts +22 -0
- package/dist/cli/tui/runs-view.d.ts.map +1 -0
- package/dist/cli/tui/runs-view.js +48 -0
- package/dist/cli/tui/runs-view.js.map +1 -0
- package/dist/cli/tui/settings-view.d.ts +9 -0
- package/dist/cli/tui/settings-view.d.ts.map +1 -0
- package/dist/cli/tui/settings-view.js +21 -0
- package/dist/cli/tui/settings-view.js.map +1 -0
- package/dist/cli/tui/stats-view.d.ts +11 -0
- package/dist/cli/tui/stats-view.d.ts.map +1 -0
- package/dist/cli/tui/stats-view.js +48 -0
- package/dist/cli/tui/stats-view.js.map +1 -0
- package/dist/cli/tui/titles.d.ts +36 -0
- package/dist/cli/tui/titles.d.ts.map +1 -0
- package/dist/cli/tui/titles.js +50 -0
- package/dist/cli/tui/titles.js.map +1 -0
- package/dist/cli/tui/types.d.ts +10 -0
- package/dist/cli/tui/types.d.ts.map +1 -0
- package/dist/cli/tui/types.js +2 -0
- package/dist/cli/tui/types.js.map +1 -0
- package/dist/cli/tui/view-model.d.ts +20 -0
- package/dist/cli/tui/view-model.d.ts.map +1 -0
- package/dist/cli/tui/view-model.js +80 -0
- package/dist/cli/tui/view-model.js.map +1 -0
- package/dist/components/button/button.tui.d.ts +4 -0
- package/dist/components/button/button.tui.d.ts.map +1 -0
- package/dist/components/button/button.tui.js +16 -0
- package/dist/components/button/button.tui.js.map +1 -0
- package/dist/components/button/button.web.d.ts +4 -0
- package/dist/components/button/button.web.d.ts.map +1 -0
- package/dist/components/button/button.web.js +7 -0
- package/dist/components/button/button.web.js.map +1 -0
- package/dist/components/button/index.d.ts +5 -0
- package/dist/components/button/index.d.ts.map +1 -0
- package/dist/components/button/index.js +4 -0
- package/dist/components/button/index.js.map +1 -0
- package/dist/components/button/types.d.ts +28 -0
- package/dist/components/button/types.d.ts.map +1 -0
- package/dist/components/button/types.js +2 -0
- package/dist/components/button/types.js.map +1 -0
- package/dist/components/button/view-model.d.ts +3 -0
- package/dist/components/button/view-model.d.ts.map +1 -0
- package/dist/components/button/view-model.js +53 -0
- package/dist/components/button/view-model.js.map +1 -0
- package/dist/components/card/card.tui.d.ts +4 -0
- package/dist/components/card/card.tui.d.ts.map +1 -0
- package/dist/components/card/card.tui.js +41 -0
- package/dist/components/card/card.tui.js.map +1 -0
- package/dist/components/card/card.web.d.ts +4 -0
- package/dist/components/card/card.web.d.ts.map +1 -0
- package/dist/components/card/card.web.js +12 -0
- package/dist/components/card/card.web.js.map +1 -0
- package/dist/components/card/index.d.ts +5 -0
- package/dist/components/card/index.d.ts.map +1 -0
- package/dist/components/card/index.js +4 -0
- package/dist/components/card/index.js.map +1 -0
- package/dist/components/card/types.d.ts +22 -0
- package/dist/components/card/types.d.ts.map +1 -0
- package/dist/components/card/types.js +2 -0
- package/dist/components/card/types.js.map +1 -0
- package/dist/components/card/view-model.d.ts +3 -0
- package/dist/components/card/view-model.d.ts.map +1 -0
- package/dist/components/card/view-model.js +35 -0
- package/dist/components/card/view-model.js.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +5 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/issue-row/index.d.ts +5 -0
- package/dist/components/issue-row/index.d.ts.map +1 -0
- package/dist/components/issue-row/index.js +4 -0
- package/dist/components/issue-row/index.js.map +1 -0
- package/dist/components/issue-row/issue-row.tui.d.ts +4 -0
- package/dist/components/issue-row/issue-row.tui.d.ts.map +1 -0
- package/dist/components/issue-row/issue-row.tui.js +15 -0
- package/dist/components/issue-row/issue-row.tui.js.map +1 -0
- package/dist/components/issue-row/issue-row.web.d.ts +4 -0
- package/dist/components/issue-row/issue-row.web.d.ts.map +1 -0
- package/dist/components/issue-row/issue-row.web.js +14 -0
- package/dist/components/issue-row/issue-row.web.js.map +1 -0
- package/dist/components/issue-row/types.d.ts +18 -0
- package/dist/components/issue-row/types.d.ts.map +1 -0
- package/dist/components/issue-row/types.js +2 -0
- package/dist/components/issue-row/types.js.map +1 -0
- package/dist/components/issue-row/view-model.d.ts +3 -0
- package/dist/components/issue-row/view-model.d.ts.map +1 -0
- package/dist/components/issue-row/view-model.js +31 -0
- package/dist/components/issue-row/view-model.js.map +1 -0
- package/dist/components/modal/index.d.ts +3 -0
- package/dist/components/modal/index.d.ts.map +1 -0
- package/dist/components/modal/index.js +2 -0
- package/dist/components/modal/index.js.map +1 -0
- package/dist/components/modal/modal.web.d.ts +16 -0
- package/dist/components/modal/modal.web.d.ts.map +1 -0
- package/dist/components/modal/modal.web.js +92 -0
- package/dist/components/modal/modal.web.js.map +1 -0
- package/dist/components/modal/types.d.ts +15 -0
- package/dist/components/modal/types.d.ts.map +1 -0
- package/dist/components/modal/types.js +2 -0
- package/dist/components/modal/types.js.map +1 -0
- package/dist/config/loader.d.ts +23 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +136 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +6 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +24 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/schema.d.ts +3088 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +328 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/discovery/commands.d.ts +33 -0
- package/dist/discovery/commands.d.ts.map +1 -0
- package/dist/discovery/commands.js +82 -0
- package/dist/discovery/commands.js.map +1 -0
- package/dist/discovery/decomposer.d.ts +6 -0
- package/dist/discovery/decomposer.d.ts.map +1 -0
- package/dist/discovery/decomposer.js +102 -0
- package/dist/discovery/decomposer.js.map +1 -0
- package/dist/discovery/discover.d.ts +21 -0
- package/dist/discovery/discover.d.ts.map +1 -0
- package/dist/discovery/discover.js +98 -0
- package/dist/discovery/discover.js.map +1 -0
- package/dist/discovery/followup.d.ts +14 -0
- package/dist/discovery/followup.d.ts.map +1 -0
- package/dist/discovery/followup.js +27 -0
- package/dist/discovery/followup.js.map +1 -0
- package/dist/discovery/roles.d.ts +15 -0
- package/dist/discovery/roles.d.ts.map +1 -0
- package/dist/discovery/roles.js +36 -0
- package/dist/discovery/roles.js.map +1 -0
- package/dist/discovery/selector.d.ts +13 -0
- package/dist/discovery/selector.d.ts.map +1 -0
- package/dist/discovery/selector.js +27 -0
- package/dist/discovery/selector.js.map +1 -0
- package/dist/discovery/triage.d.ts +29 -0
- package/dist/discovery/triage.d.ts.map +1 -0
- package/dist/discovery/triage.js +61 -0
- package/dist/discovery/triage.js.map +1 -0
- package/dist/environment/bootstrap.d.ts +19 -0
- package/dist/environment/bootstrap.d.ts.map +1 -0
- package/dist/environment/bootstrap.js +89 -0
- package/dist/environment/bootstrap.js.map +1 -0
- package/dist/environment/dedicated.d.ts +17 -0
- package/dist/environment/dedicated.d.ts.map +1 -0
- package/dist/environment/dedicated.js +68 -0
- package/dist/environment/dedicated.js.map +1 -0
- package/dist/environment/env-file.d.ts +16 -0
- package/dist/environment/env-file.d.ts.map +1 -0
- package/dist/environment/env-file.js +74 -0
- package/dist/environment/env-file.js.map +1 -0
- package/dist/environment/manager.d.ts +33 -0
- package/dist/environment/manager.d.ts.map +1 -0
- package/dist/environment/manager.js +113 -0
- package/dist/environment/manager.js.map +1 -0
- package/dist/environment/port.d.ts +15 -0
- package/dist/environment/port.d.ts.map +1 -0
- package/dist/environment/port.js +21 -0
- package/dist/environment/port.js.map +1 -0
- package/dist/environment/shared.d.ts +6 -0
- package/dist/environment/shared.d.ts.map +1 -0
- package/dist/environment/shared.js +30 -0
- package/dist/environment/shared.js.map +1 -0
- package/dist/events/bus.d.ts +18 -0
- package/dist/events/bus.d.ts.map +1 -0
- package/dist/events/bus.js +70 -0
- package/dist/events/bus.js.map +1 -0
- package/dist/events/observability.d.ts +32 -0
- package/dist/events/observability.d.ts.map +1 -0
- package/dist/events/observability.js +155 -0
- package/dist/events/observability.js.map +1 -0
- package/dist/events/types.d.ts +18 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +2 -0
- package/dist/events/types.js.map +1 -0
- package/dist/forge/bot-comment.d.ts +17 -0
- package/dist/forge/bot-comment.d.ts.map +1 -0
- package/dist/forge/bot-comment.js +30 -0
- package/dist/forge/bot-comment.js.map +1 -0
- package/dist/forge/factory.d.ts +4 -0
- package/dist/forge/factory.d.ts.map +1 -0
- package/dist/forge/factory.js +31 -0
- package/dist/forge/factory.js.map +1 -0
- package/dist/forge/forgejo-client.d.ts +20 -0
- package/dist/forge/forgejo-client.d.ts.map +1 -0
- package/dist/forge/forgejo-client.js +114 -0
- package/dist/forge/forgejo-client.js.map +1 -0
- package/dist/forge/forgejo-labels.d.ts +13 -0
- package/dist/forge/forgejo-labels.d.ts.map +1 -0
- package/dist/forge/forgejo-labels.js +51 -0
- package/dist/forge/forgejo-labels.js.map +1 -0
- package/dist/forge/forgejo.d.ts +31 -0
- package/dist/forge/forgejo.d.ts.map +1 -0
- package/dist/forge/forgejo.js +264 -0
- package/dist/forge/forgejo.js.map +1 -0
- package/dist/forge/github.d.ts +31 -0
- package/dist/forge/github.d.ts.map +1 -0
- package/dist/forge/github.js +438 -0
- package/dist/forge/github.js.map +1 -0
- package/dist/forge/status-comment.d.ts +19 -0
- package/dist/forge/status-comment.d.ts.map +1 -0
- package/dist/forge/status-comment.js +44 -0
- package/dist/forge/status-comment.js.map +1 -0
- package/dist/forge/types.d.ts +118 -0
- package/dist/forge/types.d.ts.map +1 -0
- package/dist/forge/types.js +2 -0
- package/dist/forge/types.js.map +1 -0
- package/dist/git/process.d.ts +15 -0
- package/dist/git/process.d.ts.map +1 -0
- package/dist/git/process.js +38 -0
- package/dist/git/process.js.map +1 -0
- package/dist/git/repo.d.ts +64 -0
- package/dist/git/repo.d.ts.map +1 -0
- package/dist/git/repo.js +158 -0
- package/dist/git/repo.js.map +1 -0
- package/dist/git/slug.d.ts +13 -0
- package/dist/git/slug.d.ts.map +1 -0
- package/dist/git/slug.js +28 -0
- package/dist/git/slug.js.map +1 -0
- package/dist/git/worktree.d.ts +23 -0
- package/dist/git/worktree.d.ts.map +1 -0
- package/dist/git/worktree.js +200 -0
- package/dist/git/worktree.js.map +1 -0
- package/dist/labels/bootstrap.d.ts +12 -0
- package/dist/labels/bootstrap.d.ts.map +1 -0
- package/dist/labels/bootstrap.js +101 -0
- package/dist/labels/bootstrap.js.map +1 -0
- package/dist/labels/config.d.ts +6 -0
- package/dist/labels/config.d.ts.map +1 -0
- package/dist/labels/config.js +56 -0
- package/dist/labels/config.js.map +1 -0
- package/dist/labels/manager.d.ts +10 -0
- package/dist/labels/manager.d.ts.map +1 -0
- package/dist/labels/manager.js +30 -0
- package/dist/labels/manager.js.map +1 -0
- package/dist/labels/transitions.d.ts +33 -0
- package/dist/labels/transitions.d.ts.map +1 -0
- package/dist/labels/transitions.js +81 -0
- package/dist/labels/transitions.js.map +1 -0
- package/dist/loop/checkpoint.d.ts +60 -0
- package/dist/loop/checkpoint.d.ts.map +1 -0
- package/dist/loop/checkpoint.js +226 -0
- package/dist/loop/checkpoint.js.map +1 -0
- package/dist/loop/commit.d.ts +17 -0
- package/dist/loop/commit.d.ts.map +1 -0
- package/dist/loop/commit.js +65 -0
- package/dist/loop/commit.js.map +1 -0
- package/dist/loop/context.d.ts +11 -0
- package/dist/loop/context.d.ts.map +1 -0
- package/dist/loop/context.js +26 -0
- package/dist/loop/context.js.map +1 -0
- package/dist/loop/cost.d.ts +79 -0
- package/dist/loop/cost.d.ts.map +1 -0
- package/dist/loop/cost.js +223 -0
- package/dist/loop/cost.js.map +1 -0
- package/dist/loop/decision.d.ts +22 -0
- package/dist/loop/decision.d.ts.map +1 -0
- package/dist/loop/decision.js +118 -0
- package/dist/loop/decision.js.map +1 -0
- package/dist/loop/diff-guard.d.ts +21 -0
- package/dist/loop/diff-guard.d.ts.map +1 -0
- package/dist/loop/diff-guard.js +52 -0
- package/dist/loop/diff-guard.js.map +1 -0
- package/dist/loop/engine.d.ts +32 -0
- package/dist/loop/engine.d.ts.map +1 -0
- package/dist/loop/engine.js +376 -0
- package/dist/loop/engine.js.map +1 -0
- package/dist/loop/parallel.d.ts +20 -0
- package/dist/loop/parallel.d.ts.map +1 -0
- package/dist/loop/parallel.js +144 -0
- package/dist/loop/parallel.js.map +1 -0
- package/dist/loop/plan-summary-comment.d.ts +5 -0
- package/dist/loop/plan-summary-comment.d.ts.map +1 -0
- package/dist/loop/plan-summary-comment.js +89 -0
- package/dist/loop/plan-summary-comment.js.map +1 -0
- package/dist/loop/pricing.d.ts +23 -0
- package/dist/loop/pricing.d.ts.map +1 -0
- package/dist/loop/pricing.js +64 -0
- package/dist/loop/pricing.js.map +1 -0
- package/dist/loop/review-feedback.d.ts +12 -0
- package/dist/loop/review-feedback.d.ts.map +1 -0
- package/dist/loop/review-feedback.js +55 -0
- package/dist/loop/review-feedback.js.map +1 -0
- package/dist/loop/step-executor.d.ts +70 -0
- package/dist/loop/step-executor.d.ts.map +1 -0
- package/dist/loop/step-executor.js +328 -0
- package/dist/loop/step-executor.js.map +1 -0
- package/dist/loop/supervisor.d.ts +9 -0
- package/dist/loop/supervisor.d.ts.map +1 -0
- package/dist/loop/supervisor.js +22 -0
- package/dist/loop/supervisor.js.map +1 -0
- package/dist/loop/types.d.ts +66 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +2 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/loop/verifier.d.ts +9 -0
- package/dist/loop/verifier.d.ts.map +1 -0
- package/dist/loop/verifier.js +59 -0
- package/dist/loop/verifier.js.map +1 -0
- package/dist/loop/workflow.d.ts +38 -0
- package/dist/loop/workflow.d.ts.map +1 -0
- package/dist/loop/workflow.js +64 -0
- package/dist/loop/workflow.js.map +1 -0
- package/dist/mcp/http.d.ts +8 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +76 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/resources/index.d.ts +11 -0
- package/dist/mcp/resources/index.d.ts.map +1 -0
- package/dist/mcp/resources/index.js +137 -0
- package/dist/mcp/resources/index.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +54 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +19 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +847 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mentions/manager.d.ts +12 -0
- package/dist/mentions/manager.d.ts.map +1 -0
- package/dist/mentions/manager.js +50 -0
- package/dist/mentions/manager.js.map +1 -0
- package/dist/mentions/resolver.d.ts +12 -0
- package/dist/mentions/resolver.d.ts.map +1 -0
- package/dist/mentions/resolver.js +26 -0
- package/dist/mentions/resolver.js.map +1 -0
- package/dist/mentions/tracker.d.ts +8 -0
- package/dist/mentions/tracker.d.ts.map +1 -0
- package/dist/mentions/tracker.js +18 -0
- package/dist/mentions/tracker.js.map +1 -0
- package/dist/merge-queue/batch.d.ts +19 -0
- package/dist/merge-queue/batch.d.ts.map +1 -0
- package/dist/merge-queue/batch.js +100 -0
- package/dist/merge-queue/batch.js.map +1 -0
- package/dist/merge-queue/bisect.d.ts +10 -0
- package/dist/merge-queue/bisect.d.ts.map +1 -0
- package/dist/merge-queue/bisect.js +18 -0
- package/dist/merge-queue/bisect.js.map +1 -0
- package/dist/merge-queue/eligibility.d.ts +25 -0
- package/dist/merge-queue/eligibility.d.ts.map +1 -0
- package/dist/merge-queue/eligibility.js +125 -0
- package/dist/merge-queue/eligibility.js.map +1 -0
- package/dist/merge-queue/finalize.d.ts +7 -0
- package/dist/merge-queue/finalize.d.ts.map +1 -0
- package/dist/merge-queue/finalize.js +36 -0
- package/dist/merge-queue/finalize.js.map +1 -0
- package/dist/merge-queue/runner.d.ts +13 -0
- package/dist/merge-queue/runner.d.ts.map +1 -0
- package/dist/merge-queue/runner.js +246 -0
- package/dist/merge-queue/runner.js.map +1 -0
- package/dist/merge-queue/staging.d.ts +13 -0
- package/dist/merge-queue/staging.d.ts.map +1 -0
- package/dist/merge-queue/staging.js +88 -0
- package/dist/merge-queue/staging.js.map +1 -0
- package/dist/merge-queue/types.d.ts +23 -0
- package/dist/merge-queue/types.d.ts.map +1 -0
- package/dist/merge-queue/types.js +2 -0
- package/dist/merge-queue/types.js.map +1 -0
- package/dist/metrics/collectors.d.ts +24 -0
- package/dist/metrics/collectors.d.ts.map +1 -0
- package/dist/metrics/collectors.js +132 -0
- package/dist/metrics/collectors.js.map +1 -0
- package/dist/metrics/server.d.ts +8 -0
- package/dist/metrics/server.d.ts.map +1 -0
- package/dist/metrics/server.js +41 -0
- package/dist/metrics/server.js.map +1 -0
- package/dist/metrics/service.d.ts +26 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +161 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/notify/channels/console.d.ts +10 -0
- package/dist/notify/channels/console.d.ts.map +1 -0
- package/dist/notify/channels/console.js +17 -0
- package/dist/notify/channels/console.js.map +1 -0
- package/dist/notify/channels/discord.d.ts +13 -0
- package/dist/notify/channels/discord.d.ts.map +1 -0
- package/dist/notify/channels/discord.js +183 -0
- package/dist/notify/channels/discord.js.map +1 -0
- package/dist/notify/channels/github-comment.d.ts +13 -0
- package/dist/notify/channels/github-comment.d.ts.map +1 -0
- package/dist/notify/channels/github-comment.js +52 -0
- package/dist/notify/channels/github-comment.js.map +1 -0
- package/dist/notify/channels/smtp.d.ts +17 -0
- package/dist/notify/channels/smtp.d.ts.map +1 -0
- package/dist/notify/channels/smtp.js +65 -0
- package/dist/notify/channels/smtp.js.map +1 -0
- package/dist/notify/channels/webhook-common.d.ts +19 -0
- package/dist/notify/channels/webhook-common.d.ts.map +1 -0
- package/dist/notify/channels/webhook-common.js +111 -0
- package/dist/notify/channels/webhook-common.js.map +1 -0
- package/dist/notify/channels/webhook.d.ts +13 -0
- package/dist/notify/channels/webhook.d.ts.map +1 -0
- package/dist/notify/channels/webhook.js +72 -0
- package/dist/notify/channels/webhook.js.map +1 -0
- package/dist/notify/dispatcher.d.ts +11 -0
- package/dist/notify/dispatcher.d.ts.map +1 -0
- package/dist/notify/dispatcher.js +66 -0
- package/dist/notify/dispatcher.js.map +1 -0
- package/dist/notify/factory.d.ts +5 -0
- package/dist/notify/factory.d.ts.map +1 -0
- package/dist/notify/factory.js +46 -0
- package/dist/notify/factory.js.map +1 -0
- package/dist/notify/payload.d.ts +8 -0
- package/dist/notify/payload.d.ts.map +1 -0
- package/dist/notify/payload.js +37 -0
- package/dist/notify/payload.js.map +1 -0
- package/dist/notify/types.d.ts +34 -0
- package/dist/notify/types.d.ts.map +1 -0
- package/dist/notify/types.js +2 -0
- package/dist/notify/types.js.map +1 -0
- package/dist/ops/auto-cleanup.d.ts +14 -0
- package/dist/ops/auto-cleanup.d.ts.map +1 -0
- package/dist/ops/auto-cleanup.js +58 -0
- package/dist/ops/auto-cleanup.js.map +1 -0
- package/dist/ops/cleanup.d.ts +32 -0
- package/dist/ops/cleanup.d.ts.map +1 -0
- package/dist/ops/cleanup.js +208 -0
- package/dist/ops/cleanup.js.map +1 -0
- package/dist/ops/continue.d.ts +12 -0
- package/dist/ops/continue.d.ts.map +1 -0
- package/dist/ops/continue.js +240 -0
- package/dist/ops/continue.js.map +1 -0
- package/dist/ops/cost-override.d.ts +16 -0
- package/dist/ops/cost-override.d.ts.map +1 -0
- package/dist/ops/cost-override.js +28 -0
- package/dist/ops/cost-override.js.map +1 -0
- package/dist/ops/daily-cost-override.d.ts +15 -0
- package/dist/ops/daily-cost-override.d.ts.map +1 -0
- package/dist/ops/daily-cost-override.js +23 -0
- package/dist/ops/daily-cost-override.js.map +1 -0
- package/dist/ops/delete-entry.d.ts +44 -0
- package/dist/ops/delete-entry.d.ts.map +1 -0
- package/dist/ops/delete-entry.js +381 -0
- package/dist/ops/delete-entry.js.map +1 -0
- package/dist/ops/labels-init.d.ts +43 -0
- package/dist/ops/labels-init.d.ts.map +1 -0
- package/dist/ops/labels-init.js +149 -0
- package/dist/ops/labels-init.js.map +1 -0
- package/dist/ops/rebase-and-check.d.ts +34 -0
- package/dist/ops/rebase-and-check.d.ts.map +1 -0
- package/dist/ops/rebase-and-check.js +110 -0
- package/dist/ops/rebase-and-check.js.map +1 -0
- package/dist/ops/rebase.d.ts +18 -0
- package/dist/ops/rebase.d.ts.map +1 -0
- package/dist/ops/rebase.js +67 -0
- package/dist/ops/rebase.js.map +1 -0
- package/dist/ops/retention.d.ts +29 -0
- package/dist/ops/retention.d.ts.map +1 -0
- package/dist/ops/retention.js +120 -0
- package/dist/ops/retention.js.map +1 -0
- package/dist/ops/retry.d.ts +19 -0
- package/dist/ops/retry.d.ts.map +1 -0
- package/dist/ops/retry.js +106 -0
- package/dist/ops/retry.js.map +1 -0
- package/dist/ops/summary.d.ts +42 -0
- package/dist/ops/summary.d.ts.map +1 -0
- package/dist/ops/summary.js +86 -0
- package/dist/ops/summary.js.map +1 -0
- package/dist/ops/sync.d.ts +47 -0
- package/dist/ops/sync.d.ts.map +1 -0
- package/dist/ops/sync.js +445 -0
- package/dist/ops/sync.js.map +1 -0
- package/dist/planning/mode.d.ts +14 -0
- package/dist/planning/mode.d.ts.map +1 -0
- package/dist/planning/mode.js +33 -0
- package/dist/planning/mode.js.map +1 -0
- package/dist/poller/control.d.ts +21 -0
- package/dist/poller/control.d.ts.map +1 -0
- package/dist/poller/control.js +42 -0
- package/dist/poller/control.js.map +1 -0
- package/dist/poller/shutdown.d.ts +20 -0
- package/dist/poller/shutdown.d.ts.map +1 -0
- package/dist/poller/shutdown.js +94 -0
- package/dist/poller/shutdown.js.map +1 -0
- package/dist/publishing/pr-body.d.ts +25 -0
- package/dist/publishing/pr-body.d.ts.map +1 -0
- package/dist/publishing/pr-body.js +119 -0
- package/dist/publishing/pr-body.js.map +1 -0
- package/dist/publishing/publisher.d.ts +19 -0
- package/dist/publishing/publisher.d.ts.map +1 -0
- package/dist/publishing/publisher.js +116 -0
- package/dist/publishing/publisher.js.map +1 -0
- package/dist/publishing/push.d.ts +13 -0
- package/dist/publishing/push.d.ts.map +1 -0
- package/dist/publishing/push.js +56 -0
- package/dist/publishing/push.js.map +1 -0
- package/dist/reactions/handler.d.ts +20 -0
- package/dist/reactions/handler.d.ts.map +1 -0
- package/dist/reactions/handler.js +50 -0
- package/dist/reactions/handler.js.map +1 -0
- package/dist/reactions/scanner.d.ts +13 -0
- package/dist/reactions/scanner.d.ts.map +1 -0
- package/dist/reactions/scanner.js +141 -0
- package/dist/reactions/scanner.js.map +1 -0
- package/dist/reactions/types.d.ts +41 -0
- package/dist/reactions/types.d.ts.map +1 -0
- package/dist/reactions/types.js +2 -0
- package/dist/reactions/types.js.map +1 -0
- package/dist/runner/poller.d.ts +19 -0
- package/dist/runner/poller.d.ts.map +1 -0
- package/dist/runner/poller.js +1358 -0
- package/dist/runner/poller.js.map +1 -0
- package/dist/settings/registry.d.ts +37 -0
- package/dist/settings/registry.d.ts.map +1 -0
- package/dist/settings/registry.js +299 -0
- package/dist/settings/registry.js.map +1 -0
- package/dist/settings/runtime.d.ts +33 -0
- package/dist/settings/runtime.d.ts.map +1 -0
- package/dist/settings/runtime.js +148 -0
- package/dist/settings/runtime.js.map +1 -0
- package/dist/state/db.d.ts +3 -0
- package/dist/state/db.d.ts.map +1 -0
- package/dist/state/db.js +80 -0
- package/dist/state/db.js.map +1 -0
- package/dist/state/issues.d.ts +47 -0
- package/dist/state/issues.d.ts.map +1 -0
- package/dist/state/issues.js +188 -0
- package/dist/state/issues.js.map +1 -0
- package/dist/state/leases.d.ts +27 -0
- package/dist/state/leases.d.ts.map +1 -0
- package/dist/state/leases.js +75 -0
- package/dist/state/leases.js.map +1 -0
- package/dist/state/migrations/001-initial.d.ts +3 -0
- package/dist/state/migrations/001-initial.d.ts.map +1 -0
- package/dist/state/migrations/001-initial.js +70 -0
- package/dist/state/migrations/001-initial.js.map +1 -0
- package/dist/state/migrations/002-placeholder.d.ts +7 -0
- package/dist/state/migrations/002-placeholder.d.ts.map +1 -0
- package/dist/state/migrations/002-placeholder.js +8 -0
- package/dist/state/migrations/002-placeholder.js.map +1 -0
- package/dist/state/migrations/003-mention-tracking.d.ts +3 -0
- package/dist/state/migrations/003-mention-tracking.d.ts.map +1 -0
- package/dist/state/migrations/003-mention-tracking.js +13 -0
- package/dist/state/migrations/003-mention-tracking.js.map +1 -0
- package/dist/state/migrations/004-command-tracking.d.ts +3 -0
- package/dist/state/migrations/004-command-tracking.d.ts.map +1 -0
- package/dist/state/migrations/004-command-tracking.js +13 -0
- package/dist/state/migrations/004-command-tracking.js.map +1 -0
- package/dist/state/migrations/005-block-reason.d.ts +3 -0
- package/dist/state/migrations/005-block-reason.d.ts.map +1 -0
- package/dist/state/migrations/005-block-reason.js +4 -0
- package/dist/state/migrations/005-block-reason.js.map +1 -0
- package/dist/state/migrations/006-parent-run.d.ts +3 -0
- package/dist/state/migrations/006-parent-run.d.ts.map +1 -0
- package/dist/state/migrations/006-parent-run.js +4 -0
- package/dist/state/migrations/006-parent-run.js.map +1 -0
- package/dist/state/migrations/007-merge-queue.d.ts +3 -0
- package/dist/state/migrations/007-merge-queue.d.ts.map +1 -0
- package/dist/state/migrations/007-merge-queue.js +21 -0
- package/dist/state/migrations/007-merge-queue.js.map +1 -0
- package/dist/state/migrations/008-agent-events.d.ts +3 -0
- package/dist/state/migrations/008-agent-events.d.ts.map +1 -0
- package/dist/state/migrations/008-agent-events.js +15 -0
- package/dist/state/migrations/008-agent-events.js.map +1 -0
- package/dist/state/migrations/009-run-titles.d.ts +3 -0
- package/dist/state/migrations/009-run-titles.d.ts.map +1 -0
- package/dist/state/migrations/009-run-titles.js +11 -0
- package/dist/state/migrations/009-run-titles.js.map +1 -0
- package/dist/state/migrations/010-issues.d.ts +9 -0
- package/dist/state/migrations/010-issues.d.ts.map +1 -0
- package/dist/state/migrations/010-issues.js +193 -0
- package/dist/state/migrations/010-issues.js.map +1 -0
- package/dist/state/migrations/011-rebuild-issues-from-latest-run.d.ts +8 -0
- package/dist/state/migrations/011-rebuild-issues-from-latest-run.d.ts.map +1 -0
- package/dist/state/migrations/011-rebuild-issues-from-latest-run.js +125 -0
- package/dist/state/migrations/011-rebuild-issues-from-latest-run.js.map +1 -0
- package/dist/state/migrations/012-settings-overrides.d.ts +3 -0
- package/dist/state/migrations/012-settings-overrides.d.ts.map +1 -0
- package/dist/state/migrations/012-settings-overrides.js +14 -0
- package/dist/state/migrations/012-settings-overrides.js.map +1 -0
- package/dist/state/migrations/013-token-usage.d.ts +3 -0
- package/dist/state/migrations/013-token-usage.d.ts.map +1 -0
- package/dist/state/migrations/013-token-usage.js +21 -0
- package/dist/state/migrations/013-token-usage.js.map +1 -0
- package/dist/state/migrations/014-daily-run-usage.d.ts +3 -0
- package/dist/state/migrations/014-daily-run-usage.d.ts.map +1 -0
- package/dist/state/migrations/014-daily-run-usage.js +14 -0
- package/dist/state/migrations/014-daily-run-usage.js.map +1 -0
- package/dist/state/migrations/015-run-cost-override.d.ts +11 -0
- package/dist/state/migrations/015-run-cost-override.d.ts.map +1 -0
- package/dist/state/migrations/015-run-cost-override.js +20 -0
- package/dist/state/migrations/015-run-cost-override.js.map +1 -0
- package/dist/state/migrations/016-daily-cost-cap-override.d.ts +15 -0
- package/dist/state/migrations/016-daily-cost-cap-override.d.ts.map +1 -0
- package/dist/state/migrations/016-daily-cost-cap-override.js +24 -0
- package/dist/state/migrations/016-daily-cost-cap-override.js.map +1 -0
- package/dist/state/migrations/017-merge-batch-merged-prs.d.ts +13 -0
- package/dist/state/migrations/017-merge-batch-merged-prs.d.ts.map +1 -0
- package/dist/state/migrations/017-merge-batch-merged-prs.js +22 -0
- package/dist/state/migrations/017-merge-batch-merged-prs.js.map +1 -0
- package/dist/state/migrations/018-run-retry-count.d.ts +13 -0
- package/dist/state/migrations/018-run-retry-count.d.ts.map +1 -0
- package/dist/state/migrations/018-run-retry-count.js +22 -0
- package/dist/state/migrations/018-run-retry-count.js.map +1 -0
- package/dist/state/migrations/019-runs-active-index-top-level.d.ts +16 -0
- package/dist/state/migrations/019-runs-active-index-top-level.d.ts.map +1 -0
- package/dist/state/migrations/019-runs-active-index-top-level.js +23 -0
- package/dist/state/migrations/019-runs-active-index-top-level.js.map +1 -0
- package/dist/state/runs.d.ts +100 -0
- package/dist/state/runs.d.ts.map +1 -0
- package/dist/state/runs.js +321 -0
- package/dist/state/runs.js.map +1 -0
- package/dist/state/settings.d.ts +16 -0
- package/dist/state/settings.d.ts.map +1 -0
- package/dist/state/settings.js +55 -0
- package/dist/state/settings.js.map +1 -0
- package/dist/state/stats.d.ts +133 -0
- package/dist/state/stats.d.ts.map +1 -0
- package/dist/state/stats.js +419 -0
- package/dist/state/stats.js.map +1 -0
- package/dist/supervisor/health.d.ts +24 -0
- package/dist/supervisor/health.d.ts.map +1 -0
- package/dist/supervisor/health.js +186 -0
- package/dist/supervisor/health.js.map +1 -0
- package/dist/supervisor/index.d.ts +31 -0
- package/dist/supervisor/index.d.ts.map +1 -0
- package/dist/supervisor/index.js +387 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/supervisor/status.d.ts +18 -0
- package/dist/supervisor/status.d.ts.map +1 -0
- package/dist/supervisor/status.js +30 -0
- package/dist/supervisor/status.js.map +1 -0
- package/dist/supervisor/updater.d.ts +18 -0
- package/dist/supervisor/updater.d.ts.map +1 -0
- package/dist/supervisor/updater.js +108 -0
- package/dist/supervisor/updater.js.map +1 -0
- package/dist/utils/build-info.d.ts +6 -0
- package/dist/utils/build-info.d.ts.map +1 -0
- package/dist/utils/build-info.js +56 -0
- package/dist/utils/build-info.js.map +1 -0
- package/dist/utils/command.d.ts +8 -0
- package/dist/utils/command.d.ts.map +1 -0
- package/dist/utils/command.js +71 -0
- package/dist/utils/command.js.map +1 -0
- package/dist/utils/ids.d.ts +4 -0
- package/dist/utils/ids.d.ts.map +1 -0
- package/dist/utils/ids.js +17 -0
- package/dist/utils/ids.js.map +1 -0
- package/dist/utils/install-method.d.ts +4 -0
- package/dist/utils/install-method.d.ts.map +1 -0
- package/dist/utils/install-method.js +13 -0
- package/dist/utils/install-method.js.map +1 -0
- package/dist/utils/issue-repo.d.ts +2 -0
- package/dist/utils/issue-repo.d.ts.map +1 -0
- package/dist/utils/issue-repo.js +7 -0
- package/dist/utils/issue-repo.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +63 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-root.d.ts +8 -0
- package/dist/utils/project-root.d.ts.map +1 -0
- package/dist/utils/project-root.js +22 -0
- package/dist/utils/project-root.js.map +1 -0
- package/dist/utils/sanitize-error.d.ts +36 -0
- package/dist/utils/sanitize-error.d.ts.map +1 -0
- package/dist/utils/sanitize-error.js +89 -0
- package/dist/utils/sanitize-error.js.map +1 -0
- package/dist/utils/time.d.ts +7 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +47 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/web/server.d.ts +14 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +1185 -0
- package/dist/web/server.js.map +1 -0
- package/dist/workers/acp.d.ts +9 -0
- package/dist/workers/acp.d.ts.map +1 -0
- package/dist/workers/acp.js +190 -0
- package/dist/workers/acp.js.map +1 -0
- package/dist/workers/acpx-imports.d.ts +18 -0
- package/dist/workers/acpx-imports.d.ts.map +1 -0
- package/dist/workers/acpx-imports.js +43 -0
- package/dist/workers/acpx-imports.js.map +1 -0
- package/dist/workers/claude.d.ts +9 -0
- package/dist/workers/claude.d.ts.map +1 -0
- package/dist/workers/claude.js +341 -0
- package/dist/workers/claude.js.map +1 -0
- package/dist/workers/codex.d.ts +21 -0
- package/dist/workers/codex.d.ts.map +1 -0
- package/dist/workers/codex.js +337 -0
- package/dist/workers/codex.js.map +1 -0
- package/dist/workers/command.d.ts +6 -0
- package/dist/workers/command.d.ts.map +1 -0
- package/dist/workers/command.js +15 -0
- package/dist/workers/command.js.map +1 -0
- package/dist/workers/env.d.ts +18 -0
- package/dist/workers/env.d.ts.map +1 -0
- package/dist/workers/env.js +172 -0
- package/dist/workers/env.js.map +1 -0
- package/dist/workers/events.d.ts +7 -0
- package/dist/workers/events.d.ts.map +1 -0
- package/dist/workers/events.js +30 -0
- package/dist/workers/events.js.map +1 -0
- package/dist/workers/factory.d.ts +6 -0
- package/dist/workers/factory.d.ts.map +1 -0
- package/dist/workers/factory.js +13 -0
- package/dist/workers/factory.js.map +1 -0
- package/dist/workers/parsers/coder.d.ts +6 -0
- package/dist/workers/parsers/coder.d.ts.map +1 -0
- package/dist/workers/parsers/coder.js +59 -0
- package/dist/workers/parsers/coder.js.map +1 -0
- package/dist/workers/parsers/decomposer.d.ts +16 -0
- package/dist/workers/parsers/decomposer.d.ts.map +1 -0
- package/dist/workers/parsers/decomposer.js +35 -0
- package/dist/workers/parsers/decomposer.js.map +1 -0
- package/dist/workers/parsers/extract.d.ts +41 -0
- package/dist/workers/parsers/extract.d.ts.map +1 -0
- package/dist/workers/parsers/extract.js +231 -0
- package/dist/workers/parsers/extract.js.map +1 -0
- package/dist/workers/parsers/planner.d.ts +6 -0
- package/dist/workers/parsers/planner.d.ts.map +1 -0
- package/dist/workers/parsers/planner.js +77 -0
- package/dist/workers/parsers/planner.js.map +1 -0
- package/dist/workers/parsers/reviewer.d.ts +6 -0
- package/dist/workers/parsers/reviewer.d.ts.map +1 -0
- package/dist/workers/parsers/reviewer.js +105 -0
- package/dist/workers/parsers/reviewer.js.map +1 -0
- package/dist/workers/prompt/compiler.d.ts +11 -0
- package/dist/workers/prompt/compiler.d.ts.map +1 -0
- package/dist/workers/prompt/compiler.js +199 -0
- package/dist/workers/prompt/compiler.js.map +1 -0
- package/dist/workers/prompt/templates.d.ts +13 -0
- package/dist/workers/prompt/templates.d.ts.map +1 -0
- package/dist/workers/prompt/templates.js +110 -0
- package/dist/workers/prompt/templates.js.map +1 -0
- package/dist/workers/registry.d.ts +9 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +21 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/streaming-exec.d.ts +26 -0
- package/dist/workers/streaming-exec.d.ts.map +1 -0
- package/dist/workers/streaming-exec.js +166 -0
- package/dist/workers/streaming-exec.js.map +1 -0
- package/dist/workers/timeout.d.ts +17 -0
- package/dist/workers/timeout.d.ts.map +1 -0
- package/dist/workers/timeout.js +37 -0
- package/dist/workers/timeout.js.map +1 -0
- package/dist/workers/types.d.ts +120 -0
- package/dist/workers/types.d.ts.map +1 -0
- package/dist/workers/types.js +2 -0
- package/dist/workers/types.js.map +1 -0
- package/docker-compose.example.yaml +29 -0
- package/examples/config.example.yaml +256 -0
- package/examples/night-orch.service +40 -0
- package/monitoring/grafana/dashboards/night-orch.json +140 -0
- package/monitoring/grafana/provisioning/dashboards/dashboards.yml +10 -0
- package/monitoring/grafana/provisioning/datasources/prometheus.yml +9 -0
- package/monitoring/prometheus.yml +8 -0
- package/package.json +104 -0
- package/web/dist/assets/index-CBFNqVuV.js +9 -0
- package/web/dist/assets/index-RCNGmuI2.css +1 -0
- package/web/dist/icon.svg +10 -0
- package/web/dist/index.html +22 -0
- package/web/dist/manifest.webmanifest +16 -0
- package/web/dist/sw.js +58 -0
|
@@ -0,0 +1,1358 @@
|
|
|
1
|
+
import { createForgeAdapter } from '../forge/factory.js';
|
|
2
|
+
import { LeaseManager } from '../state/leases.js';
|
|
3
|
+
import { RunManager } from '../state/runs.js';
|
|
4
|
+
import { IssueManager } from '../state/issues.js';
|
|
5
|
+
import { discoverEligibleIssues } from '../discovery/discover.js';
|
|
6
|
+
import { resolveRoles } from '../discovery/roles.js';
|
|
7
|
+
import { adjustLimitsForTriage } from '../discovery/triage.js';
|
|
8
|
+
import { getOrPinSlug, buildWorktreePath } from '../git/slug.js';
|
|
9
|
+
import { createWorktreeManager } from '../git/worktree.js';
|
|
10
|
+
import { resolveEnvironmentMode, setupEnvironment, teardownEnvironment, } from '../environment/manager.js';
|
|
11
|
+
import { createWorkerAdapter } from '../workers/factory.js';
|
|
12
|
+
import { executeLoop } from '../loop/engine.js';
|
|
13
|
+
import { resolveWorkflow } from '../loop/workflow.js';
|
|
14
|
+
import { publishPR } from '../publishing/publisher.js';
|
|
15
|
+
import { MergeConflictError } from '../publishing/push.js';
|
|
16
|
+
import { transitionLabels } from '../labels/manager.js';
|
|
17
|
+
import { buildLabelConfig } from '../labels/config.js';
|
|
18
|
+
import { NotificationDispatcher } from '../notify/dispatcher.js';
|
|
19
|
+
import { createChannels } from '../notify/factory.js';
|
|
20
|
+
import { CostTracker } from '../loop/cost.js';
|
|
21
|
+
import { branchName } from '../utils/ids.js';
|
|
22
|
+
import { logger } from '../utils/logger.js';
|
|
23
|
+
import { nowUtcIso } from '../utils/time.js';
|
|
24
|
+
import { postPlanSummaryComment } from '../loop/plan-summary-comment.js';
|
|
25
|
+
import { executeRebase, queueRebase } from '../ops/rebase-and-check.js';
|
|
26
|
+
import { queueContinue } from '../ops/continue.js';
|
|
27
|
+
import { markerTag, upsertBotComment } from '../forge/bot-comment.js';
|
|
28
|
+
import { formatStatusComment } from '../forge/status-comment.js';
|
|
29
|
+
import { scanForReactions } from '../reactions/scanner.js';
|
|
30
|
+
import { handleReaction } from '../reactions/handler.js';
|
|
31
|
+
import { processMergeQueue } from '../merge-queue/runner.js';
|
|
32
|
+
import { decomposeIssue, shouldAttemptDecompose } from '../discovery/decomposer.js';
|
|
33
|
+
import { executeParallelSubtasks } from '../loop/parallel.js';
|
|
34
|
+
import { buildWorkerEnv } from '../workers/env.js';
|
|
35
|
+
import { isPlanningIssue } from '../planning/mode.js';
|
|
36
|
+
import { resolveIssueRepo } from '../utils/issue-repo.js';
|
|
37
|
+
import { isCommandProcessed, markCommandProcessed, parseOrchCommands, } from '../discovery/commands.js';
|
|
38
|
+
import { AgentObservability, setActiveAgentObservability, clearActiveAgentObservability, } from '../events/observability.js';
|
|
39
|
+
const STATUS_MARKER = markerTag('status');
|
|
40
|
+
/**
|
|
41
|
+
* Process one poll cycle: discover eligible issues, claim and process.
|
|
42
|
+
* Repositories are processed in parallel; each repo runs up to
|
|
43
|
+
* `repo.maxConcurrentRuns` issues concurrently (default: 1).
|
|
44
|
+
*/
|
|
45
|
+
export async function pollOnce(config, db, dryRun, metrics, targetIssue) {
|
|
46
|
+
const leaseManager = new LeaseManager(db);
|
|
47
|
+
const runManager = new RunManager(db);
|
|
48
|
+
const issueManager = new IssueManager(db);
|
|
49
|
+
const worktreeManager = createWorktreeManager();
|
|
50
|
+
const costTracker = new CostTracker(db);
|
|
51
|
+
let processed = 0;
|
|
52
|
+
let errors = 0;
|
|
53
|
+
const immediateFollowupRepos = new Set();
|
|
54
|
+
const observability = new AgentObservability(db, config);
|
|
55
|
+
setActiveAgentObservability(observability);
|
|
56
|
+
try {
|
|
57
|
+
// Update active runs gauge
|
|
58
|
+
try {
|
|
59
|
+
const activeRuns = runManager.getActive();
|
|
60
|
+
metrics?.setActiveRuns(activeRuns.length);
|
|
61
|
+
metrics?.setDailyCost(costTracker.getDailyCost());
|
|
62
|
+
}
|
|
63
|
+
catch { /* best-effort */ }
|
|
64
|
+
// Clean expired leases
|
|
65
|
+
leaseManager.cleanExpired();
|
|
66
|
+
const reposToProcess = targetIssue
|
|
67
|
+
? config.repos.filter((repoConfig) => {
|
|
68
|
+
const issueRepos = new Set([repoConfig.repo, ...(repoConfig.linkedProjects ?? [])]);
|
|
69
|
+
return issueRepos.has(targetIssue.repo);
|
|
70
|
+
})
|
|
71
|
+
: config.repos;
|
|
72
|
+
const usedPortsInPass = [];
|
|
73
|
+
const repoResults = await Promise.all(reposToProcess.map(async (repoConfig) => {
|
|
74
|
+
let repoProcessed = 0;
|
|
75
|
+
let repoErrors = 0;
|
|
76
|
+
const repoImmediateFollowupRepos = new Set();
|
|
77
|
+
try {
|
|
78
|
+
const forge = createForgeAdapter(repoConfig, config);
|
|
79
|
+
const channels = createChannels(config.notifications, forge);
|
|
80
|
+
const notifier = new NotificationDispatcher(channels, config.notifications.events);
|
|
81
|
+
// Resolve bot user for comment upserts (best-effort, fallback to empty string)
|
|
82
|
+
let botUser = '';
|
|
83
|
+
try {
|
|
84
|
+
const authInfo = await forge.validateAuth();
|
|
85
|
+
botUser = authInfo.user;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
logger.debug({ repo: repoConfig.repo }, 'Could not resolve bot user for comment upserts');
|
|
89
|
+
}
|
|
90
|
+
// --- Reaction scan: check review_ready PRs for CI failures or human reviews ---
|
|
91
|
+
try {
|
|
92
|
+
await scanAndHandleReactions({
|
|
93
|
+
db, forge, runManager, repoConfig, botUser,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
logger.warn({ repo: repoConfig.repo, err }, 'Reaction scan failed — continuing with issue discovery');
|
|
98
|
+
}
|
|
99
|
+
// --- Merge queue: process pending merges before discovering new work ---
|
|
100
|
+
try {
|
|
101
|
+
await processMergeQueue(db, forge, repoConfig);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
logger.warn({ repo: repoConfig.repo, err }, 'Merge queue processing failed — continuing');
|
|
105
|
+
}
|
|
106
|
+
// --- Comment commands: /orch retry|rebase|continue|cancel ---
|
|
107
|
+
try {
|
|
108
|
+
await processCommentCommands({
|
|
109
|
+
config,
|
|
110
|
+
db,
|
|
111
|
+
forge,
|
|
112
|
+
runManager,
|
|
113
|
+
leaseManager,
|
|
114
|
+
repoConfig,
|
|
115
|
+
botUser,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
logger.warn({ repo: repoConfig.repo, err }, 'Comment command processing failed — continuing');
|
|
120
|
+
}
|
|
121
|
+
const discoveredAll = await discoverEligibleIssues(repoConfig, forge, leaseManager);
|
|
122
|
+
const discovered = targetIssue
|
|
123
|
+
? discoveredAll.filter((d) => {
|
|
124
|
+
const issueRepo = d.issueRepo || d.issue.repo || repoConfig.repo;
|
|
125
|
+
return d.issue.number === targetIssue.issueNumber && issueRepo === targetIssue.repo;
|
|
126
|
+
})
|
|
127
|
+
: prioritizeDiscoveredIssues(runManager, repoConfig.repo, discoveredAll);
|
|
128
|
+
issueManager.upsertDiscovered(discovered.map((d) => ({
|
|
129
|
+
repo: d.issueRepo || d.issue.repo || repoConfig.repo,
|
|
130
|
+
issueNumber: d.issue.number,
|
|
131
|
+
issueNodeId: d.issue.nodeId,
|
|
132
|
+
issueTitle: d.issue.title,
|
|
133
|
+
})));
|
|
134
|
+
try {
|
|
135
|
+
metrics?.setEligibleIssues(repoConfig.repo, discovered.length);
|
|
136
|
+
}
|
|
137
|
+
catch { /* best-effort */ }
|
|
138
|
+
if (discovered.length === 0) {
|
|
139
|
+
logger.debug({ repo: repoConfig.repo }, 'No eligible issues');
|
|
140
|
+
return {
|
|
141
|
+
processed: repoProcessed,
|
|
142
|
+
errors: repoErrors,
|
|
143
|
+
immediateFollowupRepos: [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (dryRun) {
|
|
147
|
+
for (const d of discovered) {
|
|
148
|
+
logger.info({ issue: d.issue.number, triage: d.triage.level, title: d.issue.title }, '[dry-run] Discovered issue');
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
processed: repoProcessed,
|
|
152
|
+
errors: repoErrors,
|
|
153
|
+
immediateFollowupRepos: [],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const maxConcurrentRuns = targetIssue ? 1 : (repoConfig.maxConcurrentRuns ?? 1);
|
|
157
|
+
const discoveredQueue = [...discovered];
|
|
158
|
+
const workerCount = Math.min(maxConcurrentRuns, discoveredQueue.length);
|
|
159
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
160
|
+
while (true) {
|
|
161
|
+
const discoveredIssue = discoveredQueue.shift();
|
|
162
|
+
if (!discoveredIssue) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
const issueRepo = discoveredIssue.issueRepo || discoveredIssue.issue.repo || repoConfig.repo;
|
|
166
|
+
if (discoveredIssue.triage.level === 'architectural') {
|
|
167
|
+
const labelConfig = buildLabelConfig(repoConfig, discoveredIssue.issue.labels);
|
|
168
|
+
await forge.addLabels(issueRepo, discoveredIssue.issue.number, [labelConfig.needsHuman]);
|
|
169
|
+
const archBody = formatStatusComment({ blockReason: 'This issue is classified as architectural and requires human guidance.' });
|
|
170
|
+
if (botUser) {
|
|
171
|
+
await upsertBotComment(forge, issueRepo, discoveredIssue.issue.number, STATUS_MARKER, archBody, botUser);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
await forge.commentOnIssue(issueRepo, discoveredIssue.issue.number, `🏗️ **night-orch**: This issue is classified as architectural and requires human guidance.`);
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// Lease duration is deliberately short (30 min) so a
|
|
179
|
+
// crashed poller's leases expire promptly. The engine
|
|
180
|
+
// bumps the deadline on every phase checkpoint via
|
|
181
|
+
// leaseHeartbeat below, so a long run is not at risk of
|
|
182
|
+
// expiring mid-work.
|
|
183
|
+
if (!leaseManager.acquire(issueRepo, discoveredIssue.issue.number, 'poller', 1800)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let runId = null;
|
|
187
|
+
let envSetup = null;
|
|
188
|
+
let activeWorktreePath = null;
|
|
189
|
+
try {
|
|
190
|
+
const workflow = resolveWorkflow(repoConfig, config, discoveredIssue.issue.labels, discoveredIssue.triage.level);
|
|
191
|
+
const repoConfigForRun = applyWorkflowAgentOverrides(repoConfig, workflow);
|
|
192
|
+
const roleDefaults = applyWorkflowRoleDefaults(repoConfigForRun.defaults, workflow, repoConfigForRun, config);
|
|
193
|
+
const resolvedRoles = resolveRoles(discoveredIssue.issue.labels, roleDefaults);
|
|
194
|
+
const queuedRun = runManager.getLatestQueuedByIssue(repoConfig.repo, discoveredIssue.issue.number);
|
|
195
|
+
const replayableRun = queuedRun
|
|
196
|
+
? null
|
|
197
|
+
: selectReplayableRun(runManager.getByRepoAndIssue(repoConfig.repo, discoveredIssue.issue.number));
|
|
198
|
+
const activeRun = queuedRun ?? replayableRun;
|
|
199
|
+
const roles = activeRun
|
|
200
|
+
? {
|
|
201
|
+
planner: coerceAgentName(activeRun.planner, resolvedRoles.planner),
|
|
202
|
+
coder: coerceAgentName(activeRun.coder, resolvedRoles.coder),
|
|
203
|
+
reviewer: coerceAgentName(activeRun.reviewer, resolvedRoles.reviewer),
|
|
204
|
+
}
|
|
205
|
+
: resolvedRoles;
|
|
206
|
+
const slug = getOrPinSlug(db, repoConfig.repo, discoveredIssue.issue.number, discoveredIssue.issue.title);
|
|
207
|
+
const branch = branchName(repoConfig.branchPrefix, discoveredIssue.issue.number, slug);
|
|
208
|
+
const worktreePath = buildWorktreePath(config.storage.worktreeRoot, repoConfig.repo, discoveredIssue.issue.number);
|
|
209
|
+
activeWorktreePath = worktreePath;
|
|
210
|
+
const run = activeRun ?? runManager.create({
|
|
211
|
+
repo: repoConfig.repo,
|
|
212
|
+
issueNumber: discoveredIssue.issue.number,
|
|
213
|
+
issueTitle: discoveredIssue.issue.title,
|
|
214
|
+
issueNodeId: discoveredIssue.issue.nodeId,
|
|
215
|
+
planner: roles.planner,
|
|
216
|
+
coder: roles.coder,
|
|
217
|
+
reviewer: roles.reviewer,
|
|
218
|
+
});
|
|
219
|
+
const startingIteration = activeRun ? Math.max(activeRun.iterationCount, 1) : 1;
|
|
220
|
+
const previousRunStatus = run.status;
|
|
221
|
+
if (replayableRun) {
|
|
222
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, runId: run.id, status: replayableRun.status }, 'Re-queuing active run for rediscovered ready issue');
|
|
223
|
+
}
|
|
224
|
+
runId = run.id;
|
|
225
|
+
runManager.update(run.id, {
|
|
226
|
+
status: 'running',
|
|
227
|
+
iterationCount: startingIteration,
|
|
228
|
+
issueTitle: discoveredIssue.issue.title,
|
|
229
|
+
branchName: branch,
|
|
230
|
+
branchSlug: slug,
|
|
231
|
+
worktreePath,
|
|
232
|
+
phaseData: {
|
|
233
|
+
...(run.phaseData ?? {}),
|
|
234
|
+
issueRepo,
|
|
235
|
+
},
|
|
236
|
+
endedAt: null,
|
|
237
|
+
lastError: null,
|
|
238
|
+
blockReason: null,
|
|
239
|
+
});
|
|
240
|
+
// Label transition
|
|
241
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, discoveredIssue.issue.labels, previousRunStatus, 'running', buildLabelConfig(repoConfig, discoveredIssue.issue.labels));
|
|
242
|
+
// Notify
|
|
243
|
+
await notifier.dispatch(makePayload('run_started', repoConfig.repo, discoveredIssue.issue));
|
|
244
|
+
// Detect if this queued run needs a forced branch reset (e.g., after merge conflict)
|
|
245
|
+
const forceReset = activeRun?.blockReason === 'merge_conflict';
|
|
246
|
+
// Detect rebase mode from queued run's phaseData
|
|
247
|
+
// If force-resetting, ignore stale rebase context — we're starting fresh
|
|
248
|
+
const reactionType = activeRun?.phaseData?.reactionType;
|
|
249
|
+
const isRebaseRun = !forceReset && (reactionType === 'rebase' || reactionType === 'merge_conflict');
|
|
250
|
+
const followupPromptFeedback = extractFollowupPromptFeedback(activeRun?.phaseData);
|
|
251
|
+
// Check if prior run left tainted work that should be discarded
|
|
252
|
+
// Never reset to base for rebase runs — we need the existing branch
|
|
253
|
+
const planningMode = isPlanningIssue(discoveredIssue.issue.labels, repoConfigForRun);
|
|
254
|
+
const resetToBase = forceReset || (!isRebaseRun && (planningMode || shouldResetBranch(runManager, repoConfig.repo, discoveredIssue.issue.number, run.id)));
|
|
255
|
+
// Create worktree
|
|
256
|
+
await worktreeManager.ensure({
|
|
257
|
+
repoLocalPath: repoConfig.localPath,
|
|
258
|
+
baseBranch: repoConfig.baseBranch,
|
|
259
|
+
branchName: branch,
|
|
260
|
+
worktreePath,
|
|
261
|
+
resetToBase,
|
|
262
|
+
});
|
|
263
|
+
// Execute rebase if this is a rebase-queued run
|
|
264
|
+
if (isRebaseRun) {
|
|
265
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, runId: run.id }, 'Executing rebase for queued rebase run');
|
|
266
|
+
const verifyCommands = repoConfig.verify ?? [];
|
|
267
|
+
const rebaseResult = await executeRebase(repoConfig.localPath, worktreePath, branch, repoConfig.baseBranch, issueRepo, discoveredIssue.issue.number, verifyCommands);
|
|
268
|
+
if (rebaseResult.conflict) {
|
|
269
|
+
// Rebase had conflicts — block the run; retry will reset the branch and re-implement
|
|
270
|
+
runManager.update(run.id, {
|
|
271
|
+
status: 'blocked',
|
|
272
|
+
blockReason: 'merge_conflict',
|
|
273
|
+
lastError: 'Rebase failed due to merge conflicts — retry will reset the branch and re-implement from scratch',
|
|
274
|
+
endedAt: nowUtcIso(),
|
|
275
|
+
});
|
|
276
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
277
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'blocked', buildLabelConfig(repoConfig, latestIssue.labels), 'merge_conflict');
|
|
278
|
+
await postStatusComment({
|
|
279
|
+
forge,
|
|
280
|
+
issueRepo,
|
|
281
|
+
issueNumber: discoveredIssue.issue.number,
|
|
282
|
+
botUser,
|
|
283
|
+
body: formatStatusComment({
|
|
284
|
+
blockReason: 'Rebase failed due to merge conflicts while replaying the branch onto the latest base.',
|
|
285
|
+
nextStep: 'Run /orch retry to reset the branch to base and re-implement on top of latest main.',
|
|
286
|
+
}),
|
|
287
|
+
warnMessage: 'Failed to post rebase merge-conflict status comment',
|
|
288
|
+
});
|
|
289
|
+
await notifier.dispatch(makePayload('blocked', repoConfig.repo, discoveredIssue.issue, {
|
|
290
|
+
summary: 'Rebase failed due to merge conflicts',
|
|
291
|
+
blockingReason: 'merge_conflict',
|
|
292
|
+
}));
|
|
293
|
+
leaseManager.release(issueRepo, discoveredIssue.issue.number);
|
|
294
|
+
repoErrors++;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (rebaseResult.rebased && rebaseResult.verifyPassed) {
|
|
298
|
+
// Rebase succeeded and verify passes — done, transition back to review_ready
|
|
299
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number }, 'Rebase succeeded, verify passed — returning to review_ready');
|
|
300
|
+
runManager.update(run.id, {
|
|
301
|
+
status: 'review_ready',
|
|
302
|
+
endedAt: nowUtcIso(),
|
|
303
|
+
lastError: null,
|
|
304
|
+
});
|
|
305
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
306
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'review_ready', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
307
|
+
await notifier.dispatch(makePayload('pr_ready', repoConfig.repo, discoveredIssue.issue, {
|
|
308
|
+
summary: 'Rebased successfully, verify passed',
|
|
309
|
+
}));
|
|
310
|
+
leaseManager.release(issueRepo, discoveredIssue.issue.number);
|
|
311
|
+
repoProcessed++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (!rebaseResult.rebased && rebaseResult.verifyPassed) {
|
|
315
|
+
// Already up-to-date and verify passes — nothing to do
|
|
316
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number }, 'Branch already up to date — returning to review_ready');
|
|
317
|
+
runManager.update(run.id, {
|
|
318
|
+
status: 'review_ready',
|
|
319
|
+
endedAt: nowUtcIso(),
|
|
320
|
+
lastError: null,
|
|
321
|
+
});
|
|
322
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
323
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'review_ready', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
324
|
+
leaseManager.release(issueRepo, discoveredIssue.issue.number);
|
|
325
|
+
repoProcessed++;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
// Rebase succeeded but verify failed — fall through to the loop engine
|
|
329
|
+
// so the coder can fix the issues introduced by upstream changes
|
|
330
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number }, 'Rebase done but verify failed — entering code loop to fix');
|
|
331
|
+
}
|
|
332
|
+
if (repoConfigForRun.environment) {
|
|
333
|
+
const mode = resolveEnvironmentMode(discoveredIssue.issue.labels, repoConfigForRun);
|
|
334
|
+
envSetup = await setupEnvironment({
|
|
335
|
+
worktreePath,
|
|
336
|
+
issueNumber: discoveredIssue.issue.number,
|
|
337
|
+
repoConfig: repoConfigForRun,
|
|
338
|
+
mode,
|
|
339
|
+
usedPorts: usedPortsInPass,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Get worker adapters
|
|
343
|
+
const plannerProfile = resolveWorkerProfileForAgent(roles.planner, repoConfigForRun, config);
|
|
344
|
+
const coderProfile = resolveWorkerProfileForAgent(roles.coder, repoConfigForRun, config);
|
|
345
|
+
const reviewerProfile = resolveWorkerProfileForAgent(roles.reviewer, repoConfigForRun, config);
|
|
346
|
+
const adjustedLimits = adjustLimitsForTriage(config.loop, plannerProfile?.workerTimeoutSeconds ?? 1800, discoveredIssue.triage);
|
|
347
|
+
if (!plannerProfile || !coderProfile || !reviewerProfile) {
|
|
348
|
+
throw new Error('Missing worker profiles for resolved roles');
|
|
349
|
+
}
|
|
350
|
+
const initialCtx = {
|
|
351
|
+
runId: run.id,
|
|
352
|
+
repo: repoConfig.repo,
|
|
353
|
+
issueRepo,
|
|
354
|
+
issueNumber: discoveredIssue.issue.number,
|
|
355
|
+
issue: discoveredIssue.issue,
|
|
356
|
+
repoConfig: repoConfigForRun,
|
|
357
|
+
roles,
|
|
358
|
+
triageResult: discoveredIssue.triage,
|
|
359
|
+
adjustedLimits,
|
|
360
|
+
branchName: branch,
|
|
361
|
+
worktreePath,
|
|
362
|
+
plan: null,
|
|
363
|
+
codeResult: null,
|
|
364
|
+
diff: null,
|
|
365
|
+
verifyResults: [],
|
|
366
|
+
reviewResult: null,
|
|
367
|
+
reviewFindings: [],
|
|
368
|
+
iteration: startingIteration,
|
|
369
|
+
totalAgentPasses: 0,
|
|
370
|
+
estimatedCostUsd: 0,
|
|
371
|
+
currentPhase: workflow.steps[0]?.id ?? 'plan',
|
|
372
|
+
terminalStatus: 'running',
|
|
373
|
+
phaseHistory: [],
|
|
374
|
+
dryRun: false,
|
|
375
|
+
runMode: isRebaseRun ? 'rebase' : followupPromptFeedback ? 'followup' : 'fresh',
|
|
376
|
+
blockReason: null,
|
|
377
|
+
prReviewFeedback: followupPromptFeedback,
|
|
378
|
+
sessionIds: {},
|
|
379
|
+
stepOutputs: {},
|
|
380
|
+
};
|
|
381
|
+
// Check if decomposition is enabled and appropriate
|
|
382
|
+
const shouldDecompose = config.loop.decompose
|
|
383
|
+
&& discoveredIssue.triage.level === 'standard'
|
|
384
|
+
&& !planningMode
|
|
385
|
+
&& shouldAttemptDecompose(discoveredIssue.issue);
|
|
386
|
+
if (shouldDecompose) {
|
|
387
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number }, 'Attempting issue decomposition');
|
|
388
|
+
const decomposition = await decomposeIssue(discoveredIssue.issue, createWorkerAdapter(plannerProfile), plannerProfile, buildWorkerEnv(plannerProfile, envSetup?.envOverrides ?? {}), worktreePath, config.loop.maxSubtasks);
|
|
389
|
+
if (decomposition.shouldDecompose && decomposition.subtasks.length > 1) {
|
|
390
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, subtasks: decomposition.subtasks.length }, 'Decomposed issue into sub-tasks — executing in parallel');
|
|
391
|
+
const loopDeps = {
|
|
392
|
+
db, config,
|
|
393
|
+
adapters: {
|
|
394
|
+
planner: createWorkerAdapter(plannerProfile),
|
|
395
|
+
coder: createWorkerAdapter(coderProfile),
|
|
396
|
+
reviewer: createWorkerAdapter(reviewerProfile),
|
|
397
|
+
},
|
|
398
|
+
workflow,
|
|
399
|
+
envOverrides: envSetup?.envOverrides ?? {},
|
|
400
|
+
metrics,
|
|
401
|
+
onAgentEvent: (event) => observability.record(event),
|
|
402
|
+
};
|
|
403
|
+
const subResults = await executeParallelSubtasks(initialCtx, decomposition.subtasks, loopDeps, config.loop.maxConcurrentSubtasks);
|
|
404
|
+
const allSucceeded = subResults.every((r) => r.success);
|
|
405
|
+
if (allSucceeded) {
|
|
406
|
+
runManager.update(run.id, { status: 'review_ready', endedAt: nowUtcIso() });
|
|
407
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
408
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'review_ready', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
409
|
+
await notifier.dispatch(makePayload('pr_ready', repoConfig.repo, discoveredIssue.issue, {
|
|
410
|
+
summary: `Decomposed into ${decomposition.subtasks.length} sub-tasks, all completed`,
|
|
411
|
+
}));
|
|
412
|
+
repoProcessed++;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
const failed = subResults.filter((r) => !r.success).length;
|
|
416
|
+
runManager.update(run.id, {
|
|
417
|
+
status: 'blocked',
|
|
418
|
+
lastError: `${failed}/${decomposition.subtasks.length} sub-tasks failed`,
|
|
419
|
+
endedAt: nowUtcIso(),
|
|
420
|
+
});
|
|
421
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
422
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'blocked', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
423
|
+
repoErrors++;
|
|
424
|
+
}
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Execute loop (single-issue path)
|
|
429
|
+
const loopStart = Date.now();
|
|
430
|
+
const finalCtx = await executeLoop(initialCtx, {
|
|
431
|
+
db,
|
|
432
|
+
config,
|
|
433
|
+
adapters: {
|
|
434
|
+
planner: createWorkerAdapter(plannerProfile),
|
|
435
|
+
coder: createWorkerAdapter(coderProfile),
|
|
436
|
+
reviewer: createWorkerAdapter(reviewerProfile),
|
|
437
|
+
},
|
|
438
|
+
workflow,
|
|
439
|
+
envOverrides: envSetup?.envOverrides ?? {},
|
|
440
|
+
metrics,
|
|
441
|
+
onAgentEvent: (event) => observability.record(event),
|
|
442
|
+
onPlanReady: async (ctx) => {
|
|
443
|
+
await postPlanSummaryComment(forge, ctx.issueRepo ?? ctx.repo, ctx.issueNumber, ctx.plan, botUser);
|
|
444
|
+
},
|
|
445
|
+
leaseHeartbeat: () => leaseManager.heartbeat(issueRepo, discoveredIssue.issue.number, 'poller', 1800),
|
|
446
|
+
});
|
|
447
|
+
const runDurationSec = (Date.now() - loopStart) / 1000;
|
|
448
|
+
const outcome = await finalizeRunOutcome({
|
|
449
|
+
finalCtx,
|
|
450
|
+
runId: run.id,
|
|
451
|
+
issue: discoveredIssue.issue,
|
|
452
|
+
runDurationSec,
|
|
453
|
+
repo: repoConfig.repo,
|
|
454
|
+
repoConfig,
|
|
455
|
+
issueRepo,
|
|
456
|
+
issueNumber: discoveredIssue.issue.number,
|
|
457
|
+
db,
|
|
458
|
+
forge,
|
|
459
|
+
runManager,
|
|
460
|
+
notifier,
|
|
461
|
+
metrics,
|
|
462
|
+
maxAutoRetries: config.loop.maxAutoRetries,
|
|
463
|
+
botUser,
|
|
464
|
+
});
|
|
465
|
+
if (outcome === 'processed')
|
|
466
|
+
repoProcessed++;
|
|
467
|
+
else
|
|
468
|
+
repoErrors++;
|
|
469
|
+
// Release per-run observability resources (session log
|
|
470
|
+
// streams + event bus history) once the run has reached
|
|
471
|
+
// a terminal state — without this both Maps grow for the
|
|
472
|
+
// daemon's entire lifetime.
|
|
473
|
+
try {
|
|
474
|
+
await observability.closeRun(run.id);
|
|
475
|
+
}
|
|
476
|
+
catch (closeErr) {
|
|
477
|
+
logger.debug({ runId: run.id, err: closeErr }, 'closeRun failed (best-effort)');
|
|
478
|
+
}
|
|
479
|
+
// Evict per-issue caches so they don't grow unbounded.
|
|
480
|
+
cleanupRunCaches(repoConfig.repo, discoveredIssue.issue.number);
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
logger.error({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, err }, 'Failed to process issue');
|
|
484
|
+
if (runId) {
|
|
485
|
+
const errorMessage = toErrorMessage(err);
|
|
486
|
+
const existing = runManager.getById(runId);
|
|
487
|
+
const currentRetries = existing?.retryCount ?? 0;
|
|
488
|
+
const maxRetries = config.loop.maxAutoRetries;
|
|
489
|
+
const canAutoRetry = currentRetries < maxRetries;
|
|
490
|
+
const attemptCount = currentRetries + 1;
|
|
491
|
+
runManager.update(runId, {
|
|
492
|
+
status: 'error',
|
|
493
|
+
lastError: errorMessage,
|
|
494
|
+
endedAt: nowUtcIso(),
|
|
495
|
+
});
|
|
496
|
+
if (canAutoRetry) {
|
|
497
|
+
// Bump retry_count on the same row so repeated replay
|
|
498
|
+
// retries converge on maxRetries. Without this the
|
|
499
|
+
// same run row cycles error → queued → error forever.
|
|
500
|
+
runManager.incrementRetryCount(runId);
|
|
501
|
+
// Auto-retry: transition back to queued so the next poll picks it up
|
|
502
|
+
logger.info({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, attempt: attemptCount, maxRetries }, 'Infra error — auto-retrying (transitioning back to ready)');
|
|
503
|
+
try {
|
|
504
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
505
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'queued', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
506
|
+
}
|
|
507
|
+
catch (labelErr) {
|
|
508
|
+
logger.warn({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, err: labelErr }, 'Failed to transition labels for auto-retry');
|
|
509
|
+
}
|
|
510
|
+
await postErrorStatusComment({
|
|
511
|
+
forge,
|
|
512
|
+
issueRepo,
|
|
513
|
+
issueNumber: discoveredIssue.issue.number,
|
|
514
|
+
botUser,
|
|
515
|
+
error: `Attempt ${attemptCount} failed. Last error: ${errorMessage}`,
|
|
516
|
+
retryCount: attemptCount,
|
|
517
|
+
maxRetries,
|
|
518
|
+
nextStep: 'Automatic retry queued. night-orch will retry this issue on the next poll cycle.',
|
|
519
|
+
warnMessage: 'Failed to post auto-retry status comment',
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
// Retries exhausted: mark as error, require human
|
|
524
|
+
logger.warn({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, currentRetries, maxRetries }, 'Auto-retry limit reached — marking as error');
|
|
525
|
+
try {
|
|
526
|
+
const latestIssue = await forge.getIssue(issueRepo, discoveredIssue.issue.number);
|
|
527
|
+
await transitionLabels(forge, issueRepo, discoveredIssue.issue.number, latestIssue.labels, 'running', 'error', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
528
|
+
}
|
|
529
|
+
catch (labelErr) {
|
|
530
|
+
logger.warn({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, err: labelErr }, 'Failed to transition labels after retry exhaustion');
|
|
531
|
+
}
|
|
532
|
+
await postErrorStatusComment({
|
|
533
|
+
forge,
|
|
534
|
+
issueRepo,
|
|
535
|
+
issueNumber: discoveredIssue.issue.number,
|
|
536
|
+
botUser,
|
|
537
|
+
error: `Failed after ${attemptCount} attempts. Last error: ${errorMessage}`,
|
|
538
|
+
retryCount: attemptCount,
|
|
539
|
+
maxRetries,
|
|
540
|
+
nextStep: 'Manual action required: inspect the failure, then run /orch retry or /orch continue.',
|
|
541
|
+
warnMessage: 'Failed to post retry-exhausted status comment',
|
|
542
|
+
});
|
|
543
|
+
const sanitizedErrorForSummary = sanitizeErrorForComment(errorMessage);
|
|
544
|
+
try {
|
|
545
|
+
await notifier.dispatch(makePayload('retry_exhausted', repoConfig.repo, discoveredIssue.issue, {
|
|
546
|
+
summary: `Failed after ${attemptCount} attempts: ${sanitizedErrorForSummary}`,
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
catch (notifyErr) {
|
|
550
|
+
logger.warn({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, err: notifyErr }, 'Failed to send retry exhaustion notification');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
repoErrors++;
|
|
555
|
+
}
|
|
556
|
+
finally {
|
|
557
|
+
if (runId) {
|
|
558
|
+
const finalRun = runManager.getById(runId);
|
|
559
|
+
if (finalRun && isImmediateFollowupStatus(finalRun.status)) {
|
|
560
|
+
repoImmediateFollowupRepos.add(finalRun.repo);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (envSetup && activeWorktreePath) {
|
|
564
|
+
try {
|
|
565
|
+
await teardownEnvironment({
|
|
566
|
+
worktreePath: activeWorktreePath,
|
|
567
|
+
issueNumber: discoveredIssue.issue.number,
|
|
568
|
+
repoConfig,
|
|
569
|
+
mode: envSetup.mode,
|
|
570
|
+
composeProjectName: envSetup.composeProjectName,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
catch (envErr) {
|
|
574
|
+
logger.warn({ repo: repoConfig.repo, issue: discoveredIssue.issue.number, err: envErr }, 'Failed to tear down environment');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
leaseManager.release(issueRepo, discoveredIssue.issue.number);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}));
|
|
581
|
+
return {
|
|
582
|
+
processed: repoProcessed,
|
|
583
|
+
errors: repoErrors,
|
|
584
|
+
immediateFollowupRepos: [...repoImmediateFollowupRepos],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
logger.error({ repo: repoConfig.repo, err }, 'Repository poll failed');
|
|
589
|
+
return {
|
|
590
|
+
processed: repoProcessed,
|
|
591
|
+
errors: repoErrors + 1,
|
|
592
|
+
immediateFollowupRepos: [...repoImmediateFollowupRepos],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}));
|
|
596
|
+
for (const repoResult of repoResults) {
|
|
597
|
+
processed += repoResult.processed;
|
|
598
|
+
errors += repoResult.errors;
|
|
599
|
+
for (const repo of repoResult.immediateFollowupRepos) {
|
|
600
|
+
immediateFollowupRepos.add(repo);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return { processed, errors, immediateFollowupRepos: [...immediateFollowupRepos] };
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
clearActiveAgentObservability(observability);
|
|
607
|
+
await observability.close();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function finalizeRunOutcome(params) {
|
|
611
|
+
const { finalCtx, runId, issue, runDurationSec, repo, repoConfig, issueRepo, issueNumber, db, forge, runManager, notifier, metrics, maxAutoRetries, botUser, } = params;
|
|
612
|
+
if (finalCtx.terminalStatus === 'publish') {
|
|
613
|
+
try {
|
|
614
|
+
const publishResult = await publishPR(finalCtx, forge, db);
|
|
615
|
+
runManager.update(runId, {
|
|
616
|
+
status: 'review_ready',
|
|
617
|
+
iterationCount: finalCtx.iteration,
|
|
618
|
+
prNumber: publishResult.prNumber,
|
|
619
|
+
prTitle: publishResult.prTitle,
|
|
620
|
+
endedAt: nowUtcIso(),
|
|
621
|
+
});
|
|
622
|
+
const latestIssue = await forge.getIssue(issueRepo, issueNumber);
|
|
623
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'review_ready', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
624
|
+
const notificationEvent = publishResult.created ? 'pr_ready' : 'pr_updated';
|
|
625
|
+
const notifyResult = await notifier.dispatch(makePayload(notificationEvent, repo, issue, {
|
|
626
|
+
prUrl: publishResult.prUrl,
|
|
627
|
+
prNumber: publishResult.prNumber,
|
|
628
|
+
summary: publishResult.created
|
|
629
|
+
? `PR ready: ${publishResult.prUrl}`
|
|
630
|
+
: `PR updated: ${publishResult.prUrl}`,
|
|
631
|
+
}));
|
|
632
|
+
try {
|
|
633
|
+
metrics?.incRunsTotal('completed');
|
|
634
|
+
metrics?.observeRunDuration(runDurationSec);
|
|
635
|
+
metrics?.incPROperations(publishResult.created ? 'created' : 'updated');
|
|
636
|
+
for (const s of notifyResult.sent) {
|
|
637
|
+
metrics?.incNotifications(s.channel, s.success ? 'sent' : 'failed');
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch { /* best-effort */ }
|
|
641
|
+
return 'processed';
|
|
642
|
+
}
|
|
643
|
+
catch (err) {
|
|
644
|
+
logger.error({ err }, 'Failed to publish PR');
|
|
645
|
+
const errorMessage = toErrorMessage(err);
|
|
646
|
+
// Merge conflicts during push get structured block reason so retry resets the branch
|
|
647
|
+
if (err instanceof MergeConflictError) {
|
|
648
|
+
runManager.update(runId, {
|
|
649
|
+
status: 'blocked',
|
|
650
|
+
iterationCount: finalCtx.iteration,
|
|
651
|
+
blockReason: 'merge_conflict',
|
|
652
|
+
lastError: err.message,
|
|
653
|
+
endedAt: nowUtcIso(),
|
|
654
|
+
});
|
|
655
|
+
const latestIssue = await forge.getIssue(issueRepo, issueNumber);
|
|
656
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'blocked', buildLabelConfig(repoConfig, latestIssue.labels), 'merge_conflict');
|
|
657
|
+
await postStatusComment({
|
|
658
|
+
forge,
|
|
659
|
+
issueRepo,
|
|
660
|
+
issueNumber,
|
|
661
|
+
botUser,
|
|
662
|
+
body: formatStatusComment({
|
|
663
|
+
blockReason: 'Publish failed due to merge conflicts while pushing branch updates.',
|
|
664
|
+
nextStep: 'Run /orch retry to reset the branch to base and re-implement on top of latest main.',
|
|
665
|
+
}),
|
|
666
|
+
warnMessage: 'Failed to post publish merge-conflict status comment',
|
|
667
|
+
});
|
|
668
|
+
try {
|
|
669
|
+
metrics?.incRunsTotal('blocked');
|
|
670
|
+
metrics?.observeRunDuration(runDurationSec);
|
|
671
|
+
}
|
|
672
|
+
catch { /* best-effort */ }
|
|
673
|
+
return 'error';
|
|
674
|
+
}
|
|
675
|
+
const currentRun = runManager.getById(runId);
|
|
676
|
+
const currentRetries = currentRun?.retryCount ?? 0;
|
|
677
|
+
runManager.update(runId, { status: 'error', iterationCount: finalCtx.iteration, lastError: errorMessage, endedAt: nowUtcIso() });
|
|
678
|
+
const latestIssue = await forge.getIssue(issueRepo, issueNumber);
|
|
679
|
+
if (currentRetries < maxAutoRetries) {
|
|
680
|
+
runManager.incrementRetryCount(runId);
|
|
681
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'queued', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
682
|
+
logger.info({ repo, issueNumber, attempt: currentRetries + 1, maxAutoRetries }, 'Publish failed — auto-retrying');
|
|
683
|
+
await postErrorStatusComment({
|
|
684
|
+
forge,
|
|
685
|
+
issueRepo,
|
|
686
|
+
issueNumber,
|
|
687
|
+
botUser,
|
|
688
|
+
error: `Publish failed. Last error: ${errorMessage}`,
|
|
689
|
+
retryCount: currentRetries + 1,
|
|
690
|
+
maxRetries: maxAutoRetries,
|
|
691
|
+
nextStep: 'Automatic retry queued. night-orch will retry this issue on the next poll cycle.',
|
|
692
|
+
warnMessage: 'Failed to post publish auto-retry status comment',
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'error', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
697
|
+
const attemptCount = currentRetries + 1;
|
|
698
|
+
await postErrorStatusComment({
|
|
699
|
+
forge,
|
|
700
|
+
issueRepo,
|
|
701
|
+
issueNumber,
|
|
702
|
+
botUser,
|
|
703
|
+
error: `Failed after ${attemptCount} attempts. Last error: ${errorMessage}`,
|
|
704
|
+
retryCount: attemptCount,
|
|
705
|
+
maxRetries: maxAutoRetries,
|
|
706
|
+
nextStep: 'Manual action required: inspect the failure, then run /orch retry or /orch continue.',
|
|
707
|
+
warnMessage: 'Failed to post publish retry-exhausted status comment',
|
|
708
|
+
});
|
|
709
|
+
const sanitizedErrorForSummary = sanitizeErrorForComment(errorMessage);
|
|
710
|
+
try {
|
|
711
|
+
await notifier.dispatch(makePayload('retry_exhausted', repo, issue, {
|
|
712
|
+
summary: `Publish failed after ${attemptCount} attempts: ${sanitizedErrorForSummary}`,
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
catch (notifyErr) {
|
|
716
|
+
logger.warn({ repo, issueNumber, err: notifyErr }, 'Failed to send publish retry exhaustion notification');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
metrics?.incRunsTotal('error');
|
|
721
|
+
metrics?.observeRunDuration(runDurationSec);
|
|
722
|
+
}
|
|
723
|
+
catch { /* best-effort */ }
|
|
724
|
+
return 'error';
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (finalCtx.terminalStatus === 'blocked') {
|
|
728
|
+
const blockReason = buildBlockReason(finalCtx);
|
|
729
|
+
runManager.update(runId, {
|
|
730
|
+
status: 'blocked',
|
|
731
|
+
iterationCount: finalCtx.iteration,
|
|
732
|
+
lastError: blockReason,
|
|
733
|
+
blockReason: finalCtx.blockReason ?? null,
|
|
734
|
+
endedAt: nowUtcIso(),
|
|
735
|
+
});
|
|
736
|
+
const latestIssue = await forge.getIssue(issueRepo, issueNumber);
|
|
737
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'blocked', buildLabelConfig(repoConfig, latestIssue.labels), finalCtx.blockReason ?? undefined);
|
|
738
|
+
// Upsert status comment with block reason
|
|
739
|
+
try {
|
|
740
|
+
const statusBody = formatStatusComment({
|
|
741
|
+
blockReason,
|
|
742
|
+
iteration: finalCtx.iteration,
|
|
743
|
+
maxIterations: finalCtx.adjustedLimits.maxReviewIterations,
|
|
744
|
+
cost: finalCtx.estimatedCostUsd,
|
|
745
|
+
});
|
|
746
|
+
if (botUser) {
|
|
747
|
+
await upsertBotComment(forge, issueRepo, issueNumber, STATUS_MARKER, statusBody, botUser);
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
await forge.commentOnIssue(issueRepo, issueNumber, formatBlockComment(blockReason, finalCtx));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch (commentErr) {
|
|
754
|
+
logger.warn({ repo, issueNumber, err: commentErr }, 'Failed to post block reason comment');
|
|
755
|
+
}
|
|
756
|
+
const notifyResult = await notifier.dispatch(makePayload('blocked', repo, issue, {
|
|
757
|
+
summary: blockReason,
|
|
758
|
+
blockingReason: blockReason,
|
|
759
|
+
reviewSummary: finalCtx.reviewResult?.summary ?? null,
|
|
760
|
+
}));
|
|
761
|
+
try {
|
|
762
|
+
metrics?.incRunsTotal('blocked');
|
|
763
|
+
metrics?.observeRunDuration(runDurationSec);
|
|
764
|
+
for (const s of notifyResult.sent) {
|
|
765
|
+
metrics?.incNotifications(s.channel, s.success ? 'sent' : 'failed');
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
catch { /* best-effort */ }
|
|
769
|
+
return 'processed';
|
|
770
|
+
}
|
|
771
|
+
const unexpectedError = `Loop ended in unexpected state: ${finalCtx.terminalStatus}/${finalCtx.currentPhase}`;
|
|
772
|
+
const currentRunForUnexpected = runManager.getById(runId);
|
|
773
|
+
const currentRetriesUnexpected = currentRunForUnexpected?.retryCount ?? 0;
|
|
774
|
+
runManager.update(runId, { status: 'error', iterationCount: finalCtx.iteration, lastError: unexpectedError, endedAt: nowUtcIso() });
|
|
775
|
+
const latestIssue = await forge.getIssue(issueRepo, issueNumber);
|
|
776
|
+
if (currentRetriesUnexpected < maxAutoRetries) {
|
|
777
|
+
runManager.incrementRetryCount(runId);
|
|
778
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'queued', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
779
|
+
logger.info({ repo, issueNumber, attempt: currentRetriesUnexpected + 1, maxAutoRetries }, 'Unexpected state — auto-retrying');
|
|
780
|
+
await postErrorStatusComment({
|
|
781
|
+
forge,
|
|
782
|
+
issueRepo,
|
|
783
|
+
issueNumber,
|
|
784
|
+
botUser,
|
|
785
|
+
error: `Loop entered unexpected state: ${finalCtx.terminalStatus}/${finalCtx.currentPhase}`,
|
|
786
|
+
retryCount: currentRetriesUnexpected + 1,
|
|
787
|
+
maxRetries: maxAutoRetries,
|
|
788
|
+
nextStep: 'Automatic retry queued. night-orch will retry this issue on the next poll cycle.',
|
|
789
|
+
warnMessage: 'Failed to post unexpected-state auto-retry status comment',
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
await transitionLabels(forge, issueRepo, issueNumber, latestIssue.labels, 'running', 'error', buildLabelConfig(repoConfig, latestIssue.labels));
|
|
794
|
+
const attemptCount = currentRetriesUnexpected + 1;
|
|
795
|
+
await postErrorStatusComment({
|
|
796
|
+
forge,
|
|
797
|
+
issueRepo,
|
|
798
|
+
issueNumber,
|
|
799
|
+
botUser,
|
|
800
|
+
error: `Failed after ${attemptCount} attempts. Last error: ${unexpectedError}`,
|
|
801
|
+
retryCount: attemptCount,
|
|
802
|
+
maxRetries: maxAutoRetries,
|
|
803
|
+
nextStep: 'Manual action required: inspect the failure, then run /orch retry or /orch continue.',
|
|
804
|
+
warnMessage: 'Failed to post unexpected-state retry-exhausted status comment',
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
try {
|
|
808
|
+
metrics?.incRunsTotal('error');
|
|
809
|
+
metrics?.observeRunDuration(runDurationSec);
|
|
810
|
+
}
|
|
811
|
+
catch { /* best-effort */ }
|
|
812
|
+
return 'error';
|
|
813
|
+
}
|
|
814
|
+
function coerceAgentName(value, fallback) {
|
|
815
|
+
if (value === 'claude' || value === 'codex') {
|
|
816
|
+
return value;
|
|
817
|
+
}
|
|
818
|
+
return fallback;
|
|
819
|
+
}
|
|
820
|
+
function isImmediateFollowupStatus(status) {
|
|
821
|
+
return status === 'review_ready'
|
|
822
|
+
|| status === 'blocked'
|
|
823
|
+
|| status === 'error'
|
|
824
|
+
|| status === 'completed';
|
|
825
|
+
}
|
|
826
|
+
function applyWorkflowAgentOverrides(repoConfig, workflow) {
|
|
827
|
+
if (!workflow.agents || Object.keys(workflow.agents).length === 0) {
|
|
828
|
+
return repoConfig;
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
...repoConfig,
|
|
832
|
+
agents: {
|
|
833
|
+
...repoConfig.agents,
|
|
834
|
+
...workflow.agents,
|
|
835
|
+
},
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function applyWorkflowRoleDefaults(repoDefaults, workflow, repoConfig, config) {
|
|
839
|
+
if (!workflow.roles) {
|
|
840
|
+
return repoDefaults;
|
|
841
|
+
}
|
|
842
|
+
const merged = {
|
|
843
|
+
...repoDefaults,
|
|
844
|
+
...workflow.roles,
|
|
845
|
+
};
|
|
846
|
+
for (const role of ['planner', 'coder', 'reviewer']) {
|
|
847
|
+
const preferredAgent = merged[role];
|
|
848
|
+
if (canResolveAgent(preferredAgent, repoConfig, config))
|
|
849
|
+
continue;
|
|
850
|
+
merged[role] = repoDefaults[role];
|
|
851
|
+
}
|
|
852
|
+
return merged;
|
|
853
|
+
}
|
|
854
|
+
function canResolveAgent(agent, repoConfig, config) {
|
|
855
|
+
return resolveWorkerProfileForAgent(agent, repoConfig, config) !== null;
|
|
856
|
+
}
|
|
857
|
+
function resolveWorkerProfileForAgent(agent, repoConfig, config) {
|
|
858
|
+
const mappedProfileName = repoConfig.agents[agent];
|
|
859
|
+
if (mappedProfileName) {
|
|
860
|
+
const mappedProfile = config.workerProfiles[mappedProfileName];
|
|
861
|
+
if (mappedProfile)
|
|
862
|
+
return mappedProfile;
|
|
863
|
+
}
|
|
864
|
+
return Object.values(config.workerProfiles).find((profile) => profile.type === agent) ?? null;
|
|
865
|
+
}
|
|
866
|
+
function buildBlockReason(ctx) {
|
|
867
|
+
const blockMessage = ctx.stepOutputs?.['blockMessage'];
|
|
868
|
+
if (typeof blockMessage === 'string' && blockMessage.trim().length > 0) {
|
|
869
|
+
return blockMessage;
|
|
870
|
+
}
|
|
871
|
+
if (ctx.reviewResult) {
|
|
872
|
+
const findings = ctx.reviewResult.findings
|
|
873
|
+
.filter((f) => f.severity === 'critical' || f.severity === 'major')
|
|
874
|
+
.map((f) => `[${f.severity}] ${f.message}`)
|
|
875
|
+
.join('; ');
|
|
876
|
+
return findings
|
|
877
|
+
? `${ctx.reviewResult.summary} — ${findings}`
|
|
878
|
+
: ctx.reviewResult.summary;
|
|
879
|
+
}
|
|
880
|
+
if (ctx.blockReason) {
|
|
881
|
+
return blockReasonSummary(ctx.blockReason, ctx);
|
|
882
|
+
}
|
|
883
|
+
return `Blocked in phase ${ctx.currentPhase} (no review result available)`;
|
|
884
|
+
}
|
|
885
|
+
function blockReasonSummary(reason, ctx) {
|
|
886
|
+
switch (reason) {
|
|
887
|
+
case 'cost_limit':
|
|
888
|
+
// The engine writes a precise, limit-specific message into stepOutputs.blockMessage
|
|
889
|
+
// (see src/loop/engine.ts cost check). This branch is only reached when that
|
|
890
|
+
// structured message is missing — keep it vague so we do not claim the per-run
|
|
891
|
+
// limit tripped when it might have been the daily cap.
|
|
892
|
+
return `Cost limit exceeded for this run (estimated run cost: $${ctx.estimatedCostUsd.toFixed(4)})`;
|
|
893
|
+
case 'iteration_limit':
|
|
894
|
+
return `Maximum review iterations reached (${ctx.iteration}/${ctx.adjustedLimits.maxReviewIterations})`;
|
|
895
|
+
case 'agent_pass_limit':
|
|
896
|
+
return `Maximum total agent passes reached (${ctx.totalAgentPasses}/${ctx.adjustedLimits.maxTotalAgentPasses})`;
|
|
897
|
+
case 'reviewer_blocked':
|
|
898
|
+
return 'Reviewer marked this run as blocked';
|
|
899
|
+
case 'ambiguous_review':
|
|
900
|
+
return 'Review output was not parseable and blockOnAmbiguousReview is enabled';
|
|
901
|
+
case 'verify_config':
|
|
902
|
+
return 'Verification is required but verify commands or results are unavailable';
|
|
903
|
+
case 'merge_conflict':
|
|
904
|
+
return 'Merge conflict encountered while applying updates';
|
|
905
|
+
default:
|
|
906
|
+
return `Blocked in phase ${ctx.currentPhase}`;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function formatBlockComment(reason, ctx) {
|
|
910
|
+
const parts = [`⛔ **night-orch**: Run blocked.\n\n**Reason:** ${reason}`];
|
|
911
|
+
if (ctx.reviewResult?.findings && ctx.reviewResult.findings.length > 0) {
|
|
912
|
+
parts.push('\n**Findings:**');
|
|
913
|
+
for (const f of ctx.reviewResult.findings) {
|
|
914
|
+
const fix = f.suggestedFix ? ` → ${f.suggestedFix}` : '';
|
|
915
|
+
parts.push(`- **${f.severity}**: ${f.message}${fix}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
parts.push(`\n*Iteration ${ctx.iteration}, cost: $${ctx.estimatedCostUsd.toFixed(4)}*`);
|
|
919
|
+
return parts.join('\n');
|
|
920
|
+
}
|
|
921
|
+
function makePayload(event, repo, issue, extra = {}) {
|
|
922
|
+
return {
|
|
923
|
+
event,
|
|
924
|
+
repo,
|
|
925
|
+
issueNumber: issue.number,
|
|
926
|
+
issueTitle: issue.title,
|
|
927
|
+
issueUrl: issue.url ?? null,
|
|
928
|
+
state: event,
|
|
929
|
+
prUrl: null,
|
|
930
|
+
prNumber: null,
|
|
931
|
+
summary: `${event}: #${issue.number} ${issue.title}`,
|
|
932
|
+
blockingReason: null,
|
|
933
|
+
reviewSummary: null,
|
|
934
|
+
iterationCount: 0,
|
|
935
|
+
timestamp: nowUtcIso(),
|
|
936
|
+
...extra,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
async function postStatusComment(params) {
|
|
940
|
+
const { forge, issueRepo, issueNumber, botUser, body, warnMessage, } = params;
|
|
941
|
+
try {
|
|
942
|
+
if (botUser) {
|
|
943
|
+
await upsertBotComment(forge, issueRepo, issueNumber, STATUS_MARKER, body, botUser);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
await forge.commentOnIssue(issueRepo, issueNumber, body);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
catch (commentErr) {
|
|
950
|
+
logger.warn({ repo: issueRepo, issueNumber, err: commentErr }, warnMessage);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const ERROR_COMMENT_MAX_LENGTH = 400;
|
|
954
|
+
const TOKEN_REDACTION_PATTERNS = [
|
|
955
|
+
/\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g,
|
|
956
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
957
|
+
/\bsk-[A-Za-z0-9]{20,}\b/g,
|
|
958
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
959
|
+
/\bASIA[0-9A-Z]{16}\b/g,
|
|
960
|
+
/\bAIza[0-9A-Za-z\-_]{20,}\b/g,
|
|
961
|
+
/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g,
|
|
962
|
+
];
|
|
963
|
+
async function postErrorStatusComment(params) {
|
|
964
|
+
const { forge, issueRepo, issueNumber, botUser, error, retryCount, maxRetries, nextStep, warnMessage, } = params;
|
|
965
|
+
const sanitizedError = sanitizeErrorForComment(error);
|
|
966
|
+
const body = formatStatusComment({
|
|
967
|
+
error: sanitizedError,
|
|
968
|
+
retryCount,
|
|
969
|
+
maxRetries,
|
|
970
|
+
nextStep,
|
|
971
|
+
});
|
|
972
|
+
await postStatusComment({
|
|
973
|
+
forge,
|
|
974
|
+
issueRepo,
|
|
975
|
+
issueNumber,
|
|
976
|
+
botUser,
|
|
977
|
+
body,
|
|
978
|
+
warnMessage,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function toErrorMessage(err) {
|
|
982
|
+
if (err instanceof Error && typeof err.message === 'string' && err.message.trim().length > 0) {
|
|
983
|
+
return err.message;
|
|
984
|
+
}
|
|
985
|
+
return String(err);
|
|
986
|
+
}
|
|
987
|
+
function sanitizeErrorForComment(errorMessage) {
|
|
988
|
+
let sanitized = errorMessage
|
|
989
|
+
.replace(/[\r\n]+/g, ' ');
|
|
990
|
+
sanitized = stripControlChars(sanitized);
|
|
991
|
+
sanitized = sanitized
|
|
992
|
+
.replace(/\b(token|secret|password|passwd|api[_-]?key)\s*[:=]\s*([^\s,;]+)/gi, '$1=[REDACTED]')
|
|
993
|
+
.trim();
|
|
994
|
+
for (const pattern of TOKEN_REDACTION_PATTERNS) {
|
|
995
|
+
sanitized = sanitized.replace(pattern, '[REDACTED]');
|
|
996
|
+
}
|
|
997
|
+
sanitized = sanitized.replace(/\s+/g, ' ').trim();
|
|
998
|
+
if (!sanitized)
|
|
999
|
+
return 'unknown error';
|
|
1000
|
+
const clipped = sanitized.length > ERROR_COMMENT_MAX_LENGTH
|
|
1001
|
+
? `${sanitized.slice(0, ERROR_COMMENT_MAX_LENGTH - 1)}…`
|
|
1002
|
+
: sanitized;
|
|
1003
|
+
return escapeMarkdownForComment(clipped);
|
|
1004
|
+
}
|
|
1005
|
+
function escapeMarkdownForComment(value) {
|
|
1006
|
+
return value
|
|
1007
|
+
.replace(/\\/g, '\\\\')
|
|
1008
|
+
.replace(/([`*_#[\]])/g, '\\$1')
|
|
1009
|
+
.replace(/</g, '<')
|
|
1010
|
+
.replace(/>/g, '>')
|
|
1011
|
+
.replace(/@/g, '@\u200B');
|
|
1012
|
+
}
|
|
1013
|
+
function stripControlChars(value) {
|
|
1014
|
+
let out = '';
|
|
1015
|
+
for (const ch of value) {
|
|
1016
|
+
const code = ch.charCodeAt(0);
|
|
1017
|
+
if ((code >= 0 && code <= 31) || code === 127) {
|
|
1018
|
+
out += ' ';
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
out += ch;
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1024
|
+
}
|
|
1025
|
+
/** Issues that returned 404 during comment scan in this process lifecycle.
|
|
1026
|
+
* Bounded: entries are evicted when the key's run reaches a terminal state
|
|
1027
|
+
* via {@link cleanupRunCaches}. */
|
|
1028
|
+
const missingCommentCommandIssues = new Set();
|
|
1029
|
+
function getHttpStatus(err) {
|
|
1030
|
+
if (typeof err !== 'object' || err === null)
|
|
1031
|
+
return null;
|
|
1032
|
+
const e = err;
|
|
1033
|
+
if (typeof e.status === 'number')
|
|
1034
|
+
return e.status;
|
|
1035
|
+
if (typeof e.response?.status === 'number')
|
|
1036
|
+
return e.response.status;
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
async function processCommentCommands(params) {
|
|
1040
|
+
const { config, db, forge, runManager, leaseManager, repoConfig, botUser, } = params;
|
|
1041
|
+
const commandSettings = config.commentCommands ?? { enabled: true, requireCollaborator: false };
|
|
1042
|
+
if (!commandSettings.enabled)
|
|
1043
|
+
return;
|
|
1044
|
+
// Warn once per poll when comment commands accept non-collaborators —
|
|
1045
|
+
// for public repos this means any GitHub user can trigger retry/rebase/
|
|
1046
|
+
// continue/delete operations.
|
|
1047
|
+
if (!commandSettings.requireCollaborator) {
|
|
1048
|
+
logger.warn({ repo: repoConfig.repo }, 'commentCommands.requireCollaborator=false — /orch commands accept any commenter. Enable on public repos.');
|
|
1049
|
+
}
|
|
1050
|
+
const activeRuns = runManager
|
|
1051
|
+
.getActive()
|
|
1052
|
+
.filter((run) => run.repo === repoConfig.repo);
|
|
1053
|
+
const issueRows = [...new Map(activeRuns.map((run) => {
|
|
1054
|
+
const issueRepo = resolveIssueRepo(run.phaseData, repoConfig.repo);
|
|
1055
|
+
return [`${issueRepo}#${run.issueNumber}`, { issue_number: run.issueNumber, issue_repo: issueRepo }];
|
|
1056
|
+
})).values()]
|
|
1057
|
+
.sort((a, b) => a.issue_repo.localeCompare(b.issue_repo) || a.issue_number - b.issue_number);
|
|
1058
|
+
if (issueRows.length === 0)
|
|
1059
|
+
return;
|
|
1060
|
+
const collaboratorCache = new Map();
|
|
1061
|
+
for (const row of issueRows) {
|
|
1062
|
+
const issueKey = `${row.issue_repo}#${row.issue_number}`;
|
|
1063
|
+
if (missingCommentCommandIssues.has(issueKey)) {
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
let comments;
|
|
1067
|
+
try {
|
|
1068
|
+
comments = await forge.listIssueComments(row.issue_repo, row.issue_number);
|
|
1069
|
+
}
|
|
1070
|
+
catch (err) {
|
|
1071
|
+
if (getHttpStatus(err) === 404) {
|
|
1072
|
+
missingCommentCommandIssues.add(issueKey);
|
|
1073
|
+
logger.debug({ repo: row.issue_repo, issueNumber: row.issue_number }, 'Skipping comment command scan for missing or inaccessible issue');
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
throw err;
|
|
1077
|
+
}
|
|
1078
|
+
const parsed = parseOrchCommands(comments, '1970-01-01T00:00:00Z');
|
|
1079
|
+
for (const item of parsed) {
|
|
1080
|
+
if (isCommandProcessed(db, row.issue_repo, row.issue_number, item.commentId))
|
|
1081
|
+
continue;
|
|
1082
|
+
// Track whether the command reached a terminal outcome — applied,
|
|
1083
|
+
// denied (policy decision), or rejected (validated failure). Only
|
|
1084
|
+
// terminal outcomes mark the command as processed; transient
|
|
1085
|
+
// failures must remain retriable on the next poll cycle.
|
|
1086
|
+
let commandStatus = null;
|
|
1087
|
+
try {
|
|
1088
|
+
const allowed = await canExecuteCommentCommand({
|
|
1089
|
+
forge,
|
|
1090
|
+
repo: row.issue_repo,
|
|
1091
|
+
user: item.user,
|
|
1092
|
+
requireCollaborator: commandSettings.requireCollaborator,
|
|
1093
|
+
cache: collaboratorCache,
|
|
1094
|
+
});
|
|
1095
|
+
if (!allowed) {
|
|
1096
|
+
commandStatus = 'denied';
|
|
1097
|
+
logger.info({ repo: repoConfig.repo, issueNumber: row.issue_number, user: item.user, commentId: item.commentId }, 'Ignoring comment command from non-collaborator');
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const result = await executeCommentCommand({
|
|
1101
|
+
command: item.command,
|
|
1102
|
+
db,
|
|
1103
|
+
forge,
|
|
1104
|
+
runManager,
|
|
1105
|
+
leaseManager,
|
|
1106
|
+
repoConfig,
|
|
1107
|
+
issueRepo: row.issue_repo,
|
|
1108
|
+
issueNumber: row.issue_number,
|
|
1109
|
+
botUser,
|
|
1110
|
+
user: item.user,
|
|
1111
|
+
});
|
|
1112
|
+
if (!result.ok) {
|
|
1113
|
+
commandStatus = 'rejected';
|
|
1114
|
+
logger.info({ repo: repoConfig.repo, issueNumber: row.issue_number, command: item.command.type, reason: result.reason }, 'Comment command rejected');
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
commandStatus = 'applied';
|
|
1118
|
+
logger.info({ repo: repoConfig.repo, issueNumber: row.issue_number, command: item.command.type, user: item.user }, 'Comment command applied');
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
catch (err) {
|
|
1122
|
+
// Transient failure: leave commandStatus=null so the command
|
|
1123
|
+
// remains unprocessed and will be retried on the next poll.
|
|
1124
|
+
logger.warn({ repo: repoConfig.repo, issueNumber: row.issue_number, commentId: item.commentId, command: item.command.type, err }, 'Comment command failed (transient — will retry)');
|
|
1125
|
+
}
|
|
1126
|
+
finally {
|
|
1127
|
+
if (commandStatus !== null) {
|
|
1128
|
+
markCommandProcessed(db, row.issue_repo, row.issue_number, item.commentId, `${item.command.type}:${commandStatus}`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
async function canExecuteCommentCommand(params) {
|
|
1135
|
+
const { forge, repo, user, requireCollaborator, cache } = params;
|
|
1136
|
+
if (!requireCollaborator)
|
|
1137
|
+
return true;
|
|
1138
|
+
if (!user)
|
|
1139
|
+
return false;
|
|
1140
|
+
// Cache key must include the repo — a user might be a collaborator on
|
|
1141
|
+
// one linked project but not another, and reusing a single-user cache
|
|
1142
|
+
// across repos in the same scan would erroneously grant or deny access.
|
|
1143
|
+
const cacheKey = `${repo}\n${user}`;
|
|
1144
|
+
const cached = cache.get(cacheKey);
|
|
1145
|
+
if (cached !== undefined)
|
|
1146
|
+
return cached;
|
|
1147
|
+
if (!forge.isCollaborator) {
|
|
1148
|
+
logger.warn({ repo, user }, 'requireCollaborator=true but forge adapter has no isCollaborator() implementation');
|
|
1149
|
+
cache.set(cacheKey, false);
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
1152
|
+
try {
|
|
1153
|
+
const allowed = await forge.isCollaborator(repo, user);
|
|
1154
|
+
cache.set(cacheKey, allowed);
|
|
1155
|
+
return allowed;
|
|
1156
|
+
}
|
|
1157
|
+
catch (err) {
|
|
1158
|
+
logger.warn({ repo, user, err }, 'Failed collaborator check for comment command user');
|
|
1159
|
+
cache.set(cacheKey, false);
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
async function executeCommentCommand(params) {
|
|
1164
|
+
const { command, db, forge, runManager, leaseManager, repoConfig, issueRepo, issueNumber, botUser, user, } = params;
|
|
1165
|
+
switch (command.type) {
|
|
1166
|
+
case 'retry':
|
|
1167
|
+
return queueRetryFromComment({
|
|
1168
|
+
runManager,
|
|
1169
|
+
leaseManager,
|
|
1170
|
+
forge,
|
|
1171
|
+
repoConfig,
|
|
1172
|
+
issueRepo,
|
|
1173
|
+
issueNumber,
|
|
1174
|
+
resetPlan: command.resetPlan,
|
|
1175
|
+
});
|
|
1176
|
+
case 'continue':
|
|
1177
|
+
{
|
|
1178
|
+
const result = await queueContinue(db, forge, repoConfig, issueNumber, botUser, { issueRepo });
|
|
1179
|
+
return result.queued ? { ok: true } : { ok: false, reason: result.reason };
|
|
1180
|
+
}
|
|
1181
|
+
case 'rebase': {
|
|
1182
|
+
// queueRebase currently always verifies after rebase; keep behavior stable.
|
|
1183
|
+
const result = await queueRebase(db, forge, repoConfig, issueNumber, botUser);
|
|
1184
|
+
return result.queued ? { ok: true } : { ok: false, reason: result.reason };
|
|
1185
|
+
}
|
|
1186
|
+
case 'cancel':
|
|
1187
|
+
return cancelRunFromComment({
|
|
1188
|
+
runManager,
|
|
1189
|
+
leaseManager,
|
|
1190
|
+
forge,
|
|
1191
|
+
repoConfig,
|
|
1192
|
+
issueRepo,
|
|
1193
|
+
issueNumber,
|
|
1194
|
+
user,
|
|
1195
|
+
});
|
|
1196
|
+
default: {
|
|
1197
|
+
const exhaustive = command;
|
|
1198
|
+
return { ok: false, reason: `Unsupported command: ${String(exhaustive)}` };
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
async function queueRetryFromComment(params) {
|
|
1203
|
+
const { runManager, leaseManager, forge, repoConfig, issueRepo, issueNumber, resetPlan } = params;
|
|
1204
|
+
const run = runManager.getByRepoAndIssue(repoConfig.repo, issueNumber);
|
|
1205
|
+
if (!run)
|
|
1206
|
+
return { ok: false, reason: 'No run found for issue' };
|
|
1207
|
+
if (run.status === 'running')
|
|
1208
|
+
return { ok: false, reason: 'Run is currently running' };
|
|
1209
|
+
if (!['blocked', 'error', 'review_ready'].includes(run.status)) {
|
|
1210
|
+
return { ok: false, reason: `Retry not allowed from status ${run.status}` };
|
|
1211
|
+
}
|
|
1212
|
+
runManager.update(run.id, {
|
|
1213
|
+
status: 'queued',
|
|
1214
|
+
currentPhase: null,
|
|
1215
|
+
endedAt: null,
|
|
1216
|
+
lastError: null,
|
|
1217
|
+
phaseData: resetPlan ? null : run.phaseData,
|
|
1218
|
+
blockReason: null,
|
|
1219
|
+
});
|
|
1220
|
+
leaseManager.release(issueRepo, issueNumber);
|
|
1221
|
+
if (issueRepo !== repoConfig.repo) {
|
|
1222
|
+
leaseManager.release(repoConfig.repo, issueNumber);
|
|
1223
|
+
}
|
|
1224
|
+
const issue = await forge.getIssue(issueRepo, issueNumber);
|
|
1225
|
+
await transitionLabels(forge, issueRepo, issueNumber, issue.labels, run.status, 'queued', buildLabelConfig(repoConfig, issue.labels));
|
|
1226
|
+
return { ok: true };
|
|
1227
|
+
}
|
|
1228
|
+
async function cancelRunFromComment(params) {
|
|
1229
|
+
const { runManager, leaseManager, forge, repoConfig, issueRepo, issueNumber, user } = params;
|
|
1230
|
+
const run = runManager.getByRepoAndIssue(repoConfig.repo, issueNumber);
|
|
1231
|
+
if (!run)
|
|
1232
|
+
return { ok: false, reason: 'No run found for issue' };
|
|
1233
|
+
if (run.status !== 'running' && run.status !== 'queued') {
|
|
1234
|
+
return { ok: false, reason: `Cancel only supports running/queued runs (current: ${run.status})` };
|
|
1235
|
+
}
|
|
1236
|
+
runManager.update(run.id, {
|
|
1237
|
+
status: 'blocked',
|
|
1238
|
+
endedAt: nowUtcIso(),
|
|
1239
|
+
lastError: `Cancelled by @${user} via comment command`,
|
|
1240
|
+
blockReason: null,
|
|
1241
|
+
});
|
|
1242
|
+
leaseManager.release(issueRepo, issueNumber);
|
|
1243
|
+
if (issueRepo !== repoConfig.repo) {
|
|
1244
|
+
leaseManager.release(repoConfig.repo, issueNumber);
|
|
1245
|
+
}
|
|
1246
|
+
const issue = await forge.getIssue(issueRepo, issueNumber);
|
|
1247
|
+
await transitionLabels(forge, issueRepo, issueNumber, issue.labels, run.status, 'blocked', buildLabelConfig(repoConfig, issue.labels));
|
|
1248
|
+
return { ok: true };
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Block reasons that indicate the coder's work is broken and the branch
|
|
1252
|
+
* should be hard-reset to base on the next attempt. Salvageable states
|
|
1253
|
+
* (reviewer_blocked, iteration_limit, ambiguous_review, verify_config)
|
|
1254
|
+
* preserve the branch so existing work can be continued.
|
|
1255
|
+
*/
|
|
1256
|
+
const TAINTED_BLOCK_REASONS = new Set(['agent_pass_limit', 'cost_limit', 'merge_conflict']);
|
|
1257
|
+
// --- Reaction scanning ---
|
|
1258
|
+
/** In-memory reaction cursors, keyed by "repo#issueNumber".
|
|
1259
|
+
* Bounded: entries are evicted via {@link cleanupRunCaches}. */
|
|
1260
|
+
const reactionCursors = new Map();
|
|
1261
|
+
/**
|
|
1262
|
+
* Evict entries from process-global caches that are keyed by repo+issue.
|
|
1263
|
+
* Called when a run reaches a terminal state so the caches don't grow
|
|
1264
|
+
* unbounded over the daemon's lifetime.
|
|
1265
|
+
*/
|
|
1266
|
+
function cleanupRunCaches(repo, issueNumber) {
|
|
1267
|
+
const key = `${repo}#${issueNumber}`;
|
|
1268
|
+
missingCommentCommandIssues.delete(key);
|
|
1269
|
+
reactionCursors.delete(key);
|
|
1270
|
+
}
|
|
1271
|
+
async function scanAndHandleReactions(params) {
|
|
1272
|
+
const { db, forge, runManager, repoConfig, botUser } = params;
|
|
1273
|
+
// Find review_ready issues with PRs for this repo.
|
|
1274
|
+
const rows = runManager
|
|
1275
|
+
.getActive()
|
|
1276
|
+
.filter((run) => run.repo === repoConfig.repo && run.status === 'review_ready' && run.prNumber !== null)
|
|
1277
|
+
.map((run) => ({
|
|
1278
|
+
id: run.id,
|
|
1279
|
+
repo: run.repo,
|
|
1280
|
+
issue_number: run.issueNumber,
|
|
1281
|
+
pr_number: run.prNumber,
|
|
1282
|
+
}));
|
|
1283
|
+
for (const row of rows) {
|
|
1284
|
+
const cursorKey = `${row.repo}#${row.issue_number}`;
|
|
1285
|
+
const cursor = reactionCursors.get(cursorKey);
|
|
1286
|
+
const result = await scanForReactions(forge, row.repo, row.pr_number, row.issue_number, botUser, cursor);
|
|
1287
|
+
// Update cursor regardless of reactions
|
|
1288
|
+
reactionCursors.set(cursorKey, result.cursor);
|
|
1289
|
+
// Handle each reaction
|
|
1290
|
+
for (const reaction of result.reactions) {
|
|
1291
|
+
try {
|
|
1292
|
+
await handleReaction(reaction, { db, forge, runManager, repoConfig });
|
|
1293
|
+
}
|
|
1294
|
+
catch (err) {
|
|
1295
|
+
logger.warn({ repo: row.repo, issueNumber: row.issue_number, reactionType: reaction.type, err }, 'Failed to handle reaction');
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function extractFollowupPromptFeedback(phaseData) {
|
|
1301
|
+
if (!phaseData)
|
|
1302
|
+
return null;
|
|
1303
|
+
const context = phaseData['reactionContext'];
|
|
1304
|
+
if (typeof context !== 'string' || context.trim().length === 0)
|
|
1305
|
+
return null;
|
|
1306
|
+
const type = typeof phaseData['reactionType'] === 'string' && phaseData['reactionType'].trim().length > 0
|
|
1307
|
+
? phaseData['reactionType']
|
|
1308
|
+
: 'continue';
|
|
1309
|
+
const summary = typeof phaseData['reactionSummary'] === 'string' && phaseData['reactionSummary'].trim().length > 0
|
|
1310
|
+
? phaseData['reactionSummary']
|
|
1311
|
+
: 'Follow-up context available';
|
|
1312
|
+
return { type, summary, context };
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Prioritize follow-up work over fresh issues so reactive runs (especially
|
|
1316
|
+
* merge conflict rebases) are handled promptly and don't starve behind newer
|
|
1317
|
+
* ready issues.
|
|
1318
|
+
*/
|
|
1319
|
+
function prioritizeDiscoveredIssues(runManager, repo, discovered) {
|
|
1320
|
+
const ranked = discovered.map((item) => ({
|
|
1321
|
+
item,
|
|
1322
|
+
rank: getIssueQueuePriority(runManager, repo, item.issue.number),
|
|
1323
|
+
}));
|
|
1324
|
+
ranked.sort((a, b) => a.rank - b.rank);
|
|
1325
|
+
return ranked.map((entry) => entry.item);
|
|
1326
|
+
}
|
|
1327
|
+
function getIssueQueuePriority(runManager, repo, issueNumber) {
|
|
1328
|
+
const queuedRun = runManager.getLatestQueuedByIssue(repo, issueNumber);
|
|
1329
|
+
if (!queuedRun)
|
|
1330
|
+
return 3;
|
|
1331
|
+
const reactionType = queuedRun.phaseData?.reactionType;
|
|
1332
|
+
if (reactionType === 'merge_conflict' || reactionType === 'rebase')
|
|
1333
|
+
return 0;
|
|
1334
|
+
if (typeof reactionType === 'string' && reactionType.length > 0)
|
|
1335
|
+
return 1;
|
|
1336
|
+
return 2;
|
|
1337
|
+
}
|
|
1338
|
+
function selectReplayableRun(run) {
|
|
1339
|
+
if (!run)
|
|
1340
|
+
return null;
|
|
1341
|
+
if (run.status === 'blocked' || run.status === 'review_ready' || run.status === 'error') {
|
|
1342
|
+
return run;
|
|
1343
|
+
}
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
function shouldResetBranch(runManager, repo, issueNumber, currentRunId) {
|
|
1347
|
+
const prior = runManager.getLatestFinishedByIssue(repo, issueNumber, currentRunId);
|
|
1348
|
+
if (!prior)
|
|
1349
|
+
return false;
|
|
1350
|
+
// Infrastructure errors — work is unreliable
|
|
1351
|
+
if (prior.status === 'error')
|
|
1352
|
+
return true;
|
|
1353
|
+
// Blocked with tainted block reason — coder couldn't produce working code
|
|
1354
|
+
if (prior.status === 'blocked' && prior.blockReason && TAINTED_BLOCK_REASONS.has(prior.blockReason))
|
|
1355
|
+
return true;
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
//# sourceMappingURL=poller.js.map
|