@tea-agent/loop-agent 0.7.5 → 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.
Files changed (127) hide show
  1. package/AGENTS.md +143 -142
  2. package/CHANGELOG.md +148 -164
  3. package/README.md +206 -204
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/commands/init.js +518 -488
  7. package/dist/commands/loop-benchmark.js +11 -11
  8. package/dist/commands/pi-reuse-benchmark.js +16 -16
  9. package/dist/executors/cursor-executor.js +1 -1
  10. package/dist/governance/manifest-types.js +1 -1
  11. package/dist/task/runtime.js +27 -27
  12. package/dist/worker/cli.js +3 -3
  13. package/dist/worker/observability/event-store.js +2 -1
  14. package/dist/worker/observability/read-model.js +13 -11
  15. package/dist/worker/observe/paths.js +2 -2
  16. package/dist/worker/observe/routes.js +4 -3
  17. package/dist/worker/observe/static/app.js +1479 -1480
  18. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  19. package/dist/worker/observe/static/dag-layout.js +83 -83
  20. package/dist/worker/observe/static/index.html +63 -63
  21. package/dist/worker/observe/static/styles.css +722 -722
  22. package/dist/worker/pool/run-store.js +7 -8
  23. package/dist/worker/run-task/run-task.js +11 -2
  24. package/dist/worker/runner/run-ready.js +1 -1
  25. package/dist/workflows/dag/canvas-observer.js +275 -275
  26. package/docs/README.md +80 -79
  27. package/docs/agent-dag-recovery-playbook.md +184 -184
  28. package/docs/agent-dag-runner.md +42 -42
  29. package/docs/architecture/runtime-boundaries.md +162 -162
  30. package/docs/cursor-executor-usage.md +25 -25
  31. package/docs/decisions/README.md +3 -3
  32. package/docs/design/README.md +49 -49
  33. package/docs/development-principles.md +73 -73
  34. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
  35. package/docs/exec-plans/README.md +6 -6
  36. package/docs/exec-plans/active/README.md +11 -11
  37. package/docs/exec-plans/completed/README.md +35 -34
  38. package/docs/feature-workflow.md +187 -187
  39. package/docs/harness-methodology-debugging.md +153 -153
  40. package/docs/harness-methodology-tdd.md +130 -130
  41. package/docs/harness-methodology-verification.md +27 -27
  42. package/docs/init-surface.manifest.json +245 -241
  43. package/docs/loop-agent-harness.md +63 -55
  44. package/docs/production-readiness.md +96 -96
  45. package/docs/progress/README.md +3 -3
  46. package/docs/reports/README.md +9 -9
  47. package/docs/skills/README.md +6 -6
  48. package/docs/skills/vetted-skill-registry.md +26 -26
  49. package/docs/templates/adr.md +60 -60
  50. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  51. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  52. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  53. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  54. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  55. package/docs/templates/agent-dag-report.schema.json +454 -454
  56. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  57. package/docs/templates/agent-dag.base.json +195 -195
  58. package/docs/templates/agent-dag.final-verification.json +190 -190
  59. package/docs/templates/agent-dag.schema.json +316 -316
  60. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  61. package/docs/templates/exec-plan.md +64 -64
  62. package/docs/templates/feature-spec.md +53 -53
  63. package/docs/templates/harness.schema.json +218 -0
  64. package/docs/templates/hybrid-dag.json +193 -193
  65. package/docs/templates/init-evolution-review.md +33 -33
  66. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  67. package/docs/templates/product-line/AGENTS.md +8 -8
  68. package/docs/templates/product-line/README.md +9 -9
  69. package/docs/templates/product-line/acceptance.yaml +14 -14
  70. package/docs/templates/product-line/closeout.yaml +9 -9
  71. package/docs/templates/product-line/design.md +13 -13
  72. package/docs/templates/product-line/links.md +10 -10
  73. package/docs/templates/product-line/requirement.md +17 -17
  74. package/docs/templates/product-line/task-graph.yaml +15 -15
  75. package/docs/templates/product-line/task.yaml +65 -65
  76. package/docs/templates/product-line/test-plan.md +7 -7
  77. package/docs/templates/production-readiness-checklist.md +57 -57
  78. package/docs/templates/progress-log.md +17 -17
  79. package/docs/templates/project-start-checklist.md +9 -9
  80. package/docs/templates/qa-report.md +48 -48
  81. package/docs/templates/sprint-contract.md +29 -29
  82. package/docs/templates/worker-dogfood-evidence.md +52 -52
  83. package/docs/templates/worker-dogfood-setup.md +48 -48
  84. package/docs/verification-matrix.md +49 -49
  85. package/examples/decision-gate-agent-dag.json +123 -123
  86. package/examples/example-dag.json +51 -51
  87. package/examples/hybrid-loop-agent-dag.json +194 -194
  88. package/harness.json +73 -71
  89. package/package.json +68 -67
  90. package/scripts/check-product-line-docs.sh +22 -22
  91. package/scripts/check-task-pool-root.sh +32 -0
  92. package/skills/ai-engineering-context/SKILL.md +48 -48
  93. package/skills/code-review-core/SKILL.md +20 -20
  94. package/skills/codebase-scout/SKILL.md +19 -19
  95. package/skills/init-capability-evolution/SKILL.md +69 -69
  96. package/skills/loop-agent/SKILL.md +149 -149
  97. package/skills/loop-agent/references/README.md +67 -67
  98. package/skills/loop-agent/references/command-reference.md +432 -412
  99. package/skills/loop-agent/references/harness-policy.md +263 -263
  100. package/skills/loop-agent/references/hybrid-dag.md +216 -216
  101. package/skills/loop-agent/references/learned/README.md +21 -21
  102. package/skills/loop-agent/references/long-running-loop.md +59 -59
  103. package/skills/loop-agent/references/model-routing.md +36 -36
  104. package/skills/loop-agent/references/multi-worktree.md +54 -54
  105. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  106. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  107. package/skills/loop-agent/references/pi-prompt.md +23 -23
  108. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
  109. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  110. package/skills/loop-agent/references/task-workflow.md +89 -89
  111. package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
  112. package/skills/requesting-code-review/SKILL.md +101 -101
  113. package/skills/requesting-code-review/code-reviewer.md +168 -168
  114. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  115. package/skills/systematic-debugging/SKILL.md +296 -296
  116. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  117. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  118. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  119. package/skills/systematic-debugging/find-polluter.sh +63 -63
  120. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  121. package/skills/systematic-debugging/test-academic.md +14 -14
  122. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  123. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  124. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  125. package/skills/test-driven-development/SKILL.md +20 -20
  126. package/skills/verification-before-completion/SKILL.md +154 -154
  127. 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", "level-1"])
