@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
|
@@ -4,10 +4,41 @@ import path from "node:path";
|
|
|
4
4
|
import { truncateUtf8Preview } from "../../shared/preview.js";
|
|
5
5
|
import { parseWorkerEventLine } from "./events.js";
|
|
6
6
|
import { readBoundedJsonlTail } from "./event-history.js";
|
|
7
|
-
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
7
|
+
import { getTaskPoolRoot, listLegacyStateFiles, listTaskPoolStates, } from "../pool/run-store.js";
|
|
8
8
|
import { assessDagRunLiveness, assessDagRunRecoveryEligibility, deriveDagRunEffectiveStatus, } from "../../workflows/dag/lifecycle.js";
|
|
9
9
|
import { parseDagSpec, resolveModelForTask, } from "../../workflows/dag/types.js";
|
|
10
10
|
import { loadFeatureDecisionModels } from "../feature/decision-loader.js";
|
|
11
|
+
/** Internal map key: featureId + NUL + taskId (stable, non-printable delimiter). */
|
|
12
|
+
export function taskIdentityKey(featureId, taskId) {
|
|
13
|
+
return `${featureId}\0${taskId}`;
|
|
14
|
+
}
|
|
15
|
+
export function compareTaskIdentity(a, b) {
|
|
16
|
+
const fa = a.featureId ?? "";
|
|
17
|
+
const fb = b.featureId ?? "";
|
|
18
|
+
const byFeature = fa.localeCompare(fb);
|
|
19
|
+
if (byFeature !== 0)
|
|
20
|
+
return byFeature;
|
|
21
|
+
return a.taskId.localeCompare(b.taskId);
|
|
22
|
+
}
|
|
23
|
+
export function findTasksByTaskId(tasks, taskId) {
|
|
24
|
+
return tasks.filter((task) => task.taskId === taskId);
|
|
25
|
+
}
|
|
26
|
+
export function resolveLegacyTask(tasks, taskId) {
|
|
27
|
+
const matches = findTasksByTaskId(tasks, taskId);
|
|
28
|
+
const candidates = matches.map((task) => ({
|
|
29
|
+
featureId: task.featureId,
|
|
30
|
+
taskId: task.taskId,
|
|
31
|
+
status: task.status,
|
|
32
|
+
workerRunId: task.workerRunId,
|
|
33
|
+
}));
|
|
34
|
+
if (matches.length === 0) {
|
|
35
|
+
return { kind: "missing", candidates: [] };
|
|
36
|
+
}
|
|
37
|
+
if (matches.length === 1) {
|
|
38
|
+
return { kind: "unique", task: matches[0], candidates };
|
|
39
|
+
}
|
|
40
|
+
return { kind: "ambiguous", candidates };
|
|
41
|
+
}
|
|
11
42
|
export const TASK_RUN_HISTORY_DEFAULT_LIMIT = 20;
|
|
12
43
|
export const TASK_RUN_HISTORY_MAX_LIMIT = 100;
|
|
13
44
|
const ACTIVE_TASK_STATUSES = new Set(["running", "pending"]);
|
|
@@ -19,7 +50,7 @@ export async function buildGlobalSnapshot(options) {
|
|
|
19
50
|
const staleThresholdMs = options.staleThresholdMs ?? 90_000;
|
|
20
51
|
const quietThresholdMs = options.quietThresholdMs ?? 60_000;
|
|
21
52
|
const taskPoolPresent = existsSync(getTaskPoolRoot(repoRoot));
|
|
22
|
-
const [eventsLoad, ledgerLoad, ledgerBatches,
|
|
53
|
+
const [eventsLoad, ledgerLoad, ledgerBatches, statesLoad, handoffs, dagRuns,] = await Promise.all([
|
|
23
54
|
loadObservabilityEvents(repoRoot),
|
|
24
55
|
loadLedgerRuns(repoRoot),
|
|
25
56
|
loadLedgerBatches(repoRoot),
|
|
@@ -29,23 +60,30 @@ export async function buildGlobalSnapshot(options) {
|
|
|
29
60
|
]);
|
|
30
61
|
const events = eventsLoad.events;
|
|
31
62
|
const ledgerRuns = ledgerLoad.runs;
|
|
63
|
+
const identityWarnings = [];
|
|
32
64
|
const taskMap = new Map();
|
|
33
|
-
|
|
34
|
-
mergeStateRecord(taskMap, state);
|
|
35
|
-
}
|
|
65
|
+
// Ledger first so featureId-bearing rows seed composite identity before states/events.
|
|
36
66
|
for (const run of ledgerRuns) {
|
|
37
|
-
mergeLedgerRun(taskMap, run);
|
|
67
|
+
mergeLedgerRun(taskMap, run, identityWarnings);
|
|
68
|
+
}
|
|
69
|
+
for (const state of statesLoad.states) {
|
|
70
|
+
mergeStateRecord(taskMap, state, identityWarnings);
|
|
71
|
+
}
|
|
72
|
+
// Attach uniquely-resolvable legacy flat states after owned rows exist.
|
|
73
|
+
for (const state of statesLoad.legacyCandidates) {
|
|
74
|
+
mergeLegacyStateRecord(taskMap, state, identityWarnings);
|
|
38
75
|
}
|
|
39
|
-
|
|
76
|
+
const workerRunFeatureIndex = buildWorkerRunFeatureIndex(ledgerRuns, taskMap);
|
|
77
|
+
mergeEvents(taskMap, events, workerRunFeatureIndex, identityWarnings);
|
|
40
78
|
mergeHandoffs(taskMap, handoffs);
|
|
41
79
|
applyStaleDetection(taskMap, now(), staleThresholdMs, quietThresholdMs);
|
|
42
|
-
const historicalRunMap = buildHistoricalRunMap(ledgerRuns, events);
|
|
80
|
+
const historicalRunMap = buildHistoricalRunMap(ledgerRuns, events, workerRunFeatureIndex, identityWarnings);
|
|
43
81
|
mergeHandoffs(historicalRunMap, handoffs);
|
|
44
82
|
applyStaleDetection(historicalRunMap, now(), staleThresholdMs, quietThresholdMs);
|
|
45
83
|
const tasks = [...taskMap.values()]
|
|
46
84
|
.map(toPublicTaskSummary)
|
|
47
85
|
.map((task) => normalizeTaskArtifactRefs(task, repoRoot))
|
|
48
|
-
.sort(
|
|
86
|
+
.sort(compareTaskIdentity);
|
|
49
87
|
const batches = buildBatches(ledgerBatches, events, historicalRunMap).map((batch) => ({
|
|
50
88
|
...batch,
|
|
51
89
|
tasks: batch.tasks.map((task) => normalizeTaskArtifactRefs(task, repoRoot)),
|
|
@@ -55,6 +93,8 @@ export async function buildGlobalSnapshot(options) {
|
|
|
55
93
|
const projectionWarnings = [
|
|
56
94
|
...(eventsLoad.warnings ?? []),
|
|
57
95
|
...(ledgerLoad.warnings ?? []),
|
|
96
|
+
...(statesLoad.warnings ?? []),
|
|
97
|
+
...identityWarnings,
|
|
58
98
|
...(featureLoad.projectionWarnings ?? []),
|
|
59
99
|
];
|
|
60
100
|
return {
|
|
@@ -94,9 +134,10 @@ export async function buildGlobalSnapshot(options) {
|
|
|
94
134
|
}
|
|
95
135
|
/**
|
|
96
136
|
* Bounded Task run history from Task Pool ledger facts (newest first).
|
|
137
|
+
* Filtered by composite `{featureId, taskId}`.
|
|
97
138
|
* `before` is an exclusive upper bound on the sort key (`recordedAt` or empty).
|
|
98
139
|
*/
|
|
99
|
-
export async function listTaskRunHistory(repoRoot, taskId, options = {}) {
|
|
140
|
+
export async function listTaskRunHistory(repoRoot, featureId, taskId, options = {}) {
|
|
100
141
|
const limit = clampTaskRunHistoryLimit(options.limit);
|
|
101
142
|
const before = decodeTaskRunHistoryCursor(options.before);
|
|
102
143
|
const [ledgerLoad, eventsLoad] = await Promise.all([
|
|
@@ -105,15 +146,19 @@ export async function listTaskRunHistory(repoRoot, taskId, options = {}) {
|
|
|
105
146
|
]);
|
|
106
147
|
const ledgerRuns = ledgerLoad.runs;
|
|
107
148
|
const events = eventsLoad.events;
|
|
108
|
-
const
|
|
149
|
+
const workerRunFeatureIndex = buildWorkerRunFeatureIndex(ledgerRuns);
|
|
150
|
+
const historical = buildHistoricalRunMap(ledgerRuns, events, workerRunFeatureIndex);
|
|
109
151
|
const items = [];
|
|
110
152
|
for (const run of ledgerRuns) {
|
|
111
153
|
if (run.taskId !== taskId)
|
|
112
154
|
continue;
|
|
155
|
+
if ((run.featureId ?? "") !== featureId)
|
|
156
|
+
continue;
|
|
113
157
|
const enriched = historical.get(run.workerRunId);
|
|
114
158
|
items.push({
|
|
115
159
|
workerRunId: run.workerRunId,
|
|
116
160
|
taskId: run.taskId,
|
|
161
|
+
featureId: run.featureId ?? featureId,
|
|
117
162
|
status: enriched?.status ?? mapRunRecordStatus(run.status),
|
|
118
163
|
startedAt: enriched?.startedAt,
|
|
119
164
|
finishedAt: enriched?.finishedAt ?? run.recordedAt,
|
|
@@ -142,6 +187,7 @@ export async function listTaskRunHistory(repoRoot, taskId, options = {}) {
|
|
|
142
187
|
? encodeTaskRunHistoryCursor(page[page.length - 1])
|
|
143
188
|
: null;
|
|
144
189
|
return {
|
|
190
|
+
featureId,
|
|
145
191
|
taskId,
|
|
146
192
|
runs: page,
|
|
147
193
|
limit,
|
|
@@ -226,13 +272,21 @@ function toPublicTaskSummary(task) {
|
|
|
226
272
|
const { timeoutMs: _to, commandStartedAt: _cs, ...rest } = task;
|
|
227
273
|
return rest;
|
|
228
274
|
}
|
|
229
|
-
function mergeStateRecord(taskMap, state) {
|
|
230
|
-
const
|
|
275
|
+
function mergeStateRecord(taskMap, state, warnings) {
|
|
276
|
+
const featureId = state.featureId?.trim();
|
|
277
|
+
if (!featureId) {
|
|
278
|
+
warnings?.push(`state for task ${state.taskId} missing featureId; excluded from composite projection`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const key = taskIdentityKey(featureId, state.taskId);
|
|
282
|
+
const existing = taskMap.get(key) ?? {
|
|
231
283
|
taskId: state.taskId,
|
|
284
|
+
featureId,
|
|
232
285
|
status: "unknown",
|
|
233
286
|
};
|
|
234
|
-
taskMap.set(
|
|
287
|
+
taskMap.set(key, {
|
|
235
288
|
...existing,
|
|
289
|
+
featureId,
|
|
236
290
|
status: existing.status !== "unknown"
|
|
237
291
|
? existing.status
|
|
238
292
|
: mapStateStatus(state.status),
|
|
@@ -245,9 +299,35 @@ function mergeStateRecord(taskMap, state) {
|
|
|
245
299
|
recommendedFollowUp: existing.recommendedFollowUp ?? state.recommendedFollowUp,
|
|
246
300
|
});
|
|
247
301
|
}
|
|
248
|
-
|
|
249
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Legacy flat states/<taskId>.json: only merge when the taskId already maps to
|
|
304
|
+
* exactly one composite row in the snapshot (unique ownership). Otherwise warn+exclude.
|
|
305
|
+
*/
|
|
306
|
+
function mergeLegacyStateRecord(taskMap, state, warnings) {
|
|
307
|
+
const owners = [...taskMap.values()].filter((task) => task.taskId === state.taskId && Boolean(task.featureId));
|
|
308
|
+
if (owners.length === 1) {
|
|
309
|
+
const owner = owners[0];
|
|
310
|
+
mergeStateRecord(taskMap, { ...state, featureId: owner.featureId }, warnings);
|
|
311
|
+
warnings.push(`legacy flat state for task ${state.taskId} attached to unique feature ${owner.featureId}`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (owners.length === 0) {
|
|
315
|
+
// No owned composite row yet: still surface as a warning and do not invent featureId.
|
|
316
|
+
warnings.push(`legacy flat state for task ${state.taskId} has no unique feature ownership; excluded from composite projection`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
warnings.push(`legacy flat state for task ${state.taskId} is ambiguous across features; excluded from composite projection`);
|
|
320
|
+
}
|
|
321
|
+
function mergeLedgerRun(taskMap, run, warnings) {
|
|
322
|
+
const featureId = run.featureId?.trim();
|
|
323
|
+
if (!featureId) {
|
|
324
|
+
warnings?.push(`ledger run ${run.workerRunId} for task ${run.taskId} missing featureId; excluded from composite projection`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const key = taskIdentityKey(featureId, run.taskId);
|
|
328
|
+
const existing = taskMap.get(key) ?? {
|
|
250
329
|
taskId: run.taskId,
|
|
330
|
+
featureId,
|
|
251
331
|
status: "unknown",
|
|
252
332
|
};
|
|
253
333
|
const sameRun = existing.workerRunId === run.workerRunId;
|
|
@@ -256,17 +336,17 @@ function mergeLedgerRun(taskMap, run) {
|
|
|
256
336
|
dagPath: run.dagPath,
|
|
257
337
|
...run.failureArtifacts,
|
|
258
338
|
};
|
|
259
|
-
taskMap.set(
|
|
339
|
+
taskMap.set(key, {
|
|
260
340
|
...(sameRun
|
|
261
341
|
? existing
|
|
262
342
|
: {
|
|
263
343
|
taskId: run.taskId,
|
|
344
|
+
featureId,
|
|
264
345
|
status: "unknown",
|
|
265
|
-
featureId: existing.featureId,
|
|
266
346
|
harnessTaskId: existing.harnessTaskId,
|
|
267
347
|
}),
|
|
268
348
|
batchRunId: run.batchRunId,
|
|
269
|
-
featureId
|
|
349
|
+
featureId,
|
|
270
350
|
status: mapRunRecordStatus(run.status),
|
|
271
351
|
workerRunId: run.workerRunId,
|
|
272
352
|
harnessTaskId: run.harnessTaskId ?? existing.harnessTaskId,
|
|
@@ -277,21 +357,82 @@ function mergeLedgerRun(taskMap, run) {
|
|
|
277
357
|
artifactRefs: mergeArtifactRefs(sameRun ? existing.artifactRefs : undefined, runArtifactRefs),
|
|
278
358
|
});
|
|
279
359
|
}
|
|
280
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Build workerRunId → featureId unique index from ledger + already-owned task rows.
|
|
362
|
+
* Ambiguous (0 or N) mappings are omitted so callers treat them as unowned.
|
|
363
|
+
*/
|
|
364
|
+
function buildWorkerRunFeatureIndex(ledgerRuns, taskMap) {
|
|
365
|
+
const candidates = new Map();
|
|
366
|
+
const add = (workerRunId, featureId) => {
|
|
367
|
+
const wr = workerRunId?.trim();
|
|
368
|
+
const fid = featureId?.trim();
|
|
369
|
+
if (!wr || !fid)
|
|
370
|
+
return;
|
|
371
|
+
let set = candidates.get(wr);
|
|
372
|
+
if (!set) {
|
|
373
|
+
set = new Set();
|
|
374
|
+
candidates.set(wr, set);
|
|
375
|
+
}
|
|
376
|
+
set.add(fid);
|
|
377
|
+
};
|
|
378
|
+
for (const run of ledgerRuns) {
|
|
379
|
+
add(run.workerRunId, run.featureId);
|
|
380
|
+
}
|
|
381
|
+
if (taskMap) {
|
|
382
|
+
for (const task of taskMap.values()) {
|
|
383
|
+
add(task.workerRunId, task.featureId);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const unique = new Map();
|
|
387
|
+
for (const [workerRunId, features] of candidates) {
|
|
388
|
+
if (features.size === 1) {
|
|
389
|
+
unique.set(workerRunId, [...features][0]);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return unique;
|
|
393
|
+
}
|
|
394
|
+
function mergeEvents(taskMap, events, workerRunFeatureIndex, warnings) {
|
|
281
395
|
const sorted = [...events].sort((a, b) => a.at.localeCompare(b.at));
|
|
282
396
|
for (const event of sorted) {
|
|
283
397
|
if (event.taskId) {
|
|
284
|
-
applyTaskEvent(taskMap, event);
|
|
398
|
+
applyTaskEvent(taskMap, event, workerRunFeatureIndex, warnings);
|
|
285
399
|
}
|
|
286
400
|
}
|
|
287
401
|
}
|
|
288
|
-
function applyTaskEvent(taskMap, event) {
|
|
402
|
+
function applyTaskEvent(taskMap, event, workerRunFeatureIndex, warnings) {
|
|
289
403
|
const taskId = event.taskId;
|
|
290
|
-
const
|
|
404
|
+
const featureId = resolveEventFeatureId(event, taskMap, workerRunFeatureIndex, warnings);
|
|
405
|
+
if (!featureId)
|
|
406
|
+
return;
|
|
407
|
+
const key = taskIdentityKey(featureId, taskId);
|
|
408
|
+
const existing = taskMap.get(key) ?? {
|
|
291
409
|
taskId,
|
|
410
|
+
featureId,
|
|
292
411
|
status: "unknown",
|
|
293
412
|
};
|
|
294
|
-
taskMap.set(
|
|
413
|
+
taskMap.set(key, applyEventToSummary(existing, event));
|
|
414
|
+
}
|
|
415
|
+
function resolveEventFeatureId(event, taskMap, workerRunFeatureIndex, warnings) {
|
|
416
|
+
const taskId = event.taskId;
|
|
417
|
+
if (!taskId)
|
|
418
|
+
return undefined;
|
|
419
|
+
if (event.workerRunId) {
|
|
420
|
+
const fromIndex = workerRunFeatureIndex.get(event.workerRunId);
|
|
421
|
+
if (fromIndex)
|
|
422
|
+
return fromIndex;
|
|
423
|
+
// Fall through: maybe a single existing composite row already owns this workerRunId.
|
|
424
|
+
const byWorkerRun = [...taskMap.values()].filter((task) => task.workerRunId === event.workerRunId && Boolean(task.featureId));
|
|
425
|
+
if (byWorkerRun.length === 1)
|
|
426
|
+
return byWorkerRun[0].featureId;
|
|
427
|
+
}
|
|
428
|
+
// Unique taskId ownership in current map (single Feature).
|
|
429
|
+
const byTaskId = [...taskMap.values()].filter((task) => task.taskId === taskId && Boolean(task.featureId));
|
|
430
|
+
if (byTaskId.length === 1)
|
|
431
|
+
return byTaskId[0].featureId;
|
|
432
|
+
warnings?.push(`unowned event ${event.type} for task ${taskId}` +
|
|
433
|
+
(event.workerRunId ? ` workerRunId ${event.workerRunId}` : "") +
|
|
434
|
+
` cannot uniquely resolve featureId; excluded from task projection`);
|
|
435
|
+
return undefined;
|
|
295
436
|
}
|
|
296
437
|
function applyEventToSummary(existing, event) {
|
|
297
438
|
const updated = {
|
|
@@ -342,9 +483,10 @@ function applyEventToSummary(existing, event) {
|
|
|
342
483
|
}
|
|
343
484
|
return updated;
|
|
344
485
|
}
|
|
345
|
-
function buildHistoricalRunMap(ledgerRuns, events) {
|
|
486
|
+
function buildHistoricalRunMap(ledgerRuns, events, workerRunFeatureIndex = new Map(), warnings) {
|
|
346
487
|
const runMap = new Map();
|
|
347
488
|
for (const run of ledgerRuns) {
|
|
489
|
+
// History map is keyed by workerRunId (run-scoped); featureId still required when present.
|
|
348
490
|
runMap.set(run.workerRunId, {
|
|
349
491
|
taskId: run.taskId,
|
|
350
492
|
batchRunId: run.batchRunId,
|
|
@@ -365,12 +507,23 @@ function buildHistoricalRunMap(ledgerRuns, events) {
|
|
|
365
507
|
for (const event of [...events].sort((a, b) => a.at.localeCompare(b.at))) {
|
|
366
508
|
if (!event.workerRunId || !event.taskId)
|
|
367
509
|
continue;
|
|
368
|
-
const existing = runMap.get(event.workerRunId)
|
|
510
|
+
const existing = runMap.get(event.workerRunId);
|
|
511
|
+
if (existing) {
|
|
512
|
+
runMap.set(event.workerRunId, applyEventToSummary(existing, event));
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
// Only invent a historical row when workerRunId uniquely maps to a feature.
|
|
516
|
+
const featureId = workerRunFeatureIndex.get(event.workerRunId);
|
|
517
|
+
if (!featureId) {
|
|
518
|
+
warnings?.push(`unowned historical event ${event.type} for task ${event.taskId} workerRunId ${event.workerRunId} cannot uniquely resolve featureId; excluded from historical run map`);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
runMap.set(event.workerRunId, applyEventToSummary({
|
|
369
522
|
taskId: event.taskId,
|
|
523
|
+
featureId,
|
|
370
524
|
workerRunId: event.workerRunId,
|
|
371
525
|
status: "unknown",
|
|
372
|
-
};
|
|
373
|
-
runMap.set(event.workerRunId, applyEventToSummary(existing, event));
|
|
526
|
+
}, event));
|
|
374
527
|
}
|
|
375
528
|
return runMap;
|
|
376
529
|
}
|
|
@@ -512,7 +665,7 @@ function buildBatches(ledgerBatches, events, taskMap) {
|
|
|
512
665
|
batch.tasks.push(toPublicTaskSummary(task));
|
|
513
666
|
}
|
|
514
667
|
for (const batch of batchMap.values()) {
|
|
515
|
-
batch.tasks.sort(
|
|
668
|
+
batch.tasks.sort(compareTaskIdentity);
|
|
516
669
|
if (batch.summary === undefined && batch.tasks.length > 0) {
|
|
517
670
|
batch.summary = summarizeTasks(batch.tasks);
|
|
518
671
|
}
|
|
@@ -784,6 +937,7 @@ function mapStateStatus(status) {
|
|
|
784
937
|
case "Running":
|
|
785
938
|
return "running";
|
|
786
939
|
case "Queued":
|
|
940
|
+
case "Ready":
|
|
787
941
|
return "pending";
|
|
788
942
|
case "Done":
|
|
789
943
|
return "succeeded";
|
|
@@ -973,21 +1127,71 @@ async function loadLedgerBatches(repoRoot) {
|
|
|
973
1127
|
}
|
|
974
1128
|
async function loadStateRecords(repoRoot) {
|
|
975
1129
|
const states = [];
|
|
976
|
-
const
|
|
1130
|
+
const legacyCandidates = [];
|
|
1131
|
+
const warnings = [];
|
|
1132
|
+
// Primary path: v2 nested states via pool list API (read-only import).
|
|
977
1133
|
try {
|
|
978
|
-
const
|
|
979
|
-
for (const
|
|
980
|
-
if (!
|
|
1134
|
+
const v2 = await listTaskPoolStates(repoRoot);
|
|
1135
|
+
for (const record of v2) {
|
|
1136
|
+
if (!record.taskId || !record.status)
|
|
981
1137
|
continue;
|
|
982
|
-
|
|
983
|
-
|
|
1138
|
+
if (!record.featureId) {
|
|
1139
|
+
warnings.push(`v2 state for task ${record.taskId} missing featureId; excluded from composite projection`);
|
|
984
1140
|
continue;
|
|
985
|
-
|
|
1141
|
+
}
|
|
1142
|
+
states.push({
|
|
1143
|
+
taskId: record.taskId,
|
|
1144
|
+
featureId: record.featureId,
|
|
1145
|
+
status: record.status,
|
|
1146
|
+
workerRunId: record.workerRunId,
|
|
1147
|
+
lastRunRecordPath: record.lastRunRecordPath,
|
|
1148
|
+
updatedAt: record.updatedAt,
|
|
1149
|
+
failureCategory: record.failure?.category,
|
|
1150
|
+
recommendedFollowUp: record.failure?.recommendedFollowUpKind,
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
catch (error) {
|
|
1155
|
+
warnings.push(`failed to load feature-scoped task pool states: ${error instanceof Error ? error.message : String(error)}`);
|
|
1156
|
+
}
|
|
1157
|
+
// Compatibility: legacy flat states/<taskId>.json (warning + unique-only merge later).
|
|
1158
|
+
try {
|
|
1159
|
+
const legacyFiles = await listLegacyStateFiles(repoRoot);
|
|
1160
|
+
for (const file of legacyFiles) {
|
|
1161
|
+
const parsed = await safeReadJson(file.path);
|
|
1162
|
+
if (!parsed) {
|
|
1163
|
+
warnings.push(`legacy flat state file unreadable or corrupt: ${file.path}`);
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
const taskId = readString(parsed, "taskId") ?? file.taskId;
|
|
986
1167
|
const status = readString(parsed, "status");
|
|
987
|
-
if (!status)
|
|
1168
|
+
if (!taskId || !status) {
|
|
1169
|
+
warnings.push(`legacy flat state missing taskId/status: ${file.path}`);
|
|
988
1170
|
continue;
|
|
1171
|
+
}
|
|
989
1172
|
const failure = readObject(parsed, "failure");
|
|
990
|
-
|
|
1173
|
+
const embeddedFeatureId = readString(parsed, "featureId");
|
|
1174
|
+
if (embeddedFeatureId) {
|
|
1175
|
+
// Rare: flat file already carries featureId — treat as owned state.
|
|
1176
|
+
states.push({
|
|
1177
|
+
taskId,
|
|
1178
|
+
featureId: embeddedFeatureId,
|
|
1179
|
+
status,
|
|
1180
|
+
workerRunId: readString(parsed, "workerRunId"),
|
|
1181
|
+
lastRunRecordPath: readString(parsed, "lastRunRecordPath"),
|
|
1182
|
+
updatedAt: readString(parsed, "updatedAt"),
|
|
1183
|
+
failureCategory: failure
|
|
1184
|
+
? readString(failure, "category")
|
|
1185
|
+
: undefined,
|
|
1186
|
+
recommendedFollowUp: failure
|
|
1187
|
+
? readString(failure, "recommendedFollowUpKind")
|
|
1188
|
+
: undefined,
|
|
1189
|
+
legacyFlat: true,
|
|
1190
|
+
});
|
|
1191
|
+
warnings.push(`legacy flat state for task ${taskId} carries featureId ${embeddedFeatureId}`);
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
legacyCandidates.push({
|
|
991
1195
|
taskId,
|
|
992
1196
|
status,
|
|
993
1197
|
workerRunId: readString(parsed, "workerRunId"),
|
|
@@ -997,13 +1201,14 @@ async function loadStateRecords(repoRoot) {
|
|
|
997
1201
|
recommendedFollowUp: failure
|
|
998
1202
|
? readString(failure, "recommendedFollowUpKind")
|
|
999
1203
|
: undefined,
|
|
1204
|
+
legacyFlat: true,
|
|
1000
1205
|
});
|
|
1001
1206
|
}
|
|
1002
1207
|
}
|
|
1003
|
-
catch {
|
|
1004
|
-
|
|
1208
|
+
catch (error) {
|
|
1209
|
+
warnings.push(`failed to scan legacy flat states: ${error instanceof Error ? error.message : String(error)}`);
|
|
1005
1210
|
}
|
|
1006
|
-
return states;
|
|
1211
|
+
return { states, legacyCandidates, warnings };
|
|
1007
1212
|
}
|
|
1008
1213
|
async function loadFailureHandoffs(repoRoot) {
|
|
1009
1214
|
const handoffs = [];
|