@claudexor/orchestrator 3.9.7 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-resolution.d.ts +1 -1
- package/dist/account-resolution.d.ts.map +1 -1
- package/dist/account-resolution.js +8 -3
- package/dist/account-resolution.js.map +1 -1
- package/dist/candidateReview.d.ts +41 -0
- package/dist/candidateReview.d.ts.map +1 -0
- package/dist/candidateReview.js +200 -0
- package/dist/candidateReview.js.map +1 -0
- package/dist/credential-cooldown.d.ts.map +1 -1
- package/dist/credential-cooldown.js +28 -3
- package/dist/credential-cooldown.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator.d.ts +2 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +74 -140
- package/dist/orchestrator.js.map +1 -1
- package/dist/policyFindings.d.ts +1 -1
- package/dist/policyFindings.d.ts.map +1 -1
- package/dist/policyFindings.js +2 -2
- package/dist/policyFindings.js.map +1 -1
- package/dist/runFactsBuilder.d.ts.map +1 -1
- package/dist/runFactsBuilder.js +3 -0
- package/dist/runFactsBuilder.js.map +1 -1
- package/dist/runFactsReview.d.ts.map +1 -1
- package/dist/runFactsReview.js +5 -1
- package/dist/runFactsReview.js.map +1 -1
- package/dist/runSupport.d.ts +2 -1
- package/dist/runSupport.d.ts.map +1 -1
- package/dist/runSupport.js +3 -3
- package/dist/runSupport.js.map +1 -1
- package/dist/task-contract-builder.d.ts +1 -0
- package/dist/task-contract-builder.d.ts.map +1 -1
- package/dist/task-contract-builder.js +10 -0
- package/dist/task-contract-builder.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -10,10 +10,11 @@ import * as AC from "./attemptUsageCost.js";
|
|
|
10
10
|
import { candidateRoster, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, unanimousDeclaredFailure, } from "./candidateEvidence.js";
|
|
11
11
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
12
12
|
import { policyFindings } from "./policyFindings.js";
|
|
13
|
+
import { reviewCandidateRuns, resolveEngineReview, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
import { finalizePlanRun, runCouncilPlan, writePlanHarnessFailure, } from "./planRun.js";
|
|
15
16
|
import { runPlannerAttempt as executePlannerAttempt, } from "./plannerAttempt.js";
|
|
16
|
-
import { HarnessRunSpec, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
17
|
+
import { HarnessRunSpec, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, reviewAllowsApply, workStateVetoes, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
17
18
|
import { globalConfigDir, loadConfig } from "@claudexor/config";
|
|
18
19
|
import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
19
20
|
import { assertRouteModelsAllowed, runModelGovernedRoute } from "./modelGovernance.js";
|
|
@@ -115,6 +116,8 @@ export class Orchestrator {
|
|
|
115
116
|
throw new Error(`unknown mode: ${String(resolved.mode)}`);
|
|
116
117
|
}
|
|
117
118
|
const mode = parsedMode.data;
|
|
119
|
+
if (mode === "agent")
|
|
120
|
+
resolved.review = resolveEngineReview(resolved, this.deps);
|
|
118
121
|
if (resolved.delegate === true && mode !== "agent") {
|
|
119
122
|
throw new Error(`Delegate is an agent-only strategy (got mode=${mode})`);
|
|
120
123
|
}
|
|
@@ -220,6 +223,8 @@ export class Orchestrator {
|
|
|
220
223
|
* pre-announce throw with no artifacts (artifact clause).
|
|
221
224
|
*/
|
|
222
225
|
async resolveReviewersWithArtifacts(input, log, store, paths, runId, taskId, mode) {
|
|
226
|
+
if (input.review === false)
|
|
227
|
+
return { reviewers: [] };
|
|
223
228
|
try {
|
|
224
229
|
// Auto-panel dropped knobs (reviewerEfforts) → ignored-settings channel (QA-070):
|
|
225
230
|
const warn = (d) => void log.emit("review.preflight", { ignored_settings: [d] });
|
|
@@ -1917,11 +1922,12 @@ export class Orchestrator {
|
|
|
1917
1922
|
// themselves inside their envelopes; only the read-only report modes
|
|
1918
1923
|
// (explore/plan/readonly_audit) build and attach the compact atlas.
|
|
1919
1924
|
const reviewDir = join(paths.root, "review-evidence");
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
+
if (contract.review_requested !== false)
|
|
1926
|
+
writeEvidencePacket(reviewDir, {
|
|
1927
|
+
userIntent: redactSecrets(input.prompt),
|
|
1928
|
+
diff: "(per-candidate diffs are supplied to reviewers individually)\n",
|
|
1929
|
+
tests: renderTestsEvidence(contract),
|
|
1930
|
+
});
|
|
1925
1931
|
let adapters;
|
|
1926
1932
|
try {
|
|
1927
1933
|
// Best-of races the whole pool. The `log` is passed so an AUTO pool that
|
|
@@ -2450,7 +2456,11 @@ export class Orchestrator {
|
|
|
2450
2456
|
const configuredFamilies = new Set(reviewers.map((r) => r.providerFamily)).size;
|
|
2451
2457
|
if (reviewableRuns.length === 0 || reviewers.length === 0) {
|
|
2452
2458
|
log.emit("review.skipped", {
|
|
2453
|
-
reason:
|
|
2459
|
+
reason: contract.review_requested === false
|
|
2460
|
+
? "not_requested"
|
|
2461
|
+
: reviewers.length === 0
|
|
2462
|
+
? "no_reviewers"
|
|
2463
|
+
: "no_changes",
|
|
2454
2464
|
reviewable_candidates: reviewableRuns.length,
|
|
2455
2465
|
configured_reviewers: reviewers.length,
|
|
2456
2466
|
configured_provider_families: configuredFamilies,
|
|
@@ -2569,7 +2579,10 @@ export class Orchestrator {
|
|
|
2569
2579
|
}
|
|
2570
2580
|
let result;
|
|
2571
2581
|
try {
|
|
2572
|
-
result = arbitrate(evidences,
|
|
2582
|
+
result = arbitrate(evidences, {
|
|
2583
|
+
...arbitrationBudgetOptions(ledger),
|
|
2584
|
+
reviewRequested: contract.review_requested,
|
|
2585
|
+
});
|
|
2573
2586
|
}
|
|
2574
2587
|
catch (err) {
|
|
2575
2588
|
// Arbitration throws end terminally with artifacts, never as an orphan.
|
|
@@ -2609,7 +2622,11 @@ export class Orchestrator {
|
|
|
2609
2622
|
// budget reason IS a RunReason.
|
|
2610
2623
|
const budgetTerminal = ledger.terminal();
|
|
2611
2624
|
if (facts.lifecycle === "succeeded" && budgetTerminal) {
|
|
2612
|
-
facts = makeOutcomeFacts("failed", {
|
|
2625
|
+
facts = makeOutcomeFacts("failed", {
|
|
2626
|
+
reason: budgetTerminal,
|
|
2627
|
+
noChanges: facts.noChanges,
|
|
2628
|
+
review_requested: contract.review_requested,
|
|
2629
|
+
});
|
|
2613
2630
|
}
|
|
2614
2631
|
// FinalVerifier blocks adoption until the patch and gates pass on a fresh base.
|
|
2615
2632
|
let finalVerify = null;
|
|
@@ -2687,11 +2704,12 @@ export class Orchestrator {
|
|
|
2687
2704
|
? winnerEvidence.findings.filter((f) => isBlocking(f)).length
|
|
2688
2705
|
: 0;
|
|
2689
2706
|
const resultKind = hasDiff ? "patch" : winnerAnswer.length > 0 ? "answer" : "none";
|
|
2690
|
-
//
|
|
2691
|
-
// or needs-decision terminal remains
|
|
2707
|
+
// Adoption honors the frozen review policy and all independent checks.
|
|
2708
|
+
// Unfinished work or a needs-decision terminal remains inspectable.
|
|
2692
2709
|
const adoptable = facts.lifecycle === "succeeded" &&
|
|
2693
|
-
facts
|
|
2694
|
-
facts.checks !== "failed"
|
|
2710
|
+
reviewAllowsApply(facts) &&
|
|
2711
|
+
facts.checks !== "failed" &&
|
|
2712
|
+
!workStateVetoes(facts);
|
|
2695
2713
|
let adopted = null;
|
|
2696
2714
|
let applyState = "not_applied";
|
|
2697
2715
|
let postTurnSha = null;
|
|
@@ -2800,8 +2818,9 @@ export class Orchestrator {
|
|
|
2800
2818
|
kind: "summary",
|
|
2801
2819
|
path: "final/summary.md",
|
|
2802
2820
|
state: (facts.lifecycle === "succeeded" &&
|
|
2803
|
-
facts
|
|
2804
|
-
facts.checks !== "failed"
|
|
2821
|
+
reviewAllowsApply(facts) &&
|
|
2822
|
+
facts.checks !== "failed" &&
|
|
2823
|
+
!workStateVetoes(facts)) ||
|
|
2805
2824
|
winnerAnswer.length > 0
|
|
2806
2825
|
? "ready"
|
|
2807
2826
|
: "diagnostic",
|
|
@@ -2980,124 +2999,26 @@ export class Orchestrator {
|
|
|
2980
2999
|
});
|
|
2981
3000
|
}
|
|
2982
3001
|
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal, reservationEstimateUsd) {
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
: undefined;
|
|
3004
|
-
const result = hasDiff && reviewers.length > 0 && (reviewLease?.granted ?? true)
|
|
3005
|
-
? await this.reviewScoped({
|
|
3006
|
-
candidateLabel: run.label,
|
|
3007
|
-
diff: run.diff,
|
|
3008
|
-
evidenceDir: candidateEvidenceDir,
|
|
3009
|
-
artifactsDir: join(paths.reviewsDir, `${run.attemptId}-reviewers`),
|
|
3010
|
-
cwd: candidateCwd,
|
|
3011
|
-
reviewers,
|
|
3012
|
-
reviewerTimeoutMs: reviewerTimeoutMs(this.config(contract.repo.root)),
|
|
3013
|
-
envInheritance: envInheritance(this.config(cwd)),
|
|
3014
|
-
signal,
|
|
3015
|
-
onReviewerEvent: (event) => log.emit(event.type, { ...event }),
|
|
3016
|
-
})
|
|
3017
|
-
: {
|
|
3018
|
-
findings: [],
|
|
3019
|
-
routeProofs: [],
|
|
3020
|
-
reviewerRequests: [],
|
|
3021
|
-
crossFamilyHealthy: false,
|
|
3022
|
-
healthyProviders: [],
|
|
3023
|
-
crossFamilyVerified: false,
|
|
3024
|
-
distinctProviders: [],
|
|
3025
|
-
reviewSpendUsd: 0,
|
|
3026
|
-
reviewSpendEstimated: false,
|
|
3027
|
-
reviewCashUsd: 0,
|
|
3028
|
-
reviewCashKnowledge: "unknown",
|
|
3029
|
-
reviewValuationUsd: 0,
|
|
3030
|
-
reviewValuationKnowledge: "unknown",
|
|
3031
|
-
reviewUnknownUsd: 0,
|
|
3032
|
-
};
|
|
3033
|
-
if (reviewLease?.granted) {
|
|
3034
|
-
ledger?.settle(reviewLease.lease?.lease_id ?? "", reviewUsageCostSettlement(result.reviewCashUsd, result.reviewValuationUsd, {
|
|
3035
|
-
cash: result.reviewCashKnowledge,
|
|
3036
|
-
valuation: result.reviewValuationKnowledge,
|
|
3037
|
-
}, [`attempt:${run.attemptId}`, "review:panel"], result.reviewUnknownUsd));
|
|
3038
|
-
if ((result.reviewSpendUsd ?? 0) > 0) {
|
|
3039
|
-
log.emit("budget.observation", {
|
|
3040
|
-
harness_id: "review-panel",
|
|
3041
|
-
attempt_id: run.attemptId,
|
|
3042
|
-
kind: "spend",
|
|
3043
|
-
usd: result.reviewSpendUsd,
|
|
3044
|
-
cash_usd: result.reviewCashUsd,
|
|
3045
|
-
valuation_usd: result.reviewValuationUsd,
|
|
3046
|
-
unknown_usd: result.reviewUnknownUsd,
|
|
3047
|
-
estimated: result.reviewSpendEstimated === true,
|
|
3048
|
-
});
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
else if (reviewLease && !reviewLease.granted) {
|
|
3052
|
-
log.emit("budget.lease.created", {
|
|
3053
|
-
granted: false,
|
|
3054
|
-
reason: reviewLease.reason,
|
|
3055
|
-
attempt_id: run.attemptId,
|
|
3056
|
-
harness_id: "review-panel",
|
|
3057
|
-
});
|
|
3058
|
-
}
|
|
3059
|
-
const revalidated = await revalidateFindings(result.findings, {
|
|
3060
|
-
candidateRoot: candidateCwd,
|
|
3061
|
-
evidenceDir: candidateEvidenceDir,
|
|
3062
|
-
});
|
|
3063
|
-
// The high-risk human gate must key off the ACTUAL cross-family verification
|
|
3064
|
-
// (stream-observed route proofs), not the preliminary routeVerified (families
|
|
3065
|
-
// merely configured). Otherwise a high-risk diff skips its NEEDS_HUMAN gate
|
|
3066
|
-
// when two families were configured but their route proofs went unverified.
|
|
3067
|
-
// Mirrors the convergence path (actualReviewVerified).
|
|
3068
|
-
const candidateReviewVerified = reviewVerified && result.crossFamilyHealthy && result.crossFamilyVerified;
|
|
3069
|
-
// Typed policy gate (risk + protected paths) merges with reviewer findings.
|
|
3070
|
-
const policy = policyFindings(run, candidateReviewVerified, contract.constraints.protected_paths, contract.constraints.auto_protected_paths, contract.constraints.protected_path_approvals, contract.constraints.deny_paths);
|
|
3071
|
-
const allFindings = [...policy.findings, ...revalidated];
|
|
3072
|
-
const inconclusive = allFindings.some((f) => f.severity === "INSUFFICIENT_EVIDENCE" || f.status === "insufficient_evidence");
|
|
3073
|
-
const noBlockers = !allFindings.some((f) => isBlocking(f));
|
|
3074
|
-
const reviewClean = result.crossFamilyHealthy && result.crossFamilyVerified && noBlockers && !inconclusive;
|
|
3075
|
-
store.writeYaml(join(paths.reviewsDir, `${run.attemptId}.yaml`), {
|
|
3076
|
-
attempt_id: run.attemptId,
|
|
3077
|
-
review_verified: candidateReviewVerified,
|
|
3078
|
-
final_review_clean: reviewClean,
|
|
3079
|
-
cross_family_healthy: result.crossFamilyHealthy,
|
|
3080
|
-
cross_family_verified: result.crossFamilyVerified,
|
|
3081
|
-
healthy_providers: result.healthyProviders,
|
|
3082
|
-
verified_providers: result.distinctProviders,
|
|
3083
|
-
reviewer_requests: result.reviewerRequests,
|
|
3084
|
-
risk: policy.risk,
|
|
3085
|
-
findings: allFindings,
|
|
3086
|
-
route_proofs: result.routeProofs,
|
|
3087
|
-
});
|
|
3088
|
-
for (const f of allFindings)
|
|
3089
|
-
log.emit("finding.revalidated", {
|
|
3090
|
-
attempt_id: run.attemptId,
|
|
3091
|
-
severity: f.severity,
|
|
3092
|
-
status: f.status,
|
|
3093
|
-
});
|
|
3094
|
-
evidences.push(toCandidateEvidence(run, contract, allFindings, reviewClean, candidateReviewVerified));
|
|
3095
|
-
}
|
|
3096
|
-
finally {
|
|
3097
|
-
this.recordReviewEvidenceCleanup(store, join(paths.reviewsDir, `${run.attemptId}-evidence-cleanup.yaml`), run.attemptId, candidateEvidenceDir, candidateCwd);
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
|
-
return evidences;
|
|
3002
|
+
return reviewCandidateRuns({
|
|
3003
|
+
runs,
|
|
3004
|
+
reviewers,
|
|
3005
|
+
reviewVerified,
|
|
3006
|
+
reviewDir,
|
|
3007
|
+
cwd,
|
|
3008
|
+
contract,
|
|
3009
|
+
store,
|
|
3010
|
+
paths,
|
|
3011
|
+
log,
|
|
3012
|
+
ledger,
|
|
3013
|
+
taskId,
|
|
3014
|
+
signal,
|
|
3015
|
+
reservationEstimateUsd,
|
|
3016
|
+
}, {
|
|
3017
|
+
prepareReviewEvidenceDir: this.prepareReviewEvidenceDir.bind(this),
|
|
3018
|
+
recordReviewEvidenceCleanup: this.recordReviewEvidenceCleanup.bind(this),
|
|
3019
|
+
reviewScoped: this.reviewScoped.bind(this),
|
|
3020
|
+
config: this.config.bind(this),
|
|
3021
|
+
});
|
|
3101
3022
|
}
|
|
3102
3023
|
prepareReviewEvidenceDir(sourceDir, _candidateCwd) {
|
|
3103
3024
|
// Evidence is an external runtime artifact. ReviewEngine builds a separate
|
|
@@ -3191,11 +3112,12 @@ export class Orchestrator {
|
|
|
3191
3112
|
this.ensureClaudeBridgeForRun(input.repoRoot, false, log);
|
|
3192
3113
|
}
|
|
3193
3114
|
const reviewDir = join(paths.root, "review-evidence");
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3115
|
+
if (contract.review_requested !== false)
|
|
3116
|
+
writeEvidencePacket(reviewDir, {
|
|
3117
|
+
userIntent: redactSecrets(input.prompt),
|
|
3118
|
+
diff: "(per-attempt)\n",
|
|
3119
|
+
tests: renderTestsEvidence(contract),
|
|
3120
|
+
});
|
|
3199
3121
|
const reviewersOutcome = await this.resolveReviewersWithArtifacts(input, log, store, paths, runId, taskId, mode);
|
|
3200
3122
|
if ("failed" in reviewersOutcome)
|
|
3201
3123
|
return reviewersOutcome.failed;
|
|
@@ -3464,6 +3386,14 @@ export class Orchestrator {
|
|
|
3464
3386
|
let conv;
|
|
3465
3387
|
try {
|
|
3466
3388
|
conv = await (async () => {
|
|
3389
|
+
if (contract.review_requested === false) {
|
|
3390
|
+
const evidence = unreviewedCandidateEvidence(run, contract, store, paths, log);
|
|
3391
|
+
lastFindings = evidence.findings;
|
|
3392
|
+
lastFinalReviewClean = false;
|
|
3393
|
+
actualReviewVerified = false;
|
|
3394
|
+
log.emit("review.skipped", { reason: "not_requested", attempt_id: attemptId });
|
|
3395
|
+
return evaluateUnreviewedConvergence(evidence, contract);
|
|
3396
|
+
}
|
|
3467
3397
|
const candidateReviewCwd = run.reviewCwd ?? input.repoRoot;
|
|
3468
3398
|
const candidateReviewEvidenceDir = this.prepareReviewEvidenceDir(reviewDir, candidateReviewCwd);
|
|
3469
3399
|
try {
|
|
@@ -3705,7 +3635,7 @@ export class Orchestrator {
|
|
|
3705
3635
|
if (lastRun && !interrupted) {
|
|
3706
3636
|
const arb = arbitrate([
|
|
3707
3637
|
toCandidateEvidence(lastRun, contract, lastFindings, lastFinalReviewClean, actualReviewVerified),
|
|
3708
|
-
], arbitrationBudgetOptions(ledger));
|
|
3638
|
+
], { ...arbitrationBudgetOptions(ledger), reviewRequested: contract.review_requested });
|
|
3709
3639
|
decision = arb.decision;
|
|
3710
3640
|
store.writeYaml(join(paths.arbitrationDir, "decision.yaml"), decision);
|
|
3711
3641
|
// A converged run adopts the arbitration axes (checks/review); an
|
|
@@ -3724,6 +3654,7 @@ export class Orchestrator {
|
|
|
3724
3654
|
facts = makeOutcomeFacts("failed", {
|
|
3725
3655
|
reason: convBudgetTerminal,
|
|
3726
3656
|
noChanges: facts.noChanges,
|
|
3657
|
+
review_requested: contract.review_requested,
|
|
3727
3658
|
});
|
|
3728
3659
|
}
|
|
3729
3660
|
// A reviewer escalation to a human forces the REVIEW axis to blocked.
|
|
@@ -3774,7 +3705,10 @@ export class Orchestrator {
|
|
|
3774
3705
|
// convergence run with inPlace mutated the live tree directly across its
|
|
3775
3706
|
// attempts, so it is "applied" even when review blocked (Revert offered).
|
|
3776
3707
|
const convHasDiff = lastRun.diff.trim().length > 0;
|
|
3777
|
-
const convAdoptable = facts.lifecycle === "succeeded" &&
|
|
3708
|
+
const convAdoptable = facts.lifecycle === "succeeded" &&
|
|
3709
|
+
reviewAllowsApply(facts) &&
|
|
3710
|
+
facts.checks !== "failed" &&
|
|
3711
|
+
!workStateVetoes(facts);
|
|
3778
3712
|
const convAdopted = input.inPlace === true && convHasDiff ? true : null;
|
|
3779
3713
|
const convApplyState = convAdopted === true
|
|
3780
3714
|
? convAdoptable
|