@tea-agent/loop-agent 0.40.0-next.1 → 0.40.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 (208) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +106 -109
  3. package/README.md +2 -2
  4. package/dist/application/task-lifecycle/advance.js +12 -0
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/cli/update/runtime-activity.js +1 -29
  7. package/dist/commands/dag-rerun.js +1 -1
  8. package/dist/executors/dag-pi-executor.js +69 -7
  9. package/dist/executors/pi-executor.js +4 -2
  10. package/dist/executors/pi-sdk-executor.js +46 -1
  11. package/dist/executors/pi-writer-tool-policy.js +57 -1
  12. package/dist/executors/shell-executor.js +43 -3
  13. package/dist/executors/workspace-write-snapshot.js +68 -0
  14. package/dist/infrastructure/harness/atomic-write.js +23 -0
  15. package/dist/shared/dag-prompt-override.js +4 -5
  16. package/dist/shared/update/npm-client.js +33 -11
  17. package/dist/task/config-types.js +10 -0
  18. package/dist/task/contract/apply.js +11 -1
  19. package/dist/task/contract/constants.js +2 -0
  20. package/dist/task/contract/hash.js +3 -0
  21. package/dist/task/contract/observe.js +25 -4
  22. package/dist/task/contract/paths.js +2 -1
  23. package/dist/task/contract/project.js +15 -1
  24. package/dist/task/contract/schema.js +2 -0
  25. package/dist/task/contract/transaction.js +12 -0
  26. package/dist/task/source-prepare/completeness.js +188 -0
  27. package/dist/task/source-prepare/fragment-inventory.js +474 -0
  28. package/dist/task/source-prepare/index.js +3 -0
  29. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  30. package/dist/task/source-prepare/ledger-review.js +214 -0
  31. package/dist/task/source-prepare/ledger.js +545 -0
  32. package/dist/task/source-prepare/prepare.js +262 -1
  33. package/dist/task/source-prepare/semantic-intake.js +19 -3
  34. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  35. package/dist/task/source-prepare/types.js +22 -0
  36. package/dist/worker/cli.js +12 -26
  37. package/dist/worker/console/chat/chat-event-store.js +75 -5
  38. package/dist/worker/console/chat/context-panel.js +7 -0
  39. package/dist/worker/console/chat/deferred-turn.js +12 -0
  40. package/dist/worker/console/chat/pi-runtime.js +331 -18
  41. package/dist/worker/console/chat/provider-error.js +98 -0
  42. package/dist/worker/console/chat/routes.js +389 -56
  43. package/dist/worker/console/chat/session-stats.js +179 -0
  44. package/dist/worker/console/chat/session-store.js +93 -63
  45. package/dist/worker/console/chat/todos.js +115 -0
  46. package/dist/worker/console/chat/tool-preview.js +42 -0
  47. package/dist/worker/console/chat/turn-process.js +20 -55
  48. package/dist/worker/console/chat/user-questions.js +266 -0
  49. package/dist/worker/console/chat/workspace-landing.js +79 -0
  50. package/dist/worker/console/console-handoff.js +82 -0
  51. package/dist/worker/console/console-update-and-init.js +115 -0
  52. package/dist/worker/console/console-update-runtime.js +84 -0
  53. package/dist/worker/console/draft-store.js +49 -0
  54. package/dist/worker/console/native-directory-picker.js +13 -0
  55. package/dist/worker/console/operation-store.js +1 -1
  56. package/dist/worker/console/operator-actions.js +129 -4
  57. package/dist/worker/console/operator-surface-health.js +1 -0
  58. package/dist/worker/console/prd-intake-bridge.js +13 -0
  59. package/dist/worker/console/routes.js +312 -1
  60. package/dist/worker/console/security.js +4 -4
  61. package/dist/worker/console/server.js +583 -172
  62. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
  63. package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
  64. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
  65. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
  66. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
  67. package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
  68. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
  78. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
  79. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
  80. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
  81. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
  82. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
  83. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
  84. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
  89. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
  90. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
  91. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
  92. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
  93. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
  94. package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
  95. package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
  96. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
  97. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
  98. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
  99. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
  100. package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
  101. package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
  102. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
  103. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
  104. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
  105. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
  106. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
  107. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
  108. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
  109. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
  110. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
  111. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
  112. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
  113. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
  114. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
  115. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
  116. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
  117. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
  118. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
  119. package/dist/worker/console/static/index.html +2 -2
  120. package/dist/worker/console/static-src/app/console-types.js +68 -4
  121. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  122. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  123. package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
  124. package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
  125. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  126. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  127. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  128. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  129. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  130. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  131. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  132. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  133. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  134. package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
  135. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  136. package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
  137. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  138. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  139. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  140. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  141. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  142. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  143. package/dist/worker/console/workspace-context.js +234 -0
  144. package/dist/worker/console/workspace-registry.js +214 -0
  145. package/dist/worker/observability/init-runtime-activity.js +26 -0
  146. package/dist/worker/observe/node-input.js +88 -2
  147. package/dist/worker/observe/routes.js +188 -0
  148. package/dist/worker/observe/server.js +4 -0
  149. package/dist/worker/observe/static/api.js +29 -9
  150. package/dist/worker/observe/static/app.js +18 -70
  151. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  152. package/dist/worker/observe/static/dag-node-purpose.js +203 -0
  153. package/dist/worker/observe/static/format.js +37 -0
  154. package/dist/worker/observe/static/index.html +6 -1
  155. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  156. package/dist/worker/observe/static/operator-chrome.css +145 -20
  157. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  158. package/dist/worker/observe/static/operator-chrome.js +330 -100
  159. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  160. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  161. package/dist/worker/observe/static/router.d.ts +46 -0
  162. package/dist/worker/observe/static/router.js +25 -1
  163. package/dist/worker/observe/static/state.d.ts +50 -0
  164. package/dist/worker/observe/static/state.js +174 -2
  165. package/dist/worker/observe/static/styles.css +357 -35
  166. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  167. package/dist/worker/observe/static/views/dag-inspector.js +533 -79
  168. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  169. package/dist/worker/observe/static/views/dag.js +6 -4
  170. package/dist/worker/observe/static/views/dashboard.js +6 -4
  171. package/dist/worker/observe/static/views/night.js +2 -2
  172. package/dist/worker/observe/static/views/pool.js +1 -0
  173. package/dist/worker/observe/static/views/run.js +3 -1
  174. package/dist/worker/observe/static/views/task.js +56 -1
  175. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +29 -1
  176. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  177. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  178. package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
  179. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  180. package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
  181. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  182. package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
  183. package/dist/workflows/dag/frontend-review-context.js +92 -10
  184. package/dist/workflows/dag/init-hybrid.js +238 -51
  185. package/dist/workflows/dag/interrupt-request.js +3 -3
  186. package/dist/workflows/dag/lifecycle.js +3 -3
  187. package/dist/workflows/dag/node-execution.js +45 -0
  188. package/dist/workflows/dag/types.js +62 -12
  189. package/docs/README.md +2 -0
  190. package/docs/architecture/evolution.md +45 -0
  191. package/docs/init-surface.manifest.json +1 -0
  192. package/docs/operations/local-development-environment.md +21 -0
  193. package/docs/templates/README.md +1 -1
  194. package/docs/templates/agent-dag.schema.json +18 -0
  195. package/docs/templates/backend-test-dag.json +265 -237
  196. package/docs/templates/frontend-implementation-contract.schema.json +681 -24
  197. package/harness.json +3 -3
  198. package/package.json +3 -1
  199. package/skills/loop-agent/references/hybrid-dag.md +7 -0
  200. package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
  201. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
  202. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
  203. package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
  204. package/dist/worker/console/static/assets/index-Ci8ksvBm.js +0 -406
  205. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
  206. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
  207. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  208. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -1,8 +1,9 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { classifyRecoveryFact, consoleRecoveryCtas, partitionRecoveryCtas, recommendedRecoveryCtaId, } from "../../recovery-cta.js";
