@tea-agent/loop-agent 0.43.0 → 0.44.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 (226) hide show
  1. package/CHANGELOG.md +408 -367
  2. package/dist/application/evaluation/budget.js +21 -0
  3. package/dist/application/evaluation/corpus-hash.js +10 -15
  4. package/dist/application/evaluation/corpus.js +2 -1
  5. package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
  6. package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
  7. package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
  8. package/dist/application/evaluation/frontend-pair-registration.js +143 -0
  9. package/dist/application/evaluation/frontend-paired-summary.js +150 -0
  10. package/dist/application/evaluation/frontend-run-observation.js +144 -0
  11. package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
  12. package/dist/application/evaluation/types.js +45 -12
  13. package/dist/application/task-lifecycle/observe.js +26 -1
  14. package/dist/build-stamp.json +3 -3
  15. package/dist/cli/command-definitions.js +11 -0
  16. package/dist/cli/program.js +4 -0
  17. package/dist/commands/task-advance.js +3 -5
  18. package/dist/commands/task-source-prepare.js +13 -1
  19. package/dist/executors/dag-pi/guards.js +4 -0
  20. package/dist/executors/dag-pi/model-config.js +30 -0
  21. package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
  22. package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
  23. package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
  24. package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
  25. package/dist/executors/dag-pi/plan/record-tools.js +861 -0
  26. package/dist/executors/dag-pi/plan/schema.js +129 -0
  27. package/dist/executors/dag-pi/plan/session.js +1 -0
  28. package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
  29. package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
  30. package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
  31. package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
  32. package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
  33. package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
  34. package/dist/executors/dag-pi-executor.js +398 -3384
  35. package/dist/executors/pi-executor.js +6 -1
  36. package/dist/executors/pi-sdk-executor.js +112 -19
  37. package/dist/executors/shell-executor.js +88 -12
  38. package/dist/executors/shell-presets.js +1 -1
  39. package/dist/executors/shell-write-guard.js +26 -3
  40. package/dist/infrastructure/console/operation-store.js +48 -14
  41. package/dist/infrastructure/evaluation/corpus-store.js +37 -12
  42. package/dist/shared/file-identity.js +23 -0
  43. package/dist/shared/git-workspace-status.js +482 -0
  44. package/dist/shared/operator/capabilities.js +10 -5
  45. package/dist/shared/path-safety.js +2 -2
  46. package/dist/shared/wizard-local-telemetry.js +51 -0
  47. package/dist/task/config-types.js +22 -8
  48. package/dist/task/contract/project.js +13 -0
  49. package/dist/task/contract/schema.js +2 -7
  50. package/dist/task/frontend-preflight.js +1 -1
  51. package/dist/task/source-prepare/parse-intent.js +124 -5
  52. package/dist/task/source-prepare/prepare.js +6 -1
  53. package/dist/task/source-prepare/semantic-intake.js +18 -3
  54. package/dist/task/source-prepare/source-execution.js +65 -0
  55. package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
  56. package/dist/task/source-prepare/source-provider-budget.js +290 -0
  57. package/dist/worker/console/chat/assistant-content.js +5 -34
  58. package/dist/worker/console/chat/chat-event-store.js +62 -10
  59. package/dist/worker/console/chat/context-insights.js +723 -0
  60. package/dist/worker/console/chat/conversation-nodes.js +383 -0
  61. package/dist/worker/console/chat/db-connections.js +854 -0
  62. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
  63. package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
  64. package/dist/worker/console/chat/pi-runtime.js +445 -36
  65. package/dist/worker/console/chat/provider-error.js +0 -12
  66. package/dist/worker/console/chat/repo-browser.js +52 -4
  67. package/dist/worker/console/chat/routes.js +577 -72
  68. package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
  69. package/dist/worker/console/chat/session-store.js +147 -19
  70. package/dist/worker/console/chat/shortcuts.js +7 -0
  71. package/dist/worker/console/chat/timeline-message-order.js +22 -0
  72. package/dist/worker/console/chat/tools.js +34 -0
  73. package/dist/worker/console/chat/turn-process.js +237 -66
  74. package/dist/worker/console/chat/user-turn-outline.js +19 -0
  75. package/dist/worker/console/console-update-runtime.js +2 -2
  76. package/dist/worker/console/dag-confirmation.js +3 -0
  77. package/dist/worker/console/dag-execution-receipt.js +3 -0
  78. package/dist/worker/console/operator-actions.js +32 -3
  79. package/dist/worker/console/operator-user-error.js +28 -1
  80. package/dist/worker/console/pi-readiness.js +4 -4
  81. package/dist/worker/console/prd-intake-bridge.js +5 -12
  82. package/dist/worker/console/recovery-cta.js +14 -14
  83. package/dist/worker/console/server.js +5 -1
  84. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
  85. package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
  86. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
  87. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
  88. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
  89. package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
  90. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
  100. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
  101. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
  102. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
  103. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
  104. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
  105. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
  110. package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
  111. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
  112. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
  113. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
  114. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
  115. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
  116. package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
  117. package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
  118. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
  119. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
  120. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
  121. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
  122. package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
  123. package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
  124. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
  125. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
  126. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
  127. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
  128. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
  129. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
  130. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
  131. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
  132. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
  133. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
  134. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
  135. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
  136. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
  137. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
  138. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
  139. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
  140. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
  141. package/dist/worker/console/static/index.html +2 -2
  142. package/dist/worker/console/static-src/app/console-types.js +9 -1
  143. package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
  144. package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
  145. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
  146. package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
  147. package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
  148. package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
  149. package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
  150. package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
  151. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
  152. package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
  153. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
  154. package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
  155. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
  156. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
  157. package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
  158. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
  159. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
  160. package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
  161. package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
  162. package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
  163. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
  164. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
  165. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
  166. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  167. package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
  168. package/dist/worker/console/workspace-context.js +2 -0
  169. package/dist/worker/console/workspace-initialization.js +3 -3
  170. package/dist/worker/observability/read-model.js +23 -2
  171. package/dist/worker/observe/node-context-insights.js +479 -0
  172. package/dist/worker/observe/static/console-theme.css +174 -0
  173. package/dist/worker/observe/static/console-theme.js +36 -0
  174. package/dist/worker/observe/static/operator-chrome.css +85 -0
  175. package/dist/worker/observe/static/operator-chrome.js +51 -0
  176. package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
  177. package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
  178. package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
  179. package/dist/workflows/dag/budget-enforcement.js +31 -2
  180. package/dist/workflows/dag/frontend-closeout.js +3 -1
  181. package/dist/workflows/dag/frontend-contract-facts.js +11 -8
  182. package/dist/workflows/dag/frontend-design-policy.js +6 -6
  183. package/dist/workflows/dag/frontend-durable-tools.js +90 -70
  184. package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
  185. package/dist/workflows/dag/frontend-plan-canary.js +66 -16
  186. package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
  187. package/dist/workflows/dag/frontend-plan-progress.js +92 -0
  188. package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
  189. package/dist/workflows/dag/frontend-recovery-run.js +51 -46
  190. package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
  191. package/dist/workflows/dag/frontend-review-context.js +29 -1
  192. package/dist/workflows/dag/frontend-review-scopes.js +92 -2
  193. package/dist/workflows/dag/frontend-risk.js +8 -3
  194. package/dist/workflows/dag/frontend-root-observation.js +261 -0
  195. package/dist/workflows/dag/frontend-session-budget.js +52 -39
  196. package/dist/workflows/dag/frontend-session-context.js +10 -0
  197. package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
  198. package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
  199. package/dist/workflows/dag/frontend-verification-trace.js +6 -0
  200. package/dist/workflows/dag/frontend-writer-admission.js +3 -1
  201. package/dist/workflows/dag/init-hybrid.js +108 -37
  202. package/dist/workflows/dag/node-execution.js +11 -11
  203. package/dist/workflows/dag/output-protocol.js +1 -0
  204. package/dist/workflows/dag/rerun-feedback.js +35 -4
  205. package/dist/workflows/dag/rerun-task.js +62 -66
  206. package/dist/workflows/dag/runner.js +58 -12
  207. package/dist/workflows/dag/types.js +15 -8
  208. package/dist/workflows/loop/benchmark.js +11 -8
  209. package/docs/README.md +1 -1
  210. package/docs/architecture/runtime-boundaries.md +1 -1
  211. package/docs/governance/harness-methodology-verification.md +11 -0
  212. package/docs/templates/backend-test-dag.json +5 -4
  213. package/docs/templates/frontend-implementation-contract.schema.json +31 -11
  214. package/package.json +6 -4
  215. package/skills/frontend-design-review/SKILL.md +8 -11
  216. package/skills/frontend-design-review/references/review-checklist.md +3 -4
  217. package/skills/frontend-review/SKILL.md +5 -1
  218. package/skills/loop-agent/references/command-reference.md +8 -0
  219. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  220. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
  221. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
  222. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
  223. package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
  224. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
  225. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
  226. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
