@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.4

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 (202) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +187 -92
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/recommendations.js +13 -2
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +429 -28
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/executors/dag-pi-executor.js +1691 -29
  17. package/dist/executors/pi-extension-resolver.js +233 -0
  18. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  19. package/dist/executors/pi-sdk-executor.js +180 -33
  20. package/dist/executors/shell-executor.js +1152 -171
  21. package/dist/executors/shell-write-guard.js +14 -0
  22. package/dist/shared/dag-prompt-override.js +28 -0
  23. package/dist/shared/openspec-spec.js +70 -4
  24. package/dist/shared/operator/capabilities.js +32 -0
  25. package/dist/shared/playwright-cli-command-policy.js +15 -0
  26. package/dist/shared/update/console-notifier.js +100 -0
  27. package/dist/{cli → shared}/update/npm-client.js +41 -8
  28. package/dist/{cli → shared}/update/state.js +41 -13
  29. package/dist/task/config-types.js +19 -5
  30. package/dist/task/contract/project.js +3 -0
  31. package/dist/task/contract/schema.js +2 -1
  32. package/dist/task/frontend-project-capability.js +60 -17
  33. package/dist/task/source-prepare/parse-intent.js +24 -4
  34. package/dist/task/source-prepare/prepare.js +36 -0
  35. package/dist/worker/cli.js +31 -1
  36. package/dist/worker/console/chat/pi-console-config.js +81 -10
  37. package/dist/worker/console/chat/routes.js +1 -0
  38. package/dist/worker/console/dag-execution-receipt.js +33 -0
  39. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  40. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  41. package/dist/worker/console/index.js +3 -0
  42. package/dist/worker/console/operation-store.js +226 -6
  43. package/dist/worker/console/operator-actions.js +263 -0
  44. package/dist/worker/console/operator-user-error.js +2 -2
  45. package/dist/worker/console/routes.js +43 -2
  46. package/dist/worker/console/security.js +63 -9
  47. package/dist/worker/console/server.js +151 -6
  48. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
  49. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
  50. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
  51. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
  52. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
  53. package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
  54. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
  64. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
  65. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
  66. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
  69. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
  75. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
  76. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
  77. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
  78. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
  79. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
  80. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
  81. package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
  82. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
  83. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
  84. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
  85. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
  86. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
  87. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
  88. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
  89. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
  90. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
  91. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
  92. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
  93. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
  94. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
  95. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
  96. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
  97. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
  98. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
  99. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
  100. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
  101. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
  102. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
  104. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
  105. package/dist/worker/console/static/index.html +2 -2
  106. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  107. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  108. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  109. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  110. package/dist/worker/observe/node-input.js +74 -3
  111. package/dist/worker/observe/node-process.js +377 -0
  112. package/dist/worker/observe/routes.js +36 -3
  113. package/dist/worker/observe/shell-handler-keys.js +34 -0
  114. package/dist/worker/observe/static/api.js +66 -0
  115. package/dist/worker/observe/static/constants.js +8 -1
  116. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  117. package/dist/worker/observe/static/format.js +23 -0
  118. package/dist/worker/observe/static/markdown-render.js +20 -1
  119. package/dist/worker/observe/static/operator-chrome.js +4 -0
  120. package/dist/worker/observe/static/state.js +13 -2
  121. package/dist/worker/observe/static/styles.css +73 -0
  122. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  123. package/dist/worker/observe/static/views/dag.js +8 -1
  124. package/dist/worker/observe/static/views/failures.js +5 -2
  125. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  126. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
  127. package/dist/workflows/dag/contract-output-registry.js +15 -0
  128. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  129. package/dist/workflows/dag/failure-category.js +12 -0
  130. package/dist/workflows/dag/frontend-closeout.js +221 -0
  131. package/dist/workflows/dag/frontend-design-policy.js +366 -0
  132. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  133. package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
  134. package/dist/workflows/dag/frontend-prewrite-gate.js +343 -391
  135. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  136. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  137. package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
  138. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  139. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  140. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  141. package/dist/workflows/dag/frontend-repair.js +7 -424
  142. package/dist/workflows/dag/frontend-review-context.js +195 -8
  143. package/dist/workflows/dag/frontend-risk.js +15 -2
  144. package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
  145. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  146. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  147. package/dist/workflows/dag/frontend-shape.js +425 -0
  148. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  149. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  150. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  151. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  152. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  153. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  154. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  155. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  156. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  157. package/dist/workflows/dag/frontend-typed-event-store.js +439 -0
  158. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  159. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  160. package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
  161. package/dist/workflows/dag/frontend-writer-admission.js +260 -0
  162. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  163. package/dist/workflows/dag/init-hybrid.js +572 -500
  164. package/dist/workflows/dag/node-execution.js +307 -70
  165. package/dist/workflows/dag/prompt.js +10 -18
  166. package/dist/workflows/dag/rerun-plan.js +144 -5
  167. package/dist/workflows/dag/rerun-run.js +58 -6
  168. package/dist/workflows/dag/retry-policy.js +228 -4
  169. package/dist/workflows/dag/runner.js +314 -119
  170. package/dist/workflows/dag/scheduler.js +114 -16
  171. package/dist/workflows/dag/structured-output-repair.js +712 -0
  172. package/dist/workflows/dag/types.js +235 -23
  173. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  174. package/dist/workflows/dag/validate.js +60 -10
  175. package/docs/init-surface.manifest.json +5 -0
  176. package/docs/operations/README.md +1 -1
  177. package/docs/templates/agent-dag.schema.json +44 -0
  178. package/docs/templates/frontend-test-dag.json +8 -10
  179. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  180. package/docs/templates/init-managed-agents.md +13 -3
  181. package/harness.json +3 -3
  182. package/package.json +5 -1
  183. package/scripts/next-info.mjs +356 -0
  184. package/scripts/next-publish-gate.mjs +238 -0
  185. package/scripts/release-source-binding.mjs +251 -0
  186. package/skills/codebase-scout/SKILL.md +1 -1
  187. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  188. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  189. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  190. package/skills/frontend-implementation/SKILL.md +4 -4
  191. package/skills/frontend-implementation/references/design-spec.md +11 -7
  192. package/skills/frontend-implementation/references/node-contracts.md +20 -11
  193. package/skills/loop-agent/references/command-reference.md +11 -4
  194. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  195. package/skills/playwright-cli/SKILL.md +22 -0
  196. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  197. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  198. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  199. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  200. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  201. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  202. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -3,13 +3,13 @@
