@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.11
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/CHANGELOG.md +379 -416
- package/dist/application/evaluation/budget.js +21 -0
- package/dist/application/evaluation/corpus-hash.js +10 -15
- package/dist/application/evaluation/corpus.js +2 -1
- package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
- package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
- package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
- package/dist/application/evaluation/frontend-pair-registration.js +143 -0
- package/dist/application/evaluation/frontend-paired-summary.js +150 -0
- package/dist/application/evaluation/frontend-run-observation.js +144 -0
- package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
- package/dist/application/evaluation/types.js +45 -12
- package/dist/application/task-lifecycle/observe.js +26 -1
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +11 -0
- package/dist/cli/program.js +4 -0
- package/dist/commands/task-advance.js +3 -5
- package/dist/commands/task-source-prepare.js +13 -1
- package/dist/executors/dag-pi/guards.js +4 -0
- package/dist/executors/dag-pi/model-config.js +30 -0
- package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
- package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
- package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/plan/record-tools.js +861 -0
- package/dist/executors/dag-pi/plan/schema.js +129 -0
- package/dist/executors/dag-pi/plan/session.js +1 -0
- package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
- package/dist/executors/dag-pi/sessions/index.js +6 -0
- package/dist/executors/dag-pi/sessions/plan-batches.js +280 -0
- package/dist/executors/dag-pi/sessions/plan-prompts.js +367 -0
- package/dist/executors/dag-pi/sessions/scout-parallel.js +197 -0
- package/dist/executors/dag-pi/sessions/segmented-plan.js +1184 -0
- package/dist/executors/dag-pi/sessions/writer-evidence.js +60 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +733 -5782
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +112 -19
- package/dist/executors/shell-executor.js +88 -12
- package/dist/executors/shell-presets.js +1 -1
- package/dist/executors/shell-write-guard.js +26 -3
- package/dist/infrastructure/console/operation-store.js +48 -14
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/file-identity.js +23 -0
- package/dist/shared/git-workspace-status.js +482 -0
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/shared/path-safety.js +2 -2
- package/dist/shared/wizard-local-telemetry.js +51 -0
- package/dist/task/config-types.js +22 -8
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -7
- package/dist/task/frontend-preflight.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +124 -5
- package/dist/task/source-prepare/prepare.js +6 -1
- package/dist/task/source-prepare/semantic-intake.js +18 -3
- package/dist/task/source-prepare/source-execution.js +65 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
- package/dist/task/source-prepare/source-provider-budget.js +290 -0
- package/dist/worker/console/chat/assistant-content.js +5 -34
- package/dist/worker/console/chat/chat-event-store.js +62 -10
- package/dist/worker/console/chat/context-insights.js +723 -0
- package/dist/worker/console/chat/conversation-nodes.js +383 -0
- package/dist/worker/console/chat/db-connections.js +854 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
- package/dist/worker/console/chat/pi-runtime.js +445 -36
- package/dist/worker/console/chat/provider-error.js +0 -12
- package/dist/worker/console/chat/repo-browser.js +52 -4
- package/dist/worker/console/chat/routes.js +577 -72
- package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
- package/dist/worker/console/chat/session-store.js +147 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/timeline-message-order.js +22 -0
- package/dist/worker/console/chat/tools.js +34 -0
- package/dist/worker/console/chat/turn-process.js +237 -66
- package/dist/worker/console/chat/user-turn-outline.js +19 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/dag-confirmation.js +3 -0
- package/dist/worker/console/dag-execution-receipt.js +3 -0
- package/dist/worker/console/operator-actions.js +32 -3
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/prd-intake-bridge.js +5 -12
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/server.js +5 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
- package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
- package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
- package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
- package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
- package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
- package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-context.js +2 -0
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/observe/node-context-insights.js +479 -0
- package/dist/worker/observe/static/console-theme.css +174 -0
- package/dist/worker/observe/static/console-theme.js +36 -0
- package/dist/worker/observe/static/operator-chrome.css +85 -0
- package/dist/worker/observe/static/operator-chrome.js +51 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
- package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
- package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- package/dist/workflows/dag/checkpoint.js +686 -0
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-contract-facts.js +11 -8
- package/dist/workflows/dag/frontend-design-policy.js +6 -6
- package/dist/workflows/dag/frontend-durable-tools.js +90 -70
- package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
- package/dist/workflows/dag/frontend-plan-canary.js +66 -16
- package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
- package/dist/workflows/dag/frontend-plan-progress.js +92 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
- package/dist/workflows/dag/frontend-recovery-run.js +51 -46
- package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
- package/dist/workflows/dag/frontend-review-context.js +29 -1
- package/dist/workflows/dag/frontend-review-scopes.js +92 -2
- package/dist/workflows/dag/frontend-risk.js +8 -3
- package/dist/workflows/dag/frontend-root-observation.js +261 -0
- package/dist/workflows/dag/frontend-session-budget.js +52 -39
- package/dist/workflows/dag/frontend-session-context.js +10 -0
- package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +3 -1
- package/dist/workflows/dag/hybrid/sources.js +1812 -0
- package/dist/workflows/dag/hybrid/templates/backend-test.js +1807 -0
- package/dist/workflows/dag/hybrid/templates/frontend-test.js +702 -0
- package/dist/workflows/dag/hybrid/templates/frontend.js +1529 -0
- package/dist/workflows/dag/hybrid/templates/index.js +8 -0
- package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +449 -0
- package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +495 -0
- package/dist/workflows/dag/hybrid/templates/shared.js +101 -0
- package/dist/workflows/dag/hybrid/templates/standard.js +265 -0
- package/dist/workflows/dag/hybrid/types.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +57 -7106
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/output-protocol.js +1 -0
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -66
- package/dist/workflows/dag/runner.js +38 -1159
- package/dist/workflows/dag/terminal-status.js +502 -0
- package/dist/workflows/dag/types.js +15 -8
- package/dist/workflows/loop/benchmark.js +11 -8
- package/docs/README.md +1 -1
- package/docs/architecture/dag-execution.md +1 -1
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/governance/harness-methodology-verification.md +11 -0
- package/docs/templates/backend-test-dag.json +5 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +6 -4
- package/skills/frontend-design-review/SKILL.md +8 -11
- package/skills/frontend-design-review/references/review-checklist.md +3 -4
- package/skills/frontend-review/SKILL.md +5 -1
- package/skills/loop-agent/references/command-reference.md +8 -0
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
|
@@ -790,7 +790,7 @@ export async function executePiStep(options) {
|
|
|
790
790
|
reuseRuntimeActive,
|
|
791
791
|
});
|
|
792
792
|
}
|
|
793
|
-
if (options.reserveProviderRequest || !shouldFallbackSdkToCli(sdkResult, validationIssues, options)) {
|
|
793
|
+
if (options.frontendPlanControl || options.reserveProviderRequest || !shouldFallbackSdkToCli(sdkResult, validationIssues, options)) {
|
|
794
794
|
const failureCategory = validationIssues.length > 0
|
|
795
795
|
? "invalid-output"
|
|
796
796
|
: sdkResult.failureCategory;
|
|
@@ -1104,6 +1104,11 @@ export function classifyPiFailure(input) {
|
|
|
1104
1104
|
// Provider/transport verdicts read error-shaped channels only; the raw stdout
|
|
1105
1105
|
// stream embeds assistant text, tool arguments and findings verbatim.
|
|
1106
1106
|
const combined = `${input.stderr}\n${extractProviderFailureText(input.stdout)}`.toLowerCase();
|
|
1107
|
+
// An explicit spend-budget stop is terminal even if a transport wrapper also
|
|
1108
|
+
// reports a closed connection. Read only the error channels above, never
|
|
1109
|
+
// assistant/tool prose; generic quota and context limits keep their categories.
|
|
1110
|
+
if (/(?<![\w-])(?:frontend_provider_budget_exhausted|budget_exhausted|budget_breach|budget exhausted)(?![\w-])/.test(combined))
|
|
1111
|
+
return "budget_breach";
|
|
1107
1112
|
// Context overflow (400 request-too-large / context window exceeded).
|
|
1108
1113
|
// Must be detected BEFORE the generic rate-limit/quota phrases: an overflow
|
|
1109
1114
|
// error often contains "too many tokens" / "maximum context" which would
|
|
@@ -9,33 +9,65 @@ import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DE
|
|
|
9
9
|
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
10
10
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
11
11
|
import { createCompactionController, createContextPressureExtension, createSiftBridgeState, inMemoryCompactionSettings, isDagSiftEnabled, parseCompactionPolicy, parseSiftBridgeConfig, SIFT_RETRIEVE_TOOL_NAME, } from "../shared/pi-context-pressure/index.js";
|
|
12
|
+
import { DAG_TELEMETRY_SESSION_ID_ENV, injectWizardLocalTelemetryHeaders, isTelemetrySessionId, } from "../shared/wizard-local-telemetry.js";
|
|
12
13
|
/** Maximum number of same-session continuations after provider output truncation. */
|
|
13
14
|
export const PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX = 3;
|
|
14
15
|
export const PI_SDK_OUTPUT_LIMIT_CONTINUE_PROMPT = "继续。上一轮因输出长度上限(stopReason=length)截断;当前会话、工具与已完成工作仍保留。只完成尚未完成的部分,不重做已成功的写入或事实提交。";
|
|
16
|
+
export function createDagWizardLocalTelemetryHeadersExtension(telemetrySessionId) {
|
|
17
|
+
if (!isTelemetrySessionId(telemetrySessionId))
|
|
18
|
+
return undefined;
|
|
19
|
+
return {
|
|
20
|
+
name: "dag-wizard-local-telemetry-headers",
|
|
21
|
+
hidden: true,
|
|
22
|
+
factory: (api) => {
|
|
23
|
+
api.on("before_provider_headers", async (rawEvent, context) => {
|
|
24
|
+
if (!context ||
|
|
25
|
+
!rawEvent.headers ||
|
|
26
|
+
typeof rawEvent.headers !== "object") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await injectWizardLocalTelemetryHeaders({
|
|
30
|
+
headers: rawEvent.headers,
|
|
31
|
+
context,
|
|
32
|
+
telemetrySessionId,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
15
38
|
/** Wrap the actual stream seam: tool turns, retries and compaction all await it. */
|
|
16
|
-
export async function installPiProviderRequestBudget(session, reserve, onFailure, capacity) {
|
|
39
|
+
export async function installPiProviderRequestBudget(session, reserve, onFailure, capacity, plan) {
|
|
17
40
|
const agent = session.agent;
|
|
18
41
|
const original = agent?.streamFunction;
|
|
19
42
|
if (!agent || typeof original !== "function")
|
|
20
|
-
throw Error("FRONTEND_PROVIDER_BUDGET_UNSUPPORTED: SDK actual request stream unavailable");
|
|
43
|
+
throw new Error("FRONTEND_PROVIDER_BUDGET_UNSUPPORTED: SDK actual request stream unavailable");
|
|
21
44
|
const { createAssistantMessageEventStream } = await import("@earendil-works/pi-ai");
|
|
22
45
|
agent.streamFunction = async (...args) => {
|
|
23
46
|
try {
|
|
24
47
|
if (args[2]?.signal?.aborted)
|
|
25
|
-
throw Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
48
|
+
throw new Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
49
|
+
const protocolStop = plan?.readStop();
|
|
50
|
+
if (protocolStop)
|
|
51
|
+
throw Object.assign(new Error(`${protocolStop.code}: ${protocolStop.error}`), { frontendPlanStop: true });
|
|
26
52
|
if (capacity) {
|
|
27
53
|
const measurement = measureFrontendRequestEnvelope(args[1], args[0], capacity.policy);
|
|
28
54
|
capacity.observe(measurement);
|
|
29
55
|
if (measurement.exceedsEstimate)
|
|
30
|
-
throw Error(`FRONTEND_CONTEXT_ESTIMATE_EXCEEDED: estimated=${measurement.estimatedTokens} available=${measurement.availableInputTokens}; estimator=${measurement.estimatorVersion}; registry capability is not a measured gateway limit`);
|
|
56
|
+
throw new Error(`FRONTEND_CONTEXT_ESTIMATE_EXCEEDED: estimated=${measurement.estimatedTokens} available=${measurement.availableInputTokens}; estimator=${measurement.estimatorVersion}; registry capability is not a measured gateway limit`);
|
|
31
57
|
}
|
|
32
58
|
await reserve();
|
|
33
59
|
if (args[2]?.signal?.aborted)
|
|
34
|
-
throw Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
60
|
+
throw new Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
61
|
+
const stoppedDuringReservation = plan?.readStop();
|
|
62
|
+
if (stoppedDuringReservation)
|
|
63
|
+
throw Object.assign(new Error(`${stoppedDuringReservation.code}: ${stoppedDuringReservation.error}`), { frontendPlanStop: true });
|
|
35
64
|
}
|
|
36
65
|
catch (error) {
|
|
37
66
|
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
-
|
|
67
|
+
if (error.frontendPlanStop)
|
|
68
|
+
plan?.onStop();
|
|
69
|
+
else
|
|
70
|
+
onFailure(message);
|
|
39
71
|
const stream = createAssistantMessageEventStream();
|
|
40
72
|
const model = args[0] ?? {};
|
|
41
73
|
stream.push({ type: "error", reason: "error", error: { role: "assistant", content: [], api: model.api ?? "unknown", provider: model.provider ?? "unknown", model: model.id ?? "unknown", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "error", errorMessage: message, timestamp: Date.now() } });
|
|
@@ -101,7 +133,7 @@ export function setPiSdkImportOverrideForTests(fn) {
|
|
|
101
133
|
export function setPiSdkModuleOverrideForTests(fn) {
|
|
102
134
|
sdkModuleOverrideForTests = fn;
|
|
103
135
|
}
|
|
104
|
-
/** Check whether the Pi SDK runtime dependency satisfies the 0.
|
|
136
|
+
/** Check whether the Pi SDK runtime dependency satisfies the 0.84.4 runtime contract. */
|
|
105
137
|
export async function checkPiSdkAvailability(_repoRoot) {
|
|
106
138
|
if (sdkSessionFactoryOverride) {
|
|
107
139
|
return { ok: true, detail: "pi SDK session factory override active" };
|
|
@@ -117,10 +149,10 @@ export async function checkPiSdkAvailability(_repoRoot) {
|
|
|
117
149
|
typeof ModelRuntime?.create !== "function") {
|
|
118
150
|
return {
|
|
119
151
|
ok: false,
|
|
120
|
-
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.
|
|
152
|
+
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.84.4 contract)",
|
|
121
153
|
};
|
|
122
154
|
}
|
|
123
|
-
return { ok: true, detail: "pi SDK 0.
|
|
155
|
+
return { ok: true, detail: "pi SDK 0.84.4 contract available" };
|
|
124
156
|
}
|
|
125
157
|
catch (error) {
|
|
126
158
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -290,6 +322,8 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
290
322
|
controller,
|
|
291
323
|
sift,
|
|
292
324
|
});
|
|
325
|
+
const telemetryExtension = createDagWizardLocalTelemetryHeadersExtension(input.telemetrySessionId);
|
|
326
|
+
const extensionFactories = [pressureExtension, telemetryExtension].filter((extension) => Boolean(extension));
|
|
293
327
|
const loader = new DefaultResourceLoader({
|
|
294
328
|
cwd: input.cwd,
|
|
295
329
|
agentDir,
|
|
@@ -299,7 +333,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
299
333
|
...(input.additionalExtensionPaths && input.additionalExtensionPaths.length > 0
|
|
300
334
|
? { additionalExtensionPaths: input.additionalExtensionPaths }
|
|
301
335
|
: {}),
|
|
302
|
-
...(
|
|
336
|
+
...(extensionFactories.length > 0 ? { extensionFactories } : {}),
|
|
303
337
|
...(settingsManager ? { settingsManager } : {}),
|
|
304
338
|
appendSystemPromptOverride: (base) => [
|
|
305
339
|
...base,
|
|
@@ -625,6 +659,34 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
625
659
|
let terminationConfirmed = true;
|
|
626
660
|
let stderr = "";
|
|
627
661
|
let providerBudgetFailure;
|
|
662
|
+
let providerBudgetAbort = false;
|
|
663
|
+
let providerBudgetStopRequested = false;
|
|
664
|
+
let resolveProviderBudgetStop;
|
|
665
|
+
const providerBudgetStopPromise = new Promise(resolve => { resolveProviderBudgetStop = resolve; });
|
|
666
|
+
const frontendBudgetEnabled = Boolean(options.reserveProviderRequest || options.frontendExecutionPolicy || options.frontendPlanControl);
|
|
667
|
+
const observeProviderBudgetError = (message) => {
|
|
668
|
+
if (!frontendBudgetEnabled || typeof message !== "string" || providerBudgetStopRequested)
|
|
669
|
+
return;
|
|
670
|
+
if (classifyPiFailure({ assistantText: "", exitCode: 1, outputTooLarge: false, stderr: message, stdout: "", timedOut: false }) !== "budget_breach")
|
|
671
|
+
return;
|
|
672
|
+
providerBudgetFailure = message;
|
|
673
|
+
providerBudgetStopRequested = true;
|
|
674
|
+
if (!stderr.includes(message))
|
|
675
|
+
appendStderr(message);
|
|
676
|
+
resolveProviderBudgetStop("provider-budget");
|
|
677
|
+
};
|
|
678
|
+
let protocolStop;
|
|
679
|
+
let protocolAbort = false;
|
|
680
|
+
let unsubscribePlan;
|
|
681
|
+
let resolveProtocolStop;
|
|
682
|
+
const protocolStopPromise = new Promise(resolve => { resolveProtocolStop = resolve; });
|
|
683
|
+
const requestProtocolStop = () => {
|
|
684
|
+
const stop = options.frontendPlanControl?.readStop();
|
|
685
|
+
if (stop && stopReason !== "aborted" && !providerBudgetFailure) {
|
|
686
|
+
protocolStop = stop;
|
|
687
|
+
resolveProtocolStop("protocol-stop");
|
|
688
|
+
}
|
|
689
|
+
};
|
|
628
690
|
let capabilitiesObservation = frontendModelCapabilities(undefined);
|
|
629
691
|
const requestEnvelopeObservation = { peakBytes: 0, peakEstimatedTokens: 0, estimatorVersion: "utf8-bytes-div3-v1-estimate" };
|
|
630
692
|
const stdoutPreview = new BoundedTextPreview("stdout");
|
|
@@ -718,6 +780,9 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
718
780
|
provider: modelConfig.provider,
|
|
719
781
|
model: modelConfig.model,
|
|
720
782
|
thinking: modelConfig.thinking,
|
|
783
|
+
telemetrySessionId: isTelemetrySessionId(process.env[DAG_TELEMETRY_SESSION_ID_ENV])
|
|
784
|
+
? process.env[DAG_TELEMETRY_SESSION_ID_ENV]
|
|
785
|
+
: undefined,
|
|
721
786
|
...(Array.isArray(writerCustomTools) && writerCustomTools.length > 0
|
|
722
787
|
? { customTools: writerCustomTools }
|
|
723
788
|
: {}),
|
|
@@ -730,12 +795,14 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
730
795
|
: {}),
|
|
731
796
|
});
|
|
732
797
|
capabilitiesObservation = frontendModelCapabilities(session.agent?.state?.model);
|
|
733
|
-
if (options.reserveProviderRequest || options.frontendExecutionPolicy)
|
|
734
|
-
await installPiProviderRequestBudget(session, options.reserveProviderRequest ?? (async () => { }), message => {
|
|
798
|
+
if (options.frontendPlanControl || options.reserveProviderRequest || options.frontendExecutionPolicy)
|
|
799
|
+
await installPiProviderRequestBudget(session, options.reserveProviderRequest ?? (async () => { }), message => { if (protocolAbort && message.includes("REQUEST_ABORTED"))
|
|
800
|
+
return; providerBudgetFailure = message; appendStderr(message); observeProviderBudgetError(message); }, options.frontendExecutionPolicy ? { policy: options.frontendExecutionPolicy, observe: measurement => {
|
|
735
801
|
capabilitiesObservation = measurement.capabilities;
|
|
736
802
|
requestEnvelopeObservation.peakBytes = Math.max(requestEnvelopeObservation.peakBytes, measurement.bytes);
|
|
737
803
|
requestEnvelopeObservation.peakEstimatedTokens = Math.max(requestEnvelopeObservation.peakEstimatedTokens, measurement.estimatedTokens);
|
|
738
|
-
} } : undefined);
|
|
804
|
+
} } : undefined, options.frontendPlanControl ? { readStop: options.frontendPlanControl.readStop, onStop: requestProtocolStop } : undefined);
|
|
805
|
+
unsubscribePlan = options.frontendPlanControl?.subscribe(requestProtocolStop);
|
|
739
806
|
let resolveStall;
|
|
740
807
|
let resolveSettlement;
|
|
741
808
|
let resolveContextBudget;
|
|
@@ -816,13 +883,23 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
816
883
|
}
|
|
817
884
|
}
|
|
818
885
|
const observationMessage = isRecord(event.message) ? event.message : undefined;
|
|
819
|
-
|
|
886
|
+
observeProviderBudgetError(observationMessage?.errorMessage ?? (event.type === "auto_retry_start" ? event.errorMessage : undefined));
|
|
887
|
+
if (providerBudgetStopRequested && event.type === "auto_retry_start") {
|
|
888
|
+
// Pi emits the notification before installing its retry controller.
|
|
889
|
+
// Cancel after that synchronous setup, including when abort() already
|
|
890
|
+
// ran on message_end and is still waiting for the session to idle.
|
|
891
|
+
queueMicrotask(() => { try {
|
|
892
|
+
session?.abortRetry?.();
|
|
893
|
+
}
|
|
894
|
+
catch { /* abort/dispose confirmation remains authoritative */ } });
|
|
895
|
+
}
|
|
896
|
+
if (typeof observationMessage?.errorMessage === "string" && !(protocolStop && observationMessage.errorMessage === `${protocolStop.code}: ${protocolStop.error}`))
|
|
820
897
|
providerErrorMessage = observationMessage.errorMessage;
|
|
821
898
|
try {
|
|
822
899
|
readObservation.observe(event);
|
|
823
900
|
}
|
|
824
901
|
catch { /* observation must not change execution */ }
|
|
825
|
-
if (!(typeof observationMessage?.errorMessage === "string" && observationMessage.errorMessage
|
|
902
|
+
if (!(typeof observationMessage?.errorMessage === "string" && /^(FRONTEND_PROVIDER_|FRONTEND_PLAN_NO_PROGRESS|FRONTEND_CONTEXT_ESTIMATE_EXCEEDED)/.test(observationMessage.errorMessage)) && (event.usage || observationMessage?.usage || event.tokenUsage))
|
|
826
903
|
observationEvents.push({ responseId: event.responseId, messageId: event.messageId, usage: event.usage, tokenUsage: event.tokenUsage, message: observationMessage ? { role: observationMessage.role, id: observationMessage.id, responseId: observationMessage.responseId, usage: observationMessage.usage } : undefined });
|
|
827
904
|
const usageSample = extractSdkUsageSample(event);
|
|
828
905
|
if (usageSample)
|
|
@@ -870,8 +947,10 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
870
947
|
promptPromise.then(() => "done"),
|
|
871
948
|
settlementPromise,
|
|
872
949
|
...(timeoutPromise ? [timeoutPromise] : []),
|
|
950
|
+
...(frontendBudgetEnabled ? [providerBudgetStopPromise] : []),
|
|
873
951
|
...(stallPromise ? [stallPromise] : []),
|
|
874
952
|
...(contextBudgetPromise ? [contextBudgetPromise] : []),
|
|
953
|
+
...(options.frontendPlanControl ? [protocolStopPromise] : []),
|
|
875
954
|
]);
|
|
876
955
|
};
|
|
877
956
|
const disposeSession = async () => {
|
|
@@ -900,7 +979,18 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
900
979
|
let continuesSent = 0;
|
|
901
980
|
let raced = await runTurn(promptMessage);
|
|
902
981
|
while (true) {
|
|
903
|
-
if (
|
|
982
|
+
if (providerBudgetStopRequested) {
|
|
983
|
+
providerBudgetAbort = true;
|
|
984
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
985
|
+
await disposeSession();
|
|
986
|
+
}
|
|
987
|
+
else if ((raced === "protocol-stop" || ((raced === "done" || raced === "settled") && protocolStop)) && !providerBudgetFailure && !contextBudgetExhausted && stopReason !== "aborted") {
|
|
988
|
+
protocolAbort = true;
|
|
989
|
+
appendStderr(`${protocolStop.code}: ${protocolStop.error}; fingerprint=${protocolStop.fingerprint}`);
|
|
990
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
991
|
+
await disposeSession();
|
|
992
|
+
}
|
|
993
|
+
else if (raced === "context-budget" || contextBudgetExhausted) {
|
|
904
994
|
appendStderr(contextBudgetDetail);
|
|
905
995
|
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
906
996
|
await disposeSession();
|
|
@@ -922,6 +1012,8 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
922
1012
|
maxContinues: PI_SDK_STALL_CONTINUE_MAX,
|
|
923
1013
|
});
|
|
924
1014
|
raced = await runTurn(PI_SDK_STALL_CONTINUE_PROMPT);
|
|
1015
|
+
if (raced === "protocol-stop" || raced === "context-budget" || raced === "provider-budget")
|
|
1016
|
+
continue;
|
|
925
1017
|
if (raced !== "done" && raced !== "settled") {
|
|
926
1018
|
await failClosedAfterWait(raced === "stall" ? "stall" : "absolute-timeout", raced === "stall"
|
|
927
1019
|
? "pi SDK stall continue did not recover; treating as timeout"
|
|
@@ -962,6 +1054,7 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
962
1054
|
if (stallHandle)
|
|
963
1055
|
clearTimeout(stallHandle);
|
|
964
1056
|
unsubscribe?.();
|
|
1057
|
+
unsubscribePlan?.();
|
|
965
1058
|
if (session && !disposeAttempted) {
|
|
966
1059
|
disposeAttempted = true;
|
|
967
1060
|
const disposeConfirmed = await runSessionActionWithGrace("dispose", () => session.dispose());
|
|
@@ -976,7 +1069,7 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
976
1069
|
}
|
|
977
1070
|
}
|
|
978
1071
|
}
|
|
979
|
-
if (providerErrorMessage && stopReason === "error" && !stderr)
|
|
1072
|
+
if (providerErrorMessage && stopReason === "error" && !stderr.includes(providerErrorMessage))
|
|
980
1073
|
appendStderr(providerErrorMessage);
|
|
981
1074
|
if (options.outputLimitRecovery && stopReason === "length" && !committedTerminalObserved && outputContinuesSent >= PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX) {
|
|
982
1075
|
const note = serializeSessionEvent({ type: "loop-agent-output-limit-exhausted", maxContinues: PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX });
|
|
@@ -996,9 +1089,9 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
996
1089
|
? collected.parsedEvents
|
|
997
1090
|
: (fallbackParsed?.parsedEvents ?? 0);
|
|
998
1091
|
const tokensUsed = aggregateSdkTokenUsage(usageSamples);
|
|
999
|
-
const cancelled = stopReason === "aborted";
|
|
1092
|
+
const cancelled = stopReason === "aborted" && !protocolAbort && !providerBudgetAbort;
|
|
1000
1093
|
const outputLimitExhausted = Boolean(options.outputLimitRecovery && stopReason === "length" && !committedTerminalObserved && !timedOut && !stderr);
|
|
1001
|
-
const failureCategory = !terminationConfirmed ? "termination-unconfirmed" :
|
|
1094
|
+
const failureCategory = !terminationConfirmed ? "termination-unconfirmed" : cancelled ? "cancelled" : outputLimitExhausted ? "output-limit" : providerBudgetFailure ? (providerBudgetFailure.includes("REQUEST_ABORTED") ? "interrupted" : providerBudgetFailure.startsWith("FRONTEND_CONTEXT_ESTIMATE_EXCEEDED") ? "context-budget-exhausted" : "budget_breach") : contextBudgetExhausted ? "context-budget-exhausted" : protocolStop && !providerErrorMessage && !timedOut ? "invalid-output" : terminationConfirmed
|
|
1002
1095
|
? classifyPiFailure({
|
|
1003
1096
|
assistantText,
|
|
1004
1097
|
exitCode: timedOut ? 1 : stderr ? 1 : 0,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { collectExistingFrontendVerificationPaths } from "../workflows/dag/frontend-implementation-contract.js";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
2
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
+
import { inspectFrontendTestCommand, compareFrontendTestObservation, parseFrontendTestExecutionReport } from "../workflows/dag/frontend-test-execution-evidence.js";
|
|
4
5
|
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
6
7
|
import path from "node:path";
|
|
@@ -493,6 +494,25 @@ export function buildFrontendVerificationEvidence(input) {
|
|
|
493
494
|
lintCommandTexts: input.bundle.lintEvidence?.commandTexts ?? [],
|
|
494
495
|
});
|
|
495
496
|
const entryByCommand = new Map(directory.map((entry) => [entry.command.trim(), entry]));
|
|
497
|
+
const entryForExecutedCommand = (command) => {
|
|
498
|
+
const normalized = command.trim();
|
|
499
|
+
const exact = entryByCommand.get(normalized);
|
|
500
|
+
if (exact)
|
|
501
|
+
return exact;
|
|
502
|
+
// Behavior commands with Vitest JSON capability are executed with the
|
|
503
|
+
// runtime-owned reporter/outputFile suffix. Preserve their frozen command
|
|
504
|
+
// identity in review evidence, but accept *only* that exact suffix so an
|
|
505
|
+
// arbitrary command sharing a textual prefix is never attributed to a
|
|
506
|
+
// verification-directory entry.
|
|
507
|
+
return directory.find((entry) => {
|
|
508
|
+
if (entry.lane !== "behavior")
|
|
509
|
+
return false;
|
|
510
|
+
if (!normalized.startsWith(entry.command.trim()))
|
|
511
|
+
return false;
|
|
512
|
+
const suffix = normalized.slice(entry.command.trim().length);
|
|
513
|
+
return /^(?:\s+--)?\s+--reporter=json\s+--outputFile=(?:'[^']*'|"[^"]*"|\S+)$/u.test(suffix);
|
|
514
|
+
});
|
|
515
|
+
};
|
|
496
516
|
return {
|
|
497
517
|
schemaVersion: 1,
|
|
498
518
|
schemaId: "frontend-verification-evidence-v1",
|
|
@@ -503,7 +523,7 @@ export function buildFrontendVerificationEvidence(input) {
|
|
|
503
523
|
lintStatus: input.lintStatus,
|
|
504
524
|
lintConfigured: input.lintConfigured,
|
|
505
525
|
commands: input.results.map((result, index) => {
|
|
506
|
-
const entry =
|
|
526
|
+
const entry = entryForExecutedCommand(result.command);
|
|
507
527
|
return {
|
|
508
528
|
index: index + 1,
|
|
509
529
|
commandId: entry?.commandId ?? null,
|
|
@@ -1993,12 +2013,29 @@ async function deriveUnresolvedVerificationEntrypointRefs(runDir, frozenCommandI
|
|
|
1993
2013
|
return [];
|
|
1994
2014
|
}
|
|
1995
2015
|
}
|
|
2016
|
+
async function inspectFrontendBundleTests(bundle, cwd, workspaceRoot, phase, writeSet = []) {
|
|
2017
|
+
const inspected = new Map();
|
|
2018
|
+
const failures = [];
|
|
2019
|
+
for (const [index, command] of bundle.behaviorCommands.entries()) {
|
|
2020
|
+
const label = bundle.behaviorEvidence.commandLabels[index];
|
|
2021
|
+
const frozen = bundle.behaviorEvidence.preflight.find(entry => entry.label === label)?.testObservation;
|
|
2022
|
+
const observed = await inspectFrontendTestCommand({ command, cwd, workspaceRoot, allowedPaths: writeSet });
|
|
2023
|
+
const reason = frozen ? compareFrontendTestObservation(frozen, observed.observation, phase, writeSet) : "missing frozen test observation preflight; regenerate the DAG";
|
|
2024
|
+
if (reason)
|
|
2025
|
+
failures.push({ code: "FRONTEND_TEST_OBSERVATION_INVALID", commandId: frozen?.commandId, command, reason });
|
|
2026
|
+
else
|
|
2027
|
+
inspected.set(command, observed);
|
|
2028
|
+
}
|
|
2029
|
+
return { inspected, failures };
|
|
2030
|
+
}
|
|
1996
2031
|
async function executeFrontendVerificationBundle(input, meta) {
|
|
1997
2032
|
const started = Date.now();
|
|
1998
2033
|
const shell = input.task.shell;
|
|
1999
2034
|
const bundle = shell.frontendVerificationBundle;
|
|
2000
2035
|
const cwd = resolveShellCwd(input.cwd, shell.cwd);
|
|
2001
2036
|
const results = [];
|
|
2037
|
+
const testPreflight = await inspectFrontendBundleTests(bundle, cwd, input.cwd, "execution");
|
|
2038
|
+
let testObservationFailure = testPreflight.failures[0];
|
|
2002
2039
|
const commandResultsByKey = new Map();
|
|
2003
2040
|
const commandKey = (command) => `${cwd}\u0000${command.trim()}`;
|
|
2004
2041
|
const reuseCommandResult = (result, command) => {
|
|
@@ -2027,8 +2064,9 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2027
2064
|
const successfulLabels = new Map();
|
|
2028
2065
|
const successfulCommandTexts = new Map();
|
|
2029
2066
|
const executedTests = new Map();
|
|
2067
|
+
const testExecutionEvidence = [];
|
|
2030
2068
|
const commandExecutionEvidence = new Map();
|
|
2031
|
-
for (const command of bundle.lintCommands ?? []) {
|
|
2069
|
+
for (const command of testObservationFailure ? [] : bundle.lintCommands ?? []) {
|
|
2032
2070
|
const key = commandKey(command);
|
|
2033
2071
|
const cached = commandResultsByKey.get(key);
|
|
2034
2072
|
let result = cached ? reuseCommandResult(cached, command) : undefined;
|
|
@@ -2085,17 +2123,26 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2085
2123
|
];
|
|
2086
2124
|
const failedGroups = new Set();
|
|
2087
2125
|
const groupFailures = [];
|
|
2088
|
-
for (const group of lintBlocked ? [] : groups) {
|
|
2126
|
+
for (const group of lintBlocked || testObservationFailure ? [] : groups) {
|
|
2089
2127
|
for (const [index, command] of group.commands.entries()) {
|
|
2090
|
-
if (failedGroups.has(group.name))
|
|
2128
|
+
if (failedGroups.has(group.name) || testObservationFailure)
|
|
2091
2129
|
break;
|
|
2130
|
+
if (testPreflight.inspected.has(command)) {
|
|
2131
|
+
const fresh = await inspectFrontendBundleTests(bundle, cwd, input.cwd, "execution");
|
|
2132
|
+
testObservationFailure = fresh.failures[0];
|
|
2133
|
+
if (testObservationFailure)
|
|
2134
|
+
break;
|
|
2135
|
+
for (const [text, observed] of fresh.inspected)
|
|
2136
|
+
testPreflight.inspected.set(text, observed);
|
|
2137
|
+
}
|
|
2092
2138
|
const key = commandKey(command);
|
|
2093
2139
|
const cached = commandResultsByKey.get(key);
|
|
2094
2140
|
let result = cached ? reuseCommandResult(cached, command) : undefined;
|
|
2095
2141
|
if (!result) {
|
|
2096
2142
|
const commandNumber = results.length + 1;
|
|
2097
2143
|
const reportPath = path.join(meta.runDir, input.task.id, "commands", `${commandNumber}-${randomUUID()}.vitest.json`);
|
|
2098
|
-
const
|
|
2144
|
+
const observedTest = testPreflight.inspected.get(command);
|
|
2145
|
+
const observedCommand = observedTest?.withReporter?.(reportPath);
|
|
2099
2146
|
if (observedCommand)
|
|
2100
2147
|
await mkdir(path.dirname(reportPath), { recursive: true });
|
|
2101
2148
|
result = await executeShellCommand({
|
|
@@ -2116,7 +2163,17 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2116
2163
|
// Vitest reports physical paths (e.g. /private/var on macOS).
|
|
2117
2164
|
// Match those in workspace coordinates even when shell.cwd is
|
|
2118
2165
|
// a subdirectory or the caller used a directory alias.
|
|
2119
|
-
|
|
2166
|
+
const rawReport = await readFile(reportPath, "utf8");
|
|
2167
|
+
const reportSha256 = createHash("sha256").update(rawReport).digest("hex");
|
|
2168
|
+
commandExecutionEvidence.set(key, parseFrontendTestExecutionReport(JSON.parse(rawReport), await realpath(observedTest?.effectiveCwd ?? cwd), await realpath(input.cwd)));
|
|
2169
|
+
const actual = commandExecutionEvidence.get(key);
|
|
2170
|
+
for (let offset = 0; offset < actual.length; offset += 24) {
|
|
2171
|
+
const relative = `${input.task.id}/commands/${commandNumber}-execution-${offset / 24}.json`;
|
|
2172
|
+
const excerpt = { schemaVersion: 1, commandSha256: observedTest.observation.commandSha256, cwd: observedTest.observation.cwd, reportSha256, offset, total: actual.length, behaviorCoverage: "unconfirmed", tests: actual.slice(offset, offset + 24).map(test => ({ file: test.file, status: test.status, title: test.title.slice(0, 500), ...(test.title.length > 500 ? { titleTruncated: true } : {}) })) };
|
|
2173
|
+
await writeDagRunJsonArtifact(meta.runDir, relative, excerpt);
|
|
2174
|
+
const bytes = await readFile(path.join(meta.runDir, relative));
|
|
2175
|
+
testExecutionEvidence.push({ path: relative, sha256: createHash("sha256").update(bytes).digest("hex"), reportPath: path.relative(meta.runDir, reportPath).replace(/\\/g, "/"), reportSha256 });
|
|
2176
|
+
}
|
|
2120
2177
|
}
|
|
2121
2178
|
catch (error) {
|
|
2122
2179
|
reportError = `frontend test execution report unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -2212,6 +2269,8 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2212
2269
|
: undefined;
|
|
2213
2270
|
let traceError;
|
|
2214
2271
|
try {
|
|
2272
|
+
if (testObservationFailure)
|
|
2273
|
+
throw new Error(`${testObservationFailure.code}: ${testObservationFailure.reason}`);
|
|
2215
2274
|
await runFrontendVerificationTraceGate({
|
|
2216
2275
|
runDir: meta.runDir,
|
|
2217
2276
|
workspaceRoot: input.cwd,
|
|
@@ -2229,6 +2288,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2229
2288
|
nodeId: input.task.id,
|
|
2230
2289
|
commandLabels: successfulLabels.get("behavior") ?? [],
|
|
2231
2290
|
executedTests,
|
|
2291
|
+
testExecutionEvidence,
|
|
2232
2292
|
},
|
|
2233
2293
|
},
|
|
2234
2294
|
});
|
|
@@ -2261,9 +2321,10 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2261
2321
|
nodeId: failedNodeId,
|
|
2262
2322
|
rawFailureCategory: failureCategory,
|
|
2263
2323
|
});
|
|
2264
|
-
const failureOwner =
|
|
2265
|
-
|
|
2266
|
-
|
|
2324
|
+
const failureOwner = testObservationFailure ? "verification-config" :
|
|
2325
|
+
classifiedOwner === "unknown" &&
|
|
2326
|
+
["typecheck", "build", "lint", "component-test", "unit-test", "trace"].includes(failureClass)
|
|
2327
|
+
? "implementation" : classifiedOwner;
|
|
2267
2328
|
const restartPhase = classifyRestartPhase(failureOwner);
|
|
2268
2329
|
// A+B (AC-008): verification-config routing. Produce structured
|
|
2269
2330
|
// unresolved entrypoint refs from committed plan facts and the frozen
|
|
@@ -2325,6 +2386,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2325
2386
|
...(verificationConfigRoute
|
|
2326
2387
|
? { verificationConfigRoute }
|
|
2327
2388
|
: {}),
|
|
2389
|
+
...(testObservationFailure ? { testObservationFailure, commandsExecuted: results.some(result => !result.reused) } : {}),
|
|
2328
2390
|
commandResults,
|
|
2329
2391
|
failedAt: new Date().toISOString(),
|
|
2330
2392
|
};
|
|
@@ -2689,8 +2751,9 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2689
2751
|
durationMs: Date.now() - started,
|
|
2690
2752
|
};
|
|
2691
2753
|
}
|
|
2754
|
+
const existingVerificationPaths = await collectExistingFrontendVerificationPaths(input.cwd, contract);
|
|
2692
2755
|
const uncoveredVTs = contract.verificationTargets
|
|
2693
|
-
.filter((vt) => vt.mode !== "static")
|
|
2756
|
+
.filter((vt) => vt.mode !== "static" && !existingVerificationPaths.includes(vt.file))
|
|
2694
2757
|
.filter((vt) => ![...writeSet].some((pattern) => pathMatchesPattern(vt.file, pattern) || vt.file === pattern));
|
|
2695
2758
|
if (uncoveredVTs.length > 0) {
|
|
2696
2759
|
return {
|
|
@@ -2757,6 +2820,7 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2757
2820
|
[],
|
|
2758
2821
|
allowedDependencies: config.allowedDependencies,
|
|
2759
2822
|
writeSetPatterns: config.implementationWriteSet ?? [],
|
|
2823
|
+
existingVerificationPaths: await collectExistingFrontendVerificationPaths(input.cwd, contract),
|
|
2760
2824
|
// Fresh-existence evidence at gate time (Scout pathEvidence.fresh
|
|
2761
2825
|
// semantics): a reuse-existing evidencePath must name a file that
|
|
2762
2826
|
// exists NOW; greenfield paths are decision=new only.
|
|
@@ -2894,12 +2958,21 @@ async function executeFrontendWriterAdmission(input, meta) {
|
|
|
2894
2958
|
const workspaceFiles = await collectWorkspaceFiles(input.cwd);
|
|
2895
2959
|
const admission = deriveFrontendWriterAdmissionShell({
|
|
2896
2960
|
contract,
|
|
2961
|
+
existingVerificationPaths: await collectExistingFrontendVerificationPaths(input.cwd, contract),
|
|
2897
2962
|
designPolicyResult,
|
|
2898
2963
|
baseline,
|
|
2899
2964
|
frozenCommandLabels: config.frozenCommandLabels,
|
|
2900
2965
|
allowedMockStrategies: config.allowedMockStrategies,
|
|
2901
2966
|
workspaceFiles,
|
|
2902
2967
|
});
|
|
2968
|
+
const testObservationFindings = [];
|
|
2969
|
+
for (const node of meta.spec.tasks) {
|
|
2970
|
+
const verification = node.shell?.frontendVerificationBundle;
|
|
2971
|
+
if (!verification)
|
|
2972
|
+
continue;
|
|
2973
|
+
const checked = await inspectFrontendBundleTests(verification, resolveShellCwd(input.cwd, node.shell?.cwd), input.cwd, "admission", admission.writeSet);
|
|
2974
|
+
testObservationFindings.push(...checked.failures.map(failure => ({ code: failure.code, message: failure.reason })));
|
|
2975
|
+
}
|
|
2903
2976
|
// 6. Design verdict enforcement: request_design_changes is a blocked
|
|
2904
2977
|
// admission. This keeps the writer fail-closed until the recovery plan
|
|
2905
2978
|
// has incorporated the reviewer's concrete findings.
|
|
@@ -2907,9 +2980,10 @@ async function executeFrontendWriterAdmission(input, meta) {
|
|
|
2907
2980
|
? "frontend-design-policy-shell"
|
|
2908
2981
|
: "frontend-writer-admission-shell";
|
|
2909
2982
|
const findings = verdictPass
|
|
2910
|
-
? admission.findings
|
|
2983
|
+
? [...admission.findings, ...testObservationFindings]
|
|
2911
2984
|
: [
|
|
2912
2985
|
...admission.findings,
|
|
2986
|
+
...testObservationFindings,
|
|
2913
2987
|
{
|
|
2914
2988
|
code: designRequested
|
|
2915
2989
|
? "design-review-requested"
|
|
@@ -3101,6 +3175,8 @@ async function executeFrontendCloseout(input, meta) {
|
|
|
3101
3175
|
status: target.status === "ok" ? "passed" : "failed",
|
|
3102
3176
|
evidence: trace?.targets?.[index]?.evidence ?? "unavailable",
|
|
3103
3177
|
behaviorCoverage: "unconfirmed",
|
|
3178
|
+
plannedEvidenceLevel: contract.verificationTargets.find(entry => entry.id === trace?.targets?.[index]?.id)?.evidenceLevel ?? "unknown",
|
|
3179
|
+
boundary: contract.verificationTargets.find(entry => entry.id === trace?.targets?.[index]?.id)?.boundary,
|
|
3104
3180
|
})),
|
|
3105
3181
|
lintStatus: "unavailable",
|
|
3106
3182
|
integrationFacts,
|
|
@@ -3,7 +3,7 @@ export const DAG_SHELL_PRESETS = {
|
|
|
3
3
|
"loop-agent-standard-verify": [
|
|
4
4
|
"npm run lint",
|
|
5
5
|
"npm run typecheck",
|
|
6
|
-
"npx vitest run test/dag-shell-executor.test.ts test/dag-validate.test.ts test/dag-runner.test.ts --reporter=dot",
|
|
6
|
+
"npx vitest run test/dag-shell-executor.test.ts test/dag-validate.test.ts test/dag-runner.test.ts test/dag-runner-convergence.test.ts test/dag-runner-artifacts.test.ts test/dag-runner-decisions.test.ts --reporter=dot",
|
|
7
7
|
],
|
|
8
8
|
};
|
|
9
9
|
export function expandShellPreset(preset) {
|
|
@@ -4,6 +4,7 @@ import { constants, createReadStream } from "node:fs";
|
|
|
4
4
|
import { access, lstat, readlink, unlink } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathMatchesPattern } from "../shared/git-progress.js";
|
|
7
|
+
import { expandWorkspaceStatus, hasGitmodulesFile, synthesizeExpandedPorcelain, } from "../shared/git-workspace-status.js";
|
|
7
8
|
import { ResilientGitCommandError, runResilientGitCommand, } from "../shared/resilient-git.js";
|
|
8
9
|
async function sha256File(filePath) {
|
|
9
10
|
const hash = createHash("sha256");
|
|
@@ -411,6 +412,7 @@ async function resolveGitExecutableCandidates(platform, env) {
|
|
|
411
412
|
return ["git"];
|
|
412
413
|
}
|
|
413
414
|
export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
415
|
+
let parentPorcelain;
|
|
414
416
|
try {
|
|
415
417
|
const result = await runResilientGitCommand({
|
|
416
418
|
cwd,
|
|
@@ -419,9 +421,12 @@ export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
|
419
421
|
attempts: options.attempts,
|
|
420
422
|
retryDelayMs: options.retryDelayMs,
|
|
421
423
|
});
|
|
422
|
-
if (result.code === 0)
|
|
423
|
-
|
|
424
|
-
|
|
424
|
+
if (result.code === 0) {
|
|
425
|
+
parentPorcelain = result.stdout;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
throw new Error(result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`);
|
|
429
|
+
}
|
|
425
430
|
}
|
|
426
431
|
catch (error) {
|
|
427
432
|
if (error instanceof ResilientGitCommandError) {
|
|
@@ -436,6 +441,24 @@ export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
|
436
441
|
}
|
|
437
442
|
throw error;
|
|
438
443
|
}
|
|
444
|
+
// Submodule workspaces: a parent-only porcelain hides every file inside a
|
|
445
|
+
// registered submodule behind a single directory-level gitlink entry,
|
|
446
|
+
// which historically produced false empty-diff and false violations
|
|
447
|
+
// (plan 2026-09-15-development-workflow-git-submodule). Repositories
|
|
448
|
+
// without .gitmodules keep the parent bytes verbatim — byte-identical
|
|
449
|
+
// behavior for every non-submodule workspace.
|
|
450
|
+
if (!(await hasGitmodulesFile(cwd)))
|
|
451
|
+
return parentPorcelain;
|
|
452
|
+
const observation = await expandWorkspaceStatus({
|
|
453
|
+
rootCwd: cwd,
|
|
454
|
+
parentPorcelain,
|
|
455
|
+
hasGitmodulesFile: true,
|
|
456
|
+
});
|
|
457
|
+
if (!observation.complete) {
|
|
458
|
+
const failed = observation.repos.find((repo) => repo.state === "status-read-failed");
|
|
459
|
+
throw new Error(`workspace status expansion incomplete: submodule ${failed?.repoRelativePath ?? "unknown"} status read failed: ${failed?.errorDetail ?? "unknown error"}`);
|
|
460
|
+
}
|
|
461
|
+
return synthesizeExpandedPorcelain(observation.entries);
|
|
439
462
|
}
|
|
440
463
|
export async function readGitStatusPorcelainWithDependencies(cwd, options, dependencies) {
|
|
441
464
|
const candidates = await dependencies.resolveExecutableCandidates();
|