@@ -1,15 +1,113 @@
1
1
  /**
2
2
  * Turn grouping for Operator Chat inline process UX (pi-web ProcessDetails mind).
3
3
  */
4
- import { compareChatOrder } from "./turn-order.js";
5
4
  import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
6
5
  import { dagProgressActivityForTool, semanticActivityForTool, } from "./semantic-activity.js";
7
- function emptyGroup(key) {
6
+ import { compareChatOrder } from "./turn-order.js";
7
+ import { ensureLinkedSubmissionOrder } from "./timeline-message-order.js";
8
+ function conversationNodeOrder(node) {
9
+ return { eventSeq: node.anchorSeq, createdAt: node.at };
10
+ }
11
+ function processItemOrder(item, group) {
12
+ const createdAt = item.createdAt ??
13
+ (item.kind === "tool" ? item.tool.createdAt : undefined) ??
14
+ group.assistants[0]?.createdAt ??
15
+ 0;
16
+ const eventSeq = item.eventSeq ?? (item.kind === "tool" ? item.tool.eventSeq : undefined);
17
+ return eventSeq === undefined ? { createdAt } : { eventSeq, createdAt };
18
+ }
19
+ function compareFlowEntries(a, b) {
20
+ return compareChatOrder(a.order, b.order);
21
+ }
22
+ /** Base identity for a rendered process row. Content is deliberately excluded
23
+ * so streaming text updates keep the same React instance. */
24
+ function processItemKey(item, order) {
25
+ if (item.kind === "tool")
26
+ return `tool:${item.tool.id}`;
27
+ if (item.kind === "activity")
28
+ return `activity:${item.activity.id}`;
29
+ if (item.kind === "dag-progress")
30
+ return `dag-progress:${item.progress.id}`;
31
+ const anchor = order.eventSeq ?? order.createdAt;
32
+ return `${item.kind}:${anchor}`;
33
+ }
34
+ export function turnProcessItemKey(item, occurrence = 0) {
35
+ const order = {
36
+ ...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
37
+ createdAt: item.createdAt ?? (item.kind === "tool" ? item.tool.createdAt : 0),
38
+ };
39
+ const base = processItemKey(item, order);
40
+ return occurrence === 0 ? base : `${base}:${occurrence}`;
41
+ }
42
+ /**
43
+ * Project one turn into the same ordered buckets used by the main timeline and
44
+ * the trajectory view. Only existing request/running-step nodes are prelude;
45
+ * retry and other lifecycle nodes stay in the collapsible process window.
46
+ */
47
+ export function projectTurnFlow(group) {
48
+ const conversationItems = group.conversationItems ?? [];
49
+ const prelude = conversationItems
50
+ .filter((item) => item.kind === "request" ||
51
+ (item.kind === "step" && item.node.status === "running"))
52
+ .sort((a, b) => compareChatOrder(conversationNodeOrder(a.node), conversationNodeOrder(b.node)));
53
+ const postAnswer = conversationItems
54
+ .filter((item) => item.kind === "max-tokens" || item.kind === "turn-error")
55
+ .sort((a, b) => compareChatOrder(conversationNodeOrder(a.node), conversationNodeOrder(b.node)));
56
+ const preludeKeys = new Set(prelude.map((item) => item.node.key));
57
+ const postAnswerKeys = new Set(postAnswer.map((item) => item.node.key));
58
+ const process = [];
59
+ for (const item of group.processItems) {
60
+ process.push({
61
+ source: "process",
62
+ key: "",
63
+ item,
64
+ order: processItemOrder(item, group),
65
+ });
66
+ }
67
+ for (const item of conversationItems) {
68
+ if (preludeKeys.has(item.node.key) || postAnswerKeys.has(item.node.key))
69
+ continue;
70
+ process.push({
71
+ source: "conversation",
72
+ key: `conversation:${item.node.key}`,
73
+ item,
74
+ order: conversationNodeOrder(item.node),
75
+ });
76
+ }
77
+ process.sort(compareFlowEntries);
78
+ const occurrences = new Map();
79
+ for (const entry of process) {
80
+ const base = entry.source === "conversation"
81
+ ? entry.key
82
+ : processItemKey(entry.item, entry.order);
83
+ const occurrence = occurrences.get(base) ?? 0;
84
+ occurrences.set(base, occurrence + 1);
85
+ entry.key = occurrence === 0 ? base : `${base}:${occurrence}`;
86
+ }
87
+ return { prelude, process, postAnswer };
88
+ }
89
+ /**
90
+ * Main-chat projection: request provenance and model-step bookkeeping remain
91
+ * available to Trajectory, but do not occupy the user-facing message stream.
92
+ */
93
+ export function projectTimelineTurnFlow(group) {
94
+ const flow = projectTurnFlow(group);
95
+ return {
96
+ prelude: [],
97
+ process: flow.process.filter((entry) => entry.source === "process" ||
98
+ (entry.item.kind !== "request" && entry.item.kind !== "step")),
99
+ postAnswer: flow.postAnswer,
100
+ };
101
+ }
102
+ function emptyGroup(key, turnId) {
8
103
  return {
9
104
  key,
105
+ ...(turnId ? { turnId } : {}),
10
106
  assistants: [],
11
107
  tools: [],
12
108
  processItems: [],
109
+ conversationItems: [],
110
+ turnClosed: false,
13
111
  live: false,
14
112
  };
15
113
  }
@@ -45,6 +143,7 @@ export function hydrateSessionThread(raw) {
45
143
  ...(item.clientRequestId ? { clientRequestId: item.clientRequestId } : {}),
46
144
  ...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
47
145
  ...(item.turnId ? { turnId: item.turnId } : {}),
146
+ ...(item.attachments?.length ? { attachments: item.attachments } : {}),
48
147
  });
