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.
Files changed (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +589 -0
  3. package/SKILL.md +149 -0
  4. package/assets/workflow-catalog.json +57 -0
  5. package/bin/audit-issue-routing.sh +74 -0
  6. package/bin/issue-resource-class.sh +58 -0
  7. package/bin/label-follow-up-issues.sh +114 -0
  8. package/bin/pr-risk.sh +532 -0
  9. package/bin/sync-pr-labels.sh +112 -0
  10. package/hooks/heartbeat-hooks.sh +573 -0
  11. package/hooks/issue-reconcile-hooks.sh +217 -0
  12. package/hooks/pr-reconcile-hooks.sh +225 -0
  13. package/npm/bin/agent-control-plane.js +1984 -0
  14. package/npm/public-bin/agent-control-plane +3 -0
  15. package/package.json +61 -0
  16. package/tools/bin/agent-cleanup-worktree +247 -0
  17. package/tools/bin/agent-github-update-labels +66 -0
  18. package/tools/bin/agent-init-worktree +216 -0
  19. package/tools/bin/agent-project-archive-run +52 -0
  20. package/tools/bin/agent-project-capture-worker +46 -0
  21. package/tools/bin/agent-project-catch-up-merged-prs +137 -0
  22. package/tools/bin/agent-project-cleanup-session +244 -0
  23. package/tools/bin/agent-project-detached-launch +107 -0
  24. package/tools/bin/agent-project-heartbeat-loop +2347 -0
  25. package/tools/bin/agent-project-open-issue-worktree +89 -0
  26. package/tools/bin/agent-project-open-pr-worktree +80 -0
  27. package/tools/bin/agent-project-publish-issue-pr +349 -0
  28. package/tools/bin/agent-project-reconcile-issue-session +1128 -0
  29. package/tools/bin/agent-project-reconcile-pr-session +1005 -0
  30. package/tools/bin/agent-project-retry-state +147 -0
  31. package/tools/bin/agent-project-run-claude-session +657 -0
  32. package/tools/bin/agent-project-run-codex-resilient +718 -0
  33. package/tools/bin/agent-project-run-codex-session +316 -0
  34. package/tools/bin/agent-project-run-kilo-session +27 -0
  35. package/tools/bin/agent-project-run-openclaw-session +984 -0
  36. package/tools/bin/agent-project-run-opencode-session +27 -0
  37. package/tools/bin/agent-project-sync-anchor-repo +128 -0
  38. package/tools/bin/agent-project-worker-status +143 -0
  39. package/tools/bin/audit-agent-worktrees.sh +310 -0
  40. package/tools/bin/audit-issue-routing.sh +11 -0
  41. package/tools/bin/audit-retained-layout.sh +58 -0
  42. package/tools/bin/audit-retained-overlap.sh +135 -0
  43. package/tools/bin/audit-retained-worktrees.sh +228 -0
  44. package/tools/bin/branch-verification-guard.sh +351 -0
  45. package/tools/bin/capture-worker.sh +18 -0
  46. package/tools/bin/check-skill-contracts.sh +324 -0
  47. package/tools/bin/cleanup-worktree.sh +44 -0
  48. package/tools/bin/codex-quota +31 -0
  49. package/tools/bin/create-follow-up-issue.sh +114 -0
  50. package/tools/bin/dashboard-launchd-bootstrap.sh +38 -0
  51. package/tools/bin/flow-config-lib.sh +2127 -0
  52. package/tools/bin/flow-resident-worker-lib.sh +683 -0
  53. package/tools/bin/flow-runtime-doctor.sh +97 -0
  54. package/tools/bin/flow-shell-lib.sh +266 -0
  55. package/tools/bin/heartbeat-recovery-preflight.sh +106 -0
  56. package/tools/bin/heartbeat-safe-auto.sh +551 -0
  57. package/tools/bin/install-dashboard-launchd.sh +152 -0
  58. package/tools/bin/install-project-launchd.sh +219 -0
  59. package/tools/bin/issue-publish-scope-guard.sh +242 -0
  60. package/tools/bin/issue-requires-local-workspace-install.sh +31 -0
  61. package/tools/bin/issue-resource-class.sh +12 -0
  62. package/tools/bin/kick-scheduler.sh +75 -0
  63. package/tools/bin/label-follow-up-issues.sh +14 -0
  64. package/tools/bin/new-pr-worktree.sh +50 -0
  65. package/tools/bin/new-worktree.sh +49 -0
  66. package/tools/bin/pr-risk.sh +12 -0
  67. package/tools/bin/prepare-worktree.sh +140 -0
  68. package/tools/bin/profile-activate.sh +109 -0
  69. package/tools/bin/profile-adopt.sh +219 -0
  70. package/tools/bin/profile-smoke.sh +461 -0
  71. package/tools/bin/project-init.sh +189 -0
  72. package/tools/bin/project-launchd-bootstrap.sh +54 -0
  73. package/tools/bin/project-remove.sh +155 -0
  74. package/tools/bin/project-runtime-supervisor.sh +56 -0
  75. package/tools/bin/project-runtimectl.sh +586 -0
  76. package/tools/bin/provider-cooldown-state.sh +166 -0
  77. package/tools/bin/publish-issue-worker.sh +31 -0
  78. package/tools/bin/reconcile-issue-worker.sh +34 -0
  79. package/tools/bin/reconcile-pr-worker.sh +34 -0
  80. package/tools/bin/record-verification.sh +71 -0
  81. package/tools/bin/render-architecture-infographics.sh +110 -0
  82. package/tools/bin/render-dashboard-demo-media.sh +333 -0
  83. package/tools/bin/render-dashboard-snapshot.py +16 -0
  84. package/tools/bin/render-flow-config.sh +86 -0
  85. package/tools/bin/retry-state.sh +31 -0
  86. package/tools/bin/reuse-issue-worktree.sh +75 -0
  87. package/tools/bin/run-codex-bypass.sh +3 -0
  88. package/tools/bin/run-codex-safe.sh +3 -0
  89. package/tools/bin/run-codex-task.sh +231 -0
  90. package/tools/bin/scaffold-profile.sh +374 -0
  91. package/tools/bin/serve-dashboard.sh +5 -0
  92. package/tools/bin/split-retained-slice.sh +124 -0
  93. package/tools/bin/start-issue-worker.sh +796 -0
  94. package/tools/bin/start-pr-fix-worker.sh +458 -0
  95. package/tools/bin/start-pr-merge-repair-worker.sh +8 -0
  96. package/tools/bin/start-pr-review-worker.sh +227 -0
  97. package/tools/bin/start-resident-issue-loop.sh +908 -0
  98. package/tools/bin/sync-agent-repo.sh +52 -0
  99. package/tools/bin/sync-dependency-baseline.sh +247 -0
  100. package/tools/bin/sync-pr-labels.sh +12 -0
  101. package/tools/bin/sync-recurring-issue-checklist.sh +274 -0
  102. package/tools/bin/sync-shared-agent-home.sh +214 -0
  103. package/tools/bin/sync-vscode-workspace.sh +157 -0
  104. package/tools/bin/test-smoke.sh +63 -0
  105. package/tools/bin/uninstall-project-launchd.sh +55 -0
  106. package/tools/bin/update-github-labels.sh +14 -0
  107. package/tools/bin/worker-status.sh +19 -0
  108. package/tools/bin/workflow-catalog.sh +77 -0
  109. package/tools/dashboard/app.js +286 -0
  110. package/tools/dashboard/dashboard_snapshot.py +466 -0
  111. package/tools/dashboard/index.html +41 -0
  112. package/tools/dashboard/server.py +64 -0
  113. package/tools/dashboard/styles.css +351 -0
  114. package/tools/templates/issue-prompt-template.md +109 -0
  115. package/tools/templates/pr-fix-template.md +120 -0
  116. package/tools/templates/pr-merge-repair-template.md +91 -0
  117. package/tools/templates/pr-review-template.md +62 -0
  118. package/tools/templates/scheduled-issue-prompt-template.md +62 -0
  119. package/tools/tests/test-agent-control-plane-npm-cli.sh +279 -0
  120. package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +56 -0
  121. package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +89 -0
  122. package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +82 -0
  123. package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +90 -0
  124. package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +73 -0
  125. package/tools/tests/test-agent-project-claude-session-wrapper.sh +103 -0
  126. package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +90 -0
  127. package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +90 -0
  128. package/tools/tests/test-agent-project-codex-live-thread-persist.sh +76 -0
  129. package/tools/tests/test-agent-project-codex-recovery.sh +731 -0
  130. package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +105 -0
  131. package/tools/tests/test-agent-project-codex-session-wrapper.sh +97 -0
  132. package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +81 -0
  133. package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +109 -0
  134. package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +89 -0
  135. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +113 -0
  136. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +135 -0
  137. package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +179 -0
  138. package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +119 -0
  139. package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +91 -0
  140. package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +117 -0
  141. package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +148 -0
  142. package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +146 -0
  143. package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +145 -0
  144. package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +147 -0
  145. package/tools/tests/test-agent-project-reconcile-failure-reason.sh +456 -0
  146. package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +96 -0
  147. package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +90 -0
  148. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +212 -0
  149. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +207 -0
  150. package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +101 -0
  151. package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +113 -0
  152. package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +117 -0
  153. package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +55 -0
  154. package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +125 -0
  155. package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +118 -0
  156. package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +144 -0
  157. package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +145 -0
  158. package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +139 -0
  159. package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +242 -0
  160. package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +142 -0
  161. package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +106 -0
  162. package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +66 -0
  163. package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +129 -0
  164. package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +69 -0
  165. package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +43 -0
  166. package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +46 -0
  167. package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +79 -0
  168. package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +56 -0
  169. package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +72 -0
  170. package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +125 -0
  171. package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +178 -0
  172. package/tools/tests/test-codex-quota-wrapper.sh +37 -0
  173. package/tools/tests/test-contribution-docs.sh +18 -0
  174. package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +343 -0
  175. package/tools/tests/test-create-follow-up-issue.sh +73 -0
  176. package/tools/tests/test-dashboard-launchd-bootstrap.sh +55 -0
  177. package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +30 -0
  178. package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +48 -0
  179. package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +85 -0
  180. package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +60 -0
  181. package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +64 -0
  182. package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +77 -0
  183. package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +52 -0
  184. package/tools/tests/test-flow-resident-reap-stale-controllers.sh +63 -0
  185. package/tools/tests/test-flow-resolve-codex-quota-tools.sh +104 -0
  186. package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +27 -0
  187. package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +79 -0
  188. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +115 -0
  189. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +117 -0
  190. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +96 -0
  191. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +96 -0
  192. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +96 -0
  193. package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +170 -0
  194. package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +201 -0
  195. package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +201 -0
  196. package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +160 -0
  197. package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +133 -0
  198. package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +157 -0
  199. package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +181 -0
  200. package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +160 -0
  201. package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +134 -0
  202. package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +162 -0
  203. package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +136 -0
  204. package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +139 -0
  205. package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +119 -0
  206. package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +140 -0
  207. package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +142 -0
  208. package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +141 -0
  209. package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +65 -0
  210. package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +179 -0
  211. package/tools/tests/test-install-dashboard-launchd.sh +78 -0
  212. package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +87 -0
  213. package/tools/tests/test-install-project-launchd.sh +110 -0
  214. package/tools/tests/test-issue-local-workspace-install-policy.sh +81 -0
  215. package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +70 -0
  216. package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +36 -0
  217. package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +47 -0
  218. package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +132 -0
  219. package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +64 -0
  220. package/tools/tests/test-package-funding-metadata.sh +21 -0
  221. package/tools/tests/test-package-public-metadata.sh +62 -0
  222. package/tools/tests/test-placeholder-worker-adapters.sh +38 -0
  223. package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +110 -0
  224. package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +70 -0
  225. package/tools/tests/test-pr-risk-fix-label-semantics.sh +114 -0
  226. package/tools/tests/test-pr-risk-local-first-no-checks.sh +70 -0
  227. package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +67 -0
  228. package/tools/tests/test-profile-activate.sh +33 -0
  229. package/tools/tests/test-profile-adopt-allow-missing-repo.sh +68 -0
  230. package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +61 -0
  231. package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +90 -0
  232. package/tools/tests/test-profile-smoke-collision.sh +44 -0
  233. package/tools/tests/test-profile-smoke-invalid-claude-config.sh +31 -0
  234. package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +68 -0
  235. package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +36 -0
  236. package/tools/tests/test-profile-smoke.sh +45 -0
  237. package/tools/tests/test-project-init-force-and-skip-sync.sh +61 -0
  238. package/tools/tests/test-project-init-repo-slug-mismatch.sh +29 -0
  239. package/tools/tests/test-project-init.sh +66 -0
  240. package/tools/tests/test-project-launchd-bootstrap.sh +66 -0
  241. package/tools/tests/test-project-remove.sh +150 -0
  242. package/tools/tests/test-project-runtime-supervisor.sh +47 -0
  243. package/tools/tests/test-project-runtimectl-launchd.sh +115 -0
  244. package/tools/tests/test-project-runtimectl-missing-profile.sh +54 -0
  245. package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +108 -0
  246. package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +95 -0
  247. package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +59 -0
  248. package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +85 -0
  249. package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +78 -0
  250. package/tools/tests/test-project-runtimectl.sh +212 -0
  251. package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +39 -0
  252. package/tools/tests/test-provider-cooldown-state.sh +59 -0
  253. package/tools/tests/test-public-repo-docs.sh +159 -0
  254. package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +75 -0
  255. package/tools/tests/test-render-dashboard-snapshot.sh +149 -0
  256. package/tools/tests/test-render-flow-config-demo-profile.sh +36 -0
  257. package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +81 -0
  258. package/tools/tests/test-render-flow-config.sh +52 -0
  259. package/tools/tests/test-run-codex-task-claude-routing.sh +125 -0
  260. package/tools/tests/test-run-codex-task-codex-resident-routing.sh +108 -0
  261. package/tools/tests/test-run-codex-task-kilo-routing.sh +98 -0
  262. package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +117 -0
  263. package/tools/tests/test-run-codex-task-openclaw-routing.sh +113 -0
  264. package/tools/tests/test-run-codex-task-opencode-routing.sh +98 -0
  265. package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +146 -0
  266. package/tools/tests/test-scaffold-profile.sh +108 -0
  267. package/tools/tests/test-serve-dashboard.sh +93 -0
  268. package/tools/tests/test-start-issue-worker-blocked-context.sh +129 -0
  269. package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +189 -0
  270. package/tools/tests/test-start-issue-worker-local-install-routing.sh +157 -0
  271. package/tools/tests/test-start-issue-worker-profile-template-routing.sh +149 -0
  272. package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +212 -0
  273. package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +219 -0
  274. package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +155 -0
  275. package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +199 -0
  276. package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +275 -0
  277. package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +185 -0
  278. package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +152 -0
  279. package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +186 -0
  280. package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +212 -0
  281. package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +148 -0
  282. package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +194 -0
  283. package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +198 -0
  284. package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +145 -0
  285. package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +67 -0
  286. package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +70 -0
  287. package/tools/tests/test-sync-recurring-issue-checklist.sh +95 -0
  288. package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +66 -0
  289. package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +47 -0
  290. package/tools/tests/test-test-smoke.sh +86 -0
  291. package/tools/tests/test-uninstall-project-launchd.sh +37 -0
  292. package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +49 -0
  293. package/tools/tests/test-workflow-catalog.sh +43 -0
  294. package/tools/vendor/codex-quota/LICENSE +21 -0
  295. package/tools/vendor/codex-quota/README.md +459 -0
  296. package/tools/vendor/codex-quota/codex-quota.js +261 -0
  297. package/tools/vendor/codex-quota/lib/claude-accounts.js +226 -0
  298. package/tools/vendor/codex-quota/lib/claude-oauth.js +174 -0
  299. package/tools/vendor/codex-quota/lib/claude-tokens.js +471 -0
  300. package/tools/vendor/codex-quota/lib/claude-usage.js +929 -0
  301. package/tools/vendor/codex-quota/lib/codex-accounts.js +205 -0
  302. package/tools/vendor/codex-quota/lib/codex-tokens.js +326 -0
  303. package/tools/vendor/codex-quota/lib/codex-usage.js +32 -0
  304. package/tools/vendor/codex-quota/lib/color.js +72 -0
  305. package/tools/vendor/codex-quota/lib/constants.js +57 -0
  306. package/tools/vendor/codex-quota/lib/container.js +143 -0
  307. package/tools/vendor/codex-quota/lib/display.js +1111 -0
  308. package/tools/vendor/codex-quota/lib/fs.js +63 -0
  309. package/tools/vendor/codex-quota/lib/handlers.js +2060 -0
  310. package/tools/vendor/codex-quota/lib/jwt.js +33 -0
  311. package/tools/vendor/codex-quota/lib/oauth.js +486 -0
  312. package/tools/vendor/codex-quota/lib/paths.js +34 -0
  313. package/tools/vendor/codex-quota/lib/prompts.js +44 -0
  314. package/tools/vendor/codex-quota/lib/sync.js +1438 -0
  315. package/tools/vendor/codex-quota/lib/token-match.js +96 -0
  316. package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +500 -0
  317. package/tools/vendor/codex-quota-manager/scripts/batch-add.sh +123 -0
@@ -0,0 +1,1005 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ bootstrap_flow_shell_lib() {
7
+ local candidate=""
8
+ local skill_name=""
9
+
10
+ for candidate in \
11
+ "${SCRIPT_DIR}/flow-shell-lib.sh" \
12
+ "${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/flow-shell-lib.sh" \
13
+ "${ACP_ROOT:-}/tools/bin/flow-shell-lib.sh" \
14
+ "${F_LOSNING_FLOW_ROOT:-}/tools/bin/flow-shell-lib.sh" \
15
+ "${AGENT_FLOW_SKILL_ROOT:-}/tools/bin/flow-shell-lib.sh" \
16
+ "${SHARED_AGENT_HOME:-}/tools/bin/flow-shell-lib.sh" \
17
+ "$(pwd)/tools/bin/flow-shell-lib.sh"; do
18
+ if [[ -n "${candidate}" && -f "${candidate}" ]]; then
19
+ printf '%s\n' "${candidate}"
20
+ return 0
21
+ fi
22
+ done
23
+
24
+ if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
25
+ for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
26
+ [[ -n "${skill_name}" ]] || continue
27
+ candidate="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}/tools/bin/flow-shell-lib.sh"
28
+ if [[ -f "${candidate}" ]]; then
29
+ printf '%s\n' "${candidate}"
30
+ return 0
31
+ fi
32
+ done
33
+ fi
34
+
35
+ echo "unable to locate flow-shell-lib.sh for reconcile bootstrap" >&2
36
+ return 1
37
+ }
38
+
39
+ FLOW_SHELL_LIB_PATH="$(bootstrap_flow_shell_lib)"
40
+ BOOTSTRAP_TOOLS_DIR="$(cd "$(dirname "${FLOW_SHELL_LIB_PATH}")" && pwd)"
41
+ # shellcheck source=/dev/null
42
+ source "${FLOW_SHELL_LIB_PATH}"
43
+
44
+ resolve_reconcile_tools_dir() {
45
+ local candidate_root=""
46
+ local skill_name=""
47
+
48
+ for candidate_root in \
49
+ "${AGENT_CONTROL_PLANE_ROOT:-}" \
50
+ "${ACP_ROOT:-}" \
51
+ "${F_LOSNING_FLOW_ROOT:-}" \
52
+ "${AGENT_FLOW_SKILL_ROOT:-}"; do
53
+ if [[ -n "${candidate_root}" && -d "${candidate_root}/tools/bin" ]]; then
54
+ printf '%s/tools/bin\n' "${candidate_root}"
55
+ return 0
56
+ fi
57
+ done
58
+
59
+ if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
60
+ if [[ -d "${SHARED_AGENT_HOME}/tools/bin" ]]; then
61
+ printf '%s/tools/bin\n' "${SHARED_AGENT_HOME}"
62
+ return 0
63
+ fi
64
+ for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
65
+ [[ -n "${skill_name}" ]] || continue
66
+ candidate_root="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}"
67
+ if [[ -d "${candidate_root}/tools/bin" ]]; then
68
+ printf '%s/tools/bin\n' "${candidate_root}"
69
+ return 0
70
+ fi
71
+ done
72
+ fi
73
+
74
+ if [[ -d "${SCRIPT_DIR}" ]]; then
75
+ printf '%s\n' "${SCRIPT_DIR}"
76
+ return 0
77
+ fi
78
+
79
+ printf '%s\n' "${BOOTSTRAP_TOOLS_DIR}"
80
+ }
81
+
82
+ usage() {
83
+ cat <<'EOF'
84
+ Usage:
85
+ agent-project-reconcile-pr-session --session <id> --repo-slug <owner/repo> --repo-root <path> --runs-root <path> --history-root <path> [--hook-file <path>]
86
+
87
+ Reconcile a completed PR worker run using shared lifecycle control flow while
88
+ allowing project adapters to inject policy hooks.
89
+ EOF
90
+ }
91
+
92
+ shared_tools_dir="$(resolve_reconcile_tools_dir)"
93
+ resolve_reconcile_helper_path() {
94
+ local helper_name="${1:?helper name required}"
95
+ local candidate=""
96
+
97
+ for candidate in \
98
+ "${SCRIPT_DIR}/${helper_name}" \
99
+ "${BOOTSTRAP_TOOLS_DIR}/${helper_name}" \
100
+ "${shared_tools_dir}/${helper_name}"; do
101
+ if [[ -n "${candidate}" && -f "${candidate}" ]]; then
102
+ printf '%s\n' "${candidate}"
103
+ return 0
104
+ fi
105
+ done
106
+
107
+ echo "unable to locate ${helper_name} for reconcile bootstrap" >&2
108
+ return 1
109
+ }
110
+
111
+ FLOW_CONFIG_LIB_PATH="$(resolve_reconcile_helper_path "flow-config-lib.sh")"
112
+ # shellcheck source=/dev/null
113
+ source "${FLOW_CONFIG_LIB_PATH}"
114
+ verification_guard_script="${shared_tools_dir}/branch-verification-guard.sh"
115
+ session=""
116
+ repo_slug=""
117
+ repo_root=""
118
+ runs_root=""
119
+ history_root=""
120
+ hook_file=""
121
+
122
+ while [[ $# -gt 0 ]]; do
123
+ case "$1" in
124
+ --session) session="${2:-}"; shift 2 ;;
125
+ --repo-slug) repo_slug="${2:-}"; shift 2 ;;
126
+ --repo-root) repo_root="${2:-}"; shift 2 ;;
127
+ --runs-root) runs_root="${2:-}"; shift 2 ;;
128
+ --history-root) history_root="${2:-}"; shift 2 ;;
129
+ --hook-file) hook_file="${2:-}"; shift 2 ;;
130
+ --help|-h) usage; exit 0 ;;
131
+ *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
132
+ esac
133
+ done
134
+
135
+ if [[ -z "$session" || -z "$repo_slug" || -z "$repo_root" || -z "$runs_root" || -z "$history_root" ]]; then
136
+ usage >&2
137
+ exit 1
138
+ fi
139
+
140
+ status_out="$(
141
+ "${shared_tools_dir}/agent-project-worker-status" \
142
+ --runs-root "$runs_root" \
143
+ --session "$session"
144
+ )"
145
+ status="$(awk -F= '/^STATUS=/{print $2}' <<<"$status_out")"
146
+ failure_reason="$(awk -F= '/^FAILURE_REASON=/{print $2}' <<<"$status_out" | tail -n 1)"
147
+
148
+ meta_file="$(awk -F= '/^META_FILE=/{print $2}' <<<"$status_out")"
149
+ if [[ -z "$meta_file" || ! -f "$meta_file" ]]; then
150
+ echo "missing metadata for session $session" >&2
151
+ exit 1
152
+ fi
153
+
154
+ run_dir="$(dirname "$meta_file")"
155
+
156
+ set -a
157
+ # shellcheck source=/dev/null
158
+ source "$meta_file"
159
+ set +a
160
+ pr_worktree="${WORKTREE:-}"
161
+
162
+ pr_number="${PR_NUMBER:-}"
163
+ if [[ -z "$pr_number" ]]; then
164
+ echo "session $session is missing PR_NUMBER" >&2
165
+ exit 1
166
+ fi
167
+
168
+ result_outcome=""
169
+ result_action=""
170
+ result_issue_id="${ISSUE_ID:-}"
171
+ host_blocker_file="${run_dir}/host-blocker.md"
172
+ prompt_file="${run_dir}/prompt.md"
173
+ pr_comment_file="${run_dir}/pr-comment.md"
174
+ session_log_file="${run_dir}/${session}.log"
175
+ record_verification_script="${FLOW_TOOLS_DIR:-${shared_tools_dir}}/record-verification.sh"
176
+ result_file_candidate="${run_dir}/result.env"
177
+ if [[ ! -f "$result_file_candidate" && -n "${RESULT_FILE:-}" && -f "${RESULT_FILE}" ]]; then
178
+ result_file_candidate="${RESULT_FILE}"
179
+ fi
180
+ if [[ -f "$result_file_candidate" ]]; then
181
+ set -a
182
+ # shellcheck source=/dev/null
183
+ source "$result_file_candidate"
184
+ set +a
185
+ result_outcome="${OUTCOME:-}"
186
+ result_action="${ACTION:-}"
187
+ result_issue_id="${ISSUE_ID:-${result_issue_id}}"
188
+ fi
189
+
190
+ pr_schedule_retry() { :; }
191
+ pr_clear_retry() { :; }
192
+ pr_cleanup_linked_issue_session() { :; }
193
+ pr_cleanup_merged_residue() { :; }
194
+ pr_linked_issue_should_close() { printf 'yes\n'; }
195
+ pr_after_merged() { :; }
196
+ pr_after_closed() { :; }
197
+ pr_automerge_allowed() { printf 'no\n'; }
198
+ pr_review_pass_action() { printf 'merge\n'; }
199
+ pr_after_double_check_advanced() { :; }
200
+ pr_after_updated_branch() { :; }
201
+ pr_after_blocked() { :; }
202
+ pr_after_succeeded() { :; }
203
+ pr_after_failed() { :; }
204
+ pr_after_reconciled() { :; }
205
+ pr_result_contract_note=""
206
+
207
+ if [[ -n "$hook_file" && -f "$hook_file" ]]; then
208
+ # shellcheck source=/dev/null
209
+ source "$hook_file"
210
+ fi
211
+
212
+ provider_cooldown_script="${shared_tools_dir}/provider-cooldown-state.sh"
213
+
214
+ schedule_provider_quota_cooldown() {
215
+ local reason="${1:-provider-quota-limit}"
216
+ [[ "${failure_reason:-}" == "provider-quota-limit" ]] || return 0
217
+ [[ -x "${provider_cooldown_script}" ]] || return 0
218
+
219
+ "${provider_cooldown_script}" schedule "${reason}" >/dev/null || true
220
+ }
221
+
222
+ clear_provider_quota_cooldown() {
223
+ [[ -x "${provider_cooldown_script}" ]] || return 0
224
+
225
+ "${provider_cooldown_script}" clear >/dev/null || true
226
+ }
227
+
228
+ owner="${repo_slug%%/*}"
229
+ repo="${repo_slug#*/}"
230
+ pr_view_json="$(flow_github_pr_view_json "$repo_slug" "$pr_number")"
231
+ pr_state="$(jq -r '.state' <<<"$pr_view_json")"
232
+ pr_base_ref="$(jq -r '.baseRefName // "main"' <<<"$pr_view_json")"
233
+
234
+ if [[ "$status" == "RUNNING" && "$pr_state" != "MERGED" && "$pr_state" != "CLOSED" ]]; then
235
+ printf 'STATUS=%s\n' "$status"
236
+ exit 0
237
+ fi
238
+
239
+ review_pass_action_from_result_action() {
240
+ case "${1:-}" in
241
+ host-advance-double-check-2)
242
+ printf 'advance-double-check-2\n'
243
+ ;;
244
+ host-await-human-review)
245
+ printf 'wait-human\n'
246
+ ;;
247
+ host-approve-and-merge|"")
248
+ printf 'merge\n'
249
+ ;;
250
+ *)
251
+ return 1
252
+ ;;
253
+ esac
254
+ }
255
+
256
+ normalize_pr_result_contract() {
257
+ [[ "$status" == "SUCCEEDED" ]] || return 0
258
+ [[ "$pr_state" == "MERGED" ]] && return 0
259
+
260
+ case "${result_outcome:-}" in
261
+ approved-local-review-passed)
262
+ local review_pass_action expected_action legacy_action=""
263
+ if [[ -n "${result_action:-}" ]]; then
264
+ if review_pass_action="$(review_pass_action_from_result_action "${result_action}" 2>/dev/null)"; then
265
+ :
266
+ elif [[ "${result_action}" == "host-approve-and-merge" ]]; then
267
+ review_pass_action="merge"
268
+ pr_result_contract_note="normalized-legacy-review-action"
269
+ else
270
+ echo "invalid PR review result contract for session ${session}: ACTION='${result_action}'" >&2
271
+ return 1
272
+ fi
273
+ else
274
+ review_pass_action="$(pr_review_pass_action "$pr_number")"
275
+ fi
276
+ case "$review_pass_action" in
277
+ advance-double-check-2)
278
+ expected_action="host-advance-double-check-2"
279
+ legacy_action="host-approve-and-merge"
280
+ ;;
281
+ wait-human)
282
+ expected_action="host-await-human-review"
283
+ ;;
284
+ merge|"")
285
+ expected_action="host-approve-and-merge"
286
+ ;;
287
+ *)
288
+ echo "unsupported review pass action for PR ${pr_number}: ${review_pass_action}" >&2
289
+ return 1
290
+ ;;
291
+ esac
292
+ if [[ -z "${result_action:-}" ]]; then
293
+ result_action="$expected_action"
294
+ pr_result_contract_note="normalized-missing-review-action"
295
+ return 0
296
+ fi
297
+ if [[ "$result_action" == "$expected_action" ]]; then
298
+ return 0
299
+ fi
300
+ if [[ -n "$legacy_action" && "$result_action" == "$legacy_action" ]]; then
301
+ result_action="$expected_action"
302
+ pr_result_contract_note="normalized-legacy-review-action"
303
+ return 0
304
+ fi
305
+ echo "invalid PR review result contract for session ${session}: OUTCOME='${result_outcome}' ACTION='${result_action}' EXPECTED='${expected_action}'" >&2
306
+ return 1
307
+ ;;
308
+ updated-branch)
309
+ if [[ -z "${result_action:-}" ]]; then
310
+ result_action="host-push-pr-branch"
311
+ pr_result_contract_note="normalized-missing-updated-branch-action"
312
+ return 0
313
+ fi
314
+ if [[ "$result_action" == "host-push-pr-branch" ]]; then
315
+ return 0
316
+ fi
317
+ echo "invalid PR updated-branch result contract for session ${session}: ACTION='${result_action}'" >&2
318
+ return 1
319
+ ;;
320
+ no-change-needed)
321
+ if [[ -z "${result_action:-}" ]]; then
322
+ result_action="host-refresh-pr-state"
323
+ pr_result_contract_note="normalized-missing-no-change-needed-action"
324
+ return 0
325
+ fi
326
+ if [[ "$result_action" == "host-refresh-pr-state" ]]; then
327
+ return 0
328
+ fi
329
+ echo "invalid PR no-change-needed result contract for session ${session}: ACTION='${result_action}'" >&2
330
+ return 1
331
+ ;;
332
+ blocked)
333
+ if [[ -z "${result_action:-}" ]]; then
334
+ result_action="host-comment-pr-blocker"
335
+ pr_result_contract_note="normalized-missing-blocked-action"
336
+ return 0
337
+ fi
338
+ case "$result_action" in
339
+ host-comment-pr-blocker)
340
+ return 0
341
+ ;;
342
+ requested-changes-or-blocked)
343
+ result_action="host-comment-pr-blocker"
344
+ pr_result_contract_note="normalized-legacy-blocked-action"
345
+ return 0
346
+ ;;
347
+ *)
348
+ echo "invalid PR blocked result contract for session ${session}: ACTION='${result_action}'" >&2
349
+ return 1
350
+ ;;
351
+ esac
352
+ ;;
353
+ *)
354
+ echo "invalid PR worker result contract for session ${session}: OUTCOME='${result_outcome:-}' ACTION='${result_action:-}'" >&2
355
+ return 1
356
+ ;;
357
+ esac
358
+ }
359
+
360
+ mark_reconciled() {
361
+ if [[ -d "$run_dir" ]]; then
362
+ touch "${run_dir}/reconciled.ok"
363
+ fi
364
+ }
365
+
366
+ post_pr_comment_if_present() {
367
+ local comment_file="${run_dir}/pr-comment.md"
368
+ [[ -s "$comment_file" ]] || return 0
369
+ if pr_comment_already_posted; then
370
+ return 0
371
+ fi
372
+ flow_github_api_repo "${repo_slug}" "issues/${pr_number}/comments" --method POST -f body="$(cat "$comment_file")" >/dev/null
373
+ }
374
+
375
+ pr_comment_already_posted() {
376
+ local comment_file="${run_dir}/pr-comment.md"
377
+ [[ -s "$comment_file" ]] || return 1
378
+ local comment_body comments_json
379
+ comment_body="$(cat "$comment_file")"
380
+ comments_json="$(flow_github_pr_view_json "$repo_slug" "$pr_number" 2>/dev/null || true)"
381
+ [[ -n "$comments_json" ]] || return 1
382
+ jq -e --arg body "$comment_body" 'any(.comments[]?; .body == $body)' >/dev/null <<<"$comments_json"
383
+ }
384
+
385
+ blocked_result_indicates_local_bind_failure() {
386
+ local candidate_file
387
+ for candidate_file in "$pr_comment_file" "$session_log_file"; do
388
+ [[ -f "$candidate_file" ]] || continue
389
+ if grep -Eiq 'listen EPERM|failed to bind to local ports|Process from config\.webServer exited early' "$candidate_file"; then
390
+ return 0
391
+ fi
392
+ done
393
+ return 1
394
+ }
395
+
396
+ extract_preapproved_host_recovery_commands() {
397
+ [[ -f "$prompt_file" ]] || return 0
398
+ sed -n 's/^.*loopback retry command: `\(.*\)`$/\1/p' "$prompt_file"
399
+ }
400
+
401
+ record_host_verification_result() {
402
+ local verification_status="${1:?verification status required}"
403
+ local command_text="${2:?command text required}"
404
+ local note_text="${3:-}"
405
+
406
+ if [[ ! -x "$record_verification_script" ]]; then
407
+ return 0
408
+ fi
409
+
410
+ if [[ -n "$note_text" ]]; then
411
+ bash "$record_verification_script" --run-dir "$run_dir" --status "$verification_status" --command "$command_text" --note "$note_text" >/dev/null
412
+ else
413
+ bash "$record_verification_script" --run-dir "$run_dir" --status "$verification_status" --command "$command_text" >/dev/null
414
+ fi
415
+ }
416
+
417
+ run_host_verification_command() {
418
+ local command_text="${1:?command text required}"
419
+
420
+ {
421
+ printf '\n[host-recovery] command=%s\n' "$command_text"
422
+ } >>"$session_log_file"
423
+
424
+ if WORKTREE_DIR="$pr_worktree" HOST_COMMAND="$command_text" bash -lc 'set -euo pipefail; cd "$WORKTREE_DIR"; eval "$HOST_COMMAND"' >>"$session_log_file" 2>&1; then
425
+ record_host_verification_result "pass" "$command_text" "host-recovery-after-sandbox-bind-failure"
426
+ return 0
427
+ fi
428
+
429
+ record_host_verification_result "fail" "$command_text" "host-recovery-after-sandbox-bind-failure"
430
+ printf '[host-recovery] command failed=%s\n' "$command_text" >>"$session_log_file"
431
+ return 1
432
+ }
433
+
434
+ rewrite_pr_comment_for_host_recovery() {
435
+ local commands_text="${1:-}"
436
+ local tmp_comment_file="${pr_comment_file}.tmp.$$"
437
+ local command_text=""
438
+
439
+ if [[ -f "$pr_comment_file" ]]; then
440
+ awk '
441
+ /^\*\*Blocker\*\*$/ { exit }
442
+ { print }
443
+ ' "$pr_comment_file" >"$tmp_comment_file"
444
+ else
445
+ : >"$tmp_comment_file"
446
+ fi
447
+
448
+ {
449
+ if [[ -s "$tmp_comment_file" ]]; then
450
+ printf '\n\n'
451
+ fi
452
+ printf '**Host Recovery**\n'
453
+ printf -- '- Host reran the pre-approved verification outside the worker sandbox after sandbox-only local port bind failures.\n'
454
+ while IFS= read -r command_text; do
455
+ [[ -n "$command_text" ]] || continue
456
+ printf -- '- ✅ `%s`\n' "$command_text"
457
+ done <<<"$commands_text"
458
+ } >>"$tmp_comment_file"
459
+
460
+ mv "$tmp_comment_file" "$pr_comment_file"
461
+ }
462
+
463
+ persist_updated_branch_result_contract() {
464
+ cat >"${run_dir}/result.env" <<EOF
465
+ OUTCOME=updated-branch
466
+ ACTION=host-push-pr-branch
467
+ PR_NUMBER=${pr_number}
468
+ ISSUE_ID=${result_issue_id}
469
+ EOF
470
+ }
471
+
472
+ attempt_blocked_pr_host_verification_recovery() {
473
+ local recovery_commands=""
474
+ local command_text=""
475
+
476
+ [[ "${result_outcome:-}" == "blocked" ]] || return 1
477
+ [[ -n "$pr_worktree" && -d "$pr_worktree" ]] || return 1
478
+ blocked_result_indicates_local_bind_failure || return 1
479
+
480
+ recovery_commands="$(extract_preapproved_host_recovery_commands)"
481
+ [[ -n "$recovery_commands" ]] || return 1
482
+
483
+ while IFS= read -r command_text; do
484
+ [[ -n "$command_text" ]] || continue
485
+ if ! run_host_verification_command "$command_text"; then
486
+ return 1
487
+ fi
488
+ done <<<"$recovery_commands"
489
+
490
+ rewrite_pr_comment_for_host_recovery "$recovery_commands"
491
+ persist_updated_branch_result_contract
492
+ result_outcome="updated-branch"
493
+ result_action="host-push-pr-branch"
494
+ failure_reason=""
495
+ pr_result_contract_note="host-recovered-sandbox-bind-failure"
496
+ return 0
497
+ }
498
+
499
+ require_transition() {
500
+ local step="${1:?step required}"
501
+ shift
502
+ if ! "$@"; then
503
+ echo "reconcile transition failed: ${step}" >&2
504
+ exit 1
505
+ fi
506
+ }
507
+
508
+ close_linked_issue_if_open() {
509
+ local issue_id="${1:-}"
510
+ [[ -n "$issue_id" ]] || return 0
511
+
512
+ local issue_state
513
+ issue_state="$(flow_github_issue_view_json "$repo_slug" "$issue_id" 2>/dev/null | jq -r '.state // empty' || true)"
514
+ if [[ "$issue_state" == "OPEN" ]]; then
515
+ flow_github_issue_close "$repo_slug" "$issue_id" "Closed automatically after PR #${pr_number} merged." >/dev/null 2>&1 || true
516
+ fi
517
+ }
518
+
519
+ push_pr_branch() {
520
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
521
+ echo "missing PR worktree for session $session" >&2
522
+ return 1
523
+ fi
524
+ if [[ -z "${PR_HEAD_REF:-}" ]]; then
525
+ echo "session $session is missing PR_HEAD_REF" >&2
526
+ return 1
527
+ fi
528
+
529
+ git -C "$pr_worktree" push origin "HEAD:${PR_HEAD_REF}"
530
+ }
531
+
532
+ pr_branch_commits_ahead_remote_count() {
533
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
534
+ echo "missing PR worktree for session $session" >&2
535
+ return 1
536
+ fi
537
+ if [[ -z "${PR_HEAD_REF:-}" ]]; then
538
+ echo "session $session is missing PR_HEAD_REF" >&2
539
+ return 1
540
+ fi
541
+ if ! git -C "$pr_worktree" fetch origin "+refs/heads/${PR_HEAD_REF}:refs/remotes/origin/${PR_HEAD_REF}" --prune >/dev/null 2>&1; then
542
+ echo "unable to refresh remote tracking ref for PR head ${PR_HEAD_REF}" >&2
543
+ return 1
544
+ fi
545
+
546
+ git -C "$pr_worktree" rev-list --count "origin/${PR_HEAD_REF}..HEAD"
547
+ }
548
+
549
+ guard_pr_branch_verification() {
550
+ local guard_output=""
551
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
552
+ echo "missing PR worktree for session $session" >&2
553
+ return 1
554
+ fi
555
+
556
+ if guard_output="$(
557
+ "${verification_guard_script}" \
558
+ --worktree "$pr_worktree" \
559
+ --base-ref "origin/${pr_base_ref}" \
560
+ --run-dir "$run_dir" 2>&1
561
+ )"; then
562
+ rm -f "$host_blocker_file"
563
+ return 0
564
+ fi
565
+
566
+ printf '%s\n' "$guard_output" >"$host_blocker_file"
567
+ printf '%s\n' "$guard_output" >&2
568
+ return 1
569
+ }
570
+
571
+ collect_stageable_paths() {
572
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
573
+ return 0
574
+ fi
575
+
576
+ {
577
+ git -C "$pr_worktree" diff --name-only --relative
578
+ git -C "$pr_worktree" diff --cached --name-only --relative
579
+ git -C "$pr_worktree" ls-files --others --exclude-standard 2>/dev/null || true
580
+ } | awk '
581
+ NF == 0 { next }
582
+ $0 ~ /(^|\/)node_modules(\/|$)/ { next }
583
+ $0 ~ /^\.openclaw-artifacts(\/|$)/ { next }
584
+ !seen[$0]++ { print $0 }
585
+ ' | sort
586
+ }
587
+
588
+ host_commit_pr_worktree_changes() {
589
+ local stageable_paths
590
+ local commit_message_file="${run_dir}/commit-message.txt"
591
+ local commit_message=""
592
+
593
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
594
+ echo "missing PR worktree for session $session" >&2
595
+ return 1
596
+ fi
597
+
598
+ stageable_paths="$(collect_stageable_paths)"
599
+ if [[ -n "$stageable_paths" ]]; then
600
+ while IFS= read -r relative_path; do
601
+ [[ -n "$relative_path" ]] || continue
602
+ git -C "$pr_worktree" add -- "$relative_path"
603
+ done <<<"$stageable_paths"
604
+ fi
605
+
606
+ if git -C "$pr_worktree" diff --cached --quiet && git -C "$pr_worktree" diff --quiet; then
607
+ return 0
608
+ fi
609
+
610
+ if [[ -f "$commit_message_file" ]]; then
611
+ commit_message="$(tr -d '\r' <"$commit_message_file" | sed -n '1p')"
612
+ fi
613
+
614
+ if [[ -z "$commit_message" ]]; then
615
+ if git -C "$pr_worktree" rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
616
+ commit_message="fix(pr): resolve merge drift for PR #${pr_number}"
617
+ else
618
+ commit_message="fix(pr): apply repair for PR #${pr_number}"
619
+ fi
620
+ fi
621
+
622
+ git -C "$pr_worktree" commit -m "$commit_message" >/dev/null
623
+ }
624
+
625
+ remaining_merge_conflict_paths() {
626
+ local base_ref="${1:?base ref required}"
627
+
628
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
629
+ printf '%s\n' "__missing_worktree__"
630
+ return 0
631
+ fi
632
+
633
+ if git -C "$pr_worktree" rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
634
+ git -C "$pr_worktree" ls-files -u \
635
+ | awk '
636
+ NF >= 4 {
637
+ path=$4
638
+ if (!(path in seen)) {
639
+ seen[path]=1
640
+ print path
641
+ }
642
+ }
643
+ '
644
+ return 0
645
+ fi
646
+
647
+ if ! git -C "$repo_root" fetch origin "+refs/heads/${base_ref}:refs/remotes/origin/${base_ref}" --prune >/dev/null 2>&1; then
648
+ printf '%s\n' "__unable_to_fetch_base_ref__:${base_ref}"
649
+ return 0
650
+ fi
651
+
652
+ local base_sha
653
+ base_sha="$(git -C "$pr_worktree" merge-base HEAD "origin/${base_ref}" 2>/dev/null || true)"
654
+ if [[ -z "$base_sha" ]]; then
655
+ printf '%s\n' "__unable_to_compute_merge_base__:${base_ref}"
656
+ return 0
657
+ fi
658
+
659
+ git -C "$pr_worktree" merge-tree "$base_sha" HEAD "origin/${base_ref}" 2>/dev/null \
660
+ | awk '
661
+ /^changed in both$/ { capture=1; next }
662
+ capture && /^( base| our| their) / {
663
+ path=$NF
664
+ if (!(path in seen)) {
665
+ seen[path]=1
666
+ print path
667
+ }
668
+ }
669
+ capture && /^@@ / { capture=0 }
670
+ '
671
+ }
672
+
673
+ append_host_guard_comment() {
674
+ local base_ref="${1:?base ref required}"
675
+ local conflict_paths="${2:-}"
676
+ local comment_file="${run_dir}/pr-comment.md"
677
+ local host_blocker_body=""
678
+
679
+ if [[ -s "$comment_file" ]]; then
680
+ printf '\n\n' >>"$comment_file"
681
+ fi
682
+
683
+ host_blocker_body="$(cat <<EOF
684
+ ## PR repair host guard
685
+
686
+ Host rejected this repair and did not push it because local merge simulation against \`${base_ref}\` still reports unresolved conflict paths:
687
+
688
+ $(printf '%s\n' "$conflict_paths" | sed 's/^/- /')
689
+
690
+ No partial repair commit was pushed to the PR branch. The PR stays in fix lane and will retry after cooldown.
691
+ EOF
692
+ )"
693
+
694
+ printf '%s\n' "$host_blocker_body" >>"$comment_file"
695
+ printf '%s\n' "$host_blocker_body" >"$host_blocker_file"
696
+ }
697
+
698
+ merge_state_prepared() {
699
+ [[ -n "$pr_worktree" && -d "$pr_worktree" ]] || return 1
700
+ git -C "$pr_worktree" rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1
701
+ }
702
+
703
+ approve_and_merge() {
704
+ local approve_output
705
+ if ! approve_output="$(
706
+ flow_github_api_repo "${repo_slug}" "pulls/${pr_number}/reviews" \
707
+ --method POST \
708
+ -f event=APPROVE \
709
+ -f body="Automated final review passed. Safe low-risk scope, green checks, and host-side merge approved." \
710
+ 2>&1
711
+ )"; then
712
+ if ! grep -q "Can not approve your own pull request" <<<"$approve_output"; then
713
+ printf '%s\n' "$approve_output" >&2
714
+ return 1
715
+ fi
716
+ fi
717
+
718
+ flow_github_pr_merge "$repo_slug" "$pr_number" "squash" "yes"
719
+ }
720
+
721
+ cleanup_pr_session() {
722
+ "${shared_tools_dir}/agent-project-cleanup-session" \
723
+ --repo-root "$repo_root" \
724
+ --runs-root "$runs_root" \
725
+ --history-root "$history_root" \
726
+ --session "$session" \
727
+ --worktree "$pr_worktree" \
728
+ --mode pr >/dev/null || true
729
+ }
730
+
731
+ notify_pr_reconciled() {
732
+ pr_after_reconciled "$status" "$pr_state" "${result_outcome:-}" "${result_action:-}" "$pr_number" || true
733
+ }
734
+
735
+ handle_verification_guard_block() {
736
+ failure_reason="verification-guard-blocked"
737
+ require_transition "pr_schedule_retry" pr_schedule_retry "$failure_reason"
738
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
739
+ cleanup_pr_session
740
+ result_outcome="blocked"
741
+ result_action="host-verification-guard-blocked"
742
+ notify_pr_reconciled
743
+ }
744
+
745
+ handle_linked_issue_merge_cleanup() {
746
+ local issue_id="${1:-}"
747
+ [[ -n "$issue_id" ]] || return 0
748
+ pr_cleanup_linked_issue_session "$issue_id" || true
749
+ if [[ "$(pr_linked_issue_should_close "$issue_id" || printf 'yes\n')" == "yes" ]]; then
750
+ close_linked_issue_if_open "$issue_id"
751
+ fi
752
+ }
753
+
754
+ handle_updated_branch_result() {
755
+ if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
756
+ if pr_comment_already_posted; then
757
+ require_transition "pr_clear_retry" pr_clear_retry
758
+ require_transition "pr_after_updated_branch" pr_after_updated_branch "$pr_number"
759
+ cleanup_pr_session
760
+ result_action="${result_action:-pushed-branch-for-ci}"
761
+ notify_pr_reconciled
762
+ else
763
+ require_transition "pr_schedule_retry" pr_schedule_retry "missing-pr-worktree-before-publish"
764
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
765
+ cleanup_pr_session
766
+ result_outcome="blocked"
767
+ result_action="host-missing-pr-worktree"
768
+ notify_pr_reconciled
769
+ fi
770
+ return 0
771
+ fi
772
+
773
+ if ! guard_pr_branch_verification; then
774
+ handle_verification_guard_block
775
+ return 0
776
+ fi
777
+
778
+ host_commit_pr_worktree_changes
779
+ remaining_conflict_paths="$(remaining_merge_conflict_paths "$pr_base_ref" || true)"
780
+ if [[ -n "$remaining_conflict_paths" ]]; then
781
+ append_host_guard_comment "$pr_base_ref" "$remaining_conflict_paths"
782
+ # Keep host-guard notes local when nothing was pushed. Posting them to GitHub
783
+ # makes unchanged PR heads look like successful repairs and can re-queue review.
784
+ require_transition "pr_schedule_retry" pr_schedule_retry "remaining-merge-conflicts-after-updated-branch"
785
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
786
+ cleanup_pr_session
787
+ result_outcome="blocked"
788
+ result_action="host-rejected-partial-repair"
789
+ notify_pr_reconciled
790
+ return 0
791
+ fi
792
+
793
+ if ! ahead_count="$(pr_branch_commits_ahead_remote_count)"; then
794
+ failure_reason="updated-branch-remote-ref-unavailable"
795
+ require_transition "pr_schedule_retry" pr_schedule_retry "$failure_reason"
796
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
797
+ cleanup_pr_session
798
+ result_outcome="blocked"
799
+ result_action="host-branch-ahead-check-failed"
800
+ notify_pr_reconciled
801
+ return 0
802
+ fi
803
+
804
+ if [[ "${ahead_count}" == "0" ]]; then
805
+ failure_reason="updated-branch-no-commits-ahead"
806
+ require_transition "pr_schedule_retry" pr_schedule_retry "$failure_reason"
807
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
808
+ cleanup_pr_session
809
+ result_outcome="blocked"
810
+ result_action="host-noop-updated-branch"
811
+ notify_pr_reconciled
812
+ return 0
813
+ fi
814
+
815
+ push_pr_branch
816
+ post_pr_comment_if_present
817
+ require_transition "pr_clear_retry" pr_clear_retry
818
+ require_transition "pr_after_updated_branch" pr_after_updated_branch "$pr_number"
819
+ cleanup_pr_session
820
+ result_action="${result_action:-pushed-branch-for-ci}"
821
+ notify_pr_reconciled
822
+ }
823
+
824
+ if [[ "$status" == "SUCCEEDED" ]] && ! normalize_pr_result_contract; then
825
+ status="FAILED"
826
+ failure_reason="invalid-result-contract"
827
+ pr_result_contract_note="invalid-result-contract"
828
+ require_transition "pr_schedule_retry" pr_schedule_retry "$failure_reason"
829
+ require_transition "pr_after_failed" pr_after_failed "$pr_number"
830
+ cleanup_pr_session
831
+ result_outcome="invalid-contract"
832
+ result_action="queued-pr-retry"
833
+ notify_pr_reconciled
834
+ elif [[ "$pr_state" == "MERGED" ]]; then
835
+ status="SUCCEEDED"
836
+ failure_reason=""
837
+ clear_provider_quota_cooldown
838
+ require_transition "pr_clear_retry" pr_clear_retry
839
+ require_transition "handle_linked_issue_merge_cleanup" handle_linked_issue_merge_cleanup "$result_issue_id"
840
+ require_transition "pr_after_merged" pr_after_merged "$pr_number"
841
+ require_transition "pr_cleanup_merged_residue" pr_cleanup_merged_residue "$pr_number"
842
+ cleanup_pr_session
843
+ result_outcome="${result_outcome:-merged}"
844
+ result_action="${result_action:-approved-and-merged}"
845
+ notify_pr_reconciled
846
+ elif [[ "$pr_state" == "CLOSED" ]]; then
847
+ status="SUCCEEDED"
848
+ failure_reason=""
849
+ clear_provider_quota_cooldown
850
+ require_transition "pr_clear_retry" pr_clear_retry
851
+ require_transition "pr_after_closed" pr_after_closed "$pr_number"
852
+ cleanup_pr_session
853
+ result_outcome="${result_outcome:-closed}"
854
+ result_action="${result_action:-cleaned-closed-pr}"
855
+ notify_pr_reconciled
856
+ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "approved-local-review-passed" ]]; then
857
+ if ! review_pass_action="$(review_pass_action_from_result_action "${result_action:-}" 2>/dev/null)"; then
858
+ review_pass_action="$(pr_review_pass_action "$pr_number")"
859
+ fi
860
+ case "$review_pass_action" in
861
+ advance-double-check-2)
862
+ require_transition "pr_clear_retry" pr_clear_retry
863
+ require_transition "pr_after_double_check_advanced" pr_after_double_check_advanced "$pr_number" "2"
864
+ cleanup_pr_session
865
+ result_outcome="double-check-1-approved"
866
+ result_action="queued-double-check-2"
867
+ notify_pr_reconciled
868
+ ;;
869
+ wait-human)
870
+ require_transition "pr_clear_retry" pr_clear_retry
871
+ require_transition "pr_after_succeeded" pr_after_succeeded "$pr_number"
872
+ cleanup_pr_session
873
+ result_outcome="waiting-human-review"
874
+ result_action="queued-human-review"
875
+ notify_pr_reconciled
876
+ ;;
877
+ merge|"")
878
+ if [[ "$(pr_automerge_allowed "$pr_number")" != "yes" ]]; then
879
+ echo "PR ${pr_number} is no longer eligible for auto-merge" >&2
880
+ exit 1
881
+ fi
882
+
883
+ require_transition "pr_clear_retry" pr_clear_retry
884
+ approve_and_merge
885
+ pr_state="$(flow_github_pr_view_json "$repo_slug" "$pr_number" | jq -r '.state')"
886
+ if [[ "$pr_state" != "MERGED" ]]; then
887
+ echo "PR ${pr_number} did not merge successfully" >&2
888
+ exit 1
889
+ fi
890
+
891
+ require_transition "handle_linked_issue_merge_cleanup" handle_linked_issue_merge_cleanup "$result_issue_id"
892
+ require_transition "pr_after_merged" pr_after_merged "$pr_number"
893
+ require_transition "pr_cleanup_merged_residue" pr_cleanup_merged_residue "$pr_number"
894
+ cleanup_pr_session
895
+ result_outcome="merged"
896
+ result_action="approved-and-merged"
897
+ notify_pr_reconciled
898
+ ;;
899
+ *)
900
+ echo "unsupported review pass action for PR ${pr_number}: ${review_pass_action}" >&2
901
+ exit 1
902
+ ;;
903
+ esac
904
+ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "updated-branch" ]]; then
905
+ handle_updated_branch_result
906
+ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "no-change-needed" ]]; then
907
+ if merge_state_prepared; then
908
+ remaining_conflict_paths="$(remaining_merge_conflict_paths "$pr_base_ref" || true)"
909
+ if [[ -n "$remaining_conflict_paths" ]]; then
910
+ append_host_guard_comment "$pr_base_ref" "$remaining_conflict_paths"
911
+ post_pr_comment_if_present
912
+ require_transition "pr_schedule_retry" pr_schedule_retry "remaining-merge-conflicts-after-no-change-needed"
913
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
914
+ cleanup_pr_session
915
+ result_outcome="blocked"
916
+ result_action="host-rejected-no-change-needed"
917
+ notify_pr_reconciled
918
+ else
919
+ if ! guard_pr_branch_verification; then
920
+ handle_verification_guard_block
921
+ else
922
+ host_commit_pr_worktree_changes
923
+ if ! ahead_count="$(pr_branch_commits_ahead_remote_count)"; then
924
+ failure_reason="no-change-needed-remote-ref-unavailable"
925
+ require_transition "pr_schedule_retry" pr_schedule_retry "$failure_reason"
926
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
927
+ cleanup_pr_session
928
+ result_outcome="blocked"
929
+ result_action="host-branch-ahead-check-failed"
930
+ elif [[ "${ahead_count}" == "0" ]]; then
931
+ failure_reason="no-change-needed-promoted-without-branch-advance"
932
+ require_transition "pr_schedule_retry" pr_schedule_retry "$failure_reason"
933
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
934
+ cleanup_pr_session
935
+ result_outcome="blocked"
936
+ result_action="host-rejected-noop-promotion"
937
+ else
938
+ push_pr_branch
939
+ post_pr_comment_if_present
940
+ require_transition "pr_clear_retry" pr_clear_retry
941
+ require_transition "pr_after_updated_branch" pr_after_updated_branch "$pr_number"
942
+ cleanup_pr_session
943
+ result_outcome="updated-branch"
944
+ result_action="host-promoted-no-change-needed-to-updated-branch"
945
+ fi
946
+ notify_pr_reconciled
947
+ fi
948
+ fi
949
+ else
950
+ remaining_conflict_paths="$(remaining_merge_conflict_paths "$pr_base_ref" || true)"
951
+ if [[ -n "$remaining_conflict_paths" ]]; then
952
+ append_host_guard_comment "$pr_base_ref" "$remaining_conflict_paths"
953
+ # Keep host-guard notes local when nothing was pushed. Posting them to GitHub
954
+ # makes unchanged PR heads look like successful repairs and can re-queue review.
955
+ require_transition "pr_schedule_retry" pr_schedule_retry "remaining-merge-conflicts-after-no-change-needed"
956
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
957
+ cleanup_pr_session
958
+ result_outcome="blocked"
959
+ result_action="host-rejected-no-change-needed"
960
+ notify_pr_reconciled
961
+ else
962
+ post_pr_comment_if_present
963
+ require_transition "pr_clear_retry" pr_clear_retry
964
+ require_transition "pr_after_succeeded" pr_after_succeeded "$pr_number"
965
+ cleanup_pr_session
966
+ result_action="${result_action:-refreshed-pr-state}"
967
+ notify_pr_reconciled
968
+ fi
969
+ fi
970
+ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "blocked" ]]; then
971
+ if attempt_blocked_pr_host_verification_recovery; then
972
+ handle_updated_branch_result
973
+ else
974
+ post_pr_comment_if_present
975
+ require_transition "pr_clear_retry" pr_clear_retry
976
+ require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
977
+ cleanup_pr_session
978
+ result_action="${result_action:-queued-pr-fix}"
979
+ notify_pr_reconciled
980
+ fi
981
+ elif [[ "$status" == "SUCCEEDED" ]]; then
982
+ clear_provider_quota_cooldown
983
+ require_transition "pr_clear_retry" pr_clear_retry
984
+ require_transition "pr_after_succeeded" pr_after_succeeded "$pr_number"
985
+ cleanup_pr_session
986
+ notify_pr_reconciled
987
+ elif [[ "$status" == "FAILED" ]]; then
988
+ schedule_provider_quota_cooldown "${failure_reason:-worker-exit-failed}"
989
+ require_transition "pr_schedule_retry" pr_schedule_retry "${failure_reason:-worker-exit-failed}"
990
+ require_transition "pr_after_failed" pr_after_failed "$pr_number"
991
+ notify_pr_reconciled
992
+ fi
993
+
994
+ mark_reconciled
995
+ printf 'STATUS=%s\n' "$status"
996
+ printf 'PR_NUMBER=%s\n' "$pr_number"
997
+ printf 'PR_STATE=%s\n' "$pr_state"
998
+ printf 'OUTCOME=%s\n' "${result_outcome:-unknown}"
999
+ printf 'ACTION=%s\n' "${result_action:-unknown}"
1000
+ if [[ -n "$failure_reason" ]]; then
1001
+ printf 'FAILURE_REASON=%s\n' "$failure_reason"
1002
+ fi
1003
+ if [[ -n "$pr_result_contract_note" ]]; then
1004
+ printf 'RESULT_CONTRACT_NOTE=%s\n' "$pr_result_contract_note"
1005
+ fi