@tea-agent/loop-agent 0.42.0-next.1 → 0.42.0-next.10

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 (238) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +319 -1
  3. package/dist/application/dag/generate-task-dag.js +75 -19
  4. package/dist/application/dag/run-dag.js +41 -0
  5. package/dist/application/task-lifecycle/advance.js +24 -5
  6. package/dist/application/task-lifecycle/observe.js +171 -17
  7. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  8. package/dist/application/task-lifecycle/recommendations.js +13 -2
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +7 -0
  11. package/dist/cli/program.js +6 -1
  12. package/dist/commands/client-recovery.js +3 -0
  13. package/dist/commands/dag-follow-up.js +138 -0
  14. package/dist/commands/dag-rerun-task.js +2 -0
  15. package/dist/commands/init.js +27 -1
  16. package/dist/commands/task-advance.js +19 -0
  17. package/dist/executors/dag-pi-executor.js +3976 -76
  18. package/dist/executors/pi-executor.js +15 -4
  19. package/dist/executors/pi-read-budget-policy.js +239 -0
  20. package/dist/executors/shell-executor.js +978 -188
  21. package/dist/executors/shell-write-guard.js +7 -0
  22. package/dist/infrastructure/console/operation-store.js +226 -6
  23. package/dist/shared/dag-failure-category.js +12 -0
  24. package/dist/shared/openspec-spec.js +70 -4
  25. package/dist/shared/operator/capabilities.js +7 -0
  26. package/dist/task/config-types.js +105 -5
  27. package/dist/task/contract/adopt.js +4 -0
  28. package/dist/task/contract/import-revision.js +4 -0
  29. package/dist/task/contract/project.js +3 -0
  30. package/dist/task/contract/schema.js +2 -1
  31. package/dist/task/frontend-project-capability.js +203 -20
  32. package/dist/task/runtime.js +5 -2
  33. package/dist/task/source-prepare/build-draft.js +3 -3
  34. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  35. package/dist/task/source-prepare/prepare.js +126 -21
  36. package/dist/task/source-prepare/semantic-intake.js +6 -2
  37. package/dist/task/source-references.js +22 -1
  38. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  39. package/dist/worker/console/chat/model-resolver.js +7 -4
  40. package/dist/worker/console/chat/pi-runtime.js +258 -11
  41. package/dist/worker/console/chat/repo-browser.js +10 -3
  42. package/dist/worker/console/chat/routes.js +165 -71
  43. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  44. package/dist/worker/console/chat/session-catalog.js +2 -0
  45. package/dist/worker/console/chat/session-store.js +159 -70
  46. package/dist/worker/console/chat/shortcuts.js +19 -3
  47. package/dist/worker/console/chat/turn-process.js +111 -56
  48. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  49. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  50. package/dist/worker/console/index.js +3 -0
  51. package/dist/worker/console/operator-actions.js +169 -7
  52. package/dist/worker/console/pi-readiness.js +3 -2
  53. package/dist/worker/console/server.js +23 -1
  54. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  55. package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
  56. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  57. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  58. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  59. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  60. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  70. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  71. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  72. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  73. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  74. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  75. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  81. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  82. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  83. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  84. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  85. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  86. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  87. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  88. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  89. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  90. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  91. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  92. package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
  93. package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
  94. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  95. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  96. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  97. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  98. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  99. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  100. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  101. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  102. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  103. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  104. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  105. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  106. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  107. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  108. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  109. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  111. package/dist/worker/console/static/index.html +2 -2
  112. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  113. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  114. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  115. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  116. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  117. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  118. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  119. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  120. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  121. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  122. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  123. package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
  124. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  125. package/dist/worker/console/workspace-context.js +115 -1
  126. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  127. package/dist/worker/observe/routes.js +4 -0
  128. package/dist/worker/observe/static/dag-helpers.js +4 -2
  129. package/dist/worker/observe/static/dag-node-purpose.js +5 -0
  130. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  131. package/dist/worker/observe/static/kpi.js +2 -2
  132. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  133. package/dist/worker/observe/static/operator-chrome.js +37 -23
  134. package/dist/worker/observe/static/relations.js +2 -2
  135. package/dist/worker/observe/static/router.d.ts +12 -1
  136. package/dist/worker/observe/static/router.js +48 -6
  137. package/dist/worker/observe/static/run-processing.js +4 -2
  138. package/dist/worker/observe/static/shell-chrome.js +2 -2
  139. package/dist/worker/observe/static/state.js +5 -0
  140. package/dist/worker/observe/static/styles.css +95 -0
  141. package/dist/worker/observe/static/views/dag-inspector.js +14 -7
  142. package/dist/worker/observe/static/views/dag.js +10 -2
  143. package/dist/worker/observe/static/views/dags.js +3 -1
  144. package/dist/worker/observe/static/views/dashboard.js +14 -6
  145. package/dist/worker/observe/static/views/pool.js +7 -2
  146. package/dist/worker/observe/static/views/run.js +12 -3
  147. package/dist/worker/observe/static/views/session-timeline.js +69 -10
  148. package/dist/worker/observe/static/views/task.js +7 -2
  149. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  150. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  151. package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
  152. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  153. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  154. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  155. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  156. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  157. package/dist/workflows/dag/frontend-closeout.js +221 -0
  158. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  159. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  160. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  161. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  162. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  163. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  164. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  165. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  166. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  167. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  168. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  169. package/dist/workflows/dag/frontend-repair.js +1 -432
  170. package/dist/workflows/dag/frontend-review-context.js +261 -15
  171. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  172. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  173. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  174. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  175. package/dist/workflows/dag/frontend-shape.js +435 -0
  176. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  177. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  178. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  179. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  180. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  181. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  182. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  183. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  184. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  185. package/dist/workflows/dag/init-hybrid.js +1028 -562
  186. package/dist/workflows/dag/interrupt-request.js +7 -0
  187. package/dist/workflows/dag/node-execution.js +828 -4
  188. package/dist/workflows/dag/prompt.js +70 -3
  189. package/dist/workflows/dag/recovery-lease.js +170 -0
  190. package/dist/workflows/dag/report.js +37 -1
  191. package/dist/workflows/dag/rerun-feedback.js +315 -1
  192. package/dist/workflows/dag/rerun-plan.js +17 -6
  193. package/dist/workflows/dag/rerun-run.js +44 -6
  194. package/dist/workflows/dag/rerun-task.js +251 -13
  195. package/dist/workflows/dag/retry-policy.js +219 -104
  196. package/dist/workflows/dag/runner.js +580 -124
  197. package/dist/workflows/dag/scheduler.js +133 -20
  198. package/dist/workflows/dag/types.js +244 -16
  199. package/dist/workflows/dag/validate.js +28 -12
  200. package/docs/examples/README.md +5 -0
  201. package/docs/init-surface.manifest.json +30 -12
  202. package/docs/skills/vetted-skill-registry.md +4 -2
  203. package/docs/templates/README.md +2 -0
  204. package/docs/templates/agent-dag-report.schema.json +8 -2
  205. package/docs/templates/agent-dag.schema.json +1 -1
  206. package/docs/templates/backend-test-dag.json +23 -20
  207. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  208. package/docs/templates/frontend-implementation-dag.json +89 -0
  209. package/docs/templates/spec-registry.schema.json +45 -0
  210. package/harness.json +1 -1
  211. package/package.json +4 -3
  212. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  213. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  214. package/skills/frontend-contract/SKILL.md +23 -0
  215. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  216. package/skills/frontend-design-review/SKILL.md +22 -41
  217. package/skills/frontend-plan/SKILL.md +26 -0
  218. package/skills/frontend-plan/references/decision-contract.md +37 -0
  219. package/skills/frontend-plan/references/design-decisions.md +17 -0
  220. package/skills/frontend-review/SKILL.md +20 -15
  221. package/skills/frontend-review/references/review-findings.md +6 -7
  222. package/skills/frontend-scout/SKILL.md +25 -0
  223. package/skills/frontend-scout/references/design-evidence.md +16 -0
  224. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  225. package/skills/frontend-verification/SKILL.md +1 -1
  226. package/skills/loop-agent/references/command-reference.md +1 -0
  227. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  228. package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
  229. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
  230. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
  231. package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
  232. package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
  233. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
  234. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
  235. package/skills/frontend-implementation/SKILL.md +0 -52
  236. package/skills/frontend-implementation/references/code-standards.md +0 -33
  237. package/skills/frontend-implementation/references/design-spec.md +0 -56
  238. package/skills/frontend-implementation/references/node-contracts.md +0 -31
