@yemi33/minions 0.1.2448 → 0.1.2449

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 (315) hide show
  1. package/bin/cli-api-client.js +1 -1
  2. package/bin/install-internal-minions.js +1382 -44
  3. package/bin/install-layout.js +150 -0
  4. package/bin/minions.js +460 -167
  5. package/dashboard/docs/typography.md +65 -12
  6. package/dashboard/js/command-center.js +58 -4
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +179 -20
  10. package/dashboard/js/refresh.js +148 -12
  11. package/dashboard/js/render-dispatch.js +3 -4
  12. package/dashboard/js/render-inbox.js +2 -2
  13. package/dashboard/js/render-other.js +3 -3
  14. package/dashboard/js/render-pipelines.js +14 -0
  15. package/dashboard/js/render-plans.js +57 -9
  16. package/dashboard/js/render-prd.js +132 -23
  17. package/dashboard/js/render-prs.js +195 -166
  18. package/dashboard/js/render-schedules.js +63 -3
  19. package/dashboard/js/render-utils.js +3 -3
  20. package/dashboard/js/render-watches.js +19 -3
  21. package/dashboard/js/render-work-items.js +238 -30
  22. package/dashboard/js/settings.js +205 -54
  23. package/dashboard/js/utils.js +51 -1
  24. package/dashboard/pages/home.html +1 -1
  25. package/dashboard/pages/qa.html +1 -16
  26. package/dashboard/pages/work.html +40 -0
  27. package/dashboard/shared/cc-limits.js +79 -0
  28. package/dashboard/shared/pr-filters.js +21 -38
  29. package/dashboard/shared/project-git-summary.js +1 -1
  30. package/dashboard/shared/record-filters.js +169 -0
  31. package/dashboard/shared/watches-source.js +1 -1
  32. package/dashboard/shared/welcome-popup.js +1 -1
  33. package/dashboard/shared/wi-filters.js +302 -0
  34. package/dashboard/slim/body.html +1 -0
  35. package/dashboard/slim/js/command-send.js +26 -0
  36. package/dashboard/slim/js/modals-tiles.js +380 -39
  37. package/dashboard/slim/js/status.js +13 -21
  38. package/dashboard/slim/layout.html +1 -0
  39. package/dashboard/slim/panel-bootstrap.js +6 -2
  40. package/dashboard/slim/styles.css +38 -0
  41. package/dashboard/styles.css +159 -55
  42. package/dashboard-build.js +13 -2
  43. package/dashboard.js +956 -423
  44. package/docs/README.md +11 -6
  45. package/docs/api-errors.md +2 -2
  46. package/docs/architecture-review-2026-07-09.md +1 -1
  47. package/docs/architecture.excalidraw +2 -2
  48. package/docs/auto-discovery.md +18 -9
  49. package/docs/branch-derivation.md +4 -4
  50. package/docs/capture-demos.js +39 -2
  51. package/docs/ci-runner-canary.md +123 -0
  52. package/docs/claude-md-propagation.md +2 -2
  53. package/docs/cloud-agent-dispatch.md +204 -0
  54. package/docs/command-center.md +7 -7
  55. package/docs/completion-reports.md +43 -20
  56. package/docs/constants.md +10 -3
  57. package/docs/constellation-bridge.md +134 -6
  58. package/docs/constellation-style-telemetry.md +4 -4
  59. package/docs/contracts/capability-protocol.v1.json +165 -0
  60. package/docs/cooldown-merge-semantics.md +12 -12
  61. package/docs/copilot-cli-schema.md +7 -7
  62. package/docs/cross-repo-plans.md +10 -10
  63. package/docs/dead-code-audit-retractions.md +5 -5
  64. package/docs/default-branch-ci.md +173 -0
  65. package/docs/deprecated.json +31 -31
  66. package/docs/design-inbox-entries-schema.md +3 -3
  67. package/docs/design-language.md +1051 -0
  68. package/docs/design-state-storage.md +11 -11
  69. package/docs/diagnostics-crash-reports.md +9 -9
  70. package/docs/diagnostics-memory.md +5 -5
  71. package/docs/documentation-audit-2026-07-09.md +7 -7
  72. package/docs/engine-restart.md +90 -5
  73. package/docs/harness-mode.md +1 -1
  74. package/docs/internal-install.md +338 -39
  75. package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
  76. package/docs/kb-pr3223-cascade-archiving.md +1 -1
  77. package/docs/kb-pr696-merge-conflict-docs.md +6 -6
  78. package/docs/kb-sweep.md +35 -35
  79. package/docs/keep-processes.md +1 -1
  80. package/docs/live-checkout-mode.md +30 -30
  81. package/docs/managed-spawn.md +18 -14
  82. package/docs/named-agents.md +7 -7
  83. package/docs/plan-lifecycle.md +69 -2
  84. package/docs/pr-author-identity.md +114 -0
  85. package/docs/pr-auto-fix-dispatch.md +19 -4
  86. package/docs/pr-comment-followup.md +6 -6
  87. package/docs/pr-review-fix-loop.md +59 -10
  88. package/docs/process-termination.md +40 -0
  89. package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
  90. package/docs/qa-runbook-lifecycle.md +367 -17
  91. package/docs/qa-runbooks.md +3 -3
  92. package/docs/rfc-completion-json.md +18 -18
  93. package/docs/runtime-adapters.md +26 -21
  94. package/docs/security.md +6 -6
  95. package/docs/self-improvement.md +4 -4
  96. package/docs/shared-lifecycle-module-map.md +473 -472
  97. package/docs/skills.md +52 -3
  98. package/docs/slim-ux/concepts.md +121 -116
  99. package/docs/specs/agent-configurability.md +18 -18
  100. package/docs/specs/agent-rename.md +18 -18
  101. package/docs/team-memory.md +38 -21
  102. package/docs/timeouts-and-liveness.md +118 -10
  103. package/docs/tutorials/01-install-and-connect.md +1 -1
  104. package/docs/watches.md +40 -39
  105. package/docs/workspace-manifests.md +4 -4
  106. package/docs/worktree-lifecycle.md +293 -14
  107. package/engine/README.md +46 -0
  108. package/engine/{ado-comment.js → ado/comment.js} +8 -8
  109. package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
  110. package/engine/{ado.js → ado/index.js} +417 -63
  111. package/engine/{ado-status.js → ado/status.js} +6 -8
  112. package/engine/{ado-token.js → ado/token.js} +1 -1
  113. package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
  114. package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
  115. package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
  116. package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
  117. package/engine/{harness-context.js → agents/harness-context.js} +5 -5
  118. package/engine/{harness.js → agents/harness.js} +3 -3
  119. package/engine/{llm.js → agents/llm.js} +18 -14
  120. package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
  121. package/engine/{playbook.js → agents/playbook.js} +155 -22
  122. package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
  123. package/engine/{preflight.js → agents/preflight.js} +29 -10
  124. package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
  125. package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
  126. package/engine/{steering.js → agents/steering.js} +5 -5
  127. package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
  128. package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
  129. package/engine/{api-validation.js → api/api-validation.js} +1 -1
  130. package/engine/api/bridge.js +787 -0
  131. package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
  132. package/engine/api/companion.js +560 -0
  133. package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
  134. package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
  135. package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
  136. package/engine/api-contracts/agent-content.js +4 -4
  137. package/engine/api-contracts/capability-manifest.js +236 -0
  138. package/engine/api-contracts/capability-protocol.js +333 -0
  139. package/engine/api-contracts/cc-ops.js +1 -1
  140. package/engine/api-contracts/config-runtime.js +5 -0
  141. package/engine/api-contracts/core.js +28 -1
  142. package/engine/api-contracts/index.js +100 -0
  143. package/engine/api-contracts/orchestration.js +18 -5
  144. package/engine/api-contracts/pull-requests.js +37 -6
  145. package/engine/api-contracts/qa-process.js +29 -6
  146. package/engine/api-contracts/work-plan-prd.js +21 -1
  147. package/engine/cloud/contract.js +212 -0
  148. package/engine/cloud/index.js +159 -0
  149. package/engine/{execution-model.js → core/execution-model.js} +1 -1
  150. package/engine/{features.js → core/features.js} +4 -4
  151. package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
  152. package/engine/{queries.js → core/queries.js} +201 -36
  153. package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
  154. package/engine/{shared.js → core/shared.js} +1637 -175
  155. package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
  156. package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
  157. package/engine/db/index.js +11 -2
  158. package/engine/db/migrations/002-dispatches.js +3 -3
  159. package/engine/db/migrations/003-work-items.js +1 -1
  160. package/engine/db/migrations/004-pull-requests.js +1 -1
  161. package/engine/db/migrations/006-metrics.js +1 -1
  162. package/engine/db/migrations/007-watches.js +2 -2
  163. package/engine/db/migrations/008-small-state.js +1 -1
  164. package/engine/db/migrations/009-qa.js +1 -1
  165. package/engine/db/migrations/010-pr-links.js +1 -1
  166. package/engine/db/migrations/011-remaining-state.js +1 -1
  167. package/engine/db/migrations/012-steering-deliveries.js +2 -2
  168. package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
  169. package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
  170. package/engine/db/migrations/015-plans-prds.js +0 -0
  171. package/engine/db/migrations/018-sql-only-cutover.js +2 -2
  172. package/engine/db/migrations/021-archived-work-items.js +1 -1
  173. package/engine/db/migrations/022-global-cc-session.js +1 -1
  174. package/engine/db/migrations/023-engine-state.js +1 -1
  175. package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
  176. package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
  177. package/engine/db/migrations/029-repair-reused-versions.js +20 -0
  178. package/engine/db/migrations/031-pr-author-identity.js +137 -0
  179. package/engine/{consolidation.js → memory/consolidation.js} +6 -6
  180. package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
  181. package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
  182. package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
  183. package/engine/{memory-store.js → memory/memory-store.js} +3 -3
  184. package/engine/{promotion.js → memory/promotion.js} +3 -3
  185. package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
  186. package/engine/{review-learning.js → memory/review-learning.js} +10 -5
  187. package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
  188. package/engine/{logs-store.js → observability/logs-store.js} +5 -5
  189. package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
  190. package/engine/{check-status.js → operations/check-status.js} +3 -3
  191. package/engine/{cli.js → operations/cli.js} +271 -113
  192. package/engine/{distribution.js → operations/distribution.js} +5 -6
  193. package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
  194. package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
  195. package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
  196. package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
  197. package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
  198. package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
  199. package/engine/{meeting.js → orchestration/meeting.js} +6 -16
  200. package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
  201. package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
  202. package/engine/{routing.js → orchestration/routing.js} +3 -3
  203. package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
  204. package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
  205. package/engine/{timeout.js → orchestration/timeout.js} +158 -109
  206. package/engine/{db-events.js → persistence/db-events.js} +2 -2
  207. package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
  208. package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
  209. package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
  210. package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
  211. package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
  212. package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
  213. package/engine/persistence/state-operations.js +350 -0
  214. package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
  215. package/engine/{issues.js → planning/issues.js} +2 -2
  216. package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
  217. package/engine/planning/prd-result-sidecar.js +190 -0
  218. package/engine/{prd-store.js → planning/prd-store.js} +17 -17
  219. package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
  220. package/engine/{projects.js → planning/projects.js} +10 -10
  221. package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
  222. package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
  223. package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
  224. package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
  225. package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
  226. package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
  227. package/engine/{process-utils.js → processes/process-utils.js} +599 -55
  228. package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
  229. package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
  230. package/engine/{comment-format.js → providers/comment-format.js} +5 -5
  231. package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
  232. package/engine/{gh-token.js → providers/gh-token.js} +4 -4
  233. package/engine/{github.js → providers/github.js} +131 -54
  234. package/engine/{pr-action.js → providers/pr-action.js} +13 -12
  235. package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
  236. package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
  237. package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
  238. package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
  239. package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
  240. package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
  241. package/engine/{pr-track.js → providers/pr-track.js} +11 -13
  242. package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
  243. package/engine/qa/auto-prd-qa.js +313 -0
  244. package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
  245. package/engine/qa/prd-session.js +240 -0
  246. package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
  247. package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
  248. package/engine/{qa-runs.js → qa/runs.js} +286 -15
  249. package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
  250. package/engine/qa/visual-journey.js +654 -0
  251. package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
  252. package/engine/qa-runners/maestro.js +3 -3
  253. package/engine/qa-runners/playwright.js +2 -2
  254. package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
  255. package/engine/recovery/stop-stack.js +607 -0
  256. package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
  257. package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
  258. package/engine/runtimes/claude.js +14 -12
  259. package/engine/runtimes/codex.js +8 -6
  260. package/engine/runtimes/copilot.js +17 -16
  261. package/engine/{watch-actions.js → watches/actions.js} +13 -13
  262. package/engine/{watches.js → watches/index.js} +43 -32
  263. package/engine/{watches-store.js → watches/store.js} +4 -4
  264. package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
  265. package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
  266. package/engine/worktrees/inventory.js +671 -0
  267. package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
  268. package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
  269. package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
  270. package/engine/worktrees/quarantine-refs.js +173 -0
  271. package/engine.js +1137 -208
  272. package/minions.js +147 -77
  273. package/package.json +10 -6
  274. package/playbooks/_pr-description-audit.md +110 -78
  275. package/playbooks/build-fix-complex.md +2 -0
  276. package/playbooks/fix.md +16 -12
  277. package/playbooks/implement-shared.md +2 -0
  278. package/playbooks/implement.md +19 -20
  279. package/playbooks/plan-to-prd.md +18 -3
  280. package/playbooks/qa-session-draft.md +136 -1
  281. package/playbooks/qa-session-execute.md +80 -2
  282. package/playbooks/qa-session-setup.md +17 -1
  283. package/playbooks/qa-validate.md +1 -1
  284. package/playbooks/setup.md +2 -0
  285. package/playbooks/shared-rules.md +25 -32
  286. package/playbooks/templates/followup-dispatch.md +4 -3
  287. package/playbooks/verify.md +1 -1
  288. package/prompts/cc-system.md +19 -27
  289. package/watch-plugins/README.md +92 -0
  290. package/watch-plugins/ado-author-prs.js +336 -0
  291. package/watch-plugins/gh-author-prs.js +375 -0
  292. package/watch-plugins/http.js +474 -0
  293. package/watch-plugins/teams-channel.js +869 -0
  294. package/docs/dev-composite-workflow.md +0 -101
  295. package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
  296. package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
  297. package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
  298. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  299. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  300. package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
  301. package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
  302. package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
  303. package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
  304. package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
  305. package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
  306. package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
  307. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  308. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  309. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  310. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  311. package/docs/visual-evidence-ci.md +0 -103
  312. package/engine/bridge.js +0 -379
  313. package/engine/quarantine-refs.js +0 -103
  314. package/engine/state-operations.js +0 -178
  315. /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
