@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.139
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 +19 -0
- package/README.md +9 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/argument-values.js +5 -5
- package/dist/cli/command-inputs.js +89 -20
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +240 -6
- package/dist/cli/input-preparation.js +54 -5
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/runtime-process.js +1 -1
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +56 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +53 -22
- package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +12 -6
- package/dist/harness-runtime/lib/dd-codex.mjs +24 -12
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +32 -9
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +11 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +100 -15
- package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +22 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +16 -3
- package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-children.d.mts +2 -0
- package/dist/harness-runtime/lib/native-children.mjs +20 -1
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +32 -9
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +26 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +18 -2
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +71 -12
- package/dist/services/dashboard.js +8 -2
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +39 -22
- package/dist/services/eval-snapshots.js +83 -25
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +20 -16
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +93 -15
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/managed-processes.js +31 -10
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +296 -0
- package/dist/services/memory-init-preflight.js +330 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +181 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +178 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/merge-server.js +2 -4
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
- package/dist/services/recovery-snapshot-database.js +417 -2
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-adapter.js +1 -1
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +5 -5
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +158 -64
- package/dist/services/run-recovery-guard.js +5 -3
- package/dist/services/run-recovery-runtime.js +3 -3
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +413 -30
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-report-renderer.js +63 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code-review.js +1 -1
- package/dist/services/vnext-code.js +6 -6
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +73 -11
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +7 -5
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/vnext-protocolize.js +20 -6
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +616 -71
- package/dist/services/workspace-files.js +1 -1
- package/dist/storage/database.js +84 -6
- package/dist/storage/writer-contract.js +11 -7
- package/package.json +13 -3
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Final native overlay: launch-bound routing, never inherited invocation authority. */
|
|
2
|
+
export function managedFlowEnv(config, daemonId = config.daemonId) {
|
|
3
|
+
const managed = !config.noFlow && Boolean(config.ddFlowHome && config.ddFlowBin && daemonId);
|
|
4
|
+
return {
|
|
5
|
+
DD_FLOW_DAEMON_ID: managed ? daemonId : undefined,
|
|
6
|
+
DD_FLOW_HOME: managed ? config.ddFlowHome : undefined,
|
|
7
|
+
DD_FLOW_BIN: managed ? config.ddFlowBin : undefined,
|
|
8
|
+
DD_FLOW_RESOURCE_HOME: config.resourceHome ?? undefined,
|
|
9
|
+
DD_FLOW_RUNTIME_OWNER: managed && config.runtime_owner ? JSON.stringify(config.runtime_owner) : undefined,
|
|
10
|
+
DD_FLOW_INVOCATION_SCOPE: undefined, DD_FLOW_CURRENT_INVOCATION: undefined,
|
|
11
|
+
DD_FLOW_SESSION_ID: undefined, DD_FLOW_WORK_ID: undefined, DD_FLOW_CONTROLLER_TOKEN: undefined,
|
|
12
|
+
DD_FLOW_CONTROL_TOKEN: undefined, DD_FLOW_SCOPE_CONTROL_TOKEN: undefined,
|
|
13
|
+
DD_FLOW_HOOK_DEADLINE_AT: undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const NATIVE_CHILD_CONTRACT_VERSION: "native-children@1";
|
|
2
|
+
export function settledNativeTree(root: string, children: Record<string, unknown>[], settled: boolean, rootSettled?: boolean): Record<string, unknown>;
|
|
2
3
|
export function safeNativeFailure(value: unknown): Record<string, unknown> | undefined;
|
|
3
4
|
export interface NativeChild {
|
|
4
5
|
session_id: string;
|
|
@@ -9,6 +10,7 @@ export interface NativeChild {
|
|
|
9
10
|
terminal_turn_id?: string;
|
|
10
11
|
terminal_observed_at?: string;
|
|
11
12
|
provider_error?: unknown;
|
|
13
|
+
native_source?: "opencode.v2.session.execution" | "agy.result" | "acp.error_response" | "droid.agent_turn_outcome" | "grok.subagent.event";
|
|
12
14
|
native_turn_items?: { observed?: boolean; possible_effects?: boolean; pending?: boolean };
|
|
13
15
|
}
|
|
14
16
|
export function normalizeNativeChildren(receipt: Record<string, unknown>, rootSessionId: string): NativeChild[];
|
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
export const NATIVE_CHILD_CONTRACT_VERSION = "native-children@1";
|
|
2
2
|
import { errorRecord } from "./operation-errors.mjs";
|
|
3
|
+
import { safeProviderError } from "./provider-overload-policy.mjs";
|
|
3
4
|
|
|
4
5
|
function nativeFailure(code, message, exitCode, details) { return Object.assign(new Error(message), { code, exitCode, details }); }
|
|
5
6
|
|
|
7
|
+
/** Project a positively settled native tree, never infer it from missing activity. */
|
|
8
|
+
export function settledNativeTree(root, children, settled, rootSettled = false) {
|
|
9
|
+
if (settled !== true && rootSettled !== true || typeof root !== "string" || !root || !Array.isArray(children)) return {};
|
|
10
|
+
const ids = children.map(child => child.provider_session_id ?? child.session_id ?? child.childSessionId ?? child.sessionId);
|
|
11
|
+
if (ids.some(id => typeof id !== "string" || !id || id === root) || new Set(ids).size !== ids.length) return {};
|
|
12
|
+
const observations = [{ provider_session_id: root, status: "idle", settled: true }, ...children.map((child, index) => {
|
|
13
|
+
const status = String(child.status ?? child.state ?? "unknown").toLowerCase();
|
|
14
|
+
const terminal = settled === true || ["completed", "success", "succeeded", "failed", "cancelled", "canceled", "settled_by_root"].includes(status);
|
|
15
|
+
return { provider_session_id: ids[index], status: terminal ? "idle" : ["running", "active", "inprogress", "pending"].includes(status) ? "running" : "unknown", settled: terminal };
|
|
16
|
+
})];
|
|
17
|
+
return { tree_session_ids: [root, ...ids], unsettled_sessions: observations.filter(item => !item.settled).map(item => item.provider_session_id), tree_observations: observations };
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
/** Retain typed failure facts, never provider headers, request bodies or free-form messages. */
|
|
7
21
|
export function safeNativeFailure(value, depth = 0) {
|
|
8
22
|
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
9
23
|
const result = {};
|
|
10
|
-
for (const key of ["type", "code", "source", "reason", "errorPhase", "exceptionKind", "providerId", "modelId", "providerKind", "transport", "retryable", "willRetry", "statusCode", "resets_at", "
|
|
24
|
+
for (const key of ["type", "code", "source", "reason", "errorPhase", "exceptionKind", "providerId", "modelId", "providerKind", "transport", "retryable", "willRetry", "statusCode", "resets_at", "retryAfterMs", "resetAt"]) {
|
|
11
25
|
const scalar = value[key];
|
|
12
26
|
if (typeof scalar === "boolean" || typeof scalar === "number" && Number.isFinite(scalar)
|
|
13
27
|
|| typeof scalar === "string" && scalar.length <= 256 && /^[\w.:/ -]+$/.test(scalar)) result[key] = scalar;
|
|
14
28
|
}
|
|
29
|
+
// Timing metadata is not an identifier: HTTP dates contain commas and an
|
|
30
|
+
// invalid value must reach the policy as invalid, not disappear into default backoff.
|
|
31
|
+
const timing = safeProviderError(value);
|
|
32
|
+
if (Object.hasOwn(timing, "retryAfter")) result.retryAfter = timing.retryAfter;
|
|
15
33
|
if (typeof value.codexErrorInfo === "string" && /^[\w-]{1,128}$/.test(value.codexErrorInfo)) result.codexErrorInfo = value.codexErrorInfo;
|
|
16
34
|
else if (value.codexErrorInfo && typeof value.codexErrorInfo.code === "string" && /^[\w-]{1,128}$/.test(value.codexErrorInfo.code)) result.codexErrorInfo = { code: value.codexErrorInfo.code };
|
|
17
35
|
const attribution = depth === 0 && value.attribution && safeNativeFailure(value.attribution, 1);
|
|
@@ -55,6 +73,7 @@ export function normalizeNativeChildren(receipt, rootSessionId) {
|
|
|
55
73
|
...(text(value, ["terminal_turn_id"]) ? { terminal_turn_id: text(value, ["terminal_turn_id"]) } : {}),
|
|
56
74
|
...(text(value, ["terminal_observed_at"]) ? { terminal_observed_at: text(value, ["terminal_observed_at"]) } : {}),
|
|
57
75
|
...(value.provider_error !== undefined ? { provider_error: value.provider_error } : {}),
|
|
76
|
+
...(["opencode.v2.session.execution", "agy.result", "acp.error_response", "droid.agent_turn_outcome", "grok.subagent.event"].includes(value.native_source) ? { native_source: value.native_source } : {}),
|
|
58
77
|
...(value.native_turn_items && typeof value.native_turn_items === "object" ? { native_turn_items: value.native_turn_items } : {}) });
|
|
59
78
|
};
|
|
60
79
|
for (const value of list(receipt.descendants)) add(value, "adapter.descendants");
|
|
@@ -62,7 +62,7 @@ export async function readNativeHookFailures(stateDir, requests, current, reject
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** The caller supplies its qualified native limit; phases share that deadline. */
|
|
65
|
-
export function hookBudget(timeoutMs = 25_000, deadlineAt = process.env.DD_FLOW_HOOK_DEADLINE_AT
|
|
65
|
+
export function hookBudget(timeoutMs = 25_000, deadlineAt = process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null) {
|
|
66
66
|
if (deadlineAt !== null && (!Number.isSafeInteger(deadlineAt) || deadlineAt < 0)) throw Object.assign(new Error("Invalid native hook deadline"), { code: "hook_deadline_invalid", details: { effect: "unknown" } });
|
|
67
67
|
const started = performance.now();
|
|
68
68
|
const duration = Math.max(0, Math.min(timeoutMs, Number.isFinite(deadlineAt) ? deadlineAt - Date.now() : timeoutMs));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export function nativeCommandFingerprint(input: { executable: string; args: string[]; cwd: string; env: Record<string, string | undefined>; operationId: string }): string;
|
|
2
|
+
export function prepareDaemonRequest<T extends { id: string; operation: string; params?: Record<string, unknown> }>(request: T): T & { invocation_sha256?: string };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { hashJson } from './runner-events.mjs';
|
|
6
|
+
|
|
7
|
+
const environmentKeys = ['HOME', 'CODEX_HOME', 'DD_FLOW_CONFIG_HOME', 'DD_FLOW_RESOURCE_HOME', 'DD_FLOW_HOME', 'DD_FLOW_BIN', 'DD_FLOW_ENGINE_HOME'];
|
|
8
|
+
const fail = reason => { throw Object.assign(new Error('Native request proof does not match its prepared command'), {
|
|
9
|
+
code: 'native_request_proof_conflict', details: { reason, effect: 'no_effect' }
|
|
10
|
+
}); };
|
|
11
|
+
|
|
12
|
+
/** Independent transport identity: prepared by the caller before launching
|
|
13
|
+
* the adapter, and recomputed by the adapter's shared request builder. */
|
|
14
|
+
export function nativeCommandFingerprint({ executable, args, cwd, env, operationId }) {
|
|
15
|
+
return hashJson({ executable, args, cwd, operation_id: operationId,
|
|
16
|
+
environment: Object.fromEntries(environmentKeys.map(key => [key, env[key] ?? null])) });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Called after the adapter's own argument preparation but before its RPC or
|
|
20
|
+
* daemon-start action. No provider response can supply this fingerprint. */
|
|
21
|
+
export function prepareDaemonRequest(request) {
|
|
22
|
+
const file = process.env.DD_FLOW_NATIVE_REQUEST_PROOF_FILE;
|
|
23
|
+
const expected = process.env.DD_FLOW_NATIVE_COMMAND_SHA256;
|
|
24
|
+
const operation = process.env.DD_FLOW_NATIVE_REQUEST_OPERATION;
|
|
25
|
+
if (file === undefined && expected === undefined) return request;
|
|
26
|
+
// Control helpers may inspect status before their owned stop RPC. Status
|
|
27
|
+
// cannot consume the productive/control request's independently bound proof.
|
|
28
|
+
if (operation && request.operation !== operation) {
|
|
29
|
+
if (request.operation === 'daemon.status' && operation === 'daemon.stop') return request;
|
|
30
|
+
fail('operation_changed');
|
|
31
|
+
}
|
|
32
|
+
const ownerText = process.env.DD_FLOW_NATIVE_REQUEST_PROOF_OWNER;
|
|
33
|
+
// Never forward caller proof authority into the daemon/provider process.
|
|
34
|
+
delete process.env.DD_FLOW_NATIVE_REQUEST_PROOF_FILE;
|
|
35
|
+
delete process.env.DD_FLOW_NATIVE_COMMAND_SHA256;
|
|
36
|
+
delete process.env.DD_FLOW_NATIVE_REQUEST_OPERATION;
|
|
37
|
+
delete process.env.DD_FLOW_NATIVE_REQUEST_PROOF_OWNER;
|
|
38
|
+
if (!file || !path.isAbsolute(file) || !/^[a-f0-9]{64}$/.test(expected ?? '')
|
|
39
|
+
|| typeof request.id !== 'string' || !request.id || typeof request.operation !== 'string' || request.operation !== operation) fail('invalid_owner');
|
|
40
|
+
const command = nativeCommandFingerprint({ executable: process.argv[1], args: process.argv.slice(2),
|
|
41
|
+
cwd: process.cwd(), env: process.env, operationId: request.id });
|
|
42
|
+
if (command !== expected || fs.realpathSync(path.dirname(file)) !== path.dirname(file)) fail('command_changed');
|
|
43
|
+
let owner;
|
|
44
|
+
try { owner = JSON.parse(ownerText ?? 'null'); } catch { fail('invalid_parent_owner'); }
|
|
45
|
+
const parent = fs.lstatSync(path.dirname(file));
|
|
46
|
+
if (!parent.isDirectory() || parent.dev !== owner?.dev || parent.ino !== owner?.ino) fail('proof_parent_changed');
|
|
47
|
+
const proof = { schema_id: 'dd-flow/native-request-proof@1', operation_id: request.id, operation: request.operation,
|
|
48
|
+
command_sha256: command, params_sha256: hashJson({ operation: request.operation, params: request.params ?? {} }),
|
|
49
|
+
session_id: request.params?.sessionId ?? null };
|
|
50
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
51
|
+
const fd = fs.openSync(temporary, 'wx', 0o600);
|
|
52
|
+
try {
|
|
53
|
+
fs.writeFileSync(fd, JSON.stringify(proof)); fs.fsyncSync(fd);
|
|
54
|
+
const current = fs.lstatSync(path.dirname(file));
|
|
55
|
+
if (parent.dev !== current.dev || parent.ino !== current.ino || !current.isDirectory()) fail('proof_parent_changed');
|
|
56
|
+
try { fs.linkSync(temporary, file); }
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code !== 'EEXIST') throw error;
|
|
59
|
+
const stat = fs.lstatSync(file);
|
|
60
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 16 * 1024
|
|
61
|
+
|| hashJson(JSON.parse(fs.readFileSync(file, 'utf8'))) !== hashJson(proof)) fail('retained_request_changed');
|
|
62
|
+
}
|
|
63
|
+
} finally { fs.closeSync(fd); fs.unlinkSync(temporary); }
|
|
64
|
+
return { ...request, invocation_sha256: command };
|
|
65
|
+
}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
2
3
|
import * as Plugin from "../vendor/opencode-plugin.mjs";
|
|
3
|
-
import { hookBudget, invokeNativeHook } from "./native-hook-command.mjs";
|
|
4
|
+
import { hookBudget, invokeNativeHook, writeNativeHookFailure } from "./native-hook-command.mjs";
|
|
5
|
+
import { callDaemon } from "./dd-opencode-daemon.mjs";
|
|
6
|
+
import { errorRecord } from "./operation-errors.mjs";
|
|
4
7
|
|
|
5
8
|
/** Launch-bound server plugin; never uses a process-global 'last Session'. */
|
|
6
|
-
export function makeOpenCodePlugin(binding, invoke = invokeNativeHook) {
|
|
9
|
+
export function makeOpenCodePlugin(binding, invoke = invokeNativeHook, resolve = callDaemon) {
|
|
7
10
|
return Plugin.define({ id: binding.ddFlowBin ? "dd-flow" : "dd-flow-security", async setup(ctx) {
|
|
8
11
|
const participating = new Map();
|
|
9
12
|
const key = event => JSON.stringify([binding.daemonId, event.sessionID, event.messageID, event.id]);
|
|
10
13
|
const envelope = async (phase, event, budget, outcome) => {
|
|
11
|
-
const info = await budget.run(() => ctx.session.get({ sessionID: event.sessionID }), "identity");
|
|
12
|
-
if (!info?.id || info.id !== event.sessionID || !info.location?.directory) throw new Error("OpenCode Session identity is unavailable");
|
|
13
14
|
const input = event.input;
|
|
14
15
|
if (!input || typeof input !== "object" || typeof input.command !== "string") throw new Error("OpenCode shell input is unavailable");
|
|
15
|
-
const
|
|
16
|
+
const identity = await budget.run(() => resolve(binding.stateDir, "hook.resolve", { sessionId: event.sessionID,
|
|
17
|
+
payload: { phase, message_id: event.messageID, tool_call_id: event.id, input } }, budget.remaining("identity")), "identity");
|
|
18
|
+
const info = identity?.nativeSession;
|
|
19
|
+
if (!identity?.nativeHookBinding || info?.id !== event.sessionID || !info.directory) throw Object.assign(new Error("OpenCode Session identity is unavailable"), { code: "native_hook_unproven" });
|
|
20
|
+
const cwd = path.resolve(info.directory, input.workdir ?? ".");
|
|
16
21
|
return { schema_id: "dd-flow/opencode-tool-event@1", native_protocol: 2,
|
|
22
|
+
native_hook_binding: identity.nativeHookBinding,
|
|
17
23
|
event_id: [binding.daemonId, event.sessionID, event.messageID, event.id].join(":"), phase,
|
|
18
24
|
daemon_id: binding.daemonId, server_version: binding.version, plugin_sha256: process.env.DD_OPENCODE_PLUGIN_SHA256,
|
|
19
25
|
session: { provider_session_id: info.id, parent_provider_session_id: info.parentID ?? null,
|
|
@@ -22,10 +28,28 @@ export function makeOpenCodePlugin(binding, invoke = invokeNativeHook) {
|
|
|
22
28
|
profile: { provider: info.model?.providerID, model: info.model?.id, variant: info.model?.variant ?? "default", agent: event.agent },
|
|
23
29
|
input: { ...input, workdir: cwd }, ...(outcome ? { outcome } : {}) };
|
|
24
30
|
};
|
|
25
|
-
const deliver = (payload, budget) =>
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
const deliver = async (payload, budget) => {
|
|
32
|
+
const identity = { nativeHookBinding: payload.native_hook_binding };
|
|
33
|
+
try {
|
|
34
|
+
const result = await invoke({ bin: binding.ddFlowBin, nodeBin: binding.nodeBin,
|
|
35
|
+
args: ["opencode", "event", "handle", "--project-root", binding.projectRoot, "--json"],
|
|
36
|
+
payload: { ...payload, native_hook_binding: identity.nativeHookBinding }, home: binding.ddFlowHome, timeoutMs: budget.remaining("admission", 15000) });
|
|
37
|
+
await budget.run(() => resolve(binding.stateDir, "hook.verify", { binding: identity.nativeHookBinding }, budget.remaining("generation")), "generation");
|
|
38
|
+
if (result?.permissionDecision === "deny" || result?.hookSpecificOutput?.permissionDecision === "deny") throw Object.assign(new Error("dd-flow denied native shell admission"), { code: "native_hook_rejected" });
|
|
39
|
+
return result;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
error.details = { ...error.details, native_hook_binding: identity.nativeHookBinding, ...(payload.phase === "after" ? { effect: "unknown", tool_outcome: payload.outcome } : {}) };
|
|
42
|
+
try { await writeNativeHookFailure(binding.stateDir, identity.nativeHookBinding, error, hookBudget(1_000, null)); await resolve(binding.stateDir, "hook.failed", {}, 1_000); }
|
|
43
|
+
catch (storage) {
|
|
44
|
+
error.details.receipt_error = { code: storage.code ?? "hook_receipt_write_failed", message: storage.message };
|
|
45
|
+
try { await resolve(binding.stateDir, "hook.failed", { binding: identity.nativeHookBinding, failure: errorRecord(error) }, 1_000); }
|
|
46
|
+
catch (notify) { error.details.receipt_error.notification_error = notify.code ?? "hook_notification_failed"; }
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
28
51
|
await ctx.shell.hook("create.before", event => {
|
|
52
|
+
Object.assign(event.env, managedFlowEnv(binding));
|
|
29
53
|
delete event.env.OPENCODE_PASSWORD; delete event.env.OPENCODE_SERVER_PASSWORD;
|
|
30
54
|
});
|
|
31
55
|
await ctx.tool.hook("execute.before", async event => {
|
|
@@ -35,7 +59,6 @@ export function makeOpenCodePlugin(binding, invoke = invokeNativeHook) {
|
|
|
35
59
|
const budget = hookBudget(), payload = await envelope("before", event, budget);
|
|
36
60
|
event.input = { ...event.input, workdir: payload.session.directory };
|
|
37
61
|
const result = await deliver({ ...payload, input: event.input }, budget);
|
|
38
|
-
if (result?.permissionDecision === "deny" || result?.hookSpecificOutput?.permissionDecision === "deny") throw new Error("dd-flow denied native shell admission");
|
|
39
62
|
if (result?.observed) {
|
|
40
63
|
participating.set(key(event), payload);
|
|
41
64
|
await ctx.storage.set(key(event), JSON.parse(JSON.stringify(payload)));
|
|
@@ -64,7 +64,9 @@ export function publishOperationProgress(root, id, phase, metadata = {}) {
|
|
|
64
64
|
const prior = await readOperationProgress(root, id);
|
|
65
65
|
const observedAt = metadata.observed_at ?? new Date().toISOString();
|
|
66
66
|
if (prior && Date.parse(observedAt)<Date.parse(prior.observed_at)) return prior;
|
|
67
|
-
|
|
67
|
+
// A later successful ownership gate is fresh liveness evidence while admission waits.
|
|
68
|
+
if (prior && phase === prior.phase && phase !== 'native_progress'
|
|
69
|
+
&& (phase !== 'ownership_confirmed' || Date.parse(observedAt) <= Date.parse(prior.observed_at))) return prior;
|
|
68
70
|
if (prior && phase === 'native_progress' && Date.parse(observedAt) - Date.parse(prior.observed_at) < 1_000) return prior;
|
|
69
71
|
const value = { schema_id: OPERATION_PROGRESS_CONTRACT,
|
|
70
72
|
...Object.fromEntries(fields.map(field => [field, requested[field] ?? null])),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const CAPACITY_POLICY: "provider-overload-episode@1";
|
|
2
|
+
export const MAX_OVERLOAD_RETRIES: 2;
|
|
3
|
+
export const MAX_RETRY_AFTER_MS: 30000;
|
|
4
|
+
export function classifyProviderError(native: unknown): string;
|
|
5
|
+
export function safeProviderError(native: unknown): Record<string, unknown>;
|
|
6
|
+
export type CapacityRefusal = { policy: string; ordinal: number; session_id: string; turn_id: string; first_observed_at: string; native_terminal_at?: string; native_terminal_basis?: string };
|
|
7
|
+
export function parseCapacityOperation(id: string): { root: string; ordinal: number };
|
|
8
|
+
export function normalizeProviderFailure(error: unknown, sessionId?: string, turnId?: string): null | { code: string; session_id: string; turn_id: string; native_error: Record<string, unknown>; observed_at: unknown; terminal_at: unknown; native_turn_items: unknown; primary_error: { details?: Record<string, unknown> } };
|
|
9
|
+
export function terminalProviderOverload(error: unknown, sessionId?: string, turnId?: string): boolean;
|
|
10
|
+
export function nativeItemsSettled(items: unknown): boolean;
|
|
11
|
+
export function capacityBackoff(error: unknown, ordinal: number, now?: number, random?: () => number): number;
|
|
12
|
+
export function refusalObservation(error: unknown, ordinal: number, firstObservedAt: string): CapacityRefusal;
|
|
13
|
+
export function overloadExhausted(refusals: CapacityRefusal[], maxRetries?: number): null | { policy: string; reason: string; attempts: number; refusals: CapacityRefusal[] };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** Pure policy shared by the controller and pinned-engine EVAL consumers. */
|
|
2
|
+
export const CAPACITY_POLICY = "provider-overload-episode@1";
|
|
3
|
+
export const MAX_OVERLOAD_RETRIES = 2;
|
|
4
|
+
export const MAX_RETRY_AFTER_MS = 30_000;
|
|
5
|
+
|
|
6
|
+
function policyError(code, message, details = {}) { return Object.assign(new Error(message), { code, details }); }
|
|
7
|
+
|
|
8
|
+
/** Only call on a native error envelope, never prompt or tool output. */
|
|
9
|
+
export function classifyProviderError(native) {
|
|
10
|
+
if (!native || typeof native !== "object") return "provider_failed";
|
|
11
|
+
const code = [native.code, native.type, native.codexErrorInfo?.code ?? native.codexErrorInfo].filter(value => typeof value === "string").join(" ");
|
|
12
|
+
const message = String(native.message ?? "");
|
|
13
|
+
const text = `${code} ${message}`;
|
|
14
|
+
const status = Number(native.status ?? native.statusCode ?? native.status_code ?? native.http_status);
|
|
15
|
+
if (status === 402 || /quota|usageLimitExceeded|sessionBudgetExceeded|billing|payment.required|balance.exhausted|insufficient.credit|resource_exhausted.*limit/i.test(text)) return "provider_quota_exhausted";
|
|
16
|
+
if (status === 401 || status === 403 || /provider_auth_failed|invalid.api.key|authentication|unauthorized|eligib|forbidden|access.denied|rejected.this.account/i.test(text)) return "provider_auth_failed";
|
|
17
|
+
if (/provider_capacity_transient|serverOverloaded|model.?capacity|at capacity|server.overload|model.overload|server.?busy/i.test(text) || /(?:^|\s)overloaded_error(?:\s|$)/i.test(code) || status === 503 || status === 529) return "provider_capacity_transient";
|
|
18
|
+
if (/rateLimitExceeded|rate.limit/i.test(text) && (native.retryAfter !== undefined || native.retry_after !== undefined || Object.keys(native.headers ?? {}).some(key => key.toLowerCase() === "retry-after"))) return "provider_rate_limited";
|
|
19
|
+
return status === 429 || /provider_limit_unknown|\b429\b|rateLimitExceeded|rate.limit/i.test(text) ? "provider_limit_unknown" : "provider_failed";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Public retry evidence excludes response bodies, credentials and other headers. */
|
|
23
|
+
export function safeProviderError(native) {
|
|
24
|
+
const result = { code: classifyProviderError(native) };
|
|
25
|
+
if (!native || typeof native !== "object") return result;
|
|
26
|
+
const status = native.status ?? native.statusCode ?? native.status_code ?? native.http_status;
|
|
27
|
+
if (Number.isInteger(Number(status))) result.status = Number(status);
|
|
28
|
+
const header = Object.entries(native.headers ?? {}).find(([key]) => key.toLowerCase() === "retry-after")?.[1];
|
|
29
|
+
const retry = header ?? native.retryAfter ?? native.retry_after;
|
|
30
|
+
if (retry !== undefined && retry !== null) result.retryAfter = typeof retry === "string" && retry.length <= 128 || typeof retry === "number" && Number.isFinite(retry) ? retry : "invalid-retry-after";
|
|
31
|
+
if (typeof native.willRetry === "boolean") result.willRetry = native.willRetry;
|
|
32
|
+
for (const key of ["reset_at", "resets_at", "resetAt", "resetsAt", "reset_time"]) {
|
|
33
|
+
const value = native[key];
|
|
34
|
+
if (typeof value === "string" && value.length <= 128 || typeof value === "number" && Number.isFinite(value)) result[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseCapacityOperation(id) {
|
|
40
|
+
if (typeof id !== "string" || !id) throw policyError("capacity_continuation_conflict", "Missing capacity operation identity");
|
|
41
|
+
const match = /^(.*):capacity:(\d+):session\.prompt$/.exec(id);
|
|
42
|
+
if (!match) return { root: id, ordinal: 0 };
|
|
43
|
+
const ordinal = Number(match[2]);
|
|
44
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 1 || String(ordinal) !== match[2]) throw policyError("capacity_continuation_conflict", "Capacity ordinal is not a canonical positive safe integer", { operation_id: id });
|
|
45
|
+
return { root: match[1], ordinal };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeProviderFailure(error, expectedSessionId, expectedTurnId) {
|
|
49
|
+
let current = error;
|
|
50
|
+
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
|
51
|
+
// The same continuation authority applies to a proven v2 execution
|
|
52
|
+
// refusal. Never classify transport loss or an active native retry here.
|
|
53
|
+
if (current.code === "provider_capacity_transient" || current.code === "provider_rate_limited") {
|
|
54
|
+
const details = current.details ?? {};
|
|
55
|
+
if (!["opencode.v2.session.execution", "agy.result", "acp.error_response", "droid.agent_turn_outcome", "grok.subagent.event"].includes(details.native_source) || details.terminal_status !== "failed"
|
|
56
|
+
|| details.will_retry === true || details.native_retry_active === true || details.provider_error?.willRetry === true
|
|
57
|
+
|| details.provider_error && !["provider_capacity_transient", "provider_rate_limited"].includes(classifyProviderError(details.provider_error))
|
|
58
|
+
|| typeof details.provider_session_id !== "string" || !details.provider_session_id
|
|
59
|
+
|| typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
|
|
60
|
+
|| expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
|
|
61
|
+
|| expectedTurnId !== undefined && details.turn_id !== expectedTurnId) return null;
|
|
62
|
+
return { code: "serverOverloaded", session_id: details.provider_session_id, turn_id: details.turn_id,
|
|
63
|
+
native_error: details.provider_error ?? { type: details.native_error_type, status: details.http_status }, observed_at: details.observed_at ?? null,
|
|
64
|
+
terminal_at: null, terminal_basis: null, native_turn_items: details.native_turn_items ?? null, primary_error: current };
|
|
65
|
+
}
|
|
66
|
+
if (current.code === "turn_interrupted") {
|
|
67
|
+
const details = current.details ?? {}, native = details.provider_error;
|
|
68
|
+
const code = typeof native?.codexErrorInfo === "string" ? native.codexErrorInfo : native?.codexErrorInfo?.code;
|
|
69
|
+
if (native?.willRetry === true || details.terminal_status !== "failed" || typeof details.provider_session_id !== "string" || !details.provider_session_id
|
|
70
|
+
|| expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
|
|
71
|
+
|| typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
|
|
72
|
+
|| expectedTurnId !== undefined && details.turn_id !== expectedTurnId || typeof code !== "string") return null;
|
|
73
|
+
return { code, session_id: details.provider_session_id, turn_id: details.turn_id, native_error: native,
|
|
74
|
+
observed_at: details.observed_at ?? null, terminal_at: details.terminal_at ?? null, terminal_basis: details.terminal_basis ?? null,
|
|
75
|
+
native_turn_items: details.native_turn_items ?? null, primary_error: current };
|
|
76
|
+
}
|
|
77
|
+
// No arbitrary recursive cause search: an ownership/hook/lifecycle error wins.
|
|
78
|
+
if (current.code !== "harness_adapter_failed") return null;
|
|
79
|
+
current = current.details?.cause ?? current.cause;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function terminalProviderOverload(error, expectedSessionId, expectedTurnId) {
|
|
85
|
+
const normalized = normalizeProviderFailure(error, expectedSessionId, expectedTurnId);
|
|
86
|
+
return normalized !== null && ["provider_capacity_transient", "provider_rate_limited"].includes(classifyProviderError(normalized.native_error));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function nativeItemsSettled(items) {
|
|
90
|
+
return items?.observed === true && typeof items.possible_effects === "boolean" && items.pending !== true
|
|
91
|
+
&& (items.possible_effects === false || items.pending === false);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function capacityBackoff(error, ordinal, now = Date.now(), random = Math.random) {
|
|
95
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 1 || !Number.isFinite(now)) throw policyError("capacity_continuation_conflict", "Invalid capacity timing identity");
|
|
96
|
+
const native = normalizeProviderFailure(error)?.native_error ?? error?.details?.provider_error ?? error;
|
|
97
|
+
const header = Object.entries(native?.headers ?? {}).find(([key]) => key.toLowerCase() === "retry-after")?.[1];
|
|
98
|
+
const value = header ?? native?.retryAfter ?? native?.retry_after;
|
|
99
|
+
let retry = 0;
|
|
100
|
+
if (value !== undefined && value !== null) {
|
|
101
|
+
retry = typeof value === "number" || typeof value === "string" && /^\d+(\.\d+)?$/.test(value.trim()) ? Number(value) * 1000 : typeof value === "string" && /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(value.trim()) ? Math.max(0, Date.parse(value) - now) : NaN;
|
|
102
|
+
if (!Number.isFinite(retry) || retry < 0 || !Number.isFinite(now + retry) || now + retry > 8.64e15)
|
|
103
|
+
throw policyError("capacity_retry_metadata_invalid", "Retry-After cannot be represented safely", { retry_after: value });
|
|
104
|
+
}
|
|
105
|
+
const jitter = random();
|
|
106
|
+
if (!Number.isFinite(jitter) || jitter < 0 || jitter >= 1) throw policyError("capacity_retry_metadata_invalid", "Invalid retry jitter");
|
|
107
|
+
const wait = Math.max((ordinal === 1 ? 5_000 : 15_000) + Math.floor(jitter * 251), retry);
|
|
108
|
+
if (wait > MAX_RETRY_AFTER_MS) throw policyError("provider_unavailable", "Provider requested a delay beyond the retry budget", { policy: CAPACITY_POLICY, reason: "retry_after_exceeds_budget", retry_after_ms: retry, suggested_retry_at: new Date(now + retry).toISOString() });
|
|
109
|
+
return wait;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function refusalObservation(error, ordinal, firstObservedAt) {
|
|
113
|
+
const failure = normalizeProviderFailure(error);
|
|
114
|
+
const observed = Date.parse(firstObservedAt);
|
|
115
|
+
if (!failure || !Number.isSafeInteger(ordinal) || ordinal < 0 || !Number.isFinite(observed))
|
|
116
|
+
throw policyError("capacity_continuation_conflict", "Refusal lacks an immutable native identity/time");
|
|
117
|
+
if (failure.observed_at !== null && (typeof failure.observed_at !== "string" || !Number.isFinite(Date.parse(failure.observed_at)) || Date.parse(failure.observed_at) !== observed))
|
|
118
|
+
throw policyError("capacity_continuation_conflict", "Native refusal observation time is invalid or changed");
|
|
119
|
+
const native = failure.terminal_at && Date.parse(failure.terminal_at);
|
|
120
|
+
return { policy: CAPACITY_POLICY, ordinal, session_id: failure.session_id, turn_id: failure.turn_id,
|
|
121
|
+
first_observed_at: firstObservedAt, ...(Number.isFinite(native) && typeof failure.terminal_basis === "string" && failure.terminal_basis ? { native_terminal_at: failure.terminal_at, native_terminal_basis: failure.terminal_basis } : {}) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function overloadExhausted(refusals, maxRetries = MAX_OVERLOAD_RETRIES) {
|
|
125
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0 || maxRetries > MAX_OVERLOAD_RETRIES) throw policyError("capacity_continuation_conflict", "Invalid retry budget");
|
|
126
|
+
for (const [index, refusal] of refusals.entries()) {
|
|
127
|
+
const previous = refusals[index - 1];
|
|
128
|
+
if (typeof refusal.first_observed_at !== "string" || !Number.isFinite(Date.parse(refusal.first_observed_at))
|
|
129
|
+
|| previous && Date.parse(refusal.first_observed_at) < Date.parse(previous.first_observed_at))
|
|
130
|
+
throw policyError("capacity_continuation_conflict", "Refusal observation clock reversed or is invalid");
|
|
131
|
+
}
|
|
132
|
+
for (const [index, refusal] of refusals.entries()) {
|
|
133
|
+
if (refusal.policy !== CAPACITY_POLICY) throw policyError("capacity_policy_mismatch", "Retained episode uses another retry policy");
|
|
134
|
+
const previous = refusals[index - 1];
|
|
135
|
+
if (refusal.ordinal !== index || previous && (refusal.session_id !== previous.session_id || refusals.slice(0, index).some(item => item.turn_id === refusal.turn_id))) throw policyError("capacity_continuation_conflict", "Refusal chain identity is inconsistent");
|
|
136
|
+
}
|
|
137
|
+
return refusals.length > maxRetries ? { policy: CAPACITY_POLICY, reason: "overload_retries_exhausted", attempts: refusals.length, refusals } : null;
|
|
138
|
+
}
|
package/dist/runtime/context.js
CHANGED
|
@@ -11,7 +11,7 @@ export function createRouterContext(env, deadlineAt) {
|
|
|
11
11
|
}
|
|
12
12
|
export function createContext(env, mode = "initialize", importPermitId, options = {}) {
|
|
13
13
|
const ddFlowHome = resolveDdFlowHome(env);
|
|
14
|
-
if (options.resourceDatabaseMode === "prepared_existing" || options.resourceDatabaseMode === "initialize")
|
|
14
|
+
if (options.resourceDatabaseMode === "prepared_existing" || options.resourceDatabaseMode === "initialize" || options.resourceDatabaseMode === "create_new")
|
|
15
15
|
assertImportAdmission(ddFlowHome, importPermitId);
|
|
16
16
|
return {
|
|
17
17
|
ddFlowHome,
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "dd-flow/discovery-result@1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"required": [
|
|
7
|
+
"schema_id",
|
|
8
|
+
"request_id",
|
|
9
|
+
"observed_at",
|
|
10
|
+
"probe_scope",
|
|
11
|
+
"live_authorization_receipt",
|
|
12
|
+
"entries"
|
|
13
|
+
],
|
|
14
|
+
"properties": {
|
|
15
|
+
"schema_id": {
|
|
16
|
+
"const": "dd-flow/discovery-result@1"
|
|
17
|
+
},
|
|
18
|
+
"request_id": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"minLength": 1
|
|
21
|
+
},
|
|
22
|
+
"observed_at": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]+)?Z$"
|
|
25
|
+
},
|
|
26
|
+
"probe_scope": {
|
|
27
|
+
"enum": [
|
|
28
|
+
"static",
|
|
29
|
+
"non-generative",
|
|
30
|
+
"live"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"live_authorization_receipt": {
|
|
34
|
+
"anyOf": [
|
|
35
|
+
{
|
|
36
|
+
"type": "object",
|
|
37
|
+
"additionalProperties": false,
|
|
38
|
+
"required": [
|
|
39
|
+
"path",
|
|
40
|
+
"sha256"
|
|
41
|
+
],
|
|
42
|
+
"properties": {
|
|
43
|
+
"path": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"pattern": "^[a-zA-Z0-9_-][a-zA-Z0-9_.-]*(/[a-zA-Z0-9_-][a-zA-Z0-9_.-]*)*$"
|
|
46
|
+
},
|
|
47
|
+
"sha256": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "null"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"entries": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"minItems": 0,
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"required": [
|
|
65
|
+
"harness",
|
|
66
|
+
"candidates",
|
|
67
|
+
"installed",
|
|
68
|
+
"compatible",
|
|
69
|
+
"protocol",
|
|
70
|
+
"auth",
|
|
71
|
+
"qualified",
|
|
72
|
+
"reason",
|
|
73
|
+
"evidence"
|
|
74
|
+
],
|
|
75
|
+
"properties": {
|
|
76
|
+
"harness": {
|
|
77
|
+
"enum": [
|
|
78
|
+
"codex",
|
|
79
|
+
"zcode",
|
|
80
|
+
"agy",
|
|
81
|
+
"opencode",
|
|
82
|
+
"grok",
|
|
83
|
+
"droid"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"candidates": {
|
|
87
|
+
"type": "array",
|
|
88
|
+
"minItems": 0,
|
|
89
|
+
"items": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"additionalProperties": false,
|
|
92
|
+
"required": [
|
|
93
|
+
"path",
|
|
94
|
+
"version",
|
|
95
|
+
"selected"
|
|
96
|
+
],
|
|
97
|
+
"properties": {
|
|
98
|
+
"path": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1
|
|
101
|
+
},
|
|
102
|
+
"version": {
|
|
103
|
+
"anyOf": [
|
|
104
|
+
{
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"type": "null"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"selected": {
|
|
114
|
+
"type": "boolean"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"installed": {
|
|
120
|
+
"enum": [
|
|
121
|
+
"yes",
|
|
122
|
+
"no",
|
|
123
|
+
"unknown"
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
"compatible": {
|
|
127
|
+
"enum": [
|
|
128
|
+
"yes",
|
|
129
|
+
"no",
|
|
130
|
+
"unknown"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
"protocol": {
|
|
134
|
+
"enum": [
|
|
135
|
+
"verified",
|
|
136
|
+
"incompatible",
|
|
137
|
+
"unknown"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"auth": {
|
|
141
|
+
"enum": [
|
|
142
|
+
"verified",
|
|
143
|
+
"unavailable",
|
|
144
|
+
"unknown"
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
"qualified": {
|
|
148
|
+
"enum": [
|
|
149
|
+
"yes",
|
|
150
|
+
"no",
|
|
151
|
+
"unknown"
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
"reason": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"minLength": 1
|
|
157
|
+
},
|
|
158
|
+
"evidence": {
|
|
159
|
+
"type": "array",
|
|
160
|
+
"minItems": 0,
|
|
161
|
+
"items": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"additionalProperties": false,
|
|
164
|
+
"required": [
|
|
165
|
+
"path",
|
|
166
|
+
"sha256"
|
|
167
|
+
],
|
|
168
|
+
"properties": {
|
|
169
|
+
"path": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"pattern": "^[a-zA-Z0-9_-][a-zA-Z0-9_.-]*(/[a-zA-Z0-9_-][a-zA-Z0-9_.-]*)*$"
|
|
172
|
+
},
|
|
173
|
+
"sha256": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|