@tea-agent/loop-agent 0.13.0 → 0.14.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 (270) hide show
  1. package/AGENTS.md +157 -157
  2. package/CHANGELOG.md +73 -301
  3. package/README.md +338 -334
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/commands/cursor-prompt.js +6 -6
  7. package/dist/commands/init.js +505 -505
  8. package/dist/commands/loop-benchmark.js +11 -11
  9. package/dist/commands/pi-reuse-benchmark.js +16 -16
  10. package/dist/executors/pi-event-serializer.js +33 -11
  11. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  12. package/dist/task/runtime.js +27 -27
  13. package/dist/worker/observe/spec-evidence.js +19 -10
  14. package/dist/worker/observe/static/api.js +46 -46
  15. package/dist/worker/observe/static/app.js +151 -150
  16. package/dist/worker/observe/static/constants.js +156 -148
  17. package/dist/worker/observe/static/copy.js +67 -67
  18. package/dist/worker/observe/static/dag-helpers.js +201 -172
  19. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  20. package/dist/worker/observe/static/dag-layout.js +83 -83
  21. package/dist/worker/observe/static/dag-model.js +72 -72
  22. package/dist/worker/observe/static/dom.js +122 -122
  23. package/dist/worker/observe/static/format-pool.d.ts +71 -0
  24. package/dist/worker/observe/static/format-pool.js +134 -67
  25. package/dist/worker/observe/static/format.js +317 -292
  26. package/dist/worker/observe/static/index.html +350 -308
  27. package/dist/worker/observe/static/kpi.js +100 -94
  28. package/dist/worker/observe/static/markdown-render.js +124 -0
  29. package/dist/worker/observe/static/relations.js +133 -133
  30. package/dist/worker/observe/static/router.js +93 -93
  31. package/dist/worker/observe/static/run-processing.js +148 -148
  32. package/dist/worker/observe/static/shell-chrome.js +74 -68
  33. package/dist/worker/observe/static/state.js +273 -267
  34. package/dist/worker/observe/static/styles.css +2504 -1902
  35. package/dist/worker/observe/static/views/batch.js +227 -227
  36. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  37. package/dist/worker/observe/static/views/dag-inspector.js +530 -627
  38. package/dist/worker/observe/static/views/dag.js +371 -371
  39. package/dist/worker/observe/static/views/dashboard.js +86 -100
  40. package/dist/worker/observe/static/views/failures.js +143 -143
  41. package/dist/worker/observe/static/views/feature.js +492 -492
  42. package/dist/worker/observe/static/views/pool.js +708 -350
  43. package/dist/worker/observe/static/views/run.js +453 -453
  44. package/dist/worker/observe/static/views/session-timeline.js +771 -219
  45. package/dist/worker/observe/static/views/shell.js +7 -7
  46. package/dist/worker/observe/static/views/task.js +314 -314
  47. package/dist/worker/observe/static/views/timeline.js +163 -163
  48. package/dist/workflows/dag/canvas-observer.js +275 -275
  49. package/docs/README.md +105 -104
  50. package/docs/agent-dag-recovery-playbook.md +195 -195
  51. package/docs/agent-dag-runner.md +67 -67
  52. package/docs/architecture/README.md +26 -26
  53. package/docs/architecture/dag-execution.md +140 -140
  54. package/docs/architecture/evolution.md +54 -54
  55. package/docs/architecture/facts-and-state.md +71 -71
  56. package/docs/architecture/runtime-boundaries.md +191 -191
  57. package/docs/architecture/system-overview.md +93 -93
  58. package/docs/architecture/worker-and-feature.md +85 -85
  59. package/docs/cursor-prompt-sidecar.md +36 -36
  60. package/docs/decisions/README.md +18 -18
  61. package/docs/design/README.md +167 -167
  62. package/docs/development-principles.md +73 -73
  63. package/docs/exec-plans/README.md +6 -6
  64. package/docs/exec-plans/active/README.md +2 -1
  65. package/docs/exec-plans/completed/README.md +105 -104
  66. package/docs/feature-workflow.md +414 -414
  67. package/docs/harness-methodology-debugging.md +153 -153
  68. package/docs/harness-methodology-tdd.md +130 -130
  69. package/docs/harness-methodology-verification.md +27 -27
  70. package/docs/init-surface.manifest.json +307 -307
  71. package/docs/loop-agent-harness.md +142 -142
  72. package/docs/production-readiness.md +96 -96
  73. package/docs/progress/README.md +59 -58
  74. package/docs/reports/README.md +123 -119
  75. package/docs/skills/README.md +7 -7
  76. package/docs/skills/vetted-skill-registry.md +29 -29
  77. package/docs/templates/adr.md +60 -60
  78. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  79. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  80. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  81. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  82. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  83. package/docs/templates/agent-dag-report.schema.json +473 -473
  84. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  85. package/docs/templates/agent-dag.base.json +190 -190
  86. package/docs/templates/agent-dag.final-verification.json +185 -185
  87. package/docs/templates/agent-dag.schema.json +411 -411
  88. package/docs/templates/agent-dag.supervised-implementation.json +620 -620
  89. package/docs/templates/backend-test-analysis.schema.json +44 -44
  90. package/docs/templates/backend-test-case-manifest.schema.json +190 -190
  91. package/docs/templates/backend-test-dag.classify.prompt.md +75 -75
  92. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +204 -204
  93. package/docs/templates/backend-test-dag.json +559 -559
  94. package/docs/templates/backend-test-dag.retrospect.prompt.md +139 -139
  95. package/docs/templates/backend-test-dag.review-cases.prompt.md +83 -83
  96. package/docs/templates/backend-test-execution.schema.json +133 -133
  97. package/docs/templates/backend-test-result.schema.json +99 -99
  98. package/docs/templates/exec-plan.md +64 -64
  99. package/docs/templates/feature-spec.md +53 -53
  100. package/docs/templates/frontend-design-contract.md +42 -42
  101. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  102. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  103. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  104. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  105. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  106. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  107. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  108. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  109. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  110. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  111. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  112. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  113. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  114. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  115. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  116. package/docs/templates/frontend-eval/metrics.md +138 -138
  117. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  118. package/docs/templates/frontend-implementation-contract.schema.json +27 -27
  119. package/docs/templates/frontend-task-constraints.md +35 -35
  120. package/docs/templates/frontend-task-requirement.md +70 -70
  121. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -5
  122. package/docs/templates/frontend-test-dag.json +23 -23
  123. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -3
  124. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -3
  125. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -3
  126. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -3
  127. package/docs/templates/harness.schema.json +221 -221
  128. package/docs/templates/hybrid-dag.json +188 -188
  129. package/docs/templates/init-evolution-review.md +35 -35
  130. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  131. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  132. package/docs/templates/knowledge-sync-dag.json +178 -178
  133. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  134. package/docs/templates/product-line/AGENTS.md +8 -8
  135. package/docs/templates/product-line/README.md +9 -9
  136. package/docs/templates/product-line/acceptance.yaml +14 -14
  137. package/docs/templates/product-line/closeout.yaml +9 -9
  138. package/docs/templates/product-line/design.md +13 -13
  139. package/docs/templates/product-line/links.md +10 -10
  140. package/docs/templates/product-line/requirement.md +17 -17
  141. package/docs/templates/product-line/task-graph.yaml +15 -15
  142. package/docs/templates/product-line/task.yaml +64 -64
  143. package/docs/templates/product-line/test-plan.md +7 -7
  144. package/docs/templates/production-readiness-checklist.md +57 -57
  145. package/docs/templates/progress-log.md +17 -17
  146. package/docs/templates/project-start-checklist.md +9 -9
  147. package/docs/templates/qa-report.md +48 -48
  148. package/docs/templates/sprint-contract.md +29 -29
  149. package/docs/templates/worker-dogfood-evidence.md +80 -80
  150. package/docs/templates/worker-dogfood-setup.md +68 -68
  151. package/docs/verification-matrix.md +70 -70
  152. package/examples/decision-gate-agent-dag.json +173 -173
  153. package/examples/example-dag.json +46 -46
  154. package/examples/hybrid-loop-agent-dag.json +188 -188
  155. package/harness.json +66 -66
  156. package/package.json +88 -52
  157. package/scripts/check-product-line-docs.sh +29 -29
  158. package/scripts/check-task-pool-root.sh +32 -32
  159. package/scripts/kb-bootstrap-init-skeleton.sh +240 -240
  160. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  161. package/scripts/kb-graph-incremental-prepare.sh +5 -5
  162. package/scripts/kb-graph-materialize.mjs +105 -105
  163. package/scripts/kb-graph-materialize.sh +4 -4
  164. package/scripts/kb-graph-promote.mjs +164 -164
  165. package/scripts/kb-graph-promote.sh +4 -4
  166. package/scripts/kb-query.mjs +554 -554
  167. package/scripts/kb-query.sh +5 -5
  168. package/skills/agent-worker/SKILL.md +39 -39
  169. package/skills/agent-worker/references/agent-worker-operator.md +60 -60
  170. package/skills/ai-engineering-context/SKILL.md +48 -48
  171. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  172. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  173. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  174. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  175. package/skills/analyze-product-dependencies/references/example.md +76 -76
  176. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  177. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  178. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  179. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  180. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  181. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  182. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  183. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  184. package/skills/analyze-product-requirements/SKILL.md +90 -90
  185. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  186. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  187. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  188. package/skills/analyze-product-requirements/references/example.md +86 -86
  189. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  190. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  191. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  192. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  193. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  194. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  195. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  196. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  197. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  198. package/skills/browser-tools/SKILL.md +196 -196
  199. package/skills/browser-tools/browser-content.js +103 -103
  200. package/skills/browser-tools/browser-cookies.js +35 -35
  201. package/skills/browser-tools/browser-eval.js +53 -53
  202. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  203. package/skills/browser-tools/browser-nav.js +44 -44
  204. package/skills/browser-tools/browser-pick.js +162 -162
  205. package/skills/browser-tools/browser-screenshot.js +34 -34
  206. package/skills/browser-tools/browser-start.js +86 -86
  207. package/skills/browser-tools/package-lock.json +2556 -2556
  208. package/skills/browser-tools/package.json +19 -19
  209. package/skills/code-review-core/SKILL.md +20 -20
  210. package/skills/codebase-scout/SKILL.md +19 -19
  211. package/skills/frontend-design-review/SKILL.md +66 -66
  212. package/skills/frontend-design-review/references/review-checklist.md +58 -58
  213. package/skills/frontend-implementation/SKILL.md +49 -49
  214. package/skills/frontend-implementation/references/code-standards.md +32 -32
  215. package/skills/frontend-implementation/references/design-spec.md +46 -46
  216. package/skills/frontend-implementation/references/node-contracts.md +27 -27
  217. package/skills/frontend-review/SKILL.md +59 -59
  218. package/skills/frontend-review/references/review-findings.md +47 -47
  219. package/skills/frontend-verification/SKILL.md +53 -53
  220. package/skills/frontend-verification/references/verification-checklist.md +68 -68
  221. package/skills/grill-me/SKILL.md +10 -10
  222. package/skills/grill-with-docs/SKILL.md +88 -88
  223. package/skills/grill-with-docs/adr-format.md +47 -47
  224. package/skills/grill-with-docs/context-format.md +60 -60
  225. package/skills/init-capability-evolution/SKILL.md +70 -70
  226. package/skills/loop-agent/SKILL.md +151 -151
  227. package/skills/loop-agent/references/README.md +67 -67
  228. package/skills/loop-agent/references/command-reference.md +527 -527
  229. package/skills/loop-agent/references/docs-converge.md +126 -126
  230. package/skills/loop-agent/references/harness-policy.md +263 -263
  231. package/skills/loop-agent/references/hybrid-dag.md +243 -243
  232. package/skills/loop-agent/references/learned/README.md +21 -21
  233. package/skills/loop-agent/references/long-running-loop.md +57 -57
  234. package/skills/loop-agent/references/model-routing.md +36 -36
  235. package/skills/loop-agent/references/multi-worktree.md +54 -54
  236. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  237. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  238. package/skills/loop-agent/references/pi-prompt.md +23 -23
  239. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  240. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  241. package/skills/loop-agent/references/task-workflow.md +89 -89
  242. package/skills/loop-agent/references/verification-and-failure-handling.md +141 -141
  243. package/skills/playwright-cli/SKILL.md +420 -420
  244. package/skills/playwright-cli/references/element-attributes.md +23 -23
  245. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  246. package/skills/playwright-cli/references/request-mocking.md +87 -87
  247. package/skills/playwright-cli/references/running-code.md +241 -241
  248. package/skills/playwright-cli/references/session-management.md +225 -225
  249. package/skills/playwright-cli/references/storage-state.md +275 -275
  250. package/skills/playwright-cli/references/test-generation.md +433 -433
  251. package/skills/playwright-cli/references/tracing.md +139 -139
  252. package/skills/playwright-cli/references/video-recording.md +143 -143
  253. package/skills/playwright-cli-case-generator/SKILL.md +74 -74
  254. package/skills/requesting-code-review/SKILL.md +101 -101
  255. package/skills/requesting-code-review/code-reviewer.md +168 -168
  256. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  257. package/skills/systematic-debugging/SKILL.md +296 -296
  258. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  259. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  260. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  261. package/skills/systematic-debugging/find-polluter.sh +63 -63
  262. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  263. package/skills/systematic-debugging/test-academic.md +14 -14
  264. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  265. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  266. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  267. package/skills/test-driven-development/SKILL.md +20 -20
  268. package/skills/using-git-worktrees/SKILL.md +215 -215
  269. package/skills/verification-before-completion/SKILL.md +154 -154
  270. package/skills/webapp-testing/SKILL.md +19 -19