3
3
  import { buildRecoveryReason, isAutoRecoveryReason, pickDefaultRecoveryNodeId, pickDefaultRecoveryRunId, sortRecoveryNodeCandidates, } from "../../recovery-selection.js";
4
- import { postOperator, RECOVERY_FACT_LABEL, } from "./console-types.js";
4
+ import { postOperator, scopedReadUrl, RECOVERY_FACT_LABEL, } from "./console-types.js";
5
5
  import { useRecoveryActions } from "./useRecoveryActions.js";
6
+ import { applyCanonicalHref, buildInspectHref, } from "../shell/workspace-route.js";
6
7
  /** Recovery workspace: DAG-run / node candidate selection, fact-classified
7
8
  * CTA lanes, structured reason prefill, plan preflight and gated reruns. */
8
9
  export function useRecoveryConsole(params) {
@@ -36,12 +37,12 @@ export function useRecoveryConsole(params) {
36
37
  const inspectPath = useCallback(() => {
37
38
  if (dagRunId.trim()) {
38
39
  // from=recovery 激活统一壳的对象上下文 chip 与显式返回路径
39
- return `/inspect/#/dag/${encodeURIComponent(dagRunId.trim())}?from=recovery`;
40
+ return buildInspectHref(`/dag/${encodeURIComponent(dagRunId.trim())}`, "from=recovery");
40
41
  }
41
- return "/inspect/#/";
42
+ return "/#/inspect";
42
43
  }, [dagRunId]);
43
44
  const openFullInspect = useCallback(() => {
44
- window.location.assign(inspectPath());
45
+ applyCanonicalHref(inspectPath());
45
46
  }, [inspectPath]);
46
47
  /** Resolve DAG run id for inspect (side panel or full page). */
47
48
  const resolveDagRunForInspect = useCallback(async () => {
@@ -70,7 +71,7 @@ export function useRecoveryConsole(params) {
70
71
  setDagRunId(resolvedDag);
71
72
  setObserveLinkStatus("match");
72
73
  setObserveLinkMessage(null);
73
- setObserveUrl(`/inspect/#/dag/${encodeURIComponent(resolvedDag)}`);
74
+ setObserveUrl(buildInspectHref(`/dag/${encodeURIComponent(resolvedDag)}`));
74
75
  return resolvedDag;
75
76
  }
76
77
  setOpError("尚未找到该任务的 DAG 运行,请稍后再打开检视");
@@ -97,7 +98,7 @@ export function useRecoveryConsole(params) {
97
98
  const resolved = await resolveDagRunForInspect();
98
99
  if (!resolved)
99
100
  return;
100
- window.location.assign(`/inspect/#/dag/${encodeURIComponent(resolved)}?from=recovery`);
101
+ applyCanonicalHref(buildInspectHref(`/dag/${encodeURIComponent(resolved)}`, "from=recovery"));
101
102
  }, [resolveDagRunForInspect]);
102
103
  const loadRecoveryRuns = useCallback(async () => {
103
104
  setRecoveryRunsLoading(true);
@@ -107,7 +108,7 @@ export function useRecoveryConsole(params) {
107
108
  if (id)
108
109
  qs.set("taskId", id);
109
110
  qs.set("limit", "40");
110
- const res = await fetch(`/api/operator/v1/recovery/candidates?${qs.toString()}`, { credentials: "same-origin" });
111
+ const res = await fetch(scopedReadUrl(`/api/operator/v1/recovery/candidates?${qs.toString()}`), { credentials: "same-origin" });
111
112
  if (!res.ok) {
112
113
  setRecoveryRuns([]);
113
114
  return;
@@ -133,7 +134,7 @@ export function useRecoveryConsole(params) {
133
134
  }
134
135
  setRecoveryNodesLoading(true);
135
136
  try {
136
- const res = await fetch(`/api/dag-runs/${encodeURIComponent(id)}`, {
137
+ const res = await fetch(scopedReadUrl(`/api/dag-runs/${encodeURIComponent(id)}`), {
137
138
  credentials: "same-origin",
138
139
  });
139
140
  if (!res.ok) {
@@ -253,9 +254,7 @@ export function useRecoveryConsole(params) {
253
254
  if (!refreshRunStatus)
254
255
  return;
255
256
  try {
256
- const res = await fetch(`/api/dag-runs/${encodeURIComponent(runId)}`, {
257
- credentials: "same-origin",
258
- });
257
+ const res = await fetch(scopedReadUrl(`/api/dag-runs/${encodeURIComponent(runId)}`), { credentials: "same-origin" });
259
258
  if (!res.ok || cancelled)
260
259
  return;
261
260
  const runBody = (await res.json());
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useMemo, useState } from "react";
2
2
  import { isTerminalDagRun } from "../../inspect-split.js";
3
+ import { scopedReadUrl } from "./console-types.js";
3
4
  import { summarizeRunProgress, } from "../pages/tasks/run-panel-progress.js";
4
5
  const RUN_PROGRESS_POLL_INTERVAL_MS = 5000;
5
6
  /**
@@ -18,7 +19,7 @@ export function useRunProgress(dagRunId) {
18
19
  let timer;
19
20
  const load = async () => {
20
21
  try {
21
- const response = await fetch(`/api/dag-runs/${encodeURIComponent(runId)}`, { credentials: "same-origin", signal: controller.signal });
22
+ const response = await fetch(scopedReadUrl(`/api/dag-runs/${encodeURIComponent(runId)}`), { credentials: "same-origin", signal: controller.signal });
22
23
  if (!response.ok)
23
24
  return;
24
25
  const body = (await response.json());
@@ -1,6 +1,14 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { ensureUniqueDocumentNames, operatorRequestFits, operatorRequestTooLargeMessage, } from "../prd-file-import.js";
3
- import { requestId, WORKFLOW_OPTIONS, } from "./console-types.js";
3
+ import { requestId, scopedReadUrl, WORKFLOW_OPTIONS, } from "./console-types.js";
4
+ export async function fetchLedgerSummary(taskId, fetchImpl) {
5
+ const impl = fetchImpl ?? fetch;
6
+ const response = await impl(scopedReadUrl(`/api/tasks/${encodeURIComponent(taskId)}/ledger`), { credentials: "same-origin" });
7
+ if (!response.ok)
8
+ return { ok: false };
9
+ const summary = (await response.json());
10
+ return { ok: true, summary };
11
+ }
4
12
  /** Happy Path task wizard: import-prd → interview → contract → dag-confirm →
5
13
  * run. Owns task identity, interview projection and contract preflight/apply. */
6
14
  export function useTaskWizard(params) {
@@ -24,16 +32,27 @@ export function useTaskWizard(params) {
24
32
  const [contractDiffSummary, setContractDiffSummary] = useState(null);
25
33
  const [contractApplied, setContractApplied] = useState(false);
26
34
  const [spineOk, setSpineOk] = useState(null);
35
+ const [ledgerLoading, setLedgerLoading] = useState(false);
36
+ const [ledgerError, setLedgerError] = useState(null);
37
+ const [ledgerSummary, setLedgerSummary] = useState(null);
27
38
  const [contractPreflightKey, setContractPreflightKey] = useState(null);
28
39
  const contractPreflightRunning = useRef(false);
40
+ /** Epoch guard for ledger fetches: only the newest request may write wizard
41
+ * state, so a concurrent in-flight preflight response can never overwrite
42
+ * a fresh post-apply result (AC-001). */
43
+ const ledgerFetchEpoch = useRef(0);
29
44
  const resetContractLanding = useCallback(() => {
30
45
  setContractValidateOk(null);
31
46
  setContractValidateSummary(null);
32
47
  setContractDiffSummary(null);
33
48
  setContractApplied(false);
34
49
  setSpineOk(null);
50
+ setLedgerLoading(false);
51
+ setLedgerError(null);
52
+ setLedgerSummary(null);
35
53
  setContractPreflightKey(null);
36
54
  contractPreflightRunning.current = false;
55
+ ledgerFetchEpoch.current = 0;
37
56
  }, []);
38
57
  const enterContractStep = useCallback(() => {
39
58
  resetContractLanding();
@@ -56,6 +75,39 @@ export function useTaskWizard(params) {
56
75
  : "访谈已完成:草稿已绑定完整评估结果");
57
76
  }
58
77
  }, [enterContractStep, setOpMessage]);
78
+ /** Epoch-guarded shared ledger refresh used by both the contract-step
79
+ * preflight and the post-apply re-GET. `failureSemantics` keeps the
80
+ * preflight five-state behavior (non-ok → empty state, no error) while the
81
+ * post-apply refresh treats a non-ok/network failure as a safe-degrade
82
+ * error per AC-001. */
83
+ const refreshLedgerState = useCallback(async (id, failureSemantics) => {
84
+ const epoch = ++ledgerFetchEpoch.current;
85
+ setLedgerLoading(true);
86
+ setLedgerError(null);
87
+ try {
88
+ const outcome = await fetchLedgerSummary(id);
89
+ if (ledgerFetchEpoch.current !== epoch)
90
+ return;
91
+ if (!outcome.ok) {
92
+ setLedgerSummary(null);
93
+ if (failureSemantics) {
94
+ setLedgerError("ledger 接口不可达(安全降级)");
95
+ }
96
+ return;
97
+ }
98
+ setLedgerSummary(outcome.summary);
99
+ }
100
+ catch {
101
+ if (ledgerFetchEpoch.current !== epoch)
102
+ return;
103
+ setLedgerError("ledger 接口不可达(安全降级)");
104
+ setLedgerSummary(null);
105
+ }
106
+ finally {
107
+ if (ledgerFetchEpoch.current === epoch)
108
+ setLedgerLoading(false);
109
+ }
110
+ }, []);
59
111
  const runContractPreflight = useCallback(async (force = false) => {
60
112
  const id = taskId.trim();
61
113
  if (!id)
@@ -74,6 +126,9 @@ export function useTaskWizard(params) {
74
126
  setContractDiffSummary(null);
75
127
  setSpineOk(null);
76
128
  setContractApplied(false);
129
+ // Shared epoch-guarded refresh preserves the five-state semantics:
130
+ // non-ok → empty state, network failure → safe-degrade error.
131
+ await refreshLedgerState(id, false);
77
132
  try {
78
133
  const validateBody = await runAction("contractValidate", {
79
134
  taskId: id,
@@ -101,6 +156,7 @@ export function useTaskWizard(params) {
101
156
  contractPreflightKey,
102
157
  contractValidateOk,
103
158
  isActionSuccess,
159
+ refreshLedgerState,
104
160
  runAction,
105
161
  setOpMessage,
106
162
  summarizeEnvelope,
@@ -116,6 +172,11 @@ export function useTaskWizard(params) {
116
172
  return;
117
173
  }
118
174
  setContractApplied(true);
175
+ // AC-001: re-GET the read-only ledger right after apply succeeds and
176
+ // write the latest summary BEFORE spineCheck/setStep, so the first
177
+ // dag-confirm frame shows post-apply statistics (never a loading/empty
178
+ // flash); refresh failure safe-degrades via ledgerError.
179
+ await refreshLedgerState(id, true);
119
180
  const spineBody = await runAction("spineCheck", { taskId: id });
120
181
  if (isActionSuccess(spineBody)) {
121
182
  setSpineOk(true);
@@ -125,7 +186,15 @@ export function useTaskWizard(params) {
125
186
  }
126
187
  setSpineOk(false);
127
188
  setOpMessage("契约已应用,但 Spine 审计未通过:可重试 Spine");
128
- }, [assessmentId, isActionSuccess, runAction, setOpMessage, setStep, taskId]);
189
+ }, [
190
+ assessmentId,
191
+ isActionSuccess,
192
+ refreshLedgerState,
193
+ runAction,
194
+ setOpMessage,
195
+ setStep,
196
+ taskId,
197
+ ]);
129
198
  const retrySpineOnly = useCallback(async () => {
130
199
  const id = taskId.trim();
131
200
  if (!id)
@@ -282,6 +351,9 @@ export function useTaskWizard(params) {
282
351
  contractValidateOk,
283
352
  contractValidateSummary,
284
353
  contractDiffSummary,
354
+ ledgerLoading,
355
+ ledgerError,
356
+ ledgerSummary,
285
357
  contractApplied,
286
358
  spineOk,
287
359
  applyInterviewPayload,
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
+ import { scopedReadUrl } from "../app/console-types.js";
2
3
  /** Night board data plane: schedules list + doctor report + task pool +
3
4
  * foreground aux-ticker projection, loaded together on mount/refresh. */
4
5
  export function useNightBoard(params) {
@@ -16,10 +17,12 @@ export function useNightBoard(params) {
16
17
  setLoadError(null);
17
18
  try {
18
19
  const [jobsRes, doctorRes, tasksRes, tickerRes] = await Promise.all([
19
- fetch("/api/night-jobs", { credentials: "same-origin" }),
20
- fetch("/api/night-jobs/doctor", { credentials: "same-origin" }),
21
- fetch("/api/tasks", { credentials: "same-origin" }),
22
- fetch("/api/operator/v1/night-aux-ticker", {
20
+ fetch(scopedReadUrl("/api/night-jobs"), { credentials: "same-origin" }),
21
+ fetch(scopedReadUrl("/api/night-jobs/doctor"), {
22
+ credentials: "same-origin",
23
+ }),
24
+ fetch(scopedReadUrl("/api/tasks"), { credentials: "same-origin" }),
25
+ fetch(scopedReadUrl("/api/operator/v1/night-aux-ticker"), {
23
26
  credentials: "same-origin",
24
27
  }),
25
28
  ]);
@@ -59,7 +62,7 @@ export function useNightBoard(params) {
59
62
  }, [refreshBoard]);
60
63
  const onMorningReport = useCallback(async () => {
61
64
  try {
62
- const res = await fetch("/api/night-jobs/morning", {
65
+ const res = await fetch(scopedReadUrl("/api/night-jobs/morning"), {
63
66
  credentials: "same-origin",
64
67
  });
65
68
  if (!res.ok) {
@@ -3,6 +3,7 @@ import { humanGateCardFromEventPayload, mergeHumanGateCardState, } from "../../c
3
3
  import { mergeOperationCardState, operationFromEventPayload, } from "../../chat/operation-card.js";
4
4
  import { requestId } from "./format.js";
5
5
  import { parseAssistantContent } from "../../chat/assistant-content.js";
6
+ import { upsertCompactionMessage } from "./compaction-message.js";
6
7
  /** Shared handler for a single parsed Chat SSE event (M1 reconnect).
7
8
  *
8
9
  * Used by BOTH the inline prompt POST SSE and the background reconnect
@@ -10,7 +11,9 @@ import { parseAssistantContent } from "../../chat/assistant-content.js";
10
11
  * original request would have. `assistantId` is the in-flight assistant
11
12
  * message id to patch for streaming text; reconnect creates one if none. */
12
13
  export function createChatSseEventApplier(deps) {
13
- const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
14
+ const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setDeferredTurns = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, onTelemetryRefresh, setMessages, setToolCalls, setPendingQuestion = () => undefined, setTodos = () => undefined, } = deps;
15
+ let queueOwner;
16
+ let queueRevision = -1;
14
17
  return (eventName, data, assistantId, options) => {
15
18
  if (options?.generation !== undefined &&
16
19
  refs.sessionGenerationRef.current !== options.generation)
@@ -42,6 +45,26 @@ export function createChatSseEventApplier(deps) {
42
45
  const payload = data.data && typeof data.data === "object"
43
46
  ? data.data
44
47
  : data;
48
+ if (eventName === "todo_updated" || eventName === "todo_cleared") {
49
+ const todos = Array.isArray(payload.todos) ? payload.todos : [];
50
+ setTodos(todos.filter((item) => Boolean(item) && typeof item === "object" && typeof item.content === "string" && (item.status === "pending" || item.status === "in_progress" || item.status === "completed")));
51
+ return;
52
+ }
53
+ if ((eventName === "question_requested" ||
54
+ eventName === "question_resolved" ||
55
+ eventName === "question_cancelled") &&
56
+ payload.question &&
57
+ typeof payload.question === "object") {
58
+ const question = payload.question;
59
+ if (typeof question.requestId === "string" &&
60
+ typeof question.sessionId === "string" &&
61
+ question.sessionId === refs.sessionRef.current?.sessionId) {
62
+ setPendingQuestion(eventName === "question_requested" && question.status === "pending"
63
+ ? question
64
+ : null);
65
+ }
66
+ return;
67
+ }
45
68
  const turnId = typeof data.turnId === "string" ? data.turnId : undefined;
46
69
  const eventSeq = typeof data.seq === "number" && Number.isFinite(data.seq)
47
70
  ? data.seq
@@ -51,13 +74,20 @@ export function createChatSseEventApplier(deps) {
51
74
  : Date.now();
52
75
  if (eventName === "queue_update") {
53
76
  const revision = typeof payload.revision === "number" ? payload.revision : -1;
54
- setDeferredTurn((current) => {
55
- if (current && revision < current.revision)
56
- return current;
57
- return payload.deferredTurn && typeof payload.deferredTurn === "object"
58
- ? payload.deferredTurn
59
- : null;
60
- });
77
+ const owner = refs.sessionRef.current?.sessionId;
78
+ if (owner !== queueOwner) {
79
+ queueOwner = owner;
80
+ queueRevision = -1;
81
+ }
82
+ if (revision < queueRevision)
83
+ return;
84
+ queueRevision = revision;
85
+ setDeferredTurn(payload.deferredTurn && typeof payload.deferredTurn === "object"
86
+ ? payload.deferredTurn
87
+ : null);
88
+ if (Array.isArray(payload.deferredTurns)) {
89
+ setDeferredTurns(payload.deferredTurns);
90
+ }
61
91
  return;
62
92
  }
63
93
  if (eventName === "user_message") {
@@ -84,6 +114,17 @@ export function createChatSseEventApplier(deps) {
84
114
  }
85
115
  return;
86
116
  }
117
+ if (eventName === "compact" ||
118
+ eventName === "compaction_start" ||
119
+ eventName === "compaction_end") {
120
+ setMessages((messages) => upsertCompactionMessage(messages, {
121
+ eventId: typeof data.eventId === "string" ? data.eventId : undefined,
122
+ kind: eventName,
123
+ at: new Date(eventAt).toISOString(),
124
+ data: payload,
125
+ }));
126
+ return;
127
+ }
87
128
  if (turnId &&
88
129
  (eventName === "agent_start" || eventName === "message_update") &&
89
130
  refs.settledTurnIdsRef.current.has(turnId)) {
@@ -186,6 +227,16 @@ export function createChatSseEventApplier(deps) {
186
227
  refs.settledTurnIdsRef.current.delete(turnId);
187
228
  refs.activeTurnIdRef.current = turnId;
188
229
  }
230
+ if (refs.stopRequestedRef.current &&
231
+ (!turnId ||
232
+ turnId === refs.stoppingTurnIdRef.current ||
233
+ turnId === refs.activeTurnIdRef.current)) {
234
+ refs.streamingRef.current = false;
235
+ setStreaming(false);
236
+ setTurnPhase("idle");
237
+ setStreamingAssistantId(null);
238
+ return;
239
+ }
189
240
  refs.streamingRef.current = true;
190
241
  setStreaming(true);
191
242
  setTurnPhase("running");
@@ -200,9 +251,22 @@ export function createChatSseEventApplier(deps) {
200
251
  // (M4.4). It must not prematurely close the visible turn.
201
252
  if (eventName === "agent_end")
202
253
  return;
254
+ const pending = refs.pendingSubmissionRef.current;
255
+ if (pending && turnId && pending.acceptedTurnId === turnId) {
256
+ refs.releasedClientRequestIdsRef.current.add(pending.clientRequestId);
257
+ refs.pendingSubmissionRef.current = null;
258
+ refs.submitInFlightRef.current = false;
259
+ }
260
+ if (!turnId ||
261
+ turnId === refs.stoppingTurnIdRef.current ||
262
+ turnId === refs.activeTurnIdRef.current) {
263
+ refs.stoppingTurnIdRef.current = null;
264
+ refs.stopRequestedRef.current = false;
265
+ }
203
266
  if (turnId === refs.activeTurnIdRef.current)
204
267
  refs.activeTurnIdRef.current = null;
205
268
  refs.streamingRef.current = false;
269
+ refs.assistantIdRef.current = null;
206
270
  setStreaming(false);
207
271
  setTurnPhase("idle");
208
272
  setStreamingAssistantId(null);
@@ -236,10 +300,21 @@ export function createChatSseEventApplier(deps) {
236
300
  return true;
237
301
  return assistantOwnsTools;
238
302
  }));
303
+ if (!replay)
304
+ onTelemetryRefresh?.();
239
305
  return;
240
306
  }
241
307
  if (eventName === "operation-ref" || eventName === "operation-status") {
242
308
  const operation = operationFromEventPayload(payload);
309
+ // R9: tombstones are session-scoped. The same operation may be linked
310
+ // to another session, but delayed frames cannot resurrect it here.
311
+ const currentSessionId = refs.sessionRef.current?.sessionId;
312
+ if (operation &&
313
+ currentSessionId &&
314
+ refs.unlinkedOperationIdsRef.current
315
+ .get(currentSessionId)
316
+ ?.has(operation.operationId))
317
+ return;
243
318
  if (operation) {
244
319
  const toolCallId = typeof payload.toolCallId === "string"
245
320
  ? payload.toolCallId
@@ -256,6 +331,23 @@ export function createChatSseEventApplier(deps) {
256
331
  }
257
332
  return;
258
333
  }
334
+ if (eventName === "operation-unlinked") {
335
+ // R8 AC-001/AC-R8-001: durable tombstone — the server persisted an
336
+ // operation-unlinked event that OVERSHADOWS any earlier operation-ref.
337
+ // Drop the card from the projection; replay/recovery can never
338
+ // resurrect it because the tombstone is appended after the ref in
339
+ // the same ring.
340
+ const operationId = typeof payload.operationId === "string" ? payload.operationId : "";
341
+ const currentSessionId = refs.sessionRef.current?.sessionId;
342
+ if (operationId && currentSessionId) {
343
+ const sessionTombstones = refs.unlinkedOperationIdsRef.current.get(currentSessionId) ??
344
+ new Set();
345
+ sessionTombstones.add(operationId);
346
+ refs.unlinkedOperationIdsRef.current.set(currentSessionId, sessionTombstones);
347
+ setOperationCards((current) => current.filter((card) => card.operationId !== operationId));
348
+ }
349
+ return;
350
+ }
259
351
  if (eventName === "usage") {
260
352
  if (replay)
261
353
  return;
@@ -451,6 +543,47 @@ export function createChatSseEventApplier(deps) {
451
543
  });
452
544
  }
453
545
  else if (eventName === "error") {
546
+ const candidate = payload.providerError && typeof payload.providerError === "object"
547
+ ? payload.providerError
548
+ : undefined;
549
+ if (candidate &&
550
+ typeof candidate.message === "string" &&
551
+ typeof candidate.raw === "string" &&
552
+ (candidate.category === "authentication" ||
553
+ candidate.category === "context-overflow" ||
554
+ candidate.category === "rate-limit" ||
555
+ candidate.category === "provider")) {
556
+ // Session hydration already restores the durable card; retained replay
557
+ // must not add a second synthetic row on top of it.
558
+ if (replay)
559
+ return;
560
+ const providerError = candidate;
561
+ const errorContent = parseAssistantContent(payload.content);
562
+ const id = turnId && eventSeq !== undefined
563
+ ? `provider-error-${turnId}-${eventSeq}`
564
+ : requestId("provider-error");
565
+ setMessages((messages) => {
566
+ if (messages.some((message) => message.id === id))
567
+ return messages;
568
+ const activeAlreadyOwnsContent = messages.some((message) => message.id === resolvedAssistantId &&
569
+ Boolean(message.content?.length));
570
+ return [
571
+ ...messages,
572
+ {
573
+ id,
574
+ role: "assistant",
575
+ kind: "provider-error",
576
+ text: providerError.message,
577
+ createdAt: eventAt,
578
+ providerError,
579
+ ...(!activeAlreadyOwnsContent && errorContent
580
+ ? { content: errorContent }
581
+ : {}),
582
+ },
583
+ ];
584
+ });
585
+ return;
586
+ }
454
587
  setError(typeof payload.message === "string" ? payload.message : "chat error");
455
588
  }
456
589
  };
@@ -0,0 +1,79 @@
1
+ function finiteToken(value) {
2
+ return typeof value === "number" && Number.isFinite(value)
3
+ ? Math.max(0, Math.floor(value))
4
+ : undefined;
5
+ }
6
+ /** Project legacy final snapshots and lifecycle events to one stable row. */
7
+ export function compactionMessageFromEvent(item) {
8
+ const data = item.data && typeof item.data === "object"
9
+ ? item.data
10
+ : {};
11
+ const eventId = typeof item.eventId === "string" ? item.eventId : undefined;
12
+ const compactionId = typeof data.compactionId === "string" && data.compactionId
13
+ ? data.compactionId
14
+ : eventId;
15
+ if (!compactionId)
16
+ return undefined;
17
+ const status = data.status === "running" ||
18
+ data.status === "completed" ||
19
+ data.status === "failed" ||
20
+ data.status === "aborted"
21
+ ? data.status
22
+ : "completed";
23
+ const reason = data.reason === "manual" ||
24
+ data.reason === "threshold" ||
25
+ data.reason === "overflow"
26
+ ? data.reason
27
+ : "manual";
28
+ const summary = typeof data.summary === "string" ? data.summary : "";
29
+ const errorMessage = typeof data.errorMessage === "string" ? data.errorMessage : undefined;
30
+ return {
31
+ id: `compaction-${compactionId}`,
32
+ role: "assistant",
33
+ kind: "compaction",
34
+ text: summary ||
35
+ errorMessage ||
36
+ (status === "completed"
37
+ ? "历史上下文已精简,关键决策与当前任务已保留。"
38
+ : ""),
39
+ createdAt: Date.parse(item.at ?? "") || Date.now(),
40
+ compaction: {
41
+ compactionId,
42
+ status,
43
+ reason,
44
+ willRetry: data.willRetry === true,
45
+ ...(finiteToken(data.tokensBefore) === undefined
46
+ ? {}
47
+ : { tokensBefore: finiteToken(data.tokensBefore) }),
48
+ ...(finiteToken(data.estimatedTokensAfter) === undefined
49
+ ? {}
50
+ : { estimatedTokensAfter: finiteToken(data.estimatedTokensAfter) }),
51
+ ...(typeof data.firstKeptEntryId === "string" && data.firstKeptEntryId
52
+ ? { firstKeptEntryId: data.firstKeptEntryId }
53
+ : {}),
54
+ ...(errorMessage ? { errorMessage } : {}),
55
+ ...(eventId ? { eventId } : {}),
56
+ },
57
+ };
58
+ }
59
+ export function upsertCompactionMessage(messages, item) {
60
+ const next = compactionMessageFromEvent(item);
61
+ if (!next)
62
+ return messages;
63
+ const existing = messages.find((message) => message.id === next.id);
64
+ const result = existing
65
+ ? messages.map((message) => message.id === next.id
66
+ ? {
67
+ ...message,
68
+ ...next,
69
+ createdAt: Math.min(message.createdAt, next.createdAt),
70
+ text: next.text || message.text,
71
+ compaction: {
72
+ ...message.compaction,
73
+ ...next.compaction,
74
+ },
75
+ }
76
+ : message)
77
+ : [...messages, next];
78
+ return result.sort((a, b) => a.createdAt - b.createdAt);
79
+ }