49
148
  continue;
50
149
  }
@@ -91,6 +190,10 @@ export function hydrateSessionThread(raw) {
91
190
  ? finishedAtMs
92
191
  : createdAt,
93
192
  turnId: item.turnId ?? turnId,
193
+ ...(item.step === undefined ? {} : { step: item.step }),
194
+ ...(item.parentToolCallId
195
+ ? { parentToolCallId: item.parentToolCallId }
196
+ : {}),
94
197
  ...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
95
198
  ...(item.finishedSeq === undefined
96
199
  ? {}
@@ -131,30 +234,52 @@ function flushToolBuffer(processItems, buffer) {
131
234
  return;
132
235
  const sorted = sortToolsStable(buffer);
133
236
  for (const tool of sorted) {
237
+ const order = {
238
+ ...(tool.eventSeq === undefined ? {} : { eventSeq: tool.eventSeq }),
239
+ createdAt: tool.createdAt,
240
+ };
134
241
  const dagProgress = dagProgressActivityForTool(tool);
135
242
  if (dagProgress) {
136
- processItems.push({ kind: "dag-progress", progress: dagProgress });
243
+ processItems.push({
244
+ kind: "dag-progress",
245
+ progress: dagProgress,
246
+ toolCallCount: 1,
247
+ ...order,
248
+ });
137
249
  continue;
138
250
  }
139
251
  const activity = semanticActivityForTool(tool);
140
252
  if (activity) {
141
- processItems.push({ kind: "activity", activity });
253
+ processItems.push({ kind: "activity", activity, toolCallCount: 1, ...order });
142
254
  }
143
255
  else {
144
- processItems.push({ kind: "tool", tool });
256
+ processItems.push({ kind: "tool", tool, ...order });
145
257
  }
146
258
  }
147
259
  }
148
260
  function collapseDagProgressItems(items) {
149
- const seen = new Set();
150
- return items.filter((item) => {
151
- if (item.kind !== "dag-progress")
152
- return true;
153
- if (seen.has(item.progress.activityRef))
154
- return false;
155
- seen.add(item.progress.activityRef);
156
- return true;
157
- });
261
+ const collapsed = [];
262
+ const progressIndexByRef = new Map();
263
+ for (const item of items) {
264
+ if (item.kind !== "dag-progress") {
265
+ collapsed.push(item);
266
+ continue;
267
+ }
268
+ const existingIndex = progressIndexByRef.get(item.progress.activityRef);
269
+ if (existingIndex === undefined) {
270
+ progressIndexByRef.set(item.progress.activityRef, collapsed.length);
271
+ collapsed.push(item);
272
+ continue;
273
+ }
274
+ const existing = collapsed[existingIndex];
275
+ if (existing?.kind === "dag-progress") {
276
+ collapsed[existingIndex] = {
277
+ ...existing,
278
+ toolCallCount: (existing.toolCallCount ?? 1) + (item.toolCallCount ?? 1),
279
+ };
280
+ }
281
+ }
282
+ return collapsed;
158
283
  }
159
284
  function fillProcessDetails(group) {
160
285
  const usedToolIds = new Set();
@@ -184,12 +309,24 @@ function fillProcessDetails(group) {
184
309
  };
185
310
  for (const block of blocks) {
186
311
  if (block.type === "thinking") {
187
- items.push({ kind: "thinking", thinking: block.thinking });
312
+ items.push({
313
+ kind: "thinking",
314
+ thinking: block.thinking,
315
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
316
+ createdAt: anchorAt,
317
+ });
188
318
  continue;
189
319
  }
190
320
  if (block.type === "text") {
191
- if (block.text.trim())
192
- items.push({ kind: "text", text: block.text });
321
+ if (block.text.trim()) {
322
+ items.push({
323
+ kind: "text",
324
+ text: block.text,
325
+ assistantMessageId: assistant.id,
326
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
327
+ createdAt: anchorAt,
328
+ });
329
+ }
193
330
  continue;
194
331
  }
195
332
  const tool = group.tools.find((entry) => entry.id === block.toolCallId);
@@ -212,7 +349,12 @@ function fillProcessDetails(group) {
212
349
  anchorAt = tool.createdAt;
213
350
  }
214
351
  if (includeProviderError && assistant.providerError) {
215
- items.push({ kind: "provider-error", error: assistant.providerError });
352
+ items.push({
353
+ kind: "provider-error",
354
+ error: assistant.providerError,
355
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
356
+ createdAt: anchorAt,
357
+ });
216
358
  }
217
359
  flushItems();
218
360
  // Preserve the old-data fallback after this stage. Sequenced unmatched
@@ -245,8 +387,16 @@ function fillProcessDetails(group) {
245
387
  }
246
388
  addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []));
247
389
  }
248
- const blocks = finalAssistant?.content ?? [];
249
- const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
390
+ const blocks = finalAssistant?.content ??
391
+ (finalAssistant?.text.trim()
392
+ ? [{ type: "text", text: finalAssistant.text }]
393
+ : []);
394
+ // Without durable step-completion metadata, only a normally settled Turn can
395
+ // prove that its latest assistant generation is a final answer. Keep partial
396
+ // text in process for running/aborted/failed and legacy unknown states.
397
+ const split = splitFinalAssistantBlocks(blocks, {
398
+ isStreaming: group.live || group.turnState !== "settled",
399
+ });
250
400
  if (finalAssistant) {
251
401
  addAssistantNode(finalAssistant, split.processBlocks);
252
402
  }
@@ -265,14 +415,13 @@ function fillProcessDetails(group) {
265
415
  items,
266
416
  });
