@claudexor/orchestrator 3.1.2 → 3.2.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/attemptFinalize.d.ts +14 -11
- package/dist/attemptFinalize.d.ts.map +1 -1
- package/dist/attemptFinalize.js +41 -14
- package/dist/attemptFinalize.js.map +1 -1
- package/dist/attemptTelemetry.d.ts +7 -0
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +2 -1
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/attemptUsageCost.d.ts +17 -1
- package/dist/attemptUsageCost.d.ts.map +1 -1
- package/dist/attemptUsageCost.js +11 -1
- package/dist/attemptUsageCost.js.map +1 -1
- package/dist/budgetFailure.js.map +1 -1
- package/dist/credential-profile-rotation.d.ts +145 -0
- package/dist/credential-profile-rotation.d.ts.map +1 -0
- package/dist/credential-profile-rotation.js +315 -0
- package/dist/credential-profile-rotation.js.map +1 -0
- package/dist/credential-profiles.d.ts +35 -134
- package/dist/credential-profiles.d.ts.map +1 -1
- package/dist/credential-profiles.js +73 -295
- package/dist/credential-profiles.js.map +1 -1
- package/dist/decisionBudget.d.ts +2 -2
- package/dist/decisionBudget.d.ts.map +1 -1
- package/dist/deepScanReducer.d.ts +14 -44
- package/dist/deepScanReducer.d.ts.map +1 -1
- package/dist/deepScanReducer.js +240 -128
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/delegationBudgetAuthority.d.ts.map +1 -1
- package/dist/delegationBudgetAuthority.js.map +1 -1
- package/dist/delegationFailure.d.ts.map +1 -1
- package/dist/git-precondition.d.ts +11 -0
- package/dist/git-precondition.d.ts.map +1 -0
- package/dist/git-precondition.js +69 -0
- package/dist/git-precondition.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +14 -6
- package/dist/interaction.d.ts.map +1 -1
- package/dist/interaction.js +106 -19
- package/dist/interaction.js.map +1 -1
- package/dist/orchestrator.d.ts +39 -83
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +480 -676
- package/dist/orchestrator.js.map +1 -1
- package/dist/planBrief.d.ts +27 -0
- package/dist/planBrief.d.ts.map +1 -0
- package/dist/planBrief.js +42 -0
- package/dist/planBrief.js.map +1 -0
- package/dist/planRun.d.ts +4 -3
- package/dist/planRun.d.ts.map +1 -1
- package/dist/planRun.js +33 -33
- package/dist/planRun.js.map +1 -1
- package/dist/plannerAttempt.d.ts +64 -0
- package/dist/plannerAttempt.d.ts.map +1 -0
- package/dist/plannerAttempt.js +235 -0
- package/dist/plannerAttempt.js.map +1 -0
- package/dist/policyFindings.js.map +1 -1
- package/dist/requestRequirements.d.ts +28 -1
- package/dist/requestRequirements.d.ts.map +1 -1
- package/dist/requestRequirements.js +89 -10
- package/dist/requestRequirements.js.map +1 -1
- package/dist/reviewerPanel.js.map +1 -1
- package/dist/root-ledger.d.ts.map +1 -1
- package/dist/root-ledger.js +2 -0
- package/dist/root-ledger.js.map +1 -1
- package/dist/run-bounded.d.ts.map +1 -1
- package/dist/run-bounded.js +7 -1
- package/dist/run-bounded.js.map +1 -1
- package/dist/run-input-resolution.d.ts.map +1 -1
- package/dist/run-input-resolution.js +26 -8
- package/dist/run-input-resolution.js.map +1 -1
- package/dist/runEventLog.d.ts +48 -0
- package/dist/runEventLog.d.ts.map +1 -0
- package/dist/runEventLog.js +63 -0
- package/dist/runEventLog.js.map +1 -0
- package/dist/runFacts.d.ts +23 -0
- package/dist/runFacts.d.ts.map +1 -0
- package/dist/runFacts.js +179 -0
- package/dist/runFacts.js.map +1 -0
- package/dist/runFactsBuilder.d.ts +5 -0
- package/dist/runFactsBuilder.d.ts.map +1 -0
- package/dist/runFactsBuilder.js +412 -0
- package/dist/runFactsBuilder.js.map +1 -0
- package/dist/runFactsDeliverable.d.ts +13 -0
- package/dist/runFactsDeliverable.d.ts.map +1 -0
- package/dist/runFactsDeliverable.js +94 -0
- package/dist/runFactsDeliverable.js.map +1 -0
- package/dist/runFactsPresentation.d.ts +7 -0
- package/dist/runFactsPresentation.d.ts.map +1 -0
- package/dist/runFactsPresentation.js +113 -0
- package/dist/runFactsPresentation.js.map +1 -0
- package/dist/runFactsReview.d.ts +7 -0
- package/dist/runFactsReview.d.ts.map +1 -0
- package/dist/runFactsReview.js +63 -0
- package/dist/runFactsReview.js.map +1 -0
- package/dist/runSupport.d.ts +17 -15
- package/dist/runSupport.d.ts.map +1 -1
- package/dist/runSupport.js +16 -0
- package/dist/runSupport.js.map +1 -1
- package/dist/runTerminalContext.d.ts +31 -0
- package/dist/runTerminalContext.d.ts.map +1 -0
- package/dist/runTerminalContext.js +12 -0
- package/dist/runTerminalContext.js.map +1 -0
- package/dist/runTerminalResults.d.ts +55 -0
- package/dist/runTerminalResults.d.ts.map +1 -0
- package/dist/runTerminalResults.js +134 -0
- package/dist/runTerminalResults.js.map +1 -0
- package/dist/runTerminals.d.ts +4 -80
- package/dist/runTerminals.d.ts.map +1 -1
- package/dist/runTerminals.js +357 -260
- package/dist/runTerminals.js.map +1 -1
- package/dist/secretDiff.d.ts +3 -0
- package/dist/secretDiff.d.ts.map +1 -1
- package/dist/secretDiff.js +7 -0
- package/dist/secretDiff.js.map +1 -1
- package/dist/structuredOutput.d.ts.map +1 -1
- package/dist/structuredOutput.js.map +1 -1
- package/dist/task-contract-builder.d.ts +35 -0
- package/dist/task-contract-builder.d.ts.map +1 -0
- package/dist/task-contract-builder.js +114 -0
- package/dist/task-contract-builder.js.map +1 -0
- package/dist/transientClassify.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { observeNativeSessionEvent, preflightCredentialProfile, preflightDefaultSubject, resolveCredentialProfile, resumeSessionForProfile, rotateSpecOnTypedLimit, selectedProfileAvailability, } from "./credential-profiles.js";
|
|
2
|
+
import { effectiveAuthPreference, observeNativeSessionEvent, preflightCredentialProfile, preflightDefaultSubject, probeCredentialProfileStatus, profileHeadroomBreach, profileStatusAdmits, resolveCredentialProfile, resumeSessionForProfile, rotateSpecOnTypedLimit, selectedProfileAvailability, staticRotationCandidates, } from "./credential-profiles.js";
|
|
3
3
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
4
4
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
5
5
|
import { processAttemptUsage } from "./attemptUsage.js";
|
|
@@ -9,9 +9,10 @@ import { capabilityIntents } from "@claudexor/gateway";
|
|
|
9
9
|
import { policyFindings } from "./policyFindings.js";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { finalizePlanRun, runCouncilPlan, writePlanHarnessFailure, } from "./planRun.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { runPlannerAttempt as executePlannerAttempt, } from "./plannerAttempt.js";
|
|
13
|
+
import { HarnessRunSpec, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, normalizeUserOutputSchema, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
14
|
+
import { globalConfigDir, loadConfig } from "@claudexor/config";
|
|
15
|
+
import { AnswerAssembly, CLAUDEXOR_ARTIFACT_DIR, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
15
16
|
import { assertRouteModelsAllowed } from "./modelGovernance.js";
|
|
16
17
|
import { governRouteEffort } from "./effortGovernance.js";
|
|
17
18
|
import { isFullAccess, RequestRequirementsResolver } from "./requestRequirements.js";
|
|
@@ -20,16 +21,18 @@ import { routingFailureClassification } from "./routing-failure.js";
|
|
|
20
21
|
export { routingFailureClassification } from "./routing-failure.js";
|
|
21
22
|
import { runBounded } from "./run-bounded.js";
|
|
22
23
|
import { planPrompt } from "./plan-prompt.js";
|
|
24
|
+
import { verifiedPlanBrief, withPlanBrief } from "./planBrief.js";
|
|
23
25
|
import { resolveRunInputDefaults } from "./run-input-resolution.js";
|
|
24
|
-
import {
|
|
26
|
+
import { beginAnnouncedRun } from "./runEventLog.js";
|
|
25
27
|
import { arbitrationBudgetOptions, decisionBudgetSummary } from "./decisionBudget.js";
|
|
26
28
|
import { buildRevisePrompt } from "./revisePrompt.js";
|
|
27
|
-
import {
|
|
29
|
+
import { cancelledResult, failTerminally, guardAnnouncedRun, writeFailure, } from "./runTerminals.js";
|
|
28
30
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
29
31
|
import { assertOutputSchemaCompiles, finalizeStructuredOutput } from "./structuredOutput.js";
|
|
30
|
-
import { transientRetryDelayMs, promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPrimaryDivergence, emitPoolDegraded, deliveryRefusalFailure, writeRaceDeliveryDecision, } from "./runSupport.js";
|
|
32
|
+
import { transientRetryDelayMs, promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPrimaryDivergence, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
|
|
31
33
|
import { candidateStatusInRouteContext, resolveReadOnlyRouteContext, } from "./routeContext.js";
|
|
32
34
|
import { resolveAutoReviewerPanel, resolveExplicitReviewerPanel } from "./reviewerPanel.js";
|
|
35
|
+
import { ensureWriteModeGitBoundary } from "./git-precondition.js";
|
|
33
36
|
import { buildContinuation, } from "./continuity.js";
|
|
34
37
|
import { activePlanPointer, resolveContinuitySummary, workspaceAnchor, } from "./continuity-facts.js";
|
|
35
38
|
import { runDiffReview } from "./diffReview.js";
|
|
@@ -41,18 +44,18 @@ import { dominantHarnessFailureCategory, harnessFailureNextActions } from "./har
|
|
|
41
44
|
import { finalizeAttempt, readOnlyNoSuccessTerminal, resolveWorkReportEnvelope, unrecoveredToolErrorFailure, unwrapWorkReportEnvelope, webEvidenceFailure, } from "./attemptFinalize.js";
|
|
42
45
|
import { buildContinuationPacket, decideContinuation, synthesizeContinuationRequest, } from "./continuation.js";
|
|
43
46
|
import { interactionChannelFor } from "./interaction.js";
|
|
44
|
-
import { gateSpecsFromContract, renderTestsEvidence
|
|
47
|
+
import { gateSpecsFromContract, renderTestsEvidence } from "./contract-gates.js";
|
|
48
|
+
import { buildTaskContract } from "./task-contract-builder.js";
|
|
45
49
|
import { ArtifactStore } from "@claudexor/artifact-store";
|
|
46
|
-
import { EventLog } from "@claudexor/event-log";
|
|
47
50
|
import { assertMandatoryContext, buildContextPack, rawContextForEnvelope, preflightEvidence, writeEvidencePacket, } from "@claudexor/context";
|
|
48
|
-
import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull, ensureClaudeBridge,
|
|
51
|
+
import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull, ensureClaudeBridge, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
|
|
49
52
|
import { blockedDecisionOverride, finalVerifyBlocks, finalVerifyPatch, verifyAndDeliver, } from "@claudexor/delivery";
|
|
50
53
|
import { HarnessGateway } from "@claudexor/gateway";
|
|
51
54
|
import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate, revalidateFindings, runGates, } from "@claudexor/review";
|
|
52
55
|
import { arbitrate } from "@claudexor/arbitration";
|
|
53
56
|
import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
|
|
54
57
|
import { attemptCostEvidence, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, reviewUsageCostSettlement, } from "@claudexor/budget";
|
|
55
|
-
import { readTextSafe, appendLine, assertNoInlineSecretValues,
|
|
58
|
+
import { readTextSafe, appendLine, assertNoInlineSecretValues, DELEGATION_ENV, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, sha256, userConfigDir, writeText, } from "@claudexor/util";
|
|
56
59
|
import { assertWriteIsolation } from "./write-isolation.js";
|
|
57
60
|
const LABELS = "ABCDEFGHIJ".split("");
|
|
58
61
|
const NO_PROJECT_ROOT = noProjectRepoRoot();
|
|
@@ -110,10 +113,14 @@ export class Orchestrator {
|
|
|
110
113
|
throw new Error(`Delegate is an agent-only strategy (got mode=${mode})`);
|
|
111
114
|
}
|
|
112
115
|
const runId = resolved.runId ?? newId("run");
|
|
116
|
+
let delegatedLedgerAttached = false;
|
|
113
117
|
resolved = {
|
|
114
118
|
...resolved,
|
|
115
119
|
runId,
|
|
116
120
|
taskId: resolved.taskId ?? newId("task"),
|
|
121
|
+
onDelegatedLedgerAttached: () => {
|
|
122
|
+
delegatedLedgerAttached = true;
|
|
123
|
+
},
|
|
117
124
|
};
|
|
118
125
|
if (resolved.delegate === true) {
|
|
119
126
|
resolved = {
|
|
@@ -157,50 +164,63 @@ export class Orchestrator {
|
|
|
157
164
|
if (resolved.repoRoot !== NO_PROJECT_ROOT) {
|
|
158
165
|
assertMandatoryContext(resolved.repoRoot, this.projectConfig(resolved.repoRoot).context.mandatory_files);
|
|
159
166
|
}
|
|
160
|
-
// Reviewer panels are validated only inside
|
|
161
|
-
// review (race/convergence
|
|
167
|
+
// Reviewer panels are validated only inside Agent strategies that actually
|
|
168
|
+
// review (race/convergence; Plan Council is the plan critique path) — AFTER run-dir
|
|
162
169
|
// creation, so a doomed explicit panel yields typed failure ARTIFACTS
|
|
163
170
|
// (failure.yaml naming the refusal) instead of a bare pre-run throw.
|
|
164
|
-
//
|
|
171
|
+
// Ask and Plan never spawn code reviewers, so a panel there never spends doctor/
|
|
165
172
|
// model probes and never fails a run that would not use it.
|
|
166
173
|
// Whole-strategy terminal net: once a strategy ANNOUNCES its
|
|
167
174
|
// run, any escaped throw still stamps failure.yaml + summary + run.failed
|
|
168
175
|
// instead of orphaning events.jsonl.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
176
|
+
const releaseRunState = (settledRunId) => {
|
|
177
|
+
this.routingRationaleByRun.delete(settledRunId);
|
|
178
|
+
this.deps.delegationBudgetAuthority?.releaseRun(settledRunId);
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
return await guardAnnouncedRun(resolved.signal, (announce) => {
|
|
182
|
+
switch (mode) {
|
|
183
|
+
case "ask":
|
|
184
|
+
// `--deep-scan` widens the answer into the bounded multi-scout
|
|
185
|
+
// research sweep with synthesis (the old `audit --swarm`/`explore`).
|
|
186
|
+
return resolved.deepScan
|
|
187
|
+
? this.runDeepScan(resolved, announce)
|
|
188
|
+
: this.runAsk(resolved, announce);
|
|
189
|
+
case "agent":
|
|
190
|
+
// Engine strategies are FLAGS on agent (v0.9 collapse): `--until-clean`
|
|
191
|
+
// and `--attempts` select the convergence loop; `--n` selects the race
|
|
192
|
+
// width; `--create` switches the candidate intent to create_from_scratch.
|
|
193
|
+
if (resolved.untilClean)
|
|
194
|
+
return this.runConvergence(resolved, mode, null, announce);
|
|
195
|
+
if (resolved.attempts !== undefined && resolved.attempts !== null) {
|
|
196
|
+
return this.runConvergence(resolved, mode, resolved.attempts, announce);
|
|
197
|
+
}
|
|
198
|
+
return this.runRace({ ...resolved, n: resolved.n ?? 1 }, mode, announce);
|
|
199
|
+
case "plan":
|
|
200
|
+
return this.runPlan(resolved, announce);
|
|
201
|
+
}
|
|
202
|
+
}, async ({ runId }) => {
|
|
203
|
+
const authority = this.deps.delegationBudgetAuthority;
|
|
204
|
+
if (!authority?.hasParent(runId))
|
|
205
|
+
return;
|
|
206
|
+
authority.beginParentClose(runId);
|
|
207
|
+
await authority.waitForChildren(runId);
|
|
208
|
+
},
|
|
209
|
+
// Single per-run terminalization hook: release the routing-rationale map
|
|
210
|
+
// entry on EVERY terminal (incl. a run that died before its telemetry
|
|
211
|
+
// writer ran, which is the leak this closes).
|
|
212
|
+
releaseRunState);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
// A durable startup sink may refuse `run.created` before the strategy can
|
|
216
|
+
// announce its context. Release only a delegated child ledger acquired by
|
|
217
|
+
// THIS invocation; a caller-supplied run-id collision must never release
|
|
218
|
+
// another live parent/child authority or its routing state.
|
|
219
|
+
if (delegatedLedgerAttached) {
|
|
220
|
+
this.deps.delegationBudgetAuthority?.releaseRun(runId);
|
|
189
221
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (!authority?.hasParent(runId))
|
|
193
|
-
return;
|
|
194
|
-
authority.beginParentClose(runId);
|
|
195
|
-
await authority.waitForChildren(runId);
|
|
196
|
-
},
|
|
197
|
-
// Single per-run terminalization hook: release the routing-rationale map
|
|
198
|
-
// entry on EVERY terminal (incl. a run that died before its telemetry
|
|
199
|
-
// writer ran, which is the leak this closes).
|
|
200
|
-
(runId) => {
|
|
201
|
-
this.routingRationaleByRun.delete(runId);
|
|
202
|
-
this.deps.delegationBudgetAuthority?.releaseRun(runId);
|
|
203
|
-
});
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
204
224
|
}
|
|
205
225
|
async resolveReviewers(cwd, runAuthPreference, onIgnoredSetting) {
|
|
206
226
|
if (this.deps.reviewers)
|
|
@@ -273,11 +293,7 @@ export class Orchestrator {
|
|
|
273
293
|
}
|
|
274
294
|
authPreferenceForHarness(repoRoot, harnessId, runAuthPreference) {
|
|
275
295
|
const cfg = this.config(repoRoot)?.global;
|
|
276
|
-
|
|
277
|
-
return (explicit(runAuthPreference) ??
|
|
278
|
-
explicit(cfg?.harnesses?.[harnessId]?.auth_preference) ??
|
|
279
|
-
explicit(cfg?.routing?.auth_preference) ??
|
|
280
|
-
"auto");
|
|
296
|
+
return effectiveAuthPreference(runAuthPreference, cfg?.harnesses?.[harnessId]?.auth_preference, cfg?.routing?.auth_preference);
|
|
281
297
|
}
|
|
282
298
|
artifactStore(input) {
|
|
283
299
|
if (input.mode === "ask" && input.contextMode === "off" && input.repoRoot === NO_PROJECT_ROOT) {
|
|
@@ -300,20 +316,23 @@ export class Orchestrator {
|
|
|
300
316
|
estimateUsdFloor(repoRoot) {
|
|
301
317
|
return this.config(repoRoot)?.global.budget.estimate_usd_floor ?? 0.05;
|
|
302
318
|
}
|
|
319
|
+
/** Delegate children overlap their still-running parent, so child-side paid
|
|
320
|
+
* units use the same bounded floor as later slots in a parallel wave. */
|
|
321
|
+
reservationEstimateUsd(input, parallel = false) {
|
|
322
|
+
return parallel || Boolean(input.delegatedFromRunId)
|
|
323
|
+
? this.estimateUsdFloor(input.repoRoot)
|
|
324
|
+
: undefined;
|
|
325
|
+
}
|
|
303
326
|
execRootOf(input) {
|
|
304
327
|
return input.executionRoot ?? input.repoRoot;
|
|
305
328
|
}
|
|
306
|
-
sessionSpecFields(input, harnessId, log) {
|
|
329
|
+
async sessionSpecFields(input, harnessId, log, defaultRoute = null) {
|
|
307
330
|
const cfg = this.config(input.repoRoot)?.global;
|
|
308
|
-
const profile = this.preflightProfile(input, harnessId, log);
|
|
309
|
-
const explicit = (v) => (v && v !== "auto" ? v : undefined);
|
|
331
|
+
const profile = await this.preflightProfile(input, harnessId, log, defaultRoute);
|
|
310
332
|
return {
|
|
311
333
|
// "auto" at ANY level falls through (thread turns send the thread default
|
|
312
334
|
// "auto" as a per-run value; it must not shadow a configured preference).
|
|
313
|
-
auth_preference:
|
|
314
|
-
explicit(cfg?.harnesses?.[harnessId]?.auth_preference) ??
|
|
315
|
-
explicit(cfg?.routing?.auth_preference) ??
|
|
316
|
-
"auto",
|
|
335
|
+
auth_preference: effectiveAuthPreference(input.authPreference, cfg?.harnesses?.[harnessId]?.auth_preference, cfg?.routing?.auth_preference),
|
|
317
336
|
resume_session_id: resumeSessionForProfile(input.resumeSessions?.[harnessId], profile),
|
|
318
337
|
credential_profile: profile,
|
|
319
338
|
};
|
|
@@ -388,19 +407,60 @@ export class Orchestrator {
|
|
|
388
407
|
const policy = this.config(repoRoot)?.global.harnesses?.[harnessId]?.profile_policy;
|
|
389
408
|
return policy ?? { limit_action: "fail", rotation_eligible: [], headroom_threshold: 0.9 };
|
|
390
409
|
}
|
|
391
|
-
|
|
410
|
+
/** Fresh profile readiness for one rotation decision epoch. Accounts uses
|
|
411
|
+
* the same probe wrapper + admission predicate when projecting next_up. */
|
|
412
|
+
async readyProfileIdsForRotation(input, harnessId, current, excluded = new Set()) {
|
|
413
|
+
const profiles = staticRotationCandidates({
|
|
414
|
+
registry: this.config(input.repoRoot)?.global.credential_profiles ?? [],
|
|
415
|
+
harnessId,
|
|
416
|
+
policy: this.profilePolicy(input.repoRoot, harnessId),
|
|
417
|
+
current,
|
|
418
|
+
excluded,
|
|
419
|
+
});
|
|
420
|
+
const adapter = this.deps.registry.get(harnessId);
|
|
421
|
+
const entries = await Promise.all(profiles.map(async (profile) => ({
|
|
422
|
+
profile,
|
|
423
|
+
status: await probeCredentialProfileStatus(profile, adapter?.probeCredentialProfile?.bind(adapter)),
|
|
424
|
+
})));
|
|
425
|
+
return new Set(entries
|
|
426
|
+
.filter(({ profile, status }) => profileStatusAdmits(profile, status))
|
|
427
|
+
.map(({ profile }) => profile.profile_id));
|
|
428
|
+
}
|
|
429
|
+
async preflightProfile(input, harnessId, log, defaultRoute) {
|
|
392
430
|
const profile = this.resolveCredentialProfile(input, harnessId);
|
|
393
431
|
const policy = this.profilePolicy(input.repoRoot, harnessId);
|
|
394
432
|
const registry = this.config(input.repoRoot)?.global.credential_profiles ?? [];
|
|
395
433
|
const snapshots = this.deps.quotaSnapshots?.() ?? [];
|
|
396
434
|
const emit = (type, payload) => log?.emit(type, payload);
|
|
435
|
+
const breach = profileHeadroomBreach(snapshots, harnessId, profile?.profile_id ?? null, policy.headroom_threshold);
|
|
436
|
+
const readyProfileIds = policy.limit_action === "rotate" &&
|
|
437
|
+
breach !== null &&
|
|
438
|
+
(profile !== null || defaultRoute === "local_session")
|
|
439
|
+
? await this.readyProfileIdsForRotation(input, harnessId, profile)
|
|
440
|
+
: new Set();
|
|
397
441
|
if (!profile) {
|
|
398
442
|
// Unpinned runs (INV-135 auto-balance): under `rotate`, a fresh
|
|
399
443
|
// default-subject headroom breach starts on the next eligible
|
|
400
444
|
// subscription profile instead; `fail`/`ask` change nothing.
|
|
401
|
-
return preflightDefaultSubject({
|
|
445
|
+
return preflightDefaultSubject({
|
|
446
|
+
harnessId,
|
|
447
|
+
policy,
|
|
448
|
+
registry,
|
|
449
|
+
snapshots,
|
|
450
|
+
readyProfileIds,
|
|
451
|
+
defaultRoute,
|
|
452
|
+
emit,
|
|
453
|
+
});
|
|
402
454
|
}
|
|
403
|
-
return preflightCredentialProfile({
|
|
455
|
+
return preflightCredentialProfile({
|
|
456
|
+
profile,
|
|
457
|
+
harnessId,
|
|
458
|
+
policy,
|
|
459
|
+
registry,
|
|
460
|
+
snapshots,
|
|
461
|
+
readyProfileIds,
|
|
462
|
+
emit,
|
|
463
|
+
});
|
|
404
464
|
}
|
|
405
465
|
/**
|
|
406
466
|
* Resolve candidate adapters: explicit `--harness`, else available real harnesses, then
|
|
@@ -477,6 +537,27 @@ export class Orchestrator {
|
|
|
477
537
|
}
|
|
478
538
|
}
|
|
479
539
|
}
|
|
540
|
+
const attachments = input.attachments ?? [];
|
|
541
|
+
// Resolve the complete attachment pool once from manifest truth. If any
|
|
542
|
+
// lane still lacks usable discovery truth, preserve the existing per-lane
|
|
543
|
+
// discovery/doctor precedence and resolve that lane inside the route loop.
|
|
544
|
+
const canResolveAttachmentPool = attachments.length > 0 &&
|
|
545
|
+
ids.every((id) => {
|
|
546
|
+
const status = statusById.get(id);
|
|
547
|
+
return status?.manifest != null && status.status !== "unavailable";
|
|
548
|
+
});
|
|
549
|
+
const attachmentPoolAdmission = canResolveAttachmentPool
|
|
550
|
+
? this.requestRequirements.resolveAttachmentPool(explicitPool ? "explicit" : "auto", attachments, ids.map((id) => ({
|
|
551
|
+
harnessId: id,
|
|
552
|
+
declarations: statusById.get(id)?.manifest?.capability_profile.attachment_inputs ?? null,
|
|
553
|
+
available: true,
|
|
554
|
+
})))
|
|
555
|
+
: null;
|
|
556
|
+
if (attachmentPoolAdmission?.outcome === "refused") {
|
|
557
|
+
throw new HarnessUnavailableError(attachmentPoolAdmission.message ??
|
|
558
|
+
"no available harness lane can receive the selected attachments");
|
|
559
|
+
}
|
|
560
|
+
const attachmentRejectionById = new Map(attachmentPoolAdmission?.rejected.map((admission) => [admission.harnessId, admission]) ?? []);
|
|
480
561
|
const policy = input.web ?? input.externalContextPolicy ?? "auto";
|
|
481
562
|
const pool = [];
|
|
482
563
|
const dropped = [];
|
|
@@ -610,9 +691,10 @@ export class Orchestrator {
|
|
|
610
691
|
dropLane(id, "web", why);
|
|
611
692
|
continue;
|
|
612
693
|
}
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
694
|
+
const attachmentAdmission = attachmentRejectionById.get(id) ??
|
|
695
|
+
this.requestRequirements.resolveAttachmentLane(id, attachments, manifest.capability_profile.attachment_inputs);
|
|
696
|
+
if (!attachmentAdmission.admitted) {
|
|
697
|
+
dropLane(id, "attachment", attachmentAdmission.message ?? `${id} rejects attachments`);
|
|
616
698
|
continue;
|
|
617
699
|
}
|
|
618
700
|
const reason = status.reasons.length > 0 ? `: ${status.reasons.join("; ")}` : "";
|
|
@@ -981,119 +1063,9 @@ export class Orchestrator {
|
|
|
981
1063
|
return this.config(repoRoot).project;
|
|
982
1064
|
}
|
|
983
1065
|
buildContract(input, taskId, mode) {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const requestedAccess = input.access ?? (readOnlyMode ? "readonly" : resolvedCfg.trust.access_default);
|
|
988
|
-
// Effective access is COMPUTED by the engine, never echoed from a client:
|
|
989
|
-
// read-only modes clamp to readonly regardless of the request.
|
|
990
|
-
const effectiveAccess = readOnlyMode ? "readonly" : requestedAccess;
|
|
991
|
-
// TrustConfig is USER-LEVEL only (versioned repo config must never
|
|
992
|
-
// self-grant sensitive powers): unsandboxed full access requires an
|
|
993
|
-
// explicit allow in ~/.claudexor trust settings — loud error, no downgrade.
|
|
994
|
-
// The gate applies to the EFFECTIVE profile: a read-only run clamped to
|
|
995
|
-
// readonly never runs unsandboxed and needs no trust allow.
|
|
996
|
-
if (effectiveAccess === "full" && !resolvedCfg.trust.allow_full_access) {
|
|
997
|
-
// Typed refusal: the `code` rides the daemon job record onto the thread
|
|
998
|
-
// turn (TurnEnqueueError.code), so surfaces key remedies on the CODE —
|
|
999
|
-
// never on substring-matching this human message.
|
|
1000
|
-
throw Object.assign(new Error(`access profile 'full' requires allow_full_access: true in the user-level trust file for this repo ` +
|
|
1001
|
-
`(${trustConfigPath(input.repoRoot)}); enable it with \`claudexor trust --allow-full-access\` — refusing to run unsandboxed`),
|
|
1002
|
-
// Refusal semantics are born at the throw (W24): the one-time grant is
|
|
1003
|
-
// a 403, and the daemon persists this status onto the job record.
|
|
1004
|
-
{ code: TRUST_FULL_ACCESS_CODE, status: 403 });
|
|
1005
|
-
}
|
|
1006
|
-
const externalContextPolicy = input.web ?? input.externalContextPolicy ?? "auto";
|
|
1007
|
-
// Deterministic gate commands come from explicit run input, then versioned
|
|
1008
|
-
// project config. Without these, gateSpecs is empty and convergence is
|
|
1009
|
-
// review-only; with them, convergence is test-driven.
|
|
1010
|
-
const resolvedGates = resolveContractGates({
|
|
1011
|
-
repoRoot: input.repoRoot,
|
|
1012
|
-
effectiveAccess,
|
|
1013
|
-
config: cfg,
|
|
1014
|
-
trustGrants: resolvedCfg.trust.test_command_grants,
|
|
1015
|
-
operatorCommands: input.tests ?? [],
|
|
1016
|
-
projectCommands: cfg.tests?.commands ?? [],
|
|
1017
|
-
});
|
|
1018
|
-
const commands = resolvedGates.commands;
|
|
1019
|
-
const protectedPaths = [...new Set(cfg.constraints.protected_paths)];
|
|
1020
|
-
const autoProtectedPaths = resolvedGates.autoProtectedPaths;
|
|
1021
|
-
const protectedPathApprovals = [
|
|
1022
|
-
...new Map([...(input.protectedPathApprovals ?? [])].map((approval) => [approval.path, approval])).values(),
|
|
1023
|
-
];
|
|
1024
|
-
return TaskContractSchema.parse({
|
|
1025
|
-
schema_version: SCHEMA_VERSION,
|
|
1026
|
-
task_id: taskId,
|
|
1027
|
-
created_at: nowIso(),
|
|
1028
|
-
repo: { root: input.repoRoot, base_ref: input.baseRef ?? "HEAD", dirty_policy: "snapshot" },
|
|
1029
|
-
mode: { kind: mode },
|
|
1030
|
-
delegation_requested: input.delegate === true,
|
|
1031
|
-
run_lineage: {
|
|
1032
|
-
parent_run_id: input.parentRunId ?? null,
|
|
1033
|
-
delegated_from_run_id: input.delegatedFromRunId ?? null,
|
|
1034
|
-
},
|
|
1035
|
-
user_intent: { raw: redactSecrets(input.prompt) },
|
|
1036
|
-
// Redacted for symmetry with user_intent.raw — a no-op on fenced input
|
|
1037
|
-
// (the inline-secret fence already blocked any secret-like value at every
|
|
1038
|
-
// ingress incl. this engine boundary), so task-producing lanes read back
|
|
1039
|
-
// the real instructions via harnessSpecKnobs().
|
|
1040
|
-
instructions: input.instructions === undefined ? undefined : redactSecrets(input.instructions),
|
|
1041
|
-
// Already normalized/strictified at the engine boundary (run() refuses
|
|
1042
|
-
// unsupported shapes before any run dir exists).
|
|
1043
|
-
output_schema: input.outputSchema ?? null,
|
|
1044
|
-
auth_preference: input.authPreference ?? "auto",
|
|
1045
|
-
credential_profile_id: input.credentialProfileId ?? null,
|
|
1046
|
-
max_turns: input.maxTurns ?? null,
|
|
1047
|
-
constraints: {
|
|
1048
|
-
protected_paths: protectedPaths,
|
|
1049
|
-
deny_paths: [...new Set(input.denyPaths ?? [])],
|
|
1050
|
-
auto_protected_paths: autoProtectedPaths,
|
|
1051
|
-
protected_path_approvals: protectedPathApprovals,
|
|
1052
|
-
},
|
|
1053
|
-
tests: { commands },
|
|
1054
|
-
access: {
|
|
1055
|
-
requested_profile: requestedAccess,
|
|
1056
|
-
effective_profile: effectiveAccess,
|
|
1057
|
-
},
|
|
1058
|
-
external_context: {
|
|
1059
|
-
policy: externalContextPolicy,
|
|
1060
|
-
web_required: externalContextPolicy === "cached" || externalContextPolicy === "live",
|
|
1061
|
-
// Per-route upgrades (e.g. claude cached->live) are disclosed in events
|
|
1062
|
-
// and telemetry.yaml; the immutable contract records the requested policy.
|
|
1063
|
-
effective_mode: externalContextPolicy,
|
|
1064
|
-
},
|
|
1065
|
-
// Harness-native tool names are adapter knowledge; the neutral contract
|
|
1066
|
-
// carries only the policy plus user-configured allow/deny lists (wired
|
|
1067
|
-
// from per-harness settings).
|
|
1068
|
-
tool_permission_policy: {
|
|
1069
|
-
web: externalContextPolicy,
|
|
1070
|
-
allow: [],
|
|
1071
|
-
deny: [],
|
|
1072
|
-
},
|
|
1073
|
-
budget: {
|
|
1074
|
-
routing_goal: input.routingGoal ?? this.deps.routingGoal ?? cfg?.budget?.routing_goal ?? "auto",
|
|
1075
|
-
paid_budget: this.resolvePaidBudget(input.paidBudget, resolvedCfg),
|
|
1076
|
-
},
|
|
1077
|
-
// The resolved harness-scoped model map (scalar already expanded to the
|
|
1078
|
-
// primary by resolveRunInput). The contract is what route spec building
|
|
1079
|
-
// reads — there is no run-global model (INV-103).
|
|
1080
|
-
routing_models: input.models ?? {},
|
|
1081
|
-
// QA-035: freeze the RESOLVED reasoning-effort per known lane so Exact
|
|
1082
|
-
// Retry replays it instead of re-resolving current settings. Precedence
|
|
1083
|
-
// (specific beats general): the harness-scoped `efforts` map entry, then a
|
|
1084
|
-
// per-turn scalar `input.effort`, then the harness settings default — the
|
|
1085
|
-
// same map that Exact Retry replays so a NON-PRIMARY lane keeps its own
|
|
1086
|
-
// frozen effort (QA-035 completeness). Only known-pool lanes are frozen
|
|
1087
|
-
// here (a pure auto pool's lanes resolve later — documented seam).
|
|
1088
|
-
routing_efforts: Object.fromEntries([...new Set([...(input.harnesses ?? []), ...Object.keys(input.efforts ?? {})])]
|
|
1089
|
-
.map((hid) => [
|
|
1090
|
-
hid,
|
|
1091
|
-
input.efforts?.[hid] ??
|
|
1092
|
-
input.effort ??
|
|
1093
|
-
resolvedCfg.global.harnesses?.[hid]?.effort ??
|
|
1094
|
-
null,
|
|
1095
|
-
])
|
|
1096
|
-
.filter((entry) => entry[1] !== null)),
|
|
1066
|
+
return buildTaskContract(input, taskId, mode, {
|
|
1067
|
+
paidBudget: this.deps.paidBudget,
|
|
1068
|
+
routingGoal: this.deps.routingGoal,
|
|
1097
1069
|
});
|
|
1098
1070
|
}
|
|
1099
1071
|
/**
|
|
@@ -1172,9 +1144,9 @@ export class Orchestrator {
|
|
|
1172
1144
|
/**
|
|
1173
1145
|
* D-16: the WorkReport transport envelope for one route. Called at every
|
|
1174
1146
|
* task-producing spec-build site AFTER harnessSpecKnobs so it OVERRIDES the
|
|
1175
|
-
* plain caller-schema transport with the
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1147
|
+
* plain caller-schema transport with the resolved WorkReport channel on
|
|
1148
|
+
* capable routes. The returned `mode` is retained by the caller and handed
|
|
1149
|
+
* to `unwrapWorkReportEnvelope` when the answer is finalized.
|
|
1178
1150
|
*/
|
|
1179
1151
|
workReportEnvelopeFor(routed, contract, interactive) {
|
|
1180
1152
|
return resolveWorkReportEnvelope({
|
|
@@ -1188,7 +1160,7 @@ export class Orchestrator {
|
|
|
1188
1160
|
/**
|
|
1189
1161
|
* D-16: apply the resolved WorkReport transport to a built spec — set the
|
|
1190
1162
|
* envelope output_schema (constrained/side_tool routes) and APPEND the fenced
|
|
1191
|
-
*
|
|
1163
|
+
* metadata instruction (validated routes, e.g. cursor). Mutates the spec in
|
|
1192
1164
|
* place and returns the mode the answer unwrap consumes. Called at every
|
|
1193
1165
|
* task-producing spec-build site so the transport is never wired one-off.
|
|
1194
1166
|
*/
|
|
@@ -1265,7 +1237,7 @@ export class Orchestrator {
|
|
|
1265
1237
|
* Returns null (no packet, no pointer) for native resume, a fresh thread, or
|
|
1266
1238
|
* a non-thread run. Never throws: continuity failure degrades to no packet.
|
|
1267
1239
|
*/
|
|
1268
|
-
async resolveContinuity(runInput, harnessId, resolvedProfileId, nativeResumeAvailable, store, paths, repoRoot, log) {
|
|
1240
|
+
async resolveContinuity(runInput, harnessId, resolvedProfileId, nativeResumeAvailable, sessionFields, store, paths, repoRoot, log) {
|
|
1269
1241
|
const ctx = runInput.threadContinuity;
|
|
1270
1242
|
if (!runInput.threadId || !ctx)
|
|
1271
1243
|
return null;
|
|
@@ -1303,7 +1275,6 @@ export class Orchestrator {
|
|
|
1303
1275
|
// summary. Same credential route + scoped lane home a real read-only
|
|
1304
1276
|
// thread turn uses (INV-034/135). Best-effort in its OWN guard — a summary
|
|
1305
1277
|
// failure keeps the full mechanical packet, never drops it.
|
|
1306
|
-
const sessionFields = this.sessionSpecFields(runInput, harnessId);
|
|
1307
1278
|
req.cachedSummary = await resolveContinuitySummary({
|
|
1308
1279
|
req,
|
|
1309
1280
|
threadId: runInput.threadId,
|
|
@@ -1371,13 +1342,15 @@ export class Orchestrator {
|
|
|
1371
1342
|
// Isolated scoped-home sessions are never retained after disposal.
|
|
1372
1343
|
const inPlaceEnvelope = envelope.worktree_path === envelope.repo_root;
|
|
1373
1344
|
const rawContextPacket = await rawContextForEnvelope(routed.implementationTransport, envelope);
|
|
1374
|
-
const sessionFields = runInput
|
|
1345
|
+
const sessionFields = runInput
|
|
1346
|
+
? await this.sessionSpecFields(runInput, adapter.id, log, routed.authRouteEstimate)
|
|
1347
|
+
: undefined;
|
|
1375
1348
|
// Continuity (INV-137): once the lane (harness + resolved profile) is known,
|
|
1376
1349
|
// build the continuation packet, materialize context/THREAD.md, and point
|
|
1377
1350
|
// the prompt at it — never embed the packet body in the prompt. Replaces the
|
|
1378
1351
|
// old static session.rebound "not_portable" phrase with a real disclosure.
|
|
1379
1352
|
const laneContinuity = runInput
|
|
1380
|
-
? await this.resolveContinuity(runInput, adapter.id, sessionFields?.credential_profile?.profile_id ?? runInput.credentialProfileId ?? null, inPlaceEnvelope && !!sessionFields?.resume_session_id, store, paths, envelope.repo_root, log)
|
|
1353
|
+
? 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)
|
|
1381
1354
|
: null;
|
|
1382
1355
|
let spec = HarnessRunSpec.parse({
|
|
1383
1356
|
session_id: newId("ses"),
|
|
@@ -1480,13 +1453,16 @@ export class Orchestrator {
|
|
|
1480
1453
|
try {
|
|
1481
1454
|
const watched = withInactivityWatchdog(adapter.run(runSpec), {
|
|
1482
1455
|
timeoutMs: inactivityMs,
|
|
1456
|
+
countsAsProgress: countsAsAgentProgress,
|
|
1483
1457
|
onTimeout: () => {
|
|
1484
1458
|
attemptAbort.abort();
|
|
1485
1459
|
void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
1486
1460
|
},
|
|
1487
|
-
// Waiting on the USER
|
|
1488
|
-
//
|
|
1461
|
+
// Waiting on the USER is legitimate silence. The interaction policy
|
|
1462
|
+
// either enforces a finite deadline or waits until answer/cancel/
|
|
1463
|
+
// terminal/restart when the timeout is disabled.
|
|
1489
1464
|
isSuspended: () => (interaction?.pendingCount?.() ?? 0) > 0,
|
|
1465
|
+
suspensionVersion: () => interaction?.suspensionVersion?.() ?? 0,
|
|
1490
1466
|
});
|
|
1491
1467
|
for await (const ev of watched) {
|
|
1492
1468
|
if (signal?.aborted)
|
|
@@ -1616,13 +1592,18 @@ export class Orchestrator {
|
|
|
1616
1592
|
// W5.4 failover: a typed-limit hit rebuilds the spec on a NEW vendor
|
|
1617
1593
|
// session under the next profile with provenance (vendor_limit_rejected).
|
|
1618
1594
|
if (harnessErrored && runInput && !signal?.aborted) {
|
|
1595
|
+
const rotationPolicy = this.profilePolicy(contract.repo.root, adapter.id);
|
|
1596
|
+
const readyProfileIds = sawTypedLimit && deliverableEmpty && rotationPolicy.limit_action === "rotate"
|
|
1597
|
+
? await this.readyProfileIdsForRotation(runInput, adapter.id, spec.credential_profile ?? null, triedProfiles)
|
|
1598
|
+
: new Set();
|
|
1619
1599
|
const rotated = rotateSpecOnTypedLimit({
|
|
1620
1600
|
spec,
|
|
1621
1601
|
harnessId: adapter.id,
|
|
1622
1602
|
attemptId,
|
|
1623
|
-
policy:
|
|
1603
|
+
policy: rotationPolicy,
|
|
1624
1604
|
registry: this.config(contract.repo.root)?.global.credential_profiles ?? [],
|
|
1625
1605
|
snapshots: this.deps.quotaSnapshots?.() ?? [],
|
|
1606
|
+
readyProfileIds,
|
|
1626
1607
|
triedProfiles,
|
|
1627
1608
|
sawTypedLimit,
|
|
1628
1609
|
deliverableEmpty,
|
|
@@ -1679,7 +1660,7 @@ export class Orchestrator {
|
|
|
1679
1660
|
if (webUnsatisfied(telemetry)) {
|
|
1680
1661
|
errors.push(webEvidenceFailure(telemetry.web));
|
|
1681
1662
|
}
|
|
1682
|
-
// D-16:
|
|
1663
|
+
// D-16: remove the WorkReport transport so answer.md persists only the deliverable.
|
|
1683
1664
|
const unwrapped = unwrapWorkReportEnvelope(answer.machineText() ?? "", workReportMode, {
|
|
1684
1665
|
sideToolReport: telemetry.sideToolWorkReport ?? undefined,
|
|
1685
1666
|
});
|
|
@@ -1814,66 +1795,17 @@ export class Orchestrator {
|
|
|
1814
1795
|
// Thin delegate — the channel mechanics live in interaction.ts.
|
|
1815
1796
|
return interactionChannelFor(input, log, runId, taskId, attemptId, harnessId, supportsInteractive, DEFAULT_INTERACTION_TIMEOUT_MS);
|
|
1816
1797
|
}
|
|
1817
|
-
/**
|
|
1818
|
-
* Guarantee a git boundary for write-mode runs. Non-git project folders are
|
|
1819
|
-
* initialized in place (`git init`, deterministic baseline commit) without
|
|
1820
|
-
* creating or editing `.gitignore`, and the action is announced via a
|
|
1821
|
-
* `project.git.initialized` event. Returns the failure message when the
|
|
1822
|
-
* boundary cannot be established (the terminal failure events are already
|
|
1823
|
-
* emitted); null on success.
|
|
1824
|
-
*/
|
|
1825
|
-
async ensureWriteModeGitBoundary(repoRoot, log, store, paths, runId, mode) {
|
|
1826
|
-
if (repoRoot === NO_PROJECT_ROOT)
|
|
1827
|
-
return null;
|
|
1828
|
-
try {
|
|
1829
|
-
const result = await ensureGitRepository(repoRoot);
|
|
1830
|
-
if (result.initialized || result.baselineCommitted) {
|
|
1831
|
-
log.emit("project.git.initialized", {
|
|
1832
|
-
repo_root: repoRoot,
|
|
1833
|
-
initialized: result.initialized,
|
|
1834
|
-
baseline_committed: result.baselineCommitted,
|
|
1835
|
-
gitignore_seeded: result.gitignoreSeeded,
|
|
1836
|
-
head_sha: result.headSha,
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
return null;
|
|
1840
|
-
}
|
|
1841
|
-
catch (err) {
|
|
1842
|
-
const message = safeErrorMessage(err);
|
|
1843
|
-
writeFailure(store, paths, {
|
|
1844
|
-
phase: "workspace",
|
|
1845
|
-
category: "project",
|
|
1846
|
-
safeMessage: message,
|
|
1847
|
-
runDir: paths.root,
|
|
1848
|
-
nextActions: [
|
|
1849
|
-
"Check the project folder permissions",
|
|
1850
|
-
"Initialize git manually (git init)",
|
|
1851
|
-
"Retry the run",
|
|
1852
|
-
],
|
|
1853
|
-
});
|
|
1854
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: failed\n- Phase: workspace\n\n${message}\n`);
|
|
1855
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
1856
|
-
log.emit("run.failed", {
|
|
1857
|
-
lifecycle: "failed",
|
|
1858
|
-
facts: makeOutcomeFacts("failed", { reason: "harness_failed" }),
|
|
1859
|
-
reason: "harness_failed",
|
|
1860
|
-
phase: "workspace",
|
|
1861
|
-
error: message,
|
|
1862
|
-
failure_ref: "final/failure.yaml",
|
|
1863
|
-
});
|
|
1864
|
-
return message;
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
1798
|
/**
|
|
1868
1799
|
* D-14 layer 3 (AGENTS.md unification, INV-113): the ONE new live-tree write.
|
|
1869
1800
|
* When the PROJECT root has `AGENTS.md` and no `CLAUDE.md`, drop a thin
|
|
1870
1801
|
* `CLAUDE.md` (`@AGENTS.md` import + Claudexor ownership marker) so a Claude
|
|
1871
1802
|
* Code route reads the same instruction file codex/cursor read natively.
|
|
1872
1803
|
*
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1875
|
-
* untouched
|
|
1876
|
-
*
|
|
1804
|
+
* The project-root bridge has its own narrower fence: read-only modes never
|
|
1805
|
+
* reach this run-prep stage and `--in-place` stateful targets are left
|
|
1806
|
+
* untouched. Git admission is independently owned by `runStartRequiresGit`.
|
|
1807
|
+
* The write targets the PROJECT root (`repoRoot`), never a worktree envelope.
|
|
1808
|
+
* The workspace helper adds exclusive-create + no-follow +
|
|
1877
1809
|
* idempotency, so a hand-written or symlinked `CLAUDE.md` is never overwritten
|
|
1878
1810
|
* and a concurrent/second prep is a no-op. Announced via a typed
|
|
1879
1811
|
* `project.claude_bridge.created` event on an actual create only — the git-init
|
|
@@ -1898,37 +1830,6 @@ export class Orchestrator {
|
|
|
1898
1830
|
});
|
|
1899
1831
|
}
|
|
1900
1832
|
}
|
|
1901
|
-
/**
|
|
1902
|
-
* Freeze-on-implement delivery (D17/D27): verify the frozen plan's hash and
|
|
1903
|
-
* materialize it as context/PLAN.md in the run artifact tree — OUTSIDE every
|
|
1904
|
-
* worktree, so it can never dirty a diff — then point the prompt at the
|
|
1905
|
-
* absolute path. A mismatched or unreadable plan fails LOUDLY before any
|
|
1906
|
-
* harness spawns (the tamper fence; retry replays planRef verbatim, so a
|
|
1907
|
-
* retried implement can never silently run without its plan).
|
|
1908
|
-
*/
|
|
1909
|
-
withPlanBrief(input, store, paths, log) {
|
|
1910
|
-
if (!input.planRef)
|
|
1911
|
-
return input;
|
|
1912
|
-
const text = readTextSafe(input.planRef.path);
|
|
1913
|
-
if (!text || !text.trim()) {
|
|
1914
|
-
throw new Error(`implement plan: the frozen plan at ${input.planRef.path} is missing or unreadable`);
|
|
1915
|
-
}
|
|
1916
|
-
const digest = sha256(text).replace(/^sha256:/, "");
|
|
1917
|
-
if (digest !== input.planRef.sha256) {
|
|
1918
|
-
throw new Error(`implement plan: plan hash mismatch (expected ${input.planRef.sha256}, got ${digest}) — the plan was modified after freeze; re-run Implement from the plan turn`);
|
|
1919
|
-
}
|
|
1920
|
-
const briefPath = join(paths.contextDir, "PLAN.md");
|
|
1921
|
-
store.writeText(briefPath, text);
|
|
1922
|
-
log.emit("plan.brief.materialized", {
|
|
1923
|
-
plan_run_id: input.planRef.runId,
|
|
1924
|
-
sha256: input.planRef.sha256,
|
|
1925
|
-
path: "context/PLAN.md",
|
|
1926
|
-
});
|
|
1927
|
-
return {
|
|
1928
|
-
...input,
|
|
1929
|
-
prompt: `${input.prompt}\n\nThe approved plan is at: ${briefPath} — read it before starting and re-read it as needed.`,
|
|
1930
|
-
};
|
|
1931
|
-
}
|
|
1932
1833
|
async runRace(input, mode, announce) {
|
|
1933
1834
|
const taskId = input.taskId ?? newId("task");
|
|
1934
1835
|
const runId = input.runId ?? newId("run");
|
|
@@ -1936,20 +1837,28 @@ export class Orchestrator {
|
|
|
1936
1837
|
// announced: a refused run must fail the request loudly, not 200 a runId
|
|
1937
1838
|
// and leave an orphaned run dir without a terminal event.
|
|
1938
1839
|
const contract = this.buildContract(input, taskId, mode);
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1840
|
+
const planBrief = verifiedPlanBrief(input);
|
|
1841
|
+
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
1842
|
+
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
1843
|
+
input,
|
|
1844
|
+
contract,
|
|
1845
|
+
quotaSnapshots,
|
|
1846
|
+
store: this.artifactStore(input),
|
|
1847
|
+
authority: this.deps.delegationBudgetAuthority,
|
|
1848
|
+
runId,
|
|
1849
|
+
taskId,
|
|
1850
|
+
mode,
|
|
1851
|
+
phase: "race",
|
|
1852
|
+
prompt: input.prompt,
|
|
1853
|
+
}, announce);
|
|
1854
|
+
input = withPlanBrief(input, store, paths, log, planBrief);
|
|
1855
|
+
// The execution root is the tree the harness mutates: the project itself
|
|
1856
|
+
// for in-place threads/ordinary runs, or the thread's persistent worktree
|
|
1857
|
+
// for an isolated thread. Config/artifacts/contract stay anchored to
|
|
1858
|
+
// repoRoot. Both the WorkspaceManager and the git boundary resolve against
|
|
1859
|
+
// this SINGLE root.
|
|
1947
1860
|
const execRoot = this.execRootOf(input);
|
|
1948
1861
|
const wsm = new WorkspaceManager(execRoot);
|
|
1949
|
-
safeInvoke(input.onRunStart, { runId, taskId, runDir: paths.root });
|
|
1950
|
-
log.emit("run.created", { mode, prompt: redactSecrets(input.prompt) });
|
|
1951
|
-
const ledger = this.rootLedger(input, contract, log);
|
|
1952
|
-
announce?.(announcedRunContext({ log, store, paths, runId, taskId, mode, phase: "race" }, ledger, () => this.deps.delegationBudgetAuthority?.hasParent(runId) === true));
|
|
1953
1862
|
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
1954
1863
|
log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
|
|
1955
1864
|
// Write modes need a git boundary for worktree isolation and honest diffs.
|
|
@@ -1958,17 +1867,17 @@ export class Orchestrator {
|
|
|
1958
1867
|
// silent mutation (user-locked decision, comparator: Codex requires git).
|
|
1959
1868
|
// For an isolated thread the execution root is already a git worktree, so
|
|
1960
1869
|
// this is a no-op there; for in-place it ensures the live project is git.
|
|
1961
|
-
const gitPreconditionError = await
|
|
1870
|
+
const gitPreconditionError = await ensureWriteModeGitBoundary(execRoot, log, store, paths, runId, mode);
|
|
1962
1871
|
if (gitPreconditionError) {
|
|
1963
1872
|
return {
|
|
1964
1873
|
runId,
|
|
1965
1874
|
taskId,
|
|
1966
1875
|
mode,
|
|
1967
1876
|
lifecycle: "failed",
|
|
1968
|
-
facts: makeOutcomeFacts("failed", { reason:
|
|
1877
|
+
facts: makeOutcomeFacts("failed", { reason: gitPreconditionError.reason }),
|
|
1969
1878
|
winner: null,
|
|
1970
1879
|
runDir: paths.root,
|
|
1971
|
-
summary: gitPreconditionError,
|
|
1880
|
+
summary: gitPreconditionError.message,
|
|
1972
1881
|
candidates: [],
|
|
1973
1882
|
};
|
|
1974
1883
|
}
|
|
@@ -2066,7 +1975,7 @@ export class Orchestrator {
|
|
|
2066
1975
|
attemptId,
|
|
2067
1976
|
intent: this.candidateIntent(input),
|
|
2068
1977
|
harnessId: routed.adapter.id,
|
|
2069
|
-
cost: attemptCostEvidence(routed.adapter.id, attemptId, i > 0
|
|
1978
|
+
cost: attemptCostEvidence(routed.adapter.id, attemptId, this.reservationEstimateUsd(input, i > 0), this.routeBillingKnowledge(input, routed.adapter.id)),
|
|
2070
1979
|
});
|
|
2071
1980
|
log.emit("budget.lease.created", {
|
|
2072
1981
|
granted: lease.granted,
|
|
@@ -2232,6 +2141,13 @@ export class Orchestrator {
|
|
|
2232
2141
|
continuation_count: candidateContinuationCount,
|
|
2233
2142
|
packet_turns: packet.continuity.disclosure.packetTurns,
|
|
2234
2143
|
});
|
|
2144
|
+
log.emit("harness.started", {
|
|
2145
|
+
harness_id: adapter.id,
|
|
2146
|
+
attempt_id: contAttemptId,
|
|
2147
|
+
external_context_policy: knobs.webPolicy,
|
|
2148
|
+
...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
|
|
2149
|
+
continuation_of: run.attemptId,
|
|
2150
|
+
});
|
|
2235
2151
|
const contLeaseId = contLease.lease?.lease_id ?? "";
|
|
2236
2152
|
try {
|
|
2237
2153
|
const contRun = await this.runCandidateInEnvelope(slot.routed, envelope, contAttemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, candidateAccess, (ev) => {
|
|
@@ -2519,7 +2435,7 @@ export class Orchestrator {
|
|
|
2519
2435
|
// candidates (a trivial greeting in agent mode no longer burns two reviewers on
|
|
2520
2436
|
// "(empty diff)"). Candidates still flow through arbitration/gates so the
|
|
2521
2437
|
// no_op/answer outcome and gate failures are unchanged.
|
|
2522
|
-
evidences = await this.reviewRuns(workingRuns, reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal);
|
|
2438
|
+
evidences = await this.reviewRuns(workingRuns, reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input));
|
|
2523
2439
|
}
|
|
2524
2440
|
catch (err) {
|
|
2525
2441
|
// Review preflight/evidence failures end TERMINALLY with artifacts —
|
|
@@ -2544,7 +2460,7 @@ export class Orchestrator {
|
|
|
2544
2460
|
attemptId: "synth",
|
|
2545
2461
|
intent: "synthesize",
|
|
2546
2462
|
harnessId: synthRouted.adapter.id,
|
|
2547
|
-
cost: attemptCostEvidence(synthRouted.adapter.id, "synth",
|
|
2463
|
+
cost: attemptCostEvidence(synthRouted.adapter.id, "synth", this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, synthRouted.adapter.id)),
|
|
2548
2464
|
});
|
|
2549
2465
|
if (lease.granted) {
|
|
2550
2466
|
let envelope;
|
|
@@ -2580,7 +2496,7 @@ export class Orchestrator {
|
|
|
2580
2496
|
runs.push(run);
|
|
2581
2497
|
try {
|
|
2582
2498
|
if (isWorkingCandidate(run)) {
|
|
2583
|
-
const synthEvidence = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal);
|
|
2499
|
+
const synthEvidence = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input));
|
|
2584
2500
|
evidences.push(...synthEvidence);
|
|
2585
2501
|
workingRuns.push(run);
|
|
2586
2502
|
}
|
|
@@ -2629,8 +2545,12 @@ export class Orchestrator {
|
|
|
2629
2545
|
// Winner can only be a candidate that actually produced work; corpses are
|
|
2630
2546
|
// excluded from arbitration upstream and from the fallback here.
|
|
2631
2547
|
const winnerRun = workingRuns.find((r) => r.attemptId === result.decision.winner) ?? workingRuns[0];
|
|
2632
|
-
|
|
2633
|
-
|
|
2548
|
+
const winnerEvidence = winnerRun
|
|
2549
|
+
? evidences.find((e) => e.attemptId === winnerRun.attemptId)
|
|
2550
|
+
: undefined;
|
|
2551
|
+
// D9 winner-only NEEDS_HUMAN gate, fail-closed on a winner with no review
|
|
2552
|
+
// evidence record (see winnerNeedsHuman).
|
|
2553
|
+
const needsHuman = winnerNeedsHuman(winnerRun?.attemptId ?? null, evidences);
|
|
2634
2554
|
// Run-level review_verified is the WINNER's verification: an
|
|
2635
2555
|
// empty-diff loser's unverified route must not drag the shipped result's
|
|
2636
2556
|
// flag false. No winner -> fall back to the all-candidates view.
|
|
@@ -2693,12 +2613,11 @@ export class Orchestrator {
|
|
|
2693
2613
|
})) {
|
|
2694
2614
|
log.emit("output.ready", { kind: "artifact", path });
|
|
2695
2615
|
}
|
|
2696
|
-
assertNoSecretLikeTokens("final patch diff", winnerRun.diff);
|
|
2616
|
+
secretDiff.assertNoSecretLikeTokens("final patch diff", winnerRun.diff);
|
|
2697
2617
|
const patchSha256 = sha256(winnerRun.diff);
|
|
2698
2618
|
store.writeText(join(paths.finalDir, "patch.diff"), winnerRun.diff);
|
|
2699
2619
|
const wstats = diffStats(winnerRun.diff);
|
|
2700
2620
|
const hasDiff = winnerRun.diff.trim().length > 0;
|
|
2701
|
-
const winnerEvidence = evidences.find((e) => e.attemptId === winnerRun.attemptId);
|
|
2702
2621
|
const blockers = winnerEvidence
|
|
2703
2622
|
? winnerEvidence.findings.filter((f) => isBlocking(f)).length
|
|
2704
2623
|
: 0;
|
|
@@ -3011,7 +2930,7 @@ export class Orchestrator {
|
|
|
3011
2930
|
env: reviewHome.env,
|
|
3012
2931
|
}).finally(() => reviewHome.dispose());
|
|
3013
2932
|
}
|
|
3014
|
-
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal) {
|
|
2933
|
+
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal, reservationEstimateUsd) {
|
|
3015
2934
|
const evidences = [];
|
|
3016
2935
|
for (const run of runs) {
|
|
3017
2936
|
const candidateCwd = run.reviewCwd ?? cwd;
|
|
@@ -3030,7 +2949,7 @@ export class Orchestrator {
|
|
|
3030
2949
|
attemptId: run.attemptId,
|
|
3031
2950
|
intent: "review",
|
|
3032
2951
|
harnessId: "review-panel",
|
|
3033
|
-
cost: attemptCostEvidence("review-panel", run.attemptId),
|
|
2952
|
+
cost: attemptCostEvidence("review-panel", run.attemptId, reservationEstimateUsd),
|
|
3034
2953
|
})
|
|
3035
2954
|
: undefined;
|
|
3036
2955
|
const result = hasDiff && reviewers.length > 0 && (reviewLease?.granted ?? true)
|
|
@@ -3174,10 +3093,21 @@ export class Orchestrator {
|
|
|
3174
3093
|
const runId = input.runId ?? newId("run");
|
|
3175
3094
|
// Contract validation BEFORE the run is announced (see runRace).
|
|
3176
3095
|
const contract = this.buildContract(input, taskId, mode);
|
|
3177
|
-
const
|
|
3178
|
-
const
|
|
3179
|
-
const
|
|
3180
|
-
|
|
3096
|
+
const planBrief = verifiedPlanBrief(input);
|
|
3097
|
+
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
3098
|
+
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
3099
|
+
input,
|
|
3100
|
+
contract,
|
|
3101
|
+
quotaSnapshots,
|
|
3102
|
+
store: this.artifactStore(input),
|
|
3103
|
+
authority: this.deps.delegationBudgetAuthority,
|
|
3104
|
+
runId,
|
|
3105
|
+
taskId,
|
|
3106
|
+
mode,
|
|
3107
|
+
phase: "convergence",
|
|
3108
|
+
prompt: input.prompt,
|
|
3109
|
+
}, announce);
|
|
3110
|
+
input = withPlanBrief(input, store, paths, log, planBrief);
|
|
3181
3111
|
// The execution root is the tree the harness mutates (thread worktree for an
|
|
3182
3112
|
// isolated thread, else the project). The WorkspaceManager AND the git
|
|
3183
3113
|
// boundary must resolve against the SAME root — the race path does so via the
|
|
@@ -3186,27 +3116,11 @@ export class Orchestrator {
|
|
|
3186
3116
|
const execRoot = this.execRootOf(input);
|
|
3187
3117
|
const wsm = new WorkspaceManager(execRoot);
|
|
3188
3118
|
const readiness = new ReadinessLedger();
|
|
3189
|
-
|
|
3190
|
-
try {
|
|
3191
|
-
ledger = this.rootLedger(input, contract, log);
|
|
3192
|
-
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
3193
|
-
safeInvoke(input.onRunStart, { runId, taskId, runDir: paths.root });
|
|
3194
|
-
log.emit("run.created", { mode, prompt: redactSecrets(input.prompt) });
|
|
3195
|
-
announce?.(announcedRunContext({ log, store, paths, runId, taskId, mode, phase: "convergence" }, ledger, () => this.deps.delegationBudgetAuthority?.hasParent(runId) === true));
|
|
3196
|
-
}
|
|
3197
|
-
catch (error) {
|
|
3198
|
-
// A delegated child attaches its scoped financial view before the run is
|
|
3199
|
-
// announced. If any fallible artifact/start callback in that narrow gap
|
|
3200
|
-
// throws, the terminal net has no run context, so detach here explicitly.
|
|
3201
|
-
if (input.delegatedFromRunId) {
|
|
3202
|
-
this.deps.delegationBudgetAuthority?.releaseRun(runId);
|
|
3203
|
-
}
|
|
3204
|
-
throw error;
|
|
3205
|
-
}
|
|
3119
|
+
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
3206
3120
|
// Live (in-place) isolation deliberately tolerates non-git stateful
|
|
3207
3121
|
// environments; only envelope isolation needs the git boundary.
|
|
3208
3122
|
if (!input.inPlace) {
|
|
3209
|
-
const gitPreconditionError = await
|
|
3123
|
+
const gitPreconditionError = await ensureWriteModeGitBoundary(execRoot, log, store, paths, runId, mode);
|
|
3210
3124
|
if (gitPreconditionError) {
|
|
3211
3125
|
return {
|
|
3212
3126
|
spendUsd: ledger.spend(),
|
|
@@ -3214,10 +3128,10 @@ export class Orchestrator {
|
|
|
3214
3128
|
taskId,
|
|
3215
3129
|
mode,
|
|
3216
3130
|
lifecycle: "failed",
|
|
3217
|
-
facts: makeOutcomeFacts("failed", { reason:
|
|
3131
|
+
facts: makeOutcomeFacts("failed", { reason: gitPreconditionError.reason }),
|
|
3218
3132
|
winner: null,
|
|
3219
3133
|
runDir: paths.root,
|
|
3220
|
-
summary: gitPreconditionError,
|
|
3134
|
+
summary: gitPreconditionError.message,
|
|
3221
3135
|
candidates: [],
|
|
3222
3136
|
};
|
|
3223
3137
|
}
|
|
@@ -3406,7 +3320,7 @@ export class Orchestrator {
|
|
|
3406
3320
|
attemptId,
|
|
3407
3321
|
intent: "repair",
|
|
3408
3322
|
harnessId: adapter.id,
|
|
3409
|
-
cost: attemptCostEvidence(adapter.id, attemptId,
|
|
3323
|
+
cost: attemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, adapter.id)),
|
|
3410
3324
|
});
|
|
3411
3325
|
if (!lease.granted) {
|
|
3412
3326
|
exhausted = true;
|
|
@@ -3518,7 +3432,7 @@ export class Orchestrator {
|
|
|
3518
3432
|
attemptId,
|
|
3519
3433
|
intent: "review",
|
|
3520
3434
|
harnessId: "review-panel",
|
|
3521
|
-
cost: attemptCostEvidence("review-panel", attemptId),
|
|
3435
|
+
cost: attemptCostEvidence("review-panel", attemptId, this.reservationEstimateUsd(input)),
|
|
3522
3436
|
})
|
|
3523
3437
|
: null;
|
|
3524
3438
|
const reviewResult = reviewers.length > 0 && (reviewLease?.granted ?? false)
|
|
@@ -3808,7 +3722,7 @@ export class Orchestrator {
|
|
|
3808
3722
|
// work_product (its partial patch.diff stays diagnostic via attempts/);
|
|
3809
3723
|
// in-place keeps the product so the honest Revert offer survives.
|
|
3810
3724
|
if (lastRun && (!interrupted || input.inPlace === true)) {
|
|
3811
|
-
assertNoSecretLikeTokens("final patch diff", lastRun.diff);
|
|
3725
|
+
secretDiff.assertNoSecretLikeTokens("final patch diff", lastRun.diff);
|
|
3812
3726
|
const patchSha256 = sha256(lastRun.diff);
|
|
3813
3727
|
store.writeText(join(paths.finalDir, "patch.diff"), lastRun.diff);
|
|
3814
3728
|
// Honest apply-state (parity with runRace single-candidate in-place): a
|
|
@@ -3978,265 +3892,77 @@ export class Orchestrator {
|
|
|
3978
3892
|
reviewVerified: actualReviewVerified,
|
|
3979
3893
|
};
|
|
3980
3894
|
}
|
|
3981
|
-
/**
|
|
3982
|
-
|
|
3983
|
-
const { input, contract, taskId, runId, log, store, paths, ledger, routed, attemptId } = args;
|
|
3984
|
-
const adapter = routed.adapter;
|
|
3985
|
-
const lease = ledger.reserve({
|
|
3986
|
-
taskId,
|
|
3987
|
-
attemptId,
|
|
3988
|
-
intent: args.intent,
|
|
3989
|
-
harnessId: adapter.id,
|
|
3990
|
-
cost: attemptCostEvidence(adapter.id, attemptId, undefined, this.routeBillingKnowledge(input, adapter.id)),
|
|
3991
|
-
});
|
|
3992
|
-
if (!lease.granted) {
|
|
3993
|
-
log.emit("budget.lease.created", {
|
|
3994
|
-
granted: false,
|
|
3995
|
-
reason: lease.reason,
|
|
3996
|
-
denied: lease.denied,
|
|
3997
|
-
attempt_id: attemptId,
|
|
3998
|
-
harness_id: adapter.id,
|
|
3999
|
-
});
|
|
4000
|
-
return {
|
|
4001
|
-
attemptId,
|
|
4002
|
-
harnessId: adapter.id,
|
|
4003
|
-
status: "failed",
|
|
4004
|
-
outcomeClass: "clean", // never spawned: refused pre-flight by the budget gate
|
|
4005
|
-
error: lease.reason ?? "budget lease denied",
|
|
4006
|
-
text: null,
|
|
4007
|
-
telemetry: null,
|
|
4008
|
-
budgetDenied: true,
|
|
4009
|
-
budgetDenial: {
|
|
4010
|
-
code: lease.denied ?? "hard_cap",
|
|
4011
|
-
reason: lease.reason ?? "budget lease denied",
|
|
4012
|
-
harnessId: adapter.id,
|
|
4013
|
-
attemptId,
|
|
4014
|
-
},
|
|
4015
|
-
};
|
|
4016
|
-
}
|
|
4017
|
-
const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
|
|
4018
|
-
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
4019
|
-
const planSessionFields = this.sessionSpecFields(input, adapter.id, log);
|
|
4020
|
-
// Continuity (INV-137): a thread PLAN turn is a chat turn — hydrate a
|
|
4021
|
-
// lane switch/gap with a packet and disclose it.
|
|
4022
|
-
const laneContinuity = args.laneRun
|
|
4023
|
-
? await this.resolveContinuity(input, adapter.id, planSessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, planSessionFields.resume_session_id !== null, store, paths, this.execRootOf(input), log)
|
|
4024
|
-
: null;
|
|
4025
|
-
const spec = HarnessRunSpec.parse({
|
|
4026
|
-
session_id: newId("ses"),
|
|
4027
|
-
intent: args.intent,
|
|
4028
|
-
prompt: laneContinuity?.pointerLine
|
|
4029
|
-
? `${args.promptBody}\n\n${laneContinuity.pointerLine}`
|
|
4030
|
-
: args.promptBody,
|
|
4031
|
-
cwd: this.execRootOf(input),
|
|
4032
|
-
access: "readonly",
|
|
4033
|
-
// Planners must SEE any image/file the user attached (e.g. "plan a fix for
|
|
4034
|
-
// what's in this screenshot"), not just agent/race runs.
|
|
4035
|
-
attachments: input.attachments ?? [],
|
|
4036
|
-
...planSessionFields,
|
|
4037
|
-
...this.harnessSpecKnobs(contract, knobs, args.intent),
|
|
4038
|
-
env_inheritance: envInheritance(this.config(input.repoRoot)),
|
|
4039
|
-
// A thread plan turn spawns in its DURABLE per-lane home so its native
|
|
4040
|
-
// session is reachable for resume next turn (INV-034); a non-thread
|
|
4041
|
-
// plan keeps the disposable route-context home.
|
|
4042
|
-
env: (args.laneRun ? this.laneHomeEnvFor(input, adapter.id) : null) ?? args.fallbackHome,
|
|
4043
|
-
});
|
|
4044
|
-
const plannerAbort = new AbortController();
|
|
4045
|
-
spec.extra["abortSignal"] = input.signal
|
|
4046
|
-
? AbortSignal.any([input.signal, plannerAbort.signal])
|
|
4047
|
-
: plannerAbort.signal;
|
|
4048
|
-
const planInteraction = this.interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
4049
|
-
if (planInteraction)
|
|
4050
|
-
spec.extra["interactionChannel"] = planInteraction;
|
|
4051
|
-
// D-16: compile the WorkReport envelope for the plan lane (require plan text
|
|
4052
|
-
// below folds the deliverable; the veto rides work_state).
|
|
4053
|
-
const planWorkEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(planInteraction));
|
|
4054
|
-
const planWorkMode = this.applyWorkEnvelope(spec, planWorkEnvelope);
|
|
4055
|
-
const attemptEventsPath = join(paths.attemptsDir, attemptId, "events.jsonl");
|
|
4056
|
-
const answer = new AnswerAssembly();
|
|
4057
|
-
const telemetry = createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required ||
|
|
4058
|
-
knobs.webPolicy === "cached" ||
|
|
4059
|
-
knobs.webPolicy === "live", effectiveWeb, [],
|
|
4060
|
-
// Requested-model capture: a plan lane silently downgraded to another
|
|
4061
|
-
// model surfaces the mismatch in its route receipt, just like agent.
|
|
4062
|
-
knobs.model);
|
|
4063
|
-
const onAbort = () => {
|
|
4064
|
-
void adapter.cancel?.(spec.session_id)?.catch(() => { });
|
|
4065
|
-
};
|
|
4066
|
-
if (input.signal) {
|
|
4067
|
-
if (input.signal.aborted)
|
|
4068
|
-
onAbort();
|
|
4069
|
-
else
|
|
4070
|
-
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
4071
|
-
}
|
|
4072
|
-
let cost = 0;
|
|
4073
|
-
let costEstimated = false;
|
|
4074
|
-
let harnessError = null;
|
|
4075
|
-
const budgetSignalState = { quotaPressureDisclosed: false };
|
|
4076
|
-
try {
|
|
4077
|
-
log.emit("harness.started", {
|
|
4078
|
-
harness_id: adapter.id,
|
|
4079
|
-
attempt_id: attemptId,
|
|
4080
|
-
external_context_policy: knobs.webPolicy,
|
|
4081
|
-
...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
|
|
4082
|
-
});
|
|
4083
|
-
if (!input.signal?.aborted) {
|
|
4084
|
-
const watchedPlan = withInactivityWatchdog(adapter.run(spec), {
|
|
4085
|
-
timeoutMs: harnessInactivityTimeoutMs(this.config(input.repoRoot)),
|
|
4086
|
-
onTimeout: () => {
|
|
4087
|
-
plannerAbort.abort();
|
|
4088
|
-
void adapter.cancel?.(spec.session_id)?.catch(() => { });
|
|
4089
|
-
},
|
|
4090
|
-
isSuspended: () => (planInteraction?.pendingCount?.() ?? 0) > 0,
|
|
4091
|
-
});
|
|
4092
|
-
for await (const ev of watchedPlan) {
|
|
4093
|
-
if (input.signal?.aborted)
|
|
4094
|
-
break;
|
|
4095
|
-
const safeEv = redactHarnessEvent(ev);
|
|
4096
|
-
safeInvoke(input.onHarnessEvent, safeEv);
|
|
4097
|
-
// A thread PLAN turn IS a chat turn now (INV-034): its native
|
|
4098
|
-
// session lives in the DURABLE per-lane home, so record it for the
|
|
4099
|
-
// next lane turn's resume. Council members are distinct lanes.
|
|
4100
|
-
if (args.laneRun)
|
|
4101
|
-
observeNativeSessionEvent(input, adapter.id, safeEv);
|
|
4102
|
-
observeAuthSwitch(log, adapter.id, attemptId, safeEv);
|
|
4103
|
-
log.emit("harness.event", harnessEventPayload(adapter.id, attemptId, safeEv));
|
|
4104
|
-
appendLine(attemptEventsPath, JSON.stringify(safeEv));
|
|
4105
|
-
observeAttemptTelemetry(telemetry, safeEv);
|
|
4106
|
-
if (safeEv.plan_progress) {
|
|
4107
|
-
log.emit("plan.progress", {
|
|
4108
|
-
attempt_id: attemptId,
|
|
4109
|
-
harness_id: adapter.id,
|
|
4110
|
-
items: safeEv.plan_progress.items,
|
|
4111
|
-
});
|
|
4112
|
-
}
|
|
4113
|
-
// read-only routes burn quota too — same single owner as the agent loop.
|
|
4114
|
-
observeBudgetSignals(ledger, log, adapter.id, attemptId, safeEv, budgetSignalState);
|
|
4115
|
-
this.deps.quotaEventSink?.(adapter.id, safeEv);
|
|
4116
|
-
if (safeEv.type === "usage" && safeEv.usage?.cost_usd) {
|
|
4117
|
-
cost += safeEv.usage.cost_usd;
|
|
4118
|
-
if (safeEv.usage.estimated)
|
|
4119
|
-
costEstimated = true;
|
|
4120
|
-
log.emit("budget.observation", {
|
|
4121
|
-
harness_id: adapter.id,
|
|
4122
|
-
attempt_id: attemptId,
|
|
4123
|
-
kind: "spend",
|
|
4124
|
-
usd: safeEv.usage.cost_usd,
|
|
4125
|
-
estimated: safeEv.usage.estimated === true,
|
|
4126
|
-
});
|
|
4127
|
-
}
|
|
4128
|
-
// A TYPED final message wins verbatim over joined narration.
|
|
4129
|
-
answer.observe(safeEv);
|
|
4130
|
-
if (safeEv.type === "error")
|
|
4131
|
-
harnessError = safeEv.error ? redactSecrets(safeEv.error) : "harness emitted an error";
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
catch (err) {
|
|
4136
|
-
harnessError = safeErrorMessage(err);
|
|
4137
|
-
}
|
|
4138
|
-
finally {
|
|
4139
|
-
input.signal?.removeEventListener("abort", onAbort);
|
|
4140
|
-
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(cost, costEstimated, attemptId, adapter.id, telemetry.authMode, telemetry.usageCost));
|
|
4141
|
-
}
|
|
4142
|
-
// D-16: unwrap and require PLAN TEXT — a plan with no text is not delivered.
|
|
4143
|
-
// The unwrap runs BEFORE the error axes: the deliverable it yields is what
|
|
4144
|
-
// decides whether an unrecovered tool error is fatal (explorer parity).
|
|
4145
|
-
const planUnwrapped = unwrapWorkReportEnvelope(answer.machineText() ?? "", planWorkMode, {
|
|
4146
|
-
sideToolReport: telemetry.sideToolWorkReport ?? undefined,
|
|
4147
|
-
});
|
|
4148
|
-
const planText = redactSecrets(planUnwrapped.deliverable).trim();
|
|
4149
|
-
const unrecovered = unrecoveredToolErrors(telemetry);
|
|
4150
|
-
const webBlocked = webUnsatisfied(telemetry);
|
|
4151
|
-
if (!harnessError && webBlocked) {
|
|
4152
|
-
harnessError = webEvidenceFailure(telemetry.web);
|
|
4153
|
-
}
|
|
4154
|
-
// INV-043/INV-044, explorer parity: a DELIVERED plan keeps an unrecovered
|
|
4155
|
-
// non-web tool error as warning evidence instead of discarding the plan (see
|
|
4156
|
-
// the helper). Web keeps its hard gate above; the finalizer outranks both.
|
|
4157
|
-
harnessError ??= unrecoveredToolErrorFailure(unrecovered, planText.length > 0);
|
|
4158
|
-
const planFinalized = finalizeAttempt({
|
|
4159
|
-
deliverableEvidence: planText.length > 0,
|
|
4160
|
-
harnessErrored: harnessError !== null && !webBlocked,
|
|
4161
|
-
workReport: planUnwrapped.workReport,
|
|
4162
|
-
workReportSource: planUnwrapped.source,
|
|
4163
|
-
workReportViolation: planUnwrapped.contractViolation,
|
|
4164
|
-
contextTerminalExhausted: telemetry.contextExhausted,
|
|
4165
|
-
});
|
|
4166
|
-
// A broken WorkReport contract is a hard failure only when the finalizer
|
|
4167
|
-
// ranked it so (a terminal context exhaustion outranks it).
|
|
4168
|
-
if (!harnessError && planFinalized.outcomeClass === "contract_failure") {
|
|
4169
|
-
harnessError = `work_report contract: ${planUnwrapped.contractViolation}`;
|
|
4170
|
-
}
|
|
4171
|
-
// D-16 r9: an interrupted (context-exhausted) planner is NEVER a clean
|
|
4172
|
-
// plan — partial text must not become final/plan.md as success. A VETO
|
|
4173
|
-
// (needs_input/incomplete work_state) is DIFFERENT by the sealed D-16
|
|
4174
|
-
// contract (X35, INV-116 canaries): the plan still delivers, lifecycle
|
|
4175
|
-
// succeeded, and the work_state veto rides the OUTCOME (non-zero exit) —
|
|
4176
|
-
// it must not be laundered into a harness failure either direction.
|
|
4177
|
-
if (!harnessError && planFinalized.outcomeClass === "interrupted") {
|
|
4178
|
-
harnessError = "context capacity exhausted before the plan completed";
|
|
4179
|
-
}
|
|
4180
|
-
const attemptError = harnessError ??
|
|
4181
|
-
(planFinalized.deliverablePresent ? null : "planner produced no plan text") ??
|
|
4182
|
-
(input.signal?.aborted ? "planner cancelled" : null);
|
|
4183
|
-
setAttemptOutcome(telemetry, {
|
|
4184
|
-
deliverablePresent: planFinalized.deliverablePresent,
|
|
4185
|
-
gatesPassed: null,
|
|
4186
|
-
harnessErrored: (harnessError !== null && !webBlocked) || planFinalized.harnessErrored,
|
|
4187
|
-
webRequiredUnsatisfied: webBlocked,
|
|
4188
|
-
workState: planFinalized.workState,
|
|
4189
|
-
});
|
|
4190
|
-
if (attemptError) {
|
|
4191
|
-
log.emit("harness.completed", {
|
|
4192
|
-
harness_id: adapter.id,
|
|
4193
|
-
attempt_id: attemptId,
|
|
4194
|
-
status: webBlocked ? "blocked" : "failed",
|
|
4195
|
-
error: attemptError,
|
|
4196
|
-
...telemetrySummary(telemetry),
|
|
4197
|
-
});
|
|
4198
|
-
return {
|
|
4199
|
-
attemptId,
|
|
4200
|
-
harnessId: adapter.id,
|
|
4201
|
-
status: webBlocked ? "blocked" : "failed",
|
|
4202
|
-
outcomeClass: planFinalized.outcomeClass,
|
|
4203
|
-
error: attemptError,
|
|
4204
|
-
text: null,
|
|
4205
|
-
telemetry,
|
|
4206
|
-
budgetDenied: false,
|
|
4207
|
-
};
|
|
4208
|
-
}
|
|
4209
|
-
const text = planText || "(no output)";
|
|
4210
|
-
log.emit("harness.completed", {
|
|
4211
|
-
harness_id: adapter.id,
|
|
4212
|
-
attempt_id: attemptId,
|
|
4213
|
-
status: "success",
|
|
4214
|
-
...telemetrySummary(telemetry),
|
|
4215
|
-
});
|
|
3895
|
+
/** Bind private route/session preparation to the planner-attempt owner. */
|
|
3896
|
+
plannerAttemptDeps() {
|
|
4216
3897
|
return {
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
3898
|
+
billingKnowledge: (input, harnessId) => this.routeBillingKnowledge(input, harnessId),
|
|
3899
|
+
inactivityTimeoutMs: (repoRoot) => harnessInactivityTimeoutMs(this.config(repoRoot)),
|
|
3900
|
+
quotaEventSink: this.deps.quotaEventSink,
|
|
3901
|
+
prepare: async (args) => {
|
|
3902
|
+
const { input, contract, taskId, runId, log, store, paths, routed, attemptId } = args;
|
|
3903
|
+
const adapter = routed.adapter;
|
|
3904
|
+
const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
|
|
3905
|
+
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
3906
|
+
const sessionFields = await this.sessionSpecFields(input, adapter.id, log, routed.authRouteEstimate);
|
|
3907
|
+
const laneContinuity = args.laneRun
|
|
3908
|
+
? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, sessionFields.resume_session_id !== null, sessionFields, store, paths, this.execRootOf(input), log)
|
|
3909
|
+
: null;
|
|
3910
|
+
const spec = HarnessRunSpec.parse({
|
|
3911
|
+
session_id: newId("ses"),
|
|
3912
|
+
intent: args.intent,
|
|
3913
|
+
prompt: laneContinuity?.pointerLine
|
|
3914
|
+
? `${args.promptBody}\n\n${laneContinuity.pointerLine}`
|
|
3915
|
+
: args.promptBody,
|
|
3916
|
+
cwd: this.execRootOf(input),
|
|
3917
|
+
access: "readonly",
|
|
3918
|
+
attachments: input.attachments ?? [],
|
|
3919
|
+
...sessionFields,
|
|
3920
|
+
...this.harnessSpecKnobs(contract, knobs, args.intent),
|
|
3921
|
+
env_inheritance: envInheritance(this.config(input.repoRoot)),
|
|
3922
|
+
env: (args.laneRun ? this.laneHomeEnvFor(input, adapter.id) : null) ?? args.fallbackHome,
|
|
3923
|
+
});
|
|
3924
|
+
const plannerAbort = new AbortController();
|
|
3925
|
+
spec.extra["abortSignal"] = input.signal
|
|
3926
|
+
? AbortSignal.any([input.signal, plannerAbort.signal])
|
|
3927
|
+
: plannerAbort.signal;
|
|
3928
|
+
const planInteraction = this.interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
3929
|
+
if (planInteraction)
|
|
3930
|
+
spec.extra["interactionChannel"] = planInteraction;
|
|
3931
|
+
const planWorkMode = this.applyWorkEnvelope(spec, this.workReportEnvelopeFor(routed, contract, Boolean(planInteraction)));
|
|
3932
|
+
return {
|
|
3933
|
+
knobs,
|
|
3934
|
+
effectiveWeb,
|
|
3935
|
+
spec,
|
|
3936
|
+
plannerAbort,
|
|
3937
|
+
planInteraction,
|
|
3938
|
+
planWorkMode,
|
|
3939
|
+
};
|
|
3940
|
+
},
|
|
4225
3941
|
};
|
|
4226
3942
|
}
|
|
3943
|
+
/** One read-only planner spawn shared by solo fallback, Council drafts, and merge. */
|
|
3944
|
+
async runPlannerAttempt(args) {
|
|
3945
|
+
return executePlannerAttempt(this.plannerAttemptDeps(), args);
|
|
3946
|
+
}
|
|
4227
3947
|
async runPlan(input, announce) {
|
|
4228
3948
|
const taskId = input.taskId ?? newId("task");
|
|
4229
3949
|
const runId = input.runId ?? newId("run");
|
|
4230
3950
|
// Plan runs get the same immutable contract truth as every other mode;
|
|
4231
3951
|
// contract validation runs BEFORE the run is announced (see runRace).
|
|
4232
3952
|
const contract = this.buildContract(input, taskId, "plan");
|
|
4233
|
-
const
|
|
4234
|
-
const paths =
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
3953
|
+
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
3954
|
+
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
3955
|
+
input,
|
|
3956
|
+
contract,
|
|
3957
|
+
quotaSnapshots,
|
|
3958
|
+
store: this.artifactStore(input),
|
|
3959
|
+
authority: this.deps.delegationBudgetAuthority,
|
|
3960
|
+
runId,
|
|
3961
|
+
taskId,
|
|
3962
|
+
mode: "plan",
|
|
3963
|
+
phase: "plan",
|
|
3964
|
+
prompt: input.prompt,
|
|
3965
|
+
}, announce);
|
|
4240
3966
|
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
4241
3967
|
log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
|
|
4242
3968
|
// W3.3: ONE resolved read-only context — the routing point-probe and every
|
|
@@ -4344,6 +4070,7 @@ export class Orchestrator {
|
|
|
4344
4070
|
roHome,
|
|
4345
4071
|
contextSection,
|
|
4346
4072
|
laneRun,
|
|
4073
|
+
estimateUsdFloor: this.estimateUsdFloor(input.repoRoot),
|
|
4347
4074
|
});
|
|
4348
4075
|
}
|
|
4349
4076
|
const plans = [];
|
|
@@ -4373,6 +4100,7 @@ export class Orchestrator {
|
|
|
4373
4100
|
fallbackHome: roHome.env,
|
|
4374
4101
|
promptBody: planPrompt(input.prompt) + contextSection,
|
|
4375
4102
|
intent: "plan",
|
|
4103
|
+
reservationEstimateUsd: this.reservationEstimateUsd(input),
|
|
4376
4104
|
});
|
|
4377
4105
|
if (outcome.budgetDenied) {
|
|
4378
4106
|
// QA-050: retain the denied planner slot before breaking so the
|
|
@@ -4451,7 +4179,6 @@ export class Orchestrator {
|
|
|
4451
4179
|
}
|
|
4452
4180
|
if (plans.length === 0) {
|
|
4453
4181
|
return writePlanHarnessFailure(this.planRunDeps(), {
|
|
4454
|
-
input,
|
|
4455
4182
|
contract,
|
|
4456
4183
|
taskId,
|
|
4457
4184
|
runId,
|
|
@@ -4520,17 +4247,8 @@ export class Orchestrator {
|
|
|
4520
4247
|
defaultPrompt: "Explore this repository and synthesize evidence-cited findings, omissions, and follow-up questions.",
|
|
4521
4248
|
}, announce);
|
|
4522
4249
|
}
|
|
4523
|
-
|
|
4524
|
-
return
|
|
4525
|
-
}
|
|
4526
|
-
rootLedger(input, contract, log) {
|
|
4527
|
-
return createRootLedger({
|
|
4528
|
-
input,
|
|
4529
|
-
contract,
|
|
4530
|
-
log,
|
|
4531
|
-
authority: this.deps.delegationBudgetAuthority,
|
|
4532
|
-
quotaSnapshots: this.deps.quotaSnapshots?.() ?? [],
|
|
4533
|
-
});
|
|
4250
|
+
quotaSnapshotPreflight() {
|
|
4251
|
+
return [...(this.deps.quotaSnapshots?.() ?? [])].map((snapshot) => QuotaSnapshotSchema.parse(snapshot));
|
|
4534
4252
|
}
|
|
4535
4253
|
routeBillingKnowledge(input, harnessId) {
|
|
4536
4254
|
// A selected profile's credential_kind decides billing (round-18 #2).
|
|
@@ -4560,10 +4278,10 @@ export class Orchestrator {
|
|
|
4560
4278
|
// scout reserve) so a subscription route is not refused for lacking a
|
|
4561
4279
|
// cash quote.
|
|
4562
4280
|
attemptCostEvidence(harnessId, attemptId, this.estimateUsdFloor(input.repoRoot), this.routeBillingKnowledge(input, harnessId)),
|
|
4563
|
-
buildSpec: (routed, homeEnv, prompt, attemptId) => {
|
|
4281
|
+
buildSpec: async (routed, homeEnv, prompt, attemptId) => {
|
|
4564
4282
|
const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
|
|
4565
4283
|
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
4566
|
-
const sessionFields = this.sessionSpecFields(input, routed.adapter.id, log);
|
|
4284
|
+
const sessionFields = await this.sessionSpecFields(input, routed.adapter.id, log, routed.authRouteEstimate);
|
|
4567
4285
|
const spec = HarnessRunSpec.parse({
|
|
4568
4286
|
session_id: newId("ses"),
|
|
4569
4287
|
intent: "synthesize",
|
|
@@ -4604,13 +4322,19 @@ export class Orchestrator {
|
|
|
4604
4322
|
// Contract validation BEFORE the run is announced (see runRace). The
|
|
4605
4323
|
// recorded user intent is the CALLER's goal.
|
|
4606
4324
|
const contract = this.buildContract({ ...input, prompt: opts.contractIntent ?? prompt }, taskId, opts.mode);
|
|
4607
|
-
const
|
|
4608
|
-
const paths =
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4325
|
+
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
4326
|
+
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
4327
|
+
input,
|
|
4328
|
+
contract,
|
|
4329
|
+
quotaSnapshots,
|
|
4330
|
+
store: this.artifactStore(input),
|
|
4331
|
+
authority: this.deps.delegationBudgetAuthority,
|
|
4332
|
+
runId,
|
|
4333
|
+
taskId,
|
|
4334
|
+
mode: opts.mode,
|
|
4335
|
+
phase: "report",
|
|
4336
|
+
prompt,
|
|
4337
|
+
}, announce);
|
|
4614
4338
|
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
4615
4339
|
log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
|
|
4616
4340
|
// The ask/deep-scan report stays bare (its scouts read the tree themselves);
|
|
@@ -4752,10 +4476,10 @@ export class Orchestrator {
|
|
|
4752
4476
|
intent: opts.intent,
|
|
4753
4477
|
harnessId: adapter.id,
|
|
4754
4478
|
// QA-019: an n>1 deep-scan scout admits under a FINITE estimate floor
|
|
4755
|
-
// (mirror of the candidate loop): the first scout reserves
|
|
4756
|
-
// floor
|
|
4757
|
-
//
|
|
4758
|
-
cost: attemptCostEvidence(adapter.id, attemptId, opts.deepScan && idx > 0
|
|
4479
|
+
// (mirror of the candidate loop): the first top-level scout reserves
|
|
4480
|
+
// without a floor; later scouts and every real Delegate child pass the
|
|
4481
|
+
// repo floor because they overlap an existing family unit.
|
|
4482
|
+
cost: attemptCostEvidence(adapter.id, attemptId, this.reservationEstimateUsd(input, opts.deepScan && idx > 0), this.routeBillingKnowledge(input, adapter.id)),
|
|
4759
4483
|
});
|
|
4760
4484
|
if (!lease.granted) {
|
|
4761
4485
|
log.emit("budget.lease.created", {
|
|
@@ -4784,67 +4508,120 @@ export class Orchestrator {
|
|
|
4784
4508
|
}
|
|
4785
4509
|
return { status: "budget_denied", reason: lease.reason ?? "budget lease denied" };
|
|
4786
4510
|
}
|
|
4787
|
-
//
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
const
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4511
|
+
// As with planners, the granted lease owns profile/continuity/spec
|
|
4512
|
+
// preparation. Contain a pre-stream rejection as this attempt's failure;
|
|
4513
|
+
// parallel siblings can then finish before the shared HOME is disposed.
|
|
4514
|
+
const preparation = await (async () => {
|
|
4515
|
+
// Lease granted: the attempt is now committed to run — disclose the launch.
|
|
4516
|
+
onLaunch?.();
|
|
4517
|
+
const knobs = this.routeSpecKnobs(routed, contract, modelOverride, input.effort);
|
|
4518
|
+
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
4519
|
+
const explorerPrompt = (opts.deepScan
|
|
4520
|
+
? `${prompt}\n\nExplorer ${idx + 1}/${adapters.length}: focus on a distinct slice. Emit evidence-cited findings, explicit unknowns/omissions, and follow-up questions. Do not edit files.`
|
|
4521
|
+
: prompt) + contextSection;
|
|
4522
|
+
const sessionFields = await this.sessionSpecFields(input, adapter.id, log, routed.authRouteEstimate);
|
|
4523
|
+
const grantResume = sessionFields.resume_session_id !== null && !resumeGranted.has(adapter.id);
|
|
4524
|
+
if (grantResume)
|
|
4525
|
+
resumeGranted.add(adapter.id);
|
|
4526
|
+
// Continuity (INV-137): a thread ASK turn is a chat turn — hydrate a lane
|
|
4527
|
+
// switch/gap with a packet and disclose it. Gated on laneRun (deep-scan
|
|
4528
|
+
// scouts are excluded from laneRun); native resume is available only when
|
|
4529
|
+
// this slot was granted the lane's recorded session.
|
|
4530
|
+
const laneContinuity = laneRun
|
|
4531
|
+
? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, grantResume, sessionFields, store, paths, this.execRootOf(input), log)
|
|
4532
|
+
: null;
|
|
4533
|
+
// D-16d: the continuation packet pointer rides after the lane pointer so
|
|
4534
|
+
// the fresh session is re-grounded in the exhausted attempt's work.
|
|
4535
|
+
const promptWithPointers = [
|
|
4536
|
+
explorerPrompt,
|
|
4537
|
+
laneContinuity?.pointerLine,
|
|
4538
|
+
continuationPointer,
|
|
4539
|
+
]
|
|
4540
|
+
.filter((p) => Boolean(p))
|
|
4541
|
+
.join("\n\n");
|
|
4542
|
+
const spec = HarnessRunSpec.parse({
|
|
4543
|
+
session_id: newId("ses"),
|
|
4544
|
+
intent: opts.intent,
|
|
4545
|
+
prompt: promptWithPointers,
|
|
4546
|
+
cwd: this.execRootOf(input),
|
|
4547
|
+
access: "readonly",
|
|
4548
|
+
// ASK/EXPLORE/AUDIT read-only runs must forward the user's attachments —
|
|
4549
|
+
// a live "describe this image" turn sent an image that was being dropped here, so
|
|
4550
|
+
// the model honestly reported it saw nothing (the v0.13 attachment bug).
|
|
4551
|
+
attachments: input.attachments ?? [],
|
|
4552
|
+
auth_preference: sessionFields.auth_preference,
|
|
4553
|
+
credential_profile: sessionFields.credential_profile,
|
|
4554
|
+
resume_session_id: grantResume ? sessionFields.resume_session_id : null,
|
|
4555
|
+
...this.harnessSpecKnobs(contract, knobs, opts.intent),
|
|
4556
|
+
env_inheritance: envInheritance(this.config(input.repoRoot)),
|
|
4557
|
+
// A thread lane turn spawns in its DURABLE per-lane home so the native
|
|
4558
|
+
// session it records is reachable for resume next turn; everything else
|
|
4559
|
+
// uses the disposable route-context home.
|
|
4560
|
+
env: (laneRun ? this.laneHomeEnvFor(input, adapter.id) : null) ?? roHome.env,
|
|
4561
|
+
});
|
|
4562
|
+
const reportAbort = new AbortController();
|
|
4563
|
+
spec.extra["abortSignal"] = input.signal
|
|
4564
|
+
? AbortSignal.any([input.signal, reportAbort.signal])
|
|
4565
|
+
: reportAbort.signal;
|
|
4566
|
+
const reportInteraction = this.interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
4567
|
+
if (reportInteraction)
|
|
4568
|
+
spec.extra["interactionChannel"] = reportInteraction;
|
|
4569
|
+
// D-16: compile the WorkReport envelope for the read-only lane.
|
|
4570
|
+
const readonlyWorkEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(reportInteraction));
|
|
4571
|
+
const readonlyWorkMode = this.applyWorkEnvelope(spec, readonlyWorkEnvelope);
|
|
4572
|
+
const attemptEventsPath = join(paths.attemptsDir, attemptId, "events.jsonl");
|
|
4573
|
+
const answer = new AnswerAssembly();
|
|
4574
|
+
const telemetry = createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required ||
|
|
4575
|
+
knobs.webPolicy === "cached" ||
|
|
4576
|
+
knobs.webPolicy === "live", effectiveWeb, [],
|
|
4577
|
+
// Requested-model capture so ask/audit route receipts detect a silent
|
|
4578
|
+
// model downgrade (typed model_mismatch), not just agent runs.
|
|
4579
|
+
knobs.model);
|
|
4580
|
+
return {
|
|
4581
|
+
knobs,
|
|
4582
|
+
spec,
|
|
4583
|
+
reportAbort,
|
|
4584
|
+
reportInteraction,
|
|
4585
|
+
readonlyWorkMode,
|
|
4586
|
+
attemptEventsPath,
|
|
4587
|
+
answer,
|
|
4588
|
+
telemetry,
|
|
4589
|
+
};
|
|
4590
|
+
})().then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
|
|
4591
|
+
if (!preparation.ok) {
|
|
4592
|
+
const message = `read-only attempt setup failed: ${safeErrorMessage(preparation.error)}`;
|
|
4593
|
+
AC.settleGrantedAttemptLease({
|
|
4594
|
+
ledger,
|
|
4595
|
+
leaseId: lease.lease?.lease_id ?? "",
|
|
4596
|
+
attemptId,
|
|
4597
|
+
harnessId: adapter.id,
|
|
4598
|
+
costUsd: 0,
|
|
4599
|
+
costEstimated: false,
|
|
4600
|
+
preStreamFailureSource: "readonly-pre-stream",
|
|
4601
|
+
});
|
|
4602
|
+
const telemetry = createAttemptTelemetry(contract.external_context.policy, contract.external_context.web_required, contract.external_context.effective_mode);
|
|
4603
|
+
setAttemptOutcome(telemetry, {
|
|
4604
|
+
deliverablePresent: false,
|
|
4605
|
+
gatesPassed: null,
|
|
4606
|
+
harnessErrored: true,
|
|
4607
|
+
webRequiredUnsatisfied: false,
|
|
4608
|
+
});
|
|
4609
|
+
attempts.push({
|
|
4610
|
+
attemptId,
|
|
4611
|
+
harnessId: adapter.id,
|
|
4612
|
+
status: "failed",
|
|
4613
|
+
report: "",
|
|
4614
|
+
error: message,
|
|
4615
|
+
telemetry,
|
|
4616
|
+
});
|
|
4617
|
+
attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
|
|
4618
|
+
if (opts.deepScan) {
|
|
4619
|
+
store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${message}\n`);
|
|
4620
|
+
}
|
|
4621
|
+
return { status: "launched" };
|
|
4622
|
+
}
|
|
4623
|
+
const { knobs, spec: preparedSpec, reportAbort, reportInteraction, readonlyWorkMode, attemptEventsPath, answer, telemetry, } = preparation.value;
|
|
4624
|
+
let spec = preparedSpec;
|
|
4848
4625
|
const retryPolicy = transientRetryPolicy(this.config(input.repoRoot));
|
|
4849
4626
|
let activeSessionId = spec.session_id;
|
|
4850
4627
|
const onAbort = () => {
|
|
@@ -4884,11 +4661,13 @@ export class Orchestrator {
|
|
|
4884
4661
|
try {
|
|
4885
4662
|
const watchedReport = withInactivityWatchdog(adapter.run(runSpec), {
|
|
4886
4663
|
timeoutMs: harnessInactivityTimeoutMs(this.config(input.repoRoot)),
|
|
4664
|
+
countsAsProgress: countsAsAgentProgress,
|
|
4887
4665
|
onTimeout: () => {
|
|
4888
4666
|
reportAbort.abort();
|
|
4889
4667
|
void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4890
4668
|
},
|
|
4891
4669
|
isSuspended: () => (reportInteraction?.pendingCount?.() ?? 0) > 0,
|
|
4670
|
+
suspensionVersion: () => reportInteraction?.suspensionVersion?.() ?? 0,
|
|
4892
4671
|
});
|
|
4893
4672
|
for await (const ev of watchedReport) {
|
|
4894
4673
|
if (input.signal?.aborted)
|
|
@@ -4952,13 +4731,18 @@ export class Orchestrator {
|
|
|
4952
4731
|
// W5.4 reactive failover, READ-ONLY lane (same contract as the
|
|
4953
4732
|
// candidate lane; typed limits only, never plain transients).
|
|
4954
4733
|
if (harnessError && !input.signal?.aborted) {
|
|
4734
|
+
const rotationPolicy = this.profilePolicy(input.repoRoot, adapter.id);
|
|
4735
|
+
const readyProfileIds = sawTypedLimit && reportSoFar.length === 0 && rotationPolicy.limit_action === "rotate"
|
|
4736
|
+
? await this.readyProfileIdsForRotation(input, adapter.id, spec.credential_profile ?? null, triedProfiles)
|
|
4737
|
+
: new Set();
|
|
4955
4738
|
const rotated = rotateSpecOnTypedLimit({
|
|
4956
4739
|
spec,
|
|
4957
4740
|
harnessId: adapter.id,
|
|
4958
4741
|
attemptId,
|
|
4959
|
-
policy:
|
|
4742
|
+
policy: rotationPolicy,
|
|
4960
4743
|
registry: this.config(input.repoRoot)?.global.credential_profiles ?? [],
|
|
4961
4744
|
snapshots: this.deps.quotaSnapshots?.() ?? [],
|
|
4745
|
+
readyProfileIds,
|
|
4962
4746
|
triedProfiles,
|
|
4963
4747
|
sawTypedLimit,
|
|
4964
4748
|
deliverableEmpty: reportSoFar.length === 0,
|
|
@@ -5000,7 +4784,17 @@ export class Orchestrator {
|
|
|
5000
4784
|
}
|
|
5001
4785
|
finally {
|
|
5002
4786
|
input.signal?.removeEventListener("abort", onAbort);
|
|
5003
|
-
|
|
4787
|
+
AC.settleGrantedAttemptLease({
|
|
4788
|
+
ledger,
|
|
4789
|
+
leaseId: lease.lease?.lease_id ?? "",
|
|
4790
|
+
attemptId,
|
|
4791
|
+
harnessId: adapter.id,
|
|
4792
|
+
costUsd: cost,
|
|
4793
|
+
costEstimated,
|
|
4794
|
+
authMode: telemetry.authMode,
|
|
4795
|
+
usageCost: telemetry.usageCost,
|
|
4796
|
+
preStreamFailureSource: "readonly-pre-stream",
|
|
4797
|
+
});
|
|
5004
4798
|
}
|
|
5005
4799
|
if (harnessError && telemetry.transientFailures.length > 0) {
|
|
5006
4800
|
log.emit("route.transient.exhausted", {
|
|
@@ -5011,11 +4805,14 @@ export class Orchestrator {
|
|
|
5011
4805
|
});
|
|
5012
4806
|
}
|
|
5013
4807
|
attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
|
|
5014
|
-
// D-16:
|
|
4808
|
+
// D-16: remove the WorkReport transport; the deliverable is the report.
|
|
5015
4809
|
const roUnwrapped = unwrapWorkReportEnvelope(answer.machineText() ?? "", readonlyWorkMode, {
|
|
5016
4810
|
sideToolReport: telemetry.sideToolWorkReport ?? undefined,
|
|
5017
4811
|
});
|
|
5018
|
-
|
|
4812
|
+
// Trim symmetrically with the plan path: a whitespace-only answer is not
|
|
4813
|
+
// a delivered report (the final-artifact wrapper heading would otherwise
|
|
4814
|
+
// make it read as present content by construction).
|
|
4815
|
+
const report = redactSecrets(roUnwrapped.deliverable).trim();
|
|
5019
4816
|
const unrecovered = unrecoveredToolErrors(telemetry);
|
|
5020
4817
|
const webBlocked = webUnsatisfied(telemetry);
|
|
5021
4818
|
const reportPresent = report.length > 0;
|
|
@@ -5043,6 +4840,13 @@ export class Orchestrator {
|
|
|
5043
4840
|
harnessErrored: harnessError !== null && !webBlocked,
|
|
5044
4841
|
webRequiredUnsatisfied: webBlocked,
|
|
5045
4842
|
workState: roFinalized.workState,
|
|
4843
|
+
// A read-only attempt that completed CLEANLY with an honestly empty
|
|
4844
|
+
// answer is a success with deliverable_present=false ("(no output)"),
|
|
4845
|
+
// never a fake contract failure — the trim above must not convert a
|
|
4846
|
+
// phantom deliverable into a phantom harness failure. Only the clean
|
|
4847
|
+
// finalizer class qualifies: contract failures, vetoes, and context
|
|
4848
|
+
// interruptions keep the strict deliverable requirement.
|
|
4849
|
+
emptyDeliverableAllowed: roFinalized.outcomeClass === "clean",
|
|
5046
4850
|
});
|
|
5047
4851
|
if (harnessError) {
|
|
5048
4852
|
log.emit("harness.completed", {
|
|
@@ -5473,7 +5277,7 @@ export class Orchestrator {
|
|
|
5473
5277
|
}
|
|
5474
5278
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, opts.deepScan ? null : (succeeded[0]?.attemptId ?? null), deepScanSynthesis);
|
|
5475
5279
|
log.emit("output.ready", {
|
|
5476
|
-
kind: opts.
|
|
5280
|
+
kind: opts.deepScan ? "report" : "answer",
|
|
5477
5281
|
path: `final/${opts.artifactName}`,
|
|
5478
5282
|
});
|
|
5479
5283
|
if (opts.deepScan) {
|
|
@@ -5531,11 +5335,16 @@ export class Orchestrator {
|
|
|
5531
5335
|
.map((a) => `${a.attemptId}:${a.harnessId}:${a.status}`)
|
|
5532
5336
|
.join(", ");
|
|
5533
5337
|
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harnesses: ${harnessLabel}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}\n\n${report}\n`);
|
|
5338
|
+
const reportProducerAttemptId = opts.deepScan &&
|
|
5339
|
+
deepScanSynthesis?.status === "succeeded" &&
|
|
5340
|
+
deepScanSynthesis.reducer_attempt_id
|
|
5341
|
+
? deepScanSynthesis.reducer_attempt_id
|
|
5342
|
+
: (succeeded[0]?.attemptId ?? "a01");
|
|
5534
5343
|
store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
|
|
5535
5344
|
id: newId("wp"),
|
|
5536
5345
|
kind: "report",
|
|
5537
5346
|
source_task_id: taskId,
|
|
5538
|
-
producer_attempt_id:
|
|
5347
|
+
producer_attempt_id: reportProducerAttemptId,
|
|
5539
5348
|
files: Object.fromEntries([[opts.artifactName, join(paths.finalDir, opts.artifactName)]]),
|
|
5540
5349
|
meta: {
|
|
5541
5350
|
harnesses: attempts.map((a) => a.harnessId),
|
|
@@ -5544,7 +5353,7 @@ export class Orchestrator {
|
|
|
5544
5353
|
read_only: true,
|
|
5545
5354
|
},
|
|
5546
5355
|
});
|
|
5547
|
-
log.emit("work_product.emitted", { kind: "report", winner:
|
|
5356
|
+
log.emit("work_product.emitted", { kind: "report", winner: reportProducerAttemptId });
|
|
5548
5357
|
const workVetoed = terminalFacts.work_state?.state === "needs_input" ||
|
|
5549
5358
|
terminalFacts.work_state?.state === "incomplete";
|
|
5550
5359
|
if (terminalFacts.lifecycle !== "succeeded") {
|
|
@@ -5596,9 +5405,4 @@ export class Orchestrator {
|
|
|
5596
5405
|
};
|
|
5597
5406
|
}
|
|
5598
5407
|
}
|
|
5599
|
-
function assertNoSecretLikeTokens(label, text) {
|
|
5600
|
-
if (containsSecretLikeToken(text)) {
|
|
5601
|
-
throw new Error(`${label} contains secret-like token; refusing to persist artifact`);
|
|
5602
|
-
}
|
|
5603
|
-
}
|
|
5604
5408
|
//# sourceMappingURL=orchestrator.js.map
|