@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
@@ -430,7 +430,11 @@ function openScheduleDetail(id) {
430
430
  // ─── Form ───────────────────────────────────────────────────────────────────
431
431
 
432
432
  function _scheduleFormHtml(sched, isEdit) {
433
- const types = ['implement', 'test', 'explore', 'ask', 'review', 'fix'];
433
+ const types = ['implement', 'test', 'explore', 'ask', 'review', 'fix', 'setup'];
434
+ // W-ms5tb6ha016fda10 — only these types may run without a repository
435
+ // checkout. Mirrors shared.CONTROL_PLANE_ELIGIBLE_TYPES; the server
436
+ // re-validates fail-closed, so this is a UX affordance, not the gate.
437
+ const controlPlaneTypes = ['setup', 'ask', 'explore', 'meeting'];
434
438
  const priorities = Array.isArray(window.MINIONS_WORK_ITEM_PRIORITIES)
435
439
  ? window.MINIONS_WORK_ITEM_PRIORITIES
436
440
  : [];
@@ -510,12 +514,21 @@ function _scheduleFormHtml(sched, isEdit) {
510
514
  '</div>' +
511
515
  '<div style="display:flex;gap:12px">' +
512
516
  '<label style="color:var(--text);font-size:var(--text-md);flex:1">Type' +
513
- '<select id="sched-edit-type" style="' + inputStyle + '">' + typeOpts + '</select>' +
517
+ '<select id="sched-edit-type" style="' + inputStyle + '" onchange="_syncScheduleSurfaceAvailability()">' + typeOpts + '</select>' +
514
518
  '</label>' +
515
519
  '<label style="color:var(--text);font-size:var(--text-md);flex:1">Priority' +
516
520
  '<select id="sched-edit-priority" style="' + inputStyle + '">' + priOpts + '</select>' +
517
521
  '</label>' +
518
522
  '</div>' +
523
+ '<label id="sched-surface-row" style="color:var(--text);font-size:var(--text-md)"' +
524
+ ' title="Control-plane tasks call the Minions API and gh/az only. The engine skips git worktree creation entirely, so they cost no checkout and never race shared remote refs. Only setup / ask / explore / meeting may use it.">' +
525
+ 'Execution surface' +
526
+ '<select id="sched-edit-surface" style="' + inputStyle + '">' +
527
+ '<option value="repo"' + (sched.executionSurface === 'control-plane' ? '' : ' selected') + '>repo — allocate a git worktree (default)</option>' +
528
+ '<option value="control-plane"' + (sched.executionSurface === 'control-plane' ? ' selected' : '') + '>control-plane — API/CLI only, no checkout</option>' +
529
+ '</select>' +
530
+ '<span id="sched-surface-hint" style="display:block;margin-top:4px;font-size:var(--text-base);color:var(--muted)"></span>' +
531
+ '</label>' +
519
532
  '<div style="display:flex;gap:12px">' +
520
533
  '<label style="color:var(--text);font-size:var(--text-md);flex:1">Project' +
521
534
  '<select id="sched-edit-project" style="' + inputStyle + '">' + projOpts + '</select>' +
@@ -534,6 +547,33 @@ function _scheduleFormHtml(sched, isEdit) {
534
547
  '</div>';
535
548
  }
536
549
 
550
+ function _scheduleControlPlaneTypes() {
551
+ return ['setup', 'ask', 'explore', 'meeting'];
552
+ }
553
+
554
+ /**
555
+ * W-ms5tb6ha016fda10 — keep the Execution-surface selector honest as the Type
556
+ * changes. Repo-reading/mutating types cannot run without a checkout, so the
557
+ * control-plane option is disabled (and any stale selection reset) rather than
558
+ * letting the operator submit something the server will reject.
559
+ */
560
+ function _syncScheduleSurfaceAvailability() {
561
+ const typeEl = document.getElementById('sched-edit-type');
562
+ const surfaceEl = document.getElementById('sched-edit-surface');
563
+ const hintEl = document.getElementById('sched-surface-hint');
564
+ if (!typeEl || !surfaceEl) return;
565
+ const eligible = _scheduleControlPlaneTypes().includes(typeEl.value);
566
+ const cpOption = surfaceEl.querySelector('option[value="control-plane"]');
567
+ if (cpOption) cpOption.disabled = !eligible;
568
+ if (!eligible && surfaceEl.value === 'control-plane') surfaceEl.value = 'repo';
569
+ surfaceEl.disabled = !eligible;
570
+ if (hintEl) {
571
+ hintEl.textContent = eligible
572
+ ? 'Control-plane skips git worktree creation — use it for API/CLI-only maintenance jobs.'
573
+ : 'Type "' + typeEl.value + '" reads or changes repository files, so it always gets a worktree.';
574
+ }
575
+ }
576
+
537
577
  function openCreateScheduleModal() {
538
578
  window._schedComputedCron = '';
539
579
  document.getElementById('modal-title').textContent = 'New Scheduled Task';
@@ -542,6 +582,7 @@ function openCreateScheduleModal() {
542
582
  // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _scheduleFormHtml() (fields: project names, agent ids/names)
543
583
  document.getElementById('modal-body').innerHTML = _scheduleFormHtml({}, false);
544
584
  document.getElementById('modal').classList.add('open');
585
+ _syncScheduleSurfaceAvailability();
545
586
  _updateCronPreview();
546
587
  }
547
588
 
@@ -556,6 +597,7 @@ function openEditScheduleModal(id) {
556
597
  document.getElementById('modal-body').innerHTML = _scheduleFormHtml(sched, true);
557
598
  window._editScheduleId = id;
558
599
  document.getElementById('modal').classList.add('open');
600
+ _syncScheduleSurfaceAvailability();
559
601
  _updateCronPreview();
560
602
  }
561
603
 
@@ -569,6 +611,8 @@ async function submitSchedule(isEdit, e) {
569
611
  const project = document.getElementById('sched-edit-project').value;
570
612
  const agent = document.getElementById('sched-edit-agent').value;
571
613
  const description = document.getElementById('sched-edit-desc').value;
614
+ const surfaceEl = document.getElementById('sched-edit-surface');
615
+ const executionSurface = surfaceEl && !surfaceEl.disabled ? surfaceEl.value : 'repo';
572
616
 
573
617
  let id;
574
618
  if (isEdit) {
@@ -581,7 +625,7 @@ async function submitSchedule(isEdit, e) {
581
625
  if (!title) { _resetSchedBtn(); _showScheduleError('Title is required'); return; }
582
626
  if (!cron) { _resetSchedBtn(); _showScheduleError('Schedule is required \u2014 select days and time, or use natural language'); return; }
583
627
 
584
- const payload = { id, title, cron, type, priority, project: project || undefined, agent: agent || undefined, description: description || undefined, enabled: true };
628
+ const payload = { id, title, cron, type, priority, project: project || undefined, agent: agent || undefined, description: description || undefined, enabled: true, executionSurface };
585
629
  const url = isEdit ? '/api/schedules/update' : '/api/schedules';
586
630
  try {
587
631
  const res = await fetch(url, {
@@ -689,3 +733,19 @@ async function deleteSchedule(id) {
689
733
  window._generateScheduleId = _generateScheduleId;
690
734
 
691
735
  window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, openScheduleDetail, submitSchedule, toggleScheduleEnabled, runScheduleNow, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview, _schedPrev, _schedNext, _schedSetView };
736
+
737
+ // ── Slim "+ Add" deep-link (W-ms258yw0000ffd8a) ────────────────────────
738
+ // The Slim Automation tile's "+ Add" picker embeds this classic screen at
739
+ // /schedule?embed=1#create and relies on this hook to auto-open the Create
740
+ // Scheduled Task dialog — the classic create flow is reused, not forked (CLAUDE
741
+ // .md slim-vs-classic). Fires only on the /schedule path with a #create hash;
742
+ // the standalone classic screen and every other embed is otherwise untouched.
743
+ // isReady is load-bearing: _scheduleFormHtml builds its Project/Agent selects
744
+ // from cmdProjects/cmdAgents (see above), which refresh.js only fills after the
745
+ // first data tick — opening earlier yields a form with empty dropdowns that
746
+ // never self-heals. Helper lives in utils.js#bootCreateDeepLink.
747
+ typeof bootCreateDeepLink === 'function' && bootCreateDeepLink({
748
+ route: '/schedule',
749
+ resolve: function() { return typeof openCreateScheduleModal === 'function' ? openCreateScheduleModal : null; },
750
+ isReady: classicCreateFormDataReady,
751
+ });
@@ -82,7 +82,7 @@ function formatToolSummary(name, input) {
82
82
  // record, rendered in sequence so prose and tool activity stay interleaved.
83
83
  //
84
84
  // Text-segment boundaries come from the server `segmentId` on chunk events
85
- // (engine/llm.js increments it on a tool boundary or a non-extending text
85
+ // (engine/agents/llm.js increments it on a tool boundary or a non-extending text
86
86
  // push). When segmentId is absent (Copilot pool path, live-stream replay,
87
87
  // older engine) ccSegmentsApplyChunk falls back to a heuristic: a chunk that
88
88
  // arrives after a tool starts a new text segment; a chunk that extends the
@@ -343,7 +343,7 @@ function _renderJsonObj(obj, state) {
343
343
  // W-mpob4nyk0006580e — the terminal banner is no longer derived here.
344
344
  // The engine's dispatch.result is the single source of truth and is
345
345
  // pushed to the live view via the SSE `dispatch.terminal` channel
346
- // (engine/dispatch-events.js → dashboard.js handleAgentLiveStream).
346
+ // (engine/orchestration/dispatch-events.js → dashboard.js handleAgentLiveStream).
347
347
  // `result` JSONL lines and the `[process-exit]` sentinel are still
348
348
  // parsed for other purposes (e.g. tool deltas above), but they must
349
349
  // NOT trigger any banner here — log heuristics can disagree with the
@@ -466,7 +466,7 @@ function renderAgentOutput(text) {
466
466
  /**
467
467
  * W-mpob4nyk0006580e — render the terminal banner from an authoritative
468
468
  * `dispatch.terminal` SSE event. The shape mirrors
469
- * engine/dispatch-events.js buildTerminalEvent():
469
+ * engine/orchestration/dispatch-events.js buildTerminalEvent():
470
470
  * { type, ts, dispatchId, agentId, result, reason, failureClass, prUrl, summary }
471
471
  *
472
472
  * The banner state mirrors `dispatch.result` exactly — green for SUCCESS,
@@ -93,8 +93,8 @@ function _targetTypeLabel(type) {
93
93
  // renderArtifactLink / openArtifact (defined in render-utils.js). Returns null
94
94
  // for targetTypes that don't correspond to a navigable artifact (teams-channel,
95
95
  // future plugin types) so callers fall back to plain text rendering. The
96
- // mapping covers the 8 first-class TARGET_TYPES registered in engine/watches.js
97
- // (engine/shared.js#WATCH_TARGET_TYPE) — dispatch ids are WI ids in practice
96
+ // mapping covers the 8 first-class TARGET_TYPES in engine/watches.js
97
+ // (engine/core/shared.js#WATCH_TARGET_TYPE) — dispatch ids are WI ids in practice
98
98
  // so they share the wi chip target.
99
99
  var _WATCH_TARGET_TO_ART_TYPE = {
100
100
  'pr': 'pr',
@@ -754,7 +754,7 @@ function submitWatch() {
754
754
  // P-w14e7a8c — Phase 7.1: harvest action steps from the chain editor.
755
755
  // 0 steps -> action: null (notify-only legacy behavior).
756
756
  // 1 step -> action: { type, params, guard? } (single-object form).
757
- // 2+ steps -> action: [ {...}, ... ] (array-form chain — engine/watch-actions.js
757
+ // 2+ steps -> action: [ {...}, ... ] (array-form chain — engine/watches/actions.js
758
758
  // runWatchActionChain handles it).
759
759
  var stepRows = document.querySelectorAll('.watch-step-row');
760
760
  var steps = [];
@@ -867,3 +867,19 @@ window.MinionsWatches = {
867
867
  _watchTargetToArtType: _watchTargetToArtType,
868
868
  _renderWatchTargetChip: _renderWatchTargetChip,
869
869
  };
870
+
871
+ // ── Slim "+ Add" deep-link (W-ms258yw0000ffd8a) ────────────────────────
872
+ // The Slim Automation tile's "+ Add" picker embeds this classic screen at
873
+ // /watches?embed=1#create and relies on this hook to auto-open the Create Watch
874
+ // dialog — the classic create flow is reused, not forked (CLAUDE.md slim-vs-
875
+ // classic). Fires only on the /watches path with a #create hash; the standalone
876
+ // classic screen and every other embed is otherwise untouched.
877
+ // isReady is load-bearing: _renderCreateWatchModal builds its Owner/Project
878
+ // selects from cmdAgents/cmdProjects (see above), which refresh.js only fills
879
+ // after the first data tick — opening earlier yields a form with empty dropdowns
880
+ // that never self-heals. Helper lives in utils.js#bootCreateDeepLink.
881
+ typeof bootCreateDeepLink === 'function' && bootCreateDeepLink({
882
+ route: '/watches',
883
+ resolve: function() { return typeof openCreateWatchModal === 'function' ? openCreateWatchModal : null; },
884
+ isReady: classicCreateFormDataReady,
885
+ });
@@ -1,9 +1,41 @@
1
1
  // dashboard/js/render-work-items.js — Work item rendering and management extracted from dashboard.html
2
2
 
3
3
  let allWorkItems = [];
4
+ let filteredWorkItems = [];
4
5
  let wiPage = 0;
6
+ let wiFilterOptionsSignature = '';
5
7
  const WI_PER_PAGE = 20;
6
8
 
9
+ // Filter + sort controls modeled on the PR table (dashboard/js/render-prs.js).
10
+ // Each dimension maps to a <select> in dashboard/pages/work.html; options are
11
+ // populated dynamically from the current data set with a signature guard so the
12
+ // user's selection survives the 4s refresh poll. (W-ms7kutzv003q17ad)
13
+ const WI_FILTER_CONTROL_IDS = {
14
+ status: 'wi-filter-status',
15
+ type: 'wi-filter-type',
16
+ project: 'wi-filter-project',
17
+ agent: 'wi-filter-agent',
18
+ priority: 'wi-filter-priority',
19
+ };
20
+ const WI_FILTER_ALL_LABELS = {
21
+ status: 'All statuses',
22
+ type: 'All types',
23
+ project: 'All projects',
24
+ agent: 'All agents',
25
+ priority: 'All priorities',
26
+ };
27
+ const WI_SORT_LABELS = [
28
+ { value: 'lifecycle', label: 'Lifecycle (active first)' },
29
+ { value: 'updated', label: 'Last updated' },
30
+ { value: 'created', label: 'Created' },
31
+ { value: 'priority', label: 'Priority' },
32
+ { value: 'status', label: 'Status' },
33
+ { value: 'type', label: 'Type' },
34
+ ];
35
+ const WI_SORT_CONTROL_ID = 'wi-sort-by';
36
+ const WI_SORT_DIR_CONTROL_ID = 'wi-sort-dir';
37
+
38
+
7
39
  // W-mq08kuog001110a6 — Needs-Attention badge for stuck pending work items.
8
40
  //
9
41
  // A pending WI is "needs attention" when the engine is intentionally refusing
@@ -26,6 +58,7 @@ const WI_PER_PAGE = 20;
26
58
  // * branch_locked — wait for the holding dispatch
27
59
  // * worktree_branch_held — configured checkout is re-checked each tick
28
60
  // * dependency_unmet — auto-clears when upstream dep finishes (engine discovery loop + DEPENDENCY_MET watch)
61
+ // * dependency_conflict — dep-branch merge conflict repair in progress; auto-clears when the conflict-repair WI completes (W-mrzqfozm00035365)
29
62
  // * live_checkout_busy — one live-mode dispatch at a time; clears when running dispatch finishes
30
63
  //
31
64
  // no_agent (kept as a trigger) now means the engine found NO eligible agent
@@ -73,6 +106,22 @@ function needsAttentionInfo(item) {
73
106
  var budgetMsg = 'Assigned agent ' + agent + ' is over its maxBudgetUsd cap.';
74
107
  return { kind: 'budget_exceeded', short: budgetMsg, full: budgetMsg };
75
108
  }
109
+ // W-mrzqfozm00035365 — a downstream item whose dependency-branch merge
110
+ // conflict could NOT be repaired (repair WI failed/cancelled, or completed
111
+ // but the branches still conflict) is an actionable block: it will not
112
+ // dispatch until a human resolves the conflict on the dep branch. The
113
+ // in-progress `dependency_conflict` state is deliberately NOT surfaced here —
114
+ // it auto-clears when the repair completes.
115
+ if (reason === 'dependency_conflict_unresolved') {
116
+ var dc = item._dependencyConflict || {};
117
+ var cb = dc.conflictBranch || '(dependency branch)';
118
+ var cw = dc.conflictsWith || 'the base branch';
119
+ var rw = dc.repairWiId || '(conflict-fix work item)';
120
+ var dcMsg = 'Dependency branch ' + cb + ' conflicts with ' + cw +
121
+ ' and the conflict-repair work item ' + rw + ' did not resolve it. ' +
122
+ 'Resolve the conflict on ' + cb + ' (or re-run ' + rw + '); this item re-runs dependency integration automatically once the repair succeeds.';
123
+ return { kind: 'dependency_conflict_unresolved', short: dcMsg, full: dcMsg };
124
+ }
76
125
  return null;
77
126
  }
78
127
 
@@ -80,6 +129,20 @@ function formatPendingReason(item, detailed) {
80
129
  var reason = item && item._pendingReason ? String(item._pendingReason) : '';
81
130
  if (!reason) return '';
82
131
  if (reason === 'already_dispatched') return 'Queued — waiting for available agent slot';
132
+ if (reason === 'dependency_conflict') {
133
+ if (!detailed) return 'dependency conflict — repair in progress';
134
+ var dcp = item._dependencyConflict || {};
135
+ return 'Dependency branch ' + (dcp.conflictBranch || '(unknown)') + ' conflicts with ' +
136
+ (dcp.conflictsWith || 'the base branch') + '. Held pending behind conflict-repair ' +
137
+ (dcp.repairWiId || '(repair WI)') + '; auto-resumes when the repair completes.';
138
+ }
139
+ if (reason === 'dependency_conflict_unresolved') {
140
+ if (!detailed) return 'dependency conflict — needs attention';
141
+ var dcu = item._dependencyConflict || {};
142
+ return 'Dependency branch ' + (dcu.conflictBranch || '(unknown)') + ' still conflicts with ' +
143
+ (dcu.conflictsWith || 'the base branch') + ' after conflict-repair ' +
144
+ (dcu.repairWiId || '(repair WI)') + '. Needs manual resolution.';
145
+ }
83
146
  if (reason === 'agents_busy') return 'Waiting for an available agent — all eligible agents are busy';
84
147
  if (reason !== 'worktree_branch_held') return reason.replace(/_/g, ' ');
85
148
  if (!detailed) return 'operator checkout holds branch';
@@ -187,6 +250,7 @@ function wiRow(item) {
187
250
  '</td>' +
188
251
  '<td>' + prLink + (failSnippet ? failSnippet : '') + '</td>' +
189
252
  '<td><span class="pr-date">' + escapeHtml((item.created || '').slice(0, 16).replace('T', ' ')) + '</span></td>' +
253
+ '<td><span class="pr-date" title="' + escapeHtml(_wiUpdatedTitle(item)) + '">' + escapeHtml(_wiUpdatedLabel(item)) + '</span></td>' +
190
254
  '<td style="white-space:nowrap;font-size:var(--text-sm);color:var(--muted)">' +
191
255
  (function() {
192
256
  // /api/status slims the full arrays into integer counts
@@ -217,44 +281,188 @@ function wiRow(item) {
217
281
  '</tr>';
218
282
  }
219
283
 
284
+ function _wiUpdatedLabel(item) {
285
+ var ms = window.MinionsWorkItemFilters && window.MinionsWorkItemFilters.lastUpdatedMs
286
+ ? window.MinionsWorkItemFilters.lastUpdatedMs(item)
287
+ : null;
288
+ if (ms == null) return '—';
289
+ return typeof timeAgo === 'function' ? timeAgo(new Date(ms).toISOString()) : new Date(ms).toISOString().slice(0, 16).replace('T', ' ');
290
+ }
291
+
292
+ function _wiUpdatedTitle(item) {
293
+ var ms = window.MinionsWorkItemFilters && window.MinionsWorkItemFilters.lastUpdatedMs
294
+ ? window.MinionsWorkItemFilters.lastUpdatedMs(item)
295
+ : null;
296
+ if (ms == null) return 'No update timestamp';
297
+ return new Date(ms).toISOString().slice(0, 16).replace('T', ' ') + ' UTC';
298
+ }
299
+
300
+ // Populate the filter <select>s from the current data set. Guarded by a
301
+ // signature so options only re-render when the derivable option set changes —
302
+ // this preserves the user's current selection across the periodic poll. Mirrors
303
+ // _syncPrFilterOptions in render-prs.js. (W-ms7kutzv003q17ad)
304
+ function _syncWiFilterOptions(items) {
305
+ if (!window.MinionsWorkItemFilters) return;
306
+ const options = window.MinionsWorkItemFilters.options(items);
307
+ const controls = Object.keys(WI_FILTER_CONTROL_IDS).map(function(dimension) {
308
+ return {
309
+ dimension: dimension,
310
+ select: document.getElementById(WI_FILTER_CONTROL_IDS[dimension]),
311
+ };
312
+ }).filter(function(entry) { return entry.select; });
313
+ if (!controls.length) return;
314
+ const signature = JSON.stringify(options);
315
+ if (signature === wiFilterOptionsSignature) return;
316
+ wiFilterOptionsSignature = signature;
317
+ controls.forEach(function(entry) {
318
+ const dimension = entry.dimension;
319
+ const select = entry.select;
320
+ const selected = select.value;
321
+ const all = document.createElement('option');
322
+ all.value = '';
323
+ all.textContent = WI_FILTER_ALL_LABELS[dimension];
324
+ const nodes = [all].concat((options[dimension] || []).map(function(option) {
325
+ const node = document.createElement('option');
326
+ node.value = option.value;
327
+ node.textContent = option.label;
328
+ return node;
329
+ }));
330
+ select.replaceChildren(...nodes);
331
+ if (nodes.some(function(node) { return node.value === selected; })) select.value = selected;
332
+ });
333
+ }
334
+
335
+ function _readWiFilters() {
336
+ const filters = {};
337
+ Object.keys(WI_FILTER_CONTROL_IDS).forEach(function(dimension) {
338
+ const select = document.getElementById(WI_FILTER_CONTROL_IDS[dimension]);
339
+ filters[dimension] = select ? select.value : '';
340
+ });
341
+ return filters;
342
+ }
343
+
344
+ function _hasActiveWiFilters(filters) {
345
+ return Object.keys(WI_FILTER_CONTROL_IDS).some(function(dimension) {
346
+ return Boolean(filters[dimension]);
347
+ });
348
+ }
349
+
350
+ // Single source for the sort default in this module: the shared helper's
351
+ // WI_SORT_DEFAULT (dashboard/shared/wi-filters.js), with one literal fallback
352
+ // for the no-module case. Kept in sync with the shared default by
353
+ // test/unit/wi-filters.test.js.
354
+ function _wiSortDefault() {
355
+ return (window.MinionsWorkItemFilters && window.MinionsWorkItemFilters.sortDefault)
356
+ || { sortBy: 'lifecycle', dir: 'desc' };
357
+ }
358
+
359
+ function _readWiSort() {
360
+ const byEl = document.getElementById(WI_SORT_CONTROL_ID);
361
+ const dirEl = document.getElementById(WI_SORT_DIR_CONTROL_ID);
362
+ const def = _wiSortDefault();
363
+ return {
364
+ sortBy: byEl && byEl.value ? byEl.value : def.sortBy,
365
+ dir: dirEl && dirEl.value ? dirEl.value : def.dir,
366
+ };
367
+ }
368
+
369
+ function applyWiFilters() {
370
+ wiPage = 0;
371
+ renderWorkItems(allWorkItems, { preserveScroll: false });
372
+ }
373
+
374
+ function resetWiFilters() {
375
+ Object.keys(WI_FILTER_CONTROL_IDS).forEach(function(dimension) {
376
+ const select = document.getElementById(WI_FILTER_CONTROL_IDS[dimension]);
377
+ if (select) select.value = '';
378
+ });
379
+ const byEl = document.getElementById(WI_SORT_CONTROL_ID);
380
+ const dirEl = document.getElementById(WI_SORT_DIR_CONTROL_ID);
381
+ const def = _wiSortDefault();
382
+ if (byEl) byEl.value = def.sortBy;
383
+ if (dirEl) {
384
+ dirEl.value = def.dir;
385
+ _syncWiSortDirLabel(dirEl);
386
+ }
387
+ applyWiFilters();
388
+ }
389
+
390
+ // Toggle the asc/desc direction button (value + arrow glyph) and re-render.
391
+ function toggleWiSortDir() {
392
+ const dirEl = document.getElementById(WI_SORT_DIR_CONTROL_ID);
393
+ if (dirEl) {
394
+ dirEl.value = dirEl.value === 'asc' ? 'desc' : 'asc';
395
+ _syncWiSortDirLabel(dirEl);
396
+ }
397
+ applyWiFilters();
398
+ }
399
+
400
+ function _syncWiSortDirLabel(dirEl) {
401
+ if (!dirEl) return;
402
+ const asc = dirEl.value === 'asc';
403
+ dirEl.setAttribute('aria-label', asc ? 'Sort ascending (oldest first)' : 'Sort descending (newest first)');
404
+ dirEl.title = asc ? 'Ascending — click for descending' : 'Descending — click for ascending';
405
+ dirEl.textContent = asc ? '\u2191 Asc' : '\u2193 Desc';
406
+ }
407
+
408
+ // Apply the active filters + sort to the raw items. dashboard-build.js always
409
+ // assembles dashboard/shared/wi-filters.js before this module (enforced by
410
+ // test/unit/wi-filters.test.js), so the shared helper is the ONE definition of
411
+ // filter and lifecycle-sort semantics. A stripped harness without it renders
412
+ // the raw list rather than carrying a second, drift-prone status order.
413
+ function _computeWiView(items) {
414
+ const filters = _readWiFilters();
415
+ const sort = _readWiSort();
416
+ const wf = window.MinionsWorkItemFilters;
417
+ if (!wf) return { view: items.slice(), filters: filters };
418
+ return { view: wf.sort(wf.filter(items, filters), sort), filters: filters };
419
+ }
420
+
220
421
  function renderWorkItems(items, opts) {
221
422
  opts = opts || {};
222
423
  items = items.filter(function(w) { return !isDeleted('wi:' + w.id); });
223
- // Sort: active/dispatched first, then by most recent activity
224
- const statusOrder = { dispatched: 0, pending: 1, queued: 1, failed: 2, done: 3 };
225
- items.sort((a, b) => {
226
- const sa = statusOrder[a.status] ?? 2, sb = statusOrder[b.status] ?? 2;
227
- if (sa !== sb) return sa - sb;
228
- const ta = a.completedAt || a.dispatched_at || a.created || '';
229
- const tb = b.completedAt || b.dispatched_at || b.created || '';
230
- return tb.localeCompare(ta); // most recent first
231
- });
232
424
  allWorkItems = items;
425
+ _syncWiFilterOptions(allWorkItems);
426
+ const computed = _computeWiView(allWorkItems);
427
+ const view = computed.view;
428
+ filteredWorkItems = view;
233
429
  const el = document.getElementById('work-items-content');
234
430
  const countEl = document.getElementById('wi-count');
235
- if (countEl) countEl.textContent = items.length;
236
- if (!items.length) {
431
+ const summaryEl = document.getElementById('wi-filter-summary');
432
+ if (countEl) countEl.textContent = view.length;
433
+ if (summaryEl) {
434
+ summaryEl.textContent = _hasActiveWiFilters(computed.filters)
435
+ ? 'Showing ' + view.length + ' of ' + allWorkItems.length
436
+ : '';
437
+ }
438
+ if (!allWorkItems.length) {
439
+ wiPage = 0;
237
440
  el.innerHTML = '<p class="empty">No work items. Add tasks via Command Center above.</p>';
238
441
  return;
239
442
  }
443
+ if (!view.length) {
444
+ wiPage = 0;
445
+ el.innerHTML = '<p class="empty">No work items match the selected filters.</p>';
446
+ return;
447
+ }
240
448
 
241
- const totalPages = Math.ceil(items.length / WI_PER_PAGE);
449
+ const totalPages = Math.ceil(view.length / WI_PER_PAGE);
242
450
  const previousPage = wiPage;
243
451
  if (wiPage >= totalPages) wiPage = totalPages - 1;
244
452
  const start = wiPage * WI_PER_PAGE;
245
- const pageItems = items.slice(start, start + WI_PER_PAGE);
453
+ const pageItems = view.slice(start, start + WI_PER_PAGE);
246
454
 
247
- let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>Title</th><th>Project</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th><th></th></tr></thead><tbody>';
455
+ let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>Title</th><th>Project</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th>Updated</th><th></th><th></th></tr></thead><tbody>';
248
456
  html += pageItems.map(wiRow).join('');
249
457
  html += '</tbody></table></div>';
250
458
 
251
- if (items.length > WI_PER_PAGE) {
459
+ if (view.length > WI_PER_PAGE) {
252
460
  html += '<div class="pr-pager">' +
253
- '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+WI_PER_PAGE, items.length) + ' of ' + items.length + '</span>' +
461
+ '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+WI_PER_PAGE, view.length) + ' of ' + view.length + '</span>' +
254
462
  '<div class="pr-pager-btns">' +
255
463
  '<button class="pr-pager-btn ' + (wiPage === 0 ? 'disabled' : '') + '" onclick="wiPrev()">Prev</button>' +
256
464
  '<button class="pr-pager-btn ' + (wiPage >= totalPages-1 ? 'disabled' : '') + '" onclick="wiNext()">Next</button>' +
257
- '<button class="pr-pager-btn see-all" onclick="openAllWorkItems()">See all ' + items.length + '</button>' +
465
+ '<button class="pr-pager-btn see-all" onclick="openAllWorkItems()">See all ' + view.length + '</button>' +
258
466
  '</div>' +
259
467
  '</div>';
260
468
  }
@@ -517,7 +725,7 @@ async function retryWorkItem(id, source) {
517
725
  }
518
726
 
519
727
  function wiPrev() { if (wiPage > 0) { wiPage--; renderWorkItems(allWorkItems, { preserveScroll: false }); } }
520
- function wiNext() { const tp = Math.ceil(allWorkItems.length / WI_PER_PAGE); if (wiPage < tp-1) { wiPage++; renderWorkItems(allWorkItems, { preserveScroll: false }); } }
728
+ function wiNext() { const tp = Math.ceil(filteredWorkItems.length / WI_PER_PAGE); if (wiPage < tp-1) { wiPage++; renderWorkItems(allWorkItems, { preserveScroll: false }); } }
521
729
 
522
730
  let _feedbackRating = null;
523
731
  function feedbackWorkItem(id, source) {
@@ -676,7 +884,7 @@ async function _submitCreateWorkItem(e) {
676
884
  }
677
885
 
678
886
  // P-79b47b0c — Derive a canonical PR id (`<host>:<slug>#<number>`) from a
679
- // GitHub or ADO PR URL. Matches the format produced by engine/queries.js
887
+ // GitHub or ADO PR URL. Matches the format produced by engine/core/queries.js
680
888
  // when stamping pr.id. Returns null when the URL doesn't match a known
681
889
  // host pattern.
682
890
  function _wiDeriveCanonicalPrIdFromUrl(url) {
@@ -691,7 +899,7 @@ function _wiDeriveCanonicalPrIdFromUrl(url) {
691
899
  // W-ms0ytfdd000d5a26 — provenance hint for the WI detail modal's Model field.
692
900
  // Explains WHERE the shown model came from so an operator can tell a recorded
693
901
  // fact from a projection. Mirrors the `source` values produced by
694
- // engine/execution-model.js#resolveWorkItemModel.
902
+ // engine/core/execution-model.js#resolveWorkItemModel.
695
903
  function _wiModelSourceHint(m) {
696
904
  var runtime = m.runtime || 'runtime';
697
905
  var hint;
@@ -766,7 +974,7 @@ function _wiRenderDetail(item) {
766
974
  html += field('Description', '<div id="wi-detail-desc" style="font-size:var(--text-md);max-height:320px;overflow-y:auto;padding:8px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm)">' + _descHtml + '</div>');
767
975
  html += field('Agent', escapeHtml(item.dispatched_to || item.agent || 'Auto'));
768
976
  // W-ms0ytfdd000d5a26 — the model this work item actually ran on. Resolved
769
- // server-side (engine/queries.js#resolveWorkItemModelInfo) so persisted
977
+ // server-side (engine/core/queries.js#resolveWorkItemModelInfo) so persisted
770
978
  // dispatch evidence — what the runtime reported, else what Minions requested
771
979
  // — always beats current config. Items with no dispatch record show the
772
980
  // projection from current config, marked as such. When nothing concrete is
@@ -795,8 +1003,8 @@ function _wiRenderDetail(item) {
795
1003
  if (item.failReason) html += field('Failure Reason', '<span style="color:var(--red)">' + escapeHtml(item.failReason) + '</span>');
796
1004
  // W-mrtdet41000h935a — surface the cancellation reason for CANCELLED items.
797
1005
  // Cancellers stamp the reason on the record: POST /api/work-items/cancel and
798
- // engine/dispatch.js/watch-actions.js write `_cancelledBy`; the goal-invalidation
799
- // path (engine/lifecycle.js) writes `cancellationReason`. Prefer _cancelledBy,
1006
+ // engine/orchestration/dispatch.js and engine/watches/actions.js write `_cancelledBy`; the goal-invalidation
1007
+ // path (engine/orchestration/lifecycle.js) writes `cancellationReason`. Prefer _cancelledBy,
800
1008
  // fall back to cancellationReason, then a no-reason marker. cancelledAt (ISO) is
801
1009
  // rendered like the other timestamps when present. Reason text can carry
802
1010
  // untrusted PR/branch content, so it is escaped.
@@ -885,7 +1093,7 @@ function _wiRenderDetail(item) {
885
1093
  // P-c549d07e — "Spawned by" back-link chip. The schedule/pipeline that
886
1094
  // dispatched this WI is the natural Back target from the WI modal. The
887
1095
  // primary source is meta.spawnedBy ('schedule:<id>' | 'pipeline:<id>'),
888
- // stamped by engine/scheduler.js + engine/pipeline.js on new dispatches.
1096
+ // stamped by engine/orchestration/scheduler.js + engine/orchestration/pipeline.js on new dispatches.
889
1097
  // For older WIs already in state we fall back to (a) item._scheduleId
890
1098
  // and (b) item.createdBy when it starts with 'pipeline:' so historical
891
1099
  // items still light up the chip — purely additive, no behavior change.
@@ -925,7 +1133,7 @@ function _wiRenderDetail(item) {
925
1133
  // named e.g. "src"). The genuine source-plan/PRD case is already fully
926
1134
  // covered by arts.plan / arts.prd / arts.sourcePlan below, which are
927
1135
  // populated server-side from the WI's real sourcePlan/_planFileName/
928
- // planFile fields (engine/queries.js) — no separate item._source-based
1136
+ // planFile fields (engine/core/queries.js) — no separate item._source-based
929
1137
  // chip is needed.
930
1138
  var arts = item._artifacts || {};
931
1139
  var artPills = '';
@@ -963,7 +1171,7 @@ function _wiRenderDetail(item) {
963
1171
 
964
1172
  // P-34fa5d79 — Mentions: notes whose YAML frontmatter cites this WI
965
1173
  // (work_item: / wi: / sourceItem:). Slim string[] field set by
966
- // engine/queries.js#getWorkItems from _buildNotesByWiMap. Each entry is
1174
+ // engine/core/queries.js#getWorkItems from _buildNotesByWiMap. Each entry is
967
1175
  // either a bare inbox filename or `archive:<filename>` for archive notes.
968
1176
  //
969
1177
  // #308 — repeated attempts for one WI used to render a flat pile of
@@ -1077,9 +1285,9 @@ function openWorkItemDetail(id, source) {
1077
1285
  }
1078
1286
 
1079
1287
  function openAllWorkItems() {
1080
- document.getElementById('modal-title').textContent = 'All Work Items (' + allWorkItems.length + ')';
1081
- const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>Title</th><th>Project</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th><th></th></tr></thead><tbody>' +
1082
- allWorkItems.map(wiRow).join('') + '</tbody></table></div>';
1288
+ document.getElementById('modal-title').textContent = 'All Work Items (' + filteredWorkItems.length + ')';
1289
+ const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>Title</th><th>Project</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th>Updated</th><th></th><th></th></tr></thead><tbody>' +
1290
+ filteredWorkItems.map(wiRow).join('') + '</tbody></table></div>';
1083
1291
  // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() by wiRow() (fields: work item id/title/source/status/agent/PR links/dates/follow-up metadata)
1084
1292
  document.getElementById('modal-body').innerHTML = html;
1085
1293
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
@@ -1206,7 +1414,7 @@ function _wiInjectSourceChipFromFrontmatter(content, containerId) {
1206
1414
  }
1207
1415
 
1208
1416
  if (typeof window !== 'undefined') {
1209
- window.MinionsWork = { wiRow, renderWorkItems, editWorkItem, submitWorkItemEdit, deleteWorkItem, archiveWorkItem, toggleWorkItemArchive, retryWorkItem, wiPrev, wiNext, feedbackWorkItem, openCreateWorkItemModal, openWorkItemDetail, openAllWorkItems, viewAgentOutput, openInboxNote, needsAttentionInfo };
1417
+ window.MinionsWork = { wiRow, renderWorkItems, editWorkItem, submitWorkItemEdit, deleteWorkItem, archiveWorkItem, toggleWorkItemArchive, retryWorkItem, wiPrev, wiNext, feedbackWorkItem, openCreateWorkItemModal, openWorkItemDetail, openAllWorkItems, viewAgentOutput, openInboxNote, needsAttentionInfo, applyWiFilters, resetWiFilters, toggleWiSortDir };
1210
1418
  }
1211
1419
  // exported for testing — pure helpers safe to require under Node (no DOM deps).
1212
1420
  if (typeof module !== 'undefined' && module.exports) {