@tea-agent/loop-agent 0.12.0 → 0.13.0-beta.0
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/AGENTS.md +155 -153
- package/CHANGELOG.md +338 -265
- package/README.md +345 -298
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/generate-task-dag.js +28 -28
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +52 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +130 -0
- package/dist/cli/command-definitions.js +27 -7
- package/dist/cli/program.js +8 -4
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/eval.js +235 -0
- package/dist/commands/init.js +544 -506
- package/dist/commands/knowledge.js +129 -31
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/pi-sdk-executor.js +38 -24
- package/dist/executors/shell-executor.js +34 -2
- package/dist/executors/shell-presets.js +20 -0
- package/dist/executors/shell-verification.js +7 -0
- package/dist/governance/manifest-types.js +4 -0
- package/dist/infrastructure/evaluation/candidate-store.js +435 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/task/config-types.js +28 -1
- package/dist/task/runtime.js +27 -27
- package/dist/worker/cli.js +96 -1
- package/dist/worker/delivery/package.js +3 -3
- package/dist/worker/feature/decision-loader.js +37 -6
- package/dist/worker/feature/next-action.js +10 -2
- package/dist/worker/feature/ready-plan-projection.js +81 -0
- package/dist/worker/feature/reducer.js +2 -1
- package/dist/worker/feature/review.js +19 -2
- package/dist/worker/feature/run.js +27 -2
- package/dist/worker/follow-up/approve.js +5 -2
- package/dist/worker/follow-up/factory.js +1 -1
- package/dist/worker/observability/read-model.js +246 -41
- package/dist/worker/observe/routes.js +173 -15
- package/dist/worker/observe/spec-evidence.js +281 -0
- package/dist/worker/observe/static/api.js +46 -27
- package/dist/worker/observe/static/app.js +150 -150
- package/dist/worker/observe/static/constants.js +148 -148
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-helpers.js +172 -172
- package/dist/worker/observe/static/dag-layout.d.ts +31 -31
- package/dist/worker/observe/static/dag-layout.js +83 -83
- package/dist/worker/observe/static/dag-model.js +72 -72
- package/dist/worker/observe/static/dom.js +61 -61
- package/dist/worker/observe/static/format-pool.js +67 -67
- package/dist/worker/observe/static/format.js +292 -292
- package/dist/worker/observe/static/index.html +308 -308
- package/dist/worker/observe/static/kpi.js +94 -94
- package/dist/worker/observe/static/relations.js +133 -128
- package/dist/worker/observe/static/router.js +93 -85
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/shell-chrome.js +68 -68
- package/dist/worker/observe/static/state.js +253 -253
- package/dist/worker/observe/static/styles.css +1902 -1890
- package/dist/worker/observe/static/views/batch.js +227 -226
- package/dist/worker/observe/static/views/dag-graph.js +172 -172
- package/dist/worker/observe/static/views/dag-inspector.js +607 -477
- package/dist/worker/observe/static/views/dag.js +362 -362
- package/dist/worker/observe/static/views/dashboard.js +445 -442
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -453
- package/dist/worker/observe/static/views/pool.js +350 -347
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/session-timeline.js +205 -205
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/task.js +314 -260
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/run-store.js +205 -17
- package/dist/worker/pool/types.js +17 -1
- package/dist/worker/pool/validation.js +100 -15
- package/dist/worker/report/morning-report.js +12 -2
- package/dist/worker/runner/run-ready.js +41 -26
- package/dist/worker/task-graph/ready-planner.js +136 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/convergence/controller.js +16 -8
- package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
- package/dist/workflows/dag/failure-routing.js +12 -1
- package/dist/workflows/dag/init-hybrid.js +2404 -360
- package/dist/workflows/dag/node-execution.js +9 -0
- package/dist/workflows/dag/prompt.js +9 -0
- package/dist/workflows/dag/report.js +35 -1
- package/dist/workflows/dag/runner.js +28 -2
- package/dist/workflows/dag/task-demand-routing.js +383 -0
- package/dist/workflows/dag/types.js +51 -13
- package/dist/workflows/dag/upstream-artifacts.js +1 -0
- package/dist/workflows/dag/validate.js +59 -1
- package/docs/README.md +106 -104
- package/docs/agent-dag-recovery-playbook.md +195 -184
- package/docs/agent-dag-runner.md +67 -67
- package/docs/architecture/README.md +26 -26
- package/docs/architecture/dag-execution.md +140 -140
- package/docs/architecture/evolution.md +54 -53
- package/docs/architecture/facts-and-state.md +71 -58
- package/docs/architecture/runtime-boundaries.md +191 -191
- package/docs/architecture/system-overview.md +93 -93
- package/docs/architecture/worker-and-feature.md +85 -81
- package/docs/cursor-prompt-sidecar.md +36 -36
- package/docs/decisions/README.md +18 -15
- package/docs/design/README.md +167 -77
- package/docs/development-principles.md +73 -73
- package/docs/exec-plans/README.md +6 -6
- package/docs/exec-plans/active/README.md +15 -9
- package/docs/exec-plans/completed/README.md +85 -73
- package/docs/feature-workflow.md +389 -261
- package/docs/harness-methodology-debugging.md +153 -153
- package/docs/harness-methodology-tdd.md +130 -130
- package/docs/harness-methodology-verification.md +27 -27
- package/docs/init-surface.manifest.json +289 -280
- package/docs/loop-agent-harness.md +142 -130
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +64 -54
- package/docs/reports/README.md +117 -94
- package/docs/skills/README.md +7 -7
- package/docs/skills/vetted-skill-registry.md +29 -27
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/agent-dag.base.json +190 -190
- package/docs/templates/agent-dag.final-verification.json +185 -185
- package/docs/templates/agent-dag.schema.json +411 -383
- package/docs/templates/agent-dag.supervised-implementation.json +501 -501
- package/docs/templates/backend-test-analysis.schema.json +44 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
- package/docs/templates/backend-test-dag.json +311 -276
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
- package/docs/templates/exec-plan.md +64 -64
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -33
- package/docs/templates/frontend-task-constraints.md +35 -25
- package/docs/templates/frontend-task-requirement.md +70 -61
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.json +23 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/harness.schema.json +221 -221
- package/docs/templates/hybrid-dag.json +188 -188
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +8 -8
- package/docs/templates/product-line/README.md +9 -9
- package/docs/templates/product-line/acceptance.yaml +14 -14
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/task-graph.yaml +15 -15
- package/docs/templates/product-line/task.yaml +64 -64
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/progress-log.md +17 -17
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/docs/verification-matrix.md +70 -66
- package/examples/decision-gate-agent-dag.json +177 -177
- package/examples/example-dag.json +46 -46
- package/examples/hybrid-loop-agent-dag.json +189 -189
- package/harness.json +66 -66
- package/package.json +88 -46
- package/scripts/check-product-line-docs.sh +29 -29
- package/scripts/check-task-pool-root.sh +32 -32
- package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-incremental-prepare.sh +5 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-materialize.sh +4 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-graph-promote.sh +4 -0
- package/scripts/kb-query.mjs +554 -0
- package/scripts/kb-query.sh +5 -0
- package/skills/agent-worker/SKILL.md +39 -37
- package/skills/agent-worker/references/agent-worker-operator.md +60 -43
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/SKILL.md +67 -0
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
- package/skills/analyze-product-requirements/SKILL.md +90 -0
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
- package/skills/analyze-product-requirements/references/example.md +86 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/frontend-design-review/SKILL.md +66 -59
- package/skills/frontend-design-review/references/review-checklist.md +58 -37
- package/skills/frontend-implementation/SKILL.md +47 -51
- package/skills/frontend-implementation/references/code-standards.md +32 -34
- package/skills/frontend-implementation/references/design-spec.md +46 -46
- package/skills/frontend-implementation/references/node-contracts.md +76 -32
- package/skills/frontend-review/SKILL.md +59 -53
- package/skills/frontend-review/references/review-findings.md +47 -42
- package/skills/frontend-verification/SKILL.md +53 -40
- package/skills/frontend-verification/references/verification-checklist.md +68 -56
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/grill-with-docs/SKILL.md +88 -88
- package/skills/grill-with-docs/adr-format.md +47 -47
- package/skills/grill-with-docs/context-format.md +60 -60
- package/skills/init-capability-evolution/SKILL.md +70 -70
- package/skills/loop-agent/SKILL.md +151 -151
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +505 -452
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/harness-policy.md +263 -263
- package/skills/loop-agent/references/hybrid-dag.md +238 -233
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +57 -57
- package/skills/loop-agent/references/model-routing.md +36 -36
- package/skills/loop-agent/references/multi-worktree.md +54 -54
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
- package/skills/loop-agent/references/task-workflow.md +89 -89
- package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-cli-case-generator/SKILL.md +74 -0
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/SKILL.md +296 -296
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/test-driven-development/SKILL.md +20 -20
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
const DEFAULTS = {
|
|
2
|
-
nodeWidth: 216,
|
|
3
|
-
nodeHeight: 92,
|
|
4
|
-
rankGap: 104,
|
|
5
|
-
nodeGap: 28,
|
|
6
|
-
padding: 32,
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function layoutDag(nodes = [], edges = [], ranks = [], options = {}) {
|
|
10
|
-
const config = { ...DEFAULTS, ...options };
|
|
11
|
-
const nodeIds = [...new Set(nodes.map((node) => node.nodeId).filter(Boolean))].sort();
|
|
12
|
-
if (nodeIds.length === 0) return { width: 320, height: 160, nodes: [], edges: [] };
|
|
13
|
-
const known = new Set(nodeIds);
|
|
14
|
-
const validEdges = edges
|
|
15
|
-
.filter((edge) => known.has(edge.from) && known.has(edge.to) && edge.from !== edge.to)
|
|
16
|
-
.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
17
|
-
const layers = normalizeLayers(nodeIds, validEdges, ranks);
|
|
18
|
-
const positioned = [];
|
|
19
|
-
for (let rank = 0; rank < layers.length; rank++) {
|
|
20
|
-
const ids = layers[rank];
|
|
21
|
-
for (let index = 0; index < ids.length; index++) {
|
|
22
|
-
positioned.push({
|
|
23
|
-
nodeId: ids[index], rank, index,
|
|
24
|
-
x: config.padding + rank * (config.nodeWidth + config.rankGap),
|
|
25
|
-
y: config.padding + index * (config.nodeHeight + config.nodeGap),
|
|
26
|
-
width: config.nodeWidth, height: config.nodeHeight,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const byId = new Map(positioned.map((node) => [node.nodeId, node]));
|
|
31
|
-
const routed = validEdges.map((edge) => {
|
|
32
|
-
const from = byId.get(edge.from);
|
|
33
|
-
const to = byId.get(edge.to);
|
|
34
|
-
const x1 = from.x + from.width;
|
|
35
|
-
const y1 = from.y + from.height / 2;
|
|
36
|
-
const x2 = to.x;
|
|
37
|
-
const y2 = to.y + to.height / 2;
|
|
38
|
-
const bend = Math.max(36, (x2 - x1) / 2);
|
|
39
|
-
return { ...edge, path: `M ${x1} ${y1} C ${x1 + bend} ${y1}, ${x2 - bend} ${y2}, ${x2} ${y2}` };
|
|
40
|
-
});
|
|
41
|
-
const maxRows = Math.max(...layers.map((layer) => layer.length), 1);
|
|
42
|
-
return {
|
|
43
|
-
width: config.padding * 2 + layers.length * config.nodeWidth + (layers.length - 1) * config.rankGap,
|
|
44
|
-
height: config.padding * 2 + maxRows * config.nodeHeight + (maxRows - 1) * config.nodeGap,
|
|
45
|
-
nodes: positioned,
|
|
46
|
-
edges: routed,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function normalizeLayers(nodeIds, edges, ranks) {
|
|
51
|
-
const known = new Set(nodeIds);
|
|
52
|
-
const seen = new Set();
|
|
53
|
-
const layers = [];
|
|
54
|
-
if (Array.isArray(ranks)) {
|
|
55
|
-
for (const rank of ranks) {
|
|
56
|
-
if (!Array.isArray(rank)) continue;
|
|
57
|
-
const ids = [...new Set(rank.filter((id) => known.has(id) && !seen.has(id)))].sort();
|
|
58
|
-
ids.forEach((id) => seen.add(id));
|
|
59
|
-
if (ids.length > 0) layers.push(ids);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (layers.length === 0 && edges.length > 0) {
|
|
63
|
-
const level = new Map(nodeIds.map((id) => [id, 0]));
|
|
64
|
-
for (let pass = 0; pass < nodeIds.length; pass++) {
|
|
65
|
-
let changed = false;
|
|
66
|
-
for (const edge of edges) {
|
|
67
|
-
const next = Math.min(nodeIds.length - 1, level.get(edge.from) + 1);
|
|
68
|
-
if (next > level.get(edge.to)) { level.set(edge.to, next); changed = true; }
|
|
69
|
-
}
|
|
70
|
-
if (!changed) break;
|
|
71
|
-
}
|
|
72
|
-
for (const id of nodeIds) {
|
|
73
|
-
const rank = level.get(id);
|
|
74
|
-
(layers[rank] ??= []).push(id);
|
|
75
|
-
seen.add(id);
|
|
76
|
-
}
|
|
77
|
-
for (const layer of layers) layer?.sort();
|
|
78
|
-
}
|
|
79
|
-
for (const id of nodeIds) {
|
|
80
|
-
if (!seen.has(id)) (layers[0] ??= []).push(id);
|
|
81
|
-
}
|
|
82
|
-
return layers.filter(Boolean);
|
|
83
|
-
}
|
|
1
|
+
const DEFAULTS = {
|
|
2
|
+
nodeWidth: 216,
|
|
3
|
+
nodeHeight: 92,
|
|
4
|
+
rankGap: 104,
|
|
5
|
+
nodeGap: 28,
|
|
6
|
+
padding: 32,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function layoutDag(nodes = [], edges = [], ranks = [], options = {}) {
|
|
10
|
+
const config = { ...DEFAULTS, ...options };
|
|
11
|
+
const nodeIds = [...new Set(nodes.map((node) => node.nodeId).filter(Boolean))].sort();
|
|
12
|
+
if (nodeIds.length === 0) return { width: 320, height: 160, nodes: [], edges: [] };
|
|
13
|
+
const known = new Set(nodeIds);
|
|
14
|
+
const validEdges = edges
|
|
15
|
+
.filter((edge) => known.has(edge.from) && known.has(edge.to) && edge.from !== edge.to)
|
|
16
|
+
.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
17
|
+
const layers = normalizeLayers(nodeIds, validEdges, ranks);
|
|
18
|
+
const positioned = [];
|
|
19
|
+
for (let rank = 0; rank < layers.length; rank++) {
|
|
20
|
+
const ids = layers[rank];
|
|
21
|
+
for (let index = 0; index < ids.length; index++) {
|
|
22
|
+
positioned.push({
|
|
23
|
+
nodeId: ids[index], rank, index,
|
|
24
|
+
x: config.padding + rank * (config.nodeWidth + config.rankGap),
|
|
25
|
+
y: config.padding + index * (config.nodeHeight + config.nodeGap),
|
|
26
|
+
width: config.nodeWidth, height: config.nodeHeight,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const byId = new Map(positioned.map((node) => [node.nodeId, node]));
|
|
31
|
+
const routed = validEdges.map((edge) => {
|
|
32
|
+
const from = byId.get(edge.from);
|
|
33
|
+
const to = byId.get(edge.to);
|
|
34
|
+
const x1 = from.x + from.width;
|
|
35
|
+
const y1 = from.y + from.height / 2;
|
|
36
|
+
const x2 = to.x;
|
|
37
|
+
const y2 = to.y + to.height / 2;
|
|
38
|
+
const bend = Math.max(36, (x2 - x1) / 2);
|
|
39
|
+
return { ...edge, path: `M ${x1} ${y1} C ${x1 + bend} ${y1}, ${x2 - bend} ${y2}, ${x2} ${y2}` };
|
|
40
|
+
});
|
|
41
|
+
const maxRows = Math.max(...layers.map((layer) => layer.length), 1);
|
|
42
|
+
return {
|
|
43
|
+
width: config.padding * 2 + layers.length * config.nodeWidth + (layers.length - 1) * config.rankGap,
|
|
44
|
+
height: config.padding * 2 + maxRows * config.nodeHeight + (maxRows - 1) * config.nodeGap,
|
|
45
|
+
nodes: positioned,
|
|
46
|
+
edges: routed,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeLayers(nodeIds, edges, ranks) {
|
|
51
|
+
const known = new Set(nodeIds);
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
const layers = [];
|
|
54
|
+
if (Array.isArray(ranks)) {
|
|
55
|
+
for (const rank of ranks) {
|
|
56
|
+
if (!Array.isArray(rank)) continue;
|
|
57
|
+
const ids = [...new Set(rank.filter((id) => known.has(id) && !seen.has(id)))].sort();
|
|
58
|
+
ids.forEach((id) => seen.add(id));
|
|
59
|
+
if (ids.length > 0) layers.push(ids);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (layers.length === 0 && edges.length > 0) {
|
|
63
|
+
const level = new Map(nodeIds.map((id) => [id, 0]));
|
|
64
|
+
for (let pass = 0; pass < nodeIds.length; pass++) {
|
|
65
|
+
let changed = false;
|
|
66
|
+
for (const edge of edges) {
|
|
67
|
+
const next = Math.min(nodeIds.length - 1, level.get(edge.from) + 1);
|
|
68
|
+
if (next > level.get(edge.to)) { level.set(edge.to, next); changed = true; }
|
|
69
|
+
}
|
|
70
|
+
if (!changed) break;
|
|
71
|
+
}
|
|
72
|
+
for (const id of nodeIds) {
|
|
73
|
+
const rank = level.get(id);
|
|
74
|
+
(layers[rank] ??= []).push(id);
|
|
75
|
+
seen.add(id);
|
|
76
|
+
}
|
|
77
|
+
for (const layer of layers) layer?.sort();
|
|
78
|
+
}
|
|
79
|
+
for (const id of nodeIds) {
|
|
80
|
+
if (!seen.has(id)) (layers[0] ??= []).push(id);
|
|
81
|
+
}
|
|
82
|
+
return layers.filter(Boolean);
|
|
83
|
+
}
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
/** DAG / dashboard pure model helpers (Observe UI R5). */
|
|
2
|
-
|
|
3
|
-
const TERMINAL_DAG_EFFECTIVE_STATUSES = new Set([
|
|
4
|
-
"finished",
|
|
5
|
-
"failed",
|
|
6
|
-
"superseded",
|
|
7
|
-
"abandoned",
|
|
8
|
-
]);
|
|
9
|
-
|
|
10
|
-
const TERMINAL_RUN_STATUSES = new Set([
|
|
11
|
-
"finished",
|
|
12
|
-
"completed",
|
|
13
|
-
"succeeded",
|
|
14
|
-
"failed",
|
|
15
|
-
"partial_failed",
|
|
16
|
-
"error",
|
|
17
|
-
"cancelled",
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
export function dagMetaVisibility(dag) {
|
|
21
|
-
const terminal =
|
|
22
|
-
(dag?.lifecycle ?? "").toLowerCase() === "completed" ||
|
|
23
|
-
TERMINAL_DAG_EFFECTIVE_STATUSES.has(dag?.effectiveStatus);
|
|
24
|
-
return {
|
|
25
|
-
showExecutorStatus: !terminal,
|
|
26
|
-
showConsistencyWarning: dag?.stateConsistent === false,
|
|
27
|
-
showResume: dag?.recoveryEligibility?.canResume === true,
|
|
28
|
-
showReconcile: dag?.recoveryEligibility?.canReconcile === true,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isNodeActive(status) {
|
|
33
|
-
if (!status) return false;
|
|
34
|
-
const s = status.toLowerCase();
|
|
35
|
-
return (
|
|
36
|
-
s === "running" || s === "started" || s === "pending" || s === "queued"
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function isDagRunActive(dag) {
|
|
41
|
-
if (
|
|
42
|
-
["running", "running-quiet", "remote-unknown", "pending"].includes(
|
|
43
|
-
dag.effectiveStatus,
|
|
44
|
-
)
|
|
45
|
-
)
|
|
46
|
-
return true;
|
|
47
|
-
if (dag.effectiveStatus && dag.effectiveStatus !== "unknown") return false;
|
|
48
|
-
const status = (dag.status ?? "").toLowerCase();
|
|
49
|
-
if ((dag.lifecycle ?? "").toLowerCase() === "paused") return false;
|
|
50
|
-
if (["orphaned", "stale"].includes((dag.liveness ?? "").toLowerCase()))
|
|
51
|
-
return false;
|
|
52
|
-
if (TERMINAL_RUN_STATUSES.has(status)) return false;
|
|
53
|
-
if (["running", "pending", "started"].includes(status)) return true;
|
|
54
|
-
return (dag.nodes ?? []).some((n) => isNodeActive(n.status));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const DASHBOARD_FEATURE_STATUSES = new Set(["running", "needs_action"]);
|
|
58
|
-
|
|
59
|
-
export function dashboardVisibleFeatures(features) {
|
|
60
|
-
return (features ?? []).filter((feature) =>
|
|
61
|
-
DASHBOARD_FEATURE_STATUSES.has(feature?.status),
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function findDagForRun(snapshot, _workerRunId, task) {
|
|
66
|
-
const dagRuns = snapshot?.dagRuns ?? [];
|
|
67
|
-
if (task?.dagRunId) {
|
|
68
|
-
const byTask = dagRuns.find((d) => d.dagRunId === task.dagRunId);
|
|
69
|
-
if (byTask) return byTask;
|
|
70
|
-
}
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
1
|
+
/** DAG / dashboard pure model helpers (Observe UI R5). */
|
|
2
|
+
|
|
3
|
+
const TERMINAL_DAG_EFFECTIVE_STATUSES = new Set([
|
|
4
|
+
"finished",
|
|
5
|
+
"failed",
|
|
6
|
+
"superseded",
|
|
7
|
+
"abandoned",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
const TERMINAL_RUN_STATUSES = new Set([
|
|
11
|
+
"finished",
|
|
12
|
+
"completed",
|
|
13
|
+
"succeeded",
|
|
14
|
+
"failed",
|
|
15
|
+
"partial_failed",
|
|
16
|
+
"error",
|
|
17
|
+
"cancelled",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export function dagMetaVisibility(dag) {
|
|
21
|
+
const terminal =
|
|
22
|
+
(dag?.lifecycle ?? "").toLowerCase() === "completed" ||
|
|
23
|
+
TERMINAL_DAG_EFFECTIVE_STATUSES.has(dag?.effectiveStatus);
|
|
24
|
+
return {
|
|
25
|
+
showExecutorStatus: !terminal,
|
|
26
|
+
showConsistencyWarning: dag?.stateConsistent === false,
|
|
27
|
+
showResume: dag?.recoveryEligibility?.canResume === true,
|
|
28
|
+
showReconcile: dag?.recoveryEligibility?.canReconcile === true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isNodeActive(status) {
|
|
33
|
+
if (!status) return false;
|
|
34
|
+
const s = status.toLowerCase();
|
|
35
|
+
return (
|
|
36
|
+
s === "running" || s === "started" || s === "pending" || s === "queued"
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isDagRunActive(dag) {
|
|
41
|
+
if (
|
|
42
|
+
["running", "running-quiet", "remote-unknown", "pending"].includes(
|
|
43
|
+
dag.effectiveStatus,
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
return true;
|
|
47
|
+
if (dag.effectiveStatus && dag.effectiveStatus !== "unknown") return false;
|
|
48
|
+
const status = (dag.status ?? "").toLowerCase();
|
|
49
|
+
if ((dag.lifecycle ?? "").toLowerCase() === "paused") return false;
|
|
50
|
+
if (["orphaned", "stale"].includes((dag.liveness ?? "").toLowerCase()))
|
|
51
|
+
return false;
|
|
52
|
+
if (TERMINAL_RUN_STATUSES.has(status)) return false;
|
|
53
|
+
if (["running", "pending", "started"].includes(status)) return true;
|
|
54
|
+
return (dag.nodes ?? []).some((n) => isNodeActive(n.status));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DASHBOARD_FEATURE_STATUSES = new Set(["running", "needs_action"]);
|
|
58
|
+
|
|
59
|
+
export function dashboardVisibleFeatures(features) {
|
|
60
|
+
return (features ?? []).filter((feature) =>
|
|
61
|
+
DASHBOARD_FEATURE_STATUSES.has(feature?.status),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function findDagForRun(snapshot, _workerRunId, task) {
|
|
66
|
+
const dagRuns = snapshot?.dagRuns ?? [];
|
|
67
|
+
if (task?.dagRunId) {
|
|
68
|
+
const byTask = dagRuns.find((d) => d.dagRunId === task.dagRunId);
|
|
69
|
+
if (byTask) return byTask;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
/** DOM helpers (Observe UI R5). */
|
|
2
|
-
|
|
3
|
-
export function clearNode(node) {
|
|
4
|
-
while (node.firstChild) node.removeChild(node.firstChild);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function el(tag, className, text) {
|
|
8
|
-
const node = document.createElement(tag);
|
|
9
|
-
if (className) node.className = className;
|
|
10
|
-
if (text !== undefined) node.textContent = text;
|
|
11
|
-
return node;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function metaGrid(entries) {
|
|
15
|
-
const dl = el("dl", "meta-grid");
|
|
16
|
-
for (const [label, value] of entries) {
|
|
17
|
-
const item = document.createElement("div");
|
|
18
|
-
item.className = "meta-item";
|
|
19
|
-
const dt = document.createElement("dt");
|
|
20
|
-
dt.textContent = label;
|
|
21
|
-
const dd = document.createElement("dd");
|
|
22
|
-
if (value instanceof Node) dd.appendChild(value);
|
|
23
|
-
else dd.textContent = value ?? "—";
|
|
24
|
-
item.appendChild(dt);
|
|
25
|
-
item.appendChild(dd);
|
|
26
|
-
dl.appendChild(item);
|
|
27
|
-
}
|
|
28
|
-
return dl;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function buildTable(headers, rows) {
|
|
32
|
-
const table = document.createElement("table");
|
|
33
|
-
const thead = document.createElement("thead");
|
|
34
|
-
const headRow = document.createElement("tr");
|
|
35
|
-
for (const h of headers) {
|
|
36
|
-
const th = document.createElement("th");
|
|
37
|
-
th.textContent = h;
|
|
38
|
-
headRow.appendChild(th);
|
|
39
|
-
}
|
|
40
|
-
thead.appendChild(headRow);
|
|
41
|
-
table.appendChild(thead);
|
|
42
|
-
|
|
43
|
-
const tbody = document.createElement("tbody");
|
|
44
|
-
for (const row of rows) {
|
|
45
|
-
const tr = document.createElement("tr");
|
|
46
|
-
if (row.className) tr.className = row.className;
|
|
47
|
-
if (row.onClick) {
|
|
48
|
-
tr.classList.add("clickable");
|
|
49
|
-
tr.addEventListener("click", row.onClick);
|
|
50
|
-
}
|
|
51
|
-
for (const cell of row.cells) {
|
|
52
|
-
const td = document.createElement("td");
|
|
53
|
-
if (cell instanceof Node) td.appendChild(cell);
|
|
54
|
-
else td.textContent = cell ?? "—";
|
|
55
|
-
tr.appendChild(td);
|
|
56
|
-
}
|
|
57
|
-
tbody.appendChild(tr);
|
|
58
|
-
}
|
|
59
|
-
table.appendChild(tbody);
|
|
60
|
-
return table;
|
|
61
|
-
}
|
|
1
|
+
/** DOM helpers (Observe UI R5). */
|
|
2
|
+
|
|
3
|
+
export function clearNode(node) {
|
|
4
|
+
while (node.firstChild) node.removeChild(node.firstChild);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function el(tag, className, text) {
|
|
8
|
+
const node = document.createElement(tag);
|
|
9
|
+
if (className) node.className = className;
|
|
10
|
+
if (text !== undefined) node.textContent = text;
|
|
11
|
+
return node;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function metaGrid(entries) {
|
|
15
|
+
const dl = el("dl", "meta-grid");
|
|
16
|
+
for (const [label, value] of entries) {
|
|
17
|
+
const item = document.createElement("div");
|
|
18
|
+
item.className = "meta-item";
|
|
19
|
+
const dt = document.createElement("dt");
|
|
20
|
+
dt.textContent = label;
|
|
21
|
+
const dd = document.createElement("dd");
|
|
22
|
+
if (value instanceof Node) dd.appendChild(value);
|
|
23
|
+
else dd.textContent = value ?? "—";
|
|
24
|
+
item.appendChild(dt);
|
|
25
|
+
item.appendChild(dd);
|
|
26
|
+
dl.appendChild(item);
|
|
27
|
+
}
|
|
28
|
+
return dl;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildTable(headers, rows) {
|
|
32
|
+
const table = document.createElement("table");
|
|
33
|
+
const thead = document.createElement("thead");
|
|
34
|
+
const headRow = document.createElement("tr");
|
|
35
|
+
for (const h of headers) {
|
|
36
|
+
const th = document.createElement("th");
|
|
37
|
+
th.textContent = h;
|
|
38
|
+
headRow.appendChild(th);
|
|
39
|
+
}
|
|
40
|
+
thead.appendChild(headRow);
|
|
41
|
+
table.appendChild(thead);
|
|
42
|
+
|
|
43
|
+
const tbody = document.createElement("tbody");
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
const tr = document.createElement("tr");
|
|
46
|
+
if (row.className) tr.className = row.className;
|
|
47
|
+
if (row.onClick) {
|
|
48
|
+
tr.classList.add("clickable");
|
|
49
|
+
tr.addEventListener("click", row.onClick);
|
|
50
|
+
}
|
|
51
|
+
for (const cell of row.cells) {
|
|
52
|
+
const td = document.createElement("td");
|
|
53
|
+
if (cell instanceof Node) td.appendChild(cell);
|
|
54
|
+
else td.textContent = cell ?? "—";
|
|
55
|
+
tr.appendChild(td);
|
|
56
|
+
}
|
|
57
|
+
tbody.appendChild(tr);
|
|
58
|
+
}
|
|
59
|
+
table.appendChild(tbody);
|
|
60
|
+
return table;
|
|
61
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
/** Pool task sorting and status filter buckets (Observe UI R5). */
|
|
2
|
-
|
|
3
|
-
export function sortPoolTasks(tasks) {
|
|
4
|
-
const list = Array.isArray(tasks) ? [...tasks] : [];
|
|
5
|
-
const active = new Set(["running", "pending", "blocked", "stale", "unknown"]);
|
|
6
|
-
const rank = (status) => {
|
|
7
|
-
const key = (status ?? "unknown").toLowerCase();
|
|
8
|
-
if (key === "running") return 0;
|
|
9
|
-
if (key === "pending" || key === "queued") return 1;
|
|
10
|
-
if (key === "blocked") return 2;
|
|
11
|
-
if (key === "stale") return 3;
|
|
12
|
-
if (active.has(key)) return 4;
|
|
13
|
-
return 10;
|
|
14
|
-
};
|
|
15
|
-
const timeKey = (task) =>
|
|
16
|
-
task.updatedAt || task.finishedAt || task.startedAt || "";
|
|
17
|
-
list.sort((a, b) => {
|
|
18
|
-
const ra = rank(a.status);
|
|
19
|
-
const rb = rank(b.status);
|
|
20
|
-
if (ra !== rb) return ra - rb;
|
|
21
|
-
if (ra >= 10) {
|
|
22
|
-
const tb = timeKey(b).localeCompare(timeKey(a));
|
|
23
|
-
if (tb !== 0) return tb;
|
|
24
|
-
}
|
|
25
|
-
return String(a.taskId ?? "").localeCompare(String(b.taskId ?? ""));
|
|
26
|
-
});
|
|
27
|
-
return list;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Match task status against a hash filter token (same buckets as KPI counts).
|
|
32
|
-
* pending ⊇ queued; succeeded ⊇ reused|done; blocked ⊇ stale.
|
|
33
|
-
*/
|
|
34
|
-
export function matchesPoolStatusFilter(taskStatus, filter) {
|
|
35
|
-
const st = (taskStatus ?? "").toLowerCase();
|
|
36
|
-
const f = (filter ?? "").toLowerCase();
|
|
37
|
-
if (!f) return true;
|
|
38
|
-
if (f === "pending") return st === "pending" || st === "queued";
|
|
39
|
-
if (f === "succeeded")
|
|
40
|
-
return st === "succeeded" || st === "reused" || st === "done";
|
|
41
|
-
if (f === "blocked") return st === "blocked" || st === "stale";
|
|
42
|
-
if (f === "failed") return st === "failed" || st === "error";
|
|
43
|
-
if (f === "running") return st === "running";
|
|
44
|
-
return st === f;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function poolStatusCounts(tasks) {
|
|
48
|
-
const counts = {
|
|
49
|
-
running: 0,
|
|
50
|
-
pending: 0,
|
|
51
|
-
blocked: 0,
|
|
52
|
-
failed: 0,
|
|
53
|
-
succeeded: 0,
|
|
54
|
-
other: 0,
|
|
55
|
-
};
|
|
56
|
-
for (const task of tasks) {
|
|
57
|
-
const key = (task.status ?? "unknown").toLowerCase();
|
|
58
|
-
if (key === "running") counts.running += 1;
|
|
59
|
-
else if (key === "pending" || key === "queued") counts.pending += 1;
|
|
60
|
-
else if (key === "blocked" || key === "stale") counts.blocked += 1;
|
|
61
|
-
else if (key === "failed" || key === "error") counts.failed += 1;
|
|
62
|
-
else if (key === "succeeded" || key === "reused" || key === "done")
|
|
63
|
-
counts.succeeded += 1;
|
|
64
|
-
else counts.other += 1;
|
|
65
|
-
}
|
|
66
|
-
return counts;
|
|
67
|
-
}
|
|
1
|
+
/** Pool task sorting and status filter buckets (Observe UI R5). */
|
|
2
|
+
|
|
3
|
+
export function sortPoolTasks(tasks) {
|
|
4
|
+
const list = Array.isArray(tasks) ? [...tasks] : [];
|
|
5
|
+
const active = new Set(["running", "pending", "blocked", "stale", "unknown"]);
|
|
6
|
+
const rank = (status) => {
|
|
7
|
+
const key = (status ?? "unknown").toLowerCase();
|
|
8
|
+
if (key === "running") return 0;
|
|
9
|
+
if (key === "pending" || key === "queued") return 1;
|
|
10
|
+
if (key === "blocked") return 2;
|
|
11
|
+
if (key === "stale") return 3;
|
|
12
|
+
if (active.has(key)) return 4;
|
|
13
|
+
return 10;
|
|
14
|
+
};
|
|
15
|
+
const timeKey = (task) =>
|
|
16
|
+
task.updatedAt || task.finishedAt || task.startedAt || "";
|
|
17
|
+
list.sort((a, b) => {
|
|
18
|
+
const ra = rank(a.status);
|
|
19
|
+
const rb = rank(b.status);
|
|
20
|
+
if (ra !== rb) return ra - rb;
|
|
21
|
+
if (ra >= 10) {
|
|
22
|
+
const tb = timeKey(b).localeCompare(timeKey(a));
|
|
23
|
+
if (tb !== 0) return tb;
|
|
24
|
+
}
|
|
25
|
+
return String(a.taskId ?? "").localeCompare(String(b.taskId ?? ""));
|
|
26
|
+
});
|
|
27
|
+
return list;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Match task status against a hash filter token (same buckets as KPI counts).
|
|
32
|
+
* pending ⊇ queued; succeeded ⊇ reused|done; blocked ⊇ stale.
|
|
33
|
+
*/
|
|
34
|
+
export function matchesPoolStatusFilter(taskStatus, filter) {
|
|
35
|
+
const st = (taskStatus ?? "").toLowerCase();
|
|
36
|
+
const f = (filter ?? "").toLowerCase();
|
|
37
|
+
if (!f) return true;
|
|
38
|
+
if (f === "pending") return st === "pending" || st === "queued";
|
|
39
|
+
if (f === "succeeded")
|
|
40
|
+
return st === "succeeded" || st === "reused" || st === "done";
|
|
41
|
+
if (f === "blocked") return st === "blocked" || st === "stale";
|
|
42
|
+
if (f === "failed") return st === "failed" || st === "error";
|
|
43
|
+
if (f === "running") return st === "running";
|
|
44
|
+
return st === f;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function poolStatusCounts(tasks) {
|
|
48
|
+
const counts = {
|
|
49
|
+
running: 0,
|
|
50
|
+
pending: 0,
|
|
51
|
+
blocked: 0,
|
|
52
|
+
failed: 0,
|
|
53
|
+
succeeded: 0,
|
|
54
|
+
other: 0,
|
|
55
|
+
};
|
|
56
|
+
for (const task of tasks) {
|
|
57
|
+
const key = (task.status ?? "unknown").toLowerCase();
|
|
58
|
+
if (key === "running") counts.running += 1;
|
|
59
|
+
else if (key === "pending" || key === "queued") counts.pending += 1;
|
|
60
|
+
else if (key === "blocked" || key === "stale") counts.blocked += 1;
|
|
61
|
+
else if (key === "failed" || key === "error") counts.failed += 1;
|
|
62
|
+
else if (key === "succeeded" || key === "reused" || key === "done")
|
|
63
|
+
counts.succeeded += 1;
|
|
64
|
+
else counts.other += 1;
|
|
65
|
+
}
|
|
66
|
+
return counts;
|
|
67
|
+
}
|