@yemi33/minions 0.1.2447 → 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 +66 -7
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +186 -45
- 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 +52 -6
- 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
|
//
|
|
@@ -28,9 +31,29 @@
|
|
|
28
31
|
// navigates.
|
|
29
32
|
|
|
30
33
|
let _qaRunsPollInterval = null;
|
|
31
|
-
let _qaTargetsCache = [];
|
|
32
34
|
let _qaRunbooksCache = [];
|
|
33
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
|
+
|
|
34
57
|
function _stopQaRunsPoll() {
|
|
35
58
|
if (_qaRunsPollInterval) {
|
|
36
59
|
clearInterval(_qaRunsPollInterval);
|
|
@@ -84,8 +107,6 @@ async function loadQaTargets() {
|
|
|
84
107
|
'<p class="empty" style="color:var(--red)">Failed to load targets: ' + escHtml(err.message || String(err)) + '</p>'
|
|
85
108
|
);
|
|
86
109
|
root.replaceChildren(frag);
|
|
87
|
-
_qaTargetsCache = [];
|
|
88
|
-
_qaPopulateTargetDropdown();
|
|
89
110
|
return;
|
|
90
111
|
}
|
|
91
112
|
|
|
@@ -130,9 +151,6 @@ async function loadQaTargets() {
|
|
|
130
151
|
});
|
|
131
152
|
}
|
|
132
153
|
|
|
133
|
-
_qaTargetsCache = targets;
|
|
134
|
-
_qaPopulateTargetDropdown();
|
|
135
|
-
|
|
136
154
|
if (!targets.length) {
|
|
137
155
|
const frag = document.createRange().createContextualFragment(
|
|
138
156
|
'<p class="empty">No live targets. Spawn a service via <code>meta.managed_spawn</code> or <code>meta.keep_processes</code> to populate this list. Full inventory on <a href="/engine">/engine</a>.</p>'
|
|
@@ -163,25 +181,13 @@ async function loadQaTargets() {
|
|
|
163
181
|
root.replaceChildren(frag);
|
|
164
182
|
}
|
|
165
183
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
placeholder.value = '';
|
|
174
|
-
placeholder.textContent = '— select a target —';
|
|
175
|
-
sel.appendChild(placeholder);
|
|
176
|
-
for (const t of _qaTargetsCache) {
|
|
177
|
-
const opt = document.createElement('option');
|
|
178
|
-
opt.value = t.name;
|
|
179
|
-
const projLabel = t.project ? ' (' + t.project + ')' : '';
|
|
180
|
-
opt.textContent = t.name + projLabel + ' [' + t.source + ']';
|
|
181
|
-
sel.appendChild(opt);
|
|
182
|
-
}
|
|
183
|
-
if (current) sel.value = current;
|
|
184
|
-
}
|
|
184
|
+
// W-mpxp7a3e000g0a49 removed the inline "Start QA Session" / runbook form from
|
|
185
|
+
// dashboard/pages/qa.html, taking the `qa-runbook-target` <select> with it
|
|
186
|
+
// (asserted absent by test/unit/qa-tab-ui.test.js). The _qaPopulateTargetDropdown
|
|
187
|
+
// helper that filled it was left behind as an unreachable no-op and has been
|
|
188
|
+
// deleted (W-ms5dlone012i457a-b), along with the `_qaTargetsCache` module state
|
|
189
|
+
// that existed only to feed it — loadQaTargets renders its rows directly.
|
|
190
|
+
// Runbook targets are chosen via Command Center.
|
|
185
191
|
|
|
186
192
|
// ── Section 2: Runbooks ────────────────────────────────────────────────────
|
|
187
193
|
async function loadQaRunbooks() {
|
|
@@ -324,7 +330,7 @@ async function loadQaRuns() {
|
|
|
324
330
|
return;
|
|
325
331
|
}
|
|
326
332
|
const rows = items.map(_qaRenderRunRow).join('');
|
|
327
|
-
// 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
|
|
328
334
|
const frag = document.createRange().createContextualFragment(rows);
|
|
329
335
|
root.replaceChildren(frag);
|
|
330
336
|
}
|
|
@@ -364,8 +370,8 @@ function _qaRenderRunRow(run) {
|
|
|
364
370
|
const actionsHtml = (liveLink || deleteBtn)
|
|
365
371
|
? '<span class="qa-run-actions">' + liveLink + deleteBtn + '</span>'
|
|
366
372
|
: '';
|
|
367
|
-
const artifactsHtml = _qaRenderArtifactPreviews(id, run.artifacts || []);
|
|
368
|
-
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) + '">' +
|
|
369
375
|
'<div class="qa-run-head">' +
|
|
370
376
|
'<span class="qa-run-status ' + escHtml(statusClass) + '">' + escHtml(status) + '</span>' +
|
|
371
377
|
'<span class="qa-run-name">' + escHtml(runbook) + '</span>' +
|
|
@@ -378,7 +384,7 @@ function _qaRenderRunRow(run) {
|
|
|
378
384
|
}
|
|
379
385
|
|
|
380
386
|
// W-mpxp90xs000i5732 — terminal status set kept in sync with
|
|
381
|
-
// engine/qa
|
|
387
|
+
// engine/qa/runs.js#TERMINAL_STATUSES. The Delete button only renders for
|
|
382
388
|
// runs in one of these states; the engine's deleteQaRun returns 409 for
|
|
383
389
|
// anything else (defense in depth).
|
|
384
390
|
function _qaIsTerminalStatus(status) {
|
|
@@ -426,9 +432,30 @@ async function qaDeleteRun(id) {
|
|
|
426
432
|
}
|
|
427
433
|
}
|
|
428
434
|
|
|
429
|
-
|
|
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) {
|
|
430
448
|
if (!runId || !Array.isArray(artifacts) || !artifacts.length) return '';
|
|
431
|
-
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 = [];
|
|
432
459
|
for (const a of artifacts) {
|
|
433
460
|
// Canonical records use { path, type }; retain the retired { file, kind }
|
|
434
461
|
// shape for older persisted runs.
|
|
@@ -446,25 +473,36 @@ function _qaRenderArtifactPreviews(runId, artifacts) {
|
|
|
446
473
|
const typeHint = String(typeRaw).toLowerCase();
|
|
447
474
|
const ext = (file.match(/\.([a-z0-9]+)$/i) || ['', ''])[1].toLowerCase();
|
|
448
475
|
const url = '/api/qa/artifacts/' + encodeURIComponent(runId) + '/' + encodeURIComponent(artifactPath);
|
|
449
|
-
if (typeHint === 'screenshot' ||
|
|
450
|
-
|
|
476
|
+
if (typeHint === 'screenshot' || IMAGE_EXTS.includes(ext)) {
|
|
477
|
+
primary.push(
|
|
451
478
|
'<div class="qa-artifact qa-artifact-image">' +
|
|
452
479
|
'<img src="' + escHtml(url) + '" alt="' + escHtml(artifactPath) + '" loading="lazy">' +
|
|
453
480
|
'<div class="qa-artifact-caption">' + escHtml(artifactPath) + '</div>' +
|
|
454
481
|
'</div>'
|
|
455
482
|
);
|
|
456
|
-
} else if (typeHint === 'video' ||
|
|
457
|
-
|
|
483
|
+
} else if (typeHint === 'video' || VIDEO_EXTS.includes(ext)) {
|
|
484
|
+
primary.push(
|
|
458
485
|
'<div class="qa-artifact qa-artifact-video">' +
|
|
459
486
|
'<video controls src="' + escHtml(url) + '"></video>' +
|
|
460
487
|
'<div class="qa-artifact-caption">' + escHtml(artifactPath) + '</div>' +
|
|
461
488
|
'</div>'
|
|
462
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
|
+
);
|
|
463
501
|
} else {
|
|
464
502
|
// Log / text — render an asynchronously-filled <pre>; first 40 lines
|
|
465
503
|
// fetched lazily so we don't N+1-flood the network on every poll.
|
|
466
504
|
const blockId = 'qa-log-' + runId + '-' + artifactPath.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
467
|
-
|
|
505
|
+
secondary.push(
|
|
468
506
|
'<div class="qa-artifact qa-artifact-log">' +
|
|
469
507
|
'<div class="qa-artifact-caption">' +
|
|
470
508
|
escHtml(artifactPath) + ' ' +
|
|
@@ -475,26 +513,89 @@ function _qaRenderArtifactPreviews(runId, artifacts) {
|
|
|
475
513
|
);
|
|
476
514
|
}
|
|
477
515
|
}
|
|
478
|
-
|
|
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>';
|
|
479
529
|
}
|
|
480
530
|
|
|
481
531
|
// Lazily fetch log previews after the runs DOM is in place. Runs every time
|
|
482
532
|
// loadQaRuns finishes; cheap because the `data-qa-log-url` lookup short-
|
|
483
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.
|
|
484
543
|
function _qaFillLogPreviews() {
|
|
485
|
-
const
|
|
486
|
-
blocks.forEach(function (el) {
|
|
544
|
+
const fill = function (el) {
|
|
487
545
|
const url = el.getAttribute('data-qa-log-url');
|
|
488
546
|
if (!url || el.__qaLoaded) return;
|
|
489
547
|
el.__qaLoaded = true;
|
|
548
|
+
if (_qaLogPreviewCache.has(url)) {
|
|
549
|
+
el.textContent = _qaLogPreviewCache.get(url);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
490
552
|
fetch(url)
|
|
491
553
|
.then(function (r) { return r.ok ? r.text() : ''; })
|
|
492
554
|
.then(function (txt) {
|
|
493
555
|
const head = (txt || '').split(/\r?\n/).slice(0, 40).join('\n');
|
|
556
|
+
_qaCacheLogPreview(url, head || '(empty)');
|
|
494
557
|
el.textContent = head || '(empty)';
|
|
495
558
|
})
|
|
496
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
|
+
});
|
|
497
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);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
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);
|
|
498
599
|
}
|
|
499
600
|
|
|
500
601
|
function qaOpenRunAgent(workItemId, agentId) {
|
|
@@ -688,7 +789,7 @@ async function loadQaSessions() {
|
|
|
688
789
|
} else {
|
|
689
790
|
let html = '';
|
|
690
791
|
for (const s of active) html += _qaRenderSessionCard(s);
|
|
691
|
-
// 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)
|
|
692
793
|
const frag = document.createRange().createContextualFragment(html);
|
|
693
794
|
activeRoot.replaceChildren(frag);
|
|
694
795
|
}
|
|
@@ -702,7 +803,7 @@ async function loadQaSessions() {
|
|
|
702
803
|
} else {
|
|
703
804
|
let html = '';
|
|
704
805
|
for (const s of terminal) html += _qaRenderSessionCard(s);
|
|
705
|
-
// 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)
|
|
706
807
|
const frag = document.createRange().createContextualFragment(html);
|
|
707
808
|
recentRoot.replaceChildren(frag);
|
|
708
809
|
}
|
|
@@ -746,6 +847,15 @@ function _qaRenderSessionCard(s) {
|
|
|
746
847
|
const summary = s && s.summary || '';
|
|
747
848
|
const createdAt = s && s.createdAt ? String(s.createdAt).slice(0, 19).replace('T', ' ') : '';
|
|
748
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 : [];
|
|
749
859
|
|
|
750
860
|
const phases = _qaRenderPhaseChips(state);
|
|
751
861
|
|
|
@@ -770,6 +880,20 @@ function _qaRenderSessionCard(s) {
|
|
|
770
880
|
meta += '<span class="qa-session-meta-item">target: <code>' + escHtml(targetSummary) + '</code></span>';
|
|
771
881
|
meta += '<span class="qa-session-meta-item">mode: <code>' + escHtml(mode) + '</code></span>';
|
|
772
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
|
+
}
|
|
773
897
|
if (createdAt) meta += '<span class="qa-session-meta-item qa-session-meta-ts">' + escHtml(createdAt) + '</span>';
|
|
774
898
|
|
|
775
899
|
let body = '';
|
|
@@ -793,6 +917,23 @@ function _qaRenderSessionCard(s) {
|
|
|
793
917
|
} else if (summary) {
|
|
794
918
|
body += '<div class="qa-session-summary">' + escHtml(summary) + '</div>';
|
|
795
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
|
+
}
|
|
796
937
|
|
|
797
938
|
const stateClass = 'qa-session-state-' + escHtml(state);
|
|
798
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
|