@tea-agent/loop-agent 0.43.0-next.18 → 0.43.0-next.19

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 (161) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +60 -2308
  3. package/README.md +7 -0
  4. package/dist/application/task-lifecycle/advance.js +38 -7
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/commands/init.js +13 -9
  7. package/dist/commands/task-advance.js +32 -0
  8. package/dist/executors/dag-pi-executor.js +1615 -188
  9. package/dist/executors/pi-executor.js +85 -3
  10. package/dist/executors/pi-sdk-executor.js +18 -0
  11. package/dist/executors/shell-executor.js +92 -7
  12. package/dist/shared/backend-dogfood-preflight.js +47 -0
  13. package/dist/shared/dag-failure-category.js +3 -0
  14. package/dist/shared/operator/capabilities.js +180 -0
  15. package/dist/task/config-types.js +29 -0
  16. package/dist/task/contract/project.js +9 -0
  17. package/dist/task/contract/schema.js +17 -0
  18. package/dist/task/source-prepare/build-draft.js +60 -0
  19. package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
  20. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +24 -16
  21. package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
  22. package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
  23. package/dist/worker/console/chat/pi-runtime.js +86 -27
  24. package/dist/worker/console/chat/routes.js +19 -0
  25. package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
  26. package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
  27. package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
  28. package/dist/worker/console/chat/session-mode.js +7 -3
  29. package/dist/worker/console/chat/session-store.js +42 -9
  30. package/dist/worker/console/chat/tool-preview.js +115 -0
  31. package/dist/worker/console/chat/turn-order.js +13 -0
  32. package/dist/worker/console/chat/turn-process.js +32 -30
  33. package/dist/worker/console/operator-actions.js +50 -0
  34. package/dist/worker/console/prd-intake-bridge.js +54 -1
  35. package/dist/worker/console/scheduled-goal-host.js +98 -0
  36. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  37. package/dist/worker/console/server.js +15 -0
  38. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_UDknhR.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  39. package/dist/worker/console/static/assets/{arc-D6hU0drN.js → arc-eoQiMvuk.js} +1 -1
  40. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DmVikdRK.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  41. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DydABCRg.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  42. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DrXKC2Jl.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  43. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  44. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D2-qFxr7.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  45. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ChfeF41P.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  46. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Drwrz55x.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  47. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CAQGErL7.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  48. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DHO38w5U.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  49. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-EYIN0EOI.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  50. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CrFLKqkT.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  51. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMchffoo.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  52. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DaSvtKQa.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DcW8W9Qj.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  54. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  55. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  56. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BUirrMEi.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  57. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXzO8iXR.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  58. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DAtgaxIn.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  59. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C1re9Noh.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  60. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-N8Kj08J9.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  61. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-D6BpSLs3.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  62. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C2Vlgnl4.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  63. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMbqtVLS.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  64. package/dist/worker/console/static/assets/{diagram-UB23O5K3-A770Eb-0.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  65. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-LV2w_2pT.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  66. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-bwqf56ah.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  67. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BnVtHhZh.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  68. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BGVToEqc.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  69. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DL2l7Vne.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  70. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  71. package/dist/worker/console/static/assets/{index-B_V4wvXs.js → index-Cdkvw_H6.js} +97 -97
  72. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWIpubOx.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  73. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DjYBD8vv.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  74. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C0_mBaHb.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  75. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cy71zbX6.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  76. package/dist/worker/console/static/assets/{linear-Dt3_w3Vn.js → linear-BDZ9riWi.js} +1 -1
  77. package/dist/worker/console/static/assets/{mermaid.core-DnNOlfEP.js → mermaid.core-7pKqYtpZ.js} +5 -5
  78. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DAbTspwq.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  79. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-NNiM141p.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  80. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CN80Z1Do.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  81. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  82. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BlWnC79M.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  83. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BrEu4P_e.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  84. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bt70igBr.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  85. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C8DQR-r9.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  86. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DEMinJAT.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  87. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-DDdDBzQc.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  88. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  89. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-G2wcBs3T.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  90. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  91. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dzn4g2gJ.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  92. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-5v2rP9oO.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  93. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-B4EOx7ew.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  94. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQ1v8eYP.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  95. package/dist/worker/console/static/index.html +2 -2
  96. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  97. package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
  98. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  99. package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -5
  100. package/dist/worker/console/workspace-context.js +11 -0
  101. package/dist/worker/observe/static/operator-chrome.js +3 -1
  102. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  103. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  104. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  105. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  106. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  107. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  108. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  109. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  110. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  111. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  112. package/dist/workflows/dag/frontend-closeout.js +4 -2
  113. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  114. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  115. package/dist/workflows/dag/frontend-implementation-contract.js +365 -8
  116. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  117. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  118. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  119. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  120. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  121. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  122. package/dist/workflows/dag/frontend-review-context.js +43 -70
  123. package/dist/workflows/dag/frontend-risk.js +92 -10
  124. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  125. package/dist/workflows/dag/frontend-shape.js +11 -55
  126. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  127. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  128. package/dist/workflows/dag/frontend-verification-trace.js +11 -2
  129. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  130. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  131. package/dist/workflows/dag/init-hybrid.js +182 -185
  132. package/dist/workflows/dag/lifecycle.js +7 -2
  133. package/dist/workflows/dag/node-execution.js +48 -13
  134. package/dist/workflows/dag/rerun-plan.js +10 -0
  135. package/dist/workflows/dag/rerun-task.js +77 -1
  136. package/dist/workflows/dag/retry-policy.js +18 -0
  137. package/dist/workflows/dag/scheduler.js +2 -4
  138. package/dist/workflows/dag/types.js +23 -0
  139. package/docs/README.md +1 -0
  140. package/docs/init-surface.manifest.json +1 -0
  141. package/docs/operations/README.md +2 -0
  142. package/docs/templates/README.md +1 -1
  143. package/docs/templates/agent-dag.schema.json +2 -2
  144. package/docs/templates/backend-test-dag.json +14 -10
  145. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  146. package/docs/templates/frontend-implementation-dag.json +8 -9
  147. package/package.json +6 -3
  148. package/skills/frontend-bounded-implement/SKILL.md +3 -4
  149. package/skills/frontend-design-review/SKILL.md +7 -12
  150. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  151. package/skills/frontend-plan/SKILL.md +8 -18
  152. package/skills/frontend-plan/references/decision-contract.md +19 -26
  153. package/skills/frontend-review/SKILL.md +21 -25
  154. package/skills/frontend-review/references/review-findings.md +48 -24
  155. package/dist/worker/console/static/assets/channel-Drecd94a.js +0 -1
  156. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +0 -1
  157. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +0 -1
  158. package/dist/worker/console/static/assets/index-DcudonhZ.css +0 -1
  159. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +0 -1
  160. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +0 -8
  161. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
@@ -712,6 +712,7 @@ export async function runDagStatus(repoRoot, rawArgs) {
712
712
  export function parseDagDoctorArgs(args) {
713
713
  let runId;
714
714
  let markdown = false;
715
+ let json = false;
715
716
  for (let i = 0; i < args.length; i += 1) {
716
717
  const arg = args[i];
717
718
  if (arg === undefined)
@@ -729,6 +730,10 @@ export function parseDagDoctorArgs(args) {
729
730
  throw new Error("dag doctor --run-id requires a value");
730
731
  continue;
731
732
  }
733
+ if (arg === "--json") {
734
+ json = true;
735
+ continue;
736
+ }
732
737
  if (arg === "--markdown") {
733
738
  markdown = true;
734
739
  continue;
@@ -738,7 +743,7 @@ export function parseDagDoctorArgs(args) {
738
743
  }
739
744
  throw new Error(`unexpected positional argument: ${arg}`);
740
745
  }
741
- return { runId, markdown };
746
+ return { runId, markdown, json };
742
747
  }
743
748
  function findDoctorFailureNode(state) {
744
749
  if (state.pausedByNodeId) {
@@ -847,7 +852,7 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
847
852
  export async function runDagDoctor(repoRoot, rawArgs) {
848
853
  const parsed = parseDagDoctorArgs(rawArgs);
849
854
  if (parsed.runId) {
850
- if (parsed.markdown) {
855
+ if (parsed.markdown && !parsed.json) {
851
856
  console.log(await formatDagDoctorMarkdown(repoRoot, parsed.runId));
852
857
  return;
853
858
  }
@@ -15,7 +15,7 @@ import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./p
15
15
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
16
16
  import { materializeDeclaredArtifactFacts } from "./artifact-bindings.js";
17
17
  import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
18
- import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, OUTPUT_LIMIT_RETRY_CATEGORY, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
18
+ import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isProviderAvailabilityFailureCategory, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, OUTPUT_LIMIT_RETRY_CATEGORY, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
19
19
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
20
20
  import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
21
21
  import { getStructuredContractValidator } from "./contract-output-registry.js";
@@ -568,7 +568,7 @@ export function renderFrontendPlanInputContext(input) {
568
568
  "Do not read upstream artifacts, task sources, or repository files. If this input cannot support a decision, record a genuine evidence gap.",
569
569
  "</frontend_plan_input>",
570
570
  "<plan_review_checklist>",
571
- "The design reviewer re-runs these exact checks on the committed facts; satisfy every line before finalize_plan:",
571
+ "Satisfy these checks in the committed facts before the runtime compiles and finalizes the Plan:",
572
572
  ...checklistLines,
573
573
  "</plan_review_checklist>",
574
574
  ].join("\n");
@@ -614,6 +614,10 @@ export async function buildFrontendPlanInputContext(runDir, componentSourceCitat
614
614
  export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
615
615
  if (attemptNumber <= 1)
616
616
  return basePrompt;
617
+ // Match the executor segmented Plan identity; custom nodes retain their terminal.
618
+ const planCompletion = task.id === FRONTEND_PLAN_NODE_ID
619
+ ? "Stop after committing the required facts. The runtime compiles and finalizes the committed ledger automatically; only repair facts reported by its validation."
620
+ : "Then call finalize_plan exactly once.";
617
621
  // Context overflow is a transport/session failure, not a plan-protocol
618
622
  // failure. It must take precedence over the plan retry ladder so every
619
623
  // retry, including a repeated plan overflow, starts from the compact
@@ -656,8 +660,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
656
660
  previousProtocolReason) {
657
661
  // The frontend plan node's compile authority is the committed typed
658
662
  // ledger, not a fenced JSON text artifact: its retry guidance must
659
- // direct the model to re-commit corrected record_* facts and
660
- // finalize_plan. The legacy full-contract JSON guidance below applies
663
+ // direct the model to re-commit corrected record_* facts and respect
664
+ // terminal ownership. The full-contract JSON guidance below applies
661
665
  // only to nodes whose authority is still a text contract artifact.
662
666
  if (task.structuredContractOutput.schemaId ===
663
667
  "frontend-implementation-contract-plan-patch-v1") {
@@ -673,7 +677,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
673
677
  "<retry_instruction>",
674
678
  "Previous plan ledger facts failed canonical contract validation:",
675
679
  previousProtocolReason,
676
- "Fix the reported violations by re-committing corrected record_* facts and calling finalize_plan exactly once. The committed typed ledger is the only compile authority.",
680
+ "Fix the reported violations by re-committing corrected record_* facts. The committed typed ledger is the only compile authority.",
681
+ planCompletion,
677
682
  "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
678
683
  ...frontendPlanValidationRetryGuidance(previousProtocolReason),
679
684
  ...splitGuidance,
@@ -711,7 +716,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
711
716
  "",
712
717
  "<retry_instruction>",
713
718
  "Previous attempt was truncated by the provider (stopReason=length) before the plan facts were fully committed.",
714
- "Re-commit the missing record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority. Do NOT re-read contract/scout outputs or source files — use the facts already in context. Commit record_* facts one tool call per message, then finalize_plan immediately.",
719
+ "Re-commit the missing record_* facts; the committed typed ledger is the only compile authority. Do NOT re-read contract/scout outputs or source files — use the facts already in context. Commit record_* facts one tool call per message.",
720
+ planCompletion,
715
721
  "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
716
722
  "</retry_instruction>",
717
723
  ].join("\n");
@@ -732,7 +738,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
732
738
  "",
733
739
  "<retry_instruction>",
734
740
  "Frontend plan retry ladder step: compact-terminal-first.",
735
- "Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
741
+ "Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts and fill the missing required facts.",
742
+ planCompletion,
736
743
  "Keep the existing committed ledger intact; do not re-derive already committed facts.",
737
744
  "</retry_instruction>",
738
745
  ].join("\n");
@@ -743,7 +750,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
743
750
  "",
744
751
  "<retry_instruction>",
745
752
  "Frontend plan retry ladder step: bounded-tool-only.",
746
- "Reduce reasoning and wall-clock time. Use only read / fact / terminal tools and only for the necessary missing facts, then call finalize_plan exactly once.",
753
+ "Reduce reasoning and wall-clock time. Use only the tools exposed for this session and only for the necessary missing facts.",
754
+ planCompletion,
747
755
  "Do not expand scope or re-derive already committed facts.",
748
756
  "</retry_instruction>",
749
757
  ].join("\n");
@@ -754,7 +762,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
754
762
  "",
755
763
  "<retry_instruction>",
756
764
  "Frontend plan retry ladder step: backup-model.",
757
- "A backup provider/model route was selected after repeated non-converging failures. Re-derive only the missing committed facts, then call finalize_plan exactly once; do not repeat the failed strategy.",
765
+ "A backup provider/model route was selected after repeated non-converging failures. Re-derive only the missing committed facts; do not repeat the failed strategy.",
766
+ planCompletion,
758
767
  "</retry_instruction>",
759
768
  ].join("\n");
760
769
  }
@@ -793,7 +802,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
793
802
  "",
794
803
  "<retry_instruction>",
795
804
  "Previous plan attempt issued too many read-only tool calls (read/grep/ls/find) and blew up the context window. Trust the upstream frontend-contract-pi typed requirement facts and frontend-scout-pi target surface already provided — do NOT re-read contract/scout stdout, PRD/source files, or component sources you already inspected.",
796
- "Minimize discovery reads: only read what you genuinely need, once. Commit record_plan_requirement / record_plan_verification_target / record_* facts directly from the facts already in context (one tool call per message), then call finalize_plan exactly once.",
805
+ "Minimize discovery reads: only read what you genuinely need, once. Commit the required record_* facts directly from the facts already in context (one tool call per message).",
806
+ planCompletion,
797
807
  "</retry_instruction>",
798
808
  ].join("\n");
799
809
  }
@@ -917,7 +927,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
917
927
  "",
918
928
  "<retry_instruction>",
919
929
  "Previous attempt exceeded the structured output size limit.",
920
- "Re-commit corrected record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority.",
930
+ "Re-commit corrected record_* facts; the committed typed ledger is the only compile authority.",
931
+ planCompletion,
921
932
  "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
922
933
  "</retry_instruction>",
923
934
  ].join("\n");
@@ -1713,6 +1724,20 @@ export async function executeDagNode(input) {
1713
1724
  ? result.stderr.slice(markerIndex, markerIndex + 4_000).trim()
1714
1725
  : `backend-test Markdown plan attempt failed with ${result.failureCategory}`;
1715
1726
  }
1727
+ // Decision-path Plan bridge failures surface from the executor as
1728
+ // invalid-output (the derived relationship patch failed pre-validation),
1729
+ // NOT through the R1 structured self-check (which only runs when
1730
+ // result.ok). Capture the bridge diagnostics into previousProtocolReason
1731
+ // so the next attempt prompt tells the model exactly which decision facts
1732
+ // to repair instead of replaying the identical under-declared session.
1733
+ if (task.id === FRONTEND_PLAN_NODE_ID &&
1734
+ !result.ok &&
1735
+ result.failureCategory === "invalid-output" &&
1736
+ /frontend decision → relationship patch failed|frontend decision finalize failed/.test(result.stderr ?? "")) {
1737
+ previousProtocolReason = (result.stderr ?? "")
1738
+ .slice(-4_000)
1739
+ .trim();
1740
+ }
1716
1741
  if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
1717
1742
  const rawText = canonicalNodeOutput(result);
1718
1743
  if (rawText.trim().length > 0) {
@@ -1905,13 +1930,23 @@ export async function executeDagNode(input) {
1905
1930
  frontendPriorFingerprints.push(fingerprint);
1906
1931
  frontendPlanRetryStep = nextStep;
1907
1932
  if (nextStep === "unsupported-provider-capability") {
1933
+ // Reachability first: when the failed attempts never reached model
1934
+ // reasoning (gateway 5xx / rate limit / quota / network), the
1935
+ // ladder's fail-closed category would read as a capability mismatch
1936
+ // and send the diagnosis the wrong way. Keep the outage visible in
1937
+ // the node's category and explain why.
1938
+ const providerOutage = isProviderAvailabilityFailureCategory(result.failureCategory);
1908
1939
  result = {
1909
1940
  ...result,
1910
1941
  ok: false,
1911
- failureCategory: "unsupported-provider-capability",
1942
+ failureCategory: providerOutage
1943
+ ? "upstream-unavailable"
1944
+ : "unsupported-provider-capability",
1912
1945
  stderr: [
1913
1946
  result.stderr,
1914
- "frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
1947
+ providerOutage
1948
+ ? "frontend plan retry ladder: attempts failed on provider reachability (5xx / rate limit / quota / network), so this is an upstream outage, not a provider capability mismatch"
1949
+ : "frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
1915
1950
  ]
1916
1951
  .filter(Boolean)
1917
1952
  .join("\n"),
@@ -1,3 +1,4 @@
1
+ import { isCanonicalFrontendShapeDag } from "./frontend-shape-facts.js";
1
2
  import { createHash } from "node:crypto";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { z } from "zod";
@@ -1348,6 +1349,15 @@ export async function evaluateDagRerunPlan(input) {
1348
1349
  }
1349
1350
  }
1350
1351
  const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, {
1352
+ // The canonical frontend template tails into read-only artifact shells
1353
+ // (review-context, closeout) that carry no commands. Without artifact
1354
+ // continuation admission every from-node restart of that template was
1355
+ // blocked as `restart-subgraph-contains-unsafe-shell`, so a frontend run
1356
+ // could only ever be re-verified by repeating the whole chain (smoke
1357
+ // r27-r36: ~30 min per iteration instead of ~6).
1358
+ ...(isCanonicalFrontendShapeDag(new Map(input.parentSpec.tasks.map((task) => [task.id, task])))
1359
+ ? { artifactContinuation: true }
1360
+ : {}),
1351
1361
  ...(transportWriterRestart && effectiveFromNodeId
1352
1362
  ? { allowedTransportWriterNodeId: effectiveFromNodeId }
1353
1363
  : {}),
@@ -1,4 +1,4 @@
1
- import { access, readFile } from "node:fs/promises";
1
+ import { access, mkdir, readFile } from "node:fs/promises";
2
2
  import { createHash } from "node:crypto";
3
3
  import path from "node:path";
4
4
  import { generateTaskDagUseCase } from "../../application/dag/generate-task-dag.js";
@@ -8,6 +8,7 @@ import { stageRecoveryChild } from "./frontend-recovery-run.js";
8
8
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
9
9
  import { DAG_RUNS_DIR, isTerminalDagRunStatus, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
10
10
  import { deriveDagRerunFeedback } from "./rerun-feedback.js";
11
+ import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
11
12
  import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, } from "./recovery-lease.js";
12
13
  import { parseDagSpec, } from "./types.js";
13
14
  export const RERUN_TASK_LINEAGE_REL_PATH = ".runtime/rerun-task-lineage.json";
@@ -398,6 +399,81 @@ async function executeFrontendRecoveryContinuation(input) {
398
399
  if (!locatedChild || locatedChild.runDir !== staged.childRunDir) {
399
400
  throw new Error(`frontend recovery child lifecycle drift: ${staged.childRunId}`);
400
401
  }
402
+ // D (local design repair): publish a read-only snapshot of the parent's
403
+ // committed decision facts into the child run. The child plan node replays
404
+ // these through its own durable tools (normal commit entry, child binding)
405
+ // and the model only corrects the rows the design review flagged; the
406
+ // parent ledger is never rewritten and the finalize terminal is not
407
+ // inherited. Best-effort: a parent without plan facts plans fresh.
408
+ try {
409
+ const parentPlanLedger = path.join(located.runDir, "frontend-plan-pi", "plan-decision-facts.jsonl");
410
+ const parentRaw = await readFile(parentPlanLedger, "utf8");
411
+ const parentRecords = await readTypedEventStoreFromJsonl(parentPlanLedger);
412
+ // Collapse superseded records first: the parent ledger is append-only
413
+ // (replace:true appends a corrected row), and replaying both the original
414
+ // and its correction would trip FACT_IDENTITY_CONFLICT in the child.
415
+ const { collapseDecisionEntriesByKind } = await import("./frontend-plan-decision-contract.js");
416
+ const byKind = new Map();
417
+ for (const record of parentRecords) {
418
+ const kind = String(record.fact.kind);
419
+ if (record.phase !== "committed" || kind === "finalize_decision")
420
+ continue;
421
+ const entry = record.fact.entry;
422
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
423
+ continue;
424
+ const bucket = byKind.get(kind) ?? [];
425
+ bucket.push(entry);
426
+ byKind.set(kind, bucket);
427
+ }
428
+ let parentCanonicalSha256;
429
+ try {
430
+ const parentCanonical = await readFile(path.join(located.runDir, "contracts", "frontend-implementation-contract.json"), "utf8");
431
+ parentCanonicalSha256 = createHash("sha256").update(parentCanonical).digest("hex");
432
+ }
433
+ catch {
434
+ // Parent canonical unavailable → the identical-plan guard is skipped.
435
+ }
436
+ // Carry the design findings that blocked the parent run: the child's
437
+ // finalize runs a per-finding closure check against them.
438
+ let designFindings = [];
439
+ try {
440
+ const designFacts = await readTypedEventStoreFromJsonl(path.join(located.runDir, "frontend-design-review-pi", "design-typed-facts.jsonl"));
441
+ for (const record of designFacts) {
442
+ const fact = record.fact;
443
+ if (record.phase !== "committed" || fact.kind !== "design-finding")
444
+ continue;
445
+ const finding = (fact.finding ?? {});
446
+ designFindings.push({
447
+ severity: typeof finding.severity === "string" ? finding.severity : "Important",
448
+ issue: typeof finding.issue === "string" ? finding.issue : "",
449
+ requiredChange: typeof finding.requiredChange === "string" ? finding.requiredChange : "",
450
+ });
451
+ }
452
+ }
453
+ catch {
454
+ // No readable design findings → the closure check has no assertions.
455
+ }
456
+ const snapshot = {
457
+ schemaVersion: 1,
458
+ schemaId: "frontend-plan-parent-decision-snapshot-v1",
459
+ parentRunId: input.parentRunId,
460
+ parentLedgerSha256: createHash("sha256").update(parentRaw).digest("hex"),
461
+ ...(parentCanonicalSha256 ? { parentCanonicalSha256 } : {}),
462
+ designFindings,
463
+ facts: [...byKind.entries()].flatMap(([kind, entries]) => collapseDecisionEntriesByKind(kind, entries).map((entry) => ({
464
+ kind,
465
+ entry,
466
+ }))),
467
+ };
468
+ await mkdir(path.join(locatedChild.runDir, "frontend-plan-pi"), {
469
+ recursive: true,
470
+ });
471
+ await writeJsonAtomic(path.join(locatedChild.runDir, "frontend-plan-pi", "parent-decision-snapshot.json"), snapshot);
472
+ }
473
+ catch {
474
+ // No parent plan ledger (or unreadable) → the child plans fresh,
475
+ // exactly like a run without recovery lineage.
476
+ }
401
477
  const childSpec = await readDagRunSpec(locatedChild.runDir);
402
478
  const childState = await readDagRunState(locatedChild.runDir);
403
479
  let runSummary;
@@ -364,6 +364,24 @@ export function computeBackoffDelayMs(attemptNumber, policy) {
364
364
  const raw = policy.initialDelayMs * 2 ** exponent;
365
365
  return Math.min(raw, policy.maxDelayMs);
366
366
  }
367
+ /**
368
+ * Provider-reachability failure categories: the attempt never reached model
369
+ * reasoning, so no capability conclusion may be drawn from it. Used to keep the
370
+ * plan retry ladder from relabelling an upstream outage as
371
+ * `unsupported-provider-capability` (2026-09-11: the gateway returned 503 while
372
+ * r18/r19 reported the vague capability category).
373
+ */
374
+ export const PROVIDER_AVAILABILITY_FAILURE_CATEGORIES = [
375
+ "unavailable",
376
+ "upstream-unavailable",
377
+ "network",
378
+ "rate-limit",
379
+ "quota",
380
+ ];
381
+ export function isProviderAvailabilityFailureCategory(category) {
382
+ return (category !== undefined &&
383
+ PROVIDER_AVAILABILITY_FAILURE_CATEGORIES.includes(category));
384
+ }
367
385
  /**
368
386
  * A+B (AC-001/AC-005): project raw executor signals to the canonical 7-value
369
387
  * protocol failure reason. Specific signals win first, then the existing
@@ -115,7 +115,7 @@ export const FRONTEND_WRITER_NODE_IDS = [
115
115
  ];
116
116
  /**
117
117
  * Run-relative artifact location read by the frontend writer admission gate.
118
- * M8 re-pointed the legacy prewrite-result source at the new
118
+ * M8 points the admission source at the canonical
119
119
  * frontend-writer-admission-result.json artifact (written by the
120
120
  * frontend-writer-admission-shell); the exported name is retained because
121
121
  * runner.ts / node-execution.ts reference it as the admission evidence source.
@@ -125,9 +125,7 @@ export { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT };
125
125
  /**
126
126
  * Read and validate the writer admission result artifact.
127
127
  * Fail-closed: a missing file or invalid payload returns ok:false and never
128
- * throws. This is the M8 write authorization source (replaces the legacy
129
- * frontend-prewrite-result-v1 for dispatch; the legacy artifact remains
130
- * produced only by the shadow `runFrontendPrewriteGate` library export).
128
+ * throws. This is the M8 write authorization source for dispatch.
131
129
  */
132
130
  export async function readFrontendPrewriteResult(runDir) {
133
131
  const artifactPath = path.join(runDir, FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT);
@@ -449,6 +449,29 @@ export const dagFrontendDesignPolicySchema = z
449
449
  outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
450
450
  requireSourceFreshness: z.literal(true),
451
451
  implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
452
+ /** Generation-frozen requirement→file ownership. Violations are
453
+ * machine-rejected at plan finalize (smoke r35: prose-only ownership
454
+ * rules were ignored by the planner and re-violated by recovery). */
455
+ requirementOwnership: z
456
+ .array(z
457
+ .object({
458
+ requirementIds: z.array(z.string().min(1)).min(1),
459
+ implementationFiles: z.array(z.string().min(1)).optional(),
460
+ verificationFiles: z.array(z.string().min(1)).optional(),
461
+ })
462
+ .strict())
463
+ .optional(),
464
+ /** Generation-frozen test capability boundary composed into the
465
+ * canonical dependencyPolicy by the runtime. */
466
+ capabilityBoundary: z
467
+ .object({
468
+ allowed: z.array(z.string().min(1)).optional(),
469
+ forbidden: z.array(z.string().min(1)).optional(),
470
+ })
471
+ .strict()
472
+ .optional(),
473
+ /** Generation-frozen interaction id vocabulary (plan finalize check). */
474
+ interactionIds: z.array(z.string().min(1)).optional(),
452
475
  /** Generation-frozen roots that bound custom normative directories. */
453
476
  openspecSpecRoots: z
454
477
  .array(z.string().refine((root) => isValidFrontendSpecRoot(root), "openspec spec root must be a normalized repo-relative directory"))
package/docs/README.md CHANGED
@@ -80,5 +80,6 @@
80
80
  | `reports/` | 验证、dogfood、merge、init evolution 与历史 handoff 证据 |
81
81
  | `skills/` | repo-local skill registry |
82
82
  | `templates/` | 可复用任务、报告、DAG 和 init 模板 |
83
+ | `fixtures/` | 文档与前端 DAG 验收使用的固定输入 |
83
84
 
84
85
  站上使用者文档在 `../website/docs/`;源仓治理事实以本目录、`AGENTS.md`、`harness.json`、`CHANGELOG.md` 和检查脚本为准。
@@ -364,6 +364,7 @@
364
364
  "description": "Documentation and user-facing guidance changes that should be considered but should not slow normal iteration.",
365
365
  "patterns": [
366
366
  "CHANGELOG.md",
367
+ "CHANGELOG/**",
367
368
  "website/docs/**",
368
369
  "docs/*.md",
369
370
  "docs/architecture/**"
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## 入口
6
6
 
7
+ - [`scheduled-goal-host.md`](scheduled-goal-host.md):Console Chat 预约宿主、服务环境、状态判读与清理。
8
+
7
9
  - [`local-development-environment.md`](local-development-environment.md):本地环境、Windows 差异和常见排障。
8
10
  - [`branch-merge-guideline.md`](branch-merge-guideline.md):按风险选择合并模式并留下 source-SHA 证据。
9
11
  - [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及发布通道。
@@ -30,7 +30,7 @@
30
30
 
31
31
  ## Backend-test
32
32
 
33
- - `backend-test-dag.json` — backend-test DAG 模板;其中 `generate-backend-md-cases-pi` 是唯一允许 `writer-empty-diff` 重试的 writer(总共两次,仅限 post-write-guard attribution 确认的空 diff);N2/N5 合同限定 Scenario Partition 必须有源有限域。
33
+ - `backend-test-dag.json` — backend-test DAG 模板;由 runtime builder 生成,Module Index 的 8 列、separator、单一 resolved Markdown Path 和 Split Reason 集合以 `backend-test-plan-protocol.ts` 为真源并由 drift test 校验;其中 `generate-backend-md-cases-pi` 是唯一允许 `writer-empty-diff` 重试的 writer(总共两次,仅限 post-write-guard attribution 确认的空 diff);N2/N5 合同限定 Scenario Partition 必须有源有限域。
34
34
  - `backend-test-dag.classify.prompt.md`、`backend-test-dag.generate-pytest.prompt.md`、`backend-test-dag.review-cases.prompt.md`、`backend-test-dag.retrospect.prompt.md` — 分类、生成、审查和复盘提示。
35
35
  - `backend-test-analysis.schema.json`、`backend-test-execution.schema.json`、`backend-test-result.schema.json`、`backend-test-case-manifest.schema.json` — 分析、执行、结果与用例清单 schema。
36
36
 
@@ -466,9 +466,9 @@
466
466
  "retryCategories": {
467
467
  "type": "array",
468
468
  "items": {
469
- "enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-limit", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
469
+ "enum": ["timeout", "network", "rate-limit", "unavailable", "upstream-unavailable", "empty-output", "output-limit", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
470
470
  },
471
- "default": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-limit"],
471
+ "default": ["timeout", "network", "rate-limit", "unavailable", "upstream-unavailable", "empty-output", "output-limit"],
472
472
  "description": "Failure categories eligible for retry. quota/auth/write-guard are never eligible. invalid-output is allowed only on explicit structured/protocol policies; writer-empty-diff and incomplete-write-set are reserved for requireChangedFiles generation/completeness writers; writer-clean-timeout is reserved for standard bounded writers after explicit zero-write-tool-call and empty-diff evidence."
473
473
  }
474
474
  }