@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.
- package/bin/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +58 -4
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +179 -20
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +13 -2
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
package/dashboard/js/qa.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// happening and exposes per-row operational controls.
|
|
8
8
|
//
|
|
9
9
|
// Sections (top → bottom):
|
|
10
|
-
// 1. Active QA Sessions — non-terminal sessions only,
|
|
11
|
-
//
|
|
10
|
+
// 1. Active QA Sessions — non-terminal sessions only, opening straight on the
|
|
11
|
+
// session list, with a collapsed disclosure for terminal sessions (last 10).
|
|
12
12
|
// Per-card controls: approve / edit / cancel / dismiss / kill.
|
|
13
13
|
// 2. Live Targets — slim dedup'd list of /api/managed-processes +
|
|
14
14
|
// /api/keep-processes with a health badge and a link to /engine
|
|
@@ -17,8 +17,11 @@
|
|
|
17
17
|
// while the QA page is active. Each row links to the live agent stream
|
|
18
18
|
// and renders inline artifact previews via /api/qa/artifacts/<runId>/<file>
|
|
19
19
|
// (screenshots as <img>, videos as <video>, logs as 40-line text preview
|
|
20
|
-
// with a "View full" link).
|
|
21
|
-
//
|
|
20
|
+
// with a "View full" link). For a run in a terminal state only the
|
|
21
|
+
// screenshots + video stay inline; every other artifact collapses behind a
|
|
22
|
+
// "Show all artifacts (N)" disclosure (W-ms9p62od00kt5b58). No direct
|
|
23
|
+
// filesystem paths are exposed — artifact URLs always go through
|
|
24
|
+
// /api/qa/artifacts/.
|
|
22
25
|
// 4. Validation Runbooks — collapsed disclosure with a small CC-pitch and
|
|
23
26
|
// the runbook list. Each row has a Run + Delete button.
|
|
24
27
|
//
|
|
@@ -30,6 +33,27 @@
|
|
|
30
33
|
let _qaRunsPollInterval = null;
|
|
31
34
|
let _qaRunbooksCache = [];
|
|
32
35
|
|
|
36
|
+
// W-ms9p62od00kt5b58 — state that must survive loadQaRuns()'s 5s
|
|
37
|
+
// replaceChildren() of the whole runs list.
|
|
38
|
+
// - _qaOpenArtifactGroups: run ids whose "Show all artifacts" disclosure the
|
|
39
|
+
// user opened. Without it the re-render would snap every group shut.
|
|
40
|
+
// - _qaLogPreviewCache: fetched preview text keyed by artifact URL. The
|
|
41
|
+
// per-element `__qaLoaded` flag dies with the node on every poll, so an
|
|
42
|
+
// opened group would otherwise refetch its previews every 5 seconds.
|
|
43
|
+
const _qaOpenArtifactGroups = new Set();
|
|
44
|
+
const QA_LOG_PREVIEW_CACHE_MAX = 200;
|
|
45
|
+
const _qaLogPreviewCache = new Map();
|
|
46
|
+
|
|
47
|
+
function _qaCacheLogPreview(url, text) {
|
|
48
|
+
if (!url) return;
|
|
49
|
+
// Re-insert so the Map's insertion order doubles as an LRU queue.
|
|
50
|
+
if (_qaLogPreviewCache.has(url)) _qaLogPreviewCache.delete(url);
|
|
51
|
+
_qaLogPreviewCache.set(url, text);
|
|
52
|
+
while (_qaLogPreviewCache.size > QA_LOG_PREVIEW_CACHE_MAX) {
|
|
53
|
+
_qaLogPreviewCache.delete(_qaLogPreviewCache.keys().next().value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
function _stopQaRunsPoll() {
|
|
34
58
|
if (_qaRunsPollInterval) {
|
|
35
59
|
clearInterval(_qaRunsPollInterval);
|
|
@@ -306,7 +330,7 @@ async function loadQaRuns() {
|
|
|
306
330
|
return;
|
|
307
331
|
}
|
|
308
332
|
const rows = items.map(_qaRenderRunRow).join('');
|
|
309
|
-
// eslint-disable-next-line no-unsanitized/method -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: run id, status, runbook, target, timestamp, workItemId, agentId, artifact file names)
|
|
333
|
+
// eslint-disable-next-line no-unsanitized/method -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: run id, status, runbook, target, timestamp, workItemId, agentId, artifact file names); the secondary-artifact count is a computed number
|
|
310
334
|
const frag = document.createRange().createContextualFragment(rows);
|
|
311
335
|
root.replaceChildren(frag);
|
|
312
336
|
}
|
|
@@ -346,8 +370,8 @@ function _qaRenderRunRow(run) {
|
|
|
346
370
|
const actionsHtml = (liveLink || deleteBtn)
|
|
347
371
|
? '<span class="qa-run-actions">' + liveLink + deleteBtn + '</span>'
|
|
348
372
|
: '';
|
|
349
|
-
const artifactsHtml = _qaRenderArtifactPreviews(id, run.artifacts || []);
|
|
350
|
-
return '<div class="qa-run-row" data-run-id="' + escHtml(id) + '">' +
|
|
373
|
+
const artifactsHtml = _qaRenderArtifactPreviews(id, run.artifacts || [], status);
|
|
374
|
+
return '<div class="qa-run-row" id="qa-run-' + escHtml(id) + '" data-run-id="' + escHtml(id) + '">' +
|
|
351
375
|
'<div class="qa-run-head">' +
|
|
352
376
|
'<span class="qa-run-status ' + escHtml(statusClass) + '">' + escHtml(status) + '</span>' +
|
|
353
377
|
'<span class="qa-run-name">' + escHtml(runbook) + '</span>' +
|
|
@@ -360,7 +384,7 @@ function _qaRenderRunRow(run) {
|
|
|
360
384
|
}
|
|
361
385
|
|
|
362
386
|
// W-mpxp90xs000i5732 — terminal status set kept in sync with
|
|
363
|
-
// engine/qa
|
|
387
|
+
// engine/qa/runs.js#TERMINAL_STATUSES. The Delete button only renders for
|
|
364
388
|
// runs in one of these states; the engine's deleteQaRun returns 409 for
|
|
365
389
|
// anything else (defense in depth).
|
|
366
390
|
function _qaIsTerminalStatus(status) {
|
|
@@ -408,9 +432,30 @@ async function qaDeleteRun(id) {
|
|
|
408
432
|
}
|
|
409
433
|
}
|
|
410
434
|
|
|
411
|
-
|
|
435
|
+
// W-ms9p62od00kt5b58 — artifact triage.
|
|
436
|
+
//
|
|
437
|
+
// A COMPLETED run used to render every artifact inline at equal weight:
|
|
438
|
+
// screenshots and video next to .json blobs, .md reports, and a binary
|
|
439
|
+
// trace.zip garbled into a <pre> by the text-preview fetcher. The final
|
|
440
|
+
// screenshots + video ARE the result of a QA run; everything else is
|
|
441
|
+
// supporting material.
|
|
442
|
+
//
|
|
443
|
+
// So for a run in a terminal state (see _qaIsTerminalStatus) primary artifacts
|
|
444
|
+
// render inline as before and the rest collapse behind one native <details>.
|
|
445
|
+
// A non-terminal run keeps the old flat layout on purpose — while a run is
|
|
446
|
+
// still going the logs are the signal, so hiding them would be a regression.
|
|
447
|
+
function _qaRenderArtifactPreviews(runId, artifacts, status) {
|
|
412
448
|
if (!runId || !Array.isArray(artifacts) || !artifacts.length) return '';
|
|
413
|
-
const
|
|
449
|
+
const IMAGE_EXTS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'];
|
|
450
|
+
const VIDEO_EXTS = ['mp4', 'webm', 'ogg', 'mov'];
|
|
451
|
+
// Extensions whose bytes are NOT text. Fetching these into a <pre> is what
|
|
452
|
+
// rendered trace.zip as mojibake; they get a download link and no preview
|
|
453
|
+
// fetch at all. An artifact with no extension is treated the same way —
|
|
454
|
+
// unknown content is not assumed to be safe to inline as text.
|
|
455
|
+
const BINARY_EXTS = ['zip', 'gz', 'tgz', 'tar', 'bz2', 'xz', 'zst', 'br', '7z', 'rar',
|
|
456
|
+
'pdf', 'wasm', 'exe', 'dll', 'so', 'dylib', 'bin', 'db', 'sqlite', 'ico'];
|
|
457
|
+
const primary = [];
|
|
458
|
+
const secondary = [];
|
|
414
459
|
for (const a of artifacts) {
|
|
415
460
|
// Canonical records use { path, type }; retain the retired { file, kind }
|
|
416
461
|
// shape for older persisted runs.
|
|
@@ -428,25 +473,36 @@ function _qaRenderArtifactPreviews(runId, artifacts) {
|
|
|
428
473
|
const typeHint = String(typeRaw).toLowerCase();
|
|
429
474
|
const ext = (file.match(/\.([a-z0-9]+)$/i) || ['', ''])[1].toLowerCase();
|
|
430
475
|
const url = '/api/qa/artifacts/' + encodeURIComponent(runId) + '/' + encodeURIComponent(artifactPath);
|
|
431
|
-
if (typeHint === 'screenshot' ||
|
|
432
|
-
|
|
476
|
+
if (typeHint === 'screenshot' || IMAGE_EXTS.includes(ext)) {
|
|
477
|
+
primary.push(
|
|
433
478
|
'<div class="qa-artifact qa-artifact-image">' +
|
|
434
479
|
'<img src="' + escHtml(url) + '" alt="' + escHtml(artifactPath) + '" loading="lazy">' +
|
|
435
480
|
'<div class="qa-artifact-caption">' + escHtml(artifactPath) + '</div>' +
|
|
436
481
|
'</div>'
|
|
437
482
|
);
|
|
438
|
-
} else if (typeHint === 'video' ||
|
|
439
|
-
|
|
483
|
+
} else if (typeHint === 'video' || VIDEO_EXTS.includes(ext)) {
|
|
484
|
+
primary.push(
|
|
440
485
|
'<div class="qa-artifact qa-artifact-video">' +
|
|
441
486
|
'<video controls src="' + escHtml(url) + '"></video>' +
|
|
442
487
|
'<div class="qa-artifact-caption">' + escHtml(artifactPath) + '</div>' +
|
|
443
488
|
'</div>'
|
|
444
489
|
);
|
|
490
|
+
} else if (!ext || BINARY_EXTS.includes(ext)) {
|
|
491
|
+
// Binary / unknown — link only. Deliberately carries no data-qa-log-url,
|
|
492
|
+
// so _qaFillLogPreviews never fetches it.
|
|
493
|
+
secondary.push(
|
|
494
|
+
'<div class="qa-artifact qa-artifact-binary">' +
|
|
495
|
+
'<div class="qa-artifact-caption">' +
|
|
496
|
+
escHtml(artifactPath) + ' ' +
|
|
497
|
+
'<a href="' + escHtml(url) + '" target="_blank" rel="noopener" download class="qa-artifact-fulllink">View full</a>' +
|
|
498
|
+
'</div>' +
|
|
499
|
+
'</div>'
|
|
500
|
+
);
|
|
445
501
|
} else {
|
|
446
502
|
// Log / text — render an asynchronously-filled <pre>; first 40 lines
|
|
447
503
|
// fetched lazily so we don't N+1-flood the network on every poll.
|
|
448
504
|
const blockId = 'qa-log-' + runId + '-' + artifactPath.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
449
|
-
|
|
505
|
+
secondary.push(
|
|
450
506
|
'<div class="qa-artifact qa-artifact-log">' +
|
|
451
507
|
'<div class="qa-artifact-caption">' +
|
|
452
508
|
escHtml(artifactPath) + ' ' +
|
|
@@ -457,28 +513,91 @@ function _qaRenderArtifactPreviews(runId, artifacts) {
|
|
|
457
513
|
);
|
|
458
514
|
}
|
|
459
515
|
}
|
|
460
|
-
|
|
516
|
+
const primaryHtml = primary.join('');
|
|
517
|
+
if (!secondary.length) return primaryHtml;
|
|
518
|
+
if (!_qaIsTerminalStatus(status)) return primaryHtml + secondary.join('');
|
|
519
|
+
// Open-state is tracked outside the DOM because loadQaRuns() replaces the
|
|
520
|
+
// whole list every 5s. Resolved defensively so the renderer stays usable in
|
|
521
|
+
// isolation (same pattern as qaFocusCommandCenter's _ccOpen lookup).
|
|
522
|
+
const openGroups = (typeof _qaOpenArtifactGroups !== 'undefined') ? _qaOpenArtifactGroups : null;
|
|
523
|
+
const isOpen = !!(openGroups && openGroups.has(runId));
|
|
524
|
+
return primaryHtml +
|
|
525
|
+
'<details class="qa-artifact-secondary" data-run-id="' + escHtml(runId) + '"' + (isOpen ? ' open' : '') + '>' +
|
|
526
|
+
'<summary class="qa-artifact-secondary-summary">Show all artifacts (' + secondary.length + ')</summary>' +
|
|
527
|
+
'<div class="qa-artifact-secondary-body">' + secondary.join('') + '</div>' +
|
|
528
|
+
'</details>';
|
|
461
529
|
}
|
|
462
530
|
|
|
463
531
|
// Lazily fetch log previews after the runs DOM is in place. Runs every time
|
|
464
532
|
// loadQaRuns finishes; cheap because the `data-qa-log-url` lookup short-
|
|
465
533
|
// circuits on already-loaded blocks.
|
|
534
|
+
//
|
|
535
|
+
// W-ms9p62od00kt5b58 — two rules now bound the fetching:
|
|
536
|
+
// 1. Previews inside a COLLAPSED <details> are not fetched. A completed run
|
|
537
|
+
// hides its logs/json behind one disclosure, and filling them eagerly
|
|
538
|
+
// would fire N background requests for content nobody is looking at.
|
|
539
|
+
// Each group is instead wired once to fill on its first open (`toggle`).
|
|
540
|
+
// 2. Preview text is memoized by URL, so re-opening a group — or the 5s
|
|
541
|
+
// poll re-rendering an already-open one — reuses the fetched text
|
|
542
|
+
// instead of refetching it.
|
|
466
543
|
function _qaFillLogPreviews() {
|
|
467
|
-
const
|
|
468
|
-
blocks.forEach(function (el) {
|
|
544
|
+
const fill = function (el) {
|
|
469
545
|
const url = el.getAttribute('data-qa-log-url');
|
|
470
546
|
if (!url || el.__qaLoaded) return;
|
|
471
547
|
el.__qaLoaded = true;
|
|
548
|
+
if (_qaLogPreviewCache.has(url)) {
|
|
549
|
+
el.textContent = _qaLogPreviewCache.get(url);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
472
552
|
fetch(url)
|
|
473
553
|
.then(function (r) { return r.ok ? r.text() : ''; })
|
|
474
554
|
.then(function (txt) {
|
|
475
555
|
const head = (txt || '').split(/\r?\n/).slice(0, 40).join('\n');
|
|
556
|
+
_qaCacheLogPreview(url, head || '(empty)');
|
|
476
557
|
el.textContent = head || '(empty)';
|
|
477
558
|
})
|
|
478
559
|
.catch(function () { el.textContent = '(failed to load)'; });
|
|
560
|
+
};
|
|
561
|
+
const groups = document.querySelectorAll('#qa-runs-content details.qa-artifact-secondary');
|
|
562
|
+
groups.forEach(function (d) {
|
|
563
|
+
if (d.__qaToggleWired) return;
|
|
564
|
+
d.__qaToggleWired = true;
|
|
565
|
+
d.addEventListener('toggle', function () {
|
|
566
|
+
const runId = d.getAttribute ? d.getAttribute('data-run-id') : '';
|
|
567
|
+
if (runId) {
|
|
568
|
+
if (d.open) _qaOpenArtifactGroups.add(runId);
|
|
569
|
+
else _qaOpenArtifactGroups.delete(runId);
|
|
570
|
+
}
|
|
571
|
+
if (!d.open) return;
|
|
572
|
+
d.querySelectorAll('.qa-artifact-log-preview[data-qa-log-url]').forEach(fill);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
const blocks = document.querySelectorAll('#qa-runs-content .qa-artifact-log-preview[data-qa-log-url]');
|
|
576
|
+
blocks.forEach(function (el) {
|
|
577
|
+
// Collapsed group → defer to the toggle handler wired above.
|
|
578
|
+
const group = el.closest ? el.closest('details.qa-artifact-secondary') : null;
|
|
579
|
+
if (group && !group.open) return;
|
|
580
|
+
fill(el);
|
|
479
581
|
});
|
|
480
582
|
}
|
|
481
583
|
|
|
584
|
+
// W-msb9kgs402813a97-d — a session card's run chip scrolls the matching Recent
|
|
585
|
+
// Runs row into view instead of navigating. The default fragment jump is
|
|
586
|
+
// suppressed by the chip's onclick because the URL hash is owned by the
|
|
587
|
+
// modal-stack router (state.js#getModalStackFromUrl); the href stays as the
|
|
588
|
+
// no-JS fallback and as the accessible link target.
|
|
589
|
+
function qaScrollToRun(runId) {
|
|
590
|
+
if (!runId) return;
|
|
591
|
+
const sel = (window.CSS && CSS.escape) ? CSS.escape(runId) : runId.replace(/"/g, '\\"');
|
|
592
|
+
const row = document.querySelector('.qa-run-row[data-run-id="' + sel + '"]');
|
|
593
|
+
if (!row) return;
|
|
594
|
+
try { row.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
|
|
595
|
+
catch { row.scrollIntoView(); }
|
|
596
|
+
// Brief highlight so the eye lands on the right row after the scroll.
|
|
597
|
+
row.classList.add('qa-run-row--flash');
|
|
598
|
+
setTimeout(function () { row.classList.remove('qa-run-row--flash'); }, 1600);
|
|
599
|
+
}
|
|
600
|
+
|
|
482
601
|
function qaOpenRunAgent(workItemId, agentId) {
|
|
483
602
|
// Prefer the agent live-detail panel if available; fall back to the
|
|
484
603
|
// work-item detail; fall back to a hard navigation as a last resort.
|
|
@@ -670,7 +789,7 @@ async function loadQaSessions() {
|
|
|
670
789
|
} else {
|
|
671
790
|
let html = '';
|
|
672
791
|
for (const s of active) html += _qaRenderSessionCard(s);
|
|
673
|
-
// eslint-disable-next-line no-unsanitized/method -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: session id, state, target sub-fields, mode, runner, error/summary/failureClass)
|
|
792
|
+
// eslint-disable-next-line no-unsanitized/method -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: session id, state, target sub-fields, mode, runner, error/summary/failureClass, linked run id + run status)
|
|
674
793
|
const frag = document.createRange().createContextualFragment(html);
|
|
675
794
|
activeRoot.replaceChildren(frag);
|
|
676
795
|
}
|
|
@@ -684,7 +803,7 @@ async function loadQaSessions() {
|
|
|
684
803
|
} else {
|
|
685
804
|
let html = '';
|
|
686
805
|
for (const s of terminal) html += _qaRenderSessionCard(s);
|
|
687
|
-
// eslint-disable-next-line no-unsanitized/method -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: session id, state, target sub-fields, mode, runner, error/summary/failureClass)
|
|
806
|
+
// eslint-disable-next-line no-unsanitized/method -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: session id, state, target sub-fields, mode, runner, error/summary/failureClass, linked run id + run status + artifact file names via _qaRenderArtifactPreviews)
|
|
688
807
|
const frag = document.createRange().createContextualFragment(html);
|
|
689
808
|
recentRoot.replaceChildren(frag);
|
|
690
809
|
}
|
|
@@ -728,6 +847,15 @@ function _qaRenderSessionCard(s) {
|
|
|
728
847
|
const summary = s && s.summary || '';
|
|
729
848
|
const createdAt = s && s.createdAt ? String(s.createdAt).slice(0, 19).replace('T', ' ') : '';
|
|
730
849
|
const isTerminal = QA_SESSION_TERMINAL_STATES.has(state);
|
|
850
|
+
// W-msb9kgs402813a97-d — `linkedRun` is the bounded read-side projection the
|
|
851
|
+
// sessions endpoint attaches (engine/qa/sessions.js#listSessions with
|
|
852
|
+
// includeLinkedRun): { id, status, artifacts:[{type,path,label}] }, primary
|
|
853
|
+
// (image/video) artifacts only. It is absent when the run was deleted or the
|
|
854
|
+
// runs store was unreadable, so every read below is defensive.
|
|
855
|
+
const qaRunId = (s && s.qaRunId) || '';
|
|
856
|
+
const linkedRun = (s && s.linkedRun) || null;
|
|
857
|
+
const linkedRunStatus = (linkedRun && linkedRun.status) || '';
|
|
858
|
+
const linkedArtifacts = (linkedRun && Array.isArray(linkedRun.artifacts)) ? linkedRun.artifacts : [];
|
|
731
859
|
|
|
732
860
|
const phases = _qaRenderPhaseChips(state);
|
|
733
861
|
|
|
@@ -752,6 +880,20 @@ function _qaRenderSessionCard(s) {
|
|
|
752
880
|
meta += '<span class="qa-session-meta-item">target: <code>' + escHtml(targetSummary) + '</code></span>';
|
|
753
881
|
meta += '<span class="qa-session-meta-item">mode: <code>' + escHtml(mode) + '</code></span>';
|
|
754
882
|
meta += '<span class="qa-session-meta-item">runner: <code>' + escHtml(runner) + '</code></span>';
|
|
883
|
+
// W-msb9kgs402813a97-d — the linked qa-run. Without it a session that DID
|
|
884
|
+
// capture screenshots reads as if it produced nothing, because the run (and
|
|
885
|
+
// its artifacts) only ever appeared in the separate Recent Runs list. The
|
|
886
|
+
// anchor is the run row's id (see _qaRenderRunRow); the click is intercepted
|
|
887
|
+
// so the URL hash stays owned by the modal-stack router in state.js.
|
|
888
|
+
if (qaRunId) {
|
|
889
|
+
meta += '<span class="qa-session-meta-item">run: ' +
|
|
890
|
+
'<a class="qa-session-run-link" href="#qa-run-' + escHtml(qaRunId) + '"' +
|
|
891
|
+
' data-run-id="' + escHtml(qaRunId) + '"' +
|
|
892
|
+
' onclick="event.preventDefault();qaScrollToRun(this.dataset.runId);">' +
|
|
893
|
+
'<code>' + escHtml(qaRunId) + '</code></a>' +
|
|
894
|
+
(linkedRunStatus ? ' <span class="qa-session-run-status">' + escHtml(linkedRunStatus) + '</span>' : '') +
|
|
895
|
+
'</span>';
|
|
896
|
+
}
|
|
755
897
|
if (createdAt) meta += '<span class="qa-session-meta-item qa-session-meta-ts">' + escHtml(createdAt) + '</span>';
|
|
756
898
|
|
|
757
899
|
let body = '';
|
|
@@ -775,6 +917,23 @@ function _qaRenderSessionCard(s) {
|
|
|
775
917
|
} else if (summary) {
|
|
776
918
|
body += '<div class="qa-session-summary">' + escHtml(summary) + '</div>';
|
|
777
919
|
}
|
|
920
|
+
// W-msb9kgs402813a97-d — evidence for a finished session. Only terminal
|
|
921
|
+
// sessions get this block: a still-executing run has not finished capturing,
|
|
922
|
+
// so "none captured" would be a lie. The screenshots/video render through the
|
|
923
|
+
// SAME helper the Recent Runs strip uses, so artifact URLs keep going through
|
|
924
|
+
// /api/qa/artifacts/<runId>/<path> and no filesystem path is ever exposed.
|
|
925
|
+
// The empty case is stated out loud — silence is what made a terminal session
|
|
926
|
+
// that captured nothing indistinguishable from one that captured plenty.
|
|
927
|
+
if (isTerminal && qaRunId) {
|
|
928
|
+
const previews = (linkedArtifacts.length && typeof _qaRenderArtifactPreviews === 'function')
|
|
929
|
+
? _qaRenderArtifactPreviews(linkedRun.id || qaRunId, linkedArtifacts, linkedRunStatus)
|
|
930
|
+
: '';
|
|
931
|
+
if (previews) {
|
|
932
|
+
body += '<div class="qa-session-artifacts">' + previews + '</div>';
|
|
933
|
+
} else if (!linkedArtifacts.length) {
|
|
934
|
+
body += '<div class="qa-session-artifacts-empty">No screenshots or video captured for this run.</div>';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
778
937
|
|
|
779
938
|
const stateClass = 'qa-session-state-' + escHtml(state);
|
|
780
939
|
return (
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -138,7 +138,10 @@ const RENDER_VERSIONS = {
|
|
|
138
138
|
// Bumped 2→3 for client-ticked relative last-action timestamps.
|
|
139
139
|
// Bumped 3→4 to show the runtime-reported model when no model is pinned.
|
|
140
140
|
agents: 4,
|
|
141
|
-
|
|
141
|
+
// Bumped 3→4: `missing` no longer counts as active, so the group summary
|
|
142
|
+
// reports only genuinely in-progress items and surfaces "to do" separately
|
|
143
|
+
// (W-msa2tagf01cse21c).
|
|
144
|
+
prdProgress: 4,
|
|
142
145
|
prdPrs: 1,
|
|
143
146
|
inbox: 2,
|
|
144
147
|
// Bumped 4→5 for the clickable checkout-mode pill + picker (W-mr1b67zi0006b788).
|
|
@@ -146,11 +149,23 @@ const RENDER_VERSIONS = {
|
|
|
146
149
|
// Bumped 6→7 for the liveValidation.autoDispatch toggle + pill "· auto-validate" suffix (W-mr2q361a00097e5c).
|
|
147
150
|
projects: 9,
|
|
148
151
|
notes: 1,
|
|
149
|
-
|
|
152
|
+
// Bumped 3→4: an approved all-missing PRD no longer derives as
|
|
153
|
+
// dispatched/"In Progress" in the header badge (W-msa2tagf01cse21c).
|
|
154
|
+
prd: 4,
|
|
150
155
|
// Bumped 4→5 for shared author/lifecycle/project/review/build filters.
|
|
151
|
-
|
|
156
|
+
// Bumped 5→6 for draft/ready pill + Merge column removal (W-ms8z6l8z).
|
|
157
|
+
// Bumped 6→7 for the dedicated Project column (W-ms90po5j).
|
|
158
|
+
// Bumped 8→9 for independent Auto Fix and Auto Review row controls.
|
|
159
|
+
// Bumped 9→10: Status cell badges moved into a .pr-status-badges flex
|
|
160
|
+
// container so a wrapped conflict badge no longer overlaps the lifecycle
|
|
161
|
+
// badge (W-msakop2o).
|
|
162
|
+
// Bumped 10→11: Agent column replaced by Author (real PR author identity,
|
|
163
|
+
// separate from the Minions agent) + Author/Minions-agent split in PR detail
|
|
164
|
+
// (W-mscibegy005e8b74).
|
|
165
|
+
prs: 11,
|
|
152
166
|
archivedPrds: 1,
|
|
153
|
-
|
|
167
|
+
// Bumped 4→5 for the lifecycle-owned worktree count + foreign suffix (W-ms9q3dql00vxd4a8).
|
|
168
|
+
engine: 5,
|
|
154
169
|
version: 1,
|
|
155
170
|
adoThrottle: 1,
|
|
156
171
|
ghThrottle: 1,
|
|
@@ -175,7 +190,19 @@ const RENDER_VERSIONS = {
|
|
|
175
190
|
// a client reload past both.
|
|
176
191
|
// Bumped 17→18: WI detail modal shows the effective model (persisted dispatch
|
|
177
192
|
// evidence, else the current-config projection) — W-ms0ytfdd000d5a26.
|
|
178
|
-
|
|
193
|
+
// Bumped 18→19 (main via #1092): merge of dependency-conflict needs-attention
|
|
194
|
+
// renderer change with main's row-render changes — both touch the row render.
|
|
195
|
+
// Bumped 19→20 (#1092): Work Items filter bar (project/type/status).
|
|
196
|
+
// Bumped 20→21 (#1100): Work Items table gains the richer filter + sort controls
|
|
197
|
+
// and an Updated column (supersedes the #1092 filter bar); row/table markup
|
|
198
|
+
// changed for byte-identical input — W-ms7kutzv003q17ad.
|
|
199
|
+
// Bumped 21→22: default sort is now lifecycle-grouped (active → pending →
|
|
200
|
+
// failed → terminal, last-updated desc inside each group), so row ORDER
|
|
201
|
+
// changes for byte-identical input — W-ms9bmp8k003if9cf.
|
|
202
|
+
// Bumped 22→23: 'paused' joins the lifecycle order between failed and the
|
|
203
|
+
// terminal tail (it was unranked, so it sorted behind every done row in both
|
|
204
|
+
// directions) — row ORDER changes again for byte-identical input.
|
|
205
|
+
workItems: 23,
|
|
179
206
|
skills: 1,
|
|
180
207
|
commands: 1,
|
|
181
208
|
mcpServers: 1,
|
|
@@ -190,6 +217,16 @@ const RENDER_VERSIONS = {
|
|
|
190
217
|
pipelines: 2,
|
|
191
218
|
pinned: 1,
|
|
192
219
|
kbPayload: 2,
|
|
220
|
+
// The QA runs list renders on its own 5s poll (loadQaRuns → replaceChildren)
|
|
221
|
+
// rather than through the tick-driven _changed() cache, so — like `settings`
|
|
222
|
+
// above — this entry is the section's cache-bust knob rather than a live
|
|
223
|
+
// cache key today. Registered by W-ms9p62od00kt5b58, which changed
|
|
224
|
+
// _qaRenderArtifactPreviews' output for byte-identical input (terminal runs
|
|
225
|
+
// now collapse non-screenshot artifacts behind a <details>).
|
|
226
|
+
// Bumped 1→2 by W-msb9kgs402813a97-d: the session card now renders the linked
|
|
227
|
+
// qa-run chip and (for terminal sessions) its primary artifacts, so the card
|
|
228
|
+
// output changed for byte-identical session input.
|
|
229
|
+
qa: 2,
|
|
193
230
|
// settings is a modal-driven section, not a tick-driven render — but the
|
|
194
231
|
// cache-bust entry exists so future tick-driven code (or feature-flag-aware
|
|
195
232
|
// renderers) can call `_changed('settings', …)` and bump on visual revamps.
|
|
@@ -200,7 +237,9 @@ const RENDER_VERSIONS = {
|
|
|
200
237
|
// Bumped to 6 for the fleet ACP pool opt-in default and warning copy.
|
|
201
238
|
// Bumped to 7 on origin/main.
|
|
202
239
|
// Bumped to 8 for the Auto-fix pane tile-grid revamp (W-mryyzhug000454b7).
|
|
203
|
-
|
|
240
|
+
// Bumped to 11 for the Cloud dispatch control in Agents Runtime & Models
|
|
241
|
+
// (registry-populated provider selector, disabled when none are registered).
|
|
242
|
+
settings: 11,
|
|
204
243
|
};
|
|
205
244
|
const _sectionCache = {};
|
|
206
245
|
const _lastValueByKey = {};
|
|
@@ -772,6 +811,14 @@ function _processStatusUpdate(data, opts) {
|
|
|
772
811
|
var qs = document.getElementById('engine-quick-stats');
|
|
773
812
|
if (qs && data.engine) {
|
|
774
813
|
var wt = data.engine.worktreeCount != null ? data.engine.worktreeCount : '-';
|
|
814
|
+
// W-ms9q3dql00vxd4a8 — `worktreeCount` counts only lifecycle-owned
|
|
815
|
+
// worktrees. Operator-created scratch checkouts arrive separately as
|
|
816
|
+
// `worktreeForeignCount` and are shown as a dimmed suffix so they stay
|
|
817
|
+
// visible without being presented as active Minions worktrees.
|
|
818
|
+
var foreignWt = Number(data.engine.worktreeForeignCount) || 0;
|
|
819
|
+
var foreignChip = foreignWt > 0
|
|
820
|
+
? ' <span style="opacity:.65" title="Operator-created or unclassifiable directories under the worktree root. Minions never auto-removes these.">+' + foreignWt + ' foreign</span>'
|
|
821
|
+
: '';
|
|
775
822
|
var pid = data.engine.pid || '-';
|
|
776
823
|
// W-mpnc4u8c001d9d6c — replace the dead "Tick: -" chip (control.json
|
|
777
824
|
// never carried a `tick` field) with a live "Next tick in Xs" countdown
|
|
@@ -788,7 +835,7 @@ function _processStatusUpdate(data, opts) {
|
|
|
788
835
|
// eslint-disable-next-line no-unsanitized/property -- reason: composed from internal engine metrics (pid, lastTickAt/tickInterval, worktreeCount) and a literal id; no user data flows in
|
|
789
836
|
qs.innerHTML = '<span>PID: <b>' + pid + '</b></span>' +
|
|
790
837
|
'<span>Next tick in: <b id="engine-next-tick">' + _formatNextTickText() + '</b></span>' +
|
|
791
|
-
'<span>Worktrees: <b>' + wt + '</b
|
|
838
|
+
'<span>Worktrees: <b>' + wt + '</b>' + foreignChip + '</span>';
|
|
792
839
|
_startNextTickTicker();
|
|
793
840
|
}
|
|
794
841
|
});
|
|
@@ -1010,7 +1057,7 @@ function _processStatusUpdate(data, opts) {
|
|
|
1010
1057
|
});
|
|
1011
1058
|
});
|
|
1012
1059
|
// Watches come from GET /api/watches — the canonical SQL-backed read
|
|
1013
|
-
// surface (engine/watches
|
|
1060
|
+
// surface (engine/watches/store.js). The retired engine/watches.json mirror
|
|
1014
1061
|
// is never written, so /state/ has nothing to serve.
|
|
1015
1062
|
_safeRender('watches', function() {
|
|
1016
1063
|
// Seq guard — the watches table is mutated every 3 ticks by
|
|
@@ -1109,7 +1156,7 @@ function _processStatusUpdate(data, opts) {
|
|
|
1109
1156
|
// (dashboard.js _plansCache / invalidatePlansCache) — back-to-back polls
|
|
1110
1157
|
// hit the cache; external file edits surface within 5s.
|
|
1111
1158
|
// - /api/knowledge: 30s in-memory TTL + invalidation on POST /api/knowledge
|
|
1112
|
-
// and after every kb-sweep (engine/queries.js _kbCache / kb-sweep.js).
|
|
1159
|
+
// and after every kb-sweep (engine/core/queries.js _kbCache / kb-sweep.js).
|
|
1113
1160
|
// Previously throttled to every 3rd cycle (~12s) — see W-mphfb6ss000a3b9e
|
|
1114
1161
|
// for the cadence audit + Playwright coverage.
|
|
1115
1162
|
_safeRender('refreshKnowledgeBase', function() { refreshKnowledgeBase(); });
|
|
@@ -1175,6 +1222,18 @@ let _embedPollSuspended = false;
|
|
|
1175
1222
|
const d = ev.data;
|
|
1176
1223
|
if (!d || d.source !== 'minions-slim-embed' || d.type !== 'visibility') return;
|
|
1177
1224
|
_embedPollSuspended = !d.visible;
|
|
1225
|
+
// Also pause/resume the /engine screen's heavy diagnostics pollers
|
|
1226
|
+
// (memory-panel.js: 10s /api/diagnostics/memory + 60s memory/history)
|
|
1227
|
+
// through the SAME message — a hidden engine embed must not keep those
|
|
1228
|
+
// running forever (W-ms4odb8h009a5d16). Guarded: the panel is only present
|
|
1229
|
+
// on the /engine page, and its hooks are no-ops before mount.
|
|
1230
|
+
try {
|
|
1231
|
+
const mp = window.MinionsMemoryPanel;
|
|
1232
|
+
if (mp) {
|
|
1233
|
+
if (d.visible) { if (typeof mp.resumeMemoryPanel === 'function') mp.resumeMemoryPanel(); }
|
|
1234
|
+
else if (typeof mp.suspendMemoryPanel === 'function') { mp.suspendMemoryPanel(); }
|
|
1235
|
+
}
|
|
1236
|
+
} catch (e) { /* memory panel not on this embedded page — ignore */ }
|
|
1178
1237
|
if (d.visible) { try { refresh(); } catch (e) { /* refresh not yet defined / transient */ } }
|
|
1179
1238
|
});
|
|
1180
1239
|
} catch (e) { /* defensive — never block boot on this bridge */ }
|
|
@@ -1198,8 +1257,19 @@ let _embedPollSuspended = false;
|
|
|
1198
1257
|
// (~12s) because connection-refused rejects fast rather than waiting out the
|
|
1199
1258
|
// 15s timeout. The old "> N seconds since last success" OR side was REMOVED:
|
|
1200
1259
|
// aborts during a healthy cold rebuild pushed elapsed-time past the budget and
|
|
1201
|
-
// surfaced the banner while the dashboard was alive-but-busy.
|
|
1202
|
-
//
|
|
1260
|
+
// surfaced the banner while the dashboard was alive-but-busy.
|
|
1261
|
+
//
|
|
1262
|
+
// Liveness confirmation (W-ms95wh14): consecutive /api/status failures alone are
|
|
1263
|
+
// still NOT proof the process is gone. During a long-running Command Center turn
|
|
1264
|
+
// the status-cache rebuild is starved on the event loop, so every /api/status
|
|
1265
|
+
// poll aborts even though the dashboard is alive and the CC stream is flowing —
|
|
1266
|
+
// reading that as "unreachable" is the false "Dashboard connection lost" bug.
|
|
1267
|
+
// So on the 3rd consecutive status failure we CONFIRM with a lightweight
|
|
1268
|
+
// /api/health probe (no status-cache rebuild, no per-agent file IO): if health
|
|
1269
|
+
// answers we treat the dashboard as alive-but-slow (reset counter, no banner, no
|
|
1270
|
+
// backoff); only a health-probe failure trips the banner. While the banner is up
|
|
1271
|
+
// an independent /api/health recovery probe (fixed 3s cadence, NOT gated by the
|
|
1272
|
+
// status-poll backoff) clears it the instant the process answers again.
|
|
1203
1273
|
// On trip we show a sticky red banner with two recovery actions:
|
|
1204
1274
|
// 1. "Restart Minions" → ccRestartMinions (works when dashboard is
|
|
1205
1275
|
// alive-but-stale; falls through to copy-to-clipboard when the POST
|
|
@@ -1216,6 +1286,48 @@ const _UNREACHABLE_FAIL_THRESHOLD = 3;
|
|
|
1216
1286
|
// Once the banner is up we throttle polls (exponential, capped at 30s) to
|
|
1217
1287
|
// avoid hammering a struggling dashboard / network. Reset on recovery.
|
|
1218
1288
|
let _nextPollAllowedAt = 0;
|
|
1289
|
+
// Recovery probe — a short, fixed-cadence /api/health poll that runs ONLY while
|
|
1290
|
+
// the unreachable banner is up. It is deliberately independent of the status
|
|
1291
|
+
// backoff above: once the dashboard process answers /api/health again we clear
|
|
1292
|
+
// the banner immediately and resume steady status polling, instead of waiting
|
|
1293
|
+
// out the (up to 30s) exponential status-poll backoff. /api/health does no
|
|
1294
|
+
// status-cache rebuild, so it recovers the instant the process is reachable.
|
|
1295
|
+
let _recoveryProbeTimer = null;
|
|
1296
|
+
const _RECOVERY_PROBE_INTERVAL_MS = 3000;
|
|
1297
|
+
const _LIVENESS_PROBE_TIMEOUT_MS = 4000;
|
|
1298
|
+
|
|
1299
|
+
// Lightweight liveness probe. A /api/status timeout/abort is NOT proof the
|
|
1300
|
+
// dashboard process is down — the status-cache rebuild can be starved on the
|
|
1301
|
+
// event loop by a long-running Command Center turn. /api/health rebuilds no
|
|
1302
|
+
// status cache and does no per-agent file IO, so a fast 200 proves the process
|
|
1303
|
+
// is alive and servicing requests. Resolves true only on an ok response.
|
|
1304
|
+
function _probeDashboardAlive() {
|
|
1305
|
+
return safeFetch('/api/health', { timeout: _LIVENESS_PROBE_TIMEOUT_MS })
|
|
1306
|
+
.then(function(r) { return !!(r && r.ok); })
|
|
1307
|
+
.catch(function() { return false; });
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function _startRecoveryProbe() {
|
|
1311
|
+
if (_recoveryProbeTimer) return;
|
|
1312
|
+
_recoveryProbeTimer = setInterval(function() {
|
|
1313
|
+
_probeDashboardAlive().then(function(alive) {
|
|
1314
|
+
if (!alive) return;
|
|
1315
|
+
_stopRecoveryProbe();
|
|
1316
|
+
// Process is reachable again. Snap back to steady status polling (no
|
|
1317
|
+
// backoff) and kick an immediate refresh so the frozen UI repopulates
|
|
1318
|
+
// and the badge/banner restore on the next successful poll — don't wait
|
|
1319
|
+
// for the status cache to warm to CLEAR the "unreachable" state.
|
|
1320
|
+
_consecutiveStatusFails = 0;
|
|
1321
|
+
_nextPollAllowedAt = 0;
|
|
1322
|
+
_markDashboardReachable();
|
|
1323
|
+
try { refresh(); } catch (e) { /* transient — next tick retries */ }
|
|
1324
|
+
});
|
|
1325
|
+
}, _RECOVERY_PROBE_INTERVAL_MS);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function _stopRecoveryProbe() {
|
|
1329
|
+
if (_recoveryProbeTimer) { clearInterval(_recoveryProbeTimer); _recoveryProbeTimer = null; }
|
|
1330
|
+
}
|
|
1219
1331
|
|
|
1220
1332
|
function _formatAge(ms) {
|
|
1221
1333
|
if (ms < 1000) return 'just now';
|
|
@@ -1282,6 +1394,7 @@ function _markDashboardReachable() {
|
|
|
1282
1394
|
_unreachableSince = 0;
|
|
1283
1395
|
delete window._dashboardUnreachable;
|
|
1284
1396
|
if (_unreachableAgeTimer) { clearInterval(_unreachableAgeTimer); _unreachableAgeTimer = null; }
|
|
1397
|
+
_stopRecoveryProbe();
|
|
1285
1398
|
// Hand #engine-alert back to renderEngineAlert. We can't leave our content
|
|
1286
1399
|
// sitting in there because the next renderEngineAlert call only runs when
|
|
1287
1400
|
// `_changed('engine', data.engine)` returns true — if engine state hasn't
|
|
@@ -1300,6 +1413,7 @@ window._resetDashboardUnreachableForTest = function() {
|
|
|
1300
1413
|
_unreachableSince = 0;
|
|
1301
1414
|
_nextPollAllowedAt = 0;
|
|
1302
1415
|
if (_unreachableAgeTimer) { clearInterval(_unreachableAgeTimer); _unreachableAgeTimer = null; }
|
|
1416
|
+
_stopRecoveryProbe();
|
|
1303
1417
|
delete window._dashboardUnreachable;
|
|
1304
1418
|
};
|
|
1305
1419
|
|
|
@@ -1474,8 +1588,30 @@ async function refresh(opts) {
|
|
|
1474
1588
|
_consecutiveStatusFails++;
|
|
1475
1589
|
// Sustained failure only — see the detector header for why elapsed time
|
|
1476
1590
|
// alone must not trip (it flashed the banner on healthy-but-slow rebuilds).
|
|
1477
|
-
|
|
1591
|
+
// But a sustained run of /api/status timeouts is STILL not proof the
|
|
1592
|
+
// process is gone: the ~2 KB status snapshot rebuild can be starved on the
|
|
1593
|
+
// event loop by a long-running Command Center turn, so every poll aborts at
|
|
1594
|
+
// safeFetch's 15s budget while the dashboard is perfectly alive. Before
|
|
1595
|
+
// tripping the banner, confirm true unreachability with a lightweight
|
|
1596
|
+
// /api/health probe that rebuilds no status cache. If health answers, the
|
|
1597
|
+
// dashboard is alive-but-slow — reset to steady polling (no banner, no
|
|
1598
|
+
// backoff) so we recover the instant the cache warms; only a health-probe
|
|
1599
|
+
// failure is treated as "actually unreachable".
|
|
1600
|
+
let _statusFailConfirmedDead = false;
|
|
1601
|
+
if (_consecutiveStatusFails >= _UNREACHABLE_FAIL_THRESHOLD && !_unreachableSince) {
|
|
1602
|
+
const _aliveViaHealth = await _probeDashboardAlive();
|
|
1603
|
+
if (_aliveViaHealth) {
|
|
1604
|
+
_consecutiveStatusFails = 0;
|
|
1605
|
+
_nextPollAllowedAt = 0;
|
|
1606
|
+
} else {
|
|
1607
|
+
_statusFailConfirmedDead = true;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (_consecutiveStatusFails >= _UNREACHABLE_FAIL_THRESHOLD && _statusFailConfirmedDead) {
|
|
1478
1611
|
_markDashboardUnreachable(e);
|
|
1612
|
+
// Start the independent /api/health recovery probe so we clear the banner
|
|
1613
|
+
// promptly on recovery instead of waiting out the status-poll backoff.
|
|
1614
|
+
_startRecoveryProbe();
|
|
1479
1615
|
}
|
|
1480
1616
|
// Backoff: once we've tripped the banner, throttle subsequent polls
|
|
1481
1617
|
// (4s → 8s → 16s → 30s cap). Reset to 0 in the success path below so
|
|
@@ -349,8 +349,8 @@ function renderDispatch(dispatch, opts) {
|
|
|
349
349
|
const compStart = _completedPage * COMPLETED_PER_PAGE;
|
|
350
350
|
const pageCompleted = completed.slice(compStart, compStart + COMPLETED_PER_PAGE);
|
|
351
351
|
|
|
352
|
-
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields:
|
|
353
|
-
completedEl.innerHTML = '<table class="pr-table"><thead><tr><th>
|
|
352
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: type, agent, task, result, reason)
|
|
353
|
+
completedEl.innerHTML = '<table class="pr-table"><thead><tr><th>Type</th><th>Agent</th><th>Task</th><th>Result</th><th>Completed</th></tr></thead><tbody>' +
|
|
354
354
|
pageCompleted.map(d => {
|
|
355
355
|
const isError = d.result === 'error';
|
|
356
356
|
const agentId = (d.agent || '').toLowerCase();
|
|
@@ -358,10 +358,9 @@ function renderDispatch(dispatch, opts) {
|
|
|
358
358
|
? ' <button class="error-details-btn" data-agent="' + escHtml(agentId) + '" data-reason="' + escHtml(d.reason || 'No reason recorded') + '" data-task="' + escHtml((d.task || '').slice(0, 100)) + '" onclick="showErrorDetails(this.dataset.agent, this.dataset.reason, this.dataset.task)" title="View error details">details</button>'
|
|
359
359
|
: '';
|
|
360
360
|
return '<tr>' +
|
|
361
|
-
'<td style="font-family:Consolas;font-size:var(--text-sm)" title="' + escHtml(d.id || '') + '">' + escHtml(d.id || '') + '</td>' +
|
|
362
361
|
'<td><span class="dispatch-type ' + (d.type || '') + '">' + escHtml(d.type || '') + '</span></td>' +
|
|
363
362
|
'<td>' + escHtml(d.agentName || d.agent || '') + '</td>' +
|
|
364
|
-
'<td style="max-width:
|
|
363
|
+
'<td style="width:100%;max-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml((d.task || '').slice(0, 60)) + '</td>' +
|
|
365
364
|
'<td style="color:' + (d.result === 'success' ? 'var(--green)' : 'var(--red)') + '">' + escHtml(d.result || '') + errorBtn + '</td>' +
|
|
366
365
|
'<td class="pr-date">' + shortTime(d.completed_at) + '</td>' +
|
|
367
366
|
'</tr>';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const INBOX_PER_PAGE = 15;
|
|
4
4
|
let _inboxPage = 0;
|
|
5
5
|
|
|
6
|
-
// Quick relative-time formatter — mirrors engine/queries.js timeSince so the
|
|
6
|
+
// Quick relative-time formatter — mirrors engine/core/queries.js timeSince so the
|
|
7
7
|
// inbox-item "age" chip reads the same as it did via /api/status.
|
|
8
8
|
function _timeSinceMs(mtimeMs) {
|
|
9
9
|
if (!mtimeMs) return '';
|
|
@@ -78,7 +78,7 @@ async function fetchNotesFromDisk() {
|
|
|
78
78
|
// Promote a human-readable title to the inbox card heading. Prefers an
|
|
79
79
|
// explicit `title` field from the API, then a `title:` YAML frontmatter key,
|
|
80
80
|
// then the first markdown heading, then the first non-empty body line. Mirrors
|
|
81
|
-
// the engine's own title derivation (engine/consolidation.js — first `# ` ATX
|
|
81
|
+
// the engine's own title derivation (engine/memory/consolidation.js — first `# ` ATX
|
|
82
82
|
// heading). Returns '' when nothing readable is extractable so the caller can
|
|
83
83
|
// fall back to the raw filename.
|
|
84
84
|
function _extractInboxTitle(item) {
|