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
|
@@ -62,18 +62,21 @@
|
|
|
62
62
|
* isError: true with termination_reason: "wall" or "idle", which the
|
|
63
63
|
* workflow engine treats as a transient failure eligible for retry.
|
|
64
64
|
*/
|
|
65
|
+
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
65
66
|
import { stat as fsStat } from "node:fs/promises";
|
|
66
67
|
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
67
68
|
import { basename, isAbsolute, join, resolve, sep } from "node:path";
|
|
68
|
-
import { callClink } from "./clink-client.js";
|
|
69
|
+
import { callClink, extractObservedInvocation } from "./clink-client.js";
|
|
69
70
|
import { getArtifactsDir, getChildMaxConcurrent, getWorkflowsContextDefault, getWorkflowsContextDefaultCurator, getMaxParallel, getMaxPromptBytes, getStateDir, resolveWorkspaceRoot, } from "./config.js";
|
|
70
71
|
import { validatePlanReviewCallBindings, validatePlanReviewPreparedInputPolicy, } from "./plan-review/runtime-policy.js";
|
|
71
72
|
import { CouncilPlanReviewErrorSchema, } from "./plan-review/publication.js";
|
|
72
73
|
import { PlanReviewStageError } from "./plan-review/stage-error.js";
|
|
73
|
-
import { CallExecutionError, InvalidCallError, WorkflowCancellationError, WorkflowError, } from "./errors.js";
|
|
74
|
-
import { analyzeOutputFidelity, enforceOutputFidelity } from "./fidelity.js";
|
|
74
|
+
import { CallExecutionError, InvalidCallError, WorkflowDispatchDeadlineError, WorkflowCancellationError, WorkflowError, } from "./errors.js";
|
|
75
|
+
import { analyzeOutputFidelity, enforceOutputFidelity, enforceTypedResultFidelity, TypedResultFidelityError, } from "./fidelity.js";
|
|
76
|
+
import { monotonicNowMs } from "./monotonic-clock.js";
|
|
75
77
|
import { validateDynamicPaths } from "./path-validation.js";
|
|
76
|
-
import { estimatePromptBudget, enforcePromptBudget, createPathContributor } from "./prompt-budget.js";
|
|
78
|
+
import { DEFAULT_RESERVE_BYTES, estimatePromptBudget, enforcePromptBudget, createPathContributor, } from "./prompt-budget.js";
|
|
79
|
+
import { buildCouncilDeadlineGuidance, COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES, isCouncilExecutionWorkflow, shouldVetoWallTimeoutRetry, } from "./council-execution-policy.js";
|
|
77
80
|
import { DispatchQueue } from "./queue.js";
|
|
78
81
|
import { renderCallPrompt } from "./template.js";
|
|
79
82
|
import { ArtifactStore, OutputSizeError, SchemaValidationError } from "./artifacts.js";
|
|
@@ -85,6 +88,7 @@ import { addPathWithBasename, buildAllowlistFromDiff } from "./citation-check.js
|
|
|
85
88
|
import { logger } from "./logger.js";
|
|
86
89
|
import { resolveWorkflowContextMode, WorkflowContextModeResolverError, } from "./context-mode-resolver.js";
|
|
87
90
|
import { WorkflowDelegateValidationError } from "./delegate-validate.js";
|
|
91
|
+
import { isDelegateExternalGapDegrade } from "./delegate-signature.js";
|
|
88
92
|
import { contextSeedSourceValidationError, resolveSeedSource, synthesizeSeedTerms, } from "./seed-terms.js";
|
|
89
93
|
/**
|
|
90
94
|
* Declarations for variables the ENGINE derives at runtime (not declared in the
|
|
@@ -214,6 +218,26 @@ function receiptDelegateFailureReason(result) {
|
|
|
214
218
|
? failureReason
|
|
215
219
|
: null;
|
|
216
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* The allowed roots a workflow engine derives for itself when the caller supplies
|
|
223
|
+
* none. Exported so admission-time path checks can validate against the SAME set
|
|
224
|
+
* the engine will enforce at dispatch, rather than re-deriving a near-copy.
|
|
225
|
+
*
|
|
226
|
+
* Note this set is engine-owned: `CLINKX_ALLOWED_ROOTS` is not consulted and
|
|
227
|
+
* cannot widen it.
|
|
228
|
+
*/
|
|
229
|
+
export function defaultWorkflowAllowedRoots(runDir) {
|
|
230
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
231
|
+
const stateDir = getStateDir();
|
|
232
|
+
const configuredArtifactsDir = getArtifactsDir();
|
|
233
|
+
return [
|
|
234
|
+
workspaceRoot,
|
|
235
|
+
stateDir,
|
|
236
|
+
configuredArtifactsDir,
|
|
237
|
+
runDir,
|
|
238
|
+
join(runDir, "artifacts"),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
217
241
|
export class WorkflowEngine {
|
|
218
242
|
definition;
|
|
219
243
|
session;
|
|
@@ -247,6 +271,21 @@ export class WorkflowEngine {
|
|
|
247
271
|
planReviewCallBindings;
|
|
248
272
|
commitPlanReviewContextProvenance;
|
|
249
273
|
terminalResultPolicy;
|
|
274
|
+
deriveInvocationAudit;
|
|
275
|
+
/**
|
|
276
|
+
* Workflow budget expiry on the MONOTONIC clock (`monotonicNowMs()`), not on
|
|
277
|
+
* the wall clock. Never compare it to `Date.now()`; read it only through
|
|
278
|
+
* `workflowRemainingMs()`.
|
|
279
|
+
*/
|
|
280
|
+
workflowDeadlineAtMonoMs;
|
|
281
|
+
/**
|
|
282
|
+
* `stage.id`.`call.id` keys already written to a terminal state by
|
|
283
|
+
* `emitTerminalCallAbort`. A parallel stage still surfaces that throw through
|
|
284
|
+
* `materializeUnexpectedFailure`, whose emit carries no startedAt/durationMs
|
|
285
|
+
* and attempt 1 / retryCount 0 — persisted unconditionally, it would erase the
|
|
286
|
+
* terminal entry's timing. Bounded by the run's call count.
|
|
287
|
+
*/
|
|
288
|
+
terminalizedCallKeys = new Set();
|
|
250
289
|
constructor(options) {
|
|
251
290
|
this.definition = options.definition;
|
|
252
291
|
this.session = options.session;
|
|
@@ -284,13 +323,16 @@ export class WorkflowEngine {
|
|
|
284
323
|
});
|
|
285
324
|
this.commitPlanReviewContextProvenance = options.commitPlanReviewContextProvenance;
|
|
286
325
|
this.terminalResultPolicy = options.terminalResultPolicy ?? { kind: "final_call" };
|
|
326
|
+
this.deriveInvocationAudit = options.deriveInvocationAudit;
|
|
287
327
|
this.validateFinalStage();
|
|
288
328
|
this.validateAdvertisedCliNames();
|
|
289
329
|
this.contextProducingCalls = collectContextProducingCalls(this.definition);
|
|
290
330
|
this.seedResumedArtifacts();
|
|
291
331
|
}
|
|
292
332
|
async execute() {
|
|
293
|
-
|
|
333
|
+
this.workflowDeadlineAtMonoMs =
|
|
334
|
+
monotonicNowMs() + this.definition.settings.timeout_seconds * 1000;
|
|
335
|
+
const deadlineController = this.createDeadlineController(this.definition.settings.timeout_seconds * 1000);
|
|
294
336
|
const stages = [];
|
|
295
337
|
const startedAt = new Date().toISOString();
|
|
296
338
|
try {
|
|
@@ -339,7 +381,14 @@ export class WorkflowEngine {
|
|
|
339
381
|
message: `Stage ${stage.id} ${executedStage.ok ? "succeeded" : "failed"}`,
|
|
340
382
|
});
|
|
341
383
|
if (!executedStage.ok && stage.on_failure === "abort") {
|
|
342
|
-
|
|
384
|
+
// Generic boundary for ALL stage aborts, so it must stay permanent by
|
|
385
|
+
// default — a stage that aborts on an ordinary permanent call failure
|
|
386
|
+
// is not a replayable run. The one exception is a genuinely exhausted
|
|
387
|
+
// wall clock: a parallel stage funnels the deadline throw through
|
|
388
|
+
// materializeUnexpectedFailure, which drops the class, so without this
|
|
389
|
+
// the environmental deadline would persist permanent and re-arm the
|
|
390
|
+
// spurious duplicate_of_failed_run warning on an honest replay.
|
|
391
|
+
throw new WorkflowError(`Stage ${stage.id} failed and on_failure=abort`, this.isWorkflowDeadlineExhausted() ? "transient" : undefined);
|
|
343
392
|
}
|
|
344
393
|
}
|
|
345
394
|
const outputText = await this.resolveTerminalResult(stages);
|
|
@@ -419,6 +468,26 @@ export class WorkflowEngine {
|
|
|
419
468
|
}
|
|
420
469
|
finally {
|
|
421
470
|
deadlineController.dispose();
|
|
471
|
+
this.workflowDeadlineAtMonoMs = undefined;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Resume-path entry point. `execute()` is never called on the resume preflight
|
|
476
|
+
* engine, so without this the deadline is never installed and the
|
|
477
|
+
* curator/finalizer delegate runs unbounded. Installs the SAME fresh full
|
|
478
|
+
* `timeout_seconds` budget a resumed run gets (D1), runs preflight under it,
|
|
479
|
+
* and always tears it down.
|
|
480
|
+
*/
|
|
481
|
+
async prepareContextPreflightWithDeadline() {
|
|
482
|
+
this.workflowDeadlineAtMonoMs =
|
|
483
|
+
monotonicNowMs() + this.definition.settings.timeout_seconds * 1000;
|
|
484
|
+
const deadlineController = this.createDeadlineController(this.definition.settings.timeout_seconds * 1000);
|
|
485
|
+
try {
|
|
486
|
+
await this.prepareContextPreflight(deadlineController.signal);
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
deadlineController.dispose();
|
|
490
|
+
this.workflowDeadlineAtMonoMs = undefined;
|
|
422
491
|
}
|
|
423
492
|
}
|
|
424
493
|
/**
|
|
@@ -633,7 +702,9 @@ export class WorkflowEngine {
|
|
|
633
702
|
const maxBackoffSeconds = stage.retry.max_backoff_seconds;
|
|
634
703
|
// Resume never replenishes an already-consumed provider attempt.
|
|
635
704
|
const initialRetryCount = this.resumeNextRetryCounts.get(callKey) ?? 0;
|
|
636
|
-
// Emit call prepared ONCE (input is stable across retries)
|
|
705
|
+
// Emit call prepared ONCE (input is stable across retries).
|
|
706
|
+
// Invocation audit rides the event, never ClinkInput / input.json.
|
|
707
|
+
const invocation = this.maybeInvocationAudit(input);
|
|
637
708
|
await this.emitCallPrepared({
|
|
638
709
|
runId: this.runId,
|
|
639
710
|
workflowName: this.definition.name,
|
|
@@ -643,6 +714,7 @@ export class WorkflowEngine {
|
|
|
643
714
|
retryCount: initialRetryCount,
|
|
644
715
|
contextProducing,
|
|
645
716
|
input: structuredClone(input),
|
|
717
|
+
...(invocation === undefined ? {} : { invocation }),
|
|
646
718
|
});
|
|
647
719
|
let lastError = initialRetryCount > maxRetries
|
|
648
720
|
? new Error("Retry budget exhausted before resume")
|
|
@@ -653,10 +725,23 @@ export class WorkflowEngine {
|
|
|
653
725
|
// the original `input.prompt`, so it never stacks. In-memory only — a
|
|
654
726
|
// resumed retry has no lastError and simply gets no suffix.
|
|
655
727
|
let correctiveSuffix;
|
|
728
|
+
// D7: at most ONE extra JSON-corrective attempt per call under
|
|
729
|
+
// transport_only, whatever max_retries allows. Loop-local, so a resumed
|
|
730
|
+
// call can earn it again — bounded by the persisted retry count anyway.
|
|
731
|
+
let correctiveRetryUsed = false;
|
|
656
732
|
for (let retryCount = initialRetryCount; retryCount <= maxRetries; retryCount += 1) {
|
|
657
733
|
const attempt = retryCount + 1;
|
|
658
734
|
lastRetryCount = retryCount;
|
|
659
|
-
|
|
735
|
+
// Deliberately guarded HERE rather than moved inside the attempt `try`:
|
|
736
|
+
// inside it, a bare deadline WorkflowError would become retryable under
|
|
737
|
+
// retry.mode:"all". Record the terminal outcome, then rethrow unchanged.
|
|
738
|
+
try {
|
|
739
|
+
this.throwIfAborted(signal);
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, error instanceof WorkflowCancellationError ? "cancelled" : "failed");
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
660
745
|
await this.emitCallStateChange({
|
|
661
746
|
runId: this.runId,
|
|
662
747
|
workflowName: this.definition.name,
|
|
@@ -689,6 +774,11 @@ export class WorkflowEngine {
|
|
|
689
774
|
prompt: `${typeof input["prompt"] === "string" ? input["prompt"] : ""}${correctiveSuffix}`,
|
|
690
775
|
};
|
|
691
776
|
const result = await this.invokeCall(stage, call, attemptInput, signal, attempt);
|
|
777
|
+
enforceTypedResultFidelity(result.debugMetadata, {
|
|
778
|
+
stageId: stage.id,
|
|
779
|
+
callId: call.id,
|
|
780
|
+
typedOutput: call.response_contract === "json" || call.validator_schema_id != null,
|
|
781
|
+
});
|
|
692
782
|
const fidelity = analyzeOutputFidelity(result.outputText, result.debugMetadata);
|
|
693
783
|
enforceOutputFidelity(fidelity, {
|
|
694
784
|
stageId: stage.id,
|
|
@@ -698,6 +788,7 @@ export class WorkflowEngine {
|
|
|
698
788
|
const artifact = this.maybeWriteArtifact(stage, call, stageIndex, callIndex, result.outputText);
|
|
699
789
|
const completedAt = new Date().toISOString();
|
|
700
790
|
const durationMs = Date.now() - startedAtMs;
|
|
791
|
+
const observedInvocation = extractObservedInvocation(result.debugMetadata);
|
|
701
792
|
await this.emitCallStateChange({
|
|
702
793
|
runId: this.runId,
|
|
703
794
|
workflowName: this.definition.name,
|
|
@@ -712,6 +803,7 @@ export class WorkflowEngine {
|
|
|
712
803
|
outputText: result.outputText,
|
|
713
804
|
fidelity,
|
|
714
805
|
artifact,
|
|
806
|
+
...(observedInvocation === undefined ? {} : { observedInvocation }),
|
|
715
807
|
});
|
|
716
808
|
await this.emitProgress({
|
|
717
809
|
runId: this.runId,
|
|
@@ -735,42 +827,32 @@ export class WorkflowEngine {
|
|
|
735
827
|
};
|
|
736
828
|
}
|
|
737
829
|
catch (error) {
|
|
830
|
+
// The deadline is never retryable, but the call still has to land in a
|
|
831
|
+
// terminal state before the throw leaves the loop.
|
|
832
|
+
if (error instanceof WorkflowDispatchDeadlineError) {
|
|
833
|
+
await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, "failed");
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
738
836
|
// Cancellation is never retryable
|
|
739
837
|
if (error instanceof WorkflowCancellationError) {
|
|
740
|
-
|
|
741
|
-
const durationMs = Date.now() - startedAtMs;
|
|
742
|
-
await this.emitCallStateChange({
|
|
743
|
-
runId: this.runId,
|
|
744
|
-
workflowName: this.definition.name,
|
|
745
|
-
stageId: stage.id,
|
|
746
|
-
callId: call.id,
|
|
747
|
-
state: "cancelled",
|
|
748
|
-
attempt,
|
|
749
|
-
retryCount,
|
|
750
|
-
startedAt,
|
|
751
|
-
completedAt,
|
|
752
|
-
durationMs,
|
|
753
|
-
error: error.message,
|
|
754
|
-
});
|
|
755
|
-
await this.emitProgress({
|
|
756
|
-
runId: this.runId,
|
|
757
|
-
workflowName: this.definition.name,
|
|
758
|
-
scope: "call",
|
|
759
|
-
stageId: stage.id,
|
|
760
|
-
stageDescription: stage.description,
|
|
761
|
-
callId: call.id,
|
|
762
|
-
state: "cancelled",
|
|
763
|
-
attempt,
|
|
764
|
-
retryCount,
|
|
765
|
-
message: `Call ${callKey} cancelled`,
|
|
766
|
-
});
|
|
838
|
+
await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, "cancelled");
|
|
767
839
|
throw error;
|
|
768
840
|
}
|
|
769
841
|
const failureClass = classifyFailureClass(error);
|
|
770
842
|
lastError = error;
|
|
771
843
|
lastFailureClass = failureClass;
|
|
772
844
|
// Determine if this error is retryable
|
|
773
|
-
const
|
|
845
|
+
const ordinaryRetry = retryCount < maxRetries &&
|
|
846
|
+
isRetryable(error, failureClass, retryMode) &&
|
|
847
|
+
!shouldVetoWallTimeoutRetry(this.definition.name, error instanceof CallExecutionError ? error.terminationReason : undefined);
|
|
848
|
+
const correctiveRetry = !ordinaryRetry &&
|
|
849
|
+
!correctiveRetryUsed &&
|
|
850
|
+
retryCount < maxRetries &&
|
|
851
|
+
this.isCorrectiveRetryEligible(stage, call, error);
|
|
852
|
+
if (correctiveRetry) {
|
|
853
|
+
correctiveRetryUsed = true;
|
|
854
|
+
}
|
|
855
|
+
const canRetry = ordinaryRetry || correctiveRetry;
|
|
774
856
|
if (canRetry) {
|
|
775
857
|
// F4: corrective suffix rides only on the retry that was already
|
|
776
858
|
// decided above — it never changes retry eligibility. Unconditional
|
|
@@ -850,6 +932,51 @@ export class WorkflowEngine {
|
|
|
850
932
|
error: message,
|
|
851
933
|
};
|
|
852
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Terminalize a call that is about to be abandoned by a throw rather than
|
|
937
|
+
* by the retry loop's own failure path.
|
|
938
|
+
*
|
|
939
|
+
* Both abort/deadline exits from executeSingleCall leave the loop OUTSIDE
|
|
940
|
+
* the attempt `try`, so without this the persisted entry is stranded as
|
|
941
|
+
* "running" with no completedAt — a durable lie that resume, the dashboard
|
|
942
|
+
* and run health all read. The caller still rethrows: this only records the
|
|
943
|
+
* outcome, it never swallows it.
|
|
944
|
+
*/
|
|
945
|
+
async emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, state) {
|
|
946
|
+
const callKey = `${stage.id}.${call.id}`;
|
|
947
|
+
this.terminalizedCallKeys.add(callKey);
|
|
948
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
949
|
+
const completedAt = new Date().toISOString();
|
|
950
|
+
const durationMs = Date.now() - startedAtMs;
|
|
951
|
+
await this.emitCallStateChange({
|
|
952
|
+
runId: this.runId,
|
|
953
|
+
workflowName: this.definition.name,
|
|
954
|
+
stageId: stage.id,
|
|
955
|
+
callId: call.id,
|
|
956
|
+
state,
|
|
957
|
+
attempt,
|
|
958
|
+
retryCount,
|
|
959
|
+
startedAt,
|
|
960
|
+
completedAt,
|
|
961
|
+
durationMs,
|
|
962
|
+
error: message,
|
|
963
|
+
failureClass: classifyFailureClass(error),
|
|
964
|
+
});
|
|
965
|
+
await this.emitProgress({
|
|
966
|
+
runId: this.runId,
|
|
967
|
+
workflowName: this.definition.name,
|
|
968
|
+
scope: "call",
|
|
969
|
+
stageId: stage.id,
|
|
970
|
+
stageDescription: stage.description,
|
|
971
|
+
callId: call.id,
|
|
972
|
+
state,
|
|
973
|
+
attempt,
|
|
974
|
+
retryCount,
|
|
975
|
+
message: state === "cancelled"
|
|
976
|
+
? `Call ${callKey} cancelled`
|
|
977
|
+
: `Call ${callKey} failed: ${message}`,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
853
980
|
/**
|
|
854
981
|
* F4: bounded corrective suffix for the next already-scheduled retry after
|
|
855
982
|
* a JSON/schema-shaped failure. Returns undefined (no suffix) unless ALL of:
|
|
@@ -868,13 +995,40 @@ export class WorkflowEngine {
|
|
|
868
995
|
* The suffix is always derived from the ORIGINAL prompt, so it can never
|
|
869
996
|
* stack across attempts, and it is never persisted to state.
|
|
870
997
|
*/
|
|
998
|
+
/**
|
|
999
|
+
* Typed plan review (identity: the plan-review-receipts workflow name, plus
|
|
1000
|
+
* the plan-review runtime signals as defense-in-depth). Its dispatch bytes
|
|
1001
|
+
* and retry behavior are pinned, so no corrective machinery may touch it.
|
|
1002
|
+
*/
|
|
1003
|
+
isTypedPlanReviewCall(call) {
|
|
1004
|
+
return (this.definition.name === "plan-review-receipts" ||
|
|
1005
|
+
this.planReviewContextPolicy !== undefined ||
|
|
1006
|
+
call.plan_review_snapshot_text !== undefined);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* D7: under retry.mode "transport_only" a malformed-JSON answer is a
|
|
1010
|
+
* permanent failure, so it would never be retried. A JSON-contract call gets
|
|
1011
|
+
* ONE corrective attempt when the failure is primarily JSON-shaped; a
|
|
1012
|
+
* secondary-only invalid_json_output reason never creates eligibility.
|
|
1013
|
+
* failureClass is not reclassified.
|
|
1014
|
+
*/
|
|
1015
|
+
isCorrectiveRetryEligible(stage, call, error) {
|
|
1016
|
+
if (stage.retry.mode !== "transport_only") {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
if (!declaresJsonContract(call) || this.isTypedPlanReviewCall(call)) {
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
return (error instanceof SchemaValidationError ||
|
|
1023
|
+
error instanceof OutputSizeError ||
|
|
1024
|
+
(error instanceof CallExecutionError &&
|
|
1025
|
+
error.childDebugMetadata?.semantic_failure_reason === "invalid_json_output"));
|
|
1026
|
+
}
|
|
871
1027
|
buildCorrectiveRetrySuffix(stage, call, input, error) {
|
|
872
|
-
if (call
|
|
1028
|
+
if (!declaresJsonContract(call)) {
|
|
873
1029
|
return undefined;
|
|
874
1030
|
}
|
|
875
|
-
if (this.
|
|
876
|
-
this.planReviewContextPolicy !== undefined ||
|
|
877
|
-
call.plan_review_snapshot_text !== undefined) {
|
|
1031
|
+
if (this.isTypedPlanReviewCall(call)) {
|
|
878
1032
|
return undefined;
|
|
879
1033
|
}
|
|
880
1034
|
const detail = correctiveFailureDetail(error);
|
|
@@ -896,7 +1050,9 @@ export class WorkflowEngine {
|
|
|
896
1050
|
fileContributors: files.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
|
|
897
1051
|
imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
|
|
898
1052
|
maxBytes: this.maxPromptBytes,
|
|
899
|
-
reserveBytes: this.
|
|
1053
|
+
reserveBytes: isCouncilExecutionWorkflow(this.definition.name)
|
|
1054
|
+
? (this.promptReserveBytes ?? DEFAULT_RESERVE_BYTES) + COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES
|
|
1055
|
+
: this.promptReserveBytes,
|
|
900
1056
|
});
|
|
901
1057
|
if (estimate.exceeded) {
|
|
902
1058
|
logger.warn({ run_id: this.runId, stage_id: stage.id, call_id: call.id }, "corrective retry suffix skipped: appending would exceed the prompt budget");
|
|
@@ -915,9 +1071,8 @@ export class WorkflowEngine {
|
|
|
915
1071
|
return suffix;
|
|
916
1072
|
}
|
|
917
1073
|
async invokeCall(stage, call, input, signal, attempt) {
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
: undefined;
|
|
1074
|
+
const dispatchDeadline = this.computeDispatchDeadline(call);
|
|
1075
|
+
const dispatchInput = this.prepareDispatchInput(input, dispatchDeadline.callRemainingMs);
|
|
921
1076
|
const sessionLogOwner = this.sessionLogGeneration !== undefined && this.session.supportsSessionLogOwner === true
|
|
922
1077
|
? {
|
|
923
1078
|
family: "workflow",
|
|
@@ -928,7 +1083,69 @@ export class WorkflowEngine {
|
|
|
928
1083
|
attempt,
|
|
929
1084
|
}
|
|
930
1085
|
: undefined;
|
|
931
|
-
|
|
1086
|
+
// Prompt sizing above performs synchronous filesystem work, during which
|
|
1087
|
+
// the deadline timer cannot run. Recheck at dispatch; the already-rendered
|
|
1088
|
+
// guidance remains a conservative completion target if preparation took time.
|
|
1089
|
+
const finalDispatchDeadline = this.computeDispatchDeadline(call);
|
|
1090
|
+
this.throwIfAborted(signal);
|
|
1091
|
+
return await callClink(this.session, dispatchInput, signal, { stageId: stage.id, callId: call.id }, finalDispatchDeadline.timeoutMs, sessionLogOwner);
|
|
1092
|
+
}
|
|
1093
|
+
computeDispatchDeadline(call) {
|
|
1094
|
+
const configuredCallTimeoutMs = call.timeout_seconds != null
|
|
1095
|
+
? Math.max((call.timeout_seconds * 1000) + CALL_TIMEOUT_BUFFER_MS, DEFAULT_PARENT_CALL_TIMEOUT_MS)
|
|
1096
|
+
: undefined;
|
|
1097
|
+
const workflowRemainingMs = this.workflowRemainingMs();
|
|
1098
|
+
if (workflowRemainingMs != null && workflowRemainingMs <= 0) {
|
|
1099
|
+
throw new WorkflowDispatchDeadlineError();
|
|
1100
|
+
}
|
|
1101
|
+
// Keep the existing backend timeout contract for generic and excluded
|
|
1102
|
+
// workflows. The workflow AbortSignal still enforces the outer deadline;
|
|
1103
|
+
// only admitted councils with an explicit call cap receive the tighter
|
|
1104
|
+
// per-dispatch timeout when the workflow is closer to expiry.
|
|
1105
|
+
const timeoutMs = !isCouncilExecutionWorkflow(this.definition.name) ||
|
|
1106
|
+
configuredCallTimeoutMs == null || workflowRemainingMs == null
|
|
1107
|
+
? configuredCallTimeoutMs
|
|
1108
|
+
: Math.min(configuredCallTimeoutMs, workflowRemainingMs);
|
|
1109
|
+
const callRemainingMs = configuredCallTimeoutMs == null
|
|
1110
|
+
? workflowRemainingMs
|
|
1111
|
+
: (isCouncilExecutionWorkflow(this.definition.name) && workflowRemainingMs != null
|
|
1112
|
+
? Math.min(configuredCallTimeoutMs, workflowRemainingMs)
|
|
1113
|
+
: configuredCallTimeoutMs);
|
|
1114
|
+
return {
|
|
1115
|
+
timeoutMs,
|
|
1116
|
+
callRemainingMs,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
prepareDispatchInput(input, callRemainingMs) {
|
|
1120
|
+
if (!isCouncilExecutionWorkflow(this.definition.name) ||
|
|
1121
|
+
this.workflowDeadlineAtMonoMs == null ||
|
|
1122
|
+
callRemainingMs == null ||
|
|
1123
|
+
typeof input["prompt"] !== "string") {
|
|
1124
|
+
return input;
|
|
1125
|
+
}
|
|
1126
|
+
const guidedInput = {
|
|
1127
|
+
...input,
|
|
1128
|
+
prompt: `${input["prompt"]}${buildCouncilDeadlineGuidance({
|
|
1129
|
+
workflowRemainingMs: this.workflowRemainingMs() ?? 0,
|
|
1130
|
+
callRemainingMs,
|
|
1131
|
+
})}`,
|
|
1132
|
+
};
|
|
1133
|
+
const filePaths = Array.isArray(input["absolute_file_paths"])
|
|
1134
|
+
? input["absolute_file_paths"].filter((path) => typeof path === "string")
|
|
1135
|
+
: [];
|
|
1136
|
+
const imagePaths = Array.isArray(input["images"])
|
|
1137
|
+
? input["images"].filter((path) => typeof path === "string")
|
|
1138
|
+
: [];
|
|
1139
|
+
const estimate = estimatePromptBudget({
|
|
1140
|
+
promptText: guidedInput["prompt"],
|
|
1141
|
+
contextText: typeof input["context"] === "string" ? input["context"] : undefined,
|
|
1142
|
+
fileContributors: filePaths.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
|
|
1143
|
+
imageContributors: imagePaths.map((path) => createPathContributor(`image:${path}`, path, "image")),
|
|
1144
|
+
maxBytes: this.maxPromptBytes,
|
|
1145
|
+
reserveBytes: this.promptReserveBytes,
|
|
1146
|
+
});
|
|
1147
|
+
enforcePromptBudget(estimate);
|
|
1148
|
+
return guidedInput;
|
|
932
1149
|
}
|
|
933
1150
|
maybeWriteArtifact(stage, call, stageIndex, callIndex, outputText) {
|
|
934
1151
|
const requiresValidation = call.validator_schema_id != null;
|
|
@@ -1043,9 +1260,15 @@ export class WorkflowEngine {
|
|
|
1043
1260
|
}
|
|
1044
1261
|
/**
|
|
1045
1262
|
* Build the citation allowlist for a post-processor run: the union of files
|
|
1046
|
-
* parsed from the `diff_context` variable, the resolved `context_files`,
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1263
|
+
* parsed from the `diff_context` variable, the resolved `context_files`, the
|
|
1264
|
+
* resolved `context_bundle_files`, and the source files selected by any
|
|
1265
|
+
* curated context-builder bundle among them. Both full paths and basenames
|
|
1266
|
+
* are stored so citation matching is lenient.
|
|
1267
|
+
*
|
|
1268
|
+
* A curated preflight copies only `bundle.md` and `manifest.json` into
|
|
1269
|
+
* `context_bundle_files`, so without the manifest's `selected_context`
|
|
1270
|
+
* every citation to a real source file would be stripped. Reading from the
|
|
1271
|
+
* (persisted) variable keeps this working on resume.
|
|
1049
1272
|
*/
|
|
1050
1273
|
buildPostProcessorAllowlist() {
|
|
1051
1274
|
const diffRaw = this.effectiveVariables["diff_context"];
|
|
@@ -1055,8 +1278,92 @@ export class WorkflowEngine {
|
|
|
1055
1278
|
addPathWithBasename(allowlist, path);
|
|
1056
1279
|
}
|
|
1057
1280
|
}
|
|
1281
|
+
const manifestPaths = new Set(this.resolvePathListLenient("context_bundle_files").filter((path) => isAbsolute(path) && basename(path) === "manifest.json"));
|
|
1282
|
+
for (const manifestPath of manifestPaths) {
|
|
1283
|
+
this.addCuratedBundleSelections(allowlist, manifestPath);
|
|
1284
|
+
}
|
|
1058
1285
|
return allowlist;
|
|
1059
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Add each `selected_context` entry of a context-builder manifest to the
|
|
1289
|
+
* allowlist: its root-relative `path` and, when its root resolves, the
|
|
1290
|
+
* absolute form. The allowlist is advisory, so an unreadable, oversized
|
|
1291
|
+
* (> 8 MiB), non-JSON, or non-context-builder manifest is skipped silently
|
|
1292
|
+
* and malformed entries are ignored.
|
|
1293
|
+
*/
|
|
1294
|
+
addCuratedBundleSelections(allowlist, manifestPath) {
|
|
1295
|
+
const maxBytes = 8 * 1024 * 1024;
|
|
1296
|
+
let text;
|
|
1297
|
+
let fd;
|
|
1298
|
+
try {
|
|
1299
|
+
fd = openSync(manifestPath, "r");
|
|
1300
|
+
const stats = fstatSync(fd);
|
|
1301
|
+
if (!stats.isFile() || stats.size > maxBytes) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
// One spare byte detects a file that grew after fstat; skip it rather
|
|
1305
|
+
// than parse a torn read.
|
|
1306
|
+
const buffer = Buffer.alloc(stats.size + 1);
|
|
1307
|
+
let length = 0;
|
|
1308
|
+
while (length < buffer.length) {
|
|
1309
|
+
const read = readSync(fd, buffer, length, buffer.length - length, null);
|
|
1310
|
+
if (read === 0) {
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
length += read;
|
|
1314
|
+
}
|
|
1315
|
+
if (length > stats.size) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
text = buffer.toString("utf-8", 0, length);
|
|
1319
|
+
}
|
|
1320
|
+
catch {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
finally {
|
|
1324
|
+
if (fd !== undefined) {
|
|
1325
|
+
try {
|
|
1326
|
+
closeSync(fd);
|
|
1327
|
+
}
|
|
1328
|
+
catch {
|
|
1329
|
+
// Advisory read; a close failure must not fail the call.
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
let manifest;
|
|
1334
|
+
try {
|
|
1335
|
+
manifest = JSON.parse(text);
|
|
1336
|
+
}
|
|
1337
|
+
catch {
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const { selected_context: selected, roots } = manifest;
|
|
1344
|
+
if (!Array.isArray(selected) || !Array.isArray(roots)) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
for (const entry of selected) {
|
|
1348
|
+
if (typeof entry !== "object" || entry === null) {
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const { path, root_index: rootIndex } = entry;
|
|
1352
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
addPathWithBasename(allowlist, path);
|
|
1356
|
+
const root = typeof rootIndex === "number" && Number.isInteger(rootIndex) && rootIndex >= 0
|
|
1357
|
+
? roots[rootIndex]
|
|
1358
|
+
: undefined;
|
|
1359
|
+
if (typeof root === "object" && root !== null) {
|
|
1360
|
+
const rootPath = root["path"];
|
|
1361
|
+
if (typeof rootPath === "string" && rootPath.length > 0) {
|
|
1362
|
+
addPathWithBasename(allowlist, join(rootPath, path));
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1060
1367
|
/**
|
|
1061
1368
|
* Resolve a path-list variable to absolute paths without enforcing allowed
|
|
1062
1369
|
* roots (the post-processor allowlist is advisory, not a security boundary).
|
|
@@ -1162,7 +1469,11 @@ export class WorkflowEngine {
|
|
|
1162
1469
|
? {}
|
|
1163
1470
|
: { context_budget_adapter: contextBudgetAdapter }),
|
|
1164
1471
|
...(budget == null ? {} : { budget }),
|
|
1165
|
-
...(policy.effectiveDelegate == null
|
|
1472
|
+
...(policy.effectiveDelegate == null
|
|
1473
|
+
? {}
|
|
1474
|
+
: {
|
|
1475
|
+
delegate: foldExternalGapPolicy(policy.effectiveDelegate, this.definition.settings.context_external_gap_policy),
|
|
1476
|
+
}),
|
|
1166
1477
|
};
|
|
1167
1478
|
// Light-caller provenance: capture how anchors were derived. `root_wide_used`
|
|
1168
1479
|
// records the REALIZED `input.allowRootWideDiscovery` flag actually passed to
|
|
@@ -1190,7 +1501,12 @@ export class WorkflowEngine {
|
|
|
1190
1501
|
const effectiveCoverageStatus = result.metrics.files_selected > 0
|
|
1191
1502
|
? result.coverage?.status ?? "not_evaluated"
|
|
1192
1503
|
: "not_evaluated";
|
|
1193
|
-
|
|
1504
|
+
// External gaps under external_gap_policy:"degrade" are the only producer of
|
|
1505
|
+
// a partial delegate without fallback; the coverage report itself is left
|
|
1506
|
+
// untouched so persisted coverage stays the policy's own verdict.
|
|
1507
|
+
const externalGapsDegraded = isDelegateExternalGapDegrade(result.delegate);
|
|
1508
|
+
const contextQuality = externalGapsDegraded ? "degraded" : effectiveCoverageStatus;
|
|
1509
|
+
this.setEffectiveVariable("context_quality", contextQuality);
|
|
1194
1510
|
const delegateInput = policy.effectiveDelegate?.enabled === true ? policy.effectiveDelegate : undefined;
|
|
1195
1511
|
const fallbackUsed = result.delegate?.fallback_used === true;
|
|
1196
1512
|
const fallbackAggregate = recordPreflightFallbackAggregate({
|
|
@@ -1248,6 +1564,12 @@ export class WorkflowEngine {
|
|
|
1248
1564
|
files_selected: 0,
|
|
1249
1565
|
}, "context-builder preflight selected no files; proceeding without bundle");
|
|
1250
1566
|
}
|
|
1567
|
+
if (externalGapsDegraded && effectiveCoverageStatus !== "degraded") {
|
|
1568
|
+
if (this.definition.settings.fail_on_degraded_context_coverage === true) {
|
|
1569
|
+
throw new WorkflowError("Context-builder preflight is degraded: the finalizer reported evidence gaps outside the delegate packet");
|
|
1570
|
+
}
|
|
1571
|
+
logger.warn({ run_id: this.runId, source_run_id: result.source_run_id }, "context-builder preflight reported external evidence gaps; proceeding degraded");
|
|
1572
|
+
}
|
|
1251
1573
|
if (effectiveCoverageStatus === "degraded") {
|
|
1252
1574
|
if (this.definition.settings.fail_on_degraded_context_coverage === true) {
|
|
1253
1575
|
throw new WorkflowError("Context-builder preflight coverage is degraded");
|
|
@@ -1394,11 +1716,11 @@ export class WorkflowEngine {
|
|
|
1394
1716
|
}
|
|
1395
1717
|
return value;
|
|
1396
1718
|
}
|
|
1397
|
-
createDeadlineController() {
|
|
1719
|
+
createDeadlineController(delayMs) {
|
|
1398
1720
|
const controller = new AbortController();
|
|
1399
1721
|
const timeout = setTimeout(() => {
|
|
1400
1722
|
controller.abort(new Error(`Workflow timeout exceeded after ${String(this.definition.settings.timeout_seconds)} seconds`));
|
|
1401
|
-
},
|
|
1723
|
+
}, Math.max(0, delayMs));
|
|
1402
1724
|
if (this.signal != null) {
|
|
1403
1725
|
if (this.signal.aborted) {
|
|
1404
1726
|
controller.abort(new WorkflowCancellationError());
|
|
@@ -1412,6 +1734,24 @@ export class WorkflowEngine {
|
|
|
1412
1734
|
dispose: () => clearTimeout(timeout),
|
|
1413
1735
|
};
|
|
1414
1736
|
}
|
|
1737
|
+
/** Remaining workflow budget in ms; undefined when no deadline is installed. */
|
|
1738
|
+
workflowRemainingMs() {
|
|
1739
|
+
return this.workflowDeadlineAtMonoMs == null
|
|
1740
|
+
? undefined
|
|
1741
|
+
: Math.floor(this.workflowDeadlineAtMonoMs - monotonicNowMs());
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* True only while a workflow deadline is set AND already past. Pure: reads
|
|
1745
|
+
* the deadline the running `execute()` installed and the clock, nothing else.
|
|
1746
|
+
* `execute()` clears `workflowDeadlineAtMonoMs` in its `finally`, so this can
|
|
1747
|
+
* never report a stale deadline from a prior run. The reading is monotonic:
|
|
1748
|
+
* a wall-clock step (NTP correction, suspend/resume, manual change) can
|
|
1749
|
+
* neither manufacture nor erase an exhausted budget.
|
|
1750
|
+
*/
|
|
1751
|
+
isWorkflowDeadlineExhausted() {
|
|
1752
|
+
const remainingMs = this.workflowRemainingMs();
|
|
1753
|
+
return remainingMs != null && remainingMs <= 0;
|
|
1754
|
+
}
|
|
1415
1755
|
throwIfAborted(signal) {
|
|
1416
1756
|
if (!signal.aborted) {
|
|
1417
1757
|
return;
|
|
@@ -1421,7 +1761,11 @@ export class WorkflowEngine {
|
|
|
1421
1761
|
throw reason;
|
|
1422
1762
|
}
|
|
1423
1763
|
if (reason instanceof Error && reason.message.includes("Workflow timeout exceeded")) {
|
|
1424
|
-
|
|
1764
|
+
// Same environmental cause as WorkflowDispatchDeadlineError, so it
|
|
1765
|
+
// carries the same class. Kept as a bare WorkflowError: the two are now
|
|
1766
|
+
// handled identically by materializeUnexpectedFailure (both materialize
|
|
1767
|
+
// rather than rethrow), so the distinction buys nothing here.
|
|
1768
|
+
throw new WorkflowError(reason.message, "transient");
|
|
1425
1769
|
}
|
|
1426
1770
|
throw new WorkflowCancellationError();
|
|
1427
1771
|
}
|
|
@@ -1447,16 +1791,7 @@ export class WorkflowEngine {
|
|
|
1447
1791
|
`Known cli_name values: ${knownCliNames.join(", ")}`);
|
|
1448
1792
|
}
|
|
1449
1793
|
defaultAllowedRoots() {
|
|
1450
|
-
|
|
1451
|
-
const stateDir = getStateDir();
|
|
1452
|
-
const configuredArtifactsDir = getArtifactsDir();
|
|
1453
|
-
return [
|
|
1454
|
-
workspaceRoot,
|
|
1455
|
-
stateDir,
|
|
1456
|
-
configuredArtifactsDir,
|
|
1457
|
-
this.runDir,
|
|
1458
|
-
join(this.runDir, "artifacts"),
|
|
1459
|
-
];
|
|
1794
|
+
return defaultWorkflowAllowedRoots(this.runDir);
|
|
1460
1795
|
}
|
|
1461
1796
|
recordPlanReviewDeliveredAliases(callKey, call, absoluteFilePaths) {
|
|
1462
1797
|
if (call.plan_review_anonymous_aliases == null)
|
|
@@ -1531,6 +1866,9 @@ export class WorkflowEngine {
|
|
|
1531
1866
|
];
|
|
1532
1867
|
this.recordPlanReviewDeliveredAliases(callKey, call, contextArtifacts.absoluteFilePaths);
|
|
1533
1868
|
const images = [...staticImages, ...dynamicImages];
|
|
1869
|
+
const reserveBytes = isCouncilExecutionWorkflow(this.definition.name)
|
|
1870
|
+
? (this.promptReserveBytes ?? DEFAULT_RESERVE_BYTES) + COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES
|
|
1871
|
+
: this.promptReserveBytes;
|
|
1534
1872
|
const estimate = estimatePromptBudget({
|
|
1535
1873
|
promptText: prompt,
|
|
1536
1874
|
contextText: contextArtifacts.context,
|
|
@@ -1541,13 +1879,15 @@ export class WorkflowEngine {
|
|
|
1541
1879
|
],
|
|
1542
1880
|
imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
|
|
1543
1881
|
maxBytes: this.maxPromptBytes,
|
|
1544
|
-
reserveBytes
|
|
1882
|
+
reserveBytes,
|
|
1545
1883
|
});
|
|
1546
1884
|
enforcePromptBudget(estimate);
|
|
1547
1885
|
const input = {
|
|
1548
1886
|
cli_name: call.cli_name,
|
|
1549
1887
|
role: call.role,
|
|
1550
1888
|
prompt,
|
|
1889
|
+
...(call.model !== undefined ? { model: call.model } : {}),
|
|
1890
|
+
...(call.effort !== undefined ? { effort: call.effort } : {}),
|
|
1551
1891
|
};
|
|
1552
1892
|
if (absoluteFilePaths.length > 0) {
|
|
1553
1893
|
input["absolute_file_paths"] = absoluteFilePaths;
|
|
@@ -1572,26 +1912,59 @@ export class WorkflowEngine {
|
|
|
1572
1912
|
}
|
|
1573
1913
|
return input;
|
|
1574
1914
|
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Derive a per-call invocation audit from the materialized input.
|
|
1917
|
+
* Uses the dispatched cli_name/role (plan-review bindings may rewrite
|
|
1918
|
+
* cli_name on resume) and never mutates `input`.
|
|
1919
|
+
*/
|
|
1920
|
+
maybeInvocationAudit(input) {
|
|
1921
|
+
const derive = this.deriveInvocationAudit;
|
|
1922
|
+
if (derive == null) {
|
|
1923
|
+
return undefined;
|
|
1924
|
+
}
|
|
1925
|
+
const cliName = input["cli_name"];
|
|
1926
|
+
const role = input["role"];
|
|
1927
|
+
if (typeof cliName !== "string" || typeof role !== "string") {
|
|
1928
|
+
return undefined;
|
|
1929
|
+
}
|
|
1930
|
+
const model = input["model"];
|
|
1931
|
+
const effort = input["effort"];
|
|
1932
|
+
return derive(cliName, role, {
|
|
1933
|
+
...(typeof model === "string" ? { model } : {}),
|
|
1934
|
+
...(typeof effort === "string" ? { effort } : {}),
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1575
1937
|
async materializeUnexpectedFailure(stage, call, error) {
|
|
1938
|
+
// Only cancellation escapes a parallel stage. Every other failure —
|
|
1939
|
+
// environmental (an exhausted dispatch deadline) as much as a child
|
|
1940
|
+
// failure — materializes into an ExecutedCall{ok:false} so already-settled
|
|
1941
|
+
// sibling results survive Promise.allSettled instead of being discarded by
|
|
1942
|
+
// a rethrow.
|
|
1576
1943
|
if (error instanceof WorkflowCancellationError) {
|
|
1577
1944
|
throw error;
|
|
1578
1945
|
}
|
|
1579
1946
|
const message = error instanceof Error ? error.message : String(error);
|
|
1580
1947
|
this.artifactStore.noteFailedCall(stage.id, call.id, message);
|
|
1581
1948
|
logger.error({ run_id: this.runId, stage_id: stage.id, call_id: call.id, err: message }, "parallel stage call rejected unexpectedly");
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1949
|
+
// Only the genuinely un-terminalized failures need a state emit here.
|
|
1950
|
+
// `emitTerminalCallAbort` already wrote the real attempt/retryCount and
|
|
1951
|
+
// startedAt/durationMs, and onCallStateChange assigns those fields
|
|
1952
|
+
// unconditionally, so re-emitting would overwrite them with placeholders.
|
|
1953
|
+
if (!this.terminalizedCallKeys.has(`${stage.id}.${call.id}`)) {
|
|
1954
|
+
await this.emitCallStateChange({
|
|
1955
|
+
runId: this.runId,
|
|
1956
|
+
workflowName: this.definition.name,
|
|
1957
|
+
stageId: stage.id,
|
|
1958
|
+
callId: call.id,
|
|
1959
|
+
state: "failed",
|
|
1960
|
+
attempt: 1,
|
|
1961
|
+
retryCount: 0,
|
|
1962
|
+
completedAt: new Date().toISOString(),
|
|
1963
|
+
error: message,
|
|
1964
|
+
failureClass: classifyFailureClass(error),
|
|
1965
|
+
childDebugMetadata: error instanceof CallExecutionError ? error.childDebugMetadata : undefined,
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1595
1968
|
await this.emitProgress({
|
|
1596
1969
|
runId: this.runId,
|
|
1597
1970
|
workflowName: this.definition.name,
|
|
@@ -1829,18 +2202,41 @@ const CORRECTIVE_SUFFIX_FOOTER = "\nReturn ONLY a single JSON object conforming
|
|
|
1829
2202
|
* is not one a corrective re-prompt could fix (transport death, timeout, …).
|
|
1830
2203
|
*/
|
|
1831
2204
|
function correctiveFailureDetail(error) {
|
|
2205
|
+
if (error instanceof TypedResultFidelityError) {
|
|
2206
|
+
return error.message;
|
|
2207
|
+
}
|
|
1832
2208
|
if (error instanceof SchemaValidationError) {
|
|
1833
2209
|
return error.message;
|
|
1834
2210
|
}
|
|
1835
2211
|
if (error instanceof OutputSizeError) {
|
|
1836
2212
|
return error.message;
|
|
1837
2213
|
}
|
|
1838
|
-
if (error instanceof CallExecutionError
|
|
1839
|
-
error.childDebugMetadata
|
|
1840
|
-
|
|
2214
|
+
if (error instanceof CallExecutionError) {
|
|
2215
|
+
const debug = error.childDebugMetadata;
|
|
2216
|
+
if (debug?.rejection_reason === "invalid_json_output" ||
|
|
2217
|
+
debug?.semantic_failure_reason === "invalid_json_output" ||
|
|
2218
|
+
debug?.secondary_failure_reason === "invalid_json_output") {
|
|
2219
|
+
return "output did not contain valid JSON";
|
|
2220
|
+
}
|
|
1841
2221
|
}
|
|
1842
2222
|
return undefined;
|
|
1843
2223
|
}
|
|
2224
|
+
/** Mirrors the expect_json derivation in buildCallInput. */
|
|
2225
|
+
function declaresJsonContract(call) {
|
|
2226
|
+
return call.response_contract === "json" || call.validator_schema_id != null;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* D1b: fold the workflow's `context_external_gap_policy` into an ENABLED
|
|
2230
|
+
* preflight delegate that does not choose one itself. Runtime-only: the
|
|
2231
|
+
* caller's `cb_delegate` variable (and so the inputs snapshot) is untouched,
|
|
2232
|
+
* and an explicit delegate `external_gap_policy` always wins.
|
|
2233
|
+
*/
|
|
2234
|
+
function foldExternalGapPolicy(delegate, policy) {
|
|
2235
|
+
if (policy === undefined || delegate.enabled !== true || delegate.external_gap_policy !== undefined) {
|
|
2236
|
+
return delegate;
|
|
2237
|
+
}
|
|
2238
|
+
return { ...delegate, external_gap_policy: policy };
|
|
2239
|
+
}
|
|
1844
2240
|
function buildCorrectiveSuffixText(detail, header = CORRECTIVE_SUFFIX_HEADER) {
|
|
1845
2241
|
const budget = CORRECTIVE_SUFFIX_MAX_CHARS -
|
|
1846
2242
|
header.length -
|