@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.11

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 (245) hide show
  1. package/CHANGELOG.md +379 -416
  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/sessions/index.js +6 -0
  30. package/dist/executors/dag-pi/sessions/plan-batches.js +280 -0
  31. package/dist/executors/dag-pi/sessions/plan-prompts.js +367 -0
  32. package/dist/executors/dag-pi/sessions/scout-parallel.js +197 -0
  33. package/dist/executors/dag-pi/sessions/segmented-plan.js +1184 -0
  34. package/dist/executors/dag-pi/sessions/writer-evidence.js +60 -0
  35. package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
  36. package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
  37. package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
  38. package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
  39. package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
  40. package/dist/executors/dag-pi-executor.js +733 -5782
  41. package/dist/executors/pi-executor.js +6 -1
  42. package/dist/executors/pi-sdk-executor.js +112 -19
  43. package/dist/executors/shell-executor.js +88 -12
  44. package/dist/executors/shell-presets.js +1 -1
  45. package/dist/executors/shell-write-guard.js +26 -3
  46. package/dist/infrastructure/console/operation-store.js +48 -14
  47. package/dist/infrastructure/evaluation/corpus-store.js +37 -12
  48. package/dist/shared/file-identity.js +23 -0
  49. package/dist/shared/git-workspace-status.js +482 -0
  50. package/dist/shared/operator/capabilities.js +10 -5
  51. package/dist/shared/path-safety.js +2 -2
  52. package/dist/shared/wizard-local-telemetry.js +51 -0
  53. package/dist/task/config-types.js +22 -8
  54. package/dist/task/contract/project.js +13 -0
  55. package/dist/task/contract/schema.js +2 -7
  56. package/dist/task/frontend-preflight.js +1 -1
  57. package/dist/task/source-prepare/parse-intent.js +124 -5
  58. package/dist/task/source-prepare/prepare.js +6 -1
  59. package/dist/task/source-prepare/semantic-intake.js +18 -3
  60. package/dist/task/source-prepare/source-execution.js +65 -0
  61. package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
  62. package/dist/task/source-prepare/source-provider-budget.js +290 -0
  63. package/dist/worker/console/chat/assistant-content.js +5 -34
  64. package/dist/worker/console/chat/chat-event-store.js +62 -10
  65. package/dist/worker/console/chat/context-insights.js +723 -0
  66. package/dist/worker/console/chat/conversation-nodes.js +383 -0
  67. package/dist/worker/console/chat/db-connections.js +854 -0
  68. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
  69. package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
  70. package/dist/worker/console/chat/pi-runtime.js +445 -36
  71. package/dist/worker/console/chat/provider-error.js +0 -12
  72. package/dist/worker/console/chat/repo-browser.js +52 -4
  73. package/dist/worker/console/chat/routes.js +577 -72
  74. package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
  75. package/dist/worker/console/chat/session-store.js +147 -19
  76. package/dist/worker/console/chat/shortcuts.js +7 -0
  77. package/dist/worker/console/chat/timeline-message-order.js +22 -0
  78. package/dist/worker/console/chat/tools.js +34 -0
  79. package/dist/worker/console/chat/turn-process.js +237 -66
  80. package/dist/worker/console/chat/user-turn-outline.js +19 -0
  81. package/dist/worker/console/console-update-runtime.js +2 -2
  82. package/dist/worker/console/dag-confirmation.js +3 -0
  83. package/dist/worker/console/dag-execution-receipt.js +3 -0
  84. package/dist/worker/console/operator-actions.js +32 -3
  85. package/dist/worker/console/operator-user-error.js +28 -1
  86. package/dist/worker/console/pi-readiness.js +4 -4
  87. package/dist/worker/console/prd-intake-bridge.js +5 -12
  88. package/dist/worker/console/recovery-cta.js +14 -14
  89. package/dist/worker/console/server.js +5 -1
  90. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
  91. package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
  92. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
  93. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
  94. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
  95. package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
  96. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
  101. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
  102. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
  106. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
  107. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
  108. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
  109. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
  110. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
  111. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
  112. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
  113. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
  114. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
  115. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
  116. package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
  117. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
  118. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
  119. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
  120. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
  121. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
  122. package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
  123. package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
  124. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
  125. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
  126. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
  127. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
  128. package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
  129. package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
  130. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
  131. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
  132. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
  133. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
  134. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
  135. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
  136. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
  137. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
  138. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
  139. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
  140. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
  141. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
  142. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
  143. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
  144. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
  145. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
  146. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
  147. package/dist/worker/console/static/index.html +2 -2
  148. package/dist/worker/console/static-src/app/console-types.js +9 -1
  149. package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
  150. package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
  151. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
  152. package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
  153. package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
  154. package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
  155. package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
  156. package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
  157. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
  158. package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
  159. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
  160. package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
  161. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
  162. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
  163. package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
  164. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
  165. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
  166. package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
  167. package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
  168. package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
  169. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
  170. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
  171. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
  172. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  173. package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
  174. package/dist/worker/console/workspace-context.js +2 -0
  175. package/dist/worker/console/workspace-initialization.js +3 -3
  176. package/dist/worker/observability/read-model.js +23 -2
  177. package/dist/worker/observe/node-context-insights.js +479 -0
  178. package/dist/worker/observe/static/console-theme.css +174 -0
  179. package/dist/worker/observe/static/console-theme.js +36 -0
  180. package/dist/worker/observe/static/operator-chrome.css +85 -0
  181. package/dist/worker/observe/static/operator-chrome.js +51 -0
  182. package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
  183. package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
  184. package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
  185. package/dist/workflows/dag/budget-enforcement.js +31 -2
  186. package/dist/workflows/dag/checkpoint.js +686 -0
  187. package/dist/workflows/dag/frontend-closeout.js +3 -1
  188. package/dist/workflows/dag/frontend-contract-facts.js +11 -8
  189. package/dist/workflows/dag/frontend-design-policy.js +6 -6
  190. package/dist/workflows/dag/frontend-durable-tools.js +90 -70
  191. package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
  192. package/dist/workflows/dag/frontend-plan-canary.js +66 -16
  193. package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
  194. package/dist/workflows/dag/frontend-plan-progress.js +92 -0
  195. package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
  196. package/dist/workflows/dag/frontend-recovery-run.js +51 -46
  197. package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
  198. package/dist/workflows/dag/frontend-review-context.js +29 -1
  199. package/dist/workflows/dag/frontend-review-scopes.js +92 -2
  200. package/dist/workflows/dag/frontend-risk.js +8 -3
  201. package/dist/workflows/dag/frontend-root-observation.js +261 -0
  202. package/dist/workflows/dag/frontend-session-budget.js +52 -39
  203. package/dist/workflows/dag/frontend-session-context.js +10 -0
  204. package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
  205. package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
  206. package/dist/workflows/dag/frontend-verification-trace.js +6 -0
  207. package/dist/workflows/dag/frontend-writer-admission.js +3 -1
  208. package/dist/workflows/dag/hybrid/sources.js +1812 -0
  209. package/dist/workflows/dag/hybrid/templates/backend-test.js +1807 -0
  210. package/dist/workflows/dag/hybrid/templates/frontend-test.js +702 -0
  211. package/dist/workflows/dag/hybrid/templates/frontend.js +1529 -0
  212. package/dist/workflows/dag/hybrid/templates/index.js +8 -0
  213. package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +449 -0
  214. package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +495 -0
  215. package/dist/workflows/dag/hybrid/templates/shared.js +101 -0
  216. package/dist/workflows/dag/hybrid/templates/standard.js +265 -0
  217. package/dist/workflows/dag/hybrid/types.js +32 -0
  218. package/dist/workflows/dag/init-hybrid.js +57 -7106
  219. package/dist/workflows/dag/node-execution.js +11 -11
  220. package/dist/workflows/dag/output-protocol.js +1 -0
  221. package/dist/workflows/dag/rerun-feedback.js +35 -4
  222. package/dist/workflows/dag/rerun-task.js +62 -66
  223. package/dist/workflows/dag/runner.js +38 -1159
  224. package/dist/workflows/dag/terminal-status.js +502 -0
  225. package/dist/workflows/dag/types.js +15 -8
  226. package/dist/workflows/loop/benchmark.js +11 -8
  227. package/docs/README.md +1 -1
  228. package/docs/architecture/dag-execution.md +1 -1
  229. package/docs/architecture/runtime-boundaries.md +1 -1
  230. package/docs/governance/harness-methodology-verification.md +11 -0
  231. package/docs/templates/backend-test-dag.json +5 -4
  232. package/docs/templates/frontend-implementation-contract.schema.json +31 -11
  233. package/package.json +6 -4
  234. package/skills/frontend-design-review/SKILL.md +8 -11
  235. package/skills/frontend-design-review/references/review-checklist.md +3 -4
  236. package/skills/frontend-review/SKILL.md +5 -1
  237. package/skills/loop-agent/references/command-reference.md +8 -0
  238. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  239. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
  240. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
  241. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
  242. package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
  243. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
  244. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
  245. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