49
+ .enum(["agent-dag"])
50
50
  .optional()
51
51
  .default("agent-dag"),
52
52
  dag: z
@@ -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;
@@ -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, ".task-pool", "artifacts", batchRunId),
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, ".task-pool", "reports", "morning-report.md");
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), ".task-pool", "observability");
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, ".task-pool", "observability");
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, ".task-pool", "runs", "runs.jsonl"));
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 runsDir = path.join(repoRoot, ".task-pool", "runs");
557
+ const artifactsDir = path.join(getTaskPoolRoot(repoRoot), "artifacts");
557
558
  try {
558
- const entries = await readdir(runsDir, { withFileTypes: true });
559
+ const entries = await readdir(artifactsDir, { withFileTypes: true });
559
560
  for (const entry of entries) {
560
- if (!entry.isFile() || !entry.name.endsWith(".json") || entry.name === "runs.jsonl")
561
+ if (!entry.isDirectory())
561
562
  continue;
562
- const batchRunId = entry.name.replace(/\.json$/, "");
563
- const parsed = await safeReadJson(path.join(runsDir, entry.name));
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: path.join(runsDir, entry.name),
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, ".task-pool", "state");
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, ".task-pool", "failure-handoffs");
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) {
@@ -736,7 +738,7 @@ function nodeErrorPreview(node, status) {
736
738
  return truncateUtf8Preview(stderr);
737
739
  }
738
740
  async function loadDagEventFiles(repoRoot) {
739
- const runsDir = path.join(repoRoot, ".task-pool", "observability", "runs");
741
+ const runsDir = path.join(getTaskPoolRoot(repoRoot), "observability", "runs");
740
742
  let entries;
741
743
  try {
742
744
  entries = await readdir(runsDir, { withFileTypes: true });
@@ -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[1] ?? "");
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, ".task-pool", "observability", "runs", workerRunId, "events.jsonl");
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, ".task-pool", "observability", "runs", workerRunId, "events.jsonl");
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, ".task-pool", "observability");
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) => {