@tea-agent/loop-agent 0.39.0-beta.6 → 0.39.0-beta.7

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 (89) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/commands/client-recovery.js +3 -0
  4. package/dist/commands/task-advance.js +19 -0
  5. package/dist/executors/dag-pi-executor.js +44 -11
  6. package/dist/executors/pi-executor.js +7 -0
  7. package/dist/executors/shell-executor.js +1 -1
  8. package/dist/shared/dag-failure-category.js +12 -0
  9. package/dist/task/frontend-project-capability.js +19 -0
  10. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CfDSkLtp.js → abnfDiagram-N423BO3Z-bPer1waP.js} +1 -1
  11. package/dist/worker/console/static/assets/{arc-BlefM6I5.js → arc-BLdkwucW.js} +1 -1
  12. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4tHfzHn.js → architectureDiagram-T3A2C74G-Bw4wo4I1.js} +1 -1
  13. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-G-G77Rh1.js → blockDiagram-VBNYF7ZC-DUUkc0gr.js} +1 -1
  14. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DWgxmY72.js → c4Diagram-5PPSVZJV-BC3O0nob.js} +1 -1
  15. package/dist/worker/console/static/assets/channel-AbFtVnFm.js +1 -0
  16. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CJQcfbCY.js → chunk-2GRJ4B5K-Dd4g2nti.js} +1 -1
  17. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-vUPviH9y.js → chunk-2Q5K7J3B-s2EDm7-n.js} +1 -1
  18. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DzRXSTeh.js → chunk-5RXB4S5H-GqtHM2yQ.js} +1 -1
  19. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CWfgqm07.js → chunk-5VM5RSS4-BzRFrN8j.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BtTDauXA.js → chunk-6Q2QTUOP-Bw9nFjCJ.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bc-NNt84.js → chunk-GF5L2VYU-B4n-03Qm.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B_f-emL4.js → chunk-JWPE2WC7-Dt75ZdBD.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DV-jgJ66.js → chunk-KBJHAD2P-B_afMhHB.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DMqzn4RZ.js → chunk-RYQCIY6F-Bd8Rqqg9.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CVNtlAm4.js → chunk-XXDRQBXY-9boOMji4.js} +1 -1
  26. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +1 -0
  27. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +1 -0
  28. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BBZBhVTd.js → cose-bilkent-JH36ORCC-BtCycfCc.js} +1 -1
  29. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DbMU2PZ2.js → cynefin-VYW2F7L2-eTOts9L1.js} +1 -1
  30. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dj0v8rjc.js → cynefinDiagram-MW4NZA55-Cibd2mjg.js} +1 -1
  31. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BW8R4WvJ.js → dagre-VZM6K2ZE-CDjDZtDV.js} +1 -1
  32. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DEgWz3ld.js → diagram-7IWD3JNH-Btexn-JJ.js} +1 -1
  33. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BAA01rlR.js → diagram-B4RE2ZJO-Cyl8sdpM.js} +1 -1
  34. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Cau6qqpW.js → diagram-LBJQPF4R-Ev-83E1t.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-8HiPsLos.js → diagram-Q27KOJAE-41LqE0Mc.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CmkXuTYL.js → diagram-UB23O5K3-BC39T5R3.js} +1 -1
  37. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DCV_0seW.js → ebnfDiagram-BXEA7PRR-DzcNRuri.js} +1 -1
  38. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-gckfWESj.js → erDiagram-JOGREHBK-B5Frvvnz.js} +1 -1
  39. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D2mT2BUu.js → flowDiagram-UKHOOZJN-BapxROIM.js} +1 -1
  40. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-B_HR5qEw.js → ganttDiagram-PKOTCBZU-Cd4d4wQ0.js} +1 -1
  41. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BUw4fpSa.js → gitGraphDiagram-DS77QQ5N-CrL6buFW.js} +1 -1
  42. package/dist/worker/console/static/assets/{index-DPbsruV5.js → index-DBh_x_Ek.js} +46 -46
  43. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BSRAqEj0.js → infoDiagram-6WML65LV-BrSiNxyd.js} +1 -1
  44. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CLxE8hUp.js → ishikawaDiagram-WSZJBQD7-gvlvY-n9.js} +1 -1
  45. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-juFzVXIj.js → journeyDiagram-NVQOT4AX-D-YaPzRQ.js} +1 -1
  46. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CwLHVhOU.js → kanban-definition-27J2QSJJ-BsmDv9E_.js} +1 -1
  47. package/dist/worker/console/static/assets/{linear-9LfUyPAs.js → linear-UWaoDN3h.js} +1 -1
  48. package/dist/worker/console/static/assets/{mermaid.core-TPPHfQbe.js → mermaid.core-BlvpGVG8.js} +5 -5
  49. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-Dn_Z9RxF.js → mindmap-definition-FAOFIHXS-CUCIP5CP.js} +1 -1
  50. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CiEMKTgm.js → pegDiagram-VL7TDLO6-CB1aQUAK.js} +1 -1
  51. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-JASaGxjZ.js → pieDiagram-7S7Q4E2Y-BwehHxhf.js} +1 -1
  52. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-ChmH0ply.js → quadrantDiagram-CIZ2JOQS-CFz30APB.js} +1 -1
  53. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CtyM-HGQ.js → railroadDiagram-AXF67PYL-BwNmw1i6.js} +1 -1
  54. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BN9y_Bvs.js → requirementDiagram-LRYGKXZP-jg-tPXlI.js} +1 -1
  55. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-IyS0GQ1-.js → sankeyDiagram-W5VNT64P-BlyReDKl.js} +1 -1
  56. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DjxGXurN.js → sequenceDiagram-SI44F4Z6-DBUzW1f3.js} +1 -1
  57. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Cr3Z0S8y.js → sizeCapture-X5ZJPWSS-BYcI2RAr.js} +1 -1
  58. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BjJFC6pW.js → stateDiagram-OKZ733FA-D7AiyJdo.js} +1 -1
  59. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +1 -0
  60. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DeMUJQiW.js → swimlanes-SLNWSIFB-BHGt7Kkv.js} +2 -2
  61. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D2F7-U4H.js +8 -0
  62. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bc2Rv2Oo.js → timeline-definition-Z64GVDOM-OPjooWcX.js} +1 -1
  63. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CDhRUeqG.js → vennDiagram-T6HMQDX7-ggsWDvrO.js} +1 -1
  64. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DaN_z3Xy.js → wardleyDiagram-T6FBY63Y-DjNe-BjZ.js} +1 -1
  65. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-C1vTLBDG.js → xychartDiagram-ELKLHX3M-Byp63Hei.js} +1 -1
  66. package/dist/worker/console/static/index.html +1 -1
  67. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +29 -1
  68. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  69. package/dist/workflows/dag/backend-test-pytest-collection.js +87 -4
  70. package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
  71. package/dist/workflows/dag/frontend-design-policy.js +19 -0
  72. package/dist/workflows/dag/frontend-implementation-contract.js +74 -2
  73. package/dist/workflows/dag/frontend-review-findings.js +238 -0
  74. package/dist/workflows/dag/frontend-shape.js +5 -3
  75. package/dist/workflows/dag/init-hybrid.js +90 -1
  76. package/dist/workflows/dag/node-execution.js +7 -0
  77. package/dist/workflows/dag/report.js +37 -1
  78. package/dist/workflows/dag/rerun-feedback.js +101 -1
  79. package/dist/workflows/dag/retry-policy.js +21 -3
  80. package/dist/workflows/dag/runner.js +46 -1
  81. package/dist/workflows/dag/validate.js +9 -3
  82. package/docs/templates/agent-dag-report.schema.json +8 -2
  83. package/docs/templates/agent-dag.schema.json +1 -1
  84. package/package.json +1 -1
  85. package/dist/worker/console/static/assets/channel-DsdX3ZUX.js +0 -1
  86. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CrvCyJ-8.js +0 -1
  87. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CrvCyJ-8.js +0 -1
  88. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Bq44O9Nt.js +0 -1
  89. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BT4tWyW3.js +0 -8
