@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.
Files changed (284) hide show
  1. package/AGENTS.md +155 -153
  2. package/CHANGELOG.md +338 -265
  3. package/README.md +345 -298
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/generate-task-dag.js +28 -28
  7. package/dist/application/evaluation/candidate-hash.js +75 -0
  8. package/dist/application/evaluation/candidate.js +52 -0
  9. package/dist/application/evaluation/replay.js +289 -0
  10. package/dist/application/evaluation/types.js +130 -0
  11. package/dist/cli/command-definitions.js +27 -7
  12. package/dist/cli/program.js +8 -4
  13. package/dist/commands/cursor-prompt.js +6 -6
  14. package/dist/commands/eval.js +235 -0
  15. package/dist/commands/init.js +544 -506
  16. package/dist/commands/knowledge.js +129 -31
  17. package/dist/commands/loop-benchmark.js +11 -11
  18. package/dist/commands/pi-reuse-benchmark.js +16 -16
  19. package/dist/executors/pi-sdk-executor.js +38 -24
  20. package/dist/executors/shell-executor.js +34 -2
  21. package/dist/executors/shell-presets.js +20 -0
  22. package/dist/executors/shell-verification.js +7 -0
  23. package/dist/governance/manifest-types.js +4 -0
  24. package/dist/infrastructure/evaluation/candidate-store.js +435 -0
  25. package/dist/infrastructure/evaluation/store.js +40 -0
  26. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  27. package/dist/task/config-types.js +28 -1
  28. package/dist/task/runtime.js +27 -27
  29. package/dist/worker/cli.js +96 -1
  30. package/dist/worker/delivery/package.js +3 -3
  31. package/dist/worker/feature/decision-loader.js +37 -6
  32. package/dist/worker/feature/next-action.js +10 -2
  33. package/dist/worker/feature/ready-plan-projection.js +81 -0
  34. package/dist/worker/feature/reducer.js +2 -1
  35. package/dist/worker/feature/review.js +19 -2
  36. package/dist/worker/feature/run.js +27 -2
  37. package/dist/worker/follow-up/approve.js +5 -2
  38. package/dist/worker/follow-up/factory.js +1 -1
  39. package/dist/worker/observability/read-model.js +246 -41
  40. package/dist/worker/observe/routes.js +173 -15
  41. package/dist/worker/observe/spec-evidence.js +281 -0
  42. package/dist/worker/observe/static/api.js +46 -27
  43. package/dist/worker/observe/static/app.js +150 -150
  44. package/dist/worker/observe/static/constants.js +148 -148
  45. package/dist/worker/observe/static/copy.js +67 -67
  46. package/dist/worker/observe/static/dag-helpers.js +172 -172
  47. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  48. package/dist/worker/observe/static/dag-layout.js +83 -83
  49. package/dist/worker/observe/static/dag-model.js +72 -72
  50. package/dist/worker/observe/static/dom.js +61 -61
  51. package/dist/worker/observe/static/format-pool.js +67 -67
  52. package/dist/worker/observe/static/format.js +292 -292
  53. package/dist/worker/observe/static/index.html +308 -308
  54. package/dist/worker/observe/static/kpi.js +94 -94
  55. package/dist/worker/observe/static/relations.js +133 -128
  56. package/dist/worker/observe/static/router.js +93 -85
  57. package/dist/worker/observe/static/run-processing.js +148 -148
  58. package/dist/worker/observe/static/shell-chrome.js +68 -68
  59. package/dist/worker/observe/static/state.js +253 -253
  60. package/dist/worker/observe/static/styles.css +1902 -1890
  61. package/dist/worker/observe/static/views/batch.js +227 -226
  62. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  63. package/dist/worker/observe/static/views/dag-inspector.js +607 -477
  64. package/dist/worker/observe/static/views/dag.js +362 -362
  65. package/dist/worker/observe/static/views/dashboard.js +445 -442
  66. package/dist/worker/observe/static/views/failures.js +143 -143
  67. package/dist/worker/observe/static/views/feature.js +492 -453
  68. package/dist/worker/observe/static/views/pool.js +350 -347
  69. package/dist/worker/observe/static/views/run.js +453 -453
  70. package/dist/worker/observe/static/views/session-timeline.js +205 -205
  71. package/dist/worker/observe/static/views/shell.js +7 -7
  72. package/dist/worker/observe/static/views/task.js +314 -260
  73. package/dist/worker/observe/static/views/timeline.js +163 -163
  74. package/dist/worker/pool/doctor.js +165 -0
  75. package/dist/worker/pool/migrate-state.js +303 -0
  76. package/dist/worker/pool/run-store.js +205 -17
  77. package/dist/worker/pool/types.js +17 -1
  78. package/dist/worker/pool/validation.js +100 -15
  79. package/dist/worker/report/morning-report.js +12 -2
  80. package/dist/worker/runner/run-ready.js +41 -26
  81. package/dist/worker/task-graph/ready-planner.js +136 -0
  82. package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
  83. package/dist/workflows/dag/canvas-observer.js +275 -275
  84. package/dist/workflows/dag/convergence/controller.js +16 -8
  85. package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
  86. package/dist/workflows/dag/failure-routing.js +12 -1
  87. package/dist/workflows/dag/init-hybrid.js +2404 -360
  88. package/dist/workflows/dag/node-execution.js +9 -0
  89. package/dist/workflows/dag/prompt.js +9 -0
  90. package/dist/workflows/dag/report.js +35 -1
  91. package/dist/workflows/dag/runner.js +28 -2
  92. package/dist/workflows/dag/task-demand-routing.js +383 -0
  93. package/dist/workflows/dag/types.js +51 -13
  94. package/dist/workflows/dag/upstream-artifacts.js +1 -0
  95. package/dist/workflows/dag/validate.js +59 -1
  96. package/docs/README.md +106 -104
  97. package/docs/agent-dag-recovery-playbook.md +195 -184
  98. package/docs/agent-dag-runner.md +67 -67
  99. package/docs/architecture/README.md +26 -26
  100. package/docs/architecture/dag-execution.md +140 -140
  101. package/docs/architecture/evolution.md +54 -53
  102. package/docs/architecture/facts-and-state.md +71 -58
  103. package/docs/architecture/runtime-boundaries.md +191 -191
  104. package/docs/architecture/system-overview.md +93 -93
  105. package/docs/architecture/worker-and-feature.md +85 -81
  106. package/docs/cursor-prompt-sidecar.md +36 -36
  107. package/docs/decisions/README.md +18 -15
  108. package/docs/design/README.md +167 -77
  109. package/docs/development-principles.md +73 -73
  110. package/docs/exec-plans/README.md +6 -6
  111. package/docs/exec-plans/active/README.md +15 -9
  112. package/docs/exec-plans/completed/README.md +85 -73
  113. package/docs/feature-workflow.md +389 -261
  114. package/docs/harness-methodology-debugging.md +153 -153
  115. package/docs/harness-methodology-tdd.md +130 -130
  116. package/docs/harness-methodology-verification.md +27 -27
  117. package/docs/init-surface.manifest.json +289 -280
  118. package/docs/loop-agent-harness.md +142 -130
  119. package/docs/production-readiness.md +96 -96
  120. package/docs/progress/README.md +64 -54
  121. package/docs/reports/README.md +117 -94
  122. package/docs/skills/README.md +7 -7
  123. package/docs/skills/vetted-skill-registry.md +29 -27
  124. package/docs/templates/adr.md +60 -60
  125. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  126. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  127. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  128. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  129. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  130. package/docs/templates/agent-dag-report.schema.json +473 -473
  131. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  132. package/docs/templates/agent-dag.base.json +190 -190
  133. package/docs/templates/agent-dag.final-verification.json +185 -185
  134. package/docs/templates/agent-dag.schema.json +411 -383
  135. package/docs/templates/agent-dag.supervised-implementation.json +501 -501
  136. package/docs/templates/backend-test-analysis.schema.json +44 -0
  137. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
  138. package/docs/templates/backend-test-dag.json +311 -276
  139. package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
  140. package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
  141. package/docs/templates/exec-plan.md +64 -64
  142. package/docs/templates/feature-spec.md +53 -53
  143. package/docs/templates/frontend-design-contract.md +42 -33
  144. package/docs/templates/frontend-task-constraints.md +35 -25
  145. package/docs/templates/frontend-task-requirement.md +70 -61
  146. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
  147. package/docs/templates/frontend-test-dag.json +23 -0
  148. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
  149. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
  150. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
  151. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  152. package/docs/templates/harness.schema.json +221 -221
  153. package/docs/templates/hybrid-dag.json +188 -188
  154. package/docs/templates/init-evolution-review.md +35 -35
  155. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  156. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
  157. package/docs/templates/knowledge-sync-dag.json +178 -0
  158. package/docs/templates/knowledge-sync-draft.schema.json +71 -0
  159. package/docs/templates/product-line/AGENTS.md +8 -8
  160. package/docs/templates/product-line/README.md +9 -9
  161. package/docs/templates/product-line/acceptance.yaml +14 -14
  162. package/docs/templates/product-line/closeout.yaml +9 -9
  163. package/docs/templates/product-line/design.md +13 -13
  164. package/docs/templates/product-line/links.md +10 -10
  165. package/docs/templates/product-line/requirement.md +17 -17
  166. package/docs/templates/product-line/task-graph.yaml +15 -15
  167. package/docs/templates/product-line/task.yaml +64 -64
  168. package/docs/templates/product-line/test-plan.md +7 -7
  169. package/docs/templates/production-readiness-checklist.md +57 -57
  170. package/docs/templates/progress-log.md +17 -17
  171. package/docs/templates/project-start-checklist.md +9 -9
  172. package/docs/templates/qa-report.md +48 -48
  173. package/docs/templates/sprint-contract.md +29 -29
  174. package/docs/templates/worker-dogfood-evidence.md +80 -80
  175. package/docs/templates/worker-dogfood-setup.md +68 -68
  176. package/docs/verification-matrix.md +70 -66
  177. package/examples/decision-gate-agent-dag.json +177 -177
  178. package/examples/example-dag.json +46 -46
  179. package/examples/hybrid-loop-agent-dag.json +189 -189
  180. package/harness.json +66 -66
  181. package/package.json +88 -46
  182. package/scripts/check-product-line-docs.sh +29 -29
  183. package/scripts/check-task-pool-root.sh +32 -32
  184. package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
  185. package/scripts/kb-graph-incremental-prepare.mjs +386 -0
  186. package/scripts/kb-graph-incremental-prepare.sh +5 -0
  187. package/scripts/kb-graph-materialize.mjs +105 -0
  188. package/scripts/kb-graph-materialize.sh +4 -0
  189. package/scripts/kb-graph-promote.mjs +164 -0
  190. package/scripts/kb-graph-promote.sh +4 -0
  191. package/scripts/kb-query.mjs +554 -0
  192. package/scripts/kb-query.sh +5 -0
  193. package/skills/agent-worker/SKILL.md +39 -37
  194. package/skills/agent-worker/references/agent-worker-operator.md +60 -43
  195. package/skills/ai-engineering-context/SKILL.md +48 -48
  196. package/skills/analyze-product-dependencies/SKILL.md +67 -0
  197. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
  198. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
  199. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
  200. package/skills/analyze-product-dependencies/references/example.md +76 -0
  201. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
  202. package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
  203. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
  204. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
  205. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
  206. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
  207. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
  208. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
  209. package/skills/analyze-product-requirements/SKILL.md +90 -0
  210. package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
  211. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
  212. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
  213. package/skills/analyze-product-requirements/references/example.md +86 -0
  214. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
  215. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
  216. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
  217. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
  218. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
  219. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
  220. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
  221. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
  222. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
  223. package/skills/code-review-core/SKILL.md +20 -20
  224. package/skills/codebase-scout/SKILL.md +19 -19
  225. package/skills/frontend-design-review/SKILL.md +66 -59
  226. package/skills/frontend-design-review/references/review-checklist.md +58 -37
  227. package/skills/frontend-implementation/SKILL.md +47 -51
  228. package/skills/frontend-implementation/references/code-standards.md +32 -34
  229. package/skills/frontend-implementation/references/design-spec.md +46 -46
  230. package/skills/frontend-implementation/references/node-contracts.md +76 -32
  231. package/skills/frontend-review/SKILL.md +59 -53
  232. package/skills/frontend-review/references/review-findings.md +47 -42
  233. package/skills/frontend-verification/SKILL.md +53 -40
  234. package/skills/frontend-verification/references/verification-checklist.md +68 -56
  235. package/skills/grill-me/SKILL.md +10 -10
  236. package/skills/grill-with-docs/SKILL.md +88 -88
  237. package/skills/grill-with-docs/adr-format.md +47 -47
  238. package/skills/grill-with-docs/context-format.md +60 -60
  239. package/skills/init-capability-evolution/SKILL.md +70 -70
  240. package/skills/loop-agent/SKILL.md +151 -151
  241. package/skills/loop-agent/references/README.md +67 -67
  242. package/skills/loop-agent/references/command-reference.md +505 -452
  243. package/skills/loop-agent/references/docs-converge.md +126 -126
  244. package/skills/loop-agent/references/harness-policy.md +263 -263
  245. package/skills/loop-agent/references/hybrid-dag.md +238 -233
  246. package/skills/loop-agent/references/learned/README.md +21 -21
  247. package/skills/loop-agent/references/long-running-loop.md +57 -57
  248. package/skills/loop-agent/references/model-routing.md +36 -36
  249. package/skills/loop-agent/references/multi-worktree.md +54 -54
  250. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  251. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  252. package/skills/loop-agent/references/pi-prompt.md +23 -23
  253. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  254. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  255. package/skills/loop-agent/references/task-workflow.md +89 -89
  256. package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
  257. package/skills/playwright-cli/SKILL.md +420 -0
  258. package/skills/playwright-cli/references/element-attributes.md +23 -0
  259. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  260. package/skills/playwright-cli/references/request-mocking.md +87 -0
  261. package/skills/playwright-cli/references/running-code.md +241 -0
  262. package/skills/playwright-cli/references/session-management.md +225 -0
  263. package/skills/playwright-cli/references/storage-state.md +275 -0
  264. package/skills/playwright-cli/references/test-generation.md +433 -0
  265. package/skills/playwright-cli/references/tracing.md +139 -0
  266. package/skills/playwright-cli/references/video-recording.md +143 -0
  267. package/skills/playwright-cli-case-generator/SKILL.md +74 -0
  268. package/skills/requesting-code-review/SKILL.md +101 -101
  269. package/skills/requesting-code-review/code-reviewer.md +168 -168
  270. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  271. package/skills/systematic-debugging/SKILL.md +296 -296
  272. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  273. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  274. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  275. package/skills/systematic-debugging/find-polluter.sh +63 -63
  276. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  277. package/skills/systematic-debugging/test-academic.md +14 -14
  278. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  279. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  280. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  281. package/skills/test-driven-development/SKILL.md +20 -20
  282. package/skills/using-git-worktrees/SKILL.md +215 -215
  283. package/skills/verification-before-completion/SKILL.md +154 -154
  284. package/skills/webapp-testing/SKILL.md +19 -19