@@ -18,6 +18,15 @@ import { authorizeOperatorChatTool } from "./tools.js";
18
18
  import { projectOperationForChat } from "./chat-event-store.js";
19
19
  import { scrubSecrets } from "./explore-tools.js";
20
20
  import { hashDeferredTurnPayload, parseDeferredTurns, } from "./deferred-turn.js";
21
+ /** True only for a durable session that has never received user work. */
22
+ export function isBlankChatSession(record) {
23
+ return (record.state === "active" &&
24
+ !record.title &&
25
+ !record.displayTitle &&
26
+ record.messages.length === 0 &&
27
+ record.toolInvocations.length === 0 &&
28
+ (record.deferredTurns?.length ?? 0) === 0);
29
+ }
21
30
  const CHAT_TITLE_MAX_CHARS = 80;
22
31
  /** Safe, one-line bounded title used for placeholders and model output. */
23
32
  export function cleanChatTitle(value) {
@@ -38,6 +47,16 @@ export function cleanChatTitle(value) {
38
47
  function recordPath(appData, sessionId) {
39
48
  return path.join(appData.chats, `${sessionId}.json`);
40
49
  }
50
+ /** True when `sessionFile` lives inside this store's dedicated Pi JSONL dir. */
51
+ export function isConsoleOwnedSessionFile(appData, sessionFile) {
52
+ const value = sessionFile?.trim();
53
+ if (!value)
54
+ return false;
55
+ const root = path.resolve(appData.chatSessions);
56
+ const target = path.resolve(value);
57
+ const relative = path.relative(root, target);
58
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
59
+ }
41
60
  export class ChatSessionStore {
42
61
  appData;
43
62
  options;
@@ -81,6 +100,9 @@ export class ChatSessionStore {
81
100
  ...(typeof raw.modelProvider === "string" ? { modelProvider: raw.modelProvider } : {}),
82
101
  ...(typeof raw.modelId === "string" ? { modelId: raw.modelId } : {}),
83
102
  ...(typeof raw.thinkingLevel === "string" ? { thinkingLevel: raw.thinkingLevel } : {}),
103
+ ...(typeof raw.telemetrySessionId === "string" && raw.telemetrySessionId
104
+ ? { telemetrySessionId: raw.telemetrySessionId }
105
+ : {}),
84
106
  ...(typeof raw.workspaceId === "string" && raw.workspaceId
85
107
  ? { workspaceId: raw.workspaceId }
86
108
  : {}),
@@ -165,6 +187,7 @@ export class ChatSessionStore {
165
187
  const now = new Date().toISOString();
166
188
  const record = {
167
189
  ...source,
190
+ telemetrySessionId: undefined,
168
191
  sessionId: input.targetSessionId,
169
192
  state: "active",
170
193
  ...(input.targetSessionFile ? { sessionFile: input.targetSessionFile } : { sessionFile: undefined }),
@@ -179,6 +202,19 @@ export class ChatSessionStore {
179
202
  return record;
180
203
  });
181
204
  }
205
+ /** Atomically backfill the per-conversation simulated identity on first use. */
206
+ async ensureTelemetrySessionId(sessionId, createId) {
207
+ return this.serialize(sessionId, async () => {
208
+ const record = await this.get(sessionId);
209
+ if (!record)
210
+ return undefined;
211
+ if (record.telemetrySessionId)
212
+ return record.telemetrySessionId;
213
+ record.telemetrySessionId = createId();
214
+ await this.persist(record);
215
+ return record.telemetrySessionId;
216
+ });
217
+ }
182
218
  async rename(sessionId, title) {
183
219
  return this.serialize(sessionId, async () => {
184
220
  const record = await this.get(sessionId);
@@ -237,8 +273,14 @@ export class ChatSessionStore {
237
273
  const record = await this.get(sessionId);
238
274
  if (!record)
239
275
  return false;
276
+ const sessionFile = record.sessionFile;
240
277
  await rm(recordPath(this.appData, sessionId), { force: true });
241
278
  this.options.onRecordChange?.(sessionId, undefined);
279
+ // Record first, then best-effort JSONL unlink. Final consistency is
280
+ // "both gone" — a leftover JSONL is harmless, a leftover record is not.
281
+ if (isConsoleOwnedSessionFile(this.appData, sessionFile) && sessionFile) {
282
+ await rm(sessionFile, { force: true }).catch(() => undefined);
283
+ }
242
284
  return true;
243
285
  });
244
286
  }
@@ -384,6 +426,44 @@ export class ChatSessionStore {
384
426
  return { deferredTurn: existing, clearedRevision: existing.revision + 1 };
385
427
  });
386
428
  }
429
+ /**
430
+ * Commit Pi's steering acceptance as one durable record transition. Removing
431
+ * the dispatching queue item and appending its user message together prevents
432
+ * a crash between two writes from replaying or losing the accepted input.
433
+ */
434
+ async acceptSteeringMessage(sessionId, clientRequestId, text) {
435
+ return this.serialize(sessionId, async () => {
436
+ const record = await this.get(sessionId);
437
+ if (!record)
438
+ throw new Error(`chat session not found: ${sessionId}`);
439
+ const queue = [...(record.deferredTurns ?? [])];
440
+ const index = queue.findIndex((item) => item.clientRequestId === clientRequestId);
441
+ const deferredTurn = index >= 0 ? queue[index] : undefined;
442
+ if (!deferredTurn || deferredTurn.state !== "dispatching")
443
+ return undefined;
444
+ const message = {
445
+ id: `m_${record.messages.length + 1}_${Math.random().toString(36).slice(2, 8)}`,
446
+ role: "user",
447
+ text,
448
+ at: new Date().toISOString(),
449
+ };
450
+ queue.splice(index, 1);
451
+ record.messages.push(message);
452
+ record.deferredTurns = queue;
453
+ record.deferredTurn = queue[0];
454
+ record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
455
+ if (!record.displayTitle)
456
+ record.displayTitle = cleanChatTitle(message.text);
457
+ record.updatedAt = message.at;
458
+ await this.persist(record);
459
+ return {
460
+ record,
461
+ message,
462
+ deferredTurn,
463
+ clearedRevision: deferredTurn.revision + 1,
464
+ };
465
+ });
466
+ }
387
467
  async appendMessage(sessionId, message) {
388
468
  return this.serialize(sessionId, async () => {
389
469
  const record = await this.get(sessionId);
@@ -520,84 +600,91 @@ export async function runChatTurn(options) {
520
600
  // observable to tests and to a reconnecting client reading the record).
521
601
  const pending = [];
522
602
  let settledEvent;
523
- try {
524
- await runtime.prompt(sessionId, text, (event) => {
525
- if (event.type === "agent_settled") {
526
- // The durable operation-ref linker may perform an async canonical
527
- // operation read. Hold settled until those writes finish so replay
528
- // never observes agent_settled before its accepted operation ref.
529
- settledEvent = event;
530
- }
531
- else {
532
- options.onEvent?.(event);
603
+ let eventChain = Promise.resolve();
604
+ const processEvent = async (event) => {
605
+ if (event.type === "steering_accepted") {
606
+ const committed = await store.acceptSteeringMessage(sessionId, event.clientRequestId, event.text);
607
+ if (committed) {
608
+ await options.onSteeringMessagePersisted?.(committed.record, committed.message, {
609
+ clientRequestId: event.clientRequestId,
610
+ });
533
611
  }
534
- switch (event.type) {
535
- case "message_update":
536
- case "message_end":
537
- if (event.type === "message_end") {
538
- pending.push(store.appendMessage(sessionId, {
539
- role: "assistant",
540
- text: event.text,
541
- ...(event.content && event.content.length > 0
542
- ? { content: event.content }
543
- : {}),
544
- }));
545
- }
546
- break;
547
- case "tool_call":
548
- // Forwarded to the caller (SSE) for UI feedback. The actual tool
549
- // execution happens in the SDK's customTools[].execute(), which
550
- // routes to dispatchChatToolCall (Gate 3). Do NOT dispatch here —
551
- // that would double-execute every tool call (P0.2).
552
- break;
553
- case "tool_result": {
554
- const acceptedOperationId = acceptedOperationIdFromResult(event.toolName, event.result);
555
- if (acceptedOperationId && options.onOperationAccepted) {
556
- pending.push(Promise.resolve(options.onOperationAccepted({
557
- sessionId,
558
- toolCallId: event.toolCallId,
559
- toolName: event.toolName,
560
- operationId: acceptedOperationId,
561
- })));
562
- }
563
- // tool_execution_end carries the real result of the tool call.
564
- // Persist exactly once (the tool message + audit trail). This is
565
- // the single point of record for tool outcomes (P0.2).
566
- const resultText = safeStringifyResult(event.result);
567
- // ChatSessionStore serializes writes per session, so these two calls
568
- // are safe to fire concurrently — neither clobbers the other.
569
- pending.push(store.appendMessage(sessionId, {
570
- role: "tool",
571
- text: resultText,
572
- toolCallId: event.toolCallId,
573
- toolName: event.toolName,
574
- isError: event.isError,
575
- // Durable tool→operation link (plan A): the restored timeline can
576
- // resolve operation:<id> refs without the live operation-ref event.
577
- ...(acceptedOperationId
578
- ? { operationId: acceptedOperationId }
579
- : {}),
580
- }));
581
- pending.push(store.recordToolInvocation(sessionId, {
582
- toolCallId: event.toolCallId,
583
- toolName: event.toolName,
584
- ok: !event.isError,
585
- }));
586
- break;
587
- }
588
- case "error":
589
- turnError = { code: "CHAT_TURN_ERROR", message: event.message };
612
+ return;
613
+ }
614
+ if (event.type === "agent_settled") {
615
+ // The durable operation-ref linker may perform an async canonical
616
+ // operation read. Hold settled until those writes finish so replay
617
+ // never observes agent_settled before its accepted operation ref.
618
+ settledEvent = event;
619
+ }
620
+ else {
621
+ options.onEvent?.(event);
622
+ }
623
+ switch (event.type) {
624
+ case "message_update":
625
+ case "message_end":
626
+ if (event.type === "message_end") {
590
627
  pending.push(store.appendMessage(sessionId, {
591
628
  role: "assistant",
592
- kind: "provider-error",
593
- text: event.providerError.message,
594
- providerError: event.providerError,
629
+ text: event.text,
595
630
  ...(event.content && event.content.length > 0
596
631
  ? { content: event.content }
597
632
  : {}),
598
633
  }));
599
- break;
634
+ }
635
+ break;
636
+ case "tool_call":
637
+ // Forwarded to the caller (SSE) for UI feedback. The actual tool
638
+ // execution happens in the SDK's customTools[].execute(), which
639
+ // routes to dispatchChatToolCall (Gate 3). Do NOT dispatch here —
640
+ // that would double-execute every tool call (P0.2).
641
+ break;
642
+ case "tool_result": {
643
+ const acceptedOperationId = acceptedOperationIdFromResult(event.toolName, event.result);
644
+ if (acceptedOperationId && options.onOperationAccepted) {
645
+ pending.push(Promise.resolve(options.onOperationAccepted({
646
+ sessionId,
647
+ toolCallId: event.toolCallId,
648
+ toolName: event.toolName,
649
+ operationId: acceptedOperationId,
650
+ })));
651
+ }
652
+ const resultText = safeStringifyResult(event.result);
653
+ pending.push(store.appendMessage(sessionId, {
654
+ role: "tool",
655
+ text: resultText,
656
+ toolCallId: event.toolCallId,
657
+ toolName: event.toolName,
658
+ isError: event.isError,
659
+ ...(acceptedOperationId ? { operationId: acceptedOperationId } : {}),
660
+ }));
661
+ pending.push(store.recordToolInvocation(sessionId, {
662
+ toolCallId: event.toolCallId,
663
+ toolName: event.toolName,
664
+ ok: !event.isError,
665
+ }));
666
+ break;
600
667
  }
668
+ case "error":
669
+ turnError = { code: "CHAT_TURN_ERROR", message: event.message };
670
+ pending.push(store.appendMessage(sessionId, {
671
+ role: "assistant",
672
+ kind: "provider-error",
673
+ text: event.providerError.message,
674
+ providerError: event.providerError,
675
+ ...(event.content && event.content.length > 0
676
+ ? { content: event.content }
677
+ : {}),
678
+ }));
679
+ break;
680
+ }
681
+ };
682
+ try {
683
+ await runtime.prompt(sessionId, text, (event) => {
684
+ // Pi's event callback is synchronous. Serialize projection work so the
685
+ // accepted steering user_message is durable and sequenced before any
686
+ // assistant/tool event that follows it.
687
+ eventChain = eventChain.then(() => processEvent(event)).catch(() => undefined);
601
688
  }, {
602
689
  signal: options.signal,
603
690
  images: options.images,
@@ -605,10 +692,12 @@ export async function runChatTurn(options) {
605
692
  });
606
693
  }
607
694
  catch (error) {
695
+ await eventChain;
608
696
  await Promise.allSettled(pending);
609
697
  const message = error instanceof Error ? error.message : String(error);
610
698
  return { ok: false, error: { code: "CHAT_TURN_FAILED", message } };
611
699
  }
700
+ await eventChain;
612
701
  await Promise.allSettled(pending);
613
702
  if (settledEvent)
614
703
  options.onEvent?.(settledEvent);
@@ -37,11 +37,18 @@ export const SHORTCUTS = [
37
37
  {
38
38
  command: "/reload",
39
39
  label: "Reload session",
40
- description: "重新加载当前 Pi 会话的设置、扩展、技能、提示词、主题与上下文文件(不产生消息)",
40
+ description: "重新加载当前 Pi 会话的设置、扩展、技能、提示词、主题、上下文文件及模型与认证配置(不产生消息)",
41
41
  action: "reload",
42
42
  source: "operator",
43
43
  },
44
44
  ];
45
+ export const SDD_BINGO_SHORTCUT = {
46
+ command: "/sdd-bingo",
47
+ label: "sdd-bingo",
48
+ description: "SDD数据对齐",
49
+ action: "sdd-bingo",
50
+ source: "operator",
51
+ };
45
52
  /** Pi Web commands with real browser-equivalent behavior (UI-11). */
46
53
  export const WEB_SLASH_COMMANDS = [
47
54
  {
@@ -98,7 +105,7 @@ export function resolveShortcut(input) {
98
105
  const token = input.trim().split(/\s+/, 1)[0]?.toLowerCase();
99
106
  if (!token?.startsWith("/"))
100
107
  return undefined;
101
- const local = [...SHORTCUTS, ...WEB_SLASH_COMMANDS].find((shortcut) => shortcut.command === token);
108
+ const local = [...SHORTCUTS, ...WEB_SLASH_COMMANDS, SDD_BINGO_SHORTCUT].find((shortcut) => shortcut.command === token);
102
109
  if (local)
103
110
  return { command: local.command, action: local.action };
104
111
  return { command: token, action: "unknown" };
@@ -116,7 +123,7 @@ export function workspaceForShortcut(action) {
116
123
  * Merge Operator, Web, and dynamic Session commands with conflict dedupe.
117
124
  * Priority: operator/web > extension > skill > prompt. /reload is single-card.
118
125
  */
119
- export function mergeSlashCommands(dynamic = []) {
126
+ export function mergeSlashCommands(dynamic = [], capabilities = {}) {
120
127
  const byName = new Map();
121
128
  const upsert = (item) => {
122
129
  const key = normalizeCommandName(item.command);
@@ -149,6 +156,15 @@ export function mergeSlashCommands(dynamic = []) {
149
156
  action: item.action,
150
157
  });
151
158
  }
159
+ if (capabilities.sddBingo) {
160
+ upsert({
161
+ command: SDD_BINGO_SHORTCUT.command,
162
+ label: SDD_BINGO_SHORTCUT.label,
163
+ description: SDD_BINGO_SHORTCUT.description,
164
+ source: SDD_BINGO_SHORTCUT.source,
165
+ action: SDD_BINGO_SHORTCUT.action,
166
+ });
167
+ }
152
168
  for (const item of dynamic) {
153
169
  const command = normalizeCommandName(item.command);
154
170
  if (!command)
@@ -96,7 +96,10 @@ export function hydrateSessionThread(raw) {
96
96
  ...(item.operationId ? { operationId: item.operationId } : {}),
97
97
  };
98
98
  toolCalls.push(entry);
99
- pendingTools.push(entry);
99
+ // Durable rows with an event sequence are already self-ordering. Only
100
+ // legacy rows need the historical "next assistant" attribution fallback.
101
+ if (item.eventSeq === undefined)
102
+ pendingTools.push(entry);
100
103
  }
101
104
  }
102
105
  return { messages, toolCalls };
@@ -151,48 +154,81 @@ function collapseDagProgressItems(items) {
151
154
  return true;
152
155
  });
153
156
  }
154
- function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
155
- let buffer = [];
156
- for (const block of blocks) {
157
- if (block.type === "thinking") {
158
- flushToolBuffer(processItems, buffer);
159
- buffer = [];
160
- processItems.push({ kind: "thinking", thinking: block.thinking });
161
- continue;
162
- }
163
- if (block.type === "text") {
164
- flushToolBuffer(processItems, buffer);
165
- buffer = [];
166
- if (block.text.trim()) {
167
- processItems.push({ kind: "text", text: block.text });
157
+ function fillProcessDetails(group) {
158
+ const usedToolIds = new Set();
159
+ const nodes = [];
160
+ let insertionOrder = 0;
161
+ const addNode = (input) => {
162
+ if (input.items.length === 0)
163
+ return;
164
+ nodes.push({
165
+ ...(input.eventSeq === undefined ? {} : { eventSeq: input.eventSeq }),
166
+ createdAt: input.createdAt,
167
+ insertionOrder: insertionOrder++,
168
+ items: input.items,
169
+ });
170
+ };
171
+ const addAssistantNode = (assistant, blocks, includeProviderError = false) => {
172
+ let items = [];
173
+ let anchorSeq = assistant.eventSeq;
174
+ let anchorAt = assistant.createdAt;
175
+ const flushItems = () => {
176
+ addNode({
177
+ items,
178
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
179
+ createdAt: anchorAt,
180
+ });
181
+ items = [];
182
+ };
183
+ for (const block of blocks) {
184
+ if (block.type === "thinking") {
185
+ items.push({ kind: "thinking", thinking: block.thinking });
186
+ continue;
168
187
  }
169
- continue;
188
+ if (block.type === "text") {
189
+ if (block.text.trim())
190
+ items.push({ kind: "text", text: block.text });
191
+ continue;
192
+ }
193
+ const tool = group.tools.find((entry) => entry.id === block.toolCallId);
194
+ if (!tool || usedToolIds.has(tool.id))
195
+ continue;
196
+ flushItems();
197
+ usedToolIds.add(tool.id);
198
+ const toolItems = [];
199
+ flushToolBuffer(toolItems, [tool]);
200
+ addNode({
201
+ items: toolItems,
202
+ ...(tool.eventSeq === undefined
203
+ ? anchorSeq === undefined
204
+ ? {}
205
+ : { eventSeq: anchorSeq }
206
+ : { eventSeq: tool.eventSeq }),
207
+ createdAt: tool.createdAt,
208
+ });
209
+ anchorSeq = tool.eventSeq ?? anchorSeq;
210
+ anchorAt = tool.createdAt;
170
211
  }
171
- const tool = group.tools.find((entry) => entry.id === block.toolCallId);
172
- if (tool && !usedToolIds.has(tool.id)) {
173
- buffer.push(tool);
212
+ if (includeProviderError && assistant.providerError) {
213
+ items.push({ kind: "provider-error", error: assistant.providerError });
214
+ }
215
+ flushItems();
216
+ // Preserve the old-data fallback after this stage. Sequenced unmatched
217
+ // tools stay independent and are merged by their own eventSeq below.
218
+ const legacyUnmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) &&
219
+ tool.eventSeq === undefined &&
220
+ tool.assistantMessageId === assistant.id);
221
+ for (const tool of sortToolsStable(legacyUnmatched)) {
174
222
  usedToolIds.add(tool.id);
223
+ const toolItems = [];
224
+ flushToolBuffer(toolItems, [tool]);
225
+ addNode({
226
+ items: toolItems,
227
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
228
+ createdAt: Math.max(anchorAt, tool.createdAt),
229
+ });
175
230
  }
176
- }
177
- flushToolBuffer(processItems, buffer);
178
- }
179
- /**
180
- * Insert tools attributed to this assistant stage but without a matching
181
- * content toolCall block (live SSE tool_call frames often arrive before the
182
- * assistant content does). They follow the stage's thinking/text instead of
183
- * sinking to the end of the whole turn.
184
- */
185
- function appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds) {
186
- const unmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) && tool.assistantMessageId === assistant.id);
187
- if (unmatched.length === 0)
188
- return;
189
- for (const tool of unmatched)
190
- usedToolIds.add(tool.id);
191
- flushToolBuffer(processItems, unmatched);
192
- }
193
- function fillProcessDetails(group) {
194
- const usedToolIds = new Set();
195
- const processItems = [];
231
+ };
196
232
  const lastAssistant = group.assistants.at(-1);
197
233
  const finalAssistant = lastAssistant?.kind === "provider-error" ? undefined : lastAssistant;
198
234
  const finalIndex = finalAssistant
@@ -202,29 +238,46 @@ function fillProcessDetails(group) {
202
238
  if (index === finalIndex)
203
239
  continue;
204
240
  if (assistant.kind === "provider-error" && assistant.providerError) {
205
- emitProcessBlocks(getDisplayableAssistantBlocks(assistant.content ?? []), group, processItems, usedToolIds);
206
- appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
207
- processItems.push({
208
- kind: "provider-error",
209
- error: assistant.providerError,
210
- });
241
+ addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []), true);
211
242
  continue;
212
243
  }
213
- emitProcessBlocks(getDisplayableAssistantBlocks(assistant.content ?? []), group, processItems, usedToolIds);
214
- appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
244
+ addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []));
215
245
  }
216
246
  const blocks = finalAssistant?.content ?? [];
217
247
  const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
218
- emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
219
248
  if (finalAssistant) {
220
- appendAssistantUnmatchedTools(group, finalAssistant, processItems, usedToolIds);
249
+ addAssistantNode(finalAssistant, split.processBlocks);
221
250
  }
222
- // Bounded fallback for legacy restored rows with no assistant attribution:
223
- // keep them at this turn's end in stable server order instead of randomly
224
- // interleaving them into earlier stages.
225
- const orphans = group.tools.filter((tool) => !usedToolIds.has(tool.id));
226
- if (orphans.length > 0)
227
- flushToolBuffer(processItems, orphans);
251
+ // DSH-style peer nodes: a sequenced tool keeps its own anchor instead of
252
+ // being appended to whichever assistant id happens to own it at render time.
253
+ for (const tool of group.tools) {
254
+ if (usedToolIds.has(tool.id) || tool.eventSeq === undefined)
255
+ continue;
256
+ usedToolIds.add(tool.id);
257
+ const items = [];
258
+ flushToolBuffer(items, [tool]);
259
+ nodes.push({
260
+ eventSeq: tool.eventSeq,
261
+ createdAt: tool.createdAt,
262
+ insertionOrder: insertionOrder++,
263
+ items,
264
+ });
265
+ }
266
+ const fullySequenced = nodes.every((node) => node.eventSeq !== undefined);
267
+ nodes.sort((a, b) => {
268
+ if (fullySequenced && a.eventSeq !== b.eventSeq) {
269
+ return a.eventSeq - b.eventSeq;
270
+ }
271
+ if (a.createdAt !== b.createdAt)
272
+ return a.createdAt - b.createdAt;
273
+ return a.insertionOrder - b.insertionOrder;
274
+ });
275
+ const processItems = nodes.flatMap((node) => node.items);
276
+ // Bounded fallback for legacy restored rows with neither event sequence nor
277
+ // assistant attribution: keep them at the turn end in stable local order.
278
+ const legacyOrphans = group.tools.filter((tool) => !usedToolIds.has(tool.id));
279
+ if (legacyOrphans.length > 0)
280
+ flushToolBuffer(processItems, legacyOrphans);
228
281
  group.processItems = collapseDagProgressItems(processItems);
229
282
  if (!finalAssistant)
230
283
  return;
@@ -306,7 +359,9 @@ export function buildTurnProcessGroups(input) {
306
359
  if (assigned.has(tool.id))
307
360
  continue;
308
361
  if (tool.turnId) {
309
- const byTurn = groups.find((group) => group.tools.some((t) => t.turnId === tool.turnId) ||
362
+ const byTurn = groups.find((group) => group.user?.turnId === tool.turnId ||
363
+ group.assistants.some((assistant) => assistant.turnId === tool.turnId) ||
364
+ group.tools.some((t) => t.turnId === tool.turnId) ||
310
365
  group.key === `turn-${tool.turnId}` ||
311
366
  (group.user && `restored-${group.user.id}` === tool.turnId));
312
367
  if (byTurn) {
@@ -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 "../../infrastructure/console/app-data.js";
13
13
  export { OperationStore } from "../../infrastructure/console/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";