clinkx 0.2.3 → 1.0.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 +33 -0
- package/LICENSE +10 -0
- package/README.md +113 -18
- 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/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 +2 -1
- package/clinkx-workflows/dist/clink-client.js +17 -4
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +145 -16
- package/clinkx-workflows/dist/config.js +384 -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 +184 -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-expander.d.ts +1 -1
- package/clinkx-workflows/dist/council-expander.js +2 -1
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +165 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js +105 -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 +86 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js +480 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +36 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +1047 -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-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 +222 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +1178 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +8936 -0
- package/clinkx-workflows/dist/dashboard/contracts.js +1157 -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 +28 -0
- package/clinkx-workflows/dist/dashboard/index.js +16 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +101 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js +748 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +303 -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 +581 -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 +28 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +110 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-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 +126 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +75 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +1110 -0
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js +431 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2611 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +480 -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 +522 -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 +6 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +367 -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-DOkMXTfB.js +25 -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-ChJsn0KD.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-validate.d.ts +78 -0
- package/clinkx-workflows/dist/delegate-validate.js +97 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
- package/clinkx-workflows/dist/engine.d.ts +141 -0
- package/clinkx-workflows/dist/engine.js +882 -28
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +2 -0
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +1 -1
- package/clinkx-workflows/dist/fingerprint.js +1 -1
- package/clinkx-workflows/dist/index.d.ts +19 -0
- package/clinkx-workflows/dist/index.js +15 -0
- 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/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/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 +76 -21
- package/clinkx-workflows/dist/schema.js +10 -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 +82 -52
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +18 -20
- package/clinkx-workflows/dist/shutdown.js +43 -61
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +38 -0
- package/clinkx-workflows/dist/state.d.ts +170 -3
- package/clinkx-workflows/dist/state.js +1275 -28
- package/clinkx-workflows/dist/state.js.map +1 -1
- 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 +14 -2
- package/clinkx-workflows/dist/validators/council.d.ts +318 -85
- 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 +48 -3
- package/clinkx-workflows/dist/workflow-tools.js +1402 -59
- 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-specialist.txt +2 -1
- package/clinkx-workflows/templates/council/code-review-chairman.txt +13 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +4 -1
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +19 -3
- package/clinkx-workflows/templates/council/debug-debugger.txt +2 -1
- package/clinkx-workflows/templates/council/discover-analyst.txt +2 -1
- package/clinkx-workflows/templates/council/member.txt +2 -1
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +68 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +51 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +60 -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 +97 -8
- package/clinkx-workflows/workflows/council-code-review.yaml +103 -8
- package/clinkx-workflows/workflows/council-debug.yaml +97 -8
- package/clinkx-workflows/workflows/council-default.yaml +97 -8
- package/clinkx-workflows/workflows/council-discover.yaml +97 -8
- package/clinkx-workflows/workflows/council-plan-review.yaml +250 -0
- package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
- package/conf/adapters/agy.json +49 -0
- package/conf/adapters/claude.json +5 -0
- package/conf/adapters/codex-app-server.json +44 -0
- package/conf/adapters/codex.json +7 -2
- package/conf/adapters/glm.json +5 -0
- package/conf/adapters/grok.json +60 -0
- package/conf/adapters/hapi/agy.json +52 -0
- package/conf/adapters/hapi/claude.json +5 -0
- package/conf/adapters/hapi/codex.json +7 -2
- package/conf/adapters/hapi/glm.json +5 -0
- package/conf/prompts/json-codereviewer.txt +1 -0
- package/conf/prompts/json.txt +1 -0
- package/dist/activity-journal.d.ts +42 -0
- package/dist/activity-journal.js +381 -0
- package/dist/activity-journal.js.map +1 -0
- package/dist/allowed-roots.d.ts +53 -0
- package/dist/allowed-roots.js +216 -0
- package/dist/allowed-roots.js.map +1 -0
- 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 +1270 -0
- package/dist/codex/agent-tools.js.map +1 -0
- package/dist/codex/app-server-client.d.ts +76 -0
- package/dist/codex/app-server-client.js +582 -0
- package/dist/codex/app-server-client.js.map +1 -0
- package/dist/codex/app-server-registry.d.ts +389 -0
- package/dist/codex/app-server-registry.js +1407 -0
- package/dist/codex/app-server-registry.js.map +1 -0
- package/dist/codex/client-types.d.ts +109 -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 +185 -19
- package/dist/config.js +82 -16
- package/dist/config.js.map +1 -1
- package/dist/context-builder/anchors.d.ts +12 -0
- package/dist/context-builder/anchors.js +22 -0
- package/dist/context-builder/anchors.js.map +1 -0
- package/dist/context-builder/bundle.d.ts +109 -0
- package/dist/context-builder/bundle.js +1498 -0
- package/dist/context-builder/bundle.js.map +1 -0
- package/dist/context-builder/codemap.d.ts +22 -0
- package/dist/context-builder/codemap.js +675 -0
- package/dist/context-builder/codemap.js.map +1 -0
- package/dist/context-builder/config.d.ts +61 -0
- package/dist/context-builder/config.js +216 -0
- package/dist/context-builder/config.js.map +1 -0
- 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-packet.d.ts +219 -0
- package/dist/context-builder/delegate-packet.js +1190 -0
- package/dist/context-builder/delegate-packet.js.map +1 -0
- package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
- package/dist/context-builder/delegate-reconstruction.js +220 -0
- package/dist/context-builder/delegate-reconstruction.js.map +1 -0
- package/dist/context-builder/delegate-validation.d.ts +27 -0
- package/dist/context-builder/delegate-validation.js +479 -0
- package/dist/context-builder/delegate-validation.js.map +1 -0
- package/dist/context-builder/delegate.d.ts +26 -0
- package/dist/context-builder/delegate.js +134 -0
- package/dist/context-builder/delegate.js.map +1 -0
- package/dist/context-builder/diagnostics.d.ts +36 -0
- package/dist/context-builder/diagnostics.js +107 -0
- package/dist/context-builder/diagnostics.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +146 -0
- package/dist/context-builder/discovery.js +2129 -0
- package/dist/context-builder/discovery.js.map +1 -0
- package/dist/context-builder/globs.d.ts +14 -0
- package/dist/context-builder/globs.js +175 -0
- package/dist/context-builder/globs.js.map +1 -0
- package/dist/context-builder/handler.d.ts +41 -0
- package/dist/context-builder/handler.js +2590 -0
- package/dist/context-builder/handler.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/path-signals.d.ts +7 -0
- package/dist/context-builder/path-signals.js +70 -0
- package/dist/context-builder/path-signals.js.map +1 -0
- package/dist/context-builder/paths.d.ts +30 -0
- package/dist/context-builder/paths.js +153 -0
- package/dist/context-builder/paths.js.map +1 -0
- package/dist/context-builder/progress.d.ts +17 -0
- package/dist/context-builder/progress.js +93 -0
- package/dist/context-builder/progress.js.map +1 -0
- package/dist/context-builder/receipt.d.ts +91 -0
- package/dist/context-builder/receipt.js +180 -0
- package/dist/context-builder/receipt.js.map +1 -0
- 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 +84 -0
- package/dist/context-builder/review-coverage.js +437 -0
- package/dist/context-builder/review-coverage.js.map +1 -0
- package/dist/context-builder/run-id.d.ts +7 -0
- package/dist/context-builder/run-id.js +31 -0
- package/dist/context-builder/run-id.js.map +1 -0
- package/dist/context-builder/schema.d.ts +669 -0
- package/dist/context-builder/schema.js +399 -0
- package/dist/context-builder/schema.js.map +1 -0
- package/dist/context-builder/secrets.d.ts +41 -0
- package/dist/context-builder/secrets.js +1651 -0
- package/dist/context-builder/secrets.js.map +1 -0
- package/dist/context-builder/selection.d.ts +56 -0
- package/dist/context-builder/selection.js +1111 -0
- package/dist/context-builder/selection.js.map +1 -0
- 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 +66 -0
- package/dist/context-builder/status.js +160 -0
- package/dist/context-builder/status.js.map +1 -0
- package/dist/context-builder/storage.d.ts +118 -0
- package/dist/context-builder/storage.js +1003 -0
- package/dist/context-builder/storage.js.map +1 -0
- package/dist/context-builder/token-estimate.d.ts +19 -0
- package/dist/context-builder/token-estimate.js +90 -0
- package/dist/context-builder/token-estimate.js.map +1 -0
- package/dist/context-builder/versions.d.ts +29 -0
- package/dist/context-builder/versions.js +30 -0
- package/dist/context-builder/versions.js.map +1 -0
- package/dist/context-builder/workflow-runner.d.ts +69 -0
- package/dist/context-builder/workflow-runner.js +1051 -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 +46 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -0
- package/dist/dashboard-cli.d.ts +79 -0
- package/dist/dashboard-cli.js +585 -0
- package/dist/dashboard-cli.js.map +1 -0
- package/dist/dashboard-companion/client.d.ts +36 -0
- package/dist/dashboard-companion/client.js +210 -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 +455 -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 +11 -0
- package/dist/dashboard-gateway/index.js +755 -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 +10 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -3
- 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/package-info.d.ts +2 -0
- package/dist/package-info.js +18 -0
- package/dist/package-info.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 +11 -3
- package/dist/parsers/extract.js.map +1 -1
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +31 -43
- package/dist/paths.js.map +1 -1
- package/dist/pipeline.d.ts +51 -3
- package/dist/pipeline.js +264 -47
- package/dist/pipeline.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 +13 -0
- package/dist/runner.js +184 -15
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +4 -4
- package/dist/safety.js +4 -4
- package/dist/schema.d.ts +7 -7
- package/dist/server.d.ts +2 -0
- package/dist/server.js +396 -24
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +18 -0
- package/dist/session-log-stream.js +319 -0
- package/dist/session-log-stream.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/artifacts.d.ts +0 -9
- package/dist/artifacts.js +0 -24
- package/dist/artifacts.js.map +0 -1
- 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
|
@@ -5,23 +5,27 @@
|
|
|
5
5
|
* - Standalone STDIO server (ClinkSession child process backend)
|
|
6
6
|
* - In-process integration (LocalClinkBackend via root ClinkX)
|
|
7
7
|
*
|
|
8
|
-
* The `registerWorkflowTools()` function registers
|
|
8
|
+
* The `registerWorkflowTools()` function registers five tools on a Server:
|
|
9
9
|
* - run_workflow: start a new workflow execution
|
|
10
10
|
* - resume_workflow: resume a failed/cancelled run
|
|
11
11
|
* - list_workflows: list available workflow definitions
|
|
12
|
+
* - workflow_run_status: read workflow run progress/result
|
|
13
|
+
* - cancel_workflow: cancel a live or orphaned workflow run
|
|
12
14
|
*
|
|
13
15
|
* The caller provides a `createBackend` factory that determines how clink
|
|
14
16
|
* calls are dispatched — either via child process or in-process.
|
|
15
17
|
*/
|
|
16
18
|
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { basename, dirname, join } from "node:path";
|
|
17
21
|
import { loadWorkflow, listWorkflowCatalog } from "./loader.js";
|
|
18
22
|
import { WorkflowEngine } from "./engine.js";
|
|
19
|
-
import { WorkflowStateStore } from "./state.js";
|
|
23
|
+
import { readRunStateForStatus, reapRunCancelled, WorkflowStateStore } from "./state.js";
|
|
24
|
+
import { parseContextBuilderLifecycleStateV1, } from "./context-builder-lifecycle-contract.js";
|
|
20
25
|
import { validateRunId } from "./run-id.js";
|
|
21
|
-
import { getTemplatesPath } from "./config.js";
|
|
26
|
+
import { getMaxPromptBytes, getRunStateDir, getTemplatesPath, getWorkflowsContextDefault, getWorkflowsContextDefaultCurator, getWorkflowsMaxConcurrentAsync, resolveWorkspaceRoot, } from "./config.js";
|
|
22
27
|
import { WorkflowCancellationError, } from "./errors.js";
|
|
23
28
|
import { shutdownCoordinator } from "./shutdown.js";
|
|
24
|
-
import { basename } from "node:path";
|
|
25
29
|
import { logger } from "./logger.js";
|
|
26
30
|
import { createValidatorRegistry } from "./validators/index.js";
|
|
27
31
|
import { deriveDecisiveFailure } from "./run-summary-derived.js";
|
|
@@ -29,20 +33,93 @@ import { buildRunSummaryOrUndefined, buildFailureRunSummary } from "./run-summar
|
|
|
29
33
|
import { extractCard } from "./run-summary/cards/index.js";
|
|
30
34
|
import { renderWorkflowReceipt } from "./workflow-receipt.js";
|
|
31
35
|
import { supportsRuntimeMembers, COUNCIL_RUNTIME_MEMBER_WORKFLOWS, expandCouncilComposition } from "./council-expander.js";
|
|
36
|
+
import { resolveWorkflowContextMode, WorkflowContextModeResolverError, } from "./context-mode-resolver.js";
|
|
37
|
+
import { WorkflowDelegateValidationError } from "./delegate-validate.js";
|
|
38
|
+
import { contextSearchTermsValidationError, contextSeedSourceValidationError, resolveSeedSource, } from "./seed-terms.js";
|
|
39
|
+
import { LEGACY_PLAN_REVIEW_RECEIPTS_WORKFLOW_NAME, PLAN_REVIEW_RECEIPTS_WORKFLOW, admitCouncilPlanReview, assertPlanReviewPromptBudget, isPlanReviewRunState, } from "./plan-review/admission.js";
|
|
40
|
+
import { PLAN_REVIEW_V1_CLI_NAMES } from "./plan-review/contract.js";
|
|
41
|
+
import { buildCouncilPlanReviewDiscovery } from "./plan-review/discovery.js";
|
|
42
|
+
import { expandCouncilPlanReview } from "./plan-review/expander.js";
|
|
43
|
+
import { buildPlanReviewContextPolicy, findRejectedPlanContextAnchors, } from "./plan-review/context-policy.js";
|
|
44
|
+
import { PLAN_SNAPSHOT_RELPATH, persistCanonicalPlanSnapshot, readCanonicalPlanSnapshot, writeExecutionContractSnapshot, } from "./plan-review/snapshot.js";
|
|
45
|
+
import { deriveSeatMappings, reconstructPlanReviewCallBindings, } from "./plan-review/run-state.js";
|
|
46
|
+
import { restorePlanReviewRun, } from "./plan-review/resume.js";
|
|
47
|
+
import { PlanReviewStageError } from "./plan-review/stage-error.js";
|
|
48
|
+
import { CouncilPlanReviewErrorSchema, createPlanReviewPublicationError, inspectPlanReviewPublication, } from "./plan-review/publication.js";
|
|
49
|
+
import { validatePlanReviewCallBindings, validatePlanReviewPreparedInputPolicy, } from "./plan-review/runtime-policy.js";
|
|
50
|
+
import { ContainedFilePolicyError, readContainedRelativeFile, } from "./contained-file.js";
|
|
51
|
+
import { createDashboardRunReader } from "./dashboard/read-model.js";
|
|
52
|
+
async function inspectLegacyWorkflowSessionLogGeneration(runDir, runId) {
|
|
53
|
+
try {
|
|
54
|
+
const result = await createDashboardRunReader().inspectGeneration?.(dirname(runDir), runId);
|
|
55
|
+
if (result?.kind === "present")
|
|
56
|
+
return result.runGeneration;
|
|
57
|
+
logger.debug({
|
|
58
|
+
run_id: runId,
|
|
59
|
+
reason: result === undefined ? "unsupported" : result.kind,
|
|
60
|
+
}, "workflow session-log generation unavailable; capture disabled");
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
logger.debug({ run_id: runId, reason: "inspection_failed" }, "workflow session-log generation unavailable; capture disabled");
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Allowed roots for reading the caller's `plan_file`.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately the workspace root ONLY. The state/artifact dirs are
|
|
71
|
+
* artifact-only roots — they are where runtime-owned output goes, never a
|
|
72
|
+
* source of reviewable plan text.
|
|
73
|
+
*/
|
|
74
|
+
function planFileAllowedRoots() {
|
|
75
|
+
return [resolveWorkspaceRoot()];
|
|
76
|
+
}
|
|
77
|
+
function createPreflightOnlyBackend(advertisedCliNames) {
|
|
78
|
+
return {
|
|
79
|
+
cliNames: [...(advertisedCliNames ?? [])],
|
|
80
|
+
roleNames: [],
|
|
81
|
+
isTerminated: false,
|
|
82
|
+
hasChildDied: false,
|
|
83
|
+
connect: async () => { },
|
|
84
|
+
callTool: async () => {
|
|
85
|
+
throw new Error("preflight-only backend cannot dispatch provider calls");
|
|
86
|
+
},
|
|
87
|
+
terminate: async () => { },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
32
90
|
// ---------------------------------------------------------------------------
|
|
33
91
|
// Admission control
|
|
34
92
|
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
93
|
+
const activeRuns = new Map();
|
|
94
|
+
/**
|
|
95
|
+
* In-process registry of AbortControllers for live async runs, keyed by run_id.
|
|
96
|
+
* cancel_workflow aborts the controller to drive the run's own cancellation
|
|
97
|
+
* unwind. Registered at async admission (foreground) and deleted in the detached
|
|
98
|
+
* run's finally (a delete on an absent key is a harmless no-op, so the
|
|
99
|
+
* synchronous run path is unaffected).
|
|
100
|
+
*/
|
|
101
|
+
const cancelRegistry = new Map();
|
|
102
|
+
export function resolveWorkflowAsyncAdmissionCap(options) {
|
|
103
|
+
const injected = options?.asyncAdmissionCap;
|
|
104
|
+
if (typeof injected === "number" &&
|
|
105
|
+
Number.isSafeInteger(injected) &&
|
|
106
|
+
injected >= 1) {
|
|
107
|
+
return injected;
|
|
108
|
+
}
|
|
109
|
+
return getWorkflowsMaxConcurrentAsync();
|
|
110
|
+
}
|
|
111
|
+
function acquireMutex(runId, options) {
|
|
112
|
+
if (activeRuns.has(runId)) {
|
|
113
|
+
throw new McpError(ErrorCode.InvalidParams, `run_id ${runId} already running`);
|
|
114
|
+
}
|
|
115
|
+
const maxConcurrent = resolveWorkflowAsyncAdmissionCap(options);
|
|
116
|
+
if (activeRuns.size >= maxConcurrent) {
|
|
117
|
+
throw new McpError(ErrorCode.InvalidRequest, `max ${maxConcurrent} concurrent workflow(s); retry later`);
|
|
118
|
+
}
|
|
119
|
+
activeRuns.set(runId, { startedAt: new Date().toISOString() });
|
|
43
120
|
}
|
|
44
|
-
function releaseMutex() {
|
|
45
|
-
|
|
121
|
+
function releaseMutex(runId) {
|
|
122
|
+
activeRuns.delete(runId);
|
|
46
123
|
}
|
|
47
124
|
export function parseMembersOverride(raw, knownCliNames) {
|
|
48
125
|
if (raw == null)
|
|
@@ -125,6 +202,44 @@ export function parseChairmanOverride(raw, knownCliNames) {
|
|
|
125
202
|
}
|
|
126
203
|
return trimmed;
|
|
127
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Point a generic-council caller at the input shape the workflow actually takes.
|
|
207
|
+
*
|
|
208
|
+
* Appended by the boundary decorator wrapping the whole pre-mutex admission
|
|
209
|
+
* block, so it rides on ANY `InvalidParams` rejection of a generic-shaped
|
|
210
|
+
* `plan-review-receipts` request — not only the `members`/`chairman` roster
|
|
211
|
+
* gates. A generic `user_request`-shaped caller can trip several independent
|
|
212
|
+
* pre-state checks (the roster-override parsers themselves, the two roster
|
|
213
|
+
* gates, unknown `user_request`/`cb_delegate` variables, context-mode and root
|
|
214
|
+
* validation, the conditional `cb_task` seed, task-secret validation, and the
|
|
215
|
+
* missing typed `plan_review`); whichever fires first now names the
|
|
216
|
+
* destination, instead of the caller re-submitting and being told something
|
|
217
|
+
* different each round trip.
|
|
218
|
+
*
|
|
219
|
+
* The decorator is gated on a RAW-`args` shape predicate, not on parsed
|
|
220
|
+
* values, specifically because a parser throw means no parsed value exists
|
|
221
|
+
* yet: `parseMembersOverride` rejects `[]` and unknown `cli_name`s before
|
|
222
|
+
* `parsedMembers` is ever bound, and that is exactly a case the hint must
|
|
223
|
+
* cover.
|
|
224
|
+
*
|
|
225
|
+
* Deliberately a HINT appended to the existing message, not a reordered check.
|
|
226
|
+
* Hoisting the `plan_review` requirement above the roster gate would mask
|
|
227
|
+
* malformed roster overrides behind a missing-contract error, and split the
|
|
228
|
+
* requirement from its authoritative mirror in `admitCouncilPlanReview`. The
|
|
229
|
+
* decorator changes no ordering, so that reasoning still holds.
|
|
230
|
+
*/
|
|
231
|
+
function planReviewShapeHint() {
|
|
232
|
+
return (`. ${PLAN_REVIEW_RECEIPTS_WORKFLOW} takes a typed "plan_review" contract instead:` +
|
|
233
|
+
` the roster is plan_review.seats[] and the chairman is plan_review.chairman_seat` +
|
|
234
|
+
` (a seat_id, not an adapter name). It declares neither a "user_request" nor a` +
|
|
235
|
+
` "cb_delegate" variable, and its "cb_task" seed is conditionally required:` +
|
|
236
|
+
` trimmed-nonblank whenever context preflight is available (the default` +
|
|
237
|
+
` deployment), and genuinely optional only on a server where preflight is` +
|
|
238
|
+
` unavailable. It is driven by the plan-review-loop skill, not a generic` +
|
|
239
|
+
` council template. For a generic members/chairman/user_request plan review` +
|
|
240
|
+
` without the typed contract, use the "council-plan-review" workflow; the` +
|
|
241
|
+
` typed plan_review contract belongs only to "plan-review-receipts".`);
|
|
242
|
+
}
|
|
128
243
|
// ---------------------------------------------------------------------------
|
|
129
244
|
// Variable validation (6-6)
|
|
130
245
|
// ---------------------------------------------------------------------------
|
|
@@ -136,7 +251,8 @@ function formatExpectedVariables(schema) {
|
|
|
136
251
|
}
|
|
137
252
|
return parts.length > 0 ? `Expected variables: ${parts.join(", ")}` : "";
|
|
138
253
|
}
|
|
139
|
-
|
|
254
|
+
/** @internal Shared with the compiled producer-fixture admission check. */
|
|
255
|
+
export function validateVariables(supplied, schema) {
|
|
140
256
|
const resolved = {};
|
|
141
257
|
const errors = [];
|
|
142
258
|
for (const [name, def] of Object.entries(schema)) {
|
|
@@ -174,6 +290,65 @@ function validateVariables(supplied, schema) {
|
|
|
174
290
|
}
|
|
175
291
|
return resolved;
|
|
176
292
|
}
|
|
293
|
+
function validateContextBuilderAnchorSufficiency(variables, runner, contextDefault, defaultCurator) {
|
|
294
|
+
if (runner == null) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
let policy;
|
|
298
|
+
try {
|
|
299
|
+
// Throws on an invalid cb_context_mode / cb_delegate combination (surfaced
|
|
300
|
+
// below as -32602). The resolved policy is also returned for admission-time
|
|
301
|
+
// checks that must stay inert under soft off.
|
|
302
|
+
policy = resolveWorkflowContextMode(variables["cb_context_mode"], contextDefault, true, variables["cb_delegate"], defaultCurator);
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
if (error instanceof WorkflowContextModeResolverError ||
|
|
306
|
+
error instanceof WorkflowDelegateValidationError) {
|
|
307
|
+
throw new McpError(ErrorCode.InvalidParams, error.message);
|
|
308
|
+
}
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
const cbRoots = readStringArrayForAnchorCheck(variables["cb_roots"]);
|
|
312
|
+
// Eager allowlist validation: explicit cb_roots are validated whenever
|
|
313
|
+
// provided, including soft off. Out-of-allowlist roots are rejected at the
|
|
314
|
+
// boundary as -32602. The disabled hard kill never reaches this validator.
|
|
315
|
+
if (cbRoots.length > 0) {
|
|
316
|
+
try {
|
|
317
|
+
runner.assertRootsAllowed(cbRoots);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
throw new McpError(ErrorCode.InvalidParams, err instanceof Error ? err.message : String(err));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// policy.preflightDisabled is unreachable here: this validator early-returns
|
|
324
|
+
// when runner == null and always resolves with preflightEnabled:true, so the
|
|
325
|
+
// resolver never returns a disabled policy on this path.
|
|
326
|
+
return policy;
|
|
327
|
+
}
|
|
328
|
+
function validateContextSeedSourceAtAdmission(variables, policy) {
|
|
329
|
+
if (policy == null) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const cbTask = typeof variables["cb_task"] === "string"
|
|
333
|
+
? variables["cb_task"]
|
|
334
|
+
: undefined;
|
|
335
|
+
const userRequest = typeof variables["user_request"] === "string"
|
|
336
|
+
? variables["user_request"]
|
|
337
|
+
: undefined;
|
|
338
|
+
const seedSource = resolveSeedSource(cbTask, userRequest);
|
|
339
|
+
const seedSourceError = contextSeedSourceValidationError(seedSource);
|
|
340
|
+
if (seedSourceError != null) {
|
|
341
|
+
throw new McpError(ErrorCode.InvalidParams, seedSourceError);
|
|
342
|
+
}
|
|
343
|
+
const searchTerms = readStringArrayForAnchorCheck(variables["cb_search_terms"]);
|
|
344
|
+
const searchTermsError = contextSearchTermsValidationError(searchTerms);
|
|
345
|
+
if (searchTermsError != null) {
|
|
346
|
+
throw new McpError(ErrorCode.InvalidParams, searchTermsError);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function readStringArrayForAnchorCheck(value) {
|
|
350
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : [];
|
|
351
|
+
}
|
|
177
352
|
function validateVariableType(name, value, expectedType) {
|
|
178
353
|
switch (expectedType) {
|
|
179
354
|
case "string":
|
|
@@ -597,6 +772,37 @@ function finalizeWorkflowPresentation(options) {
|
|
|
597
772
|
}
|
|
598
773
|
return content;
|
|
599
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Wire projection of the warn-only duplicate-replay signal recorded at
|
|
777
|
+
* `WorkflowStateStore.create()` (failure investigation F6). Additive and
|
|
778
|
+
* advisory: identical snapshot hashes do NOT imply an identical outcome —
|
|
779
|
+
* workspace content is not covered by the hashes and providers are
|
|
780
|
+
* nondeterministic — so this field never gates admission or execution.
|
|
781
|
+
* Defensive optional access: partial store mocks may omit the property.
|
|
782
|
+
*/
|
|
783
|
+
function buildDuplicateOfFailedRunField(store) {
|
|
784
|
+
const signal = store?.duplicateOfFailedRun;
|
|
785
|
+
if (signal === undefined || signal === null)
|
|
786
|
+
return undefined;
|
|
787
|
+
return {
|
|
788
|
+
run_id: signal.runId,
|
|
789
|
+
...(signal.completedAt !== undefined ? { completed_at: signal.completedAt } : {}),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Appends the duplicate-replay signal as a trailing additive content block on
|
|
794
|
+
* sync run_workflow results (success and error). No block is added when no
|
|
795
|
+
* signal was detected, so existing content ordering is untouched.
|
|
796
|
+
*/
|
|
797
|
+
function appendDuplicateReplayBlock(content, store) {
|
|
798
|
+
const duplicateOfFailedRun = buildDuplicateOfFailedRunField(store);
|
|
799
|
+
if (duplicateOfFailedRun === undefined)
|
|
800
|
+
return;
|
|
801
|
+
content.push({
|
|
802
|
+
type: "text",
|
|
803
|
+
text: JSON.stringify({ duplicate_of_failed_run: duplicateOfFailedRun }),
|
|
804
|
+
});
|
|
805
|
+
}
|
|
600
806
|
// ---------------------------------------------------------------------------
|
|
601
807
|
// Actionable error formatting (5-6)
|
|
602
808
|
// ---------------------------------------------------------------------------
|
|
@@ -655,15 +861,46 @@ function formatWorkflowError(runId, error, store, workflowSource) {
|
|
|
655
861
|
let nextAction = "";
|
|
656
862
|
if (store != null) {
|
|
657
863
|
const isResumable = state?.resumable === true;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
864
|
+
const isTerminalPlanReview = state != null &&
|
|
865
|
+
isPlanReviewRunState(state) &&
|
|
866
|
+
(state.status === "failed" ||
|
|
867
|
+
state.status === "cancelled" ||
|
|
868
|
+
state.status === "succeeded");
|
|
869
|
+
nextAction = isTerminalPlanReview
|
|
870
|
+
? `Start a new run with run_workflow and a new run_id; terminal plan-review-receipts runs are immutable.`
|
|
871
|
+
: isResumable
|
|
872
|
+
? `Use resume_workflow with run_id "${runId}" to retry from the last checkpoint.`
|
|
873
|
+
: `Start a new run with run_workflow (this run is not resumable — set CLINKX_WORKFLOWS_CHILD_CONFIG_PATH for resumable runs).`;
|
|
661
874
|
}
|
|
662
875
|
return (`Workflow failed [run_id: ${runId}]${sourceLabel}${location}` +
|
|
663
876
|
(retryCount > 0 ? ` (after ${String(retryCount)} retries)` : "") +
|
|
664
877
|
`: ${message}${metaBlock}` +
|
|
665
878
|
(nextAction !== "" ? `\n\n${nextAction}` : ""));
|
|
666
879
|
}
|
|
880
|
+
function toCouncilPlanReviewError(runId, error) {
|
|
881
|
+
if (!(error instanceof PlanReviewStageError)) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
return CouncilPlanReviewErrorSchema.parse({
|
|
885
|
+
schema_version: "council-plan-review-error-v1",
|
|
886
|
+
run_id: runId,
|
|
887
|
+
execution_status: "failed",
|
|
888
|
+
stage: error.stage,
|
|
889
|
+
error: {
|
|
890
|
+
reason: error.reason,
|
|
891
|
+
detail: error.message.slice(0, 2_048),
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
async function markWorkflowFailed(store, runId, error) {
|
|
896
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
897
|
+
const planReviewError = toCouncilPlanReviewError(runId, error);
|
|
898
|
+
if (planReviewError === undefined) {
|
|
899
|
+
await store.markFailed(message);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
await store.markFailed(message, undefined, planReviewError);
|
|
903
|
+
}
|
|
667
904
|
function getCallEntryByLocation(stages, location) {
|
|
668
905
|
const dotIndex = location.indexOf(".");
|
|
669
906
|
if (dotIndex === -1) {
|
|
@@ -704,30 +941,350 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
704
941
|
const knownCliNames = options.availableCliNames != null
|
|
705
942
|
? new Set(options.availableCliNames)
|
|
706
943
|
: undefined;
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
944
|
+
// Read RAW `args`, not parsed values: `parseMembersOverride` throws on `[]`
|
|
945
|
+
// and on an unknown `cli_name`, so in exactly the cases the hint exists to
|
|
946
|
+
// cover, `parsedMembers` is never bound.
|
|
947
|
+
const genericShaped = workflow === PLAN_REVIEW_RECEIPTS_WORKFLOW &&
|
|
948
|
+
(args["plan_review"] == null ||
|
|
949
|
+
args["members"] != null ||
|
|
950
|
+
args["chairman"] != null);
|
|
951
|
+
// Declarations hoisted out of the decorator's try so the bindings stay in
|
|
952
|
+
// scope for the post-admission code below; the assignments and their order
|
|
953
|
+
// are unchanged.
|
|
954
|
+
let parsedMembers;
|
|
955
|
+
let parsedChairman;
|
|
956
|
+
let resolvedVariables;
|
|
957
|
+
let initialContextPolicy;
|
|
958
|
+
let planReviewAdmission;
|
|
959
|
+
try {
|
|
960
|
+
parsedMembers = parseMembersOverride(args["members"], knownCliNames);
|
|
961
|
+
parsedChairman = parseChairmanOverride(args["chairman"], knownCliNames);
|
|
962
|
+
if (parsedMembers != null && !COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
963
|
+
throw new McpError(ErrorCode.InvalidParams, `members parameter is not supported for workflow "${workflow}"`);
|
|
964
|
+
}
|
|
965
|
+
if (parsedChairman != null && !COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
966
|
+
throw new McpError(ErrorCode.InvalidParams, `chairman parameter is not supported for workflow "${workflow}"`);
|
|
967
|
+
}
|
|
968
|
+
resolvedVariables = validateVariables(variables, loaded.definition.variables);
|
|
969
|
+
// When the runner is absent (preflight disabled / hard kill), the boundary
|
|
970
|
+
// validator is inert. Skip it WITHOUT evaluating the context-default env
|
|
971
|
+
// parsers — getWorkflowsContextDefaultCurator() throws on empty env, and a
|
|
972
|
+
// runner-less run_workflow must never trip context env parsing.
|
|
973
|
+
let contextPolicy;
|
|
974
|
+
if (options.contextBuilderRunner != null) {
|
|
975
|
+
contextPolicy = validateContextBuilderAnchorSufficiency(resolvedVariables, options.contextBuilderRunner, options.contextDefault ?? getWorkflowsContextDefault(), options.defaultCurator ?? getWorkflowsContextDefaultCurator());
|
|
976
|
+
}
|
|
977
|
+
validateContextSeedSourceAtAdmission(resolvedVariables, contextPolicy);
|
|
978
|
+
initialContextPolicy = contextPolicy ?? resolveWorkflowContextMode(undefined, options.contextDefault ?? "off", false, undefined, options.defaultCurator);
|
|
979
|
+
if (options.contextBuilderRunner != null) {
|
|
980
|
+
try {
|
|
981
|
+
options.contextBuilderRunner.assertTaskSecretsAllowed(resolvedVariables);
|
|
982
|
+
}
|
|
983
|
+
catch (err) {
|
|
984
|
+
if (err instanceof McpError) {
|
|
985
|
+
throw err;
|
|
986
|
+
}
|
|
987
|
+
throw new McpError(ErrorCode.InvalidParams, err instanceof Error ? err.message : String(err));
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const planReviewCliNames = options.planReviewAvailableCliNames ?? options.availableCliNames;
|
|
991
|
+
planReviewAdmission = admitCouncilPlanReview({
|
|
992
|
+
workflow,
|
|
993
|
+
planReview: args["plan_review"],
|
|
994
|
+
variables: resolvedVariables,
|
|
995
|
+
...(planReviewCliNames !== undefined
|
|
996
|
+
? { availableCliNames: planReviewCliNames }
|
|
997
|
+
: {}),
|
|
998
|
+
});
|
|
711
999
|
}
|
|
712
|
-
|
|
713
|
-
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
// Append the hint by MUTATING the caught error and rethrowing that same
|
|
1002
|
+
// instance. Do NOT re-wrap it in `new McpError(err.code, err.message + …)`:
|
|
1003
|
+
// the SDK constructor prefixes its argument with `MCP error <code>: `, so
|
|
1004
|
+
// `err.message` already carries that prefix and re-constructing emits it
|
|
1005
|
+
// twice on the wire (`MCP error -32602: MCP error -32602: …`). Mutating in
|
|
1006
|
+
// place also preserves `code`, `data`, and the original stack.
|
|
1007
|
+
if (genericShaped && err instanceof McpError && err.code === ErrorCode.InvalidParams) {
|
|
1008
|
+
err.message = `${err.message}${planReviewShapeHint()}`;
|
|
1009
|
+
}
|
|
1010
|
+
throw err;
|
|
714
1011
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1012
|
+
// A-7: reject caller context anchors that resolve to the bound plan or any
|
|
1013
|
+
// plan-workspace PLAN.md. Pre-mutex, so a rejected request creates no state:
|
|
1014
|
+
// the caller must not be able to hand actors a MUTABLE plan alongside the
|
|
1015
|
+
// immutable snapshot.
|
|
1016
|
+
let planReviewPlan;
|
|
1017
|
+
if (planReviewAdmission !== undefined) {
|
|
1018
|
+
const contextPolicy = buildPlanReviewContextPolicy(planReviewAdmission.input.plan_file);
|
|
1019
|
+
const rejected = findRejectedPlanContextAnchors(contextPolicy, resolvedVariables, resolveWorkspaceRoot());
|
|
1020
|
+
if (rejected.length > 0) {
|
|
1021
|
+
throw new McpError(ErrorCode.InvalidParams, `plan-review-receipts forbids plan context anchors; reviewers receive only the immutable snapshot. Offending entries: ${rejected
|
|
1022
|
+
.map((entry) => `${entry.variable}: ${entry.path}`)
|
|
1023
|
+
.join(", ")}`);
|
|
1024
|
+
}
|
|
1025
|
+
planReviewPlan = {
|
|
1026
|
+
admission: planReviewAdmission,
|
|
1027
|
+
contextPolicy,
|
|
1028
|
+
// Deterministic, runtime-owned: the definition must name the snapshot
|
|
1029
|
+
// before `create()` makes the run dir, so the path is derived rather than
|
|
1030
|
+
// discovered. A post-create guard asserts the store agrees.
|
|
1031
|
+
snapshotAbsolutePath: join(getRunStateDir(runId), PLAN_SNAPSHOT_RELPATH),
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
acquireMutex(runId, options);
|
|
1035
|
+
const isAsync = args["async"] === true;
|
|
1036
|
+
if (isAsync) {
|
|
1037
|
+
// Everything between acquireMutex and the detached spawn must release the
|
|
1038
|
+
// mutex on ANY throw, or the run slot leaks permanently (bricks the server
|
|
1039
|
+
// at the default cap of 1).
|
|
1040
|
+
let store;
|
|
1041
|
+
let effectiveDefinition;
|
|
1042
|
+
let initializedPlanReview;
|
|
1043
|
+
let preparedSnapshot;
|
|
1044
|
+
try {
|
|
1045
|
+
const runDir = getRunStateDir(runId);
|
|
1046
|
+
if (existsSync(runDir)) {
|
|
1047
|
+
throw new McpError(ErrorCode.InvalidParams, `run_id ${runId} already has persisted state at ${runDir}`);
|
|
1048
|
+
}
|
|
1049
|
+
// Read + digest-verify the plan BEFORE composing: the expander inlines
|
|
1050
|
+
// the snapshot text, so it must exist by expansion time (D9).
|
|
1051
|
+
preparedSnapshot = planReviewPlan === undefined
|
|
1052
|
+
? undefined
|
|
1053
|
+
: await preparePlanReviewSnapshot(planReviewPlan);
|
|
1054
|
+
// Expand composition ONCE in the foreground; the detached run reuses it.
|
|
1055
|
+
effectiveDefinition = expandRunWorkflowDefinition(loaded.normalized, workflow, parsedMembers, parsedChairman, planReviewPlan, preparedSnapshot);
|
|
1056
|
+
// Create the durable, pollable state record in the FOREGROUND so a failure
|
|
1057
|
+
// here surfaces synchronously (a real error, never a false "running").
|
|
1058
|
+
try {
|
|
1059
|
+
store = await WorkflowStateStore.create({
|
|
1060
|
+
runId,
|
|
1061
|
+
definition: effectiveDefinition,
|
|
1062
|
+
variables: resolvedVariables,
|
|
1063
|
+
debugKeepArtifacts: debug,
|
|
1064
|
+
async: true,
|
|
1065
|
+
initialContextPolicy,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
catch (err) {
|
|
1069
|
+
throw new McpError(ErrorCode.InternalError, `Failed to initialize async run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1070
|
+
}
|
|
1071
|
+
// A-10: the async acknowledgement is a durability boundary. A caller
|
|
1072
|
+
// cannot observe `running` until every plan-review snapshot and the
|
|
1073
|
+
// strict typed state subtree have committed successfully.
|
|
1074
|
+
if (planReviewPlan !== undefined) {
|
|
1075
|
+
initializedPlanReview = await initializePlanReviewRunState(store, planReviewPlan, effectiveDefinition, preparedSnapshot);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
catch (err) {
|
|
1079
|
+
if (store !== undefined && planReviewPlan !== undefined) {
|
|
1080
|
+
try {
|
|
1081
|
+
try {
|
|
1082
|
+
await markWorkflowFailed(store, runId, err);
|
|
1083
|
+
}
|
|
1084
|
+
catch (markFailedError) {
|
|
1085
|
+
logger.warn({ err: markFailedError }, "markFailed during async initialization error handling failed");
|
|
1086
|
+
}
|
|
1087
|
+
const errorText = formatWorkflowError(runId, err, store, {
|
|
1088
|
+
source: loaded.source,
|
|
1089
|
+
sourcePath: loaded.sourcePath,
|
|
1090
|
+
});
|
|
1091
|
+
let failureSummary;
|
|
1092
|
+
if (receipt) {
|
|
1093
|
+
try {
|
|
1094
|
+
failureSummary = buildFailureRunSummary(store);
|
|
1095
|
+
}
|
|
1096
|
+
catch (summaryError) {
|
|
1097
|
+
logger.warn({ err: summaryError }, "failure summary construction failed, omitting receipt");
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
const content = finalizeWorkflowPresentation({
|
|
1101
|
+
outputText: errorText,
|
|
1102
|
+
receipt,
|
|
1103
|
+
debug,
|
|
1104
|
+
summary: failureSummary,
|
|
1105
|
+
debugBlock: debug ? buildErrorDebugBlock(runId, store) : undefined,
|
|
1106
|
+
});
|
|
1107
|
+
return { content, isError: true };
|
|
1108
|
+
}
|
|
1109
|
+
finally {
|
|
1110
|
+
try {
|
|
1111
|
+
await store.close();
|
|
1112
|
+
}
|
|
1113
|
+
catch (closeError) {
|
|
1114
|
+
logger.warn({ err: closeError }, "store.close() failed during async initialization cleanup");
|
|
1115
|
+
}
|
|
1116
|
+
releaseMutex(runId);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
releaseMutex(runId);
|
|
1120
|
+
throw err;
|
|
1121
|
+
}
|
|
1122
|
+
if (store === undefined || effectiveDefinition === undefined) {
|
|
1123
|
+
releaseMutex(runId);
|
|
1124
|
+
throw new McpError(ErrorCode.InternalError, `Failed to initialize async run ${runId}`);
|
|
1125
|
+
}
|
|
1126
|
+
const detached = new AbortController();
|
|
1127
|
+
cancelRegistry.set(runId, detached);
|
|
1128
|
+
void runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, true, detached.signal, undefined, store, effectiveDefinition, undefined, initializedPlanReview, initialContextPolicy).catch((err) => {
|
|
1129
|
+
if (err instanceof WorkflowCancellationError) {
|
|
1130
|
+
logger.info({ runId }, "workflow run cancelled");
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
logger.error({ err, runId }, "detached async run failed");
|
|
1134
|
+
});
|
|
1135
|
+
// Additive-only ack shape: base {run_id, status:"running"} is unchanged;
|
|
1136
|
+
// the duplicate-replay field appears only when detected (warn-only, F6).
|
|
1137
|
+
const duplicateOfFailedRun = buildDuplicateOfFailedRunField(store);
|
|
1138
|
+
return {
|
|
1139
|
+
content: [{
|
|
1140
|
+
type: "text",
|
|
1141
|
+
text: JSON.stringify({
|
|
1142
|
+
run_id: runId,
|
|
1143
|
+
status: "running",
|
|
1144
|
+
...(duplicateOfFailedRun !== undefined
|
|
1145
|
+
? { duplicate_of_failed_run: duplicateOfFailedRun }
|
|
1146
|
+
: {}),
|
|
1147
|
+
}),
|
|
1148
|
+
}],
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return await runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, false, signal, progressToken, undefined, undefined, planReviewPlan, undefined, initialContextPolicy);
|
|
1152
|
+
}
|
|
1153
|
+
async function preparePlanReviewSnapshot(plan) {
|
|
1154
|
+
try {
|
|
1155
|
+
const canonical = await readCanonicalPlanSnapshot({
|
|
1156
|
+
planFile: plan.admission.input.plan_file,
|
|
1157
|
+
allowedRoots: planFileAllowedRoots(),
|
|
1158
|
+
claimedDigest: plan.admission.input.digest,
|
|
1159
|
+
});
|
|
1160
|
+
// Fail fast on a roster that cannot fit its own prompts. This is caller
|
|
1161
|
+
// sizing, not a TOCTOU: it is deterministic and pre-state.
|
|
1162
|
+
//
|
|
1163
|
+
// It raises `McpError(-32602)`, which the re-throw below deliberately lets
|
|
1164
|
+
// escape rather than carrying as `{ok:false}` — but the CODE only reaches
|
|
1165
|
+
// the caller on the ASYNC path. `executeWorkflowRun` wraps the sync path in
|
|
1166
|
+
// its generic failure handler, so the same error surfaces there as an
|
|
1167
|
+
// `isError` TEXT result carrying the message but NOT the code. Do not
|
|
1168
|
+
// describe this as "-32602" unqualified; the invariant that holds on BOTH
|
|
1169
|
+
// paths is: no run directory, no persisted state, no provider work.
|
|
1170
|
+
assertPlanReviewPromptBudget({
|
|
1171
|
+
seatCount: plan.admission.execution_contract.seats.length,
|
|
1172
|
+
snapshotBytes: canonical.canonicalBytes.byteLength,
|
|
1173
|
+
maxPromptBytes: getMaxPromptBytes(),
|
|
1174
|
+
});
|
|
1175
|
+
return { ok: true, canonical };
|
|
1176
|
+
}
|
|
1177
|
+
catch (err) {
|
|
1178
|
+
if (err instanceof McpError)
|
|
1179
|
+
throw err;
|
|
1180
|
+
return { ok: false, error: err };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* A-5/A-6: materialize the immutable snapshot + persisted execution contract
|
|
1185
|
+
* into runtime-owned storage and commit the typed plan-review subtree, all
|
|
1186
|
+
* BEFORE any seat is dispatched.
|
|
1187
|
+
*
|
|
1188
|
+
* A failure here is a post-state PRE-SEAT failure: the run is marked `failed`
|
|
1189
|
+
* with the offending `stage`, and no result sidecar is ever attempted.
|
|
1190
|
+
*/
|
|
1191
|
+
async function initializePlanReviewRunState(store, plan, definition, prepared) {
|
|
1192
|
+
// Raise a carried read/verify failure now that state exists, so a swapped or
|
|
1193
|
+
// unreadable plan is a `failed` run rather than a pre-state rejection.
|
|
1194
|
+
if (!prepared.ok)
|
|
1195
|
+
throw prepared.error;
|
|
1196
|
+
const contract = plan.admission.execution_contract;
|
|
1197
|
+
const snapshot = await persistCanonicalPlanSnapshot(store.runDir, prepared.canonical);
|
|
1198
|
+
if (snapshot.absolutePath !== plan.snapshotAbsolutePath) {
|
|
1199
|
+
throw new PlanReviewStageError("snapshot", `Snapshot path ${snapshot.absolutePath} does not match the runtime-owned path derived for this run (${plan.snapshotAbsolutePath})`);
|
|
1200
|
+
}
|
|
1201
|
+
const persistedContract = await writeExecutionContractSnapshot(store.runDir, contract);
|
|
1202
|
+
const state = {
|
|
1203
|
+
schema_version: "council-plan-review-state-v1",
|
|
1204
|
+
execution_contract_path: persistedContract.relPath,
|
|
1205
|
+
execution_contract_sha256: persistedContract.sha256,
|
|
1206
|
+
execution_contract_digest: plan.admission.execution_contract_digest,
|
|
1207
|
+
snapshot_path: snapshot.relPath,
|
|
1208
|
+
snapshot_sha256: snapshot.sha256,
|
|
1209
|
+
plan_digest: snapshot.planDigest,
|
|
1210
|
+
seat_mappings: deriveSeatMappings(contract),
|
|
1211
|
+
call_bindings: reconstructPlanReviewCallBindings(contract, resolvePlanReviewChairmanCallId(definition)),
|
|
1212
|
+
context_policy: plan.contextPolicy,
|
|
1213
|
+
};
|
|
1214
|
+
validatePlanReviewCallBindings(definition, state.call_bindings);
|
|
1215
|
+
await store.commitPlanReviewState(state);
|
|
1216
|
+
return state;
|
|
1217
|
+
}
|
|
1218
|
+
/** The chairman stage's call id, read from the normalized definition. */
|
|
1219
|
+
function resolvePlanReviewChairmanCallId(definition) {
|
|
1220
|
+
const stage = definition.stages.find((entry) => entry.id === "chairman");
|
|
1221
|
+
const callId = stage?.calls[0]?.id;
|
|
1222
|
+
if (callId === undefined) {
|
|
1223
|
+
throw new PlanReviewStageError("execution_contract", "Plan-review definition has no chairman call");
|
|
1224
|
+
}
|
|
1225
|
+
return callId;
|
|
1226
|
+
}
|
|
1227
|
+
function expandRunWorkflowDefinition(definition, workflow, parsedMembers, parsedChairman, planReviewPlan,
|
|
1228
|
+
/**
|
|
1229
|
+
* The already-verified snapshot. This function must NEVER read `plan_file`:
|
|
1230
|
+
* the text it inlines is the digest-verified canonical copy or nothing.
|
|
1231
|
+
*/
|
|
1232
|
+
preparedSnapshot) {
|
|
1233
|
+
if (planReviewPlan !== undefined) {
|
|
1234
|
+
return expandCouncilPlanReview(definition, planReviewPlan.admission.execution_contract,
|
|
1235
|
+
// On a carried read failure the run is already doomed; expand without a
|
|
1236
|
+
// snapshot so the store can be created and mark the run `failed`.
|
|
1237
|
+
//
|
|
1238
|
+
// The verified snapshot is passed WHOLE rather than flattened to `.text`:
|
|
1239
|
+
// the expander's guard re-checks the digest-bearing shape, which a bare
|
|
1240
|
+
// string cannot carry. This is the single read — never re-read `plan_file`.
|
|
1241
|
+
preparedSnapshot?.ok === true ? preparedSnapshot.canonical : undefined);
|
|
1242
|
+
}
|
|
1243
|
+
return parsedMembers != null || parsedChairman != null
|
|
1244
|
+
? expandCouncilComposition(definition, parsedMembers, workflow, parsedChairman)
|
|
1245
|
+
: definition;
|
|
1246
|
+
}
|
|
1247
|
+
async function runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, isAsync, signal, progressToken, preCreatedStore, preExpandedDefinition, planReviewPlan, preInitializedPlanReview, initialContextPolicy) {
|
|
1248
|
+
let store = preCreatedStore ?? null;
|
|
718
1249
|
let backend = null;
|
|
719
1250
|
let reporter;
|
|
1251
|
+
let shutdownHandle;
|
|
1252
|
+
let releaseLifecycleBinding;
|
|
1253
|
+
let initializedPlanReview = preInitializedPlanReview;
|
|
720
1254
|
try {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1255
|
+
// The async admission path already expanded the composition (and read the
|
|
1256
|
+
// snapshot) in the foreground; reuse both to avoid redundant work and a
|
|
1257
|
+
// second `plan_file` read.
|
|
1258
|
+
const preparedSnapshot = preExpandedDefinition !== undefined ||
|
|
1259
|
+
planReviewPlan === undefined
|
|
1260
|
+
? undefined
|
|
1261
|
+
: await preparePlanReviewSnapshot(planReviewPlan);
|
|
1262
|
+
const effectiveDefinition = preExpandedDefinition ??
|
|
1263
|
+
expandRunWorkflowDefinition(loaded.normalized, workflow, parsedMembers, parsedChairman, planReviewPlan, preparedSnapshot);
|
|
1264
|
+
// Create state store (resumable if child config path is set) unless the
|
|
1265
|
+
// async admission path already created it in the foreground.
|
|
1266
|
+
if (store == null) {
|
|
1267
|
+
store = await WorkflowStateStore.create({
|
|
1268
|
+
runId,
|
|
1269
|
+
definition: effectiveDefinition,
|
|
1270
|
+
variables: resolvedVariables,
|
|
1271
|
+
debugKeepArtifacts: debug,
|
|
1272
|
+
async: isAsync,
|
|
1273
|
+
initialContextPolicy,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
releaseLifecycleBinding = options.contextBuilderLifecycleRouter?.bind(runId, store);
|
|
1277
|
+
// A-5/A-6: snapshot + contract + typed subtree land here — after state
|
|
1278
|
+
// exists, before the backend is even created, so a snapshot/digest failure
|
|
1279
|
+
// is a `failed` RunState with zero seats dispatched (never a result
|
|
1280
|
+
// sidecar, never a pre-state -32602).
|
|
1281
|
+
if (planReviewPlan !== undefined && preparedSnapshot !== undefined) {
|
|
1282
|
+
initializedPlanReview = await initializePlanReviewRunState(store, planReviewPlan, effectiveDefinition, preparedSnapshot);
|
|
1283
|
+
}
|
|
1284
|
+
const sessionLogGeneration = store.sessionLogGeneration ??
|
|
1285
|
+
await inspectLegacyWorkflowSessionLogGeneration(store.runDir, runId);
|
|
1286
|
+
if (signal.aborted)
|
|
1287
|
+
throw new WorkflowCancellationError();
|
|
731
1288
|
// Create backend via factory
|
|
732
1289
|
backend = options.createBackend(runId);
|
|
733
1290
|
await backend.connect();
|
|
@@ -741,7 +1298,7 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
741
1298
|
controller.abort(new WorkflowCancellationError());
|
|
742
1299
|
}, { once: true });
|
|
743
1300
|
}
|
|
744
|
-
shutdownCoordinator.register(backend, controller, async () => {
|
|
1301
|
+
shutdownHandle = shutdownCoordinator.register(backend, controller, async () => {
|
|
745
1302
|
if (store != null) {
|
|
746
1303
|
await store.markCancelled();
|
|
747
1304
|
}
|
|
@@ -773,12 +1330,30 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
773
1330
|
variables: resolvedVariables,
|
|
774
1331
|
validatorRegistry,
|
|
775
1332
|
runId,
|
|
1333
|
+
sessionLogGeneration,
|
|
776
1334
|
runDir: storeRef.runDir,
|
|
777
1335
|
signal: controller.signal,
|
|
778
1336
|
hooks,
|
|
1337
|
+
contextBuilderRunner: options.contextBuilderRunner,
|
|
1338
|
+
contextDefault: options.contextDefault,
|
|
1339
|
+
defaultCurator: options.defaultCurator,
|
|
1340
|
+
commitPreflightContext: storeRef.commitPreflightContext?.bind(storeRef),
|
|
1341
|
+
initialContextPolicy,
|
|
1342
|
+
...(initializedPlanReview !== undefined
|
|
1343
|
+
? {
|
|
1344
|
+
planReviewContextPolicy: initializedPlanReview.context_policy,
|
|
1345
|
+
planReviewCallBindings: initializedPlanReview.call_bindings,
|
|
1346
|
+
commitPlanReviewContextProvenance: storeRef.commitPlanReviewContextProvenance?.bind(storeRef),
|
|
1347
|
+
terminalResultPolicy: {
|
|
1348
|
+
kind: "plan_review_artifacts",
|
|
1349
|
+
project: storeRef.projectPlanReviewTerminalResult.bind(storeRef),
|
|
1350
|
+
},
|
|
1351
|
+
}
|
|
1352
|
+
: {}),
|
|
779
1353
|
});
|
|
780
1354
|
const result = await engine.execute();
|
|
781
|
-
|
|
1355
|
+
if (shutdownHandle !== undefined)
|
|
1356
|
+
shutdownCoordinator.unregister(shutdownHandle);
|
|
782
1357
|
// Build summary (fail-open)
|
|
783
1358
|
let summary;
|
|
784
1359
|
if (receipt) {
|
|
@@ -797,16 +1372,29 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
797
1372
|
summary,
|
|
798
1373
|
debugBlock: debug ? buildDebugBlock(runId, result, store) : undefined,
|
|
799
1374
|
});
|
|
1375
|
+
appendDuplicateReplayBlock(content, store);
|
|
800
1376
|
return { content };
|
|
801
1377
|
}
|
|
802
1378
|
catch (error) {
|
|
803
|
-
|
|
1379
|
+
if (shutdownHandle !== undefined)
|
|
1380
|
+
shutdownCoordinator.unregister(shutdownHandle);
|
|
804
1381
|
if (error instanceof WorkflowCancellationError) {
|
|
805
1382
|
if (store != null) {
|
|
806
1383
|
await store.markCancelled();
|
|
807
1384
|
}
|
|
808
1385
|
throw error;
|
|
809
1386
|
}
|
|
1387
|
+
// Persist a terminal "failed" record for post-admission setup failures
|
|
1388
|
+
// (e.g. backend.connect() / engine construction). Idempotent: the engine
|
|
1389
|
+
// already persists "failed" for in-engine failures, so this won't clobber.
|
|
1390
|
+
if (store != null) {
|
|
1391
|
+
try {
|
|
1392
|
+
await markWorkflowFailed(store, runId, error);
|
|
1393
|
+
}
|
|
1394
|
+
catch (err) {
|
|
1395
|
+
logger.warn({ err }, "markFailed during error handling failed");
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
810
1398
|
const errorText = formatWorkflowError(runId, error, store, {
|
|
811
1399
|
source: loaded.source,
|
|
812
1400
|
sourcePath: loaded.sourcePath,
|
|
@@ -828,9 +1416,11 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
828
1416
|
summary: failureSummary,
|
|
829
1417
|
debugBlock: debug && store != null ? buildErrorDebugBlock(runId, store) : undefined,
|
|
830
1418
|
});
|
|
1419
|
+
appendDuplicateReplayBlock(content, store);
|
|
831
1420
|
return { content, isError: true };
|
|
832
1421
|
}
|
|
833
1422
|
finally {
|
|
1423
|
+
releaseLifecycleBinding?.();
|
|
834
1424
|
reporter?.stop();
|
|
835
1425
|
try {
|
|
836
1426
|
if (backend != null && !backend.isTerminated) {
|
|
@@ -848,7 +1438,8 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
848
1438
|
catch (cleanupErr) {
|
|
849
1439
|
logger.warn({ err: cleanupErr }, "store.close() failed during cleanup");
|
|
850
1440
|
}
|
|
851
|
-
|
|
1441
|
+
cancelRegistry.delete(runId);
|
|
1442
|
+
releaseMutex(runId);
|
|
852
1443
|
}
|
|
853
1444
|
}
|
|
854
1445
|
async function executeResumeWorkflow(server, options, args, signal, progressToken) {
|
|
@@ -864,18 +1455,133 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
864
1455
|
catch {
|
|
865
1456
|
throw new McpError(ErrorCode.InvalidParams, `Invalid run_id "${runId}". Must match [A-Za-z0-9_-]{1,64}.`);
|
|
866
1457
|
}
|
|
867
|
-
|
|
1458
|
+
// Plan-review terminal state is authoritative even while the detached run's
|
|
1459
|
+
// finally still owns the in-process mutex. Read it before duplicate-run
|
|
1460
|
+
// arbitration so terminal-before-cleanup never leaks as "already running".
|
|
1461
|
+
// Any read/validation error is retried by WorkflowStateStore.resume under the
|
|
1462
|
+
// run lock, preserving the existing error surface for corrupt generic state.
|
|
1463
|
+
let persistedBeforeMutex;
|
|
1464
|
+
try {
|
|
1465
|
+
persistedBeforeMutex = await readRunStateForStatus(runId);
|
|
1466
|
+
}
|
|
1467
|
+
catch {
|
|
1468
|
+
persistedBeforeMutex = null;
|
|
1469
|
+
}
|
|
1470
|
+
const persistedState = persistedBeforeMutex?.state;
|
|
1471
|
+
if (persistedState != null &&
|
|
1472
|
+
isPlanReviewRunState(persistedState) &&
|
|
1473
|
+
(persistedState.status === "failed" ||
|
|
1474
|
+
persistedState.status === "cancelled" ||
|
|
1475
|
+
persistedState.status === "succeeded")) {
|
|
1476
|
+
const error = new Error(`Run ${runId} is terminal (${persistedState.status}) and cannot be resumed; start a new run instead.`);
|
|
1477
|
+
return {
|
|
1478
|
+
content: finalizeWorkflowPresentation({
|
|
1479
|
+
outputText: formatWorkflowError(runId, error, null),
|
|
1480
|
+
receipt,
|
|
1481
|
+
debug,
|
|
1482
|
+
summary: undefined,
|
|
1483
|
+
debugBlock: undefined,
|
|
1484
|
+
}),
|
|
1485
|
+
isError: true,
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
acquireMutex(runId, options);
|
|
868
1489
|
let store = null;
|
|
869
1490
|
let backend = null;
|
|
870
1491
|
let reporter;
|
|
1492
|
+
let shutdownHandle;
|
|
1493
|
+
let releaseLifecycleBinding;
|
|
871
1494
|
try {
|
|
872
1495
|
const resumeOptions = {
|
|
873
1496
|
runId,
|
|
874
1497
|
debugKeepArtifacts: debug,
|
|
875
1498
|
};
|
|
876
1499
|
store = await WorkflowStateStore.resume(resumeOptions);
|
|
1500
|
+
releaseLifecycleBinding = options.contextBuilderLifecycleRouter?.bind(runId, store);
|
|
877
1501
|
const validatorRegistry = createValidatorRegistry();
|
|
878
|
-
|
|
1502
|
+
const initialContextPolicy = store.runState.initialContextPolicy ??
|
|
1503
|
+
reconstructLegacyInitialContextPolicy(store.runState, options.defaultCurator);
|
|
1504
|
+
// A-5 resume fidelity: the persisted contract + snapshot are the SOLE
|
|
1505
|
+
// inputs — `plan_file` is never re-read and caller input is never
|
|
1506
|
+
// re-parsed. Decode + hash-verify + RECOMPUTE before the engine exists, so
|
|
1507
|
+
// a corrupt contract fails post-state/pre-seat rather than dispatching
|
|
1508
|
+
// seats under unverified (or absent) per-call bindings.
|
|
1509
|
+
// Typed detection is by PERSISTED plan-review state presence, not the
|
|
1510
|
+
// definition name alone: legacy runs persisted under the pre-rename name
|
|
1511
|
+
// "council-plan-review" (which now names the GENERIC plan-review council)
|
|
1512
|
+
// must keep routing through the typed restore path, while a new generic
|
|
1513
|
+
// council-plan-review run (no plan_review_state) must never enter it. A
|
|
1514
|
+
// tampered generic run that smuggles in a plan_review_state subtree fails
|
|
1515
|
+
// closed downstream: restorePlanReviewRun / validatePlanReviewCallBindings
|
|
1516
|
+
// reject any definition whose stages/calls do not match the contract.
|
|
1517
|
+
const persistedPlanReview = store.runState.plan_review_state;
|
|
1518
|
+
let restoredPlanReview;
|
|
1519
|
+
if (store.definition.name === PLAN_REVIEW_RECEIPTS_WORKFLOW &&
|
|
1520
|
+
persistedPlanReview === undefined) {
|
|
1521
|
+
throw new PlanReviewStageError("execution_contract", "Persisted plan-review-receipts run is missing plan_review_state");
|
|
1522
|
+
}
|
|
1523
|
+
if (persistedPlanReview !== undefined &&
|
|
1524
|
+
store.definition.name !== PLAN_REVIEW_RECEIPTS_WORKFLOW &&
|
|
1525
|
+
store.definition.name !== LEGACY_PLAN_REVIEW_RECEIPTS_WORKFLOW_NAME) {
|
|
1526
|
+
throw new PlanReviewStageError("execution_contract", `Persisted workflow "${store.definition.name}" cannot carry plan_review_state`);
|
|
1527
|
+
}
|
|
1528
|
+
if (persistedPlanReview !== undefined) {
|
|
1529
|
+
restoredPlanReview = await restorePlanReviewRun(store.runDir, persistedPlanReview, resolvePlanReviewChairmanCallId(store.definition));
|
|
1530
|
+
const bindingsByCall = validatePlanReviewCallBindings(store.definition, restoredPlanReview.call_bindings);
|
|
1531
|
+
/**
|
|
1532
|
+
* DELIBERATELY the FIRST of two identical checks; `WorkflowEngine`'s
|
|
1533
|
+
* constructor runs the same one. This is not drift.
|
|
1534
|
+
*
|
|
1535
|
+
* The engine's copy is the load-bearing seam: it is the one no caller can
|
|
1536
|
+
* bypass, and it is what a future construction path would inherit. This
|
|
1537
|
+
* copy buys ORDERING — it rejects tampered state before `createBackend` /
|
|
1538
|
+
* `connect`, so a doomed resume never spawns a provider process. The
|
|
1539
|
+
* function is pure and cheap over a handful of prepared inputs, so paying
|
|
1540
|
+
* twice costs nothing measurable and removing EITHER one loses a real
|
|
1541
|
+
* property. If you are here to delete a redundant call: don't.
|
|
1542
|
+
*/
|
|
1543
|
+
validatePlanReviewPreparedInputPolicy(store.resume.preparedInputs ?? new Map(), bindingsByCall, {
|
|
1544
|
+
// From the SAME read that re-verified the snapshot above (D9).
|
|
1545
|
+
snapshotText: restoredPlanReview.snapshot.text,
|
|
1546
|
+
contextPolicy: restoredPlanReview.state.context_policy,
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
// Make this resume attempt durable before its preflight gate. Generic runs
|
|
1550
|
+
// otherwise remain in the prior terminal state, causing markFailed /
|
|
1551
|
+
// markCancelled to suppress a new pre-dispatch outcome as "idempotent".
|
|
1552
|
+
await store.beginResumeAttempt();
|
|
1553
|
+
const sessionLogGeneration = store.sessionLogGeneration ??
|
|
1554
|
+
await inspectLegacyWorkflowSessionLogGeneration(store.runDir, runId);
|
|
1555
|
+
if (signal.aborted)
|
|
1556
|
+
throw new WorkflowCancellationError();
|
|
1557
|
+
if (store.runState.workflow_context === undefined) {
|
|
1558
|
+
const preflightEngine = new WorkflowEngine({
|
|
1559
|
+
definition: store.definition,
|
|
1560
|
+
session: createPreflightOnlyBackend(options.availableCliNames),
|
|
1561
|
+
variables: store.variables,
|
|
1562
|
+
validatorRegistry,
|
|
1563
|
+
runId,
|
|
1564
|
+
runDir: store.runDir,
|
|
1565
|
+
signal,
|
|
1566
|
+
resume: store.resume,
|
|
1567
|
+
contextBuilderRunner: options.contextBuilderRunner,
|
|
1568
|
+
contextDefault: options.contextDefault,
|
|
1569
|
+
defaultCurator: options.defaultCurator,
|
|
1570
|
+
commitPreflightContext: store.commitPreflightContext.bind(store),
|
|
1571
|
+
requireContextPreflight: true,
|
|
1572
|
+
initialContextPolicy,
|
|
1573
|
+
...(restoredPlanReview === undefined
|
|
1574
|
+
? {}
|
|
1575
|
+
: {
|
|
1576
|
+
planReviewContextPolicy: restoredPlanReview.state.context_policy,
|
|
1577
|
+
planReviewCallBindings: restoredPlanReview.call_bindings,
|
|
1578
|
+
planReviewSnapshotText: restoredPlanReview.snapshot.text,
|
|
1579
|
+
commitPlanReviewContextProvenance: store.commitPlanReviewContextProvenance.bind(store),
|
|
1580
|
+
}),
|
|
1581
|
+
});
|
|
1582
|
+
await preflightEngine.prepareContextPreflight(signal);
|
|
1583
|
+
}
|
|
1584
|
+
// Create backend only after the resume context gate has committed or bypassed.
|
|
879
1585
|
backend = options.createBackend(runId);
|
|
880
1586
|
await backend.connect();
|
|
881
1587
|
// Wire up shutdown coordinator
|
|
@@ -888,7 +1594,7 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
888
1594
|
controller.abort(new WorkflowCancellationError());
|
|
889
1595
|
}, { once: true });
|
|
890
1596
|
}
|
|
891
|
-
shutdownCoordinator.register(backend, controller, async () => {
|
|
1597
|
+
shutdownHandle = shutdownCoordinator.register(backend, controller, async () => {
|
|
892
1598
|
await store.markCancelled();
|
|
893
1599
|
});
|
|
894
1600
|
// Build engine hooks with progress reporter
|
|
@@ -922,13 +1628,36 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
922
1628
|
variables: store.variables,
|
|
923
1629
|
validatorRegistry,
|
|
924
1630
|
runId,
|
|
1631
|
+
sessionLogGeneration,
|
|
925
1632
|
runDir: store.runDir,
|
|
926
1633
|
signal: controller.signal,
|
|
927
1634
|
hooks: rpt != null ? hooks : store.hooks,
|
|
928
1635
|
resume: store.resume,
|
|
1636
|
+
contextBuilderRunner: options.contextBuilderRunner,
|
|
1637
|
+
contextDefault: options.contextDefault,
|
|
1638
|
+
defaultCurator: options.defaultCurator,
|
|
1639
|
+
commitPreflightContext: store.commitPreflightContext?.bind(store),
|
|
1640
|
+
restoredWorkflowContext: store.runState.workflow_context,
|
|
1641
|
+
requireContextPreflight: false,
|
|
1642
|
+
initialContextPolicy,
|
|
1643
|
+
// A-7: replay the SAME enforced exclusion on resume, reconstructed from
|
|
1644
|
+
// persisted state rather than re-derived from caller variables.
|
|
1645
|
+
...(restoredPlanReview !== undefined
|
|
1646
|
+
? {
|
|
1647
|
+
planReviewContextPolicy: restoredPlanReview.state.context_policy,
|
|
1648
|
+
planReviewCallBindings: restoredPlanReview.call_bindings,
|
|
1649
|
+
planReviewSnapshotText: restoredPlanReview.snapshot.text,
|
|
1650
|
+
commitPlanReviewContextProvenance: store.commitPlanReviewContextProvenance?.bind(store),
|
|
1651
|
+
terminalResultPolicy: {
|
|
1652
|
+
kind: "plan_review_artifacts",
|
|
1653
|
+
project: store.projectPlanReviewTerminalResult.bind(store),
|
|
1654
|
+
},
|
|
1655
|
+
}
|
|
1656
|
+
: {}),
|
|
929
1657
|
});
|
|
930
1658
|
const result = await engine.execute();
|
|
931
|
-
|
|
1659
|
+
if (shutdownHandle !== undefined)
|
|
1660
|
+
shutdownCoordinator.unregister(shutdownHandle);
|
|
932
1661
|
// Build summary (fail-open)
|
|
933
1662
|
let summary;
|
|
934
1663
|
if (receipt) {
|
|
@@ -950,13 +1679,24 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
950
1679
|
return { content };
|
|
951
1680
|
}
|
|
952
1681
|
catch (error) {
|
|
953
|
-
|
|
1682
|
+
if (shutdownHandle !== undefined)
|
|
1683
|
+
shutdownCoordinator.unregister(shutdownHandle);
|
|
954
1684
|
if (error instanceof WorkflowCancellationError) {
|
|
955
1685
|
if (store != null) {
|
|
956
1686
|
await store.markCancelled();
|
|
957
1687
|
}
|
|
958
1688
|
throw error;
|
|
959
1689
|
}
|
|
1690
|
+
// Persist a terminal "failed" record for post-admission setup failures.
|
|
1691
|
+
// Idempotent: won't clobber an engine-emitted terminal status.
|
|
1692
|
+
if (store != null) {
|
|
1693
|
+
try {
|
|
1694
|
+
await markWorkflowFailed(store, runId, error);
|
|
1695
|
+
}
|
|
1696
|
+
catch (err) {
|
|
1697
|
+
logger.warn({ err }, "markFailed during error handling failed");
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
960
1700
|
const errorText = formatWorkflowError(runId, error, store);
|
|
961
1701
|
// Build failure summary (fail-open)
|
|
962
1702
|
let failureSummary;
|
|
@@ -978,6 +1718,7 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
978
1718
|
return { content, isError: true };
|
|
979
1719
|
}
|
|
980
1720
|
finally {
|
|
1721
|
+
releaseLifecycleBinding?.();
|
|
981
1722
|
reporter?.stop();
|
|
982
1723
|
try {
|
|
983
1724
|
if (backend != null && !backend.isTerminated) {
|
|
@@ -995,28 +1736,435 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
995
1736
|
catch (cleanupErr) {
|
|
996
1737
|
logger.warn({ err: cleanupErr }, "store.close() failed during cleanup");
|
|
997
1738
|
}
|
|
998
|
-
releaseMutex();
|
|
1739
|
+
releaseMutex(runId);
|
|
999
1740
|
}
|
|
1000
1741
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1742
|
+
// ---------------------------------------------------------------------------
|
|
1743
|
+
// Sidecar access (atomic, fd-based, cheap-poll-safe)
|
|
1744
|
+
// ---------------------------------------------------------------------------
|
|
1745
|
+
/**
|
|
1746
|
+
* Thrown for genuine containment-policy violations (absolute outputPath or an
|
|
1747
|
+
* escape past the run directory). These MUST reject even on a cheap default
|
|
1748
|
+
* poll. Everything else (missing file, fs race, permission) is swallowed on the
|
|
1749
|
+
* cheap path so liveness polling never throws for a recoverable condition.
|
|
1750
|
+
*
|
|
1751
|
+
* Alias of the shared reader's policy error so the existing `instanceof`
|
|
1752
|
+
* cheap-poll discrimination below is unchanged.
|
|
1753
|
+
*/
|
|
1754
|
+
const SidecarPolicyError = ContainedFilePolicyError;
|
|
1755
|
+
/**
|
|
1756
|
+
* Read a run sidecar through the shared containment-safe, no-follow reader.
|
|
1757
|
+
*
|
|
1758
|
+
* The reader is shared (not re-implemented) with the plan-review snapshot and
|
|
1759
|
+
* contract paths: one audited realpath + `O_NOFOLLOW` + single-handle
|
|
1760
|
+
* implementation, so a fix to the TOCTOU handling lands everywhere at once.
|
|
1761
|
+
*/
|
|
1762
|
+
async function readContainedSidecar(runDir, outputPath, wantContent) {
|
|
1763
|
+
const read = await readContainedRelativeFile(runDir, outputPath, {
|
|
1764
|
+
wantContent,
|
|
1765
|
+
// Preserve this path's established error wording byte-for-byte.
|
|
1766
|
+
containerLabel: "run directory",
|
|
1767
|
+
pathLabel: "outputPath",
|
|
1768
|
+
});
|
|
1769
|
+
return read.content !== undefined
|
|
1770
|
+
? { size: read.size, content: read.content }
|
|
1771
|
+
: { size: read.size };
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Legacy state-v1 runs did not persist their admission-time default. Rebuild
|
|
1775
|
+
* only caller-explicit choices; otherwise require deterministic preflight so a
|
|
1776
|
+
* changed server default cannot silently introduce or switch a delegate.
|
|
1777
|
+
*/
|
|
1778
|
+
export function reconstructLegacyInitialContextPolicy(state, defaultCurator) {
|
|
1779
|
+
const persistedMode = state.workflow_context?.context_builder.context_mode;
|
|
1780
|
+
if (persistedMode === "disabled") {
|
|
1781
|
+
return {
|
|
1782
|
+
requestedMode: "off",
|
|
1783
|
+
effectiveMode: "disabled",
|
|
1784
|
+
preflightDisabled: true,
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
const legacyVariables = state.variables ?? {};
|
|
1788
|
+
const callerMode = legacyVariables["cb_context_mode"];
|
|
1789
|
+
const normalizedMode = typeof callerMode === "string" ? callerMode.trim() : "";
|
|
1790
|
+
if (normalizedMode === "off" || normalizedMode === "deterministic") {
|
|
1791
|
+
return {
|
|
1792
|
+
requestedMode: normalizedMode,
|
|
1793
|
+
effectiveMode: normalizedMode,
|
|
1794
|
+
preflightDisabled: false,
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
if (normalizedMode === "full" && legacyVariables["cb_delegate"] != null) {
|
|
1798
|
+
try {
|
|
1799
|
+
return resolveWorkflowContextMode("full", "deterministic", true, legacyVariables["cb_delegate"], defaultCurator);
|
|
1800
|
+
}
|
|
1801
|
+
catch {
|
|
1802
|
+
// Legacy hard-disabled runs may contain a delegate value that was never
|
|
1803
|
+
// validated. Do not turn it into a new resume-time fatal or provider.
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
return {
|
|
1807
|
+
requestedMode: normalizedMode === "full" || persistedMode === "full"
|
|
1808
|
+
? "full"
|
|
1809
|
+
: "deterministic",
|
|
1810
|
+
effectiveMode: "deterministic",
|
|
1811
|
+
preflightDisabled: false,
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Best-effort read-time parse of the persisted context-builder lifecycle
|
|
1816
|
+
* snapshot. Status projection is observability-only and must never throw for
|
|
1817
|
+
* a malformed or absent snapshot — invalid input yields undefined and the
|
|
1818
|
+
* dependent health/context fields are simply omitted.
|
|
1819
|
+
*/
|
|
1820
|
+
function parseLifecycleForStatus(value) {
|
|
1821
|
+
if (value == null)
|
|
1822
|
+
return undefined;
|
|
1823
|
+
try {
|
|
1824
|
+
return parseContextBuilderLifecycleStateV1(value);
|
|
1825
|
+
}
|
|
1826
|
+
catch {
|
|
1827
|
+
return undefined;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
async function executeWorkflowRunStatus(_options, args) {
|
|
1831
|
+
const runId = args["run_id"];
|
|
1832
|
+
const includeResult = args["include_result"] === true;
|
|
1833
|
+
if (typeof runId !== "string" || runId === "") {
|
|
1834
|
+
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid "run_id" parameter');
|
|
1835
|
+
}
|
|
1836
|
+
try {
|
|
1837
|
+
validateRunId(runId);
|
|
1838
|
+
}
|
|
1839
|
+
catch {
|
|
1840
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid run_id "${runId}". Must match [A-Za-z0-9_-]{1,64}.`);
|
|
1841
|
+
}
|
|
1842
|
+
const read = await readRunStateForStatus(runId);
|
|
1843
|
+
if (read == null) {
|
|
1844
|
+
return {
|
|
1845
|
+
content: [{ type: "text", text: JSON.stringify({ status: "not_found", run_id: runId }) }],
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
const { state, lock } = read;
|
|
1849
|
+
const status = (state.status === "running" || state.status === "pending") && lock === "stale_or_absent"
|
|
1850
|
+
? "orphaned"
|
|
1851
|
+
: state.status;
|
|
1852
|
+
const runDir = getRunStateDir(runId);
|
|
1853
|
+
const stages = state.stages.map((stage) => {
|
|
1854
|
+
const completedCalls = stage.calls.filter((call) => call.state === "succeeded").length;
|
|
1855
|
+
const runningCalls = stage.calls.filter((call) => call.state === "running").length;
|
|
1856
|
+
const failedCalls = stage.calls.filter((call) => call.state === "failed").length;
|
|
1857
|
+
return {
|
|
1858
|
+
stage_id: stage.stageId,
|
|
1859
|
+
state: stage.state,
|
|
1860
|
+
total_calls: stage.calls.length,
|
|
1861
|
+
completed_calls: completedCalls,
|
|
1862
|
+
running_calls: runningCalls,
|
|
1863
|
+
failed_calls: failedCalls,
|
|
1864
|
+
calls: stage.calls.map((call) => ({
|
|
1865
|
+
call_id: call.callId,
|
|
1866
|
+
state: call.state,
|
|
1867
|
+
retry_count: call.retryCount,
|
|
1868
|
+
...(call.startedAt != null ? { started_at: call.startedAt } : {}),
|
|
1869
|
+
...(call.completedAt != null ? { completed_at: call.completedAt } : {}),
|
|
1870
|
+
...(call.durationMs != null ? { duration_ms: call.durationMs } : {}),
|
|
1871
|
+
...(call.errorMessage != null ? { error: call.errorMessage } : {}),
|
|
1872
|
+
})),
|
|
1873
|
+
};
|
|
1874
|
+
});
|
|
1875
|
+
const projection = {
|
|
1876
|
+
run_id: state.runId,
|
|
1877
|
+
status,
|
|
1878
|
+
async: state.async ?? false,
|
|
1879
|
+
workflow: {
|
|
1880
|
+
name: state.workflowName,
|
|
1881
|
+
version: state.workflowVersion,
|
|
1882
|
+
},
|
|
1883
|
+
...(state.startedAt != null ? { started_at: state.startedAt } : {}),
|
|
1884
|
+
...(state.completedAt != null ? { completed_at: state.completedAt } : {}),
|
|
1885
|
+
...(lock === "unknown" ? { lock_ownership: "unknown" } : {}),
|
|
1886
|
+
stages,
|
|
1887
|
+
};
|
|
1888
|
+
// artifacts_dir is cheap (pure join, no I/O) and present for every found run.
|
|
1889
|
+
projection["artifacts_dir"] = join(runDir, "artifacts");
|
|
1890
|
+
// Read-only context-selection observability from already-persisted state.
|
|
1891
|
+
// No file I/O — only projects state.workflow_context.context_builder and
|
|
1892
|
+
// the state.context_builder_lifecycle snapshot.
|
|
1893
|
+
const lifecycle = parseLifecycleForStatus(state.context_builder_lifecycle);
|
|
1894
|
+
const cb = state.workflow_context?.context_builder;
|
|
1895
|
+
if (cb != null) {
|
|
1896
|
+
const context = {
|
|
1897
|
+
...(cb.context_mode != null ? { context_mode: cb.context_mode } : {}),
|
|
1898
|
+
status: cb.status,
|
|
1899
|
+
files_selected: cb.metrics.files_selected,
|
|
1900
|
+
};
|
|
1901
|
+
if (cb.delegate != null) {
|
|
1902
|
+
context["delegate"] = {
|
|
1903
|
+
status: cb.delegate.status,
|
|
1904
|
+
fallback_used: cb.delegate.fallback_used,
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
if (lifecycle != null) {
|
|
1908
|
+
// Additive lifecycle observability. `coverage.missing` is a bounded
|
|
1909
|
+
// integer count in the persisted schema (never a list), so it is
|
|
1910
|
+
// projected as-is with no truncation concern.
|
|
1911
|
+
context["lifecycle_status"] = lifecycle.status;
|
|
1912
|
+
context["coverage"] = {
|
|
1913
|
+
policy: lifecycle.coverage.policy,
|
|
1914
|
+
status: lifecycle.coverage.status,
|
|
1915
|
+
required: lifecycle.coverage.required,
|
|
1916
|
+
selected: lifecycle.coverage.selected,
|
|
1917
|
+
missing: lifecycle.coverage.missing,
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
projection["context"] = context;
|
|
1921
|
+
}
|
|
1922
|
+
// F7 read-time health projection: top-level run status alone overstates
|
|
1923
|
+
// health when on_failure:continue swallowed seat failures or context
|
|
1924
|
+
// preflight completed partial/degraded. Computed only from already-persisted
|
|
1925
|
+
// data; additive and advisory — never mutates status semantics.
|
|
1926
|
+
const healthReasons = [];
|
|
1927
|
+
if (status === "succeeded" && stages.some((stage) => stage.failed_calls > 0)) {
|
|
1928
|
+
healthReasons.push("failed_calls_in_succeeded_run");
|
|
1929
|
+
}
|
|
1930
|
+
if (lifecycle?.status === "partial") {
|
|
1931
|
+
healthReasons.push("context_partial");
|
|
1932
|
+
}
|
|
1933
|
+
if (lifecycle?.status === "failed") {
|
|
1934
|
+
healthReasons.push("context_lifecycle_failed");
|
|
1935
|
+
}
|
|
1936
|
+
if (lifecycle?.coverage.status === "degraded") {
|
|
1937
|
+
healthReasons.push("coverage_degraded");
|
|
1938
|
+
}
|
|
1939
|
+
if (cb?.delegate?.fallback_used === true) {
|
|
1940
|
+
healthReasons.push("delegate_fallback_used");
|
|
1941
|
+
}
|
|
1942
|
+
projection["health"] = {
|
|
1943
|
+
classification: healthReasons.length > 0 ? "degraded" : "ok",
|
|
1944
|
+
reasons: healthReasons,
|
|
1945
|
+
};
|
|
1946
|
+
if (isPlanReviewRunState(state)) {
|
|
1947
|
+
const terminalExecutionStatus = state.status === "succeeded" ||
|
|
1948
|
+
state.status === "failed" ||
|
|
1949
|
+
state.status === "cancelled"
|
|
1950
|
+
? state.status
|
|
1951
|
+
: undefined;
|
|
1952
|
+
const publication = state.plan_review_state?.publication;
|
|
1953
|
+
const inspected = await inspectPlanReviewPublication(runDir, runId, terminalExecutionStatus ??
|
|
1954
|
+
(includeResult && publication?.committed === true ? "failed" : undefined), publication, includeResult);
|
|
1955
|
+
projection["result_available"] = inspected.result_available;
|
|
1956
|
+
projection["result_bytes"] = inspected.result_bytes;
|
|
1957
|
+
if (inspected.result !== undefined) {
|
|
1958
|
+
projection["result"] = inspected.result;
|
|
1959
|
+
}
|
|
1960
|
+
let planReviewError = (includeResult && publication?.committed === true ? inspected.error : undefined) ??
|
|
1961
|
+
state.plan_review_error ??
|
|
1962
|
+
inspected.error;
|
|
1963
|
+
if (planReviewError === undefined &&
|
|
1964
|
+
publication === undefined &&
|
|
1965
|
+
state.plan_review_state === undefined &&
|
|
1966
|
+
(state.status === "succeeded" || state.status === "failed")) {
|
|
1967
|
+
planReviewError = createPlanReviewPublicationError(runId, state.status, "no_artifact", "Historical plan-review run has no publication marker; rerun with a Track-C-capable ClinkX runtime.");
|
|
1968
|
+
}
|
|
1969
|
+
if (planReviewError !== undefined) {
|
|
1970
|
+
projection["plan_review_error"] = planReviewError;
|
|
1971
|
+
}
|
|
1972
|
+
if (state.status === "failed" || state.status === "cancelled") {
|
|
1973
|
+
if (state.error != null) {
|
|
1974
|
+
projection["error"] = state.error;
|
|
1975
|
+
}
|
|
1976
|
+
if (state.failureClass != null) {
|
|
1977
|
+
projection["failure_class"] = state.failureClass;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
else {
|
|
1982
|
+
projection["result_available"] = state.status === "succeeded";
|
|
1983
|
+
if (state.status === "succeeded") {
|
|
1984
|
+
const final = state.stages.at(-1);
|
|
1985
|
+
const call = final?.calls[0];
|
|
1986
|
+
// Cheap size hint without reading contents. Containment (via
|
|
1987
|
+
// readContainedSidecar) runs even on a default poll so an absolute path or
|
|
1988
|
+
// symlink escape is rejected regardless of include_result. Everything else
|
|
1989
|
+
// — a missing final call, a missing/unreadable file, an fs race — omits the
|
|
1990
|
+
// hint rather than throwing, preserving the "a cheap poll never throws for a
|
|
1991
|
+
// recoverable/missing-file condition" invariant that liveness polling needs.
|
|
1992
|
+
if (call?.outputInline != null) {
|
|
1993
|
+
projection["result_bytes"] = Buffer.byteLength(call.outputInline, "utf-8");
|
|
1994
|
+
}
|
|
1995
|
+
else if (!includeResult && call?.outputPath != null) {
|
|
1996
|
+
// Skip this size-only open when include_result is set — the block below
|
|
1997
|
+
// opens the sidecar once for both size and content, so a second open here
|
|
1998
|
+
// would be redundant.
|
|
1999
|
+
try {
|
|
2000
|
+
const { size } = await readContainedSidecar(runDir, call.outputPath, false);
|
|
2001
|
+
projection["result_bytes"] = size;
|
|
2002
|
+
}
|
|
2003
|
+
catch (err) {
|
|
2004
|
+
if (err instanceof SidecarPolicyError) {
|
|
2005
|
+
// Absolute path / containment escape rejects even on a cheap poll.
|
|
2006
|
+
throw new Error(`Succeeded run ${runId} ${err.message}`);
|
|
2007
|
+
}
|
|
2008
|
+
// Missing file / fs race / permission on a cheap poll: omit the hint.
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
if (includeResult) {
|
|
2012
|
+
if (call == null) {
|
|
2013
|
+
throw new Error(`Succeeded run ${runId} is missing final call state`);
|
|
2014
|
+
}
|
|
2015
|
+
if (call.outputInline != null) {
|
|
2016
|
+
projection["result"] = call.outputInline;
|
|
2017
|
+
}
|
|
2018
|
+
else if (call.outputPath != null) {
|
|
2019
|
+
try {
|
|
2020
|
+
const { size, content } = await readContainedSidecar(runDir, call.outputPath, true);
|
|
2021
|
+
projection["result_bytes"] = size;
|
|
2022
|
+
projection["result"] = content;
|
|
2023
|
+
}
|
|
2024
|
+
catch (err) {
|
|
2025
|
+
if (err instanceof SidecarPolicyError) {
|
|
2026
|
+
throw new Error(`Succeeded run ${runId} ${err.message}`);
|
|
2027
|
+
}
|
|
2028
|
+
throw new Error(`Succeeded run ${runId} output file is missing or unreadable`);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
else {
|
|
2032
|
+
throw new Error(`Succeeded run ${runId} final call has neither inline nor persisted output`);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
else if (state.status === "failed" || state.status === "cancelled") {
|
|
2037
|
+
if (state.error != null) {
|
|
2038
|
+
projection["error"] = state.error;
|
|
2039
|
+
}
|
|
2040
|
+
if (state.failureClass != null) {
|
|
2041
|
+
projection["failure_class"] = state.failureClass;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
return {
|
|
2046
|
+
content: [{ type: "text", text: JSON.stringify(projection) }],
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
async function executeCancelWorkflow(_options, args) {
|
|
2050
|
+
const runId = args["run_id"];
|
|
2051
|
+
if (typeof runId !== "string" || runId === "") {
|
|
2052
|
+
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid "run_id" parameter');
|
|
2053
|
+
}
|
|
2054
|
+
try {
|
|
2055
|
+
validateRunId(runId);
|
|
2056
|
+
}
|
|
2057
|
+
catch {
|
|
2058
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid run_id "${runId}". Must match [A-Za-z0-9_-]{1,64}.`);
|
|
2059
|
+
}
|
|
2060
|
+
const text = (value) => ({
|
|
2061
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
2062
|
+
});
|
|
2063
|
+
// 1) Live in-process async run: abort its controller and let the run's own
|
|
2064
|
+
// unwind drive it to terminal "cancelled". We do NOT touch the mutex here —
|
|
2065
|
+
// the detached run's finally owns the slot (touching it risks a double-free).
|
|
2066
|
+
const controller = cancelRegistry.get(runId);
|
|
2067
|
+
if (controller != null) {
|
|
2068
|
+
// The engine can have already persisted a terminal status while its
|
|
2069
|
+
// controller is still in the registry (deleted only in the run's finally).
|
|
2070
|
+
// Re-read first so we never claim "cancelling" for an already-done run.
|
|
2071
|
+
const live = await readRunStateForStatus(runId);
|
|
2072
|
+
if (live != null && live.state.status !== "running" && live.state.status !== "pending") {
|
|
2073
|
+
return text({ run_id: runId, status: live.state.status, already_terminal: true });
|
|
2074
|
+
}
|
|
2075
|
+
if (!controller.signal.aborted) {
|
|
2076
|
+
controller.abort(new WorkflowCancellationError());
|
|
2077
|
+
}
|
|
2078
|
+
return text({ run_id: runId, status: "cancelling" });
|
|
2079
|
+
}
|
|
2080
|
+
// 2) Not in our registry — inspect persisted state via the same path as status.
|
|
2081
|
+
const read = await readRunStateForStatus(runId);
|
|
2082
|
+
if (read == null) {
|
|
2083
|
+
return text({ run_id: runId, status: "not_found" });
|
|
2084
|
+
}
|
|
2085
|
+
const { state, lock } = read;
|
|
2086
|
+
if (state.status !== "running" && state.status !== "pending") {
|
|
2087
|
+
return text({ run_id: runId, status: state.status, already_terminal: true });
|
|
2088
|
+
}
|
|
2089
|
+
if (lock === "unknown") {
|
|
2090
|
+
return text({
|
|
2091
|
+
run_id: runId,
|
|
2092
|
+
status: state.status,
|
|
2093
|
+
cancellable: false,
|
|
2094
|
+
lock_ownership: "unknown",
|
|
2095
|
+
error: `Run lock ownership is unknown for run_id "${runId}"; refusing cancellation until the lock record is repaired.`,
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
// running|pending and not in our registry. The run.lock is the single source
|
|
2099
|
+
// of truth for ownership: reapRunCancelled acquires it itself (reclaiming a
|
|
2100
|
+
// truly stale/dead lock, refusing a live one), closing the TOCTOU where a
|
|
2101
|
+
// concurrent resume_workflow could grab the lock between read and reap.
|
|
2102
|
+
const outcome = await reapRunCancelled(runId);
|
|
2103
|
+
if (outcome === "cancelled") {
|
|
2104
|
+
return text({ run_id: runId, status: "cancelled", reaped: true });
|
|
2105
|
+
}
|
|
2106
|
+
if (outcome === "locked") {
|
|
2107
|
+
// The run is owned by another live worker (or a blocking run): the lock
|
|
2108
|
+
// could not be acquired. Refuse without mutating — this process cannot
|
|
2109
|
+
// safely kill a worker it does not own.
|
|
2110
|
+
return text({ run_id: runId, status: "running", cancellable: false });
|
|
2111
|
+
}
|
|
2112
|
+
if (outcome === "ownership_unknown") {
|
|
2113
|
+
return text({
|
|
2114
|
+
run_id: runId,
|
|
2115
|
+
status: state.status,
|
|
2116
|
+
cancellable: false,
|
|
2117
|
+
lock_ownership: "unknown",
|
|
2118
|
+
error: `Run lock ownership became unknown for run_id "${runId}"; refusing cancellation until the lock record is repaired.`,
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
// "skipped": became terminal between our read and the reap. Re-read and report.
|
|
2122
|
+
const reread = await readRunStateForStatus(runId);
|
|
2123
|
+
const status = reread?.state.status ?? "cancelled";
|
|
2124
|
+
return text({ run_id: runId, status, already_terminal: true });
|
|
2125
|
+
}
|
|
2126
|
+
function executeListWorkflows(options) {
|
|
2127
|
+
const workflows = listWorkflowCatalog(options.workflowsPath);
|
|
2128
|
+
const preflightEnabled = options.contextBuilderRunner != null;
|
|
2129
|
+
const contextDefault = preflightEnabled
|
|
2130
|
+
? options.contextDefault ?? getWorkflowsContextDefault()
|
|
2131
|
+
: options.contextDefault ?? "off";
|
|
2132
|
+
const effectiveContextDefault = resolveWorkflowContextMode(undefined, contextDefault, preflightEnabled, undefined, options.defaultCurator).effectiveMode;
|
|
1003
2133
|
const entries = [];
|
|
1004
2134
|
for (const [name, entry] of workflows) {
|
|
1005
2135
|
const def = entry.definition;
|
|
2136
|
+
const contextStrategy = deriveContextStrategy(def);
|
|
2137
|
+
const runtimeMembers = supportsRuntimeMembers(name);
|
|
1006
2138
|
entries.push({
|
|
1007
2139
|
name,
|
|
1008
2140
|
description: def.description,
|
|
1009
2141
|
version: def.version,
|
|
1010
2142
|
source: entry.source,
|
|
1011
2143
|
shadowed_yaml: entry.shadowedYaml,
|
|
1012
|
-
supports_members:
|
|
1013
|
-
supports_chairman:
|
|
1014
|
-
|
|
2144
|
+
supports_members: runtimeMembers,
|
|
2145
|
+
supports_chairman: runtimeMembers,
|
|
2146
|
+
supports_context_preflight: contextStrategy.supports_context_preflight,
|
|
2147
|
+
effective_context_default: effectiveContextDefault,
|
|
2148
|
+
fails_on_empty_selection: contextStrategy.fails_on_empty_selection,
|
|
2149
|
+
context_anchor_variables: contextStrategy.context_anchor_variables,
|
|
2150
|
+
context_override_variables: contextStrategy.context_override_variables,
|
|
2151
|
+
...(contextStrategy.default_context_profile !== undefined
|
|
2152
|
+
? { default_context_profile: contextStrategy.default_context_profile }
|
|
2153
|
+
: {}),
|
|
2154
|
+
context_recipient_stages: contextStrategy.context_recipient_stages,
|
|
2155
|
+
...(runtimeMembers ? {
|
|
1015
2156
|
members_min: 1,
|
|
1016
2157
|
members_max: 10,
|
|
1017
2158
|
members_stance_required_from_index: 3,
|
|
1018
2159
|
} : {}),
|
|
1019
2160
|
...(entry.path != null ? { path: basename(entry.path) } : {}),
|
|
2161
|
+
...(name === PLAN_REVIEW_RECEIPTS_WORKFLOW
|
|
2162
|
+
? {
|
|
2163
|
+
plan_review: buildCouncilPlanReviewDiscovery(options.planReviewAvailableCliNames ??
|
|
2164
|
+
options.availableCliNames ??
|
|
2165
|
+
[]),
|
|
2166
|
+
}
|
|
2167
|
+
: {}),
|
|
1020
2168
|
variables: Object.fromEntries(Object.entries(def.variables).map(([varName, varDef]) => [
|
|
1021
2169
|
varName,
|
|
1022
2170
|
{
|
|
@@ -1029,8 +2177,8 @@ function executeListWorkflows(workflowsPath, availableCliNames) {
|
|
|
1029
2177
|
});
|
|
1030
2178
|
}
|
|
1031
2179
|
const response = { workflows: entries };
|
|
1032
|
-
if (availableCliNames != null && availableCliNames.length > 0) {
|
|
1033
|
-
response["available_cli_names"] = [...availableCliNames].sort();
|
|
2180
|
+
if (options.availableCliNames != null && options.availableCliNames.length > 0) {
|
|
2181
|
+
response["available_cli_names"] = [...options.availableCliNames].sort();
|
|
1034
2182
|
}
|
|
1035
2183
|
return {
|
|
1036
2184
|
content: [
|
|
@@ -1041,10 +2189,137 @@ function executeListWorkflows(workflowsPath, availableCliNames) {
|
|
|
1041
2189
|
],
|
|
1042
2190
|
};
|
|
1043
2191
|
}
|
|
2192
|
+
function includesVar(from, name) {
|
|
2193
|
+
if (typeof from === "string") {
|
|
2194
|
+
return from === name;
|
|
2195
|
+
}
|
|
2196
|
+
return Array.isArray(from) && from.includes(name);
|
|
2197
|
+
}
|
|
2198
|
+
// cb_* variables that tune preflight execution rather than source selection —
|
|
2199
|
+
// overrides, not anchors. Advertised separately as context_override_variables.
|
|
2200
|
+
const CONTEXT_OVERRIDE_VARIABLES = new Set([
|
|
2201
|
+
"cb_allow_secrets",
|
|
2202
|
+
"cb_budget_max_estimated_tokens",
|
|
2203
|
+
"cb_budget_max_files",
|
|
2204
|
+
"cb_budget_max_scan_files",
|
|
2205
|
+
"cb_budget_max_bytes_per_file",
|
|
2206
|
+
"cb_budget_max_total_bytes",
|
|
2207
|
+
"cb_budget_max_search_results",
|
|
2208
|
+
"cb_budget_adapter",
|
|
2209
|
+
"cb_delegate",
|
|
2210
|
+
"cb_context_mode",
|
|
2211
|
+
]);
|
|
2212
|
+
function deriveContextStrategy(def) {
|
|
2213
|
+
const contextRecipientStages = def.stages
|
|
2214
|
+
.filter((stage) => stage.calls.some((call) => includesVar(call.absolute_file_paths_from, "context_bundle_files")))
|
|
2215
|
+
.map((stage) => stage.id);
|
|
2216
|
+
const cbVariables = Object.keys(def.variables)
|
|
2217
|
+
.filter((name) => name.startsWith("cb_"))
|
|
2218
|
+
.sort();
|
|
2219
|
+
const contextAnchorVariables = cbVariables.filter((name) => !CONTEXT_OVERRIDE_VARIABLES.has(name));
|
|
2220
|
+
const contextOverrideVariables = cbVariables.filter((name) => CONTEXT_OVERRIDE_VARIABLES.has(name));
|
|
2221
|
+
const defaultContextProfile = def.variables["cb_profile"]?.default;
|
|
2222
|
+
const hasSelectionScopeVariable = "cb_focus_paths" in def.variables || "cb_search_terms" in def.variables;
|
|
2223
|
+
return {
|
|
2224
|
+
supports_context_preflight: "context_bundle_files" in def.variables &&
|
|
2225
|
+
contextRecipientStages.length > 0 &&
|
|
2226
|
+
hasSelectionScopeVariable,
|
|
2227
|
+
context_anchor_variables: contextAnchorVariables,
|
|
2228
|
+
context_override_variables: contextOverrideVariables,
|
|
2229
|
+
...(typeof defaultContextProfile === "string"
|
|
2230
|
+
? { default_context_profile: defaultContextProfile }
|
|
2231
|
+
: {}),
|
|
2232
|
+
context_recipient_stages: contextRecipientStages,
|
|
2233
|
+
fails_on_empty_selection: def.settings.fail_on_empty_context_selection,
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
1044
2236
|
// ---------------------------------------------------------------------------
|
|
1045
2237
|
// Tool input schemas (JSON Schema)
|
|
1046
2238
|
// ---------------------------------------------------------------------------
|
|
1047
2239
|
const MAX_INLINE_WORKFLOW_DETAILS = 8;
|
|
2240
|
+
const PLAN_REVIEW_INPUT_SCHEMA = {
|
|
2241
|
+
type: "object",
|
|
2242
|
+
description: 'Immutable plan-review contract. Accepted only when workflow is "plan-review-receipts".',
|
|
2243
|
+
properties: {
|
|
2244
|
+
schema_version: { type: "string", const: "council-plan-review-input-v1" },
|
|
2245
|
+
workspace_schema: { type: "string", const: "plan-workspace-v1" },
|
|
2246
|
+
digest_profile: { type: "string", const: "checkpoint-text-v1" },
|
|
2247
|
+
review_id: {
|
|
2248
|
+
type: "string",
|
|
2249
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$",
|
|
2250
|
+
},
|
|
2251
|
+
repository_id: {
|
|
2252
|
+
type: "string",
|
|
2253
|
+
maxLength: 128,
|
|
2254
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._-]*(?:/[A-Za-z0-9][A-Za-z0-9._-]*)*$",
|
|
2255
|
+
},
|
|
2256
|
+
iteration: { type: "integer", minimum: 1 },
|
|
2257
|
+
plan_id: {
|
|
2258
|
+
type: "string",
|
|
2259
|
+
pattern: "^[a-z0-9][a-z0-9-]{0,62}$",
|
|
2260
|
+
},
|
|
2261
|
+
revision: { type: "integer", minimum: 1 },
|
|
2262
|
+
digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
2263
|
+
expected_execution_contract_digest: {
|
|
2264
|
+
type: "string",
|
|
2265
|
+
pattern: "^[0-9a-f]{64}$",
|
|
2266
|
+
},
|
|
2267
|
+
threshold: { type: "number", minimum: 0, maximum: 10 },
|
|
2268
|
+
designated_editor: { type: "string", minLength: 1 },
|
|
2269
|
+
chairman_seat: {
|
|
2270
|
+
type: "string",
|
|
2271
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$",
|
|
2272
|
+
},
|
|
2273
|
+
plan_file: {
|
|
2274
|
+
type: "string",
|
|
2275
|
+
minLength: 1,
|
|
2276
|
+
description: "Absolute path to the plan snapshot source",
|
|
2277
|
+
},
|
|
2278
|
+
seats: {
|
|
2279
|
+
type: "array",
|
|
2280
|
+
minItems: 1,
|
|
2281
|
+
maxItems: 10,
|
|
2282
|
+
items: {
|
|
2283
|
+
type: "object",
|
|
2284
|
+
properties: {
|
|
2285
|
+
seat_id: {
|
|
2286
|
+
type: "string",
|
|
2287
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$",
|
|
2288
|
+
},
|
|
2289
|
+
reviewer: { type: "string", minLength: 1 },
|
|
2290
|
+
cli_name: {
|
|
2291
|
+
type: "string",
|
|
2292
|
+
enum: [...PLAN_REVIEW_V1_CLI_NAMES].sort(),
|
|
2293
|
+
},
|
|
2294
|
+
stance: {
|
|
2295
|
+
type: "string",
|
|
2296
|
+
enum: ["strict", "pragmatic", "adversarial"],
|
|
2297
|
+
},
|
|
2298
|
+
},
|
|
2299
|
+
required: ["seat_id", "reviewer", "cli_name", "stance"],
|
|
2300
|
+
additionalProperties: false,
|
|
2301
|
+
},
|
|
2302
|
+
},
|
|
2303
|
+
},
|
|
2304
|
+
required: [
|
|
2305
|
+
"schema_version",
|
|
2306
|
+
"workspace_schema",
|
|
2307
|
+
"digest_profile",
|
|
2308
|
+
"review_id",
|
|
2309
|
+
"repository_id",
|
|
2310
|
+
"iteration",
|
|
2311
|
+
"plan_id",
|
|
2312
|
+
"revision",
|
|
2313
|
+
"digest",
|
|
2314
|
+
"expected_execution_contract_digest",
|
|
2315
|
+
"threshold",
|
|
2316
|
+
"designated_editor",
|
|
2317
|
+
"chairman_seat",
|
|
2318
|
+
"plan_file",
|
|
2319
|
+
"seats",
|
|
2320
|
+
],
|
|
2321
|
+
additionalProperties: false,
|
|
2322
|
+
};
|
|
1048
2323
|
const RUN_WORKFLOW_SCHEMA = {
|
|
1049
2324
|
type: "object",
|
|
1050
2325
|
properties: {
|
|
@@ -1072,9 +2347,14 @@ const RUN_WORKFLOW_SCHEMA = {
|
|
|
1072
2347
|
description: "When true, append a structured receipt summarizing the workflow run as a content block",
|
|
1073
2348
|
default: false,
|
|
1074
2349
|
},
|
|
2350
|
+
async: {
|
|
2351
|
+
type: "boolean",
|
|
2352
|
+
default: false,
|
|
2353
|
+
description: "When true, start the run detached and return {run_id, status:'running'} immediately; poll workflow_run_status for progress and result.",
|
|
2354
|
+
},
|
|
1075
2355
|
members: {
|
|
1076
2356
|
type: "array",
|
|
1077
|
-
description: "Optional runtime council member composition override. Accepted only for council workflows (council-default, council-answer, council-code-review, council-debug, council-discover). Each entry specifies a CLI adapter and an optional analytical stance. Seats at indices 0-2 may omit stance (inherits default); seats at indices 3-9 MUST provide stance. When provided, both the member stage (stage 0) and reviewer stage (stage 1) are expanded to match the specified count and CLI mapping. Reviewer stances are NOT inherited — reviewers keep their default review lens.",
|
|
2357
|
+
description: "Optional runtime council member composition override. Accepted only for council workflows (council-default, council-answer, council-code-review, council-debug, council-discover, council-plan-review). Each entry specifies a CLI adapter and an optional analytical stance. Seats at indices 0-2 may omit stance (inherits default); seats at indices 3-9 MUST provide stance. When provided, both the member stage (stage 0) and reviewer stage (stage 1) are expanded to match the specified count and CLI mapping. Reviewer stances are NOT inherited — reviewers keep their default review lens.",
|
|
1078
2358
|
minItems: 1,
|
|
1079
2359
|
maxItems: 10,
|
|
1080
2360
|
items: {
|
|
@@ -1097,6 +2377,7 @@ const RUN_WORKFLOW_SCHEMA = {
|
|
|
1097
2377
|
type: "string",
|
|
1098
2378
|
description: "Optional CLI adapter name override for the council chairman seat. Accepted only for council workflows. Only changes the LLM adapter; the chairman's role and prompt template are preserved.",
|
|
1099
2379
|
},
|
|
2380
|
+
plan_review: PLAN_REVIEW_INPUT_SCHEMA,
|
|
1100
2381
|
},
|
|
1101
2382
|
required: ["workflow", "run_id"],
|
|
1102
2383
|
additionalProperties: false,
|
|
@@ -1128,8 +2409,35 @@ const LIST_WORKFLOWS_SCHEMA = {
|
|
|
1128
2409
|
properties: {},
|
|
1129
2410
|
additionalProperties: false,
|
|
1130
2411
|
};
|
|
2412
|
+
const WORKFLOW_RUN_STATUS_SCHEMA = {
|
|
2413
|
+
type: "object",
|
|
2414
|
+
properties: {
|
|
2415
|
+
run_id: {
|
|
2416
|
+
type: "string",
|
|
2417
|
+
pattern: "^[A-Za-z0-9_-]{1,64}$",
|
|
2418
|
+
},
|
|
2419
|
+
include_result: {
|
|
2420
|
+
type: "boolean",
|
|
2421
|
+
default: false,
|
|
2422
|
+
description: "When true, include the generic final result for a succeeded run or the committed plan-review envelope regardless of terminal status. Default false keeps polling body-blind.",
|
|
2423
|
+
},
|
|
2424
|
+
},
|
|
2425
|
+
required: ["run_id"],
|
|
2426
|
+
additionalProperties: false,
|
|
2427
|
+
};
|
|
2428
|
+
const CANCEL_WORKFLOW_SCHEMA = {
|
|
2429
|
+
type: "object",
|
|
2430
|
+
properties: {
|
|
2431
|
+
run_id: {
|
|
2432
|
+
type: "string",
|
|
2433
|
+
pattern: "^[A-Za-z0-9_-]{1,64}$",
|
|
2434
|
+
},
|
|
2435
|
+
},
|
|
2436
|
+
required: ["run_id"],
|
|
2437
|
+
additionalProperties: false,
|
|
2438
|
+
};
|
|
1131
2439
|
/**
|
|
1132
|
-
* Return the
|
|
2440
|
+
* Return the workflow tool definitions for tools/list.
|
|
1133
2441
|
*
|
|
1134
2442
|
* Dynamically discovers available workflows to include in the
|
|
1135
2443
|
* run_workflow description. Safe to call repeatedly — no side effects.
|
|
@@ -1170,6 +2478,22 @@ export function getWorkflowToolDefinitions(workflowsPath, availableCliNames) {
|
|
|
1170
2478
|
}
|
|
1171
2479
|
}
|
|
1172
2480
|
}
|
|
2481
|
+
// plan_review.seats[].cli_name enum (intersection with the v1 provider set)
|
|
2482
|
+
const planReview = props["plan_review"];
|
|
2483
|
+
const planReviewProps = planReview?.["properties"];
|
|
2484
|
+
const planReviewSeats = planReviewProps?.["seats"];
|
|
2485
|
+
const planReviewItems = planReviewSeats?.["items"];
|
|
2486
|
+
const planReviewSeatProps = planReviewItems?.["properties"];
|
|
2487
|
+
const planReviewCliNameSet = new Set(PLAN_REVIEW_V1_CLI_NAMES);
|
|
2488
|
+
const supportedPlanReviewCliNames = sorted.filter((name) => planReviewCliNameSet.has(name));
|
|
2489
|
+
if (planReviewSeatProps?.["cli_name"] != null &&
|
|
2490
|
+
supportedPlanReviewCliNames.length > 0) {
|
|
2491
|
+
planReviewSeatProps["cli_name"] = {
|
|
2492
|
+
...planReviewSeatProps["cli_name"],
|
|
2493
|
+
enum: supportedPlanReviewCliNames,
|
|
2494
|
+
description: `Configured v1 plan-review adapters: ${supportedPlanReviewCliNames.join(", ")}`,
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
1173
2497
|
// chairman enum
|
|
1174
2498
|
if (props["chairman"] != null) {
|
|
1175
2499
|
props["chairman"] = {
|
|
@@ -1212,7 +2536,7 @@ export function getWorkflowToolDefinitions(workflowsPath, availableCliNames) {
|
|
|
1212
2536
|
return [
|
|
1213
2537
|
{
|
|
1214
2538
|
name: "run_workflow",
|
|
1215
|
-
description: `Start a new multi-stage workflow execution.${workflowSuffix}\nAccepts optional \`members\` and \`chairman\` parameters for council workflows (council-default, council-answer, council-code-review, council-debug, council-discover) to override member/reviewer composition and chairman CLI adapter at runtime.`,
|
|
2539
|
+
description: `Start a new multi-stage workflow execution.${workflowSuffix}\nAccepts optional \`members\` and \`chairman\` parameters for council workflows (council-default, council-answer, council-code-review, council-debug, council-discover, council-plan-review) to override member/reviewer composition and chairman CLI adapter at runtime.`,
|
|
1216
2540
|
inputSchema: runSchema,
|
|
1217
2541
|
},
|
|
1218
2542
|
{
|
|
@@ -1227,6 +2551,18 @@ export function getWorkflowToolDefinitions(workflowsPath, availableCliNames) {
|
|
|
1227
2551
|
inputSchema: LIST_WORKFLOWS_SCHEMA,
|
|
1228
2552
|
annotations: { readOnlyHint: true },
|
|
1229
2553
|
},
|
|
2554
|
+
{
|
|
2555
|
+
name: "workflow_run_status",
|
|
2556
|
+
description: "Read-only status/progress of a workflow run. The full result is included only with include_result:true. Generic workflows preserve succeeded-final-call behavior; plan-review-receipts reports marker-driven availability and bytes independently of terminal status, with body-blind default polling. Every found run also carries a top-level `health` block {classification: \"ok\"|\"degraded\", reasons: string[]} computed read-time from persisted data — a run can be status:\"succeeded\" yet degraded when on_failure:continue swallowed failed calls or context preflight was partial/degraded (reasons: failed_calls_in_succeeded_run, context_partial, context_lifecycle_failed, coverage_degraded, delegate_fallback_used). When a context-builder lifecycle snapshot is persisted, the `context` block additionally includes `lifecycle_status` and `coverage` {policy, status, required, selected, missing}.",
|
|
2557
|
+
inputSchema: WORKFLOW_RUN_STATUS_SCHEMA,
|
|
2558
|
+
annotations: { readOnlyHint: true },
|
|
2559
|
+
},
|
|
2560
|
+
{
|
|
2561
|
+
name: "cancel_workflow",
|
|
2562
|
+
description: "Cancel a workflow run by run_id. Aborts a live in-process async run (returns status:'cancelling'; poll workflow_run_status until it reaches terminal 'cancelled'). Reaps an orphaned/no-live-worker run to terminal 'cancelled' (reaped:true). Refuses a lock-held run owned by another live worker or a blocking run (cancellable:false). Idempotent; not_found and already_terminal are soft results.",
|
|
2563
|
+
inputSchema: CANCEL_WORKFLOW_SCHEMA,
|
|
2564
|
+
annotations: { destructiveHint: true },
|
|
2565
|
+
},
|
|
1230
2566
|
];
|
|
1231
2567
|
}
|
|
1232
2568
|
/** Names of the workflow tools (for dispatch matching). */
|
|
@@ -1234,6 +2570,8 @@ export const WORKFLOW_TOOL_NAMES = new Set([
|
|
|
1234
2570
|
"run_workflow",
|
|
1235
2571
|
"resume_workflow",
|
|
1236
2572
|
"list_workflows",
|
|
2573
|
+
"workflow_run_status",
|
|
2574
|
+
"cancel_workflow",
|
|
1237
2575
|
]);
|
|
1238
2576
|
/**
|
|
1239
2577
|
* Dispatch a workflow tool call.
|
|
@@ -1248,7 +2586,11 @@ export async function handleWorkflowToolCall(server, options, name, args, signal
|
|
|
1248
2586
|
case "resume_workflow":
|
|
1249
2587
|
return await executeResumeWorkflow(server, options, args, signal, progressToken);
|
|
1250
2588
|
case "list_workflows":
|
|
1251
|
-
return executeListWorkflows(options
|
|
2589
|
+
return executeListWorkflows(options);
|
|
2590
|
+
case "workflow_run_status":
|
|
2591
|
+
return await executeWorkflowRunStatus(options, args);
|
|
2592
|
+
case "cancel_workflow":
|
|
2593
|
+
return await executeCancelWorkflow(options, args);
|
|
1252
2594
|
default:
|
|
1253
2595
|
return null;
|
|
1254
2596
|
}
|
|
@@ -1260,7 +2602,8 @@ export async function handleWorkflowToolCall(server, options, name, args, signal
|
|
|
1260
2602
|
* Register workflow tools on an MCP Server instance.
|
|
1261
2603
|
*
|
|
1262
2604
|
* This is the reusable core extracted from createWorkflowServer().
|
|
1263
|
-
* It registers
|
|
2605
|
+
* It registers five tools: run_workflow, resume_workflow, list_workflows,
|
|
2606
|
+
* workflow_run_status, cancel_workflow.
|
|
1264
2607
|
*
|
|
1265
2608
|
* The `options.createBackend` factory determines how clink calls are
|
|
1266
2609
|
* dispatched — callers provide either a child-process or in-process backend.
|