267
417
  }
268
- // Unified order (DSH rule): server seq is the single authoritative key,
269
- // createdAt falls back, insertion order breaks remaining ties. No
270
- // "fullySequenced" switch — a partially-sequenced group must not
271
- // silently re-sort its peers by wall clock when most nodes carry seq.
418
+ const fullySequenced = nodes.every((node) => node.eventSeq !== undefined);
272
419
  nodes.sort((a, b) => {
273
- const byOrder = compareChatOrder(a, b);
274
- if (byOrder !== 0)
275
- return byOrder;
420
+ if (fullySequenced && a.eventSeq !== b.eventSeq) {
421
+ return a.eventSeq - b.eventSeq;
422
+ }
423
+ if (a.createdAt !== b.createdAt)
424
+ return a.createdAt - b.createdAt;
276
425
  return a.insertionOrder - b.insertionOrder;
277
426
  });
278
427
  const processItems = nodes.flatMap((node) => node.items);
@@ -284,10 +433,6 @@ function fillProcessDetails(group) {
284
433
  group.processItems = collapseDagProgressItems(processItems);
285
434
  if (!finalAssistant)
286
435
  return;
287
- if (blocks.length === 0) {
288
- group.answer = finalAssistant;
289
- return;
290
- }
291
436
  if (split.answerBlocks.length === 0)
292
437
  return;
293
438
  const answerText = joinAssistantText(split.answerBlocks);
@@ -297,43 +442,49 @@ function fillProcessDetails(group) {
297
442
  content: split.answerBlocks,
298
443
  };
299
444
  }
