clinkx 0.3.0 → 1.1.0
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 +119 -0
- package/LICENSE +10 -0
- package/README.md +154 -26
- package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
- package/clinkx-workflows/dist/allowed-roots.js +5 -0
- package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
- package/clinkx-workflows/dist/artifacts.d.ts +20 -1
- package/clinkx-workflows/dist/artifacts.js +38 -9
- package/clinkx-workflows/dist/artifacts.js.map +1 -1
- package/clinkx-workflows/dist/backend.d.ts +4 -0
- package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
- package/clinkx-workflows/dist/chairman-knobs.js +27 -0
- package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
- package/clinkx-workflows/dist/citation-check.d.ts +40 -0
- package/clinkx-workflows/dist/citation-check.js +348 -0
- package/clinkx-workflows/dist/citation-check.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +12 -1
- package/clinkx-workflows/dist/clink-client.js +56 -5
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +173 -16
- package/clinkx-workflows/dist/config.js +417 -26
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/contained-file.d.ts +172 -0
- package/clinkx-workflows/dist/contained-file.js +483 -0
- package/clinkx-workflows/dist/contained-file.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
- package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
- package/clinkx-workflows/dist/context-cache-key.js +91 -0
- package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
- package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
- package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
- package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
- package/clinkx-workflows/dist/context-preflight.d.ts +189 -0
- package/clinkx-workflows/dist/context-preflight.js +91 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -0
- package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
- package/clinkx-workflows/dist/council-execution-policy.js +110 -0
- package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +10 -9
- package/clinkx-workflows/dist/council-expander.js +20 -10
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5718 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js +157 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js +493 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +40 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +1155 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +256 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +1301 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +28483 -0
- package/clinkx-workflows/dist/dashboard/contracts.js +1284 -0
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
- package/clinkx-workflows/dist/dashboard/generation.js +41 -0
- package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/index.d.ts +32 -0
- package/clinkx-workflows/dist/dashboard/index.js +20 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +114 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js +799 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +446 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +529 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +29 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +111 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
- package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
- package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +130 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +78 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +1154 -0
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +63 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js +483 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2778 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +506 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +529 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +14 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +1101 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
- package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
- package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
- package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
- package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
- package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
- package/clinkx-workflows/dist/delegate-signature.js +14 -0
- package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
- package/clinkx-workflows/dist/delegate-validate.d.ts +108 -0
- package/clinkx-workflows/dist/delegate-validate.js +148 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
- package/clinkx-workflows/dist/engine.d.ts +251 -0
- package/clinkx-workflows/dist/engine.js +1342 -92
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +24 -2
- package/clinkx-workflows/dist/errors.js +30 -2
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fidelity.d.ts +47 -1
- package/clinkx-workflows/dist/fidelity.js +54 -1
- package/clinkx-workflows/dist/fidelity.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +25 -7
- package/clinkx-workflows/dist/fingerprint.js +33 -10
- package/clinkx-workflows/dist/fingerprint.js.map +1 -1
- package/clinkx-workflows/dist/index.d.ts +19 -0
- package/clinkx-workflows/dist/index.js +68 -1
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/loader.d.ts +9 -3
- package/clinkx-workflows/dist/loader.js +92 -21
- package/clinkx-workflows/dist/loader.js.map +1 -1
- package/clinkx-workflows/dist/logger.d.ts +1 -1
- package/clinkx-workflows/dist/logger.js +17 -3
- package/clinkx-workflows/dist/logger.js.map +1 -1
- package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
- package/clinkx-workflows/dist/monotonic-clock.js +22 -0
- package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
- package/clinkx-workflows/dist/plan-review/admission.js +290 -0
- package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
- package/clinkx-workflows/dist/plan-review/contract.js +289 -0
- package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
- package/clinkx-workflows/dist/plan-review/digest.js +241 -0
- package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
- package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
- package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
- package/clinkx-workflows/dist/plan-review/expander.js +243 -0
- package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
- package/clinkx-workflows/dist/plan-review/projector.js +437 -0
- package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
- package/clinkx-workflows/dist/plan-review/publication.js +182 -0
- package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
- package/clinkx-workflows/dist/plan-review/resume.js +122 -0
- package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
- package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
- package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
- package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
- package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
- package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
- package/clinkx-workflows/dist/post-processors/index.js +21 -0
- package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
- package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
- package/clinkx-workflows/dist/prompt-budget.js +7 -1
- package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
- package/clinkx-workflows/dist/reducers/index.js +2 -0
- package/clinkx-workflows/dist/reducers/index.js.map +1 -1
- package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
- package/clinkx-workflows/dist/roster-contract.js +180 -0
- package/clinkx-workflows/dist/roster-contract.js.map +1 -0
- package/clinkx-workflows/dist/rosters.d.ts +14 -0
- package/clinkx-workflows/dist/rosters.js +169 -0
- package/clinkx-workflows/dist/rosters.js.map +1 -0
- package/clinkx-workflows/dist/run-health.d.ts +7 -0
- package/clinkx-workflows/dist/run-health.js +25 -0
- package/clinkx-workflows/dist/run-health.js.map +1 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
- package/clinkx-workflows/dist/run-summary.d.ts +13 -0
- package/clinkx-workflows/dist/run-summary.js +29 -1
- package/clinkx-workflows/dist/run-summary.js.map +1 -1
- package/clinkx-workflows/dist/schema.d.ts +140 -21
- package/clinkx-workflows/dist/schema.js +34 -1
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
- package/clinkx-workflows/dist/seed-terms.js +176 -0
- package/clinkx-workflows/dist/seed-terms.js.map +1 -0
- package/clinkx-workflows/dist/server.d.ts +5 -2
- package/clinkx-workflows/dist/server.js +84 -52
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +29 -18
- package/clinkx-workflows/dist/shutdown.js +90 -64
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +61 -0
- package/clinkx-workflows/dist/state.d.ts +175 -3
- package/clinkx-workflows/dist/state.js +1398 -44
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
- package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
- package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
- package/clinkx-workflows/dist/transport.d.ts +15 -0
- package/clinkx-workflows/dist/transport.js +19 -10
- package/clinkx-workflows/dist/transport.js.map +1 -1
- package/clinkx-workflows/dist/types.d.ts +20 -2
- package/clinkx-workflows/dist/validators/council.d.ts +334 -101
- package/clinkx-workflows/dist/validators/council.js +52 -13
- package/clinkx-workflows/dist/validators/council.js.map +1 -1
- package/clinkx-workflows/dist/validators/index.d.ts +1 -0
- package/clinkx-workflows/dist/validators/index.js +3 -0
- package/clinkx-workflows/dist/validators/index.js.map +1 -1
- package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
- package/clinkx-workflows/dist/validators/plan-review.js +316 -0
- package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
- package/clinkx-workflows/dist/workflow-receipt.js +188 -4
- package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
- package/clinkx-workflows/dist/workflow-tools.d.ts +118 -3
- package/clinkx-workflows/dist/workflow-tools.js +2110 -175
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
- package/clinkx-workflows/dist/workspace-signature.js +388 -0
- package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
- package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +5 -1
- package/clinkx-workflows/templates/council/chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +16 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +7 -1
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +22 -3
- package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-debugger.txt +5 -1
- package/clinkx-workflows/templates/council/discover-analyst.txt +5 -1
- package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/member.txt +5 -1
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +71 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +54 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +63 -0
- package/clinkx-workflows/templates/council/reviewer.txt +3 -0
- package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
- package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
- package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
- package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
- package/clinkx-workflows/workflows/council-answer.yaml +100 -8
- package/clinkx-workflows/workflows/council-code-review.yaml +106 -8
- package/clinkx-workflows/workflows/council-debug.yaml +100 -8
- package/clinkx-workflows/workflows/council-default.yaml +104 -8
- package/clinkx-workflows/workflows/council-discover.yaml +100 -8
- package/clinkx-workflows/workflows/council-plan-review.yaml +253 -0
- package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
- package/conf/adapters/agy.json +56 -0
- package/conf/adapters/claude.json +12 -0
- package/conf/adapters/codex-app-server.json +44 -0
- package/conf/adapters/codex.json +14 -2
- package/conf/adapters/glm.json +24 -3
- package/conf/adapters/grok.json +67 -0
- package/conf/adapters/hapi/agy.json +59 -0
- package/conf/adapters/hapi/claude.json +12 -0
- package/conf/adapters/hapi/codex.json +14 -2
- package/conf/adapters/hapi/glm.json +25 -4
- package/conf/rosters/default-council.json +21 -0
- package/dist/activity-journal.d.ts +56 -0
- package/dist/activity-journal.js +706 -0
- package/dist/activity-journal.js.map +1 -0
- package/dist/allowed-roots.d.ts +41 -0
- package/dist/allowed-roots.js +125 -3
- package/dist/allowed-roots.js.map +1 -1
- package/dist/codex/activity-session-log.d.ts +14 -0
- package/dist/codex/activity-session-log.js +206 -0
- package/dist/codex/activity-session-log.js.map +1 -0
- package/dist/codex/agent-tools.d.ts +23 -0
- package/dist/codex/agent-tools.js +1306 -0
- package/dist/codex/agent-tools.js.map +1 -0
- package/dist/codex/app-server-client.d.ts +79 -0
- package/dist/codex/app-server-client.js +593 -0
- package/dist/codex/app-server-client.js.map +1 -0
- package/dist/codex/app-server-registry.d.ts +407 -0
- package/dist/codex/app-server-registry.js +1493 -0
- package/dist/codex/app-server-registry.js.map +1 -0
- package/dist/codex/client-types.d.ts +115 -0
- package/dist/codex/client-types.js +2 -0
- package/dist/codex/client-types.js.map +1 -0
- package/dist/codex/protocol.d.ts +5375 -0
- package/dist/codex/protocol.js +614 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/codex/session.d.ts +263 -0
- package/dist/codex/session.js +466 -0
- package/dist/codex/session.js.map +1 -0
- package/dist/config.d.ts +386 -23
- package/dist/config.js +184 -15
- package/dist/config.js.map +1 -1
- package/dist/context-builder/anchors.d.ts +1 -0
- package/dist/context-builder/anchors.js +3 -0
- package/dist/context-builder/anchors.js.map +1 -1
- package/dist/context-builder/bundle.d.ts +48 -2
- package/dist/context-builder/bundle.js +388 -2417
- package/dist/context-builder/bundle.js.map +1 -1
- package/dist/context-builder/codemap.d.ts +5 -0
- package/dist/context-builder/codemap.js +387 -13
- package/dist/context-builder/codemap.js.map +1 -1
- package/dist/context-builder/config.d.ts +1 -0
- package/dist/context-builder/config.js +12 -0
- package/dist/context-builder/config.js.map +1 -1
- package/dist/context-builder/context-run-catalog.d.ts +17 -0
- package/dist/context-builder/context-run-catalog.js +99 -0
- package/dist/context-builder/context-run-catalog.js.map +1 -0
- package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
- package/dist/context-builder/delegate-finalizer-effective.js +82 -0
- package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +55 -0
- package/dist/context-builder/delegate-packet.js +430 -74
- package/dist/context-builder/delegate-packet.js.map +1 -1
- package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
- package/dist/context-builder/delegate-reconstruction.js +110 -41
- package/dist/context-builder/delegate-reconstruction.js.map +1 -1
- package/dist/context-builder/delegate-validation.d.ts +41 -1
- package/dist/context-builder/delegate-validation.js +237 -48
- package/dist/context-builder/delegate-validation.js.map +1 -1
- package/dist/context-builder/delegate.d.ts +11 -0
- package/dist/context-builder/delegate.js +210 -58
- package/dist/context-builder/delegate.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +11 -2
- package/dist/context-builder/diagnostics.js +39 -6
- package/dist/context-builder/diagnostics.js.map +1 -1
- package/dist/context-builder/discovery-progress.d.ts +25 -0
- package/dist/context-builder/discovery-progress.js +64 -0
- package/dist/context-builder/discovery-progress.js.map +1 -0
- package/dist/context-builder/discovery-watchdog.d.ts +15 -0
- package/dist/context-builder/discovery-watchdog.js +39 -0
- package/dist/context-builder/discovery-watchdog.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +36 -1
- package/dist/context-builder/discovery.js +584 -172
- package/dist/context-builder/discovery.js.map +1 -1
- package/dist/context-builder/external-gaps.d.ts +73 -0
- package/dist/context-builder/external-gaps.js +248 -0
- package/dist/context-builder/external-gaps.js.map +1 -0
- package/dist/context-builder/globs.d.ts +1 -0
- package/dist/context-builder/globs.js +25 -10
- package/dist/context-builder/globs.js.map +1 -1
- package/dist/context-builder/handler.d.ts +67 -2
- package/dist/context-builder/handler.js +1810 -2440
- package/dist/context-builder/handler.js.map +1 -1
- package/dist/context-builder/invocation-sidecar.d.ts +41 -0
- package/dist/context-builder/invocation-sidecar.js +189 -0
- package/dist/context-builder/invocation-sidecar.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +424 -0
- package/dist/context-builder/lifecycle-contract.js +523 -0
- package/dist/context-builder/lifecycle-contract.js.map +1 -0
- package/dist/context-builder/lifecycle-journal.d.ts +62 -0
- package/dist/context-builder/lifecycle-journal.js +88 -0
- package/dist/context-builder/lifecycle-journal.js.map +1 -0
- package/dist/context-builder/lifecycle-observer.d.ts +102 -0
- package/dist/context-builder/lifecycle-observer.js +258 -0
- package/dist/context-builder/lifecycle-observer.js.map +1 -0
- package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
- package/dist/context-builder/lifecycle-reconcile.js +191 -0
- package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
- package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
- package/dist/context-builder/lifecycle-recovery.js +14 -0
- package/dist/context-builder/lifecycle-recovery.js.map +1 -0
- package/dist/context-builder/mutation-lock.d.ts +32 -0
- package/dist/context-builder/mutation-lock.js +321 -0
- package/dist/context-builder/mutation-lock.js.map +1 -0
- package/dist/context-builder/path-signals.js +9 -5
- package/dist/context-builder/path-signals.js.map +1 -1
- package/dist/context-builder/progress.d.ts +3 -1
- package/dist/context-builder/progress.js +23 -0
- package/dist/context-builder/progress.js.map +1 -1
- package/dist/context-builder/receipt.d.ts +6 -4
- package/dist/context-builder/receipt.js +12 -1
- package/dist/context-builder/receipt.js.map +1 -1
- package/dist/context-builder/refine-handler.d.ts +14 -0
- package/dist/context-builder/refine-handler.js +790 -0
- package/dist/context-builder/refine-handler.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +116 -0
- package/dist/context-builder/review-coverage.js +544 -0
- package/dist/context-builder/review-coverage.js.map +1 -0
- package/dist/context-builder/schema.d.ts +315 -2
- package/dist/context-builder/schema.js +133 -7
- package/dist/context-builder/schema.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +59 -0
- package/dist/context-builder/secrets.js +2255 -44
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/selection.d.ts +7 -3
- package/dist/context-builder/selection.js +200 -738
- package/dist/context-builder/selection.js.map +1 -1
- package/dist/context-builder/session-log-identity.d.ts +1 -0
- package/dist/context-builder/session-log-identity.js +66 -0
- package/dist/context-builder/session-log-identity.js.map +1 -0
- package/dist/context-builder/slice-range-math.d.ts +16 -0
- package/dist/context-builder/slice-range-math.js +137 -0
- package/dist/context-builder/slice-range-math.js.map +1 -0
- package/dist/context-builder/status.d.ts +23 -2
- package/dist/context-builder/status.js +60 -4
- package/dist/context-builder/status.js.map +1 -1
- package/dist/context-builder/storage.d.ts +59 -2
- package/dist/context-builder/storage.js +500 -168
- package/dist/context-builder/storage.js.map +1 -1
- package/dist/context-builder/token-estimate.d.ts +13 -2
- package/dist/context-builder/token-estimate.js +77 -4
- package/dist/context-builder/token-estimate.js.map +1 -1
- package/dist/context-builder/versions.d.ts +44 -0
- package/dist/context-builder/versions.js +45 -0
- package/dist/context-builder/versions.js.map +1 -0
- package/dist/context-builder/workflow-runner.d.ts +75 -0
- package/dist/context-builder/workflow-runner.js +1241 -0
- package/dist/context-builder/workflow-runner.js.map +1 -0
- package/dist/context-builder/write-watchdog.d.ts +14 -0
- package/dist/context-builder/write-watchdog.js +38 -0
- package/dist/context-builder/write-watchdog.js.map +1 -0
- package/dist/dashboard-adapter-catalog.d.ts +9 -0
- package/dist/dashboard-adapter-catalog.js +53 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -0
- package/dist/dashboard-cli.d.ts +81 -0
- package/dist/dashboard-cli.js +623 -0
- package/dist/dashboard-cli.js.map +1 -0
- package/dist/dashboard-companion/client.d.ts +37 -0
- package/dist/dashboard-companion/client.js +229 -0
- package/dist/dashboard-companion/client.js.map +1 -0
- package/dist/dashboard-companion/config.d.ts +8 -0
- package/dist/dashboard-companion/config.js +52 -0
- package/dist/dashboard-companion/config.js.map +1 -0
- package/dist/dashboard-companion/http-proxy.d.ts +10 -0
- package/dist/dashboard-companion/http-proxy.js +234 -0
- package/dist/dashboard-companion/http-proxy.js.map +1 -0
- package/dist/dashboard-companion/protocol.d.ts +80 -0
- package/dist/dashboard-companion/protocol.js +458 -0
- package/dist/dashboard-companion/protocol.js.map +1 -0
- package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
- package/dist/dashboard-companion/standalone-assets.js +72 -0
- package/dist/dashboard-companion/standalone-assets.js.map +1 -0
- package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
- package/dist/dashboard-companion/standalone-entry.js +17 -0
- package/dist/dashboard-companion/standalone-entry.js.map +1 -0
- package/dist/dashboard-gateway/config.d.ts +9 -0
- package/dist/dashboard-gateway/config.js +44 -0
- package/dist/dashboard-gateway/config.js.map +1 -0
- package/dist/dashboard-gateway/index.d.ts +12 -0
- package/dist/dashboard-gateway/index.js +786 -0
- package/dist/dashboard-gateway/index.js.map +1 -0
- package/dist/dashboard-gateway/main.d.ts +1 -0
- package/dist/dashboard-gateway/main.js +25 -0
- package/dist/dashboard-gateway/main.js.map +1 -0
- package/dist/dashboard-pairing-token.d.ts +1 -0
- package/dist/dashboard-pairing-token.js +5 -0
- package/dist/dashboard-pairing-token.js.map +1 -0
- package/dist/elaris/client.d.ts +73 -0
- package/dist/elaris/client.js +825 -0
- package/dist/elaris/client.js.map +1 -0
- package/dist/elaris/config.d.ts +18 -0
- package/dist/elaris/config.js +75 -0
- package/dist/elaris/config.js.map +1 -0
- package/dist/elaris/errors.d.ts +41 -0
- package/dist/elaris/errors.js +205 -0
- package/dist/elaris/errors.js.map +1 -0
- package/dist/elaris/github-repo.d.ts +18 -0
- package/dist/elaris/github-repo.js +89 -0
- package/dist/elaris/github-repo.js.map +1 -0
- package/dist/elaris/index.d.ts +10 -0
- package/dist/elaris/index.js +11 -0
- package/dist/elaris/index.js.map +1 -0
- package/dist/elaris/redact.d.ts +89 -0
- package/dist/elaris/redact.js +423 -0
- package/dist/elaris/redact.js.map +1 -0
- package/dist/elaris/run-id.d.ts +5 -0
- package/dist/elaris/run-id.js +20 -0
- package/dist/elaris/run-id.js.map +1 -0
- package/dist/elaris/status.d.ts +45 -0
- package/dist/elaris/status.js +122 -0
- package/dist/elaris/status.js.map +1 -0
- package/dist/elaris/tool-schemas.d.ts +314 -0
- package/dist/elaris/tool-schemas.js +208 -0
- package/dist/elaris/tool-schemas.js.map +1 -0
- package/dist/elaris/tools.d.ts +73 -0
- package/dist/elaris/tools.js +1065 -0
- package/dist/elaris/tools.js.map +1 -0
- package/dist/elaris/types.d.ts +1541 -0
- package/dist/elaris/types.js +252 -0
- package/dist/elaris/types.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -1
- package/dist/handler.d.ts +4 -0
- package/dist/handler.js +15 -2
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +154 -6
- package/dist/index.js.map +1 -1
- package/dist/local-clink-backend.d.ts +3 -0
- package/dist/local-clink-backend.js +4 -0
- package/dist/local-clink-backend.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +18 -3
- package/dist/logger.js.map +1 -1
- package/dist/package-info.d.ts +2 -0
- package/dist/package-info.js +18 -0
- package/dist/package-info.js.map +1 -0
- package/dist/parsers/agy-stream-json.d.ts +14 -0
- package/dist/parsers/agy-stream-json.js +172 -0
- package/dist/parsers/agy-stream-json.js.map +1 -0
- package/dist/parsers/codex-jsonl.js +48 -3
- package/dist/parsers/codex-jsonl.js.map +1 -1
- package/dist/parsers/extract.d.ts +10 -1
- package/dist/parsers/extract.js +16 -4
- package/dist/parsers/extract.js.map +1 -1
- package/dist/parsers/observed-invocation.d.ts +20 -0
- package/dist/parsers/observed-invocation.js +42 -0
- package/dist/parsers/observed-invocation.js.map +1 -0
- package/dist/pipeline.d.ts +17 -0
- package/dist/pipeline.js +181 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/progress-report/lock.d.ts +74 -0
- package/dist/progress-report/lock.js +343 -0
- package/dist/progress-report/lock.js.map +1 -0
- package/dist/progress-report/schema.d.ts +808 -0
- package/dist/progress-report/schema.js +119 -0
- package/dist/progress-report/schema.js.map +1 -0
- package/dist/progress-report/store.d.ts +66 -0
- package/dist/progress-report/store.js +766 -0
- package/dist/progress-report/store.js.map +1 -0
- package/dist/progress-report/tools.d.ts +20 -0
- package/dist/progress-report/tools.js +141 -0
- package/dist/progress-report/tools.js.map +1 -0
- package/dist/prompt-mode.d.ts +1 -1
- package/dist/prompt-mode.js +7 -2
- package/dist/prompt-mode.js.map +1 -1
- package/dist/run-dir.d.ts +24 -0
- package/dist/run-dir.js +201 -2
- package/dist/run-dir.js.map +1 -1
- package/dist/runner.d.ts +20 -0
- package/dist/runner.js +192 -16
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +54 -4
- package/dist/safety.js +186 -5
- package/dist/safety.js.map +1 -1
- package/dist/schema.d.ts +15 -7
- package/dist/schema.js +20 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +451 -23
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +18 -0
- package/dist/session-log-stream.js +566 -0
- package/dist/session-log-stream.js.map +1 -0
- package/dist/stdio-activity.d.ts +28 -0
- package/dist/stdio-activity.js +72 -0
- package/dist/stdio-activity.js.map +1 -0
- package/package.json +47 -5
- package/conf/adapters/gemini.json +0 -44
- package/conf/adapters/hapi/gemini.json +0 -47
- package/dist/parsers/gemini-json.d.ts +0 -2
- package/dist/parsers/gemini-json.js +0 -72
- package/dist/parsers/gemini-json.js.map +0 -1
|
@@ -62,29 +62,190 @@
|
|
|
62
62
|
* isError: true with termination_reason: "wall" or "idle", which the
|
|
63
63
|
* workflow engine treats as a transient failure eligible for retry.
|
|
64
64
|
*/
|
|
65
|
+
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
65
66
|
import { stat as fsStat } from "node:fs/promises";
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
67
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
68
|
+
import { basename, isAbsolute, join, resolve, sep } from "node:path";
|
|
69
|
+
import { callClink, extractObservedInvocation } from "./clink-client.js";
|
|
70
|
+
import { getArtifactsDir, getChildMaxConcurrent, getWorkflowsContextDefault, getWorkflowsContextDefaultCurator, getMaxParallel, getMaxPromptBytes, getStateDir, resolveWorkspaceRoot, } from "./config.js";
|
|
71
|
+
import { validatePlanReviewCallBindings, validatePlanReviewPreparedInputPolicy, } from "./plan-review/runtime-policy.js";
|
|
72
|
+
import { CouncilPlanReviewErrorSchema, } from "./plan-review/publication.js";
|
|
73
|
+
import { PlanReviewStageError } from "./plan-review/stage-error.js";
|
|
74
|
+
import { CallExecutionError, InvalidCallError, WorkflowDispatchDeadlineError, WorkflowCancellationError, WorkflowError, } from "./errors.js";
|
|
75
|
+
import { analyzeOutputFidelity, enforceOutputFidelity, enforceTypedResultFidelity, TypedResultFidelityError, } from "./fidelity.js";
|
|
76
|
+
import { monotonicNowMs } from "./monotonic-clock.js";
|
|
71
77
|
import { validateDynamicPaths } from "./path-validation.js";
|
|
72
|
-
import { estimatePromptBudget, enforcePromptBudget, createPathContributor } from "./prompt-budget.js";
|
|
78
|
+
import { DEFAULT_RESERVE_BYTES, estimatePromptBudget, enforcePromptBudget, createPathContributor, } from "./prompt-budget.js";
|
|
79
|
+
import { buildCouncilDeadlineGuidance, COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES, isCouncilExecutionWorkflow, shouldVetoWallTimeoutRetry, } from "./council-execution-policy.js";
|
|
73
80
|
import { DispatchQueue } from "./queue.js";
|
|
74
81
|
import { renderCallPrompt } from "./template.js";
|
|
75
|
-
import { ArtifactStore } from "./artifacts.js";
|
|
82
|
+
import { ArtifactStore, OutputSizeError, SchemaValidationError } from "./artifacts.js";
|
|
83
|
+
import { projectPlanReviewSemanticBody, renderPlanSnapshotBlock, } from "./plan-review/expander.js";
|
|
84
|
+
import { filterExcludedPlanPaths, foldPlanReviewExcludeGlobs, } from "./plan-review/context-policy.js";
|
|
76
85
|
import { getReducer } from "./reducers/index.js";
|
|
86
|
+
import { getPostProcessor } from "./post-processors/index.js";
|
|
87
|
+
import { addPathWithBasename, buildAllowlistFromDiff } from "./citation-check.js";
|
|
77
88
|
import { logger } from "./logger.js";
|
|
89
|
+
import { resolveWorkflowContextMode, WorkflowContextModeResolverError, } from "./context-mode-resolver.js";
|
|
90
|
+
import { WorkflowDelegateValidationError } from "./delegate-validate.js";
|
|
91
|
+
import { isDelegateExternalGapDegrade } from "./delegate-signature.js";
|
|
92
|
+
import { contextSeedSourceValidationError, resolveSeedSource, synthesizeSeedTerms, } from "./seed-terms.js";
|
|
93
|
+
/**
|
|
94
|
+
* Declarations for variables the ENGINE derives at runtime (not declared in the
|
|
95
|
+
* workflow YAML or accepted as caller input). These are merged into the
|
|
96
|
+
* declaration map passed to the template renderer so that templates may
|
|
97
|
+
* reference them ({{context_available}}) without `template.ts` rejecting them as
|
|
98
|
+
* "Unknown variable", while genuinely-unknown placeholders are still rejected.
|
|
99
|
+
*
|
|
100
|
+
* The VALUES are produced by the engine (e.g. {@link deriveContextAvailable},
|
|
101
|
+
* which always overwrites any caller-supplied `context_available` before stages
|
|
102
|
+
* run), so listing them here does NOT widen the public caller-input surface.
|
|
103
|
+
*/
|
|
104
|
+
const ENGINE_DERIVED_VARIABLE_DECLS = {
|
|
105
|
+
context_available: {
|
|
106
|
+
type: "string",
|
|
107
|
+
required: false,
|
|
108
|
+
description: "Engine-derived: whether repository context is attached to the run",
|
|
109
|
+
},
|
|
110
|
+
context_quality: {
|
|
111
|
+
type: "string",
|
|
112
|
+
required: false,
|
|
113
|
+
description: "Engine-derived: effective context coverage quality",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
78
116
|
/** Default parent-side timeout for a single tools/call RPC (10 minutes). */
|
|
79
117
|
const DEFAULT_PARENT_CALL_TIMEOUT_MS = 600_000;
|
|
80
118
|
/** Buffer added to per-call timeout to account for child-side teardown latency. */
|
|
81
119
|
const CALL_TIMEOUT_BUFFER_MS = 5_000;
|
|
120
|
+
const preflightFallbackAggregate = {
|
|
121
|
+
curated_count: 0,
|
|
122
|
+
fallback_count: 0,
|
|
123
|
+
};
|
|
124
|
+
function orderedUnique(values) {
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
const result = [];
|
|
127
|
+
for (const value of values) {
|
|
128
|
+
if (seen.has(value)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
seen.add(value);
|
|
132
|
+
result.push(value);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Maps a runner's {@link PreflightContextResult} to the persisted {@link WorkflowContext}.
|
|
138
|
+
* Single source of truth for this mapping: the transient `receipt` field is dropped, and
|
|
139
|
+
* optional `delegate` metadata is spread only when present.
|
|
140
|
+
*/
|
|
141
|
+
export function mapPreflightResultToWorkflowContext(result, contextMode, lightCaller) {
|
|
142
|
+
// Mirror the engine's bundle-attachment decision exactly: bundle artifacts are
|
|
143
|
+
// appended to context_bundle_files IFF files_selected > 0 (see ~line 1090). On a
|
|
144
|
+
// zero-selection run we proceed WITHOUT a bundle, so the reported copied_paths
|
|
145
|
+
// must be empty too — otherwise the receipt would imply a bundle was attached.
|
|
146
|
+
const copied_paths = result.metrics.files_selected > 0 ? result.copied_paths : [];
|
|
147
|
+
return {
|
|
148
|
+
context_builder: {
|
|
149
|
+
source_run_id: result.source_run_id,
|
|
150
|
+
source_artifact_dir: result.source_artifact_dir,
|
|
151
|
+
cache: result.cache,
|
|
152
|
+
schema_version: result.schema_version,
|
|
153
|
+
status: result.status,
|
|
154
|
+
...(contextMode !== undefined ? { context_mode: contextMode } : {}),
|
|
155
|
+
copied_paths,
|
|
156
|
+
...(result.coverage !== undefined ? { coverage: result.coverage } : {}),
|
|
157
|
+
metrics: result.metrics,
|
|
158
|
+
warning_count: result.warning_count,
|
|
159
|
+
omission_count: result.omission_count,
|
|
160
|
+
...(result.delegate !== undefined ? { delegate: result.delegate } : {}),
|
|
161
|
+
},
|
|
162
|
+
...(lightCaller !== undefined ? { light_caller: lightCaller } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function classifyPreflightPartial(metrics) {
|
|
166
|
+
if (metrics.truncated ||
|
|
167
|
+
metrics.scan_cap_hit ||
|
|
168
|
+
metrics.search_cap_hit ||
|
|
169
|
+
metrics.discovery_byte_cap_hit ||
|
|
170
|
+
metrics.discovery_candidate_cap_hit ||
|
|
171
|
+
metrics.candidate_cap_hit) {
|
|
172
|
+
return { severity: "degraded", partial_reason: "cap_or_truncation" };
|
|
173
|
+
}
|
|
174
|
+
return { severity: "benign", partial_reason: "omissions_only" };
|
|
175
|
+
}
|
|
176
|
+
function recordPreflightFallbackAggregate(input) {
|
|
177
|
+
if (input.delegateEnabled) {
|
|
178
|
+
if (input.fallbackUsed) {
|
|
179
|
+
preflightFallbackAggregate.fallback_count += 1;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
preflightFallbackAggregate.curated_count += 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const total = preflightFallbackAggregate.curated_count +
|
|
186
|
+
preflightFallbackAggregate.fallback_count;
|
|
187
|
+
return {
|
|
188
|
+
curated_count: preflightFallbackAggregate.curated_count,
|
|
189
|
+
fallback_count: preflightFallbackAggregate.fallback_count,
|
|
190
|
+
total_delegate_count: total,
|
|
191
|
+
fallback_ratio: total === 0
|
|
192
|
+
? 0
|
|
193
|
+
: preflightFallbackAggregate.fallback_count / total,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function firstReceiptWarningCode(result) {
|
|
197
|
+
const warnings = result.receipt["warnings"];
|
|
198
|
+
if (!Array.isArray(warnings)) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
for (const warning of warnings) {
|
|
202
|
+
if (warning !== null && typeof warning === "object") {
|
|
203
|
+
const code = warning["code"];
|
|
204
|
+
if (typeof code === "string" && code.trim() !== "") {
|
|
205
|
+
return code;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function receiptDelegateFailureReason(result) {
|
|
212
|
+
const delegate = result.receipt["delegate"];
|
|
213
|
+
if (delegate === null || typeof delegate !== "object") {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const failureReason = delegate["failure_reason"];
|
|
217
|
+
return typeof failureReason === "string" && failureReason.trim() !== ""
|
|
218
|
+
? failureReason
|
|
219
|
+
: null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The allowed roots a workflow engine derives for itself when the caller supplies
|
|
223
|
+
* none. Exported so admission-time path checks can validate against the SAME set
|
|
224
|
+
* the engine will enforce at dispatch, rather than re-deriving a near-copy.
|
|
225
|
+
*
|
|
226
|
+
* Note this set is engine-owned: `CLINKX_ALLOWED_ROOTS` is not consulted and
|
|
227
|
+
* cannot widen it.
|
|
228
|
+
*/
|
|
229
|
+
export function defaultWorkflowAllowedRoots(runDir) {
|
|
230
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
231
|
+
const stateDir = getStateDir();
|
|
232
|
+
const configuredArtifactsDir = getArtifactsDir();
|
|
233
|
+
return [
|
|
234
|
+
workspaceRoot,
|
|
235
|
+
stateDir,
|
|
236
|
+
configuredArtifactsDir,
|
|
237
|
+
runDir,
|
|
238
|
+
join(runDir, "artifacts"),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
82
241
|
export class WorkflowEngine {
|
|
83
242
|
definition;
|
|
84
243
|
session;
|
|
85
244
|
variables;
|
|
245
|
+
effectiveVariables;
|
|
86
246
|
validatorRegistry;
|
|
87
247
|
runId;
|
|
248
|
+
sessionLogGeneration;
|
|
88
249
|
runDir;
|
|
89
250
|
signal;
|
|
90
251
|
queueDepth;
|
|
@@ -95,14 +256,44 @@ export class WorkflowEngine {
|
|
|
95
256
|
resumeCompletedCalls;
|
|
96
257
|
resumePreparedInputs;
|
|
97
258
|
resumeFailedCalls;
|
|
259
|
+
resumeNextRetryCounts;
|
|
98
260
|
contextProducingCalls = new Set();
|
|
261
|
+
planReviewDeliveredAliases = new Map();
|
|
99
262
|
artifactStore;
|
|
263
|
+
contextBuilderRunner;
|
|
264
|
+
contextDefault;
|
|
265
|
+
defaultCurator;
|
|
266
|
+
commitPreflightContext;
|
|
267
|
+
restoredWorkflowContext;
|
|
268
|
+
requireContextPreflight;
|
|
269
|
+
initialContextPolicy;
|
|
270
|
+
planReviewContextPolicy;
|
|
271
|
+
planReviewCallBindings;
|
|
272
|
+
commitPlanReviewContextProvenance;
|
|
273
|
+
terminalResultPolicy;
|
|
274
|
+
deriveInvocationAudit;
|
|
275
|
+
/**
|
|
276
|
+
* Workflow budget expiry on the MONOTONIC clock (`monotonicNowMs()`), not on
|
|
277
|
+
* the wall clock. Never compare it to `Date.now()`; read it only through
|
|
278
|
+
* `workflowRemainingMs()`.
|
|
279
|
+
*/
|
|
280
|
+
workflowDeadlineAtMonoMs;
|
|
281
|
+
/**
|
|
282
|
+
* `stage.id`.`call.id` keys already written to a terminal state by
|
|
283
|
+
* `emitTerminalCallAbort`. A parallel stage still surfaces that throw through
|
|
284
|
+
* `materializeUnexpectedFailure`, whose emit carries no startedAt/durationMs
|
|
285
|
+
* and attempt 1 / retryCount 0 — persisted unconditionally, it would erase the
|
|
286
|
+
* terminal entry's timing. Bounded by the run's call count.
|
|
287
|
+
*/
|
|
288
|
+
terminalizedCallKeys = new Set();
|
|
100
289
|
constructor(options) {
|
|
101
290
|
this.definition = options.definition;
|
|
102
291
|
this.session = options.session;
|
|
103
292
|
this.variables = options.variables;
|
|
293
|
+
this.effectiveVariables = { ...options.variables };
|
|
104
294
|
this.validatorRegistry = options.validatorRegistry;
|
|
105
295
|
this.runId = options.runId;
|
|
296
|
+
this.sessionLogGeneration = options.sessionLogGeneration;
|
|
106
297
|
this.runDir = options.runDir;
|
|
107
298
|
this.signal = options.signal;
|
|
108
299
|
this.queueDepth = Math.min(options.maxParallel ?? getMaxParallel(), options.childMaxConcurrent ?? getChildMaxConcurrent());
|
|
@@ -113,14 +304,35 @@ export class WorkflowEngine {
|
|
|
113
304
|
this.resumeCompletedCalls = options.resume?.completedCalls ?? new Map();
|
|
114
305
|
this.resumePreparedInputs = options.resume?.preparedInputs ?? new Map();
|
|
115
306
|
this.resumeFailedCalls = options.resume?.failedCalls ?? new Map();
|
|
307
|
+
this.resumeNextRetryCounts = options.resume?.nextRetryCounts ?? new Map();
|
|
116
308
|
this.artifactStore = new ArtifactStore(options.runDir, options.validatorRegistry);
|
|
309
|
+
this.contextBuilderRunner = options.contextBuilderRunner;
|
|
310
|
+
this.contextDefault = options.contextDefault ?? getWorkflowsContextDefault();
|
|
311
|
+
this.defaultCurator = options.defaultCurator ?? getWorkflowsContextDefaultCurator();
|
|
312
|
+
this.commitPreflightContext = options.commitPreflightContext;
|
|
313
|
+
this.restoredWorkflowContext = options.restoredWorkflowContext;
|
|
314
|
+
this.requireContextPreflight = options.requireContextPreflight ?? false;
|
|
315
|
+
this.initialContextPolicy = options.initialContextPolicy;
|
|
316
|
+
this.planReviewContextPolicy = options.planReviewContextPolicy;
|
|
317
|
+
this.planReviewCallBindings = options.planReviewCallBindings === undefined
|
|
318
|
+
? new Map()
|
|
319
|
+
: validatePlanReviewCallBindings(options.definition, options.planReviewCallBindings);
|
|
320
|
+
validatePlanReviewPreparedInputPolicy(this.resumePreparedInputs, this.planReviewCallBindings, {
|
|
321
|
+
snapshotText: options.planReviewSnapshotText,
|
|
322
|
+
contextPolicy: options.planReviewContextPolicy,
|
|
323
|
+
});
|
|
324
|
+
this.commitPlanReviewContextProvenance = options.commitPlanReviewContextProvenance;
|
|
325
|
+
this.terminalResultPolicy = options.terminalResultPolicy ?? { kind: "final_call" };
|
|
326
|
+
this.deriveInvocationAudit = options.deriveInvocationAudit;
|
|
117
327
|
this.validateFinalStage();
|
|
118
328
|
this.validateAdvertisedCliNames();
|
|
119
329
|
this.contextProducingCalls = collectContextProducingCalls(this.definition);
|
|
120
330
|
this.seedResumedArtifacts();
|
|
121
331
|
}
|
|
122
332
|
async execute() {
|
|
123
|
-
|
|
333
|
+
this.workflowDeadlineAtMonoMs =
|
|
334
|
+
monotonicNowMs() + this.definition.settings.timeout_seconds * 1000;
|
|
335
|
+
const deadlineController = this.createDeadlineController(this.definition.settings.timeout_seconds * 1000);
|
|
124
336
|
const stages = [];
|
|
125
337
|
const startedAt = new Date().toISOString();
|
|
126
338
|
try {
|
|
@@ -137,7 +349,7 @@ export class WorkflowEngine {
|
|
|
137
349
|
state: "running",
|
|
138
350
|
message: `Workflow ${this.definition.name} is running`,
|
|
139
351
|
});
|
|
140
|
-
await this.
|
|
352
|
+
await this.prepareContextPreflight(deadlineController.signal);
|
|
141
353
|
for (let stageIndex = 0; stageIndex < this.definition.stages.length; stageIndex += 1) {
|
|
142
354
|
this.throwIfAborted(deadlineController.signal);
|
|
143
355
|
const stage = this.definition.stages[stageIndex];
|
|
@@ -169,14 +381,17 @@ export class WorkflowEngine {
|
|
|
169
381
|
message: `Stage ${stage.id} ${executedStage.ok ? "succeeded" : "failed"}`,
|
|
170
382
|
});
|
|
171
383
|
if (!executedStage.ok && stage.on_failure === "abort") {
|
|
172
|
-
|
|
384
|
+
// Generic boundary for ALL stage aborts, so it must stay permanent by
|
|
385
|
+
// default — a stage that aborts on an ordinary permanent call failure
|
|
386
|
+
// is not a replayable run. The one exception is a genuinely exhausted
|
|
387
|
+
// wall clock: a parallel stage funnels the deadline throw through
|
|
388
|
+
// materializeUnexpectedFailure, which drops the class, so without this
|
|
389
|
+
// the environmental deadline would persist permanent and re-arm the
|
|
390
|
+
// spurious duplicate_of_failed_run warning on an honest replay.
|
|
391
|
+
throw new WorkflowError(`Stage ${stage.id} failed and on_failure=abort`, this.isWorkflowDeadlineExhausted() ? "transient" : undefined);
|
|
173
392
|
}
|
|
174
393
|
}
|
|
175
|
-
const
|
|
176
|
-
const finalCall = finalStage?.calls[0];
|
|
177
|
-
if (finalCall == null || !finalCall.ok || finalCall.outputText == null) {
|
|
178
|
-
throw new WorkflowError("Final stage did not produce a successful terminal call result");
|
|
179
|
-
}
|
|
394
|
+
const outputText = await this.resolveTerminalResult(stages);
|
|
180
395
|
const completedAt = new Date().toISOString();
|
|
181
396
|
await this.emitRunStateChange({
|
|
182
397
|
runId: this.runId,
|
|
@@ -193,7 +408,7 @@ export class WorkflowEngine {
|
|
|
193
408
|
message: `Workflow ${this.definition.name} succeeded`,
|
|
194
409
|
});
|
|
195
410
|
return {
|
|
196
|
-
outputText
|
|
411
|
+
outputText,
|
|
197
412
|
stages,
|
|
198
413
|
artifactsDir: this.artifactStore.artifactsDir,
|
|
199
414
|
};
|
|
@@ -219,6 +434,18 @@ export class WorkflowEngine {
|
|
|
219
434
|
});
|
|
220
435
|
}
|
|
221
436
|
else {
|
|
437
|
+
const planReviewError = error instanceof PlanReviewStageError
|
|
438
|
+
? CouncilPlanReviewErrorSchema.parse({
|
|
439
|
+
schema_version: "council-plan-review-error-v1",
|
|
440
|
+
run_id: this.runId,
|
|
441
|
+
execution_status: "failed",
|
|
442
|
+
stage: error.stage,
|
|
443
|
+
error: {
|
|
444
|
+
reason: error.reason,
|
|
445
|
+
detail: message.slice(0, 2_048),
|
|
446
|
+
},
|
|
447
|
+
})
|
|
448
|
+
: undefined;
|
|
222
449
|
await this.emitRunStateChange({
|
|
223
450
|
runId: this.runId,
|
|
224
451
|
workflowName: this.definition.name,
|
|
@@ -227,6 +454,7 @@ export class WorkflowEngine {
|
|
|
227
454
|
completedAt,
|
|
228
455
|
error: message,
|
|
229
456
|
failureClass: classifyFailureClass(error),
|
|
457
|
+
planReviewError,
|
|
230
458
|
});
|
|
231
459
|
await this.emitProgress({
|
|
232
460
|
runId: this.runId,
|
|
@@ -240,7 +468,63 @@ export class WorkflowEngine {
|
|
|
240
468
|
}
|
|
241
469
|
finally {
|
|
242
470
|
deadlineController.dispose();
|
|
471
|
+
this.workflowDeadlineAtMonoMs = undefined;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Resume-path entry point. `execute()` is never called on the resume preflight
|
|
476
|
+
* engine, so without this the deadline is never installed and the
|
|
477
|
+
* curator/finalizer delegate runs unbounded. Installs the SAME fresh full
|
|
478
|
+
* `timeout_seconds` budget a resumed run gets (D1), runs preflight under it,
|
|
479
|
+
* and always tears it down.
|
|
480
|
+
*/
|
|
481
|
+
async prepareContextPreflightWithDeadline() {
|
|
482
|
+
this.workflowDeadlineAtMonoMs =
|
|
483
|
+
monotonicNowMs() + this.definition.settings.timeout_seconds * 1000;
|
|
484
|
+
const deadlineController = this.createDeadlineController(this.definition.settings.timeout_seconds * 1000);
|
|
485
|
+
try {
|
|
486
|
+
await this.prepareContextPreflight(deadlineController.signal);
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
deadlineController.dispose();
|
|
490
|
+
this.workflowDeadlineAtMonoMs = undefined;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Complete the workflow context gate without dispatching a provider call.
|
|
495
|
+
* Resume uses this before backend construction when workflow_context is absent.
|
|
496
|
+
*/
|
|
497
|
+
async prepareContextPreflight(signal) {
|
|
498
|
+
// Protect engine-derived variables from caller overrides before every
|
|
499
|
+
// branch (runner absent, off, zero-selection, or legacy result).
|
|
500
|
+
if (this.restoredWorkflowContext === undefined) {
|
|
501
|
+
this.setEffectiveVariable("context_quality", "not_evaluated");
|
|
243
502
|
}
|
|
503
|
+
try {
|
|
504
|
+
await this.runContextBuilderPreflight(signal);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
if (this.planReviewContextPolicy !== undefined &&
|
|
508
|
+
!(err instanceof PlanReviewStageError) &&
|
|
509
|
+
!(err instanceof WorkflowCancellationError)) {
|
|
510
|
+
throw new PlanReviewStageError("context_preflight", err instanceof Error ? err.message : String(err), "internal_error");
|
|
511
|
+
}
|
|
512
|
+
throw err;
|
|
513
|
+
}
|
|
514
|
+
this.enforcePlanReviewContextExclusions();
|
|
515
|
+
this.deriveContextAvailable();
|
|
516
|
+
await this.validateInputPathsExist();
|
|
517
|
+
}
|
|
518
|
+
async resolveTerminalResult(stages) {
|
|
519
|
+
if (this.terminalResultPolicy.kind === "plan_review_artifacts") {
|
|
520
|
+
return await this.terminalResultPolicy.project();
|
|
521
|
+
}
|
|
522
|
+
const finalStage = stages[stages.length - 1];
|
|
523
|
+
const finalCall = finalStage?.calls[0];
|
|
524
|
+
if (finalCall == null || !finalCall.ok || finalCall.outputText == null) {
|
|
525
|
+
throw new WorkflowError("Final stage did not produce a successful terminal call result");
|
|
526
|
+
}
|
|
527
|
+
return finalCall.outputText;
|
|
244
528
|
}
|
|
245
529
|
async executeParallelStage(stage, stageIndex, signal) {
|
|
246
530
|
const queue = new DispatchQueue(this.queueDepth);
|
|
@@ -282,6 +566,15 @@ export class WorkflowEngine {
|
|
|
282
566
|
if (resumedCall != null) {
|
|
283
567
|
return resumedCall;
|
|
284
568
|
}
|
|
569
|
+
const resumedFailure = this.resumeFailedCalls.get(callKey);
|
|
570
|
+
if (resumedFailure?.terminal === true) {
|
|
571
|
+
return {
|
|
572
|
+
stageId: resumedFailure.stageId,
|
|
573
|
+
callId: resumedFailure.callId,
|
|
574
|
+
ok: false,
|
|
575
|
+
error: resumedFailure.error,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
285
578
|
// Handle built-in reducers — no LLM call, no retry loop
|
|
286
579
|
if (call.reducer != null) {
|
|
287
580
|
const reducer = getReducer(call.reducer);
|
|
@@ -407,24 +700,48 @@ export class WorkflowEngine {
|
|
|
407
700
|
const retryMode = stage.retry.mode;
|
|
408
701
|
const backoffSeconds = stage.retry.backoff_seconds;
|
|
409
702
|
const maxBackoffSeconds = stage.retry.max_backoff_seconds;
|
|
410
|
-
//
|
|
703
|
+
// Resume never replenishes an already-consumed provider attempt.
|
|
704
|
+
const initialRetryCount = this.resumeNextRetryCounts.get(callKey) ?? 0;
|
|
705
|
+
// Emit call prepared ONCE (input is stable across retries).
|
|
706
|
+
// Invocation audit rides the event, never ClinkInput / input.json.
|
|
707
|
+
const invocation = this.maybeInvocationAudit(input);
|
|
411
708
|
await this.emitCallPrepared({
|
|
412
709
|
runId: this.runId,
|
|
413
710
|
workflowName: this.definition.name,
|
|
414
711
|
stageId: stage.id,
|
|
415
712
|
callId: call.id,
|
|
416
|
-
attempt: 1,
|
|
417
|
-
retryCount:
|
|
713
|
+
attempt: initialRetryCount + 1,
|
|
714
|
+
retryCount: initialRetryCount,
|
|
418
715
|
contextProducing,
|
|
419
716
|
input: structuredClone(input),
|
|
717
|
+
...(invocation === undefined ? {} : { invocation }),
|
|
420
718
|
});
|
|
421
|
-
let lastError =
|
|
719
|
+
let lastError = initialRetryCount > maxRetries
|
|
720
|
+
? new Error("Retry budget exhausted before resume")
|
|
721
|
+
: undefined;
|
|
422
722
|
let lastFailureClass = "permanent";
|
|
423
|
-
let lastRetryCount =
|
|
424
|
-
|
|
723
|
+
let lastRetryCount = initialRetryCount;
|
|
724
|
+
// F4: set in the catch below for the NEXT attempt only; always derived from
|
|
725
|
+
// the original `input.prompt`, so it never stacks. In-memory only — a
|
|
726
|
+
// resumed retry has no lastError and simply gets no suffix.
|
|
727
|
+
let correctiveSuffix;
|
|
728
|
+
// D7: at most ONE extra JSON-corrective attempt per call under
|
|
729
|
+
// transport_only, whatever max_retries allows. Loop-local, so a resumed
|
|
730
|
+
// call can earn it again — bounded by the persisted retry count anyway.
|
|
731
|
+
let correctiveRetryUsed = false;
|
|
732
|
+
for (let retryCount = initialRetryCount; retryCount <= maxRetries; retryCount += 1) {
|
|
425
733
|
const attempt = retryCount + 1;
|
|
426
734
|
lastRetryCount = retryCount;
|
|
427
|
-
|
|
735
|
+
// Deliberately guarded HERE rather than moved inside the attempt `try`:
|
|
736
|
+
// inside it, a bare deadline WorkflowError would become retryable under
|
|
737
|
+
// retry.mode:"all". Record the terminal outcome, then rethrow unchanged.
|
|
738
|
+
try {
|
|
739
|
+
this.throwIfAborted(signal);
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, error instanceof WorkflowCancellationError ? "cancelled" : "failed");
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
428
745
|
await this.emitCallStateChange({
|
|
429
746
|
runId: this.runId,
|
|
430
747
|
workflowName: this.definition.name,
|
|
@@ -450,7 +767,18 @@ export class WorkflowEngine {
|
|
|
450
767
|
: `Call ${callKey} is retrying (attempt ${String(attempt)})`,
|
|
451
768
|
});
|
|
452
769
|
try {
|
|
453
|
-
const
|
|
770
|
+
const attemptInput = correctiveSuffix == null
|
|
771
|
+
? input
|
|
772
|
+
: {
|
|
773
|
+
...input,
|
|
774
|
+
prompt: `${typeof input["prompt"] === "string" ? input["prompt"] : ""}${correctiveSuffix}`,
|
|
775
|
+
};
|
|
776
|
+
const result = await this.invokeCall(stage, call, attemptInput, signal, attempt);
|
|
777
|
+
enforceTypedResultFidelity(result.debugMetadata, {
|
|
778
|
+
stageId: stage.id,
|
|
779
|
+
callId: call.id,
|
|
780
|
+
typedOutput: call.response_contract === "json" || call.validator_schema_id != null,
|
|
781
|
+
});
|
|
454
782
|
const fidelity = analyzeOutputFidelity(result.outputText, result.debugMetadata);
|
|
455
783
|
enforceOutputFidelity(fidelity, {
|
|
456
784
|
stageId: stage.id,
|
|
@@ -460,6 +788,7 @@ export class WorkflowEngine {
|
|
|
460
788
|
const artifact = this.maybeWriteArtifact(stage, call, stageIndex, callIndex, result.outputText);
|
|
461
789
|
const completedAt = new Date().toISOString();
|
|
462
790
|
const durationMs = Date.now() - startedAtMs;
|
|
791
|
+
const observedInvocation = extractObservedInvocation(result.debugMetadata);
|
|
463
792
|
await this.emitCallStateChange({
|
|
464
793
|
runId: this.runId,
|
|
465
794
|
workflowName: this.definition.name,
|
|
@@ -474,6 +803,7 @@ export class WorkflowEngine {
|
|
|
474
803
|
outputText: result.outputText,
|
|
475
804
|
fidelity,
|
|
476
805
|
artifact,
|
|
806
|
+
...(observedInvocation === undefined ? {} : { observedInvocation }),
|
|
477
807
|
});
|
|
478
808
|
await this.emitProgress({
|
|
479
809
|
runId: this.runId,
|
|
@@ -497,43 +827,37 @@ export class WorkflowEngine {
|
|
|
497
827
|
};
|
|
498
828
|
}
|
|
499
829
|
catch (error) {
|
|
830
|
+
// The deadline is never retryable, but the call still has to land in a
|
|
831
|
+
// terminal state before the throw leaves the loop.
|
|
832
|
+
if (error instanceof WorkflowDispatchDeadlineError) {
|
|
833
|
+
await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, "failed");
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
500
836
|
// Cancellation is never retryable
|
|
501
837
|
if (error instanceof WorkflowCancellationError) {
|
|
502
|
-
|
|
503
|
-
const durationMs = Date.now() - startedAtMs;
|
|
504
|
-
await this.emitCallStateChange({
|
|
505
|
-
runId: this.runId,
|
|
506
|
-
workflowName: this.definition.name,
|
|
507
|
-
stageId: stage.id,
|
|
508
|
-
callId: call.id,
|
|
509
|
-
state: "cancelled",
|
|
510
|
-
attempt,
|
|
511
|
-
retryCount,
|
|
512
|
-
startedAt,
|
|
513
|
-
completedAt,
|
|
514
|
-
durationMs,
|
|
515
|
-
error: error.message,
|
|
516
|
-
});
|
|
517
|
-
await this.emitProgress({
|
|
518
|
-
runId: this.runId,
|
|
519
|
-
workflowName: this.definition.name,
|
|
520
|
-
scope: "call",
|
|
521
|
-
stageId: stage.id,
|
|
522
|
-
stageDescription: stage.description,
|
|
523
|
-
callId: call.id,
|
|
524
|
-
state: "cancelled",
|
|
525
|
-
attempt,
|
|
526
|
-
retryCount,
|
|
527
|
-
message: `Call ${callKey} cancelled`,
|
|
528
|
-
});
|
|
838
|
+
await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, "cancelled");
|
|
529
839
|
throw error;
|
|
530
840
|
}
|
|
531
841
|
const failureClass = classifyFailureClass(error);
|
|
532
842
|
lastError = error;
|
|
533
843
|
lastFailureClass = failureClass;
|
|
534
844
|
// Determine if this error is retryable
|
|
535
|
-
const
|
|
845
|
+
const ordinaryRetry = retryCount < maxRetries &&
|
|
846
|
+
isRetryable(error, failureClass, retryMode) &&
|
|
847
|
+
!shouldVetoWallTimeoutRetry(this.definition.name, error instanceof CallExecutionError ? error.terminationReason : undefined);
|
|
848
|
+
const correctiveRetry = !ordinaryRetry &&
|
|
849
|
+
!correctiveRetryUsed &&
|
|
850
|
+
retryCount < maxRetries &&
|
|
851
|
+
this.isCorrectiveRetryEligible(stage, call, error);
|
|
852
|
+
if (correctiveRetry) {
|
|
853
|
+
correctiveRetryUsed = true;
|
|
854
|
+
}
|
|
855
|
+
const canRetry = ordinaryRetry || correctiveRetry;
|
|
536
856
|
if (canRetry) {
|
|
857
|
+
// F4: corrective suffix rides only on the retry that was already
|
|
858
|
+
// decided above — it never changes retry eligibility. Unconditional
|
|
859
|
+
// reassignment clears a stale suffix after a non-JSON failure.
|
|
860
|
+
correctiveSuffix = this.buildCorrectiveRetrySuffix(stage, call, input, error);
|
|
537
861
|
// Compute backoff with jitter
|
|
538
862
|
const delayMs = computeRetryDelay(retryCount, backoffSeconds, maxBackoffSeconds);
|
|
539
863
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -608,11 +932,220 @@ export class WorkflowEngine {
|
|
|
608
932
|
error: message,
|
|
609
933
|
};
|
|
610
934
|
}
|
|
611
|
-
|
|
612
|
-
|
|
935
|
+
/**
|
|
936
|
+
* Terminalize a call that is about to be abandoned by a throw rather than
|
|
937
|
+
* by the retry loop's own failure path.
|
|
938
|
+
*
|
|
939
|
+
* Both abort/deadline exits from executeSingleCall leave the loop OUTSIDE
|
|
940
|
+
* the attempt `try`, so without this the persisted entry is stranded as
|
|
941
|
+
* "running" with no completedAt — a durable lie that resume, the dashboard
|
|
942
|
+
* and run health all read. The caller still rethrows: this only records the
|
|
943
|
+
* outcome, it never swallows it.
|
|
944
|
+
*/
|
|
945
|
+
async emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, state) {
|
|
946
|
+
const callKey = `${stage.id}.${call.id}`;
|
|
947
|
+
this.terminalizedCallKeys.add(callKey);
|
|
948
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
949
|
+
const completedAt = new Date().toISOString();
|
|
950
|
+
const durationMs = Date.now() - startedAtMs;
|
|
951
|
+
await this.emitCallStateChange({
|
|
952
|
+
runId: this.runId,
|
|
953
|
+
workflowName: this.definition.name,
|
|
954
|
+
stageId: stage.id,
|
|
955
|
+
callId: call.id,
|
|
956
|
+
state,
|
|
957
|
+
attempt,
|
|
958
|
+
retryCount,
|
|
959
|
+
startedAt,
|
|
960
|
+
completedAt,
|
|
961
|
+
durationMs,
|
|
962
|
+
error: message,
|
|
963
|
+
failureClass: classifyFailureClass(error),
|
|
964
|
+
});
|
|
965
|
+
await this.emitProgress({
|
|
966
|
+
runId: this.runId,
|
|
967
|
+
workflowName: this.definition.name,
|
|
968
|
+
scope: "call",
|
|
969
|
+
stageId: stage.id,
|
|
970
|
+
stageDescription: stage.description,
|
|
971
|
+
callId: call.id,
|
|
972
|
+
state,
|
|
973
|
+
attempt,
|
|
974
|
+
retryCount,
|
|
975
|
+
message: state === "cancelled"
|
|
976
|
+
? `Call ${callKey} cancelled`
|
|
977
|
+
: `Call ${callKey} failed: ${message}`,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* F4: bounded corrective suffix for the next already-scheduled retry after
|
|
982
|
+
* a JSON/schema-shaped failure. Returns undefined (no suffix) unless ALL of:
|
|
983
|
+
* - the call declares a JSON contract (mirrors the expect_json derivation
|
|
984
|
+
* in buildCallInput);
|
|
985
|
+
* - the run is NOT typed plan review (identity: the plan-review-receipts
|
|
986
|
+
* workflow name on the
|
|
987
|
+
* normalized definition, plus the plan-review runtime signals as
|
|
988
|
+
* defense-in-depth — plan-review dispatch bytes are pinned);
|
|
989
|
+
* - the failure is JSON/schema-shaped (SchemaValidationError,
|
|
990
|
+
* OutputSizeError as the second correctable class, or a child
|
|
991
|
+
* invalid_json_output rejection);
|
|
992
|
+
* - appending stays inside the same prompt-budget ceiling the dispatch-time
|
|
993
|
+
* gate enforces (best-effort re-estimate; skip with a warn on overflow or
|
|
994
|
+
* estimator failure — never a new failure mode).
|
|
995
|
+
* The suffix is always derived from the ORIGINAL prompt, so it can never
|
|
996
|
+
* stack across attempts, and it is never persisted to state.
|
|
997
|
+
*/
|
|
998
|
+
/**
|
|
999
|
+
* Typed plan review (identity: the plan-review-receipts workflow name, plus
|
|
1000
|
+
* the plan-review runtime signals as defense-in-depth). Its dispatch bytes
|
|
1001
|
+
* and retry behavior are pinned, so no corrective machinery may touch it.
|
|
1002
|
+
*/
|
|
1003
|
+
isTypedPlanReviewCall(call) {
|
|
1004
|
+
return (this.definition.name === "plan-review-receipts" ||
|
|
1005
|
+
this.planReviewContextPolicy !== undefined ||
|
|
1006
|
+
call.plan_review_snapshot_text !== undefined);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* D7: under retry.mode "transport_only" a malformed-JSON answer is a
|
|
1010
|
+
* permanent failure, so it would never be retried. A JSON-contract call gets
|
|
1011
|
+
* ONE corrective attempt when the failure is primarily JSON-shaped; a
|
|
1012
|
+
* secondary-only invalid_json_output reason never creates eligibility.
|
|
1013
|
+
* failureClass is not reclassified.
|
|
1014
|
+
*/
|
|
1015
|
+
isCorrectiveRetryEligible(stage, call, error) {
|
|
1016
|
+
if (stage.retry.mode !== "transport_only") {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
if (!declaresJsonContract(call) || this.isTypedPlanReviewCall(call)) {
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
return (error instanceof SchemaValidationError ||
|
|
1023
|
+
error instanceof OutputSizeError ||
|
|
1024
|
+
(error instanceof CallExecutionError &&
|
|
1025
|
+
error.childDebugMetadata?.semantic_failure_reason === "invalid_json_output"));
|
|
1026
|
+
}
|
|
1027
|
+
buildCorrectiveRetrySuffix(stage, call, input, error) {
|
|
1028
|
+
if (!declaresJsonContract(call)) {
|
|
1029
|
+
return undefined;
|
|
1030
|
+
}
|
|
1031
|
+
if (this.isTypedPlanReviewCall(call)) {
|
|
1032
|
+
return undefined;
|
|
1033
|
+
}
|
|
1034
|
+
const detail = correctiveFailureDetail(error);
|
|
1035
|
+
if (detail == null) {
|
|
1036
|
+
return undefined;
|
|
1037
|
+
}
|
|
1038
|
+
const suffix = buildCorrectiveSuffixText(detail, error instanceof OutputSizeError ? CORRECTIVE_SIZE_SUFFIX_HEADER : CORRECTIVE_SUFFIX_HEADER);
|
|
1039
|
+
try {
|
|
1040
|
+
const basePrompt = typeof input["prompt"] === "string" ? input["prompt"] : "";
|
|
1041
|
+
const files = Array.isArray(input["absolute_file_paths"])
|
|
1042
|
+
? input["absolute_file_paths"].filter((path) => typeof path === "string")
|
|
1043
|
+
: [];
|
|
1044
|
+
const images = Array.isArray(input["images"])
|
|
1045
|
+
? input["images"].filter((path) => typeof path === "string")
|
|
1046
|
+
: [];
|
|
1047
|
+
const estimate = estimatePromptBudget({
|
|
1048
|
+
promptText: `${basePrompt}${suffix}`,
|
|
1049
|
+
contextText: typeof input["context"] === "string" ? input["context"] : undefined,
|
|
1050
|
+
fileContributors: files.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
|
|
1051
|
+
imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
|
|
1052
|
+
maxBytes: this.maxPromptBytes,
|
|
1053
|
+
reserveBytes: isCouncilExecutionWorkflow(this.definition.name)
|
|
1054
|
+
? (this.promptReserveBytes ?? DEFAULT_RESERVE_BYTES) + COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES
|
|
1055
|
+
: this.promptReserveBytes,
|
|
1056
|
+
});
|
|
1057
|
+
if (estimate.exceeded) {
|
|
1058
|
+
logger.warn({ run_id: this.runId, stage_id: stage.id, call_id: call.id }, "corrective retry suffix skipped: appending would exceed the prompt budget");
|
|
1059
|
+
return undefined;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
catch (budgetError) {
|
|
1063
|
+
logger.warn({
|
|
1064
|
+
run_id: this.runId,
|
|
1065
|
+
stage_id: stage.id,
|
|
1066
|
+
call_id: call.id,
|
|
1067
|
+
err: budgetError instanceof Error ? budgetError.message : String(budgetError),
|
|
1068
|
+
}, "corrective retry suffix skipped: prompt budget estimate failed");
|
|
1069
|
+
return undefined;
|
|
1070
|
+
}
|
|
1071
|
+
return suffix;
|
|
1072
|
+
}
|
|
1073
|
+
async invokeCall(stage, call, input, signal, attempt) {
|
|
1074
|
+
const dispatchDeadline = this.computeDispatchDeadline(call);
|
|
1075
|
+
const dispatchInput = this.prepareDispatchInput(input, dispatchDeadline.callRemainingMs);
|
|
1076
|
+
const sessionLogOwner = this.sessionLogGeneration !== undefined && this.session.supportsSessionLogOwner === true
|
|
1077
|
+
? {
|
|
1078
|
+
family: "workflow",
|
|
1079
|
+
runId: this.runId,
|
|
1080
|
+
runGeneration: this.sessionLogGeneration,
|
|
1081
|
+
stageId: stage.id,
|
|
1082
|
+
callId: call.id,
|
|
1083
|
+
attempt,
|
|
1084
|
+
}
|
|
1085
|
+
: undefined;
|
|
1086
|
+
// Prompt sizing above performs synchronous filesystem work, during which
|
|
1087
|
+
// the deadline timer cannot run. Recheck at dispatch; the already-rendered
|
|
1088
|
+
// guidance remains a conservative completion target if preparation took time.
|
|
1089
|
+
const finalDispatchDeadline = this.computeDispatchDeadline(call);
|
|
1090
|
+
this.throwIfAborted(signal);
|
|
1091
|
+
return await callClink(this.session, dispatchInput, signal, { stageId: stage.id, callId: call.id }, finalDispatchDeadline.timeoutMs, sessionLogOwner);
|
|
1092
|
+
}
|
|
1093
|
+
computeDispatchDeadline(call) {
|
|
1094
|
+
const configuredCallTimeoutMs = call.timeout_seconds != null
|
|
613
1095
|
? Math.max((call.timeout_seconds * 1000) + CALL_TIMEOUT_BUFFER_MS, DEFAULT_PARENT_CALL_TIMEOUT_MS)
|
|
614
1096
|
: undefined;
|
|
615
|
-
|
|
1097
|
+
const workflowRemainingMs = this.workflowRemainingMs();
|
|
1098
|
+
if (workflowRemainingMs != null && workflowRemainingMs <= 0) {
|
|
1099
|
+
throw new WorkflowDispatchDeadlineError();
|
|
1100
|
+
}
|
|
1101
|
+
// Keep the existing backend timeout contract for generic and excluded
|
|
1102
|
+
// workflows. The workflow AbortSignal still enforces the outer deadline;
|
|
1103
|
+
// only admitted councils with an explicit call cap receive the tighter
|
|
1104
|
+
// per-dispatch timeout when the workflow is closer to expiry.
|
|
1105
|
+
const timeoutMs = !isCouncilExecutionWorkflow(this.definition.name) ||
|
|
1106
|
+
configuredCallTimeoutMs == null || workflowRemainingMs == null
|
|
1107
|
+
? configuredCallTimeoutMs
|
|
1108
|
+
: Math.min(configuredCallTimeoutMs, workflowRemainingMs);
|
|
1109
|
+
const callRemainingMs = configuredCallTimeoutMs == null
|
|
1110
|
+
? workflowRemainingMs
|
|
1111
|
+
: (isCouncilExecutionWorkflow(this.definition.name) && workflowRemainingMs != null
|
|
1112
|
+
? Math.min(configuredCallTimeoutMs, workflowRemainingMs)
|
|
1113
|
+
: configuredCallTimeoutMs);
|
|
1114
|
+
return {
|
|
1115
|
+
timeoutMs,
|
|
1116
|
+
callRemainingMs,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
prepareDispatchInput(input, callRemainingMs) {
|
|
1120
|
+
if (!isCouncilExecutionWorkflow(this.definition.name) ||
|
|
1121
|
+
this.workflowDeadlineAtMonoMs == null ||
|
|
1122
|
+
callRemainingMs == null ||
|
|
1123
|
+
typeof input["prompt"] !== "string") {
|
|
1124
|
+
return input;
|
|
1125
|
+
}
|
|
1126
|
+
const guidedInput = {
|
|
1127
|
+
...input,
|
|
1128
|
+
prompt: `${input["prompt"]}${buildCouncilDeadlineGuidance({
|
|
1129
|
+
workflowRemainingMs: this.workflowRemainingMs() ?? 0,
|
|
1130
|
+
callRemainingMs,
|
|
1131
|
+
})}`,
|
|
1132
|
+
};
|
|
1133
|
+
const filePaths = Array.isArray(input["absolute_file_paths"])
|
|
1134
|
+
? input["absolute_file_paths"].filter((path) => typeof path === "string")
|
|
1135
|
+
: [];
|
|
1136
|
+
const imagePaths = Array.isArray(input["images"])
|
|
1137
|
+
? input["images"].filter((path) => typeof path === "string")
|
|
1138
|
+
: [];
|
|
1139
|
+
const estimate = estimatePromptBudget({
|
|
1140
|
+
promptText: guidedInput["prompt"],
|
|
1141
|
+
contextText: typeof input["context"] === "string" ? input["context"] : undefined,
|
|
1142
|
+
fileContributors: filePaths.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
|
|
1143
|
+
imageContributors: imagePaths.map((path) => createPathContributor(`image:${path}`, path, "image")),
|
|
1144
|
+
maxBytes: this.maxPromptBytes,
|
|
1145
|
+
reserveBytes: this.promptReserveBytes,
|
|
1146
|
+
});
|
|
1147
|
+
enforcePromptBudget(estimate);
|
|
1148
|
+
return guidedInput;
|
|
616
1149
|
}
|
|
617
1150
|
maybeWriteArtifact(stage, call, stageIndex, callIndex, outputText) {
|
|
618
1151
|
const requiresValidation = call.validator_schema_id != null;
|
|
@@ -624,19 +1157,78 @@ export class WorkflowEngine {
|
|
|
624
1157
|
throw new WorkflowError(`Validated call ${stage.id}.${call.id} must declare validator_schema_id`);
|
|
625
1158
|
}
|
|
626
1159
|
const validated = this.artifactStore.validateJsonOutput(stage.id, call.id, validatorSchemaId, outputText);
|
|
1160
|
+
if (call.plan_review_anonymous_aliases != null) {
|
|
1161
|
+
const parsed = validated.parsed;
|
|
1162
|
+
const allowed = this.planReviewDeliveredAliases.get(`${stage.id}.${call.id}`) ?? new Set();
|
|
1163
|
+
const assessmentIds = (parsed.peer_assessments ?? []).map((entry) => entry.candidate_id);
|
|
1164
|
+
const rankingIds = (parsed.ranking ?? []).map((entry) => entry.candidate_id);
|
|
1165
|
+
const exactAliasSet = (ids) => {
|
|
1166
|
+
if (!ids.every((candidate) => typeof candidate === "string" && allowed.has(candidate)))
|
|
1167
|
+
return false;
|
|
1168
|
+
const unique = new Set(ids);
|
|
1169
|
+
return ids.length === allowed.size && unique.size === allowed.size &&
|
|
1170
|
+
[...allowed].every((candidate) => unique.has(candidate));
|
|
1171
|
+
};
|
|
1172
|
+
if (!exactAliasSet(assessmentIds) || !exactAliasSet(rankingIds)) {
|
|
1173
|
+
throw new WorkflowError(`Validation failed for ${stage.id}.${call.id}: peer_assessments and ranking must each contain every bound peer alias exactly once`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
// Optional post-processing: a call may declare `post_processor: <name>` to
|
|
1177
|
+
// transform the validated value before it is persisted. Unknown names are a
|
|
1178
|
+
// load-time validation error (loader.ts:validatePostProcessors); we fail
|
|
1179
|
+
// fast here too as defense-in-depth, since silently writing the
|
|
1180
|
+
// un-transformed output through could ship un-scrubbed data (e.g. an
|
|
1181
|
+
// un-run citation_check). The transform runs on the parsed data; the
|
|
1182
|
+
// artifact is re-serialized so the persisted bytes reflect it.
|
|
1183
|
+
let persisted = validated;
|
|
1184
|
+
const postProcessorName = call.post_processor;
|
|
1185
|
+
if (postProcessorName != null) {
|
|
1186
|
+
const postProcessor = getPostProcessor(postProcessorName);
|
|
1187
|
+
if (postProcessor == null) {
|
|
1188
|
+
throw new WorkflowError(`Call ${stage.id}.${call.id} declares post_processor ` +
|
|
1189
|
+
`"${postProcessorName}" which is not registered`);
|
|
1190
|
+
}
|
|
1191
|
+
const allowlist = this.buildPostProcessorAllowlist();
|
|
1192
|
+
const processed = postProcessor(validated.parsed, { allowlist, schemaId: validatorSchemaId });
|
|
1193
|
+
const serialized = `${JSON.stringify(processed, null, 2)}\n`;
|
|
1194
|
+
persisted = {
|
|
1195
|
+
parsed: processed,
|
|
1196
|
+
serialized,
|
|
1197
|
+
sizeBytes: Buffer.byteLength(serialized, "utf-8"),
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
627
1200
|
return this.artifactStore.writeCallArtifact({
|
|
628
1201
|
stage,
|
|
629
1202
|
call,
|
|
630
1203
|
stageIndex,
|
|
631
1204
|
callIndex,
|
|
632
|
-
validated,
|
|
1205
|
+
validated: persisted,
|
|
633
1206
|
});
|
|
634
1207
|
}
|
|
635
|
-
resolveDynamicPaths(
|
|
636
|
-
if (
|
|
1208
|
+
resolveDynamicPaths(variableNames, context) {
|
|
1209
|
+
if (variableNames == null) {
|
|
637
1210
|
return [];
|
|
638
1211
|
}
|
|
639
|
-
|
|
1212
|
+
if (typeof variableNames === "string") {
|
|
1213
|
+
const values = this.resolveDynamicPathVariable(variableNames, context);
|
|
1214
|
+
validateDynamicPaths(values, this.allowedRoots, context);
|
|
1215
|
+
return values;
|
|
1216
|
+
}
|
|
1217
|
+
const values = [];
|
|
1218
|
+
const seen = new Set();
|
|
1219
|
+
for (const variableName of variableNames) {
|
|
1220
|
+
for (const value of this.resolveDynamicPathVariable(variableName, context)) {
|
|
1221
|
+
if (!seen.has(value)) {
|
|
1222
|
+
seen.add(value);
|
|
1223
|
+
values.push(value);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
validateDynamicPaths(values, this.allowedRoots, context);
|
|
1228
|
+
return values;
|
|
1229
|
+
}
|
|
1230
|
+
resolveDynamicPathVariable(variableName, context) {
|
|
1231
|
+
const rawValue = this.effectiveVariables[variableName];
|
|
640
1232
|
if (!Array.isArray(rawValue)) {
|
|
641
1233
|
throw new WorkflowError(`Variable "${variableName}" for ${context} must be a string[]`);
|
|
642
1234
|
}
|
|
@@ -647,22 +1239,519 @@ export class WorkflowEngine {
|
|
|
647
1239
|
}
|
|
648
1240
|
return isAbsolute(value) ? value : resolve(workspaceRoot, value);
|
|
649
1241
|
});
|
|
650
|
-
validateDynamicPaths(values, this.allowedRoots, context);
|
|
651
1242
|
return values;
|
|
652
1243
|
}
|
|
653
|
-
|
|
1244
|
+
setEffectiveVariable(name, value) {
|
|
1245
|
+
this.effectiveVariables[name] = value;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Derive the engine-computed `context_available` template variable after
|
|
1249
|
+
* context-builder preflight has had a chance to append to
|
|
1250
|
+
* `context_bundle_files`. True when the union of the effective
|
|
1251
|
+
* `context_files` and `context_bundle_files` path lists is non-empty.
|
|
1252
|
+
* Stored as the STRING "true"/"false" since the template engine stringifies
|
|
1253
|
+
* variable values. Engine-derived; setting it is expected.
|
|
1254
|
+
*/
|
|
1255
|
+
deriveContextAvailable() {
|
|
1256
|
+
const contextFiles = this.readStringArrayVariable("context_files");
|
|
1257
|
+
const bundleFiles = this.readStringArrayVariable("context_bundle_files");
|
|
1258
|
+
const available = contextFiles.length > 0 || bundleFiles.length > 0;
|
|
1259
|
+
this.setEffectiveVariable("context_available", available ? "true" : "false");
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Build the citation allowlist for a post-processor run: the union of files
|
|
1263
|
+
* parsed from the `diff_context` variable, the resolved `context_files`, the
|
|
1264
|
+
* resolved `context_bundle_files`, and the source files selected by any
|
|
1265
|
+
* curated context-builder bundle among them. Both full paths and basenames
|
|
1266
|
+
* are stored so citation matching is lenient.
|
|
1267
|
+
*
|
|
1268
|
+
* A curated preflight copies only `bundle.md` and `manifest.json` into
|
|
1269
|
+
* `context_bundle_files`, so without the manifest's `selected_context`
|
|
1270
|
+
* every citation to a real source file would be stripped. Reading from the
|
|
1271
|
+
* (persisted) variable keeps this working on resume.
|
|
1272
|
+
*/
|
|
1273
|
+
buildPostProcessorAllowlist() {
|
|
1274
|
+
const diffRaw = this.effectiveVariables["diff_context"];
|
|
1275
|
+
const allowlist = buildAllowlistFromDiff(typeof diffRaw === "string" ? diffRaw : "");
|
|
1276
|
+
for (const name of ["context_files", "context_bundle_files"]) {
|
|
1277
|
+
for (const path of this.resolvePathListLenient(name)) {
|
|
1278
|
+
addPathWithBasename(allowlist, path);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const manifestPaths = new Set(this.resolvePathListLenient("context_bundle_files").filter((path) => isAbsolute(path) && basename(path) === "manifest.json"));
|
|
1282
|
+
for (const manifestPath of manifestPaths) {
|
|
1283
|
+
this.addCuratedBundleSelections(allowlist, manifestPath);
|
|
1284
|
+
}
|
|
1285
|
+
return allowlist;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Add each `selected_context` entry of a context-builder manifest to the
|
|
1289
|
+
* allowlist: its root-relative `path` and, when its root resolves, the
|
|
1290
|
+
* absolute form. The allowlist is advisory, so an unreadable, oversized
|
|
1291
|
+
* (> 8 MiB), non-JSON, or non-context-builder manifest is skipped silently
|
|
1292
|
+
* and malformed entries are ignored.
|
|
1293
|
+
*/
|
|
1294
|
+
addCuratedBundleSelections(allowlist, manifestPath) {
|
|
1295
|
+
const maxBytes = 8 * 1024 * 1024;
|
|
1296
|
+
let text;
|
|
1297
|
+
let fd;
|
|
1298
|
+
try {
|
|
1299
|
+
fd = openSync(manifestPath, "r");
|
|
1300
|
+
const stats = fstatSync(fd);
|
|
1301
|
+
if (!stats.isFile() || stats.size > maxBytes) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
// One spare byte detects a file that grew after fstat; skip it rather
|
|
1305
|
+
// than parse a torn read.
|
|
1306
|
+
const buffer = Buffer.alloc(stats.size + 1);
|
|
1307
|
+
let length = 0;
|
|
1308
|
+
while (length < buffer.length) {
|
|
1309
|
+
const read = readSync(fd, buffer, length, buffer.length - length, null);
|
|
1310
|
+
if (read === 0) {
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
length += read;
|
|
1314
|
+
}
|
|
1315
|
+
if (length > stats.size) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
text = buffer.toString("utf-8", 0, length);
|
|
1319
|
+
}
|
|
1320
|
+
catch {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
finally {
|
|
1324
|
+
if (fd !== undefined) {
|
|
1325
|
+
try {
|
|
1326
|
+
closeSync(fd);
|
|
1327
|
+
}
|
|
1328
|
+
catch {
|
|
1329
|
+
// Advisory read; a close failure must not fail the call.
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
let manifest;
|
|
1334
|
+
try {
|
|
1335
|
+
manifest = JSON.parse(text);
|
|
1336
|
+
}
|
|
1337
|
+
catch {
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const { selected_context: selected, roots } = manifest;
|
|
1344
|
+
if (!Array.isArray(selected) || !Array.isArray(roots)) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
for (const entry of selected) {
|
|
1348
|
+
if (typeof entry !== "object" || entry === null) {
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const { path, root_index: rootIndex } = entry;
|
|
1352
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
addPathWithBasename(allowlist, path);
|
|
1356
|
+
const root = typeof rootIndex === "number" && Number.isInteger(rootIndex) && rootIndex >= 0
|
|
1357
|
+
? roots[rootIndex]
|
|
1358
|
+
: undefined;
|
|
1359
|
+
if (typeof root === "object" && root !== null) {
|
|
1360
|
+
const rootPath = root["path"];
|
|
1361
|
+
if (typeof rootPath === "string" && rootPath.length > 0) {
|
|
1362
|
+
addPathWithBasename(allowlist, join(rootPath, path));
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Resolve a path-list variable to absolute paths without enforcing allowed
|
|
1369
|
+
* roots (the post-processor allowlist is advisory, not a security boundary).
|
|
1370
|
+
* Both the raw and resolved forms are returned so basename matching works
|
|
1371
|
+
* regardless of how a citation spells the file.
|
|
1372
|
+
*/
|
|
1373
|
+
resolvePathListLenient(name) {
|
|
1374
|
+
const value = this.effectiveVariables[name];
|
|
1375
|
+
if (!Array.isArray(value)) {
|
|
1376
|
+
return [];
|
|
1377
|
+
}
|
|
1378
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
1379
|
+
const out = [];
|
|
1380
|
+
for (const entry of value) {
|
|
1381
|
+
if (typeof entry !== "string") {
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
out.push(entry);
|
|
1385
|
+
out.push(isAbsolute(entry) ? entry : resolve(workspaceRoot, entry));
|
|
1386
|
+
}
|
|
1387
|
+
return out;
|
|
1388
|
+
}
|
|
1389
|
+
async runContextBuilderPreflight(signal) {
|
|
1390
|
+
if (this.restoredWorkflowContext !== undefined) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
const policy = this.resolveContextPolicy();
|
|
1394
|
+
if (policy.effectiveMode === "off" || policy.effectiveMode === "disabled") {
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const runner = this.contextBuilderRunner;
|
|
1398
|
+
if (runner == null) {
|
|
1399
|
+
if (this.requireContextPreflight) {
|
|
1400
|
+
throw new WorkflowError(`Workflow context preflight is required in ${policy.effectiveMode} mode but no runner is available`);
|
|
1401
|
+
}
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
// A-7(a): strip force-excluded plan anchors from the caller's focus BEFORE
|
|
1405
|
+
// anything reads them, so the curator is never even asked to select the plan.
|
|
1406
|
+
this.enforcePlanReviewContextExclusions();
|
|
1407
|
+
const focusPaths = this.readStringArrayVariable("cb_focus_paths");
|
|
1408
|
+
const callerSearchTerms = this.readStringArrayVariable("cb_search_terms");
|
|
1409
|
+
this.throwIfAborted(signal);
|
|
1410
|
+
// Optional budget overrides: 6 flat number vars folded into a single budget
|
|
1411
|
+
// object containing ONLY the keys actually supplied. When none are present
|
|
1412
|
+
// the object is undefined and omitted via conditional spread, keeping the
|
|
1413
|
+
// no-budget input byte-identical to the historical shape (cache equality).
|
|
1414
|
+
const budgetEntries = [
|
|
1415
|
+
["max_estimated_tokens", this.readOptionalNumberVariable("cb_budget_max_estimated_tokens")],
|
|
1416
|
+
["max_files", this.readOptionalNumberVariable("cb_budget_max_files")],
|
|
1417
|
+
["max_scan_files", this.readOptionalNumberVariable("cb_budget_max_scan_files")],
|
|
1418
|
+
["max_bytes_per_file", this.readOptionalNumberVariable("cb_budget_max_bytes_per_file")],
|
|
1419
|
+
["max_total_bytes", this.readOptionalNumberVariable("cb_budget_max_total_bytes")],
|
|
1420
|
+
["max_search_results", this.readOptionalNumberVariable("cb_budget_max_search_results")],
|
|
1421
|
+
];
|
|
1422
|
+
const budgetObject = {};
|
|
1423
|
+
for (const [key, value] of budgetEntries) {
|
|
1424
|
+
if (value !== undefined) {
|
|
1425
|
+
budgetObject[key] = value;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
const budget = Object.keys(budgetObject).length > 0 ? budgetObject : undefined;
|
|
1429
|
+
const contextBudgetAdapter = this.readOptionalStringVariable("cb_budget_adapter");
|
|
1430
|
+
const hasCallerSearchTerms = callerSearchTerms.length > 0;
|
|
1431
|
+
const shouldSynthesizeSeedTerms = focusPaths.length === 0 && !hasCallerSearchTerms;
|
|
1432
|
+
// Match mapPreflightContextInput's nonblank rule: a whitespace-only cb_task
|
|
1433
|
+
// must fall through to user_request, else seed synthesis yields no terms and
|
|
1434
|
+
// allowRootWideDiscovery runs a broad scan with no search terms.
|
|
1435
|
+
const cbTaskSeed = this.readOptionalStringVariable("cb_task");
|
|
1436
|
+
const seedText = resolveSeedSource(cbTaskSeed, this.readOptionalStringVariable("user_request"));
|
|
1437
|
+
const seedSourceError = contextSeedSourceValidationError(seedText);
|
|
1438
|
+
if (shouldSynthesizeSeedTerms && seedSourceError != null) {
|
|
1439
|
+
throw new McpError(ErrorCode.InvalidParams, seedSourceError);
|
|
1440
|
+
}
|
|
1441
|
+
const synthesizedSearchTerms = shouldSynthesizeSeedTerms
|
|
1442
|
+
? synthesizeSeedTerms(seedText)
|
|
1443
|
+
: [];
|
|
1444
|
+
const searchTerms = shouldSynthesizeSeedTerms ? synthesizedSearchTerms : callerSearchTerms;
|
|
1445
|
+
const input = {
|
|
1446
|
+
runId: this.runId,
|
|
1447
|
+
runDir: this.runDir,
|
|
1448
|
+
cb_task: this.readOptionalStringVariable("cb_task") ?? "",
|
|
1449
|
+
user_request: this.readOptionalStringVariable("user_request") ?? "",
|
|
1450
|
+
roots: this.readStringArrayVariable("cb_roots"),
|
|
1451
|
+
focus_paths: focusPaths,
|
|
1452
|
+
include_globs: this.readStringArrayVariable("cb_include_globs"),
|
|
1453
|
+
// A-7(a): the enforced plan exclusions are folded in HERE — before the
|
|
1454
|
+
// delegate packet is built — so the default-on curator+finalizer never
|
|
1455
|
+
// sees the plan as a candidate. Caller `cb_exclude_globs:[]` cannot drop
|
|
1456
|
+
// them; the fold is unconditional.
|
|
1457
|
+
exclude_globs: this.planReviewContextPolicy == null
|
|
1458
|
+
? this.readStringArrayVariable("cb_exclude_globs")
|
|
1459
|
+
: foldPlanReviewExcludeGlobs(this.planReviewContextPolicy, this.readStringArrayVariable("cb_exclude_globs")),
|
|
1460
|
+
search_terms: searchTerms,
|
|
1461
|
+
profile: this.readOptionalStringVariable("cb_profile"),
|
|
1462
|
+
allow_secrets: this.readOptionalBooleanVariable("cb_allow_secrets"),
|
|
1463
|
+
context_mode: policy.effectiveMode,
|
|
1464
|
+
...(shouldSynthesizeSeedTerms ? { allowRootWideDiscovery: true } : {}),
|
|
1465
|
+
...(this.definition.settings.include_selected_context_artifact === true
|
|
1466
|
+
? { includeSelectedContext: true }
|
|
1467
|
+
: {}),
|
|
1468
|
+
...(contextBudgetAdapter == null || contextBudgetAdapter.trim() === ""
|
|
1469
|
+
? {}
|
|
1470
|
+
: { context_budget_adapter: contextBudgetAdapter }),
|
|
1471
|
+
...(budget == null ? {} : { budget }),
|
|
1472
|
+
...(policy.effectiveDelegate == null
|
|
1473
|
+
? {}
|
|
1474
|
+
: {
|
|
1475
|
+
delegate: foldExternalGapPolicy(policy.effectiveDelegate, this.definition.settings.context_external_gap_policy),
|
|
1476
|
+
}),
|
|
1477
|
+
};
|
|
1478
|
+
// Light-caller provenance: capture how anchors were derived. `root_wide_used`
|
|
1479
|
+
// records the REALIZED `input.allowRootWideDiscovery` flag actually passed to
|
|
1480
|
+
// the runner, while `anchor_source` reflects whether seed synthesis ran. Today
|
|
1481
|
+
// both flow from the same `shouldSynthesizeSeedTerms` decision.
|
|
1482
|
+
const lightCaller = {
|
|
1483
|
+
anchor_source: shouldSynthesizeSeedTerms ? "synthesized" : "caller",
|
|
1484
|
+
root_wide_used: input.allowRootWideDiscovery === true,
|
|
1485
|
+
synthesized_search_terms: shouldSynthesizeSeedTerms ? synthesizedSearchTerms : [],
|
|
1486
|
+
};
|
|
1487
|
+
let result;
|
|
1488
|
+
try {
|
|
1489
|
+
result = await runner.runOrReuse(input, { signal });
|
|
1490
|
+
}
|
|
1491
|
+
catch (error) {
|
|
1492
|
+
// The injected runner lives in the root package, which clinkx-workflows
|
|
1493
|
+
// must not import. We therefore cannot catch the root CancellationError by
|
|
1494
|
+
// type; instead, normalize at the engine boundary using the signal: if the
|
|
1495
|
+
// signal aborted, surface it as a WorkflowCancellationError (preserving any
|
|
1496
|
+
// existing abort reason). A genuine non-abort failure rethrows unchanged.
|
|
1497
|
+
this.throwIfAborted(signal);
|
|
1498
|
+
throw error;
|
|
1499
|
+
}
|
|
1500
|
+
this.throwIfAborted(signal);
|
|
1501
|
+
const effectiveCoverageStatus = result.metrics.files_selected > 0
|
|
1502
|
+
? result.coverage?.status ?? "not_evaluated"
|
|
1503
|
+
: "not_evaluated";
|
|
1504
|
+
// External gaps under external_gap_policy:"degrade" are the only producer of
|
|
1505
|
+
// a partial delegate without fallback; the coverage report itself is left
|
|
1506
|
+
// untouched so persisted coverage stays the policy's own verdict.
|
|
1507
|
+
const externalGapsDegraded = isDelegateExternalGapDegrade(result.delegate);
|
|
1508
|
+
const contextQuality = externalGapsDegraded ? "degraded" : effectiveCoverageStatus;
|
|
1509
|
+
this.setEffectiveVariable("context_quality", contextQuality);
|
|
1510
|
+
const delegateInput = policy.effectiveDelegate?.enabled === true ? policy.effectiveDelegate : undefined;
|
|
1511
|
+
const fallbackUsed = result.delegate?.fallback_used === true;
|
|
1512
|
+
const fallbackAggregate = recordPreflightFallbackAggregate({
|
|
1513
|
+
delegateEnabled: result.delegate?.enabled === true,
|
|
1514
|
+
fallbackUsed,
|
|
1515
|
+
});
|
|
1516
|
+
logger.info({
|
|
1517
|
+
run_id: this.runId,
|
|
1518
|
+
workflow: this.definition.name,
|
|
1519
|
+
source_run_id: result.source_run_id,
|
|
1520
|
+
mode: policy.effectiveMode,
|
|
1521
|
+
delegate_enabled: delegateInput != null,
|
|
1522
|
+
fallback_used: fallbackUsed,
|
|
1523
|
+
curator_ms: result.observability?.curator_ms ?? null,
|
|
1524
|
+
finalizer_ms: result.observability?.finalizer_ms ?? null,
|
|
1525
|
+
files_seen: result.metrics.files_seen,
|
|
1526
|
+
bytes_read: result.metrics.bytes_read,
|
|
1527
|
+
scan_cap_hit: result.metrics.scan_cap_hit,
|
|
1528
|
+
candidate_pool_size: result.observability?.candidate_pool_size ?? 0,
|
|
1529
|
+
delegate_packet_size: result.observability?.delegate_packet_size ?? 0,
|
|
1530
|
+
files_selected: result.metrics.files_selected,
|
|
1531
|
+
cache: {
|
|
1532
|
+
hit: result.cache.hit,
|
|
1533
|
+
write: result.cache.write,
|
|
1534
|
+
key_prefix: result.cache.key_prefix,
|
|
1535
|
+
},
|
|
1536
|
+
curator: delegateInput == null
|
|
1537
|
+
? null
|
|
1538
|
+
: {
|
|
1539
|
+
adapter: delegateInput.curator.cli_name,
|
|
1540
|
+
role: delegateInput.curator.role,
|
|
1541
|
+
},
|
|
1542
|
+
finalizer: delegateInput == null
|
|
1543
|
+
? null
|
|
1544
|
+
: {
|
|
1545
|
+
adapter: delegateInput.finalizer.cli_name,
|
|
1546
|
+
role: delegateInput.finalizer.role,
|
|
1547
|
+
},
|
|
1548
|
+
failure_reason: receiptDelegateFailureReason(result),
|
|
1549
|
+
warning_code: firstReceiptWarningCode(result),
|
|
1550
|
+
budget_caps: result.observability?.budget_caps ?? null,
|
|
1551
|
+
profile: input.profile ?? null,
|
|
1552
|
+
context_budget_adapter: input.context_budget_adapter ?? null,
|
|
1553
|
+
root_source: input.roots.length > 0 ? "explicit" : "fallback",
|
|
1554
|
+
seed_term_count: searchTerms.length,
|
|
1555
|
+
fallback_aggregate: fallbackAggregate,
|
|
1556
|
+
}, "context-builder preflight summary");
|
|
1557
|
+
if (result.metrics.files_selected === 0) {
|
|
1558
|
+
if (this.definition.settings.fail_on_empty_context_selection === true) {
|
|
1559
|
+
throw new WorkflowError("Context-builder preflight selected no files");
|
|
1560
|
+
}
|
|
1561
|
+
logger.warn({
|
|
1562
|
+
run_id: this.runId,
|
|
1563
|
+
source_run_id: result.source_run_id,
|
|
1564
|
+
files_selected: 0,
|
|
1565
|
+
}, "context-builder preflight selected no files; proceeding without bundle");
|
|
1566
|
+
}
|
|
1567
|
+
if (externalGapsDegraded && effectiveCoverageStatus !== "degraded") {
|
|
1568
|
+
if (this.definition.settings.fail_on_degraded_context_coverage === true) {
|
|
1569
|
+
throw new WorkflowError("Context-builder preflight is degraded: the finalizer reported evidence gaps outside the delegate packet");
|
|
1570
|
+
}
|
|
1571
|
+
logger.warn({ run_id: this.runId, source_run_id: result.source_run_id }, "context-builder preflight reported external evidence gaps; proceeding degraded");
|
|
1572
|
+
}
|
|
1573
|
+
if (effectiveCoverageStatus === "degraded") {
|
|
1574
|
+
if (this.definition.settings.fail_on_degraded_context_coverage === true) {
|
|
1575
|
+
throw new WorkflowError("Context-builder preflight coverage is degraded");
|
|
1576
|
+
}
|
|
1577
|
+
logger.warn({
|
|
1578
|
+
run_id: this.runId,
|
|
1579
|
+
source_run_id: result.source_run_id,
|
|
1580
|
+
coverage_status: effectiveCoverageStatus,
|
|
1581
|
+
missing_required: result.coverage?.missing_required ?? [],
|
|
1582
|
+
}, "context-builder preflight coverage degraded; proceeding");
|
|
1583
|
+
}
|
|
1584
|
+
if (result.status === "partial") {
|
|
1585
|
+
const partial = classifyPreflightPartial(result.metrics);
|
|
1586
|
+
const payload = {
|
|
1587
|
+
run_id: this.runId,
|
|
1588
|
+
source_run_id: result.source_run_id,
|
|
1589
|
+
files_selected: result.metrics.files_selected,
|
|
1590
|
+
warning_count: result.warning_count,
|
|
1591
|
+
omission_count: result.omission_count,
|
|
1592
|
+
partial_reason: partial.partial_reason,
|
|
1593
|
+
};
|
|
1594
|
+
if (partial.severity === "degraded") {
|
|
1595
|
+
logger.warn(payload, "context-builder preflight returned partial context; proceeding");
|
|
1596
|
+
}
|
|
1597
|
+
else {
|
|
1598
|
+
logger.info(payload, "context-builder preflight returned partial context; proceeding");
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
// Only attach the generated bundle artifacts when the preflight actually
|
|
1602
|
+
// selected files. On a zero-selection run we proceed WITHOUT a bundle
|
|
1603
|
+
// (matching the warn log above): the runner always emits two copied paths
|
|
1604
|
+
// (bundle.md + manifest.json) even for an empty selection, so appending
|
|
1605
|
+
// them would hand downstream seats an empty bundle that looks intentional.
|
|
1606
|
+
if (result.metrics.files_selected > 0) {
|
|
1607
|
+
const existingBundleFiles = this.readStringArrayVariable("context_bundle_files");
|
|
1608
|
+
this.setEffectiveVariable("context_bundle_files", orderedUnique([...existingBundleFiles, ...result.copied_paths]));
|
|
1609
|
+
}
|
|
1610
|
+
// A-7(b): post-filter the FINALIZED selection. This is not redundant with
|
|
1611
|
+
// the pre-dispatch fold — caller `cb_include_globs`/focus anchors can
|
|
1612
|
+
// re-admit exactly what the fold excluded, and this is the point where the
|
|
1613
|
+
// exclusion has to survive them. Runs BEFORE commitPreflightContext so the
|
|
1614
|
+
// persisted inputs snapshot never records an excluded path.
|
|
1615
|
+
const removed = this.enforcePlanReviewContextExclusions();
|
|
1616
|
+
if (this.planReviewContextPolicy != null) {
|
|
1617
|
+
await this.commitPlanReviewContextProvenance?.({
|
|
1618
|
+
enforced: true,
|
|
1619
|
+
...(policy.effectiveMode != null ? { context_mode: policy.effectiveMode } : {}),
|
|
1620
|
+
copied_paths: [...this.readStringArrayVariable("context_bundle_files")],
|
|
1621
|
+
filtered_out_paths: removed,
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
await this.commitPreflightContext?.({
|
|
1625
|
+
variables: { ...this.effectiveVariables },
|
|
1626
|
+
workflow_context: this.buildWorkflowContext(result, policy.effectiveMode, lightCaller),
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Strip every force-excluded plan path from the effective context path
|
|
1631
|
+
* variables (A-7). Idempotent and safe to call on any path — no policy means
|
|
1632
|
+
* no-op, so non-plan-review runs are byte-unchanged.
|
|
1633
|
+
*
|
|
1634
|
+
* Returns the paths that were removed, for provenance.
|
|
1635
|
+
*/
|
|
1636
|
+
enforcePlanReviewContextExclusions() {
|
|
1637
|
+
const planPolicy = this.planReviewContextPolicy;
|
|
1638
|
+
if (planPolicy == null) {
|
|
1639
|
+
return [];
|
|
1640
|
+
}
|
|
1641
|
+
const removedAll = [];
|
|
1642
|
+
for (const name of ["context_files", "context_bundle_files", "cb_focus_paths"]) {
|
|
1643
|
+
const values = this.readStringArrayVariable(name);
|
|
1644
|
+
if (values.length === 0)
|
|
1645
|
+
continue;
|
|
1646
|
+
const { kept, removed } = filterExcludedPlanPaths(planPolicy, values, resolveWorkspaceRoot());
|
|
1647
|
+
if (removed.length === 0)
|
|
1648
|
+
continue;
|
|
1649
|
+
removedAll.push(...removed);
|
|
1650
|
+
this.setEffectiveVariable(name, kept);
|
|
1651
|
+
logger.warn({ run_id: this.runId, variable: name, removed }, "plan-review context policy stripped excluded plan paths");
|
|
1652
|
+
}
|
|
1653
|
+
return removedAll;
|
|
1654
|
+
}
|
|
1655
|
+
buildWorkflowContext(result, contextMode, lightCaller) {
|
|
1656
|
+
return mapPreflightResultToWorkflowContext(result, contextMode, lightCaller);
|
|
1657
|
+
}
|
|
1658
|
+
resolveContextPolicy() {
|
|
1659
|
+
if (this.initialContextPolicy !== undefined) {
|
|
1660
|
+
return this.initialContextPolicy;
|
|
1661
|
+
}
|
|
1662
|
+
try {
|
|
1663
|
+
return resolveWorkflowContextMode(this.effectiveVariables["cb_context_mode"], this.contextDefault, true, this.effectiveVariables["cb_delegate"], this.defaultCurator);
|
|
1664
|
+
}
|
|
1665
|
+
catch (error) {
|
|
1666
|
+
if (error instanceof WorkflowContextModeResolverError ||
|
|
1667
|
+
error instanceof WorkflowDelegateValidationError) {
|
|
1668
|
+
throw new WorkflowError(error.message);
|
|
1669
|
+
}
|
|
1670
|
+
throw error;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
readOptionalStringVariable(name) {
|
|
1674
|
+
const value = this.effectiveVariables[name];
|
|
1675
|
+
if (value === undefined) {
|
|
1676
|
+
return undefined;
|
|
1677
|
+
}
|
|
1678
|
+
if (typeof value !== "string") {
|
|
1679
|
+
throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a string`);
|
|
1680
|
+
}
|
|
1681
|
+
return value;
|
|
1682
|
+
}
|
|
1683
|
+
readOptionalNumberVariable(name) {
|
|
1684
|
+
const value = this.effectiveVariables[name];
|
|
1685
|
+
if (value === undefined) {
|
|
1686
|
+
return undefined;
|
|
1687
|
+
}
|
|
1688
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1689
|
+
throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a finite number`);
|
|
1690
|
+
}
|
|
1691
|
+
return value;
|
|
1692
|
+
}
|
|
1693
|
+
readOptionalBooleanVariable(name) {
|
|
1694
|
+
const value = this.effectiveVariables[name];
|
|
1695
|
+
if (value === undefined) {
|
|
1696
|
+
return undefined;
|
|
1697
|
+
}
|
|
1698
|
+
if (typeof value === "boolean") {
|
|
1699
|
+
return value;
|
|
1700
|
+
}
|
|
1701
|
+
if (value === "true" || value === "1") {
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1704
|
+
if (value === "false" || value === "0") {
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1707
|
+
throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a boolean`);
|
|
1708
|
+
}
|
|
1709
|
+
readStringArrayVariable(name) {
|
|
1710
|
+
const value = this.effectiveVariables[name];
|
|
1711
|
+
if (value === undefined) {
|
|
1712
|
+
return [];
|
|
1713
|
+
}
|
|
1714
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
1715
|
+
throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a string[]`);
|
|
1716
|
+
}
|
|
1717
|
+
return value;
|
|
1718
|
+
}
|
|
1719
|
+
createDeadlineController(delayMs) {
|
|
654
1720
|
const controller = new AbortController();
|
|
655
1721
|
const timeout = setTimeout(() => {
|
|
656
1722
|
controller.abort(new Error(`Workflow timeout exceeded after ${String(this.definition.settings.timeout_seconds)} seconds`));
|
|
657
|
-
},
|
|
1723
|
+
}, Math.max(0, delayMs));
|
|
658
1724
|
if (this.signal != null) {
|
|
659
|
-
this.signal.
|
|
1725
|
+
if (this.signal.aborted) {
|
|
1726
|
+
controller.abort(new WorkflowCancellationError());
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
this.signal.addEventListener("abort", () => controller.abort(new WorkflowCancellationError()), { once: true });
|
|
1730
|
+
}
|
|
660
1731
|
}
|
|
661
1732
|
return {
|
|
662
1733
|
signal: controller.signal,
|
|
663
1734
|
dispose: () => clearTimeout(timeout),
|
|
664
1735
|
};
|
|
665
1736
|
}
|
|
1737
|
+
/** Remaining workflow budget in ms; undefined when no deadline is installed. */
|
|
1738
|
+
workflowRemainingMs() {
|
|
1739
|
+
return this.workflowDeadlineAtMonoMs == null
|
|
1740
|
+
? undefined
|
|
1741
|
+
: Math.floor(this.workflowDeadlineAtMonoMs - monotonicNowMs());
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* True only while a workflow deadline is set AND already past. Pure: reads
|
|
1745
|
+
* the deadline the running `execute()` installed and the clock, nothing else.
|
|
1746
|
+
* `execute()` clears `workflowDeadlineAtMonoMs` in its `finally`, so this can
|
|
1747
|
+
* never report a stale deadline from a prior run. The reading is monotonic:
|
|
1748
|
+
* a wall-clock step (NTP correction, suspend/resume, manual change) can
|
|
1749
|
+
* neither manufacture nor erase an exhausted budget.
|
|
1750
|
+
*/
|
|
1751
|
+
isWorkflowDeadlineExhausted() {
|
|
1752
|
+
const remainingMs = this.workflowRemainingMs();
|
|
1753
|
+
return remainingMs != null && remainingMs <= 0;
|
|
1754
|
+
}
|
|
666
1755
|
throwIfAborted(signal) {
|
|
667
1756
|
if (!signal.aborted) {
|
|
668
1757
|
return;
|
|
@@ -672,7 +1761,11 @@ export class WorkflowEngine {
|
|
|
672
1761
|
throw reason;
|
|
673
1762
|
}
|
|
674
1763
|
if (reason instanceof Error && reason.message.includes("Workflow timeout exceeded")) {
|
|
675
|
-
|
|
1764
|
+
// Same environmental cause as WorkflowDispatchDeadlineError, so it
|
|
1765
|
+
// carries the same class. Kept as a bare WorkflowError: the two are now
|
|
1766
|
+
// handled identically by materializeUnexpectedFailure (both materialize
|
|
1767
|
+
// rather than rethrow), so the distinction buys nothing here.
|
|
1768
|
+
throw new WorkflowError(reason.message, "transient");
|
|
676
1769
|
}
|
|
677
1770
|
throw new WorkflowCancellationError();
|
|
678
1771
|
}
|
|
@@ -698,29 +1791,69 @@ export class WorkflowEngine {
|
|
|
698
1791
|
`Known cli_name values: ${knownCliNames.join(", ")}`);
|
|
699
1792
|
}
|
|
700
1793
|
defaultAllowedRoots() {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1794
|
+
return defaultWorkflowAllowedRoots(this.runDir);
|
|
1795
|
+
}
|
|
1796
|
+
recordPlanReviewDeliveredAliases(callKey, call, absoluteFilePaths) {
|
|
1797
|
+
if (call.plan_review_anonymous_aliases == null)
|
|
1798
|
+
return;
|
|
1799
|
+
const configured = new Set(Object.values(call.plan_review_anonymous_aliases));
|
|
1800
|
+
const delivered = new Set();
|
|
1801
|
+
const anonymousRoot = resolve(this.artifactStore.artifactsDir, "anonymous");
|
|
1802
|
+
if (Array.isArray(absoluteFilePaths)) {
|
|
1803
|
+
for (const path of absoluteFilePaths) {
|
|
1804
|
+
if (typeof path !== "string")
|
|
1805
|
+
continue;
|
|
1806
|
+
const absolute = resolve(path);
|
|
1807
|
+
if (!absolute.startsWith(anonymousRoot + sep))
|
|
1808
|
+
continue;
|
|
1809
|
+
const alias = basename(path, ".json");
|
|
1810
|
+
if (configured.has(alias))
|
|
1811
|
+
delivered.add(alias);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
this.planReviewDeliveredAliases.set(callKey, delivered);
|
|
711
1815
|
}
|
|
712
1816
|
buildCallInput(stage, call, callKey, callIndex) {
|
|
713
1817
|
const resumedInput = this.resumePreparedInputs.get(callKey);
|
|
714
1818
|
if (resumedInput != null) {
|
|
715
|
-
|
|
1819
|
+
const input = structuredClone(resumedInput);
|
|
1820
|
+
this.recordPlanReviewDeliveredAliases(callKey, call, input["absolute_file_paths"]);
|
|
1821
|
+
const binding = this.planReviewCallBindings.get(callKey);
|
|
1822
|
+
if (binding !== undefined) {
|
|
1823
|
+
input["cli_name"] = binding.cli_name;
|
|
1824
|
+
}
|
|
1825
|
+
return input;
|
|
716
1826
|
}
|
|
717
|
-
const
|
|
1827
|
+
const renderedPrompt = renderCallPrompt(call.prompt, { ...this.definition.variables, ...ENGINE_DERIVED_VARIABLE_DECLS }, this.effectiveVariables, {
|
|
718
1828
|
workflow: { run_id: this.runId },
|
|
719
1829
|
stage: { id: stage.id },
|
|
720
1830
|
call: { id: call.id },
|
|
721
1831
|
});
|
|
1832
|
+
/**
|
|
1833
|
+
* A-6/D9: append the verified plan snapshot AFTER rendering, never before.
|
|
1834
|
+
*
|
|
1835
|
+
* The snapshot must not be an input to `renderCallPrompt`. A plan is
|
|
1836
|
+
* untrusted text that may legitimately contain `{{...}}` (plans in this
|
|
1837
|
+
* repo do), and `PLACEHOLDER_RE` scans the WHOLE string it is given: a
|
|
1838
|
+
* `{{call.id}}` inside the plan would be silently substituted — handing the
|
|
1839
|
+
* actor bytes that are NOT the verified snapshot, which is a D9 violation
|
|
1840
|
+
* requiring no tampering at all — and an undeclared `{{user_request}}`
|
|
1841
|
+
* would throw and brick an honest run. Appending after rendering is what
|
|
1842
|
+
* makes the snapshot reach the actor VERBATIM.
|
|
1843
|
+
*
|
|
1844
|
+
* This lands before `estimatePromptBudget` below on purpose: the snapshot
|
|
1845
|
+
* is real dispatched bytes (up to 512 KiB) and must be billed as such, or
|
|
1846
|
+
* the authoritative gate would under-count by the largest term in it.
|
|
1847
|
+
*/
|
|
1848
|
+
const prompt = call.plan_review_snapshot_text === undefined
|
|
1849
|
+
? renderedPrompt
|
|
1850
|
+
: `${renderedPrompt}${renderPlanSnapshotBlock(call.plan_review_snapshot_text)}`;
|
|
722
1851
|
const contextArtifacts = call.anonymize_context === true
|
|
723
|
-
? this.artifactStore.buildAnonymizedContext(this.definition, call.context_from, callIndex
|
|
1852
|
+
? this.artifactStore.buildAnonymizedContext(this.definition, call.context_from, callIndex, call.plan_review_semantic_context === true
|
|
1853
|
+
? projectPlanReviewSemanticBody
|
|
1854
|
+
: undefined, call.plan_review_semantic_context === true
|
|
1855
|
+
? `${stage.id}-${call.id}`
|
|
1856
|
+
: undefined, call.plan_review_anonymous_aliases)
|
|
724
1857
|
: this.artifactStore.buildContext(this.definition, call.context_from);
|
|
725
1858
|
const staticFiles = call.absolute_file_paths ?? [];
|
|
726
1859
|
const dynamicFiles = this.resolveDynamicPaths(call.absolute_file_paths_from, `${stage.id}.${call.id}.absolute_file_paths_from`);
|
|
@@ -731,7 +1864,11 @@ export class WorkflowEngine {
|
|
|
731
1864
|
...staticFiles,
|
|
732
1865
|
...dynamicFiles,
|
|
733
1866
|
];
|
|
1867
|
+
this.recordPlanReviewDeliveredAliases(callKey, call, contextArtifacts.absoluteFilePaths);
|
|
734
1868
|
const images = [...staticImages, ...dynamicImages];
|
|
1869
|
+
const reserveBytes = isCouncilExecutionWorkflow(this.definition.name)
|
|
1870
|
+
? (this.promptReserveBytes ?? DEFAULT_RESERVE_BYTES) + COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES
|
|
1871
|
+
: this.promptReserveBytes;
|
|
735
1872
|
const estimate = estimatePromptBudget({
|
|
736
1873
|
promptText: prompt,
|
|
737
1874
|
contextText: contextArtifacts.context,
|
|
@@ -742,13 +1879,15 @@ export class WorkflowEngine {
|
|
|
742
1879
|
],
|
|
743
1880
|
imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
|
|
744
1881
|
maxBytes: this.maxPromptBytes,
|
|
745
|
-
reserveBytes
|
|
1882
|
+
reserveBytes,
|
|
746
1883
|
});
|
|
747
1884
|
enforcePromptBudget(estimate);
|
|
748
1885
|
const input = {
|
|
749
1886
|
cli_name: call.cli_name,
|
|
750
1887
|
role: call.role,
|
|
751
1888
|
prompt,
|
|
1889
|
+
...(call.model !== undefined ? { model: call.model } : {}),
|
|
1890
|
+
...(call.effort !== undefined ? { effort: call.effort } : {}),
|
|
752
1891
|
};
|
|
753
1892
|
if (absoluteFilePaths.length > 0) {
|
|
754
1893
|
input["absolute_file_paths"] = absoluteFilePaths;
|
|
@@ -773,26 +1912,59 @@ export class WorkflowEngine {
|
|
|
773
1912
|
}
|
|
774
1913
|
return input;
|
|
775
1914
|
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Derive a per-call invocation audit from the materialized input.
|
|
1917
|
+
* Uses the dispatched cli_name/role (plan-review bindings may rewrite
|
|
1918
|
+
* cli_name on resume) and never mutates `input`.
|
|
1919
|
+
*/
|
|
1920
|
+
maybeInvocationAudit(input) {
|
|
1921
|
+
const derive = this.deriveInvocationAudit;
|
|
1922
|
+
if (derive == null) {
|
|
1923
|
+
return undefined;
|
|
1924
|
+
}
|
|
1925
|
+
const cliName = input["cli_name"];
|
|
1926
|
+
const role = input["role"];
|
|
1927
|
+
if (typeof cliName !== "string" || typeof role !== "string") {
|
|
1928
|
+
return undefined;
|
|
1929
|
+
}
|
|
1930
|
+
const model = input["model"];
|
|
1931
|
+
const effort = input["effort"];
|
|
1932
|
+
return derive(cliName, role, {
|
|
1933
|
+
...(typeof model === "string" ? { model } : {}),
|
|
1934
|
+
...(typeof effort === "string" ? { effort } : {}),
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
776
1937
|
async materializeUnexpectedFailure(stage, call, error) {
|
|
1938
|
+
// Only cancellation escapes a parallel stage. Every other failure —
|
|
1939
|
+
// environmental (an exhausted dispatch deadline) as much as a child
|
|
1940
|
+
// failure — materializes into an ExecutedCall{ok:false} so already-settled
|
|
1941
|
+
// sibling results survive Promise.allSettled instead of being discarded by
|
|
1942
|
+
// a rethrow.
|
|
777
1943
|
if (error instanceof WorkflowCancellationError) {
|
|
778
1944
|
throw error;
|
|
779
1945
|
}
|
|
780
1946
|
const message = error instanceof Error ? error.message : String(error);
|
|
781
1947
|
this.artifactStore.noteFailedCall(stage.id, call.id, message);
|
|
782
1948
|
logger.error({ run_id: this.runId, stage_id: stage.id, call_id: call.id, err: message }, "parallel stage call rejected unexpectedly");
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1949
|
+
// Only the genuinely un-terminalized failures need a state emit here.
|
|
1950
|
+
// `emitTerminalCallAbort` already wrote the real attempt/retryCount and
|
|
1951
|
+
// startedAt/durationMs, and onCallStateChange assigns those fields
|
|
1952
|
+
// unconditionally, so re-emitting would overwrite them with placeholders.
|
|
1953
|
+
if (!this.terminalizedCallKeys.has(`${stage.id}.${call.id}`)) {
|
|
1954
|
+
await this.emitCallStateChange({
|
|
1955
|
+
runId: this.runId,
|
|
1956
|
+
workflowName: this.definition.name,
|
|
1957
|
+
stageId: stage.id,
|
|
1958
|
+
callId: call.id,
|
|
1959
|
+
state: "failed",
|
|
1960
|
+
attempt: 1,
|
|
1961
|
+
retryCount: 0,
|
|
1962
|
+
completedAt: new Date().toISOString(),
|
|
1963
|
+
error: message,
|
|
1964
|
+
failureClass: classifyFailureClass(error),
|
|
1965
|
+
childDebugMetadata: error instanceof CallExecutionError ? error.childDebugMetadata : undefined,
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
796
1968
|
await this.emitProgress({
|
|
797
1969
|
runId: this.runId,
|
|
798
1970
|
workflowName: this.definition.name,
|
|
@@ -834,7 +2006,9 @@ export class WorkflowEngine {
|
|
|
834
2006
|
}
|
|
835
2007
|
}
|
|
836
2008
|
for (const failedCall of this.resumeFailedCalls.values()) {
|
|
837
|
-
|
|
2009
|
+
if (failedCall.terminal) {
|
|
2010
|
+
this.artifactStore.seedFailedCall(failedCall.stageId, failedCall.callId, failedCall.error);
|
|
2011
|
+
}
|
|
838
2012
|
}
|
|
839
2013
|
}
|
|
840
2014
|
/**
|
|
@@ -994,8 +2168,84 @@ function classifyFailureClass(error) {
|
|
|
994
2168
|
if (error instanceof WorkflowCancellationError) {
|
|
995
2169
|
return "permanent";
|
|
996
2170
|
}
|
|
2171
|
+
// Cross-package classification marker: errors thrown by the injected root
|
|
2172
|
+
// package (e.g. a context-builder preflight delegate timeout) cannot be
|
|
2173
|
+
// narrowed by instanceof here, so honor a well-formed `failureClass`
|
|
2174
|
+
// property when present. Anything else keeps the permanent default.
|
|
2175
|
+
const marker = crossPackageFailureClass(error);
|
|
2176
|
+
if (marker != null) {
|
|
2177
|
+
return marker;
|
|
2178
|
+
}
|
|
997
2179
|
return "permanent";
|
|
998
2180
|
}
|
|
2181
|
+
function crossPackageFailureClass(error) {
|
|
2182
|
+
if (error == null || typeof error !== "object") {
|
|
2183
|
+
return undefined;
|
|
2184
|
+
}
|
|
2185
|
+
const value = error.failureClass;
|
|
2186
|
+
return value === "transient" || value === "permanent" ? value : undefined;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Bounded corrective suffix appended to the NEXT attempt's prompt after a
|
|
2190
|
+
* JSON/schema-shaped failure (SchemaValidationError, or OutputSizeError as
|
|
2191
|
+
* the second correctable class) that is already being retried. Total suffix
|
|
2192
|
+
* length is capped at CORRECTIVE_SUFFIX_MAX_CHARS; the failure detail is
|
|
2193
|
+
* truncated to fit. Never persisted — resume replays the original input.
|
|
2194
|
+
*/
|
|
2195
|
+
const CORRECTIVE_SUFFIX_MAX_CHARS = 1_500;
|
|
2196
|
+
const CORRECTIVE_SUFFIX_HEADER = "\n\n---\nCORRECTION: Your previous response was rejected because it was not schema-valid. Failure: ";
|
|
2197
|
+
const CORRECTIVE_SIZE_SUFFIX_HEADER = "\n\n---\nCORRECTION: Your previous response was rejected because the pretty-printed JSON exceeded the validator byte cap. Shrink the deliverable first. Failure: ";
|
|
2198
|
+
const CORRECTIVE_SUFFIX_FOOTER = "\nReturn ONLY a single JSON object conforming to the declared contract — no prose, no markdown, " +
|
|
2199
|
+
"no code fences, and nothing before or after the JSON object.";
|
|
2200
|
+
/**
|
|
2201
|
+
* Detail text for a JSON/schema-shaped failure, or undefined when the failure
|
|
2202
|
+
* is not one a corrective re-prompt could fix (transport death, timeout, …).
|
|
2203
|
+
*/
|
|
2204
|
+
function correctiveFailureDetail(error) {
|
|
2205
|
+
if (error instanceof TypedResultFidelityError) {
|
|
2206
|
+
return error.message;
|
|
2207
|
+
}
|
|
2208
|
+
if (error instanceof SchemaValidationError) {
|
|
2209
|
+
return error.message;
|
|
2210
|
+
}
|
|
2211
|
+
if (error instanceof OutputSizeError) {
|
|
2212
|
+
return error.message;
|
|
2213
|
+
}
|
|
2214
|
+
if (error instanceof CallExecutionError) {
|
|
2215
|
+
const debug = error.childDebugMetadata;
|
|
2216
|
+
if (debug?.rejection_reason === "invalid_json_output" ||
|
|
2217
|
+
debug?.semantic_failure_reason === "invalid_json_output" ||
|
|
2218
|
+
debug?.secondary_failure_reason === "invalid_json_output") {
|
|
2219
|
+
return "output did not contain valid JSON";
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
return undefined;
|
|
2223
|
+
}
|
|
2224
|
+
/** Mirrors the expect_json derivation in buildCallInput. */
|
|
2225
|
+
function declaresJsonContract(call) {
|
|
2226
|
+
return call.response_contract === "json" || call.validator_schema_id != null;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* D1b: fold the workflow's `context_external_gap_policy` into an ENABLED
|
|
2230
|
+
* preflight delegate that does not choose one itself. Runtime-only: the
|
|
2231
|
+
* caller's `cb_delegate` variable (and so the inputs snapshot) is untouched,
|
|
2232
|
+
* and an explicit delegate `external_gap_policy` always wins.
|
|
2233
|
+
*/
|
|
2234
|
+
function foldExternalGapPolicy(delegate, policy) {
|
|
2235
|
+
if (policy === undefined || delegate.enabled !== true || delegate.external_gap_policy !== undefined) {
|
|
2236
|
+
return delegate;
|
|
2237
|
+
}
|
|
2238
|
+
return { ...delegate, external_gap_policy: policy };
|
|
2239
|
+
}
|
|
2240
|
+
function buildCorrectiveSuffixText(detail, header = CORRECTIVE_SUFFIX_HEADER) {
|
|
2241
|
+
const budget = CORRECTIVE_SUFFIX_MAX_CHARS -
|
|
2242
|
+
header.length -
|
|
2243
|
+
CORRECTIVE_SUFFIX_FOOTER.length;
|
|
2244
|
+
const bounded = detail.length > budget
|
|
2245
|
+
? `${detail.slice(0, Math.max(0, budget - 1))}…`
|
|
2246
|
+
: detail;
|
|
2247
|
+
return `${header}${bounded}${CORRECTIVE_SUFFIX_FOOTER}`;
|
|
2248
|
+
}
|
|
999
2249
|
/**
|
|
1000
2250
|
* Determine whether an error is retryable based on the retry mode (5-1, 5-2).
|
|
1001
2251
|
*
|