@@ -258,6 +258,12 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
258
258
  // only to nodes whose authority is still a text contract artifact.
259
259
  if (task.structuredContractOutput.schemaId ===
260
260
  "frontend-implementation-contract-plan-patch-v1") {
261
+ const splitGuidance = /write-set-too-large|split the task/.test(previousProtocolReason ?? "")
262
+ ? [
263
+ "",
264
+ "The writeSet is too large for one implement node. Do NOT delete implementation files to squeeze under the limit — that drops required work. Split the task via record_split_proposal (or narrow targets.files to a genuine subset) so each implement node stays bounded; the full file set must remain covered across the split.",
265
+ ]
266
+ : [];
261
267
  return [
262
268
  basePrompt,
263
269
  "",
@@ -266,6 +272,7 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
266
272
  previousProtocolReason,
267
273
  "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.",
268
274
  "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.",
275
+ ...splitGuidance,
269
276
  "</retry_instruction>",
270
277
  ].join("\n");
271
278
  }
@@ -125,6 +125,8 @@ const dagNodeArtifactsReportSchema = z
125
125
  humanEscalationMd: dagArtifactRefSchema,
126
126
  humanApproval: dagArtifactRefSchema,
127
127
  humanRejection: dagArtifactRefSchema,
128
+ changeManifest: dagArtifactRefSchema,
129
+ changeManifestChangedFiles: z.array(z.string()).optional(),
128
130
  })