300
- /**
301
- * Build user → process → answer groups for the message timeline.
302
- */
303
445
  export function buildTurnProcessGroups(input) {
304
- const visible = input.messages
446
+ const visible = ensureLinkedSubmissionOrder(input.messages
305
447
  .filter((m) => m.role === "user" || m.role === "assistant")
306
- .sort(compareChatOrder);
448
+ .sort((a, b) => {
449
+ if (a.eventSeq !== undefined &&
450
+ b.eventSeq !== undefined &&
451
+ a.eventSeq !== b.eventSeq) {
452
+ return a.eventSeq - b.eventSeq;
453
+ }
454
+ if (a.createdAt !== b.createdAt)
455
+ return a.createdAt - b.createdAt;
456
+ if (a.eventSeq !== undefined && b.eventSeq === undefined)
457
+ return 1;
458
+ if (a.eventSeq === undefined && b.eventSeq !== undefined)
459
+ return -1;
460
+ return 0;
461
+ }));
307
462
  const groups = [];
308
463
  let current = null;
309
- const startGroup = (key) => {
310
- const group = emptyGroup(key);
464
+ const startGroup = (key, turnId) => {
465
+ const group = emptyGroup(key, turnId);
311
466
  groups.push(group);
312
467
  return group;
313
468
  };
314
469
  for (const message of visible) {
315
470
  if (message.role === "user") {
316
- current = startGroup(`user-${message.id}`);
471
+ current = startGroup(`user-${message.id}`, message.turnId);
317
472
  current.user = message;
318
- if (message.turnId)
319
- current.turnId = message.turnId;
320
473
  continue;
321
474
  }
322
475
  if (message.kind === "compaction") {
323
476
  // Compaction is a standalone timeline event. Starting a fresh group
324
477
  // prevents it from replacing the previous turn's assistant answer.
325
- current = startGroup(`compaction-${message.id}`);
478
+ current = startGroup(`compaction-${message.id}`, message.turnId);
326
479
  current.assistants.push(message);
327
- if (message.turnId)
328
- current.turnId = message.turnId;
329
480
  current = null;
330
481
  continue;
331
482
  }
332
483
  if (!current)
333
- current = startGroup(`assistant-${message.id}`);
334
- current.assistants.push(message);
335
- if (current.turnId === undefined && message.turnId)
484
+ current = startGroup(`assistant-${message.id}`, message.turnId);
485
+ if (!current.turnId && message.turnId)
336
486
  current.turnId = message.turnId;
487
+ current.assistants.push(message);
337
488
  }
338
489
  const assigned = new Set();
339
490
  const assistantToGroup = new Map();
@@ -355,10 +506,10 @@ export function buildTurnProcessGroups(input) {
355
506
  if (assigned.has(tool.id))
356
507
  continue;
357
508
  if (tool.turnId) {
358
- const byTurn = groups.find((group) => group.turnId === tool.turnId ||
359
- group.user?.turnId === tool.turnId ||
509
+ const byTurn = groups.find((group) => group.user?.turnId === tool.turnId ||
360
510
  group.assistants.some((assistant) => assistant.turnId === tool.turnId) ||
361
511
  group.tools.some((t) => t.turnId === tool.turnId) ||
512
+ group.key === `turn-${tool.turnId}` ||
362
513
  (group.user && `restored-${group.user.id}` === tool.turnId));
363
514
  if (byTurn) {
364
515
  byTurn.tools.push(tool);
@@ -374,25 +525,15 @@ export function buildTurnProcessGroups(input) {
374
525
  }
375
526
  }
376
527
  }
377
- // Time-window fallback for legacy rows with neither assistantMessageId nor
378
- // turnId. The window ends at the NEXT group's first anchor (user OR first
379
- // assistant) — using POSITIVE_INFINITY would swallow every later group's
380
- // tools when the very next group has no user message (e.g. compaction or a
381
- // stand-alone assistant row), dumping one turn's bash cards into another.
382
528
  for (const tool of input.toolCalls) {
383
529
  if (assigned.has(tool.id))
384
530
  continue;
385
531
  const windowGroup = groups.find((group, index) => {
386
532
  const start = group.user?.createdAt ?? group.assistants[0]?.createdAt ?? 0;
387
- let end = Number.POSITIVE_INFINITY;
388
- for (let i = index + 1; i < groups.length; i += 1) {
389
- const next = groups[i];
390
- const nextAnchor = next?.user?.createdAt ?? next?.assistants[0]?.createdAt;
391
- if (nextAnchor !== undefined) {
392
- end = nextAnchor;
393
- break;
394
- }
395
- }
533
+ const next = groups[index + 1];
534
+ const end = next?.user?.createdAt ??
535
+ next?.assistants[0]?.createdAt ??
536
+ Number.POSITIVE_INFINITY;
396
537
  return tool.createdAt >= start && tool.createdAt < end;
397
538
  });
398
539
  // Time window only. Never dump unmatched tools onto the latest group:
@@ -402,13 +543,43 @@ export function buildTurnProcessGroups(input) {
402
543
  assigned.add(tool.id);
403
544
  }
404
545
  }
546
+ for (const node of input.conversationNodes ?? []) {
547
+ if (node.location.kind === "session")
548
+ continue;
549
+ const turnId = node.location.turnId;
550
+ if (!groups.some((group) => group.turnId === turnId)) {
551
+ startGroup(`turn-${turnId}`, turnId);
552
+ }
553
+ }
405
554
  for (const group of groups) {
406
- group.tools.sort(compareChatOrder);
555
+ group.tools.sort((a, b) => {
556
+ if (a.eventSeq !== undefined && b.eventSeq !== undefined) {
557
+ return a.eventSeq - b.eventSeq;
558
+ }
559
+ return a.createdAt - b.createdAt;
560
+ });
561
+ const turnState = group.turnId
562
+ ? input.turnStatesById?.[group.turnId]
563
+ : undefined;
564
+ if (turnState !== undefined)
565
+ group.turnState = turnState;
566
+ group.turnClosed =
567
+ turnState === "settled" ||
568
+ turnState === "aborted" ||
569
+ turnState === "failed";
570
+ const targetsActiveAssistant = input.activeAssistantId !== null &&
571
+ group.assistants.some((assistant) => assistant.id === input.activeAssistantId);
407
572
  group.live =
408
573
  input.streaming &&
409
- input.activeAssistantId !== null &&
410
- group.assistants.some((a) => a.id === input.activeAssistantId);
574
+ !group.turnClosed &&
575
+ (targetsActiveAssistant || turnState === "running");
411
576
  fillProcessDetails(group);
577
+ group.conversationItems = (input.conversationNodes ?? [])
578
+ .filter((node) => node.location.kind !== "session" && node.location.turnId === group.turnId)
579
+ .map((node) => ({ kind: node.kind, node }));
580
+ if (group.conversationItems.some((item) => item.kind === "turn-error")) {
581
+ group.processItems = group.processItems.filter((item) => item.kind !== "provider-error");
582
+ }
412
583
  }
413
584
  return groups;
414
585
  }
@@ -0,0 +1,19 @@
1
+ const PREVIEW_LIMIT = 160;
2
+ /** Project the complete durable user-message index without shipping full history. */
3
+ export function projectUserTurnOutline(messages) {
4
+ let index = 0;
5
+ return messages.flatMap((message) => {
6
+ if (message.role !== "user")
7
+ return [];
8
+ index += 1;
9
+ const flat = message.text.trim().replace(/\s+/g, " ");
10
+ return [{
11
+ messageId: message.id,
12
+ index,
13
+ textPreview: flat.length > PREVIEW_LIMIT
14
+ ? `${flat.slice(0, PREVIEW_LIMIT).trimEnd()}…`
15
+ : flat,
16
+ createdAt: Date.parse(message.at) || 0,
17
+ }];
18
+ });
19
+ }
@@ -4,7 +4,7 @@ import { createNpmUpdateClient } from "../../shared/update/npm-client.js";
4
4
  import { acquireUpdateLock, updateLockPath } from "../../shared/update/state.js";
5
5
  import { readInitRuntimeActivity } from "../observability/init-runtime-activity.js";
6
6
  import { performConsoleUpdateAndInit, } from "./console-update-and-init.js";
7
- import { TERMINAL_OPERATION_STATES } from "../../infrastructure/console/operation-store.js";
7
+ import { isOperationOccupied, } from "../../infrastructure/console/operation-store.js";
8
8
  export function createConsoleUpdateExecutor(input) {
9
9
  return async (channel) => {
10
10
  const currentVersion = readPackageVersion(input.packageRoot);
@@ -49,7 +49,7 @@ export function createConsoleUpdateExecutor(input) {
49
49
  };
50
50
  }
51
51
  const liveOtherOperation = operations.some((operation) => operation.action !== "consoleUpdateAndInit" &&
52
- !TERMINAL_OPERATION_STATES.has(operation.state));
52
+ isOperationOccupied(operation.state));
53
53
  return liveOtherOperation
54
54
  ? {
55
55
  ok: false,
@@ -142,6 +142,9 @@ export class DagConfirmationStore {
142
142
  preparedAt: now.toISOString(),
143
143
  expiresAt,
144
144
  operatorSessionId: input.operatorSessionId,
145
+ ...(input.telemetrySessionId
146
+ ? { telemetrySessionId: input.telemetrySessionId }
147
+ : {}),
145
148
  dagHandle: input.dagHandle,
146
149
  dagSha256,
147
150
  dagBytesPath: input.dagBytesPath,
@@ -286,6 +286,9 @@ export class DagExecutionReceiptStore {
286
286
  preparedAt: now.toISOString(),
287
287
  expiresAt: new Date(now.getTime() + ttl).toISOString(),
288
288
  operatorSessionId: input.operatorSessionId,
289
+ ...(input.telemetrySessionId
290
+ ? { telemetrySessionId: input.telemetrySessionId }
291
+ : {}),
289
292
  taskId: input.taskId,
290
293
  dagHandle: input.dagHandle,
291
294
  dagSha256: hashDagBytes(input.dagBytes),
@@ -35,6 +35,7 @@ import { evaluateConsoleDagInterruptEligibility } from "../observability/interru
35
35
  import { INTERRUPT_ERROR_MESSAGES, isInterruptReasonCode, normalizeInterruptReasonDetail, } from "../observability/interrupt-eligibility.js";
36
36
  import { assessAutonomousExecutionEligibility, deriveEligibilityFactsFromDagSpec, } from "./dag-execution-receipt.js";
37
37
  import { issueHumanGateToken, mutationGatePayloadHash, verifyHumanGateToken, } from "./human-gate-token.js";
38
+ import { DAG_TELEMETRY_SESSION_ID_ENV, isTelemetrySessionId, } from "../../shared/wizard-local-telemetry.js";
38
39
  const MUTATION_GATE_ACTIONS = new Set([
39
40
  // 2026-08-11: standaloneTaskRerun / workerTaskRetry are autonomous (server
40
41
  // run-facts checks). Destructive DAG reconciliation, live cooperative
@@ -565,7 +566,11 @@ export async function acceptOperation(ctx, input) {
565
566
  events: ctx.events,
566
567
  client: ctx.client,
567
568
  repoRoot: ctx.repoRoot,
568
- env: { ...process.env, LOOP_CONSOLE_APP_DATA: ctx.appData.root },
569
+ env: {
570
+ ...process.env,
571
+ LOOP_CONSOLE_APP_DATA: ctx.appData.root,
572
+ ...input.env,
573
+ },
569
574
  runCommand: ctx.runCommand,
570
575
  beforeTerminal: input.beforeTerminal,
571
576
  onSettled: async (operation, state, info) => {
@@ -822,6 +827,21 @@ async function acceptConsoleUpdateAndInit(ctx, clientRequestId, channel) {
822
827
  },
823
828
  };
824
829
  }
830
+ async function resolvePreparedDagTelemetrySessionId(ctx) {
831
+ if (!ctx.chatSessionRef || !ctx.sddDataAlignment)
832
+ return undefined;
833
+ if (!(await ctx.sddDataAlignment.isEligible()))
834
+ return undefined;
835
+ return ctx.sddDataAlignment.ensureTelemetrySessionId(ctx.chatSessionRef);
836
+ }
837
+ async function resolveDagTelemetryEnv(ctx, telemetrySessionId) {
838
+ if (!isTelemetrySessionId(telemetrySessionId) ||
839
+ !ctx.sddDataAlignment ||
840
+ !(await ctx.sddDataAlignment.isEligible())) {
841
+ return undefined;
842
+ }
843
+ return { [DAG_TELEMETRY_SESSION_ID_ENV]: telemetrySessionId };
844
+ }
825
845
  export async function dispatchOperatorAction(ctx, req) {
826
846
  const action = req.action?.trim();
827
847
  if (!action) {
@@ -1668,8 +1688,10 @@ export async function dispatchOperatorAction(ctx, req) {
1668
1688
  canonicalizerVersion: ref.canonicalizerVersion,
1669
1689
  taskConfigSchemaVersion: ref.taskConfigSchemaVersion,
1670
1690
  };
1691
+ const telemetrySessionId = await resolvePreparedDagTelemetrySessionId(ctx);
1671
1692
  const confirmation = await ctx.confirmations.prepare({
1672
1693
  operatorSessionId: ctx.operatorSessionId,
1694
+ ...(telemetrySessionId ? { telemetrySessionId } : {}),
1673
1695
  dagHandle,
1674
1696
  dagBytes: dagText,
1675
1697
  dagBytesPath: staged.path,
@@ -1910,8 +1932,10 @@ export async function dispatchOperatorAction(ctx, req) {
1910
1932
  }),
1911
1933
  };
1912
1934
  }
1935
+ const telemetrySessionId = await resolvePreparedDagTelemetrySessionId(ctx);
1913
1936
  const prepared = await ctx.dagExecutionReceipts.prepare({
1914
1937
  operatorSessionId: ctx.operatorSessionId,
1938
+ ...(telemetrySessionId ? { telemetrySessionId } : {}),
1915
1939
  taskId,
1916
1940
  dagHandle: "staged-dag.json",
1917
1941
  dagBytes: dagText,
@@ -2350,7 +2374,7 @@ export async function dispatchOperatorAction(ctx, req) {
2350
2374
  if (!confirmationId) {
2351
2375
  return invalid(action, "runDag requires executionId (autonomous) or confirmationId (browser Human Gate)");
2352
2376
  }
2353
- // Preserved browser Human Gate flow (dag-confirmation.ts untouched).
2377
+ // Preserved browser Human Gate flow.
2354
2378
  const conf = await ctx.confirmations.get(confirmationId);
2355
2379
  if (!conf) {
2356
2380
  return {
@@ -2409,12 +2433,14 @@ export async function dispatchOperatorAction(ctx, req) {
2409
2433
  }
2410
2434
  const taskId = conf.taskContractBinding.taskId;
2411
2435
  const gateToken = readWriteSetGateToken(conf.reviewPacket);
2436
+ const dagTelemetryEnv = await resolveDagTelemetryEnv(ctx, conf.telemetrySessionId);
2412
2437
  if (gateToken) {
2413
2438
  return acceptOperation(ctx, {
2414
2439
  action,
2415
2440
  actionParams: { confirmationId },
2416
2441
  clientRequestId,
2417
2442
  taskId,
2443
+ env: dagTelemetryEnv,
2418
2444
  cliArgs: [
2419
2445
  "task",
2420
2446
  "advance",
@@ -2430,6 +2456,7 @@ export async function dispatchOperatorAction(ctx, req) {
2430
2456
  actionParams: { confirmationId },
2431
2457
  clientRequestId,
2432
2458
  taskId,
2459
+ env: dagTelemetryEnv,
2433
2460
  cliArgs: [
2434
2461
  "dag",
2435
2462
  "execute",
@@ -2503,6 +2530,7 @@ export async function dispatchOperatorAction(ctx, req) {
2503
2530
  };
2504
2531
  }
2505
2532
  const gateToken = readWriteSetGateToken(receipt.reviewPacket);
2533
+ const dagTelemetryEnv = await resolveDagTelemetryEnv(ctx, receipt.telemetrySessionId);
2506
2534
  const dispatchRun = async () => {
2507
2535
  if (gateToken) {
2508
2536
  // Standard lifecycle path: the execution receipt replaces the
@@ -2512,6 +2540,7 @@ export async function dispatchOperatorAction(ctx, req) {
2512
2540
  actionParams: { executionId },
2513
2541
  clientRequestId,
2514
2542
  taskId,
2543
+ env: dagTelemetryEnv,
2515
2544
  cliArgs: [
2516
2545
  "task",
2517
2546
  "advance",
@@ -2528,6 +2557,7 @@ export async function dispatchOperatorAction(ctx, req) {
2528
2557
  actionParams: { executionId },
2529
2558
  clientRequestId,
2530
2559
  taskId,
2560
+ env: dagTelemetryEnv,
2531
2561
  cliArgs: [
2532
2562
  "dag",
2533
2563
  "execute",
@@ -2759,7 +2789,6 @@ export async function dispatchOperatorAction(ctx, req) {
2759
2789
  reconcileAction,
2760
2790
  "--reason",
2761
2791
  reason,
2762
- "--json",
2763
2792
  ],
2764
2793
  });
2765
2794
  await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
@@ -114,7 +114,7 @@ const CONTEXT_TRANSFER_CODE_COPY = [
114
114
  },
115
115
  {
116
116
  pattern: /\bOPERATION_NEEDS_RECONCILE\b/,
117
- copy: "操作记录需要先恢复核对(OPERATION_NEEDS_RECONCILE):上次执行未确认结束,请刷新操作状态后重试。",
117
+ copy: "当前操作的执行结果尚未确认,需要先核对(OPERATION_NEEDS_RECONCILE)。请查看操作记录和相关任务状态,核对实际执行结果后再决定后续操作;确认前请勿重复提交。"
118
118
  },
119
119
  {
120
120
  pattern: /\bCONTEXT_EXPORT_FAILED\b/,
@@ -169,6 +169,33 @@ const CONTEXT_TRANSFER_CODE_COPY = [
169
169
  pattern: /\bINVALID_VERSION\b/,
170
170
  copy: "安装版本或渠道版本号无法解析(INVALID_VERSION),请重试;若持续出现请反馈。",
171
171
  },
172
+ // 源绑定漂移(dag-confirmation.ts / dag-execution-receipt.ts 的英文原文)
173
+ // 与 frontend split CAS 冲突(operator-actions.ts)原本会原样透传到操作者
174
+ // 横幅,这里给出可执行的中文说明。
175
+ {
176
+ pattern: /\bBINDING_DRIFT\b|\bbinding drift\b|\bbinding drifted\b/i,
177
+ copy: "这次确认所依据的内容已经变了(任务契约、源文件、程序版本或校验结果在确认之后被修改),因此旧确认不能再使用。这是保护措施,不是故障:请回到任务页重新生成确认,然后重新开始运行。",
178
+ },
179
+ {
180
+ pattern: /frontend split CAS conflict/i,
181
+ copy: "前端拆分任务的状态在你操作期间被其他操作改过了(通常是另一个标签页或并发操作),为避免覆盖对方的结果,本次修改没有生效。请刷新页面,确认当前状态后重试。",
182
+ },
183
+ {
184
+ pattern: /\bCONFIRMATION_EXPIRED\b/,
185
+ copy: "这次确认已经超时失效。请回到任务页重新生成确认后再继续。",
186
+ },
187
+ {
188
+ pattern: /\bCONFIRMATION_STALE\b/,
189
+ copy: "这次确认对应的是改动之前的内容,已经过期作废。请回到任务页重新生成确认后再继续。",
190
+ },
191
+ {
192
+ pattern: /\bCONFIRMATION_CONSUMED\b/,
193
+ copy: "这次确认已经被使用过了,不能重复提交。如需再次执行,请重新生成确认。",
194
+ },
195
+ {
196
+ pattern: /\bHUMAN_CONFIRMATION_REQUIRED\b/,
197
+ copy: "这一步需要你先在页面上确认一次才能继续。请按提示完成确认后再重试。",
198
+ },
172
199
  ];
173
200
  /** Map internal spawn/controller errors to actionable Chinese copy. */
174
201
  export function formatOperatorUserError(message) {