@tea-agent/loop-agent 0.7.4 → 0.8.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 +143 -142
- package/CHANGELOG.md +148 -161
- package/README.md +206 -204
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/commands/init.js +518 -488
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/cursor-executor.js +1 -1
- package/dist/governance/manifest-types.js +1 -1
- package/dist/task/runtime.js +27 -27
- package/dist/worker/cli.js +3 -3
- package/dist/worker/observability/event-store.js +2 -1
- package/dist/worker/observability/read-model.js +51 -13
- package/dist/worker/observe/paths.js +2 -2
- package/dist/worker/observe/routes.js +4 -3
- package/dist/worker/observe/static/app.js +1479 -1419
- package/dist/worker/observe/static/dag-layout.d.ts +31 -0
- package/dist/worker/observe/static/dag-layout.js +83 -0
- package/dist/worker/observe/static/index.html +63 -63
- package/dist/worker/observe/static/styles.css +722 -613
- package/dist/worker/pool/run-store.js +7 -8
- package/dist/worker/run-task/run-task.js +11 -2
- package/dist/worker/runner/run-ready.js +1 -1
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/docs/README.md +80 -76
- package/docs/agent-dag-recovery-playbook.md +184 -184
- package/docs/agent-dag-runner.md +42 -42
- package/docs/architecture/runtime-boundaries.md +162 -162
- package/docs/cursor-executor-usage.md +25 -25
- package/docs/decisions/README.md +3 -3
- package/docs/design/README.md +49 -49
- package/docs/development-principles.md +73 -73
- package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
- package/docs/exec-plans/README.md +6 -6
- package/docs/exec-plans/active/README.md +11 -12
- package/docs/exec-plans/completed/README.md +35 -32
- package/docs/feature-workflow.md +187 -187
- 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 +245 -241
- package/docs/loop-agent-harness.md +63 -55
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +3 -3
- package/docs/reports/README.md +9 -9
- package/docs/skills/README.md +6 -6
- package/docs/skills/vetted-skill-registry.md +26 -26
- 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 +454 -454
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/agent-dag.base.json +195 -195
- package/docs/templates/agent-dag.final-verification.json +190 -190
- package/docs/templates/agent-dag.schema.json +316 -316
- package/docs/templates/agent-dag.supervised-implementation.json +500 -500
- package/docs/templates/exec-plan.md +64 -64
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/harness.schema.json +218 -0
- package/docs/templates/hybrid-dag.json +193 -193
- package/docs/templates/init-evolution-review.md +33 -33
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- 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 +65 -65
- 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 +52 -52
- package/docs/templates/worker-dogfood-setup.md +48 -48
- package/docs/verification-matrix.md +49 -49
- package/examples/decision-gate-agent-dag.json +123 -123
- package/examples/example-dag.json +51 -51
- package/examples/hybrid-loop-agent-dag.json +194 -194
- package/harness.json +73 -71
- package/package.json +68 -67
- package/scripts/check-product-line-docs.sh +22 -22
- package/scripts/check-task-pool-root.sh +32 -0
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/init-capability-evolution/SKILL.md +69 -69
- package/skills/loop-agent/SKILL.md +149 -149
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +432 -412
- package/skills/loop-agent/references/harness-policy.md +263 -263
- package/skills/loop-agent/references/hybrid-dag.md +216 -216
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +59 -59
- 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 +81 -81
- 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 +128 -128
- 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/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
|
@@ -37,17 +37,17 @@ export function parseLoopBenchmarkArgs(args) {
|
|
|
37
37
|
return { json, markdown, outputPath };
|
|
38
38
|
}
|
|
39
39
|
export function printLoopBenchmarkUsage() {
|
|
40
|
-
console.log(`usage: loop-benchmark [options]
|
|
41
|
-
|
|
42
|
-
Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
|
|
43
|
-
|
|
44
|
-
Options:
|
|
45
|
-
--json Emit JSON (default when no format flag is set)
|
|
46
|
-
--markdown Emit Markdown report
|
|
47
|
-
--output <path> Write Markdown report to a repo-relative or absolute path
|
|
48
|
-
-h, --help Show this help
|
|
49
|
-
|
|
50
|
-
Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
|
|
40
|
+
console.log(`usage: loop-benchmark [options]
|
|
41
|
+
|
|
42
|
+
Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
--json Emit JSON (default when no format flag is set)
|
|
46
|
+
--markdown Emit Markdown report
|
|
47
|
+
--output <path> Write Markdown report to a repo-relative or absolute path
|
|
48
|
+
-h, --help Show this help
|
|
49
|
+
|
|
50
|
+
Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
|
|
51
51
|
Recommendation never changes convergence.enabled default.`);
|
|
52
52
|
}
|
|
53
53
|
export async function runLoopBenchmark(repoRoot, rawArgs) {
|
|
@@ -106,22 +106,22 @@ export function parsePiReuseBenchmarkArgs(args) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
export function printPiReuseBenchmarkUsage() {
|
|
109
|
-
console.log(`usage: pi-reuse-benchmark [options]
|
|
110
|
-
|
|
111
|
-
Deterministic Pi runtime reuse benchmark/decision summary (no live Pi calls).
|
|
112
|
-
|
|
113
|
-
Options:
|
|
114
|
-
--report <path> Benchmark report markdown (approval status)
|
|
115
|
-
--approval <path> Explicit approval JSON artifact
|
|
116
|
-
--off-executor <path> Baseline executor.jsonl (reuse off)
|
|
117
|
-
--on-executor <path> Treatment executor.jsonl (reuse on)
|
|
118
|
-
--off-task <task-id> Resolve baseline from .harness/tasks/<id>/logs/executor.jsonl
|
|
119
|
-
--on-task <task-id> Resolve treatment from .harness/tasks/<id>/logs/executor.jsonl
|
|
120
|
-
--json Emit JSON (default when no format flag is set)
|
|
121
|
-
--markdown Emit Markdown summary
|
|
122
|
-
-h, --help Show this help
|
|
123
|
-
|
|
124
|
-
Recommendations: defer | maintain-opt-in | eligible-for-human-review
|
|
109
|
+
console.log(`usage: pi-reuse-benchmark [options]
|
|
110
|
+
|
|
111
|
+
Deterministic Pi runtime reuse benchmark/decision summary (no live Pi calls).
|
|
112
|
+
|
|
113
|
+
Options:
|
|
114
|
+
--report <path> Benchmark report markdown (approval status)
|
|
115
|
+
--approval <path> Explicit approval JSON artifact
|
|
116
|
+
--off-executor <path> Baseline executor.jsonl (reuse off)
|
|
117
|
+
--on-executor <path> Treatment executor.jsonl (reuse on)
|
|
118
|
+
--off-task <task-id> Resolve baseline from .harness/tasks/<id>/logs/executor.jsonl
|
|
119
|
+
--on-task <task-id> Resolve treatment from .harness/tasks/<id>/logs/executor.jsonl
|
|
120
|
+
--json Emit JSON (default when no format flag is set)
|
|
121
|
+
--markdown Emit Markdown summary
|
|
122
|
+
-h, --help Show this help
|
|
123
|
+
|
|
124
|
+
Recommendations: defer | maintain-opt-in | eligible-for-human-review
|
|
125
125
|
Never changes CODE_AGENT_PI_REUSE_RUNTIME default (off).`);
|
|
126
126
|
}
|
|
127
127
|
function resolveRepoRelative(repoRoot, filePath) {
|
|
@@ -29,7 +29,7 @@ export function resolveArtifactWriteDir(options) {
|
|
|
29
29
|
}
|
|
30
30
|
export function buildArtifactPathPrompt(writeDir) {
|
|
31
31
|
if (!writeDir)
|
|
32
|
-
return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
|
|
32
|
+
return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
|
|
33
33
|
${ARTIFACT_INSTRUCTIONS}`;
|
|
34
34
|
return [
|
|
35
35
|
`After changes, write the following files:`,
|
|
@@ -46,7 +46,7 @@ export const workflowPolicySchema = z
|
|
|
46
46
|
.object({
|
|
47
47
|
/** Preferred implementation workflow for recoverable loop-agent work. Declarative policy; callers may still require explicit CLI flags. */
|
|
48
48
|
defaultImplementationWorkflow: z
|
|
49
|
-
.enum(["agent-dag"
|
|
49
|
+
.enum(["agent-dag"])
|
|
50
50
|
.optional()
|
|
51
51
|
.default("agent-dag"),
|
|
52
52
|
dag: z
|
package/dist/task/runtime.js
CHANGED
|
@@ -237,35 +237,35 @@ export function shouldInjectSubagentGuidance(step, mode) {
|
|
|
237
237
|
return false;
|
|
238
238
|
}
|
|
239
239
|
/** Advisory guidance for `analyze-plan` mode. */
|
|
240
|
-
export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
|
|
241
|
-
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
242
|
-
Use it only for read-only tasks:
|
|
243
|
-
- Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
|
|
244
|
-
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
245
|
-
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
246
|
-
Do NOT use subagent for writing, editing, or executing commands.
|
|
247
|
-
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
248
|
-
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
240
|
+
export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
|
|
241
|
+
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
242
|
+
Use it only for read-only tasks:
|
|
243
|
+
- Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
|
|
244
|
+
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
245
|
+
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
246
|
+
Do NOT use subagent for writing, editing, or executing commands.
|
|
247
|
+
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
248
|
+
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
249
249
|
</subagent_guidance>`;
|
|
250
250
|
/** Strong guidance for `full` mode — prescriptive when to delegate. */
|
|
251
|
-
export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
|
|
252
|
-
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
253
|
-
|
|
254
|
-
You SHOULD delegate to subagent scouts when:
|
|
255
|
-
- The task requires scanning 3+ directories or comparing implementations across modules
|
|
256
|
-
- You would otherwise need 5+ sequential read/grep calls to gather context
|
|
257
|
-
- A reviewer subagent can independently catch scope drift before you finalize your output
|
|
258
|
-
|
|
259
|
-
Delegation saves context tokens and produces better results.
|
|
260
|
-
|
|
261
|
-
Allowed patterns:
|
|
262
|
-
- Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
|
|
263
|
-
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
264
|
-
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
265
|
-
|
|
266
|
-
Do NOT use subagent for writing, editing, or executing commands.
|
|
267
|
-
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
268
|
-
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
251
|
+
export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
|
|
252
|
+
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
253
|
+
|
|
254
|
+
You SHOULD delegate to subagent scouts when:
|
|
255
|
+
- The task requires scanning 3+ directories or comparing implementations across modules
|
|
256
|
+
- You would otherwise need 5+ sequential read/grep calls to gather context
|
|
257
|
+
- A reviewer subagent can independently catch scope drift before you finalize your output
|
|
258
|
+
|
|
259
|
+
Delegation saves context tokens and produces better results.
|
|
260
|
+
|
|
261
|
+
Allowed patterns:
|
|
262
|
+
- Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
|
|
263
|
+
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
264
|
+
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
265
|
+
|
|
266
|
+
Do NOT use subagent for writing, editing, or executing commands.
|
|
267
|
+
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
268
|
+
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
269
269
|
</subagent_guidance>`;
|
|
270
270
|
/** Preserved for backward compatibility (alias of STANDARD). */
|
|
271
271
|
export const SUBAGENT_GUIDANCE = SUBAGENT_GUIDANCE_STANDARD;
|
package/dist/worker/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createCompositeProgressReporter } from "./observability/progress-compos
|
|
|
13
13
|
import { createRoutedWorkerEventStore } from "./observability/event-store.js";
|
|
14
14
|
import { buildGlobalSnapshot } from "./observability/read-model.js";
|
|
15
15
|
import { createObserveServer } from "./observe/server.js";
|
|
16
|
-
import { prepareTaskPoolRetry } from "./pool/run-store.js";
|
|
16
|
+
import { getTaskPoolRoot, prepareTaskPoolRetry } from "./pool/run-store.js";
|
|
17
17
|
import { taskSpecSchema } from "./task-spec/schema.js";
|
|
18
18
|
import { validateTaskSpec } from "./task-spec/validate.js";
|
|
19
19
|
import { validateFeatureTaskGraph } from "./task-graph/validate.js";
|
|
@@ -91,7 +91,7 @@ export function buildAgentWorkerProgram() {
|
|
|
91
91
|
const batchRunId = options.batchRunId ?? buildBatchRunId(new Date());
|
|
92
92
|
const client = new LoopAgentClient({
|
|
93
93
|
loopAgentBin: options.loopAgentBin,
|
|
94
|
-
artifactRoot: path.join(repoRoot, "
|
|
94
|
+
artifactRoot: path.join(getTaskPoolRoot(repoRoot), "artifacts", batchRunId),
|
|
95
95
|
});
|
|
96
96
|
let progress;
|
|
97
97
|
try {
|
|
@@ -161,7 +161,7 @@ export function buildAgentWorkerProgram() {
|
|
|
161
161
|
.action(async (options) => {
|
|
162
162
|
const repoRoot = path.resolve(options.repo);
|
|
163
163
|
const outputPath = options.output ??
|
|
164
|
-
path.join(repoRoot, "
|
|
164
|
+
path.join(getTaskPoolRoot(repoRoot), "reports", "morning-report.md");
|
|
165
165
|
await writeMorningReport({
|
|
166
166
|
repoRoot,
|
|
167
167
|
...(options.batchRunId ? { batchRunId: options.batchRunId } : {}),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
4
5
|
function writeDiagnostic(context, error) {
|
|
5
6
|
const message = error instanceof Error ? error.message : String(error);
|
|
6
7
|
process.stderr.write(`[worker-event-store] ${context}: ${message}\n`);
|
|
@@ -43,7 +44,7 @@ export function createWorkerEventStore(jsonlPath) {
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
export function createRoutedWorkerEventStore(repoRoot) {
|
|
46
|
-
const observabilityRoot = path.join(path.resolve(repoRoot), "
|
|
47
|
+
const observabilityRoot = path.join(getTaskPoolRoot(path.resolve(repoRoot)), "observability");
|
|
47
48
|
const globalStore = createWorkerEventStore(path.join(observabilityRoot, "events.jsonl"));
|
|
48
49
|
let queue = Promise.resolve();
|
|
49
50
|
function storesFor(event) {
|
|
@@ -3,6 +3,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { truncateUtf8Preview } from "../../shared/preview.js";
|
|
5
5
|
import { parseWorkerEventLine } from "./events.js";
|
|
6
|
+
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
6
7
|
const ACTIVE_TASK_STATUSES = new Set(["running", "pending"]);
|
|
7
8
|
const ACTIVE_BATCH_STATUSES = new Set(["running", "pending"]);
|
|
8
9
|
export async function buildGlobalSnapshot(options) {
|
|
@@ -474,7 +475,7 @@ function mapBatchStatus(status) {
|
|
|
474
475
|
}
|
|
475
476
|
async function loadObservabilityEvents(repoRoot) {
|
|
476
477
|
const events = [];
|
|
477
|
-
const obsRoot = path.join(repoRoot, "
|
|
478
|
+
const obsRoot = path.join(getTaskPoolRoot(repoRoot), "observability");
|
|
478
479
|
await appendJsonlEvents(path.join(obsRoot, "events.jsonl"), events);
|
|
479
480
|
const batchesDir = path.join(obsRoot, "batches");
|
|
480
481
|
await forEachSubdirJsonl(batchesDir, "events.jsonl", events);
|
|
@@ -510,7 +511,7 @@ async function appendJsonlEvents(filePath, events) {
|
|
|
510
511
|
}
|
|
511
512
|
async function loadLedgerRuns(repoRoot) {
|
|
512
513
|
const runs = [];
|
|
513
|
-
const raw = await safeReadText(path.join(repoRoot, "
|
|
514
|
+
const raw = await safeReadText(path.join(getTaskPoolRoot(repoRoot), "runs.jsonl"));
|
|
514
515
|
if (!raw)
|
|
515
516
|
return runs;
|
|
516
517
|
for (const line of raw.split("\n")) {
|
|
@@ -553,14 +554,15 @@ async function loadLedgerRuns(repoRoot) {
|
|
|
553
554
|
}
|
|
554
555
|
async function loadLedgerBatches(repoRoot) {
|
|
555
556
|
const batches = [];
|
|
556
|
-
const
|
|
557
|
+
const artifactsDir = path.join(getTaskPoolRoot(repoRoot), "artifacts");
|
|
557
558
|
try {
|
|
558
|
-
const entries = await readdir(
|
|
559
|
+
const entries = await readdir(artifactsDir, { withFileTypes: true });
|
|
559
560
|
for (const entry of entries) {
|
|
560
|
-
if (!entry.
|
|
561
|
+
if (!entry.isDirectory())
|
|
561
562
|
continue;
|
|
562
|
-
const batchRunId = entry.name
|
|
563
|
-
const
|
|
563
|
+
const batchRunId = entry.name;
|
|
564
|
+
const batchRunPath = path.join(artifactsDir, batchRunId, "batch-run.json");
|
|
565
|
+
const parsed = await safeReadJson(batchRunPath);
|
|
564
566
|
if (!parsed)
|
|
565
567
|
continue;
|
|
566
568
|
const summaryObj = readObject(parsed, "summary");
|
|
@@ -579,7 +581,7 @@ async function loadLedgerBatches(repoRoot) {
|
|
|
579
581
|
}
|
|
580
582
|
: undefined,
|
|
581
583
|
tasks: [],
|
|
582
|
-
batchRunPath
|
|
584
|
+
batchRunPath,
|
|
583
585
|
});
|
|
584
586
|
}
|
|
585
587
|
}
|
|
@@ -590,7 +592,7 @@ async function loadLedgerBatches(repoRoot) {
|
|
|
590
592
|
}
|
|
591
593
|
async function loadStateRecords(repoRoot) {
|
|
592
594
|
const states = [];
|
|
593
|
-
const stateDir = path.join(repoRoot, "
|
|
595
|
+
const stateDir = path.join(getTaskPoolRoot(repoRoot), "states");
|
|
594
596
|
try {
|
|
595
597
|
const entries = await readdir(stateDir, { withFileTypes: true });
|
|
596
598
|
for (const entry of entries) {
|
|
@@ -621,7 +623,7 @@ async function loadStateRecords(repoRoot) {
|
|
|
621
623
|
}
|
|
622
624
|
async function loadFailureHandoffs(repoRoot) {
|
|
623
625
|
const handoffs = [];
|
|
624
|
-
const handoffDir = path.join(repoRoot, "
|
|
626
|
+
const handoffDir = path.join(getTaskPoolRoot(repoRoot), "failure-handoffs");
|
|
625
627
|
try {
|
|
626
628
|
const entries = await readdir(handoffDir, { withFileTypes: true });
|
|
627
629
|
for (const entry of entries) {
|
|
@@ -645,7 +647,7 @@ async function loadDagRuns(repoRoot) {
|
|
|
645
647
|
const byId = new Map();
|
|
646
648
|
const statePaths = await findStateJsonFiles(path.join(repoRoot, ".harness", "dag-runs"));
|
|
647
649
|
for (const statePath of statePaths) {
|
|
648
|
-
const summary = parseDagStateFile(statePath);
|
|
650
|
+
const summary = await parseDagStateFile(statePath);
|
|
649
651
|
if (summary)
|
|
650
652
|
byId.set(summary.dagRunId, mergeDagRun(byId.get(summary.dagRunId), summary));
|
|
651
653
|
}
|
|
@@ -674,6 +676,7 @@ function mergeDagRun(existing, incoming) {
|
|
|
674
676
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
675
677
|
...(ranks && ranks.length > 0 ? { ranks } : {}),
|
|
676
678
|
nodes: mergeDagNodes(existing.nodes, incoming.nodes),
|
|
679
|
+
edges: incoming.edges.length > 0 ? incoming.edges : existing.edges,
|
|
677
680
|
...(incoming.dagPath ?? existing.dagPath ? { dagPath: incoming.dagPath ?? existing.dagPath } : {}),
|
|
678
681
|
};
|
|
679
682
|
}
|
|
@@ -735,7 +738,7 @@ function nodeErrorPreview(node, status) {
|
|
|
735
738
|
return truncateUtf8Preview(stderr);
|
|
736
739
|
}
|
|
737
740
|
async function loadDagEventFiles(repoRoot) {
|
|
738
|
-
const runsDir = path.join(repoRoot, "
|
|
741
|
+
const runsDir = path.join(getTaskPoolRoot(repoRoot), "observability", "runs");
|
|
739
742
|
let entries;
|
|
740
743
|
try {
|
|
741
744
|
entries = await readdir(runsDir, { withFileTypes: true });
|
|
@@ -819,6 +822,7 @@ async function loadDagEventFiles(repoRoot) {
|
|
|
819
822
|
...(finishedAt ? { finishedAt } : {}),
|
|
820
823
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
821
824
|
nodes: [...nodeMap.values()].sort((a, b) => a.nodeId.localeCompare(b.nodeId)),
|
|
825
|
+
edges: [],
|
|
822
826
|
});
|
|
823
827
|
}
|
|
824
828
|
return result;
|
|
@@ -845,7 +849,7 @@ async function walkForStateJson(dir, results) {
|
|
|
845
849
|
// skip
|
|
846
850
|
}
|
|
847
851
|
}
|
|
848
|
-
function parseDagStateFile(statePath) {
|
|
852
|
+
async function parseDagStateFile(statePath) {
|
|
849
853
|
try {
|
|
850
854
|
if (!existsSync(statePath))
|
|
851
855
|
return undefined;
|
|
@@ -863,6 +867,7 @@ function parseDagStateFile(statePath) {
|
|
|
863
867
|
const ranks = readStringMatrix(parsed, "ranks");
|
|
864
868
|
const rankByNode = buildRankIndex(ranks);
|
|
865
869
|
const nodes = parseDagNodes(parsed, rankByNode);
|
|
870
|
+
const edges = await parseDagEdges(path.join(runDir, "run.json"), nodes);
|
|
866
871
|
return {
|
|
867
872
|
dagRunId,
|
|
868
873
|
status,
|
|
@@ -872,6 +877,7 @@ function parseDagStateFile(statePath) {
|
|
|
872
877
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
873
878
|
...(ranks.length > 0 ? { ranks } : {}),
|
|
874
879
|
nodes,
|
|
880
|
+
edges,
|
|
875
881
|
dagPath: runDir,
|
|
876
882
|
};
|
|
877
883
|
}
|
|
@@ -879,6 +885,30 @@ function parseDagStateFile(statePath) {
|
|
|
879
885
|
return undefined;
|
|
880
886
|
}
|
|
881
887
|
}
|
|
888
|
+
async function parseDagEdges(runPath, nodes) {
|
|
889
|
+
const parsed = await safeReadJson(runPath);
|
|
890
|
+
if (!parsed)
|
|
891
|
+
return [];
|
|
892
|
+
const tasks = readObjectArray(parsed, "tasks");
|
|
893
|
+
if (tasks.length === 0)
|
|
894
|
+
return [];
|
|
895
|
+
const knownNodeIds = new Set(nodes.map((node) => node.nodeId));
|
|
896
|
+
const edges = new Map();
|
|
897
|
+
for (const task of tasks) {
|
|
898
|
+
const taskId = readString(task, "id") ?? readString(task, "nodeId");
|
|
899
|
+
if (!taskId || !knownNodeIds.has(taskId))
|
|
900
|
+
continue;
|
|
901
|
+
const dependencies = readStringArray(task, "depends_on");
|
|
902
|
+
const compatibleDependencies = dependencies.length > 0 ? dependencies : readStringArray(task, "dependsOn");
|
|
903
|
+
for (const dependencyId of compatibleDependencies) {
|
|
904
|
+
if (dependencyId === taskId || !knownNodeIds.has(dependencyId))
|
|
905
|
+
continue;
|
|
906
|
+
const edge = { from: dependencyId, to: taskId };
|
|
907
|
+
edges.set(`${edge.from}\u0000${edge.to}`, edge);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return [...edges.values()].sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
911
|
+
}
|
|
882
912
|
function buildRankIndex(ranks) {
|
|
883
913
|
const index = new Map();
|
|
884
914
|
for (let i = 0; i < ranks.length; i++) {
|
|
@@ -995,6 +1025,14 @@ function readString(value, key) {
|
|
|
995
1025
|
const child = value[key];
|
|
996
1026
|
return typeof child === "string" ? child : undefined;
|
|
997
1027
|
}
|
|
1028
|
+
function readStringArray(value, key) {
|
|
1029
|
+
if (!value || typeof value !== "object")
|
|
1030
|
+
return [];
|
|
1031
|
+
const child = value[key];
|
|
1032
|
+
if (!Array.isArray(child))
|
|
1033
|
+
return [];
|
|
1034
|
+
return child.filter((item) => typeof item === "string");
|
|
1035
|
+
}
|
|
998
1036
|
function readNumber(value, key) {
|
|
999
1037
|
if (!value || typeof value !== "object")
|
|
1000
1038
|
return undefined;
|
|
@@ -33,8 +33,8 @@ export function resolveArtifactPath(repoRoot, relative) {
|
|
|
33
33
|
function isAllowedArtifactRelativePath(relative) {
|
|
34
34
|
const normalized = path.normalize(relative);
|
|
35
35
|
const segments = normalized.split(path.sep).filter(Boolean);
|
|
36
|
-
if (segments[0] === ".task-pool") {
|
|
37
|
-
return ["artifacts", "failure-handoffs", "reports"].includes(segments[
|
|
36
|
+
if (segments[0] === ".harness" && segments[1] === "task-pool") {
|
|
37
|
+
return ["artifacts", "failure-handoffs", "reports"].includes(segments[2] ?? "");
|
|
38
38
|
}
|
|
39
39
|
if (segments[0] === ".harness" && segments[1] === "tasks") {
|
|
40
40
|
return segments.includes("artifacts");
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { parseWorkerEventLine } from "../observability/events.js";
|
|
5
5
|
import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
|
|
6
6
|
import { buildGlobalSnapshot } from "../observability/read-model.js";
|
|
7
|
+
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
7
8
|
import { resolveArtifactPath, toRepoRelativeArtifactPath, } from "./paths.js";
|
|
8
9
|
const ROUTES = [
|
|
9
10
|
{ method: "GET", pattern: /^\/api\/health$/, handler: handleHealth },
|
|
@@ -148,7 +149,7 @@ async function handleRunEvents(_req, res, match, ctx) {
|
|
|
148
149
|
return;
|
|
149
150
|
}
|
|
150
151
|
const after = parseNonNegativeInt(match.query.get("after"), 0);
|
|
151
|
-
const eventsPath = path.join(ctx.repoRoot, "
|
|
152
|
+
const eventsPath = path.join(getTaskPoolRoot(ctx.repoRoot), "observability", "runs", workerRunId, "events.jsonl");
|
|
152
153
|
const events = (await readJsonlLines(eventsPath, after)).map((event) => normalizeEventArtifactRefs(ctx.repoRoot, event));
|
|
153
154
|
sendJson(res, 200, { events });
|
|
154
155
|
}
|
|
@@ -170,7 +171,7 @@ async function handleRunArtifacts(_req, res, match, ctx) {
|
|
|
170
171
|
artifacts.push({ path: artifactPath, label });
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
|
-
const eventsPath = path.join(ctx.repoRoot, "
|
|
174
|
+
const eventsPath = path.join(getTaskPoolRoot(ctx.repoRoot), "observability", "runs", workerRunId, "events.jsonl");
|
|
174
175
|
const runEvents = await readJsonlLines(eventsPath, 0);
|
|
175
176
|
for (const event of runEvents) {
|
|
176
177
|
const parsed = event;
|
|
@@ -254,7 +255,7 @@ async function handleEventStream(req, res, match, ctx) {
|
|
|
254
255
|
"Cache-Control": "no-cache",
|
|
255
256
|
Connection: "keep-alive",
|
|
256
257
|
});
|
|
257
|
-
const observabilityRoot = path.join(ctx.repoRoot, "
|
|
258
|
+
const observabilityRoot = path.join(getTaskPoolRoot(ctx.repoRoot), "observability");
|
|
258
259
|
const eventsPath = path.join(observabilityRoot, "events.jsonl");
|
|
259
260
|
let offset = 0;
|
|
260
261
|
const sendMatchingEvents = async (initial) => {
|