@@ -1,83 +1,83 @@
1
- const DEFAULTS = {
2
- nodeWidth: 216,
3
- nodeHeight: 92,
4
- rankGap: 104,
5
- nodeGap: 28,
6
- padding: 32,
7
- };
8
-
9
- export function layoutDag(nodes = [], edges = [], ranks = [], options = {}) {
10
- const config = { ...DEFAULTS, ...options };
11
- const nodeIds = [...new Set(nodes.map((node) => node.nodeId).filter(Boolean))].sort();
12
- if (nodeIds.length === 0) return { width: 320, height: 160, nodes: [], edges: [] };
13
- const known = new Set(nodeIds);
14
- const validEdges = edges
15
- .filter((edge) => known.has(edge.from) && known.has(edge.to) && edge.from !== edge.to)
16
- .sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
17
- const layers = normalizeLayers(nodeIds, validEdges, ranks);
18
- const positioned = [];
19
- for (let rank = 0; rank < layers.length; rank++) {
20
- const ids = layers[rank];
21
- for (let index = 0; index < ids.length; index++) {
22
- positioned.push({
23
- nodeId: ids[index], rank, index,
24
- x: config.padding + rank * (config.nodeWidth + config.rankGap),
25
- y: config.padding + index * (config.nodeHeight + config.nodeGap),
26
- width: config.nodeWidth, height: config.nodeHeight,
27
- });
28
- }
29
- }
30
- const byId = new Map(positioned.map((node) => [node.nodeId, node]));
31
- const routed = validEdges.map((edge) => {
32
- const from = byId.get(edge.from);
33
- const to = byId.get(edge.to);
34
- const x1 = from.x + from.width;
35
- const y1 = from.y + from.height / 2;
36
- const x2 = to.x;
37
- const y2 = to.y + to.height / 2;
38
- const bend = Math.max(36, (x2 - x1) / 2);
39
- return { ...edge, path: `M ${x1} ${y1} C ${x1 + bend} ${y1}, ${x2 - bend} ${y2}, ${x2} ${y2}` };
40
- });
41
- const maxRows = Math.max(...layers.map((layer) => layer.length), 1);
42
- return {
43
- width: config.padding * 2 + layers.length * config.nodeWidth + (layers.length - 1) * config.rankGap,
44
- height: config.padding * 2 + maxRows * config.nodeHeight + (maxRows - 1) * config.nodeGap,
45
- nodes: positioned,
46
- edges: routed,
47
- };
48
- }
49
-
50
- function normalizeLayers(nodeIds, edges, ranks) {
51
- const known = new Set(nodeIds);
52
- const seen = new Set();
53
- const layers = [];
54
- if (Array.isArray(ranks)) {
55
- for (const rank of ranks) {
56
- if (!Array.isArray(rank)) continue;
57
- const ids = [...new Set(rank.filter((id) => known.has(id) && !seen.has(id)))].sort();
58
- ids.forEach((id) => seen.add(id));
59
- if (ids.length > 0) layers.push(ids);
60
- }
61
- }
62
- if (layers.length === 0 && edges.length > 0) {
63
- const level = new Map(nodeIds.map((id) => [id, 0]));
64
- for (let pass = 0; pass < nodeIds.length; pass++) {
65
- let changed = false;
66
- for (const edge of edges) {
67
- const next = Math.min(nodeIds.length - 1, level.get(edge.from) + 1);
68
- if (next > level.get(edge.to)) { level.set(edge.to, next); changed = true; }
69
- }
70
- if (!changed) break;
71
- }
72
- for (const id of nodeIds) {
73
- const rank = level.get(id);
74
- (layers[rank] ??= []).push(id);
75
- seen.add(id);
76
- }
77
- for (const layer of layers) layer?.sort();
78
- }
79
- for (const id of nodeIds) {
80
- if (!seen.has(id)) (layers[0] ??= []).push(id);
81
- }
82
- return layers.filter(Boolean);
83
- }
1
+ const DEFAULTS = {
2
+ nodeWidth: 216,
3
+ nodeHeight: 92,
4
+ rankGap: 104,
5
+ nodeGap: 28,
6
+ padding: 32,
7
+ };
8
+
9
+ export function layoutDag(nodes = [], edges = [], ranks = [], options = {}) {
10
+ const config = { ...DEFAULTS, ...options };
11
+ const nodeIds = [...new Set(nodes.map((node) => node.nodeId).filter(Boolean))].sort();
12
+ if (nodeIds.length === 0) return { width: 320, height: 160, nodes: [], edges: [] };
13
+ const known = new Set(nodeIds);
14
+ const validEdges = edges
15
+ .filter((edge) => known.has(edge.from) && known.has(edge.to) && edge.from !== edge.to)
16
+ .sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
17
+ const layers = normalizeLayers(nodeIds, validEdges, ranks);
18
+ const positioned = [];
19
+ for (let rank = 0; rank < layers.length; rank++) {
20
+ const ids = layers[rank];
21
+ for (let index = 0; index < ids.length; index++) {
22
+ positioned.push({
23
+ nodeId: ids[index], rank, index,
24
+ x: config.padding + rank * (config.nodeWidth + config.rankGap),
25
+ y: config.padding + index * (config.nodeHeight + config.nodeGap),
26
+ width: config.nodeWidth, height: config.nodeHeight,
27
+ });
28
+ }
29
+ }
30
+ const byId = new Map(positioned.map((node) => [node.nodeId, node]));
31
+ const routed = validEdges.map((edge) => {
32
+ const from = byId.get(edge.from);
33
+ const to = byId.get(edge.to);
34
+ const x1 = from.x + from.width;
35
+ const y1 = from.y + from.height / 2;
36
+ const x2 = to.x;
37
+ const y2 = to.y + to.height / 2;
38
+ const bend = Math.max(36, (x2 - x1) / 2);
39
+ return { ...edge, path: `M ${x1} ${y1} C ${x1 + bend} ${y1}, ${x2 - bend} ${y2}, ${x2} ${y2}` };
40
+ });
41
+ const maxRows = Math.max(...layers.map((layer) => layer.length), 1);
42
+ return {
43
+ width: config.padding * 2 + layers.length * config.nodeWidth + (layers.length - 1) * config.rankGap,
44
+ height: config.padding * 2 + maxRows * config.nodeHeight + (maxRows - 1) * config.nodeGap,
45
+ nodes: positioned,
46
+ edges: routed,
47
+ };
48
+ }
49
+
50
+ function normalizeLayers(nodeIds, edges, ranks) {
51
+ const known = new Set(nodeIds);
52
+ const seen = new Set();
53
+ const layers = [];
54
+ if (Array.isArray(ranks)) {
55
+ for (const rank of ranks) {
56
+ if (!Array.isArray(rank)) continue;
57
+ const ids = [...new Set(rank.filter((id) => known.has(id) && !seen.has(id)))].sort();
58
+ ids.forEach((id) => seen.add(id));
59
+ if (ids.length > 0) layers.push(ids);
60
+ }
61
+ }
62
+ if (layers.length === 0 && edges.length > 0) {
63
+ const level = new Map(nodeIds.map((id) => [id, 0]));
64
+ for (let pass = 0; pass < nodeIds.length; pass++) {
65
+ let changed = false;
66
+ for (const edge of edges) {
67
+ const next = Math.min(nodeIds.length - 1, level.get(edge.from) + 1);
68
+ if (next > level.get(edge.to)) { level.set(edge.to, next); changed = true; }
69
+ }
70
+ if (!changed) break;
71
+ }
72
+ for (const id of nodeIds) {
73
+ const rank = level.get(id);
74
+ (layers[rank] ??= []).push(id);
75
+ seen.add(id);
76
+ }
77
+ for (const layer of layers) layer?.sort();
78
+ }
79
+ for (const id of nodeIds) {
80
+ if (!seen.has(id)) (layers[0] ??= []).push(id);
81
+ }
82
+ return layers.filter(Boolean);
83
+ }
@@ -1,72 +1,72 @@
1
- /** DAG / dashboard pure model helpers (Observe UI R5). */
2
-
3
- const TERMINAL_DAG_EFFECTIVE_STATUSES = new Set([
4
- "finished",
5
- "failed",
6
- "superseded",
7
- "abandoned",
8
- ]);
9
-
10
- const TERMINAL_RUN_STATUSES = new Set([
11
- "finished",
12
- "completed",
13
- "succeeded",
14
- "failed",
15
- "partial_failed",
16
- "error",
17
- "cancelled",
18
- ]);
19
-
20
- export function dagMetaVisibility(dag) {
21
- const terminal =
22
- (dag?.lifecycle ?? "").toLowerCase() === "completed" ||
23
- TERMINAL_DAG_EFFECTIVE_STATUSES.has(dag?.effectiveStatus);
24
- return {
25
- showExecutorStatus: !terminal,
26
- showConsistencyWarning: dag?.stateConsistent === false,
27
- showResume: dag?.recoveryEligibility?.canResume === true,
28
- showReconcile: dag?.recoveryEligibility?.canReconcile === true,
29
- };
30
- }
31
-
32
- function isNodeActive(status) {
33
- if (!status) return false;
34
- const s = status.toLowerCase();
35
- return (
36
- s === "running" || s === "started" || s === "pending" || s === "queued"
37
- );
38
- }
39
-
40
- export function isDagRunActive(dag) {
41
- if (
42
- ["running", "running-quiet", "remote-unknown", "pending"].includes(
43
- dag.effectiveStatus,
44
- )
45
- )
46
- return true;
47
- if (dag.effectiveStatus && dag.effectiveStatus !== "unknown") return false;
48
- const status = (dag.status ?? "").toLowerCase();
49
- if ((dag.lifecycle ?? "").toLowerCase() === "paused") return false;
50
- if (["orphaned", "stale"].includes((dag.liveness ?? "").toLowerCase()))
51
- return false;
52
- if (TERMINAL_RUN_STATUSES.has(status)) return false;
53
- if (["running", "pending", "started"].includes(status)) return true;
54
- return (dag.nodes ?? []).some((n) => isNodeActive(n.status));
55
- }
56
-
57
- const DASHBOARD_FEATURE_STATUSES = new Set(["running", "needs_action"]);
58
-
59
- export function dashboardVisibleFeatures(features) {
60
- return (features ?? []).filter((feature) =>
61
- DASHBOARD_FEATURE_STATUSES.has(feature?.status),
62
- );
63
- }
64
-
65
- export function findDagForRun(snapshot, _workerRunId, task) {
66
- const dagRuns = snapshot?.dagRuns ?? [];
67
- if (task?.dagRunId) {
68
- const byTask = dagRuns.find((d) => d.dagRunId === task.dagRunId);
69
- if (byTask) return byTask;
70
- }
71
- return undefined;
72
- }
1
+ /** DAG / dashboard pure model helpers (Observe UI R5). */
2
+
3
+ const TERMINAL_DAG_EFFECTIVE_STATUSES = new Set([
4
+ "finished",
5
+ "failed",
6
+ "superseded",
7
+ "abandoned",
8
+ ]);
9
+
10
+ const TERMINAL_RUN_STATUSES = new Set([
11
+ "finished",
12
+ "completed",
13
+ "succeeded",
14
+ "failed",
15
+ "partial_failed",
16
+ "error",
17
+ "cancelled",
18
+ ]);
19
+
20
+ export function dagMetaVisibility(dag) {
21
+ const terminal =
22
+ (dag?.lifecycle ?? "").toLowerCase() === "completed" ||
23
+ TERMINAL_DAG_EFFECTIVE_STATUSES.has(dag?.effectiveStatus);
24
+ return {
25
+ showExecutorStatus: !terminal,
26
+ showConsistencyWarning: dag?.stateConsistent === false,
27
+ showResume: dag?.recoveryEligibility?.canResume === true,
28
+ showReconcile: dag?.recoveryEligibility?.canReconcile === true,
29
+ };
30
+ }
31
+
32
+ function isNodeActive(status) {
33
+ if (!status) return false;
34
+ const s = status.toLowerCase();
35
+ return (
36
+ s === "running" || s === "started" || s === "pending" || s === "queued"
37
+ );
38
+ }
39
+
40
+ export function isDagRunActive(dag) {
41
+ if (
42
+ ["running", "running-quiet", "remote-unknown", "pending"].includes(
43
+ dag.effectiveStatus,
44
+ )
45
+ )
46
+ return true;
47
+ if (dag.effectiveStatus && dag.effectiveStatus !== "unknown") return false;
48
+ const status = (dag.status ?? "").toLowerCase();
49
+ if ((dag.lifecycle ?? "").toLowerCase() === "paused") return false;
50
+ if (["orphaned", "stale"].includes((dag.liveness ?? "").toLowerCase()))
51
+ return false;
52
+ if (TERMINAL_RUN_STATUSES.has(status)) return false;
53
+ if (["running", "pending", "started"].includes(status)) return true;
54
+ return (dag.nodes ?? []).some((n) => isNodeActive(n.status));
55
+ }
56
+
57
+ const DASHBOARD_FEATURE_STATUSES = new Set(["running", "needs_action"]);
58
+
59
+ export function dashboardVisibleFeatures(features) {
60
+ return (features ?? []).filter((feature) =>
61
+ DASHBOARD_FEATURE_STATUSES.has(feature?.status),
62
+ );
63
+ }
64
+
65
+ export function findDagForRun(snapshot, _workerRunId, task) {
66
+ const dagRuns = snapshot?.dagRuns ?? [];
67
+ if (task?.dagRunId) {
68
+ const byTask = dagRuns.find((d) => d.dagRunId === task.dagRunId);
69
+ if (byTask) return byTask;
70
+ }
71
+ return undefined;
72
+ }
@@ -1,70 +1,70 @@
1
- /** DOM helpers (Observe UI R5). */
2
-
3
- /**
4
- * Stable key → node maps, kept per (parent, scope) so reconcileKeyed never
5
- * depends on dataset support. A WeakMap keyed by parent holds a map of
6
- * scope → Map<key, node>. A stale parent (e.g. after view teardown) releases
7
- * its bookkeeping automatically. The default scope "default" is used when a
8
- * caller reconciles all keyed children of a parent in a single pass; multiple
9
- * reconcilers that share one parent must pass distinct scope strings so they
10
- * do not clobber each other's keys.
11
- */
12
- const reconcileMaps = new WeakMap();
13
-
14
- /**
15
- * Keyed DOM reconciliation: preserve node identity when a stable key recurs
16
- * across renders; create a fresh node (with fresh closures) when the key is
17
- * new; remove nodes whose key dropped out of the item set.
18
- *
19
- * Callers drive node construction via `create` and content sync via optional
20
- * `update`. The parent is not bulk-cleared, so headings/headers placed outside
21
- * this call survive, and scrolling/focus/pointer state on reused nodes is kept.
22
- *
23
- * @param {Node} parent
24
- * @param {Array} items
25
- * @param {{ getKey: (item: any, index: number) => string,
26
- * create: (item: any, index: number) => Node,
27
- * update?: (node: any, item: any, index: number) => void,
28
- * scope?: string }} opts
29
- * @returns {Array<Node>} the (possibly reused) nodes in item order
30
- */
1
+ /** DOM helpers (Observe UI R5). */
2
+
3
+ /**
4
+ * Stable key → node maps, kept per (parent, scope) so reconcileKeyed never
5
+ * depends on dataset support. A WeakMap keyed by parent holds a map of
6
+ * scope → Map<key, node>. A stale parent (e.g. after view teardown) releases
7
+ * its bookkeeping automatically. The default scope "default" is used when a
8
+ * caller reconciles all keyed children of a parent in a single pass; multiple
9
+ * reconcilers that share one parent must pass distinct scope strings so they
10
+ * do not clobber each other's keys.
11
+ */
12
+ const reconcileMaps = new WeakMap();
13
+
14
+ /**
15
+ * Keyed DOM reconciliation: preserve node identity when a stable key recurs
16
+ * across renders; create a fresh node (with fresh closures) when the key is
17
+ * new; remove nodes whose key dropped out of the item set.
18
+ *
19
+ * Callers drive node construction via `create` and content sync via optional
20
+ * `update`. The parent is not bulk-cleared, so headings/headers placed outside
21
+ * this call survive, and scrolling/focus/pointer state on reused nodes is kept.
22
+ *
23
+ * @param {Node} parent
24
+ * @param {Array} items
25
+ * @param {{ getKey: (item: any, index: number) => string,
26
+ * create: (item: any, index: number) => Node,
27
+ * update?: (node: any, item: any, index: number) => void,
28
+ * scope?: string }} opts
29
+ * @returns {Array<Node>} the (possibly reused) nodes in item order
30
+ */
31
31
  export function reconcileKeyed(parent, items, opts) {
32
- const { getKey, create, update, scope = "default" } = opts;
33
- let scopes = reconcileMaps.get(parent);
34
- if (!scopes) {
35
- scopes = new Map();
36
- reconcileMaps.set(parent, scopes);
37
- }
38
- let map = scopes.get(scope);
39
- if (!map) {
40
- map = new Map();
41
- scopes.set(scope, map);
42
- }
43
-
44
- const nextKeys = new Set();
45
- const result = [];
46
- for (let i = 0; i < items.length; i++) {
47
- const item = items[i];
48
- const key = String(getKey(item, i));
49
- nextKeys.add(key);
50
- let node = map.get(key);
51
- if (node) {
52
- if (update) update(node, item, i);
53
- } else {
54
- node = create(item, i);
55
- map.set(key, node);
56
- // Sync freshly created nodes so callers can split structure (create) from
57
- // content (update) and still populate the first paint.
58
- if (update) update(node, item, i);
59
- }
32
+ const { getKey, create, update, scope = "default" } = opts;
33
+ let scopes = reconcileMaps.get(parent);
34
+ if (!scopes) {
35
+ scopes = new Map();
36
+ reconcileMaps.set(parent, scopes);
37
+ }
38
+ let map = scopes.get(scope);
39
+ if (!map) {
40
+ map = new Map();
41
+ scopes.set(scope, map);
42
+ }
43
+
44
+ const nextKeys = new Set();
45
+ const result = [];
46
+ for (let i = 0; i < items.length; i++) {
47
+ const item = items[i];
48
+ const key = String(getKey(item, i));
49
+ nextKeys.add(key);
50
+ let node = map.get(key);
51
+ if (node) {
52
+ if (update) update(node, item, i);
53
+ } else {
54
+ node = create(item, i);
55
+ map.set(key, node);
56
+ // Sync freshly created nodes so callers can split structure (create) from
57
+ // content (update) and still populate the first paint.
58
+ if (update) update(node, item, i);
59
+ }
60
60
  result.push(node);
61
61
  }
62
-
63
- // Remove nodes whose key is no longer present in THIS scope.
64
- for (const [key, node] of map) {
65
- if (!nextKeys.has(key)) {
66
- parent.removeChild(node);
67
- map.delete(key);
62
+
63
+ // Remove nodes whose key is no longer present in THIS scope.
64
+ for (const [key, node] of map) {
65
+ if (!nextKeys.has(key)) {
66
+ parent.removeChild(node);
67
+ map.delete(key);
68
68
  }
69
69
  }
70
70
 
@@ -160,61 +160,61 @@ export function syncCompatibleNode(current, next) {
160
160
  }
161
161
 
162
162
  export function clearNode(node) {
163
- while (node.firstChild) node.removeChild(node.firstChild);
164
- }
165
-
166
- export function el(tag, className, text) {
167
- const node = document.createElement(tag);
168
- if (className) node.className = className;
169
- if (text !== undefined) node.textContent = text;
170
- return node;
171
- }
172
-
173
- export function metaGrid(entries) {
174
- const dl = el("dl", "meta-grid");
175
- for (const [label, value] of entries) {
176
- const item = document.createElement("div");
177
- item.className = "meta-item";
178
- const dt = document.createElement("dt");
179
- dt.textContent = label;
180
- const dd = document.createElement("dd");
181
- if (value instanceof Node) dd.appendChild(value);
182
- else dd.textContent = value ?? "—";
183
- item.appendChild(dt);
184
- item.appendChild(dd);
185
- dl.appendChild(item);
186
- }
187
- return dl;
188
- }
189
-
190
- export function buildTable(headers, rows) {
191
- const table = document.createElement("table");
192
- const thead = document.createElement("thead");
193
- const headRow = document.createElement("tr");
194
- for (const h of headers) {
195
- const th = document.createElement("th");
196
- th.textContent = h;
197
- headRow.appendChild(th);
198
- }
199
- thead.appendChild(headRow);
200
- table.appendChild(thead);
201
-
202
- const tbody = document.createElement("tbody");
203
- for (const row of rows) {
204
- const tr = document.createElement("tr");
205
- if (row.className) tr.className = row.className;
206
- if (row.onClick) {
207
- tr.classList.add("clickable");
208
- tr.addEventListener("click", row.onClick);
209
- }
210
- for (const cell of row.cells) {
211
- const td = document.createElement("td");
212
- if (cell instanceof Node) td.appendChild(cell);
213
- else td.textContent = cell ?? "—";
214
- tr.appendChild(td);
215
- }
216
- tbody.appendChild(tr);
217
- }
218
- table.appendChild(tbody);
219
- return table;
220
- }
163
+ while (node.firstChild) node.removeChild(node.firstChild);
164
+ }
165
+
166
+ export function el(tag, className, text) {
167
+ const node = document.createElement(tag);
168
+ if (className) node.className = className;
169
+ if (text !== undefined) node.textContent = text;
170
+ return node;
171
+ }
172
+
173
+ export function metaGrid(entries) {
174
+ const dl = el("dl", "meta-grid");
175
+ for (const [label, value] of entries) {
176
+ const item = document.createElement("div");
177
+ item.className = "meta-item";
178
+ const dt = document.createElement("dt");
179
+ dt.textContent = label;
180
+ const dd = document.createElement("dd");
181
+ if (value instanceof Node) dd.appendChild(value);
182
+ else dd.textContent = value ?? "—";
183
+ item.appendChild(dt);
184
+ item.appendChild(dd);
185
+ dl.appendChild(item);
186
+ }
187
+ return dl;
188
+ }
189
+
190
+ export function buildTable(headers, rows) {
191
+ const table = document.createElement("table");
192
+ const thead = document.createElement("thead");
193
+ const headRow = document.createElement("tr");
194
+ for (const h of headers) {
195
+ const th = document.createElement("th");
196
+ th.textContent = h;
197
+ headRow.appendChild(th);
198
+ }
199
+ thead.appendChild(headRow);
200
+ table.appendChild(thead);
201
+
202
+ const tbody = document.createElement("tbody");
203
+ for (const row of rows) {
204
+ const tr = document.createElement("tr");
205
+ if (row.className) tr.className = row.className;
206
+ if (row.onClick) {
207
+ tr.classList.add("clickable");
208
+ tr.addEventListener("click", row.onClick);
209
+ }
210
+ for (const cell of row.cells) {
211
+ const td = document.createElement("td");
212
+ if (cell instanceof Node) td.appendChild(cell);
213
+ else td.textContent = cell ?? "—";
214
+ tr.appendChild(td);
215
+ }
216
+ tbody.appendChild(tr);
217
+ }
218
+ table.appendChild(tbody);
219
+ return table;
220
+ }
@@ -0,0 +1,71 @@
1
+ export type PoolTaskLike = {
2
+ taskId?: string;
3
+ featureId?: string;
4
+ status?: string;
5
+ liveness?: string;
6
+ updatedAt?: string;
7
+ finishedAt?: string;
8
+ startedAt?: string;
9
+ title?: string;
10
+ priority?: string;
11
+ type?: string;
12
+ reason?: string;
13
+ recommendedFollowUp?: string;
14
+ failureCategory?: string;
15
+ [key: string]: unknown;
16
+ };
17
+
18
+ export type PoolStatusCounts = {
19
+ running: number;
20
+ pending: number;
21
+ blocked: number;
22
+ failed: number;
23
+ succeeded: number;
24
+ other: number;
25
+ };
26
+
27
+ export type PlannerReadyItem = {
28
+ featureId: string;
29
+ taskId: string;
30
+ title?: string;
31
+ priority?: string;
32
+ type?: string;
33
+ reason: string;
34
+ };
35
+
36
+ export type FeaturePlanningLike = {
37
+ featureId?: string;
38
+ planning?: {
39
+ selected?: Array<{
40
+ featureId?: string;
41
+ taskId?: string;
42
+ title?: string;
43
+ priority?: string;
44
+ type?: string;
45
+ reason?: string;
46
+ }>;
47
+ };
48
+ };
49
+
50
+ export function sortPoolTasks(tasks: PoolTaskLike[] | null | undefined): PoolTaskLike[];
51
+ export function matchesPoolStatusFilter(
52
+ taskStatus: string | null | undefined,
53
+ filter: string | null | undefined,
54
+ ): boolean;
55
+ export function poolStatusCounts(
56
+ tasks: Array<{ status?: string }> | null | undefined,
57
+ ): PoolStatusCounts;
58
+ export function isPoolAttentionTask(
59
+ task: PoolTaskLike | null | undefined,
60
+ ): boolean;
61
+ export function attentionRank(task: PoolTaskLike | null | undefined): number;
62
+ export function sortAttentionTasks(
63
+ tasks: PoolTaskLike[] | null | undefined,
64
+ ): PoolTaskLike[];
65
+ export function collectPlannerReadyTasks(
66
+ features: FeaturePlanningLike[] | null | undefined,
67
+ ): PlannerReadyItem[];
68
+ export function taskIdentityKey(
69
+ featureId: string | null | undefined,
70
+ taskId: string | null | undefined,
71
+ ): string;