@claudexor/orchestrator 3.10.4 → 3.11.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/attemptTelemetry.d.ts +2 -1
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +11 -0
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/attemptUsage.d.ts.map +1 -1
- package/dist/attemptUsage.js +2 -1
- package/dist/attemptUsage.js.map +1 -1
- package/dist/attemptUsageCost.d.ts +1 -1
- package/dist/attemptUsageCost.d.ts.map +1 -1
- package/dist/attemptUsageCost.js +19 -110
- package/dist/attemptUsageCost.js.map +1 -1
- package/dist/candidateEvidence.d.ts +8 -0
- package/dist/candidateEvidence.d.ts.map +1 -1
- package/dist/candidateEvidence.js +22 -4
- package/dist/candidateEvidence.js.map +1 -1
- package/dist/candidateOutputs.d.ts +6 -0
- package/dist/candidateOutputs.d.ts.map +1 -1
- package/dist/candidateOutputs.js +6 -1
- package/dist/candidateOutputs.js.map +1 -1
- package/dist/candidateReview.d.ts +2 -0
- package/dist/candidateReview.d.ts.map +1 -1
- package/dist/candidateReview.js +27 -3
- package/dist/candidateReview.js.map +1 -1
- package/dist/candidateTerminals.d.ts +34 -0
- package/dist/candidateTerminals.d.ts.map +1 -0
- package/dist/candidateTerminals.js +183 -0
- package/dist/candidateTerminals.js.map +1 -0
- package/dist/continuity-facts.d.ts +4 -1
- package/dist/continuity-facts.d.ts.map +1 -1
- package/dist/continuity-facts.js +3 -0
- package/dist/continuity-facts.js.map +1 -1
- package/dist/continuity-summary.d.ts +7 -2
- package/dist/continuity-summary.d.ts.map +1 -1
- package/dist/continuity-summary.js +19 -2
- package/dist/continuity-summary.js.map +1 -1
- package/dist/deepScanOutput.d.ts +19 -0
- package/dist/deepScanOutput.d.ts.map +1 -0
- package/dist/deepScanOutput.js +38 -0
- package/dist/deepScanOutput.js.map +1 -0
- package/dist/deepScanReducer.d.ts +2 -17
- package/dist/deepScanReducer.d.ts.map +1 -1
- package/dist/deepScanReducer.js +19 -40
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/delegationBelt.d.ts +9 -0
- package/dist/delegationBelt.d.ts.map +1 -0
- package/dist/delegationBelt.js +54 -0
- package/dist/delegationBelt.js.map +1 -0
- package/dist/delegationBudgetAuthority.d.ts +1 -1
- package/dist/delegationBudgetAuthority.d.ts.map +1 -1
- package/dist/delegationBudgetAuthority.js.map +1 -1
- package/dist/directoryCandidate.d.ts +75 -0
- package/dist/directoryCandidate.d.ts.map +1 -0
- package/dist/directoryCandidate.js +171 -0
- package/dist/directoryCandidate.js.map +1 -0
- package/dist/git-precondition.d.ts +2 -0
- package/dist/git-precondition.d.ts.map +1 -1
- package/dist/git-precondition.js +20 -1
- package/dist/git-precondition.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 +2 -1
- package/dist/modelGovernance.d.ts.map +1 -1
- package/dist/modelGovernance.js +33 -2
- package/dist/modelGovernance.js.map +1 -1
- package/dist/orchestrator.d.ts +8 -33
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +468 -513
- package/dist/orchestrator.js.map +1 -1
- package/dist/plannerAttempt.d.ts +1 -1
- package/dist/plannerAttempt.d.ts.map +1 -1
- package/dist/plannerAttempt.js +17 -2
- package/dist/plannerAttempt.js.map +1 -1
- package/dist/policyFindings.d.ts +2 -0
- package/dist/policyFindings.d.ts.map +1 -1
- package/dist/policyFindings.js +11 -1
- package/dist/policyFindings.js.map +1 -1
- package/dist/processing-dispatch.d.ts +25 -0
- package/dist/processing-dispatch.d.ts.map +1 -0
- package/dist/processing-dispatch.js +122 -0
- package/dist/processing-dispatch.js.map +1 -0
- package/dist/processing-routing.d.ts +9 -0
- package/dist/processing-routing.d.ts.map +1 -0
- package/dist/processing-routing.js +86 -0
- package/dist/processing-routing.js.map +1 -0
- package/dist/processing-telemetry.d.ts +11 -0
- package/dist/processing-telemetry.d.ts.map +1 -0
- package/dist/processing-telemetry.js +31 -0
- package/dist/processing-telemetry.js.map +1 -0
- package/dist/reviewerPanel.d.ts.map +1 -1
- package/dist/reviewerPanel.js +12 -9
- package/dist/reviewerPanel.js.map +1 -1
- package/dist/root-ledger.d.ts.map +1 -1
- package/dist/root-ledger.js +2 -1
- package/dist/root-ledger.js.map +1 -1
- package/dist/runFactsBuilder.d.ts.map +1 -1
- package/dist/runFactsBuilder.js +23 -10
- package/dist/runFactsBuilder.js.map +1 -1
- package/dist/runFactsDeliverable.d.ts.map +1 -1
- package/dist/runFactsDeliverable.js +14 -1
- package/dist/runFactsDeliverable.js.map +1 -1
- package/dist/runFactsPresentation.d.ts.map +1 -1
- package/dist/runFactsPresentation.js +1 -0
- package/dist/runFactsPresentation.js.map +1 -1
- package/dist/runSupport.d.ts +1 -1
- package/dist/runTerminalResults.d.ts.map +1 -1
- package/dist/runTerminalResults.js +14 -6
- package/dist/runTerminalResults.js.map +1 -1
- package/dist/task-contract-builder.d.ts +2 -1
- package/dist/task-contract-builder.d.ts.map +1 -1
- package/dist/task-contract-builder.js +1 -0
- package/dist/task-contract-builder.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { delegationBeltFor } from "./delegationBelt.js";
|
|
2
|
+
import { bindProcessingAdmission, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
|
|
3
|
+
import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, } from "./candidateTerminals.js";
|
|
1
4
|
import { existsSync } from "node:fs";
|
|
2
5
|
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, rotateSpecOnTypedLimit, selectedProfileAvailability, } from "./credential-profiles.js";
|
|
3
6
|
import { OrchestratorCredentials, reviewerProfileResolver, rotatedSpecInLaneHome, } from "./orchestrator-credentials.js";
|
|
@@ -5,17 +8,18 @@ import { accountPoolRows } from "./account-pool.js";
|
|
|
5
8
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
6
9
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
7
10
|
import { processAttemptUsage } from "./attemptUsage.js";
|
|
11
|
+
import { captureDirectoryCandidate, directoryHasOutput, directoryCandidateStable, observeDirectoryPaths, publishDirectoryCandidate, } from "./directoryCandidate.js";
|
|
8
12
|
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, } from "./delegatedHome.js";
|
|
9
13
|
import * as AC from "./attemptUsageCost.js";
|
|
10
|
-
import { candidateRoster, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence,
|
|
14
|
+
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
|
|
11
15
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
12
|
-
import { policyFindings } from "./policyFindings.js";
|
|
13
16
|
import { reviewCandidateRuns, resolveEngineReview, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
|
|
14
17
|
import { join } from "node:path";
|
|
15
18
|
import { finalizePlanRun, runCouncilPlan, writePlanHarnessFailure, } from "./planRun.js";
|
|
16
19
|
import { runPlannerAttempt as executePlannerAttempt, } from "./plannerAttempt.js";
|
|
17
20
|
import { HarnessRunSpec, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, reviewAllowsApply, workStateVetoes, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
18
21
|
import { globalConfigDir, loadConfig } from "@claudexor/config";
|
|
22
|
+
import { prepareRoutedProcessing, processingCostEvidence, prepareReviewerProcessing, } from "./processing-routing.js";
|
|
19
23
|
import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
20
24
|
import { assertRouteModelsAllowed, runModelGovernedRoute } from "./modelGovernance.js";
|
|
21
25
|
import { authModeForCredentialRoute, authModeForPreference } from "./auth-route-classification.js";
|
|
@@ -29,7 +33,7 @@ import { planPrompt } from "./plan-prompt.js";
|
|
|
29
33
|
import { verifiedPlanBrief, withPlanBrief } from "./planBrief.js";
|
|
30
34
|
import { resolveRunInputDefaults } from "./run-input-resolution.js";
|
|
31
35
|
import { beginAnnouncedRun } from "./runEventLog.js";
|
|
32
|
-
import { arbitrationBudgetOptions
|
|
36
|
+
import { arbitrationBudgetOptions } from "./decisionBudget.js";
|
|
33
37
|
import { buildRevisePrompt } from "./revisePrompt.js";
|
|
34
38
|
import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure, cancelReasonFromSignalToken, } from "./runTerminals.js";
|
|
35
39
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
@@ -39,7 +43,7 @@ import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted, emitTran
|
|
|
39
43
|
import { promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPrimaryDivergence, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
|
|
40
44
|
import { candidateStatusInRouteContext, resolveReadOnlyRouteContext, } from "./routeContext.js";
|
|
41
45
|
import { resolveAutoReviewerPanel, resolveExplicitReviewerPanel } from "./reviewerPanel.js";
|
|
42
|
-
import { ensureWriteModeGitBoundary } from "./git-precondition.js";
|
|
46
|
+
import { ensureClaudeBridgeForRun, ensureWriteModeGitBoundary } from "./git-precondition.js";
|
|
43
47
|
import { buildContinuation, } from "./continuity.js";
|
|
44
48
|
import { activePlanPointer, resolveContinuitySummary, workspaceAnchor, } from "./continuity-facts.js";
|
|
45
49
|
import { runDiffReview } from "./diffReview.js";
|
|
@@ -56,14 +60,14 @@ import { gateSpecsFromContract, renderTestsEvidence } from "./contract-gates.js"
|
|
|
56
60
|
import { buildTaskContract } from "./task-contract-builder.js";
|
|
57
61
|
import { ArtifactStore } from "@claudexor/artifact-store";
|
|
58
62
|
import { buildContextPack, rawContextForEnvelope, preflightEvidence, writeEvidencePacket, } from "@claudexor/context";
|
|
59
|
-
import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull,
|
|
60
|
-
import { blockedDecisionOverride, finalVerifyBlocks, finalVerifyPatch, verifyAndDeliver, } from "@claudexor/delivery";
|
|
63
|
+
import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
|
|
64
|
+
import { blockedDecisionOverride, finalVerifyBlocks, finalVerifyPatch, finalVerifyFiles, verifyAndDeliverFiles, verifyAndDeliver, } from "@claudexor/delivery";
|
|
61
65
|
import { HarnessGateway } from "@claudexor/gateway";
|
|
62
|
-
import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate,
|
|
66
|
+
import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate, runGates, } from "@claudexor/review";
|
|
63
67
|
import { arbitrate } from "@claudexor/arbitration";
|
|
64
68
|
import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
|
|
65
|
-
import { attemptCostEvidence, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses,
|
|
66
|
-
import { readTextSafe, appendLine, assertNoInlineSecretValues,
|
|
69
|
+
import { attemptCostEvidence, billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, } from "@claudexor/budget";
|
|
70
|
+
import { readTextSafe, appendLine, assertNoInlineSecretValues, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, sha256, userConfigDir, } from "@claudexor/util";
|
|
67
71
|
const LABELS = "ABCDEFGHIJ".split("");
|
|
68
72
|
const NO_PROJECT_ROOT = noProjectRepoRoot();
|
|
69
73
|
/** Concurrency cap for parallel candidates/explorers (locked decision: min(n, 4)). */
|
|
@@ -228,7 +232,11 @@ export class Orchestrator {
|
|
|
228
232
|
try {
|
|
229
233
|
// Auto-panel dropped knobs (reviewerEfforts) → ignored-settings channel (QA-070):
|
|
230
234
|
const warn = (d) => void log.emit("review.preflight", { ignored_settings: [d] });
|
|
231
|
-
|
|
235
|
+
const config = this.config(input.repoRoot).global;
|
|
236
|
+
const reviewers = await this.resolveReviewers(input.repoRoot, input.authPreference, warn);
|
|
237
|
+
return {
|
|
238
|
+
reviewers: await prepareReviewerProcessing(reviewers, input, config, input.paidBudget ?? this.deps.paidBudget ?? config.budget.paid_budget_per_run),
|
|
239
|
+
};
|
|
232
240
|
}
|
|
233
241
|
catch (err) {
|
|
234
242
|
const message = safeErrorMessage(err);
|
|
@@ -664,6 +672,7 @@ export class Orchestrator {
|
|
|
664
672
|
}),
|
|
665
673
|
effortLevels: manifest.capabilities.effort_levels,
|
|
666
674
|
knownModels: manifest.capabilities.known_models,
|
|
675
|
+
modelInventoryRoutes: manifest.capabilities.model_inventory_routes,
|
|
667
676
|
// A selected profile's credential_kind IS the route (round-18 #2);
|
|
668
677
|
// the default store's sources apply only to profile-less runs.
|
|
669
678
|
authRouteEstimate: this.credentials.profileAuthRoute(input, id) ??
|
|
@@ -751,6 +760,8 @@ export class Orchestrator {
|
|
|
751
760
|
}
|
|
752
761
|
for (const { routed, error } of quotaRefusals)
|
|
753
762
|
dropLane(routed.adapter.id, "credential", safeErrorMessage(error));
|
|
763
|
+
const processingConfig = this.config(input.repoRoot).global;
|
|
764
|
+
await prepareRoutedProcessing(quotaPreparedPool, input, this.execRootOf(input), processingConfig, input.paidBudget ?? this.deps.paidBudget ?? processingConfig.budget.paid_budget_per_run, (id) => (routeContext ? (routeContext.envForHarness?.(id) ?? routeContext.env) : undefined));
|
|
754
765
|
const ordered = this.orderPool(quotaPreparedPool, input, intent, statusById, ledger, runId);
|
|
755
766
|
if (ordered.length === 0) {
|
|
756
767
|
if (primaryQuotaRefusal)
|
|
@@ -873,6 +884,7 @@ export class Orchestrator {
|
|
|
873
884
|
// reading as unknown/paid. Absent (unknown route) falls back to the
|
|
874
885
|
// metric-derived billingKnowledge below.
|
|
875
886
|
const authRoute = this.authRouteEvidenceFor(authMode, status?.authSources ?? []);
|
|
887
|
+
const processingCost = processingCostEvidence(r.processing, authRoute ? billingKnowledgeForAuthRoute(authRoute) : "unknown", [`harness:${r.adapter.id}`, `profile:${credentialSubjectId ?? "default"}`]);
|
|
876
888
|
return {
|
|
877
889
|
harnessId: r.adapter.id,
|
|
878
890
|
available: true,
|
|
@@ -882,7 +894,12 @@ export class Orchestrator {
|
|
|
882
894
|
config.harnesses[r.adapter.id]?.effort ??
|
|
883
895
|
undefined,
|
|
884
896
|
billingKnowledge: authMode === "api_key" ? "metered" : "unknown",
|
|
885
|
-
|
|
897
|
+
costEvidence: processingCost,
|
|
898
|
+
incrementalCostUsd: processingCost
|
|
899
|
+
? processingCost.estimatedUsd
|
|
900
|
+
: authMode === "api_key"
|
|
901
|
+
? (metric?.avg_cost_usd ?? null)
|
|
902
|
+
: null,
|
|
886
903
|
credentialRoute: r.quotaAdmission.route ??
|
|
887
904
|
(authMode === "api_key"
|
|
888
905
|
? "managed_api_key"
|
|
@@ -1068,11 +1085,6 @@ export class Orchestrator {
|
|
|
1068
1085
|
routingGoal: this.deps.routingGoal,
|
|
1069
1086
|
});
|
|
1070
1087
|
}
|
|
1071
|
-
/**
|
|
1072
|
-
* Per-harness settings applied to one route's run spec (model/effort/web
|
|
1073
|
-
* defaults, max_turns, tool lists). Knobs the manifest does not support are
|
|
1074
|
-
* RETURNED as ignored reasons (disclosed by the caller), never silently sent.
|
|
1075
|
-
*/
|
|
1076
1088
|
/**
|
|
1077
1089
|
* The HarnessRunSpec fields every TASK-PRODUCING lane shares (primary,
|
|
1078
1090
|
* candidate, planner, explorer, orchestrate-planner). Extracting the identical
|
|
@@ -1083,43 +1095,6 @@ export class Orchestrator {
|
|
|
1083
1095
|
* execution — owner Quiz-5a); reviewers and the auth smoke build their own
|
|
1084
1096
|
* specs and never call this.
|
|
1085
1097
|
*/
|
|
1086
|
-
/** The extra MCP servers injected into one agent lane's sandbox. Today only
|
|
1087
|
-
* the delegation belt (D32): present when `--delegate` is on, the daemon built
|
|
1088
|
-
* a belt descriptor, the lane's adapter can inject MCP servers, and the lane is
|
|
1089
|
-
* a WRITING agent intent (the delegator integrates results in its workspace;
|
|
1090
|
-
* read lanes and reviewers have nothing to delegate). */
|
|
1091
|
-
delegationBeltFor(input, intent, routed, resolvedBudget) {
|
|
1092
|
-
if (!input?.delegate ||
|
|
1093
|
-
!input.delegationBelt ||
|
|
1094
|
-
!input.delegationParentRunId ||
|
|
1095
|
-
!routed.delegationRequirement.effective)
|
|
1096
|
-
return [];
|
|
1097
|
-
// A lane that sandbox-cancels the belt below full access (codex) must NOT
|
|
1098
|
-
// receive a belt it cannot use. Per-lane requirement resolution records the
|
|
1099
|
-
// typed degradation, while a mixed pool keeps the belt on lanes that can
|
|
1100
|
-
// host it.
|
|
1101
|
-
if (routed.mcpInjectionRequiresFullAccess && !isFullAccess(routed.adapterAccess))
|
|
1102
|
-
return [];
|
|
1103
|
-
const writingIntents = ["implement", "create_from_scratch", "repair"];
|
|
1104
|
-
if (!writingIntents.includes(intent))
|
|
1105
|
-
return [];
|
|
1106
|
-
// The CLI built the descriptor from the RAW request budget (undefined when
|
|
1107
|
-
// the caller relied on a config/dep default), which would leave the belt
|
|
1108
|
-
// unlimited while the real run is capped. Rebind the belt's parent-budget
|
|
1109
|
-
// env to the RESOLVED budget (resolvePaidBudget output) so sub-run draws are
|
|
1110
|
-
// bounded by the same headroom the parent run enforces — one budget owner.
|
|
1111
|
-
return [
|
|
1112
|
-
{
|
|
1113
|
-
...input.delegationBelt,
|
|
1114
|
-
env: {
|
|
1115
|
-
...input.delegationBelt.env,
|
|
1116
|
-
[DELEGATION_ENV.parentRunId]: input.delegationParentRunId,
|
|
1117
|
-
[DELEGATION_ENV.repoRoot]: input.repoRoot,
|
|
1118
|
-
[DELEGATION_ENV.budget]: JSON.stringify(resolvedBudget),
|
|
1119
|
-
},
|
|
1120
|
-
},
|
|
1121
|
-
];
|
|
1122
|
-
}
|
|
1123
1098
|
harnessSpecKnobs(contract, knobs, intent) {
|
|
1124
1099
|
return {
|
|
1125
1100
|
external_context_policy: knobs.webPolicy,
|
|
@@ -1130,6 +1105,10 @@ export class Orchestrator {
|
|
|
1130
1105
|
},
|
|
1131
1106
|
model_hint: knobs.model,
|
|
1132
1107
|
effort_hint: knobs.effort,
|
|
1108
|
+
processing_preference: contract.processing_preference,
|
|
1109
|
+
processing: knobs.processing?.receipt,
|
|
1110
|
+
processing_cost_basis: knobs.processing?.costBasis,
|
|
1111
|
+
processing_allow_paid: knobs.processingAllowPaid,
|
|
1133
1112
|
max_turns: knobs.maxTurns,
|
|
1134
1113
|
...(intent === "synthesize" ? {} : { instructions: contract.instructions }),
|
|
1135
1114
|
// The user's answer contract rides every answer-producing lane INCLUDING
|
|
@@ -1221,6 +1200,8 @@ export class Orchestrator {
|
|
|
1221
1200
|
return {
|
|
1222
1201
|
model,
|
|
1223
1202
|
effort,
|
|
1203
|
+
processing: routed.processing,
|
|
1204
|
+
processingAllowPaid: routed.processingAllowPaid,
|
|
1224
1205
|
webPolicy,
|
|
1225
1206
|
maxTurns,
|
|
1226
1207
|
toolsAllow,
|
|
@@ -1237,7 +1218,7 @@ export class Orchestrator {
|
|
|
1237
1218
|
* Returns null (no packet, no pointer) for native resume, a fresh thread, or
|
|
1238
1219
|
* a non-thread run. Never throws: continuity failure degrades to no packet.
|
|
1239
1220
|
*/
|
|
1240
|
-
async resolveContinuity(runInput, harnessId, resolvedProfileId, nativeResumeAvailable, sessionFields, store, paths, repoRoot, log) {
|
|
1221
|
+
async resolveContinuity(runInput, harnessId, resolvedProfileId, nativeResumeAvailable, sessionFields, store, paths, repoRoot, log, processing, processingAdmission, physicalDispatchStarted) {
|
|
1241
1222
|
const ctx = runInput.threadContinuity;
|
|
1242
1223
|
if (!runInput.threadId || !ctx)
|
|
1243
1224
|
return null;
|
|
@@ -1286,6 +1267,9 @@ export class Orchestrator {
|
|
|
1286
1267
|
laneEnv: this.laneHomeEnvFor(runInput, harnessId, profileId) ?? {},
|
|
1287
1268
|
envInheritance: envInheritance(this.config(runInput.repoRoot)),
|
|
1288
1269
|
signal: runInput.signal,
|
|
1270
|
+
processing,
|
|
1271
|
+
processingAdmission,
|
|
1272
|
+
physicalDispatchStarted,
|
|
1289
1273
|
});
|
|
1290
1274
|
const result = buildContinuation(req);
|
|
1291
1275
|
// Disclose on every lane and stamp the turn (INV-137: never silent).
|
|
@@ -1343,7 +1327,7 @@ export class Orchestrator {
|
|
|
1343
1327
|
// Refuses (never silently degrades) when a delegated run cannot be confined.
|
|
1344
1328
|
return scopedHarnessHome(wsm, envelope, envelope.worktree_path === envelope.repo_root, runInput?.delegated === true, routed.adapterAccess);
|
|
1345
1329
|
}
|
|
1346
|
-
async runCandidateInEnvelope(routed, envelope, attemptId, label, contract, prompt, store, paths, wsm, ledger, access, onHarnessEvent, signal, modelHint, effortHint, intent, log, effectiveWebMode, interaction, budgetGuard, runInput, streamDeltas, fileBackedContext,
|
|
1330
|
+
async runCandidateInEnvelope(routed, envelope, attemptId, label, contract, prompt, store, paths, wsm, ledger, processingLease, access, onHarnessEvent, signal, modelHint, effortHint, intent, log, effectiveWebMode, interaction, budgetGuard, runInput, streamDeltas, fileBackedContext,
|
|
1347
1331
|
/** D-16d: when set, the mechanical continuation checkpoint pointer for a
|
|
1348
1332
|
* one-shot fresh-session continuation — appended to the prompt so the model
|
|
1349
1333
|
* (and the offline fake) re-grounds in the exhausted attempt's partial work. */
|
|
@@ -1353,21 +1337,33 @@ export class Orchestrator {
|
|
|
1353
1337
|
* must record what it ran under, not just why it stopped. REQUIRED (no
|
|
1354
1338
|
* default): a silent fallback here would spawn a delegated attempt on the
|
|
1355
1339
|
* operator's real home while the record still claimed scoped state. */
|
|
1356
|
-
harnessHome) {
|
|
1340
|
+
harnessHome, observedPaths = new Set()) {
|
|
1357
1341
|
const adapter = routed.adapter;
|
|
1358
1342
|
const knobs = this.routeSpecKnobs(routed, contract, modelHint, effortHint);
|
|
1359
1343
|
// Isolated scoped-home sessions are never retained after disposal.
|
|
1360
1344
|
const inPlaceEnvelope = envelope.worktree_path === envelope.repo_root;
|
|
1345
|
+
const directory = envelope.workspace_kind === "directory";
|
|
1346
|
+
let directoryCapture = {};
|
|
1347
|
+
const captureDirectory = async () => (directoryCapture = await captureDirectoryCandidate({
|
|
1348
|
+
manager: wsm,
|
|
1349
|
+
envelope,
|
|
1350
|
+
artifactRoot: join(paths.attemptsDir, attemptId),
|
|
1351
|
+
sourceRoot: contract.repo.root,
|
|
1352
|
+
observedPaths: [...observedPaths],
|
|
1353
|
+
}));
|
|
1361
1354
|
const rawContextPacket = await rawContextForEnvelope(routed.implementationTransport, envelope);
|
|
1362
1355
|
const sessionFields = runInput
|
|
1363
1356
|
? await this.sessionSpecFields(runInput, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission)
|
|
1364
1357
|
: undefined;
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1358
|
+
const processingAdmission = processingAdmissionForLease(ledger, processingLease.id, adapter.id, attemptId, processingLease.onDenied);
|
|
1359
|
+
const capturedProcessing = {
|
|
1360
|
+
processing_preference: contract.processing_preference,
|
|
1361
|
+
processing: knobs.processing?.receipt,
|
|
1362
|
+
processing_cost_basis: knobs.processing?.costBasis,
|
|
1363
|
+
processing_allow_paid: knobs.processingAllowPaid,
|
|
1364
|
+
};
|
|
1369
1365
|
const laneContinuity = runInput
|
|
1370
|
-
? 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)
|
|
1366
|
+
? 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.id))
|
|
1371
1367
|
: null;
|
|
1372
1368
|
const artifactRelativeDir = routed.browserRequirement.effective
|
|
1373
1369
|
? (wsm.ensureArtifactDirectory(envelope), wsm.ownedArtifactRelativeDirectory(envelope))
|
|
@@ -1392,7 +1388,7 @@ export class Orchestrator {
|
|
|
1392
1388
|
artifactRelativeDir === null
|
|
1393
1389
|
? ""
|
|
1394
1390
|
: join(envelope.worktree_path, artifactRelativeDir, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR)),
|
|
1395
|
-
extra_mcp_servers:
|
|
1391
|
+
extra_mcp_servers: delegationBeltFor(runInput, intent, routed, contract.budget.paid_budget),
|
|
1396
1392
|
cwd: envelope.worktree_path,
|
|
1397
1393
|
access: routed.adapterAccess,
|
|
1398
1394
|
...this.harnessSpecKnobs(contract, knobs, intent),
|
|
@@ -1406,18 +1402,22 @@ export class Orchestrator {
|
|
|
1406
1402
|
...(inPlaceEnvelope && sessionFields?.resume_session_id
|
|
1407
1403
|
? { resume_session_id: sessionFields.resume_session_id }
|
|
1408
1404
|
: {}),
|
|
1409
|
-
// Scoped harness home for isolated envelopes AND for every delegated run;
|
|
1410
|
-
// an ordinary in-place run keeps the native environment so the resumed
|
|
1411
|
-
// vendor session is reachable. See scopedHarnessHome for the cost a
|
|
1412
|
-
// delegated in-place attempt pays for that scoped state.
|
|
1413
1405
|
...(harnessHome.env ? { env: harnessHome.env } : {}),
|
|
1414
1406
|
raw_context_packet: rawContextPacket,
|
|
1415
1407
|
stream_deltas: streamDeltas,
|
|
1416
1408
|
});
|
|
1409
|
+
bindProcessingAdmission(spec, ledger, processingLease.id, adapter.id, attemptId, processingLease.onDenied, processingAdmission);
|
|
1410
|
+
const billingInput = runInput ?? { repoRoot: contract.repo.root };
|
|
1411
|
+
// Keep billing route-bound until admission observes the actual prepared
|
|
1412
|
+
// profile. The resolver is evaluated per physical dispatch; preparedCost
|
|
1413
|
+
// gives an actual profile precedence over this pool fallback.
|
|
1414
|
+
spec.extra["routeBillingKnowledge"] = (actual) => actual.credential_profile?.credential_kind === "api_key"
|
|
1415
|
+
? "metered"
|
|
1416
|
+
: actual.credential_profile
|
|
1417
|
+
? "subscription_entitlement"
|
|
1418
|
+
: this.routeBillingKnowledge(billingInput, adapter.id);
|
|
1417
1419
|
if (interaction)
|
|
1418
1420
|
spec.extra["interactionChannel"] = interaction;
|
|
1419
|
-
// D-16: compile the WorkReport envelope onto the spec (overriding the plain
|
|
1420
|
-
// caller-schema transport) and keep the mode for the answer unwrap.
|
|
1421
1421
|
const workEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(interaction));
|
|
1422
1422
|
const workReportMode = this.applyWorkEnvelope(spec, workEnvelope);
|
|
1423
1423
|
const inactivityMs = harnessInactivityTimeoutMs(this.config(contract.repo.root));
|
|
@@ -1434,6 +1434,7 @@ export class Orchestrator {
|
|
|
1434
1434
|
let cost = 0;
|
|
1435
1435
|
let costEstimated = false;
|
|
1436
1436
|
let harnessErrored = false;
|
|
1437
|
+
let processingRefusal = null;
|
|
1437
1438
|
let poolExhausted = null; // A5: typed pool-exhausted refusal
|
|
1438
1439
|
const deltaFlood = { count: 0, disclosed: false }; // W-C4 per-attempt delta budget
|
|
1439
1440
|
// QA-024: emit the belt-failure disclosure event at most once per attempt.
|
|
@@ -1509,6 +1510,8 @@ export class Orchestrator {
|
|
|
1509
1510
|
if (ev.type === "patch_produced")
|
|
1510
1511
|
continue;
|
|
1511
1512
|
const safeEv = redactHarnessEvent(ev);
|
|
1513
|
+
if (directory)
|
|
1514
|
+
observeDirectoryPaths(observedPaths, safeEv, envelope.worktree_path);
|
|
1512
1515
|
if (dropDeltaPastBudget(safeEv, deltaFlood, Orchestrator.MAX_DELTAS_PER_ATTEMPT, (t, p) => log?.emit(t, p), adapter.id, attemptId))
|
|
1513
1516
|
continue;
|
|
1514
1517
|
safeInvoke(onHarnessEvent, safeEv);
|
|
@@ -1586,6 +1589,10 @@ export class Orchestrator {
|
|
|
1586
1589
|
// gate and required-actions read a typed category, not a bare boolean.
|
|
1587
1590
|
harnessErrored = true;
|
|
1588
1591
|
errors.push(safeErrorMessage(err));
|
|
1592
|
+
if (err instanceof ProcessingBudgetAdmissionError) {
|
|
1593
|
+
processingRefusal = err;
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1589
1596
|
telemetry.transientFailures.push(classifyAdapterThrow({ errorName: err instanceof Error ? err.name : null }));
|
|
1590
1597
|
}
|
|
1591
1598
|
finally {
|
|
@@ -1596,8 +1603,13 @@ export class Orchestrator {
|
|
|
1596
1603
|
// #31: the centralized retry gate reads the classified `retryable` verdict.
|
|
1597
1604
|
const sawRetryable = newTransients.some((f) => f.retryable);
|
|
1598
1605
|
const sawTypedLimit = telemetry.rateLimits.length > rateLimitStart;
|
|
1599
|
-
|
|
1600
|
-
|
|
1606
|
+
if (directory)
|
|
1607
|
+
await captureDirectory();
|
|
1608
|
+
const currentDiff = directory ? "" : await wsm.diff(envelope);
|
|
1609
|
+
const workspaceUnchanged = directory
|
|
1610
|
+
? directoryCapture.files?.noChanges === true
|
|
1611
|
+
: currentDiff.trim().length === 0;
|
|
1612
|
+
const deliverableEmpty = workspaceUnchanged && answer.text().length === 0;
|
|
1601
1613
|
// W5.4 + A2 failover: a typed-limit hit OR a structural pre-progress
|
|
1602
1614
|
// death rebuilds the spec on a NEW session under the next profile.
|
|
1603
1615
|
if (harnessErrored && runInput && !signal?.aborted) {
|
|
@@ -1618,9 +1630,8 @@ export class Orchestrator {
|
|
|
1618
1630
|
// Rotation evidence reads the POLICY-accepted try output: refusal
|
|
1619
1631
|
// prose arriving as mid-stream MESSAGE events (claude org-disabled)
|
|
1620
1632
|
// is no deliverable; the transient gate keeps RAW deliverableEmpty.
|
|
1621
|
-
deliverableEmpty:
|
|
1622
|
-
|
|
1623
|
-
workspaceDiffNonEmpty: currentDiff.trim().length > 0,
|
|
1633
|
+
deliverableEmpty: workspaceUnchanged && acceptedTryOutput(answer, harnessErrored).length === 0,
|
|
1634
|
+
workspaceDiffNonEmpty: !workspaceUnchanged,
|
|
1624
1635
|
lastLimit: telemetry.rateLimits.at(-1) ?? null,
|
|
1625
1636
|
emit: (type, payload) => log?.emit(type, payload),
|
|
1626
1637
|
newSessionId: () => newId("ses"),
|
|
@@ -1663,7 +1674,7 @@ export class Orchestrator {
|
|
|
1663
1674
|
}
|
|
1664
1675
|
// A pool-exhausted terminal is rotation's verdict, not the transient
|
|
1665
1676
|
// machinery's — no `route.transient.exhausted` rides along with it.
|
|
1666
|
-
if (harnessErrored && !poolExhausted) {
|
|
1677
|
+
if (harnessErrored && !poolExhausted && !processingRefusal) {
|
|
1667
1678
|
emitTransientExhausted((t, p) => log?.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
|
|
1668
1679
|
}
|
|
1669
1680
|
const attemptStreamEndedMs = Date.now();
|
|
@@ -1675,10 +1686,14 @@ export class Orchestrator {
|
|
|
1675
1686
|
const unwrapped = unwrapWorkReportEnvelope(acceptedTryOutput(answer, harnessErrored), workReportMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
|
|
1676
1687
|
const redacted = redactSecrets(unwrapped.deliverable);
|
|
1677
1688
|
const candidateAnswer = redacted.trim().length > 0 ? redacted : undefined;
|
|
1678
|
-
|
|
1689
|
+
if (directory)
|
|
1690
|
+
await captureDirectory();
|
|
1691
|
+
const { diff, refusal: secretDiffRefusal } = directory
|
|
1692
|
+
? { diff: "", refusal: directoryCapture.refusal }
|
|
1693
|
+
: await secretDiff.quarantineCandidateWorkspace(wsm, envelope, inPlaceEnvelope, candidateAnswer);
|
|
1679
1694
|
harnessErrored = secretDiff.recordSecretDiffRefusal(secretDiffRefusal, errors, harnessErrored);
|
|
1680
1695
|
const answerText = secretDiffRefusal ? undefined : candidateAnswer;
|
|
1681
|
-
const deliverableEvidence = diff.trim().length > 0 || Boolean(answerText);
|
|
1696
|
+
const deliverableEvidence = diff.trim().length > 0 || Boolean(answerText) || directoryHasOutput(directoryCapture.files);
|
|
1682
1697
|
// Cancelled attempts skip gates: running a 600s-per-gate suite delays the ack
|
|
1683
1698
|
// and burns compute on a result nobody will adopt. Diff/attempt.yaml
|
|
1684
1699
|
// still land, so partial work stays inspectable.
|
|
@@ -1757,8 +1772,8 @@ export class Orchestrator {
|
|
|
1757
1772
|
worktreePath: envelope.worktree_path,
|
|
1758
1773
|
artifactRelativeDir,
|
|
1759
1774
|
diff,
|
|
1760
|
-
persistPatch: secretDiffRefusal === undefined && isMutatingAccess(access),
|
|
1761
|
-
persistProducedMedia: secretDiffRefusal === undefined,
|
|
1775
|
+
persistPatch: !directory && secretDiffRefusal === undefined && isMutatingAccess(access),
|
|
1776
|
+
persistProducedMedia: !directory && secretDiffRefusal === undefined,
|
|
1762
1777
|
answerText,
|
|
1763
1778
|
record: {
|
|
1764
1779
|
attempt_id: attemptId,
|
|
@@ -1773,6 +1788,15 @@ export class Orchestrator {
|
|
|
1773
1788
|
...(secretDiffRefusal ? { secret_diff_refusal: secretDiffRefusal } : {}),
|
|
1774
1789
|
gates: gates.map((g) => ({ id: g.id, status: g.status })),
|
|
1775
1790
|
branch: envelope.branch_name,
|
|
1791
|
+
...(directoryCapture.files
|
|
1792
|
+
? {
|
|
1793
|
+
files_manifest: directoryCapture.files.manifestPath,
|
|
1794
|
+
manifest_sha256: directoryCapture.files.manifestSha256,
|
|
1795
|
+
source_root: contract.repo.root,
|
|
1796
|
+
execution_root: envelope.worktree_path,
|
|
1797
|
+
no_changes: directoryCapture.files.noChanges,
|
|
1798
|
+
}
|
|
1799
|
+
: {}),
|
|
1776
1800
|
// Applied facts, not promises: historical proofs stay readable and
|
|
1777
1801
|
// current delegated runs state deliberate outer-boundary absence.
|
|
1778
1802
|
// Built by the SAME shape the failure path writes.
|
|
@@ -1788,6 +1812,7 @@ export class Orchestrator {
|
|
|
1788
1812
|
harnessId: adapter.id,
|
|
1789
1813
|
label,
|
|
1790
1814
|
diff,
|
|
1815
|
+
...(directoryCapture.files ? { files: directoryCapture.files } : {}),
|
|
1791
1816
|
answerText,
|
|
1792
1817
|
reviewCwd: envelope.worktree_path,
|
|
1793
1818
|
baseSha: envelope.base_sha ?? undefined,
|
|
@@ -1800,7 +1825,9 @@ export class Orchestrator {
|
|
|
1800
1825
|
telemetry,
|
|
1801
1826
|
...(secretDiffRefusal ? { secretDiffRefusal } : {}),
|
|
1802
1827
|
// A5: the typed refusal survives NORMAL attempt finalization (no throw).
|
|
1803
|
-
...(poolExhausted
|
|
1828
|
+
...(poolExhausted || processingRefusal
|
|
1829
|
+
? { declaredFailure: declaredFailure(processingRefusal ?? poolExhausted) }
|
|
1830
|
+
: {}),
|
|
1804
1831
|
outcomeClass: finalized.outcomeClass,
|
|
1805
1832
|
applied,
|
|
1806
1833
|
};
|
|
@@ -1812,41 +1839,6 @@ export class Orchestrator {
|
|
|
1812
1839
|
// Thin delegate — the channel mechanics live in interaction.ts.
|
|
1813
1840
|
return interactionChannelFor(input, log, runId, taskId, attemptId, harnessId, supportsInteractive, DEFAULT_INTERACTION_TIMEOUT_MS);
|
|
1814
1841
|
}
|
|
1815
|
-
/**
|
|
1816
|
-
* D-14 layer 3 (AGENTS.md unification, INV-113): the ONE new live-tree write.
|
|
1817
|
-
* When the PROJECT root has `AGENTS.md` and no `CLAUDE.md`, drop a thin
|
|
1818
|
-
* `CLAUDE.md` (`@AGENTS.md` import + Claudexor ownership marker) so a Claude
|
|
1819
|
-
* Code route reads the same instruction file codex/cursor read natively.
|
|
1820
|
-
*
|
|
1821
|
-
* The project-root bridge has its own narrower fence: read-only modes never
|
|
1822
|
-
* reach this run-prep stage and `--in-place` stateful targets are left
|
|
1823
|
-
* untouched. Git admission is independently owned by `runStartRequiresGit`.
|
|
1824
|
-
* The write targets the PROJECT root (`repoRoot`), never a worktree envelope.
|
|
1825
|
-
* The workspace helper adds exclusive-create + no-follow +
|
|
1826
|
-
* idempotency, so a hand-written or symlinked `CLAUDE.md` is never overwritten
|
|
1827
|
-
* and a concurrent/second prep is a no-op. Announced via a typed
|
|
1828
|
-
* `project.claude_bridge.created` event on an actual create only — the git-init
|
|
1829
|
-
* pattern. A bridge is a convenience, not a precondition: any failure is
|
|
1830
|
-
* swallowed so it can never fail an otherwise-valid write run.
|
|
1831
|
-
*/
|
|
1832
|
-
ensureClaudeBridgeForRun(repoRoot, inPlace, log) {
|
|
1833
|
-
if (repoRoot === NO_PROJECT_ROOT || inPlace)
|
|
1834
|
-
return;
|
|
1835
|
-
let result;
|
|
1836
|
-
try {
|
|
1837
|
-
result = ensureClaudeBridge(repoRoot);
|
|
1838
|
-
}
|
|
1839
|
-
catch {
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
if (result.created) {
|
|
1843
|
-
log.emit("project.claude_bridge.created", {
|
|
1844
|
-
project_root: repoRoot,
|
|
1845
|
-
path: "CLAUDE.md",
|
|
1846
|
-
source: "AGENTS.md",
|
|
1847
|
-
});
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
1842
|
async runRace(input, mode, announce) {
|
|
1851
1843
|
const taskId = input.taskId ?? newId("task");
|
|
1852
1844
|
const runId = input.runId ?? newId("run");
|
|
@@ -1885,7 +1877,7 @@ export class Orchestrator {
|
|
|
1885
1877
|
// directory, a filesystem root, or one that cannot be classified), which
|
|
1886
1878
|
// gets a typed refusal BEFORE any mutation (INV-075). For an isolated
|
|
1887
1879
|
// thread the execution root is already a git worktree: a no-op there.
|
|
1888
|
-
if (mutatingRun) {
|
|
1880
|
+
if (mutatingRun && input.workspaceKind !== "directory") {
|
|
1889
1881
|
const gitPreconditionError = await ensureWriteModeGitBoundary(execRoot, log, store, paths, runId, mode);
|
|
1890
1882
|
if (gitPreconditionError) {
|
|
1891
1883
|
return {
|
|
@@ -1903,14 +1895,14 @@ export class Orchestrator {
|
|
|
1903
1895
|
}
|
|
1904
1896
|
// Same run-prep stage as the git boundary: if the PROJECT root uses AGENTS.md
|
|
1905
1897
|
// with no CLAUDE.md, bridge it so a Claude Code candidate reads it (INV-113).
|
|
1906
|
-
if (mutatingRun) {
|
|
1907
|
-
|
|
1898
|
+
if (mutatingRun && input.workspaceKind !== "directory") {
|
|
1899
|
+
ensureClaudeBridgeForRun(input.repoRoot, input.inPlace === true, log);
|
|
1908
1900
|
}
|
|
1909
1901
|
// Pre-turn snapshot of the live tree for in-place runs: the revert restore
|
|
1910
1902
|
// target (server-owned revertInPlace). A snapshot failure must never fail the
|
|
1911
1903
|
// run — revert is simply unavailable then.
|
|
1912
1904
|
let preTurnSha = null;
|
|
1913
|
-
if (mutatingRun && input.inPlace === true) {
|
|
1905
|
+
if (mutatingRun && input.inPlace === true && input.workspaceKind !== "directory") {
|
|
1914
1906
|
try {
|
|
1915
1907
|
preTurnSha = await snapshotTree(execRoot);
|
|
1916
1908
|
}
|
|
@@ -1983,7 +1975,7 @@ export class Orchestrator {
|
|
|
1983
1975
|
attemptId,
|
|
1984
1976
|
intent: this.candidateIntent(input),
|
|
1985
1977
|
harnessId: routed.adapter.id,
|
|
1986
|
-
cost: attemptCostEvidence(routed.adapter.id, attemptId, this.reservationEstimateUsd(input, i > 0), this.routeBillingKnowledge(input, routed.adapter.id)),
|
|
1978
|
+
cost: attemptCostEvidence(routed.adapter.id, attemptId, this.reservationEstimateUsd(input, i > 0), this.routeBillingKnowledge(input, routed.adapter.id), processingCostEvidence(routed.processing, this.routeBillingKnowledge(input, routed.adapter.id), [`harness:${routed.adapter.id}`])),
|
|
1987
1979
|
});
|
|
1988
1980
|
log.emit("budget.lease.created", {
|
|
1989
1981
|
granted: lease.granted,
|
|
@@ -2079,6 +2071,8 @@ export class Orchestrator {
|
|
|
2079
2071
|
baseRef: contract.repo.base_ref,
|
|
2080
2072
|
dirtyPolicy: "snapshot",
|
|
2081
2073
|
accessProfile: candidateAccess,
|
|
2074
|
+
workspaceKind: input.workspaceKind,
|
|
2075
|
+
scopePaths: input.scopePaths,
|
|
2082
2076
|
// Direct-workspace singletons run in place. Races and patch-envelope
|
|
2083
2077
|
// transports stay isolated and adopt through the delivery service.
|
|
2084
2078
|
inPlace: !mutatingRun ||
|
|
@@ -2087,7 +2081,13 @@ export class Orchestrator {
|
|
|
2087
2081
|
slot.routed.implementationTransport !== "git_patch_envelope"),
|
|
2088
2082
|
});
|
|
2089
2083
|
harnessHome = this.harnessHomeFor(wsm, envelope, slot.routed, input);
|
|
2090
|
-
const run = await this.runCandidateInEnvelope(slot.routed, envelope, slot.attemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger,
|
|
2084
|
+
const run = await this.runCandidateInEnvelope(slot.routed, envelope, slot.attemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
|
|
2085
|
+
id: slot.leaseId,
|
|
2086
|
+
onDenied: (denial) => {
|
|
2087
|
+
budgetStopped = true;
|
|
2088
|
+
budgetDenial ??= denial;
|
|
2089
|
+
},
|
|
2090
|
+
}, candidateAccess, (ev) => {
|
|
2091
2091
|
const safeEv = redactHarnessEvent(ev);
|
|
2092
2092
|
safeInvoke(input.onHarnessEvent, safeEv);
|
|
2093
2093
|
log.emit("harness.event", harnessEventPayload(adapter.id, slot.attemptId, safeEv));
|
|
@@ -2145,7 +2145,7 @@ export class Orchestrator {
|
|
|
2145
2145
|
attemptId: contAttemptId,
|
|
2146
2146
|
intent: this.candidateIntent(input),
|
|
2147
2147
|
harnessId: adapter.id,
|
|
2148
|
-
cost: attemptCostEvidence(adapter.id, contAttemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, adapter.id)),
|
|
2148
|
+
cost: attemptCostEvidence(adapter.id, contAttemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, adapter.id), processingCostEvidence(slot.routed.processing, this.routeBillingKnowledge(input, slot.routed.adapter.id), [`harness:${adapter.id}`])),
|
|
2149
2149
|
});
|
|
2150
2150
|
if (contLease.granted) {
|
|
2151
2151
|
candidateContinuationCount += 1; // claim the one-shot only once it launches
|
|
@@ -2164,7 +2164,13 @@ export class Orchestrator {
|
|
|
2164
2164
|
});
|
|
2165
2165
|
const contLeaseId = contLease.lease?.lease_id ?? "";
|
|
2166
2166
|
try {
|
|
2167
|
-
const contRun = await this.runCandidateInEnvelope(slot.routed, envelope, contAttemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger,
|
|
2167
|
+
const contRun = await this.runCandidateInEnvelope(slot.routed, envelope, contAttemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
|
|
2168
|
+
id: contLeaseId,
|
|
2169
|
+
onDenied: (denial) => {
|
|
2170
|
+
budgetStopped = true;
|
|
2171
|
+
budgetDenial ??= denial;
|
|
2172
|
+
},
|
|
2173
|
+
}, candidateAccess, (ev) => {
|
|
2168
2174
|
const safeEv = redactHarnessEvent(ev);
|
|
2169
2175
|
safeInvoke(input.onHarnessEvent, safeEv);
|
|
2170
2176
|
log.emit("harness.event", harnessEventPayload(adapter.id, contAttemptId, safeEv));
|
|
@@ -2263,18 +2269,18 @@ export class Orchestrator {
|
|
|
2263
2269
|
// Fail-closed terminal: a delegated mutating run whose attempts state
|
|
2264
2270
|
// neither historical proof nor deliberate absence refuses instead of passing.
|
|
2265
2271
|
assertDelegatedEvidence(input.delegated === true, candidateAccess, runs);
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2272
|
+
const cancelledRaceResult = () => cancelledCandidatesResult({
|
|
2273
|
+
store,
|
|
2274
|
+
paths,
|
|
2275
|
+
log,
|
|
2276
|
+
runId,
|
|
2277
|
+
taskId,
|
|
2278
|
+
mode,
|
|
2279
|
+
ledger,
|
|
2280
|
+
runs,
|
|
2281
|
+
signal: input.signal,
|
|
2282
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2283
|
+
});
|
|
2278
2284
|
// Revert divergence fence for the single-candidate in-place path: the
|
|
2279
2285
|
// candidate mutated the LIVE tree during execution above, so the post-turn
|
|
2280
2286
|
// snapshot must be taken NOW — before review/synthesis/arbitration, which can
|
|
@@ -2283,6 +2289,7 @@ export class Orchestrator {
|
|
|
2283
2289
|
// into the revert target and let a later revert clobber them.
|
|
2284
2290
|
let earlyPostTurnSha = null;
|
|
2285
2291
|
if (mutatingRun &&
|
|
2292
|
+
input.workspaceKind !== "directory" &&
|
|
2286
2293
|
input.inPlace &&
|
|
2287
2294
|
requestedSingleCandidate &&
|
|
2288
2295
|
runs.every((run) => !run.secretDiffRefusal)) {
|
|
@@ -2301,7 +2308,19 @@ export class Orchestrator {
|
|
|
2301
2308
|
if (failedDelegation) {
|
|
2302
2309
|
const failure = delegateFailure.candidateFailureTerminal(failedDelegation, "race");
|
|
2303
2310
|
await disposeReviewEnvelopes();
|
|
2304
|
-
if (mutatingRun) {
|
|
2311
|
+
if (mutatingRun && failedDelegation.files) {
|
|
2312
|
+
await publishDirectoryCandidate({
|
|
2313
|
+
files: failedDelegation.files,
|
|
2314
|
+
store,
|
|
2315
|
+
paths,
|
|
2316
|
+
taskId,
|
|
2317
|
+
attemptId: failedDelegation.attemptId,
|
|
2318
|
+
harnessId: failedDelegation.harnessId,
|
|
2319
|
+
facts: makeOutcomeFacts("failed", { noChanges: failedDelegation.files.noChanges }),
|
|
2320
|
+
log,
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
else if (mutatingRun && input.workspaceKind !== "directory") {
|
|
2305
2324
|
await delegateFailure.persistFailedInPlaceWorkProduct({
|
|
2306
2325
|
...{ store, log, paths, execRoot, preTurnSha, taskId, mode },
|
|
2307
2326
|
live: input.inPlace === true && failedDelegation.reviewCwd === execRoot,
|
|
@@ -2313,63 +2332,18 @@ export class Orchestrator {
|
|
|
2313
2332
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null);
|
|
2314
2333
|
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), failure.metadata);
|
|
2315
2334
|
}
|
|
2316
|
-
if (runs.length === 0)
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
reason: agentBudgetMapping?.reason ?? (budgetStopped ? "budget_exhausted" : "harness_failed"),
|
|
2326
|
-
noChanges: true,
|
|
2327
|
-
});
|
|
2328
|
-
const why = agentBudgetMapping?.safeMessage ?? "no candidates produced";
|
|
2329
|
-
store.writeYaml(join(paths.arbitrationDir, "decision.yaml"), {
|
|
2330
|
-
winner: null,
|
|
2331
|
-
facts,
|
|
2332
|
-
why_winner: why,
|
|
2333
|
-
evidence_facts: ["no candidates were produced"],
|
|
2334
|
-
apply_recommendation: "continue",
|
|
2335
|
-
budget_summary: decisionBudgetSummary(ledger),
|
|
2336
|
-
});
|
|
2337
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Phase: ${agentBudgetMapping ? "budget" : "executor"}\n\n${why}\n`);
|
|
2338
|
-
if (agentBudgetMapping) {
|
|
2339
|
-
writeFailure(store, paths, budgetFailureRecord(agentBudgetMapping, { runDir: paths.root }));
|
|
2340
|
-
}
|
|
2341
|
-
else {
|
|
2342
|
-
writeFailure(store, paths, {
|
|
2343
|
-
phase: "executor",
|
|
2344
|
-
category: "internal",
|
|
2345
|
-
safeMessage: why,
|
|
2346
|
-
runDir: paths.root,
|
|
2347
|
-
nextActions: ["Open diagnostics", "Retry the run"],
|
|
2348
|
-
});
|
|
2349
|
-
}
|
|
2350
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
2351
|
-
log.emit("run.failed", {
|
|
2352
|
-
lifecycle: facts.lifecycle,
|
|
2353
|
-
facts,
|
|
2354
|
-
reason: facts.reason,
|
|
2355
|
-
phase: agentBudgetMapping ? "budget" : "executor",
|
|
2356
|
-
...(agentBudgetMapping?.harnessId ? { harness_id: agentBudgetMapping.harnessId } : {}),
|
|
2357
|
-
error: why,
|
|
2358
|
-
failure_ref: "final/failure.yaml",
|
|
2359
|
-
});
|
|
2360
|
-
return {
|
|
2335
|
+
if (runs.length === 0)
|
|
2336
|
+
return emptyCandidateResult({
|
|
2337
|
+
ledger,
|
|
2338
|
+
budgetStopped,
|
|
2339
|
+
budgetDenial,
|
|
2340
|
+
mode,
|
|
2341
|
+
store,
|
|
2342
|
+
paths,
|
|
2343
|
+
log,
|
|
2361
2344
|
runId,
|
|
2362
2345
|
taskId,
|
|
2363
|
-
|
|
2364
|
-
lifecycle: facts.lifecycle,
|
|
2365
|
-
facts,
|
|
2366
|
-
winner: null,
|
|
2367
|
-
runDir: paths.root,
|
|
2368
|
-
summary: why,
|
|
2369
|
-
candidates: [],
|
|
2370
|
-
spendUsd: ledger.spend(),
|
|
2371
|
-
};
|
|
2372
|
-
}
|
|
2346
|
+
});
|
|
2373
2347
|
// Reviewers, synthesis, and arbitration only ever see candidates WITH
|
|
2374
2348
|
// work (a real diff or a completed stream). Attempts that died before
|
|
2375
2349
|
// producing anything are corpses: reviewing "(empty diff)" spends real
|
|
@@ -2378,72 +2352,18 @@ export class Orchestrator {
|
|
|
2378
2352
|
const workingRuns = partitionCandidates(runs).working;
|
|
2379
2353
|
if (workingRuns.length === 0) {
|
|
2380
2354
|
await disposeReviewEnvelopes();
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
winner: null,
|
|
2386
|
-
facts,
|
|
2387
|
-
why_winner: rootCause,
|
|
2388
|
-
evidence_facts: runs.map((r) => `${r.attemptId} produced no work: ${r.errors[0] ?? "unknown"}`),
|
|
2389
|
-
apply_recommendation: "continue",
|
|
2390
|
-
budget_summary: decisionBudgetSummary(ledger),
|
|
2391
|
-
});
|
|
2392
|
-
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null);
|
|
2393
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}\n- Phase: ${phase}\n\n${rootCause}\n`);
|
|
2394
|
-
const existingEventRefs = runs
|
|
2395
|
-
.map((r) => `attempts/${r.attemptId}/events.jsonl`)
|
|
2396
|
-
.filter((rel) => existsSync(join(paths.root, rel)));
|
|
2397
|
-
// #31: auth guidance only on a classified auth failure; every other
|
|
2398
|
-
// harness cause (timeout, rate limit, crash, config) gets remediation that
|
|
2399
|
-
// fits it, instead of a doomed "Check harness authentication".
|
|
2400
|
-
const harnessCategory = dominantHarnessFailureCategory(first.telemetry.transientFailures);
|
|
2401
|
-
// A run speaks with a candidate's TYPED refusal only when EVERY candidate
|
|
2402
|
-
// died of the same one (candidateEvidence owns that rule); mixed causes
|
|
2403
|
-
// keep the honest harness terminal.
|
|
2404
|
-
const unanimous = unanimousDeclaredFailure(runs);
|
|
2405
|
-
writeFailure(store, paths, {
|
|
2406
|
-
phase,
|
|
2407
|
-
category: unanimous?.category ?? (phase === "workspace" ? "project" : "harness_error"),
|
|
2408
|
-
code: unanimous?.code ?? null,
|
|
2409
|
-
harnessId: first.harnessId,
|
|
2410
|
-
attemptId: first.attemptId,
|
|
2411
|
-
safeMessage: rootCause,
|
|
2412
|
-
rawDetailRef: `attempts/${first.attemptId}/attempt.yaml`,
|
|
2413
|
-
eventRefs: existingEventRefs,
|
|
2414
|
-
runDir: paths.root,
|
|
2415
|
-
resetsAt: unanimous?.resetsAt ?? null,
|
|
2416
|
-
nextActions: first.secretDiffRefusal
|
|
2417
|
-
? secretDiff.secretDiffNextActions(first.secretDiffRefusal)
|
|
2418
|
-
: phase === "workspace"
|
|
2419
|
-
? ["Check the project folder", "Open diagnostics", "Retry the run"]
|
|
2420
|
-
: harnessFailureNextActions(harnessCategory),
|
|
2421
|
-
});
|
|
2422
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
2423
|
-
log.emit("run.failed", {
|
|
2424
|
-
lifecycle: facts.lifecycle,
|
|
2425
|
-
facts,
|
|
2426
|
-
reason: facts.reason,
|
|
2427
|
-
phase,
|
|
2428
|
-
error: rootCause,
|
|
2429
|
-
failure_ref: "final/failure.yaml",
|
|
2430
|
-
});
|
|
2431
|
-
return {
|
|
2355
|
+
return failedCandidatesResult({
|
|
2356
|
+
store,
|
|
2357
|
+
paths,
|
|
2358
|
+
log,
|
|
2432
2359
|
runId,
|
|
2433
2360
|
taskId,
|
|
2434
2361
|
mode,
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
candidates: runs.map((r) => ({
|
|
2441
|
-
attemptId: r.attemptId,
|
|
2442
|
-
harnessId: r.harnessId,
|
|
2443
|
-
status: "red",
|
|
2444
|
-
})),
|
|
2445
|
-
spendUsd: ledger.spend(),
|
|
2446
|
-
};
|
|
2362
|
+
ledger,
|
|
2363
|
+
runs,
|
|
2364
|
+
budgetDenial,
|
|
2365
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2366
|
+
});
|
|
2447
2367
|
}
|
|
2448
2368
|
// QA-025: only announce that review STARTED when the panel will actually
|
|
2449
2369
|
// run. A candidate that changed no files is skipped inside reviewRuns; a
|
|
@@ -2452,7 +2372,7 @@ export class Orchestrator {
|
|
|
2452
2372
|
// real verification). Compute the reviewable set first and emit a typed
|
|
2453
2373
|
// `review.skipped` when nothing is reviewable, so every start has a matching
|
|
2454
2374
|
// terminal and the no-diff path records `not_run` consistently.
|
|
2455
|
-
const reviewableRuns = workingRuns.filter((r) => r.diff.trim().length > 0);
|
|
2375
|
+
const reviewableRuns = workingRuns.filter((r) => r.files ? r.files.noChanges !== true : r.diff.trim().length > 0);
|
|
2456
2376
|
const configuredFamilies = new Set(reviewers.map((r) => r.providerFamily)).size;
|
|
2457
2377
|
if (reviewableRuns.length === 0 || reviewers.length === 0) {
|
|
2458
2378
|
log.emit("review.skipped", {
|
|
@@ -2511,7 +2431,7 @@ export class Orchestrator {
|
|
|
2511
2431
|
attemptId: "synth",
|
|
2512
2432
|
intent: "synthesize",
|
|
2513
2433
|
harnessId: synthRouted.adapter.id,
|
|
2514
|
-
cost: attemptCostEvidence(synthRouted.adapter.id, "synth", this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, synthRouted.adapter.id)),
|
|
2434
|
+
cost: attemptCostEvidence(synthRouted.adapter.id, "synth", this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, synthRouted.adapter.id), processingCostEvidence(synthRouted.processing, this.routeBillingKnowledge(input, synthRouted.adapter.id), [`harness:${synthRouted.adapter.id}`])),
|
|
2515
2435
|
});
|
|
2516
2436
|
if (lease.granted) {
|
|
2517
2437
|
let envelope;
|
|
@@ -2533,9 +2453,17 @@ export class Orchestrator {
|
|
|
2533
2453
|
baseRef: contract.repo.base_ref,
|
|
2534
2454
|
dirtyPolicy: "snapshot",
|
|
2535
2455
|
accessProfile: candidateAccess,
|
|
2456
|
+
workspaceKind: input.workspaceKind,
|
|
2457
|
+
scopePaths: input.scopePaths,
|
|
2536
2458
|
});
|
|
2537
2459
|
const synthHome = this.harnessHomeFor(wsm, envelope, synthRouted, input);
|
|
2538
|
-
const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger,
|
|
2460
|
+
const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger, {
|
|
2461
|
+
id: lease.lease.lease_id,
|
|
2462
|
+
onDenied: (denial) => {
|
|
2463
|
+
budgetStopped = true;
|
|
2464
|
+
budgetDenial ??= denial;
|
|
2465
|
+
},
|
|
2466
|
+
}, candidateAccess, (ev) => {
|
|
2539
2467
|
const safeEv = redactHarnessEvent(ev);
|
|
2540
2468
|
safeInvoke(input.onHarnessEvent, safeEv);
|
|
2541
2469
|
log.emit("harness.event", harnessEventPayload(synthAdapter.id, "synth", safeEv));
|
|
@@ -2553,7 +2481,7 @@ export class Orchestrator {
|
|
|
2553
2481
|
workingRuns.push(run);
|
|
2554
2482
|
}
|
|
2555
2483
|
if (input.signal?.aborted) {
|
|
2556
|
-
return cancelledResult(log, runId, taskId, mode, paths.root,
|
|
2484
|
+
return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs), () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null), ledger.spend(), input.signal, store);
|
|
2557
2485
|
}
|
|
2558
2486
|
}
|
|
2559
2487
|
finally {
|
|
@@ -2613,6 +2541,12 @@ export class Orchestrator {
|
|
|
2613
2541
|
? (evidences.find((e) => e.attemptId === winnerRun.attemptId)?.reviewVerified ?? false)
|
|
2614
2542
|
: evidences.length > 0 && evidences.every((e) => e.reviewVerified);
|
|
2615
2543
|
let facts = result.decision.facts;
|
|
2544
|
+
if (winnerRun?.files)
|
|
2545
|
+
facts = {
|
|
2546
|
+
...facts,
|
|
2547
|
+
noChanges: winnerRun.files.noChanges,
|
|
2548
|
+
reason: facts.reason === "no_changes" && winnerRun.files.noChanges !== true ? null : facts.reason,
|
|
2549
|
+
};
|
|
2616
2550
|
// A reviewer NEEDS_HUMAN escalation forces the REVIEW axis to blocked (a
|
|
2617
2551
|
// needs-decision terminal), unless the decision is already applyable-clean.
|
|
2618
2552
|
if (needsHuman && facts.lifecycle === "succeeded" && facts.review !== "blocked") {
|
|
@@ -2633,27 +2567,24 @@ export class Orchestrator {
|
|
|
2633
2567
|
let finalVerifyFailed = false;
|
|
2634
2568
|
let deliveryFailureReason = null;
|
|
2635
2569
|
let raceDeliveryReceipt = null;
|
|
2636
|
-
|
|
2637
|
-
// instead defers verification until immediately before delivery.
|
|
2570
|
+
let directoryDeliveryReceipt = false;
|
|
2638
2571
|
const inPlaceWinner = winnerRun?.reviewCwd === execRoot;
|
|
2639
2572
|
const deferredRaceVerify = input.inPlace === true && !inPlaceWinner;
|
|
2640
2573
|
if (mutatingRun &&
|
|
2641
2574
|
winnerRun &&
|
|
2642
2575
|
!inPlaceWinner &&
|
|
2643
2576
|
!deferredRaceVerify &&
|
|
2644
|
-
winnerRun.diff.trim().length > 0 &&
|
|
2577
|
+
(winnerRun.files ? winnerRun.files.noChanges !== true : winnerRun.diff.trim().length > 0) &&
|
|
2645
2578
|
facts.lifecycle === "succeeded" &&
|
|
2646
2579
|
facts.review !== "blocked" &&
|
|
2647
2580
|
!input.signal?.aborted) {
|
|
2648
|
-
finalVerify =
|
|
2649
|
-
|
|
2650
|
-
|
|
2581
|
+
finalVerify = winnerRun.files
|
|
2582
|
+
? await finalVerifyFiles(winnerRun.files, undefined, gateSpecsFromContract(contract), log)
|
|
2583
|
+
: await finalVerifyPatch(execRoot, winnerRun, gateSpecsFromContract(contract), log);
|
|
2651
2584
|
finalVerifyFailed = finalVerifyBlocks(finalVerify);
|
|
2652
2585
|
if (finalVerifyFailed)
|
|
2653
2586
|
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
2654
2587
|
}
|
|
2655
|
-
// A needs-decision terminal (review blocked or checks failed) overrides the
|
|
2656
|
-
// persisted green arbitration fields; otherwise the facts pass through.
|
|
2657
2588
|
const needsDec = facts.review === "blocked" || facts.checks === "failed";
|
|
2658
2589
|
store.writeYaml(join(paths.arbitrationDir, "decision.yaml"), {
|
|
2659
2590
|
...result.decision,
|
|
@@ -2674,17 +2605,9 @@ export class Orchestrator {
|
|
|
2674
2605
|
log.emit("output.ready", { kind: "artifact", path });
|
|
2675
2606
|
}
|
|
2676
2607
|
const winnerAnswer = winnerRun.answerText ?? "";
|
|
2677
|
-
// The winner's final MESSAGE is the human-facing answer and materializes
|
|
2678
|
-
// for diff-ful runs too: the chat renders final/answer.md (the projection
|
|
2679
|
-
// prefers it), never the arbitration summary — "Run … Winner: a01 …" is
|
|
2680
|
-
// machine telemetry, not what the agent said. The diff stays in the
|
|
2681
|
-
// Diff tab; summary.md remains a diagnostics artifact.
|
|
2682
2608
|
if (winnerAnswer.length > 0) {
|
|
2683
2609
|
store.writeText(join(paths.finalDir, "answer.md"), winnerAnswer + "\n");
|
|
2684
2610
|
}
|
|
2685
|
-
// The run's structured-output contract: ONE engine validator, called on
|
|
2686
|
-
// the winner's answer regardless of diff presence (a non-conformant
|
|
2687
|
-
// answer stays success-with-warnings; the receipt is the truth).
|
|
2688
2611
|
if (contract.output_schema) {
|
|
2689
2612
|
finalizeStructuredOutput({
|
|
2690
2613
|
store,
|
|
@@ -2694,7 +2617,48 @@ export class Orchestrator {
|
|
|
2694
2617
|
answerText: winnerAnswer,
|
|
2695
2618
|
});
|
|
2696
2619
|
}
|
|
2697
|
-
if (mutatingRun) {
|
|
2620
|
+
if (mutatingRun && winnerRun.files) {
|
|
2621
|
+
let directoryDelivery;
|
|
2622
|
+
const directoryAdoptable = facts.lifecycle === "succeeded" &&
|
|
2623
|
+
reviewAllowsApply(facts) &&
|
|
2624
|
+
facts.checks !== "failed" &&
|
|
2625
|
+
!workStateVetoes(facts);
|
|
2626
|
+
if (input.inPlace === true && !inPlaceWinner && directoryAdoptable) {
|
|
2627
|
+
const delivered = await verifyAndDeliverFiles(execRoot, winnerRun.files, {}, gateSpecsFromContract(contract), (freshVerify) => finalVerifyBlocks(freshVerify)
|
|
2628
|
+
? (freshVerify.reason ?? "final verify failed before directory race adoption")
|
|
2629
|
+
: null, log);
|
|
2630
|
+
store.writeYaml(join(paths.finalDir, "delivery_receipt.yaml"), delivered);
|
|
2631
|
+
directoryDeliveryReceipt = true;
|
|
2632
|
+
directoryDelivery = delivered;
|
|
2633
|
+
finalVerify = delivered.finalVerify;
|
|
2634
|
+
if (finalVerifyBlocks(finalVerify))
|
|
2635
|
+
finalVerifyFailed = true;
|
|
2636
|
+
if (!delivered.applied) {
|
|
2637
|
+
deliveryFailureReason = delivered.detail ?? "directory race adoption was refused";
|
|
2638
|
+
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
2639
|
+
}
|
|
2640
|
+
writeRaceDeliveryDecision(store, decisionPath, {
|
|
2641
|
+
decision: result.decision,
|
|
2642
|
+
facts,
|
|
2643
|
+
reviewVerified: actualReviewVerified,
|
|
2644
|
+
finalVerify,
|
|
2645
|
+
deliveryFailureReason,
|
|
2646
|
+
deliveryReceiptPath: "final/delivery_receipt.yaml",
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
await publishDirectoryCandidate({
|
|
2650
|
+
files: winnerRun.files,
|
|
2651
|
+
store,
|
|
2652
|
+
paths,
|
|
2653
|
+
taskId,
|
|
2654
|
+
attemptId: winnerRun.attemptId,
|
|
2655
|
+
harnessId: winnerRun.harnessId,
|
|
2656
|
+
facts,
|
|
2657
|
+
log,
|
|
2658
|
+
delivery: directoryDelivery,
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
else if (mutatingRun) {
|
|
2698
2662
|
secretDiff.assertNoSecretLikeTokens("final patch diff", winnerRun.diff);
|
|
2699
2663
|
const patchSha256 = sha256(winnerRun.diff);
|
|
2700
2664
|
store.writeText(join(paths.finalDir, "patch.diff"), winnerRun.diff);
|
|
@@ -2771,7 +2735,7 @@ export class Orchestrator {
|
|
|
2771
2735
|
reviewVerified: actualReviewVerified,
|
|
2772
2736
|
finalVerify,
|
|
2773
2737
|
deliveryFailureReason,
|
|
2774
|
-
deliveryReceiptPath: raceDeliveryReceipt ? "final/delivery_receipt.yaml" : null,
|
|
2738
|
+
deliveryReceiptPath: raceDeliveryReceipt || directoryDeliveryReceipt ? "final/delivery_receipt.yaml" : null,
|
|
2775
2739
|
});
|
|
2776
2740
|
if (inPlaceWinner && requestedSingleCandidate && adopted === true) {
|
|
2777
2741
|
revertAnchorId = await createRevertAnchorOrNull(execRoot, preTurnSha, postTurnSha);
|
|
@@ -2781,7 +2745,7 @@ export class Orchestrator {
|
|
|
2781
2745
|
kind: input.create === true ? "new_repo" : "patch",
|
|
2782
2746
|
source_task_id: taskId,
|
|
2783
2747
|
producer_attempt_id: winnerRun.attemptId,
|
|
2784
|
-
...(raceDeliveryReceipt
|
|
2748
|
+
...(raceDeliveryReceipt || directoryDeliveryReceipt
|
|
2785
2749
|
? { files: { delivery_receipt: "final/delivery_receipt.yaml" } }
|
|
2786
2750
|
: {}),
|
|
2787
2751
|
meta: {
|
|
@@ -2948,20 +2912,12 @@ export class Orchestrator {
|
|
|
2948
2912
|
winner: result.decision.winner,
|
|
2949
2913
|
runDir: paths.root,
|
|
2950
2914
|
summary: result.decision.why_winner,
|
|
2951
|
-
candidates: runs
|
|
2952
|
-
attemptId: r.attemptId,
|
|
2953
|
-
harnessId: r.harnessId,
|
|
2954
|
-
// gatesPassed([]) is vacuously true: a successful zero-gate run is a
|
|
2955
|
-
// legitimate green STATUS (never a "gates passed" claim — the label
|
|
2956
|
-
// surfaces render that honestly as n/a).
|
|
2957
|
-
status: gatesPassed(r.gates) && !r.errored ? "green" : "red",
|
|
2958
|
-
})),
|
|
2915
|
+
candidates: candidateStatuses(runs),
|
|
2959
2916
|
decisionPath,
|
|
2960
2917
|
reviewVerified: actualReviewVerified,
|
|
2961
2918
|
spendUsd: ledger.spend(),
|
|
2962
2919
|
};
|
|
2963
2920
|
}
|
|
2964
|
-
/** Single-owner telemetry artifact (final/telemetry.yaml); surfaces project it, never recompute. */
|
|
2965
2921
|
writeRunTelemetry(store, paths, contract, runId, taskId, mode, attempts, finalAttemptId, deepScanSynthesis) {
|
|
2966
2922
|
// QA-034: attach the routing rationale recorded at pool ordering (if this
|
|
2967
2923
|
// run computed one), then clear it — telemetry is written once at terminal.
|
|
@@ -2981,7 +2937,6 @@ export class Orchestrator {
|
|
|
2981
2937
|
resolveAuthPreference: (harnessId) => this.authPreferenceForHarness(contract.repo.root, harnessId, contract.auth_preference),
|
|
2982
2938
|
});
|
|
2983
2939
|
}
|
|
2984
|
-
/** Review a set of runs and return their evidence (with finalReviewClean + review_verified caveat). */
|
|
2985
2940
|
/**
|
|
2986
2941
|
* SINGLE funnel for every reviewer-panel invocation: run it inside a per-review
|
|
2987
2942
|
* scoped harness HOME (Bible §6) so reviewer scratch state and injected auth
|
|
@@ -2998,7 +2953,7 @@ export class Orchestrator {
|
|
|
2998
2953
|
transientRetryPolicy: input.transientRetryPolicy ?? transientRetryPolicy(this.config(input.cwd)),
|
|
2999
2954
|
});
|
|
3000
2955
|
}
|
|
3001
|
-
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal, reservationEstimateUsd) {
|
|
2956
|
+
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal, reservationEstimateUsd, reviewUnchanged = false) {
|
|
3002
2957
|
return reviewCandidateRuns({
|
|
3003
2958
|
runs,
|
|
3004
2959
|
reviewers,
|
|
@@ -3013,6 +2968,7 @@ export class Orchestrator {
|
|
|
3013
2968
|
taskId,
|
|
3014
2969
|
signal,
|
|
3015
2970
|
reservationEstimateUsd,
|
|
2971
|
+
reviewUnchanged,
|
|
3016
2972
|
}, {
|
|
3017
2973
|
prepareReviewEvidenceDir: this.prepareReviewEvidenceDir.bind(this),
|
|
3018
2974
|
recordReviewEvidenceCleanup: this.recordReviewEvidenceCleanup.bind(this),
|
|
@@ -3087,9 +3043,9 @@ export class Orchestrator {
|
|
|
3087
3043
|
const wsm = new WorkspaceManager(execRoot);
|
|
3088
3044
|
const readiness = new ReadinessLedger();
|
|
3089
3045
|
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
3090
|
-
//
|
|
3091
|
-
//
|
|
3092
|
-
if (!input.inPlace) {
|
|
3046
|
+
// Legacy Git envelopes need their boundary; explicit directory execution
|
|
3047
|
+
// keeps the requested strategy without initializing the selected folder.
|
|
3048
|
+
if (!input.inPlace && input.workspaceKind !== "directory") {
|
|
3093
3049
|
const gitPreconditionError = await ensureWriteModeGitBoundary(execRoot, log, store, paths, runId, mode);
|
|
3094
3050
|
if (gitPreconditionError) {
|
|
3095
3051
|
return {
|
|
@@ -3109,7 +3065,7 @@ export class Orchestrator {
|
|
|
3109
3065
|
// exclusion — we are inside that branch, so inPlace is false here): bridge
|
|
3110
3066
|
// an AGENTS.md-only PROJECT root so a Claude Code convergence attempt reads
|
|
3111
3067
|
// it (INV-113).
|
|
3112
|
-
|
|
3068
|
+
ensureClaudeBridgeForRun(input.repoRoot, false, log);
|
|
3113
3069
|
}
|
|
3114
3070
|
const reviewDir = join(paths.root, "review-evidence");
|
|
3115
3071
|
if (contract.review_requested !== false)
|
|
@@ -3198,6 +3154,7 @@ export class Orchestrator {
|
|
|
3198
3154
|
let attempt = 0;
|
|
3199
3155
|
let converged = false;
|
|
3200
3156
|
let exhausted = false;
|
|
3157
|
+
let processingBudgetDenial = null;
|
|
3201
3158
|
let interrupted = false; // D-16 r8: terminalizes the run interrupted
|
|
3202
3159
|
let lastFindings = [];
|
|
3203
3160
|
let lastRun = null;
|
|
@@ -3223,12 +3180,13 @@ export class Orchestrator {
|
|
|
3223
3180
|
const allCooledDown = () => adapterPool.every((a) => convergenceQuotaLedger.cooldownActive(a.adapter.id));
|
|
3224
3181
|
const attemptTelemetries = [];
|
|
3225
3182
|
let lastDiffStable = true;
|
|
3183
|
+
const observedPaths = new Set();
|
|
3226
3184
|
try {
|
|
3227
3185
|
// The contract's ENGINE-COMPUTED effective profile drives the envelope and
|
|
3228
3186
|
// every attempt spec (parity with runRace); telemetry must never claim an
|
|
3229
3187
|
// access level the envelope did not actually run with.
|
|
3230
3188
|
const convergenceAccess = contract.access.effective_profile;
|
|
3231
|
-
if (input.inPlace === true) {
|
|
3189
|
+
if (input.inPlace === true && input.workspaceKind !== "directory") {
|
|
3232
3190
|
try {
|
|
3233
3191
|
preTurnSha = await snapshotTree(execRoot);
|
|
3234
3192
|
}
|
|
@@ -3243,6 +3201,8 @@ export class Orchestrator {
|
|
|
3243
3201
|
dirtyPolicy: "snapshot",
|
|
3244
3202
|
inPlace: input.inPlace ?? false,
|
|
3245
3203
|
accessProfile: convergenceAccess,
|
|
3204
|
+
workspaceKind: input.workspaceKind,
|
|
3205
|
+
scopePaths: input.scopePaths,
|
|
3246
3206
|
});
|
|
3247
3207
|
for (;;) {
|
|
3248
3208
|
if (input.signal?.aborted)
|
|
@@ -3277,7 +3237,7 @@ export class Orchestrator {
|
|
|
3277
3237
|
attemptId,
|
|
3278
3238
|
intent: "repair",
|
|
3279
3239
|
harnessId: adapter.id,
|
|
3280
|
-
cost: attemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, adapter.id)),
|
|
3240
|
+
cost: attemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, adapter.id), processingCostEvidence(routed.processing, this.routeBillingKnowledge(input, routed.adapter.id), [`harness:${adapter.id}`])),
|
|
3281
3241
|
});
|
|
3282
3242
|
if (!lease.granted) {
|
|
3283
3243
|
exhausted = true;
|
|
@@ -3295,7 +3255,13 @@ export class Orchestrator {
|
|
|
3295
3255
|
external_context_policy: knobs.webPolicy,
|
|
3296
3256
|
...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
|
|
3297
3257
|
});
|
|
3298
|
-
run = await this.runCandidateInEnvelope(routed, envelope, attemptId, `Attempt ${attempt}`, contract, prompt, store, paths, wsm, ledger,
|
|
3258
|
+
run = await this.runCandidateInEnvelope(routed, envelope, attemptId, `Attempt ${attempt}`, contract, prompt, store, paths, wsm, ledger, {
|
|
3259
|
+
id: lease.lease.lease_id,
|
|
3260
|
+
onDenied: (denial) => {
|
|
3261
|
+
exhausted = true;
|
|
3262
|
+
processingBudgetDenial ??= denial;
|
|
3263
|
+
},
|
|
3264
|
+
}, convergenceAccess, (ev) => {
|
|
3299
3265
|
const safeEv = redactHarnessEvent(ev);
|
|
3300
3266
|
safeInvoke(input.onHarnessEvent, safeEv);
|
|
3301
3267
|
log.emit("harness.event", harnessEventPayload(adapter.id, attemptId, safeEv));
|
|
@@ -3303,7 +3269,7 @@ export class Orchestrator {
|
|
|
3303
3269
|
ledger.updateHold(lease.lease?.lease_id ?? "", streamedUsd);
|
|
3304
3270
|
return ledger.tier() === "hard";
|
|
3305
3271
|
}, input, true, // convergence runs one candidate: live deltas on (W-C4)
|
|
3306
|
-
undefined, undefined, harnessHome);
|
|
3272
|
+
undefined, undefined, harnessHome, observedPaths);
|
|
3307
3273
|
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
3308
3274
|
log.emit("harness.completed", {
|
|
3309
3275
|
harness_id: adapter.id,
|
|
@@ -3343,6 +3309,17 @@ export class Orchestrator {
|
|
|
3343
3309
|
...(declared.code ? { declaredFailure: declared } : {}),
|
|
3344
3310
|
telemetry: createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [routed.browserRequirement, routed.denyRequirement, routed.delegationRequirement], knobs.model),
|
|
3345
3311
|
};
|
|
3312
|
+
if (envelope.workspace_kind === "directory") {
|
|
3313
|
+
const captured = await captureDirectoryCandidate({
|
|
3314
|
+
manager: wsm,
|
|
3315
|
+
envelope,
|
|
3316
|
+
artifactRoot: join(paths.attemptsDir, attemptId),
|
|
3317
|
+
sourceRoot: contract.repo.root,
|
|
3318
|
+
observedPaths: [...observedPaths],
|
|
3319
|
+
});
|
|
3320
|
+
run.files = captured.files;
|
|
3321
|
+
run.secretDiffRefusal = captured.refusal;
|
|
3322
|
+
}
|
|
3346
3323
|
}
|
|
3347
3324
|
lastRun = run;
|
|
3348
3325
|
// Fail-closed twin of the candidate lane's gate: this loop terminalizes
|
|
@@ -3350,20 +3327,47 @@ export class Orchestrator {
|
|
|
3350
3327
|
assertDelegatedEvidence(input.delegated === true, convergenceAccess, [run]);
|
|
3351
3328
|
attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry: run.telemetry });
|
|
3352
3329
|
// Cancellation/deadline keeps priority over a belt failure finalized concurrently.
|
|
3353
|
-
if (input.signal?.aborted)
|
|
3330
|
+
if (input.signal?.aborted || processingBudgetDenial)
|
|
3354
3331
|
break;
|
|
3355
3332
|
if (delegateFailure.candidateFailureKind(run)) {
|
|
3356
3333
|
const failure = delegateFailure.candidateFailureTerminal(run, "convergence");
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3334
|
+
if (run.files) {
|
|
3335
|
+
await publishDirectoryCandidate({
|
|
3336
|
+
files: run.files,
|
|
3337
|
+
store,
|
|
3338
|
+
paths,
|
|
3339
|
+
taskId,
|
|
3340
|
+
attemptId,
|
|
3341
|
+
harnessId: run.harnessId,
|
|
3342
|
+
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3343
|
+
log,
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
else if (input.workspaceKind !== "directory") {
|
|
3347
|
+
await delegateFailure.persistFailedInPlaceWorkProduct({
|
|
3348
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode },
|
|
3349
|
+
live: input.inPlace === true,
|
|
3350
|
+
run,
|
|
3351
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
3352
|
+
attempts: attempt,
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3364
3355
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null);
|
|
3365
3356
|
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), failure.metadata);
|
|
3366
3357
|
}
|
|
3358
|
+
if (input.workspaceKind === "directory" && run.secretDiffRefusal) {
|
|
3359
|
+
return failedCandidatesResult({
|
|
3360
|
+
ledger,
|
|
3361
|
+
mode,
|
|
3362
|
+
store,
|
|
3363
|
+
paths,
|
|
3364
|
+
log,
|
|
3365
|
+
runId,
|
|
3366
|
+
taskId,
|
|
3367
|
+
runs: [run],
|
|
3368
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null),
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3367
3371
|
// D-16 r8: interrupted (errored===false) would CONVERGE a partial diff
|
|
3368
3372
|
// as clean — break BEFORE review; a harness error still gate-retries.
|
|
3369
3373
|
if (run.outcomeClass === "interrupted") {
|
|
@@ -3373,7 +3377,7 @@ export class Orchestrator {
|
|
|
3373
3377
|
// Post-mutation fence for in-place: snapshot the live tree NOW (after the
|
|
3374
3378
|
// harness mutated it, before this attempt's review). The last attempt's
|
|
3375
3379
|
// value is the revert target persisted into work_product.yaml.
|
|
3376
|
-
if (input.inPlace === true) {
|
|
3380
|
+
if (input.inPlace === true && input.workspaceKind !== "directory") {
|
|
3377
3381
|
try {
|
|
3378
3382
|
lastPostTurnSha = await snapshotTree(execRoot);
|
|
3379
3383
|
}
|
|
@@ -3394,148 +3398,43 @@ export class Orchestrator {
|
|
|
3394
3398
|
log.emit("review.skipped", { reason: "not_requested", attempt_id: attemptId });
|
|
3395
3399
|
return evaluateUnreviewedConvergence(evidence, contract);
|
|
3396
3400
|
}
|
|
3397
|
-
const
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
reviewers,
|
|
3421
|
-
envInheritance: envInheritance(this.config(input.repoRoot)),
|
|
3422
|
-
signal: input.signal,
|
|
3423
|
-
onReviewerEvent: (event) => log.emit(event.type, { ...event }),
|
|
3424
|
-
})
|
|
3425
|
-
: {
|
|
3426
|
-
findings: [],
|
|
3427
|
-
routeProofs: [],
|
|
3428
|
-
reviewerRequests: [],
|
|
3429
|
-
crossFamilyHealthy: false,
|
|
3430
|
-
healthyProviders: [],
|
|
3431
|
-
crossFamilyVerified: false,
|
|
3432
|
-
distinctProviders: [],
|
|
3433
|
-
reviewSpendUsd: 0,
|
|
3434
|
-
reviewSpendEstimated: false,
|
|
3435
|
-
reviewCashUsd: 0,
|
|
3436
|
-
reviewCashKnowledge: "unknown",
|
|
3437
|
-
reviewValuationUsd: 0,
|
|
3438
|
-
reviewValuationKnowledge: "unknown",
|
|
3439
|
-
reviewUnknownUsd: 0,
|
|
3440
|
-
};
|
|
3441
|
-
if (reviewLease?.granted) {
|
|
3442
|
-
ledger.settle(reviewLease.lease?.lease_id ?? "", reviewUsageCostSettlement(reviewResult.reviewCashUsd, reviewResult.reviewValuationUsd, {
|
|
3443
|
-
cash: reviewResult.reviewCashKnowledge,
|
|
3444
|
-
valuation: reviewResult.reviewValuationKnowledge,
|
|
3445
|
-
}, [`attempt:${attemptId}`, "review:panel"], reviewResult.reviewUnknownUsd));
|
|
3446
|
-
if ((reviewResult.reviewSpendUsd ?? 0) > 0) {
|
|
3447
|
-
log.emit("budget.observation", {
|
|
3448
|
-
harness_id: "review-panel",
|
|
3449
|
-
attempt_id: attemptId,
|
|
3450
|
-
kind: "spend",
|
|
3451
|
-
usd: reviewResult.reviewSpendUsd,
|
|
3452
|
-
cash_usd: reviewResult.reviewCashUsd,
|
|
3453
|
-
valuation_usd: reviewResult.reviewValuationUsd,
|
|
3454
|
-
unknown_usd: reviewResult.reviewUnknownUsd,
|
|
3455
|
-
estimated: reviewResult.reviewSpendEstimated === true,
|
|
3456
|
-
});
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
else if (reviewLease && !reviewLease.granted) {
|
|
3460
|
-
log.emit("budget.lease.created", {
|
|
3461
|
-
granted: false,
|
|
3462
|
-
reason: reviewLease.reason,
|
|
3463
|
-
attempt_id: attemptId,
|
|
3464
|
-
harness_id: "review-panel",
|
|
3465
|
-
});
|
|
3466
|
-
}
|
|
3467
|
-
actualReviewVerified =
|
|
3468
|
-
reviewVerified &&
|
|
3469
|
-
reviewResult.crossFamilyHealthy &&
|
|
3470
|
-
reviewResult.crossFamilyVerified;
|
|
3471
|
-
const revalidated = await revalidateFindings(reviewResult.findings, {
|
|
3472
|
-
candidateRoot: candidateReviewCwd,
|
|
3473
|
-
evidenceDir: candidateReviewEvidenceDir,
|
|
3474
|
-
});
|
|
3475
|
-
// Typed policy gate (risk + protected paths) merges with reviewer findings.
|
|
3476
|
-
const policy = policyFindings(run, actualReviewVerified, contract.constraints.protected_paths, contract.constraints.auto_protected_paths, contract.constraints.protected_path_approvals, contract.constraints.deny_paths);
|
|
3477
|
-
const allFindings = [...policy.findings, ...revalidated];
|
|
3478
|
-
lastFindings = allFindings;
|
|
3479
|
-
const inconclusive = allFindings.some((f) => f.severity === "INSUFFICIENT_EVIDENCE" || f.status === "insufficient_evidence");
|
|
3480
|
-
const finalReviewClean = reviewResult.crossFamilyHealthy &&
|
|
3481
|
-
reviewResult.crossFamilyVerified &&
|
|
3482
|
-
!inconclusive &&
|
|
3483
|
-
!allFindings.some((f) => isBlocking(f));
|
|
3484
|
-
store.writeYaml(join(paths.reviewsDir, `${attemptId}.yaml`), {
|
|
3485
|
-
attempt_id: attemptId,
|
|
3486
|
-
review_verified: actualReviewVerified,
|
|
3487
|
-
final_review_clean: finalReviewClean,
|
|
3488
|
-
cross_family_healthy: reviewResult.crossFamilyHealthy,
|
|
3489
|
-
cross_family_verified: reviewResult.crossFamilyVerified,
|
|
3490
|
-
healthy_providers: reviewResult.healthyProviders,
|
|
3491
|
-
verified_providers: reviewResult.distinctProviders,
|
|
3492
|
-
reviewer_requests: reviewResult.reviewerRequests,
|
|
3493
|
-
risk: policy.risk,
|
|
3494
|
-
findings: allFindings,
|
|
3495
|
-
route_proofs: reviewResult.routeProofs,
|
|
3496
|
-
});
|
|
3497
|
-
lastFinalReviewClean = finalReviewClean;
|
|
3498
|
-
// Measure diff stability instead of asserting it: the tree must not have
|
|
3499
|
-
// changed between the candidate diff capture and the end of review.
|
|
3500
|
-
const postReviewDiff = await wsm.diff(envelope);
|
|
3501
|
-
const diffStableAfterReview = sha256(postReviewDiff) === sha256(run.diff);
|
|
3502
|
-
lastDiffStable = diffStableAfterReview;
|
|
3503
|
-
const evaluated = evaluateConvergence({
|
|
3504
|
-
predicate: contract.convergence,
|
|
3505
|
-
gates: run.errored
|
|
3506
|
-
? [
|
|
3507
|
-
...run.gates,
|
|
3508
|
-
{
|
|
3509
|
-
id: "harness",
|
|
3510
|
-
command: "harness",
|
|
3511
|
-
exit_code: 1,
|
|
3512
|
-
status: "failed",
|
|
3513
|
-
duration_ms: 0,
|
|
3514
|
-
required: true,
|
|
3515
|
-
stdout_tail: null,
|
|
3516
|
-
stderr_tail: null,
|
|
3517
|
-
output_truncated: false,
|
|
3518
|
-
},
|
|
3519
|
-
]
|
|
3520
|
-
: run.gates,
|
|
3521
|
-
findings: allFindings,
|
|
3522
|
-
finalReviewClean,
|
|
3523
|
-
diffStableAfterReview,
|
|
3524
|
-
});
|
|
3525
|
-
log.emit("finding.revalidated", {
|
|
3526
|
-
attempt_id: attemptId,
|
|
3527
|
-
converged: evaluated.converged,
|
|
3528
|
-
reasons: evaluated.reasons,
|
|
3529
|
-
diff_stable_after_review: diffStableAfterReview,
|
|
3530
|
-
});
|
|
3531
|
-
return evaluated;
|
|
3532
|
-
}
|
|
3533
|
-
finally {
|
|
3534
|
-
this.recordReviewEvidenceCleanup(store, join(paths.reviewsDir, `${attemptId}-evidence-cleanup.yaml`), attemptId, candidateReviewEvidenceDir, candidateReviewCwd);
|
|
3535
|
-
}
|
|
3401
|
+
const [evidence] = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), true);
|
|
3402
|
+
if (!evidence)
|
|
3403
|
+
throw new Error("Convergence review produced no evidence");
|
|
3404
|
+
lastFindings = evidence.findings;
|
|
3405
|
+
lastFinalReviewClean = evidence.finalReviewClean;
|
|
3406
|
+
actualReviewVerified = evidence.reviewVerified ?? false;
|
|
3407
|
+
lastDiffStable = run.files
|
|
3408
|
+
? await directoryCandidateStable(wsm, envelope, run.files)
|
|
3409
|
+
: sha256(await wsm.diff(envelope)) === sha256(run.diff);
|
|
3410
|
+
const evaluated = evaluateConvergence({
|
|
3411
|
+
predicate: contract.convergence,
|
|
3412
|
+
gates: evidence.gates,
|
|
3413
|
+
findings: evidence.findings,
|
|
3414
|
+
finalReviewClean: evidence.finalReviewClean,
|
|
3415
|
+
diffStableAfterReview: lastDiffStable,
|
|
3416
|
+
});
|
|
3417
|
+
log.emit("finding.revalidated", {
|
|
3418
|
+
attempt_id: attemptId,
|
|
3419
|
+
converged: evaluated.converged,
|
|
3420
|
+
reasons: evaluated.reasons,
|
|
3421
|
+
diff_stable_after_review: lastDiffStable,
|
|
3422
|
+
});
|
|
3423
|
+
return evaluated;
|
|
3536
3424
|
})();
|
|
3537
3425
|
}
|
|
3538
3426
|
catch (err) {
|
|
3427
|
+
if (run.files)
|
|
3428
|
+
await publishDirectoryCandidate({
|
|
3429
|
+
files: run.files,
|
|
3430
|
+
store,
|
|
3431
|
+
paths,
|
|
3432
|
+
taskId,
|
|
3433
|
+
attemptId,
|
|
3434
|
+
harnessId: run.harnessId,
|
|
3435
|
+
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3436
|
+
log,
|
|
3437
|
+
});
|
|
3539
3438
|
return failTerminally(log, store, paths, runId, taskId, mode, "review", err, ledger.spend());
|
|
3540
3439
|
}
|
|
3541
3440
|
if (conv.converged) {
|
|
@@ -3543,13 +3442,17 @@ export class Orchestrator {
|
|
|
3543
3442
|
break;
|
|
3544
3443
|
}
|
|
3545
3444
|
const requiredGateFailing = run.gates.length > 0 && !gatesPassed(run.gates);
|
|
3546
|
-
const diffHash =
|
|
3547
|
-
|
|
3445
|
+
const diffHash = run.files
|
|
3446
|
+
? run.files.manifestSha256
|
|
3447
|
+
: input.workspaceKind === "directory"
|
|
3448
|
+
? null
|
|
3449
|
+
: sha256(run.diff);
|
|
3450
|
+
if (requiredGateFailing && diffHash !== null && diffHash === lastFailingGateDiffHash) {
|
|
3548
3451
|
sameFailingGateDiffs += 1;
|
|
3549
3452
|
}
|
|
3550
3453
|
else {
|
|
3551
3454
|
sameFailingGateDiffs = requiredGateFailing ? 1 : 0;
|
|
3552
|
-
lastFailingGateDiffHash = requiredGateFailing ? diffHash : "";
|
|
3455
|
+
lastFailingGateDiffHash = requiredGateFailing ? (diffHash ?? "") : "";
|
|
3553
3456
|
}
|
|
3554
3457
|
if (sameFailingGateDiffs >= 2) {
|
|
3555
3458
|
stuckNoProgress = true;
|
|
@@ -3565,7 +3468,10 @@ export class Orchestrator {
|
|
|
3565
3468
|
});
|
|
3566
3469
|
break;
|
|
3567
3470
|
}
|
|
3568
|
-
const sig = failureSignature(
|
|
3471
|
+
const sig = failureSignature([
|
|
3472
|
+
...conv.reasons,
|
|
3473
|
+
...(run.files ? [`files_manifest:${run.files.manifestSha256}`] : []),
|
|
3474
|
+
]);
|
|
3569
3475
|
readiness.recordRound(sig, conv.reasons.join("; "));
|
|
3570
3476
|
if (sig !== lastSig) {
|
|
3571
3477
|
triedSinceProgress = new Set();
|
|
@@ -3648,6 +3554,12 @@ export class Orchestrator {
|
|
|
3648
3554
|
facts = decision.facts;
|
|
3649
3555
|
}
|
|
3650
3556
|
}
|
|
3557
|
+
if (lastRun?.files)
|
|
3558
|
+
facts = {
|
|
3559
|
+
...facts,
|
|
3560
|
+
noChanges: lastRun.files.noChanges,
|
|
3561
|
+
reason: facts.reason === "no_changes" && lastRun.files.noChanges !== true ? null : facts.reason,
|
|
3562
|
+
};
|
|
3651
3563
|
// A budget terminal turns a succeeded lifecycle into a failed one (D8).
|
|
3652
3564
|
const convBudgetTerminal = ledger.terminal();
|
|
3653
3565
|
if (facts.lifecycle === "succeeded" && convBudgetTerminal) {
|
|
@@ -3671,11 +3583,13 @@ export class Orchestrator {
|
|
|
3671
3583
|
let convFinalVerify = null;
|
|
3672
3584
|
if (input.inPlace !== true &&
|
|
3673
3585
|
lastRun &&
|
|
3674
|
-
lastRun.diff.trim().length > 0 &&
|
|
3586
|
+
(lastRun.files ? lastRun.files.noChanges !== true : lastRun.diff.trim().length > 0) &&
|
|
3675
3587
|
facts.lifecycle === "succeeded" &&
|
|
3676
3588
|
facts.review !== "blocked" &&
|
|
3677
3589
|
!input.signal?.aborted) {
|
|
3678
|
-
convFinalVerify =
|
|
3590
|
+
convFinalVerify = lastRun.files
|
|
3591
|
+
? await finalVerifyFiles(lastRun.files, undefined, gateSpecsFromContract(contract), log)
|
|
3592
|
+
: await finalVerifyPatch(execRoot, lastRun, gateSpecsFromContract(contract), log);
|
|
3679
3593
|
if (finalVerifyBlocks(convFinalVerify))
|
|
3680
3594
|
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
3681
3595
|
}
|
|
@@ -3694,51 +3608,68 @@ export class Orchestrator {
|
|
|
3694
3608
|
}
|
|
3695
3609
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, lastRun?.attemptId ?? null);
|
|
3696
3610
|
// Deliver the converged/last work to final/ so `apply` and `inspect` can
|
|
3697
|
-
// use it.
|
|
3698
|
-
//
|
|
3699
|
-
|
|
3700
|
-
if (lastRun && (!interrupted || input.inPlace === true)) {
|
|
3701
|
-
secretDiff.assertNoSecretLikeTokens("final patch diff", lastRun.diff);
|
|
3702
|
-
const patchSha256 = sha256(lastRun.diff);
|
|
3703
|
-
store.writeText(join(paths.finalDir, "patch.diff"), lastRun.diff);
|
|
3704
|
-
// Honest apply-state (parity with runRace single-candidate in-place): a
|
|
3705
|
-
// convergence run with inPlace mutated the live tree directly across its
|
|
3706
|
-
// attempts, so it is "applied" even when review blocked (Revert offered).
|
|
3707
|
-
const convHasDiff = lastRun.diff.trim().length > 0;
|
|
3611
|
+
// use it. Directory results retain complete partial files with their actual
|
|
3612
|
+
// lifecycle; direct effects are already applied and promise no rollback.
|
|
3613
|
+
if (lastRun && (lastRun.files || !interrupted || input.inPlace === true)) {
|
|
3708
3614
|
const convAdoptable = facts.lifecycle === "succeeded" &&
|
|
3709
3615
|
reviewAllowsApply(facts) &&
|
|
3710
3616
|
facts.checks !== "failed" &&
|
|
3711
3617
|
!workStateVetoes(facts);
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3618
|
+
if (lastRun.files) {
|
|
3619
|
+
await publishDirectoryCandidate({
|
|
3620
|
+
files: lastRun.files,
|
|
3621
|
+
store,
|
|
3622
|
+
paths,
|
|
3623
|
+
taskId,
|
|
3624
|
+
attemptId: lastRun.attemptId,
|
|
3625
|
+
harnessId: lastRun.harnessId,
|
|
3626
|
+
facts,
|
|
3627
|
+
log,
|
|
3628
|
+
});
|
|
3629
|
+
if (lastRun.answerText) {
|
|
3630
|
+
store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
|
|
3631
|
+
log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
else {
|
|
3635
|
+
secretDiff.assertNoSecretLikeTokens("final patch diff", lastRun.diff);
|
|
3636
|
+
const patchSha256 = sha256(lastRun.diff);
|
|
3637
|
+
store.writeText(join(paths.finalDir, "patch.diff"), lastRun.diff);
|
|
3638
|
+
// Honest apply-state (parity with runRace single-candidate in-place): a
|
|
3639
|
+
// convergence run with inPlace mutated the live tree directly across its
|
|
3640
|
+
// attempts, so it is "applied" even when review blocked (Revert offered).
|
|
3641
|
+
const convHasDiff = lastRun.diff.trim().length > 0;
|
|
3642
|
+
const convAdopted = input.inPlace === true && convHasDiff ? true : null;
|
|
3643
|
+
const convApplyState = convAdopted === true
|
|
3644
|
+
? convAdoptable
|
|
3645
|
+
? "applied"
|
|
3646
|
+
: "applied_review_blocked"
|
|
3647
|
+
: "not_applied";
|
|
3648
|
+
const revertAnchorId = convAdopted === true
|
|
3649
|
+
? await createRevertAnchorOrNull(execRoot, preTurnSha, lastPostTurnSha)
|
|
3650
|
+
: null;
|
|
3651
|
+
store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
|
|
3652
|
+
id: newId("wp"),
|
|
3653
|
+
kind: "patch",
|
|
3654
|
+
source_task_id: taskId,
|
|
3655
|
+
producer_attempt_id: lastRun.attemptId,
|
|
3656
|
+
meta: {
|
|
3657
|
+
harness_id: lastRun.harnessId,
|
|
3658
|
+
result_kind: "patch",
|
|
3659
|
+
mode,
|
|
3660
|
+
attempts: attempt,
|
|
3661
|
+
lifecycle: facts.lifecycle,
|
|
3662
|
+
outcome_facts: facts,
|
|
3663
|
+
review_verified: actualReviewVerified,
|
|
3664
|
+
patch_sha256: patchSha256,
|
|
3665
|
+
adopted: convAdopted,
|
|
3666
|
+
apply_state: convApplyState,
|
|
3667
|
+
pre_turn_sha: convAdopted === true ? preTurnSha : null,
|
|
3668
|
+
post_turn_sha: convAdopted === true ? lastPostTurnSha : null,
|
|
3669
|
+
revert_anchor_id: revertAnchorId,
|
|
3670
|
+
},
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3742
3673
|
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}\n`);
|
|
3743
3674
|
// Lifecycle invariant (all modes): output.ready precedes the terminal
|
|
3744
3675
|
// event so a client that applied the terminal event has the output.
|
|
@@ -3757,6 +3688,9 @@ export class Orchestrator {
|
|
|
3757
3688
|
const convDeclared = !convNeedsDecision && facts.lifecycle === "failed" && !isBudgetTerminal(facts.reason)
|
|
3758
3689
|
? lastRun?.declaredFailure
|
|
3759
3690
|
: undefined;
|
|
3691
|
+
const processingBudgetMapping = processingBudgetDenial
|
|
3692
|
+
? classifyBudgetFailure({ denial: processingBudgetDenial, terminal: ledger.terminal() })
|
|
3693
|
+
: null;
|
|
3760
3694
|
writeFailure(store, paths, {
|
|
3761
3695
|
phase: convNeedsDecision ? "review" : "convergence",
|
|
3762
3696
|
category: isBudgetTerminal(facts.reason)
|
|
@@ -3766,7 +3700,7 @@ export class Orchestrator {
|
|
|
3766
3700
|
: convNeedsDecision
|
|
3767
3701
|
? "policy"
|
|
3768
3702
|
: (convDeclared?.category ?? "internal"),
|
|
3769
|
-
code: convDeclared?.code ?? null,
|
|
3703
|
+
code: processingBudgetMapping?.code ?? convDeclared?.code ?? null,
|
|
3770
3704
|
resetsAt: convDeclared?.resetsAt ?? null,
|
|
3771
3705
|
safeMessage: convNeedsDecision
|
|
3772
3706
|
? `review escalated to a human decision after ${attempt} attempt(s)`
|
|
@@ -3799,12 +3733,15 @@ export class Orchestrator {
|
|
|
3799
3733
|
"Inspect latest patch and review findings",
|
|
3800
3734
|
"Retry with more attempts or a narrower prompt",
|
|
3801
3735
|
],
|
|
3736
|
+
...(processingBudgetMapping
|
|
3737
|
+
? budgetFailureRecord(processingBudgetMapping, { runDir: paths.root })
|
|
3738
|
+
: {}),
|
|
3802
3739
|
});
|
|
3803
3740
|
// D-16 r8/r9: an INTERRUPTED envelope run still gets its diagnostic
|
|
3804
3741
|
// summary + output.ready (only patch/work_product are withheld) — the
|
|
3805
3742
|
// ARCHITECTURE event contract guarantees output.ready precedes the
|
|
3806
3743
|
// terminal in every mode.
|
|
3807
|
-
if (!lastRun || (interrupted && input.inPlace !== true)) {
|
|
3744
|
+
if (!lastRun || (interrupted && input.inPlace !== true && !lastRun.files)) {
|
|
3808
3745
|
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n`);
|
|
3809
3746
|
log.emit("output.ready", {
|
|
3810
3747
|
kind: "summary",
|
|
@@ -3814,7 +3751,7 @@ export class Orchestrator {
|
|
|
3814
3751
|
}
|
|
3815
3752
|
}
|
|
3816
3753
|
// work_product.emitted only when a product was actually written (r9).
|
|
3817
|
-
if (lastRun && (!interrupted || input.inPlace === true)) {
|
|
3754
|
+
if (lastRun && (lastRun.files || !interrupted || input.inPlace === true)) {
|
|
3818
3755
|
log.emit("work_product.emitted", { winner: lastRun.attemptId });
|
|
3819
3756
|
}
|
|
3820
3757
|
if (!convIsFailureTerminal) {
|
|
@@ -4226,16 +4163,19 @@ export class Orchestrator {
|
|
|
4226
4163
|
// A selected profile's credential_kind decides billing (round-18 #2).
|
|
4227
4164
|
const profileRoute = this.credentials.profileAuthRoute(input, harnessId);
|
|
4228
4165
|
if (profileRoute)
|
|
4229
|
-
return profileRoute === "api_key" ? "metered" : "
|
|
4166
|
+
return profileRoute === "api_key" ? "metered" : "subscription_entitlement";
|
|
4230
4167
|
// Deps-closure site: no selected route exists yet, so the RESOLVED
|
|
4231
4168
|
// preference (per-run > per-harness config > global) speaks — never the
|
|
4232
4169
|
// raw run input (#121).
|
|
4233
4170
|
const mode = authModeForPreference(this.authPreferenceForHarness(input.repoRoot, harnessId, input.authPreference));
|
|
4234
4171
|
if (mode)
|
|
4235
|
-
return mode === "api_key" ? "metered" : "
|
|
4236
|
-
|
|
4172
|
+
return mode === "api_key" ? "metered" : "subscription_entitlement";
|
|
4173
|
+
const lastAuth = loadHarnessMetrics(globalConfigDir())[harnessId]?.last_auth_mode;
|
|
4174
|
+
return lastAuth === "api_key"
|
|
4237
4175
|
? "metered"
|
|
4238
|
-
: "
|
|
4176
|
+
: lastAuth === "local_session"
|
|
4177
|
+
? "subscription_entitlement"
|
|
4178
|
+
: "unknown";
|
|
4239
4179
|
}
|
|
4240
4180
|
/**
|
|
4241
4181
|
* #27 / D-6: build the engine-side deps closure for the deep-scan bounded
|
|
@@ -4247,11 +4187,13 @@ export class Orchestrator {
|
|
|
4247
4187
|
deepScanReducerDeps(input, contract, log) {
|
|
4248
4188
|
return {
|
|
4249
4189
|
newReadOnlyHome: () => resolveReadOnlyRouteContext(this.execRootOf(input)),
|
|
4250
|
-
costEvidence: (harnessId, attemptId) =>
|
|
4190
|
+
costEvidence: (harnessId, attemptId, routed) =>
|
|
4251
4191
|
// The reducer admits under a finite estimate floor (mirror of the n>1
|
|
4252
4192
|
// scout reserve) so a subscription route is not refused for lacking a
|
|
4253
4193
|
// cash quote.
|
|
4254
|
-
attemptCostEvidence(harnessId, attemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, harnessId)),
|
|
4194
|
+
attemptCostEvidence(harnessId, attemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, harnessId), processingCostEvidence(routed?.processing, this.routeBillingKnowledge(input, harnessId), [
|
|
4195
|
+
`harness:${harnessId}`,
|
|
4196
|
+
])),
|
|
4255
4197
|
buildSpec: async (routed, homeEnv, prompt, attemptId) => {
|
|
4256
4198
|
const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
|
|
4257
4199
|
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
@@ -4434,7 +4376,7 @@ export class Orchestrator {
|
|
|
4434
4376
|
// (mirror of the candidate loop): the first top-level scout reserves
|
|
4435
4377
|
// without a floor; later scouts and every real Delegate child pass the
|
|
4436
4378
|
// repo floor because they overlap an existing family unit.
|
|
4437
|
-
cost: attemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input, opts.deepScan && idx > 0), this.routeBillingKnowledge(input, adapter.id)),
|
|
4379
|
+
cost: attemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input, opts.deepScan && idx > 0), this.routeBillingKnowledge(input, adapter.id), processingCostEvidence(routed.processing, this.routeBillingKnowledge(input, routed.adapter.id), [`harness:${adapter.id}`])),
|
|
4438
4380
|
});
|
|
4439
4381
|
if (!lease.granted) {
|
|
4440
4382
|
log.emit("budget.lease.created", {
|
|
@@ -4580,6 +4522,10 @@ export class Orchestrator {
|
|
|
4580
4522
|
const { knobs, spec: preparedSpec, reportAbort, reportInteraction, readonlyWorkMode, attemptEventsPath, telemetry, } = preparation.value;
|
|
4581
4523
|
let { answer } = preparation.value;
|
|
4582
4524
|
let spec = preparedSpec;
|
|
4525
|
+
bindProcessingAdmission(spec, ledger, lease.lease.lease_id, adapter.id, attemptId, (denial) => {
|
|
4526
|
+
budgetStopped = true;
|
|
4527
|
+
budgetDenial ??= denial;
|
|
4528
|
+
});
|
|
4583
4529
|
const retryPolicy = transientRetryPolicy(this.config(input.repoRoot));
|
|
4584
4530
|
let activeSessionId = spec.session_id;
|
|
4585
4531
|
const onAbort = () => {
|
|
@@ -4594,6 +4540,8 @@ export class Orchestrator {
|
|
|
4594
4540
|
let cost = 0;
|
|
4595
4541
|
let costEstimated = false;
|
|
4596
4542
|
let harnessError = null;
|
|
4543
|
+
let streamBudgetDenied = false;
|
|
4544
|
+
let processingRefusal = null;
|
|
4597
4545
|
let poolExhausted = null; // A5: typed pool-exhausted refusal
|
|
4598
4546
|
try {
|
|
4599
4547
|
const triedProfiles = new Set(); // W5.4 failover: each profile at most once
|
|
@@ -4659,6 +4607,15 @@ export class Orchestrator {
|
|
|
4659
4607
|
const spend = observeReadonlySpend(safeEv, (t, p) => log.emit(t, p), adapter.id, attemptId);
|
|
4660
4608
|
cost += spend.costUsd;
|
|
4661
4609
|
costEstimated ||= spend.estimated;
|
|
4610
|
+
const streamDenial = updateProcessingStreamHold(runSpec, telemetry.usageCost, ledger, lease.lease.lease_id, adapter.id, attemptId);
|
|
4611
|
+
if (streamDenial) {
|
|
4612
|
+
budgetStopped = streamBudgetDenied = true;
|
|
4613
|
+
budgetDenial ??= streamDenial;
|
|
4614
|
+
harnessError = streamDenial.reason;
|
|
4615
|
+
reportAbort.abort();
|
|
4616
|
+
void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4617
|
+
break;
|
|
4618
|
+
}
|
|
4662
4619
|
// A TYPED final message wins verbatim over joined narration.
|
|
4663
4620
|
answer.observe(safeEv);
|
|
4664
4621
|
if (safeEv.type === "error")
|
|
@@ -4669,9 +4626,15 @@ export class Orchestrator {
|
|
|
4669
4626
|
}
|
|
4670
4627
|
catch (err) {
|
|
4671
4628
|
harnessError = safeErrorMessage(err);
|
|
4629
|
+
if (err instanceof ProcessingBudgetAdmissionError) {
|
|
4630
|
+
processingRefusal = err;
|
|
4631
|
+
break;
|
|
4632
|
+
}
|
|
4672
4633
|
// #31: classify the throw (watchdog timeout vs process crash) as typed.
|
|
4673
4634
|
telemetry.transientFailures.push(classifyAdapterThrow({ errorName: err instanceof Error ? err.name : null }));
|
|
4674
4635
|
}
|
|
4636
|
+
if (streamBudgetDenied)
|
|
4637
|
+
break;
|
|
4675
4638
|
const newTransients = telemetry.transientFailures.slice(transientStart);
|
|
4676
4639
|
const transient = newTransients.at(-1) ?? null;
|
|
4677
4640
|
const sawRetryable = newTransients.some((f) => f.retryable);
|
|
@@ -4748,7 +4711,7 @@ export class Orchestrator {
|
|
|
4748
4711
|
preStreamFailureSource: "readonly-pre-stream",
|
|
4749
4712
|
});
|
|
4750
4713
|
}
|
|
4751
|
-
if (harnessError && !poolExhausted) {
|
|
4714
|
+
if (harnessError && !poolExhausted && !processingRefusal) {
|
|
4752
4715
|
emitTransientExhausted((t, p) => log.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
|
|
4753
4716
|
}
|
|
4754
4717
|
attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
|
|
@@ -4809,7 +4772,9 @@ export class Orchestrator {
|
|
|
4809
4772
|
report,
|
|
4810
4773
|
error: harnessError,
|
|
4811
4774
|
telemetry,
|
|
4812
|
-
...(poolExhausted
|
|
4775
|
+
...(poolExhausted || processingRefusal
|
|
4776
|
+
? { declaredFailure: declaredFailure(processingRefusal ?? poolExhausted) }
|
|
4777
|
+
: {}),
|
|
4813
4778
|
});
|
|
4814
4779
|
if (opts.deepScan) {
|
|
4815
4780
|
store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
|
|
@@ -5062,7 +5027,7 @@ export class Orchestrator {
|
|
|
5062
5027
|
});
|
|
5063
5028
|
const terminalFacts = makeOutcomeFacts(roTerminal.lifecycle, {
|
|
5064
5029
|
...(roTerminal.review ? { review: roTerminal.review } : {}),
|
|
5065
|
-
reason: roTerminal.reason,
|
|
5030
|
+
reason: budgetMapping?.reason ?? roTerminal.reason,
|
|
5066
5031
|
});
|
|
5067
5032
|
const terminalHarnessId = budgetMapping?.harnessId ?? last?.harnessId;
|
|
5068
5033
|
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harness: ${terminalHarnessId ?? "none"}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}\n\n${singleError}\n`);
|
|
@@ -5254,19 +5219,12 @@ export class Orchestrator {
|
|
|
5254
5219
|
});
|
|
5255
5220
|
store.writeText(join(paths.finalDir, "omissions.md"), `# Omissions\n\n${unsuccessful.map((a) => `- ${a.attemptId} / ${a.harnessId} (${a.status}): ${a.error}`).join("\n") || "- None recorded by the runner. Synthesis claims still require evidence checks."}\n`);
|
|
5256
5221
|
}
|
|
5257
|
-
// A read-only report (ask / deep-scan) has no live-tree work; the only
|
|
5258
|
-
// non-clean terminal is an aggregate paid-budget stop.
|
|
5259
5222
|
let terminalFacts = makeOutcomeFacts("succeeded");
|
|
5260
5223
|
const reportBudgetTerminal = ledger.terminal();
|
|
5261
5224
|
if (reportBudgetTerminal) {
|
|
5262
5225
|
terminalFacts = makeOutcomeFacts("failed", { reason: reportBudgetTerminal });
|
|
5263
5226
|
}
|
|
5264
5227
|
else if (!opts.deepScan) {
|
|
5265
|
-
// D-16: fold the winning read-only attempt's work_state into the terminal.
|
|
5266
|
-
// A terminal context exhaustion with no completed report ⇒ interrupted;
|
|
5267
|
-
// a needs_input/incomplete report ⇒ a succeeded run whose work_state
|
|
5268
|
-
// vetoes applyability and a clean exit (INV-116). answer.md was already
|
|
5269
|
-
// persisted from the unwrapped OUTPUT.
|
|
5270
5228
|
const winnerTelemetry = succeeded[0]?.telemetry;
|
|
5271
5229
|
const winnerWorkState = winnerTelemetry?.outcome?.workState;
|
|
5272
5230
|
if (winnerTelemetry?.contextExhausted && winnerWorkState?.state !== "completed") {
|
|
@@ -5327,9 +5285,6 @@ export class Orchestrator {
|
|
|
5327
5285
|
});
|
|
5328
5286
|
}
|
|
5329
5287
|
else if (workVetoed) {
|
|
5330
|
-
// D-16: a succeeded lifecycle whose work_state vetoes is a needs-me
|
|
5331
|
-
// terminal — run.blocked (not run.completed); the outcome-aware exit
|
|
5332
|
-
// projection returns non-zero from the same facts.
|
|
5333
5288
|
log.emit("run.blocked", {
|
|
5334
5289
|
lifecycle: terminalFacts.lifecycle,
|
|
5335
5290
|
facts: terminalFacts,
|