3
3
  * Strong evidence only: package.json deps, config files, source imports.
4
4
  * Lockfile-only or directory-name-only must NOT activate adapters.
5
5
  */
6
- import { readFile, readdir, stat } from "node:fs/promises";
6
+ import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { z } from "zod";
9
- import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, MAX_OPENSPEC_CANDIDATE_PATHS, OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isGovernanceSubtreePath, isOpenspecSpecFilePath, } from "../shared/openspec-spec.js";
9
+ import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, MAX_OPENSPEC_CANDIDATE_PATHS, OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isGovernanceSubtreePath, } from "../shared/openspec-spec.js";
10
10
  const classifiedPathSchema = z
11
11
  .string()
12
- .refine((candidate) => isOpenspecSpecFilePath(candidate), "classified path must be a repo-relative supported spec file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
12
+ .refine((candidate) => OPENSPEC_SPEC_EXT_RE.test(candidate), "classified path must be a repo-relative supported spec file");
13
13
  /**
14
14
  * Stable semantic partition of `designEvidence.normativePaths`. Keys are a
15
15
  * generic directory convention (not project-specific file names); the same
@@ -81,6 +81,34 @@ async function exists(filePath) {
81
81
  return false;
82
82
  }
83
83
  }
84
+ /**
85
+ * Resolve only unambiguous symlink-root aliases. The physical target is never
86
+ * emitted as a candidate path: it merely lets task-source wording such as
87
+ * `ai_doc/rules.md` resolve to the stable repo-local path `ai_workspace/rules.md`.
88
+ */
89
+ export async function resolveFrontendSpecRootAliases(repoRoot, specRoots) {
90
+ const candidates = [];
91
+ for (const logicalRoot of specRoots) {
92
+ const full = path.join(repoRoot, logicalRoot);
93
+ try {
94
+ if (!(await lstat(full)).isSymbolicLink())
95
+ continue;
96
+ const alias = path.basename(await realpath(full));
97
+ if (alias && alias !== path.basename(logicalRoot)) {
98
+ candidates.push({ alias, logicalRoot });
99
+ }
100
+ }
101
+ catch {
102
+ // Missing/broken links are handled by normal candidate discovery drift.
103
+ }
104
+ }
105
+ const counts = new Map();
106
+ for (const item of candidates)
107
+ counts.set(item.alias, (counts.get(item.alias) ?? 0) + 1);
108
+ return candidates
109
+ .filter((item) => counts.get(item.alias) === 1)
110
+ .sort((a, b) => a.alias.localeCompare(b.alias));
111
+ }
84
112
  /**
85
113
  * Resolve the governance subtree root from harness.json.governanceRoot.
86
114
  * Falls back to DEFAULT_OPENSPEC_GOVERNANCE_ROOT when the manifest is missing,
@@ -110,22 +138,20 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
110
138
  return true;
111
139
  return extraExcludedPrefixes.some((prefix) => rel === prefix || rel.startsWith(prefix + "/"));
112
140
  };
113
- for (const specDir of OPENSPEC_SPEC_DIRS) {
141
+ for (const specDir of options.specRoots ?? [...OPENSPEC_SPEC_DIRS]) {
114
142
  const root = path.join(repoRoot, specDir);
115
143
  if (!(await exists(root)))
116
144
  continue;
117
145
  await walk(root, 0);
118
146
  }
119
- // openspec/schemas/** and openspec/project-specs/** sort first; remaining
120
- // ai_workspace/** business files follow. Dedupe preserves discovery order.
147
+ // Keep the established default-root priority for deterministic compatibility;
148
+ // configured extra roots are appended in lexical order.
121
149
  const unique = [...new Set(collected)];
122
- const openspecPaths = unique
123
- .filter((candidate) => candidate.startsWith("openspec/"))
150
+ const defaultPaths = OPENSPEC_SPEC_DIRS.flatMap((root) => unique.filter((candidate) => candidate.startsWith(root + "/")).sort());
151
+ const customPaths = unique
152
+ .filter((candidate) => !defaultPaths.includes(candidate))
124
153
  .sort();
125
- const aiWorkspacePaths = unique
126
- .filter((candidate) => candidate.startsWith("ai_workspace/"))
127
- .sort();
128
- const deduped = [...openspecPaths, ...aiWorkspacePaths];
154
+ const deduped = [...defaultPaths, ...customPaths];
129
155
  const truncated = deduped.length > MAX_OPENSPEC_CANDIDATE_PATHS;
130
156
  const paths = truncated
131
157
  ? deduped.slice(0, MAX_OPENSPEC_CANDIDATE_PATHS)
@@ -241,6 +267,14 @@ function emptyClassified() {
241
267
  * project-specific file names. Basename stems use word-boundary matching so
242
268
  * e.g. `*api*` (case-insensitive) maps to `rule.api` without hardcoding names.
243
269
  */
270
+ /** True when any path segment (other than the file itself) carries a
271
+ * component/theme kind marker. Directory-organized libraries such as
272
+ * `ui/ucp-components-md/AdvancedSearch.md` rely on this; templates/ and
273
+ * rules/ are handled by their own branches before this fallback runs. */
274
+ function segmentMatchesComponentKind(segments) {
275
+ const dirSegments = segments.slice(0, -1);
276
+ return dirSegments.some((segment) => /\bcomponents?\b/.test(segment.toLowerCase()));
277
+ }
244
278
  function classifyOpenspecPaths(paths) {
245
279
  const classified = emptyClassified();
246
280
  const ruleStem = (basename) => {
@@ -260,7 +294,7 @@ function classifyOpenspecPaths(paths) {
260
294
  return "other";
261
295
  };
262
296
  for (const candidate of paths) {
263
- if (!isOpenspecSpecFilePath(candidate))
297
+ if (!OPENSPEC_SPEC_EXT_RE.test(candidate))
264
298
  continue;
265
299
  const segments = candidate.split("/");
266
300
  const basename = segments[segments.length - 1] ?? "";
@@ -279,8 +313,17 @@ function classifyOpenspecPaths(paths) {
279
313
  classified.theme.push(candidate);
280
314
  else if (/\bcomponents?\b/.test(lower))
281
315
  classified.component.push(candidate);
282
- else
316
+ else if (segmentMatchesComponentKind(segments)) {
317
+ // Directory-organized component libraries (e.g.
318
+ // ui/ucp-components-md/AdvancedSearch.md) carry the kind in the
319
+ // parent directory name, not the file name. The parent-dir rule
320
+ // is a fallback so such layouts still land in the component
321
+ // bucket instead of being dropped into uiOther.
322
+ classified.component.push(candidate);
323
+ }
324
+ else {
283
325
  classified.uiOther.push(candidate);
326
+ }
284
327
  }
285
328
  else {
286
329
  classified.advisoryOther.push(candidate);
@@ -361,13 +404,13 @@ export function buildAdapterGuidance(capability) {
361
404
  * Discover frontend project capability from repo root.
362
405
  * Strong evidence: package.json direct deps + config/bootstrap files.
363
406
  */
364
- export async function discoverFrontendProjectCapability(repoRoot) {
407
+ export async function discoverFrontendProjectCapability(repoRoot, options = {}) {
365
408
  const evidencePaths = [];
366
409
  const reasons = [];
367
410
  const pkgPath = path.join(repoRoot, "package.json");
368
411
  const pkgRaw = await readJson(pkgPath);
369
412
  if (!pkgRaw || typeof pkgRaw !== "object") {
370
- const discovery = await listOpenspecDiscovery(repoRoot);
413
+ const discovery = await listOpenspecDiscovery(repoRoot, options);
371
414
  const designEvidence = buildDesignEvidence(discovery.paths);
372
415
  const base = {
373
416
  schemaVersion: 1,
@@ -540,7 +583,7 @@ export async function discoverFrontendProjectCapability(repoRoot) {
540
583
  router = router ?? "next-router";
541
584
  if (hasDep(deps, "vue-router"))
542
585
  router = "vue-router";
543
- const discovery = await listOpenspecDiscovery(repoRoot);
586
+ const discovery = await listOpenspecDiscovery(repoRoot, options);
544
587
  const designEvidence = buildDesignEvidence(discovery.paths);
545
588
  if (discovery.paths.length)
546
589
  evidencePaths.push(...discovery.paths.slice(0, 5));
@@ -35,8 +35,15 @@ const HEADING_ALIASES = {
35
35
  assumptions: new Set(["假设", "assumptions"]),
36
36
  };
37
37
  const OBJECTIVE_MAX_CHARS = 1200;
38
- /** Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 / AC-BE-12 / AC-001. */
39
- const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?\d+(?:[-_]\d+)?)\s*[::]?\s*(.*)$/iu;
38
+ /**
39
+ * Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 /
40
+ * AC-BE-12 / AC-001 — and catalog-qualified feature segments such as
41
+ * AC-FE-INS-001 / AC-FE-CHAT-005 (uppercase letter/digit segments).
42
+ * Catalog ids embedded in bold (“**AC-FE-INS-001**:…”) must bind as-is;
43
+ * falling through to a minted AC-00N historically produced dual ids and
44
+ * acceptanceCoverage.missing noise (2026-08-23 fe-test loop finding 5).
45
+ */
46
+ const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?[A-Z0-9]+(?:[-_][A-Z0-9]+)*)\s*[::]?\s*(.*)$/u;
40
47
  function stripFencedAndComments(text) {
41
48
  // Remove fenced code blocks and HTML comments so headings inside them are ignored.
42
49
  return text
@@ -52,12 +59,25 @@ function normalizeAcceptanceId(raw) {
52
59
  .replace(/^(AC)(FE|BE|UI|API)(?=-|\d)/u, "$1-$2");
53
60
  }
54
61
  function parseEmbeddedAcHeading(headingText) {
55
- const normalized = headingText.trim().replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "");
62
+ const normalized = headingText
63
+ .trim()
64
+ // Strip leading section numbers ("2.1、" style) and bold/emphasis
65
+ // wrappers ("**AC-FE-INS-001**") so embedded catalog ids bind.
66
+ .replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
67
+ .replace(/^\*\*|\*\*$/g, "")
68
+ .replace(/^__|__$/g, "")
69
+ .trim();
56
70
  const match = EMBEDDED_AC_HEADING.exec(normalized);
57
71
  if (!match)
58
72
  return null;
59
73
  const id = normalizeAcceptanceId(match[1]);
60
- const text = match[2]?.trim() || id;
74
+ // Bold-wrapped ids leave their closing marker inside the captured text
75
+ // ("**AC-FE-INS-001**:xxx" captures "**:xxx"); strip emphasis remnants
76
+ // and leading separators so the bound text is clean prose.
77
+ const text = (match[2] ?? "")
78
+ .replace(/^[*_]+\s*/, "")
79
+ .replace(/^\s*[::]\s*/, "")
80
+ .trim() || id;
61
81
  return { id, text };
62
82
  }
63
83
  function normalizeHeadingText(raw) {
@@ -5,6 +5,8 @@ import { ContractMutationError, applyTaskContract, logicalRevisionForState, obse
5
5
  import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
6
6
  import { getTaskContractPaths } from "../contract/paths.js";
7
7
  import { loadTaskConfig } from "../runtime.js";
8
+ import { resolveFrontendSpecRootAliases } from "../frontend-project-capability.js";
9
+ import { DEFAULT_FRONTEND_SPEC_ROOTS, extractDeclaredFrontendSpecRoots, } from "../../shared/openspec-spec.js";
8
10
  import { buildPrepareDraft } from "./build-draft.js";
9
11
  import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, } from "./completeness.js";
10
12
  import { detectProductArtifactMeta, isIntakeSoftGapCode, } from "./artifact-meta.js";
@@ -591,6 +593,40 @@ export async function prepareTaskSource(input) {
591
593
  flags: input.flags,
592
594
  references,
593
595
  });
596
+ // A user can explicitly name a non-default normative directory in the task
597
+ // prompt (for example “规范目录 `design-system`”). Freeze that root into
598
+ // the managed task config before the DAG exists; runtime selectors never
599
+ // mutate task.json.
600
+ if (built.draft.taskKind === "frontend-implementation") {
601
+ // An imported PRD is materialized under sourceIntegrity rather than copied
602
+ // into intent.text. Use both inputs so a `--prd` invocation can declare a
603
+ // task-owned spec root just like supplemental free-form text can.
604
+ const sourceText = [
605
+ ...(sourceIntegrity?.documents ?? []).flatMap((document) => document.content ? [document.content] : []),
606
+ ...(input.intent.kind === "facts" && input.intent.text
607
+ ? [input.intent.text]
608
+ : []),
609
+ ].join("\n");
610
+ const inferredRoots = extractDeclaredFrontendSpecRoots(sourceText);
611
+ if (inferredRoots.length > 0) {
612
+ const current = existingTaskConfig?.frontendOpenspec;
613
+ const configuredRoots = current?.specRoots ?? [...DEFAULT_FRONTEND_SPEC_ROOTS];
614
+ // Include roots declared by this source before resolving physical-target
615
+ // aliases. A newly declared root may itself be a symlink (for example
616
+ // `project-specs -> .../ai_doc`), so resolving aliases against only the
617
+ // previous config cannot map `ai_doc/rules.md` back to project-specs.
618
+ const provisionalRoots = [
619
+ ...new Set([...configuredRoots, ...inferredRoots]),
620
+ ].sort();
621
+ const aliases = await resolveFrontendSpecRootAliases(repoRoot, provisionalRoots);
622
+ const aliasMap = new Map(aliases.map(({ alias, logicalRoot }) => [alias, logicalRoot]));
623
+ built.draft.frontendOpenspec = {
624
+ policy: current?.policy ?? "cited",
625
+ specRoots: [...new Set([...configuredRoots, ...inferredRoots.map((root) => aliasMap.get(root) ?? root)])].sort(),
626
+ requiredReadPaths: current?.requiredReadPaths ?? [],
627
+ };
628
+ }
629
+ }
594
630
  const { gaps, risks } = listPrepareGaps({
595
631
  draft: built.draft,
596
632
  state,
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from "node:fs/promises";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { Command } from "commander";
7
+ import { createNpmUpdateClient } from "../shared/update/npm-client.js";
8
+ import { runConsoleDailyUpdateCheck } from "../shared/update/console-notifier.js";
6
9
  import { readPackageVersion } from "../shared/package-metadata.js";
7
10
  import YAML from "yaml";
8
11
  import { LoopAgentClient } from "./loop-agent/loop-agent-client.js";
@@ -838,10 +841,33 @@ function parseOptionalTimeoutMs(raw) {
838
841
  return value;
839
842
  }
840
843
  /** Console HTTP entry for bare `agent-worker console` (listen first, then optional browser open). */
841
- async function runConsoleServe(options) {
844
+ export async function runConsoleServe(options) {
842
845
  const { createConsoleServer } = await import("./console/server.js");
843
846
  const { maybeOpenConsoleBrowser } = await import("./console/open-browser.js");
844
847
  try {
848
+ try {
849
+ const packageRoot = path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))));
850
+ const currentVersion = readPackageVersion(packageRoot) ?? "0.0.0";
851
+ await runConsoleDailyUpdateCheck({
852
+ currentVersion,
853
+ packageRoot,
854
+ homeDir: os.homedir(),
855
+ cwd: options.repoRoot,
856
+ env: process.env,
857
+ stdinIsTTY: process.stdin.isTTY === true,
858
+ stderrIsTTY: process.stderr.isTTY === true,
859
+ client: createNpmUpdateClient({
860
+ currentVersion,
861
+ packageRoot,
862
+ cwd: options.repoRoot,
863
+ env: process.env,
864
+ }),
865
+ });
866
+ }
867
+ catch (error) {
868
+ const detail = error instanceof Error ? error.message : String(error);
869
+ process.stderr.write(`loop-agent 更新检查已跳过:${detail}\n`);
870
+ }
845
871
  const server = await createConsoleServer({
846
872
  repoRoot: options.repoRoot,
847
873
  host: options.host,
@@ -857,6 +883,10 @@ async function runConsoleServe(options) {
857
883
  noOpen: options.noOpen === true,
858
884
  serverUrl: server.url,
859
885
  });
886
+ if (options.holdUntilSignal === false) {
887
+ await server.close();
888
+ return;
889
+ }
860
890
  await new Promise((resolve) => {
861
891
  const shutdown = () => resolve();
862
892
  process.once("SIGINT", shutdown);
@@ -1,6 +1,13 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { open, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ /** 新建模型时,未显式提供的字段使用的默认值(仅含 6 个定义参数)。 */
5
+ const DEFAULT_MODEL_FIELDS = {
6
+ contextWindow: 32000,
7
+ maxTokens: 8192,
8
+ reasoning: false,
9
+ input: ["text"],
10
+ };
4
11
  const revision = (raw) => createHash("sha256").update(raw).digest("hex");
5
12
  export async function readModelsConfig(agentDir) {
6
13
  let raw = "{}";
@@ -29,6 +36,18 @@ export async function readModelsConfig(agentDir) {
29
36
  return {
30
37
  id: String(item.id ?? ""),
31
38
  name: typeof item.name === "string" ? item.name : undefined,
39
+ contextWindow: typeof item.contextWindow === "number"
40
+ ? item.contextWindow
41
+ : undefined,
42
+ maxTokens: typeof item.maxTokens === "number"
43
+ ? item.maxTokens
44
+ : undefined,
45
+ input: Array.isArray(item.input)
46
+ ? item.input.filter((entry) => typeof entry === "string")
47
+ : undefined,
48
+ reasoning: typeof item.reasoning === "boolean"
49
+ ? item.reasoning
50
+ : undefined,
32
51
  };
33
52
  })
34
53
  .filter((model) => model.id)
@@ -81,19 +100,25 @@ export async function patchModelsConfig(agentDir, input) {
81
100
  code: "INVALID_INPUT",
82
101
  status: 400,
83
102
  });
103
+ const fields = validateModelFields(input.model);
104
+ const existingIndex = models.findIndex((model) => model.id === input.model.id);
105
+ const merged = {
106
+ // id/name 在前;新建模型用默认字段补齐(保证定义完整),更新保留已有值。
107
+ id: input.model.id,
108
+ ...(fields.name !== undefined ? { name: fields.name } : {}),
109
+ ...(existingIndex >= 0 ? models[existingIndex] : DEFAULT_MODEL_FIELDS),
110
+ ...fields,
111
+ };
84
112
  provider.models =
85
113
  input.action === "remove-model"
86
114
  ? models.filter((model) => model.id !== input.model.id)
87
- : [
88
- ...models.filter((model) => model.id !== input.model.id),
89
- {
90
- ...(models.find((model) => model.id === input.model.id) ?? {}),
91
- id: input.model.id,
92
- ...(input.model.name !== undefined
93
- ? { name: input.model.name }
94
- : {}),
95
- },
96
- ];
115
+ : existingIndex >= 0
116
+ ? [
117
+ ...models.slice(0, existingIndex),
118
+ merged,
119
+ ...models.slice(existingIndex + 1),
120
+ ]
121
+ : [...models, merged];
97
122
  }
98
123
  const body = `${JSON.stringify(parsed, null, 2)}\n`;
99
124
  const tmp = path.join(agentDir, `.models.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
@@ -108,6 +133,52 @@ export async function patchModelsConfig(agentDir, input) {
108
133
  await rename(tmp, file);
109
134
  return readModelsConfig(agentDir);
110
135
  }
136
+ /** Whitelist + validate model fields carried by a client upsert. Never accept
137
+ * arbitrary keys; only known numeric/boolean/string-array shape fields. */
138
+ function validateModelFields(model) {
139
+ const fields = {};
140
+ if (model.name !== undefined)
141
+ fields.name = String(model.name);
142
+ if (model.contextWindow !== undefined) {
143
+ if (typeof model.contextWindow !== "number" || model.contextWindow < 0)
144
+ throw invalid("contextWindow must be a non-negative number");
145
+ fields.contextWindow = model.contextWindow;
146
+ }
147
+ if (model.maxTokens !== undefined) {
148
+ if (typeof model.maxTokens !== "number" || model.maxTokens < 0)
149
+ throw invalid("maxTokens must be a non-negative number");
150
+ fields.maxTokens = model.maxTokens;
151
+ }
152
+ if (model.reasoning !== undefined) {
153
+ if (typeof model.reasoning !== "boolean")
154
+ throw invalid("reasoning must be a boolean");
155
+ fields.reasoning = model.reasoning;
156
+ }
157
+ if (model.input !== undefined) {
158
+ if (!Array.isArray(model.input) ||
159
+ !model.input.every((item) => typeof item === "string"))
160
+ throw invalid("input must be an array of strings");
161
+ fields.input = model.input.map((item) => String(item));
162
+ }
163
+ if (model.cost !== undefined) {
164
+ if (!model.cost || typeof model.cost !== "object")
165
+ throw invalid("cost must be an object");
166
+ const cost = {};
167
+ for (const key of ["input", "output", "cacheRead", "cacheWrite"]) {
168
+ const value = model.cost[key];
169
+ if (value !== undefined) {
170
+ if (typeof value !== "number" || value < 0)
171
+ throw invalid(`cost.${key} must be a non-negative number`);
172
+ cost[key] = value;
173
+ }
174
+ }
175
+ fields.cost = cost;
176
+ }
177
+ return fields;
178
+ }
179
+ function invalid(message) {
180
+ return Object.assign(new Error(message), { code: "INVALID_INPUT", status: 400 });
181
+ }
111
182
  /** Max bytes of a package.json we are willing to read (read-only, bounded). */
112
183
  export const MAX_PACKAGE_JSON_BYTES = 64 * 1024;
113
184
  /** Max directory levels walked upward from an owned resolved resource path. */
@@ -270,6 +270,7 @@ export function parseChatImages(value) {
270
270
  }
271
271
  function gateMutation(req, deps) {
272
272
  const failure = evaluateMutationGate(req, {
273
+ cookieName: deps.bootToken.cookieName,
273
274
  bootToken: deps.bootToken.value,
274
275
  confirmationToken: deps.bootToken.confirmationToken,
275
276
  consoleOrigin: deps.getConsoleOrigin(),
@@ -35,6 +35,32 @@ import { hashDagBytes, } from "./dag-confirmation.js";
35
35
  * framework invocations that don't embed the word "verify".
36
36
  */
37
37
  export const STRUCTURED_VERIFICATION_COMMAND_RE = /\b(vitest|jest|playwright|cypress|pytest|py-test|go\s+test|cargo\s+test|npm\s+run\s+(lint|typecheck|test|verify)|npm\s+(test|run\s+test)|check-repo\.sh|loop-agent-standard-verify)\b/i;
38
+ /**
39
+ * Deterministic shell preset keys whose executors are themselves structured
40
+ * verification/gate materializers: their shell.commands[] is empty and the
41
+ * preset alone carries the verification authority (hash-bound artifact
42
+ * gates, evidence validators, report finalize). Mirrors the named-shell
43
+ * handler registry in shell-executor.ts so preset-only workflows
44
+ * (frontend-test finalize/reports, backend-test pipeline, verify-evidence
45
+ * gates) pass the G2 autonomous execution gate without depending on node-id
46
+ * naming conventions.
47
+ */
48
+ export const STRUCTURED_VERIFICATION_SHELL_PRESETS = new Set([
49
+ "frontendTestCaseChecklist",
50
+ "frontendTestCaseManifest",
51
+ "frontendTestEvidenceValidation",
52
+ "frontendTestResultFinalize",
53
+ "frontendTestL5Report",
54
+ "frontendTestHtmlReport",
55
+ "frontendTestReports",
56
+ "backendTestPipeline",
57
+ "verifyEvidence",
58
+ "repairArtifactGate",
59
+ "jacocoCoverage",
60
+ "requirementCoverageGate",
61
+ "jsonArtifactGate",
62
+ "verdictGate",
63
+ ]);
38
64
  export const DEFAULT_EXECUTION_RECEIPT_TTL_MS = 30 * 60 * 1000;
39
65
  function normalizePathEntry(entry) {
40
66
  return entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
@@ -157,6 +183,13 @@ export function deriveEligibilityFactsFromDagSpec(input) {
157
183
  return false;
158
184
  if (/verify|verification/i.test(task.id ?? ""))
159
185
  return true;
186
+ // Preset-only verification shells (empty commands[]): the preset key is
187
+ // the verification authority (frontend-test finalize, backend-test
188
+ // pipeline, evidence/artifact gates).
189
+ const shellRecord = task.shell;
190
+ const hasVerificationPreset = Object.keys(shellRecord).some((key) => STRUCTURED_VERIFICATION_SHELL_PRESETS.has(key));
191
+ if (hasVerificationPreset)
192
+ return true;
160
193
  const commands = stringList(task.shell.commands);
161
194
  return commands.some((command) => STRUCTURED_VERIFICATION_COMMAND_RE.test(command));
162
195
  });
@@ -0,0 +1,19 @@
1
+ import { parseFrontendHumanDecisionProjection } from "../../workflows/dag/frontend-human-decision.js";
2
+ /** Worker/Console persistence seam. Canonical validation and freshness remain in the workflow service. */
3
+ export function createFrontendHumanDecisionAdapter(store) {
4
+ return {
5
+ async load(operationId) {
6
+ const record = await store.get(operationId);
7
+ if (!record || record.revision === undefined || !record.frontendHumanDecision)
8
+ return undefined;
9
+ return { revision: record.revision, projection: parseFrontendHumanDecisionProjection(record.frontendHumanDecision) };
10
+ },
11
+ materialize(input) {
12
+ return store.materializeFrontendHumanDecision(input);
13
+ },
14
+ consume(input) {
15
+ return store.consumeFrontendHumanDecision(input);
16
+ },
17
+ initializeSplit: store.initializeFrontendHumanApprovedSplit.bind(store),
18
+ };
19
+ }
@@ -0,0 +1,20 @@
1
+ import { FrontendSplitOrchestrator, frontendSplitProjectionSchema, } from "../../workflows/dag/frontend-split-application-service.js";
2
+ /** Worker/Console boundary adapter. All orchestration policy remains canonical. */
3
+ export function createFrontendSplitOperationAdapter(dependencies) {
4
+ const ports = {
5
+ load: async (operationId) => {
6
+ const record = await dependencies.store.get(operationId);
7
+ return record?.frontendSplit === undefined ? undefined : frontendSplitProjectionSchema.parse(record.frontendSplit);
8
+ },
9
+ compareAndSwap: (input) => dependencies.store.compareAndSwapFrontendSplit(input),
10
+ materialize: async (request) => {
11
+ const result = await dependencies.materializer.materialize(request);
12
+ return { taskId: result.taskId, operationId: result.operationId };
13
+ },
14
+ schedule: dependencies.schedule,
15
+ auditStale: dependencies.auditStale,
16
+ loadChildCloseoutFacts: (input) => dependencies.store.loadChildCloseoutFacts(input),
17
+ settleAggregate: (input) => dependencies.store.settleFrontendSplitAggregate({ ...input, prepareAggregate: dependencies.prepareAggregate }),
18
+ };
19
+ return new FrontendSplitOrchestrator(ports);
20
+ }
@@ -11,6 +11,9 @@ export { createClosedResourceLoader, createInterviewResourceLoader, tryActivateT
11
11
  export { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
12
12
  export { openConsoleAppData, defaultConsoleAppDataRoot } from "./app-data.js";
13
13
  export { OperationStore } from "./operation-store.js";
14
+ export { createFrontendSplitOperationAdapter } from "./frontend-split-operation-adapter.js";
15
+ export { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
16
+ export { FrontendSplitTaskMaterializer } from "../materialize/frontend-split-task-materializer.js";
14
17
  export { createOperationEventStore } from "./operation-sse.js";
15
18
  export { DagConfirmationStore } from "./dag-confirmation.js";
16
19
  export { DraftStore } from "./draft-store.js";