agent-control-plane 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/LICENSE +21 -0
- package/README.md +589 -0
- package/SKILL.md +149 -0
- package/assets/workflow-catalog.json +57 -0
- package/bin/audit-issue-routing.sh +74 -0
- package/bin/issue-resource-class.sh +58 -0
- package/bin/label-follow-up-issues.sh +114 -0
- package/bin/pr-risk.sh +532 -0
- package/bin/sync-pr-labels.sh +112 -0
- package/hooks/heartbeat-hooks.sh +573 -0
- package/hooks/issue-reconcile-hooks.sh +217 -0
- package/hooks/pr-reconcile-hooks.sh +225 -0
- package/npm/bin/agent-control-plane.js +1984 -0
- package/npm/public-bin/agent-control-plane +3 -0
- package/package.json +61 -0
- package/tools/bin/agent-cleanup-worktree +247 -0
- package/tools/bin/agent-github-update-labels +66 -0
- package/tools/bin/agent-init-worktree +216 -0
- package/tools/bin/agent-project-archive-run +52 -0
- package/tools/bin/agent-project-capture-worker +46 -0
- package/tools/bin/agent-project-catch-up-merged-prs +137 -0
- package/tools/bin/agent-project-cleanup-session +244 -0
- package/tools/bin/agent-project-detached-launch +107 -0
- package/tools/bin/agent-project-heartbeat-loop +2347 -0
- package/tools/bin/agent-project-open-issue-worktree +89 -0
- package/tools/bin/agent-project-open-pr-worktree +80 -0
- package/tools/bin/agent-project-publish-issue-pr +349 -0
- package/tools/bin/agent-project-reconcile-issue-session +1128 -0
- package/tools/bin/agent-project-reconcile-pr-session +1005 -0
- package/tools/bin/agent-project-retry-state +147 -0
- package/tools/bin/agent-project-run-claude-session +657 -0
- package/tools/bin/agent-project-run-codex-resilient +718 -0
- package/tools/bin/agent-project-run-codex-session +316 -0
- package/tools/bin/agent-project-run-kilo-session +27 -0
- package/tools/bin/agent-project-run-openclaw-session +984 -0
- package/tools/bin/agent-project-run-opencode-session +27 -0
- package/tools/bin/agent-project-sync-anchor-repo +128 -0
- package/tools/bin/agent-project-worker-status +143 -0
- package/tools/bin/audit-agent-worktrees.sh +310 -0
- package/tools/bin/audit-issue-routing.sh +11 -0
- package/tools/bin/audit-retained-layout.sh +58 -0
- package/tools/bin/audit-retained-overlap.sh +135 -0
- package/tools/bin/audit-retained-worktrees.sh +228 -0
- package/tools/bin/branch-verification-guard.sh +351 -0
- package/tools/bin/capture-worker.sh +18 -0
- package/tools/bin/check-skill-contracts.sh +324 -0
- package/tools/bin/cleanup-worktree.sh +44 -0
- package/tools/bin/codex-quota +31 -0
- package/tools/bin/create-follow-up-issue.sh +114 -0
- package/tools/bin/dashboard-launchd-bootstrap.sh +38 -0
- package/tools/bin/flow-config-lib.sh +2127 -0
- package/tools/bin/flow-resident-worker-lib.sh +683 -0
- package/tools/bin/flow-runtime-doctor.sh +97 -0
- package/tools/bin/flow-shell-lib.sh +266 -0
- package/tools/bin/heartbeat-recovery-preflight.sh +106 -0
- package/tools/bin/heartbeat-safe-auto.sh +551 -0
- package/tools/bin/install-dashboard-launchd.sh +152 -0
- package/tools/bin/install-project-launchd.sh +219 -0
- package/tools/bin/issue-publish-scope-guard.sh +242 -0
- package/tools/bin/issue-requires-local-workspace-install.sh +31 -0
- package/tools/bin/issue-resource-class.sh +12 -0
- package/tools/bin/kick-scheduler.sh +75 -0
- package/tools/bin/label-follow-up-issues.sh +14 -0
- package/tools/bin/new-pr-worktree.sh +50 -0
- package/tools/bin/new-worktree.sh +49 -0
- package/tools/bin/pr-risk.sh +12 -0
- package/tools/bin/prepare-worktree.sh +140 -0
- package/tools/bin/profile-activate.sh +109 -0
- package/tools/bin/profile-adopt.sh +219 -0
- package/tools/bin/profile-smoke.sh +461 -0
- package/tools/bin/project-init.sh +189 -0
- package/tools/bin/project-launchd-bootstrap.sh +54 -0
- package/tools/bin/project-remove.sh +155 -0
- package/tools/bin/project-runtime-supervisor.sh +56 -0
- package/tools/bin/project-runtimectl.sh +586 -0
- package/tools/bin/provider-cooldown-state.sh +166 -0
- package/tools/bin/publish-issue-worker.sh +31 -0
- package/tools/bin/reconcile-issue-worker.sh +34 -0
- package/tools/bin/reconcile-pr-worker.sh +34 -0
- package/tools/bin/record-verification.sh +71 -0
- package/tools/bin/render-architecture-infographics.sh +110 -0
- package/tools/bin/render-dashboard-demo-media.sh +333 -0
- package/tools/bin/render-dashboard-snapshot.py +16 -0
- package/tools/bin/render-flow-config.sh +86 -0
- package/tools/bin/retry-state.sh +31 -0
- package/tools/bin/reuse-issue-worktree.sh +75 -0
- package/tools/bin/run-codex-bypass.sh +3 -0
- package/tools/bin/run-codex-safe.sh +3 -0
- package/tools/bin/run-codex-task.sh +231 -0
- package/tools/bin/scaffold-profile.sh +374 -0
- package/tools/bin/serve-dashboard.sh +5 -0
- package/tools/bin/split-retained-slice.sh +124 -0
- package/tools/bin/start-issue-worker.sh +796 -0
- package/tools/bin/start-pr-fix-worker.sh +458 -0
- package/tools/bin/start-pr-merge-repair-worker.sh +8 -0
- package/tools/bin/start-pr-review-worker.sh +227 -0
- package/tools/bin/start-resident-issue-loop.sh +908 -0
- package/tools/bin/sync-agent-repo.sh +52 -0
- package/tools/bin/sync-dependency-baseline.sh +247 -0
- package/tools/bin/sync-pr-labels.sh +12 -0
- package/tools/bin/sync-recurring-issue-checklist.sh +274 -0
- package/tools/bin/sync-shared-agent-home.sh +214 -0
- package/tools/bin/sync-vscode-workspace.sh +157 -0
- package/tools/bin/test-smoke.sh +63 -0
- package/tools/bin/uninstall-project-launchd.sh +55 -0
- package/tools/bin/update-github-labels.sh +14 -0
- package/tools/bin/worker-status.sh +19 -0
- package/tools/bin/workflow-catalog.sh +77 -0
- package/tools/dashboard/app.js +286 -0
- package/tools/dashboard/dashboard_snapshot.py +466 -0
- package/tools/dashboard/index.html +41 -0
- package/tools/dashboard/server.py +64 -0
- package/tools/dashboard/styles.css +351 -0
- package/tools/templates/issue-prompt-template.md +109 -0
- package/tools/templates/pr-fix-template.md +120 -0
- package/tools/templates/pr-merge-repair-template.md +91 -0
- package/tools/templates/pr-review-template.md +62 -0
- package/tools/templates/scheduled-issue-prompt-template.md +62 -0
- package/tools/tests/test-agent-control-plane-npm-cli.sh +279 -0
- package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +56 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +89 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +82 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +90 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +73 -0
- package/tools/tests/test-agent-project-claude-session-wrapper.sh +103 -0
- package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +90 -0
- package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +90 -0
- package/tools/tests/test-agent-project-codex-live-thread-persist.sh +76 -0
- package/tools/tests/test-agent-project-codex-recovery.sh +731 -0
- package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +105 -0
- package/tools/tests/test-agent-project-codex-session-wrapper.sh +97 -0
- package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +81 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +109 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +89 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +113 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +135 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +179 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +119 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +91 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +117 -0
- package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +148 -0
- package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +146 -0
- package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +145 -0
- package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +147 -0
- package/tools/tests/test-agent-project-reconcile-failure-reason.sh +456 -0
- package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +96 -0
- package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +90 -0
- package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +212 -0
- package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +207 -0
- package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +101 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +113 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +117 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +55 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +125 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +118 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +144 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +145 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +139 -0
- package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +242 -0
- package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +142 -0
- package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +106 -0
- package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +66 -0
- package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +129 -0
- package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +69 -0
- package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +43 -0
- package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +46 -0
- package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +79 -0
- package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +56 -0
- package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +72 -0
- package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +125 -0
- package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +178 -0
- package/tools/tests/test-codex-quota-wrapper.sh +37 -0
- package/tools/tests/test-contribution-docs.sh +18 -0
- package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +343 -0
- package/tools/tests/test-create-follow-up-issue.sh +73 -0
- package/tools/tests/test-dashboard-launchd-bootstrap.sh +55 -0
- package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +30 -0
- package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +48 -0
- package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +85 -0
- package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +60 -0
- package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +64 -0
- package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +77 -0
- package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +52 -0
- package/tools/tests/test-flow-resident-reap-stale-controllers.sh +63 -0
- package/tools/tests/test-flow-resolve-codex-quota-tools.sh +104 -0
- package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +27 -0
- package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +79 -0
- package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +115 -0
- package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +117 -0
- package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +96 -0
- package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +96 -0
- package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +96 -0
- package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +170 -0
- package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +201 -0
- package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +201 -0
- package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +160 -0
- package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +133 -0
- package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +157 -0
- package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +181 -0
- package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +160 -0
- package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +134 -0
- package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +162 -0
- package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +136 -0
- package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +139 -0
- package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +119 -0
- package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +140 -0
- package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +142 -0
- package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +141 -0
- package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +65 -0
- package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +179 -0
- package/tools/tests/test-install-dashboard-launchd.sh +78 -0
- package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +87 -0
- package/tools/tests/test-install-project-launchd.sh +110 -0
- package/tools/tests/test-issue-local-workspace-install-policy.sh +81 -0
- package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +70 -0
- package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +36 -0
- package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +47 -0
- package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +132 -0
- package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +64 -0
- package/tools/tests/test-package-funding-metadata.sh +21 -0
- package/tools/tests/test-package-public-metadata.sh +62 -0
- package/tools/tests/test-placeholder-worker-adapters.sh +38 -0
- package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +110 -0
- package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +70 -0
- package/tools/tests/test-pr-risk-fix-label-semantics.sh +114 -0
- package/tools/tests/test-pr-risk-local-first-no-checks.sh +70 -0
- package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +67 -0
- package/tools/tests/test-profile-activate.sh +33 -0
- package/tools/tests/test-profile-adopt-allow-missing-repo.sh +68 -0
- package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +61 -0
- package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +90 -0
- package/tools/tests/test-profile-smoke-collision.sh +44 -0
- package/tools/tests/test-profile-smoke-invalid-claude-config.sh +31 -0
- package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +68 -0
- package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +36 -0
- package/tools/tests/test-profile-smoke.sh +45 -0
- package/tools/tests/test-project-init-force-and-skip-sync.sh +61 -0
- package/tools/tests/test-project-init-repo-slug-mismatch.sh +29 -0
- package/tools/tests/test-project-init.sh +66 -0
- package/tools/tests/test-project-launchd-bootstrap.sh +66 -0
- package/tools/tests/test-project-remove.sh +150 -0
- package/tools/tests/test-project-runtime-supervisor.sh +47 -0
- package/tools/tests/test-project-runtimectl-launchd.sh +115 -0
- package/tools/tests/test-project-runtimectl-missing-profile.sh +54 -0
- package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +108 -0
- package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +95 -0
- package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +59 -0
- package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +85 -0
- package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +78 -0
- package/tools/tests/test-project-runtimectl.sh +212 -0
- package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +39 -0
- package/tools/tests/test-provider-cooldown-state.sh +59 -0
- package/tools/tests/test-public-repo-docs.sh +159 -0
- package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +75 -0
- package/tools/tests/test-render-dashboard-snapshot.sh +149 -0
- package/tools/tests/test-render-flow-config-demo-profile.sh +36 -0
- package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +81 -0
- package/tools/tests/test-render-flow-config.sh +52 -0
- package/tools/tests/test-run-codex-task-claude-routing.sh +125 -0
- package/tools/tests/test-run-codex-task-codex-resident-routing.sh +108 -0
- package/tools/tests/test-run-codex-task-kilo-routing.sh +98 -0
- package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +117 -0
- package/tools/tests/test-run-codex-task-openclaw-routing.sh +113 -0
- package/tools/tests/test-run-codex-task-opencode-routing.sh +98 -0
- package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +146 -0
- package/tools/tests/test-scaffold-profile.sh +108 -0
- package/tools/tests/test-serve-dashboard.sh +93 -0
- package/tools/tests/test-start-issue-worker-blocked-context.sh +129 -0
- package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +189 -0
- package/tools/tests/test-start-issue-worker-local-install-routing.sh +157 -0
- package/tools/tests/test-start-issue-worker-profile-template-routing.sh +149 -0
- package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +212 -0
- package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +219 -0
- package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +155 -0
- package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +199 -0
- package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +275 -0
- package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +185 -0
- package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +152 -0
- package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +186 -0
- package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +212 -0
- package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +148 -0
- package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +194 -0
- package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +198 -0
- package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +145 -0
- package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +67 -0
- package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +70 -0
- package/tools/tests/test-sync-recurring-issue-checklist.sh +95 -0
- package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +66 -0
- package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +47 -0
- package/tools/tests/test-test-smoke.sh +86 -0
- package/tools/tests/test-uninstall-project-launchd.sh +37 -0
- package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +49 -0
- package/tools/tests/test-workflow-catalog.sh +43 -0
- package/tools/vendor/codex-quota/LICENSE +21 -0
- package/tools/vendor/codex-quota/README.md +459 -0
- package/tools/vendor/codex-quota/codex-quota.js +261 -0
- package/tools/vendor/codex-quota/lib/claude-accounts.js +226 -0
- package/tools/vendor/codex-quota/lib/claude-oauth.js +174 -0
- package/tools/vendor/codex-quota/lib/claude-tokens.js +471 -0
- package/tools/vendor/codex-quota/lib/claude-usage.js +929 -0
- package/tools/vendor/codex-quota/lib/codex-accounts.js +205 -0
- package/tools/vendor/codex-quota/lib/codex-tokens.js +326 -0
- package/tools/vendor/codex-quota/lib/codex-usage.js +32 -0
- package/tools/vendor/codex-quota/lib/color.js +72 -0
- package/tools/vendor/codex-quota/lib/constants.js +57 -0
- package/tools/vendor/codex-quota/lib/container.js +143 -0
- package/tools/vendor/codex-quota/lib/display.js +1111 -0
- package/tools/vendor/codex-quota/lib/fs.js +63 -0
- package/tools/vendor/codex-quota/lib/handlers.js +2060 -0
- package/tools/vendor/codex-quota/lib/jwt.js +33 -0
- package/tools/vendor/codex-quota/lib/oauth.js +486 -0
- package/tools/vendor/codex-quota/lib/paths.js +34 -0
- package/tools/vendor/codex-quota/lib/prompts.js +44 -0
- package/tools/vendor/codex-quota/lib/sync.js +1438 -0
- package/tools/vendor/codex-quota/lib/token-match.js +96 -0
- package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +500 -0
- package/tools/vendor/codex-quota-manager/scripts/batch-add.sh +123 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
# shellcheck source=/dev/null
|
|
6
|
+
source "${SCRIPT_DIR}/flow-config-lib.sh"
|
|
7
|
+
# shellcheck source=/dev/null
|
|
8
|
+
source "${SCRIPT_DIR}/flow-resident-worker-lib.sh"
|
|
9
|
+
|
|
10
|
+
ISSUE_ID="${1:?usage: start-issue-worker.sh ISSUE_ID [safe|bypass]}"
|
|
11
|
+
MODE="${2:-safe}"
|
|
12
|
+
WORKSPACE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
13
|
+
FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
14
|
+
if ! flow_require_explicit_profile_selection "${FLOW_SKILL_DIR}" "start-issue-worker.sh"; then
|
|
15
|
+
exit 64
|
|
16
|
+
fi
|
|
17
|
+
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
18
|
+
flow_export_execution_env "${CONFIG_YAML}"
|
|
19
|
+
flow_export_project_env_aliases
|
|
20
|
+
AGENT_ROOT="$(flow_resolve_agent_root "${CONFIG_YAML}")"
|
|
21
|
+
ISSUE_SESSION_PREFIX="$(flow_resolve_issue_session_prefix "${CONFIG_YAML}")"
|
|
22
|
+
RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
|
|
23
|
+
HISTORY_ROOT="$(flow_resolve_history_root "${CONFIG_YAML}")"
|
|
24
|
+
REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
|
|
25
|
+
AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
|
|
26
|
+
FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
|
|
27
|
+
TEMPLATE_FILE="$(flow_resolve_template_file "issue-prompt-template.md" "${WORKSPACE_DIR}" "${CONFIG_YAML}")"
|
|
28
|
+
SCHEDULED_TEMPLATE_FILE="$(flow_resolve_template_file "scheduled-issue-prompt-template.md" "${WORKSPACE_DIR}" "${CONFIG_YAML}")"
|
|
29
|
+
LOCAL_INSTALL_POLICY_BIN="${WORKSPACE_DIR}/bin/issue-requires-local-workspace-install.sh"
|
|
30
|
+
SESSION="${ISSUE_SESSION_PREFIX}${ISSUE_ID}"
|
|
31
|
+
RUN_DIR="${RUNS_ROOT}/${SESSION}"
|
|
32
|
+
UPDATE_LABELS_BIN="${WORKSPACE_DIR}/bin/agent-github-update-labels"
|
|
33
|
+
CODING_WORKER="${ACP_CODING_WORKER:-${F_LOSNING_CODING_WORKER:-codex}}"
|
|
34
|
+
launch_success="no"
|
|
35
|
+
label_rollback_armed="no"
|
|
36
|
+
RECURRING_CHECKLIST_SYNC_BIN="${FLOW_TOOLS_DIR}/sync-recurring-issue-checklist.sh"
|
|
37
|
+
|
|
38
|
+
issue_block_and_exit() {
|
|
39
|
+
local comment_body="${1:?comment body required}"
|
|
40
|
+
local comment_marker="${2:?comment marker required}"
|
|
41
|
+
|
|
42
|
+
if ! jq -e --arg marker "$comment_marker" 'any(.comments[]?; (.body // "") | contains($marker))' >/dev/null <<<"$ISSUE_JSON"; then
|
|
43
|
+
flow_github_api_repo "$REPO_SLUG" "issues/${ISSUE_ID}/comments" --method POST -f body="$comment_body" >/dev/null 2>&1 || true
|
|
44
|
+
fi
|
|
45
|
+
if [[ -x "${UPDATE_LABELS_BIN}" ]]; then
|
|
46
|
+
bash "${UPDATE_LABELS_BIN}" --repo-slug "${REPO_SLUG}" --number "${ISSUE_ID}" --add agent-blocked --remove agent-running >/dev/null 2>&1 || true
|
|
47
|
+
fi
|
|
48
|
+
label_rollback_armed="no"
|
|
49
|
+
launch_success="yes"
|
|
50
|
+
exit 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rollback_labels_on_failure() {
|
|
54
|
+
if [[ "${label_rollback_armed}" != "yes" || "${launch_success}" == "yes" ]]; then
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
if [[ -x "${UPDATE_LABELS_BIN}" ]]; then
|
|
58
|
+
bash "${UPDATE_LABELS_BIN}" --repo-slug "${REPO_SLUG}" --number "${ISSUE_ID}" --remove agent-running >/dev/null 2>&1 || true
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
recurring_checklist_total="0"
|
|
63
|
+
recurring_checklist_unchecked="0"
|
|
64
|
+
recurring_checklist_matched_pr_numbers=""
|
|
65
|
+
|
|
66
|
+
refresh_recurring_issue_from_github() {
|
|
67
|
+
ISSUE_JSON="$(flow_github_issue_view_json "$REPO_SLUG" "$ISSUE_ID")"
|
|
68
|
+
ISSUE_TITLE="$(jq -r '.title' <<<"$ISSUE_JSON")"
|
|
69
|
+
ISSUE_BODY="$(jq -r '.body // ""' <<<"$ISSUE_JSON")"
|
|
70
|
+
ISSUE_URL="$(jq -r '.url' <<<"$ISSUE_JSON")"
|
|
71
|
+
ISSUE_AUTOMERGE="$(jq -r 'if any(.labels[]?; .name == "agent-automerge") then "yes" else "no" end' <<<"$ISSUE_JSON")"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
refresh_recurring_issue_checklist_state() {
|
|
75
|
+
local sync_out=""
|
|
76
|
+
|
|
77
|
+
recurring_checklist_total="0"
|
|
78
|
+
recurring_checklist_unchecked="0"
|
|
79
|
+
recurring_checklist_matched_pr_numbers=""
|
|
80
|
+
|
|
81
|
+
[[ "${ISSUE_IS_KEEP_OPEN:-no}" == "yes" ]] || return 0
|
|
82
|
+
[[ -x "${RECURRING_CHECKLIST_SYNC_BIN}" ]] || return 0
|
|
83
|
+
|
|
84
|
+
sync_out="$(
|
|
85
|
+
ACP_REPO_ID="${ACP_REPO_ID:-${F_LOSNING_REPO_ID:-}}" \
|
|
86
|
+
bash "${RECURRING_CHECKLIST_SYNC_BIN}" \
|
|
87
|
+
--repo-slug "${REPO_SLUG}" \
|
|
88
|
+
--issue-id "${ISSUE_ID}" 2>/dev/null || true
|
|
89
|
+
)"
|
|
90
|
+
|
|
91
|
+
recurring_checklist_total="$(awk -F= '/^CHECKLIST_TOTAL=/{print $2; exit}' <<<"${sync_out}")"
|
|
92
|
+
recurring_checklist_unchecked="$(awk -F= '/^CHECKLIST_UNCHECKED=/{print $2; exit}' <<<"${sync_out}")"
|
|
93
|
+
recurring_checklist_matched_pr_numbers="$(awk -F= '/^CHECKLIST_MATCHED_PR_NUMBERS=/{print $2; exit}' <<<"${sync_out}")"
|
|
94
|
+
|
|
95
|
+
case "${recurring_checklist_total}" in
|
|
96
|
+
''|*[!0-9]*) recurring_checklist_total="0" ;;
|
|
97
|
+
esac
|
|
98
|
+
case "${recurring_checklist_unchecked}" in
|
|
99
|
+
''|*[!0-9]*) recurring_checklist_unchecked="0" ;;
|
|
100
|
+
esac
|
|
101
|
+
|
|
102
|
+
refresh_recurring_issue_from_github
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
block_if_recurring_checklist_complete() {
|
|
106
|
+
[[ "${ISSUE_IS_KEEP_OPEN:-no}" == "yes" ]] || return 0
|
|
107
|
+
|
|
108
|
+
refresh_recurring_issue_checklist_state
|
|
109
|
+
|
|
110
|
+
if [[ "${recurring_checklist_total}" -gt 0 && "${recurring_checklist_unchecked}" -eq 0 ]]; then
|
|
111
|
+
local blocker_comment=""
|
|
112
|
+
|
|
113
|
+
blocker_comment="$(cat <<EOF
|
|
114
|
+
# Blocker: All checklist items already completed
|
|
115
|
+
|
|
116
|
+
All checklist items for issue #${ISSUE_ID} appear to be satisfied on the current baseline.
|
|
117
|
+
|
|
118
|
+
Why this run was stopped early:
|
|
119
|
+
- recurring automation should not spend another worker cycle when every listed improvement is already checked off
|
|
120
|
+
- the issue body was refreshed against merged PR history before this decision
|
|
121
|
+
|
|
122
|
+
Required next step:
|
|
123
|
+
- refresh the issue body with new unchecked improvement items before re-queueing this issue
|
|
124
|
+
EOF
|
|
125
|
+
)"
|
|
126
|
+
|
|
127
|
+
if [[ -n "${recurring_checklist_matched_pr_numbers}" ]]; then
|
|
128
|
+
blocker_comment="${blocker_comment}"$'\n\n'"Recently matched PRs: #$(sed 's/,/, #/g' <<<"${recurring_checklist_matched_pr_numbers}")"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
issue_block_and_exit "${blocker_comment}" "# Blocker: All checklist items already completed"
|
|
132
|
+
fi
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
reap_stale_run_dir() {
|
|
136
|
+
if [[ ! -d "$RUN_DIR" ]]; then
|
|
137
|
+
return 0
|
|
138
|
+
fi
|
|
139
|
+
if [[ -f "$RUN_DIR/run.env" ]]; then
|
|
140
|
+
if grep -q '^RESIDENT_WORKER_ENABLED=yes$' "$RUN_DIR/run.env" 2>/dev/null; then
|
|
141
|
+
if "${FLOW_TOOLS_DIR}/agent-project-archive-run" \
|
|
142
|
+
--runs-root "$RUNS_ROOT" \
|
|
143
|
+
--history-root "$HISTORY_ROOT" \
|
|
144
|
+
--session "$SESSION" >/dev/null 2>&1; then
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
if "${WORKSPACE_DIR}/bin/cleanup-worktree.sh" "" "$SESSION" >/dev/null 2>&1; then
|
|
149
|
+
return 0
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
mkdir -p "$HISTORY_ROOT"
|
|
153
|
+
mv "$RUN_DIR" "${HISTORY_ROOT}/${SESSION}-stale-$(date +%Y%m%d-%H%M%S)"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if tmux has-session -t "$SESSION" 2>/dev/null; then
|
|
157
|
+
echo "worker session already exists: $SESSION" >&2
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
label_rollback_armed="yes"
|
|
162
|
+
trap rollback_labels_on_failure EXIT INT TERM
|
|
163
|
+
|
|
164
|
+
refresh_recurring_issue_from_github
|
|
165
|
+
ISSUE_SCHEDULE_INTERVAL_SECONDS="$(
|
|
166
|
+
ISSUE_BODY="$ISSUE_BODY" node <<'EOF'
|
|
167
|
+
const body = process.env.ISSUE_BODY || '';
|
|
168
|
+
const match = body.match(/^\s*(?:Agent schedule|Schedule|Cadence)\s*:\s*(?:every\s+)?(\d+)\s*([mhd])\s*$/im);
|
|
169
|
+
if (!match) {
|
|
170
|
+
process.stdout.write('0\n');
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
const value = Number(match[1]);
|
|
174
|
+
const unit = String(match[2] || '').toLowerCase();
|
|
175
|
+
const multiplier = { m: 60, h: 3600, d: 86400 }[unit] || 0;
|
|
176
|
+
const seconds = Number.isFinite(value) && value > 0 ? value * multiplier : 0;
|
|
177
|
+
process.stdout.write(`${seconds}\n`);
|
|
178
|
+
EOF
|
|
179
|
+
)"
|
|
180
|
+
ISSUE_REQUIRES_LOCAL_WORKSPACE_INSTALL="$(
|
|
181
|
+
ISSUE_BODY="$ISSUE_BODY" bash "$LOCAL_INSTALL_POLICY_BIN"
|
|
182
|
+
)"
|
|
183
|
+
if [[ "${ISSUE_SCHEDULE_INTERVAL_SECONDS}" =~ ^[1-9][0-9]*$ ]]; then
|
|
184
|
+
TEMPLATE_FILE="${SCHEDULED_TEMPLATE_FILE}"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
ISSUE_IS_KEEP_OPEN="$(jq -r 'if any(.labels[]?; .name == "agent-keep-open") then "yes" else "no" end' <<<"$ISSUE_JSON")"
|
|
188
|
+
RESIDENT_WORKER_ENABLED="no"
|
|
189
|
+
RESIDENT_WORKER_KEY=""
|
|
190
|
+
RESIDENT_WORKER_DIR=""
|
|
191
|
+
RESIDENT_WORKER_META_FILE=""
|
|
192
|
+
RESIDENT_LANE_KIND=""
|
|
193
|
+
RESIDENT_LANE_VALUE=""
|
|
194
|
+
RESIDENT_WORKTREE_REUSED="no"
|
|
195
|
+
RESIDENT_TASK_COUNT="0"
|
|
196
|
+
RESIDENT_OPENCLAW_AGENT_ID=""
|
|
197
|
+
RESIDENT_OPENCLAW_SESSION_ID=""
|
|
198
|
+
RESIDENT_OPENCLAW_AGENT_DIR=""
|
|
199
|
+
RESIDENT_OPENCLAW_STATE_DIR=""
|
|
200
|
+
RESIDENT_OPENCLAW_CONFIG_PATH=""
|
|
201
|
+
RESIDENT_WORKTREE_REALPATH=""
|
|
202
|
+
|
|
203
|
+
if flow_resident_issue_backend_supported "${CODING_WORKER}" \
|
|
204
|
+
&& flow_is_truthy "$(flow_resident_issue_workers_enabled "${CONFIG_YAML}")" \
|
|
205
|
+
&& ( [[ "${ISSUE_IS_KEEP_OPEN}" == "yes" ]] || [[ "${ISSUE_SCHEDULE_INTERVAL_SECONDS}" =~ ^[1-9][0-9]*$ ]] ); then
|
|
206
|
+
RESIDENT_WORKER_ENABLED="yes"
|
|
207
|
+
if [[ "${ISSUE_SCHEDULE_INTERVAL_SECONDS}" =~ ^[1-9][0-9]*$ ]]; then
|
|
208
|
+
RESIDENT_LANE_KIND="scheduled"
|
|
209
|
+
RESIDENT_LANE_VALUE="${ISSUE_SCHEDULE_INTERVAL_SECONDS}"
|
|
210
|
+
else
|
|
211
|
+
RESIDENT_LANE_KIND="recurring"
|
|
212
|
+
RESIDENT_LANE_VALUE="general"
|
|
213
|
+
fi
|
|
214
|
+
RESIDENT_WORKER_KEY="$(flow_resident_issue_lane_key "${CODING_WORKER}" "${MODE}" "${RESIDENT_LANE_KIND}" "${RESIDENT_LANE_VALUE}")"
|
|
215
|
+
RESIDENT_WORKER_DIR="$(flow_resident_issue_lane_dir "${CONFIG_YAML}" "${RESIDENT_WORKER_KEY}")"
|
|
216
|
+
RESIDENT_WORKER_META_FILE="$(flow_resident_issue_lane_meta_file "${CONFIG_YAML}" "${RESIDENT_WORKER_KEY}")"
|
|
217
|
+
if [[ "${CODING_WORKER}" == "openclaw" ]]; then
|
|
218
|
+
RESIDENT_OPENCLAW_AGENT_ID="$(flow_resident_issue_lane_openclaw_agent_id "${CONFIG_YAML}" "${RESIDENT_WORKER_KEY}")"
|
|
219
|
+
RESIDENT_OPENCLAW_SESSION_ID="$(flow_resident_issue_lane_openclaw_session_id "${CONFIG_YAML}" "${RESIDENT_WORKER_KEY}")"
|
|
220
|
+
RESIDENT_OPENCLAW_AGENT_DIR="${RESIDENT_WORKER_DIR}/openclaw-agent"
|
|
221
|
+
RESIDENT_OPENCLAW_STATE_DIR="${RESIDENT_WORKER_DIR}/openclaw-state"
|
|
222
|
+
RESIDENT_OPENCLAW_CONFIG_PATH="${RESIDENT_WORKER_DIR}/openclaw-config/openclaw.json"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [[ -d "$RUN_DIR" ]]; then
|
|
227
|
+
reap_stale_run_dir
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
block_if_recurring_checklist_complete
|
|
231
|
+
|
|
232
|
+
mkdir -p "$RUN_DIR"
|
|
233
|
+
|
|
234
|
+
MISSING_CHANGE_PATHS="$(
|
|
235
|
+
ISSUE_BODY="$ISSUE_BODY" AGENT_REPO_ROOT="$AGENT_REPO_ROOT" node <<'EOF'
|
|
236
|
+
const fs = require('fs');
|
|
237
|
+
const path = require('path');
|
|
238
|
+
|
|
239
|
+
const body = process.env.ISSUE_BODY || '';
|
|
240
|
+
const repoRoot = process.env.AGENT_REPO_ROOT || '';
|
|
241
|
+
const matches = Array.from(body.matchAll(/openspec\/changes\/[A-Za-z0-9._/-]+/g))
|
|
242
|
+
.map((match) => match[0]);
|
|
243
|
+
const unique = [...new Set(matches)];
|
|
244
|
+
const missing = unique.filter((relativePath) => !fs.existsSync(path.join(repoRoot, relativePath)));
|
|
245
|
+
process.stdout.write(missing.join('\n'));
|
|
246
|
+
EOF
|
|
247
|
+
)"
|
|
248
|
+
|
|
249
|
+
if [[ -n "$MISSING_CHANGE_PATHS" ]]; then
|
|
250
|
+
missing_bullets="$(printf '%s\n' "$MISSING_CHANGE_PATHS" | sed 's/^/- /')"
|
|
251
|
+
issue_block_and_exit \
|
|
252
|
+
"Blocked on missing referenced OpenSpec paths for issue #${ISSUE_ID}.
|
|
253
|
+
|
|
254
|
+
The issue body points at change-package paths that do not exist in the clean baseline checkout used by automation:
|
|
255
|
+
${missing_bullets}
|
|
256
|
+
|
|
257
|
+
Why this blocks safe implementation:
|
|
258
|
+
- the issue is asking automation to follow a planning/change artifact that is not present in the repo baseline
|
|
259
|
+
- without a real canonical source, the worker would have to invent scope or requirements
|
|
260
|
+
|
|
261
|
+
Fastest unblock:
|
|
262
|
+
- update the issue to point at existing canonical specs/paths, or
|
|
263
|
+
- restore the missing OpenSpec change package before re-queueing this issue." \
|
|
264
|
+
"Blocked on missing referenced OpenSpec paths for issue #${ISSUE_ID}."
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
ISSUE_RECURRING_CONTEXT_FILE="${RUN_DIR}/issue-recurring-context.md"
|
|
268
|
+
ISSUE_JSON="$ISSUE_JSON" REPO_SLUG="$REPO_SLUG" FLOW_CONFIG_LIB_PATH="${SCRIPT_DIR}/flow-config-lib.sh" node <<'EOF' >"$ISSUE_RECURRING_CONTEXT_FILE"
|
|
269
|
+
const { execFileSync } = require('child_process');
|
|
270
|
+
|
|
271
|
+
const issue = JSON.parse(process.env.ISSUE_JSON || '{}');
|
|
272
|
+
const repoSlug = process.env.REPO_SLUG || '';
|
|
273
|
+
const isRecurring = Array.isArray(issue.labels)
|
|
274
|
+
&& issue.labels.some((label) => label && label.name === 'agent-keep-open');
|
|
275
|
+
|
|
276
|
+
if (!isRecurring) {
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const prNumbers = [];
|
|
281
|
+
for (const comment of issue.comments || []) {
|
|
282
|
+
const body = comment?.body || '';
|
|
283
|
+
for (const match of body.matchAll(/Opened PR #(\d+)/g)) {
|
|
284
|
+
const number = Number(match[1]);
|
|
285
|
+
if (number && !prNumbers.includes(number)) {
|
|
286
|
+
prNumbers.push(number);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const recentNumbers = prNumbers.slice(-5).reverse();
|
|
292
|
+
const recentPrs = recentNumbers.map((number) => {
|
|
293
|
+
try {
|
|
294
|
+
const raw = execFileSync(
|
|
295
|
+
'bash',
|
|
296
|
+
['-lc', `source "${process.env.FLOW_CONFIG_LIB_PATH}"; flow_github_pr_view_json "${repoSlug}" "${number}"`],
|
|
297
|
+
{ encoding: 'utf8' },
|
|
298
|
+
);
|
|
299
|
+
const pr = JSON.parse(raw);
|
|
300
|
+
return {
|
|
301
|
+
number: pr.number,
|
|
302
|
+
title: pr.title,
|
|
303
|
+
url: pr.url,
|
|
304
|
+
state: pr.isDraft ? 'draft' : String(pr.state || '').toLowerCase(),
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return {
|
|
308
|
+
number,
|
|
309
|
+
title: 'Unable to load PR details',
|
|
310
|
+
url: '',
|
|
311
|
+
state: 'unknown',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const activePrs = recentPrs.filter((pr) => pr.state === 'open' || pr.state === 'draft');
|
|
317
|
+
const completedPrs = recentPrs.filter((pr) => pr.state !== 'open' && pr.state !== 'draft');
|
|
318
|
+
|
|
319
|
+
const formatPr = (pr) => {
|
|
320
|
+
const suffix = pr.url ? ` ${pr.url}` : '';
|
|
321
|
+
return `- #${pr.number} (${pr.state}): ${pr.title}${suffix}`;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const lines = [
|
|
325
|
+
'',
|
|
326
|
+
'## Recurring Issue Guardrails',
|
|
327
|
+
'Because this issue carries `agent-keep-open`:',
|
|
328
|
+
'- Before editing, choose exactly one concrete target module, screen, or flow and keep the cycle limited to that target.',
|
|
329
|
+
'- Do not work on a target already covered by an open or draft PR for this issue, or by the most recent completed cycles listed below, unless you are explicitly fixing a regression introduced there.',
|
|
330
|
+
'- If you cannot identify a small non-overlapping target after reviewing recent cycle history, stop blocked using the blocker contract instead of forcing another PR.',
|
|
331
|
+
'- In your final worker output, start with `Target:` and `Why now:` lines before the changed-files list.',
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
if (activePrs.length > 0) {
|
|
335
|
+
lines.push('', '### Active PRs to avoid overlapping');
|
|
336
|
+
for (const pr of activePrs) {
|
|
337
|
+
lines.push(formatPr(pr));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (completedPrs.length > 0) {
|
|
342
|
+
lines.push('', '### Most recent completed cycles');
|
|
343
|
+
for (const pr of completedPrs) {
|
|
344
|
+
lines.push(formatPr(pr));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
349
|
+
EOF
|
|
350
|
+
ISSUE_RECURRING_CONTEXT="$(cat "$ISSUE_RECURRING_CONTEXT_FILE")"
|
|
351
|
+
ISSUE_RETRY_STATE="$(
|
|
352
|
+
bash "${WORKSPACE_DIR}/bin/retry-state.sh" issue "$ISSUE_ID" get 2>/dev/null || true
|
|
353
|
+
)"
|
|
354
|
+
ISSUE_BLOCKER_CONTEXT="$(
|
|
355
|
+
ISSUE_JSON="$ISSUE_JSON" ISSUE_RETRY_STATE="$ISSUE_RETRY_STATE" node <<'EOF'
|
|
356
|
+
const issue = JSON.parse(process.env.ISSUE_JSON || '{}');
|
|
357
|
+
const labels = new Set((issue.labels || []).map((label) => label?.name).filter(Boolean));
|
|
358
|
+
const retryState = String(process.env.ISSUE_RETRY_STATE || '');
|
|
359
|
+
|
|
360
|
+
const retryMap = Object.fromEntries(
|
|
361
|
+
retryState
|
|
362
|
+
.split(/\r?\n/)
|
|
363
|
+
.map((line) => line.trim())
|
|
364
|
+
.filter(Boolean)
|
|
365
|
+
.map((line) => {
|
|
366
|
+
const idx = line.indexOf('=');
|
|
367
|
+
if (idx === -1) {
|
|
368
|
+
return [line, ''];
|
|
369
|
+
}
|
|
370
|
+
return [line.slice(0, idx), line.slice(idx + 1)];
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
const attempts = Number.parseInt(retryMap.ATTEMPTS || '0', 10);
|
|
374
|
+
const lastReason = String(retryMap.LAST_REASON || '').trim();
|
|
375
|
+
const nextAttemptAt = String(retryMap.NEXT_ATTEMPT_AT || '').trim();
|
|
376
|
+
|
|
377
|
+
if (!labels.has('agent-blocked')) {
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const blockerComment = [...(issue.comments || [])]
|
|
382
|
+
.reverse()
|
|
383
|
+
.find((comment) =>
|
|
384
|
+
/Host-side publish blocked for session|Host-side publish failed for session|Blocked on missing referenced OpenSpec paths for issue|Superseded by focused follow-up issues:|Why it was blocked:|^# Blocker:/i.test(
|
|
385
|
+
comment?.body || '',
|
|
386
|
+
),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
if (!blockerComment || !blockerComment.body) {
|
|
390
|
+
const fallbackLines = [
|
|
391
|
+
'',
|
|
392
|
+
'## Prior Blocker Context',
|
|
393
|
+
'This issue is being retried after an `agent-blocked` stop.',
|
|
394
|
+
'- First resolve the prior blocker instead of repeating the same broad implementation path.',
|
|
395
|
+
];
|
|
396
|
+
if (lastReason) {
|
|
397
|
+
fallbackLines.push(`- Last recorded blocker: \`${lastReason}\`.`);
|
|
398
|
+
}
|
|
399
|
+
if (attempts > 0) {
|
|
400
|
+
fallbackLines.push(`- Blocked retries so far: ${attempts}.`);
|
|
401
|
+
}
|
|
402
|
+
if (lastReason === 'scope-guard-blocked' && attempts >= 2) {
|
|
403
|
+
fallbackLines.push(
|
|
404
|
+
'- This issue has already hit the scope guard multiple times. Do not attempt another broad multi-surface patch.',
|
|
405
|
+
`- Either ship one focused slice that stays under the scope guard, or create focused follow-up issues with \`bash "$FLOW_TOOLS_DIR/create-follow-up-issue.sh" --parent ${issue.number} --title "..." --body-file /tmp/follow-up.md\` and supersede the umbrella.`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
process.stdout.write(fallbackLines.join('\n'));
|
|
409
|
+
process.exit(0);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const normalizedBody = String(blockerComment.body).trim();
|
|
413
|
+
const clippedBody =
|
|
414
|
+
normalizedBody.length > 1600 ? `${normalizedBody.slice(0, 1600).trimEnd()}\n\n[truncated]` : normalizedBody;
|
|
415
|
+
|
|
416
|
+
const lines = [
|
|
417
|
+
'',
|
|
418
|
+
'## Prior Blocker Context',
|
|
419
|
+
'This issue is being retried after an `agent-blocked` stop.',
|
|
420
|
+
'- Address the blocker below before attempting a new implementation/publish cycle.',
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
if (lastReason) {
|
|
424
|
+
lines.push(`- Last recorded blocker: \`${lastReason}\`.`);
|
|
425
|
+
}
|
|
426
|
+
if (attempts > 0) {
|
|
427
|
+
lines.push(`- Blocked retries so far: ${attempts}.`);
|
|
428
|
+
}
|
|
429
|
+
if (nextAttemptAt) {
|
|
430
|
+
lines.push(`- Last scheduled retry target was ${nextAttemptAt}.`);
|
|
431
|
+
}
|
|
432
|
+
if (lastReason === 'scope-guard-blocked') {
|
|
433
|
+
lines.push('- Treat this as a scope problem first: narrow to one safe slice or decompose into focused follow-up issues.');
|
|
434
|
+
if (attempts >= 2) {
|
|
435
|
+
lines.push(`- Because the scope guard has already fired multiple times, do not retry the same umbrella patch. Use \`bash "$FLOW_TOOLS_DIR/create-follow-up-issue.sh" --parent ${issue.number} --title "..." --body-file /tmp/follow-up.md\` for the remaining slices, then supersede the umbrella if you covered the full decomposition.`);
|
|
436
|
+
}
|
|
437
|
+
} else if (lastReason === 'verification-guard-blocked') {
|
|
438
|
+
lines.push('- Add the missing verification or shrink the touched surface before attempting another publish cycle.');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
lines.push('', clippedBody);
|
|
442
|
+
process.stdout.write(lines.join('\n'));
|
|
443
|
+
EOF
|
|
444
|
+
)"
|
|
445
|
+
|
|
446
|
+
ISSUE_SLUG="$(printf '%s' "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-+/-/g' | cut -c1-48)"
|
|
447
|
+
if [[ -z "$ISSUE_SLUG" ]]; then
|
|
448
|
+
ISSUE_SLUG="issue"
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
ensure_resident_issue_worktree_alias() {
|
|
452
|
+
local target_worktree=""
|
|
453
|
+
local alias_path=""
|
|
454
|
+
|
|
455
|
+
[[ "${RESIDENT_WORKER_ENABLED}" == "yes" ]] || return 0
|
|
456
|
+
[[ -n "${WORKTREE:-}" && -d "${WORKTREE:-}" ]] || return 0
|
|
457
|
+
[[ -n "${RESIDENT_WORKER_DIR:-}" ]] || return 0
|
|
458
|
+
|
|
459
|
+
target_worktree="$(cd "${WORKTREE}" && pwd -P)"
|
|
460
|
+
alias_path="${RESIDENT_WORKER_DIR}/worktree"
|
|
461
|
+
mkdir -p "${RESIDENT_WORKER_DIR}"
|
|
462
|
+
rm -rf "${alias_path}" 2>/dev/null || true
|
|
463
|
+
ln -s "${target_worktree}" "${alias_path}"
|
|
464
|
+
WORKTREE="${alias_path}"
|
|
465
|
+
RESIDENT_WORKTREE_REALPATH="${target_worktree}"
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
write_resident_issue_metadata_started() {
|
|
469
|
+
local started_at="${1:?started_at required}"
|
|
470
|
+
local resident_lane_kind="${RESIDENT_LANE_KIND:-}"
|
|
471
|
+
local resident_lane_value="${RESIDENT_LANE_VALUE:-}"
|
|
472
|
+
[[ "${RESIDENT_WORKER_ENABLED}" == "yes" ]] || return 0
|
|
473
|
+
|
|
474
|
+
if [[ -z "${resident_lane_kind}" ]]; then
|
|
475
|
+
resident_lane_kind="$(flow_resident_issue_lane_field_from_key "${RESIDENT_WORKER_KEY:-}" kind 2>/dev/null || true)"
|
|
476
|
+
fi
|
|
477
|
+
if [[ -z "${resident_lane_value}" ]]; then
|
|
478
|
+
resident_lane_value="$(flow_resident_issue_lane_field_from_key "${RESIDENT_WORKER_KEY:-}" value 2>/dev/null || true)"
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
flow_resident_write_metadata "${RESIDENT_WORKER_META_FILE}" \
|
|
482
|
+
"RESIDENT_WORKER_KIND=issue" \
|
|
483
|
+
"RESIDENT_WORKER_SCOPE=lane" \
|
|
484
|
+
"RESIDENT_WORKER_KEY=${RESIDENT_WORKER_KEY}" \
|
|
485
|
+
"RESIDENT_LANE_KIND=${resident_lane_kind}" \
|
|
486
|
+
"RESIDENT_LANE_VALUE=${resident_lane_value}" \
|
|
487
|
+
"ISSUE_ID=${ISSUE_ID}" \
|
|
488
|
+
"ADAPTER_ID=$(flow_resolve_adapter_id "${CONFIG_YAML}")" \
|
|
489
|
+
"CODING_WORKER=${CODING_WORKER}" \
|
|
490
|
+
"WORKTREE=${WORKTREE}" \
|
|
491
|
+
"WORKTREE_REALPATH=${RESIDENT_WORKTREE_REALPATH:-${WORKTREE}}" \
|
|
492
|
+
"LAST_BRANCH=${BRANCH}" \
|
|
493
|
+
"OPENCLAW_AGENT_ID=${RESIDENT_OPENCLAW_AGENT_ID}" \
|
|
494
|
+
"OPENCLAW_SESSION_ID=${RESIDENT_OPENCLAW_SESSION_ID}" \
|
|
495
|
+
"OPENCLAW_AGENT_DIR=${RESIDENT_OPENCLAW_AGENT_DIR}" \
|
|
496
|
+
"OPENCLAW_STATE_DIR=${RESIDENT_OPENCLAW_STATE_DIR}" \
|
|
497
|
+
"OPENCLAW_CONFIG_PATH=${RESIDENT_OPENCLAW_CONFIG_PATH}" \
|
|
498
|
+
"TASK_COUNT=${RESIDENT_TASK_COUNT}" \
|
|
499
|
+
"LAST_STARTED_AT=${started_at}" \
|
|
500
|
+
"LAST_FINISHED_AT=${LAST_FINISHED_AT:-}" \
|
|
501
|
+
"LAST_RUN_SESSION=${SESSION}" \
|
|
502
|
+
"LAST_WORKTREE_REUSED=${RESIDENT_WORKTREE_REUSED}" \
|
|
503
|
+
"LAST_STATUS=running"
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
open_or_reuse_issue_worktree() {
|
|
507
|
+
local resident_started_at=""
|
|
508
|
+
local max_tasks=""
|
|
509
|
+
local max_age_seconds=""
|
|
510
|
+
local reuse_output=""
|
|
511
|
+
local current_issue_id="${ISSUE_ID}"
|
|
512
|
+
local current_session="${SESSION}"
|
|
513
|
+
local previous_issue_id=""
|
|
514
|
+
local current_resident_worker_scope="${RESIDENT_WORKER_SCOPE:-lane}"
|
|
515
|
+
local current_resident_worker_key="${RESIDENT_WORKER_KEY}"
|
|
516
|
+
local current_resident_worker_dir="${RESIDENT_WORKER_DIR}"
|
|
517
|
+
local current_resident_worker_meta_file="${RESIDENT_WORKER_META_FILE}"
|
|
518
|
+
local current_resident_lane_kind="${RESIDENT_LANE_KIND}"
|
|
519
|
+
local current_resident_lane_value="${RESIDENT_LANE_VALUE}"
|
|
520
|
+
local current_resident_openclaw_agent_id="${RESIDENT_OPENCLAW_AGENT_ID}"
|
|
521
|
+
local current_resident_openclaw_session_id="${RESIDENT_OPENCLAW_SESSION_ID}"
|
|
522
|
+
local current_resident_openclaw_agent_dir="${RESIDENT_OPENCLAW_AGENT_DIR}"
|
|
523
|
+
local current_resident_openclaw_state_dir="${RESIDENT_OPENCLAW_STATE_DIR}"
|
|
524
|
+
local current_resident_openclaw_config_path="${RESIDENT_OPENCLAW_CONFIG_PATH}"
|
|
525
|
+
|
|
526
|
+
if [[ "${RESIDENT_WORKER_ENABLED}" == "yes" ]]; then
|
|
527
|
+
max_tasks="$(flow_resident_issue_worker_max_tasks "${CONFIG_YAML}")"
|
|
528
|
+
max_age_seconds="$(flow_resident_issue_worker_max_age_seconds "${CONFIG_YAML}")"
|
|
529
|
+
if flow_resident_issue_can_reuse "${RESIDENT_WORKER_META_FILE}" "${max_tasks}" "${max_age_seconds}"; then
|
|
530
|
+
set -a
|
|
531
|
+
# shellcheck source=/dev/null
|
|
532
|
+
source "${RESIDENT_WORKER_META_FILE}"
|
|
533
|
+
set +a
|
|
534
|
+
previous_issue_id="${ISSUE_ID:-}"
|
|
535
|
+
ISSUE_ID="${current_issue_id}"
|
|
536
|
+
SESSION="${current_session}"
|
|
537
|
+
RESIDENT_WORKER_SCOPE="${current_resident_worker_scope}"
|
|
538
|
+
RESIDENT_WORKER_KEY="${current_resident_worker_key}"
|
|
539
|
+
RESIDENT_WORKER_DIR="${current_resident_worker_dir}"
|
|
540
|
+
RESIDENT_WORKER_META_FILE="${current_resident_worker_meta_file}"
|
|
541
|
+
RESIDENT_LANE_KIND="${current_resident_lane_kind}"
|
|
542
|
+
RESIDENT_LANE_VALUE="${current_resident_lane_value}"
|
|
543
|
+
RESIDENT_OPENCLAW_AGENT_ID="${current_resident_openclaw_agent_id}"
|
|
544
|
+
RESIDENT_OPENCLAW_SESSION_ID="${current_resident_openclaw_session_id}"
|
|
545
|
+
RESIDENT_OPENCLAW_AGENT_DIR="${current_resident_openclaw_agent_dir}"
|
|
546
|
+
RESIDENT_OPENCLAW_STATE_DIR="${current_resident_openclaw_state_dir}"
|
|
547
|
+
RESIDENT_OPENCLAW_CONFIG_PATH="${current_resident_openclaw_config_path}"
|
|
548
|
+
RESIDENT_TASK_COUNT="$(( ${TASK_COUNT:-0} + 1 ))"
|
|
549
|
+
RESIDENT_WORKTREE_REUSED="yes"
|
|
550
|
+
if [[ "${CODING_WORKER}" == "openclaw" && -n "${previous_issue_id}" && "${previous_issue_id}" != "${current_issue_id}" ]]; then
|
|
551
|
+
# Keep the resident lane's warm workspace/agent files, but rotate the
|
|
552
|
+
# OpenClaw conversation thread when switching issues to reduce context drift.
|
|
553
|
+
RESIDENT_OPENCLAW_SESSION_ID="$(flow_resident_issue_openclaw_session_id "${CONFIG_YAML}" "${current_issue_id}")"
|
|
554
|
+
fi
|
|
555
|
+
if reuse_output="$("${WORKSPACE_DIR}/bin/reuse-issue-worktree.sh" "${WORKTREE}" "${ISSUE_ID}" "${ISSUE_SLUG}" 2>&1)"; then
|
|
556
|
+
WORKTREE_OUT="${reuse_output}"
|
|
557
|
+
else
|
|
558
|
+
printf 'RESIDENT_REUSE_FALLBACK=issue-%s reason=%s\n' "${ISSUE_ID}" "$(printf '%s' "${reuse_output}" | tr '\n' ' ' | sed 's/ */ /g')" >&2
|
|
559
|
+
RESIDENT_TASK_COUNT="1"
|
|
560
|
+
RESIDENT_WORKTREE_REUSED="no"
|
|
561
|
+
if [[ "$ISSUE_REQUIRES_LOCAL_WORKSPACE_INSTALL" == "yes" ]]; then
|
|
562
|
+
WORKTREE_OUT="$(ACP_WORKTREE_LOCAL_INSTALL=true F_LOSNING_WORKTREE_LOCAL_INSTALL=true "${WORKSPACE_DIR}/bin/new-worktree.sh" "$ISSUE_ID" "$ISSUE_SLUG")"
|
|
563
|
+
else
|
|
564
|
+
WORKTREE_OUT="$("${WORKSPACE_DIR}/bin/new-worktree.sh" "$ISSUE_ID" "$ISSUE_SLUG")"
|
|
565
|
+
fi
|
|
566
|
+
fi
|
|
567
|
+
else
|
|
568
|
+
RESIDENT_TASK_COUNT="1"
|
|
569
|
+
RESIDENT_WORKTREE_REUSED="no"
|
|
570
|
+
if [[ "$ISSUE_REQUIRES_LOCAL_WORKSPACE_INSTALL" == "yes" ]]; then
|
|
571
|
+
WORKTREE_OUT="$(ACP_WORKTREE_LOCAL_INSTALL=true F_LOSNING_WORKTREE_LOCAL_INSTALL=true "${WORKSPACE_DIR}/bin/new-worktree.sh" "$ISSUE_ID" "$ISSUE_SLUG")"
|
|
572
|
+
else
|
|
573
|
+
WORKTREE_OUT="$("${WORKSPACE_DIR}/bin/new-worktree.sh" "$ISSUE_ID" "$ISSUE_SLUG")"
|
|
574
|
+
fi
|
|
575
|
+
fi
|
|
576
|
+
else
|
|
577
|
+
if [[ "$ISSUE_REQUIRES_LOCAL_WORKSPACE_INSTALL" == "yes" ]]; then
|
|
578
|
+
WORKTREE_OUT="$(ACP_WORKTREE_LOCAL_INSTALL=true F_LOSNING_WORKTREE_LOCAL_INSTALL=true "${WORKSPACE_DIR}/bin/new-worktree.sh" "$ISSUE_ID" "$ISSUE_SLUG")"
|
|
579
|
+
else
|
|
580
|
+
WORKTREE_OUT="$("${WORKSPACE_DIR}/bin/new-worktree.sh" "$ISSUE_ID" "$ISSUE_SLUG")"
|
|
581
|
+
fi
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
WORKTREE="$(awk -F= '/^WORKTREE=/{print $2}' <<<"$WORKTREE_OUT")"
|
|
585
|
+
BRANCH="$(awk -F= '/^BRANCH=/{print $2}' <<<"$WORKTREE_OUT")"
|
|
586
|
+
ensure_resident_issue_worktree_alias
|
|
587
|
+
ISSUE_BASELINE_HEAD_SHA="$(git -C "$WORKTREE" rev-parse HEAD)"
|
|
588
|
+
|
|
589
|
+
if [[ "${RESIDENT_WORKER_ENABLED}" == "yes" ]]; then
|
|
590
|
+
resident_started_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
591
|
+
write_resident_issue_metadata_started "${resident_started_at}"
|
|
592
|
+
fi
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
open_or_reuse_issue_worktree
|
|
596
|
+
|
|
597
|
+
PROMPT_FILE="${RUN_DIR}/prompt.md"
|
|
598
|
+
|
|
599
|
+
build_issue_verification_command_snippet() {
|
|
600
|
+
ISSUE_BODY="$ISSUE_BODY" AGENT_REPO_ROOT="$AGENT_REPO_ROOT" node <<'EOF'
|
|
601
|
+
const fs = require('fs');
|
|
602
|
+
const path = require('path');
|
|
603
|
+
|
|
604
|
+
const body = String(process.env.ISSUE_BODY || '');
|
|
605
|
+
const repoRoot = String(process.env.AGENT_REPO_ROOT || '');
|
|
606
|
+
const commands = [];
|
|
607
|
+
const seen = new Set();
|
|
608
|
+
const backtick = String.fromCharCode(96);
|
|
609
|
+
|
|
610
|
+
const addCommand = (value) => {
|
|
611
|
+
const command = String(value || '').trim();
|
|
612
|
+
if (!command || seen.has(command)) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
seen.add(command);
|
|
616
|
+
commands.push(command);
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
for (const line of body.split(/\r?\n/).slice(0, 40)) {
|
|
620
|
+
if (!/^\s*-\s+/.test(line)) continue;
|
|
621
|
+
if (!/(?:\bRun\b|\balso run\b|\bafter code changes\b|\bevery completed cycle\b)/i.test(line)) continue;
|
|
622
|
+
const matches = line.matchAll(new RegExp(backtick + '([^' + backtick + ']+)' + backtick, 'g'));
|
|
623
|
+
for (const match of matches) {
|
|
624
|
+
addCommand(match[1]);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (commands.length === 0 && repoRoot) {
|
|
629
|
+
const packageJsonPath = path.join(repoRoot, 'package.json');
|
|
630
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
631
|
+
try {
|
|
632
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
633
|
+
if (packageJson?.scripts?.test) {
|
|
634
|
+
if (fs.existsSync(path.join(repoRoot, 'pnpm-lock.yaml'))) {
|
|
635
|
+
addCommand('pnpm test');
|
|
636
|
+
} else if (fs.existsSync(path.join(repoRoot, 'yarn.lock'))) {
|
|
637
|
+
addCommand('yarn test');
|
|
638
|
+
} else {
|
|
639
|
+
addCommand('npm test');
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} catch (_error) {
|
|
643
|
+
// Ignore parse errors and fall through to generic guidance.
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (commands.length === 0) {
|
|
649
|
+
addCommand('pnpm test');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const escapeDoubleQuotes = (value) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
653
|
+
const snippet = commands
|
|
654
|
+
.map((command) =>
|
|
655
|
+
command + '\n' +
|
|
656
|
+
'bash "$ACP_FLOW_TOOLS_DIR/record-verification.sh" --run-dir "$ACP_RUN_DIR" --status pass --command "' +
|
|
657
|
+
escapeDoubleQuotes(command) +
|
|
658
|
+
'"',
|
|
659
|
+
)
|
|
660
|
+
.join('\n\n');
|
|
661
|
+
|
|
662
|
+
process.stdout.write(snippet);
|
|
663
|
+
EOF
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
ISSUE_VERIFICATION_COMMAND_SNIPPET="$(build_issue_verification_command_snippet)"
|
|
667
|
+
ISSUE_RESIDENT_CONTEXT=""
|
|
668
|
+
if [[ "${RESIDENT_WORKER_ENABLED}" == "yes" ]]; then
|
|
669
|
+
ISSUE_RESIDENT_CONTEXT="$(cat <<EOF
|
|
670
|
+
|
|
671
|
+
## Resident Worker Context
|
|
672
|
+
|
|
673
|
+
- This recurring/scheduled issue is running in resident-worker mode for the same issue lane.
|
|
674
|
+
- Resident task count for this lane: ${RESIDENT_TASK_COUNT}.
|
|
675
|
+
- Worktree reused from a prior cycle: ${RESIDENT_WORKTREE_REUSED}.
|
|
676
|
+
- Reuse the saved context to avoid rereading the full repo, but first verify the latest repo state before editing so you do not act on stale assumptions.
|
|
677
|
+
- Treat this cycle as one focused slice. Do not reopen broad scope just because prior context is available.
|
|
678
|
+
EOF
|
|
679
|
+
)"
|
|
680
|
+
fi
|
|
681
|
+
ISSUE_QUALITY_GUARDRAILS="$(cat <<'EOF'
|
|
682
|
+
|
|
683
|
+
## Host Quality Guardrails
|
|
684
|
+
|
|
685
|
+
- Before committing, run `git status --short` and remove unrelated generated files or local bootstrap artifacts so only intended code/test/doc changes remain.
|
|
686
|
+
- Do not commit `.agent-session.env`, `.openclaw*`, or incidental lockfile churn unless the issue explicitly changes dependency manifests or package-manager/tooling files.
|
|
687
|
+
- If you changed CLI or operator-facing commands, flags, argument parsing, usage/help text, or entrypoint scripts, add/update regression coverage and record at least one direct invocation that exercises the changed path before you commit.
|
|
688
|
+
EOF
|
|
689
|
+
)"
|
|
690
|
+
|
|
691
|
+
ISSUE_ID="$ISSUE_ID" \
|
|
692
|
+
ISSUE_TITLE="$ISSUE_TITLE" \
|
|
693
|
+
ISSUE_URL="$ISSUE_URL" \
|
|
694
|
+
ISSUE_AUTOMERGE="$ISSUE_AUTOMERGE" \
|
|
695
|
+
ISSUE_BASELINE_HEAD_SHA="$ISSUE_BASELINE_HEAD_SHA" \
|
|
696
|
+
ISSUE_BODY="$ISSUE_BODY" \
|
|
697
|
+
ISSUE_RECURRING_CONTEXT="$ISSUE_RECURRING_CONTEXT" \
|
|
698
|
+
ISSUE_BLOCKER_CONTEXT="$ISSUE_BLOCKER_CONTEXT" \
|
|
699
|
+
ISSUE_VERIFICATION_COMMAND_SNIPPET="$ISSUE_VERIFICATION_COMMAND_SNIPPET" \
|
|
700
|
+
ISSUE_RESIDENT_CONTEXT="$ISSUE_RESIDENT_CONTEXT" \
|
|
701
|
+
ISSUE_QUALITY_GUARDRAILS="$ISSUE_QUALITY_GUARDRAILS" \
|
|
702
|
+
REPO_SLUG="$REPO_SLUG" \
|
|
703
|
+
TEMPLATE_FILE="$TEMPLATE_FILE" \
|
|
704
|
+
node <<'EOF' >"$PROMPT_FILE"
|
|
705
|
+
const fs = require('fs');
|
|
706
|
+
|
|
707
|
+
const template = fs.readFileSync(process.env.TEMPLATE_FILE, 'utf8');
|
|
708
|
+
const replacements = {
|
|
709
|
+
'{ISSUE_ID}': process.env.ISSUE_ID || '',
|
|
710
|
+
'{ISSUE_TITLE}': process.env.ISSUE_TITLE || '',
|
|
711
|
+
'{ISSUE_URL}': process.env.ISSUE_URL || '',
|
|
712
|
+
'{ISSUE_AUTOMERGE}': process.env.ISSUE_AUTOMERGE || 'no',
|
|
713
|
+
'{ISSUE_BASELINE_HEAD_SHA}': process.env.ISSUE_BASELINE_HEAD_SHA || '',
|
|
714
|
+
'{REPO_SLUG}': process.env.REPO_SLUG || '',
|
|
715
|
+
'{ISSUE_BODY}': process.env.ISSUE_BODY || '',
|
|
716
|
+
'{ISSUE_RECURRING_CONTEXT}': process.env.ISSUE_RECURRING_CONTEXT || '',
|
|
717
|
+
'{ISSUE_BLOCKER_CONTEXT}': process.env.ISSUE_BLOCKER_CONTEXT || '',
|
|
718
|
+
'{ISSUE_VERIFICATION_COMMAND_SNIPPET}': process.env.ISSUE_VERIFICATION_COMMAND_SNIPPET || '',
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
let rendered = template;
|
|
722
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
723
|
+
rendered = rendered.split(key).join(value);
|
|
724
|
+
}
|
|
725
|
+
const addendum = String(process.env.ISSUE_QUALITY_GUARDRAILS || '').trim();
|
|
726
|
+
const residentContext = String(process.env.ISSUE_RESIDENT_CONTEXT || '').trim();
|
|
727
|
+
const addendumParts = [residentContext, addendum].filter(Boolean);
|
|
728
|
+
if (addendumParts.length > 0) {
|
|
729
|
+
rendered = `${rendered.trimEnd()}\n\n${addendumParts.join('\n\n')}\n`;
|
|
730
|
+
}
|
|
731
|
+
process.stdout.write(rendered);
|
|
732
|
+
EOF
|
|
733
|
+
|
|
734
|
+
launch_issue_worker() {
|
|
735
|
+
local runner="${1:?runner required}"
|
|
736
|
+
|
|
737
|
+
ACP_ISSUE_ID="$ISSUE_ID" \
|
|
738
|
+
ACP_ISSUE_URL="$ISSUE_URL" \
|
|
739
|
+
ACP_ISSUE_AUTOMERGE="$ISSUE_AUTOMERGE" \
|
|
740
|
+
ACP_RESIDENT_WORKER_ENABLED="$RESIDENT_WORKER_ENABLED" \
|
|
741
|
+
ACP_RESIDENT_WORKER_SCOPE="lane" \
|
|
742
|
+
ACP_RESIDENT_WORKER_KEY="$RESIDENT_WORKER_KEY" \
|
|
743
|
+
ACP_RESIDENT_WORKER_DIR="$RESIDENT_WORKER_DIR" \
|
|
744
|
+
ACP_RESIDENT_WORKER_META_FILE="$RESIDENT_WORKER_META_FILE" \
|
|
745
|
+
ACP_RESIDENT_LANE_KIND="$RESIDENT_LANE_KIND" \
|
|
746
|
+
ACP_RESIDENT_LANE_VALUE="$RESIDENT_LANE_VALUE" \
|
|
747
|
+
ACP_RESIDENT_TASK_COUNT="$RESIDENT_TASK_COUNT" \
|
|
748
|
+
ACP_RESIDENT_WORKTREE_REUSED="$RESIDENT_WORKTREE_REUSED" \
|
|
749
|
+
ACP_RESIDENT_OPENCLAW_AGENT_ID="$RESIDENT_OPENCLAW_AGENT_ID" \
|
|
750
|
+
ACP_RESIDENT_OPENCLAW_SESSION_ID="$RESIDENT_OPENCLAW_SESSION_ID" \
|
|
751
|
+
ACP_RESIDENT_OPENCLAW_AGENT_DIR="$RESIDENT_OPENCLAW_AGENT_DIR" \
|
|
752
|
+
ACP_RESIDENT_OPENCLAW_STATE_DIR="$RESIDENT_OPENCLAW_STATE_DIR" \
|
|
753
|
+
ACP_RESIDENT_OPENCLAW_CONFIG_PATH="$RESIDENT_OPENCLAW_CONFIG_PATH" \
|
|
754
|
+
F_LOSNING_ISSUE_ID="$ISSUE_ID" \
|
|
755
|
+
F_LOSNING_ISSUE_URL="$ISSUE_URL" \
|
|
756
|
+
F_LOSNING_ISSUE_AUTOMERGE="$ISSUE_AUTOMERGE" \
|
|
757
|
+
F_LOSNING_RESIDENT_WORKER_ENABLED="$RESIDENT_WORKER_ENABLED" \
|
|
758
|
+
F_LOSNING_RESIDENT_WORKER_SCOPE="lane" \
|
|
759
|
+
F_LOSNING_RESIDENT_WORKER_KEY="$RESIDENT_WORKER_KEY" \
|
|
760
|
+
F_LOSNING_RESIDENT_WORKER_DIR="$RESIDENT_WORKER_DIR" \
|
|
761
|
+
F_LOSNING_RESIDENT_WORKER_META_FILE="$RESIDENT_WORKER_META_FILE" \
|
|
762
|
+
F_LOSNING_RESIDENT_LANE_KIND="$RESIDENT_LANE_KIND" \
|
|
763
|
+
F_LOSNING_RESIDENT_LANE_VALUE="$RESIDENT_LANE_VALUE" \
|
|
764
|
+
F_LOSNING_RESIDENT_TASK_COUNT="$RESIDENT_TASK_COUNT" \
|
|
765
|
+
F_LOSNING_RESIDENT_WORKTREE_REUSED="$RESIDENT_WORKTREE_REUSED" \
|
|
766
|
+
F_LOSNING_RESIDENT_OPENCLAW_AGENT_ID="$RESIDENT_OPENCLAW_AGENT_ID" \
|
|
767
|
+
F_LOSNING_RESIDENT_OPENCLAW_SESSION_ID="$RESIDENT_OPENCLAW_SESSION_ID" \
|
|
768
|
+
F_LOSNING_RESIDENT_OPENCLAW_AGENT_DIR="$RESIDENT_OPENCLAW_AGENT_DIR" \
|
|
769
|
+
F_LOSNING_RESIDENT_OPENCLAW_STATE_DIR="$RESIDENT_OPENCLAW_STATE_DIR" \
|
|
770
|
+
F_LOSNING_RESIDENT_OPENCLAW_CONFIG_PATH="$RESIDENT_OPENCLAW_CONFIG_PATH" \
|
|
771
|
+
"$runner" "$SESSION" "$WORKTREE" "$PROMPT_FILE"
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
case "$MODE" in
|
|
775
|
+
safe)
|
|
776
|
+
launch_issue_worker "${WORKSPACE_DIR}/bin/run-codex-safe.sh"
|
|
777
|
+
;;
|
|
778
|
+
bypass)
|
|
779
|
+
launch_issue_worker "${WORKSPACE_DIR}/bin/run-codex-bypass.sh"
|
|
780
|
+
;;
|
|
781
|
+
*)
|
|
782
|
+
echo "unknown mode: $MODE" >&2
|
|
783
|
+
exit 1
|
|
784
|
+
;;
|
|
785
|
+
esac
|
|
786
|
+
|
|
787
|
+
launch_success="yes"
|
|
788
|
+
|
|
789
|
+
printf 'ISSUE_ID=%s\n' "$ISSUE_ID"
|
|
790
|
+
printf 'TITLE=%s\n' "$ISSUE_TITLE"
|
|
791
|
+
printf 'URL=%s\n' "$ISSUE_URL"
|
|
792
|
+
printf 'AUTOMERGE=%s\n' "$ISSUE_AUTOMERGE"
|
|
793
|
+
printf 'SESSION=%s\n' "$SESSION"
|
|
794
|
+
printf 'WORKTREE=%s\n' "$WORKTREE"
|
|
795
|
+
printf 'BRANCH=%s\n' "$BRANCH"
|
|
796
|
+
printf 'PROMPT=%s\n' "$PROMPT_FILE"
|