@@ -0,0 +1,303 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { copyFile, mkdir, readFile, rename, rm, unlink, writeFile, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { diagnoseTaskPoolStates, loadOperatorMapping, } from "./doctor.js";
5
+ import { getEventsJsonlPath, getLegacyTaskStatePath, getRunsJsonlPath, getTaskPoolRoot, getTaskStatePath, pathExists, sha256File, sha256Text, } from "./run-store.js";
6
+ import { TASK_POOL_STATE_ERROR_CODES, TaskPoolStateError, } from "./types.js";
7
+ /**
8
+ * Migrate legacy flat Task Pool states to feature-scoped v2.
9
+ * Defaults to dry-run (zero writes). Apply requires --owner and --reason.
10
+ */
11
+ export async function migrateTaskPoolStates(input) {
12
+ const apply = input.apply === true;
13
+ const migrationId = input.migrationId ??
14
+ `migration-${(input.now ?? new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
15
+ const mapping = input.mapping ?? (await loadOperatorMapping(input.mappingPath));
16
+ const doctor = await diagnoseTaskPoolStates(input.repoRoot, { mapping });
17
+ const planned = [];
18
+ const skipped = [];
19
+ for (const item of doctor.legacy) {
20
+ if (item.resolution === "ambiguous") {
21
+ skipped.push({
22
+ taskId: item.taskId,
23
+ reason: item.detail ?? "identity ambiguous",
24
+ code: TASK_POOL_STATE_ERROR_CODES.IDENTITY_AMBIGUOUS,
25
+ });
26
+ continue;
27
+ }
28
+ if (item.resolution === "unresolved" || !item.featureId) {
29
+ skipped.push({
30
+ taskId: item.taskId,
31
+ reason: item.detail ?? "no unique identity evidence",
32
+ code: TASK_POOL_STATE_ERROR_CODES.MIGRATION_REQUIRED,
33
+ });
34
+ continue;
35
+ }
36
+ const ref = {
37
+ featureId: item.featureId,
38
+ taskId: item.taskId,
39
+ };
40
+ const v2Path = getTaskStatePath(input.repoRoot, ref);
41
+ const alreadyMigrated = await pathExists(v2Path);
42
+ if (alreadyMigrated) {
43
+ // Idempotent: if v2 already exists with matching identity, skip write.
44
+ planned.push({
45
+ taskId: item.taskId,
46
+ legacyPath: item.legacyPath,
47
+ legacySha256: item.sha256,
48
+ featureId: item.featureId,
49
+ v2Path,
50
+ resolution: item.resolution,
51
+ alreadyMigrated: true,
52
+ });
53
+ continue;
54
+ }
55
+ // Conflict: v2 path occupied by different content is handled at apply time.
56
+ planned.push({
57
+ taskId: item.taskId,
58
+ legacyPath: item.legacyPath,
59
+ legacySha256: item.sha256,
60
+ featureId: item.featureId,
61
+ v2Path,
62
+ resolution: item.resolution,
63
+ alreadyMigrated: false,
64
+ });
65
+ }
66
+ const runsPath = getRunsJsonlPath(input.repoRoot);
67
+ const eventsPath = getEventsJsonlPath(input.repoRoot);
68
+ const runsHashBefore = (await pathExists(runsPath))
69
+ ? await sha256File(runsPath)
70
+ : null;
71
+ const eventsHashBefore = (await pathExists(eventsPath))
72
+ ? await sha256File(eventsPath)
73
+ : null;
74
+ if (!apply) {
75
+ return {
76
+ schemaVersion: 1,
77
+ mode: "dry-run",
78
+ status: planned.length === 0 && skipped.length === 0 ? "noop" : "planned",
79
+ migrationId,
80
+ planned,
81
+ skipped,
82
+ jsonlUnchanged: { runs: true, events: true },
83
+ doctor,
84
+ };
85
+ }
86
+ if (!input.owner?.trim()) {
87
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, "pool migrate-state --apply requires a non-empty --owner");
88
+ }
89
+ if (!input.reason?.trim()) {
90
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, "pool migrate-state --apply requires a non-empty --reason");
91
+ }
92
+ if (skipped.some((item) => item.code === TASK_POOL_STATE_ERROR_CODES.IDENTITY_AMBIGUOUS)) {
93
+ return {
94
+ schemaVersion: 1,
95
+ mode: "apply",
96
+ status: "failed",
97
+ migrationId,
98
+ owner: input.owner,
99
+ reason: input.reason,
100
+ planned,
101
+ skipped,
102
+ jsonlUnchanged: { runs: true, events: true },
103
+ doctor,
104
+ error: {
105
+ code: TASK_POOL_STATE_ERROR_CODES.IDENTITY_AMBIGUOUS,
106
+ message: "refusing apply while any legacy identity is ambiguous",
107
+ },
108
+ };
109
+ }
110
+ const actionable = planned.filter((item) => !item.alreadyMigrated);
111
+ if (actionable.length === 0 && planned.every((item) => item.alreadyMigrated)) {
112
+ // Idempotent re-run: move any remaining legacy files that match already-migrated v2.
113
+ // If legacy still present with alreadyMigrated, still archive them.
114
+ }
115
+ const artifactDir = path.join(getTaskPoolRoot(input.repoRoot), "artifacts", "migrations", migrationId);
116
+ const beforeDir = path.join(artifactDir, "before");
117
+ const stagingDir = path.join(artifactDir, "staging");
118
+ const writtenV2 = [];
119
+ const movedLegacy = [];
120
+ try {
121
+ await mkdir(beforeDir, { recursive: true });
122
+ await mkdir(stagingDir, { recursive: true });
123
+ // Stage all v2 payloads first.
124
+ const staged = [];
125
+ for (const item of planned) {
126
+ const legacyBytes = await readFile(item.legacyPath);
127
+ const legacyText = legacyBytes.toString("utf-8");
128
+ const hash = await sha256Text(legacyText);
129
+ if (hash !== item.legacySha256) {
130
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `legacy state changed during migration: ${item.taskId}`);
131
+ }
132
+ const parsed = JSON.parse(legacyText);
133
+ const payload = {
134
+ ...parsed,
135
+ schemaVersion: 2,
136
+ featureId: item.featureId,
137
+ taskId: item.taskId,
138
+ };
139
+ const stagedPath = path.join(stagingDir, `${item.featureId}__${item.taskId}.json`);
140
+ await writeFile(stagedPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
141
+ staged.push({ item, stagedPath, payload, legacyBytes });
142
+ }
143
+ // Conflict checks against existing v2 targets.
144
+ for (const entry of staged) {
145
+ if (await pathExists(entry.item.v2Path)) {
146
+ const existing = JSON.parse(await readFile(entry.item.v2Path, "utf-8"));
147
+ if (existing.featureId === entry.item.featureId &&
148
+ existing.taskId === entry.item.taskId) {
149
+ // already migrated — ok, will only archive legacy
150
+ continue;
151
+ }
152
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `v2 target already exists with different identity: ${entry.item.v2Path}`);
153
+ }
154
+ }
155
+ // Atomic rename of staged v2 files.
156
+ for (const entry of staged) {
157
+ if (await pathExists(entry.item.v2Path)) {
158
+ // already migrated
159
+ continue;
160
+ }
161
+ await mkdir(path.dirname(entry.item.v2Path), { recursive: true });
162
+ const tempPath = `${entry.item.v2Path}.${randomUUID()}.tmp`;
163
+ try {
164
+ await copyFile(entry.stagedPath, tempPath);
165
+ await input.faultInjector?.("before-v2-rename", entry.item.taskId);
166
+ await rename(tempPath, entry.item.v2Path);
167
+ writtenV2.push(entry.item.v2Path);
168
+ }
169
+ finally {
170
+ await unlink(tempPath).catch(() => { });
171
+ }
172
+ }
173
+ await input.faultInjector?.("after-partial-v2");
174
+ // Move legacy into artifact before/.
175
+ for (const entry of staged) {
176
+ const dest = path.join(beforeDir, path.basename(entry.item.legacyPath));
177
+ await input.faultInjector?.("before-legacy-move", entry.item.taskId);
178
+ if (await pathExists(entry.item.legacyPath)) {
179
+ // Preserve exact bytes
180
+ await writeFile(dest, entry.legacyBytes);
181
+ await unlink(entry.item.legacyPath);
182
+ movedLegacy.push({
183
+ from: entry.item.legacyPath,
184
+ to: dest,
185
+ originalBytes: entry.legacyBytes,
186
+ });
187
+ }
188
+ }
189
+ const mappingDoc = {
190
+ schemaVersion: 1,
191
+ migrationId,
192
+ items: planned.map((item) => ({
193
+ taskId: item.taskId,
194
+ featureId: item.featureId,
195
+ resolution: item.resolution,
196
+ legacySha256: item.legacySha256,
197
+ v2Path: item.v2Path,
198
+ alreadyMigrated: item.alreadyMigrated,
199
+ })),
200
+ skipped,
201
+ };
202
+ const manifest = {
203
+ schemaVersion: 1,
204
+ migrationId,
205
+ owner: input.owner,
206
+ reason: input.reason,
207
+ appliedAt: (input.now ?? new Date()).toISOString(),
208
+ plannedCount: planned.length,
209
+ skippedCount: skipped.length,
210
+ };
211
+ const resultDoc = {
212
+ schemaVersion: 1,
213
+ status: "applied",
214
+ migrationId,
215
+ writtenV2,
216
+ archivedLegacy: movedLegacy.map((item) => item.to),
217
+ };
218
+ await writeFile(path.join(artifactDir, "mapping.json"), `${JSON.stringify(mappingDoc, null, 2)}\n`, "utf-8");
219
+ await writeFile(path.join(artifactDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
220
+ await writeFile(path.join(artifactDir, "result.json"), `${JSON.stringify(resultDoc, null, 2)}\n`, "utf-8");
221
+ // Clean staging
222
+ await rm(stagingDir, { recursive: true, force: true }).catch(() => { });
223
+ const runsHashAfter = (await pathExists(runsPath))
224
+ ? await sha256File(runsPath)
225
+ : null;
226
+ const eventsHashAfter = (await pathExists(eventsPath))
227
+ ? await sha256File(eventsPath)
228
+ : null;
229
+ return {
230
+ schemaVersion: 1,
231
+ mode: "apply",
232
+ status: "applied",
233
+ migrationId,
234
+ owner: input.owner,
235
+ reason: input.reason,
236
+ planned,
237
+ skipped,
238
+ artifactDir,
239
+ jsonlUnchanged: {
240
+ runs: runsHashBefore === runsHashAfter,
241
+ events: eventsHashBefore === eventsHashAfter,
242
+ },
243
+ doctor,
244
+ };
245
+ }
246
+ catch (error) {
247
+ // Full rollback: restore legacy, remove half-written v2.
248
+ for (const v2Path of writtenV2) {
249
+ await unlink(v2Path).catch(() => { });
250
+ }
251
+ for (const moved of movedLegacy) {
252
+ await writeFile(moved.from, moved.originalBytes).catch(() => { });
253
+ await unlink(moved.to).catch(() => { });
254
+ }
255
+ // Also restore any legacy that was deleted without movedLegacy tracking
256
+ // (should not happen with current flow).
257
+ for (const item of planned) {
258
+ if (!(await pathExists(item.legacyPath))) {
259
+ // try recover from beforeDir if present
260
+ const dest = path.join(beforeDir, path.basename(item.legacyPath));
261
+ if (await pathExists(dest)) {
262
+ await copyFile(dest, item.legacyPath).catch(() => { });
263
+ }
264
+ }
265
+ if (!item.alreadyMigrated &&
266
+ (await pathExists(item.v2Path)) &&
267
+ writtenV2.includes(item.v2Path)) {
268
+ await unlink(item.v2Path).catch(() => { });
269
+ }
270
+ }
271
+ await rm(stagingDir, { recursive: true, force: true }).catch(() => { });
272
+ const detail = error instanceof Error ? error.message : String(error);
273
+ const code = error instanceof TaskPoolStateError
274
+ ? error.code
275
+ : TASK_POOL_STATE_ERROR_CODES.CONFLICT;
276
+ return {
277
+ schemaVersion: 1,
278
+ mode: "apply",
279
+ status: "failed",
280
+ migrationId,
281
+ owner: input.owner,
282
+ reason: input.reason,
283
+ planned,
284
+ skipped,
285
+ artifactDir,
286
+ jsonlUnchanged: { runs: true, events: true },
287
+ doctor,
288
+ error: { code, message: detail },
289
+ };
290
+ }
291
+ }
292
+ /** Helper for tests: write a legacy flat state file without going through writeTaskPoolState. */
293
+ export async function writeLegacyTaskPoolState(repoRoot, state) {
294
+ const statePath = getLegacyTaskStatePath(repoRoot, state.taskId);
295
+ await mkdir(path.dirname(statePath), { recursive: true });
296
+ const payload = {
297
+ schemaVersion: 1,
298
+ ...state,
299
+ };
300
+ delete payload.featureId;
301
+ await writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
302
+ return statePath;
303
+ }
@@ -1,7 +1,9 @@
1
- import { randomUUID } from "node:crypto";
2
- import { appendFile, mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { appendFile, mkdir, readFile, readdir, rename, stat, unlink, writeFile, } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { TASK_POOL_STATE_ERROR_CODES, TaskPoolStateError, } from "./types.js";
4
5
  export const TASK_POOL_RELATIVE_ROOT = ".harness/task-pool";
6
+ const SAFE_TASK_POOL_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
5
7
  export function getTaskPoolRoot(repoRoot) {
6
8
  return path.join(repoRoot, ...TASK_POOL_RELATIVE_ROOT.split("/"));
7
9
  }
@@ -11,8 +13,41 @@ export function getRunsJsonlPath(repoRoot) {
11
13
  export function getEventsJsonlPath(repoRoot) {
12
14
  return path.join(getTaskPoolRoot(repoRoot), "events.jsonl");
13
15
  }
14
- export function getTaskStatePath(repoRoot, taskId) {
15
- return path.join(getTaskPoolRoot(repoRoot), "states", `${taskId}.json`);
16
+ export function getStatesRoot(repoRoot) {
17
+ return path.join(getTaskPoolRoot(repoRoot), "states");
18
+ }
19
+ /**
20
+ * Fail-closed id check for Task Pool path segments.
21
+ * Mirrors follow-up runtime id charset without importing that module.
22
+ */
23
+ export function assertSafeTaskPoolId(value, label) {
24
+ if (typeof value !== "string" || value.length === 0 || !SAFE_TASK_POOL_ID.test(value)) {
25
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.UNSAFE_ID, `${label} contains unsafe path characters: ${JSON.stringify(value)}`);
26
+ }
27
+ }
28
+ function assertWithinStatesRoot(repoRoot, candidatePath) {
29
+ const statesRoot = path.resolve(getStatesRoot(repoRoot));
30
+ const resolved = path.resolve(candidatePath);
31
+ const relative = path.relative(statesRoot, resolved);
32
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
33
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.PATH_MISMATCH, `Task Pool state path escapes states root: ${candidatePath}`);
34
+ }
35
+ }
36
+ export function getTaskStatePath(repoRoot, refOrTaskId) {
37
+ if (typeof refOrTaskId === "string") {
38
+ assertSafeTaskPoolId(refOrTaskId, "taskId");
39
+ const statePath = path.join(getStatesRoot(repoRoot), `${refOrTaskId}.json`);
40
+ assertWithinStatesRoot(repoRoot, statePath);
41
+ return statePath;
42
+ }
43
+ assertSafeTaskPoolId(refOrTaskId.featureId, "featureId");
44
+ assertSafeTaskPoolId(refOrTaskId.taskId, "taskId");
45
+ const statePath = path.join(getStatesRoot(repoRoot), refOrTaskId.featureId, `${refOrTaskId.taskId}.json`);
46
+ assertWithinStatesRoot(repoRoot, statePath);
47
+ return statePath;
48
+ }
49
+ export function getLegacyTaskStatePath(repoRoot, taskId) {
50
+ return getTaskStatePath(repoRoot, taskId);
16
51
  }
17
52
  export async function recordTaskPoolRun(input) {
18
53
  await ensurePoolDirs(input.repoRoot);
@@ -30,10 +65,12 @@ export async function findRunByWorkerRunId(repoRoot, workerRunId) {
30
65
  }
31
66
  /**
32
67
  * Explicitly requeue a failed task without deleting or rewriting prior evidence.
33
- * The runner consumes `retryOfWorkerRunId` and derives a guaranteed fresh run id.
68
+ * Prefer explicit featureId. Bare taskId is allowed only when exactly one v2 state matches.
34
69
  */
35
70
  export async function prepareTaskPoolRetry(input) {
36
- const current = await readTaskPoolState(input.repoRoot, input.taskId);
71
+ const featureId = await resolveRetryFeatureId(input.repoRoot, input.taskId, input.featureId);
72
+ const ref = { featureId, taskId: input.taskId };
73
+ const current = await readTaskPoolState(input.repoRoot, ref);
37
74
  if (!current) {
38
75
  throw new Error(`cannot retry ${input.taskId}: Task Pool state does not exist`);
39
76
  }
@@ -46,12 +83,15 @@ export async function prepareTaskPoolRetry(input) {
46
83
  const retryRequestedAt = (input.now ?? new Date()).toISOString();
47
84
  const result = {
48
85
  taskId: input.taskId,
86
+ featureId,
49
87
  status: "Ready",
50
88
  previousWorkerRunId: current.workerRunId,
51
89
  retryRequestedAt,
52
90
  ...(input.reason ? { reason: input.reason } : {}),
53
91
  };
54
92
  await writeTaskPoolState(input.repoRoot, {
93
+ schemaVersion: 2,
94
+ featureId,
55
95
  taskId: current.taskId,
56
96
  status: "Ready",
57
97
  updatedAt: retryRequestedAt,
@@ -65,31 +105,72 @@ export async function prepareTaskPoolRetry(input) {
65
105
  at: retryRequestedAt,
66
106
  type: "task-retry-requested",
67
107
  taskId: input.taskId,
108
+ featureId,
68
109
  previousWorkerRunId: current.workerRunId,
69
110
  ...(input.reason ? { reason: input.reason } : {}),
70
111
  });
71
112
  return result;
72
113
  }
73
- export async function readTaskPoolState(repoRoot, taskId) {
74
- const statePath = getTaskStatePath(repoRoot, taskId);
114
+ async function resolveRetryFeatureId(repoRoot, taskId, featureId) {
115
+ if (featureId) {
116
+ assertSafeTaskPoolId(featureId, "featureId");
117
+ return featureId;
118
+ }
119
+ const legacyPath = getLegacyTaskStatePath(repoRoot, taskId);
120
+ if (await pathExists(legacyPath)) {
121
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.MIGRATION_REQUIRED, `cannot retry ${taskId}: legacy state requires migration`);
122
+ }
123
+ const matches = (await listTaskPoolStates(repoRoot)).filter((state) => state.taskId === taskId);
124
+ if (matches.length === 1 && matches[0]?.featureId) {
125
+ return matches[0].featureId;
126
+ }
127
+ if (matches.length > 1) {
128
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.IDENTITY_AMBIGUOUS, `cannot retry ${taskId}: multiple feature-scoped states exist`);
129
+ }
130
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.FEATURE_REQUIRED, `cannot retry ${taskId}: featureId is required when no unique v2 state exists`);
131
+ }
132
+ export async function readTaskPoolState(repoRoot, refOrTaskId) {
133
+ if (typeof refOrTaskId === "string") {
134
+ const statePath = getTaskStatePath(repoRoot, refOrTaskId);
135
+ try {
136
+ return JSON.parse(await readFile(statePath, "utf-8"));
137
+ }
138
+ catch (error) {
139
+ if (isNotFound(error))
140
+ return undefined;
141
+ throw error;
142
+ }
143
+ }
144
+ const statePath = getTaskStatePath(repoRoot, refOrTaskId);
145
+ let raw;
75
146
  try {
76
- return JSON.parse(await readFile(statePath, "utf-8"));
147
+ raw = await readFile(statePath, "utf-8");
77
148
  }
78
149
  catch (error) {
79
150
  if (isNotFound(error))
80
151
  return undefined;
81
152
  throw error;
82
153
  }
154
+ const state = JSON.parse(raw);
155
+ if (state.taskId !== refOrTaskId.taskId ||
156
+ state.featureId !== refOrTaskId.featureId) {
157
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.PATH_MISMATCH, `Task Pool state path/content identity mismatch at ${statePath}: path=${refOrTaskId.featureId}/${refOrTaskId.taskId} content=${state.featureId ?? "<missing>"}/${state.taskId}`);
158
+ }
159
+ return state;
83
160
  }
161
+ /**
162
+ * @deprecated Legacy-only flat scan. Does not include feature-scoped v2 states.
163
+ * Prefer readFeatureTaskPoolStates / listTaskPoolStates. Removal targeted for M2.
164
+ */
84
165
  export async function readAllTaskPoolStates(repoRoot) {
85
- const stateDir = path.join(getTaskPoolRoot(repoRoot), "states");
166
+ const stateDir = getStatesRoot(repoRoot);
86
167
  try {
87
- const entries = await readdir(stateDir);
168
+ const entries = await readdir(stateDir, { withFileTypes: true });
88
169
  const states = {};
89
170
  for (const entry of entries) {
90
- if (!entry.endsWith(".json"))
171
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
91
172
  continue;
92
- const raw = await readFile(path.join(stateDir, entry), "utf-8");
173
+ const raw = await readFile(path.join(stateDir, entry.name), "utf-8");
93
174
  const state = JSON.parse(raw);
94
175
  states[state.taskId] = state;
95
176
  }
@@ -101,12 +182,79 @@ export async function readAllTaskPoolStates(repoRoot) {
101
182
  throw error;
102
183
  }
103
184
  }
185
+ /** Canonical Feature-scoped list (v2 paths only). */
186
+ export async function readFeatureTaskPoolStates(repoRoot, featureId) {
187
+ assertSafeTaskPoolId(featureId, "featureId");
188
+ const featureDir = path.join(getStatesRoot(repoRoot), featureId);
189
+ assertWithinStatesRoot(repoRoot, featureDir);
190
+ const states = {};
191
+ try {
192
+ const entries = await readdir(featureDir);
193
+ for (const entry of entries) {
194
+ if (!entry.endsWith(".json"))
195
+ continue;
196
+ const taskId = entry.slice(0, -5);
197
+ const state = await readTaskPoolState(repoRoot, { featureId, taskId });
198
+ if (state)
199
+ states[taskId] = state;
200
+ }
201
+ return states;
202
+ }
203
+ catch (error) {
204
+ if (isNotFound(error))
205
+ return {};
206
+ throw error;
207
+ }
208
+ }
209
+ /** List all schema v2 feature-scoped states (never a covering Record by bare taskId). */
210
+ export async function listTaskPoolStates(repoRoot) {
211
+ const stateDir = getStatesRoot(repoRoot);
212
+ const result = [];
213
+ try {
214
+ const entries = await readdir(stateDir, { withFileTypes: true });
215
+ for (const entry of entries) {
216
+ if (!entry.isDirectory())
217
+ continue;
218
+ const featureId = entry.name;
219
+ if (!SAFE_TASK_POOL_ID.test(featureId))
220
+ continue;
221
+ const featureStates = await readFeatureTaskPoolStates(repoRoot, featureId);
222
+ result.push(...Object.values(featureStates));
223
+ }
224
+ return result;
225
+ }
226
+ catch (error) {
227
+ if (isNotFound(error))
228
+ return [];
229
+ throw error;
230
+ }
231
+ }
232
+ /**
233
+ * Write Task Pool state.
234
+ * Requires featureId (schema v2). Throws if featureId missing or legacy flat file exists.
235
+ */
104
236
  export async function writeTaskPoolState(repoRoot, state) {
105
- const statePath = getTaskStatePath(repoRoot, state.taskId);
237
+ if (!state.featureId) {
238
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.FEATURE_REQUIRED, `writeTaskPoolState requires featureId for task ${state.taskId}`);
239
+ }
240
+ assertSafeTaskPoolId(state.featureId, "featureId");
241
+ assertSafeTaskPoolId(state.taskId, "taskId");
242
+ const legacyPath = getLegacyTaskStatePath(repoRoot, state.taskId);
243
+ if (await pathExists(legacyPath)) {
244
+ throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.MIGRATION_REQUIRED, `cannot write v2 state for ${state.taskId}: legacy state exists at ${legacyPath}`);
245
+ }
246
+ const ref = { featureId: state.featureId, taskId: state.taskId };
247
+ const statePath = getTaskStatePath(repoRoot, ref);
248
+ const payload = {
249
+ ...state,
250
+ schemaVersion: 2,
251
+ featureId: state.featureId,
252
+ taskId: state.taskId,
253
+ };
106
254
  await mkdir(path.dirname(statePath), { recursive: true });
107
255
  const tempPath = path.join(path.dirname(statePath), `.${path.basename(statePath)}.${randomUUID()}.tmp`);
108
256
  try {
109
- await writeFile(tempPath, `${JSON.stringify({ schemaVersion: 1, ...state }, null, 2)}\n`, "utf-8");
257
+ await writeFile(tempPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
110
258
  await rename(tempPath, statePath);
111
259
  }
112
260
  finally {
@@ -127,6 +275,43 @@ export async function readJsonlFile(filePath) {
127
275
  throw error;
128
276
  }
129
277
  }
278
+ export async function sha256Text(text) {
279
+ return createHash("sha256").update(text, "utf-8").digest("hex");
280
+ }
281
+ export async function sha256File(filePath) {
282
+ const raw = await readFile(filePath);
283
+ return createHash("sha256").update(raw).digest("hex");
284
+ }
285
+ export async function listLegacyStateFiles(repoRoot) {
286
+ const stateDir = getStatesRoot(repoRoot);
287
+ try {
288
+ const entries = await readdir(stateDir, { withFileTypes: true });
289
+ const result = [];
290
+ for (const entry of entries) {
291
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
292
+ continue;
293
+ const taskId = entry.name.slice(0, -5);
294
+ result.push({ taskId, path: path.join(stateDir, entry.name) });
295
+ }
296
+ return result;
297
+ }
298
+ catch (error) {
299
+ if (isNotFound(error))
300
+ return [];
301
+ throw error;
302
+ }
303
+ }
304
+ export async function pathExists(filePath) {
305
+ try {
306
+ await stat(filePath);
307
+ return true;
308
+ }
309
+ catch (error) {
310
+ if (isNotFound(error))
311
+ return false;
312
+ throw error;
313
+ }
314
+ }
130
315
  async function appendJsonlFile(filePath, value) {
131
316
  await mkdir(path.dirname(filePath), { recursive: true });
132
317
  try {
@@ -140,10 +325,12 @@ async function appendJsonlFile(filePath, value) {
140
325
  }
141
326
  }
142
327
  async function ensurePoolDirs(repoRoot) {
143
- await mkdir(path.join(getTaskPoolRoot(repoRoot), "states"), { recursive: true });
328
+ await mkdir(getStatesRoot(repoRoot), { recursive: true });
144
329
  }
145
- function stateFromRun(run) {
330
+ export function stateFromRun(run) {
146
331
  return {
332
+ schemaVersion: 2,
333
+ featureId: run.featureId,
147
334
  taskId: run.taskId,
148
335
  status: stateStatusFromRun(run),
149
336
  updatedAt: run.recordedAt,
@@ -175,6 +362,7 @@ function eventFromRun(run) {
175
362
  batchRunId: run.batchRunId,
176
363
  workerRunId: run.workerRunId,
177
364
  taskId: run.taskId,
365
+ featureId: run.featureId,
178
366
  status: run.status,
179
367
  };
180
368
  }
@@ -1 +1,17 @@
1
- export {};
1
+ /** Stable error codes for Task Pool state identity / migration. */
2
+ export const TASK_POOL_STATE_ERROR_CODES = {
3
+ MIGRATION_REQUIRED: "task-pool-state-migration-required",
4
+ IDENTITY_AMBIGUOUS: "task-pool-state-identity-ambiguous",
5
+ PATH_MISMATCH: "task-pool-state-path-mismatch",
6
+ CONFLICT: "task-pool-state-conflict",
7
+ FEATURE_REQUIRED: "task-pool-state-feature-required",
8
+ UNSAFE_ID: "task-pool-state-unsafe-id",
9
+ };
10
+ export class TaskPoolStateError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.name = "TaskPoolStateError";
15
+ this.code = code;
16
+ }
17
+ }