@@ -0,0 +1,174 @@
1
+ /* Opt-in palettes. Repeat tokens at the Inspect boundary: it owns local defaults
2
+ * and is loaded after the Console stylesheet. Specificity keeps load order safe. */
3
+ :root[data-console-theme="midnight"],
4
+ :root[data-console-theme="midnight"] body[data-shell="observe-console"],
5
+ :root[data-console-theme="midnight"] .inspect-workspace-root {
6
+ color-scheme: dark;
7
+ --canvas: #1c222a;
8
+ --canvas-soft: #202730;
9
+ --surface: #252d37;
10
+ --surface-muted: #2d3641;
11
+ --ink: #dce2e9;
12
+ --surface-ink: #dce2e9;
13
+ --body: #bcc5d0;
14
+ --muted: #a1acba;
15
+ --muted-soft: #909eaf;
16
+ --muted-ink: #a1acba;
17
+ --hairline: #353f4b;
18
+ --hairline-strong: #526171;
19
+ --orange: #a0bcd3;
20
+ --orange-active: #b8cfe0;
21
+ --blue: #a0bcd3;
22
+ --brand: #b2aacb;
23
+ --theme-action: #4d6b86;
24
+ --theme-action-hover: #557590;
25
+ --theme-on-action: #ffffff;
26
+ --green: #99c5b0;
27
+ --amber: #d3b783;
28
+ --warn: #d3b783;
29
+ --red: #dea4ac;
30
+ --peach: #303e4d;
31
+ --mint: #2d403b;
32
+ --lavender: #393746;
33
+ --gold: #c3ac80;
34
+ --shadow-sm: 0 1px 2px rgb(0 0 0 / 12%);
35
+ --shadow-panel: 0 1px 2px rgb(0 0 0 / 12%), 0 6px 18px rgb(0 0 0 / 10%);
36
+ --shadow-soft: 0 8px 24px rgb(0 0 0 / 18%);
37
+ --shadow-lg: 0 16px 40px rgb(0 0 0 / 24%);
38
+ }
39
+
40
+ :root[data-console-theme="daylight"],
41
+ :root[data-console-theme="daylight"] body[data-shell="observe-console"],
42
+ :root[data-console-theme="daylight"] .inspect-workspace-root {
43
+ color-scheme: light;
44
+ --canvas: #eef0f2;
45
+ --canvas-soft: #f3f4f5;
46
+ --surface: #f9fafb;
47
+ --surface-muted: #e5e9ed;
48
+ --ink: #303b47;
49
+ --surface-ink: #303b47;
50
+ --body: #4b5867;
51
+ --muted: #5e6a78;
52
+ --muted-soft: #697583;
53
+ --muted-ink: #5e6a78;
54
+ --hairline: #dce1e6;
55
+ --hairline-strong: #b5bfc9;
56
+ --orange: #466581;
57
+ --orange-active: #365570;
58
+ --blue: #466581;
59
+ --brand: #706387;
60
+ --theme-action: #536f88;
61
+ --theme-action-hover: #466079;
62
+ --theme-on-action: #ffffff;
63
+ --green: #3e735d;
64
+ --amber: #85632d;
65
+ --warn: #85632d;
66
+ --red: #a04f5c;
67
+ --peach: #dfe7ee;
68
+ --mint: #e0eae4;
69
+ --lavender: #e7e3ed;
70
+ --gold: #927644;
71
+ --shadow-sm: 0 1px 2px rgb(35 45 58 / 4%);
72
+ --shadow-panel: 0 1px 2px rgb(35 45 58 / 3%), 0 6px 18px rgb(35 45 58 / 3%);
73
+ --shadow-soft: 0 8px 24px rgb(35 45 58 / 6%);
74
+ --shadow-lg: 0 16px 40px rgb(35 45 58 / 9%);
75
+ }
76
+
77
+ /* Only the two added themes override component literals; original warm paper
78
+ * remains byte-for-byte defined by its existing stylesheets. */
79
+ :root:is([data-console-theme="midnight"], [data-console-theme="daylight"]) {
80
+ --orange-soft: color-mix(in srgb, var(--orange) 10%, var(--surface));
81
+ background: var(--canvas);
82
+
83
+ body[data-shell="observe-console"], .inspect-workspace-root {
84
+ --orange-soft: color-mix(in srgb, var(--orange) 10%, var(--surface));
85
+ }
86
+
87
+ ::selection { background: color-mix(in srgb, var(--blue) 32%, var(--surface)); }
88
+
89
+ .oc-composer-shell, .console-update-channel, .console-update-button,
90
+ .panel-output { background: var(--surface); color: var(--ink); }
91
+ .console-update-channel, .console-update-button { border-color: var(--hairline-strong); }
92
+ .console-update-channel-label { color: var(--body); }
93
+ .console-update-error, .console-update-result-error { color: var(--red); }
94
+ .console-update-badge { background: var(--surface-muted); color: var(--amber); }
95
+
96
+ .oc-bubble-user { background: color-mix(in srgb, var(--blue) 9%, var(--surface)); }
97
+ .oc-composer-shell { box-shadow: var(--shadow-panel); }
98
+ .oc-bubble-assistant .oc-codeblock { background: var(--canvas-soft); }
99
+ .oc-session-scroll, .oc-tree-scroll { background: var(--canvas-soft); }
100
+ .oc-sidebar-new-session:is(:hover, :focus-visible),
101
+ .oc-hero-chip:is(:hover, :focus-visible, [aria-expanded="true"]) { background: var(--surface-muted); }
102
+
103
+ .night-health-pill, .models-status-pill[data-state="ready"],
104
+ .models-probe-item.is-ok .models-probe-badge {
105
+ background: color-mix(in srgb, var(--green) 14%, var(--surface));
106
+ }
107
+ .night-health-pill.is-bad, .models-status-pill[data-state="setup-required"],
108
+ .models-probe-item.is-bad .models-probe-badge, .dag-error-node {
109
+ background: color-mix(in srgb, var(--red) 12%, var(--surface));
110
+ }
111
+ .models-status-pill[data-state="degraded"], .models-degraded-note,
112
+ .dag-active-node { background: var(--orange-soft); }
113
+ .liveness-timeout-risk { background: color-mix(in srgb, var(--amber) 12%, var(--surface)); }
114
+ .night-strip-icon { color: var(--brand); }
115
+
116
+ /* Quiet selected surfaces keep navigation secondary to the content. */
117
+ .och-mode-thumb { background: var(--surface); box-shadow: var(--shadow-sm); }
118
+ .och-mode-btn.och-on { color: var(--ink); }
119
+ .dag-inspector-tab[aria-selected="true"],
120
+ .dag-graph-mode-tab[aria-selected="true"],
121
+ #dag-inspector-panel-prompt-restart button.prompt-restart-mode-option.is-selected {
122
+ background: var(--surface-muted);
123
+ color: var(--ink);
124
+ }
125
+
126
+ /* Accent text needs a different luminance from a white-labelled action. */
127
+ .btn-primary, button.btn-primary, .oc-send, .oc-question-button.is-primary,
128
+ .oc-scm-commitbtn, .skip-link, .dags-page-num.is-current,
129
+ .dag-action-primary, .context-export-submit,
130
+ #dag-inspector-panel-prompt-restart button.prompt-restart-submit:not(.is-secondary) {
131
+ background: var(--theme-action);
132
+ border-color: var(--theme-action);
133
+ color: var(--theme-on-action);
134
+ box-shadow: var(--shadow-sm);
135
+ }
136
+ .btn-primary:hover:not(:disabled), button.btn-primary:hover:not(:disabled),
137
+ .oc-send:hover:not(:disabled), .oc-question-button.is-primary:hover:not(:disabled),
138
+ .oc-scm-commitbtn:hover:not(:disabled), .dag-action-primary:hover:not(:disabled),
139
+ .context-export-submit:hover:not(:disabled),
140
+ #dag-inspector-panel-prompt-restart button.prompt-restart-submit:not(.is-secondary):hover {
141
+ background: var(--theme-action-hover);
142
+ border-color: var(--theme-action-hover);
143
+ }
144
+ .och-context-return, .process-timeline-autoscroll::after {
145
+ background: var(--surface-muted);
146
+ color: var(--ink);
147
+ }
148
+ .markdown-output pre, .output-block { background: var(--canvas-soft); color: var(--ink); }
149
+ .context-export-ticket { background: var(--surface-muted); color: var(--ink); }
150
+ .context-export-ticket-safety { color: var(--green); }
151
+ .context-export-ticket-safety.is-risk { color: var(--red); }
152
+ }
153
+
154
+ /* Syntax highlighting is independently hardcoded to a light palette. */
155
+ :root[data-console-theme="midnight"] {
156
+ /* The SVG brand uses dark strokes; provide a subtle light backing, not a filter. */
157
+ .och-brand-mark, .oc-hero img { background: #aebac7; border-radius: 6px; }
158
+ .hljs { color: var(--ink); background: var(--canvas-soft); }
159
+ .hljs-doctag, .hljs-keyword, .hljs-meta .hljs-keyword,
160
+ .hljs-template-tag, .hljs-template-variable, .hljs-type,
161
+ .hljs-variable.language_ { color: #c7afcf; }
162
+ .hljs-title, .hljs-title.class_, .hljs-title.class_.inherited__,
163
+ .hljs-title.function_ { color: #b9b3d1; }
164
+ .hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number,
165
+ .hljs-operator, .hljs-selector-attr, .hljs-selector-class,
166
+ .hljs-selector-id, .hljs-variable, .hljs-section { color: var(--blue); }
167
+ .hljs-meta .hljs-string, .hljs-regexp, .hljs-string,
168
+ .hljs-name, .hljs-quote, .hljs-selector-pseudo, .hljs-selector-tag { color: var(--green); }
169
+ .hljs-built_in, .hljs-symbol, .hljs-bullet { color: var(--amber); }
170
+ .hljs-code, .hljs-comment, .hljs-formula { color: var(--muted); }
171
+ .hljs-subst, .hljs-emphasis, .hljs-strong { color: var(--ink); }
172
+ .hljs-addition { color: var(--green); background: var(--mint); }
173
+ .hljs-deletion { color: var(--red); background: color-mix(in srgb, var(--red) 12%, var(--surface)); }
174
+ }
@@ -0,0 +1,36 @@
1
+ /** Shared Console theme identities; the default leaves the original palette intact. */
2
+ export const CONSOLE_THEMES = Object.freeze([
3
+ { id: "default", label: "默认" },
4
+ { id: "midnight", label: "深色" },
5
+ { id: "daylight", label: "浅色" },
6
+ ]);
7
+
8
+ const STORAGE_KEY = "loop-console.theme.v1";
9
+
10
+ function normalizeTheme(value) {
11
+ return CONSOLE_THEMES.some((theme) => theme.id === value) ? value : "default";
12
+ }
13
+
14
+ export function applyStoredConsoleTheme() {
15
+ let value;
16
+ try {
17
+ value = window.localStorage.getItem(STORAGE_KEY);
18
+ } catch {
19
+ // Storage can be disabled, including the localStorage getter itself.
20
+ }
21
+ const theme = normalizeTheme(value);
22
+ document.documentElement.dataset.consoleTheme = theme;
23
+ return theme;
24
+ }
25
+
26
+ export function setConsoleTheme(value) {
27
+ const theme = normalizeTheme(value);
28
+ // Apply before persistence: a storage failure must not disable the picker.
29
+ document.documentElement.dataset.consoleTheme = theme;
30
+ try {
31
+ window.localStorage.setItem(STORAGE_KEY, theme);
32
+ } catch {
33
+ // The document attribute remains the in-memory preference.
34
+ }
35
+ return theme;
36
+ }
@@ -8,6 +8,84 @@
8
8
  * 的暖白 token 保持一致;宿主样式表已定义同名变量时以宿主为准。
9
9
  */
10
10
 
11
+ @import "./console-theme.css";
12
+
13
+ .och-theme {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: 6px;
17
+ flex: none;
18
+ color: var(--och-muted);
19
+ font-size: 12px;
20
+ }
21
+
22
+ .och-theme-trigger {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ width: 26px;
27
+ min-height: 26px;
28
+ padding: 0;
29
+ border: 1px solid transparent;
30
+ border-radius: 6px;
31
+ background: transparent;
32
+ color: var(--och-body);
33
+ font: inherit;
34
+ cursor: pointer;
35
+ }
36
+
37
+ .och-theme-trigger:hover,
38
+ .och-theme-trigger[aria-expanded="true"] {
39
+ background: var(--och-surface-muted);
40
+ color: var(--och-ink);
41
+ }
42
+
43
+ .och-theme-trigger:focus-visible,
44
+ .och-theme-option:focus-visible {
45
+ outline: 2px solid var(--och-orange);
46
+ outline-offset: 2px;
47
+ }
48
+
49
+ /* Native popover renders in the top layer, outside the narrow header's clip. */
50
+ .och-theme-menu {
51
+ position: fixed;
52
+ inset: auto;
53
+ box-sizing: border-box;
54
+ width: 112px;
55
+ margin: 0;
56
+ padding: 4px;
57
+ border: 1px solid var(--och-hairline);
58
+ border-radius: 8px;
59
+ background: var(--och-surface);
60
+ color: var(--och-ink);
61
+ box-shadow: var(--shadow-soft);
62
+ font: inherit;
63
+ }
64
+
65
+ .och-theme-option {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: space-between;
69
+ width: 100%;
70
+ min-height: 28px;
71
+ padding: 4px 8px;
72
+ border: 0;
73
+ border-radius: 4px;
74
+ background: transparent;
75
+ color: inherit;
76
+ font: inherit;
77
+ cursor: pointer;
78
+ }
79
+
80
+ .och-theme-option:hover,
81
+ .och-theme-option[aria-pressed="true"] {
82
+ background: var(--och-surface-muted);
83
+ }
84
+
85
+ .och-theme-option[aria-pressed="true"]::after {
86
+ content: "✓";
87
+ }
88
+
11
89
  .och {
12
90
  --och-canvas: var(--canvas, #f3f4f6);
13
91
  --och-surface: var(--surface, #fbfcfd);
@@ -622,7 +700,14 @@
622
700
  .och-inner {
623
701
  padding-inline: 12px;
624
702
  }
703
+ .och-theme {
704
+ position: absolute;
705
+ top: 46px;
706
+ right: 0;
707
+ }
625
708
  .och-global {
709
+ height: 84px;
710
+ padding-bottom: 40px;
626
711
  gap: 6px;
627
712
  overflow-x: auto;
628
713
  scrollbar-width: none;
@@ -12,6 +12,8 @@
12
12
  */
13
13
 
14
14
  import { withWorkspaceQuery } from "./router.js";
15
+ import { CONSOLE_THEMES, applyStoredConsoleTheme, setConsoleTheme } from "./console-theme.js";
16
+ export { CONSOLE_THEMES } from "./console-theme.js";
15
17
 
16
18
  export const OPERATE_TABS = Object.freeze([
17
19
  { id: "tasks", label: "任务", href: "/#/operate/tasks" },
@@ -296,6 +298,13 @@ export function buildOperatorChromeModel(config) {
296
298
 
297
299
  const SVG_NS = "http:" + "//www.w3.org/2000/svg";
298
300
  const ICONS = Object.freeze({
301
+ theme: {
302
+ size: 16,
303
+ parts: [
304
+ ["circle", { cx: 12, cy: 12, r: 9 }],
305
+ ["path", { d: "M12 3a9 9 0 0 1 0 18Z", fill: "currentColor", stroke: "none" }],
306
+ ],
307
+ },
299
308
  radar: {
300
309
  size: 17,
301
310
  parts: [
@@ -426,6 +435,7 @@ function readWorkspaceCollapsedPref() {
426
435
  export function mountOperatorChrome(root, config = {}) {
427
436
  if (!root) throw new Error("mountOperatorChrome: root element is required");
428
437
  root.classList.add("och-host");
438
+ if (!document.documentElement.dataset.consoleTheme) applyStoredConsoleTheme();
429
439
 
430
440
  let currentConfig = { ...config };
431
441
  let popoverOpen = false;
@@ -701,6 +711,47 @@ export function mountOperatorChrome(root, config = {}) {
701
711
 
702
712
  global.appendChild(el("span", "och-spacer"));
703
713
 
714
+ const themePicker = el("div", "och-theme");
715
+ const trigger = el("button", "och-theme-trigger");
716
+ trigger.type = "button";
717
+ trigger.title = "主题";
718
+ trigger.setAttribute("aria-label", "主题");
719
+ trigger.appendChild(makeIcon("theme"));
720
+ trigger.setAttribute("aria-expanded", "false");
721
+ const menu = el("div", "och-theme-menu");
722
+ menu.id = `${popoverId}-theme`;
723
+ menu.popover = "auto";
724
+ menu.setAttribute("role", "group");
725
+ menu.setAttribute("aria-label", "选择主题");
726
+ trigger.setAttribute("aria-controls", menu.id);
727
+ trigger.popoverTargetElement = menu;
728
+ menu.addEventListener("beforetoggle", (event) => {
729
+ if (event.newState !== "open") return;
730
+ const rect = trigger.getBoundingClientRect();
731
+ menu.style.top = `${rect.bottom + 6}px`;
732
+ menu.style.left = `${Math.max(8, Math.min(rect.right - 112, window.innerWidth - 120))}px`;
733
+ });
734
+ menu.addEventListener("toggle", (event) => {
735
+ trigger.setAttribute("aria-expanded", String(event.newState === "open"));
736
+ });
737
+ for (const theme of CONSOLE_THEMES) {
738
+ const option = el("button", "och-theme-option", theme.label);
739
+ option.type = "button";
740
+ option.dataset.theme = theme.id;
741
+ option.setAttribute("aria-pressed", String(theme.id === (document.documentElement.dataset.consoleTheme ?? "default")));
742
+ option.addEventListener("click", () => {
743
+ setConsoleTheme(theme.id);
744
+ for (const item of menu.children) {
745
+ item.setAttribute("aria-pressed", String(item.dataset.theme === theme.id));
746
+ }
747
+ menu.hidePopover();
748
+ trigger.focus();
749
+ });
750
+ menu.appendChild(option);
751
+ }
752
+ themePicker.append(trigger, menu);
753
+ global.appendChild(themePicker);
754
+
704
755
  if (model.statusVisible && model.freshness) {
705
756
  const fresh = el("span", "och-fresh");
706
757
  fresh.setAttribute("aria-label", "数据新鲜度");
@@ -69,10 +69,11 @@ export async function prepareScheduledGoalDraft(input, client) {
69
69
  if (!assessment.ok)
70
70
  throw new Error(`scheduled goal ineligible: ${assessment.reason}`);
71
71
  const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
72
+ const repositoryPath = (file) => path.relative(root, file).split(path.sep).join("/");
72
73
  return scheduledGoalDraftSchema.parse({ ...parsed, controllerRef: identity.packageFingerprint.value,
73
74
  prepared: { gateToken: `${snapshot.gate.id}:${snapshot.gate.digest}`,
74
- dagPath: path.relative(root, paths.dagDraftPath), dagHash: digest(dagBytes),
75
- contractPath: path.relative(root, paths.taskConfigPath), contractHash: digest(configBytes) }, });
75
+ dagPath: repositoryPath(paths.dagDraftPath), dagHash: digest(dagBytes),
76
+ contractPath: repositoryPath(paths.taskConfigPath), contractHash: digest(configBytes) }, });
76
77
  }
77
78
  /** Materialize only contract/runtime inputs; execution remains owned by the operation host. */
78
79
  export async function prepareDerivedScheduledGoalTask(goalId, ownerToken, goals, client) {
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { execFile } from "node:child_process";
2
+ import { execFile, spawn } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import { lstat, readFile, readlink, realpath } from "node:fs/promises";
5
5
  import path from "node:path";
@@ -7,6 +7,59 @@ import { pathMatchesPattern } from "../../shared/git-progress.js";
7
7
  import { scheduledGoalDraftSchema } from "./scheduled-goal-types.js";
8
8
  const execFileAsync = promisify(execFile);
9
9
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
10
+ /** Working-copy paths that are plain files, so Git can read them back. */
11
+ async function plainFileNames(root, names) {
12
+ const plain = [];
13
+ for (const name of names) {
14
+ const info = await lstat(path.resolve(root, name)).catch(() => undefined);
15
+ if (info?.isFile())
16
+ plain.push(name);
17
+ }
18
+ return plain;
19
+ }
20
+ /**
21
+ * Repository-form blob IDs for working-copy files, computed by Git itself so
22
+ * that `core.autocrlf` and `eol` attributes normalize exactly as a commit
23
+ * would. Hashing never consults the index, so staged-only and
24
+ * `assume-unchanged` paths keep their raw-byte meaning. A failure degrades to
25
+ * the caller's raw-byte blob, which can only over-report change, never hide it;
26
+ * `--stdin-paths` cannot carry names containing a newline.
27
+ */
28
+ async function gitNormalizedBlobIds(root, names) {
29
+ const ids = new Map();
30
+ const candidates = names.filter((name) => !name.includes("\n"));
31
+ if (candidates.length === 0)
32
+ return ids;
33
+ let stdout;
34
+ try {
35
+ stdout = await new Promise((resolve, reject) => {
36
+ const child = spawn("git", ["-C", root, "hash-object", "--stdin-paths"], { stdio: ["pipe", "pipe", "pipe"] });
37
+ let output = "";
38
+ let failure = "";
39
+ child.stdout.setEncoding("utf8");
40
+ child.stdout.on("data", (chunk) => { output += chunk; });
41
+ child.stderr.setEncoding("utf8");
42
+ child.stderr.on("data", (chunk) => { failure += chunk; });
43
+ child.on("error", reject);
44
+ child.on("close", (code) => {
45
+ if (code === 0)
46
+ resolve(output);
47
+ else
48
+ reject(new Error(`git hash-object failed: ${failure.trim()}`));
49
+ });
50
+ child.stdin.end(`${candidates.join("\n")}\n`);
51
+ });
52
+ }
53
+ catch {
54
+ return ids;
55
+ }
56
+ const hashes = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
57
+ if (hashes.length !== candidates.length)
58
+ return ids;
59
+ for (let index = 0; index < candidates.length; index += 1)
60
+ ids.set(candidates[index], hashes[index]);
61
+ return ids;
62
+ }
10
63
  /** Run only frozen AC commands. A timeout is uncertain execution, never a passing or repairable failure. */
11
64
  export async function evaluateScheduledGoalCriteria(frozen, options = {}) {
12
65
  frozen = scheduledGoalDraftSchema.pick({ worktreeRef: true, frozenBase: true, allowedPaths: true, forbiddenPaths: true, acceptance: true, time: true }).strip().parse(frozen);
@@ -115,6 +168,11 @@ export async function captureScheduledGoalTree(worktree, base, allowedPaths, for
115
168
  const blobId = (bytes) => createHash(objectFormat).update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
116
169
  const names = [...new Set([...entries(tracked), ...entries(untracked), ...baseEntries.keys()])].sort();
117
170
  const deliveryNames = new Set(names);
171
+ // Compare content identity through Git's own normalization: a working copy
172
+ // materialized with `core.autocrlf` (or an `eol` attribute) is stored as LF
173
+ // while the checkout is CRLF, and raw bytes would report that as drift on
174
+ // every text file.
175
+ const normalizedIds = await gitNormalizedBlobIds(root, await plainFileNames(root, names));
118
176
  const files = [];
119
177
  const changedPaths = [];
120
178
  for (const name of names) {
@@ -143,8 +201,13 @@ export async function captureScheduledGoalTree(worktree, base, allowedPaths, for
143
201
  }
144
202
  else if (stat.isFile()) {
145
203
  const bytes = await readFile(target);
146
- files.push({ name, kind: "file", hash: sha256(bytes), executable: Boolean(stat.mode & 0o111) });
147
- actualId = blobId(bytes);
204
+ actualId = normalizedIds.get(name) ?? blobId(bytes);
205
+ files.push({
206
+ name,
207
+ kind: "file",
208
+ hash: sha256(`${objectFormat}:${actualId}`),
209
+ executable: Boolean(stat.mode & 0o111),
210
+ });
148
211
  actualMode = stat.mode & 0o111 ? "100755" : "100644";
149
212
  }
150
213
  else
@@ -16,6 +16,16 @@ async function syncDirectory(directoryPath) {
16
16
  try {
17
17
  await directory.sync();
18
18
  }
19
+ catch (error) {
20
+ // The file body is already durable from the file-level fsync above.
21
+ // Windows refuses to flush a directory handle (EPERM) and some
22
+ // filesystems reject the call outright, so a directory-only failure must
23
+ // not abort a transaction whose data is already on disk.
24
+ const code = error.code;
25
+ if (code !== "EPERM" && code !== "EACCES" && code !== "EINVAL"
26
+ && code !== "ENOTSUP" && code !== "EOPNOTSUPP" && code !== "ENOSYS")
27
+ throw error;
28
+ }
19
29
  finally {
20
30
  await directory.close();
21
31
  }
@@ -1,6 +1,6 @@
1
1
  import { writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { checkBudgetPreNode, createBudgetLedger, formatBudgetReportMarkdown, isHardBudgetBreached, recordNodeBudgetSample, reserveProviderBudget, skipPendingNodesForBudgetBreach, } from "../../application/evaluation/budget.js";
3
+ import { campaignBudgetSchema, checkBudgetPreNode, createBudgetLedger, formatBudgetReportMarkdown, isHardBudgetBreached, recordNodeBudgetSample, reserveProviderBudget, skipPendingNodesForBudgetBreach, } from "../../application/evaluation/budget.js";
4
4
  import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
5
5
  /** A continuation already contains its ancestors' consumption. Never sum both. */
6
6
  export function validateCumulativeBudgetSuccessor(parent, child) {
@@ -9,9 +9,33 @@ export function validateCumulativeBudgetSuccessor(parent, child) {
9
9
  const next = child.budgetLedger;
10
10
  if (!previous || !next || !lineage || (child.continuation?.parentRunId ?? child.frontendRecoveryState?.parentRunId) !== parent.runId || lineage.parentLedgerSha256 !== sha256OfCanonicalJson(previous) || sha256OfCanonicalJson(lineage.inheritedConsumed) !== sha256OfCanonicalJson(previous.consumed) || sha256OfCanonicalJson(parent.budget ?? null) !== sha256OfCanonicalJson(child.budget ?? null))
11
11
  throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: unbound cumulative continuation");
12
+ const invalid = (reason) => { throw Error(`FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: ${reason}`); };
13
+ for (const [state, ledger] of [[parent, previous], [child, next]]) {
14
+ const parsed = campaignBudgetSchema.safeParse(state.budget);
15
+ if (!parsed.success || ledger.schemaVersion !== 1 || ledger.mode !== parsed.data.mode || sha256OfCanonicalJson(ledger.limits) !== sha256OfCanonicalJson(parsed.data.limits))
16
+ invalid("ledger differs from frozen budget");
17
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
18
+ for (const key of ["executorCalls", "wallTimeMs", "repairPasses", "peakContextChars"])
19
+ if (!count(ledger.consumed[key]))
20
+ invalid("invalid consumption");
21
+ if (ledger.consumed.tokens !== null && !count(ledger.consumed.tokens))
22
+ invalid("invalid token consumption");
23
+ if ((ledger.mode === "hard" && ledger.limits.maxProviderRequests !== undefined || ledger.consumed.providerRequests !== undefined) && !count(ledger.consumed.providerRequests))
24
+ invalid("invalid request consumption");
25
+ if (!Array.isArray(ledger.consumed.missingTokenNodeIds) || ledger.consumed.missingTokenNodeIds.some(id => typeof id !== "string" || !id))
26
+ invalid("invalid missing token records");
27
+ if (!Array.isArray(ledger.breaches) || (ledger.mode === "record-only" ? ledger.status !== "record-only" : !["ok", "breached"].includes(ledger.status)))
28
+ invalid("invalid ledger status");
29
+ }
12
30
  for (const key of ["executorCalls", "providerRequests", "wallTimeMs", "repairPasses", "peakContextChars"])
13
31
  if ((next.consumed[key] ?? 0) < (previous.consumed[key] ?? 0))
14
- throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: consumption decreased");
32
+ invalid("consumption decreased");
33
+ if (previous.consumed.tokens !== null && (next.consumed.tokens === null || next.consumed.tokens < previous.consumed.tokens))
34
+ invalid("token consumption decreased");
35
+ if (previous.consumed.missingTokenNodeIds.some(id => !next.consumed.missingTokenNodeIds.includes(id)))
36
+ invalid("missing token records lost");
37
+ if (previous.status === "breached" && next.status !== "breached" || previous.breaches.some(breach => !next.breaches.some(value => sha256OfCanonicalJson(value) === sha256OfCanonicalJson(breach))))
38
+ invalid("budget breach lost");
15
39
  return next;
16
40
  }
17
41
  const requestQueues = new WeakMap();
@@ -27,6 +51,11 @@ export async function reserveDagProviderRequest(input) {
27
51
  throw Error("FRONTEND_PROVIDER_BUDGET_STALE: request belongs to an inactive attempt");
28
52
  if (isHardBudgetBreached(ledger))
29
53
  throw Error("FRONTEND_PROVIDER_BUDGET_EXHAUSTED: run budget already breached");
54
+ if ((ledger.consumed.providerRequests ?? 0) < ledger.limits.maxProviderRequests && input.reserveTaskRequest) {
55
+ await input.reserveTaskRequest();
56
+ if (!active())
57
+ throw Error("FRONTEND_PROVIDER_BUDGET_STALE: attempt stopped during task reservation");
58
+ }
30
59
  const breach = reserveProviderBudget(ledger, input.nodeId);
31
60
  if (breach)
32
61
  applyHardBudgetBreach(input.state, breach);