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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +129 -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 +1214 -584
  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 +5 -2
  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 +143 -0
  21. package/dist/worker/console/chat/browser-routes.js +148 -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 +78 -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 +213 -1181
  33. package/dist/worker/console/chat/routes.js +212 -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 +48 -0
  40. package/dist/worker/console/chat/session-mode.js +218 -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-D_fNbWc_.js} +1 -1
  50. package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-_HuqLW6m.js} +1 -1
  51. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-Djy2qY1m.js} +1 -1
  52. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-bB-1TSyY.js} +1 -1
  53. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-C5Hmghrz.js} +1 -1
  54. package/dist/worker/console/static/assets/channel-C0A6UEIa.js +1 -0
  55. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-Duyl39oy.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-x6mdnSyf.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-WlBuFj7W.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CTF429HX.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-Dwd6r-SF.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-C-Br82D1.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-CuGp0DeE.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-CJhFX6Lh.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-DpGOa_jT.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-DXJaTynd.js} +1 -1
  65. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +1 -0
  66. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +1 -0
  67. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-CRBZWjE4.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-BdXcxtlb.js} +1 -1
  69. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-07UKsOts.js} +1 -1
  70. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-D38p9__S.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-nSiBNNjH.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-BFOTot5D.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-DypKaZ6k.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-B6yoOQeW.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-BHYS6viX.js} +1 -1
  76. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-D2VD5Snj.js} +1 -1
  77. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-E98mWxN1.js} +1 -1
  78. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-CT1zxhqP.js} +1 -1
  79. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-6GcngNcy.js} +1 -1
  80. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-DCL6NvKe.js} +1 -1
  81. package/dist/worker/console/static/assets/index-24jp3aBh.js +468 -0
  82. package/dist/worker/console/static/assets/index-CY6y6I8M.css +1 -0
  83. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-C0627e-9.js} +1 -1
  84. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js} +1 -1
  85. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DPDPv9HL.js} +1 -1
  86. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-D3OHdWiO.js} +1 -1
  87. package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-DI88zD7n.js} +1 -1
  88. package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-p2l8IrWY.js} +5 -5
  89. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-B1bAX-oS.js} +1 -1
  90. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-DwOnWxdD.js} +1 -1
  91. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-xRkTXnJC.js} +1 -1
  92. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-_9UC5Zio.js} +1 -1
  93. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-CgEUirWJ.js} +1 -1
  94. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-Czbpwiyz.js} +1 -1
  95. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-_j7_KiqY.js} +1 -1
  96. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-BNJa6Hyi.js} +1 -1
  97. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-DedpKtOc.js} +1 -1
  98. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-BBvthr7c.js} +1 -1
  99. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +1 -0
  100. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-Ds7umgEM.js} +2 -2
  101. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +8 -0
  102. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-CKa-p1nF.js} +1 -1
  103. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-LoUNR56o.js} +1 -1
  104. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-Bqbl6ym7.js} +1 -1
  105. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-B8dUg4lf.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-design-policy.js +2 -1
  158. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  159. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  160. package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
  161. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  162. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  163. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  164. package/dist/workflows/dag/frontend-plan-render.js +10 -3
  165. package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
  166. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  167. package/dist/workflows/dag/frontend-recovery-run.js +4 -0
  168. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  169. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  170. package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
  171. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  172. package/dist/workflows/dag/frontend-shape.js +30 -1
  173. package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
  174. package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
  175. package/dist/workflows/dag/init-hybrid.js +16 -10
  176. package/dist/workflows/dag/node-execution.js +38 -155
  177. package/dist/workflows/dag/prompt.js +4 -0
  178. package/dist/workflows/dag/rerun-plan.js +7 -1
  179. package/dist/workflows/dag/runner.js +26 -1
  180. package/dist/workflows/dag/types.js +6 -0
  181. package/docs/operations/README.md +1 -0
  182. package/docs/templates/frontend-design-contract.md +4 -4
  183. package/docs/templates/frontend-implementation-contract.schema.json +34 -2
  184. package/docs/templates/frontend-implementation-dag.json +5 -5
  185. package/package.json +6 -3
  186. package/skills/frontend-contract/SKILL.md +2 -1
  187. package/skills/frontend-contract/references/contract-protocol.md +19 -3
  188. package/skills/frontend-design-review/SKILL.md +12 -11
  189. package/skills/frontend-plan/SKILL.md +9 -2
  190. package/skills/frontend-plan/references/decision-contract.md +18 -5
  191. package/skills/frontend-review/SKILL.md +10 -11
  192. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  193. package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
  194. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
  195. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
  196. package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
  197. package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
  198. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
  199. 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
  ],
