@tea-agent/loop-agent 0.43.0-next.1 → 0.43.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 (207) hide show
  1. package/CHANGELOG.md +144 -9
  2. package/dist/application/dag/run-dag.js +5 -0
  3. package/dist/application/evaluation/budget.js +19 -1
  4. package/dist/application/task-lifecycle/advance.js +17 -4
  5. package/dist/application/task-lifecycle/observe.js +43 -29
  6. package/dist/application/task-lifecycle/plan-transitions.js +5 -4
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/executors/dag-pi-executor.js +1237 -586
  9. package/dist/executors/pi-executor.js +22 -1
  10. package/dist/executors/pi-sdk-executor.js +140 -39
  11. package/dist/executors/shell-executor.js +8 -4
  12. package/dist/shared/frontend-execution-policy.js +23 -0
  13. package/dist/task/config-types.js +4 -0
  14. package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
  15. package/dist/task/source-prepare/ledger-review.js +6 -9
  16. package/dist/task/source-prepare/semantic-intake.js +16 -26
  17. package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
  18. package/dist/task/source-references.js +48 -23
  19. package/dist/worker/console/chat/assistant-content.js +23 -2
  20. package/dist/worker/console/chat/browser-policy.js +113 -0
  21. package/dist/worker/console/chat/browser-routes.js +140 -0
  22. package/dist/worker/console/chat/explore-tools.js +13 -0
  23. package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
  24. package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
  25. package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
  26. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +86 -0
  27. package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
  28. package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
  29. package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
  30. package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
  31. package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
  32. package/dist/worker/console/chat/pi-runtime.js +207 -1178
  33. package/dist/worker/console/chat/routes.js +208 -71
  34. package/dist/worker/console/chat/scm-routes.js +217 -0
  35. package/dist/worker/console/chat/scm-service.js +283 -0
  36. package/dist/worker/console/chat/scm-tools.js +111 -0
  37. package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
  38. package/dist/worker/console/chat/session-catalog.js +32 -0
  39. package/dist/worker/console/chat/session-mode-view.js +49 -0
  40. package/dist/worker/console/chat/session-mode.js +219 -0
  41. package/dist/worker/console/chat/session-store.js +22 -6
  42. package/dist/worker/console/chat/shortcuts.js +6 -0
  43. package/dist/worker/console/chat/terminal-routes.js +216 -0
  44. package/dist/worker/console/chat/terminal-sessions.js +348 -0
  45. package/dist/worker/console/chat/terminal-tools.js +211 -0
  46. package/dist/worker/console/chat/tool-preview.js +11 -0
  47. package/dist/worker/console/chat/tools.js +2 -0
  48. package/dist/worker/console/server.js +2 -28
  49. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-CTSK0xbU.js} +1 -1
  50. package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-_2MfctFP.js} +1 -1
  51. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-DsDe2wTg.js} +1 -1
  52. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-Dtz_779U.js} +1 -1
  53. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-LaULi-gw.js} +1 -1
  54. package/dist/worker/console/static/assets/channel-CqS4yBWT.js +1 -0
  55. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-DuHBqqX8.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-BZgZs1dn.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-tzwmlIO_.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CeYrLkSn.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-C92cgilm.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-XOXpGC0F.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-CGHbJyCH.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-Bs2-LTzf.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-rN7up7E4.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-B_fWjKpR.js} +1 -1
  65. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DVOxNOq0.js +1 -0
  66. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DVOxNOq0.js +1 -0
  67. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-DE3SdJvB.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-B-4yuRAX.js} +1 -1
  69. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-TiCPFuO8.js} +1 -1
  70. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-7crzLMzA.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-Cvr-x6Fd.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-BzcRHUjH.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-BjkAXilh.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-BkzyATK7.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-rZuTarYP.js} +1 -1
  76. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-CAYENaN1.js} +1 -1
  77. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-DCEam0Uq.js} +1 -1
  78. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-e_dH4wVO.js} +1 -1
  79. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-CvZa2zOK.js} +1 -1
  80. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-CbT5TuKp.js} +1 -1
  81. package/dist/worker/console/static/assets/index-CUPeoE8W.js +468 -0
  82. package/dist/worker/console/static/assets/index-SAANIK8g.css +1 -0
  83. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-BWzHQa_Z.js} +1 -1
  84. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-BTjlovtU.js} +1 -1
  85. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DkvQoCc1.js} +1 -1
  86. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-CHSzGi3W.js} +1 -1
  87. package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-DZMkTCWH.js} +1 -1
  88. package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-BIMhty7A.js} +5 -5
  89. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-Dx-tyMl3.js} +1 -1
  90. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-C6G34rbY.js} +1 -1
  91. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-CHK9XOfm.js} +1 -1
  92. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-CDARkI0w.js} +1 -1
  93. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-DiuUe0ZQ.js} +1 -1
  94. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-RE61UR56.js} +1 -1
  95. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-KDNLHMEG.js} +1 -1
  96. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-CeVgXnD9.js} +1 -1
  97. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-CXwGyjjG.js} +1 -1
  98. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-BjTfozab.js} +1 -1
  99. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BXoYsyr2.js +1 -0
  100. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-BaXOB8GT.js} +2 -2
  101. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DfKliS4e.js +8 -0
  102. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-C3r2hqUU.js} +1 -1
  103. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-MWKgl1S9.js} +1 -1
  104. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-C9iFcZPk.js} +1 -1
  105. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-DqnM24Jp.js} +1 -1
  106. package/dist/worker/console/static/index.html +2 -2
  107. package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
  108. package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
  109. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
  110. package/dist/worker/console/static-src/operator-chat/compaction-message.js +7 -0
  111. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
  112. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  113. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  114. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
  115. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  116. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +44 -24
  117. package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
  118. package/dist/worker/console/static-src/operator-chat/useChatThread.js +41 -8
  119. package/dist/worker/console/static-src/operator-chat/useComposer.js +26 -11
  120. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  121. package/dist/worker/console/workspace-context.js +15 -1
  122. package/dist/worker/observe/node-transparency.js +81 -72
  123. package/dist/worker/observe/routes.js +20 -1
  124. package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
  125. package/dist/worker/observe/static/dom.js +20 -1
  126. package/dist/worker/observe/static/format-pool.d.ts +2 -0
  127. package/dist/worker/observe/static/format-pool.js +6 -0
  128. package/dist/worker/observe/static/format.js +7 -0
  129. package/dist/worker/observe/static/inspect-workspace.js +34 -7
  130. package/dist/worker/observe/static/inspector-submission.js +32 -0
  131. package/dist/worker/observe/static/kpi.js +1 -0
  132. package/dist/worker/observe/static/relations.js +2 -0
  133. package/dist/worker/observe/static/router.js +13 -0
  134. package/dist/worker/observe/static/run-processing.js +2 -0
  135. package/dist/worker/observe/static/shell-chrome.js +36 -3
  136. package/dist/worker/observe/static/state.js +35 -2
  137. package/dist/worker/observe/static/styles.css +263 -39
  138. package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
  139. package/dist/worker/observe/static/task-failure-labels.js +67 -0
  140. package/dist/worker/observe/static/task-history.js +12 -0
  141. package/dist/worker/observe/static/views/batch.js +6 -13
  142. package/dist/worker/observe/static/views/dag-graph.js +50 -3
  143. package/dist/worker/observe/static/views/dag-inspector.js +742 -268
  144. package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
  145. package/dist/worker/observe/static/views/dag.d.ts +6 -0
  146. package/dist/worker/observe/static/views/dag.js +49 -11
  147. package/dist/worker/observe/static/views/dags.js +2 -0
  148. package/dist/worker/observe/static/views/dashboard.js +21 -12
  149. package/dist/worker/observe/static/views/failures.js +21 -11
  150. package/dist/worker/observe/static/views/feature.js +11 -29
  151. package/dist/worker/observe/static/views/pool.js +37 -28
  152. package/dist/worker/observe/static/views/run.js +48 -5
  153. package/dist/worker/observe/static/views/session-timeline.js +189 -240
  154. package/dist/worker/observe/static/views/task.js +81 -62
  155. package/dist/workflows/dag/budget-enforcement.js +53 -3
  156. package/dist/workflows/dag/frontend-capacity.js +9 -0
  157. package/dist/workflows/dag/frontend-closeout.js +1 -1
  158. package/dist/workflows/dag/frontend-design-policy.js +2 -1
  159. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  160. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  161. package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
  162. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  163. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  164. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  165. package/dist/workflows/dag/frontend-plan-render.js +10 -3
  166. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  167. package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
  168. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  169. package/dist/workflows/dag/frontend-recovery-run.js +4 -0
  170. package/dist/workflows/dag/frontend-review-context.js +2 -1
  171. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  172. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  173. package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
  174. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  175. package/dist/workflows/dag/frontend-shape.js +30 -1
  176. package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
  177. package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
  178. package/dist/workflows/dag/frontend-verification-trace.js +1 -1
  179. package/dist/workflows/dag/init-hybrid.js +65 -167
  180. package/dist/workflows/dag/node-execution.js +38 -155
  181. package/dist/workflows/dag/prompt.js +4 -0
  182. package/dist/workflows/dag/rerun-plan.js +7 -1
  183. package/dist/workflows/dag/runner.js +26 -1
  184. package/dist/workflows/dag/types.js +6 -0
  185. package/docs/operations/README.md +1 -0
  186. package/docs/templates/frontend-design-contract.md +4 -4
  187. package/docs/templates/frontend-implementation-contract.schema.json +34 -2
  188. package/docs/templates/frontend-implementation-dag.json +14 -13
  189. package/package.json +6 -3
  190. package/skills/frontend-bounded-implement/SKILL.md +3 -3
  191. package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
  192. package/skills/frontend-contract/SKILL.md +2 -1
  193. package/skills/frontend-contract/references/contract-protocol.md +22 -4
  194. package/skills/frontend-design-review/SKILL.md +22 -16
  195. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  196. package/skills/frontend-plan/SKILL.md +21 -2
  197. package/skills/frontend-plan/references/decision-contract.md +42 -23
  198. package/skills/frontend-review/SKILL.md +22 -29
  199. package/skills/frontend-review/references/review-findings.md +24 -43
  200. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  201. package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
  202. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
  203. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
  204. package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
  205. package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
  206. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
  207. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
