@tea-agent/loop-agent 0.39.0-next.26 → 0.39.0-next.28
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 +3 -0
- package/CHANGELOG.md +148 -96
- package/README.md +8 -4
- package/bin/loop-agent.js +7 -3
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/executors/dag-pi-executor.js +76 -10
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-sdk-executor.js +79 -2
- package/dist/executors/shell-executor.js +50 -6
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/operator-user-error.js +1 -1
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/security.js +53 -5
- package/dist/worker/console/server.js +10 -4
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-DGn9iNrd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-Dc0C2Nyv.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-COnz_Bhe.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-VhIP6vgf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-D74lpmlb.js} +1 -1
- package/dist/worker/console/static/assets/channel-DWcZjWB3.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BxmiogQE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-C00Oi-2I.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DTrT1Vze.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DdKW7-fL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-D0q1WN6w.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DBa14MSI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-DkP-pEox.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-lgPogpcX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-D-apZer0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-B1T2yAIe.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-IjoI3dqU.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-IjoI3dqU.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-0P6hi3jF.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CntzMSR8.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-C9zmXJtG.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-DfBCZqZw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DLp-bhGL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BksDcwZm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-Bdag7DYP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CoY5uNbk.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-KcgrDXHt.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CikErwJb.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DqmwwzvZ.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-35krh9WG.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-CvTF1gJ4.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BiOWWClJ.js} +1 -1
- package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-CqWlWE4l.js} +3 -3
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Crha5dcA.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DEtbscOZ.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-D2YYzx3z.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-C7x18ttQ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-CCLZkz45.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-Dmy4lSGv.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CmvIol59.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-DVpEdirN.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-cKQy456v.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-bx06I38m.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-BjfIVIJr.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-DcuPGbKM.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-DvgVeDoV.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-DLBG9nm-.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-D33L7FBF.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-hvqD9TpS.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C6MII0D1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-BtjH81Mz.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D_tk0V8D.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-CJ47Kraf.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-COVPl-wh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-CCoQJLUY.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-DTWJvfAd.js} +1 -1
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/observe/node-input.js +72 -3
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/views/dag-inspector.js +54 -1
- package/dist/worker/observe/static/views/session-timeline.js +60 -4
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/failure-category.js +5 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
- package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
- package/dist/workflows/dag/init-hybrid.js +71 -80
- package/dist/workflows/dag/node-execution.js +139 -10
- package/dist/workflows/dag/prompt.js +15 -1
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +28 -1
- package/dist/workflows/dag/validate.js +39 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +13 -0
- package/docs/templates/init-managed-agents.md +13 -3
- package/package.json +5 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -11,7 +11,7 @@ import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
|
11
11
|
import { extractTaskSourceOpenspecPaths } from "../../shared/openspec-spec.js";
|
|
12
12
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
13
13
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
14
|
-
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
14
|
+
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY, isCanonicalFinalVerifyShellRetryCandidate, isSafeReadOnlyPiRetryCandidate, isTargetTemplateImplementPi, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
15
15
|
import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
|
|
16
16
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
17
17
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
@@ -21,7 +21,6 @@ import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-em
|
|
|
21
21
|
import { discoverProjectGovernancePresence } from "./project-governance-context.js";
|
|
22
22
|
import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
23
23
|
import { materializeTaskReferenceDocs } from "../../task/source-references.js";
|
|
24
|
-
import { REQUIREMENT_FACT_ROLES } from "../../task/source-prepare/artifact-meta.js";
|
|
25
24
|
import { observeTaskContract } from "../../task/contract/observe.js";
|
|
26
25
|
import { dagHasWriterExecution } from "./task-contract-binding.js";
|
|
27
26
|
import { DEFAULT_VERIFY_TIMEOUT_MS, resolveVerifyPreset, } from "../../executors/shell-verification.js";
|
|
@@ -1502,27 +1501,11 @@ function buildSourceContextBlock(sources) {
|
|
|
1502
1501
|
.relative(path.join(sources.taskDir, "source"), reference.path)
|
|
1503
1502
|
.replaceAll(path.sep, "/");
|
|
1504
1503
|
const referenceRef = toDagSourcePath(sources, reference.path);
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
const referenceExcerpt = isFactRole
|
|
1511
|
-
? {
|
|
1512
|
-
text: reference.markdown.trim(),
|
|
1513
|
-
truncated: false,
|
|
1514
|
-
originalChars: reference.markdown.trim().length,
|
|
1515
|
-
maxChars: Number.POSITIVE_INFINITY,
|
|
1516
|
-
}
|
|
1517
|
-
: excerptMarkdown(reference.markdown, {
|
|
1518
|
-
sourceRef: referenceRef,
|
|
1519
|
-
});
|
|
1520
|
-
boundReadPaths.push(`- reference ${relativePath}${isFactRole ? " (full source)" : ""}: ${referenceRef}`);
|
|
1521
|
-
parts.push(`## Task source reference: ${relativePath}`, `Bound readPath (use for Pi read-tool calls): ${referenceRef}`, ...(isFactRole
|
|
1522
|
-
? [
|
|
1523
|
-
`Full source injected (role: ${reference.role}) — complete and authoritative; no excerpt truncation applied.`,
|
|
1524
|
-
]
|
|
1525
|
-
: []), referenceExcerpt.text);
|
|
1504
|
+
const referenceExcerpt = excerptMarkdown(reference.markdown, {
|
|
1505
|
+
sourceRef: referenceRef,
|
|
1506
|
+
});
|
|
1507
|
+
boundReadPaths.push(`- reference ${relativePath}: ${referenceRef}`);
|
|
1508
|
+
parts.push(`## Task source reference: ${relativePath}`, `Bound readPath (use for Pi read-tool calls): ${referenceRef}`, referenceExcerpt.text);
|
|
1526
1509
|
}
|
|
1527
1510
|
parts.push("## Bound source read paths", ...boundReadPaths, "Use these repository-readable paths for any Pi read-tool calls. Bound files under `.harness/tasks/<taskId>/source/**` are read-only inputs: reading them is allowed even though writing `.harness/**` is forbidden.", "Never resolve task-relative citations such as `source/需求.md` or `source/references/*` against the repository root, invent `source/<taskId>/...`, search for substitutes, or fall back to `docs/**` when a bound read fails.", "## Task config summary", `- taskId: ${sources.taskConfig.taskId}`, `- flow: ${sources.taskConfig.flow}`, `- complexity: ${sources.taskConfig.complexity}`, `- contextProfile: ${sources.taskConfig.contextProfile}`, `- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none — review before execute)"}`, `- forbiddenPaths: ${sources.taskConfig.forbiddenPaths.join(", ") || "(none)"}`, '- Pi DAG nodes are read-only unless toolProfile="write" is explicitly selected for a bounded writer node.', "- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.", `- Derived execution contract and immutable references live under the Bound source read paths above (not as repo-root \`source/...\`).`);
|
|
1528
1511
|
if (sources.taskConfig.hardConstraints.length > 0) {
|
|
@@ -1560,49 +1543,10 @@ async function loadMaterializedSourceReferences(sourceDir) {
|
|
|
1560
1543
|
}
|
|
1561
1544
|
await collect(referenceDir);
|
|
1562
1545
|
referencePaths.sort((left, right) => left.localeCompare(right));
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
.replaceAll(path.sep, "/");
|
|
1568
|
-
const role = roleByPath.get(relative);
|
|
1569
|
-
return {
|
|
1570
|
-
path: filePath,
|
|
1571
|
-
markdown: await readFile(filePath, "utf-8"),
|
|
1572
|
-
...(role !== undefined ? { role } : {}),
|
|
1573
|
-
};
|
|
1574
|
-
}));
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Reads `source-manifest.json` into a materializedPath → role map so the DAG
|
|
1578
|
-
* generator can tell fact-source references (requirement/acceptance) apart
|
|
1579
|
-
* from archival ones (analysis/clarification/design). A missing or malformed
|
|
1580
|
-
* manifest yields an empty map; such references keep the bounded-excerpt
|
|
1581
|
-
* treatment instead of being injected in full.
|
|
1582
|
-
*/
|
|
1583
|
-
async function loadReferenceRoles(sourceDir) {
|
|
1584
|
-
const roleByPath = new Map();
|
|
1585
|
-
const manifestPath = path.join(sourceDir, "source-manifest.json");
|
|
1586
|
-
let raw;
|
|
1587
|
-
try {
|
|
1588
|
-
raw = await readFile(manifestPath, "utf-8");
|
|
1589
|
-
}
|
|
1590
|
-
catch {
|
|
1591
|
-
return roleByPath;
|
|
1592
|
-
}
|
|
1593
|
-
try {
|
|
1594
|
-
const manifest = JSON.parse(raw);
|
|
1595
|
-
for (const document of manifest.documents ?? []) {
|
|
1596
|
-
if (typeof document.materializedPath === "string" &&
|
|
1597
|
-
typeof document.role === "string") {
|
|
1598
|
-
roleByPath.set(document.materializedPath.replaceAll(path.sep, "/"), document.role);
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
catch {
|
|
1603
|
-
// A malformed manifest must never break reference injection.
|
|
1604
|
-
}
|
|
1605
|
-
return roleByPath;
|
|
1546
|
+
return Promise.all(referencePaths.map(async (filePath) => ({
|
|
1547
|
+
path: filePath,
|
|
1548
|
+
markdown: await readFile(filePath, "utf-8"),
|
|
1549
|
+
})));
|
|
1606
1550
|
}
|
|
1607
1551
|
export async function loadTaskHybridSources(repoRoot, taskId) {
|
|
1608
1552
|
const paths = getTaskPaths(repoRoot, taskId);
|
|
@@ -1775,6 +1719,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1775
1719
|
forbiddenPaths,
|
|
1776
1720
|
outputContract: "Archived final shell verification stdout/stderr with exit codes; no worktree writes.",
|
|
1777
1721
|
subtask_prompt: "Run the adapter-resolved final verification commands before read-only verification review.",
|
|
1722
|
+
transientRetryProfile: TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE,
|
|
1778
1723
|
shell: {
|
|
1779
1724
|
commands: verifyShellCommands,
|
|
1780
1725
|
verifyEvidence: buildVerifyEvidence({
|
|
@@ -1895,6 +1840,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1895
1840
|
allowedPaths: implementPaths.allowedPaths,
|
|
1896
1841
|
forbiddenPaths,
|
|
1897
1842
|
writerOutcomePolicy: { type: "implementation-outcome-v1" },
|
|
1843
|
+
transientRetryProfile: TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE,
|
|
1898
1844
|
subtask_prompt: [
|
|
1899
1845
|
"Implement the approved plan with minimal focused changes.",
|
|
1900
1846
|
"Stay within writeSet. Do not write root artifacts/** unless artifacts paths are explicitly declared in writeSet.",
|
|
@@ -1954,6 +1900,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1954
1900
|
],
|
|
1955
1901
|
};
|
|
1956
1902
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
1903
|
+
stampTargetTemplateTransientRetryProfile(spec);
|
|
1957
1904
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
1958
1905
|
parseDagSpec(spec);
|
|
1959
1906
|
assertValidDagSpec(spec);
|
|
@@ -2433,7 +2380,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2433
2380
|
"## Runtime contract skeleton (deterministic and protected)",
|
|
2434
2381
|
JSON.stringify(frontendContractSkeleton),
|
|
2435
2382
|
"",
|
|
2436
|
-
"The initial planner emits an editable RFC 7386 patch against this skeleton. It MUST omit schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime merges and validates the final contract, then
|
|
2383
|
+
"The initial planner emits an editable RFC 7386 patch against this skeleton. It MUST omit schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime merges and validates the final contract, then writes a hash-bound canonical JSON artifact; downstream review and prewrite consume that artifact path, not planner stdout.",
|
|
2437
2384
|
"",
|
|
2438
2385
|
"## Forbidden fields (these are NOT in the schema; do not emit)",
|
|
2439
2386
|
"- schemaId",
|
|
@@ -2550,11 +2497,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2550
2497
|
`Non-static targets (type unit/component/integration/mock) MUST set file to a concrete code file inside the implementation writeSet (task allowedPaths); the prewrite gate rejects any non-static target whose file falls outside the writeSet.`,
|
|
2551
2498
|
`Command-level checks that run project-wide (all tests, typecheck, build, governance) MUST use type "static" and must NOT be bound as non-static targets with file=package.json/tsconfig.json/vite.config.ts/scripts/*. Static targets are exempt from the writeSet containment check.`,
|
|
2552
2499
|
].join("\n");
|
|
2553
|
-
const mandatorySourceReadInstruction = [
|
|
2554
|
-
"## Mandatory full source read before contracting",
|
|
2555
|
-
"Before producing this contract/plan, use the Pi read tool to read the FULL bound source files (需求.md, 执行约束.md, and every `references/*` Bound readPath listed above) — the inline copies above may be truncated excerpts, and requirement/acceptance references are authoritative only in their full form.",
|
|
2556
|
-
"Do not drop scope fields, acceptance criteria, non-goals, UI states, or column/field definitions that exist in the full sources but are absent from the inline excerpts; if a field appears in the full source, it belongs in the contract.",
|
|
2557
|
-
].join("\n");
|
|
2558
2500
|
const strategy = resolveDagVerifyStrategy(taskConfig);
|
|
2559
2501
|
const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
|
|
2560
2502
|
const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
|
|
@@ -2751,7 +2693,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2751
2693
|
"Read task source and produce a concise frontend implementation contract.",
|
|
2752
2694
|
"Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations.",
|
|
2753
2695
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
2754
|
-
mandatorySourceReadInstruction,
|
|
2755
2696
|
sourceContext,
|
|
2756
2697
|
].join("\n\n"),
|
|
2757
2698
|
},
|
|
@@ -2792,7 +2733,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2792
2733
|
allowedPaths: readOnlyPaths,
|
|
2793
2734
|
forbiddenPaths,
|
|
2794
2735
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2795
|
-
outputContract: "JSON-only patch output: one-line lead-in, then exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. Immediately after it, append exactly one ```openspec-citations``` fenced citation block. The runtime applies the patch, validates it, and
|
|
2736
|
+
outputContract: "JSON-only patch output: one-line lead-in, then exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. Immediately after it, append exactly one ```openspec-citations``` fenced citation block. The runtime applies the patch, validates it, and writes a hash-bound canonical JSON artifact for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes.",
|
|
2796
2737
|
subtask_prompt: [
|
|
2797
2738
|
"Use frontend-contract-pi, frontend-scout-pi, task sources, and the generation-time Mock capability evidence to fill the runtime-owned frontend contract skeleton. Return JSON-only output containing only an editable RFC 7386 plan patch. The runtime already owns schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff; omit those protected paths even when their values look obvious.",
|
|
2798
2739
|
"The patch fields become the complete implementation plan after deterministic merge. Do not produce a separate plan document, prose mirror, or full contract.",
|
|
@@ -2806,7 +2747,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2806
2747
|
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2807
2748
|
verificationTargetFileInstruction,
|
|
2808
2749
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
2809
|
-
mandatorySourceReadInstruction,
|
|
2810
2750
|
fixedVerificationContext,
|
|
2811
2751
|
sourceContext,
|
|
2812
2752
|
mockContextBlock,
|
|
@@ -2828,7 +2768,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2828
2768
|
outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
|
|
2829
2769
|
outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings, Required Plan Corrections, and Checked Items. No file writes.",
|
|
2830
2770
|
subtask_prompt: [
|
|
2831
|
-
"Audit the frontend plan before implementation. frontend-plan-pi is emitted
|
|
2771
|
+
"Audit the frontend plan before implementation. frontend-plan-pi is emitted as a hash-bound canonical JSON artifact after the runtime applied and validated the planner's editable patch against its protected skeleton; read that artifact with the read tool and do not infer the contract from stdout. There is no separate plan prose.",
|
|
2832
2772
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
2833
2773
|
"Request revision when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for a declared behavior target or selected Mock strategy. Mock strategies require Mock-backed evidence. A static-only contract is allowed only when every verification target is static and maps to a declared static entrypoint. not-needed otherwise requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case the plan must preserve the real request path and record the Real Integration Gap.",
|
|
2834
2774
|
"Also request revision for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, inline fake data, schema drift, or missing deterministic verification commands.",
|
|
@@ -2857,15 +2797,15 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2857
2797
|
allowedPaths: readOnlyPaths,
|
|
2858
2798
|
forbiddenPaths,
|
|
2859
2799
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2860
|
-
outputContract: "When the initial design review requests revision, return a one-line lead-in followed by exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively). Immediately after it, append exactly one ```openspec-citations``` fenced citation block. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only;
|
|
2800
|
+
outputContract: "When the initial design review requests revision, return a one-line lead-in followed by exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively). Immediately after it, append exactly one ```openspec-citations``` fenced citation block. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only; this node compiles the patch onto the original canonical artifact and writes a hash-bound revised contract. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block or raw JSON. No file writes.",
|
|
2861
2801
|
subtask_prompt: [
|
|
2862
2802
|
"Consume frontend-plan-pi (original contract JSON) and frontend-design-review-pi (first design review findings).",
|
|
2863
2803
|
"This node runs only when frontend-design-review-pi emitted VERDICT: request-revision. Produce an RFC 7386 merge-patch delta against the original contract JSON that addresses every Required Plan Correction from the design findings.",
|
|
2864
|
-
"The patch delta may update editable contract fields such as requirements, implementationSteps, targets.routes/publicApiChanges, uiStates, interactions, mockApi.strategy/activation/endpoints, dependencyPolicy, stylingStrategy, uiComponentChoices, verificationTargets, evidenceGaps, residualRisks, and realIntegrationGap. It must not modify protected schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff. Only include fields you change; omit unchanged fields (
|
|
2804
|
+
"The patch delta may update editable contract fields such as requirements, implementationSteps, targets.routes/publicApiChanges, uiStates, interactions, mockApi.strategy/activation/endpoints, dependencyPolicy, stylingStrategy, uiComponentChoices, verificationTargets, evidenceGaps, residualRisks, and realIntegrationGap. It must not modify protected schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff. Only include fields you change; omit unchanged fields (this node applies the patch on the original canonical artifact). null deletes a key; arrays and scalars replace; plain objects merge recursively.",
|
|
2865
2805
|
requirementCoverageInstruction,
|
|
2866
2806
|
"Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
|
|
2867
2807
|
"Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
|
|
2868
|
-
"Output in this exact order: (1) exactly one fenced json object containing the merge-patch delta — this
|
|
2808
|
+
"Output in this exact order: (1) exactly one fenced json object containing the merge-patch delta — this node compiles it onto the original canonical artifact; (2) exactly one openspec-citations citation fenced block appended immediately after it. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only. Do not emit any raw JSON or JSON objects in prose. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block. Do not include secrets or unsafe paths.",
|
|
2869
2809
|
"Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the effective (merged) contract; do not reduce behavior semantics to IDs and paths.",
|
|
2870
2810
|
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2871
2811
|
verificationTargetFileInstruction,
|
|
@@ -6490,6 +6430,13 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6490
6430
|
spec = await buildSupervisedHybridDag(standard, sources);
|
|
6491
6431
|
}
|
|
6492
6432
|
applyProjectGovernanceReview(spec, template, sources);
|
|
6433
|
+
// Backend implementation templates only (D1 outer gate): assign the two
|
|
6434
|
+
// Pi extension buckets after all template-specific nodes exist.
|
|
6435
|
+
if (template === "standard-dag" ||
|
|
6436
|
+
template === "review-gated-dag" ||
|
|
6437
|
+
template === "supervised-implementation") {
|
|
6438
|
+
applyBackendPiExtensionBuckets(spec);
|
|
6439
|
+
}
|
|
6493
6440
|
// New generate path always emits DagSpec v4 + bindings.
|
|
6494
6441
|
spec.version = 4;
|
|
6495
6442
|
if (!spec.runtimeContract) {
|
|
@@ -6498,7 +6445,12 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6498
6445
|
spec.sourceBinding = buildDagSourceBinding(sources);
|
|
6499
6446
|
spec.taskContractBinding = taskContractBinding;
|
|
6500
6447
|
assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
|
|
6501
|
-
|
|
6448
|
+
if (template === "standard-dag" ||
|
|
6449
|
+
template === "review-gated-dag" ||
|
|
6450
|
+
template === "supervised-implementation") {
|
|
6451
|
+
stampTargetTemplateTransientRetryProfile(spec);
|
|
6452
|
+
}
|
|
6453
|
+
spec = parseDagSpec(spec);
|
|
6502
6454
|
assertValidDagSpec(spec);
|
|
6503
6455
|
return spec;
|
|
6504
6456
|
}
|
|
@@ -6549,6 +6501,36 @@ export async function buildHybridDagFromTask(sources, options = {}) {
|
|
|
6549
6501
|
function cloneTask(task, patch = {}) {
|
|
6550
6502
|
return { ...task, ...patch };
|
|
6551
6503
|
}
|
|
6504
|
+
/**
|
|
6505
|
+
* Backend-implementation Pi extension buckets (plan 2026-08-21 D3):
|
|
6506
|
+
* - read side: every non-writer, non-closeout Pi node gets navigation
|
|
6507
|
+
* extensions (pi-codegraph + pi-lens);
|
|
6508
|
+
* - write side: bounded writers get only pi-codegraph (pi-lens registers
|
|
6509
|
+
* ast_grep_replace, which can bypass the writeSet — not loading it is
|
|
6510
|
+
* simpler and safer than filtering after load);
|
|
6511
|
+
* - everything else (closeout, shell, static, other templates) stays closed.
|
|
6512
|
+
* Idempotent: never overwrites an explicit piExtensions a task declares.
|
|
6513
|
+
*/
|
|
6514
|
+
function applyBackendPiExtensionBuckets(spec) {
|
|
6515
|
+
for (const task of spec.tasks) {
|
|
6516
|
+
if (task.piExtensions !== undefined)
|
|
6517
|
+
continue;
|
|
6518
|
+
if (task.executor !== "pi")
|
|
6519
|
+
continue;
|
|
6520
|
+
if (task.id === "closeout-pi")
|
|
6521
|
+
continue;
|
|
6522
|
+
task.piExtensions =
|
|
6523
|
+
task.toolProfile === "write" ? ["pi-codegraph"] : ["pi-codegraph", "pi-lens"];
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
function stampTargetTemplateTransientRetryProfile(spec) {
|
|
6527
|
+
for (const task of spec.tasks) {
|
|
6528
|
+
if (isTargetTemplateImplementPi(task) ||
|
|
6529
|
+
isCanonicalFinalVerifyShellRetryCandidate(task)) {
|
|
6530
|
+
task.transientRetryProfile = TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE;
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6552
6534
|
/**
|
|
6553
6535
|
* Apply default Pi retry policies to generated DAG nodes:
|
|
6554
6536
|
* - safe read-only planner/scout/reviewer/verifier/supervisor/closeout
|
|
@@ -6569,6 +6551,10 @@ function applyDefaultReadOnlyRetryPolicy(spec) {
|
|
|
6569
6551
|
: DEFAULT_READ_ONLY_PI_RETRY_POLICY;
|
|
6570
6552
|
continue;
|
|
6571
6553
|
}
|
|
6554
|
+
if (isTargetTemplateImplementPi(task)) {
|
|
6555
|
+
task.retryPolicy = TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY;
|
|
6556
|
+
continue;
|
|
6557
|
+
}
|
|
6572
6558
|
if (isWriterTransportRetryCandidate(task)) {
|
|
6573
6559
|
task.retryPolicy = WRITER_TRANSPORT_RETRY_POLICY;
|
|
6574
6560
|
}
|
|
@@ -6829,6 +6815,7 @@ function buildReviewGatedHybridDag(standard, sources) {
|
|
|
6829
6815
|
}));
|
|
6830
6816
|
spec.tasks.splice(spec.tasks.length - 1, 0, buildReviewNode(sources), buildReviewVerdictRecoveryNode(sources), buildReviewGateNode(sources));
|
|
6831
6817
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
6818
|
+
stampTargetTemplateTransientRetryProfile(spec);
|
|
6832
6819
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
6833
6820
|
parseDagSpec(spec);
|
|
6834
6821
|
assertValidDagSpec(spec);
|
|
@@ -7160,6 +7147,9 @@ async function buildHardVerifyNode(sources) {
|
|
|
7160
7147
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
7161
7148
|
outputContract: "Archived hard verification stdout/stderr with exit codes; no worktree writes.",
|
|
7162
7149
|
subtask_prompt: "Run hard verification after repair round.",
|
|
7150
|
+
transientRetryProfile: sources.verifyCommands && commands.length > 0
|
|
7151
|
+
? TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE
|
|
7152
|
+
: undefined,
|
|
7163
7153
|
shell: {
|
|
7164
7154
|
commands,
|
|
7165
7155
|
verifyEvidence: buildVerifyEvidence({
|
|
@@ -7330,6 +7320,7 @@ async function buildSupervisedHybridDag(standard, sources) {
|
|
|
7330
7320
|
],
|
|
7331
7321
|
};
|
|
7332
7322
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
7323
|
+
stampTargetTemplateTransientRetryProfile(spec);
|
|
7333
7324
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
7334
7325
|
parseDagSpec(spec);
|
|
7335
7326
|
assertValidDagSpec(spec);
|
|
@@ -10,11 +10,13 @@ import { resolveContextPolicy } from "./context-policy.js";
|
|
|
10
10
|
import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
|
|
11
11
|
import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
|
|
12
12
|
import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
|
|
13
|
-
import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
13
|
+
import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
|
|
14
14
|
import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
|
|
15
15
|
import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
|
|
16
16
|
import { getStructuredContractValidator } from "./contract-output-registry.js";
|
|
17
17
|
import "./contract-validator-registrations.js";
|
|
18
|
+
import { allowedRepairReadPaths, auditRepairAttemptToolUse, buildStructuredOutputRepairPrompt, freezeStructuredOutputRepairContext, hasNonEmptyStructuredCandidate, isFrontendStructuredRepairSchemaId, isStructuredRepairableFailureCategory, persistStructuredAttemptRaw, sessionEventsByteLength, GOVERNANCE_BLOCKED_CATEGORY, STRUCTURED_REPAIR_EXHAUSTED_CATEGORY, } from "./structured-output-repair.js";
|
|
19
|
+
import { readFrontendCanonicalCandidate } from "./frontend-implementation-contract.js";
|
|
18
20
|
import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
19
21
|
import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
|
|
20
22
|
import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
@@ -135,6 +137,11 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
|
135
137
|
"Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by exactly one fenced openspec-citations block.",
|
|
136
138
|
"The runtime applies your patch to its protected contract skeleton. Do not emit schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff; do not emit the full contract.",
|
|
137
139
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
140
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
141
|
+
"```openspec-citations",
|
|
142
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
143
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
144
|
+
"```",
|
|
138
145
|
];
|
|
139
146
|
}
|
|
140
147
|
if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
|
|
@@ -142,6 +149,11 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
|
142
149
|
"Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by exactly one fenced openspec-citations block.",
|
|
143
150
|
"The delta contains only the fields you change; null deletes a key, arrays and scalars replace, and plain objects merge recursively. Do not emit a full contract or a schemaVersion/targets section.",
|
|
144
151
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
152
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
153
|
+
"```openspec-citations",
|
|
154
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
155
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
156
|
+
"```",
|
|
145
157
|
];
|
|
146
158
|
}
|
|
147
159
|
return [
|
|
@@ -248,6 +260,16 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
248
260
|
"</retry_instruction>",
|
|
249
261
|
].join("\n");
|
|
250
262
|
}
|
|
263
|
+
if (previousFailureCategory === "writer-clean-timeout") {
|
|
264
|
+
return [
|
|
265
|
+
basePrompt,
|
|
266
|
+
"",
|
|
267
|
+
"<retry_instruction>",
|
|
268
|
+
"Previous attempt ended in a clean provider/transport failure with zero attributed workspace writes and zero write/edit tool calls.",
|
|
269
|
+
"Do not invent existing implementation progress. Continue from the original task only if the workspace is still unchanged.",
|
|
270
|
+
"</retry_instruction>",
|
|
271
|
+
].join("\n");
|
|
272
|
+
}
|
|
251
273
|
if (previousFailureCategory !== "output-too-large") {
|
|
252
274
|
return basePrompt;
|
|
253
275
|
}
|
|
@@ -684,12 +706,18 @@ export async function executeDagNode(input) {
|
|
|
684
706
|
// Invalid or missing harness preserves the previous no-override behavior.
|
|
685
707
|
}
|
|
686
708
|
}
|
|
687
|
-
const
|
|
709
|
+
const specRetryPolicy = task.retryPolicy &&
|
|
688
710
|
(isSafeReadOnlyPiRetryCandidate(task) ||
|
|
689
711
|
isWriterEmptyDiffRetryCandidate(task) ||
|
|
690
712
|
isWriterTransportRetryCandidate(task))
|
|
691
713
|
? task.retryPolicy
|
|
692
714
|
: undefined;
|
|
715
|
+
const implicitVerifyRetryPolicy = specRetryPolicy === undefined &&
|
|
716
|
+
isCanonicalFinalVerifyShellRetryCandidate(task) &&
|
|
717
|
+
isTargetTemplateTransientRetryNode(task)
|
|
718
|
+
? VERIFY_SHELL_RETRY_POLICY
|
|
719
|
+
: undefined;
|
|
720
|
+
const retryPolicy = specRetryPolicy ?? implicitVerifyRetryPolicy;
|
|
693
721
|
const maxAttempts = retryPolicy?.maxAttempts ?? 1;
|
|
694
722
|
const attempts = [];
|
|
695
723
|
let totalAttemptWallDurationMs = 0;
|
|
@@ -731,10 +759,17 @@ export async function executeDagNode(input) {
|
|
|
731
759
|
let terminalResult;
|
|
732
760
|
let previousFailureCategory;
|
|
733
761
|
let previousProtocolReason;
|
|
762
|
+
let pendingStructuredArtifact;
|
|
763
|
+
let lastRepairAllowlist = [];
|
|
734
764
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
735
765
|
const attemptStartedAt = new Date().toISOString();
|
|
736
766
|
node.currentAttempt = attemptNumber;
|
|
737
767
|
node.livenessStatus = "active";
|
|
768
|
+
// Capture the session-events.jsonl length BEFORE the executor appends this
|
|
769
|
+
// attempt's events, so the post-attempt repair tool audit can be scoped to
|
|
770
|
+
// exactly this attempt's segment (B1: earlier attempts legitimately use
|
|
771
|
+
// ls/find and must not be attributed to the repair attempt).
|
|
772
|
+
const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
|
|
738
773
|
let result;
|
|
739
774
|
try {
|
|
740
775
|
validateRepairArtifactGateBeforeShell({
|
|
@@ -757,12 +792,53 @@ export async function executeDagNode(input) {
|
|
|
757
792
|
(acc[i.path] ??= []).push(i.detail);
|
|
758
793
|
return acc;
|
|
759
794
|
}, {});
|
|
795
|
+
let attemptPrompt = buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics);
|
|
796
|
+
if (attemptNumber > 1 &&
|
|
797
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
798
|
+
isStructuredRepairableFailureCategory(previousFailureCategory) &&
|
|
799
|
+
(await hasNonEmptyStructuredCandidate({
|
|
800
|
+
runDir,
|
|
801
|
+
nodeId,
|
|
802
|
+
beforeAttempt: attemptNumber,
|
|
803
|
+
}))) {
|
|
804
|
+
const originalCanonical = task.structuredContractOutput?.schemaId ===
|
|
805
|
+
"frontend-implementation-contract-revision-patch-v1"
|
|
806
|
+
? (await readFrontendCanonicalCandidate(runDir, "frontend-plan-pi"))
|
|
807
|
+
: undefined;
|
|
808
|
+
const frozen = await freezeStructuredOutputRepairContext({
|
|
809
|
+
runDir,
|
|
810
|
+
runId: state.runId,
|
|
811
|
+
taskId: spec.sourceBinding?.taskId ?? task.id,
|
|
812
|
+
nodeId,
|
|
813
|
+
nodeAttempt: attemptNumber,
|
|
814
|
+
targetSchemaId: task.structuredContractOutput.schemaId,
|
|
815
|
+
skeleton: task.structuredContractOutput?.skeleton,
|
|
816
|
+
originalCanonical,
|
|
817
|
+
latestStopReason: previousFailureCategory === "structured-output-truncated"
|
|
818
|
+
? "length"
|
|
819
|
+
: previousFailureCategory,
|
|
820
|
+
});
|
|
821
|
+
lastRepairAllowlist = allowedRepairReadPaths({
|
|
822
|
+
runDir,
|
|
823
|
+
context: frozen.context,
|
|
824
|
+
contextPath: frozen.relativePath,
|
|
825
|
+
});
|
|
826
|
+
attemptPrompt = await buildStructuredOutputRepairPrompt({
|
|
827
|
+
runDir,
|
|
828
|
+
context: frozen.context,
|
|
829
|
+
contextPath: frozen.relativePath,
|
|
830
|
+
contextSha256: frozen.sha256,
|
|
831
|
+
maxAttempts,
|
|
832
|
+
schemaGuidance: frontendStructuredArtifactRetryGuidance(task.structuredContractOutput.schemaId),
|
|
833
|
+
previousReason: previousProtocolReason,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
760
836
|
result = await executeNode({
|
|
761
837
|
task,
|
|
762
838
|
cwd,
|
|
763
839
|
model,
|
|
764
840
|
...(thinking ? { thinking } : {}),
|
|
765
|
-
prompt:
|
|
841
|
+
prompt: attemptPrompt,
|
|
766
842
|
attempt: attemptNumber,
|
|
767
843
|
reportActivity,
|
|
768
844
|
timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
|
|
@@ -778,6 +854,35 @@ export async function executeDagNode(input) {
|
|
|
778
854
|
durationMs: 0,
|
|
779
855
|
};
|
|
780
856
|
}
|
|
857
|
+
if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
|
|
858
|
+
const rawText = canonicalNodeOutput(result);
|
|
859
|
+
if (rawText.trim().length > 0) {
|
|
860
|
+
await persistStructuredAttemptRaw({
|
|
861
|
+
runDir,
|
|
862
|
+
nodeId,
|
|
863
|
+
attempt: attemptNumber,
|
|
864
|
+
text: rawText,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
if (attemptNumber > 1 && lastRepairAllowlist.length > 0) {
|
|
868
|
+
const audit = await auditRepairAttemptToolUse({
|
|
869
|
+
runDir,
|
|
870
|
+
nodeId,
|
|
871
|
+
allowedPaths: lastRepairAllowlist,
|
|
872
|
+
fromByteOffset: attemptSessionEventsOffset,
|
|
873
|
+
});
|
|
874
|
+
if (!audit.ok) {
|
|
875
|
+
result = {
|
|
876
|
+
...result,
|
|
877
|
+
ok: false,
|
|
878
|
+
failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
|
|
879
|
+
stderr: [result.stderr, audit.reason]
|
|
880
|
+
.filter(Boolean)
|
|
881
|
+
.join("\n"),
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
781
886
|
// R0: executor ok=true still fails closed when outputProtocol is violated.
|
|
782
887
|
// Valid semantic results (e.g. VERDICT: request-revision) pass validation.
|
|
783
888
|
if (result.ok && task.outputProtocol) {
|
|
@@ -814,16 +919,17 @@ export async function executeDagNode(input) {
|
|
|
814
919
|
structuredContractOutput: task.structuredContractOutput,
|
|
815
920
|
});
|
|
816
921
|
if (!contractCheck.ok) {
|
|
817
|
-
// A provider stopReason=length means the response was cut before the
|
|
818
|
-
// JSON contract could complete; separate it from an ordinary bad
|
|
819
|
-
// contract so the retry switches to a JSON-only output strategy.
|
|
820
922
|
const attemptStopReason = result.stopReason;
|
|
923
|
+
const failureCategory = contractCheck.classification === "governance-blocked"
|
|
924
|
+
? GOVERNANCE_BLOCKED_CATEGORY
|
|
925
|
+
: attemptStopReason === "length" ||
|
|
926
|
+
contractCheck.classification === "truncated"
|
|
927
|
+
? "structured-output-truncated"
|
|
928
|
+
: "invalid-output";
|
|
821
929
|
result = {
|
|
822
930
|
...result,
|
|
823
931
|
ok: false,
|
|
824
|
-
failureCategory
|
|
825
|
-
? "structured-output-truncated"
|
|
826
|
-
: "invalid-output",
|
|
932
|
+
failureCategory,
|
|
827
933
|
stderr: [result.stderr, contractCheck.reason]
|
|
828
934
|
.filter(Boolean)
|
|
829
935
|
.join("\n"),
|
|
@@ -838,6 +944,9 @@ export async function executeDagNode(input) {
|
|
|
838
944
|
stdout: contractCheck.normalizedText,
|
|
839
945
|
};
|
|
840
946
|
}
|
|
947
|
+
if (contractCheck.artifact) {
|
|
948
|
+
pendingStructuredArtifact = contractCheck.artifact;
|
|
949
|
+
}
|
|
841
950
|
previousProtocolReason = undefined;
|
|
842
951
|
}
|
|
843
952
|
}
|
|
@@ -850,6 +959,7 @@ export async function executeDagNode(input) {
|
|
|
850
959
|
const attemptFinishedAt = new Date().toISOString();
|
|
851
960
|
const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
|
|
852
961
|
totalAttemptWallDurationMs += attemptWallDurationMs;
|
|
962
|
+
const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
|
|
853
963
|
const attemptRecord = {
|
|
854
964
|
attempt: attemptNumber,
|
|
855
965
|
startedAt: attemptStartedAt,
|
|
@@ -861,6 +971,7 @@ export async function executeDagNode(input) {
|
|
|
861
971
|
stderr: result.stderr,
|
|
862
972
|
assistantText: result.assistantText,
|
|
863
973
|
failureCategory: result.failureCategory,
|
|
974
|
+
...(rawFailureCategory ? { rawFailureCategory } : {}),
|
|
864
975
|
backend: result.backend,
|
|
865
976
|
sdkAttempted: result.sdkAttempted,
|
|
866
977
|
tokensUsed: result.tokensUsed,
|
|
@@ -908,13 +1019,26 @@ export async function executeDagNode(input) {
|
|
|
908
1019
|
break;
|
|
909
1020
|
const canRetry = retryPolicy !== undefined && attemptNumber < maxAttempts;
|
|
910
1021
|
const retryable = retryPolicy !== undefined &&
|
|
1022
|
+
result.failureCategory !== GOVERNANCE_BLOCKED_CATEGORY &&
|
|
911
1023
|
(isRetryablePiFailureCategory(result.failureCategory, {
|
|
912
1024
|
retryCategories: retryPolicy.retryCategories,
|
|
913
1025
|
}) ||
|
|
914
1026
|
(result.failureCategory === "protocol-invalid" &&
|
|
915
1027
|
task.outputProtocol?.retryOnInvalid === true));
|
|
916
|
-
if (!canRetry || !retryable)
|
|
1028
|
+
if (!canRetry || !retryable) {
|
|
1029
|
+
if (!canRetry &&
|
|
1030
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
1031
|
+
isStructuredRepairableFailureCategory(result.failureCategory)) {
|
|
1032
|
+
result = {
|
|
1033
|
+
...result,
|
|
1034
|
+
failureCategory: STRUCTURED_REPAIR_EXHAUSTED_CATEGORY,
|
|
1035
|
+
};
|
|
1036
|
+
terminalResult = result;
|
|
1037
|
+
previousFailureCategory = result.failureCategory;
|
|
1038
|
+
node.failureCategory = result.failureCategory;
|
|
1039
|
+
}
|
|
917
1040
|
break;
|
|
1041
|
+
}
|
|
918
1042
|
const delayMs = computeBackoffDelayMs(attemptNumber + 1, retryPolicy);
|
|
919
1043
|
if (delayMs > 0) {
|
|
920
1044
|
const backoffStartedAt = new Date().toISOString();
|
|
@@ -1063,6 +1187,11 @@ export async function executeDagNode(input) {
|
|
|
1063
1187
|
node.structuredArtifactSchemaId = "backend-test-result-v1";
|
|
1064
1188
|
}
|
|
1065
1189
|
}
|
|
1190
|
+
else if (pendingStructuredArtifact) {
|
|
1191
|
+
node.structuredArtifactPath = pendingStructuredArtifact.path;
|
|
1192
|
+
node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
|
|
1193
|
+
node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
|
|
1194
|
+
}
|
|
1066
1195
|
}
|
|
1067
1196
|
const finishedAt = new Date().toISOString();
|
|
1068
1197
|
node.finishedAt = finishedAt;
|
|
@@ -116,7 +116,21 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
116
116
|
].join("\n"));
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
|
-
if (!record || record.status !== "FINISHED"
|
|
119
|
+
if (!record || record.status !== "FINISHED")
|
|
120
|
+
continue;
|
|
121
|
+
if (record.structuredArtifactPath &&
|
|
122
|
+
record.structuredArtifactSchemaId ===
|
|
123
|
+
"frontend-implementation-contract-v1") {
|
|
124
|
+
sections.push([
|
|
125
|
+
`## Upstream output: ${depId}`,
|
|
126
|
+
"Validated structured artifact (read-only runner evidence — use read tool to fetch; do not edit). Do not infer the contract from stdout.",
|
|
127
|
+
`- path: ${record.structuredArtifactPath}`,
|
|
128
|
+
`- schema: ${record.structuredArtifactSchemaId}`,
|
|
129
|
+
`- sha256: ${record.structuredArtifactSha256 ?? "unknown"}`,
|
|
130
|
+
].join("\n"));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!upstreamText)
|
|
120
134
|
continue;
|
|
121
135
|
const artifactKind = stdout ? "stdout" : "assistant";
|
|
122
136
|
const artifactPath = stdout
|