@claudexor/orchestrator 3.17.1 → 3.19.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/dist/account-pool.d.ts +11 -6
- package/dist/account-pool.d.ts.map +1 -1
- package/dist/account-pool.js +5 -8
- package/dist/account-pool.js.map +1 -1
- package/dist/account-resolution.d.ts +5 -1
- package/dist/account-resolution.d.ts.map +1 -1
- package/dist/account-resolution.js +5 -2
- package/dist/account-resolution.js.map +1 -1
- package/dist/attemptOutputMarkers.d.ts +5 -0
- package/dist/attemptOutputMarkers.d.ts.map +1 -1
- package/dist/attemptOutputMarkers.js +3 -1
- package/dist/attemptOutputMarkers.js.map +1 -1
- package/dist/attemptTelemetry.d.ts +3 -4
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +6 -5
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/auth-route-classification.d.ts +22 -1
- package/dist/auth-route-classification.d.ts.map +1 -1
- package/dist/auth-route-classification.js +38 -0
- package/dist/auth-route-classification.js.map +1 -1
- package/dist/candidateEvidence.d.ts +1 -1
- package/dist/candidateEvidence.d.ts.map +1 -1
- package/dist/candidateEvidence.js +1 -1
- package/dist/candidateEvidence.js.map +1 -1
- package/dist/candidateTerminals.d.ts.map +1 -1
- package/dist/candidateTerminals.js +1 -0
- package/dist/candidateTerminals.js.map +1 -1
- package/dist/continuity-facts.d.ts +2 -0
- package/dist/continuity-facts.d.ts.map +1 -1
- package/dist/continuity-facts.js +2 -0
- package/dist/continuity-facts.js.map +1 -1
- package/dist/continuity-summary.d.ts +3 -0
- package/dist/continuity-summary.d.ts.map +1 -1
- package/dist/continuity-summary.js +4 -3
- package/dist/continuity-summary.js.map +1 -1
- package/dist/credential-differential.d.ts +1 -1
- package/dist/credential-differential.d.ts.map +1 -1
- package/dist/credential-differential.js +5 -2
- package/dist/credential-differential.js.map +1 -1
- package/dist/credential-profile-rotation.d.ts +4 -0
- package/dist/credential-profile-rotation.d.ts.map +1 -1
- package/dist/credential-profile-rotation.js +12 -13
- package/dist/credential-profile-rotation.js.map +1 -1
- package/dist/credential-profiles.d.ts +15 -2
- package/dist/credential-profiles.d.ts.map +1 -1
- package/dist/credential-profiles.js +19 -5
- package/dist/credential-profiles.js.map +1 -1
- package/dist/deepScanReducer.js +1 -1
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modelGovernance.d.ts +5 -31
- package/dist/modelGovernance.d.ts.map +1 -1
- package/dist/modelGovernance.js +49 -4
- package/dist/modelGovernance.js.map +1 -1
- package/dist/orchestrator-credentials.d.ts +60 -4
- package/dist/orchestrator-credentials.d.ts.map +1 -1
- package/dist/orchestrator-credentials.js +128 -7
- package/dist/orchestrator-credentials.js.map +1 -1
- package/dist/orchestrator.d.ts +10 -19
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +153 -189
- package/dist/orchestrator.js.map +1 -1
- package/dist/planRun.d.ts +9 -21
- package/dist/planRun.d.ts.map +1 -1
- package/dist/planRun.js +30 -34
- package/dist/planRun.js.map +1 -1
- package/dist/plannerAttempt.d.ts +15 -4
- package/dist/plannerAttempt.d.ts.map +1 -1
- package/dist/plannerAttempt.js +52 -26
- package/dist/plannerAttempt.js.map +1 -1
- package/dist/pre-progress-refusal.d.ts +61 -0
- package/dist/pre-progress-refusal.d.ts.map +1 -0
- package/dist/pre-progress-refusal.js +67 -0
- package/dist/pre-progress-refusal.js.map +1 -0
- package/dist/processing-dispatch.d.ts +7 -5
- package/dist/processing-dispatch.d.ts.map +1 -1
- package/dist/processing-dispatch.js +65 -31
- package/dist/processing-dispatch.js.map +1 -1
- package/dist/prompt-framing.d.ts +8 -0
- package/dist/prompt-framing.d.ts.map +1 -0
- package/dist/prompt-framing.js +33 -0
- package/dist/prompt-framing.js.map +1 -0
- package/dist/reviewerPanel.d.ts.map +1 -1
- package/dist/reviewerPanel.js +3 -2
- package/dist/reviewerPanel.js.map +1 -1
- package/dist/rotation-predicate.d.ts +3 -0
- package/dist/rotation-predicate.d.ts.map +1 -1
- package/dist/rotation-predicate.js +3 -0
- package/dist/rotation-predicate.js.map +1 -1
- package/dist/runSupport.d.ts.map +1 -1
- package/dist/runSupport.js +2 -4
- package/dist/runSupport.js.map +1 -1
- package/dist/runTerminalResults.d.ts +4 -1
- package/dist/runTerminalResults.d.ts.map +1 -1
- package/dist/runTerminalResults.js +14 -2
- package/dist/runTerminalResults.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { applyWorkEnvelope, promptWithPointers as renderPromptWithPointers, threadContextPointer, } from "./prompt-framing.js";
|
|
2
|
+
import { requestRefusalFailure } from "./runTerminalResults.js";
|
|
1
3
|
import { delegationBeltFor } from "./delegationBelt.js";
|
|
2
|
-
import { bindProcessingAdmission, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
|
|
4
|
+
import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
|
|
3
5
|
import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, } from "./candidateTerminals.js";
|
|
4
6
|
import { existsSync } from "node:fs";
|
|
5
|
-
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, rotateSpecOnTypedLimit,
|
|
6
|
-
import { OrchestratorCredentials, reviewerProfileResolver, rotatedSpecInLaneHome, } from "./orchestrator-credentials.js";
|
|
7
|
+
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, rotateSpecOnTypedLimit, } from "./credential-profiles.js";
|
|
8
|
+
import { AdmissionProfileProbes, OrchestratorCredentials, reviewerProfileResolver, rotatedSpecInLaneHome, } from "./orchestrator-credentials.js";
|
|
7
9
|
import { accountPoolRows } from "./account-pool.js";
|
|
8
10
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
9
11
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
@@ -11,18 +13,18 @@ import { processAttemptUsage } from "./attemptUsage.js";
|
|
|
11
13
|
import { captureDirectoryCandidate, directoryHasOutput, directoryCandidateStable, observeDirectoryPaths, publishDirectoryCandidate, } from "./directoryCandidate.js";
|
|
12
14
|
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, } from "./delegatedHome.js";
|
|
13
15
|
import * as AC from "./attemptUsageCost.js";
|
|
14
|
-
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
|
|
16
|
+
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, unanimousDeclaredFailure, } from "./candidateEvidence.js";
|
|
15
17
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
16
18
|
import { reviewCandidateRuns, resolveEngineReview, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
|
|
17
19
|
import { join } from "node:path";
|
|
18
20
|
import { finalizePlanRun, runCouncilPlan, writePlanHarnessFailure, } from "./planRun.js";
|
|
19
|
-
import { runPlannerAttempt as executePlannerAttempt, } from "./plannerAttempt.js";
|
|
21
|
+
import { runPlannerAttempt as executePlannerAttempt, plannerAttemptSummary, } from "./plannerAttempt.js";
|
|
20
22
|
import { HarnessRunSpec, MAX_COUNCIL_MEMBERS_DEFAULT, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, reviewAllowsApply, workStateVetoes, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
21
23
|
import { globalConfigDir, loadConfig } from "@claudexor/config";
|
|
22
24
|
import { prepareRoutedProcessing, processingCostEvidence, prepareReviewerProcessing, } from "./processing-routing.js";
|
|
23
25
|
import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
24
26
|
import { assertRouteModelsAllowed, runModelGovernedRoute } from "./modelGovernance.js";
|
|
25
|
-
import { authModeForCredentialRoute, authModeForPreference } from "./auth-route-classification.js";
|
|
27
|
+
import { authModeForCredentialRoute, authModeForPreference, authRouteEvidenceFor, } from "./auth-route-classification.js";
|
|
26
28
|
import { governRouteEffort } from "./effortGovernance.js";
|
|
27
29
|
import { isFullAccess, RequestRequirementsResolver } from "./requestRequirements.js";
|
|
28
30
|
import { activateDelegationParent } from "./delegation-parent-activation.js";
|
|
@@ -67,7 +69,7 @@ import { HarnessGateway } from "@claudexor/gateway";
|
|
|
67
69
|
import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate, runGates, } from "@claudexor/review";
|
|
68
70
|
import { arbitrate } from "@claudexor/arbitration";
|
|
69
71
|
import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
|
|
70
|
-
import {
|
|
72
|
+
import { billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, RoutingPreflightError, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, } from "@claudexor/budget";
|
|
71
73
|
import { readTextSafe, appendLine, assertNoInlineSecretValues, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, sha256, userConfigDir, } from "@claudexor/util";
|
|
72
74
|
const LABELS = "ABCDEFGHIJ".split("");
|
|
73
75
|
const NO_PROJECT_ROOT = noProjectRepoRoot();
|
|
@@ -366,6 +368,7 @@ export class Orchestrator {
|
|
|
366
368
|
quotaAbsences: () => this.deps.quotaAbsences?.() ?? [],
|
|
367
369
|
credentialUnusable: () => this.deps.credentialUnusable?.() ?? [],
|
|
368
370
|
recordCredentialUnusable: (obs) => this.deps.recordCredentialUnusable?.(obs),
|
|
371
|
+
preProgressRefusals: () => this.deps.preProgressRefusals,
|
|
369
372
|
authPreferenceForHarness: (repoRoot, harnessId, runPreference) => this.authPreferenceForHarness(repoRoot, harnessId, runPreference),
|
|
370
373
|
});
|
|
371
374
|
/**
|
|
@@ -486,9 +489,11 @@ export class Orchestrator {
|
|
|
486
489
|
dropped.push(detail);
|
|
487
490
|
droppedLanes.push({ harnessId, stage, detail });
|
|
488
491
|
};
|
|
489
|
-
//
|
|
490
|
-
//
|
|
492
|
+
// Initial lane readiness shares one quota observation. Selection and each
|
|
493
|
+
// physical dispatch still consume the latest exact-profile evidence.
|
|
491
494
|
const vendorQuota = this.credentials.vendorQuotaObservations();
|
|
495
|
+
// Billing later reuses the probe that admitted each row (#260).
|
|
496
|
+
const admissionProbes = new AdmissionProfileProbes();
|
|
492
497
|
const liveUnusable = this.deps.credentialUnusable?.() ?? [];
|
|
493
498
|
for (const id of ids) {
|
|
494
499
|
const adapter = this.deps.registry.get(id);
|
|
@@ -554,43 +559,20 @@ export class Orchestrator {
|
|
|
554
559
|
// profile-admitted route joins even an AUTO pool (the run spawns with
|
|
555
560
|
// the profile's transport, so the default store's state is not the
|
|
556
561
|
// routing truth). Capability/manifest gating above still applies.
|
|
557
|
-
let profileAdmitted = false;
|
|
558
562
|
const profileAdapter = this.deps.registry.get(id);
|
|
559
|
-
const profileProbe = profileAdapter?.probeCredentialProfile?.bind(profileAdapter);
|
|
563
|
+
const profileProbe = admissionProbes.record(profileAdapter?.probeCredentialProfile?.bind(profileAdapter));
|
|
560
564
|
const explicitPin = this.credentials.effectiveProfileId(input, id);
|
|
561
|
-
const model = input.models?.[id] ?? cfgEntry?.default_model ?? null;
|
|
562
565
|
// Pins and bound/pool rows are checked against their own readiness.
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
for (const candidateId of rowCandidateIds) {
|
|
574
|
-
const verdict = await selectedProfileAvailability({
|
|
575
|
-
registry: this.config(input.repoRoot)?.global.credential_profiles ?? [],
|
|
576
|
-
profileId: candidateId,
|
|
577
|
-
harnessId: id,
|
|
578
|
-
probe: profileProbe,
|
|
579
|
-
// `verification: passed` from the local store only means a login file
|
|
580
|
-
// is present. The poller's authenticated vendor call is the only
|
|
581
|
-
// liveness evidence; admission must act on it or dispatch may use a revoked token.
|
|
582
|
-
quota: vendorQuota,
|
|
583
|
-
unusable: liveUnusable,
|
|
584
|
-
model,
|
|
585
|
-
// Only an explicit/bound route may consume bounded stale LKG evidence.
|
|
586
|
-
allowStale: explicitPin !== null || input.threadAccountBindings?.[id] === candidateId,
|
|
587
|
-
});
|
|
588
|
-
if (verdict === "available") {
|
|
589
|
-
profileAdmitted = true;
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
lastRowVerdict = verdict;
|
|
593
|
-
}
|
|
566
|
+
const rows = await this.credentials.admitRouteRows({
|
|
567
|
+
input,
|
|
568
|
+
harnessId: id,
|
|
569
|
+
defaultReady: status.status === "ok",
|
|
570
|
+
model: input.models?.[id] ?? cfgEntry?.default_model ?? null,
|
|
571
|
+
quota: vendorQuota,
|
|
572
|
+
unusable: liveUnusable,
|
|
573
|
+
probe: profileProbe,
|
|
574
|
+
});
|
|
575
|
+
const profileAdmitted = rows.admitted;
|
|
594
576
|
if (profileAdmitted) {
|
|
595
577
|
// A valid account row restores manifest intent truth when the default store failed.
|
|
596
578
|
if (status.status !== "ok") {
|
|
@@ -603,14 +585,19 @@ export class Orchestrator {
|
|
|
603
585
|
}
|
|
604
586
|
}
|
|
605
587
|
else if (explicitPin) {
|
|
606
|
-
// An explicit pin that is not ready refuses/drops the lane;
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
const why = `${id} credential profile is not ready: ${lastRowVerdict}`;
|
|
588
|
+
// An explicit pin that is not ready refuses/drops the lane; not-ready
|
|
589
|
+
// UNPINNED rows are named below, and the default doctor verdict speaks
|
|
590
|
+
// only when neither a row nor a default login exists.
|
|
591
|
+
const why = `${id} credential profile is not ready: ${rows.pinVerdict}`;
|
|
611
592
|
dropLane(id, "credential", why);
|
|
612
593
|
continue;
|
|
613
594
|
}
|
|
595
|
+
// #363: registered rows own this lane, so their observed states — not
|
|
596
|
+
// the default login's doctor advice — explain why none was admitted.
|
|
597
|
+
if (rows.unreadyRows && (status.status === "unavailable" || !explicitPool)) {
|
|
598
|
+
dropLane(id, "credential", rows.unreadyRows);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
614
601
|
if (status.status === "unavailable" && !profileAdmitted) {
|
|
615
602
|
const why = `${id} is unavailable${status.reasons.length ? `: ${status.reasons.join("; ")}` : ""}`;
|
|
616
603
|
dropLane(id, "doctor", why);
|
|
@@ -677,7 +664,7 @@ export class Orchestrator {
|
|
|
677
664
|
// the default store's sources apply only to profile-less runs.
|
|
678
665
|
authRouteEstimate: this.credentials.profileAuthRoute(input, id) ??
|
|
679
666
|
estimateEffectiveAuthRoute(this.authPreferenceForHarness(input.repoRoot, id, input.authPreference), status.authSources),
|
|
680
|
-
quotaAdmission: { model: null, profile: null, route: null },
|
|
667
|
+
quotaAdmission: { model: null, profile: null, route: null, profileVerification: null },
|
|
681
668
|
supportsSynthesize: manifest.capabilities.synthesize,
|
|
682
669
|
supportsInteractive: manifest.capabilities.interactive,
|
|
683
670
|
liveInput: manifest.capability_profile.live_input,
|
|
@@ -722,7 +709,7 @@ export class Orchestrator {
|
|
|
722
709
|
const model = input.models?.[routed.adapter.id] ?? routed.settings?.defaultModel ?? null;
|
|
723
710
|
let profile;
|
|
724
711
|
try {
|
|
725
|
-
profile = await this.credentials.preflightProfile(input, routed.adapter.id, model, log, routed.authRouteEstimate);
|
|
712
|
+
profile = await this.credentials.preflightProfile(input, routed.adapter.id, model, log, routed.authRouteEstimate, admissionProbes);
|
|
726
713
|
}
|
|
727
714
|
catch (err) {
|
|
728
715
|
// AUTO drops typed lane-local unavailability only when a sibling
|
|
@@ -746,8 +733,16 @@ export class Orchestrator {
|
|
|
746
733
|
: routed.authRouteEstimate === "local_session"
|
|
747
734
|
? "vendor_native"
|
|
748
735
|
: null;
|
|
736
|
+
const profileVerification = profile
|
|
737
|
+
? await this.credentials.selectedProfileBillingVerification(profile, admissionProbes)
|
|
738
|
+
: null;
|
|
749
739
|
return {
|
|
750
|
-
prepared: {
|
|
740
|
+
prepared: {
|
|
741
|
+
...routed,
|
|
742
|
+
quotaAdmission: { model, profile, route, profileVerification },
|
|
743
|
+
paidFallback: this.config(input.repoRoot).global.routing.paid_fallback,
|
|
744
|
+
billingVerificationForProfile: (actual) => this.credentials.selectedProfileBillingVerification(actual),
|
|
745
|
+
},
|
|
751
746
|
refused: null,
|
|
752
747
|
};
|
|
753
748
|
}));
|
|
@@ -878,13 +873,14 @@ export class Orchestrator {
|
|
|
878
873
|
// engine default. Profile A's cooldown never excludes profile B or the
|
|
879
874
|
// default on the same harness and route.
|
|
880
875
|
const credentialSubjectId = r.quotaAdmission.profile?.profile_id ?? null;
|
|
881
|
-
// QA-034: the typed auth-route evidence (
|
|
882
|
-
//
|
|
883
|
-
//
|
|
876
|
+
// QA-034: the typed auth-route evidence (the admitted profile's own
|
|
877
|
+
// verification, else doctor source verification, x the resolved route)
|
|
878
|
+
// is AUTHORITATIVE for billing knowledge in the router — a VERIFIED
|
|
879
|
+
// native route proves subscription_entitlement, so it survives
|
|
884
880
|
// paid_fallback:never and ranks with a real economy tuple instead of
|
|
885
881
|
// reading as unknown/paid. Absent (unknown route) falls back to the
|
|
886
882
|
// metric-derived billingKnowledge below.
|
|
887
|
-
const authRoute =
|
|
883
|
+
const authRoute = authRouteEvidenceFor(authMode, status?.authSources ?? [], r.quotaAdmission.profileVerification);
|
|
888
884
|
const processingCost = processingCostEvidence(r.processing, authRoute ? billingKnowledgeForAuthRoute(authRoute) : "unknown", [`harness:${r.adapter.id}`, `profile:${credentialSubjectId ?? "default"}`]);
|
|
889
885
|
return {
|
|
890
886
|
harnessId: r.adapter.id,
|
|
@@ -984,30 +980,6 @@ export class Orchestrator {
|
|
|
984
980
|
},
|
|
985
981
|
};
|
|
986
982
|
}
|
|
987
|
-
/**
|
|
988
|
-
* Typed auth-route evidence for one candidate (QA-034): the concrete
|
|
989
|
-
* credential route the resolved auth mode maps to, plus the doctor's
|
|
990
|
-
* verification for the source that route runs under. `local_session` →
|
|
991
|
-
* vendor_native + the native/OAuth source verification; `api_key` →
|
|
992
|
-
* managed_api_key + the key source verification. Unknown route → no evidence
|
|
993
|
-
* (the router keeps its conservative metric-derived billing). Verification is
|
|
994
|
-
* the source's typed verdict — never inferred from mere availability.
|
|
995
|
-
*/
|
|
996
|
-
authRouteEvidenceFor(authMode, sources) {
|
|
997
|
-
const usable = (s) => s.availability === "available" && s.verification !== "failed";
|
|
998
|
-
if (authMode === "local_session") {
|
|
999
|
-
const native = sources.find((s) => usable(s) && (s.source === "native_session" || s.source === "oauth_token_env"));
|
|
1000
|
-
return { route: "vendor_native", verification: native?.verification ?? "not_run" };
|
|
1001
|
-
}
|
|
1002
|
-
if (authMode === "api_key") {
|
|
1003
|
-
const key = sources.find((s) => usable(s) &&
|
|
1004
|
-
(s.source === "api_key_env" ||
|
|
1005
|
-
s.source === "api_key_flag" ||
|
|
1006
|
-
s.source === "provider_auth_file"));
|
|
1007
|
-
return { route: "managed_api_key", verification: key?.verification ?? "not_run" };
|
|
1008
|
-
}
|
|
1009
|
-
return undefined;
|
|
1010
|
-
}
|
|
1011
983
|
/**
|
|
1012
984
|
* Lazy ContextPack: built ONLY for the read-only report modes
|
|
1013
985
|
* (explore/plan/readonly_audit) that consume it. Persisted to
|
|
@@ -1135,25 +1107,6 @@ export class Orchestrator {
|
|
|
1135
1107
|
callerSchema: contract.output_schema ?? null,
|
|
1136
1108
|
});
|
|
1137
1109
|
}
|
|
1138
|
-
/**
|
|
1139
|
-
* D-16: apply the resolved WorkReport transport to a built spec — set the
|
|
1140
|
-
* envelope output_schema (constrained/side_tool routes) and APPEND the fenced
|
|
1141
|
-
* metadata instruction (validated routes, e.g. cursor). Mutates the spec in
|
|
1142
|
-
* place and returns the mode the answer unwrap consumes. Called at every
|
|
1143
|
-
* task-producing spec-build site so the transport is never wired one-off.
|
|
1144
|
-
*/
|
|
1145
|
-
applyWorkEnvelope(spec, workEnvelope) {
|
|
1146
|
-
if (workEnvelope.outputSchema !== undefined)
|
|
1147
|
-
spec.output_schema = workEnvelope.outputSchema;
|
|
1148
|
-
const instruction = workEnvelope.mode.instruction;
|
|
1149
|
-
if (instruction) {
|
|
1150
|
-
spec.instructions =
|
|
1151
|
-
spec.instructions && spec.instructions.trim()
|
|
1152
|
-
? `${spec.instructions}\n\n${instruction}`
|
|
1153
|
-
: instruction;
|
|
1154
|
-
}
|
|
1155
|
-
return workEnvelope.mode;
|
|
1156
|
-
}
|
|
1157
1110
|
routeSpecKnobs(routed, contract, overrideModel, effortHint) {
|
|
1158
1111
|
const s = routed.settings;
|
|
1159
1112
|
const contractPolicy = contract.external_context.policy;
|
|
@@ -1269,6 +1222,8 @@ export class Orchestrator {
|
|
|
1269
1222
|
processing,
|
|
1270
1223
|
processingAdmission,
|
|
1271
1224
|
physicalDispatchStarted,
|
|
1225
|
+
billingVerificationForProfile: (profile) => this.credentials.selectedProfileBillingVerification(profile),
|
|
1226
|
+
paidFallback: this.config(runInput.repoRoot).global.routing.paid_fallback,
|
|
1272
1227
|
});
|
|
1273
1228
|
const result = buildContinuation(req);
|
|
1274
1229
|
// Disclose on every lane and stamp the turn (INV-137: never silent).
|
|
@@ -1286,7 +1241,7 @@ export class Orchestrator {
|
|
|
1286
1241
|
const briefPath = join(paths.contextDir, "THREAD.md");
|
|
1287
1242
|
store.writeText(briefPath, result.packetMarkdown);
|
|
1288
1243
|
return {
|
|
1289
|
-
pointerLine:
|
|
1244
|
+
pointerLine: threadContextPointer(briefPath),
|
|
1290
1245
|
};
|
|
1291
1246
|
}
|
|
1292
1247
|
catch (err) {
|
|
@@ -1354,7 +1309,7 @@ export class Orchestrator {
|
|
|
1354
1309
|
const sessionFields = runInput
|
|
1355
1310
|
? await this.sessionSpecFields(runInput, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission)
|
|
1356
1311
|
: undefined;
|
|
1357
|
-
const processingAdmission = processingAdmissionForLease(ledger, processingLease.
|
|
1312
|
+
const processingAdmission = processingAdmissionForLease(ledger, processingLease.lease, adapter.id, attemptId, processingLease.onDenied);
|
|
1358
1313
|
const capturedProcessing = {
|
|
1359
1314
|
processing_preference: contract.processing_preference,
|
|
1360
1315
|
processing: knobs.processing?.receipt,
|
|
@@ -1362,7 +1317,7 @@ export class Orchestrator {
|
|
|
1362
1317
|
processing_allow_paid: knobs.processingAllowPaid,
|
|
1363
1318
|
};
|
|
1364
1319
|
const laneContinuity = runInput
|
|
1365
|
-
? await this.resolveContinuity(runInput, adapter.id, sessionFields?.credential_profile?.profile_id ?? runInput.credentialProfileId ?? null, inPlaceEnvelope && !!sessionFields?.resume_session_id, sessionFields, store, paths, envelope.repo_root, log, capturedProcessing, processingAdmission, () => ledger.markPhysicalDispatchStarted(processingLease.
|
|
1320
|
+
? await this.resolveContinuity(runInput, adapter.id, sessionFields?.credential_profile?.profile_id ?? runInput.credentialProfileId ?? null, inPlaceEnvelope && !!sessionFields?.resume_session_id, sessionFields, store, paths, envelope.repo_root, log, capturedProcessing, processingAdmission, () => ledger.markPhysicalDispatchStarted(processingLease.lease.lease_id))
|
|
1366
1321
|
: null;
|
|
1367
1322
|
const artifactRelativeDir = routed.browserRequirement.effective
|
|
1368
1323
|
? (wsm.ensureArtifactDirectory(envelope), wsm.ownedArtifactRelativeDirectory(envelope))
|
|
@@ -1405,20 +1360,18 @@ export class Orchestrator {
|
|
|
1405
1360
|
raw_context_packet: rawContextPacket,
|
|
1406
1361
|
stream_deltas: streamDeltas,
|
|
1407
1362
|
});
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
//
|
|
1412
|
-
//
|
|
1413
|
-
spec.extra["routeBillingKnowledge"] = (actual) => actual.credential_profile
|
|
1414
|
-
? "
|
|
1415
|
-
:
|
|
1416
|
-
? "subscription_entitlement"
|
|
1417
|
-
: this.routeBillingKnowledge(billingInput, adapter.id);
|
|
1363
|
+
if (runInput)
|
|
1364
|
+
this.credentials.stampProfileSelection(spec, runInput, adapter.id);
|
|
1365
|
+
bindProcessingAdmission(spec, ledger, processingLease.lease, adapter.id, attemptId, processingLease.onDenied, processingAdmission);
|
|
1366
|
+
// Profile-less processing keeps its existing route estimate. Named rows
|
|
1367
|
+
// receive their own verification at the shared physical-dispatch gate.
|
|
1368
|
+
spec.extra["routeBillingKnowledge"] = (actual) => actual.credential_profile
|
|
1369
|
+
? "unknown"
|
|
1370
|
+
: this.routeBillingKnowledge(runInput ?? { repoRoot: contract.repo.root }, adapter.id);
|
|
1418
1371
|
if (interaction)
|
|
1419
1372
|
spec.extra["interactionChannel"] = interaction;
|
|
1420
1373
|
const workEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(interaction));
|
|
1421
|
-
const workReportMode =
|
|
1374
|
+
const workReportMode = applyWorkEnvelope(spec, workEnvelope);
|
|
1422
1375
|
const inactivityMs = harnessInactivityTimeoutMs(this.config(contract.repo.root));
|
|
1423
1376
|
// Named once: the attempt record, the CandidateRun and the terminal gate all
|
|
1424
1377
|
// read the SAME applied facts rather than three re-derivations. Derived
|
|
@@ -1482,6 +1435,8 @@ export class Orchestrator {
|
|
|
1482
1435
|
? AbortSignal.any([signal, attemptAbort.signal])
|
|
1483
1436
|
: attemptAbort.signal;
|
|
1484
1437
|
activeSessionId = runSpec.session_id;
|
|
1438
|
+
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
1439
|
+
const refusal = this.credentials.bindPreProgressRefusal(adapter.id, runSpec);
|
|
1485
1440
|
const transientStart = telemetry.transientFailures.length;
|
|
1486
1441
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
1487
1442
|
let rawPatch = null;
|
|
@@ -1585,7 +1540,8 @@ export class Orchestrator {
|
|
|
1585
1540
|
// gate and required-actions read a typed category, not a bare boolean.
|
|
1586
1541
|
harnessErrored = true;
|
|
1587
1542
|
errors.push(safeErrorMessage(err));
|
|
1588
|
-
if (err instanceof ProcessingBudgetAdmissionError
|
|
1543
|
+
if (err instanceof ProcessingBudgetAdmissionError ||
|
|
1544
|
+
err instanceof RoutingPreflightError) {
|
|
1589
1545
|
processingRefusal = err;
|
|
1590
1546
|
break;
|
|
1591
1547
|
}
|
|
@@ -1595,6 +1551,8 @@ export class Orchestrator {
|
|
|
1595
1551
|
finally {
|
|
1596
1552
|
clearFileBackedContext();
|
|
1597
1553
|
}
|
|
1554
|
+
if (telemetry.requestRefusal)
|
|
1555
|
+
break;
|
|
1598
1556
|
const newTransients = telemetry.transientFailures.slice(transientStart);
|
|
1599
1557
|
// #31: the centralized retry gate reads the classified `retryable` verdict.
|
|
1600
1558
|
const sawRetryable = newTransients.some((f) => f.retryable);
|
|
@@ -1606,6 +1564,8 @@ export class Orchestrator {
|
|
|
1606
1564
|
? directoryCapture.files?.noChanges === true
|
|
1607
1565
|
: currentDiff.trim().length === 0;
|
|
1608
1566
|
const deliverableEmpty = workspaceUnchanged && answer.text().length === 0;
|
|
1567
|
+
const delivered = !harnessErrored && !deliverableEmpty && !signal?.aborted;
|
|
1568
|
+
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
1609
1569
|
// W5.4 + A2 failover: a typed-limit hit OR a structural pre-progress
|
|
1610
1570
|
// death rebuilds the spec on a NEW session under the next profile.
|
|
1611
1571
|
if (harnessErrored && runInput && !signal?.aborted) {
|
|
@@ -1617,9 +1577,10 @@ export class Orchestrator {
|
|
|
1617
1577
|
registry: this.config(contract.repo.root)?.global.credential_profiles ?? [],
|
|
1618
1578
|
snapshots: this.deps.quotaSnapshots?.() ?? [],
|
|
1619
1579
|
probeReadyProfiles: () => this.credentials.readyProfileIdsForRotation(runInput, adapter.id, spec.credential_profile ?? null, triedProfiles, spec.model_hint ?? null),
|
|
1620
|
-
...this.credentials.rotationObservations(adapter, spec, newTransients),
|
|
1580
|
+
...this.credentials.rotationObservations(adapter, spec, newTransients, refusal),
|
|
1621
1581
|
triedProfiles,
|
|
1622
1582
|
markers: telemetry.outputMarkers,
|
|
1583
|
+
requestRefused: Boolean(telemetry.requestRefusal),
|
|
1623
1584
|
sawTypedLimit,
|
|
1624
1585
|
sawRetryable,
|
|
1625
1586
|
attemptErrored: harnessErrored,
|
|
@@ -1671,7 +1632,7 @@ export class Orchestrator {
|
|
|
1671
1632
|
}
|
|
1672
1633
|
// A pool-exhausted terminal is rotation's verdict, not the transient
|
|
1673
1634
|
// machinery's — no `route.transient.exhausted` rides along with it.
|
|
1674
|
-
if (harnessErrored && !poolExhausted && !processingRefusal) {
|
|
1635
|
+
if (harnessErrored && !poolExhausted && !processingRefusal && !telemetry.requestRefusal) {
|
|
1675
1636
|
emitTransientExhausted((t, p) => log?.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
|
|
1676
1637
|
}
|
|
1677
1638
|
const attemptStreamEndedMs = Date.now();
|
|
@@ -1822,9 +1783,11 @@ export class Orchestrator {
|
|
|
1822
1783
|
telemetry,
|
|
1823
1784
|
...(secretDiffRefusal ? { secretDiffRefusal } : {}),
|
|
1824
1785
|
// A5: the typed refusal survives NORMAL attempt finalization (no throw).
|
|
1825
|
-
...(
|
|
1826
|
-
? { declaredFailure:
|
|
1827
|
-
:
|
|
1786
|
+
...(telemetry.requestRefusal
|
|
1787
|
+
? { declaredFailure: requestRefusalFailure(telemetry.requestRefusal) }
|
|
1788
|
+
: poolExhausted || processingRefusal
|
|
1789
|
+
? { declaredFailure: declaredFailure(processingRefusal ?? poolExhausted) }
|
|
1790
|
+
: {}),
|
|
1828
1791
|
outcomeClass: finalized.outcomeClass,
|
|
1829
1792
|
applied,
|
|
1830
1793
|
};
|
|
@@ -1972,7 +1935,7 @@ export class Orchestrator {
|
|
|
1972
1935
|
attemptId,
|
|
1973
1936
|
intent: this.candidateIntent(input),
|
|
1974
1937
|
harnessId: routed.adapter.id,
|
|
1975
|
-
cost:
|
|
1938
|
+
cost: processingAttemptCostEvidence(routed.adapter.id, attemptId, this.reservationEstimateUsd(input, i > 0), this.routeBillingKnowledge(input, routed.adapter.id, routed), routed.processing),
|
|
1976
1939
|
});
|
|
1977
1940
|
log.emit("budget.lease.created", {
|
|
1978
1941
|
granted: lease.granted,
|
|
@@ -1997,7 +1960,7 @@ export class Orchestrator {
|
|
|
1997
1960
|
routed,
|
|
1998
1961
|
attemptId,
|
|
1999
1962
|
label: `Candidate ${LABELS[i] ?? i + 1}`,
|
|
2000
|
-
|
|
1963
|
+
lease: lease.lease,
|
|
2001
1964
|
});
|
|
2002
1965
|
}
|
|
2003
1966
|
const runsBySlot = new Array(slots.length);
|
|
@@ -2008,14 +1971,14 @@ export class Orchestrator {
|
|
|
2008
1971
|
let candidateContinuationCount = 0;
|
|
2009
1972
|
const runSlot = async (slot, slotIdx) => {
|
|
2010
1973
|
if (input.signal?.aborted) {
|
|
2011
|
-
ledger.cancel(slot.
|
|
1974
|
+
ledger.cancel(slot.lease.lease_id);
|
|
2012
1975
|
return;
|
|
2013
1976
|
}
|
|
2014
1977
|
// Leases are granted upfront (before spend exists); a worker still
|
|
2015
1978
|
// re-checks the circuit breaker so queued slots beyond the parallel wave
|
|
2016
1979
|
// do not start after earlier candidates already blew the hard cap.
|
|
2017
1980
|
if (budgetStopped || ledger.tier() === "hard") {
|
|
2018
|
-
ledger.cancel(slot.
|
|
1981
|
+
ledger.cancel(slot.lease.lease_id);
|
|
2019
1982
|
log.emit("budget.lease.created", {
|
|
2020
1983
|
granted: false,
|
|
2021
1984
|
reason: "budget exhausted (hard cap reached)",
|
|
@@ -2079,7 +2042,7 @@ export class Orchestrator {
|
|
|
2079
2042
|
});
|
|
2080
2043
|
harnessHome = this.harnessHomeFor(wsm, envelope, slot.routed, input);
|
|
2081
2044
|
const run = await this.runCandidateInEnvelope(slot.routed, envelope, slot.attemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
|
|
2082
|
-
|
|
2045
|
+
lease: slot.lease,
|
|
2083
2046
|
onDenied: (denial) => {
|
|
2084
2047
|
budgetStopped = true;
|
|
2085
2048
|
budgetDenial ??= denial;
|
|
@@ -2089,14 +2052,14 @@ export class Orchestrator {
|
|
|
2089
2052
|
safeInvoke(input.onHarnessEvent, safeEv);
|
|
2090
2053
|
log.emit("harness.event", harnessEventPayload(adapter.id, slot.attemptId, safeEv));
|
|
2091
2054
|
}, input.signal, downgradeModel ?? undefined, input.effort, this.candidateIntent(input), log, effectiveWeb, this.interactionChannelFor(input, log, runId, taskId, slot.attemptId, adapter.id, slot.routed.supportsInteractive), (streamedUsd) => {
|
|
2092
|
-
ledger.updateHold(slot.
|
|
2055
|
+
ledger.updateHold(slot.lease.lease_id, streamedUsd);
|
|
2093
2056
|
if (ledger.tier() !== "hard")
|
|
2094
2057
|
return false;
|
|
2095
2058
|
budgetStopped = true;
|
|
2096
2059
|
return true;
|
|
2097
2060
|
}, input, requestedSingleCandidate, // W-C4 deltas: single-candidate chat lane only (racing = noise x N)
|
|
2098
2061
|
undefined, undefined, harnessHome);
|
|
2099
|
-
ledger.settle(slot.
|
|
2062
|
+
ledger.settle(slot.lease.lease_id, attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
2100
2063
|
log.emit("harness.completed", {
|
|
2101
2064
|
harness_id: adapter.id,
|
|
2102
2065
|
attempt_id: slot.attemptId,
|
|
@@ -2142,7 +2105,7 @@ export class Orchestrator {
|
|
|
2142
2105
|
attemptId: contAttemptId,
|
|
2143
2106
|
intent: this.candidateIntent(input),
|
|
2144
2107
|
harnessId: adapter.id,
|
|
2145
|
-
cost:
|
|
2108
|
+
cost: processingAttemptCostEvidence(adapter.id, contAttemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, adapter.id, slot.routed), slot.routed.processing),
|
|
2146
2109
|
});
|
|
2147
2110
|
if (contLease.granted) {
|
|
2148
2111
|
candidateContinuationCount += 1; // claim the one-shot only once it launches
|
|
@@ -2162,7 +2125,7 @@ export class Orchestrator {
|
|
|
2162
2125
|
const contLeaseId = contLease.lease?.lease_id ?? "";
|
|
2163
2126
|
try {
|
|
2164
2127
|
const contRun = await this.runCandidateInEnvelope(slot.routed, envelope, contAttemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
|
|
2165
|
-
|
|
2128
|
+
lease: contLease.lease,
|
|
2166
2129
|
onDenied: (denial) => {
|
|
2167
2130
|
budgetStopped = true;
|
|
2168
2131
|
budgetDenial ??= denial;
|
|
@@ -2221,7 +2184,7 @@ export class Orchestrator {
|
|
|
2221
2184
|
}
|
|
2222
2185
|
catch (err) {
|
|
2223
2186
|
const failureCost = AC.attemptFailureCost(err, "post-stream-error", 0);
|
|
2224
|
-
ledger.settle(slot.
|
|
2187
|
+
ledger.settle(slot.lease.lease_id, failureCost.settlement);
|
|
2225
2188
|
const message = safeErrorMessage(err);
|
|
2226
2189
|
const declared = declaredFailure(err);
|
|
2227
2190
|
const infraPhase = envelope === undefined ? "workspace" : "harness";
|
|
@@ -2428,7 +2391,7 @@ export class Orchestrator {
|
|
|
2428
2391
|
attemptId: "synth",
|
|
2429
2392
|
intent: "synthesize",
|
|
2430
2393
|
harnessId: synthRouted.adapter.id,
|
|
2431
|
-
cost:
|
|
2394
|
+
cost: processingAttemptCostEvidence(synthRouted.adapter.id, "synth", this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, synthRouted.adapter.id, synthRouted), synthRouted.processing),
|
|
2432
2395
|
});
|
|
2433
2396
|
if (lease.granted) {
|
|
2434
2397
|
let envelope;
|
|
@@ -2455,7 +2418,7 @@ export class Orchestrator {
|
|
|
2455
2418
|
});
|
|
2456
2419
|
const synthHome = this.harnessHomeFor(wsm, envelope, synthRouted, input);
|
|
2457
2420
|
const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger, {
|
|
2458
|
-
|
|
2421
|
+
lease: lease.lease,
|
|
2459
2422
|
onDenied: (denial) => {
|
|
2460
2423
|
budgetStopped = true;
|
|
2461
2424
|
budgetDenial ??= denial;
|
|
@@ -3235,7 +3198,7 @@ export class Orchestrator {
|
|
|
3235
3198
|
attemptId,
|
|
3236
3199
|
intent: "repair",
|
|
3237
3200
|
harnessId: adapter.id,
|
|
3238
|
-
cost:
|
|
3201
|
+
cost: processingAttemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, adapter.id, routed), routed.processing),
|
|
3239
3202
|
});
|
|
3240
3203
|
if (!lease.granted) {
|
|
3241
3204
|
exhausted = true;
|
|
@@ -3254,7 +3217,7 @@ export class Orchestrator {
|
|
|
3254
3217
|
...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
|
|
3255
3218
|
});
|
|
3256
3219
|
run = await this.runCandidateInEnvelope(routed, envelope, attemptId, `Attempt ${attempt}`, contract, prompt, store, paths, wsm, ledger, {
|
|
3257
|
-
|
|
3220
|
+
lease: lease.lease,
|
|
3258
3221
|
onDenied: (denial) => {
|
|
3259
3222
|
exhausted = true;
|
|
3260
3223
|
processingBudgetDenial ??= denial;
|
|
@@ -3327,7 +3290,8 @@ export class Orchestrator {
|
|
|
3327
3290
|
// Cancellation/deadline keeps priority over a belt failure finalized concurrently.
|
|
3328
3291
|
if (input.signal?.aborted || processingBudgetDenial)
|
|
3329
3292
|
break;
|
|
3330
|
-
if (run.telemetry.
|
|
3293
|
+
if (run.telemetry.requestRefusal ||
|
|
3294
|
+
run.telemetry.effortResolution?.resolution === "rejected")
|
|
3331
3295
|
break;
|
|
3332
3296
|
if (delegateFailure.candidateFailureKind(run)) {
|
|
3333
3297
|
const failure = delegateFailure.candidateFailureTerminal(run, "convergence");
|
|
@@ -3702,6 +3666,7 @@ export class Orchestrator {
|
|
|
3702
3666
|
: (convDeclared?.category ?? "internal"),
|
|
3703
3667
|
code: processingBudgetMapping?.code ?? convDeclared?.code ?? null,
|
|
3704
3668
|
resetsAt: convDeclared?.resetsAt ?? null,
|
|
3669
|
+
requestRefusal: convDeclared?.requestRefusal,
|
|
3705
3670
|
safeMessage: convNeedsDecision
|
|
3706
3671
|
? `review escalated to a human decision after ${attempt} attempt(s)`
|
|
3707
3672
|
: facts.reason === "stuck_no_progress"
|
|
@@ -3818,17 +3783,17 @@ export class Orchestrator {
|
|
|
3818
3783
|
/** Bind private route/session preparation to the planner-attempt owner. */
|
|
3819
3784
|
plannerAttemptDeps() {
|
|
3820
3785
|
return {
|
|
3821
|
-
billingKnowledge: (input, harnessId) => this.routeBillingKnowledge(input, harnessId),
|
|
3786
|
+
billingKnowledge: (input, harnessId, routed) => this.routeBillingKnowledge(input, harnessId, routed),
|
|
3822
3787
|
inactivityTimeoutMs: (repoRoot) => harnessInactivityTimeoutMs(this.config(repoRoot)),
|
|
3823
3788
|
quotaEventSink: this.deps.quotaEventSink,
|
|
3824
|
-
prepare: async (args) => {
|
|
3789
|
+
prepare: async (args, processingAdmission, physicalDispatchStarted) => {
|
|
3825
3790
|
const { input, contract, taskId, runId, log, store, paths, routed, attemptId } = args;
|
|
3826
3791
|
const adapter = routed.adapter;
|
|
3827
3792
|
const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
|
|
3828
3793
|
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
3829
3794
|
const sessionFields = await this.sessionSpecFields(input, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission);
|
|
3830
3795
|
const laneContinuity = args.laneRun
|
|
3831
|
-
? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, sessionFields.resume_session_id !== null, sessionFields, store, paths, this.execRootOf(input), log)
|
|
3796
|
+
? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, sessionFields.resume_session_id !== null, sessionFields, store, paths, this.execRootOf(input), log, this.harnessSpecKnobs(contract, knobs, args.intent), processingAdmission, physicalDispatchStarted)
|
|
3832
3797
|
: null;
|
|
3833
3798
|
const spec = HarnessRunSpec.parse({
|
|
3834
3799
|
session_id: newId("ses"),
|
|
@@ -3846,6 +3811,7 @@ export class Orchestrator {
|
|
|
3846
3811
|
? this.laneHomeEnvFor(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null)
|
|
3847
3812
|
: null) ?? args.fallbackHome,
|
|
3848
3813
|
});
|
|
3814
|
+
this.credentials.stampProfileSelection(spec, input, adapter.id);
|
|
3849
3815
|
const plannerAbort = new AbortController();
|
|
3850
3816
|
spec.extra["abortSignal"] = input.signal
|
|
3851
3817
|
? AbortSignal.any([input.signal, plannerAbort.signal])
|
|
@@ -3853,7 +3819,7 @@ export class Orchestrator {
|
|
|
3853
3819
|
const planInteraction = this.interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
3854
3820
|
if (planInteraction)
|
|
3855
3821
|
spec.extra["interactionChannel"] = planInteraction;
|
|
3856
|
-
const planWorkMode =
|
|
3822
|
+
const planWorkMode = applyWorkEnvelope(spec, this.workReportEnvelopeFor(routed, contract, Boolean(planInteraction)));
|
|
3857
3823
|
return {
|
|
3858
3824
|
knobs,
|
|
3859
3825
|
effectiveWeb,
|
|
@@ -4016,13 +3982,7 @@ export class Orchestrator {
|
|
|
4016
3982
|
// terminal names the refused route and does not read "all planners
|
|
4017
3983
|
// failed"; capture the typed denial for the budget classifier.
|
|
4018
3984
|
planBudgetDenial ??= outcome.budgetDenial ?? null;
|
|
4019
|
-
planAttempts.push(
|
|
4020
|
-
attemptId,
|
|
4021
|
-
harnessId: outcome.harnessId,
|
|
4022
|
-
status: outcome.status,
|
|
4023
|
-
outcomeClass: outcome.outcomeClass,
|
|
4024
|
-
error: outcome.error,
|
|
4025
|
-
});
|
|
3985
|
+
planAttempts.push(plannerAttemptSummary(outcome));
|
|
4026
3986
|
break;
|
|
4027
3987
|
}
|
|
4028
3988
|
if (outcome.telemetry)
|
|
@@ -4031,13 +3991,7 @@ export class Orchestrator {
|
|
|
4031
3991
|
harnessId: outcome.harnessId,
|
|
4032
3992
|
telemetry: outcome.telemetry,
|
|
4033
3993
|
});
|
|
4034
|
-
planAttempts.push(
|
|
4035
|
-
attemptId,
|
|
4036
|
-
harnessId: outcome.harnessId,
|
|
4037
|
-
status: outcome.status,
|
|
4038
|
-
outcomeClass: outcome.outcomeClass,
|
|
4039
|
-
error: outcome.error,
|
|
4040
|
-
});
|
|
3994
|
+
planAttempts.push(plannerAttemptSummary(outcome));
|
|
4041
3995
|
if (outcome.status !== "success") {
|
|
4042
3996
|
if (outcome.telemetry?.effortResolution?.resolution === "rejected")
|
|
4043
3997
|
break;
|
|
@@ -4162,7 +4116,13 @@ export class Orchestrator {
|
|
|
4162
4116
|
quotaSnapshotPreflight() {
|
|
4163
4117
|
return [...(this.deps.quotaSnapshots?.() ?? [])].map((snapshot) => QuotaSnapshotSchema.parse(snapshot));
|
|
4164
4118
|
}
|
|
4165
|
-
routeBillingKnowledge(input, harnessId) {
|
|
4119
|
+
routeBillingKnowledge(input, harnessId, routed) {
|
|
4120
|
+
if (routed?.quotaAdmission.profile) {
|
|
4121
|
+
return billingKnowledgeForAuthRoute({
|
|
4122
|
+
route: routed.quotaAdmission.route,
|
|
4123
|
+
verification: routed.quotaAdmission.profileVerification ?? "not_run",
|
|
4124
|
+
});
|
|
4125
|
+
}
|
|
4166
4126
|
// A selected profile's credential_kind decides billing (round-18 #2).
|
|
4167
4127
|
const profileRoute = this.credentials.profileAuthRoute(input, harnessId);
|
|
4168
4128
|
if (profileRoute)
|
|
@@ -4194,9 +4154,7 @@ export class Orchestrator {
|
|
|
4194
4154
|
// The reducer admits under a finite estimate floor (mirror of the n>1
|
|
4195
4155
|
// scout reserve) so a subscription route is not refused for lacking a
|
|
4196
4156
|
// cash quote.
|
|
4197
|
-
|
|
4198
|
-
`harness:${harnessId}`,
|
|
4199
|
-
])),
|
|
4157
|
+
processingAttemptCostEvidence(harnessId, attemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, harnessId, routed), routed?.processing),
|
|
4200
4158
|
buildSpec: async (routed, homeEnv, prompt, attemptId) => {
|
|
4201
4159
|
const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
|
|
4202
4160
|
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
@@ -4216,10 +4174,11 @@ export class Orchestrator {
|
|
|
4216
4174
|
env_inheritance: envInheritance(this.config(input.repoRoot)),
|
|
4217
4175
|
env: homeEnv,
|
|
4218
4176
|
});
|
|
4177
|
+
this.credentials.stampProfileSelection(spec, input, routed.adapter.id);
|
|
4219
4178
|
// D-16: compile the WorkReport transport onto the reducer spec (the
|
|
4220
4179
|
// reducer is non-interactive) so its output is unwrapped + finalized
|
|
4221
4180
|
// through the shared attempt contract, not a fourth deliverable predicate.
|
|
4222
|
-
const workReportMode =
|
|
4181
|
+
const workReportMode = applyWorkEnvelope(spec, this.workReportEnvelopeFor(routed, contract, false));
|
|
4223
4182
|
return {
|
|
4224
4183
|
spec,
|
|
4225
4184
|
webPolicy: knobs.webPolicy,
|
|
@@ -4369,7 +4328,7 @@ export class Orchestrator {
|
|
|
4369
4328
|
// (mirror of the candidate loop): the first top-level scout reserves
|
|
4370
4329
|
// without a floor; later scouts and every real Delegate child pass the
|
|
4371
4330
|
// repo floor because they overlap an existing family unit.
|
|
4372
|
-
cost:
|
|
4331
|
+
cost: processingAttemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input, opts.deepScan && idx > 0), this.routeBillingKnowledge(input, adapter.id, routed), routed.processing),
|
|
4373
4332
|
});
|
|
4374
4333
|
if (!lease.granted) {
|
|
4375
4334
|
log.emit("budget.lease.created", {
|
|
@@ -4398,6 +4357,10 @@ export class Orchestrator {
|
|
|
4398
4357
|
}
|
|
4399
4358
|
return { status: "budget_denied", reason: lease.reason ?? "budget lease denied" };
|
|
4400
4359
|
}
|
|
4360
|
+
const processingAdmission = processingAdmissionForLease(ledger, lease.lease, adapter.id, attemptId, (denial) => {
|
|
4361
|
+
budgetStopped = true;
|
|
4362
|
+
budgetDenial ??= denial;
|
|
4363
|
+
});
|
|
4401
4364
|
// As with planners, the granted lease owns profile/continuity/spec
|
|
4402
4365
|
// preparation. Contain a pre-stream rejection as this attempt's failure;
|
|
4403
4366
|
// parallel siblings can then finish before the shared HOME is disposed.
|
|
@@ -4418,17 +4381,11 @@ export class Orchestrator {
|
|
|
4418
4381
|
// scouts are excluded from laneRun); native resume is available only when
|
|
4419
4382
|
// this slot was granted the lane's recorded session.
|
|
4420
4383
|
const laneContinuity = laneRun
|
|
4421
|
-
? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, grantResume, sessionFields, store, paths, this.execRootOf(input), log)
|
|
4384
|
+
? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, grantResume, sessionFields, store, paths, this.execRootOf(input), log, this.harnessSpecKnobs(contract, knobs, opts.intent), processingAdmission, () => ledger.markPhysicalDispatchStarted(lease.lease.lease_id))
|
|
4422
4385
|
: null;
|
|
4423
4386
|
// D-16d: the continuation packet pointer rides after the lane pointer so
|
|
4424
4387
|
// the fresh session is re-grounded in the exhausted attempt's work.
|
|
4425
|
-
const promptWithPointers =
|
|
4426
|
-
explorerPrompt,
|
|
4427
|
-
laneContinuity?.pointerLine,
|
|
4428
|
-
continuationPointer,
|
|
4429
|
-
]
|
|
4430
|
-
.filter((p) => Boolean(p))
|
|
4431
|
-
.join("\n\n");
|
|
4388
|
+
const promptWithPointers = renderPromptWithPointers(explorerPrompt, laneContinuity?.pointerLine, continuationPointer);
|
|
4432
4389
|
const spec = HarnessRunSpec.parse({
|
|
4433
4390
|
session_id: newId("ses"),
|
|
4434
4391
|
intent: opts.intent,
|
|
@@ -4451,6 +4408,7 @@ export class Orchestrator {
|
|
|
4451
4408
|
? this.laneHomeEnvFor(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null)
|
|
4452
4409
|
: null) ?? roHome.env,
|
|
4453
4410
|
});
|
|
4411
|
+
this.credentials.stampProfileSelection(spec, input, adapter.id);
|
|
4454
4412
|
const reportAbort = new AbortController();
|
|
4455
4413
|
spec.extra["abortSignal"] = input.signal
|
|
4456
4414
|
? AbortSignal.any([input.signal, reportAbort.signal])
|
|
@@ -4460,7 +4418,7 @@ export class Orchestrator {
|
|
|
4460
4418
|
spec.extra["interactionChannel"] = reportInteraction;
|
|
4461
4419
|
// D-16: compile the WorkReport envelope for the read-only lane.
|
|
4462
4420
|
const readonlyWorkEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(reportInteraction));
|
|
4463
|
-
const readonlyWorkMode =
|
|
4421
|
+
const readonlyWorkMode = applyWorkEnvelope(spec, readonlyWorkEnvelope);
|
|
4464
4422
|
const attemptEventsPath = join(paths.attemptsDir, attemptId, "events.jsonl");
|
|
4465
4423
|
const answer = new AnswerAssembly();
|
|
4466
4424
|
const telemetry = createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [],
|
|
@@ -4482,7 +4440,7 @@ export class Orchestrator {
|
|
|
4482
4440
|
const message = `read-only attempt setup failed: ${safeErrorMessage(preparation.error)}`;
|
|
4483
4441
|
AC.settleGrantedAttemptLease({
|
|
4484
4442
|
ledger,
|
|
4485
|
-
leaseId: lease.lease
|
|
4443
|
+
leaseId: lease.lease.lease_id,
|
|
4486
4444
|
attemptId,
|
|
4487
4445
|
harnessId: adapter.id,
|
|
4488
4446
|
costUsd: 0,
|
|
@@ -4515,10 +4473,7 @@ export class Orchestrator {
|
|
|
4515
4473
|
const { knobs, spec: preparedSpec, reportAbort, reportInteraction, readonlyWorkMode, attemptEventsPath, telemetry, } = preparation.value;
|
|
4516
4474
|
let { answer } = preparation.value;
|
|
4517
4475
|
let spec = preparedSpec;
|
|
4518
|
-
bindProcessingAdmission(spec, ledger, lease.lease
|
|
4519
|
-
budgetStopped = true;
|
|
4520
|
-
budgetDenial ??= denial;
|
|
4521
|
-
});
|
|
4476
|
+
bindProcessingAdmission(spec, ledger, lease.lease, adapter.id, attemptId, undefined, processingAdmission);
|
|
4522
4477
|
const retryPolicy = transientRetryPolicy(this.config(input.repoRoot));
|
|
4523
4478
|
let activeSessionId = spec.session_id;
|
|
4524
4479
|
const live = liveAttempt(input, routed, paths, contract, attemptId, () => activeSessionId);
|
|
@@ -4553,6 +4508,8 @@ export class Orchestrator {
|
|
|
4553
4508
|
extra: { ...spec.extra },
|
|
4554
4509
|
});
|
|
4555
4510
|
activeSessionId = runSpec.session_id;
|
|
4511
|
+
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
4512
|
+
const refusal = this.credentials.bindPreProgressRefusal(adapter.id, runSpec);
|
|
4556
4513
|
const transientStart = telemetry.transientFailures.length;
|
|
4557
4514
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
4558
4515
|
log.emit("harness.started", {
|
|
@@ -4618,7 +4575,8 @@ export class Orchestrator {
|
|
|
4618
4575
|
}
|
|
4619
4576
|
catch (err) {
|
|
4620
4577
|
harnessError = safeErrorMessage(err);
|
|
4621
|
-
if (err instanceof ProcessingBudgetAdmissionError
|
|
4578
|
+
if (err instanceof ProcessingBudgetAdmissionError ||
|
|
4579
|
+
err instanceof RoutingPreflightError) {
|
|
4622
4580
|
processingRefusal = err;
|
|
4623
4581
|
break;
|
|
4624
4582
|
}
|
|
@@ -4628,10 +4586,14 @@ export class Orchestrator {
|
|
|
4628
4586
|
}
|
|
4629
4587
|
if (streamBudgetDenied)
|
|
4630
4588
|
break;
|
|
4589
|
+
if (telemetry.requestRefusal)
|
|
4590
|
+
break;
|
|
4631
4591
|
const newTransients = telemetry.transientFailures.slice(transientStart);
|
|
4632
4592
|
const sawRetryable = newTransients.some((f) => f.retryable);
|
|
4633
4593
|
const sawTypedLimit = telemetry.rateLimits.length > rateLimitStart;
|
|
4634
4594
|
const reportSoFar = answer.text();
|
|
4595
|
+
const delivered = !harnessError && reportSoFar.length > 0 && !input.signal?.aborted;
|
|
4596
|
+
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
4635
4597
|
// W5.4 + A2 reactive failover, READ-ONLY lane (same contract as the
|
|
4636
4598
|
// candidate lane: typed limit or structural pre-progress death).
|
|
4637
4599
|
if (harnessError && !input.signal?.aborted) {
|
|
@@ -4643,9 +4605,10 @@ export class Orchestrator {
|
|
|
4643
4605
|
registry: this.config(input.repoRoot)?.global.credential_profiles ?? [],
|
|
4644
4606
|
snapshots: this.deps.quotaSnapshots?.() ?? [],
|
|
4645
4607
|
probeReadyProfiles: () => this.credentials.readyProfileIdsForRotation(input, adapter.id, spec.credential_profile ?? null, triedProfiles, spec.model_hint ?? null),
|
|
4646
|
-
...this.credentials.rotationObservations(adapter, spec, newTransients),
|
|
4608
|
+
...this.credentials.rotationObservations(adapter, spec, newTransients, refusal),
|
|
4647
4609
|
triedProfiles,
|
|
4648
4610
|
markers: telemetry.outputMarkers,
|
|
4611
|
+
requestRefused: Boolean(telemetry.requestRefusal),
|
|
4649
4612
|
sawTypedLimit,
|
|
4650
4613
|
sawRetryable,
|
|
4651
4614
|
attemptErrored: harnessError !== null,
|
|
@@ -4694,7 +4657,7 @@ export class Orchestrator {
|
|
|
4694
4657
|
input.signal?.removeEventListener("abort", onAbort);
|
|
4695
4658
|
AC.settleGrantedAttemptLease({
|
|
4696
4659
|
ledger,
|
|
4697
|
-
leaseId: lease.lease
|
|
4660
|
+
leaseId: lease.lease.lease_id,
|
|
4698
4661
|
attemptId,
|
|
4699
4662
|
harnessId: adapter.id,
|
|
4700
4663
|
costUsd: cost,
|
|
@@ -4704,7 +4667,7 @@ export class Orchestrator {
|
|
|
4704
4667
|
preStreamFailureSource: "readonly-pre-stream",
|
|
4705
4668
|
});
|
|
4706
4669
|
}
|
|
4707
|
-
if (harnessError && !poolExhausted && !processingRefusal) {
|
|
4670
|
+
if (harnessError && !poolExhausted && !processingRefusal && !telemetry.requestRefusal) {
|
|
4708
4671
|
emitTransientExhausted((t, p) => log.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
|
|
4709
4672
|
}
|
|
4710
4673
|
attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
|
|
@@ -4765,9 +4728,11 @@ export class Orchestrator {
|
|
|
4765
4728
|
report,
|
|
4766
4729
|
error: harnessError,
|
|
4767
4730
|
telemetry,
|
|
4768
|
-
...(
|
|
4769
|
-
? { declaredFailure:
|
|
4770
|
-
:
|
|
4731
|
+
...(telemetry.requestRefusal
|
|
4732
|
+
? { declaredFailure: requestRefusalFailure(telemetry.requestRefusal) }
|
|
4733
|
+
: poolExhausted || processingRefusal
|
|
4734
|
+
? { declaredFailure: declaredFailure(processingRefusal ?? poolExhausted) }
|
|
4735
|
+
: {}),
|
|
4771
4736
|
});
|
|
4772
4737
|
if (opts.deepScan) {
|
|
4773
4738
|
store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
|
|
@@ -4882,6 +4847,7 @@ export class Orchestrator {
|
|
|
4882
4847
|
const firstModel = contract.routing_models[routed.adapter.id] ?? routed.settings?.defaultModel ?? null;
|
|
4883
4848
|
if (last &&
|
|
4884
4849
|
last.status === "failed" &&
|
|
4850
|
+
!last.telemetry.requestRefusal &&
|
|
4885
4851
|
fallbackModel &&
|
|
4886
4852
|
fallbackModel !== firstModel &&
|
|
4887
4853
|
!budgetStopped &&
|
|
@@ -5008,6 +4974,7 @@ export class Orchestrator {
|
|
|
5008
4974
|
eventRefs: roEventRefs,
|
|
5009
4975
|
runDir: paths.root,
|
|
5010
4976
|
resetsAt: roDeclared?.resetsAt ?? null,
|
|
4977
|
+
requestRefusal: roDeclared?.requestRefusal,
|
|
5011
4978
|
vendorFailure: attemptVendorFailure(attemptTelemetries, last?.attemptId),
|
|
5012
4979
|
nextActions: harnessFailureNextActions(roCategory),
|
|
5013
4980
|
});
|
|
@@ -5068,10 +5035,7 @@ export class Orchestrator {
|
|
|
5068
5035
|
const succeeded = succeededReadonly;
|
|
5069
5036
|
if (opts.deepScan && succeeded.length === 0) {
|
|
5070
5037
|
const blocked = attempts.some((a) => a.status === "blocked");
|
|
5071
|
-
// QA-050/QA-019:
|
|
5072
|
-
// before spawn) is a BUDGET failure, not harness_error — route it through
|
|
5073
|
-
// the shared classifier. Only a pure-denial scan (no scout actually errored
|
|
5074
|
-
// in the harness) qualifies, so a real explorer failure is never masked.
|
|
5038
|
+
// QA-050/QA-019: only a pure-denial scan may claim a budget failure.
|
|
5075
5039
|
const scanBudgetMapping = budgetStopped && !blocked && attempts.every((a) => a.budgetDenied === true)
|
|
5076
5040
|
? classifyBudgetFailure({ denial: budgetDenial, terminal: ledger.terminal() })
|
|
5077
5041
|
: null;
|
|
@@ -5093,12 +5057,15 @@ export class Orchestrator {
|
|
|
5093
5057
|
}));
|
|
5094
5058
|
}
|
|
5095
5059
|
else {
|
|
5096
|
-
//
|
|
5097
|
-
|
|
5060
|
+
// Mixed causes stay generic; only unanimous evidence may name the scan's cause.
|
|
5061
|
+
const declared = blocked ? null : unanimousDeclaredFailure(attempts);
|
|
5098
5062
|
const scanCategory = dominantHarnessFailureCategory(attemptTelemetries.flatMap((a) => a.telemetry.transientFailures));
|
|
5099
5063
|
writeFailure(store, paths, {
|
|
5100
5064
|
phase: "harness",
|
|
5101
|
-
category: blocked ? "policy" : "harness_error",
|
|
5065
|
+
category: blocked ? "policy" : (declared?.category ?? "harness_error"),
|
|
5066
|
+
code: declared?.code,
|
|
5067
|
+
resetsAt: declared?.resetsAt,
|
|
5068
|
+
requestRefusal: declared?.requestRefusal,
|
|
5102
5069
|
safeMessage: message || "all explorers failed",
|
|
5103
5070
|
eventRefs: attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`),
|
|
5104
5071
|
runDir: paths.root,
|
|
@@ -5109,10 +5076,7 @@ export class Orchestrator {
|
|
|
5109
5076
|
],
|
|
5110
5077
|
});
|
|
5111
5078
|
}
|
|
5112
|
-
// QA-036:
|
|
5113
|
-
// deliverable, so a blocked scan can no longer read as a succeeded
|
|
5114
|
-
// "needs review" run (exit 0). An empty scan is a failure whether the
|
|
5115
|
-
// explorers were blocked or errored.
|
|
5079
|
+
// QA-036: no successful explorer means no synthesizable deliverable.
|
|
5116
5080
|
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Lifecycle: ${scanFailFacts.lifecycle}${scanFailFacts.reason ? ` (${scanFailFacts.reason})` : ""}\n\n${message}\n`);
|
|
5117
5081
|
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
5118
5082
|
log.emit("run.failed", {
|