@@ -27,17 +27,17 @@
27
27
  "tasks": [
28
28
  {
29
29
  "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.",
30
+ "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
31
  "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
32
  },
33
33
  {
34
34
  "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.",
35
+ "outputContract":"Scout typed target-surface and design-evidence facts backed by fresh read evidence; durable receipts only. No file writes.",
36
36
  "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
37
  },
38
38
  {
39
39
  "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.",
40
+ "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.",
41
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."
42
42
  },
43
43
  {
@@ -48,7 +48,7 @@
48
48
  },
49
49
  {
50
50
  "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.",
51
+ "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.",
52
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."
53
53
  },
54
54
  {
@@ -76,7 +76,7 @@
76
76
  },
77
77
  {
78
78
  "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.",
79
+ "outputContract":"record_review_finding for individual findings; approve_review or request_review_changes provides the sole committed terminal. No file writes.",
80
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."
81
81
  },
82
82
  {
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.10",
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",
@@ -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
@@ -8,8 +8,8 @@
8
8
  source-fidelity ledger. Do not infer fragment IDs from prose.
9
9
  - Record observable outcomes, applicable UI states, runtime/environment constraints,
10
10
  non-goals, risks, and verification expectations without choosing implementation.
11
- - Classify each requirement as `explicit`, `repository-resolvable`, `assumption`, or
12
- `blocking`. Blocking facts name their owner and evidence.
11
+ - Runtime owns requirement text, source binding and disposition. Use typed questions
12
+ and constraints for uncertainties; blocked terminal facts name their owner.
13
13
 
14
14
  ## Evidence and handoff
15
15
 
@@ -29,6 +29,22 @@ enumerate or downgrade them.
29
29
 
30
30
  ## Terminal
31
31
 
32
- Commit facts in small batches and call `finalize_contract` exactly once with
32
+ Commit facts from the complete current scope, then `complete_contract_scope` only
33
+ after its evidence, constraints and source-bound deliverables are recorded. Scope
34
+ checkpoints survive interruption; confirming an ID alone is not completion.
35
+ Call `finalize_contract` with
33
36
  `ready`, `ready-with-assumptions`, or `blocked`. If the task is too large for one
34
37
  bounded writer, record a split proposal instead of broadening the contract.
38
+
39
+ A successful receipt means the fact was saved durably. Replays are idempotent;
40
+ changed facts require explicit `replace:true`. Correct rejected calls and retry,
41
+ but do not mutate after a successful terminal. Never drop tail conditions to fit.
42
+
43
+ ## Execution ownership
44
+
45
+ `record_requirement` may include `execution:{groupId,kind,summary}`. Use `behavior`,
46
+ `constraint`, or `exclusion`; reuse an existing group only when semantics agree.
47
+ Keep permission, threshold and failure-path differences separate. Do not merge
48
+ source IDs or independent acceptance conditions. Unclassified requirements remain
49
+ independent. Later scopes receive the shared group index. A group is a planning
50
+ proposal; independent design review must check every member's complete text.
@@ -9,26 +9,27 @@ references:
9
9
 
10
10
  # Frontend Design Review
11
11
 
12
- For frontend design review nodes. Read the checklist and the non-blocking contract
13
- capacity diagnostic first. Audit either the canonical contract or, when the
14
- diagnostic recommends indexed reading, its hash-bound field sections. Check the
15
- bound Mock strategy, task bounds, and traceable design evidence supplied by the
16
- upstream context. Do not rediscover task sources, search specification roots, or
17
- replace frozen evidence with nearby-code conventions. Use only exact, bound evidence
18
- paths when additional reading is necessary; never invent connector results.
12
+ Audit the canonical contract or its hash-bound indexed sections, guided by the
13
+ capacity diagnostic. Check Mock strategy, task bounds and exact bound evidence.
14
+ Do not rediscover sources, search specification roots or invent connector results.
19
15
 
20
16
  ## Authoritative Terminal
21
17
 
22
- Complete the review with exactly one committed typed tool call:
18
+ Submit findings one at a time with `record_design_finding` using stable IDs.
19
+ Use `replace:true` for revisions. The terminal automatically includes saved findings;
20
+ do not repeat their array. Correct rejected calls, then commit one successful terminal:
23
21
 
24
22
  - `approve_design` when no blocking design defect remains;
25
23
  - `request_design_changes` when a blocker exists. It must include a typed
26
- `issueCategory`, at least one `evidenceRef`, and non-empty findings.
24
+ `issueCategory`, at least one `evidenceRef`, and non-empty findings (saved or supplied).
27
25
 
28
26
  The typed terminal is the only authoritative outcome. Do not require or emit a
29
27
  `VERDICT:` line, JSON verdict, revision patch, or a second final-design-review
30
- verdict. A requested design change ends this run; the recovery controller creates
31
- any bounded follow-up run from the appropriate phase.
28
+ verdict. Requested changes end this run; recovery owns the bounded follow-up.
29
+
30
+ Check every execution group against its members' complete outcomes. Permissions,
31
+ thresholds and error paths remain independent ACs; a shared VT must prove them all.
32
+ Constraint/exclusion labels never waive source-required behavior evidence.
32
33
 
33
34
  ## Blocking Conditions
34
35