@@ -1,3 +1,5 @@
1
+ import { collectFrontendExecutionGroups } from "./frontend-execution-groups.js";
2
+ import { reserveDagProviderRequest } from "./budget-enforcement.js";
1
3
  import { createHash } from "node:crypto";
2
4
  import { existsSync } from "node:fs";
3
5
  import { readFile } from "node:fs/promises";
@@ -297,6 +299,12 @@ export function buildContextOverflowRetryPrompt(task, basePrompt) {
297
299
  compactRetrySection(basePrompt, "task", COMPACT_RETRY_TASK_MAX_CHARS),
298
300
  "</task>",
299
301
  ].join("\n"),
302
+ // Plan still needs the complete frozen obligations for ledger ownership
303
+ // and scoped projection. Its executor packs this block before dispatch;
304
+ // dropping or character-truncating it prevents safe progress recovery.
305
+ ...(isFrontendPlanLadderTask(task)
306
+ ? [basePrompt.match(/<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/)?.[0] ?? ""]
307
+ : []),
300
308
  ].join("\n\n");
301
309
  }
302
310
  function isFrontendPlanLadderTask(task) {
@@ -380,27 +388,14 @@ async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
380
388
  function planInputRecord(value) {
381
389
  return typeof value === "object" && value !== null && !Array.isArray(value);
382
390
  }
383
- function planInputText(value, maxChars = 240) {
384
- if (typeof value !== "string" || value.trim().length === 0)
385
- return undefined;
386
- const normalized = value.trim();
387
- return normalized.length <= maxChars
388
- ? normalized
389
- : `${normalized.slice(0, maxChars - 1)}…`;
391
+ function planInputText(value, _maxChars) {
392
+ return typeof value === "string" && value.trim().length ? value.trim() : undefined;
390
393
  }
391
394
  function planInputStrings(value) {
392
395
  return Array.isArray(value)
393
396
  ? value.filter((item) => typeof item === "string")
394
397
  : [];
395
398
  }
396
- /** Input bound for the planner evidence block; protects the model input budget. */
397
- const FRONTEND_PLAN_INPUT_MAX_CHARS = 12_000;
398
- const FRONTEND_PLAN_INPUT_CAP_LADDER = [
399
- { text: 240, array: 40 },
400
- { text: 120, array: 20 },
401
- { text: 60, array: 10 },
402
- { text: 24, array: 4 },
403
- ];
404
399
  /**
405
400
  * Frozen requirement→PRD citation map for the plan review checklist: lets the
406
401
  * model declare sourceRequirementIds whose section/line match the component
@@ -446,78 +441,19 @@ async function resolveComponentSourceCitations(spec, cwd) {
446
441
  return new Map();
447
442
  }
448
443
  }
449
- /** Input bound for the contract node's compiled ledger block. */
450
- const FRONTEND_CONTRACT_INPUT_MAX_CHARS = 12_000;
451
444
  /**
452
- * Render the contract node's complete-but-bounded ledger handoff. The
445
+ * Render the contract node's complete semantic ledger handoff. The
453
446
  * source-fidelity ledger already extracted canonical requirements with source
454
447
  * spans; the contract node confirms and commits them incrementally through
455
448
  * record_requirement instead of re-reading the raw source (extreme-environment:
456
449
  * a small output window cannot absorb a full source re-read).
457
450
  *
458
- * Same shape guarantees as the plan input block: always valid JSON under the
459
- * char bound, ids never drop, texts degrade through the cap ladder.
451
+ * Whole obligations are retained here; the executor selects complete scopes
452
+ * before dispatch. References and conditions are never clipped.
460
453
  */
461
454
  export function renderFrontendContractInputContext(input) {
462
- // Fragment bindings are ids, not prose: they are the one thing the contract
463
- // must never lose. r6 regression the last-resort degradation dropped
464
- // sourceFragmentIds entirely, the model (correctly refusing to invent ids)
465
- // committed empty bindings, and the plan compile failed the ledger-binding
466
- // gate for every requirement. Bindings therefore bypass the cap ladder and
467
- // every degradation level; only requirement TEXTS and fragment CONTEXT
468
- // (path/headingPath) may degrade. Fragment context is rendered only for
469
- // fragments actually referenced by a requirement and shrinks first.
470
- const referencedFragmentIds = new Set(input.canonicalRequirements.flatMap((requirement) => planInputStrings(requirement.sourceFragmentIds)));
471
- const referencedFragments = input.fragments.filter((fragment) => referencedFragmentIds.has(fragment.id));
472
- const serializeAtCap = (cap) => JSON.stringify({
473
- requirements: input.canonicalRequirements.map((requirement) => ({
474
- id: requirement.id,
475
- text: planInputText(requirement.text, cap.text),
476
- sourceFragmentIds: planInputStrings(requirement.sourceFragmentIds),
477
- })),
478
- fragments: referencedFragments.map((fragment) => ({
479
- id: fragment.id,
480
- path: planInputText(fragment.path, 200),
481
- headingPath: planInputText(fragment.headingPath, 120),
482
- lineRange: fragment.lineRange,
483
- })),
484
- });
485
- let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
486
- for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
487
- if (serialized.length <= FRONTEND_CONTRACT_INPUT_MAX_CHARS)
488
- break;
489
- serialized = serializeAtCap(cap);
490
- }
491
- if (serialized.length > FRONTEND_CONTRACT_INPUT_MAX_CHARS) {
492
- // Last resort: keep every requirement id AND its fragment bindings,
493
- // degrade texts, and shrink referenced fragment context first (halve,
494
- // then drop context fields, then drop the fragment list entirely).
495
- // Requirement ids and sourceFragmentIds are never dropped.
496
- let fragments = referencedFragments.map((fragment) => ({
497
- id: fragment.id,
498
- path: planInputText(fragment.path, 120),
499
- }));
500
- let requirements = input.canonicalRequirements.map((requirement) => {
501
- const sourceFragmentIds = planInputStrings(requirement.sourceFragmentIds);
502
- return {
503
- id: requirement.id,
504
- text: "(truncated)",
505
- // Empty bindings carry no information; omit them so the payload
506
- // stays inside the char bound when no requirement is bound.
507
- ...(sourceFragmentIds.length > 0 ? { sourceFragmentIds } : {}),
508
- };
509
- });
510
- let bounded = JSON.stringify({ degraded: "requirement-texts-truncated", requirements, fragments });
511
- while (bounded.length > FRONTEND_CONTRACT_INPUT_MAX_CHARS && fragments.length > 0) {
512
- fragments = fragments.slice(0, Math.floor(fragments.length / 2));
513
- bounded = JSON.stringify({
514
- degraded: "requirement-texts-truncated",
515
- requirements,
516
- fragments,
517
- });
518
- }
519
- serialized = bounded;
520
- }
455
+ const referenced = new Set(input.canonicalRequirements.flatMap(r => r.sourceFragmentIds));
456
+ const serialized = JSON.stringify({ requirements: input.canonicalRequirements, fragments: input.fragments.filter(f => referenced.has(f.id)) });
521
457
  return [
522
458
  "<frontend_contract_input>",
523
459
  "Canonical requirements extracted by the source-fidelity ledger, compiled by the runner. Treat them as the authoritative requirement inventory: confirm and commit each requirement through record_requirement (one per tool call); the ledger already binds source fragments, so do NOT re-read the raw source files.",
@@ -542,16 +478,13 @@ export async function buildFrontendContractInputContext(input) {
542
478
  });
543
479
  }
544
480
  /**
545
- * Render the planner's complete-but-bounded evidence handoff from committed
481
+ * Render the planner's complete semantic evidence handoff from committed
546
482
  * typed facts. It deliberately excludes upstream response prose and artifact
547
483
  * paths: Contract and Scout have already established these facts, so Plan
548
484
  * should decide and commit rather than spend another model turn reading them.
549
485
  *
550
- * The block is always valid JSON under the char bound: field texts shrink
551
- * through a cap ladder before any fact is dropped, and the last-resort
552
- * fallback keeps every requirement id (with `text: "(truncated)"`) while
553
- * declaring the degradation, so the planner records targeted evidence gaps
554
- * instead of receiving a silently corrupted tail.
486
+ * Whole obligations are retained. Session packing and scoped projection happen
487
+ * in the executor before dispatch, without dropping text or source identities.
555
488
  */
556
489
  export function renderFrontendPlanInputContext(input) {
557
490
  const committedFacts = (records) => records.flatMap((record) => record.phase === "committed" && planInputRecord(record.fact)
@@ -610,77 +543,12 @@ export function renderFrontendPlanInputContext(input) {
610
543
  const behaviorRequiredIds = requirementFacts
611
544
  .filter((requirement) => requirement.evidence.behavior === "required")
612
545
  .map((requirement) => requirement.id);
613
- const serializeAtCap = (cap) => JSON.stringify({
614
- requirements: requirements.map((requirement) => ({
615
- id: requirement.id,
616
- text: planInputText(requirement.text, cap.text),
617
- sourceFragmentIds: planInputStrings(requirement.sourceFragmentIds).slice(0, cap.array),
618
- })),
619
- requiredDeliverables,
620
- targetSurface: targetSurface.map((surface) => ({
621
- completeness: planInputText(surface.completeness, 32),
622
- entrypoint: planInputText(surface.entrypoint, cap.text),
623
- routeOrMount: planInputText(surface.routeOrMount, cap.text),
624
- implementationPaths: planInputStrings(surface.implementationPaths).slice(0, cap.array),
625
- testPaths: planInputStrings(surface.testPaths).slice(0, cap.array),
626
- dataSource: planInputText(surface.dataSource, cap.text),
627
- allowedPathConflicts: planInputStrings(surface.allowedPathConflicts).slice(0, cap.array),
628
- unresolvedPaths: planInputStrings(surface.unresolvedPaths).slice(0, cap.array),
629
- })),
630
- designEvidence: designEvidence.map((evidence) => ({
631
- source: planInputText(evidence.source, cap.text),
632
- paths: planInputStrings(evidence.paths).slice(0, cap.array),
633
- conflicts: planInputStrings(evidence.conflicts).slice(0, cap.array),
634
- })),
635
- declaredUiStates: declaredUiStates.map((state) => ({
636
- id: state.id,
637
- trigger: planInputText(state.trigger, cap.text),
638
- observableOutcome: planInputText(state.observableOutcome, cap.text),
639
- })),
640
- committedUx: committedUiStateNames.length > 0 ||
641
- committedInteractionNames.length > 0
642
- ? {
643
- uiStateNames: committedUiStateNames,
644
- interactionNames: committedInteractionNames,
645
- }
646
- : undefined,
546
+ const serialized = JSON.stringify({
547
+ requirements, requiredDeliverables, targetSurface, designEvidence, declaredUiStates,
548
+ executionGroups: collectFrontendExecutionGroups(contractFacts.filter(f => f.kind === "requirement").map(f => ({ id: String(f.id), execution: f.execution }))),
549
+ constraints: contractFacts.filter(f => ["constraint", "open-question", "split-proposal", "handoff-intent"].includes(String(f.kind))),
550
+ committedUx: committedUiStateNames.length || committedInteractionNames.length ? { uiStateNames: committedUiStateNames, interactionNames: committedInteractionNames } : undefined,
647
551
  });
648
- let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
649
- for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
650
- if (serialized.length <= FRONTEND_PLAN_INPUT_MAX_CHARS)
651
- break;
652
- serialized = serializeAtCap(cap);
653
- }
654
- if (serialized.length > FRONTEND_PLAN_INPUT_MAX_CHARS) {
655
- // Last resort: keep every requirement id (ids are short and the plan
656
- // prompt separately lists them) but drop their texts, shrink scout facts
657
- // to the minimum, and declare the degradation instead of corrupting JSON.
658
- let fallback = {
659
- degraded: "requirement-texts-truncated",
660
- requiredDeliverables,
661
- requirements: requirements.map((requirement) => ({
662
- id: requirement.id,
663
- text: "(truncated)",
664
- })),
665
- targetSurface: targetSurface.map((surface) => ({
666
- completeness: planInputText(surface.completeness, 32),
667
- implementationPaths: planInputStrings(surface.implementationPaths).slice(0, FRONTEND_PLAN_INPUT_CAP_LADDER[3].array),
668
- })),
669
- designEvidence: [],
670
- };
671
- let bounded = JSON.stringify(fallback);
672
- let keep = fallback.requirements.length;
673
- while (bounded.length > FRONTEND_PLAN_INPUT_MAX_CHARS &&
674
- keep > 0) {
675
- keep = Math.max(0, Math.floor(keep / 2));
676
- fallback = { ...fallback, requirements: fallback.requirements.slice(0, keep) };
677
- bounded = JSON.stringify({
678
- ...fallback,
679
- requirementIdsTruncated: keep < requirements.length,
680
- });
681
- }
682
- serialized = bounded;
683
- }
684
552
  const checklistLines = [
685
553
  "1. Record the GLOBAL UX vocabulary with record_state_registry BEFORE any record_state_flow: one stable behavior-domain name per state/interaction (e.g. planner-task-edit), never one name per AC number, and never a rename of an already-recorded concept. Coverage slices by AC; UX does not.",
686
554
  "2. Every recorded interaction/uiState name must be in that registry, and uiState names must use the contract's declared authoritative ids (declaredUiStates below) when present.",
@@ -1583,6 +1451,20 @@ export async function executeDagNode(input) {
1583
1451
  return;
1584
1452
  }
1585
1453
  }
1454
+ if (task.id === "frontend-scout-pi") {
1455
+ try {
1456
+ const { readTypedEventStoreFromJsonl } = await import("./frontend-typed-event-store.js");
1457
+ const facts = (await readTypedEventStoreFromJsonl(path.join(runDir, "frontend-contract-pi", "contract-typed-facts.jsonl"))).filter(r => r.phase === "committed").map(r => r.fact);
1458
+ const requirements = [...new Map(facts.filter(f => f.kind === "requirement").map(f => [String(f.id), f])).values()];
1459
+ if (!requirements.length)
1460
+ throw Error("FRONTEND_INPUT_MISSING: Scout needs committed Contract obligations");
1461
+ prompt += `\n<frontend_scout_input>\n${JSON.stringify({ requirements, sharedFacts: facts.filter(f => !["requirement", "contract-scope-completed", "contract-finalized"].includes(String(f.kind))) })}\n</frontend_scout_input>`;
1462
+ }
1463
+ catch (error) {
1464
+ await failBeforePrompt(error, "frontend-scout-input-unavailable");
1465
+ return;
1466
+ }
1467
+ }
1586
1468
  if (FRONTEND_WRITER_NODE_IDS.includes(nodeId)) {
1587
1469
  // Design-review findings are not reliable in the provider's prose output
1588
1470
  // (typed terminal nodes commonly return an empty assistant message). Inject
@@ -1770,6 +1652,7 @@ export async function executeDagNode(input) {
1770
1652
  attemptPrompt,
1771
1653
  });
1772
1654
  result = await executeNode({
1655
+ ...(state.budgetLedger?.mode === "hard" && state.budgetLedger.limits.maxProviderRequests !== undefined ? { reserveProviderRequest: () => reserveDagProviderRequest({ state, nodeId, attempt: attemptNumber, persist: input.persistState }) } : {}),
1773
1656
  task,
1774
1657
  cwd,
1775
1658
  model,
@@ -136,6 +136,10 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
136
136
  const consumedArtifactIds = new Set((task.consumesArtifacts ?? []).map((binding) => binding.artifactId));
137
137
  for (const depId of task.depends_on) {
138
138
  const record = upstream[depId];
139
+ if (task.id === "frontend-scout-pi" && depId === "frontend-contract-pi" && record?.status === "FINISHED") {
140
+ sections.push(`## Upstream output: ${depId}\nCommitted Contract obligations are supplied in frontend_scout_input. Do not reconstruct them from prose or reread the source.`);
141
+ continue;
142
+ }
139
143
  const stdout = record?.stdout?.trim() ? record.stdout : "";
140
144
  const assistantText = record?.assistantText?.trim()
141
145
  ? record.assistantText
@@ -46,6 +46,7 @@ const highRiskContinuationAuthorityV1Schema = z
46
46
  tokens: z.number().int().nonnegative().nullable(),
47
47
  wallTimeMs: z.number().nonnegative(),
48
48
  executorCalls: z.number().int().nonnegative(),
49
+ providerRequests: z.number().int().nonnegative().optional(),
49
50
  repairPasses: z.number().int().nonnegative(),
50
51
  peakContextChars: z.number().int().nonnegative(),
51
52
  missingTokenNodeIds: z.array(z.string()),
@@ -1300,7 +1301,12 @@ export async function evaluateDagRerunPlan(input) {
1300
1301
  const task = tasksByIdForPlan.get(nodeId);
1301
1302
  return task?.readSet ?? [];
1302
1303
  });
1303
- const scopedDrift = driftedPaths !== undefined &&
1304
+ // An omitted or empty readSet is unrestricted, not proof that an
1305
+ // imported node read nothing. Localize drift only when every reused
1306
+ // node has an explicit reading boundary.
1307
+ const importedReadSetsKnown = importedNodeIds.every((nodeId) => (tasksByIdForPlan.get(nodeId)?.readSet?.length ?? 0) > 0);
1308
+ const scopedDrift = importedReadSetsKnown &&
1309
+ driftedPaths !== undefined &&
1304
1310
  driftedPaths.length > 0 &&
1305
1311
  driftedPaths.every((driftPath) => !importedReadPaths.some((pattern) => pathMatchesWorkspacePattern(driftPath, pattern)) &&
1306
1312
  resetNodeIds.some((nodeId) => {
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import { isHardBudgetBreached, resolveEffectiveMaxConcurrent, } from "../../application/evaluation/budget.js";
7
7
  import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
8
8
  import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
9
- import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
9
+ import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, validateCumulativeBudgetSuccessor, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
10
10
  import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, readDagRunState, requireActiveDagRun, } from "./lifecycle.js";
11
11
  import { markInterruptNeedsReconcile, mergeAbortSignals, readInterruptRequest, runnerIdentityFromState, settleDagInterrupt, startInterruptWatcher, } from "./interrupt-request.js";
12
12
  import { moveToCompletedRunDir, moveToPausedRunDir, prepareRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
@@ -887,6 +887,31 @@ async function executeDagCheckpoint(input) {
887
887
  observeOnly: created.kind === "observed",
888
888
  deps: {
889
889
  ...input.recovery,
890
+ budget: input.recovery.budget ?? state.budget,
891
+ readLedgers: input.recovery.readLedgers ?? (async () => {
892
+ if (!state.budget)
893
+ return [];
894
+ const location = await locateDagRun(cwd, state.runId);
895
+ let current = await readDagRunState(location?.runDir ?? runDir);
896
+ const visited = new Set([current.runId]);
897
+ while (current.frontendRecoveryState?.parentRunId === current.runId && current.frontendRecoveryState.childRunId && current.frontendRecoveryState.childRunId !== current.runId) {
898
+ if (["child-staging", "child-activating"].includes(current.frontendRecoveryState.phase))
899
+ break;
900
+ const childId = current.frontendRecoveryState.childRunId;
901
+ if (visited.has(childId))
902
+ throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: cyclic recovery");
903
+ visited.add(childId);
904
+ const childLocation = await locateDagRun(cwd, childId);
905
+ if (!childLocation)
906
+ throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: missing recovery child");
907
+ const child = await readDagRunState(childLocation.runDir);
908
+ validateCumulativeBudgetSuccessor(current, child);
909
+ current = child;
910
+ }
911
+ if (!current.budgetLedger)
912
+ throw Error("FRONTEND_PROVIDER_BUDGET_INVALID: recovery ledger missing");
913
+ return [current.budgetLedger];
914
+ }),
890
915
  failureOwner: input.recovery.failureOwner ??
891
916
  recoveryTrigger.failureOwner,
892
917
  protocolFailureReason: input.recovery.protocolFailureReason ??
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { frontendExecutionPolicySchema } from "../../shared/frontend-execution-policy.js";
2
3
  import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
3
4
  import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
4
5
  import { assertDagPromptSourceRule } from "./prompt-source.js";
@@ -1385,6 +1386,7 @@ export const dagSpecSchema = z
1385
1386
  evaluation: dagEvaluationBindingSchema.optional(),
1386
1387
  /** Optional hard/record-only budget; requires version 3 or 4. */
1387
1388
  budget: dagBudgetSchema.optional(),
1389
+ frontendExecutionPolicy: frontendExecutionPolicySchema.optional(),
1388
1390
  sourceBinding: dagSourceBindingSchema.optional(),
1389
1391
  /** v4 managed Task Contract binding; only valid on version 4. */
1390
1392
  taskContractBinding: dagTaskContractBindingSchema.optional(),
@@ -1567,6 +1569,10 @@ export const frontendRecoveryStateSchema = z
1567
1569
  recoveryRootRunId: z.string().min(1),
1568
1570
  parentRunId: z.string().min(1),
1569
1571
  childRunId: z.string().min(1).optional(),
1572
+ budgetLineage: z.object({
1573
+ parentLedgerSha256: z.string().regex(/^[a-f0-9]{64}$/), inheritedAt: z.string().datetime(),
1574
+ inheritedConsumed: z.object({ tokens: z.number().nonnegative().nullable(), wallTimeMs: z.number().nonnegative(), executorCalls: z.number().int().nonnegative(), providerRequests: z.number().int().nonnegative().optional(), repairPasses: z.number().int().nonnegative(), peakContextChars: z.number().int().nonnegative(), missingTokenNodeIds: z.array(z.string()) }).strict(),
1575
+ }).strict().optional(),
1570
1576
  attemptId: z.string().min(1),
1571
1577
  attemptIndex: z.number().int().min(0).max(5),
1572
1578
  continuationCount: z.number().int().min(0).max(5),
@@ -9,6 +9,7 @@
9
9
  - [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及发布通道。
10
10
  - [`version-and-release-management.md`](version-and-release-management.md):版本发布管理——next / release-x.y / latest 渠道、移动式 `npm-next` ref、`inspect next` 查询入口、workflow 与 promote 手册。
11
11
  - [`production-readiness.md`](production-readiness.md):交付前的生产就绪判断。
12
+ - [`frontend-reliability-canary-runbook.md`](frontend-reliability-canary-runbook.md):frontend 可靠性修复的 live canary 内网执行手册(协议冻结自 exec-plan §17;配对矩阵、证据注册与人工晋升门)。
12
13
  - [`agent-worker-production-readiness.md`](agent-worker-production-readiness.md):agent-worker Feature/Task Pool 外层编排的 Production Readiness v1 操作合同。
13
14
  - [`backend-test-jacoco-coverage.md`](backend-test-jacoco-coverage.md):Java 被测服务挂载 JaCoCo tcpserver agent 的 Maven、`java -jar` 与 Docker 操作手册。
14
15
  - [`console-chat-dogfood-playbook.md`](console-chat-dogfood-playbook.md):Operator Chat(含 Explore 子 Agent)真实模型 dogfood 的 CLI 驱动步骤、mutation 双 token / SSE 超时 / question 应答等已知陷阱与验证方式。
@@ -61,8 +61,8 @@
61
61
  - **DAG 如何消费**:进入 `evidenceGaps[]` 与 Non-goals,`frontend-review-pi`/closeout 据它保留风险与后续项。
62
62
  - **缺失/冲突时 fail-closed**:范围与需求非目标冲突、或风险被隐藏时阻塞。
63
63
 
64
- ## OpenSpec 引用块(openspec-citations)
64
+ ## OpenSpec 来源与引用
65
65
 
66
- - **要写什么**:在 fenced `json` 契约块之后追加**恰好一个** fenced `openspec-citations` 块,每行一个 JSON `{"path","section","line"}`(`section` 可空串、`line` 为 int 或 null),逐条列出本计划实际读取并应用的每个 openspec 规范文件。
67
- - **DAG 如何消费**:`frontend-prewrite-gate-shell` fence 语言标签解析该块,并与生效 plan/review 节点的成功 read 事件核验;`cited` 模式下候选未引用 `openspec-not-cited`,引用无 read 背书 → `openspec-citation-not-read`,块缺失/不可解析 → `openspec-citation-block-unparseable`。
68
- - **缺失/冲突时 fail-closed**:候选非空而引用块缺失/不可解析、候选未引用、或引用未真实读取时,prewrite gate 以 `retryable-invalid` fail-closed;不要引用未读取的路径,也不要遗漏已读取的 openspec 文件。
66
+ - Contract `record_openspec_selection` 声明实际使用的 required/relevant 来源,未提及候选不自动升级为 required。
67
+ - Plan 通过 typed tools 引用冻结来源;runtime 从绑定来源与真实读取证据物化引用和 canonical JSON。模型不再输出 JSON 契约块或 `openspec-citations` fence。
68
+ - 来源未绑定、引用不可解析或缺少真实读取背书时,由确定性 gate 阻断;不得伪造读取或引用。
@@ -115,6 +115,32 @@
115
115
  "items": {
116
116
  "$ref": "#/$defs/path"
117
117
  }
118
+ },
119
+ "execution": {
120
+ "type": "object",
121
+ "additionalProperties": false,
122
+ "required": [
123
+ "groupId",
124
+ "kind",
125
+ "summary"
126
+ ],
127
+ "properties": {
128
+ "groupId": {
129
+ "type": "string",
130
+ "minLength": 1
131
+ },
132
+ "kind": {
133
+ "enum": [
134
+ "behavior",
135
+ "constraint",
136
+ "exclusion"
137
+ ]
138
+ },
139
+ "summary": {
140
+ "type": "string",
141
+ "minLength": 1
142
+ }
143
+ }
118
144
  }
119
145
  }
120
146
  }
@@ -644,10 +670,16 @@
644
670
  "allOf": [
645
671
  {
646
672
  "if": {
647
- "required": ["ledgerPath"]
673
+ "required": [
674
+ "ledgerPath"
675
+ ]
648
676
  },
649
677
  "then": {
650
- "required": ["ledgerSha256", "inputDigest", "requirementToFragments"]
678
+ "required": [
679
+ "ledgerSha256",
680
+ "inputDigest",
681
+ "requirementToFragments"
682
+ ]
651
683
  }
652
684
  }
653
685
  ],
@@ -6,10 +6,11 @@
6
6
  "successCriteria": [
7
7
  "The contract covers every required user-visible behavior, state, and interaction.",
8
8
  "The implementation is admitted only after a concrete, narrow writeSet is reviewed.",
9
- "Static and behavior verification are recorded before final review.",
9
+ "Build verification are recorded before final review.",
10
10
  "The final closeout distinguishes passed, not-run, and residual-risk evidence."
11
11
  ],
12
12
  "globalConstraints": [
13
+ "frontend-implementation plans and reviews requirement implementation coverage; npm run build is the only verification command. Tests and behavior correctness are deferred to frontend-test, non-blocking; preserve existing tests and production Mock isolation.",
13
14
  "This is a self-contained static template: do not generate, import, or merge a DAG from a runtime builder before execution.",
14
15
  "Replace every REPLACE/WITH/... placeholder with concrete repository paths or deterministic commands before execution.",
15
16
  "Do not commit runtime traces under .harness/dag-runs/.",
@@ -27,18 +28,18 @@
27
28
  "tasks": [
28
29
  {
29
30
  "id":"frontend-contract-pi", "depends_on":[], "complexity":"MED", "executor":"pi", "role":"planner", "writePolicy":"read-only", "allowedPaths":["**"], "forbiddenPaths":[".harness/**","artifacts/**"],
30
- "outputContract":"Plain Markdown frontend contract: required behavior, states, interactions, non-goals, concrete verification expectations, and unresolved gaps; no file writes.",
31
+ "outputContract":"Incremental Contract typed facts from complete input scopes; complete_contract_scope after all decisions, then one successful finalize_contract. Runtime owns canonical text and source bindings. No file writes.",
31
32
  "subtask_prompt":"Read the task inputs. Produce a concise frontend contract that maps each required behavior, UI state, and interaction to observable outcomes. Identify missing or conflicting requirements as blockers; do not infer them and do not edit files."
32
33
  },
33
34
  {
34
35
  "id":"frontend-scout-pi", "depends_on":["frontend-contract-pi"], "complexity":"LOW", "executor":"pi", "role":"scout", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
35
- "outputContract":"Plain Markdown source and verification reconnaissance: relevant routes, components, styles, state patterns, focused tests, and risks; no file writes.",
36
+ "outputContract":"Scout typed target-surface and design-evidence facts backed by fresh read evidence; durable receipts only. No file writes.",
36
37
  "subtask_prompt":"Inspect only the declared frontend source and test paths. Report reusable component, styling, accessibility, state-management, and verification patterns that satisfy the contract. Do not edit files or broaden the allowed paths."
37
38
  },
38
39
  {
39
40
  "id":"frontend-plan-pi", "depends_on":["frontend-scout-pi"], "complexity":"MED", "executor":"pi", "role":"planner", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
40
- "outputContract":"Plain Markdown implementation plan: ordered changes, concrete target files, behavior/state/interaction coverage, and focused verification mapping; no file writes.",
41
- "subtask_prompt":"Turn the contract and reconnaissance into a minimal implementation plan. Every target file and verification step must be concrete. Preserve existing design and accessibility conventions. If any behavior lacks a verifiable target, report it as blocked instead of guessing."
41
+ "outputContract":"Plan record_* tools for scoped coverage, shared execution groups and UX, one mock endpoint per record, then one successful finalize_plan. Runtime derives canonical JSON and Markdown. No file writes.",
42
+ "subtask_prompt":"List the requirement implementation plan: what to change and in which authorized files. Map requirements to the shared static build target. Do not require test plans or behavior correctness evidence; tests may be added later in frontend-test."
42
43
  },
43
44
  {
44
45
  "id":"frontend-design-policy-shell", "depends_on":["frontend-plan-pi"], "complexity":"LOW", "executor":"shell", "role":"verifier", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
@@ -48,8 +49,8 @@
48
49
  },
49
50
  {
50
51
  "id":"frontend-design-review-pi", "depends_on":["frontend-design-policy-shell"], "complexity":"MED", "executor":"pi", "role":"reviewer", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
51
- "outputContract":"Plain Markdown design review: approved plan or explicit blocking findings for behavior, state, interaction, design-system, and accessibility coverage; no file writes.",
52
- "subtask_prompt":"Review the approved plan against the frontend contract and design-policy result. Reject missing visible states, interaction behavior, accessibility obligations, or unsupported design claims. Do not edit files."
52
+ "outputContract":"record_design_finding for individual findings; approve_design or request_design_changes provides the sole committed terminal. Independently review execution-group member differences. No file writes.",
53
+ "subtask_prompt":"Review the approved plan against the frontend contract and design-policy result. Reject missing visible states, interaction behavior, accessibility obligations, or unsupported design claims. Do not edit files. Scope: requirement implementation coverage and authorized paths/dependencies only; do not require test plans, tests or behavior correctness evidence. Preserve existing tests. Only npm run build is required; test work is deferred to frontend-test."
53
54
  },
54
55
  {
55
56
  "id":"frontend-writer-admission-shell", "depends_on":["frontend-design-review-pi"], "complexity":"LOW", "executor":"shell", "role":"verifier", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
@@ -60,13 +61,13 @@
60
61
  {
61
62
  "id":"frontend-implement-pi", "depends_on":["frontend-writer-admission-shell"], "complexity":"HIGH", "executor":"pi", "role":"implementer", "toolProfile":"write", "writePolicy":"exclusive", "writeSet":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
62
63
  "outputContract":"Implementation summary with changed files, contract coverage, focused checks run, and residual risks.",
63
- "subtask_prompt":"Implement only the admitted frontend changes. Preserve established component, styling, accessibility, and state-management conventions. Cover every approved behavior, state, and interaction, then report changed files and focused verification. Do not broaden writeSet or add undeclared dependencies."
64
+ "subtask_prompt":"Implement only the admitted frontend changes. Preserve established component, styling, accessibility, and state-management conventions. Cover every approved behavior, state, and interaction, then report changed files and focused verification. Do not broaden writeSet or add undeclared dependencies. Scope: requirement implementation coverage and authorized paths/dependencies only; do not require test plans, tests or behavior correctness evidence. Preserve existing tests. Only npm run build is required; test work is deferred to frontend-test."
64
65
  },
65
66
  {
66
67
  "id":"frontend-verify-shell", "depends_on":["frontend-implement-pi"], "complexity":"LOW", "executor":"shell", "role":"verifier", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
67
- "outputContract":"Archived static and behavior verification output with command exit codes; no worktree writes.",
68
- "subtask_prompt":"Run the contract-selected deterministic static and behavior verification commands. Record all exit codes. A failed required command is terminal for this DAG run; do not silently substitute a different command.",
69
- "shell":{"preset":"loop-agent-standard-verify","cwd":".","timeoutMs":300000}
68
+ "outputContract":"Archived build verification output with command exit codes; no worktree writes.",
69
+ "subtask_prompt":"Run npm run build and record the actual exit code. Missing or failed builds must fail; never substitute a no-op.",
70
+ "shell":{"commands":["npm run build"],"cwd":".","timeoutMs":300000}
70
71
  },
71
72
  {
72
73
  "id":"frontend-review-context-shell", "depends_on":["frontend-verify-shell"], "complexity":"LOW", "executor":"shell", "role":"verifier", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
@@ -76,8 +77,8 @@
76
77
  },
77
78
  {
78
79
  "id":"frontend-review-pi", "depends_on":["frontend-review-context-shell"], "complexity":"MED", "executor":"pi", "role":"reviewer", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
79
- "outputContract":"Plain Markdown final review: approved result or explicit findings for contract coverage, regression risk, accessibility, and verification gaps; no file writes.",
80
- "subtask_prompt":"Review the actual diff and archived verification evidence against the frontend contract. Do not treat missing, weak, mock-only, or not-run evidence as passed. Report explicit residual risks; do not edit files."
80
+ "outputContract":"record_review_finding for individual findings; approve_review or request_review_changes provides the sole committed terminal. No file writes.",
81
+ "subtask_prompt":"Review the actual diff and archived verification evidence against the frontend contract. Do not treat missing, weak, mock-only, or not-run evidence as passed. Report explicit residual risks; do not edit files. Scope: requirement implementation coverage and authorized paths/dependencies only; do not require test plans, tests or behavior correctness evidence. Preserve existing tests. Only npm run build is required; test work is deferred to frontend-test."
81
82
  },
82
83
  {
83
84
  "id":"frontend-closeout-shell", "depends_on":["frontend-review-pi"], "complexity":"LOW", "executor":"shell", "role":"closeout", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.43.0-next.1",
3
+ "version": "0.43.0-next.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -78,9 +78,12 @@
78
78
  "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
79
79
  "@earendil-works/pi-ai": "0.83.0",
80
80
  "@earendil-works/pi-coding-agent": "0.83.0",
81
+ "@xterm/addon-fit": "^0.11.0",
82
+ "@xterm/xterm": "^6.0.0",
81
83
  "commander": "^12.1.0",
82
84
  "katex": "^0.16.47",
83
85
  "mermaid": "^11.16.1",
86
+ "node-pty": "^1.2.0-beta.15",
84
87
  "rehype-highlight": "^7.0.2",
85
88
  "rehype-katex": "^7.0.1",
86
89
  "remark-math": "^6.0.0",
@@ -90,8 +93,8 @@
90
93
  "zod": "^3.25.76"
91
94
  },
92
95
  "optionalDependencies": {
93
- "@cursor/sdk": "^1.0.7",
94
- "@agent-context/sift": "0.0.1-alpha.7"
96
+ "@agent-context/sift": "0.0.1-alpha.7",
97
+ "@cursor/sdk": "^1.0.7"
95
98
  },
96
99
  "devDependencies": {
97
100
  "@remixicon/react": "^4.9.0",
@@ -35,15 +35,15 @@ canonical targets and do not authorize additional discovery.
35
35
 
36
36
  ## Implementation Discipline
37
37
 
38
- - Work test-first where behavior changes. Preserve unrelated files and stay inside
38
+ - Implement the planned requirements without a test-first stage. Preserve existing tests and unrelated files; stay inside
39
39
  `allowedPaths` and `writeSet`.
40
40
  - Implement every requirement outcome, interaction, and applicable UI state
41
41
  assigned to the writer. Keep the real request path enabled by default and use
42
42
  only the approved Mock/API strategy.
43
43
  - Do not add dependencies unless the contract approves them. Never relax lint,
44
44
  type, test, or build configuration; never add `.skip` or `.only`.
45
- - Run only the focused checks already frozen by the task. Leave full verification
46
- to downstream deterministic nodes.
45
+ - Run only npm run build, frozen by this workflow. Do not add or run unit tests;
46
+ testing can be added later in the independent frontend-test workflow.
47
47
 
48
48
  ## Completion Summary
49
49
 
@@ -4,16 +4,16 @@ Apply the canonical contract and the conventions visible in target files and the
4
4
  direct local dependencies. Do not reopen task sources, OpenSpec, `ai_workspace`, or
5
5
  unrelated repository areas to discover new rules or redesign approved decisions.
6
6
 
7
- - Work test-first when behavior changes and preserve unrelated behavior.
7
+ - Implement requirement coverage and preserve existing tests and unrelated behavior.
8
8
  - Reuse existing public helpers, APIs, components, tokens, schemas, and test
9
9
  patterns that are directly available from the bounded target surface.
10
10
  - Keep rendering, transitions, validation, request lifecycle, errors, and side
11
- effects testable; cover relevant races and boundaries.
11
+ effects clear; defer race and boundary tests to frontend-test.
12
12
  - Keep real API behavior enabled by default. Mock activation must follow the
13
13
  approved strategy, remain explicit and dev/test-only, and never import test mocks
14
14
  into production code.
15
15
  - Trace fixtures to the approved API/schema evidence and never store secrets.
16
16
  - Do not add unapproved dependencies, relax lint/type/test/build configuration,
17
17
  add `.skip`/`.only`, comment out real requests, or write outside `writeSet`.
18
- - Run only frozen focused checks and report their real status. Downstream shell
18
+ - Run only npm run build and report its real status. Downstream shell
19
19
  verification remains authoritative.
@@ -15,7 +15,8 @@ language. Do not inspect implementation files or choose components, routes, Mock
15
15
  mechanisms, dependencies, or target code as requirement facts.
16
16
 
17
17
  Submit confirmed facts incrementally with the contract `record_*` tools and finish
18
- with exactly one `finalize_contract`. Start recording as soon as a requirement or
18
+ with one successful `finalize_contract`. Complete each input scope with
19
+ `complete_contract_scope` after all its decisions; correct rejected calls and retry. Start recording as soon as a requirement or
19
20
  constraint is confirmed; do not accumulate the whole contract in one response.
20
21
 
21
22
  Missing product semantics may be explicit assumptions only when they do not change