@tea-agent/loop-agent 0.39.0-beta.7 → 0.39.0-beta.9
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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +23 -5
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/commands/init.js +26 -0
- package/dist/executors/dag-pi-executor.js +326 -8
- package/dist/executors/pi-read-budget-policy.js +166 -0
- package/dist/executors/shell-executor.js +26 -30
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +15 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +123 -4
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-bPer1waP.js → abnfDiagram-N423BO3Z-D1-Z-BBa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BLdkwucW.js → arc-q8qAYm5m.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw4wo4I1.js → architectureDiagram-T3A2C74G-CAu4_lve.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DUUkc0gr.js → blockDiagram-VBNYF7ZC-D5GhQvHm.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BC3O0nob.js → c4Diagram-5PPSVZJV-ClQjeLkO.js} +1 -1
- package/dist/worker/console/static/assets/channel-ClAuhSUL.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Dd4g2nti.js → chunk-2GRJ4B5K-DLqWlFbo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-s2EDm7-n.js → chunk-2Q5K7J3B-DkRT_usL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-GqtHM2yQ.js → chunk-5RXB4S5H-Cqp7Chi3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BzRFrN8j.js → chunk-5VM5RSS4-CedEdFB9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bw9nFjCJ.js → chunk-6Q2QTUOP-BHM8vP5x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B4n-03Qm.js → chunk-GF5L2VYU-D7EFr377.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Dt75ZdBD.js → chunk-JWPE2WC7-DR38mTWV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-B_afMhHB.js → chunk-KBJHAD2P-BFv2LsR3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Bd8Rqqg9.js → chunk-RYQCIY6F-BAvpJS5z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-9boOMji4.js → chunk-XXDRQBXY-eKI9TO72.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BtCycfCc.js → cose-bilkent-JH36ORCC-BMJXpgo4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-eTOts9L1.js → cynefin-VYW2F7L2-CKJ2ofZx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Cibd2mjg.js → cynefinDiagram-MW4NZA55-CsKnsazm.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CDjDZtDV.js → dagre-VZM6K2ZE-DVQ46vIw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Btexn-JJ.js → diagram-7IWD3JNH-D_RuonYS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cyl8sdpM.js → diagram-B4RE2ZJO-D2Mt0nMu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Ev-83E1t.js → diagram-LBJQPF4R-DjBDuye6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-41LqE0Mc.js → diagram-Q27KOJAE-8VZkqpE_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BC39T5R3.js → diagram-UB23O5K3-D0izYxia.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DzcNRuri.js → ebnfDiagram-BXEA7PRR-CncG2I95.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B5Frvvnz.js → erDiagram-JOGREHBK-DiBUUXR8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BapxROIM.js → flowDiagram-UKHOOZJN-Dtc0fArb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cd4d4wQ0.js → ganttDiagram-PKOTCBZU-BPNFP5OZ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CrL6buFW.js → gitGraphDiagram-DS77QQ5N-CsgpxEEd.js} +1 -1
- package/dist/worker/console/static/assets/{index-DBh_x_Ek.js → index-Bai8Z25q.js} +81 -81
- package/dist/worker/console/static/assets/index-CH0Tr5hY.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BrSiNxyd.js → infoDiagram-6WML65LV-kHGxkJUS.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-gvlvY-n9.js → ishikawaDiagram-WSZJBQD7-CbLe9wrr.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-YaPzRQ.js → journeyDiagram-NVQOT4AX-BOkwxAcP.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BsmDv9E_.js → kanban-definition-27J2QSJJ-BHYQ2rDu.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UWaoDN3h.js → linear-CHT4ujob.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BlvpGVG8.js → mermaid.core-yL8sfqnY.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CUCIP5CP.js → mindmap-definition-FAOFIHXS-DZx1coV-.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CB1aQUAK.js → pegDiagram-VL7TDLO6-DStWRCI8.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BwehHxhf.js → pieDiagram-7S7Q4E2Y-DSKZkwzK.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CFz30APB.js → quadrantDiagram-CIZ2JOQS-CmoEGOuW.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwNmw1i6.js → railroadDiagram-AXF67PYL-BLqzhKyq.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-jg-tPXlI.js → requirementDiagram-LRYGKXZP-gbHeUabM.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BlyReDKl.js → sankeyDiagram-W5VNT64P-CaKkBCKO.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DBUzW1f3.js → sequenceDiagram-SI44F4Z6-PUbi3Upu.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BYcI2RAr.js → sizeCapture-X5ZJPWSS-dm7S9Qfe.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D7AiyJdo.js → stateDiagram-OKZ733FA-C7WSzv4p.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-sQDv5AP3.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BHGt7Kkv.js → swimlanes-SLNWSIFB-CBKCiQwd.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BXwsmcPs.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-OPjooWcX.js → timeline-definition-Z64GVDOM-CvnUlylk.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-ggsWDvrO.js → vennDiagram-T6HMQDX7-xM0StY1O.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DjNe-BjZ.js → wardleyDiagram-T6FBY63Y-CWtJMagy.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Byp63Hei.js → xychartDiagram-ELKLHX3M-BO_-MeK8.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/styles.css +331 -35
- package/dist/worker/observe/static/views/dag-inspector.js +509 -78
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-design-policy.js +19 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +117 -50
- package/dist/workflows/dag/frontend-plan-render.js +27 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +45 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +1 -0
- package/dist/workflows/dag/frontend-review-context.js +102 -15
- package/dist/workflows/dag/frontend-review-findings.js +42 -10
- package/dist/workflows/dag/frontend-shadow-dual-write.js +36 -2
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +154 -0
- package/dist/workflows/dag/frontend-writer-admission.js +12 -0
- package/dist/workflows/dag/init-hybrid.js +297 -65
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +111 -1
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/rerun-feedback.js +31 -8
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +34 -9
- package/dist/workflows/dag/runner.js +83 -16
- package/dist/workflows/dag/types.js +67 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +4 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +2 -3
- package/skills/frontend-implementation/references/design-spec.md +4 -3
- package/skills/frontend-implementation/references/node-contracts.md +4 -3
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-AbFtVnFm.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/index-DwOGauxU.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D2F7-U4H.js +0 -8
|
@@ -410,6 +410,13 @@ export function startInterruptWatcher(input) {
|
|
|
410
410
|
let watcher;
|
|
411
411
|
try {
|
|
412
412
|
watcher = watch(path.join(input.runDir, ".runtime"), () => tick());
|
|
413
|
+
// fs.watch can report EMFILE/ENOSPC asynchronously after watch() returns.
|
|
414
|
+
// Polling below is the reliable path, so watcher errors must never escape
|
|
415
|
+
// as an unhandled EventEmitter error and orphan the DAG runner.
|
|
416
|
+
watcher.on("error", () => {
|
|
417
|
+
watcher?.close();
|
|
418
|
+
watcher = undefined;
|
|
419
|
+
});
|
|
413
420
|
watcher.unref?.();
|
|
414
421
|
}
|
|
415
422
|
catch {
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
6
|
+
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
6
7
|
import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
|
|
7
8
|
import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isFrontendWriterAuthorized, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
8
9
|
import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
@@ -164,6 +165,45 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
|
164
165
|
];
|
|
165
166
|
}
|
|
166
167
|
const FRONTEND_PLAN_NODE_ID = "frontend-plan-pi";
|
|
168
|
+
const COMPACT_RETRY_SECTION_MAX_CHARS = 1_200;
|
|
169
|
+
const COMPACT_RETRY_TASK_MAX_CHARS = 4_800;
|
|
170
|
+
function compactRetryText(text, maxChars) {
|
|
171
|
+
if (text.length <= maxChars)
|
|
172
|
+
return text;
|
|
173
|
+
const headChars = Math.floor(maxChars * 0.78);
|
|
174
|
+
const tailChars = Math.max(0, maxChars - headChars - 52);
|
|
175
|
+
return `${text.slice(0, headChars)}\n… [omitted after context-overflow] …\n${text.slice(-tailChars)}`;
|
|
176
|
+
}
|
|
177
|
+
function compactRetrySection(prompt, name, maxChars) {
|
|
178
|
+
const match = prompt.match(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`));
|
|
179
|
+
return compactRetryText(match?.[1]?.trim() ?? "(not available)", maxChars);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A context-overflow retry must not resend the full resolved skills, governance
|
|
183
|
+
* payload, repeated source excerpts, and upstream previews that caused the
|
|
184
|
+
* previous session to overflow. Keep the immutable node contract plus bounded
|
|
185
|
+
* objective/criteria/task excerpts; canonical artifacts remain readable by
|
|
186
|
+
* pointer from the retained task instructions.
|
|
187
|
+
*/
|
|
188
|
+
export function buildContextOverflowRetryPrompt(task, basePrompt) {
|
|
189
|
+
return [
|
|
190
|
+
"<compact_retry_context>",
|
|
191
|
+
"This is a fresh, compact retry after provider context-overflow. The original full prompt remains audit evidence but is intentionally not resent.",
|
|
192
|
+
"Do not re-read task sources, upstream stdout, skills, or full diff artifacts that are already represented by canonical artifacts. Start from the smallest retained artifact and expand only the exact file or diff fragment needed.",
|
|
193
|
+
"</compact_retry_context>",
|
|
194
|
+
`<dag_objective>\n${compactRetrySection(basePrompt, "dag_objective", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</dag_objective>`,
|
|
195
|
+
`<success_criteria>\n${compactRetrySection(basePrompt, "success_criteria", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</success_criteria>`,
|
|
196
|
+
`<global_constraints>\n${compactRetrySection(basePrompt, "global_constraints", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</global_constraints>`,
|
|
197
|
+
`<node_contract>\n${compactRetrySection(basePrompt, "node_contract", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</node_contract>`,
|
|
198
|
+
`<upstream_context>\n${compactRetrySection(basePrompt, "upstream_context", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</upstream_context>`,
|
|
199
|
+
[
|
|
200
|
+
"<task>",
|
|
201
|
+
`Original task id: ${task.id}`,
|
|
202
|
+
compactRetrySection(basePrompt, "task", COMPACT_RETRY_TASK_MAX_CHARS),
|
|
203
|
+
"</task>",
|
|
204
|
+
].join("\n"),
|
|
205
|
+
].join("\n\n");
|
|
206
|
+
}
|
|
167
207
|
function isFrontendPlanLadderTask(task) {
|
|
168
208
|
return task.id === FRONTEND_PLAN_NODE_ID;
|
|
169
209
|
}
|
|
@@ -184,9 +224,25 @@ async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
|
|
|
184
224
|
const hasTerminalFact = committed.some((record) => record.fact.kind === "finalize_plan");
|
|
185
225
|
return { digest, hasTerminalFact };
|
|
186
226
|
}
|
|
187
|
-
function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
|
|
227
|
+
export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
|
|
188
228
|
if (attemptNumber <= 1)
|
|
189
229
|
return basePrompt;
|
|
230
|
+
// Context overflow is a transport/session failure, not a plan-protocol
|
|
231
|
+
// failure. It must take precedence over the plan retry ladder so every
|
|
232
|
+
// retry, including a repeated plan overflow, starts from the compact
|
|
233
|
+
// envelope rather than re-sending the original prompt.
|
|
234
|
+
if (previousFailureCategory === "context-overflow") {
|
|
235
|
+
return [
|
|
236
|
+
buildContextOverflowRetryPrompt(task, basePrompt),
|
|
237
|
+
"",
|
|
238
|
+
"<retry_instruction>",
|
|
239
|
+
"The previous attempt exceeded the provider context window. This retry starts a fresh session: keep the evidence surface narrow.",
|
|
240
|
+
"Do not re-read task sources, upstream stdout, skills, or full diff artifacts that are already represented by the canonical contract/review context. Read the smallest relevant artifact first, then only the specific source file or diff fragment needed to decide a finding.",
|
|
241
|
+
"Do not read artifacts/diff_patch.patch in full. For frontend review, read the bounded diff summary and then at most the specific per-file diff fragments it identifies, in part order.",
|
|
242
|
+
"Use concise tool calls and finish the required terminal/output protocol as soon as the evidence is sufficient.",
|
|
243
|
+
"</retry_instruction>",
|
|
244
|
+
].join("\n");
|
|
245
|
+
}
|
|
190
246
|
if (frontendPlanRetryStep === "compact-terminal-first") {
|
|
191
247
|
return [
|
|
192
248
|
basePrompt,
|
|
@@ -239,6 +295,16 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
239
295
|
].join("\n");
|
|
240
296
|
}
|
|
241
297
|
if (previousFailureCategory === "read-burst") {
|
|
298
|
+
if (task.id !== FRONTEND_PLAN_NODE_ID) {
|
|
299
|
+
return [
|
|
300
|
+
basePrompt,
|
|
301
|
+
"",
|
|
302
|
+
"<retry_instruction>",
|
|
303
|
+
"The previous attempt exceeded its read budget. Start a fresh, evidence-minimal pass: do not re-read task sources, upstream stdout, skills, or full diff artifacts already represented by a canonical context artifact.",
|
|
304
|
+
"Read the smallest relevant summary first, then only the specific source file or diff fragment needed for the required decision. Finish the required terminal/output protocol as soon as evidence is sufficient.",
|
|
305
|
+
"</retry_instruction>",
|
|
306
|
+
].join("\n");
|
|
307
|
+
}
|
|
242
308
|
return [
|
|
243
309
|
basePrompt,
|
|
244
310
|
"",
|
|
@@ -430,6 +496,35 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
430
496
|
"</retry_instruction>",
|
|
431
497
|
].join("\n");
|
|
432
498
|
}
|
|
499
|
+
const PROMPT_RESTART_CANDIDATE_MAX_BYTES = 64 * 1024;
|
|
500
|
+
function promptRestartCandidateForAttempt(input) {
|
|
501
|
+
if (input.attemptPrompt === input.baseRuntimePrompt) {
|
|
502
|
+
return input.taskPrompt;
|
|
503
|
+
}
|
|
504
|
+
const appendedPrefix = `${input.baseRuntimePrompt}\n\n`;
|
|
505
|
+
if (input.attemptPrompt.startsWith(appendedPrefix)) {
|
|
506
|
+
return [
|
|
507
|
+
input.taskPrompt,
|
|
508
|
+
input.attemptPrompt.slice(appendedPrefix.length),
|
|
509
|
+
]
|
|
510
|
+
.filter(Boolean)
|
|
511
|
+
.join("\n\n");
|
|
512
|
+
}
|
|
513
|
+
return input.attemptPrompt;
|
|
514
|
+
}
|
|
515
|
+
async function persistFailedPromptRestartCandidate(input) {
|
|
516
|
+
const redacted = redactSecrets(input.prompt);
|
|
517
|
+
const preview = truncateUtf8Preview(redacted, PROMPT_RESTART_CANDIDATE_MAX_BYTES);
|
|
518
|
+
await writeDagNodeJsonArtifact(input.runDir, input.nodeId, `prompt-candidate-attempt-${input.attempt}.json`, {
|
|
519
|
+
schemaVersion: 1,
|
|
520
|
+
attempt: input.attempt,
|
|
521
|
+
failureCategory: input.failureCategory ?? "unknown",
|
|
522
|
+
prompt: preview,
|
|
523
|
+
truncated: preview !== redacted,
|
|
524
|
+
maxBytes: PROMPT_RESTART_CANDIDATE_MAX_BYTES,
|
|
525
|
+
sha256: createHash("sha256").update(redacted).digest("hex"),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
433
528
|
export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd, options) {
|
|
434
529
|
const policy = resolveContextPolicy(spec);
|
|
435
530
|
const skillNames = policy.resolveSkills(spec, task);
|
|
@@ -904,6 +999,7 @@ export async function executeDagNode(input) {
|
|
|
904
999
|
// exactly this attempt's segment (B1: earlier attempts legitimately use
|
|
905
1000
|
// ls/find and must not be attributed to the repair attempt).
|
|
906
1001
|
const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
|
|
1002
|
+
let promptRestartCandidate = task.subtask_prompt;
|
|
907
1003
|
let result;
|
|
908
1004
|
try {
|
|
909
1005
|
validateRepairArtifactGateBeforeShell({
|
|
@@ -974,6 +1070,11 @@ export async function executeDagNode(input) {
|
|
|
974
1070
|
previousReason: previousProtocolReason,
|
|
975
1071
|
});
|
|
976
1072
|
}
|
|
1073
|
+
promptRestartCandidate = promptRestartCandidateForAttempt({
|
|
1074
|
+
taskPrompt: task.subtask_prompt,
|
|
1075
|
+
baseRuntimePrompt: prompt,
|
|
1076
|
+
attemptPrompt,
|
|
1077
|
+
});
|
|
977
1078
|
result = await executeNode({
|
|
978
1079
|
task,
|
|
979
1080
|
cwd,
|
|
@@ -1101,6 +1202,15 @@ export async function executeDagNode(input) {
|
|
|
1101
1202
|
const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
|
|
1102
1203
|
totalAttemptWallDurationMs += attemptWallDurationMs;
|
|
1103
1204
|
const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
|
|
1205
|
+
if (task.executor === "pi" && !result.ok) {
|
|
1206
|
+
await persistFailedPromptRestartCandidate({
|
|
1207
|
+
runDir,
|
|
1208
|
+
nodeId,
|
|
1209
|
+
attempt: attemptNumber,
|
|
1210
|
+
failureCategory: result.failureCategory,
|
|
1211
|
+
prompt: promptRestartCandidate,
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1104
1214
|
const attemptRecord = {
|
|
1105
1215
|
attempt: attemptNumber,
|
|
1106
1216
|
startedAt: attemptStartedAt,
|
|
@@ -45,9 +45,42 @@ function formatResolvedSkillInstructions(instructions) {
|
|
|
45
45
|
if (!instructions || instructions.length === 0)
|
|
46
46
|
return "(none)";
|
|
47
47
|
return instructions
|
|
48
|
-
.map((instruction) => instruction
|
|
48
|
+
.map((instruction) => formatSkillChecklist(instruction))
|
|
49
49
|
.join("\n\n---\n\n");
|
|
50
50
|
}
|
|
51
|
+
/** Keep skill provenance, but turn bounded inline manuals into a small action list. */
|
|
52
|
+
function formatSkillChecklist(instruction) {
|
|
53
|
+
const metadata = [
|
|
54
|
+
`Skill: ${instruction.name}`,
|
|
55
|
+
`Resolution: ${instruction.resolution}`,
|
|
56
|
+
...(instruction.path ? [`Path: ${instruction.path}`] : []),
|
|
57
|
+
...(instruction.sha256 ? [`Sha256: ${instruction.sha256}`] : []),
|
|
58
|
+
"Injection mode: checklist",
|
|
59
|
+
];
|
|
60
|
+
if (instruction.resolution !== "resolved")
|
|
61
|
+
return [...metadata, instruction.promptText].join("\n");
|
|
62
|
+
const source = instruction.promptText.split("Instructions:\n")[1] ?? "";
|
|
63
|
+
const candidates = source
|
|
64
|
+
.split(/\r?\n/)
|
|
65
|
+
.map((line) => line.trim())
|
|
66
|
+
.filter((line) => /^(?:#{1,3}\s+|[-*]\s+|\d+[.)]\s+)/.test(line) || line.length > 0)
|
|
67
|
+
.map((line) => line.replace(/^#{1,3}\s+/, "").replace(/^[-*]\s+/, "").replace(/^\d+[.)]\s+/, ""))
|
|
68
|
+
.filter((line) => line.length > 0);
|
|
69
|
+
const checklist = [];
|
|
70
|
+
let chars = 0;
|
|
71
|
+
for (const item of candidates) {
|
|
72
|
+
const bounded = item.slice(0, 240);
|
|
73
|
+
if (checklist.length >= 10 || chars + bounded.length > 1_600)
|
|
74
|
+
break;
|
|
75
|
+
checklist.push(`- ${bounded}`);
|
|
76
|
+
chars += bounded.length;
|
|
77
|
+
}
|
|
78
|
+
return [
|
|
79
|
+
...metadata,
|
|
80
|
+
"Checklist (apply these actions; consult the immutable resolved file only when a listed action requires detail):",
|
|
81
|
+
...(checklist.length > 0 ? checklist : ["- Follow the resolved skill's declared workflow."]),
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
51
84
|
export function formatOutputLanguageBlock(language = DEFAULT_DAG_OUTPUT_LANGUAGE) {
|
|
52
85
|
if (language === "en") {
|
|
53
86
|
return [
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
|
+
import { DAG_RUNS_DIR } from "./lifecycle.js";
|
|
6
|
+
function recoveryLeasePath(cwd, parentRunId) {
|
|
7
|
+
// Never derive a filesystem name directly from an externally supplied legacy
|
|
8
|
+
// run id. The content remains auditable while the filename is traversal-safe.
|
|
9
|
+
const key = createHash("sha256").update(parentRunId).digest("hex");
|
|
10
|
+
return path.join(cwd, DAG_RUNS_DIR, ".recovery-leases", `${key}.json`);
|
|
11
|
+
}
|
|
12
|
+
function parseLease(raw, parentRunId) {
|
|
13
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14
|
+
throw new Error("invalid dag recovery lease: expected object");
|
|
15
|
+
}
|
|
16
|
+
const record = raw;
|
|
17
|
+
if (record.schemaVersion !== 1 ||
|
|
18
|
+
record.parentRunId !== parentRunId ||
|
|
19
|
+
typeof record.requestId !== "string" ||
|
|
20
|
+
record.requestId.length === 0 ||
|
|
21
|
+
(record.kind !== "auto-frontend-recovery" &&
|
|
22
|
+
record.kind !== "rerun-from-node" &&
|
|
23
|
+
record.kind !== "standalone-task-rerun") ||
|
|
24
|
+
typeof record.leaseToken !== "string" ||
|
|
25
|
+
record.leaseToken.length === 0 ||
|
|
26
|
+
typeof record.acquiredAt !== "string" ||
|
|
27
|
+
typeof record.executionRef !== "undefined" &&
|
|
28
|
+
typeof record.executionRef !== "string") {
|
|
29
|
+
throw new Error("invalid dag recovery lease: schema mismatch");
|
|
30
|
+
}
|
|
31
|
+
return record;
|
|
32
|
+
}
|
|
33
|
+
export async function acquireDagRecoveryLease(input) {
|
|
34
|
+
const leasePath = recoveryLeasePath(input.cwd, input.parentRunId);
|
|
35
|
+
const lease = {
|
|
36
|
+
schemaVersion: 1,
|
|
37
|
+
parentRunId: input.parentRunId,
|
|
38
|
+
requestId: input.requestId,
|
|
39
|
+
kind: input.kind,
|
|
40
|
+
leaseToken: randomBytes(16).toString("hex"),
|
|
41
|
+
acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
await mkdir(path.dirname(leasePath), { recursive: true });
|
|
44
|
+
try {
|
|
45
|
+
// `wx` is the cross-process compare-and-create primitive. The lease is
|
|
46
|
+
// deliberately immutable for a parent run: a later recovery must follow
|
|
47
|
+
// the committed child lineage rather than fork another DAG.
|
|
48
|
+
await writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, {
|
|
49
|
+
encoding: "utf8",
|
|
50
|
+
flag: "wx",
|
|
51
|
+
});
|
|
52
|
+
return { kind: "acquired", leasePath, lease };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
const existing = parseLease(JSON.parse(await readFile(leasePath, "utf8")), input.parentRunId);
|
|
59
|
+
return { kind: "held", leasePath, lease: existing };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Persist the operation/run identity before the caller is allowed to execute it. */
|
|
63
|
+
export async function bindDagRecoveryLeaseExecution(input) {
|
|
64
|
+
if (!input.executionRef.trim()) {
|
|
65
|
+
throw new Error("dag recovery lease executionRef is required");
|
|
66
|
+
}
|
|
67
|
+
const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
|
|
68
|
+
if (existing.leaseToken !== input.lease.leaseToken) {
|
|
69
|
+
throw new Error("dag recovery lease ownership drift");
|
|
70
|
+
}
|
|
71
|
+
if (existing.executionRef && existing.executionRef !== input.executionRef) {
|
|
72
|
+
throw new Error("dag recovery lease execution identity drift");
|
|
73
|
+
}
|
|
74
|
+
const bound = {
|
|
75
|
+
...existing,
|
|
76
|
+
executionRef: input.executionRef,
|
|
77
|
+
};
|
|
78
|
+
await writeJsonAtomic(input.leasePath, bound);
|
|
79
|
+
return bound;
|
|
80
|
+
}
|
|
@@ -2,7 +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
|
+
import { FRONTEND_DESIGN_REVIEW_NODE_ID, FRONTEND_REVIEW_NODE_ID, readCommittedDesignRequestFact, readCommittedReviewRequestFact, } from "./frontend-review-findings.js";
|
|
6
6
|
export const MAX_RERUN_FEEDBACK_CHARS = 12_000;
|
|
7
7
|
const RERUN_FEEDBACK_TRUNCATION_MARKER = "\n\n...[上一轮反馈已按长度上限截断]...\n\n";
|
|
8
8
|
const MAX_EVIDENCE_REFS = 8;
|
|
@@ -183,6 +183,24 @@ async function readTypedReviewRequestFact(runDir) {
|
|
|
183
183
|
},
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Read the committed frontend design request fact (typed terminal channel).
|
|
188
|
+
* Returns { nodeId, fact } when the design review node committed
|
|
189
|
+
* `request_design_changes` — the design analog of request_review_changes.
|
|
190
|
+
*/
|
|
191
|
+
async function readTypedDesignRequestFact(runDir) {
|
|
192
|
+
const fact = await readCommittedDesignRequestFact({ runDir });
|
|
193
|
+
if (!fact)
|
|
194
|
+
return undefined;
|
|
195
|
+
return {
|
|
196
|
+
nodeId: FRONTEND_DESIGN_REVIEW_NODE_ID,
|
|
197
|
+
fact: {
|
|
198
|
+
issueCategory: fact.issueCategory,
|
|
199
|
+
evidenceRefs: fact.evidenceRefs,
|
|
200
|
+
findings: fact.findings,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
186
204
|
/**
|
|
187
205
|
* Derive only the latest unresolved verify/review feedback from canonical
|
|
188
206
|
* parent-run facts. Provider-only failures intentionally return undefined.
|
|
@@ -201,12 +219,15 @@ export async function deriveDagRerunFeedback(input) {
|
|
|
201
219
|
}
|
|
202
220
|
}
|
|
203
221
|
if (!sourceNodeId || !sourceNode) {
|
|
204
|
-
// Typed terminal channel: the frontend review
|
|
205
|
-
// typed
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
// round as unresolved feedback.
|
|
209
|
-
|
|
222
|
+
// Typed terminal channel: the frontend review/design review commit their
|
|
223
|
+
// verdicts as typed facts (request_review_changes / request_design_changes)
|
|
224
|
+
// instead of VERDICT text, and the frontend DAG has no convergence
|
|
225
|
+
// passHistory. Fall back to reading the typed event stores so findings
|
|
226
|
+
// still flow into the next round as unresolved feedback. Prefer review;
|
|
227
|
+
// if absent, fall through to the design review (a design rejection also
|
|
228
|
+
// carries actionable findings the next plan round must consume).
|
|
229
|
+
const typedFact = (await readTypedReviewRequestFact(input.runDir)) ??
|
|
230
|
+
(await readTypedDesignRequestFact(input.runDir));
|
|
210
231
|
if (typedFact) {
|
|
211
232
|
const parentSourceBindingHash = sourceBindingHash(input.spec);
|
|
212
233
|
return withDigest({
|
|
@@ -216,7 +237,9 @@ export async function deriveDagRerunFeedback(input) {
|
|
|
216
237
|
taskId: input.taskId,
|
|
217
238
|
sourceNodeId: typedFact.nodeId,
|
|
218
239
|
verdict: "request-revision",
|
|
219
|
-
failureCategory:
|
|
240
|
+
failureCategory: typedFact.nodeId === FRONTEND_REVIEW_NODE_ID
|
|
241
|
+
? "review-request-changes"
|
|
242
|
+
: "design-request-changes",
|
|
220
243
|
feedbackText: scrubAndBoundFeedbackText(formatTypedReviewRequestFeedback(typedFact.fact), input.runDir, input.state.cwd),
|
|
221
244
|
evidenceRefs: [
|
|
222
245
|
{
|
|
@@ -12,6 +12,7 @@ import { prepareActiveRunDir, writeNodeRecord, writeRunSpec, writeRunState, } fr
|
|
|
12
12
|
import { cloneParentSkillSnapshotForContinuation, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
13
13
|
import { topoSortToRanks } from "./topo.js";
|
|
14
14
|
import { captureWorkspaceCheckpoint, readWorkspaceCheckpoint, WORKSPACE_CHECKPOINT_START_REL, WORKSPACE_CHECKPOINT_TERMINAL_REL, writeWorkspaceCheckpoint, } from "./workspace-checkpoint.js";
|
|
15
|
+
import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, } from "./recovery-lease.js";
|
|
15
16
|
export const RERUN_PLAN_REL_PATH = ".runtime/rerun-plan.json";
|
|
16
17
|
export const IMPORT_MANIFEST_REL_PATH = ".runtime/import-manifest.json";
|
|
17
18
|
export const WORKER_ASSOCIATION_REL_PATH = ".runtime/worker-association.json";
|
|
@@ -572,6 +573,20 @@ export async function executeDagRerun(input) {
|
|
|
572
573
|
}
|
|
573
574
|
const parentSpec = await readDagRunSpec(located.runDir);
|
|
574
575
|
const parentState = await readDagRunState(located.runDir);
|
|
576
|
+
const recoveryLease = await acquireDagRecoveryLease({
|
|
577
|
+
cwd: input.cwd,
|
|
578
|
+
parentRunId: input.parentRunId,
|
|
579
|
+
requestId: input.requestId,
|
|
580
|
+
kind: "rerun-from-node",
|
|
581
|
+
});
|
|
582
|
+
if (recoveryLease.kind === "held") {
|
|
583
|
+
return {
|
|
584
|
+
ok: false,
|
|
585
|
+
plan,
|
|
586
|
+
errorCode: "RECOVERY_LEASE_HELD",
|
|
587
|
+
message: `parent run recovery is already claimed by ${recoveryLease.lease.kind} request ${recoveryLease.lease.requestId}`,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
575
590
|
const staged = await stageContinuationRun({
|
|
576
591
|
cwd: input.cwd,
|
|
577
592
|
parentRunDir: located.runDir,
|
|
@@ -583,6 +598,14 @@ export async function executeDagRerun(input) {
|
|
|
583
598
|
requestId: input.requestId,
|
|
584
599
|
promptOverrides: input.promptOverrides,
|
|
585
600
|
});
|
|
601
|
+
// Stage is non-executing; bind the child identity immediately before handing
|
|
602
|
+
// it to the runner so no recovery DAG can start without durable parent
|
|
603
|
+
// lineage ownership.
|
|
604
|
+
await bindDagRecoveryLeaseExecution({
|
|
605
|
+
leasePath: recoveryLease.leasePath,
|
|
606
|
+
lease: recoveryLease.lease,
|
|
607
|
+
executionRef: staged.state.runId,
|
|
608
|
+
});
|
|
586
609
|
const continuationOptions = {
|
|
587
610
|
cwd: input.cwd,
|
|
588
611
|
spec: staged.spec,
|
|
@@ -4,6 +4,7 @@ import { generateTaskDagUseCase } from "../../application/dag/generate-task-dag.
|
|
|
4
4
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
5
|
import { DAG_RUNS_DIR, isTerminalDagRunStatus, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
|
|
6
6
|
import { deriveDagRerunFeedback } from "./rerun-feedback.js";
|
|
7
|
+
import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, } from "./recovery-lease.js";
|
|
7
8
|
import { parseDagSpec, } from "./types.js";
|
|
8
9
|
export const RERUN_TASK_LINEAGE_REL_PATH = ".runtime/rerun-task-lineage.json";
|
|
9
10
|
export function parseDagRerunTaskArgs(args) {
|
|
@@ -369,6 +370,34 @@ export async function rerunStandaloneTask(input) {
|
|
|
369
370
|
}
|
|
370
371
|
}
|
|
371
372
|
}
|
|
373
|
+
const recoveryLease = execute
|
|
374
|
+
? await acquireDagRecoveryLease({
|
|
375
|
+
cwd: input.cwd,
|
|
376
|
+
parentRunId: input.parentRunId,
|
|
377
|
+
requestId: input.requestId,
|
|
378
|
+
kind: "standalone-task-rerun",
|
|
379
|
+
})
|
|
380
|
+
: undefined;
|
|
381
|
+
if (recoveryLease?.kind === "held") {
|
|
382
|
+
return blockedResult({
|
|
383
|
+
parentRunId: input.parentRunId,
|
|
384
|
+
reason: input.reason,
|
|
385
|
+
requestId: input.requestId,
|
|
386
|
+
blockedReason: "recovery-lease-held",
|
|
387
|
+
note: `parent run recovery is already claimed by ${recoveryLease.lease.kind} request ${recoveryLease.lease.requestId}`,
|
|
388
|
+
taskId: taskResolution.taskId,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (recoveryLease?.kind === "acquired") {
|
|
392
|
+
// Persist a non-empty execution identity before generateTaskDagUseCase can
|
|
393
|
+
// execute its new DAG. The generated run id is not available yet, so the
|
|
394
|
+
// request id is the stable lineage handle for this entry point.
|
|
395
|
+
await bindDagRecoveryLeaseExecution({
|
|
396
|
+
leasePath: recoveryLease.leasePath,
|
|
397
|
+
lease: recoveryLease.lease,
|
|
398
|
+
executionRef: `standalone-task-rerun:${input.requestId}`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
372
401
|
const generateResult = await generateTaskDagUseCase({
|
|
373
402
|
repoRoot: input.cwd,
|
|
374
403
|
taskId: taskResolution.taskId,
|
|
@@ -132,13 +132,24 @@ export const dagRetryPolicySchema = z
|
|
|
132
132
|
/**
|
|
133
133
|
* The default retry policy applied to safe generated read-only Pi nodes.
|
|
134
134
|
* Total attempts: 3, exponential backoff with cap.
|
|
135
|
+
*
|
|
136
|
+
* Includes `context-overflow`: a read-only node that blew the context window
|
|
137
|
+
* (400 request too large, e.g. a review node accumulating too many reads) is a
|
|
138
|
+
* transient, safe-to-retry failure — the retry re-runs the node with a
|
|
139
|
+
* compact-context instruction instead of failing the whole run. Previously
|
|
140
|
+
* only writer transport policies had context-overflow; contract/scout/
|
|
141
|
+
* design-review/review fell straight to ERROR without a retry attempt.
|
|
135
142
|
*/
|
|
136
143
|
export const DEFAULT_READ_ONLY_PI_RETRY_POLICY = {
|
|
137
144
|
maxAttempts: 3,
|
|
138
145
|
backoff: "exponential",
|
|
139
146
|
initialDelayMs: 2000,
|
|
140
147
|
maxDelayMs: 30000,
|
|
141
|
-
retryCategories: [
|
|
148
|
+
retryCategories: [
|
|
149
|
+
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
150
|
+
READ_BURST_RETRY_CATEGORY,
|
|
151
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
152
|
+
],
|
|
142
153
|
};
|
|
143
154
|
/**
|
|
144
155
|
* Structured planner nodes may retry with a compact-output instruction
|
|
@@ -148,7 +159,11 @@ export const DEFAULT_READ_ONLY_PI_RETRY_POLICY = {
|
|
|
148
159
|
*/
|
|
149
160
|
export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
|
|
150
161
|
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
151
|
-
retryCategories: [
|
|
162
|
+
retryCategories: [
|
|
163
|
+
...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES,
|
|
164
|
+
READ_BURST_RETRY_CATEGORY,
|
|
165
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
166
|
+
],
|
|
152
167
|
};
|
|
153
168
|
/**
|
|
154
169
|
* Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
|
|
@@ -159,7 +174,12 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
|
|
|
159
174
|
*/
|
|
160
175
|
export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
|
|
161
176
|
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
162
|
-
retryCategories: [
|
|
177
|
+
retryCategories: [
|
|
178
|
+
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
179
|
+
STRUCTURED_OUTPUT_RETRY_CATEGORY,
|
|
180
|
+
READ_BURST_RETRY_CATEGORY,
|
|
181
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
182
|
+
],
|
|
163
183
|
};
|
|
164
184
|
/**
|
|
165
185
|
* Default retry for reviewer / recovery nodes that declare outputProtocol.
|
|
@@ -180,6 +200,8 @@ export const FRONTEND_REVIEW_TERMINAL_RETRY_POLICY = {
|
|
|
180
200
|
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
181
201
|
retryCategories: [
|
|
182
202
|
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
203
|
+
READ_BURST_RETRY_CATEGORY,
|
|
204
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
183
205
|
REVIEW_TERMINAL_MISSING_RETRY_CATEGORY,
|
|
184
206
|
],
|
|
185
207
|
};
|
|
@@ -536,14 +558,16 @@ function reasonMandatedFrontendPlanRetryStep(reason) {
|
|
|
536
558
|
}
|
|
537
559
|
}
|
|
538
560
|
/**
|
|
539
|
-
* Opt-in retry policy mounted on the frontend-plan-pi node. maxAttempts=
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
561
|
+
* Opt-in retry policy mounted on the frontend-plan-pi node. maxAttempts=3
|
|
562
|
+
* bounds plan retry cost: the four-rung ladder is only partially traversable
|
|
563
|
+
* before the run fails non-converging (rungs: normal → bounded-tool-only →
|
|
564
|
+
* compact-terminal-first → backup-model). `invalid-output` (typed-fact schema
|
|
565
|
+
* violations, e.g. verification targets referencing undeclared UI states)
|
|
566
|
+
* retries at the `normal` rung with the contractCheck reason injected via
|
|
567
|
+
* `<retry_instruction>` (§5.1 ladder).
|
|
544
568
|
*/
|
|
545
569
|
export const FRONTEND_PLAN_LADDER_RETRY_POLICY = {
|
|
546
|
-
maxAttempts:
|
|
570
|
+
maxAttempts: 3,
|
|
547
571
|
backoff: "exponential",
|
|
548
572
|
initialDelayMs: 2000,
|
|
549
573
|
maxDelayMs: 30000,
|
|
@@ -551,5 +575,6 @@ export const FRONTEND_PLAN_LADDER_RETRY_POLICY = {
|
|
|
551
575
|
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
552
576
|
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
553
577
|
READ_BURST_RETRY_CATEGORY,
|
|
578
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
554
579
|
],
|
|
555
580
|
};
|