129
131
  .strict();
130
132
  const dagNodeReportRowSchema = z
@@ -259,6 +261,20 @@ export async function probeArtifact(filePath) {
259
261
  export async function buildNodeArtifactsReport(runDir, nodeId) {
260
262
  const nodeDir = path.join(runDir, nodeId);
261
263
  const artifact = (name) => probeArtifact(path.join(nodeDir, name));
264
+ const changeManifestRef = await artifact("change-manifest.json");
265
+ let changeManifestChangedFiles;
266
+ if (changeManifestRef.exists) {
267
+ try {
268
+ const manifest = JSON.parse(await readFile(path.join(nodeDir, "change-manifest.json"), "utf-8"));
269
+ changeManifestChangedFiles = Array.isArray(manifest.changedFiles)
270
+ ? manifest.changedFiles
271
+ .filter((item) => typeof item === "string")
272
+ : undefined;
273
+ }
274
+ catch {
275
+ // unreadable manifest → leave undefined
276
+ }
277
+ }
262
278
  return {
263
279
  resultSummary: await artifact("result.summary.md"),
264
280
  promptRedacted: await artifact("prompt.redacted.md"),
@@ -270,6 +286,8 @@ export async function buildNodeArtifactsReport(runDir, nodeId) {
270
286
  humanEscalationMd: await artifact("human-escalation.md"),
271
287
  humanApproval: await artifact("human-approval.json"),
272
288
  humanRejection: await artifact("human-rejection.json"),
289
+ changeManifest: changeManifestRef,
290
+ changeManifestChangedFiles,
273
291
  };
274
292
  }
275
293
  function taskById(spec) {
@@ -726,6 +744,12 @@ function formatArtifactFlags(artifacts) {
726
744
  flags.push("approval");
727
745
  if (artifacts.humanRejection.exists)
728
746
  flags.push("rejection");
747
+ if (artifacts.changeManifest.exists) {
748
+ const count = artifacts.changeManifestChangedFiles?.length;
749
+ flags.push(count !== undefined
750
+ ? `changed:${count}`
751
+ : "change-manifest");
752
+ }
729
753
  return flags.length > 0 ? flags.join(",") : "-";
730
754
  }
731
755
  function formatTimestamp(value) {
@@ -749,7 +773,19 @@ function summarizeNodeStatuses(nodes) {
749
773
  return `${total} total / ${finished} finished / ${failed} failed / ${skipped} skipped / ${pending} pending`;
750
774
  }
751
775
  function hasArtifact(artifacts) {
752
- return Object.values(artifacts).some((artifact) => artifact.exists);
776
+ return [
777
+ artifacts.resultSummary,
778
+ artifacts.promptRedacted,
779
+ artifacts.skills,
780
+ artifacts.nodeRecord,
781
+ artifacts.decisionEnvelope,
782
+ artifacts.decisionEnvelopeValidation,
783
+ artifacts.humanEscalationJson,
784
+ artifacts.humanEscalationMd,
785
+ artifacts.humanApproval,
786
+ artifacts.humanRejection,
787
+ artifacts.changeManifest,
788
+ ].some((artifact) => artifact.exists);
753
789
  }
754
790
  function failureRows(run) {
755
791
  return run.nodes.filter((node) => node.status === "ERROR" ||
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { parseJsonReviewVerdict } from "./output-protocol.js";
5
+ import { FRONTEND_REVIEW_NODE_ID, readCommittedReviewRequestFact, } from "./frontend-review-findings.js";
5
6
  export const MAX_RERUN_FEEDBACK_CHARS = 12_000;
6
7
  const RERUN_FEEDBACK_TRUNCATION_MARKER = "\n\n...[上一轮反馈已按长度上限截断]...\n\n";
7
8
  const MAX_EVIDENCE_REFS = 8;
@@ -35,6 +36,44 @@ function requestRevision(text) {
35
36
  return (emphasized?.[1] ?? trimmed).trim() === "VERDICT: request-revision";
36
37
  });
37
38
  }
39
+ /**
40
+ * Deterministically render a typed `request_review_changes` fact into feedback
41
+ * text. Unlike the legacy text channel (VERDICT: request-revision), the
42
+ * frontend review terminal is a committed typed fact; the findings are the
43
+ * actionable body the next round must consume.
44
+ */
45
+ export function formatTypedReviewRequestFeedback(fact) {
46
+ const lines = [];
47
+ lines.push(`frontend review committed request_review_changes (issueCategory=${typeof fact.issueCategory === "string" ? fact.issueCategory : "unknown"})`);
48
+ if (Array.isArray(fact.findings) && fact.findings.length > 0) {
49
+ lines.push("review findings:");
50
+ for (const finding of fact.findings) {
51
+ if (!finding || typeof finding !== "object")
52
+ continue;
53
+ const record = finding;
54
+ const severity = typeof record.severity === "string" ? record.severity : "";
55
+ const issue = typeof record.issue === "string" ? record.issue : "";
56
+ const location = [
57
+ typeof record.file === "string" ? record.file : "",
58
+ typeof record.line === "number" ? String(record.line) : "",
59
+ ]
60
+ .filter(Boolean)
61
+ .join(":");
62
+ const prefix = location ? `[${location}] ` : "";
63
+ const requiredChange = typeof record.requiredChange === "string" &&
64
+ record.requiredChange.length > 0
65
+ ? ` (requiredChange: ${record.requiredChange})`
66
+ : "";
67
+ lines.push(`- ${prefix}${severity}: ${issue}${requiredChange}`);
68
+ }
69
+ }
70
+ if (Array.isArray(fact.evidenceRefs) && fact.evidenceRefs.length > 0) {
71
+ lines.push(`evidenceRefs: ${fact.evidenceRefs
72
+ .filter((ref) => typeof ref === "string")
73
+ .join(", ")}`);
74
+ }
75
+ return lines.join("\n");
76
+ }
38
77
  function pathInside(root, candidate) {
39
78
  const relative = path.relative(root, candidate);
40
79
  return (relative === "" ||
@@ -127,6 +166,23 @@ function evidenceRefs(repoRoot, runDir, pass, sourceNodeId) {
127
166
  }
128
167
  return result;
129
168
  }
169
+ /**
170
+ * Read the committed frontend review request fact (typed terminal channel).
171
+ * Returns { nodeId, fact } when the review node committed `request_review_changes`.
172
+ */
173
+ async function readTypedReviewRequestFact(runDir) {
174
+ const fact = await readCommittedReviewRequestFact({ runDir });
175
+ if (!fact)
176
+ return undefined;
177
+ return {
178
+ nodeId: FRONTEND_REVIEW_NODE_ID,
179
+ fact: {
180
+ issueCategory: fact.issueCategory,
181
+ evidenceRefs: fact.evidenceRefs,
182
+ findings: fact.findings,
183
+ },
184
+ };
185
+ }
130
186
  /**
131
187
  * Derive only the latest unresolved verify/review feedback from canonical
132
188
  * parent-run facts. Provider-only failures intentionally return undefined.
@@ -144,8 +200,52 @@ export async function deriveDagRerunFeedback(input) {
144
200
  break;
145
201
  }
146
202
  }
147
- if (!sourceNodeId || !sourceNode)
203
+ if (!sourceNodeId || !sourceNode) {
204
+ // Typed terminal channel: the frontend review commits its verdict as a
205
+ // typed fact (request_review_changes) instead of VERDICT text, and the
206
+ // frontend DAG has no convergence passHistory. Fall back to reading the
207
+ // review node's typed event store so findings still flow into the next
208
+ // round as unresolved feedback.
209
+ const typedFact = await readTypedReviewRequestFact(input.runDir);
210
+ if (typedFact) {
211
+ const parentSourceBindingHash = sourceBindingHash(input.spec);
212
+ return withDigest({
213
+ schemaVersion: 1,
214
+ kind: "unresolved-terminal-feedback",
215
+ parentRunId: input.parentRunId,
216
+ taskId: input.taskId,
217
+ sourceNodeId: typedFact.nodeId,
218
+ verdict: "request-revision",
219
+ failureCategory: "review-request-changes",
220
+ feedbackText: scrubAndBoundFeedbackText(formatTypedReviewRequestFeedback(typedFact.fact), input.runDir, input.state.cwd),
221
+ evidenceRefs: [
222
+ {
223
+ nodeId: typedFact.nodeId,
224
+ preservedNodeRecordPath: `${typedFact.nodeId}.json`,
225
+ },
226
+ ],
227
+ attemptSummary: {
228
+ completedPasses: 1,
229
+ maxPasses: 1,
230
+ ...(input.state.terminalReason
231
+ ? { terminalReason: input.state.terminalReason }
232
+ : {}),
233
+ },
234
+ binding: {
235
+ ...(parentSourceBindingHash
236
+ ? { sourceCanonicalHash: parentSourceBindingHash }
237
+ : {}),
238
+ ...(input.spec.taskContractBinding?.canonicalHash
239
+ ? {
240
+ taskContractCanonicalHash: input.spec.taskContractBinding.canonicalHash,
241
+ }
242
+ : {}),
243
+ status: "unknown",
244
+ },
245
+ });
246
+ }
148
247
  return undefined;
248
+ }
149
249
  const completedPasses = pass?.pass ?? Math.max(1, input.state.convergence?.currentPass ?? 1);
150
250
  const maxPasses = Math.max(completedPasses, input.state.convergence?.maxPasses ?? 1);
151
251
  const parentSourceBindingHash = sourceBindingHash(input.spec);
@@ -40,6 +40,14 @@ export const REVIEW_TERMINAL_MISSING_RETRY_CATEGORY = "review-terminal-missing";
40
40
  * reduced-reading instruction.
41
41
  */
42
42
  export const READ_BURST_RETRY_CATEGORY = "read-burst";
43
+ /**
44
+ * Provider returned a context-overflow error (400 request-too-large / context
45
+ * window exceeded). The SDK compacts and auto-retries once internally; when it
46
+ * still surfaces, the executor classifies this so retry policies and reports
47
+ * treat it as overflow (not empty-output) and can retry with a compacted
48
+ * session / fresh context.
49
+ */
50
+ export const CONTEXT_OVERFLOW_RETRY_CATEGORY = "context-overflow";
43
51
  /** Retry only a proven no-op from an explicitly opt-in bounded Pi writer. */
44
52
  export const WRITER_EMPTY_DIFF_RETRY_CATEGORY = "writer-empty-diff";
45
53
  /** Retry when a backend-test writer finished but Completeness Gate found missing/broken targets. */
@@ -74,6 +82,7 @@ export const ALL_DAG_RETRY_CATEGORIES = [
74
82
  WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
75
83
  REVIEW_TERMINAL_MISSING_RETRY_CATEGORY,
76
84
  READ_BURST_RETRY_CATEGORY,
85
+ CONTEXT_OVERFLOW_RETRY_CATEGORY,
77
86
  ];
78
87
  const RETRY_SAFE_PI_ROLES = new Set([
79
88
  "planner",
@@ -196,7 +205,10 @@ export const WRITER_TRANSPORT_RETRY_POLICY = {
196
205
  backoff: "exponential",
197
206
  initialDelayMs: 5000,
198
207
  maxDelayMs: 30000,
199
- retryCategories: [WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY],
208
+ retryCategories: [
209
+ WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
210
+ CONTEXT_OVERFLOW_RETRY_CATEGORY,
211
+ ],
200
212
  };
201
213
  /**
202
214
  * Implicit runtime retry for canonical final verify shells. Not a DagRetryPolicy:
@@ -219,14 +231,20 @@ export const TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE = "standard-reviewed-superv
219
231
  /**
220
232
  * Target-template exclusive implement-pi transport retry. Same schema category
221
233
  * as frontend (`writer-clean-timeout`) so DagSpec validation stays unchanged;
222
- * only the delay cap is wider. Do not mutate {@link WRITER_TRANSPORT_RETRY_POLICY}.
234
+ * only the delay cap is wider. Context overflow is added so a writer whose
235
+ * session hit the context window (compact+retry inside the SDK) can retry once
236
+ * more at the DAG level with a fresh attempt. Do not mutate
237
+ * {@link WRITER_TRANSPORT_RETRY_POLICY}.
223
238
  */
224
239
  export const TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY = {
225
240
  maxAttempts: 2,
226
241
  backoff: "exponential",
227
242
  initialDelayMs: 5000,
228
243
  maxDelayMs: 60000,
229
- retryCategories: [WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY],
244
+ retryCategories: [
245
+ WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
246
+ CONTEXT_OVERFLOW_RETRY_CATEGORY,
247
+ ],
230
248
  };
231
249
  /**
232
250
  * Backend-test generation writers: empty-diff once, plus bounded incomplete-write-set
@@ -26,6 +26,7 @@ import { runConvergencePassController, } from "./convergence/controller.js";
26
26
  import { executeDagRanksOnce, FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isConditionSkippedReason, readFrontendPrewriteResult, } from "./scheduler.js";
27
27
  import { topoSortToRanks } from "./topo.js";
28
28
  import { isFrontendWriterTransientPartialWrite } from "./frontend-writer-recovery.js";
29
+ import { classifyReviewIssueCategoryFailureOwner, readCommittedReviewRequestFact, writeFrontendReviewFindingsPrd, } from "./frontend-review-findings.js";
29
30
  import { bindFrontendShapeStateToRunTopology, commitFrontendShapeTransitionCas, commitRuntimeFrontendShapeFact, isCanonicalFrontendShapeDag, } from "./frontend-shape-facts.js";
30
31
  import { assertFrontendTopologyBinding, buildFrontendTopologyBinding, computeFrontendShapeSourceDigest, } from "./frontend-shape.js";
31
32
  import { ensureFrontendShapeCapsuleRecord } from "./frontend-shape-capsule-store.js";
@@ -1151,6 +1152,44 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd) {
1151
1152
  }
1152
1153
  }
1153
1154
  }
1155
+ // Phase 7: frontend review "request changes" → not passed. When the review
1156
+ // node FINISHED but committed `request_review_changes` (typed terminal
1157
+ // verdict), the implementation was reviewed and rejected. The run must not
1158
+ // surface as finished: terminal aggregation below stays partial_failed, a
1159
+ // terminal reason is recorded, the committed findings are rendered into a
1160
+ // downstream-task PRD draft (run-owned), and a recovery trigger carries the
1161
+ // issueCategory-derived owner so a later DAG round consumes the findings
1162
+ // instead of repeating the previous flow.
1163
+ const reviewRequestFact = await readCommittedReviewRequestFact({
1164
+ runDir,
1165
+ });
1166
+ if (reviewRequestFact) {
1167
+ state.terminalReason = "review-request-changes";
1168
+ try {
1169
+ await writeFrontendReviewFindingsPrd({
1170
+ runDir,
1171
+ parentRunId: state.runId,
1172
+ fact: reviewRequestFact,
1173
+ });
1174
+ }
1175
+ catch (error) {
1176
+ // The PRD draft is additive; a write failure must not mask the
1177
+ // not-passed verdict. The terminal aggregation below still applies.
1178
+ console.warn(`[run-dag] warning: failed to write frontend review findings PRD: ${error instanceof Error ? error.message : String(error)}`);
1179
+ }
1180
+ if (!recoveryTrigger && !skipFrontendRecovery) {
1181
+ const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
1182
+ if (continuationCount < 1) {
1183
+ recoveryTrigger = {
1184
+ requestId: state.runId,
1185
+ failureSource: "frontend-review-pi",
1186
+ failureOwner: classifyReviewIssueCategoryFailureOwner(reviewRequestFact.issueCategory),
1187
+ protocolFailureReason: `frontend review request_review_changes (issueCategory=${reviewRequestFact.issueCategory})`,
1188
+ recoveryMode: "new-dag",
1189
+ };
1190
+ }
1191
+ }
1192
+ }
1154
1193
  const finishedCount = Object.values(state.nodes).filter((n) => n.status === "FINISHED").length;
1155
1194
  const { supersededIds, supersededFailures } = collectSupersededIntermediateFailures(state);
1156
1195
  if (state.convergence && supersededFailures.length > 0) {
@@ -1166,7 +1205,13 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd) {
1166
1205
  if (effectiveErrorCount === 0 &&
1167
1206
  successTerminal &&
1168
1207
  finishedCount + conditionSkippedCount + supersededErrorCount === taskCount) {
1169
- state.status = "finished";
1208
+ // Phase 7 guard: a committed `request_review_changes` means the review
1209
+ // rejected the implementation; the run must never surface as finished
1210
+ // even though every node technically FINISHED.
1211
+ state.status =
1212
+ state.terminalReason === "review-request-changes"
1213
+ ? "partial_failed"
1214
+ : "finished";
1170
1215
  }
1171
1216
  else if (finishedCount > 0) {
1172
1217
  state.status = "partial_failed";
@@ -3,7 +3,7 @@ import { resolveShellCommands } from "../../executors/shell-executor.js";
3
3
  import { pathMatchesPattern } from "../../shared/git-progress.js";
4
4
  import { resolveRepairTaskForGate } from "./repair-artifact.js";
5
5
  import { topoSortToRanks } from "./topo.js";
6
- import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "./retry-policy.js";
6
+ import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, CONTEXT_OVERFLOW_RETRY_CATEGORY, } from "./retry-policy.js";
7
7
  import { isBackendTestCompletenessRetryCandidate } from "./backend-test-writer-completeness.js";
8
8
  import { defaultFrontendTestLayout, frontendTestLayoutFromSpec, } from "./frontend-test-layout.js";
9
9
  const GOVERNANCE_WARNING_TYPES = new Set([
@@ -656,12 +656,18 @@ function validateRetryPolicyTaskConfig(task, issues) {
656
656
  }
657
657
  if (isWriterTransportCandidate) {
658
658
  const categories = new Set(task.retryPolicy.retryCategories);
659
+ const allowedWriterTransportCategories = new Set([
660
+ WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
661
+ CONTEXT_OVERFLOW_RETRY_CATEGORY,
662
+ ]);
663
+ const allAllowed = [...categories].every((category) => allowedWriterTransportCategories.has(category));
659
664
  if (task.retryPolicy.maxAttempts !== 2 ||
660
- categories.size !== 1 ||
665
+ categories.size === 0 ||
666
+ !allAllowed ||
661
667
  !categories.has(WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY)) {
662
668
  issues.push({
663
669
  type: "invalid-retry-policy",
664
- message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only ${WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY}`,
670
+ message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only writer-clean-timeout (plus optional context-overflow)`,
665
671
  });
666
672
  }
667
673
  }
@@ -176,7 +176,8 @@
176
176
  "humanEscalationJson",
177
177
  "humanEscalationMd",
178
178
  "humanApproval",
179
- "humanRejection"
179
+ "humanRejection",
180
+ "changeManifest"
180
181
  ],
181
182
  "properties": {
182
183
  "resultSummary": { "$ref": "#/$defs/dagArtifactRef" },
@@ -188,7 +189,12 @@
188
189
  "humanEscalationJson": { "$ref": "#/$defs/dagArtifactRef" },
189
190
  "humanEscalationMd": { "$ref": "#/$defs/dagArtifactRef" },
190
191
  "humanApproval": { "$ref": "#/$defs/dagArtifactRef" },
191
- "humanRejection": { "$ref": "#/$defs/dagArtifactRef" }
192
+ "humanRejection": { "$ref": "#/$defs/dagArtifactRef" },
193
+ "changeManifest": { "$ref": "#/$defs/dagArtifactRef" },
194
+ "changeManifestChangedFiles": {
195
+ "type": "array",
196
+ "items": { "type": "string" }
197
+ }
192
198
  }
193
199
  },
194
200
  "repairArtifact": {
@@ -466,7 +466,7 @@
466
466
  "retryCategories": {
467
467
  "type": "array",
468
468
  "items": {
469
- "enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout"]
469
+ "enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
470
470
  },
471
471
  "default": ["timeout", "network", "rate-limit", "unavailable", "empty-output"],
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."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.39.0-beta.6",
3
+ "version": "0.39.0-beta.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-TPPHfQbe.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-Bc-NNt84.js";import{_ as i}from"./mermaid.core-TPPHfQbe.js";import"./chunk-5VM5RSS4-CWfgqm07.js";import"./chunk-XXDRQBXY-CVNtlAm4.js";import"./chunk-KBJHAD2P-DV-jgJ66.js";import"./chunk-2GRJ4B5K-CJQcfbCY.js";import"./index-DPbsruV5.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-Bc-NNt84.js";import{_ as i}from"./mermaid.core-TPPHfQbe.js";import"./chunk-5VM5RSS4-CWfgqm07.js";import"./chunk-XXDRQBXY-CVNtlAm4.js";import"./chunk-KBJHAD2P-DV-jgJ66.js";import"./chunk-2GRJ4B5K-CJQcfbCY.js";import"./index-DPbsruV5.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-DzRXSTeh.js";import{_ as i}from"./mermaid.core-TPPHfQbe.js";import"./chunk-XXDRQBXY-CVNtlAm4.js";import"./chunk-KBJHAD2P-DV-jgJ66.js";import"./chunk-2GRJ4B5K-CJQcfbCY.js";import"./index-DPbsruV5.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-UKHOOZJN-D2mT2BUu.js";import{_ as a}from"./mermaid.core-TPPHfQbe.js";import"./chunk-5VM5RSS4-CWfgqm07.js";import"./chunk-XXDRQBXY-CVNtlAm4.js";import"./chunk-KBJHAD2P-DV-jgJ66.js";import"./chunk-2GRJ4B5K-CJQcfbCY.js";import"./channel-DsdX3ZUX.js";import"./index-DPbsruV5.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,n=r({defaultLayout:"swimlane",styles:s});export{n as diagram};