@claudexor/orchestrator 3.20.1 → 3.23.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 -101
- package/dist/attemptFinalize.d.ts.map +1 -1
- package/dist/attemptFinalize.js +7 -328
- package/dist/attemptFinalize.js.map +1 -1
- package/dist/candidateEvidence.d.ts +15 -6
- package/dist/candidateEvidence.d.ts.map +1 -1
- package/dist/candidateEvidence.js +1 -1
- package/dist/candidateEvidence.js.map +1 -1
- package/dist/candidateOutputs.d.ts +7 -3
- package/dist/candidateOutputs.d.ts.map +1 -1
- package/dist/candidateOutputs.js +4 -1
- package/dist/candidateOutputs.js.map +1 -1
- package/dist/candidateReview.d.ts.map +1 -1
- package/dist/candidateReview.js +3 -1
- package/dist/candidateReview.js.map +1 -1
- package/dist/candidateTerminals.d.ts +4 -0
- package/dist/candidateTerminals.d.ts.map +1 -1
- package/dist/candidateTerminals.js +18 -7
- package/dist/candidateTerminals.js.map +1 -1
- package/dist/candidateWorkProduct.d.ts.map +1 -1
- package/dist/candidateWorkProduct.js +6 -6
- package/dist/candidateWorkProduct.js.map +1 -1
- package/dist/carrier-planner.d.ts +94 -0
- package/dist/carrier-planner.d.ts.map +1 -0
- package/dist/carrier-planner.js +111 -0
- package/dist/carrier-planner.js.map +1 -0
- package/dist/continuation-custody.d.ts +57 -0
- package/dist/continuation-custody.d.ts.map +1 -0
- package/dist/continuation-custody.js +151 -0
- package/dist/continuation-custody.js.map +1 -0
- package/dist/continuation-evidence-io.d.ts +18 -0
- package/dist/continuation-evidence-io.d.ts.map +1 -0
- package/dist/continuation-evidence-io.js +167 -0
- package/dist/continuation-evidence-io.js.map +1 -0
- package/dist/continuation-evidence.d.ts +81 -0
- package/dist/continuation-evidence.d.ts.map +1 -0
- package/dist/continuation-evidence.js +122 -0
- package/dist/continuation-evidence.js.map +1 -0
- package/dist/continuation-input.d.ts +10 -0
- package/dist/continuation-input.d.ts.map +1 -0
- package/dist/continuation-input.js +69 -0
- package/dist/continuation-input.js.map +1 -0
- package/dist/continue-from.d.ts +41 -0
- package/dist/continue-from.d.ts.map +1 -0
- package/dist/continue-from.js +292 -0
- package/dist/continue-from.js.map +1 -0
- package/dist/continuity-notice.d.ts +42 -0
- package/dist/continuity-notice.d.ts.map +1 -0
- package/dist/continuity-notice.js +42 -0
- package/dist/continuity-notice.js.map +1 -0
- package/dist/continuity-terminal.d.ts +19 -0
- package/dist/continuity-terminal.d.ts.map +1 -0
- package/dist/continuity-terminal.js +34 -0
- package/dist/continuity-terminal.js.map +1 -0
- package/dist/credential-cooldown.d.ts.map +1 -1
- package/dist/credential-cooldown.js +8 -3
- package/dist/credential-cooldown.js.map +1 -1
- package/dist/credential-differential.d.ts +1 -0
- package/dist/credential-differential.d.ts.map +1 -1
- package/dist/credential-differential.js +32 -4
- package/dist/credential-differential.js.map +1 -1
- package/dist/deepScanReducer.d.ts +4 -1
- package/dist/deepScanReducer.d.ts.map +1 -1
- package/dist/deepScanReducer.js +10 -5
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/delegatedHome.d.ts +30 -1
- package/dist/delegatedHome.d.ts.map +1 -1
- package/dist/delegatedHome.js +37 -2
- package/dist/delegatedHome.js.map +1 -1
- package/dist/delegationFailure.d.ts +4 -3
- package/dist/delegationFailure.d.ts.map +1 -1
- package/dist/delegationFailure.js +25 -24
- package/dist/delegationFailure.js.map +1 -1
- package/dist/directoryCandidate.d.ts +2 -3
- package/dist/directoryCandidate.d.ts.map +1 -1
- package/dist/directoryCandidate.js +3 -2
- package/dist/directoryCandidate.js.map +1 -1
- package/dist/inrun-continuity-carrier.d.ts +76 -0
- package/dist/inrun-continuity-carrier.d.ts.map +1 -0
- package/dist/inrun-continuity-carrier.js +218 -0
- package/dist/inrun-continuity-carrier.js.map +1 -0
- package/dist/inrun-continuity-thread.d.ts +8 -0
- package/dist/inrun-continuity-thread.d.ts.map +1 -0
- package/dist/inrun-continuity-thread.js +23 -0
- package/dist/inrun-continuity-thread.js.map +1 -0
- package/dist/inrun-continuity-types.d.ts +105 -0
- package/dist/inrun-continuity-types.d.ts.map +1 -0
- package/dist/inrun-continuity-types.js +2 -0
- package/dist/inrun-continuity-types.js.map +1 -0
- package/dist/inrun-continuity.d.ts +79 -0
- package/dist/inrun-continuity.d.ts.map +1 -0
- package/dist/inrun-continuity.js +465 -0
- package/dist/inrun-continuity.js.map +1 -0
- package/dist/inrun-continuity.test-support.d.ts +374 -0
- package/dist/inrun-continuity.test-support.d.ts.map +1 -0
- package/dist/inrun-continuity.test-support.js +198 -0
- package/dist/inrun-continuity.test-support.js.map +1 -0
- package/dist/inrun-retained-output.d.ts +9 -0
- package/dist/inrun-retained-output.d.ts.map +1 -0
- package/dist/inrun-retained-output.js +16 -0
- package/dist/inrun-retained-output.js.map +1 -0
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.d.ts.map +1 -1
- package/dist/interaction.js +6 -1
- package/dist/interaction.js.map +1 -1
- package/dist/modelGovernance.d.ts +4 -0
- package/dist/modelGovernance.d.ts.map +1 -1
- package/dist/modelGovernance.js +27 -1
- package/dist/modelGovernance.js.map +1 -1
- package/dist/orchestrator-credentials.d.ts +1 -1
- package/dist/orchestrator-credentials.d.ts.map +1 -1
- package/dist/orchestrator-credentials.js +4 -3
- package/dist/orchestrator-credentials.js.map +1 -1
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +261 -235
- package/dist/orchestrator.js.map +1 -1
- package/dist/persistedPatch.d.ts +55 -0
- package/dist/persistedPatch.d.ts.map +1 -0
- package/dist/persistedPatch.js +132 -0
- package/dist/persistedPatch.js.map +1 -0
- package/dist/planFinalize.d.ts +37 -0
- package/dist/planFinalize.d.ts.map +1 -0
- package/dist/planFinalize.js +94 -0
- package/dist/planFinalize.js.map +1 -0
- package/dist/planRun.d.ts +2 -28
- package/dist/planRun.d.ts.map +1 -1
- package/dist/planRun.js +5 -88
- package/dist/planRun.js.map +1 -1
- package/dist/plannerAttempt.d.ts +4 -1
- package/dist/plannerAttempt.d.ts.map +1 -1
- package/dist/plannerAttempt.js +9 -4
- package/dist/plannerAttempt.js.map +1 -1
- package/dist/prompt-framing.d.ts +1 -1
- package/dist/prompt-framing.d.ts.map +1 -1
- package/dist/prompt-framing.js.map +1 -1
- package/dist/reviewerEffort.d.ts +41 -0
- package/dist/reviewerEffort.d.ts.map +1 -0
- package/dist/reviewerEffort.js +37 -0
- package/dist/reviewerEffort.js.map +1 -0
- package/dist/reviewerPanel.d.ts +5 -5
- package/dist/reviewerPanel.d.ts.map +1 -1
- package/dist/reviewerPanel.js +22 -50
- package/dist/reviewerPanel.js.map +1 -1
- package/dist/run-input-resolution.d.ts.map +1 -1
- package/dist/run-input-resolution.js +10 -0
- package/dist/run-input-resolution.js.map +1 -1
- package/dist/runAdmission.js +1 -1
- package/dist/runAdmission.js.map +1 -1
- package/dist/runEventLog.d.ts.map +1 -1
- package/dist/runEventLog.js +5 -0
- package/dist/runEventLog.js.map +1 -1
- package/dist/runFactsBuilder.d.ts.map +1 -1
- package/dist/runFactsBuilder.js +11 -2
- package/dist/runFactsBuilder.js.map +1 -1
- package/dist/runSupport.d.ts +2 -1
- package/dist/runSupport.d.ts.map +1 -1
- package/dist/runSupport.js +2 -1
- package/dist/runSupport.js.map +1 -1
- package/dist/runTelemetryWriter.d.ts.map +1 -1
- package/dist/runTelemetryWriter.js +7 -2
- package/dist/runTelemetryWriter.js.map +1 -1
- package/dist/runTerminalResults.d.ts +6 -2
- package/dist/runTerminalResults.d.ts.map +1 -1
- package/dist/runTerminalResults.js +8 -1
- package/dist/runTerminalResults.js.map +1 -1
- package/dist/runTerminals.d.ts.map +1 -1
- package/dist/runTerminals.js +6 -5
- package/dist/runTerminals.js.map +1 -1
- package/dist/secretDiff.d.ts +67 -27
- package/dist/secretDiff.d.ts.map +1 -1
- package/dist/secretDiff.js +143 -113
- package/dist/secretDiff.js.map +1 -1
- package/dist/session-capsule.d.ts +29 -0
- package/dist/session-capsule.d.ts.map +1 -0
- package/dist/session-capsule.js +86 -0
- package/dist/session-capsule.js.map +1 -0
- package/dist/workReportEnvelope.d.ts +123 -0
- package/dist/workReportEnvelope.d.ts.map +1 -0
- package/dist/workReportEnvelope.js +362 -0
- package/dist/workReportEnvelope.js.map +1 -0
- package/package.json +17 -17
- package/dist/effortGovernance.d.ts +0 -13
- package/dist/effortGovernance.d.ts.map +0 -1
- package/dist/effortGovernance.js +0 -7
- package/dist/effortGovernance.js.map +0 -1
package/dist/orchestrator.js
CHANGED
|
@@ -2,17 +2,20 @@ import { applyWorkEnvelope, promptWithPointers as renderPromptWithPointers, thre
|
|
|
2
2
|
import { requestRefusalFailure } from "./runTerminalResults.js";
|
|
3
3
|
import { delegationBeltFor } from "./delegationBelt.js";
|
|
4
4
|
import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
|
|
5
|
-
import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, } from "./candidateTerminals.js";
|
|
5
|
+
import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, failAfterCandidates, } from "./candidateTerminals.js";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
|
-
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile,
|
|
8
|
-
import { AdmissionProfileProbes, OrchestratorCredentials, reviewerProfileResolver,
|
|
7
|
+
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, } from "./credential-profiles.js";
|
|
8
|
+
import { AdmissionProfileProbes, OrchestratorCredentials, reviewerProfileResolver, } from "./orchestrator-credentials.js";
|
|
9
|
+
import { continuityWorkspace, resumableOf, resumableTerminal, } from "./continuity-terminal.js";
|
|
10
|
+
import { openContinuity } from "./continue-from.js";
|
|
9
11
|
import { accountPoolRows } from "./account-pool.js";
|
|
10
12
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
11
13
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
12
14
|
import { processAttemptUsage } from "./attemptUsage.js";
|
|
13
15
|
import { captureDirectoryCandidate, directoryHasOutput, directoryCandidateStable, observeDirectoryPaths, publishDirectoryCandidate, } from "./directoryCandidate.js";
|
|
14
|
-
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, } from "./delegatedHome.js";
|
|
16
|
+
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, selectLaneHome, followSpecHome, } from "./delegatedHome.js";
|
|
15
17
|
import * as AC from "./attemptUsageCost.js";
|
|
18
|
+
import * as custody from "./continuation-custody.js";
|
|
16
19
|
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, unanimousDeclaredFailure, } from "./candidateEvidence.js";
|
|
17
20
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
18
21
|
import { reviewCandidateRuns, resolveEngineReview, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
|
|
@@ -22,10 +25,9 @@ import { runPlannerAttempt as executePlannerAttempt, plannerAttemptSummary, } fr
|
|
|
22
25
|
import { HarnessRunSpec, MAX_COUNCIL_MEMBERS_DEFAULT, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, reviewAllowsApply, workStateVetoes, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
23
26
|
import { globalConfigDir, loadConfig } from "@claudexor/config";
|
|
24
27
|
import { prepareRoutedProcessing, processingCostEvidence, prepareReviewerProcessing, } from "./processing-routing.js";
|
|
25
|
-
import { acceptedTryOutput, AccessProfileIncompatibleError,
|
|
28
|
+
import { acceptedTryOutput, AccessProfileIncompatibleError, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
26
29
|
import { assertRouteModelsAllowed, runModelGovernedRoute } from "./modelGovernance.js";
|
|
27
30
|
import { authModeForCredentialRoute, authModeForPreference, authRouteEvidenceFor, } from "./auth-route-classification.js";
|
|
28
|
-
import { governRouteEffort } from "./effortGovernance.js";
|
|
29
31
|
import { isFullAccess, RequestRequirementsResolver } from "./requestRequirements.js";
|
|
30
32
|
import { activateDelegationParent } from "./delegation-parent-activation.js";
|
|
31
33
|
import { writeRoutingFailureTerminal } from "./routing-failure.js";
|
|
@@ -41,7 +43,7 @@ import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, wr
|
|
|
41
43
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
42
44
|
import { finalizeStructuredOutput } from "./structuredOutput.js";
|
|
43
45
|
import { admitRun } from "./runAdmission.js";
|
|
44
|
-
import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted,
|
|
46
|
+
import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted, observeReadonlySpend, recordAdapterThrow, } from "./laneStreamEvents.js";
|
|
45
47
|
import { promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPrimaryDivergence, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
|
|
46
48
|
import { candidateStatusInRouteContext, resolveReadOnlyRouteContext, } from "./routeContext.js";
|
|
47
49
|
import { resolveAutoReviewerPanel, resolveExplicitReviewerPanel } from "./reviewerPanel.js";
|
|
@@ -55,7 +57,8 @@ import { newAttemptOutputMarkers } from "./attemptOutputMarkers.js";
|
|
|
55
57
|
import * as delegateFailure from "./delegationFailure.js";
|
|
56
58
|
import * as secretDiff from "./secretDiff.js";
|
|
57
59
|
import { dominantHarnessFailureCategory, harnessFailureNextActions } from "./harnessFailure.js";
|
|
58
|
-
import { finalizeAttempt, readOnlyNoSuccessTerminal,
|
|
60
|
+
import { finalizeAttempt, readOnlyNoSuccessTerminal, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
|
|
61
|
+
import { resolveWorkReportEnvelope, unwrapWorkReportEnvelope, } from "./workReportEnvelope.js";
|
|
59
62
|
import { buildContinuationPacket, decideContinuation, synthesizeContinuationRequest, } from "./continuation.js";
|
|
60
63
|
import { interactionChannelFor } from "./interaction.js";
|
|
61
64
|
import { liveAttempt } from "./live-input.js";
|
|
@@ -73,8 +76,6 @@ import { billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger,
|
|
|
73
76
|
import { readTextSafe, appendLine, assertNoInlineSecretValues, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, sha256, userConfigDir, } from "@claudexor/util";
|
|
74
77
|
const LABELS = "ABCDEFGHIJ".split("");
|
|
75
78
|
const NO_PROJECT_ROOT = noProjectRepoRoot();
|
|
76
|
-
/** Default wait for one interactive answer before a benign decline. */
|
|
77
|
-
const DEFAULT_INTERACTION_TIMEOUT_MS = 900_000;
|
|
78
79
|
export class Orchestrator {
|
|
79
80
|
deps;
|
|
80
81
|
gateway;
|
|
@@ -210,7 +211,7 @@ export class Orchestrator {
|
|
|
210
211
|
if (this.deps.reviewers)
|
|
211
212
|
return this.deps.reviewers;
|
|
212
213
|
if (this.deps.reviewerPanel && this.deps.reviewerPanel.length > 0) {
|
|
213
|
-
return this.resolveExplicitReviewerPanel(cwd, this.deps.reviewerPanel, runAuthPreference);
|
|
214
|
+
return this.resolveExplicitReviewerPanel(cwd, this.deps.reviewerPanel, runAuthPreference, onIgnoredSetting);
|
|
214
215
|
}
|
|
215
216
|
return resolveAutoReviewerPanel({
|
|
216
217
|
cwd,
|
|
@@ -232,7 +233,7 @@ export class Orchestrator {
|
|
|
232
233
|
if (input.review === false)
|
|
233
234
|
return { reviewers: [] };
|
|
234
235
|
try {
|
|
235
|
-
//
|
|
236
|
+
// Reviewer knobs a panel omitted or dropped → ignored-settings channel (QA-070):
|
|
236
237
|
const warn = (d) => void log.emit("review.preflight", { ignored_settings: [d] });
|
|
237
238
|
const config = this.config(input.repoRoot).global;
|
|
238
239
|
const reviewers = await this.resolveReviewers(input.repoRoot, input.authPreference, warn);
|
|
@@ -274,13 +275,14 @@ export class Orchestrator {
|
|
|
274
275
|
};
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
|
-
async resolveExplicitReviewerPanel(cwd, panel, runAuthPreference) {
|
|
278
|
+
async resolveExplicitReviewerPanel(cwd, panel, runAuthPreference, onIgnoredSetting) {
|
|
278
279
|
return resolveExplicitReviewerPanel({
|
|
279
280
|
cwd,
|
|
280
281
|
registry: this.deps.registry,
|
|
281
282
|
harnessSettings: this.config(cwd)?.global.harnesses ?? {},
|
|
282
283
|
authPreferenceFor: (id) => this.authPreferenceForHarness(cwd, id, runAuthPreference),
|
|
283
284
|
resolveReviewerProfile: reviewerProfileResolver(this.credentials, cwd),
|
|
285
|
+
onIgnoredSetting,
|
|
284
286
|
}, panel);
|
|
285
287
|
}
|
|
286
288
|
authPreferenceForHarness(repoRoot, harnessId, runAuthPreference) {
|
|
@@ -659,7 +661,6 @@ export class Orchestrator {
|
|
|
659
661
|
requiresFullAccess: manifest.capability_profile.mcp_injection_requires_full_access,
|
|
660
662
|
fullAccess: isFullAccess(requiredAccess),
|
|
661
663
|
}),
|
|
662
|
-
effortLevels: manifest.capabilities.effort_levels,
|
|
663
664
|
knownModels: manifest.capabilities.known_models,
|
|
664
665
|
modelInventory: manifest.capabilities,
|
|
665
666
|
// A selected profile's credential_kind IS the route (round-18 #2);
|
|
@@ -819,7 +820,7 @@ export class Orchestrator {
|
|
|
819
820
|
// settles clean), so a schema run keeps its interaction channel — since
|
|
820
821
|
// the daemon always arms one, the refusal was denying claude every
|
|
821
822
|
// daemon/CLI structured-output run. The WorkReport side_tool envelope
|
|
822
|
-
// stays interactive-gated separately (
|
|
823
|
+
// stays interactive-gated separately (workReportEnvelope) as a deliberate
|
|
823
824
|
// scope choice; the caller schema itself rides regardless.
|
|
824
825
|
if (input.outputSchema !== undefined && input.outputSchema !== null) {
|
|
825
826
|
const incapable = out.filter((lane) => !lane.supportsJsonSchemaOutput);
|
|
@@ -992,10 +993,10 @@ export class Orchestrator {
|
|
|
992
993
|
async lazyContextSection(input, contract, store, paths, log) {
|
|
993
994
|
if (input.repoRoot === NO_PROJECT_ROOT || input.contextMode === "off")
|
|
994
995
|
return "";
|
|
995
|
-
//
|
|
996
|
-
//
|
|
996
|
+
// Project identity owns config; the execution tree supplies content.
|
|
997
|
+
// Mandatory files and include/exclude globs still come from project config.
|
|
997
998
|
const projectCtx = this.projectConfig(input.repoRoot).context;
|
|
998
|
-
const pack = await buildContextPack(input
|
|
999
|
+
const pack = await buildContextPack(this.execRootOf(input), contract, {
|
|
999
1000
|
mandatory: projectCtx.mandatory_files.length > 0 ? projectCtx.mandatory_files : undefined,
|
|
1000
1001
|
include: projectCtx.include,
|
|
1001
1002
|
exclude: projectCtx.exclude,
|
|
@@ -1141,16 +1142,14 @@ export class Orchestrator {
|
|
|
1141
1142
|
// per-harness map, which beats the per-harness settings default. There is
|
|
1142
1143
|
// no run-global model.
|
|
1143
1144
|
const model = overrideModel ?? contract.routing_models[routed.adapter.id] ?? s?.defaultModel ?? null;
|
|
1144
|
-
//
|
|
1145
|
-
//
|
|
1146
|
-
//
|
|
1147
|
-
//
|
|
1148
|
-
//
|
|
1149
|
-
//
|
|
1150
|
-
|
|
1151
|
-
const effort =
|
|
1152
|
-
if (governed.ignored)
|
|
1153
|
-
ignored.push(governed.ignored);
|
|
1145
|
+
// The effort PREFERENCE travels to the route unchanged (INV-105): discovery
|
|
1146
|
+
// describes the DEFAULT account, so only the final route may resolve it — a
|
|
1147
|
+
// knob adapter against the catalog of the profile env its child runs in, the
|
|
1148
|
+
// engine as `omitted` for a route without a knob — and that one resolution is
|
|
1149
|
+
// what the receipt and the disclosure record. The contract's FROZEN per-lane
|
|
1150
|
+
// effort (QA-035) wins so Exact Retry replays it without re-reading settings;
|
|
1151
|
+
// `effortHint`/settings apply only to an unfrozen lane.
|
|
1152
|
+
const effort = contract.routing_efforts[routed.adapter.id] ?? effortHint ?? s?.effort ?? null;
|
|
1154
1153
|
return {
|
|
1155
1154
|
model,
|
|
1156
1155
|
effort,
|
|
@@ -1282,7 +1281,7 @@ export class Orchestrator {
|
|
|
1282
1281
|
*/
|
|
1283
1282
|
harnessHomeFor(wsm, envelope, routed, runInput) {
|
|
1284
1283
|
// Refuses (never silently degrades) when a delegated run cannot be confined.
|
|
1285
|
-
return scopedHarnessHome(wsm, envelope, envelope.worktree_path === envelope.repo_root, runInput?.delegated === true, routed.adapterAccess);
|
|
1284
|
+
return scopedHarnessHome(wsm, envelope, envelope.worktree_path === envelope.repo_root, runInput?.delegated === true, routed.adapterAccess, runInput?.threadId ? (id) => this.laneHomeEnvFor(runInput, routed.adapter.id, id) : null);
|
|
1286
1285
|
}
|
|
1287
1286
|
async runCandidateInEnvelope(routed, envelope, attemptId, label, contract, prompt, store, paths, wsm, ledger, processingLease, access, onHarnessEvent, signal, modelHint, effortHint, intent, log, effectiveWebMode, interaction, budgetGuard, runInput, streamDeltas, fileBackedContext,
|
|
1288
1287
|
/** D-16d: when set, the mechanical continuation checkpoint pointer for a
|
|
@@ -1305,13 +1304,14 @@ export class Orchestrator {
|
|
|
1305
1304
|
manager: wsm,
|
|
1306
1305
|
envelope,
|
|
1307
1306
|
artifactRoot: join(paths.attemptsDir, attemptId),
|
|
1308
|
-
sourceRoot: contract.repo.root,
|
|
1309
1307
|
observedPaths: [...observedPaths],
|
|
1310
1308
|
}));
|
|
1311
1309
|
const rawContextPacket = await rawContextForEnvelope(routed.implementationTransport, envelope);
|
|
1312
1310
|
const sessionFields = runInput
|
|
1313
1311
|
? await this.sessionSpecFields(runInput, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission)
|
|
1314
1312
|
: undefined;
|
|
1313
|
+
// A delegated thread's in-place lane home is keyed by the RESOLVED account.
|
|
1314
|
+
selectLaneHome(harnessHome, sessionFields?.credential_profile?.profile_id ?? runInput?.credentialProfileId ?? null);
|
|
1315
1315
|
const processingAdmission = processingAdmissionForLease(ledger, processingLease.lease, adapter.id, attemptId, processingLease.onDenied);
|
|
1316
1316
|
const capturedProcessing = {
|
|
1317
1317
|
processing_preference: contract.processing_preference,
|
|
@@ -1381,21 +1381,21 @@ export class Orchestrator {
|
|
|
1381
1381
|
// from the LIVE spec, so a W5.4 failover that rotates the credential
|
|
1382
1382
|
// mid-attempt is re-recorded at the point it becomes true — the receipt
|
|
1383
1383
|
// must name the profile that ran, never the one the request asked for.
|
|
1384
|
-
const appliedNow = () => appliedAttemptFacts(harnessHome, spec.access, spec.credential_profile?.profile_id ?? runInput?.credentialProfileId ?? null);
|
|
1384
|
+
const appliedNow = () => appliedAttemptFacts(followSpecHome(harnessHome, spec, runInput?.credentialProfileId ?? null), spec.access, spec.credential_profile?.profile_id ?? runInput?.credentialProfileId ?? null);
|
|
1385
1385
|
let applied = appliedNow();
|
|
1386
1386
|
const attemptStartedMs = Date.now();
|
|
1387
1387
|
const budgetSignalState = { quotaPressureDisclosed: false };
|
|
1388
|
-
const triedProfiles = new Set(); // W5.4 failover: each profile at most once
|
|
1389
1388
|
let cost = 0;
|
|
1390
1389
|
let costEstimated = false;
|
|
1391
1390
|
let harnessErrored = false;
|
|
1391
|
+
let streamBudgetDenied = false;
|
|
1392
1392
|
let processingRefusal = null;
|
|
1393
1393
|
let poolExhausted = null; // A5: typed pool-exhausted refusal
|
|
1394
1394
|
const deltaFlood = { count: 0, disclosed: false }; // W-C4 per-attempt delta budget
|
|
1395
1395
|
// QA-024: emit the belt-failure disclosure event at most once per attempt.
|
|
1396
1396
|
let beltFailureDisclosed = false;
|
|
1397
1397
|
const errors = [];
|
|
1398
|
-
let answer = new
|
|
1398
|
+
let answer = new secretDiff.CountedAnswerAssembly();
|
|
1399
1399
|
const retryPolicy = transientRetryPolicy(this.config(contract.repo.root));
|
|
1400
1400
|
// QA-024: the delegation belt is the ONLY engine-owned extra MCP server
|
|
1401
1401
|
// injected into an agent lane (the browser MCP rides its own field), so its
|
|
@@ -1416,28 +1416,31 @@ export class Orchestrator {
|
|
|
1416
1416
|
onAbort();
|
|
1417
1417
|
else
|
|
1418
1418
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1419
|
+
let continuity;
|
|
1420
|
+
({ continuity, spec } = await openContinuity(this.continuityDeps(adapter, routed, contract, attemptId, paths, runInput, log, {
|
|
1421
|
+
envelope,
|
|
1422
|
+
harnessHome,
|
|
1423
|
+
workOrder: prompt,
|
|
1424
|
+
firstPrompt: spec.prompt,
|
|
1425
|
+
}), { spec, continuation: runInput?.continuation, store, telemetry }));
|
|
1419
1426
|
try {
|
|
1420
1427
|
for (let nativeTry = 0; !signal?.aborted; nativeTry += 1) {
|
|
1421
1428
|
// A3 per-try isolation: neither output nor progress markers leak across tries.
|
|
1422
1429
|
if (nativeTry > 0) {
|
|
1423
|
-
answer = new
|
|
1430
|
+
answer = new secretDiff.CountedAnswerAssembly();
|
|
1424
1431
|
telemetry.outputMarkers = newAttemptOutputMarkers();
|
|
1425
1432
|
}
|
|
1426
1433
|
const clearFileBackedContext = stageFileBackedContext(envelope.worktree_path, fileBackedContext);
|
|
1427
1434
|
const runSpec = nativeTry === 0
|
|
1428
1435
|
? spec
|
|
1429
1436
|
: HarnessRunSpec.parse({ ...spec, session_id: newId("ses"), extra: { ...spec.extra } });
|
|
1430
|
-
//
|
|
1431
|
-
// stream (killing the process group through the existing abort
|
|
1432
|
-
// plumbing) without touching the run-level cancel signal, so a timeout
|
|
1433
|
-
// and a user cancel stay distinguishable. Recreated per
|
|
1434
|
-
// nativeTry — a transient retry must get a LIVE signal, not the
|
|
1435
|
-
// previous try's already-aborted composite.
|
|
1437
|
+
// The watchdog aborts only this try; run cancellation also prevents successors.
|
|
1436
1438
|
const attemptAbort = new AbortController();
|
|
1437
1439
|
runSpec.extra["abortSignal"] = signal
|
|
1438
1440
|
? AbortSignal.any([signal, attemptAbort.signal])
|
|
1439
1441
|
: attemptAbort.signal;
|
|
1440
1442
|
activeSessionId = runSpec.session_id;
|
|
1443
|
+
continuity.beginTry(runSpec, nativeTry, attemptAbort);
|
|
1441
1444
|
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
1442
1445
|
const refusal = this.credentials.bindPreProgressRefusal(adapter.id, runSpec);
|
|
1443
1446
|
const transientStart = telemetry.transientFailures.length;
|
|
@@ -1471,15 +1474,16 @@ export class Orchestrator {
|
|
|
1471
1474
|
break;
|
|
1472
1475
|
if (display)
|
|
1473
1476
|
safeInvoke(onHarnessEvent, safeEv);
|
|
1474
|
-
// In-place turns run in the live tree under the native environment, so
|
|
1475
|
-
// the session they emit IS reachable for the next turn: record it. An
|
|
1476
|
-
// ISOLATED envelope-born session lives in the scoped home that dispose()
|
|
1477
|
-
// deletes, so observing it would poison the thread resume map with
|
|
1478
|
-
// unreachable ids — skip it there.
|
|
1479
|
-
if (inPlaceEnvelope)
|
|
1480
|
-
observeNativeSessionEvent(runInput, adapter.id, safeEv);
|
|
1481
1477
|
observeAuthSwitch(log, adapter.id, attemptId, safeEv);
|
|
1482
1478
|
observeAttemptTelemetry(telemetry, safeEv);
|
|
1479
|
+
const mismatch = continuity.observe(safeEv, runSpec, telemetry.outputMarkers);
|
|
1480
|
+
if (mismatch) {
|
|
1481
|
+
harnessErrored = true;
|
|
1482
|
+
errors.push(mismatch);
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
if (inPlaceEnvelope && continuity.mayPublishSession)
|
|
1486
|
+
observeNativeSessionEvent(runInput, adapter.id, safeEv);
|
|
1483
1487
|
// QA-024: the injected delegation belt's MCP server reported a
|
|
1484
1488
|
// terminal startup failure. Disclose it ONCE while live; recoverable
|
|
1485
1489
|
// exact tool-result failures are evaluated at attempt finalization.
|
|
@@ -1512,7 +1516,7 @@ export class Orchestrator {
|
|
|
1512
1516
|
cost = usage.cost;
|
|
1513
1517
|
costEstimated = usage.costEstimated;
|
|
1514
1518
|
if (usage.hardCapReached) {
|
|
1515
|
-
harnessErrored = true;
|
|
1519
|
+
harnessErrored = streamBudgetDenied = true;
|
|
1516
1520
|
errors.push("budget hard cap reached mid-attempt; stream aborted");
|
|
1517
1521
|
break;
|
|
1518
1522
|
}
|
|
@@ -1523,7 +1527,7 @@ export class Orchestrator {
|
|
|
1523
1527
|
}
|
|
1524
1528
|
// Capture assistant prose so an answer-only turn (no file changes) still
|
|
1525
1529
|
// has an honest output artifact; a TYPED final message wins verbatim.
|
|
1526
|
-
answer.
|
|
1530
|
+
answer.observeCounted(ev, safeEv);
|
|
1527
1531
|
// Observe ALL budget/quota signals (one codex usage event carries
|
|
1528
1532
|
// BOTH spend and quota); pressure disclosed once per attempt.
|
|
1529
1533
|
observeBudgetSignals(ledger, log, adapter.id, attemptId, safeEv, budgetSignalState);
|
|
@@ -1571,64 +1575,37 @@ export class Orchestrator {
|
|
|
1571
1575
|
const deliverableEmpty = workspaceUnchanged && answer.text().length === 0;
|
|
1572
1576
|
const delivered = !harnessErrored && !deliverableEmpty && !signal?.aborted;
|
|
1573
1577
|
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
lastLimit: telemetry.rateLimits.at(-1) ?? null,
|
|
1598
|
-
emit: (type, payload) => log?.emit(type, payload),
|
|
1599
|
-
newSessionId: () => newId("ses"),
|
|
1600
|
-
defaultRouteWasVendorNative: routed.authRouteEstimate === "local_session",
|
|
1601
|
-
// D-U6: an explicit pin never rotates; pool-selected rows do.
|
|
1602
|
-
pinned: runInput.credentialProfileId != null,
|
|
1603
|
-
});
|
|
1604
|
-
// A5 ordering: an exhausted pool terminalizes TYPED here, BEFORE the
|
|
1605
|
-
// transient gate below burns same-profile retries (limits classify
|
|
1606
|
-
// as retryable) on the already-refused subject.
|
|
1607
|
-
if (rotated && "poolExhausted" in rotated) {
|
|
1608
|
-
poolExhausted = rotated.poolExhausted;
|
|
1609
|
-
errors.push(safeErrorMessage(poolExhausted));
|
|
1610
|
-
break;
|
|
1611
|
-
}
|
|
1612
|
-
if (rotated) {
|
|
1613
|
-
// INV-137: the rotated row runs in ITS OWN lane home (no-op for
|
|
1614
|
-
// isolated envelopes and native-env in-place turns).
|
|
1615
|
-
spec = rotatedSpecInLaneHome(spec, rotated, (id) => this.laneHomeEnvFor(runInput, adapter.id, id), runInput.credentialProfileId ?? null);
|
|
1616
|
-
applied = appliedNow();
|
|
1617
|
-
errors.length = 0;
|
|
1618
|
-
harnessErrored = false;
|
|
1619
|
-
continue;
|
|
1620
|
-
}
|
|
1578
|
+
const verdict = await continuity.afterTry({
|
|
1579
|
+
runSpec,
|
|
1580
|
+
nativeTry,
|
|
1581
|
+
harnessErrored,
|
|
1582
|
+
budgetStopped: streamBudgetDenied,
|
|
1583
|
+
aborted: signal?.aborted === true,
|
|
1584
|
+
requestRefused: Boolean(telemetry.requestRefusal),
|
|
1585
|
+
newTransients,
|
|
1586
|
+
sawTypedLimit,
|
|
1587
|
+
sawRetryable,
|
|
1588
|
+
answer,
|
|
1589
|
+
rawDeliverableEmpty: deliverableEmpty,
|
|
1590
|
+
workspaceDiffNonEmpty: !workspaceUnchanged,
|
|
1591
|
+
currentDiff,
|
|
1592
|
+
markers: telemetry.outputMarkers,
|
|
1593
|
+
lastLimit: telemetry.rateLimits.at(-1) ?? null,
|
|
1594
|
+
refusal,
|
|
1595
|
+
telemetry,
|
|
1596
|
+
});
|
|
1597
|
+
if (verdict.kind === "terminal") {
|
|
1598
|
+
poolExhausted = verdict.error;
|
|
1599
|
+
errors.push(safeErrorMessage(poolExhausted));
|
|
1600
|
+
break;
|
|
1621
1601
|
}
|
|
1622
|
-
if (
|
|
1623
|
-
!sawRetryable ||
|
|
1624
|
-
!deliverableEmpty ||
|
|
1625
|
-
nativeTry >= retryPolicy.maxRetries ||
|
|
1626
|
-
signal?.aborted)
|
|
1602
|
+
if (verdict.kind === "break")
|
|
1627
1603
|
break;
|
|
1628
|
-
|
|
1604
|
+
spec = verdict.spec;
|
|
1605
|
+
applied = appliedNow();
|
|
1629
1606
|
errors.length = 0;
|
|
1630
1607
|
harnessErrored = false;
|
|
1631
|
-
await sleep(delayMs);
|
|
1608
|
+
await sleep(verdict.delayMs);
|
|
1632
1609
|
}
|
|
1633
1610
|
}
|
|
1634
1611
|
finally {
|
|
@@ -1648,19 +1625,23 @@ export class Orchestrator {
|
|
|
1648
1625
|
// with no typed final contributes no answer material (acceptedTryOutput).
|
|
1649
1626
|
const unwrapped = unwrapWorkReportEnvelope(acceptedTryOutput(answer, harnessErrored), workReportMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
|
|
1650
1627
|
const redacted = redactSecrets(unwrapped.deliverable);
|
|
1651
|
-
|
|
1628
|
+
// INV-062: the (already redacted) answer is kept whatever the patch holds.
|
|
1629
|
+
const answerText = redacted.trim().length > 0 ? redacted : undefined;
|
|
1652
1630
|
if (directory)
|
|
1653
1631
|
await captureDirectory();
|
|
1654
|
-
const
|
|
1655
|
-
? { diff: "",
|
|
1656
|
-
: await secretDiff.
|
|
1657
|
-
|
|
1658
|
-
|
|
1632
|
+
const captured = directory
|
|
1633
|
+
? { diff: "", captureRefusal: directoryCapture.refusal }
|
|
1634
|
+
: await secretDiff.captureCandidateWorkspace({
|
|
1635
|
+
...{ wsm, envelope, inPlace: inPlaceEnvelope, projectRoot: store.repoRoot },
|
|
1636
|
+
...{ answerText, answerMatches: answer.secretLikeMatches() },
|
|
1637
|
+
});
|
|
1638
|
+
const { diff, captureRefusal } = captured;
|
|
1639
|
+
harnessErrored = secretDiff.recordCaptureRefusal(captureRefusal, errors, harnessErrored);
|
|
1659
1640
|
const deliverableEvidence = diff.trim().length > 0 || Boolean(answerText) || directoryHasOutput(directoryCapture.files);
|
|
1660
1641
|
// Cancelled attempts skip gates: running a 600s-per-gate suite delays the ack
|
|
1661
1642
|
// and burns compute on a result nobody will adopt. Diff/attempt.yaml
|
|
1662
1643
|
// still land, so partial work stays inspectable.
|
|
1663
|
-
const gateSignalAborted = signal?.aborted === true ||
|
|
1644
|
+
const gateSignalAborted = signal?.aborted === true || captureRefusal !== undefined;
|
|
1664
1645
|
if (!gateSignalAborted) {
|
|
1665
1646
|
log?.emit("gate.started", {
|
|
1666
1647
|
attempt_id: attemptId,
|
|
@@ -1707,6 +1688,7 @@ export class Orchestrator {
|
|
|
1707
1688
|
workReport: unwrapped.workReport,
|
|
1708
1689
|
workReportSource: unwrapped.source,
|
|
1709
1690
|
workReportViolation: unwrapped.contractViolation,
|
|
1691
|
+
workReportUnverified: unwrapped.unverified ?? null,
|
|
1710
1692
|
contextTerminalExhausted: telemetry.contextExhausted,
|
|
1711
1693
|
});
|
|
1712
1694
|
harnessErrored = finalized.harnessErrored;
|
|
@@ -1714,6 +1696,14 @@ export class Orchestrator {
|
|
|
1714
1696
|
errors.push(`work_report contract: ${unwrapped.contractViolation}`);
|
|
1715
1697
|
const deliverablePresent = finalized.deliverablePresent;
|
|
1716
1698
|
const errored = harnessErrored || webBlocked;
|
|
1699
|
+
const resumable = await continuity.finish({
|
|
1700
|
+
runSpec: spec,
|
|
1701
|
+
errored,
|
|
1702
|
+
nonSuccess: !gatesPassed(gates),
|
|
1703
|
+
aborted: signal?.aborted === true,
|
|
1704
|
+
cancelReason: typeof signal?.reason === "string" ? signal.reason : null,
|
|
1705
|
+
workState: finalized.workState,
|
|
1706
|
+
});
|
|
1717
1707
|
setAttemptOutcome(telemetry, {
|
|
1718
1708
|
deliverablePresent,
|
|
1719
1709
|
gatesPassed: gates.length > 0 ? gatesPassed(gates) : null,
|
|
@@ -1734,9 +1724,9 @@ export class Orchestrator {
|
|
|
1734
1724
|
attemptDir,
|
|
1735
1725
|
worktreePath: envelope.worktree_path,
|
|
1736
1726
|
artifactRelativeDir,
|
|
1737
|
-
diff,
|
|
1738
|
-
persistPatch: !directory &&
|
|
1739
|
-
persistProducedMedia: !directory &&
|
|
1727
|
+
...{ diff, persistedDiff: captured.persistedDiff },
|
|
1728
|
+
persistPatch: !directory && captureRefusal === undefined && isMutatingAccess(access),
|
|
1729
|
+
persistProducedMedia: !directory && captureRefusal === undefined,
|
|
1740
1730
|
answerText,
|
|
1741
1731
|
record: {
|
|
1742
1732
|
attempt_id: attemptId,
|
|
@@ -1748,14 +1738,14 @@ export class Orchestrator {
|
|
|
1748
1738
|
errors: errors.slice(0, 5),
|
|
1749
1739
|
...telemetrySummary(telemetry),
|
|
1750
1740
|
outcome: telemetry.outcome,
|
|
1751
|
-
...(
|
|
1741
|
+
...secretDiff.attemptDisclosure(captured),
|
|
1752
1742
|
gates: gates.map((g) => ({ id: g.id, status: g.status })),
|
|
1753
1743
|
branch: envelope.branch_name,
|
|
1754
1744
|
...(directoryCapture.files
|
|
1755
1745
|
? {
|
|
1756
1746
|
files_manifest: directoryCapture.files.manifestPath,
|
|
1757
1747
|
manifest_sha256: directoryCapture.files.manifestSha256,
|
|
1758
|
-
source_root:
|
|
1748
|
+
source_root: directoryCapture.files.manifest.sourceRoot,
|
|
1759
1749
|
execution_root: envelope.worktree_path,
|
|
1760
1750
|
no_changes: directoryCapture.files.noChanges,
|
|
1761
1751
|
}
|
|
@@ -1780,7 +1770,7 @@ export class Orchestrator {
|
|
|
1780
1770
|
attemptId,
|
|
1781
1771
|
harnessId: adapter.id,
|
|
1782
1772
|
label,
|
|
1783
|
-
|
|
1773
|
+
...captured,
|
|
1784
1774
|
...(directoryCapture.files ? { files: directoryCapture.files } : {}),
|
|
1785
1775
|
answerText,
|
|
1786
1776
|
reviewCwd: envelope.worktree_path,
|
|
@@ -1792,7 +1782,6 @@ export class Orchestrator {
|
|
|
1792
1782
|
costEstimated,
|
|
1793
1783
|
errors: errors.slice(0, 8),
|
|
1794
1784
|
telemetry,
|
|
1795
|
-
...(secretDiffRefusal ? { secretDiffRefusal } : {}),
|
|
1796
1785
|
// A5: the typed refusal survives NORMAL attempt finalization (no throw).
|
|
1797
1786
|
...(telemetry.requestRefusal
|
|
1798
1787
|
? { declaredFailure: requestRefusalFailure(telemetry.requestRefusal) }
|
|
@@ -1801,14 +1790,47 @@ export class Orchestrator {
|
|
|
1801
1790
|
: {}),
|
|
1802
1791
|
outcomeClass: finalized.outcomeClass,
|
|
1803
1792
|
applied,
|
|
1793
|
+
...continuity.terminalFacts(resumable),
|
|
1804
1794
|
};
|
|
1805
1795
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1796
|
+
continuityDeps(adapter, routed, contract, attemptId, paths, runInput, log, lane) {
|
|
1797
|
+
const repoRoot = contract.repo.root;
|
|
1798
|
+
const cwd = lane.envelope?.worktree_path ?? (runInput ? this.execRootOf(runInput) : repoRoot);
|
|
1799
|
+
return {
|
|
1800
|
+
adapter,
|
|
1801
|
+
runId: paths.runId,
|
|
1802
|
+
attemptId,
|
|
1803
|
+
runDir: paths.root,
|
|
1804
|
+
attemptDir: join(paths.attemptsDir, attemptId),
|
|
1805
|
+
cwd,
|
|
1806
|
+
workspace: continuityWorkspace(lane.envelope, cwd),
|
|
1807
|
+
workOrder: lane.workOrder,
|
|
1808
|
+
firstPrompt: lane.firstPrompt,
|
|
1809
|
+
registry: this.config(repoRoot)?.global.credential_profiles ?? [],
|
|
1810
|
+
policy: this.credentials.profilePolicy(repoRoot, adapter.id),
|
|
1811
|
+
snapshots: this.deps.quotaSnapshots?.() ?? [],
|
|
1812
|
+
retryPolicy: transientRetryPolicy(this.config(repoRoot)),
|
|
1813
|
+
pinned: runInput?.credentialProfileId != null, // D-U6: a pin never rotates
|
|
1814
|
+
defaultRouteWasVendorNative: routed.authRouteEstimate === "local_session",
|
|
1815
|
+
requestedProfileId: runInput?.credentialProfileId ?? null,
|
|
1816
|
+
rotationEnabled: runInput !== undefined,
|
|
1817
|
+
laneEnvFor: (id) => (runInput ? this.laneHomeEnvFor(runInput, adapter.id, id) : null),
|
|
1818
|
+
probeReadyProfiles: (spec, tried) => runInput
|
|
1819
|
+
? this.credentials.readyProfileIdsForRotation(runInput, adapter.id, spec.credential_profile ?? null, tried, spec.model_hint ?? null)
|
|
1820
|
+
: Promise.resolve(new Set()),
|
|
1821
|
+
rotationObservations: (spec, transients, refusal) => this.credentials.rotationObservations(adapter, spec, transients, refusal),
|
|
1822
|
+
emit: (type, payload) => log?.emit(type, payload),
|
|
1823
|
+
route: routed,
|
|
1824
|
+
newSessionId: () => newId("ses"),
|
|
1825
|
+
thread: runInput?.threadId
|
|
1826
|
+
? {
|
|
1827
|
+
threadId: runInput.threadId,
|
|
1828
|
+
turnId: runInput.threadContinuity?.turnId ?? null,
|
|
1829
|
+
onSessionObserved: runInput.onSessionObserved,
|
|
1830
|
+
onContinuityResolved: runInput.onContinuityResolved,
|
|
1831
|
+
}
|
|
1832
|
+
: null,
|
|
1833
|
+
};
|
|
1812
1834
|
}
|
|
1813
1835
|
async runRace(input, mode, announce) {
|
|
1814
1836
|
const taskId = input.taskId ?? newId("task");
|
|
@@ -1928,7 +1950,7 @@ export class Orchestrator {
|
|
|
1928
1950
|
const disposeReviewEnvelopes = async () => {
|
|
1929
1951
|
const envelopes = reviewEnvelopes.splice(0);
|
|
1930
1952
|
for (const env of envelopes)
|
|
1931
|
-
await
|
|
1953
|
+
await kept.settle(wsm, env, runsBySlot);
|
|
1932
1954
|
};
|
|
1933
1955
|
const candidateAccess = contract.access.effective_profile;
|
|
1934
1956
|
let budgetStopped = false;
|
|
@@ -1937,6 +1959,7 @@ export class Orchestrator {
|
|
|
1937
1959
|
let budgetDenial = null;
|
|
1938
1960
|
let softWarned = false;
|
|
1939
1961
|
const requestedSingleCandidate = adapters.length === 1;
|
|
1962
|
+
const kept = custody.forRun(input, requestedSingleCandidate && mutatingRun, runId, paths, log);
|
|
1940
1963
|
const slots = [];
|
|
1941
1964
|
for (let i = 0; i < adapters.length; i++) {
|
|
1942
1965
|
const routed = adapters[i];
|
|
@@ -2036,7 +2059,7 @@ export class Orchestrator {
|
|
|
2036
2059
|
external_context_policy: knobs.webPolicy,
|
|
2037
2060
|
...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
|
|
2038
2061
|
});
|
|
2039
|
-
envelope = await
|
|
2062
|
+
envelope = await kept.envelope(wsm, {
|
|
2040
2063
|
taskId,
|
|
2041
2064
|
attemptId: slot.attemptId,
|
|
2042
2065
|
baseRef: contract.repo.base_ref,
|
|
@@ -2058,7 +2081,7 @@ export class Orchestrator {
|
|
|
2058
2081
|
budgetStopped = true;
|
|
2059
2082
|
budgetDenial ??= denial;
|
|
2060
2083
|
},
|
|
2061
|
-
}, candidateAccess, input.onHarnessEvent, input.signal, downgradeModel ?? undefined, input.effort, this.candidateIntent(input), log, effectiveWeb,
|
|
2084
|
+
}, candidateAccess, input.onHarnessEvent, input.signal, downgradeModel ?? undefined, input.effort, this.candidateIntent(input), log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, slot.attemptId, adapter.id, slot.routed.supportsInteractive), (streamedUsd) => {
|
|
2062
2085
|
ledger.updateHold(slot.lease.lease_id, streamedUsd);
|
|
2063
2086
|
if (ledger.tier() !== "hard")
|
|
2064
2087
|
return false;
|
|
@@ -2101,7 +2124,7 @@ export class Orchestrator {
|
|
|
2101
2124
|
harness: adapter.id,
|
|
2102
2125
|
profileId: input.credentialProfileId ?? null,
|
|
2103
2126
|
priorPrompt: input.prompt,
|
|
2104
|
-
priorOutput: run.answerText ?? run.diff ?? "",
|
|
2127
|
+
priorOutput: run.answerText ?? run.persistedDiff ?? run.diff ?? "",
|
|
2105
2128
|
}));
|
|
2106
2129
|
// Reserve the continuation lease BEFORE any disclosure: a denied lease
|
|
2107
2130
|
// must never emit run.continuation (which claims a continuation
|
|
@@ -2137,7 +2160,7 @@ export class Orchestrator {
|
|
|
2137
2160
|
budgetStopped = true;
|
|
2138
2161
|
budgetDenial ??= denial;
|
|
2139
2162
|
},
|
|
2140
|
-
}, candidateAccess, input.onHarnessEvent, input.signal, downgradeModel ?? undefined, input.effort, this.candidateIntent(input), log, effectiveWeb,
|
|
2163
|
+
}, candidateAccess, input.onHarnessEvent, input.signal, downgradeModel ?? undefined, input.effort, this.candidateIntent(input), log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, contAttemptId, adapter.id, slot.routed.supportsInteractive), (streamedUsd) => {
|
|
2141
2164
|
ledger.updateHold(contLeaseId, streamedUsd);
|
|
2142
2165
|
if (ledger.tier() !== "hard")
|
|
2143
2166
|
return false;
|
|
@@ -2224,7 +2247,7 @@ export class Orchestrator {
|
|
|
2224
2247
|
}
|
|
2225
2248
|
finally {
|
|
2226
2249
|
if (envelope)
|
|
2227
|
-
await
|
|
2250
|
+
await kept.settle(wsm, envelope, runsBySlot); // no leak on create/run error
|
|
2228
2251
|
}
|
|
2229
2252
|
};
|
|
2230
2253
|
await runParallelCandidates(slots, this.deps.runtimeConcurrencyCaps, runSlot);
|
|
@@ -2265,7 +2288,7 @@ export class Orchestrator {
|
|
|
2265
2288
|
input.workspaceKind !== "directory" &&
|
|
2266
2289
|
input.inPlace &&
|
|
2267
2290
|
requestedSingleCandidate &&
|
|
2268
|
-
runs.every((run) => !run.
|
|
2291
|
+
runs.every((run) => !run.captureRefusal)) {
|
|
2269
2292
|
try {
|
|
2270
2293
|
earlyPostTurnSha = await snapshotTree(execRoot);
|
|
2271
2294
|
}
|
|
@@ -2378,7 +2401,7 @@ export class Orchestrator {
|
|
|
2378
2401
|
catch (err) {
|
|
2379
2402
|
// Review preflight/evidence failures end TERMINALLY with artifacts —
|
|
2380
2403
|
// never as an escaped throw that orphans the run dir.
|
|
2381
|
-
return
|
|
2404
|
+
return failAfterCandidates({ log, store, paths, runId, taskId, mode, ledger, runs }, "review", err);
|
|
2382
2405
|
}
|
|
2383
2406
|
finally {
|
|
2384
2407
|
// Review preflight failures must not leak candidate worktrees.
|
|
@@ -2436,7 +2459,7 @@ export class Orchestrator {
|
|
|
2436
2459
|
budgetStopped = true;
|
|
2437
2460
|
budgetDenial ??= denial;
|
|
2438
2461
|
},
|
|
2439
|
-
}, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb,
|
|
2462
|
+
}, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome);
|
|
2440
2463
|
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
2441
2464
|
reviewEnvelopes.push(envelope);
|
|
2442
2465
|
envelope = undefined;
|
|
@@ -2483,7 +2506,7 @@ export class Orchestrator {
|
|
|
2483
2506
|
}
|
|
2484
2507
|
catch (err) {
|
|
2485
2508
|
// Arbitration throws end terminally with artifacts, never as an orphan.
|
|
2486
|
-
return
|
|
2509
|
+
return failAfterCandidates({ log, store, paths, runId, taskId, mode, ledger, runs }, "arbitration", err);
|
|
2487
2510
|
}
|
|
2488
2511
|
log.emit("arbitration.completed", {
|
|
2489
2512
|
winner: result.decision.winner,
|
|
@@ -2629,9 +2652,8 @@ export class Orchestrator {
|
|
|
2629
2652
|
});
|
|
2630
2653
|
}
|
|
2631
2654
|
else if (mutatingRun) {
|
|
2632
|
-
secretDiff.
|
|
2633
|
-
const patchSha256 =
|
|
2634
|
-
store.writeText(join(paths.finalDir, "patch.diff"), winnerRun.diff);
|
|
2655
|
+
const savedPatch = secretDiff.persistFinalPatch(store, paths.finalDir, winnerRun);
|
|
2656
|
+
const patchSha256 = savedPatch.patchSha256;
|
|
2635
2657
|
const wstats = diffStats(winnerRun.diff);
|
|
2636
2658
|
const hasDiff = winnerRun.diff.trim().length > 0;
|
|
2637
2659
|
const blockers = winnerEvidence
|
|
@@ -2727,7 +2749,7 @@ export class Orchestrator {
|
|
|
2727
2749
|
outcome_facts: facts,
|
|
2728
2750
|
review_verified: actualReviewVerified,
|
|
2729
2751
|
budget_stopped: budgetStopped,
|
|
2730
|
-
patch_sha256: patchSha256,
|
|
2752
|
+
...{ patch_sha256: patchSha256, ...savedPatch.meta },
|
|
2731
2753
|
result_kind: resultKind,
|
|
2732
2754
|
diffstat: {
|
|
2733
2755
|
files: wstats.paths.length,
|
|
@@ -2744,7 +2766,7 @@ export class Orchestrator {
|
|
|
2744
2766
|
},
|
|
2745
2767
|
});
|
|
2746
2768
|
}
|
|
2747
|
-
store.writeText(join(paths.finalDir, "summary.md"), renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified));
|
|
2769
|
+
store.writeText(join(paths.finalDir, "summary.md"), renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.secretLikeSummaryLine(winnerRun.secretLike)));
|
|
2748
2770
|
// summary.md is a DIAGNOSTIC artifact only (V8/PLAN addendum 2): it no
|
|
2749
2771
|
// longer carries primary-output authority. A clean applyable success or a
|
|
2750
2772
|
// winner answer still marks it ready for legacy INV-116 ordering; any
|
|
@@ -2849,7 +2871,12 @@ export class Orchestrator {
|
|
|
2849
2871
|
log.emit("work_product.emitted", { winner: result.decision.winner });
|
|
2850
2872
|
}
|
|
2851
2873
|
if (!isFailureTerminal) {
|
|
2852
|
-
log.emit("run.completed", {
|
|
2874
|
+
log.emit("run.completed", {
|
|
2875
|
+
lifecycle: facts.lifecycle,
|
|
2876
|
+
facts,
|
|
2877
|
+
reason: facts.reason,
|
|
2878
|
+
...resumableTerminal(store, paths, winnerRun ? [winnerRun] : runs, isFailureTerminal),
|
|
2879
|
+
});
|
|
2853
2880
|
}
|
|
2854
2881
|
else if (facts.lifecycle === "succeeded") {
|
|
2855
2882
|
// needsDecision at terminal — the event's phase must agree with
|
|
@@ -2857,6 +2884,7 @@ export class Orchestrator {
|
|
|
2857
2884
|
log.emit("run.blocked", {
|
|
2858
2885
|
lifecycle: facts.lifecycle,
|
|
2859
2886
|
facts,
|
|
2887
|
+
...resumableTerminal(store, paths, runs, isFailureTerminal),
|
|
2860
2888
|
phase: deliveryFailureReason && !finalVerifyFailed
|
|
2861
2889
|
? "delivery"
|
|
2862
2890
|
: finalVerifyFailed
|
|
@@ -2872,6 +2900,7 @@ export class Orchestrator {
|
|
|
2872
2900
|
reason: facts.reason,
|
|
2873
2901
|
phase: "arbitration",
|
|
2874
2902
|
failure_ref: "final/failure.yaml",
|
|
2903
|
+
...resumableTerminal(store, paths, runs, isFailureTerminal),
|
|
2875
2904
|
});
|
|
2876
2905
|
}
|
|
2877
2906
|
return {
|
|
@@ -3233,7 +3262,7 @@ export class Orchestrator {
|
|
|
3233
3262
|
exhausted = true;
|
|
3234
3263
|
processingBudgetDenial ??= denial;
|
|
3235
3264
|
},
|
|
3236
|
-
}, convergenceAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "repair", log, effectiveWeb,
|
|
3265
|
+
}, convergenceAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "repair", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive), (streamedUsd) => {
|
|
3237
3266
|
ledger.updateHold(lease.lease?.lease_id ?? "", streamedUsd);
|
|
3238
3267
|
return ledger.tier() === "hard";
|
|
3239
3268
|
}, input, true, // convergence runs one candidate: live deltas on (W-C4)
|
|
@@ -3282,11 +3311,10 @@ export class Orchestrator {
|
|
|
3282
3311
|
manager: wsm,
|
|
3283
3312
|
envelope,
|
|
3284
3313
|
artifactRoot: join(paths.attemptsDir, attemptId),
|
|
3285
|
-
sourceRoot: contract.repo.root,
|
|
3286
3314
|
observedPaths: [...observedPaths],
|
|
3287
3315
|
});
|
|
3288
3316
|
run.files = captured.files;
|
|
3289
|
-
run.
|
|
3317
|
+
run.captureRefusal = captured.refusal;
|
|
3290
3318
|
}
|
|
3291
3319
|
}
|
|
3292
3320
|
lastRun = run;
|
|
@@ -3298,9 +3326,7 @@ export class Orchestrator {
|
|
|
3298
3326
|
// Post-mutation fence for in-place: snapshot the live tree NOW (after the
|
|
3299
3327
|
// harness mutated it, before this attempt's review). The last attempt's
|
|
3300
3328
|
// value is the revert target persisted into work_product.yaml.
|
|
3301
|
-
if (input.inPlace === true &&
|
|
3302
|
-
input.workspaceKind !== "directory" &&
|
|
3303
|
-
!run.secretDiffRefusal) {
|
|
3329
|
+
if (input.inPlace === true && input.workspaceKind !== "directory" && !run.captureRefusal) {
|
|
3304
3330
|
try {
|
|
3305
3331
|
lastPostTurnSha = await snapshotTree(execRoot);
|
|
3306
3332
|
}
|
|
@@ -3340,7 +3366,7 @@ export class Orchestrator {
|
|
|
3340
3366
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null);
|
|
3341
3367
|
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), failure.metadata);
|
|
3342
3368
|
}
|
|
3343
|
-
if (input.workspaceKind === "directory" && run.
|
|
3369
|
+
if (input.workspaceKind === "directory" && run.captureRefusal) {
|
|
3344
3370
|
return failedCandidatesResult({
|
|
3345
3371
|
ledger,
|
|
3346
3372
|
mode,
|
|
@@ -3409,7 +3435,7 @@ export class Orchestrator {
|
|
|
3409
3435
|
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3410
3436
|
log,
|
|
3411
3437
|
});
|
|
3412
|
-
return
|
|
3438
|
+
return failAfterCandidates({ log, store, paths, runId, taskId, mode, ledger, runs: [run] }, "review", err);
|
|
3413
3439
|
}
|
|
3414
3440
|
if (conv.converged) {
|
|
3415
3441
|
converged = true;
|
|
@@ -3600,15 +3626,10 @@ export class Orchestrator {
|
|
|
3600
3626
|
facts,
|
|
3601
3627
|
log,
|
|
3602
3628
|
});
|
|
3603
|
-
if (lastRun.answerText) {
|
|
3604
|
-
store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
|
|
3605
|
-
log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
|
|
3606
|
-
}
|
|
3607
3629
|
}
|
|
3608
3630
|
else {
|
|
3609
|
-
secretDiff.
|
|
3610
|
-
const patchSha256 =
|
|
3611
|
-
store.writeText(join(paths.finalDir, "patch.diff"), lastRun.diff);
|
|
3631
|
+
const savedPatch = secretDiff.persistFinalPatch(store, paths.finalDir, lastRun);
|
|
3632
|
+
const patchSha256 = savedPatch.patchSha256;
|
|
3612
3633
|
// Honest apply-state (parity with runRace single-candidate in-place): a
|
|
3613
3634
|
// convergence run with inPlace mutated the live tree directly across its
|
|
3614
3635
|
// attempts, so it is "applied" even when review blocked (Revert offered).
|
|
@@ -3635,7 +3656,7 @@ export class Orchestrator {
|
|
|
3635
3656
|
lifecycle: facts.lifecycle,
|
|
3636
3657
|
outcome_facts: facts,
|
|
3637
3658
|
review_verified: actualReviewVerified,
|
|
3638
|
-
patch_sha256: patchSha256,
|
|
3659
|
+
...{ patch_sha256: patchSha256, ...savedPatch.meta },
|
|
3639
3660
|
adopted: convAdopted,
|
|
3640
3661
|
apply_state: convApplyState,
|
|
3641
3662
|
pre_turn_sha: convAdopted === true ? preTurnSha : null,
|
|
@@ -3645,7 +3666,12 @@ export class Orchestrator {
|
|
|
3645
3666
|
},
|
|
3646
3667
|
});
|
|
3647
3668
|
}
|
|
3648
|
-
|
|
3669
|
+
// INV-062: the answer is kept for every convergence result, patch or files.
|
|
3670
|
+
if (lastRun.answerText) {
|
|
3671
|
+
store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
|
|
3672
|
+
log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
|
|
3673
|
+
}
|
|
3674
|
+
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${secretDiff.summaryDisclosure(lastRun.secretLike)}\n`);
|
|
3649
3675
|
// Lifecycle invariant (all modes): output.ready precedes the terminal
|
|
3650
3676
|
// event so a client that applied the terminal event has the output.
|
|
3651
3677
|
log.emit("output.ready", {
|
|
@@ -3657,6 +3683,7 @@ export class Orchestrator {
|
|
|
3657
3683
|
// A needs-decision terminal (review blocked / checks failed) writes a
|
|
3658
3684
|
// failure record + fires run.blocked even though the lifecycle succeeded.
|
|
3659
3685
|
const convIsFailureTerminal = facts.lifecycle !== "succeeded" || convNeedsDecision;
|
|
3686
|
+
const continuation = resumableTerminal(store, paths, lastRun ? [lastRun] : [], convIsFailureTerminal);
|
|
3660
3687
|
if (convIsFailureTerminal) {
|
|
3661
3688
|
// A5 last-result lift: the LAST attempt's typed mid-attempt refusal
|
|
3662
3689
|
// survives onto the terminal; budget/cancel/needs-decision outrank it.
|
|
@@ -3732,6 +3759,7 @@ export class Orchestrator {
|
|
|
3732
3759
|
}
|
|
3733
3760
|
if (!convIsFailureTerminal) {
|
|
3734
3761
|
log.emit("run.completed", {
|
|
3762
|
+
...continuation,
|
|
3735
3763
|
lifecycle: facts.lifecycle,
|
|
3736
3764
|
facts,
|
|
3737
3765
|
reason: facts.reason,
|
|
@@ -3740,6 +3768,7 @@ export class Orchestrator {
|
|
|
3740
3768
|
}
|
|
3741
3769
|
else if (facts.lifecycle === "succeeded") {
|
|
3742
3770
|
log.emit("run.blocked", {
|
|
3771
|
+
...continuation,
|
|
3743
3772
|
lifecycle: facts.lifecycle,
|
|
3744
3773
|
facts,
|
|
3745
3774
|
attempts: attempt,
|
|
@@ -3749,6 +3778,7 @@ export class Orchestrator {
|
|
|
3749
3778
|
}
|
|
3750
3779
|
else {
|
|
3751
3780
|
log.emit("run.failed", {
|
|
3781
|
+
...continuation,
|
|
3752
3782
|
lifecycle: facts.lifecycle,
|
|
3753
3783
|
facts,
|
|
3754
3784
|
reason: facts.reason,
|
|
@@ -3827,7 +3857,7 @@ export class Orchestrator {
|
|
|
3827
3857
|
spec.extra["abortSignal"] = input.signal
|
|
3828
3858
|
? AbortSignal.any([input.signal, plannerAbort.signal])
|
|
3829
3859
|
: plannerAbort.signal;
|
|
3830
|
-
const planInteraction =
|
|
3860
|
+
const planInteraction = interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
3831
3861
|
if (planInteraction)
|
|
3832
3862
|
spec.extra["interactionChannel"] = planInteraction;
|
|
3833
3863
|
const planWorkMode = applyWorkEnvelope(spec, this.workReportEnvelopeFor(routed, contract, Boolean(planInteraction)));
|
|
@@ -4420,18 +4450,14 @@ export class Orchestrator {
|
|
|
4420
4450
|
: null) ?? roHome.env,
|
|
4421
4451
|
});
|
|
4422
4452
|
this.credentials.stampProfileSelection(spec, input, adapter.id);
|
|
4423
|
-
const
|
|
4424
|
-
spec.extra["abortSignal"] = input.signal
|
|
4425
|
-
? AbortSignal.any([input.signal, reportAbort.signal])
|
|
4426
|
-
: reportAbort.signal;
|
|
4427
|
-
const reportInteraction = this.interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
4453
|
+
const reportInteraction = interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
4428
4454
|
if (reportInteraction)
|
|
4429
4455
|
spec.extra["interactionChannel"] = reportInteraction;
|
|
4430
4456
|
// D-16: compile the WorkReport envelope for the read-only lane.
|
|
4431
4457
|
const readonlyWorkEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(reportInteraction));
|
|
4432
4458
|
const readonlyWorkMode = applyWorkEnvelope(spec, readonlyWorkEnvelope);
|
|
4433
4459
|
const attemptEventsPath = join(paths.attemptsDir, attemptId, "events.jsonl");
|
|
4434
|
-
const answer = new
|
|
4460
|
+
const answer = new secretDiff.CountedAnswerAssembly();
|
|
4435
4461
|
const telemetry = createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [],
|
|
4436
4462
|
// Requested-model capture so ask/audit route receipts detect a silent
|
|
4437
4463
|
// model downgrade (typed model_mismatch), not just agent runs.
|
|
@@ -4439,7 +4465,6 @@ export class Orchestrator {
|
|
|
4439
4465
|
return {
|
|
4440
4466
|
knobs,
|
|
4441
4467
|
spec,
|
|
4442
|
-
reportAbort,
|
|
4443
4468
|
reportInteraction,
|
|
4444
4469
|
readonlyWorkMode,
|
|
4445
4470
|
attemptEventsPath,
|
|
@@ -4481,13 +4506,20 @@ export class Orchestrator {
|
|
|
4481
4506
|
}
|
|
4482
4507
|
return { status: "launched" };
|
|
4483
4508
|
}
|
|
4484
|
-
const { knobs, spec: preparedSpec,
|
|
4509
|
+
const { knobs, spec: preparedSpec, reportInteraction, readonlyWorkMode, attemptEventsPath, telemetry, } = preparation.value;
|
|
4485
4510
|
let { answer } = preparation.value;
|
|
4486
4511
|
let spec = preparedSpec;
|
|
4487
4512
|
bindProcessingAdmission(spec, ledger, lease.lease, adapter.id, attemptId, undefined, processingAdmission);
|
|
4488
4513
|
const retryPolicy = transientRetryPolicy(this.config(input.repoRoot));
|
|
4489
4514
|
let activeSessionId = spec.session_id;
|
|
4490
4515
|
const live = liveAttempt(input, routed, paths, contract, attemptId, () => activeSessionId);
|
|
4516
|
+
let continuity;
|
|
4517
|
+
({ continuity, spec } = await openContinuity(this.continuityDeps(adapter, routed, contract, attemptId, paths, input, log, {
|
|
4518
|
+
envelope: null,
|
|
4519
|
+
harnessHome: null,
|
|
4520
|
+
workOrder: input.prompt,
|
|
4521
|
+
firstPrompt: spec.prompt,
|
|
4522
|
+
}), { spec, continuation: input.continuation, store, telemetry }));
|
|
4491
4523
|
const onAbort = () => void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4492
4524
|
if (input.signal) {
|
|
4493
4525
|
if (input.signal.aborted)
|
|
@@ -4502,12 +4534,11 @@ export class Orchestrator {
|
|
|
4502
4534
|
let processingRefusal = null;
|
|
4503
4535
|
let poolExhausted = null; // A5: typed pool-exhausted refusal
|
|
4504
4536
|
try {
|
|
4505
|
-
const triedProfiles = new Set(); // W5.4 failover: each profile at most once
|
|
4506
4537
|
for (let nativeTry = 0; !input.signal?.aborted; nativeTry += 1) {
|
|
4507
4538
|
// A3 per-try isolation (candidate-lane parity): neither a failed try's
|
|
4508
4539
|
// output nor its progress markers leak into the next try's evidence.
|
|
4509
4540
|
if (nativeTry > 0) {
|
|
4510
|
-
answer = new
|
|
4541
|
+
answer = new secretDiff.CountedAnswerAssembly();
|
|
4511
4542
|
telemetry.outputMarkers = newAttemptOutputMarkers();
|
|
4512
4543
|
}
|
|
4513
4544
|
const runSpec = nativeTry === 0
|
|
@@ -4515,10 +4546,14 @@ export class Orchestrator {
|
|
|
4515
4546
|
: HarnessRunSpec.parse({
|
|
4516
4547
|
...spec,
|
|
4517
4548
|
session_id: newId("ses"),
|
|
4518
|
-
resume_session_id: null,
|
|
4519
4549
|
extra: { ...spec.extra },
|
|
4520
4550
|
});
|
|
4521
4551
|
activeSessionId = runSpec.session_id;
|
|
4552
|
+
const tryAbort = new AbortController();
|
|
4553
|
+
runSpec.extra["abortSignal"] = input.signal
|
|
4554
|
+
? AbortSignal.any([input.signal, tryAbort.signal])
|
|
4555
|
+
: tryAbort.signal;
|
|
4556
|
+
continuity.beginTry(runSpec, nativeTry, tryAbort);
|
|
4522
4557
|
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
4523
4558
|
const refusal = this.credentials.bindPreProgressRefusal(adapter.id, runSpec);
|
|
4524
4559
|
const transientStart = telemetry.transientFailures.length;
|
|
@@ -4536,7 +4571,7 @@ export class Orchestrator {
|
|
|
4536
4571
|
timeoutMs: harnessInactivityTimeoutMs(this.config(input.repoRoot)),
|
|
4537
4572
|
countsAsProgress: countsAsAgentProgress,
|
|
4538
4573
|
onTimeout: () => {
|
|
4539
|
-
|
|
4574
|
+
tryAbort.abort();
|
|
4540
4575
|
void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4541
4576
|
},
|
|
4542
4577
|
isSuspended: () => (reportInteraction?.pendingCount?.() ?? 0) > 0,
|
|
@@ -4546,20 +4581,19 @@ export class Orchestrator {
|
|
|
4546
4581
|
const safeEv = redactHarnessEvent(ev);
|
|
4547
4582
|
if (safeEv.payload?.["buffered"] !== true)
|
|
4548
4583
|
safeInvoke(input.onHarnessEvent, safeEv);
|
|
4549
|
-
// A thread ASK turn IS a chat turn now (INV-034): its native
|
|
4550
|
-
// session lives in the DURABLE per-lane home, so record it for the
|
|
4551
|
-
// next lane turn's resume. The read-only fallback chain is
|
|
4552
|
-
// sequential (never the parallel deep-scan swarm, which is
|
|
4553
|
-
// excluded from `laneRun`), so recordSession's upsert keeps the
|
|
4554
|
-
// latest lane session without a race.
|
|
4555
|
-
if (laneRun)
|
|
4556
|
-
observeNativeSessionEvent(input, adapter.id, safeEv);
|
|
4557
4584
|
observeAuthSwitch(log, adapter.id, attemptId, safeEv);
|
|
4558
4585
|
log.emit("harness.event", harnessEventPayload(adapter.id, attemptId, safeEv), safeEv.payload?.["buffered"] !== true);
|
|
4559
4586
|
appendLine(attemptEventsPath, JSON.stringify(safeEv));
|
|
4560
4587
|
if (input.signal?.aborted)
|
|
4561
4588
|
break;
|
|
4562
4589
|
observeAttemptTelemetry(telemetry, safeEv);
|
|
4590
|
+
const mismatch = continuity.observe(safeEv, runSpec, telemetry.outputMarkers);
|
|
4591
|
+
if (mismatch) {
|
|
4592
|
+
harnessError = mismatch;
|
|
4593
|
+
break;
|
|
4594
|
+
}
|
|
4595
|
+
if (laneRun && continuity.mayPublishSession)
|
|
4596
|
+
observeNativeSessionEvent(input, adapter.id, safeEv);
|
|
4563
4597
|
emitPlanProgress((t, p) => log.emit(t, p), adapter.id, attemptId, safeEv);
|
|
4564
4598
|
// read-only routes burn quota too (the orchestrate PLANNER is
|
|
4565
4599
|
// the loudest) — same single owner as the agent loop.
|
|
@@ -4573,12 +4607,12 @@ export class Orchestrator {
|
|
|
4573
4607
|
budgetStopped = streamBudgetDenied = true;
|
|
4574
4608
|
budgetDenial ??= streamDenial;
|
|
4575
4609
|
harnessError = streamDenial.reason;
|
|
4576
|
-
|
|
4610
|
+
tryAbort.abort();
|
|
4577
4611
|
void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4578
4612
|
break;
|
|
4579
4613
|
}
|
|
4580
4614
|
// A TYPED final message wins verbatim over joined narration.
|
|
4581
|
-
answer.
|
|
4615
|
+
answer.observeCounted(ev, safeEv);
|
|
4582
4616
|
if (safeEv.type === "error")
|
|
4583
4617
|
harnessError = safeEv.error
|
|
4584
4618
|
? redactSecrets(safeEv.error)
|
|
@@ -4606,62 +4640,34 @@ export class Orchestrator {
|
|
|
4606
4640
|
const reportSoFar = answer.text();
|
|
4607
4641
|
const delivered = !harnessError && reportSoFar.length > 0 && !input.signal?.aborted;
|
|
4608
4642
|
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
emit: (type, payload) => log.emit(type, payload),
|
|
4632
|
-
newSessionId: () => newId("ses"),
|
|
4633
|
-
defaultRouteWasVendorNative: routed.authRouteEstimate === "local_session",
|
|
4634
|
-
// D-U6: an explicit pin never rotates; pool-selected rows do.
|
|
4635
|
-
pinned: input.credentialProfileId != null,
|
|
4636
|
-
});
|
|
4637
|
-
// A5 ordering: same as the candidate lane — terminalize typed
|
|
4638
|
-
// before the transient gate burns same-profile retries. The pool
|
|
4639
|
-
// refusal APPENDS to the original harness error (candidate-lane
|
|
4640
|
-
// parity: errors.push) instead of erasing the true failure.
|
|
4641
|
-
if (rotated && "poolExhausted" in rotated) {
|
|
4642
|
-
poolExhausted = rotated.poolExhausted;
|
|
4643
|
-
harnessError = harnessError
|
|
4644
|
-
? `${harnessError}; ${safeErrorMessage(poolExhausted)}`
|
|
4645
|
-
: safeErrorMessage(poolExhausted);
|
|
4646
|
-
break;
|
|
4647
|
-
}
|
|
4648
|
-
if (rotated) {
|
|
4649
|
-
// INV-137: the rotated row's session must land in ITS OWN lane
|
|
4650
|
-
// home — never the previous row's lane store.
|
|
4651
|
-
spec = rotatedSpecInLaneHome(spec, rotated, (id) => this.laneHomeEnvFor(input, adapter.id, id), input.credentialProfileId ?? null);
|
|
4652
|
-
harnessError = null;
|
|
4653
|
-
continue;
|
|
4654
|
-
}
|
|
4643
|
+
const verdict = await continuity.afterTry({
|
|
4644
|
+
runSpec,
|
|
4645
|
+
nativeTry,
|
|
4646
|
+
harnessErrored: harnessError !== null,
|
|
4647
|
+
aborted: input.signal?.aborted === true,
|
|
4648
|
+
requestRefused: Boolean(telemetry.requestRefusal),
|
|
4649
|
+
newTransients,
|
|
4650
|
+
sawTypedLimit,
|
|
4651
|
+
sawRetryable,
|
|
4652
|
+
answer,
|
|
4653
|
+
rawDeliverableEmpty: reportSoFar.length === 0,
|
|
4654
|
+
markers: telemetry.outputMarkers,
|
|
4655
|
+
lastLimit: telemetry.rateLimits.at(-1) ?? null,
|
|
4656
|
+
refusal,
|
|
4657
|
+
telemetry,
|
|
4658
|
+
});
|
|
4659
|
+
if (verdict.kind === "terminal") {
|
|
4660
|
+
poolExhausted = verdict.error;
|
|
4661
|
+
harnessError = harnessError
|
|
4662
|
+
? `${harnessError}; ${safeErrorMessage(poolExhausted)}`
|
|
4663
|
+
: safeErrorMessage(poolExhausted);
|
|
4664
|
+
break;
|
|
4655
4665
|
}
|
|
4656
|
-
if (
|
|
4657
|
-
!sawRetryable ||
|
|
4658
|
-
reportSoFar.length > 0 ||
|
|
4659
|
-
nativeTry >= retryPolicy.maxRetries ||
|
|
4660
|
-
input.signal?.aborted)
|
|
4666
|
+
if (verdict.kind === "break")
|
|
4661
4667
|
break;
|
|
4662
|
-
|
|
4668
|
+
spec = verdict.spec;
|
|
4663
4669
|
harnessError = null;
|
|
4664
|
-
await sleep(delayMs);
|
|
4670
|
+
await sleep(verdict.delayMs);
|
|
4665
4671
|
}
|
|
4666
4672
|
}
|
|
4667
4673
|
finally {
|
|
@@ -4703,6 +4709,7 @@ export class Orchestrator {
|
|
|
4703
4709
|
workReport: roUnwrapped.workReport,
|
|
4704
4710
|
workReportSource: roUnwrapped.source,
|
|
4705
4711
|
workReportViolation: roUnwrapped.contractViolation,
|
|
4712
|
+
workReportUnverified: roUnwrapped.unverified ?? null,
|
|
4706
4713
|
contextTerminalExhausted: telemetry.contextExhausted,
|
|
4707
4714
|
});
|
|
4708
4715
|
// A broken WorkReport contract is a hard failure ONLY when the finalizer
|
|
@@ -4725,6 +4732,13 @@ export class Orchestrator {
|
|
|
4725
4732
|
// interruptions keep the strict deliverable requirement.
|
|
4726
4733
|
emptyDeliverableAllowed: roFinalized.outcomeClass === "clean",
|
|
4727
4734
|
});
|
|
4735
|
+
const roResumable = await continuity.finish({
|
|
4736
|
+
runSpec: spec,
|
|
4737
|
+
errored: harnessError !== null,
|
|
4738
|
+
aborted: input.signal?.aborted === true,
|
|
4739
|
+
cancelReason: typeof input.signal?.reason === "string" ? input.signal.reason : null,
|
|
4740
|
+
workState: roFinalized.workState,
|
|
4741
|
+
});
|
|
4728
4742
|
if (harnessError) {
|
|
4729
4743
|
log.emit("harness.completed", {
|
|
4730
4744
|
harness_id: adapter.id,
|
|
@@ -4745,6 +4759,7 @@ export class Orchestrator {
|
|
|
4745
4759
|
: poolExhausted || processingRefusal
|
|
4746
4760
|
? { declaredFailure: declaredFailure(processingRefusal ?? poolExhausted) }
|
|
4747
4761
|
: {}),
|
|
4762
|
+
...continuity.terminalFacts(roResumable),
|
|
4748
4763
|
});
|
|
4749
4764
|
if (opts.deepScan) {
|
|
4750
4765
|
store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
|
|
@@ -4765,9 +4780,11 @@ export class Orchestrator {
|
|
|
4765
4780
|
harnessId: adapter.id,
|
|
4766
4781
|
status: scoutInterrupted ? "failed" : "success",
|
|
4767
4782
|
report: report || "(no output)",
|
|
4783
|
+
answerMatches: answer.secretLikeMatches(),
|
|
4768
4784
|
error: scoutInterrupted ? "context capacity exhausted before the scout completed" : null,
|
|
4769
4785
|
telemetry,
|
|
4770
4786
|
...(scoutInterrupted ? { interrupted: true } : {}),
|
|
4787
|
+
...continuity.terminalFacts(roResumable),
|
|
4771
4788
|
});
|
|
4772
4789
|
if (opts.deepScan) {
|
|
4773
4790
|
const warningNote = toolWarnings(telemetry).length
|
|
@@ -4929,7 +4946,7 @@ export class Orchestrator {
|
|
|
4929
4946
|
harnessId: a.harnessId,
|
|
4930
4947
|
status: a.status,
|
|
4931
4948
|
}));
|
|
4932
|
-
const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store);
|
|
4949
|
+
const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(attempts, "cancelled"));
|
|
4933
4950
|
if (input.signal?.aborted)
|
|
4934
4951
|
return cancelledTerminal();
|
|
4935
4952
|
const succeededReadonly = attempts.filter((a) => a.status === "success");
|
|
@@ -5006,6 +5023,7 @@ export class Orchestrator {
|
|
|
5006
5023
|
harness_id: terminalHarnessId,
|
|
5007
5024
|
error: singleError,
|
|
5008
5025
|
failure_ref: "final/failure.yaml",
|
|
5026
|
+
...resumableTerminal(store, paths, attempts, true),
|
|
5009
5027
|
});
|
|
5010
5028
|
}
|
|
5011
5029
|
else {
|
|
@@ -5017,6 +5035,7 @@ export class Orchestrator {
|
|
|
5017
5035
|
harness_id: terminalHarnessId,
|
|
5018
5036
|
error: singleError,
|
|
5019
5037
|
failure_ref: "final/failure.yaml",
|
|
5038
|
+
...resumableTerminal(store, paths, attempts, true),
|
|
5020
5039
|
});
|
|
5021
5040
|
}
|
|
5022
5041
|
return {
|
|
@@ -5090,6 +5109,7 @@ export class Orchestrator {
|
|
|
5090
5109
|
phase: scanBudgetMapping ? scanBudgetMapping.phase : "harness",
|
|
5091
5110
|
error: message,
|
|
5092
5111
|
failure_ref: "final/failure.yaml",
|
|
5112
|
+
...resumableTerminal(store, paths, attempts, true),
|
|
5093
5113
|
});
|
|
5094
5114
|
return {
|
|
5095
5115
|
spendUsd: ledger.spend(),
|
|
@@ -5116,8 +5136,9 @@ export class Orchestrator {
|
|
|
5116
5136
|
// whole decision + reducer spawn lives in deepScanReducer.ts (its owner).
|
|
5117
5137
|
let deepScanSynthesis = null;
|
|
5118
5138
|
let reducedReport = null;
|
|
5139
|
+
let answerMatches = succeeded.reduce((sum, a) => sum + (a.answerMatches ?? 0), 0);
|
|
5119
5140
|
if (opts.deepScan) {
|
|
5120
|
-
({ deepScanSynthesis, reducedReport } = await resolveDeepScanSynthesis(this.deepScanReducerDeps(input, contract, log), {
|
|
5141
|
+
({ deepScanSynthesis, reducedReport, answerMatches } = await resolveDeepScanSynthesis(this.deepScanReducerDeps(input, contract, log), {
|
|
5121
5142
|
succeeded,
|
|
5122
5143
|
adapters,
|
|
5123
5144
|
budgetStopped,
|
|
@@ -5209,7 +5230,8 @@ export class Orchestrator {
|
|
|
5209
5230
|
const harnessLabel = attempts
|
|
5210
5231
|
.map((a) => `${a.attemptId}:${a.harnessId}:${a.status}`)
|
|
5211
5232
|
.join(", ");
|
|
5212
|
-
|
|
5233
|
+
const secretLike = secretDiff.answerSecretLikeFinding(answerMatches);
|
|
5234
|
+
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harnesses: ${harnessLabel}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}${secretDiff.summaryDisclosure(secretLike)}\n\n${report}\n`);
|
|
5213
5235
|
const reportProducerAttemptId = opts.deepScan &&
|
|
5214
5236
|
deepScanSynthesis?.status === "succeeded" &&
|
|
5215
5237
|
deepScanSynthesis.reducer_attempt_id
|
|
@@ -5226,6 +5248,7 @@ export class Orchestrator {
|
|
|
5226
5248
|
mode: opts.mode,
|
|
5227
5249
|
intent: opts.intent,
|
|
5228
5250
|
read_only: true,
|
|
5251
|
+
...(secretLike ? { secret_like: secretLike } : {}),
|
|
5229
5252
|
},
|
|
5230
5253
|
});
|
|
5231
5254
|
log.emit("work_product.emitted", { kind: "report", winner: reportProducerAttemptId });
|
|
@@ -5247,6 +5270,7 @@ export class Orchestrator {
|
|
|
5247
5270
|
reason: terminalFacts.reason,
|
|
5248
5271
|
phase: "executor",
|
|
5249
5272
|
failure_ref: "final/failure.yaml",
|
|
5273
|
+
...resumableTerminal(store, paths, attempts, true),
|
|
5250
5274
|
});
|
|
5251
5275
|
}
|
|
5252
5276
|
else if (workVetoed) {
|
|
@@ -5254,6 +5278,7 @@ export class Orchestrator {
|
|
|
5254
5278
|
lifecycle: terminalFacts.lifecycle,
|
|
5255
5279
|
facts: terminalFacts,
|
|
5256
5280
|
reason: terminalFacts.reason,
|
|
5281
|
+
...resumableTerminal(store, paths, attempts, true),
|
|
5257
5282
|
});
|
|
5258
5283
|
}
|
|
5259
5284
|
else {
|
|
@@ -5261,6 +5286,7 @@ export class Orchestrator {
|
|
|
5261
5286
|
lifecycle: terminalFacts.lifecycle,
|
|
5262
5287
|
facts: terminalFacts,
|
|
5263
5288
|
reason: terminalFacts.reason,
|
|
5289
|
+
...resumableTerminal(store, paths, attempts),
|
|
5264
5290
|
});
|
|
5265
5291
|
}
|
|
5266
5292
|
return {
|