@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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// dashboard/shared/cc-limits.js
|
|
2
|
+
//
|
|
3
|
+
// Shared client-side mirror of the server's Command Center API length limits
|
|
4
|
+
// (engine/api/cc-api-validation.js CC_API_LIMITS). Used by BOTH the classic
|
|
5
|
+
// dashboard (dashboard/js/command-center.js) and Slim
|
|
6
|
+
// (dashboard/slim/js/command-send.js) so an oversized message is rejected on
|
|
7
|
+
// the client BEFORE it is cleared from the composer, queued, rendered, or
|
|
8
|
+
// persisted — and so every transcript entry is bounded to the server limit,
|
|
9
|
+
// meaning a previously-persisted oversized message can no longer poison future
|
|
10
|
+
// sends via the carried transcript (W-mscjjqdh).
|
|
11
|
+
//
|
|
12
|
+
// The REAL server values are injected into the page as window.MINIONS_CC_LIMITS
|
|
13
|
+
// by both serve paths (dashboard.js buildDashboardHtml + serveSlimUx, sourced
|
|
14
|
+
// from ccApiValidation.CC_API_LIMITS). The FALLBACK_LIMITS below only apply when
|
|
15
|
+
// that global is missing (e.g. a stale cached page) and MUST match the server
|
|
16
|
+
// constant — the whole point of the injection is that client and server limits
|
|
17
|
+
// do not silently drift, so never bump a number here without changing the
|
|
18
|
+
// server. Every helper reads the limits at CALL time so a late injection is
|
|
19
|
+
// still honored.
|
|
20
|
+
|
|
21
|
+
(function () {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
var FALLBACK_LIMITS = {
|
|
25
|
+
messageMaxChars: 100000,
|
|
26
|
+
transcriptTextMaxChars: 65536,
|
|
27
|
+
transcriptMaxEntries: 20,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function _positive(value, fallback) {
|
|
31
|
+
return (typeof value === 'number' && isFinite(value) && value > 0) ? value : fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function _limits() {
|
|
35
|
+
var injected = (typeof window !== 'undefined' && window.MINIONS_CC_LIMITS) || {};
|
|
36
|
+
return {
|
|
37
|
+
messageMaxChars: _positive(injected.messageMaxChars, FALLBACK_LIMITS.messageMaxChars),
|
|
38
|
+
transcriptTextMaxChars: _positive(injected.transcriptTextMaxChars, FALLBACK_LIMITS.transcriptTextMaxChars),
|
|
39
|
+
transcriptMaxEntries: _positive(injected.transcriptMaxEntries, FALLBACK_LIMITS.transcriptMaxEntries),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Returns { ok, length, limit }. A message longer than messageMaxChars is
|
|
44
|
+
// rejected by the server as `body.message has an invalid length`; guard it on
|
|
45
|
+
// the client so the composer is never cleared and the turn is never persisted.
|
|
46
|
+
function checkMessage(text) {
|
|
47
|
+
var s = typeof text === 'string' ? text : '';
|
|
48
|
+
var limit = _limits().messageMaxChars;
|
|
49
|
+
return { ok: s.length <= limit, length: s.length, limit: limit };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Human-readable, actionable validation message for an oversized send. The
|
|
53
|
+
// rejected text stays in the composer, so tell the user that explicitly.
|
|
54
|
+
function messageTooLongText(length, limit) {
|
|
55
|
+
var lim = _positive(limit, _limits().messageMaxChars);
|
|
56
|
+
var len = (typeof length === 'number' && isFinite(length)) ? length : 0;
|
|
57
|
+
return 'Message is too long (' + len.toLocaleString() + ' characters; limit is '
|
|
58
|
+
+ lim.toLocaleString() + '). Shorten it and send again — your text is still in the box.';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Bound a single transcript entry's text to the server's transcriptTextMaxChars
|
|
62
|
+
// so a previously-persisted oversized message cannot poison future sends
|
|
63
|
+
// (`body.transcript[N].text has an invalid length`). Truncation is safe: the
|
|
64
|
+
// transcript is best-effort carry-over context, not the authoritative turn.
|
|
65
|
+
function boundTranscriptText(text) {
|
|
66
|
+
var s = typeof text === 'string' ? text : '';
|
|
67
|
+
var limit = _limits().transcriptTextMaxChars;
|
|
68
|
+
return s.length > limit ? s.slice(0, limit) : s;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
window.MinionsCcLimits = {
|
|
72
|
+
get messageMaxChars() { return _limits().messageMaxChars; },
|
|
73
|
+
get transcriptTextMaxChars() { return _limits().transcriptTextMaxChars; },
|
|
74
|
+
get transcriptMaxEntries() { return _limits().transcriptMaxEntries; },
|
|
75
|
+
checkMessage: checkMessage,
|
|
76
|
+
messageTooLongText: messageTooLongText,
|
|
77
|
+
boundTranscriptText: boundTranscriptText,
|
|
78
|
+
};
|
|
79
|
+
})();
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Shared PR filter semantics. Slim embeds the classic /prs screen, and both
|
|
2
2
|
// bundles load this helper so lifecycle and legacy-field normalization stay
|
|
3
3
|
// identical across surfaces.
|
|
4
|
+
//
|
|
5
|
+
// Only the PR-specific normalizers live here; the dimension/match/options
|
|
6
|
+
// machinery comes from shared/record-filters.js, which the classic Work Items
|
|
7
|
+
// filter bar reuses too.
|
|
4
8
|
var PR_FILTER_DIMENSIONS = ['author', 'lifecycle', 'project', 'review', 'build'];
|
|
5
9
|
var PR_LIFECYCLE_FILTER_LABELS = {
|
|
6
10
|
open: 'Open / active',
|
|
@@ -14,7 +18,7 @@ var PR_FILTER_OPTION_ORDER = {
|
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
function _prFilterText(value) {
|
|
17
|
-
return
|
|
21
|
+
return window.MinionsRecordFilters.text(value);
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
function _prFilterName(value) {
|
|
@@ -28,15 +32,17 @@ function _prFilterName(value) {
|
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function _prFilterKey(value) {
|
|
31
|
-
return
|
|
35
|
+
return window.MinionsRecordFilters.key(value);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function normalizePrAuthor(pr) {
|
|
35
39
|
if (!pr || typeof pr !== 'object') return '';
|
|
40
|
+
// Real author only. Deliberately NOT falling back to pr.agent: the Minions
|
|
41
|
+
// agent is a separate identity (W-mscibegy005e8b74) and a legacy record with
|
|
42
|
+
// no author must group/render as Unknown rather than borrow the agent name.
|
|
36
43
|
return _prFilterName(pr.author)
|
|
37
44
|
|| _prFilterName(pr.createdBy)
|
|
38
|
-
|| _prFilterName(pr.created_by)
|
|
39
|
-
|| _prFilterName(pr.agent);
|
|
45
|
+
|| _prFilterName(pr.created_by);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
function normalizePrLifecycleStatus(pr) {
|
|
@@ -108,52 +114,29 @@ function _prFilterEntry(pr, dimension) {
|
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
function _formatPrFilterLabel(value) {
|
|
111
|
-
|
|
112
|
-
return text ? text.charAt(0).toUpperCase() + text.slice(1) : '';
|
|
117
|
+
return window.MinionsRecordFilters.label(value);
|
|
113
118
|
}
|
|
114
119
|
|
|
120
|
+
var _prFilterEngine = window.MinionsRecordFilters.create({
|
|
121
|
+
dimensions: PR_FILTER_DIMENSIONS,
|
|
122
|
+
entry: _prFilterEntry,
|
|
123
|
+
order: PR_FILTER_OPTION_ORDER,
|
|
124
|
+
});
|
|
125
|
+
|
|
115
126
|
function getPrFilterValue(pr, dimension) {
|
|
116
|
-
|
|
117
|
-
return entry ? entry.value : '';
|
|
127
|
+
return _prFilterEngine.value(pr, dimension);
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
function matchesPullRequestFilters(pr, filters) {
|
|
121
|
-
|
|
122
|
-
for (var i = 0; i < PR_FILTER_DIMENSIONS.length; i++) {
|
|
123
|
-
var dimension = PR_FILTER_DIMENSIONS[i];
|
|
124
|
-
var selected = _prFilterKey(filters[dimension]);
|
|
125
|
-
if (selected && getPrFilterValue(pr, dimension) !== selected) return false;
|
|
126
|
-
}
|
|
127
|
-
return true;
|
|
131
|
+
return _prFilterEngine.matches(pr, filters);
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
function filterPullRequests(prs, filters) {
|
|
131
|
-
|
|
132
|
-
return prs.filter(function(pr) { return matchesPullRequestFilters(pr, filters); });
|
|
135
|
+
return _prFilterEngine.filter(prs, filters);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
function getPrFilterOptions(prs) {
|
|
136
|
-
|
|
137
|
-
var records = Array.isArray(prs) ? prs : [];
|
|
138
|
-
PR_FILTER_DIMENSIONS.forEach(function(dimension) {
|
|
139
|
-
var seen = new Map();
|
|
140
|
-
records.forEach(function(pr) {
|
|
141
|
-
var entry = _prFilterEntry(pr, dimension);
|
|
142
|
-
if (entry && !seen.has(entry.value)) seen.set(entry.value, entry);
|
|
143
|
-
});
|
|
144
|
-
var order = PR_FILTER_OPTION_ORDER[dimension] || [];
|
|
145
|
-
result[dimension] = Array.from(seen.values()).sort(function(a, b) {
|
|
146
|
-
var ai = order.indexOf(a.value);
|
|
147
|
-
var bi = order.indexOf(b.value);
|
|
148
|
-
if (ai !== -1 || bi !== -1) {
|
|
149
|
-
if (ai === -1) return 1;
|
|
150
|
-
if (bi === -1) return -1;
|
|
151
|
-
if (ai !== bi) return ai - bi;
|
|
152
|
-
}
|
|
153
|
-
return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' });
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
return result;
|
|
139
|
+
return _prFilterEngine.options(prs);
|
|
157
140
|
}
|
|
158
141
|
|
|
159
142
|
window.MinionsPrFilters = {
|
|
@@ -161,7 +161,7 @@ function summarizeProjectGit(p) {
|
|
|
161
161
|
var branchDiffersFromMain = !!(mainBranch && branch !== mainBranch);
|
|
162
162
|
var comparator = mainBranch || remoteDefault;
|
|
163
163
|
// Comparator for ahead/behind is origin/<mainBranch || remoteDefaultBranch>
|
|
164
|
-
// (engine/queries.js _probeProjectGitStatus), never the branch's own upstream.
|
|
164
|
+
// (engine/core/queries.js _probeProjectGitStatus), never the branch's own upstream.
|
|
165
165
|
var comparatorRef = mainBranch || remoteDefault;
|
|
166
166
|
var branchDiffersFromComparator = !!(comparator && branch !== comparatorRef);
|
|
167
167
|
var showAheadBehind = ahead !== null && behind !== null && (ahead > 0 || behind > 0);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Generic record-filter semantics shared by every filterable classic table.
|
|
2
|
+
//
|
|
3
|
+
// Both filterable classic tables (Pull Requests, Work Items) need exactly the
|
|
4
|
+
// same machinery: normalize a record to a comparable key per dimension, AND the
|
|
5
|
+
// selected dimensions together, derive the <select> options from the records
|
|
6
|
+
// actually present, and drive the filter-bar DOM. Only the per-dimension
|
|
7
|
+
// normalizers differ. This module owns the common half so the two surfaces
|
|
8
|
+
// cannot drift ("reuse, don't fork"); callers supply just an `entry` function.
|
|
9
|
+
//
|
|
10
|
+
// Option VALUES are always derived from the records present, so no surface
|
|
11
|
+
// maintains a taxonomy that can fall out of sync with the engine. `order` only
|
|
12
|
+
// supplies the display ORDER; unknown values sort alphabetically after it.
|
|
13
|
+
|
|
14
|
+
function recordFilterText(value) {
|
|
15
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function recordFilterKey(value) {
|
|
19
|
+
return recordFilterText(value).toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatRecordFilterLabel(value) {
|
|
23
|
+
var text = recordFilterText(value).replace(/[-_]+/g, ' ');
|
|
24
|
+
return text ? text.charAt(0).toUpperCase() + text.slice(1) : '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// `entry(record, dimension)` returns `{ value, label }` or a falsy value when
|
|
28
|
+
// the record carries nothing for that dimension. A record with no value is
|
|
29
|
+
// never given a selectable option (it would read as an "empty" choice) but is
|
|
30
|
+
// still retained under "All".
|
|
31
|
+
//
|
|
32
|
+
// `order` maps a dimension to an array of values defining display order, or to
|
|
33
|
+
// a function returning one — a function lets a caller defer to a vocabulary
|
|
34
|
+
// that is bootstrapped onto `window` after this module is evaluated.
|
|
35
|
+
function createRecordFilters(spec) {
|
|
36
|
+
var dimensions = (spec && spec.dimensions) || [];
|
|
37
|
+
var entry = (spec && spec.entry) || function () { return null; };
|
|
38
|
+
var order = (spec && spec.order) || {};
|
|
39
|
+
|
|
40
|
+
function resolveOrder(dimension) {
|
|
41
|
+
var configured = order[dimension];
|
|
42
|
+
if (typeof configured === 'function') configured = configured();
|
|
43
|
+
return Array.isArray(configured) ? configured : [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function value(record, dimension) {
|
|
47
|
+
var resolved = entry(record, dimension);
|
|
48
|
+
return resolved ? resolved.value : '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function matches(record, filters) {
|
|
52
|
+
filters = filters || {};
|
|
53
|
+
for (var i = 0; i < dimensions.length; i++) {
|
|
54
|
+
var dimension = dimensions[i];
|
|
55
|
+
var selected = recordFilterKey(filters[dimension]);
|
|
56
|
+
if (selected && value(record, dimension) !== selected) return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function filter(records, filters) {
|
|
62
|
+
if (!Array.isArray(records)) return [];
|
|
63
|
+
return records.filter(function (record) { return matches(record, filters); });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function options(records) {
|
|
67
|
+
var result = {};
|
|
68
|
+
var list = Array.isArray(records) ? records : [];
|
|
69
|
+
dimensions.forEach(function (dimension) {
|
|
70
|
+
var seen = new Map();
|
|
71
|
+
list.forEach(function (record) {
|
|
72
|
+
var resolved = entry(record, dimension);
|
|
73
|
+
if (resolved && resolved.value && !seen.has(resolved.value)) {
|
|
74
|
+
seen.set(resolved.value, { value: resolved.value, label: resolved.label || resolved.value });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
var sequence = resolveOrder(dimension);
|
|
78
|
+
result[dimension] = Array.from(seen.values()).sort(function (a, b) {
|
|
79
|
+
var ai = sequence.indexOf(a.value);
|
|
80
|
+
var bi = sequence.indexOf(b.value);
|
|
81
|
+
if (ai !== -1 || bi !== -1) {
|
|
82
|
+
if (ai === -1) return 1;
|
|
83
|
+
if (bi === -1) return -1;
|
|
84
|
+
if (ai !== bi) return ai - bi;
|
|
85
|
+
}
|
|
86
|
+
return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' });
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { dimensions: dimensions, value: value, matches: matches, filter: filter, options: options };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Drives a filter bar built from one <select> per dimension plus a reset button.
|
|
96
|
+
//
|
|
97
|
+
// `syncOptions` is called on EVERY render, so it short-circuits on an unchanged
|
|
98
|
+
// option signature: rebuilding the <option> nodes would collapse an open
|
|
99
|
+
// dropdown and drop keyboard focus every time the 4s poll lands.
|
|
100
|
+
function createFilterBarController(spec) {
|
|
101
|
+
var controlIds = (spec && spec.controlIds) || {};
|
|
102
|
+
var allLabels = (spec && spec.allLabels) || {};
|
|
103
|
+
var getOptions = spec && spec.getOptions;
|
|
104
|
+
var dimensions = Object.keys(controlIds);
|
|
105
|
+
var signature = '';
|
|
106
|
+
|
|
107
|
+
function selectFor(dimension) {
|
|
108
|
+
return document.getElementById(controlIds[dimension]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function syncOptions(records) {
|
|
112
|
+
var options = getOptions(records);
|
|
113
|
+
var controls = dimensions.map(function (dimension) {
|
|
114
|
+
return { dimension: dimension, select: selectFor(dimension) };
|
|
115
|
+
}).filter(function (control) { return control.select; });
|
|
116
|
+
if (!controls.length) return;
|
|
117
|
+
var next = JSON.stringify(options);
|
|
118
|
+
if (next === signature) return;
|
|
119
|
+
signature = next;
|
|
120
|
+
controls.forEach(function (control) {
|
|
121
|
+
var select = control.select;
|
|
122
|
+
var selected = select.value;
|
|
123
|
+
var all = document.createElement('option');
|
|
124
|
+
all.value = '';
|
|
125
|
+
all.textContent = allLabels[control.dimension];
|
|
126
|
+
var nodes = [all].concat((options[control.dimension] || []).map(function (option) {
|
|
127
|
+
var node = document.createElement('option');
|
|
128
|
+
node.value = option.value;
|
|
129
|
+
node.textContent = option.label;
|
|
130
|
+
return node;
|
|
131
|
+
}));
|
|
132
|
+
select.replaceChildren.apply(select, nodes);
|
|
133
|
+
// A selection whose option disappeared (e.g. the last record of a project
|
|
134
|
+
// was archived) falls back to "All" rather than stranding the table on a
|
|
135
|
+
// permanently empty result.
|
|
136
|
+
if (nodes.some(function (node) { return node.value === selected; })) select.value = selected;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function read() {
|
|
141
|
+
var filters = {};
|
|
142
|
+
dimensions.forEach(function (dimension) {
|
|
143
|
+
var select = selectFor(dimension);
|
|
144
|
+
filters[dimension] = select ? select.value : '';
|
|
145
|
+
});
|
|
146
|
+
return filters;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function hasActive(filters) {
|
|
150
|
+
return dimensions.some(function (dimension) { return Boolean(filters[dimension]); });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function clear() {
|
|
154
|
+
dimensions.forEach(function (dimension) {
|
|
155
|
+
var select = selectFor(dimension);
|
|
156
|
+
if (select) select.value = '';
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { syncOptions: syncOptions, read: read, hasActive: hasActive, clear: clear };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
window.MinionsRecordFilters = {
|
|
164
|
+
text: recordFilterText,
|
|
165
|
+
key: recordFilterKey,
|
|
166
|
+
label: formatRecordFilterLabel,
|
|
167
|
+
create: createRecordFilters,
|
|
168
|
+
createBar: createFilterBarController,
|
|
169
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Watches are SQL-backed: migration 007 moved `engine/watches.json` into the
|
|
4
4
|
// `watches` table, and the SQL-only cutover retired the JSON mirror entirely
|
|
5
5
|
// (see engine/db/index.js#_retiredJsonTargets). The engine mutates watch
|
|
6
|
-
// status/_history through engine/watches
|
|
6
|
+
// status/_history through engine/watches/store.js — nothing writes the old
|
|
7
7
|
// file, so serving it over the static `/state/` passthrough 404s forever and
|
|
8
8
|
// any UI reading it renders an empty list. `GET /api/watches` is the canonical
|
|
9
9
|
// read surface.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Shared internal-distribution welcome popup for the classic and Slim dashboards.
|
|
2
2
|
//
|
|
3
3
|
// W-ms1jgr95000ke749 — the first dashboard launch on an INTERNAL Minions
|
|
4
|
-
// distribution (`@yemishin_microsoft/minions` et al, see engine/distribution.js)
|
|
4
|
+
// distribution (`@yemishin_microsoft/minions` et al, see engine/operations/distribution.js)
|
|
5
5
|
// shows a one-time modal. Public `@yemi33/minions` installs never see it: the
|
|
6
6
|
// server decides via installed-package metadata and simply answers
|
|
7
7
|
// `{ show: false }`, so this module renders nothing there.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Shared Work-Item filter + sort semantics. Modeled on shared/pr-filters.js so
|
|
2
|
+
// the Work Items table (dashboard/js/render-work-items.js) reuses the same
|
|
3
|
+
// option-derivation / filter / signature pattern the PR table uses, rather than
|
|
4
|
+
// forking a divergent copy. Loaded as a dashboard/shared bundle before
|
|
5
|
+
// render-work-items.js (dashboard-build.js DASHBOARD_SHARED_JS). (W-ms7kutzv003q17ad)
|
|
6
|
+
var WI_FILTER_DIMENSIONS = ['status', 'type', 'project', 'agent', 'priority'];
|
|
7
|
+
|
|
8
|
+
// Rank orders drive both the filter-option ordering AND the status/priority
|
|
9
|
+
// sorts (so sorting is by semantic rank, not raw string compare).
|
|
10
|
+
var WI_STATUS_ORDER = ['dispatched', 'pending', 'queued', 'failed', 'paused', 'done', 'decomposed', 'cancelled'];
|
|
11
|
+
var WI_PRIORITY_ORDER = ['critical', 'high', 'medium', 'low'];
|
|
12
|
+
|
|
13
|
+
var WI_FILTER_OPTION_ORDER = {
|
|
14
|
+
status: WI_STATUS_ORDER,
|
|
15
|
+
priority: WI_PRIORITY_ORDER,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function _wiFilterText(value) {
|
|
19
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function _wiFilterKey(value) {
|
|
23
|
+
return _wiFilterText(value).toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _wiFormatLabel(value) {
|
|
27
|
+
var text = _wiFilterText(value).replace(/[-_]+/g, ' ');
|
|
28
|
+
return text ? text.charAt(0).toUpperCase() + text.slice(1) : '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeWorkItemStatus(item) {
|
|
32
|
+
if (!item || typeof item !== 'object') return '';
|
|
33
|
+
return _wiFilterKey(item.status);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeWorkItemType(item) {
|
|
37
|
+
if (!item || typeof item !== 'object') return '';
|
|
38
|
+
return _wiFilterKey(item.type);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeWorkItemProject(item) {
|
|
42
|
+
if (!item || typeof item !== 'object') return '';
|
|
43
|
+
return _wiFilterText(item.project) || _wiFilterText(item._source);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeWorkItemAgent(item) {
|
|
47
|
+
if (!item || typeof item !== 'object') return '';
|
|
48
|
+
// Prefer a completed agent (multiple → first), then the assigned/dispatched
|
|
49
|
+
// agent. Mirrors the Agent column in wiRow().
|
|
50
|
+
if (Array.isArray(item.completedAgents) && item.completedAgents.length) {
|
|
51
|
+
var first = _wiFilterText(item.completedAgents[0]);
|
|
52
|
+
if (first) return first;
|
|
53
|
+
}
|
|
54
|
+
return _wiFilterText(item.dispatched_to) || _wiFilterText(item.agent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeWorkItemPriority(item) {
|
|
58
|
+
if (!item || typeof item !== 'object') return '';
|
|
59
|
+
return _wiFilterKey(item.priority);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _wiFilterEntry(item, dimension) {
|
|
63
|
+
var label = '';
|
|
64
|
+
var value = '';
|
|
65
|
+
if (dimension === 'status') {
|
|
66
|
+
value = normalizeWorkItemStatus(item);
|
|
67
|
+
label = _wiFormatLabel(value);
|
|
68
|
+
} else if (dimension === 'type') {
|
|
69
|
+
value = normalizeWorkItemType(item);
|
|
70
|
+
label = _wiFormatLabel(value);
|
|
71
|
+
} else if (dimension === 'project') {
|
|
72
|
+
label = normalizeWorkItemProject(item);
|
|
73
|
+
value = _wiFilterKey(label);
|
|
74
|
+
} else if (dimension === 'agent') {
|
|
75
|
+
label = normalizeWorkItemAgent(item);
|
|
76
|
+
value = _wiFilterKey(label);
|
|
77
|
+
} else if (dimension === 'priority') {
|
|
78
|
+
value = normalizeWorkItemPriority(item);
|
|
79
|
+
label = _wiFormatLabel(value);
|
|
80
|
+
}
|
|
81
|
+
return value ? { value: value, label: label || value } : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getWorkItemFilterValue(item, dimension) {
|
|
85
|
+
var entry = _wiFilterEntry(item, dimension);
|
|
86
|
+
return entry ? entry.value : '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function matchesWorkItemFilters(item, filters) {
|
|
90
|
+
filters = filters || {};
|
|
91
|
+
for (var i = 0; i < WI_FILTER_DIMENSIONS.length; i++) {
|
|
92
|
+
var dimension = WI_FILTER_DIMENSIONS[i];
|
|
93
|
+
var selected = _wiFilterKey(filters[dimension]);
|
|
94
|
+
if (selected && getWorkItemFilterValue(item, dimension) !== selected) return false;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function filterWorkItems(items, filters) {
|
|
100
|
+
if (!Array.isArray(items)) return [];
|
|
101
|
+
return items.filter(function(item) { return matchesWorkItemFilters(item, filters); });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getWorkItemFilterOptions(items) {
|
|
105
|
+
var result = {};
|
|
106
|
+
var records = Array.isArray(items) ? items : [];
|
|
107
|
+
WI_FILTER_DIMENSIONS.forEach(function(dimension) {
|
|
108
|
+
var seen = new Map();
|
|
109
|
+
records.forEach(function(item) {
|
|
110
|
+
var entry = _wiFilterEntry(item, dimension);
|
|
111
|
+
if (entry && !seen.has(entry.value)) seen.set(entry.value, entry);
|
|
112
|
+
});
|
|
113
|
+
var order = WI_FILTER_OPTION_ORDER[dimension] || [];
|
|
114
|
+
result[dimension] = Array.from(seen.values()).sort(function(a, b) {
|
|
115
|
+
var ai = order.indexOf(a.value);
|
|
116
|
+
var bi = order.indexOf(b.value);
|
|
117
|
+
if (ai !== -1 || bi !== -1) {
|
|
118
|
+
if (ai === -1) return 1;
|
|
119
|
+
if (bi === -1) return -1;
|
|
120
|
+
if (ai !== bi) return ai - bi;
|
|
121
|
+
}
|
|
122
|
+
return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- Sorting -------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
// Lifecycle groups: a COARSER partition of WI_STATUS_ORDER that drives the
|
|
131
|
+
// default view, so the table reads active -> waiting -> needs-attention ->
|
|
132
|
+
// halted -> terminal instead of interleaving statuses by raw recency
|
|
133
|
+
// (W-ms9bmp8k003if9cf).
|
|
134
|
+
// Every member must also appear in WI_STATUS_ORDER and the groups must be
|
|
135
|
+
// disjoint (both test-enforced), so this module stays the single definition of
|
|
136
|
+
// status ordering — dashboard/js/render-work-items.js no longer carries a
|
|
137
|
+
// divergent copy. WI_STATUS_ORDER in turn must cover every canonical engine
|
|
138
|
+
// status (engine/core/shared.js WI_STATUS), which is also test-enforced: the
|
|
139
|
+
// group ordering here is direction-independent, so an unranked status would be
|
|
140
|
+
// pinned behind the entire terminal tail with no way for the user to surface
|
|
141
|
+
// it. A status outside every group ranks last, matching the "unknown sorts
|
|
142
|
+
// last" convention _wiRank() already uses.
|
|
143
|
+
var WI_LIFECYCLE_GROUPS = [
|
|
144
|
+
['dispatched'], // active — an agent is working on it
|
|
145
|
+
['pending', 'queued'], // waiting to be dispatched
|
|
146
|
+
['failed'], // needs attention — may still auto-retry
|
|
147
|
+
['paused'], // halted by an operator (POST /api/plans/pause);
|
|
148
|
+
// non-terminal and resumable, but only a human
|
|
149
|
+
// can un-block it, so it trails the groups the
|
|
150
|
+
// engine progresses on its own
|
|
151
|
+
['done', 'decomposed', 'cancelled'], // terminal
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
var WI_SORT_KEYS = ['lifecycle', 'updated', 'created', 'priority', 'status', 'type'];
|
|
155
|
+
var WI_SORT_DIRECTIONS = ['asc', 'desc'];
|
|
156
|
+
// Default view: lifecycle groups in their inherent order, last-updated DESC
|
|
157
|
+
// inside each group. Semantics of the Asc/Desc toggle under 'lifecycle': it
|
|
158
|
+
// controls ONLY the within-group recency ordering. The group ordering is
|
|
159
|
+
// inherent to the lifecycle and is never inverted, so terminal items can't
|
|
160
|
+
// float to the top of the default view. Picking 'updated' explicitly still
|
|
161
|
+
// yields pure, status-agnostic recency.
|
|
162
|
+
var WI_SORT_DEFAULT = { sortBy: 'lifecycle', dir: 'desc' };
|
|
163
|
+
|
|
164
|
+
// Sort keys whose primary comparator is coarse-grained (many items share one
|
|
165
|
+
// rank). These tie-break by last-updated DESC before falling through to the id
|
|
166
|
+
// tie-break, so a status group reads newest-first instead of id-first.
|
|
167
|
+
// 'lifecycle' is absent by design: recency is already its within-group primary.
|
|
168
|
+
var WI_RECENCY_TIEBREAK_KEYS = ['status'];
|
|
169
|
+
|
|
170
|
+
function _wiTimeMs(value) {
|
|
171
|
+
if (value == null || value === '') return null;
|
|
172
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
173
|
+
var parsed = Date.parse(value);
|
|
174
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Canonical last-updated timestamp. Prefer the real updatedAt surfaced by the
|
|
178
|
+
// store, then completedAt, dispatched_at, created (legacy fallback only — never
|
|
179
|
+
// faked from created when a real updatedAt exists). Returns ms epoch or null.
|
|
180
|
+
function workItemLastUpdatedMs(item) {
|
|
181
|
+
if (!item || typeof item !== 'object') return null;
|
|
182
|
+
return _wiTimeMs(item.updatedAt)
|
|
183
|
+
?? _wiTimeMs(item.updated_at)
|
|
184
|
+
?? _wiTimeMs(item.completedAt)
|
|
185
|
+
?? _wiTimeMs(item.completed_at)
|
|
186
|
+
?? _wiTimeMs(item.dispatched_at)
|
|
187
|
+
?? _wiTimeMs(item.created)
|
|
188
|
+
?? _wiTimeMs(item.created_at);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function _wiCreatedMs(item) {
|
|
192
|
+
if (!item || typeof item !== 'object') return null;
|
|
193
|
+
return _wiTimeMs(item.created) ?? _wiTimeMs(item.created_at);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function _wiRank(order, value) {
|
|
197
|
+
var i = order.indexOf(_wiFilterKey(value));
|
|
198
|
+
return i === -1 ? order.length : i;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Lifecycle group index for an item (0 = active ... N = unknown/last).
|
|
202
|
+
function workItemLifecycleRank(item) {
|
|
203
|
+
var status = normalizeWorkItemStatus(item);
|
|
204
|
+
for (var i = 0; i < WI_LIFECYCLE_GROUPS.length; i++) {
|
|
205
|
+
if (WI_LIFECYCLE_GROUPS[i].indexOf(status) !== -1) return i;
|
|
206
|
+
}
|
|
207
|
+
return WI_LIFECYCLE_GROUPS.length;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Grouping whose ordering is INHERENT rather than user-chosen, and therefore
|
|
211
|
+
// direction-independent. Returns 0 for sort keys that have no fixed grouping.
|
|
212
|
+
function _wiFixedGroupCompare(a, b, sortBy) {
|
|
213
|
+
if (sortBy !== 'lifecycle') return 0;
|
|
214
|
+
return workItemLifecycleRank(a) - workItemLifecycleRank(b);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Newest-first recency comparator used as a tie-break for coarse sort keys.
|
|
218
|
+
// Missing timestamps sort last (treated as oldest), mirroring _wiCompare.
|
|
219
|
+
function _wiRecencyDesc(a, b) {
|
|
220
|
+
var av = workItemLastUpdatedMs(a);
|
|
221
|
+
var bv = workItemLastUpdatedMs(b);
|
|
222
|
+
if (av == null && bv == null) return 0;
|
|
223
|
+
if (av == null) return 1;
|
|
224
|
+
if (bv == null) return -1;
|
|
225
|
+
return bv - av;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function _wiCompare(a, b, sortBy) {
|
|
229
|
+
if (sortBy === 'priority') {
|
|
230
|
+
return _wiRank(WI_PRIORITY_ORDER, a.priority) - _wiRank(WI_PRIORITY_ORDER, b.priority);
|
|
231
|
+
}
|
|
232
|
+
if (sortBy === 'status') {
|
|
233
|
+
return _wiRank(WI_STATUS_ORDER, a.status) - _wiRank(WI_STATUS_ORDER, b.status);
|
|
234
|
+
}
|
|
235
|
+
if (sortBy === 'type') {
|
|
236
|
+
return _wiFilterKey(a.type).localeCompare(_wiFilterKey(b.type), undefined, { sensitivity: 'base' });
|
|
237
|
+
}
|
|
238
|
+
var av, bv;
|
|
239
|
+
if (sortBy === 'created') {
|
|
240
|
+
av = _wiCreatedMs(a); bv = _wiCreatedMs(b);
|
|
241
|
+
} else {
|
|
242
|
+
// 'lifecycle' (within-group ordering, after _wiFixedGroupCompare has
|
|
243
|
+
// separated the groups), 'updated' (default) and any unknown key fall
|
|
244
|
+
// through to last-updated.
|
|
245
|
+
av = workItemLastUpdatedMs(a); bv = workItemLastUpdatedMs(b);
|
|
246
|
+
}
|
|
247
|
+
// Missing timestamps sort last in ascending order (i.e. treated as oldest).
|
|
248
|
+
if (av == null && bv == null) return 0;
|
|
249
|
+
if (av == null) return -1;
|
|
250
|
+
if (bv == null) return 1;
|
|
251
|
+
return av - bv;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Stable sort: applies any fixed (direction-independent) grouping first, then
|
|
255
|
+
// the primary comparator ascending, then reverses for desc, with a recency and
|
|
256
|
+
// finally an id tie-break so equal keys keep a deterministic order.
|
|
257
|
+
function sortWorkItems(items, sort) {
|
|
258
|
+
if (!Array.isArray(items)) return [];
|
|
259
|
+
var sortBy = sort && WI_SORT_KEYS.indexOf(sort.sortBy) !== -1 ? sort.sortBy : WI_SORT_DEFAULT.sortBy;
|
|
260
|
+
var dir = sort && WI_SORT_DIRECTIONS.indexOf(sort.dir) !== -1 ? sort.dir : WI_SORT_DEFAULT.dir;
|
|
261
|
+
var mult = dir === 'asc' ? 1 : -1;
|
|
262
|
+
var recencyTieBreak = WI_RECENCY_TIEBREAK_KEYS.indexOf(sortBy) !== -1;
|
|
263
|
+
var withIdx = items.map(function(item, idx) { return { item: item, idx: idx }; });
|
|
264
|
+
withIdx.sort(function(a, b) {
|
|
265
|
+
// Inherent group ordering — never multiplied by `mult`, so toggling the
|
|
266
|
+
// direction cannot float terminal work above active work.
|
|
267
|
+
var group = _wiFixedGroupCompare(a.item, b.item, sortBy);
|
|
268
|
+
if (group !== 0) return group;
|
|
269
|
+
var primary = _wiCompare(a.item, b.item, sortBy);
|
|
270
|
+
if (primary !== 0) return primary * mult;
|
|
271
|
+
// Coarse keys tie-break by recency (newest first, direction-independent —
|
|
272
|
+
// same contract as the id tie-break) so a whole status group doesn't read
|
|
273
|
+
// in id order.
|
|
274
|
+
if (recencyTieBreak) {
|
|
275
|
+
var recency = _wiRecencyDesc(a.item, b.item);
|
|
276
|
+
if (recency !== 0) return recency;
|
|
277
|
+
}
|
|
278
|
+
// Deterministic tie-break by id (ascending, direction-independent) so ties
|
|
279
|
+
// are stable regardless of the primary direction; fall back to input index.
|
|
280
|
+
var ida = _wiFilterText(a.item && a.item.id);
|
|
281
|
+
var idb = _wiFilterText(b.item && b.item.id);
|
|
282
|
+
if (ida !== idb) return ida < idb ? -1 : 1;
|
|
283
|
+
return a.idx - b.idx;
|
|
284
|
+
});
|
|
285
|
+
return withIdx.map(function(entry) { return entry.item; });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
window.MinionsWorkItemFilters = {
|
|
289
|
+
dimensions: WI_FILTER_DIMENSIONS,
|
|
290
|
+
value: getWorkItemFilterValue,
|
|
291
|
+
matches: matchesWorkItemFilters,
|
|
292
|
+
filter: filterWorkItems,
|
|
293
|
+
options: getWorkItemFilterOptions,
|
|
294
|
+
sort: sortWorkItems,
|
|
295
|
+
lastUpdatedMs: workItemLastUpdatedMs,
|
|
296
|
+
lifecycleRank: workItemLifecycleRank,
|
|
297
|
+
sortKeys: WI_SORT_KEYS,
|
|
298
|
+
sortDefault: WI_SORT_DEFAULT,
|
|
299
|
+
statusOrder: WI_STATUS_ORDER,
|
|
300
|
+
lifecycleGroups: WI_LIFECYCLE_GROUPS,
|
|
301
|
+
priorityOrder: WI_PRIORITY_ORDER,
|
|
302
|
+
};
|