@@ -3,7 +3,6 @@
3
3
  let allPrs = [];
4
4
  let filteredPrs = [];
5
5
  let prPage = 0;
6
- let prFilterOptionsSignature = '';
7
6
  const PR_PER_PAGE = 25;
8
7
  const PR_FILTER_CONTROL_IDS = {
9
8
  author: 'pr-filter-author',
@@ -12,13 +11,32 @@ const PR_FILTER_CONTROL_IDS = {
12
11
  review: 'pr-filter-review',
13
12
  build: 'pr-filter-build',
14
13
  };
15
- const PR_FILTER_ALL_LABELS = {
16
- author: 'All authors',
17
- lifecycle: 'All lifecycle statuses',
18
- project: 'All projects',
19
- review: 'All review statuses',
20
- build: 'All build statuses',
21
- };
14
+
15
+ // DOM half of the filter bar — the generic controller from
16
+ // shared/record-filters.js, shared with the classic Work Items bar. It owns the
17
+ // option-signature gate that keeps the 4 s poll from rebuilding <option> nodes
18
+ // under an open dropdown.
19
+ //
20
+ // Built lazily rather than at module scope: this file is also require()d
21
+ // directly under Node by tests that exercise its pure helpers, where `window`
22
+ // does not exist.
23
+ let _prFilterBarInstance = null;
24
+ function _prFilterBar() {
25
+ if (!_prFilterBarInstance) {
26
+ _prFilterBarInstance = window.MinionsRecordFilters.createBar({
27
+ controlIds: PR_FILTER_CONTROL_IDS,
28
+ allLabels: {
29
+ author: 'All authors',
30
+ lifecycle: 'All lifecycle statuses',
31
+ project: 'All projects',
32
+ review: 'All review statuses',
33
+ build: 'All build statuses',
34
+ },
35
+ getOptions: getPrFilterOptions,
36
+ });
37
+ }
38
+ return _prFilterBarInstance;
39
+ }
22
40
 
23
41
  // Pull requests are served fresh by GET /api/pull-requests (see refresh.js),
24
42
  // which runs queries.getPullRequests() server-side. The per-project
@@ -103,6 +121,60 @@ function _renderPrMergeConflictBadge(pr) {
103
121
  '" aria-label="' + escapeHtml(PR_MERGE_CONFLICT_LABEL) + '">conflict</span>';
104
122
  }
105
123
 
124
+ function prAutomationToggle(idMatch, automation, label, enabled) {
125
+ if (!idMatch) return '';
126
+ var stateClass = enabled ? 'pr-automation-on' : 'pr-automation-off';
127
+ var stateLabel = enabled ? 'enabled' : 'disabled';
128
+ var title = label + ' ' + stateLabel + ' for this PR. Click to ' + (enabled ? 'disable.' : 'enable.');
129
+ return '<label class="pr-automation-switch ' + stateClass + '" '
130
+ + 'title="' + escapeHtml(title) + '" '
131
+ + 'onclick="event.stopPropagation()">'
132
+ + '<input type="checkbox" class="pr-automation-switch__input"'
133
+ + (enabled ? ' checked' : '') + ' '
134
+ + 'aria-label="' + escapeHtml(label + ': ' + stateLabel) + '" '
135
+ + 'data-pr-host="' + escapeHtml(idMatch[1]) + '" '
136
+ + 'data-pr-slug="' + escapeHtml(idMatch[2]) + '" '
137
+ + 'data-pr-number="' + escapeHtml(idMatch[3]) + '" '
138
+ + 'data-pr-automation="' + escapeHtml(automation) + '" '
139
+ + 'data-pr-enabled="' + (enabled ? '1' : '0') + '" '
140
+ + 'onchange="togglePrAutomation(this)">'
141
+ + '<span class="pr-automation-switch__track" aria-hidden="true">'
142
+ + '<span class="pr-automation-switch__knob"></span>'
143
+ + '</span>'
144
+ + '</label>';
145
+ }
146
+
147
+ // PR author cell (W-mscibegy005e8b74). Renders the real individual the PR is
148
+ // for — display name plus @login when both are known — separate from the
149
+ // Minions agent (still shown in the PR detail panel). Missing/legacy author
150
+ // renders "Unknown" rather than borrowing the agent name. Links to the provider
151
+ // profile only when a validated http(s) URL is present.
152
+ function _prAuthorCellHtml(pr) {
153
+ var a = pr ? pr.author : null;
154
+ var login = '';
155
+ var displayName = '';
156
+ var url = '';
157
+ if (a && typeof a === 'object') {
158
+ login = a.login || '';
159
+ displayName = a.displayName || '';
160
+ url = a.url || '';
161
+ } else if (typeof a === 'string') {
162
+ login = a;
163
+ }
164
+ var primary = displayName || login;
165
+ if (!primary) {
166
+ return '<span style="color:var(--muted);font-size:var(--text-base)" title="Author unavailable">Unknown</span>';
167
+ }
168
+ var secondary = (displayName && login && login !== displayName) ? login : '';
169
+ var inner = escapeHtml(primary)
170
+ + (secondary ? ' <span style="color:var(--muted);font-size:var(--text-base)">@' + escapeHtml(secondary) + '</span>' : '');
171
+ var titleText = secondary ? primary + ' (' + secondary + ')' : primary;
172
+ if (url && /^https?:\/\//i.test(url)) {
173
+ return '<a class="pr-agent" title="' + escapeHtml(titleText) + '" href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + inner + '</a>';
174
+ }
175
+ return '<span class="pr-agent" title="' + escapeHtml(titleText) + '">' + inner + '</span>';
176
+ }
177
+
106
178
  function prRow(pr) {
107
179
  // Minions review (agent) state — separate from ADO human review
108
180
  const sq = pr.minionsReview || {};
@@ -118,10 +190,24 @@ function prRow(pr) {
118
190
  ? 'ADO has builds on this PR\'s merge ref, but none target the current merge commit (typically after a rebase or target-branch advance). Engine classified from the pre-rebase build set and queued a fresh build (rate limited 30m). See Issue #2747.'
119
191
  : (pr._buildStatusDetail || '');
120
192
  const mergeConflictBadge = _renderPrMergeConflictBadge(pr);
121
- const mergeStateHtml = mergeConflictBadge ||
122
- '<span style="color:var(--muted);font-size:var(--text-base)" aria-label="No merge conflict reported">—</span>';
123
- const statusClass = pr.status === 'merged' ? 'merged' : pr.status === 'abandoned' ? 'rejected' : pr.status === 'active' ? 'active' : 'draft';
124
- const statusLabel = pr.status || 'active';
193
+ // W-ms8z6l8z the lifecycle badge is a single, mutually-exclusive state:
194
+ // an open draft PR reads "draft"; an open non-draft PR reads "active";
195
+ // terminal PRs read merged/abandoned. The canonical draft signal is
196
+ // pr.isDraft (plumbed by engine/providers/github.js + engine/ado/index.js,
197
+ // exposed via engine/api/bridge.js), never title text. There is no separate
198
+ // "ready" pill — "active" already communicates open + non-draft, so
199
+ // rendering both "active" and "ready" together was redundant. The
200
+ // merge-conflict badge (formerly its own never-populated "Merge" column) is
201
+ // folded in beside the lifecycle badge because it is a genuinely distinct
202
+ // state that "active"/"draft" does not convey.
203
+ const isDraftPr = pr.isDraft === true;
204
+ const isOpenPr = !pr.status || pr.status === 'active';
205
+ const statusClass = pr.status === 'merged' ? 'merged'
206
+ : pr.status === 'abandoned' ? 'rejected'
207
+ : (isDraftPr && isOpenPr) ? 'draft'
208
+ : pr.status === 'active' ? 'active'
209
+ : 'draft';
210
+ const statusLabel = (isDraftPr && isOpenPr) ? 'draft' : (pr.status || 'active');
125
211
  const branchError = pr._branchResolutionError?.reason || '';
126
212
  const branchLabel = pr.branch || (branchError ? 'missing branch' : '—');
127
213
  const branchClass = 'pr-branch' + (branchError ? ' branch-missing' : '');
@@ -166,7 +252,7 @@ function prRow(pr) {
166
252
  // no-op pausedCauses above: pr._buildFixIneffective is set when a
167
253
  // BUILD_FAILURE fix reported "no branch change" AND a live CI re-poll
168
254
  // confirmed the build is STILL failing on that same head (see
169
- // engine/lifecycle.js recordBuildFixIneffective). This is a separate,
255
+ // engine/orchestration/lifecycle.js recordBuildFixIneffective). This is a separate,
170
256
  // headSha-keyed pause distinct from _noOpFixes, so it needs its own,
171
257
  // clearly-labeled indicator — a still-red build after an unchanged fix
172
258
  // attempt usually means the failure isn't fixable by another agent pass
@@ -197,7 +283,7 @@ function prRow(pr) {
197
283
  // Issue #764 — worktree-held-externally chip. pr._worktreeHeldPaused is set
198
284
  // when a PR-driven review/fix dispatch hit WORKTREE_PREFLIGHT because this
199
285
  // PR's branch is checked out at the project root or in an external
200
- // worktree the engine can't reach (see engine/lifecycle.js
286
+ // worktree the engine can't reach (see engine/orchestration/lifecycle.js
201
287
  // recordWorktreeHeldPause). engine.js auto-clears this pause once it
202
288
  // detects the holder no longer holds the branch; this chip lets an
203
289
  // operator force-clear it immediately via
@@ -218,7 +304,7 @@ function prRow(pr) {
218
304
  // Polish (W-mqv5ccn7): flatten Markdown in the body so the preview shows
219
305
  // plain text instead of raw `##` / `**…**` / backtick source.
220
306
  const descPreview = stripMarkdown(pr.description || '');
221
- const agentText = pr.agent || '—';
307
+ const authorCellHtml = _prAuthorCellHtml(pr);
222
308
  const reviewerCell = sq.reviewer && sq.status !== 'waiting'
223
309
  ? '<span class="pr-agent" title="' + escapeHtml(sq.note || sq.reviewer) + '">' + escapeHtml(sq.reviewer) + '</span>'
224
310
  : sq.reviewer && sq.status === 'waiting'
@@ -227,96 +313,72 @@ function prRow(pr) {
227
313
  ? '<span class="pr-agent" title="' + escapeHtml(pr.reviewedBy.join(', ')) + '">' + escapeHtml(pr.reviewedBy.join(', ')) + '</span>'
228
314
  : '<span style="color:var(--muted);font-size:var(--text-base)">—</span>';
229
315
  const createdLabel = (pr.created || '—').slice(0, 16).replace('T', ' ');
230
- // Per-row auto-observe toggle (W-mpmwxkzm0009ba0b; canonicalized in
231
- // W-mq5s5ttx000j7ab8-c). pr.contextOnly === true means the engine polls
232
- // status/comments but does NOT dispatch review/fix agents against this
233
- // PR. Default (undefined / false) = observed.
234
- var observe = pr.contextOnly !== true;
235
- var observeClass = observe ? 'pr-observe-on' : 'pr-observe-off';
236
- var observeLabel = observe ? 'observed' : 'context';
237
- var observeTitle = observe
238
- ? 'Auto-observe ON — engine reviews this PR and dispatches fix agents on review/build feedback. Click to switch to context-only.'
239
- : 'Context only — engine polls status and comments but will not dispatch review/fix agents. Click to enable auto-observe.';
240
- // pr.id is canonical: "<host>:<slug>#<number>" (engine/shared.js getCanonicalPrId).
241
- // Parse here so the toggle handler can post {host, slug, number} without
242
- // shipping additional fields per row.
243
- var observeBtn = '';
316
+ // pr.id is canonical: "<host>:<slug>#<number>" (engine/core/shared.js getCanonicalPrId).
317
+ // Existing records inherit both controls from contextOnly until an operator
318
+ // changes either switch, at which point the server persists both explicitly.
244
319
  var idMatch = typeof pr.id === 'string' ? pr.id.match(/^([^:]+):(.+)#(\d+)$/) : null;
245
- if (idMatch) {
246
- // Real toggle-switch (W-mpof8qam000h62fb). Semantic <input type=checkbox>
247
- // inside a <label> gives free keyboard + screen-reader support; clicks on
248
- // the track or knob flip the state via the wrapping label. Data-attrs stay
249
- // on the input so togglePrObserve(this) keeps working unchanged.
250
- observeBtn = '<label class="pr-observe-switch ' + observeClass + '" '
251
- + 'title="' + escapeHtml(observeTitle) + '" '
252
- + 'onclick="event.stopPropagation()">'
253
- + '<input type="checkbox" class="pr-observe-switch__input"'
254
- + (observe ? ' checked' : '') + ' '
255
- + 'aria-label="' + escapeHtml('Auto-observe: ' + observeLabel) + '" '
256
- + 'data-pr-host="' + escapeHtml(idMatch[1]) + '" '
257
- + 'data-pr-slug="' + escapeHtml(idMatch[2]) + '" '
258
- + 'data-pr-number="' + escapeHtml(idMatch[3]) + '" '
259
- + 'data-pr-observe="' + (observe ? '1' : '0') + '" '
260
- + 'onchange="togglePrObserve(this)">'
261
- + '<span class="pr-observe-switch__track" aria-hidden="true">'
262
- + '<span class="pr-observe-switch__knob"></span>'
263
- + '</span>'
264
- + '</label> ';
265
- }
320
+ var autoFixEnabled = typeof pr.autoFixEnabled === 'boolean'
321
+ ? pr.autoFixEnabled
322
+ : pr.contextOnly !== true;
323
+ var autoReviewEnabled = typeof pr.autoReviewEnabled === 'boolean'
324
+ ? pr.autoReviewEnabled
325
+ : pr.contextOnly !== true;
326
+ var autoFixBtn = prAutomationToggle(idMatch, 'fix', 'Auto Fix', autoFixEnabled);
327
+ var autoReviewBtn = prAutomationToggle(idMatch, 'review', 'Auto Review', autoReviewEnabled);
266
328
  // Title attrs live on the inner element (link/span/badge) so hovering the
267
329
  // ellipsis-truncated content reveals the full text. Cell tags stay bare so
268
330
  // the header-to-cell count assertion in test/unit.test.js continues to
269
331
  // balance.
270
- // First-column secondary project label (W-mr3mowg9) mirrors the pr-desc
271
- // secondary-text pattern used in the Title cell so reviewers can see at a
272
- // glance which project a PR belongs to. `_project` is stamped by the engine
273
- // enrichment / getAllPrs stamping (see top-of-file note + line ~33).
332
+ // Dedicated Project column (W-ms90po5j; was a secondary label folded into
333
+ // the PR-id cell under W-mr3mowg9). `_project` is stamped by the engine
334
+ // enrichment / getAllPrs stamping (see top-of-file note + line ~33). Empty or
335
+ // unknown project values render the shared muted em-dash placeholder so the
336
+ // cell matches the table's other empty-value conventions (e.g. Observe).
274
337
  const projectName = pr._project ? String(pr._project) : '';
275
- const projectLabelHtml = projectName
276
- ? '<div class="pr-desc pr-id-project" title="' + escapeHtml(projectName) + '">' + escapeHtml(projectName) + '</div>'
277
- : '';
338
+ const projectCellHtml = projectName
339
+ ? '<span class="pr-project" title="' + escapeHtml(projectName) + '">' + escapeHtml(projectName) + '</span>'
340
+ : '<span style="color:var(--muted);font-size:var(--text-base)" aria-label="No project">—</span>';
278
341
  return '<tr>' +
279
- '<td><span class="pr-id" title="' + escapeHtml(String(prId)) + '">' + escapeHtml(prShortId(String(prId))) + '</span>' + projectLabelHtml + '</td>' +
342
+ '<td><span class="pr-id" title="' + escapeHtml(String(prId)) + '">' + escapeHtml(prShortId(String(prId))) + '</span></td>' +
343
+ '<td>' + projectCellHtml + '</td>' +
280
344
  '<td><a class="pr-title" title="' + escapeHtml(titleText) + '" href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escapeHtml(titleText) + '</a>' + followupChip + pausedChips + (descPreview ? '<div class="pr-desc" title="' + escapeHtml(descPreview) + '">' + escapeHtml(descPreview.length > 120 ? descPreview.slice(0, 120) + '...' : descPreview) + '</div>' : '') + '</td>' +
281
- '<td><span class="pr-badge ' + statusClass + '" title="' + escapeHtml(statusLabel) + '">' + escapeHtml(statusLabel) + '</span></td>' +
345
+ '<td><span class="pr-status-badges"><span class="pr-badge ' + statusClass + '" title="' + escapeHtml(statusLabel) + '">' + escapeHtml(statusLabel) + '</span>' + mergeConflictBadge + '</span></td>' +
282
346
  '<td><span class="pr-badge ' + reviewClass + '" title="' + escapeHtml(reviewTitle || reviewLabel) + '">' + escapeHtml(reviewLabel) + '</span></td>' +
283
347
  '<td><span class="pr-badge ' + buildClass + '" title="' + escapeHtml(buildTitle || buildLabel) + '">' + escapeHtml(buildLabel) + '</span></td>' +
284
- '<td>' + mergeStateHtml + '</td>' +
285
348
  '<td><span class="' + branchClass + '" title="' + escapeHtml(branchError || branchLabel) + '">' + escapeHtml(branchLabel) + '</span>' + pendingReasonHtml + '</td>' +
286
- '<td><span class="pr-agent" title="' + escapeHtml(agentText) + '">' + escapeHtml(agentText) + '</span></td>' +
349
+ '<td>' + authorCellHtml + '</td>' +
287
350
  '<td>' + reviewerCell + '</td>' +
288
- '<td>' + (observeBtn || '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
351
+ '<td>' + (autoFixBtn || '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
352
+ '<td>' + (autoReviewBtn || '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
289
353
  '<td><span class="pr-date" title="' + escapeHtml(createdLabel) + '">' + escapeHtml(createdLabel) + '</span></td>' +
290
- '<td><button class="pr-info-pill" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();openPrDetail(this.dataset.prId)" title="Open PR detail (in-stack modal)">&#x2197;</button>' +
291
- '<button class="btn-destructive" style="padding:1px 5px;margin-left:4px" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();unlinkPr(this.dataset.prId)" title="Remove from tracking">x</button></td>' +
354
+ '<td><button class="btn-destructive" style="padding:1px 5px" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();unlinkPr(this.dataset.prId)" title="Remove from tracking">x</button></td>' +
292
355
  '</tr>';
293
356
  }
294
357
 
295
- // Explicit per-column widths keep the PR table from ballooning when titles or
296
- // branches are long. Total 1600px -> table grows past viewport on narrow
297
- // windows and the .pr-table-wrap--prs container scrolls horizontally inside
298
- // the viewport (sticky scrollbar — see styles.css).
358
+ // Explicit widths keep long titles/branches bounded; narrow viewports scroll
359
+ // inside .pr-table-wrap--prs (sticky scrollbar see styles.css).
299
360
  const PRS_COLGROUP =
300
361
  '<colgroup>' +
301
- '<col style="width:130px">' + // PR id (short label: `gh #481` / `ado #5383607`) + project label
362
+ '<col style="width:130px">' + // PR id (short label: `gh #481` / `ado #5383607`)
363
+ '<col style="width:130px">' + // Project
302
364
  '<col style="width:320px">' + // Title
303
- '<col style="width:110px">' + // Status
365
+ '<col style="width:150px">' + // Status (lifecycle badge [draft/active/merged/abandoned] + merge-conflict badge)
304
366
  '<col style="width:130px">' + // Review
305
367
  '<col style="width:130px">' + // Build
306
- '<col style="width:120px">' + // Merge conflict
307
368
  '<col style="width:200px">' + // Branch
308
- '<col style="width:140px">' + // Agent
369
+ '<col style="width:160px">' + // Author
309
370
  '<col style="width:140px">' + // Signed Off By
310
- '<col style="width:100px">' + // Observe
371
+ '<col style="width:100px">' + // Auto Fix
372
+ '<col style="width:100px">' + // Auto Review
311
373
  '<col style="width:130px">' + // Created
312
- '<col style="width:80px">' + // Actions (info-pill + remove)
374
+ '<col style="width:80px">' + // Actions (remove)
313
375
  '</colgroup>';
314
376
 
315
377
  function prTableHtml(rows) {
316
378
  return '<div class="pr-table-wrap pr-table-wrap--prs"><table class="pr-table pr-table--prs">' +
317
379
  PRS_COLGROUP +
318
380
  '<thead><tr>' +
319
- '<th>PR</th><th>Title</th><th>Status</th><th>Review</th><th>Build</th><th>Merge</th><th>Branch</th><th>Agent</th><th>Signed Off By</th><th>Observe</th><th>Created</th><th></th>' +
381
+ '<th>PR</th><th>Project</th><th>Title</th><th>Status</th><th>Review</th><th>Build</th><th>Branch</th><th>Author</th><th>Signed Off By</th><th>Auto Fix</th><th>Auto Review</th><th>Created</th><th></th>' +
320
382
  '</tr></thead><tbody>' + rows + '</tbody></table></div>';
321
383
  }
322
384
 
@@ -333,61 +395,13 @@ function _restorePrScrollState(el, state) {
333
395
  tableWrap.scrollTop = state.top || 0;
334
396
  }
335
397
 
336
- function _syncPrFilterOptions(prs) {
337
- const options = getPrFilterOptions(prs);
338
- const controls = Object.keys(PR_FILTER_CONTROL_IDS).map(function(dimension) {
339
- return {
340
- dimension,
341
- select: document.getElementById(PR_FILTER_CONTROL_IDS[dimension]),
342
- };
343
- }).filter(function(entry) { return entry.select; });
344
- if (!controls.length) return;
345
- const signature = JSON.stringify(options);
346
- if (signature === prFilterOptionsSignature) return;
347
- prFilterOptionsSignature = signature;
348
- controls.forEach(function(entry) {
349
- const dimension = entry.dimension;
350
- const select = entry.select;
351
- const selected = select.value;
352
- const all = document.createElement('option');
353
- all.value = '';
354
- all.textContent = PR_FILTER_ALL_LABELS[dimension];
355
- const nodes = [all].concat((options[dimension] || []).map(function(option) {
356
- const node = document.createElement('option');
357
- node.value = option.value;
358
- node.textContent = option.label;
359
- return node;
360
- }));
361
- select.replaceChildren(...nodes);
362
- if (nodes.some(function(node) { return node.value === selected; })) select.value = selected;
363
- });
364
- }
365
-
366
- function _readPrFilters() {
367
- const filters = {};
368
- Object.keys(PR_FILTER_CONTROL_IDS).forEach(function(dimension) {
369
- const select = document.getElementById(PR_FILTER_CONTROL_IDS[dimension]);
370
- filters[dimension] = select ? select.value : '';
371
- });
372
- return filters;
373
- }
374
-
375
- function _hasActivePrFilters(filters) {
376
- return Object.keys(PR_FILTER_CONTROL_IDS).some(function(dimension) {
377
- return Boolean(filters[dimension]);
378
- });
379
- }
380
-
381
398
  function applyPrFilters() {
382
399
  prPage = 0;
383
400
  renderPrs(allPrs, { preserveScroll: false });
384
401
  }
385
402
 
386
403
  function resetPrFilters() {
387
- Object.keys(PR_FILTER_CONTROL_IDS).forEach(function(dimension) {
388
- const select = document.getElementById(PR_FILTER_CONTROL_IDS[dimension]);
389
- if (select) select.value = '';
390
- });
404
+ _prFilterBar().clear();
391
405
  applyPrFilters();
392
406
  }
393
407
 
@@ -395,21 +409,22 @@ function renderPrs(prs, opts) {
395
409
  opts = opts || {};
396
410
  prs = Array.isArray(prs) ? prs.filter(p => p && !isDeleted('pr:' + p.id)) : [];
397
411
  allPrs = prs;
398
- _syncPrFilterOptions(allPrs);
399
- const filters = _readPrFilters();
412
+ const _bar = _prFilterBar();
413
+ _bar.syncOptions(allPrs);
414
+ const filters = _bar.read();
400
415
  filteredPrs = filterPullRequests(allPrs, filters);
401
416
  const el = document.getElementById('pr-content');
402
417
  const count = document.getElementById('pr-count');
403
418
  const summary = document.getElementById('pr-filter-summary');
404
419
  count.textContent = filteredPrs.length;
405
420
  if (summary) {
406
- summary.textContent = _hasActivePrFilters(filters)
421
+ summary.textContent = _bar.hasActive(filters)
407
422
  ? 'Showing ' + filteredPrs.length + ' of ' + allPrs.length
408
423
  : '';
409
424
  }
410
425
  if (!allPrs.length) {
411
426
  prPage = 0;
412
- el.innerHTML = '<p class="pr-empty">No pull requests yet. PRs created by agents will appear here with review, build, and merge status.</p>';
427
+ el.innerHTML = '<p class="pr-empty">No pull requests yet. PRs created by agents will appear here with review and build status.</p>';
413
428
  return;
414
429
  }
415
430
  if (!filteredPrs.length) {
@@ -521,12 +536,14 @@ function _renderPrDetail(pr) {
521
536
  const mergeConflictBadge = _renderPrMergeConflictBadge(pr);
522
537
 
523
538
  const badges =
524
- '<span class="pr-badge ' + statusClass + '" title="status">' + escapeHtml(String(statusLabel)) + '</span> ' +
525
- '<span class="pr-badge ' + reviewClass + '" title="review">' + escapeHtml(String(reviewLabel)) + '</span> ' +
539
+ '<span class="pr-status-badges">' +
540
+ '<span class="pr-badge ' + statusClass + '" title="status">' + escapeHtml(String(statusLabel)) + '</span>' +
541
+ '<span class="pr-badge ' + reviewClass + '" title="review">' + escapeHtml(String(reviewLabel)) + '</span>' +
526
542
  '<span class="pr-badge ' + buildClass + '" title="build">' + escapeHtml(String(buildLabel)) + '</span>' +
527
- (mergeConflictBadge ? ' ' + mergeConflictBadge : '');
543
+ mergeConflictBadge +
544
+ '</span>';
528
545
 
529
- // Linked WIs — join via item._pr === pr.id (stamped by engine/queries.js).
546
+ // Linked WIs — join via item._pr === pr.id (stamped by engine/core/queries.js).
530
547
  const wis = (window._lastWorkItems) || [];
531
548
  const linkedWis = wis.filter(w => w && w._pr === pr.id);
532
549
  let linkedChips = '';
@@ -601,7 +618,26 @@ function _renderPrDetail(pr) {
601
618
  '</div>';
602
619
  }
603
620
 
604
- const agent = pr.agent ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Agent:</strong> ' + escapeHtml(String(pr.agent)) + '</div>' : '';
621
+ // Author (real individual the PR is for) shown alongside the Minions agent
622
+ // so operators can answer both "who is this for?" and "which agent handled it?".
623
+ let author = '';
624
+ {
625
+ const a = pr.author;
626
+ let login = '', displayName = '', url = '';
627
+ if (a && typeof a === 'object') { login = a.login || ''; displayName = a.displayName || ''; url = a.url || ''; }
628
+ else if (typeof a === 'string') { login = a; }
629
+ const primary = displayName || login;
630
+ if (primary) {
631
+ const secondary = (displayName && login && login !== displayName) ? ' <span style="color:var(--muted)">@' + escapeHtml(login) + '</span>' : '';
632
+ const valHtml = (url && /^https?:\/\//i.test(url))
633
+ ? '<a href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escapeHtml(primary) + '</a>' + secondary
634
+ : escapeHtml(primary) + secondary;
635
+ author = '<div><strong style="color:var(--muted);font-size:var(--text-base)">Author:</strong> ' + valHtml + '</div>';
636
+ } else {
637
+ author = '<div><strong style="color:var(--muted);font-size:var(--text-base)">Author:</strong> <span style="color:var(--muted)">Unknown</span></div>';
638
+ }
639
+ }
640
+ const agent = pr.agent ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Minions agent:</strong> ' + escapeHtml(String(pr.agent)) + '</div>' : '';
605
641
  const branch = pr.branch ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Branch:</strong> <code>' + escapeHtml(String(pr.branch)) + '</code></div>' : '';
606
642
  const created = pr.created ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Created:</strong> ' + escapeHtml(String(pr.created)) + '</div>' : '';
607
643
  const project = pr._project ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Project:</strong> ' + escapeHtml(String(pr._project)) + '</div>' : '';
@@ -620,7 +656,7 @@ function _renderPrDetail(pr) {
620
656
  '<code style="color:var(--muted);font-size:var(--text-base)">' + escapeHtml(String(pr.id || '')) + '</code><br>' +
621
657
  badges +
622
658
  '</div>' +
623
- agent + branch + project + created +
659
+ author + agent + branch + project + created +
624
660
  reviewers +
625
661
  linkedChips +
626
662
  parentChip +
@@ -733,57 +769,50 @@ async function unlinkPr(id) {
733
769
  // consumes canonical `contextOnly` to gate review/fix dispatch (engine/
734
770
  // shared.js isAutoManagedPrRecord + discoverFromPrs).
735
771
  //
736
- // `btn` is the <input type=checkbox> inside .pr-observe-switch. Data-attrs
737
- // live on the input so this handler keeps reading them directly; visual
738
- // state is mirrored onto the wrapping <label> via the pr-observe-on/off
739
- // class so CSS can paint the track and slide the knob.
740
- async function togglePrObserve(btn) {
772
+ async function togglePrAutomation(btn) {
741
773
  if (!btn || btn.disabled) return;
742
774
  const host = btn.dataset.prHost;
743
775
  const slug = btn.dataset.prSlug;
744
776
  const number = parseInt(btn.dataset.prNumber, 10);
745
- if (!host || !slug || !Number.isFinite(number)) return;
746
- const wasObserve = btn.dataset.prObserve === '1';
747
- const next = !wasObserve;
748
- const wrapper = btn.closest('.pr-observe-switch');
749
- // Optimistic flip
750
- btn.dataset.prObserve = next ? '1' : '0';
777
+ const automation = btn.dataset.prAutomation;
778
+ if (!host || !slug || !Number.isFinite(number) || !['fix', 'review'].includes(automation)) return;
779
+ const wasEnabled = btn.dataset.prEnabled === '1';
780
+ const next = !wasEnabled;
781
+ const label = automation === 'fix' ? 'Auto Fix' : 'Auto Review';
782
+ const wrapper = btn.closest('.pr-automation-switch');
783
+ btn.dataset.prEnabled = next ? '1' : '0';
751
784
  btn.checked = next;
752
- btn.setAttribute('aria-label', 'Auto-observe: ' + (next ? 'observed' : 'context'));
785
+ btn.setAttribute('aria-label', label + ': ' + (next ? 'enabled' : 'disabled'));
753
786
  if (wrapper) {
754
- wrapper.classList.toggle('pr-observe-on', next);
755
- wrapper.classList.toggle('pr-observe-off', !next);
787
+ wrapper.classList.toggle('pr-automation-on', next);
788
+ wrapper.classList.toggle('pr-automation-off', !next);
756
789
  }
757
790
  btn.disabled = true;
758
791
  try {
759
- const res = await fetch('/api/pull-requests/observe', {
792
+ const res = await fetch('/api/pull-requests/automation', {
760
793
  method: 'POST', headers: { 'Content-Type': 'application/json' },
761
- // Canonical contextOnly is the primary payload (P-2b6e4d81); the
762
- // legacy `observe` body param remains a server-side back-compat
763
- // fallback for any other caller, so the client no longer sends it.
764
- body: JSON.stringify({ host, slug, number, contextOnly: !next })
794
+ body: JSON.stringify({ host, slug, number, automation, enabled: next })
765
795
  });
766
796
  if (!res.ok) {
767
797
  const d = await res.json().catch(() => ({}));
768
- // Revert
769
- btn.dataset.prObserve = wasObserve ? '1' : '0';
770
- btn.checked = wasObserve;
771
- btn.setAttribute('aria-label', 'Auto-observe: ' + (wasObserve ? 'observed' : 'context'));
798
+ btn.dataset.prEnabled = wasEnabled ? '1' : '0';
799
+ btn.checked = wasEnabled;
800
+ btn.setAttribute('aria-label', label + ': ' + (wasEnabled ? 'enabled' : 'disabled'));
772
801
  if (wrapper) {
773
- wrapper.classList.toggle('pr-observe-on', wasObserve);
774
- wrapper.classList.toggle('pr-observe-off', !wasObserve);
802
+ wrapper.classList.toggle('pr-automation-on', wasEnabled);
803
+ wrapper.classList.toggle('pr-automation-off', !wasEnabled);
775
804
  }
776
805
  showToast('pr-toast', 'Failed: ' + (d.error || 'unknown'), false);
777
806
  return;
778
807
  }
779
- showToast('pr-toast', next ? 'Auto-observe enabled' : 'Switched to context only', true);
808
+ showToast('pr-toast', label + (next ? ' enabled' : ' disabled'), true);
780
809
  } catch (e) {
781
- btn.dataset.prObserve = wasObserve ? '1' : '0';
782
- btn.checked = wasObserve;
783
- btn.setAttribute('aria-label', 'Auto-observe: ' + (wasObserve ? 'observed' : 'context'));
810
+ btn.dataset.prEnabled = wasEnabled ? '1' : '0';
811
+ btn.checked = wasEnabled;
812
+ btn.setAttribute('aria-label', label + ': ' + (wasEnabled ? 'enabled' : 'disabled'));
784
813
  if (wrapper) {
785
- wrapper.classList.toggle('pr-observe-on', wasObserve);
786
- wrapper.classList.toggle('pr-observe-off', !wasObserve);
814
+ wrapper.classList.toggle('pr-automation-on', wasEnabled);
815
+ wrapper.classList.toggle('pr-automation-off', !wasEnabled);
787
816
  }
788
817
  showToast('pr-toast', 'Error: ' + e.message, false);
789
818
  } finally {
@@ -905,7 +934,7 @@ window.MinionsPrs = {
905
934
  openAddPrModal,
906
935
  openPrDetail,
907
936
  unlinkPr,
908
- togglePrObserve,
937
+ togglePrAutomation,
909
938
  resumePausedCause,
910
939
  resumeBuildFixIneffective,
911
940
  resumeWorktreeHeldPause