clinkx 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +72 -0
- package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
- package/clinkx-workflows/dist/chairman-knobs.js +27 -0
- package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +10 -0
- package/clinkx-workflows/dist/clink-client.js +39 -1
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +29 -1
- package/clinkx-workflows/dist/config.js +35 -2
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -1
- package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
- package/clinkx-workflows/dist/council-execution-policy.js +110 -0
- package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +9 -8
- package/clinkx-workflows/dist/council-expander.js +18 -9
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
- package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
- package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
- package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/index.js +4 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
- package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
- package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
- package/clinkx-workflows/dist/delegate-signature.js +14 -0
- package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
- package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
- package/clinkx-workflows/dist/delegate-validate.js +51 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
- package/clinkx-workflows/dist/engine.d.ts +113 -3
- package/clinkx-workflows/dist/engine.js +476 -80
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +22 -2
- package/clinkx-workflows/dist/errors.js +30 -2
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fidelity.d.ts +47 -1
- package/clinkx-workflows/dist/fidelity.js +54 -1
- package/clinkx-workflows/dist/fidelity.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
- package/clinkx-workflows/dist/fingerprint.js +32 -9
- package/clinkx-workflows/dist/fingerprint.js.map +1 -1
- package/clinkx-workflows/dist/index.js +53 -1
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/logger.d.ts +1 -1
- package/clinkx-workflows/dist/logger.js +17 -3
- package/clinkx-workflows/dist/logger.js.map +1 -1
- package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
- package/clinkx-workflows/dist/monotonic-clock.js +22 -0
- package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
- package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
- package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
- package/clinkx-workflows/dist/roster-contract.js +180 -0
- package/clinkx-workflows/dist/roster-contract.js.map +1 -0
- package/clinkx-workflows/dist/rosters.d.ts +14 -0
- package/clinkx-workflows/dist/rosters.js +169 -0
- package/clinkx-workflows/dist/rosters.js.map +1 -0
- package/clinkx-workflows/dist/run-health.d.ts +7 -0
- package/clinkx-workflows/dist/run-health.js +25 -0
- package/clinkx-workflows/dist/run-health.js.map +1 -0
- package/clinkx-workflows/dist/schema.d.ts +64 -0
- package/clinkx-workflows/dist/schema.js +24 -0
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/server.js +2 -0
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +15 -2
- package/clinkx-workflows/dist/shutdown.js +60 -16
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +23 -0
- package/clinkx-workflows/dist/state.d.ts +5 -0
- package/clinkx-workflows/dist/state.js +128 -21
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
- package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
- package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
- package/clinkx-workflows/dist/types.d.ts +6 -0
- package/clinkx-workflows/dist/validators/council.d.ts +50 -50
- package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
- package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
- package/clinkx-workflows/dist/workflow-tools.js +760 -168
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
- package/clinkx-workflows/templates/council/chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
- package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
- package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/member.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/reviewer.txt +3 -0
- package/clinkx-workflows/workflows/council-answer.yaml +3 -0
- package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
- package/clinkx-workflows/workflows/council-debug.yaml +3 -0
- package/clinkx-workflows/workflows/council-default.yaml +7 -0
- package/clinkx-workflows/workflows/council-discover.yaml +3 -0
- package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
- package/conf/adapters/agy.json +9 -2
- package/conf/adapters/claude.json +7 -0
- package/conf/adapters/codex.json +7 -0
- package/conf/adapters/glm.json +19 -3
- package/conf/adapters/grok.json +9 -2
- package/conf/adapters/hapi/agy.json +9 -2
- package/conf/adapters/hapi/claude.json +7 -0
- package/conf/adapters/hapi/codex.json +7 -0
- package/conf/adapters/hapi/glm.json +20 -4
- package/conf/rosters/default-council.json +21 -0
- package/dist/activity-journal.d.ts +17 -3
- package/dist/activity-journal.js +404 -79
- package/dist/activity-journal.js.map +1 -1
- package/dist/codex/agent-tools.js +36 -0
- package/dist/codex/agent-tools.js.map +1 -1
- package/dist/codex/app-server-client.d.ts +3 -0
- package/dist/codex/app-server-client.js +11 -0
- package/dist/codex/app-server-client.js.map +1 -1
- package/dist/codex/app-server-registry.d.ts +18 -0
- package/dist/codex/app-server-registry.js +88 -2
- package/dist/codex/app-server-registry.js.map +1 -1
- package/dist/codex/client-types.d.ts +7 -1
- package/dist/codex/protocol.d.ts +4 -4
- package/dist/config.d.ts +220 -23
- package/dist/config.js +104 -1
- package/dist/config.js.map +1 -1
- package/dist/context-builder/bundle.d.ts +13 -0
- package/dist/context-builder/bundle.js +46 -0
- package/dist/context-builder/bundle.js.map +1 -1
- package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
- package/dist/context-builder/delegate-finalizer-effective.js +82 -0
- package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +18 -0
- package/dist/context-builder/delegate-packet.js +73 -9
- package/dist/context-builder/delegate-packet.js.map +1 -1
- package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
- package/dist/context-builder/delegate-reconstruction.js +110 -41
- package/dist/context-builder/delegate-reconstruction.js.map +1 -1
- package/dist/context-builder/delegate-validation.d.ts +31 -0
- package/dist/context-builder/delegate-validation.js +87 -10
- package/dist/context-builder/delegate-validation.js.map +1 -1
- package/dist/context-builder/delegate.d.ts +9 -0
- package/dist/context-builder/delegate.js +210 -59
- package/dist/context-builder/delegate.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +1 -1
- package/dist/context-builder/diagnostics.js.map +1 -1
- package/dist/context-builder/discovery-progress.d.ts +25 -0
- package/dist/context-builder/discovery-progress.js +64 -0
- package/dist/context-builder/discovery-progress.js.map +1 -0
- package/dist/context-builder/discovery-watchdog.d.ts +15 -0
- package/dist/context-builder/discovery-watchdog.js +39 -0
- package/dist/context-builder/discovery-watchdog.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +7 -0
- package/dist/context-builder/discovery.js +119 -7
- package/dist/context-builder/discovery.js.map +1 -1
- package/dist/context-builder/external-gaps.d.ts +73 -0
- package/dist/context-builder/external-gaps.js +248 -0
- package/dist/context-builder/external-gaps.js.map +1 -0
- package/dist/context-builder/globs.d.ts +1 -0
- package/dist/context-builder/globs.js +15 -0
- package/dist/context-builder/globs.js.map +1 -1
- package/dist/context-builder/handler.d.ts +42 -1
- package/dist/context-builder/handler.js +690 -62
- package/dist/context-builder/handler.js.map +1 -1
- package/dist/context-builder/invocation-sidecar.d.ts +41 -0
- package/dist/context-builder/invocation-sidecar.js +189 -0
- package/dist/context-builder/invocation-sidecar.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +51 -51
- package/dist/context-builder/mutation-lock.d.ts +32 -0
- package/dist/context-builder/mutation-lock.js +321 -0
- package/dist/context-builder/mutation-lock.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +32 -0
- package/dist/context-builder/review-coverage.js +130 -23
- package/dist/context-builder/review-coverage.js.map +1 -1
- package/dist/context-builder/schema.d.ts +76 -1
- package/dist/context-builder/schema.js +21 -1
- package/dist/context-builder/schema.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +31 -0
- package/dist/context-builder/secrets.js +865 -83
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/status.d.ts +1 -1
- package/dist/context-builder/status.js +1 -0
- package/dist/context-builder/status.js.map +1 -1
- package/dist/context-builder/storage.d.ts +29 -5
- package/dist/context-builder/storage.js +311 -236
- package/dist/context-builder/storage.js.map +1 -1
- package/dist/context-builder/versions.d.ts +19 -4
- package/dist/context-builder/versions.js +19 -4
- package/dist/context-builder/versions.js.map +1 -1
- package/dist/context-builder/workflow-runner.d.ts +6 -0
- package/dist/context-builder/workflow-runner.js +363 -173
- package/dist/context-builder/workflow-runner.js.map +1 -1
- package/dist/dashboard-adapter-catalog.js +7 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -1
- package/dist/dashboard-cli.d.ts +2 -0
- package/dist/dashboard-cli.js +40 -2
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-companion/client.d.ts +1 -0
- package/dist/dashboard-companion/client.js +22 -3
- package/dist/dashboard-companion/client.js.map +1 -1
- package/dist/dashboard-companion/protocol.js +4 -1
- package/dist/dashboard-companion/protocol.js.map +1 -1
- package/dist/dashboard-gateway/index.d.ts +1 -0
- package/dist/dashboard-gateway/index.js +33 -2
- package/dist/dashboard-gateway/index.js.map +1 -1
- package/dist/handler.js +5 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +145 -7
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +18 -3
- package/dist/logger.js.map +1 -1
- package/dist/parsers/agy-stream-json.d.ts +14 -0
- package/dist/parsers/agy-stream-json.js +172 -0
- package/dist/parsers/agy-stream-json.js.map +1 -0
- package/dist/parsers/extract.js +5 -1
- package/dist/parsers/extract.js.map +1 -1
- package/dist/parsers/observed-invocation.d.ts +20 -0
- package/dist/parsers/observed-invocation.js +42 -0
- package/dist/parsers/observed-invocation.js.map +1 -0
- package/dist/pipeline.d.ts +13 -0
- package/dist/pipeline.js +60 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/progress-report/lock.d.ts +74 -0
- package/dist/progress-report/lock.js +343 -0
- package/dist/progress-report/lock.js.map +1 -0
- package/dist/progress-report/schema.d.ts +808 -0
- package/dist/progress-report/schema.js +119 -0
- package/dist/progress-report/schema.js.map +1 -0
- package/dist/progress-report/store.d.ts +66 -0
- package/dist/progress-report/store.js +766 -0
- package/dist/progress-report/store.js.map +1 -0
- package/dist/progress-report/tools.d.ts +20 -0
- package/dist/progress-report/tools.js +141 -0
- package/dist/progress-report/tools.js.map +1 -0
- package/dist/prompt-mode.d.ts +1 -1
- package/dist/prompt-mode.js +7 -2
- package/dist/prompt-mode.js.map +1 -1
- package/dist/runner.d.ts +7 -0
- package/dist/runner.js +10 -3
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +50 -0
- package/dist/safety.js +182 -1
- package/dist/safety.js.map +1 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +20 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +6 -1
- package/dist/server.js +94 -8
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +1 -1
- package/dist/session-log-stream.js +280 -33
- package/dist/session-log-stream.js.map +1 -1
- package/dist/stdio-activity.d.ts +28 -0
- package/dist/stdio-activity.js +72 -0
- package/dist/stdio-activity.js.map +1 -0
- package/package.json +2 -1
- package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
- package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
|
@@ -17,14 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
|
-
import { basename, dirname, join } from "node:path";
|
|
20
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
21
|
+
import { computeRunHealth } from "./run-health.js";
|
|
21
22
|
import { loadWorkflow, listWorkflowCatalog } from "./loader.js";
|
|
22
|
-
import { WorkflowEngine } from "./engine.js";
|
|
23
|
+
import { defaultWorkflowAllowedRoots, WorkflowEngine } from "./engine.js";
|
|
23
24
|
import { readRunStateForStatus, reapRunCancelled, WorkflowStateStore } from "./state.js";
|
|
24
|
-
import { parseContextBuilderLifecycleStateV1, } from "./context-builder-lifecycle-contract.js";
|
|
25
|
+
import { isTerminalContextBuilderLifecycleStatus, parseContextBuilderLifecycleStateV1, } from "./context-builder-lifecycle-contract.js";
|
|
25
26
|
import { validateRunId } from "./run-id.js";
|
|
27
|
+
import { validateDynamicPaths } from "./path-validation.js";
|
|
26
28
|
import { getMaxPromptBytes, getRunStateDir, getTemplatesPath, getWorkflowsContextDefault, getWorkflowsContextDefaultCurator, getWorkflowsMaxConcurrentAsync, resolveWorkspaceRoot, } from "./config.js";
|
|
27
|
-
import { WorkflowCancellationError, } from "./errors.js";
|
|
29
|
+
import { WorkflowCancellationError, WorkflowValidationError, } from "./errors.js";
|
|
28
30
|
import { shutdownCoordinator } from "./shutdown.js";
|
|
29
31
|
import { logger } from "./logger.js";
|
|
30
32
|
import { createValidatorRegistry } from "./validators/index.js";
|
|
@@ -32,9 +34,11 @@ import { deriveDecisiveFailure } from "./run-summary-derived.js";
|
|
|
32
34
|
import { buildRunSummaryOrUndefined, buildFailureRunSummary } from "./run-summary.js";
|
|
33
35
|
import { extractCard } from "./run-summary/cards/index.js";
|
|
34
36
|
import { renderWorkflowReceipt } from "./workflow-receipt.js";
|
|
35
|
-
import { supportsRuntimeMembers, COUNCIL_RUNTIME_MEMBER_WORKFLOWS, expandCouncilComposition } from "./council-expander.js";
|
|
37
|
+
import { supportsRuntimeMembers, COUNCIL_RUNTIME_MEMBER_WORKFLOWS, COUNCIL_STAGE_COUNT, expandCouncilComposition } from "./council-expander.js";
|
|
36
38
|
import { resolveWorkflowContextMode, WorkflowContextModeResolverError, } from "./context-mode-resolver.js";
|
|
37
|
-
import {
|
|
39
|
+
import { resolveEffectiveChairmanCall } from "./chairman-knobs.js";
|
|
40
|
+
import { disabledFinalizerOverrideRejection, effectiveFinalizerEnabled, } from "./delegate-finalizer-policy.js";
|
|
41
|
+
import { delegateFinalizerMinScoreExplicit, WorkflowDelegateValidationError, } from "./delegate-validate.js";
|
|
38
42
|
import { contextSearchTermsValidationError, contextSeedSourceValidationError, resolveSeedSource, } from "./seed-terms.js";
|
|
39
43
|
import { LEGACY_PLAN_REVIEW_RECEIPTS_WORKFLOW_NAME, PLAN_REVIEW_RECEIPTS_WORKFLOW, admitCouncilPlanReview, assertPlanReviewPromptBudget, isPlanReviewRunState, } from "./plan-review/admission.js";
|
|
40
44
|
import { PLAN_REVIEW_V1_CLI_NAMES } from "./plan-review/contract.js";
|
|
@@ -49,6 +53,7 @@ import { CouncilPlanReviewErrorSchema, createPlanReviewPublicationError, inspect
|
|
|
49
53
|
import { validatePlanReviewCallBindings, validatePlanReviewPreparedInputPolicy, } from "./plan-review/runtime-policy.js";
|
|
50
54
|
import { ContainedFilePolicyError, readContainedRelativeFile, } from "./contained-file.js";
|
|
51
55
|
import { createDashboardRunReader } from "./dashboard/read-model.js";
|
|
56
|
+
import { ROSTER_NAME_PATTERN, ResolvedRosterSchema, } from "./roster-contract.js";
|
|
52
57
|
async function inspectLegacyWorkflowSessionLogGeneration(runDir, runId) {
|
|
53
58
|
try {
|
|
54
59
|
const result = await createDashboardRunReader().inspectGeneration?.(dirname(runDir), runId);
|
|
@@ -91,6 +96,38 @@ function createPreflightOnlyBackend(advertisedCliNames) {
|
|
|
91
96
|
// Admission control
|
|
92
97
|
// ---------------------------------------------------------------------------
|
|
93
98
|
const activeRuns = new Map();
|
|
99
|
+
/**
|
|
100
|
+
* Number of workflow tool handlers currently executing in the standalone
|
|
101
|
+
* registration path. This includes non-run tools such as status/list/cancel;
|
|
102
|
+
* it is kept separate from activeRuns because an admitted run can outlive its
|
|
103
|
+
* request handler (async mode), while a handler can be busy before admission.
|
|
104
|
+
*/
|
|
105
|
+
let activeWorkflowHandlers = 0;
|
|
106
|
+
let workflowActivityObserver;
|
|
107
|
+
/** Internal standalone-lifecycle seam; not part of the public package API. */
|
|
108
|
+
export function getActiveWorkflowHandlerCount() {
|
|
109
|
+
return activeWorkflowHandlers;
|
|
110
|
+
}
|
|
111
|
+
/** Internal shared lifecycle seam used by the root server's compiled import. */
|
|
112
|
+
export function getActiveWorkflowRunCount() {
|
|
113
|
+
return activeRuns.size;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Install the standalone server's activity sink. The observer is optional and
|
|
117
|
+
* process-local; library callers leave it unset, so importing this module or
|
|
118
|
+
* using the in-process registration path has no timer/lifecycle side effects.
|
|
119
|
+
*/
|
|
120
|
+
export function setWorkflowActivityObserver(observer) {
|
|
121
|
+
workflowActivityObserver = observer;
|
|
122
|
+
}
|
|
123
|
+
function notifyWorkflowActivity() {
|
|
124
|
+
try {
|
|
125
|
+
workflowActivityObserver?.();
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
logger.warn({ err: error instanceof Error ? error.message : String(error) }, "workflow activity observer failed");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
94
131
|
/**
|
|
95
132
|
* In-process registry of AbortControllers for live async runs, keyed by run_id.
|
|
96
133
|
* cancel_workflow aborts the controller to drive the run's own cancellation
|
|
@@ -109,6 +146,9 @@ export function resolveWorkflowAsyncAdmissionCap(options) {
|
|
|
109
146
|
return getWorkflowsMaxConcurrentAsync();
|
|
110
147
|
}
|
|
111
148
|
function acquireMutex(runId, options) {
|
|
149
|
+
if (shutdownCoordinator.isShuttingDown()) {
|
|
150
|
+
throw new McpError(ErrorCode.InvalidRequest, "workflow server is shutting down");
|
|
151
|
+
}
|
|
112
152
|
if (activeRuns.has(runId)) {
|
|
113
153
|
throw new McpError(ErrorCode.InvalidParams, `run_id ${runId} already running`);
|
|
114
154
|
}
|
|
@@ -120,6 +160,97 @@ function acquireMutex(runId, options) {
|
|
|
120
160
|
}
|
|
121
161
|
function releaseMutex(runId) {
|
|
122
162
|
activeRuns.delete(runId);
|
|
163
|
+
notifyWorkflowActivity();
|
|
164
|
+
}
|
|
165
|
+
/** Own setup before its first await, and keep that ownership through cleanup. */
|
|
166
|
+
function acquireWorkflowRun(runId, options, requestSignal) {
|
|
167
|
+
acquireMutex(runId, options);
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const signal = requestSignal === undefined
|
|
170
|
+
? controller.signal
|
|
171
|
+
: AbortSignal.any([requestSignal, controller.signal]);
|
|
172
|
+
let backend;
|
|
173
|
+
let termination;
|
|
174
|
+
const terminate = () => {
|
|
175
|
+
if (backend === undefined || backend.isTerminated)
|
|
176
|
+
return termination ?? Promise.resolve();
|
|
177
|
+
termination ??= Promise.resolve().then(() => backend.terminate());
|
|
178
|
+
return termination;
|
|
179
|
+
};
|
|
180
|
+
let complete;
|
|
181
|
+
const completion = new Promise((resolve) => { complete = resolve; });
|
|
182
|
+
let handle;
|
|
183
|
+
try {
|
|
184
|
+
// The run owns persistence and close; shutdown only interrupts the lazy
|
|
185
|
+
// backend and waits until the run's own finally releases all resources.
|
|
186
|
+
handle = shutdownCoordinator.register({ terminate }, controller, async () => { }, completion);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
releaseMutex(runId);
|
|
190
|
+
complete();
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
if (requestSignal === undefined)
|
|
194
|
+
cancelRegistry.set(runId, controller);
|
|
195
|
+
return {
|
|
196
|
+
signal,
|
|
197
|
+
throwIfCancelled: () => {
|
|
198
|
+
if (signal.aborted)
|
|
199
|
+
throw new WorkflowCancellationError();
|
|
200
|
+
},
|
|
201
|
+
setBackend: (value) => { backend = value; },
|
|
202
|
+
finish: async (store) => {
|
|
203
|
+
try {
|
|
204
|
+
try {
|
|
205
|
+
await terminate();
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
logger.warn({ err }, "backend.terminate() failed during cleanup");
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
await store?.close();
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
logger.warn({ err }, "store.close() failed during cleanup");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
cancelRegistry.delete(runId);
|
|
219
|
+
releaseMutex(runId);
|
|
220
|
+
shutdownCoordinator.unregister(handle);
|
|
221
|
+
complete();
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async function markWorkflowCancelled(store) {
|
|
227
|
+
try {
|
|
228
|
+
await store?.markCancelled();
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
// Preserve cancellation/no-response even if best-effort persistence fails.
|
|
232
|
+
logger.warn({ err }, "markCancelled during cancellation handling failed");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Keep in sync with schema.ts model regex and ModelSchema (delegate-validate.ts).
|
|
236
|
+
const MODEL_OVERRIDE_PATTERN = /^[^\s\u0000-][^\s\u0000]*$/;
|
|
237
|
+
// Keep in sync with schema.ts effort regex and EffortSchema (delegate-validate.ts).
|
|
238
|
+
const EFFORT_OVERRIDE_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
239
|
+
function parseInvocationOverride(raw, label, maxLength, pattern) {
|
|
240
|
+
if (typeof raw !== "string") {
|
|
241
|
+
throw new McpError(ErrorCode.InvalidParams, `${label} must be a string`);
|
|
242
|
+
}
|
|
243
|
+
const value = raw.trim();
|
|
244
|
+
if (value.length === 0) {
|
|
245
|
+
throw new McpError(ErrorCode.InvalidParams, `${label} must not be empty`);
|
|
246
|
+
}
|
|
247
|
+
if (value.length > maxLength) {
|
|
248
|
+
throw new McpError(ErrorCode.InvalidParams, `${label} must be at most ${maxLength} characters`);
|
|
249
|
+
}
|
|
250
|
+
if (!pattern.test(value)) {
|
|
251
|
+
throw new McpError(ErrorCode.InvalidParams, `${label} has an invalid format`);
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
123
254
|
}
|
|
124
255
|
export function parseMembersOverride(raw, knownCliNames) {
|
|
125
256
|
if (raw == null)
|
|
@@ -140,7 +271,7 @@ export function parseMembersOverride(raw, knownCliNames) {
|
|
|
140
271
|
throw new McpError(ErrorCode.InvalidParams, `members[${i}] must be a non-null object`);
|
|
141
272
|
}
|
|
142
273
|
const keys = Object.keys(item);
|
|
143
|
-
const allowedKeys = new Set(["cli_name", "stance"]);
|
|
274
|
+
const allowedKeys = new Set(["cli_name", "stance", "model", "effort"]);
|
|
144
275
|
for (const key of keys) {
|
|
145
276
|
if (!allowedKeys.has(key)) {
|
|
146
277
|
throw new McpError(ErrorCode.InvalidParams, `members[${i}] has unexpected key "${key}"`);
|
|
@@ -179,6 +310,12 @@ export function parseMembersOverride(raw, knownCliNames) {
|
|
|
179
310
|
else if (i >= 3) {
|
|
180
311
|
throw new McpError(ErrorCode.InvalidParams, "stance is required for members at index 3 and above");
|
|
181
312
|
}
|
|
313
|
+
if (obj["model"] !== undefined) {
|
|
314
|
+
entry.model = parseInvocationOverride(obj["model"], `members[${i}].model`, 128, MODEL_OVERRIDE_PATTERN);
|
|
315
|
+
}
|
|
316
|
+
if (obj["effort"] !== undefined) {
|
|
317
|
+
entry.effort = parseInvocationOverride(obj["effort"], `members[${i}].effort`, 32, EFFORT_OVERRIDE_PATTERN);
|
|
318
|
+
}
|
|
182
319
|
result.push(entry);
|
|
183
320
|
}
|
|
184
321
|
return result;
|
|
@@ -202,6 +339,93 @@ export function parseChairmanOverride(raw, knownCliNames) {
|
|
|
202
339
|
}
|
|
203
340
|
return trimmed;
|
|
204
341
|
}
|
|
342
|
+
function parseChairmanModelOverride(raw) {
|
|
343
|
+
if (raw === undefined)
|
|
344
|
+
return undefined;
|
|
345
|
+
return parseInvocationOverride(raw, "chairman_model", 128, MODEL_OVERRIDE_PATTERN);
|
|
346
|
+
}
|
|
347
|
+
function parseChairmanEffortOverride(raw) {
|
|
348
|
+
if (raw === undefined)
|
|
349
|
+
return undefined;
|
|
350
|
+
return parseInvocationOverride(raw, "chairman_effort", 32, EFFORT_OVERRIDE_PATTERN);
|
|
351
|
+
}
|
|
352
|
+
function invocationOverrideKnobs(value) {
|
|
353
|
+
if (value.model === undefined && value.effort === undefined) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
...(value.model === undefined ? {} : { model: value.model }),
|
|
358
|
+
...(value.effort === undefined ? {} : { effort: value.effort }),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function validateInvocationOverridesAtAdmission(workflow, definition, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, initialContextPolicy, resolvedVariables, validate, callerDelegateRaw) {
|
|
362
|
+
// Structural E5: independent of the optional adapter-capability hook so the
|
|
363
|
+
// standalone server rejects a disabled-finalizer override too. No-op when
|
|
364
|
+
// the policy has no enabled delegate (including the no-runner `off` default).
|
|
365
|
+
const admissionDelegate = initialContextPolicy.effectiveDelegate;
|
|
366
|
+
if (admissionDelegate?.enabled === true) {
|
|
367
|
+
const rejection = disabledFinalizerOverrideRejection(admissionDelegate, resolvedVariables, delegateFinalizerMinScoreExplicit(callerDelegateRaw));
|
|
368
|
+
if (rejection !== undefined) {
|
|
369
|
+
throw new McpError(ErrorCode.InvalidParams, rejection);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (validate == null)
|
|
373
|
+
return;
|
|
374
|
+
const councilWorkflow = COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow);
|
|
375
|
+
if (councilWorkflow && definition.stages.length !== COUNCIL_STAGE_COUNT) {
|
|
376
|
+
throw new McpError(ErrorCode.InvalidParams, `Council workflow "${workflow}" expects ${COUNCIL_STAGE_COUNT} stages, found ${definition.stages.length}`);
|
|
377
|
+
}
|
|
378
|
+
const staticChairman = councilWorkflow
|
|
379
|
+
? definition.stages[3]?.calls[0]
|
|
380
|
+
: undefined;
|
|
381
|
+
for (const [stageIndex, stage] of definition.stages.entries()) {
|
|
382
|
+
for (const call of stage.calls) {
|
|
383
|
+
if (call === staticChairman) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (parsedMembers !== undefined && stageIndex < 2) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const knobs = invocationOverrideKnobs(call);
|
|
390
|
+
if (knobs !== undefined) {
|
|
391
|
+
validate(call.cli_name, knobs);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (parsedMembers !== undefined) {
|
|
396
|
+
for (const member of parsedMembers) {
|
|
397
|
+
const knobs = invocationOverrideKnobs(member);
|
|
398
|
+
if (knobs !== undefined) {
|
|
399
|
+
validate(member.cli_name, knobs);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (councilWorkflow && staticChairman !== undefined) {
|
|
404
|
+
const effectiveChairman = resolveEffectiveChairmanCall({
|
|
405
|
+
staticCall: staticChairman,
|
|
406
|
+
chairmanCliName: parsedChairman,
|
|
407
|
+
chairmanModel: parsedChairmanModel,
|
|
408
|
+
chairmanEffort: parsedChairmanEffort,
|
|
409
|
+
});
|
|
410
|
+
const knobs = invocationOverrideKnobs(effectiveChairman);
|
|
411
|
+
if (knobs !== undefined) {
|
|
412
|
+
validate(effectiveChairman.cli_name, knobs);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const delegate = initialContextPolicy.effectiveDelegate;
|
|
416
|
+
if (delegate?.enabled === true) {
|
|
417
|
+
const curatorKnobs = invocationOverrideKnobs(delegate.curator);
|
|
418
|
+
if (curatorKnobs !== undefined) {
|
|
419
|
+
validate(delegate.curator.cli_name, curatorKnobs);
|
|
420
|
+
}
|
|
421
|
+
if (effectiveFinalizerEnabled(delegate, resolvedVariables)) {
|
|
422
|
+
const finalizerKnobs = invocationOverrideKnobs(delegate.finalizer);
|
|
423
|
+
if (finalizerKnobs !== undefined) {
|
|
424
|
+
validate(delegate.finalizer.cli_name, finalizerKnobs);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
205
429
|
/**
|
|
206
430
|
* Point a generic-council caller at the input shape the workflow actually takes.
|
|
207
431
|
*
|
|
@@ -290,6 +514,94 @@ export function validateVariables(supplied, schema) {
|
|
|
290
514
|
}
|
|
291
515
|
return resolved;
|
|
292
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Reject caller-supplied dynamic path variables that fall outside the workflow
|
|
519
|
+
* engine's allowed roots, at ADMISSION — before the concurrency mutex, before
|
|
520
|
+
* any run state exists, and before context preflight spends a delegate
|
|
521
|
+
* round-trip. The engine already rejects these paths, but only at
|
|
522
|
+
* `validateInputPathsExist()`, which runs AFTER preflight: by then the caller
|
|
523
|
+
* has paid for a curator/finalizer subprocess and is left with a `failed` run
|
|
524
|
+
* directory to inspect and prune.
|
|
525
|
+
*
|
|
526
|
+
* This is a pre-check, NOT a replacement. The engine's post-preflight check
|
|
527
|
+
* must stay: it is the only thing that covers preflight-produced bundle copies
|
|
528
|
+
* appended to `context_bundle_files`, which do not exist yet at admission.
|
|
529
|
+
*
|
|
530
|
+
* Three deliberate narrowings, each of which exists to avoid rejecting a run
|
|
531
|
+
* that succeeds today:
|
|
532
|
+
*
|
|
533
|
+
* (i) Only variable names the DEFINITION actually wires through
|
|
534
|
+
* `absolute_file_paths_from` / `images_from` are validated. A caller may
|
|
535
|
+
* legitimately pass any other declared variable that happens to hold
|
|
536
|
+
* path-like strings; the engine never resolves those as paths, so
|
|
537
|
+
* validating them here would reject working runs.
|
|
538
|
+
* (ii) `plan-review-receipts` is skipped entirely.
|
|
539
|
+
* `enforcePlanReviewContextExclusions` strips force-excluded plan paths
|
|
540
|
+
* BEFORE the engine's check, so a path destined for stripping is never
|
|
541
|
+
* validated today and must not start being rejected at admission.
|
|
542
|
+
* (iii) The resume path does not call this. Resume prefers persisted inputs and
|
|
543
|
+
* skips completed calls, so re-validating current variables could reject a
|
|
544
|
+
* resume whose original admission passed.
|
|
545
|
+
*
|
|
546
|
+
* Non-array values and non-string entries are deliberately NOT rejected here —
|
|
547
|
+
* they are left to the engine so its existing error text is unchanged.
|
|
548
|
+
*/
|
|
549
|
+
function assertCallerDynamicPathsAllowed(normalized, variables, workflowName, runId) {
|
|
550
|
+
// Caveat (ii).
|
|
551
|
+
if (workflowName === PLAN_REVIEW_RECEIPTS_WORKFLOW) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
// Caveat (i): the set of variable names this definition wires as paths.
|
|
555
|
+
const wiredNames = new Set();
|
|
556
|
+
const collectWiredNames = (declared) => {
|
|
557
|
+
if (typeof declared === "string") {
|
|
558
|
+
wiredNames.add(declared);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (declared === undefined) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
for (const name of declared) {
|
|
565
|
+
wiredNames.add(name);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
for (const stage of normalized.stages) {
|
|
569
|
+
for (const call of stage.calls) {
|
|
570
|
+
collectWiredNames(call.absolute_file_paths_from);
|
|
571
|
+
collectWiredNames(call.images_from);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (wiredNames.size === 0) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
// The engine derives its own roots and never consults CLINKX_ALLOWED_ROOTS;
|
|
578
|
+
// mirror that exact set so admission cannot be stricter or looser than
|
|
579
|
+
// dispatch.
|
|
580
|
+
const allowedRoots = defaultWorkflowAllowedRoots(getRunStateDir(runId));
|
|
581
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
582
|
+
for (const name of wiredNames) {
|
|
583
|
+
const rawValue = variables[name];
|
|
584
|
+
if (!Array.isArray(rawValue)) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
// Mirrors resolveDynamicPathVariable (engine.ts) exactly.
|
|
588
|
+
const values = rawValue
|
|
589
|
+
.filter((entry) => typeof entry === "string")
|
|
590
|
+
.map((entry) => (isAbsolute(entry) ? entry : resolve(workspaceRoot, entry)));
|
|
591
|
+
if (values.length === 0) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
validateDynamicPaths(values, allowedRoots, `${workflowName}.${name}`);
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
if (err instanceof WorkflowValidationError) {
|
|
599
|
+
throw new McpError(ErrorCode.InvalidParams, err.message);
|
|
600
|
+
}
|
|
601
|
+
throw err;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
293
605
|
function validateContextBuilderAnchorSufficiency(variables, runner, contextDefault, defaultCurator) {
|
|
294
606
|
if (runner == null) {
|
|
295
607
|
return undefined;
|
|
@@ -803,6 +1115,21 @@ function appendDuplicateReplayBlock(content, store) {
|
|
|
803
1115
|
text: JSON.stringify({ duplicate_of_failed_run: duplicateOfFailedRun }),
|
|
804
1116
|
});
|
|
805
1117
|
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Trailing additive content block for the admission-time requested-value
|
|
1120
|
+
* roster. Omitted when the run did not persist one (non-council, plan-review,
|
|
1121
|
+
* or pre-roster state). Defensive optional access: partial store mocks may
|
|
1122
|
+
* omit runState.
|
|
1123
|
+
*/
|
|
1124
|
+
function appendResolvedRosterBlock(content, store) {
|
|
1125
|
+
const resolvedRoster = store?.runState?.resolvedRoster;
|
|
1126
|
+
if (resolvedRoster === undefined)
|
|
1127
|
+
return;
|
|
1128
|
+
content.push({
|
|
1129
|
+
type: "text",
|
|
1130
|
+
text: JSON.stringify({ resolved_roster: resolvedRoster }),
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
806
1133
|
// ---------------------------------------------------------------------------
|
|
807
1134
|
// Actionable error formatting (5-6)
|
|
808
1135
|
// ---------------------------------------------------------------------------
|
|
@@ -892,14 +1219,45 @@ function toCouncilPlanReviewError(runId, error) {
|
|
|
892
1219
|
},
|
|
893
1220
|
});
|
|
894
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Read a well-formed `failureClass` the error already carries. Structural on
|
|
1224
|
+
* purpose: errors crossing the engine boundary cannot be narrowed by
|
|
1225
|
+
* `instanceof` here, and this must never GUESS a class — an error without one
|
|
1226
|
+
* yields undefined, which `markFailed` treats as "leave the existing class
|
|
1227
|
+
* alone".
|
|
1228
|
+
*/
|
|
1229
|
+
function carriedFailureClass(error) {
|
|
1230
|
+
if (error == null || typeof error !== "object") {
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1233
|
+
const value = error.failureClass;
|
|
1234
|
+
return value === "transient" || value === "permanent" ? value : undefined;
|
|
1235
|
+
}
|
|
895
1236
|
async function markWorkflowFailed(store, runId, error) {
|
|
896
1237
|
const message = error instanceof Error ? error.message : String(error);
|
|
1238
|
+
// Forward a class the error already carries. Load-bearing on the RESUME path:
|
|
1239
|
+
// `beginResumeAttempt` clears `failureClass`, and the resume preflight engine
|
|
1240
|
+
// never runs `execute()`, so its catch never stamps one — without this a
|
|
1241
|
+
// bounded-preflight timeout persists `failed` with no class at all, while the
|
|
1242
|
+
// identical first-run failure keeps `transient`. Safe for the other call
|
|
1243
|
+
// sites: `markFailed` assigns only when defined, so an error carrying no
|
|
1244
|
+
// class still cannot clobber a class the engine already emitted.
|
|
897
1245
|
const planReviewError = toCouncilPlanReviewError(runId, error);
|
|
898
|
-
if (planReviewError
|
|
1246
|
+
if (planReviewError !== undefined) {
|
|
1247
|
+
// Plan-review terminal intent is first-wins. Forwarding a class here would
|
|
1248
|
+
// flip `replaceFailureClass` and overwrite a recorded intent, so this call
|
|
1249
|
+
// stays exactly as it was.
|
|
1250
|
+
await store.markFailed(message, undefined, planReviewError);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const failureClass = carriedFailureClass(error);
|
|
1254
|
+
if (failureClass === undefined) {
|
|
1255
|
+
// Nothing to add: keep the original single-argument call so callers that
|
|
1256
|
+
// rely on `markFailed`'s existing behavior are byte-identical.
|
|
899
1257
|
await store.markFailed(message);
|
|
900
1258
|
return;
|
|
901
1259
|
}
|
|
902
|
-
await store.markFailed(message,
|
|
1260
|
+
await store.markFailed(message, failureClass);
|
|
903
1261
|
}
|
|
904
1262
|
function getCallEntryByLocation(stages, location) {
|
|
905
1263
|
const dotIndex = location.indexOf(".");
|
|
@@ -912,6 +1270,54 @@ function getCallEntryByLocation(stages, location) {
|
|
|
912
1270
|
return stage?.calls.find((call) => call.callId === callId);
|
|
913
1271
|
}
|
|
914
1272
|
// ---------------------------------------------------------------------------
|
|
1273
|
+
// Roster preset admission (pre-mutex, pre-state)
|
|
1274
|
+
// ---------------------------------------------------------------------------
|
|
1275
|
+
function applyRosterPresetAtAdmission(workflow, args, variables, rosterLoader) {
|
|
1276
|
+
const raw = args["roster"];
|
|
1277
|
+
if (typeof raw !== "string") {
|
|
1278
|
+
throw new McpError(ErrorCode.InvalidParams, "roster must be a string");
|
|
1279
|
+
}
|
|
1280
|
+
if (!COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
1281
|
+
throw new McpError(ErrorCode.InvalidParams, `roster parameter is not supported for workflow "${workflow}"`);
|
|
1282
|
+
}
|
|
1283
|
+
const conflicts = [];
|
|
1284
|
+
if (args["members"] != null)
|
|
1285
|
+
conflicts.push("members");
|
|
1286
|
+
if (args["chairman"] != null)
|
|
1287
|
+
conflicts.push("chairman");
|
|
1288
|
+
if (args["chairman_model"] != null)
|
|
1289
|
+
conflicts.push("chairman_model");
|
|
1290
|
+
if (args["chairman_effort"] != null)
|
|
1291
|
+
conflicts.push("chairman_effort");
|
|
1292
|
+
const cbDelegate = variables?.["cb_delegate"];
|
|
1293
|
+
if (typeof cbDelegate === "string" && cbDelegate !== "") {
|
|
1294
|
+
conflicts.push("cb_delegate");
|
|
1295
|
+
}
|
|
1296
|
+
if (conflicts.length > 0) {
|
|
1297
|
+
throw new McpError(ErrorCode.InvalidParams, `roster is exclusive and cannot be combined with ${conflicts.join(", ")}`);
|
|
1298
|
+
}
|
|
1299
|
+
if (rosterLoader == null) {
|
|
1300
|
+
throw new McpError(ErrorCode.InvalidParams, "rosters are not available on this server");
|
|
1301
|
+
}
|
|
1302
|
+
const preset = rosterLoader.load(raw);
|
|
1303
|
+
if (preset === undefined) {
|
|
1304
|
+
const available = rosterLoader.list().map((entry) => entry.name).join(", ");
|
|
1305
|
+
throw new McpError(ErrorCode.InvalidParams, `roster "${raw}" is not a known roster. Available: ${available}`);
|
|
1306
|
+
}
|
|
1307
|
+
const chairman = preset.chairman;
|
|
1308
|
+
const nextVariables = preset.delegate !== undefined
|
|
1309
|
+
? { ...(variables ?? {}), cb_delegate: JSON.stringify(preset.delegate) }
|
|
1310
|
+
: variables;
|
|
1311
|
+
return {
|
|
1312
|
+
rosterName: raw,
|
|
1313
|
+
membersRaw: preset.members,
|
|
1314
|
+
chairmanRaw: chairman?.cli_name,
|
|
1315
|
+
chairmanModelRaw: chairman?.model,
|
|
1316
|
+
chairmanEffortRaw: chairman?.effort,
|
|
1317
|
+
variables: nextVariables,
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
// ---------------------------------------------------------------------------
|
|
915
1321
|
// Tool execution helpers
|
|
916
1322
|
// ---------------------------------------------------------------------------
|
|
917
1323
|
async function executeRunWorkflow(server, options, args, signal, progressToken) {
|
|
@@ -947,25 +1353,53 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
947
1353
|
const genericShaped = workflow === PLAN_REVIEW_RECEIPTS_WORKFLOW &&
|
|
948
1354
|
(args["plan_review"] == null ||
|
|
949
1355
|
args["members"] != null ||
|
|
950
|
-
args["chairman"] != null
|
|
1356
|
+
args["chairman"] != null ||
|
|
1357
|
+
args["chairman_model"] != null ||
|
|
1358
|
+
args["chairman_effort"] != null ||
|
|
1359
|
+
args["roster"] != null);
|
|
951
1360
|
// Declarations hoisted out of the decorator's try so the bindings stay in
|
|
952
1361
|
// scope for the post-admission code below; the assignments and their order
|
|
953
1362
|
// are unchanged.
|
|
954
1363
|
let parsedMembers;
|
|
955
1364
|
let parsedChairman;
|
|
1365
|
+
let parsedChairmanModel;
|
|
1366
|
+
let parsedChairmanEffort;
|
|
956
1367
|
let resolvedVariables;
|
|
957
1368
|
let initialContextPolicy;
|
|
958
1369
|
let planReviewAdmission;
|
|
1370
|
+
let admittedRosterName;
|
|
959
1371
|
try {
|
|
960
|
-
|
|
961
|
-
|
|
1372
|
+
let membersRaw = args["members"];
|
|
1373
|
+
let chairmanRaw = args["chairman"];
|
|
1374
|
+
let chairmanModelRaw = args["chairman_model"];
|
|
1375
|
+
let chairmanEffortRaw = args["chairman_effort"];
|
|
1376
|
+
let suppliedVariables = variables;
|
|
1377
|
+
if (args["roster"] !== undefined) {
|
|
1378
|
+
const applied = applyRosterPresetAtAdmission(workflow, args, variables, options.rosterLoader);
|
|
1379
|
+
admittedRosterName = applied.rosterName;
|
|
1380
|
+
membersRaw = applied.membersRaw;
|
|
1381
|
+
chairmanRaw = applied.chairmanRaw;
|
|
1382
|
+
chairmanModelRaw = applied.chairmanModelRaw;
|
|
1383
|
+
chairmanEffortRaw = applied.chairmanEffortRaw;
|
|
1384
|
+
suppliedVariables = applied.variables;
|
|
1385
|
+
}
|
|
1386
|
+
parsedMembers = parseMembersOverride(membersRaw, knownCliNames);
|
|
1387
|
+
parsedChairman = parseChairmanOverride(chairmanRaw, knownCliNames);
|
|
1388
|
+
parsedChairmanModel = parseChairmanModelOverride(chairmanModelRaw);
|
|
1389
|
+
parsedChairmanEffort = parseChairmanEffortOverride(chairmanEffortRaw);
|
|
962
1390
|
if (parsedMembers != null && !COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
963
1391
|
throw new McpError(ErrorCode.InvalidParams, `members parameter is not supported for workflow "${workflow}"`);
|
|
964
1392
|
}
|
|
965
1393
|
if (parsedChairman != null && !COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
966
1394
|
throw new McpError(ErrorCode.InvalidParams, `chairman parameter is not supported for workflow "${workflow}"`);
|
|
967
1395
|
}
|
|
968
|
-
|
|
1396
|
+
if (parsedChairmanModel != null && !COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
1397
|
+
throw new McpError(ErrorCode.InvalidParams, `chairman_model parameter is not supported for workflow "${workflow}"`);
|
|
1398
|
+
}
|
|
1399
|
+
if (parsedChairmanEffort != null && !COUNCIL_RUNTIME_MEMBER_WORKFLOWS.has(workflow)) {
|
|
1400
|
+
throw new McpError(ErrorCode.InvalidParams, `chairman_effort parameter is not supported for workflow "${workflow}"`);
|
|
1401
|
+
}
|
|
1402
|
+
resolvedVariables = validateVariables(suppliedVariables, loaded.definition.variables);
|
|
969
1403
|
// When the runner is absent (preflight disabled / hard kill), the boundary
|
|
970
1404
|
// validator is inert. Skip it WITHOUT evaluating the context-default env
|
|
971
1405
|
// parsers — getWorkflowsContextDefaultCurator() throws on empty env, and a
|
|
@@ -975,6 +1409,9 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
975
1409
|
contextPolicy = validateContextBuilderAnchorSufficiency(resolvedVariables, options.contextBuilderRunner, options.contextDefault ?? getWorkflowsContextDefault(), options.defaultCurator ?? getWorkflowsContextDefaultCurator());
|
|
976
1410
|
}
|
|
977
1411
|
validateContextSeedSourceAtAdmission(resolvedVariables, contextPolicy);
|
|
1412
|
+
// Pre-mutex, pre-state, pre-preflight: an out-of-roots caller path costs
|
|
1413
|
+
// zero delegate round-trips and leaves no run directory behind.
|
|
1414
|
+
assertCallerDynamicPathsAllowed(loaded.normalized, resolvedVariables, workflow, runId);
|
|
978
1415
|
initialContextPolicy = contextPolicy ?? resolveWorkflowContextMode(undefined, options.contextDefault ?? "off", false, undefined, options.defaultCurator);
|
|
979
1416
|
if (options.contextBuilderRunner != null) {
|
|
980
1417
|
try {
|
|
@@ -1009,6 +1446,11 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1009
1446
|
}
|
|
1010
1447
|
throw err;
|
|
1011
1448
|
}
|
|
1449
|
+
validateInvocationOverridesAtAdmission(workflow, loaded.normalized, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, initialContextPolicy, resolvedVariables, options.validateInvocationOverrides,
|
|
1450
|
+
// Caller-supplied cb_delegate only, never the roster-materialized one: a
|
|
1451
|
+
// roster preset is Zod-parsed at load, which defaults min_score and erases
|
|
1452
|
+
// whether the preset file actually requested it.
|
|
1453
|
+
admittedRosterName === undefined ? variables?.["cb_delegate"] : undefined);
|
|
1012
1454
|
// A-7: reject caller context anchors that resolve to the bound plan or any
|
|
1013
1455
|
// plan-workspace PLAN.md. Pre-mutex, so a rejected request creates no state:
|
|
1014
1456
|
// the caller must not be able to hand actors a MUTABLE plan alongside the
|
|
@@ -1031,8 +1473,8 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1031
1473
|
snapshotAbsolutePath: join(getRunStateDir(runId), PLAN_SNAPSHOT_RELPATH),
|
|
1032
1474
|
};
|
|
1033
1475
|
}
|
|
1034
|
-
acquireMutex(runId, options);
|
|
1035
1476
|
const isAsync = args["async"] === true;
|
|
1477
|
+
const ownership = acquireWorkflowRun(runId, options, isAsync ? undefined : signal);
|
|
1036
1478
|
if (isAsync) {
|
|
1037
1479
|
// Everything between acquireMutex and the detached spawn must release the
|
|
1038
1480
|
// mutex on ANY throw, or the run slot leaks permanently (bricks the server
|
|
@@ -1042,6 +1484,7 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1042
1484
|
let initializedPlanReview;
|
|
1043
1485
|
let preparedSnapshot;
|
|
1044
1486
|
try {
|
|
1487
|
+
ownership.throwIfCancelled();
|
|
1045
1488
|
const runDir = getRunStateDir(runId);
|
|
1046
1489
|
if (existsSync(runDir)) {
|
|
1047
1490
|
throw new McpError(ErrorCode.InvalidParams, `run_id ${runId} already has persisted state at ${runDir}`);
|
|
@@ -1051,8 +1494,10 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1051
1494
|
preparedSnapshot = planReviewPlan === undefined
|
|
1052
1495
|
? undefined
|
|
1053
1496
|
: await preparePlanReviewSnapshot(planReviewPlan);
|
|
1497
|
+
ownership.throwIfCancelled();
|
|
1054
1498
|
// Expand composition ONCE in the foreground; the detached run reuses it.
|
|
1055
|
-
effectiveDefinition = expandRunWorkflowDefinition(loaded.normalized, workflow, parsedMembers, parsedChairman, planReviewPlan, preparedSnapshot);
|
|
1499
|
+
effectiveDefinition = expandRunWorkflowDefinition(loaded.normalized, workflow, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, planReviewPlan, preparedSnapshot);
|
|
1500
|
+
const resolvedRoster = resolvedRosterForAdmission(workflow, effectiveDefinition, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, initialContextPolicy, resolvedVariables, admittedRosterName);
|
|
1056
1501
|
// Create the durable, pollable state record in the FOREGROUND so a failure
|
|
1057
1502
|
// here surfaces synchronously (a real error, never a false "running").
|
|
1058
1503
|
try {
|
|
@@ -1063,21 +1508,29 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1063
1508
|
debugKeepArtifacts: debug,
|
|
1064
1509
|
async: true,
|
|
1065
1510
|
initialContextPolicy,
|
|
1511
|
+
...(resolvedRoster !== undefined ? { resolvedRoster } : {}),
|
|
1512
|
+
...(admittedRosterName !== undefined ? { rosterName: admittedRosterName } : {}),
|
|
1066
1513
|
});
|
|
1067
1514
|
}
|
|
1068
1515
|
catch (err) {
|
|
1069
1516
|
throw new McpError(ErrorCode.InternalError, `Failed to initialize async run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1070
1517
|
}
|
|
1518
|
+
ownership.throwIfCancelled();
|
|
1071
1519
|
// A-10: the async acknowledgement is a durability boundary. A caller
|
|
1072
1520
|
// cannot observe `running` until every plan-review snapshot and the
|
|
1073
1521
|
// strict typed state subtree have committed successfully.
|
|
1074
1522
|
if (planReviewPlan !== undefined) {
|
|
1075
1523
|
initializedPlanReview = await initializePlanReviewRunState(store, planReviewPlan, effectiveDefinition, preparedSnapshot);
|
|
1076
1524
|
}
|
|
1525
|
+
ownership.throwIfCancelled();
|
|
1077
1526
|
}
|
|
1078
1527
|
catch (err) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1528
|
+
try {
|
|
1529
|
+
if (ownership.signal.aborted || err instanceof WorkflowCancellationError) {
|
|
1530
|
+
await markWorkflowCancelled(store);
|
|
1531
|
+
throw new WorkflowCancellationError();
|
|
1532
|
+
}
|
|
1533
|
+
if (store !== undefined && planReviewPlan !== undefined) {
|
|
1081
1534
|
try {
|
|
1082
1535
|
await markWorkflowFailed(store, runId, err);
|
|
1083
1536
|
}
|
|
@@ -1106,26 +1559,17 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1106
1559
|
});
|
|
1107
1560
|
return { content, isError: true };
|
|
1108
1561
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
catch (closeError) {
|
|
1114
|
-
logger.warn({ err: closeError }, "store.close() failed during async initialization cleanup");
|
|
1115
|
-
}
|
|
1116
|
-
releaseMutex(runId);
|
|
1117
|
-
}
|
|
1562
|
+
throw err;
|
|
1563
|
+
}
|
|
1564
|
+
finally {
|
|
1565
|
+
await ownership.finish(store);
|
|
1118
1566
|
}
|
|
1119
|
-
releaseMutex(runId);
|
|
1120
|
-
throw err;
|
|
1121
1567
|
}
|
|
1122
1568
|
if (store === undefined || effectiveDefinition === undefined) {
|
|
1123
|
-
|
|
1569
|
+
await ownership.finish(store);
|
|
1124
1570
|
throw new McpError(ErrorCode.InternalError, `Failed to initialize async run ${runId}`);
|
|
1125
1571
|
}
|
|
1126
|
-
|
|
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) => {
|
|
1572
|
+
void runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, true, ownership, undefined, store, effectiveDefinition, undefined, initializedPlanReview, initialContextPolicy, admittedRosterName).catch((err) => {
|
|
1129
1573
|
if (err instanceof WorkflowCancellationError) {
|
|
1130
1574
|
logger.info({ runId }, "workflow run cancelled");
|
|
1131
1575
|
return;
|
|
@@ -1134,7 +1578,9 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1134
1578
|
});
|
|
1135
1579
|
// Additive-only ack shape: base {run_id, status:"running"} is unchanged;
|
|
1136
1580
|
// the duplicate-replay field appears only when detected (warn-only, F6).
|
|
1581
|
+
// resolved_roster is the admission-time requested-value projection (E2).
|
|
1137
1582
|
const duplicateOfFailedRun = buildDuplicateOfFailedRunField(store);
|
|
1583
|
+
const resolvedRoster = store.runState.resolvedRoster;
|
|
1138
1584
|
return {
|
|
1139
1585
|
content: [{
|
|
1140
1586
|
type: "text",
|
|
@@ -1144,11 +1590,14 @@ async function executeRunWorkflow(server, options, args, signal, progressToken)
|
|
|
1144
1590
|
...(duplicateOfFailedRun !== undefined
|
|
1145
1591
|
? { duplicate_of_failed_run: duplicateOfFailedRun }
|
|
1146
1592
|
: {}),
|
|
1593
|
+
...(resolvedRoster !== undefined
|
|
1594
|
+
? { resolved_roster: resolvedRoster }
|
|
1595
|
+
: {}),
|
|
1147
1596
|
}),
|
|
1148
1597
|
}],
|
|
1149
1598
|
};
|
|
1150
1599
|
}
|
|
1151
|
-
return await runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, false,
|
|
1600
|
+
return await runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, false, ownership, progressToken, undefined, undefined, planReviewPlan, undefined, initialContextPolicy, admittedRosterName);
|
|
1152
1601
|
}
|
|
1153
1602
|
async function preparePlanReviewSnapshot(plan) {
|
|
1154
1603
|
try {
|
|
@@ -1224,7 +1673,100 @@ function resolvePlanReviewChairmanCallId(definition) {
|
|
|
1224
1673
|
}
|
|
1225
1674
|
return callId;
|
|
1226
1675
|
}
|
|
1227
|
-
function
|
|
1676
|
+
function projectResolvedRosterSeat(call, index, stancePresent) {
|
|
1677
|
+
return {
|
|
1678
|
+
index,
|
|
1679
|
+
cli_name: call.cli_name,
|
|
1680
|
+
...(call.model !== undefined ? { model: call.model } : {}),
|
|
1681
|
+
...(call.effort !== undefined ? { effort: call.effort } : {}),
|
|
1682
|
+
stance_present: stancePresent,
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
function projectResolvedRosterDelegate(policy, resolvedVariables) {
|
|
1686
|
+
const delegate = policy.effectiveDelegate;
|
|
1687
|
+
if (delegate?.enabled !== true) {
|
|
1688
|
+
return { enabled: false };
|
|
1689
|
+
}
|
|
1690
|
+
const finalizerEnabled = effectiveFinalizerEnabled(delegate, resolvedVariables);
|
|
1691
|
+
return {
|
|
1692
|
+
enabled: true,
|
|
1693
|
+
curator: {
|
|
1694
|
+
cli_name: delegate.curator.cli_name,
|
|
1695
|
+
role: delegate.curator.role,
|
|
1696
|
+
...(delegate.curator.model !== undefined ? { model: delegate.curator.model } : {}),
|
|
1697
|
+
...(delegate.curator.effort !== undefined ? { effort: delegate.curator.effort } : {}),
|
|
1698
|
+
},
|
|
1699
|
+
finalizer: {
|
|
1700
|
+
cli_name: delegate.finalizer.cli_name,
|
|
1701
|
+
role: delegate.finalizer.role,
|
|
1702
|
+
...(delegate.finalizer.model !== undefined ? { model: delegate.finalizer.model } : {}),
|
|
1703
|
+
...(delegate.finalizer.effort !== undefined ? { effort: delegate.finalizer.effort } : {}),
|
|
1704
|
+
effective_enabled: finalizerEnabled,
|
|
1705
|
+
},
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Requested-value roster projection for council workflows. Call AFTER
|
|
1710
|
+
* `expandCouncilComposition` / `expandRunWorkflowDefinition`, AFTER
|
|
1711
|
+
* `resolveEffectiveChairmanCall` values are on the definition (or passed as
|
|
1712
|
+
* parsed chairman knobs), and AFTER `effectiveFinalizerEnabled` can be
|
|
1713
|
+
* evaluated from `initialContextPolicy` — and BEFORE `WorkflowStateStore.create()`.
|
|
1714
|
+
* `plan-review-receipts` and any non-council workflow return undefined.
|
|
1715
|
+
*/
|
|
1716
|
+
export function buildResolvedRoster(input) {
|
|
1717
|
+
if (!supportsRuntimeMembers(input.workflow)) {
|
|
1718
|
+
return undefined;
|
|
1719
|
+
}
|
|
1720
|
+
const stages = input.definition.stages;
|
|
1721
|
+
if (stages.length !== COUNCIL_STAGE_COUNT) {
|
|
1722
|
+
return undefined;
|
|
1723
|
+
}
|
|
1724
|
+
const memberCalls = stages[0]?.calls;
|
|
1725
|
+
const reviewerCalls = stages[1]?.calls;
|
|
1726
|
+
const chairmanCall = stages[3]?.calls[0];
|
|
1727
|
+
if (memberCalls === undefined || reviewerCalls === undefined || chairmanCall === undefined) {
|
|
1728
|
+
return undefined;
|
|
1729
|
+
}
|
|
1730
|
+
const members = memberCalls.map((call, index) => projectResolvedRosterSeat(call, index, input.parsedMembers?.[index]?.stance !== undefined));
|
|
1731
|
+
const reviewers = reviewerCalls.map((call, index) => projectResolvedRosterSeat(call, index, false));
|
|
1732
|
+
const effectiveChairman = resolveEffectiveChairmanCall({
|
|
1733
|
+
staticCall: chairmanCall,
|
|
1734
|
+
chairmanCliName: input.parsedChairman,
|
|
1735
|
+
chairmanModel: input.parsedChairmanModel,
|
|
1736
|
+
chairmanEffort: input.parsedChairmanEffort,
|
|
1737
|
+
});
|
|
1738
|
+
const delegate = projectResolvedRosterDelegate(input.initialContextPolicy, input.resolvedVariables);
|
|
1739
|
+
const preflightCalls = delegate.enabled === true
|
|
1740
|
+
? 1 + (delegate.finalizer.effective_enabled ? 1 : 0)
|
|
1741
|
+
: 0;
|
|
1742
|
+
return ResolvedRosterSchema.parse({
|
|
1743
|
+
...(input.rosterName !== undefined ? { roster: input.rosterName } : {}),
|
|
1744
|
+
members,
|
|
1745
|
+
reviewers,
|
|
1746
|
+
chairman: {
|
|
1747
|
+
cli_name: effectiveChairman.cli_name,
|
|
1748
|
+
...(effectiveChairman.model !== undefined ? { model: effectiveChairman.model } : {}),
|
|
1749
|
+
...(effectiveChairman.effort !== undefined ? { effort: effectiveChairman.effort } : {}),
|
|
1750
|
+
},
|
|
1751
|
+
delegate,
|
|
1752
|
+
provider_spawns: members.length + reviewers.length + 1,
|
|
1753
|
+
preflight_calls: preflightCalls,
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
function resolvedRosterForAdmission(workflow, definition, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, initialContextPolicy, resolvedVariables, rosterName) {
|
|
1757
|
+
return buildResolvedRoster({
|
|
1758
|
+
workflow,
|
|
1759
|
+
definition,
|
|
1760
|
+
...(parsedMembers !== undefined ? { parsedMembers } : {}),
|
|
1761
|
+
...(parsedChairman !== undefined ? { parsedChairman } : {}),
|
|
1762
|
+
...(parsedChairmanModel !== undefined ? { parsedChairmanModel } : {}),
|
|
1763
|
+
...(parsedChairmanEffort !== undefined ? { parsedChairmanEffort } : {}),
|
|
1764
|
+
initialContextPolicy,
|
|
1765
|
+
resolvedVariables,
|
|
1766
|
+
...(rosterName !== undefined ? { rosterName } : {}),
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
function expandRunWorkflowDefinition(definition, workflow, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, planReviewPlan,
|
|
1228
1770
|
/**
|
|
1229
1771
|
* The already-verified snapshot. This function must NEVER read `plan_file`:
|
|
1230
1772
|
* the text it inlines is the digest-verified canonical copy or nothing.
|
|
@@ -1240,18 +1782,22 @@ preparedSnapshot) {
|
|
|
1240
1782
|
// string cannot carry. This is the single read — never re-read `plan_file`.
|
|
1241
1783
|
preparedSnapshot?.ok === true ? preparedSnapshot.canonical : undefined);
|
|
1242
1784
|
}
|
|
1243
|
-
return parsedMembers != null ||
|
|
1244
|
-
|
|
1785
|
+
return parsedMembers != null ||
|
|
1786
|
+
parsedChairman != null ||
|
|
1787
|
+
parsedChairmanModel !== undefined ||
|
|
1788
|
+
parsedChairmanEffort !== undefined
|
|
1789
|
+
? expandCouncilComposition(definition, parsedMembers, workflow, parsedChairman, parsedChairmanModel, parsedChairmanEffort)
|
|
1245
1790
|
: definition;
|
|
1246
1791
|
}
|
|
1247
|
-
async function runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, isAsync,
|
|
1792
|
+
async function runWorkflowDetached(server, options, workflow, runId, resolvedVariables, debug, receipt, validatorRegistry, loaded, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, isAsync, ownership, progressToken, preCreatedStore, preExpandedDefinition, planReviewPlan, preInitializedPlanReview, initialContextPolicy, rosterName) {
|
|
1248
1793
|
let store = preCreatedStore ?? null;
|
|
1249
1794
|
let backend = null;
|
|
1250
1795
|
let reporter;
|
|
1251
|
-
let shutdownHandle;
|
|
1252
1796
|
let releaseLifecycleBinding;
|
|
1253
1797
|
let initializedPlanReview = preInitializedPlanReview;
|
|
1798
|
+
const signal = ownership.signal;
|
|
1254
1799
|
try {
|
|
1800
|
+
ownership.throwIfCancelled();
|
|
1255
1801
|
// The async admission path already expanded the composition (and read the
|
|
1256
1802
|
// snapshot) in the foreground; reuse both to avoid redundant work and a
|
|
1257
1803
|
// second `plan_file` read.
|
|
@@ -1259,11 +1805,13 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1259
1805
|
planReviewPlan === undefined
|
|
1260
1806
|
? undefined
|
|
1261
1807
|
: await preparePlanReviewSnapshot(planReviewPlan);
|
|
1808
|
+
ownership.throwIfCancelled();
|
|
1262
1809
|
const effectiveDefinition = preExpandedDefinition ??
|
|
1263
|
-
expandRunWorkflowDefinition(loaded.normalized, workflow, parsedMembers, parsedChairman, planReviewPlan, preparedSnapshot);
|
|
1810
|
+
expandRunWorkflowDefinition(loaded.normalized, workflow, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, planReviewPlan, preparedSnapshot);
|
|
1264
1811
|
// Create state store (resumable if child config path is set) unless the
|
|
1265
1812
|
// async admission path already created it in the foreground.
|
|
1266
1813
|
if (store == null) {
|
|
1814
|
+
const resolvedRoster = resolvedRosterForAdmission(workflow, effectiveDefinition, parsedMembers, parsedChairman, parsedChairmanModel, parsedChairmanEffort, initialContextPolicy, resolvedVariables, rosterName);
|
|
1267
1815
|
store = await WorkflowStateStore.create({
|
|
1268
1816
|
runId,
|
|
1269
1817
|
definition: effectiveDefinition,
|
|
@@ -1271,8 +1819,11 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1271
1819
|
debugKeepArtifacts: debug,
|
|
1272
1820
|
async: isAsync,
|
|
1273
1821
|
initialContextPolicy,
|
|
1822
|
+
...(resolvedRoster !== undefined ? { resolvedRoster } : {}),
|
|
1823
|
+
...(rosterName !== undefined ? { rosterName } : {}),
|
|
1274
1824
|
});
|
|
1275
1825
|
}
|
|
1826
|
+
ownership.throwIfCancelled();
|
|
1276
1827
|
releaseLifecycleBinding = options.contextBuilderLifecycleRouter?.bind(runId, store);
|
|
1277
1828
|
// A-5/A-6: snapshot + contract + typed subtree land here — after state
|
|
1278
1829
|
// exists, before the backend is even created, so a snapshot/digest failure
|
|
@@ -1281,28 +1832,15 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1281
1832
|
if (planReviewPlan !== undefined && preparedSnapshot !== undefined) {
|
|
1282
1833
|
initializedPlanReview = await initializePlanReviewRunState(store, planReviewPlan, effectiveDefinition, preparedSnapshot);
|
|
1283
1834
|
}
|
|
1835
|
+
ownership.throwIfCancelled();
|
|
1284
1836
|
const sessionLogGeneration = store.sessionLogGeneration ??
|
|
1285
1837
|
await inspectLegacyWorkflowSessionLogGeneration(store.runDir, runId);
|
|
1286
|
-
|
|
1287
|
-
throw new WorkflowCancellationError();
|
|
1288
|
-
// Create backend via factory
|
|
1838
|
+
ownership.throwIfCancelled();
|
|
1289
1839
|
backend = options.createBackend(runId);
|
|
1840
|
+
ownership.setBackend(backend);
|
|
1841
|
+
ownership.throwIfCancelled();
|
|
1290
1842
|
await backend.connect();
|
|
1291
|
-
|
|
1292
|
-
const controller = new AbortController();
|
|
1293
|
-
if (signal.aborted) {
|
|
1294
|
-
controller.abort(new WorkflowCancellationError());
|
|
1295
|
-
}
|
|
1296
|
-
else {
|
|
1297
|
-
signal.addEventListener("abort", () => {
|
|
1298
|
-
controller.abort(new WorkflowCancellationError());
|
|
1299
|
-
}, { once: true });
|
|
1300
|
-
}
|
|
1301
|
-
shutdownHandle = shutdownCoordinator.register(backend, controller, async () => {
|
|
1302
|
-
if (store != null) {
|
|
1303
|
-
await store.markCancelled();
|
|
1304
|
-
}
|
|
1305
|
-
});
|
|
1843
|
+
ownership.throwIfCancelled();
|
|
1306
1844
|
// Build engine hooks — capture store in a const for closure narrowing
|
|
1307
1845
|
const storeRef = store;
|
|
1308
1846
|
reporter = progressToken != null
|
|
@@ -1332,13 +1870,16 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1332
1870
|
runId,
|
|
1333
1871
|
sessionLogGeneration,
|
|
1334
1872
|
runDir: storeRef.runDir,
|
|
1335
|
-
signal
|
|
1873
|
+
signal,
|
|
1336
1874
|
hooks,
|
|
1337
1875
|
contextBuilderRunner: options.contextBuilderRunner,
|
|
1338
1876
|
contextDefault: options.contextDefault,
|
|
1339
1877
|
defaultCurator: options.defaultCurator,
|
|
1340
1878
|
commitPreflightContext: storeRef.commitPreflightContext?.bind(storeRef),
|
|
1341
1879
|
initialContextPolicy,
|
|
1880
|
+
...(options.deriveInvocationAudit === undefined
|
|
1881
|
+
? {}
|
|
1882
|
+
: { deriveInvocationAudit: options.deriveInvocationAudit }),
|
|
1342
1883
|
...(initializedPlanReview !== undefined
|
|
1343
1884
|
? {
|
|
1344
1885
|
planReviewContextPolicy: initializedPlanReview.context_policy,
|
|
@@ -1352,8 +1893,6 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1352
1893
|
: {}),
|
|
1353
1894
|
});
|
|
1354
1895
|
const result = await engine.execute();
|
|
1355
|
-
if (shutdownHandle !== undefined)
|
|
1356
|
-
shutdownCoordinator.unregister(shutdownHandle);
|
|
1357
1896
|
// Build summary (fail-open)
|
|
1358
1897
|
let summary;
|
|
1359
1898
|
if (receipt) {
|
|
@@ -1373,16 +1912,13 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1373
1912
|
debugBlock: debug ? buildDebugBlock(runId, result, store) : undefined,
|
|
1374
1913
|
});
|
|
1375
1914
|
appendDuplicateReplayBlock(content, store);
|
|
1915
|
+
appendResolvedRosterBlock(content, store);
|
|
1376
1916
|
return { content };
|
|
1377
1917
|
}
|
|
1378
1918
|
catch (error) {
|
|
1379
|
-
if (
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (store != null) {
|
|
1383
|
-
await store.markCancelled();
|
|
1384
|
-
}
|
|
1385
|
-
throw error;
|
|
1919
|
+
if (signal.aborted || error instanceof WorkflowCancellationError) {
|
|
1920
|
+
await markWorkflowCancelled(store);
|
|
1921
|
+
throw new WorkflowCancellationError();
|
|
1386
1922
|
}
|
|
1387
1923
|
// Persist a terminal "failed" record for post-admission setup failures
|
|
1388
1924
|
// (e.g. backend.connect() / engine construction). Idempotent: the engine
|
|
@@ -1417,29 +1953,17 @@ async function runWorkflowDetached(server, options, workflow, runId, resolvedVar
|
|
|
1417
1953
|
debugBlock: debug && store != null ? buildErrorDebugBlock(runId, store) : undefined,
|
|
1418
1954
|
});
|
|
1419
1955
|
appendDuplicateReplayBlock(content, store);
|
|
1956
|
+
appendResolvedRosterBlock(content, store);
|
|
1420
1957
|
return { content, isError: true };
|
|
1421
1958
|
}
|
|
1422
1959
|
finally {
|
|
1423
|
-
releaseLifecycleBinding?.();
|
|
1424
|
-
reporter?.stop();
|
|
1425
|
-
try {
|
|
1426
|
-
if (backend != null && !backend.isTerminated) {
|
|
1427
|
-
await backend.terminate();
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
catch (cleanupErr) {
|
|
1431
|
-
logger.warn({ err: cleanupErr }, "backend.terminate() failed during cleanup");
|
|
1432
|
-
}
|
|
1433
1960
|
try {
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1961
|
+
releaseLifecycleBinding?.();
|
|
1962
|
+
reporter?.stop();
|
|
1437
1963
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1964
|
+
finally {
|
|
1965
|
+
await ownership.finish(store);
|
|
1440
1966
|
}
|
|
1441
|
-
cancelRegistry.delete(runId);
|
|
1442
|
-
releaseMutex(runId);
|
|
1443
1967
|
}
|
|
1444
1968
|
}
|
|
1445
1969
|
async function executeResumeWorkflow(server, options, args, signal, progressToken) {
|
|
@@ -1485,18 +2009,23 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1485
2009
|
isError: true,
|
|
1486
2010
|
};
|
|
1487
2011
|
}
|
|
1488
|
-
|
|
2012
|
+
const ownership = acquireWorkflowRun(runId, options, signal);
|
|
2013
|
+
const ownedSignal = ownership.signal;
|
|
1489
2014
|
let store = null;
|
|
1490
2015
|
let backend = null;
|
|
1491
2016
|
let reporter;
|
|
1492
|
-
let shutdownHandle;
|
|
1493
2017
|
let releaseLifecycleBinding;
|
|
1494
2018
|
try {
|
|
2019
|
+
ownership.throwIfCancelled();
|
|
1495
2020
|
const resumeOptions = {
|
|
1496
2021
|
runId,
|
|
1497
2022
|
debugKeepArtifacts: debug,
|
|
1498
2023
|
};
|
|
1499
2024
|
store = await WorkflowStateStore.resume(resumeOptions);
|
|
2025
|
+
// Open this attempt before classifying an abort during reconstruction;
|
|
2026
|
+
// otherwise a prior terminal record would suppress markCancelled.
|
|
2027
|
+
await store.beginResumeAttempt();
|
|
2028
|
+
ownership.throwIfCancelled();
|
|
1500
2029
|
releaseLifecycleBinding = options.contextBuilderLifecycleRouter?.bind(runId, store);
|
|
1501
2030
|
const validatorRegistry = createValidatorRegistry();
|
|
1502
2031
|
const initialContextPolicy = store.runState.initialContextPolicy ??
|
|
@@ -1546,14 +2075,10 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1546
2075
|
contextPolicy: restoredPlanReview.state.context_policy,
|
|
1547
2076
|
});
|
|
1548
2077
|
}
|
|
1549
|
-
|
|
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();
|
|
2078
|
+
ownership.throwIfCancelled();
|
|
1553
2079
|
const sessionLogGeneration = store.sessionLogGeneration ??
|
|
1554
2080
|
await inspectLegacyWorkflowSessionLogGeneration(store.runDir, runId);
|
|
1555
|
-
|
|
1556
|
-
throw new WorkflowCancellationError();
|
|
2081
|
+
ownership.throwIfCancelled();
|
|
1557
2082
|
if (store.runState.workflow_context === undefined) {
|
|
1558
2083
|
const preflightEngine = new WorkflowEngine({
|
|
1559
2084
|
definition: store.definition,
|
|
@@ -1562,7 +2087,7 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1562
2087
|
validatorRegistry,
|
|
1563
2088
|
runId,
|
|
1564
2089
|
runDir: store.runDir,
|
|
1565
|
-
signal,
|
|
2090
|
+
signal: ownedSignal,
|
|
1566
2091
|
resume: store.resume,
|
|
1567
2092
|
contextBuilderRunner: options.contextBuilderRunner,
|
|
1568
2093
|
contextDefault: options.contextDefault,
|
|
@@ -1570,6 +2095,9 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1570
2095
|
commitPreflightContext: store.commitPreflightContext.bind(store),
|
|
1571
2096
|
requireContextPreflight: true,
|
|
1572
2097
|
initialContextPolicy,
|
|
2098
|
+
...(options.deriveInvocationAudit === undefined
|
|
2099
|
+
? {}
|
|
2100
|
+
: { deriveInvocationAudit: options.deriveInvocationAudit }),
|
|
1573
2101
|
...(restoredPlanReview === undefined
|
|
1574
2102
|
? {}
|
|
1575
2103
|
: {
|
|
@@ -1579,24 +2107,20 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1579
2107
|
commitPlanReviewContextProvenance: store.commitPlanReviewContextProvenance.bind(store),
|
|
1580
2108
|
}),
|
|
1581
2109
|
});
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
2110
|
+
// D1: bound the resume-path preflight by the same fresh `timeout_seconds`
|
|
2111
|
+
// budget a resumed run receives. `execute()` is never called on this engine,
|
|
2112
|
+
// so without this the curator/finalizer delegate runs unbounded. The engine is
|
|
2113
|
+
// constructed with `signal: ownedSignal`, which createDeadlineController chains,
|
|
2114
|
+
// so cancellation ownership is unchanged.
|
|
2115
|
+
await preflightEngine.prepareContextPreflightWithDeadline();
|
|
2116
|
+
}
|
|
2117
|
+
// Create backend only after the owned resume preflight has completed.
|
|
2118
|
+
ownership.throwIfCancelled();
|
|
1585
2119
|
backend = options.createBackend(runId);
|
|
2120
|
+
ownership.setBackend(backend);
|
|
2121
|
+
ownership.throwIfCancelled();
|
|
1586
2122
|
await backend.connect();
|
|
1587
|
-
|
|
1588
|
-
const controller = new AbortController();
|
|
1589
|
-
if (signal.aborted) {
|
|
1590
|
-
controller.abort(new WorkflowCancellationError());
|
|
1591
|
-
}
|
|
1592
|
-
else {
|
|
1593
|
-
signal.addEventListener("abort", () => {
|
|
1594
|
-
controller.abort(new WorkflowCancellationError());
|
|
1595
|
-
}, { once: true });
|
|
1596
|
-
}
|
|
1597
|
-
shutdownHandle = shutdownCoordinator.register(backend, controller, async () => {
|
|
1598
|
-
await store.markCancelled();
|
|
1599
|
-
});
|
|
2123
|
+
ownership.throwIfCancelled();
|
|
1600
2124
|
// Build engine hooks with progress reporter
|
|
1601
2125
|
reporter = progressToken != null
|
|
1602
2126
|
? new WorkflowProgressReporter(server, progressToken, runId, store.definition)
|
|
@@ -1630,7 +2154,7 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1630
2154
|
runId,
|
|
1631
2155
|
sessionLogGeneration,
|
|
1632
2156
|
runDir: store.runDir,
|
|
1633
|
-
signal:
|
|
2157
|
+
signal: ownedSignal,
|
|
1634
2158
|
hooks: rpt != null ? hooks : store.hooks,
|
|
1635
2159
|
resume: store.resume,
|
|
1636
2160
|
contextBuilderRunner: options.contextBuilderRunner,
|
|
@@ -1640,6 +2164,9 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1640
2164
|
restoredWorkflowContext: store.runState.workflow_context,
|
|
1641
2165
|
requireContextPreflight: false,
|
|
1642
2166
|
initialContextPolicy,
|
|
2167
|
+
...(options.deriveInvocationAudit === undefined
|
|
2168
|
+
? {}
|
|
2169
|
+
: { deriveInvocationAudit: options.deriveInvocationAudit }),
|
|
1643
2170
|
// A-7: replay the SAME enforced exclusion on resume, reconstructed from
|
|
1644
2171
|
// persisted state rather than re-derived from caller variables.
|
|
1645
2172
|
...(restoredPlanReview !== undefined
|
|
@@ -1656,8 +2183,6 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1656
2183
|
: {}),
|
|
1657
2184
|
});
|
|
1658
2185
|
const result = await engine.execute();
|
|
1659
|
-
if (shutdownHandle !== undefined)
|
|
1660
|
-
shutdownCoordinator.unregister(shutdownHandle);
|
|
1661
2186
|
// Build summary (fail-open)
|
|
1662
2187
|
let summary;
|
|
1663
2188
|
if (receipt) {
|
|
@@ -1679,13 +2204,9 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1679
2204
|
return { content };
|
|
1680
2205
|
}
|
|
1681
2206
|
catch (error) {
|
|
1682
|
-
if (
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
if (store != null) {
|
|
1686
|
-
await store.markCancelled();
|
|
1687
|
-
}
|
|
1688
|
-
throw error;
|
|
2207
|
+
if (ownedSignal.aborted || error instanceof WorkflowCancellationError) {
|
|
2208
|
+
await markWorkflowCancelled(store);
|
|
2209
|
+
throw new WorkflowCancellationError();
|
|
1689
2210
|
}
|
|
1690
2211
|
// Persist a terminal "failed" record for post-admission setup failures.
|
|
1691
2212
|
// Idempotent: won't clobber an engine-emitted terminal status.
|
|
@@ -1718,25 +2239,13 @@ async function executeResumeWorkflow(server, options, args, signal, progressToke
|
|
|
1718
2239
|
return { content, isError: true };
|
|
1719
2240
|
}
|
|
1720
2241
|
finally {
|
|
1721
|
-
releaseLifecycleBinding?.();
|
|
1722
|
-
reporter?.stop();
|
|
1723
|
-
try {
|
|
1724
|
-
if (backend != null && !backend.isTerminated) {
|
|
1725
|
-
await backend.terminate();
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
catch (cleanupErr) {
|
|
1729
|
-
logger.warn({ err: cleanupErr }, "backend.terminate() failed during cleanup");
|
|
1730
|
-
}
|
|
1731
2242
|
try {
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
}
|
|
2243
|
+
releaseLifecycleBinding?.();
|
|
2244
|
+
reporter?.stop();
|
|
1735
2245
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
2246
|
+
finally {
|
|
2247
|
+
await ownership.finish(store);
|
|
1738
2248
|
}
|
|
1739
|
-
releaseMutex(runId);
|
|
1740
2249
|
}
|
|
1741
2250
|
}
|
|
1742
2251
|
// ---------------------------------------------------------------------------
|
|
@@ -1916,33 +2425,60 @@ async function executeWorkflowRunStatus(_options, args) {
|
|
|
1916
2425
|
selected: lifecycle.coverage.selected,
|
|
1917
2426
|
missing: lifecycle.coverage.missing,
|
|
1918
2427
|
};
|
|
2428
|
+
const finalizer = lifecycle.finalizer;
|
|
2429
|
+
if (typeof finalizer.effective_enabled === "boolean" &&
|
|
2430
|
+
typeof finalizer.execution_status === "string") {
|
|
2431
|
+
const existingDelegate = context["delegate"] !== undefined &&
|
|
2432
|
+
typeof context["delegate"] === "object" &&
|
|
2433
|
+
context["delegate"] !== null
|
|
2434
|
+
? context["delegate"]
|
|
2435
|
+
: {};
|
|
2436
|
+
context["delegate"] = {
|
|
2437
|
+
...existingDelegate,
|
|
2438
|
+
finalizer: {
|
|
2439
|
+
effective_enabled: finalizer.effective_enabled,
|
|
2440
|
+
execution_status: finalizer.execution_status,
|
|
2441
|
+
// The lifecycle contract only admits a score on succeeded
|
|
2442
|
+
// execution; re-check here so the projection cannot surface one
|
|
2443
|
+
// for any other outcome.
|
|
2444
|
+
...(finalizer.execution_status === "succeeded" &&
|
|
2445
|
+
typeof finalizer.score === "number" &&
|
|
2446
|
+
Number.isFinite(finalizer.score)
|
|
2447
|
+
? { score: finalizer.score }
|
|
2448
|
+
: {}),
|
|
2449
|
+
},
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
1919
2452
|
}
|
|
1920
2453
|
projection["context"] = context;
|
|
1921
2454
|
}
|
|
2455
|
+
else if (lifecycle != null &&
|
|
2456
|
+
!isTerminalContextBuilderLifecycleStatus(lifecycle.status)) {
|
|
2457
|
+
// Preflight in flight: `context` is reserved for committed-preflight
|
|
2458
|
+
// semantics, so mid-scan progress rides on its own top-level key.
|
|
2459
|
+
projection["context_progress"] = {
|
|
2460
|
+
phase: lifecycle.phase,
|
|
2461
|
+
status: lifecycle.status,
|
|
2462
|
+
updated_at: lifecycle.updated_at,
|
|
2463
|
+
counts: {
|
|
2464
|
+
files_seen: lifecycle.counts.files_seen,
|
|
2465
|
+
files_considered: lifecycle.counts.files_considered,
|
|
2466
|
+
files_selected: lifecycle.counts.files_selected,
|
|
2467
|
+
files_omitted: lifecycle.counts.files_omitted,
|
|
2468
|
+
search_results: lifecycle.counts.search_results,
|
|
2469
|
+
bytes_read: lifecycle.counts.bytes_read,
|
|
2470
|
+
bytes_written: lifecycle.counts.bytes_written,
|
|
2471
|
+
},
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
1922
2474
|
// F7 read-time health projection: top-level run status alone overstates
|
|
1923
2475
|
// health when on_failure:continue swallowed seat failures or context
|
|
1924
2476
|
// preflight completed partial/degraded. Computed only from already-persisted
|
|
1925
2477
|
// data; additive and advisory — never mutates status semantics.
|
|
1926
|
-
|
|
1927
|
-
if (
|
|
1928
|
-
|
|
1929
|
-
}
|
|
1930
|
-
if (lifecycle?.status === "partial") {
|
|
1931
|
-
healthReasons.push("context_partial");
|
|
2478
|
+
projection["health"] = computeRunHealth(state, lifecycle);
|
|
2479
|
+
if (state.resolvedRoster !== undefined) {
|
|
2480
|
+
projection["resolved_roster"] = state.resolvedRoster;
|
|
1932
2481
|
}
|
|
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
2482
|
if (isPlanReviewRunState(state)) {
|
|
1947
2483
|
const terminalExecutionStatus = state.status === "succeeded" ||
|
|
1948
2484
|
state.status === "failed" ||
|
|
@@ -2180,6 +2716,14 @@ function executeListWorkflows(options) {
|
|
|
2180
2716
|
if (options.availableCliNames != null && options.availableCliNames.length > 0) {
|
|
2181
2717
|
response["available_cli_names"] = [...options.availableCliNames].sort();
|
|
2182
2718
|
}
|
|
2719
|
+
if (options.adapterCapabilities != null) {
|
|
2720
|
+
const availableClis = [...options.adapterCapabilities()];
|
|
2721
|
+
availableClis.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
2722
|
+
response["available_clis"] = availableClis;
|
|
2723
|
+
}
|
|
2724
|
+
if (options.rosterLoader != null) {
|
|
2725
|
+
response["available_rosters"] = options.rosterLoader.list();
|
|
2726
|
+
}
|
|
2183
2727
|
return {
|
|
2184
2728
|
content: [
|
|
2185
2729
|
{
|
|
@@ -2352,9 +2896,15 @@ const RUN_WORKFLOW_SCHEMA = {
|
|
|
2352
2896
|
default: false,
|
|
2353
2897
|
description: "When true, start the run detached and return {run_id, status:'running'} immediately; poll workflow_run_status for progress and result.",
|
|
2354
2898
|
},
|
|
2899
|
+
roster: {
|
|
2900
|
+
type: "string",
|
|
2901
|
+
pattern: ROSTER_NAME_PATTERN.source,
|
|
2902
|
+
maxLength: 64,
|
|
2903
|
+
description: "Optional named roster preset; exclusive with members, chairman, chairman_model, chairman_effort, and a non-empty variables.cb_delegate.",
|
|
2904
|
+
},
|
|
2355
2905
|
members: {
|
|
2356
2906
|
type: "array",
|
|
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
|
|
2907
|
+
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, an optional analytical stance, and optional requested model/effort values whose support is adapter-declared. 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.",
|
|
2358
2908
|
minItems: 1,
|
|
2359
2909
|
maxItems: 10,
|
|
2360
2910
|
items: {
|
|
@@ -2368,6 +2918,20 @@ const RUN_WORKFLOW_SCHEMA = {
|
|
|
2368
2918
|
type: "string",
|
|
2369
2919
|
description: "Analytical stance or perspective for this council seat. Required for seats at indices 3-9. Server enforces a 200-character limit after whitespace normalization (trim + collapse). Must not contain {{ or }}.",
|
|
2370
2920
|
},
|
|
2921
|
+
model: {
|
|
2922
|
+
type: "string",
|
|
2923
|
+
minLength: 1,
|
|
2924
|
+
maxLength: 128,
|
|
2925
|
+
pattern: MODEL_OVERRIDE_PATTERN.source,
|
|
2926
|
+
description: "Optional requested model for this seat. Support is declared by the selected adapter; omission preserves its configured default.",
|
|
2927
|
+
},
|
|
2928
|
+
effort: {
|
|
2929
|
+
type: "string",
|
|
2930
|
+
minLength: 1,
|
|
2931
|
+
maxLength: 32,
|
|
2932
|
+
pattern: "^[a-z0-9][a-z0-9_-]*$",
|
|
2933
|
+
description: "Optional requested reasoning effort for this seat. Support is declared by the selected adapter; omission preserves its configured default.",
|
|
2934
|
+
},
|
|
2371
2935
|
},
|
|
2372
2936
|
required: ["cli_name"],
|
|
2373
2937
|
additionalProperties: false,
|
|
@@ -2377,6 +2941,20 @@ const RUN_WORKFLOW_SCHEMA = {
|
|
|
2377
2941
|
type: "string",
|
|
2378
2942
|
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.",
|
|
2379
2943
|
},
|
|
2944
|
+
chairman_model: {
|
|
2945
|
+
type: "string",
|
|
2946
|
+
minLength: 1,
|
|
2947
|
+
maxLength: 128,
|
|
2948
|
+
pattern: MODEL_OVERRIDE_PATTERN.source,
|
|
2949
|
+
description: "Optional requested model for the council chairman. Support is declared by the chairman adapter; omission preserves its configured default. May be supplied without chairman.",
|
|
2950
|
+
},
|
|
2951
|
+
chairman_effort: {
|
|
2952
|
+
type: "string",
|
|
2953
|
+
minLength: 1,
|
|
2954
|
+
maxLength: 32,
|
|
2955
|
+
pattern: "^[a-z0-9][a-z0-9_-]*$",
|
|
2956
|
+
description: "Optional requested reasoning effort for the council chairman. Support is declared by the chairman adapter; omission preserves its configured default. May be supplied without chairman.",
|
|
2957
|
+
},
|
|
2380
2958
|
plan_review: PLAN_REVIEW_INPUT_SCHEMA,
|
|
2381
2959
|
},
|
|
2382
2960
|
required: ["workflow", "run_id"],
|
|
@@ -2536,7 +3114,7 @@ export function getWorkflowToolDefinitions(workflowsPath, availableCliNames) {
|
|
|
2536
3114
|
return [
|
|
2537
3115
|
{
|
|
2538
3116
|
name: "run_workflow",
|
|
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.`,
|
|
3117
|
+
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. Council runs include a \`resolved_roster\` of requested seat and delegate values (no provenance) on the async ack, a trailing sync content block, and workflow_run_status.`,
|
|
2540
3118
|
inputSchema: runSchema,
|
|
2541
3119
|
},
|
|
2542
3120
|
{
|
|
@@ -2553,7 +3131,7 @@ export function getWorkflowToolDefinitions(workflowsPath, availableCliNames) {
|
|
|
2553
3131
|
},
|
|
2554
3132
|
{
|
|
2555
3133
|
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}.",
|
|
3134
|
+
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, context_external_gaps — the finalizer reported evidence gaps outside the delegate packet under external_gap_policy:\"degrade\", so conclusions depending on them are conditional). When a context-builder lifecycle snapshot is persisted, the `context` block additionally includes `lifecycle_status` and `coverage` {policy, status, required, selected, missing}. A parsed snapshot also projects `context.delegate.finalizer` {effective_enabled, execution_status, score?}; `score` is present only for a succeeded finalizer that recorded one, and the finalizer key is omitted when the lifecycle is missing, unparseable, or those fields are absent. While context preflight is still running (non-terminal lifecycle, no committed context yet) the `context` block is absent and a top-level `context_progress` {phase, status, updated_at, counts} reports scan progress instead. Found council runs also include a top-level `resolved_roster` of requested seat and delegate values (no provenance) when the run persisted one; older runs omit the key.",
|
|
2557
3135
|
inputSchema: WORKFLOW_RUN_STATUS_SCHEMA,
|
|
2558
3136
|
annotations: { readOnlyHint: true },
|
|
2559
3137
|
},
|
|
@@ -2615,21 +3193,35 @@ export async function handleWorkflowToolCall(server, options, name, args, signal
|
|
|
2615
3193
|
export function registerWorkflowTools(server, options) {
|
|
2616
3194
|
// tools/list
|
|
2617
3195
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2618
|
-
|
|
3196
|
+
activeWorkflowHandlers += 1;
|
|
3197
|
+
try {
|
|
3198
|
+
return { tools: getWorkflowToolDefinitions(options.workflowsPath, options.availableCliNames) };
|
|
3199
|
+
}
|
|
3200
|
+
finally {
|
|
3201
|
+
activeWorkflowHandlers -= 1;
|
|
3202
|
+
notifyWorkflowActivity();
|
|
3203
|
+
}
|
|
2619
3204
|
});
|
|
2620
3205
|
// tools/call
|
|
2621
3206
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
3207
|
+
activeWorkflowHandlers += 1;
|
|
3208
|
+
try {
|
|
3209
|
+
const { name, arguments: args } = request.params;
|
|
3210
|
+
const progressToken = request.params._meta?.progressToken;
|
|
3211
|
+
const result = await handleWorkflowToolCall(server, options, name, args ?? {}, extra.signal, progressToken);
|
|
3212
|
+
if (result != null) {
|
|
3213
|
+
return result;
|
|
3214
|
+
}
|
|
3215
|
+
logger.warn({ tool: name }, "unknown tool requested");
|
|
3216
|
+
return {
|
|
3217
|
+
content: [{ type: "text", text: `Unknown tool: ${String(name)}` }],
|
|
3218
|
+
isError: true,
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
finally {
|
|
3222
|
+
activeWorkflowHandlers -= 1;
|
|
3223
|
+
notifyWorkflowActivity();
|
|
2627
3224
|
}
|
|
2628
|
-
logger.warn({ tool: name }, "unknown tool requested");
|
|
2629
|
-
return {
|
|
2630
|
-
content: [{ type: "text", text: `Unknown tool: ${String(name)}` }],
|
|
2631
|
-
isError: true,
|
|
2632
|
-
};
|
|
2633
3225
|
});
|
|
2634
3226
|
}
|
|
2635
3227
|
//# sourceMappingURL=workflow-tools.js.map
|