@claudexor/orchestrator 3.6.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-resolution.d.ts +3 -0
- package/dist/account-resolution.d.ts.map +1 -1
- package/dist/account-resolution.js +23 -0
- package/dist/account-resolution.js.map +1 -1
- package/dist/candidateEvidence.d.ts +3 -3
- package/dist/candidateEvidence.d.ts.map +1 -1
- package/dist/candidateOutputs.d.ts +2 -2
- package/dist/candidateOutputs.d.ts.map +1 -1
- package/dist/candidateOutputs.js +11 -6
- package/dist/candidateOutputs.js.map +1 -1
- package/dist/contract-gates.d.ts +4 -4
- package/dist/contract-gates.d.ts.map +1 -1
- package/dist/contract-gates.js +2 -0
- package/dist/contract-gates.js.map +1 -1
- package/dist/credential-profiles.d.ts +12 -1
- package/dist/credential-profiles.d.ts.map +1 -1
- package/dist/credential-profiles.js +13 -3
- package/dist/credential-profiles.js.map +1 -1
- package/dist/deepScanReducer.d.ts.map +1 -1
- package/dist/deepScanReducer.js +2 -1
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/delegatedHome.d.ts +21 -69
- package/dist/delegatedHome.d.ts.map +1 -1
- package/dist/delegatedHome.js +29 -85
- package/dist/delegatedHome.js.map +1 -1
- package/dist/modelGovernance.d.ts +35 -4
- package/dist/modelGovernance.d.ts.map +1 -1
- package/dist/modelGovernance.js +71 -27
- package/dist/modelGovernance.js.map +1 -1
- package/dist/orchestrator-credentials.d.ts.map +1 -1
- package/dist/orchestrator-credentials.js +7 -1
- package/dist/orchestrator-credentials.js.map +1 -1
- package/dist/orchestrator.d.ts +5 -6
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +210 -222
- package/dist/orchestrator.js.map +1 -1
- package/dist/planRun.d.ts +5 -5
- package/dist/planRun.d.ts.map +1 -1
- package/dist/planRun.js.map +1 -1
- package/dist/plannerAttempt.d.ts +2 -2
- package/dist/plannerAttempt.d.ts.map +1 -1
- package/dist/plannerAttempt.js +2 -1
- package/dist/plannerAttempt.js.map +1 -1
- package/dist/requestRequirements.d.ts +3 -15
- package/dist/requestRequirements.d.ts.map +1 -1
- package/dist/requestRequirements.js +11 -21
- package/dist/requestRequirements.js.map +1 -1
- package/dist/routeContext.d.ts +4 -1
- package/dist/routeContext.d.ts.map +1 -1
- package/dist/routeContext.js +8 -3
- package/dist/routeContext.js.map +1 -1
- package/dist/runAdmission.d.ts +29 -0
- package/dist/runAdmission.d.ts.map +1 -0
- package/dist/runAdmission.js +63 -0
- package/dist/runAdmission.js.map +1 -0
- package/dist/task-contract-builder.d.ts +2 -2
- package/dist/task-contract-builder.d.ts.map +1 -1
- package/dist/task-contract-builder.js +5 -6
- package/dist/task-contract-builder.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -5,7 +5,7 @@ import { accountPoolRows } from "./account-pool.js";
|
|
|
5
5
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
6
6
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
7
7
|
import { processAttemptUsage } from "./attemptUsage.js";
|
|
8
|
-
import { appliedAttemptFacts, assertDelegatedEvidence,
|
|
8
|
+
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, } from "./delegatedHome.js";
|
|
9
9
|
import * as AC from "./attemptUsageCost.js";
|
|
10
10
|
import { candidateRoster, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, unanimousDeclaredFailure, } from "./candidateEvidence.js";
|
|
11
11
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
@@ -13,10 +13,10 @@ import { policyFindings } from "./policyFindings.js";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { finalizePlanRun, runCouncilPlan, writePlanHarnessFailure, } from "./planRun.js";
|
|
15
15
|
import { runPlannerAttempt as executePlannerAttempt, } from "./plannerAttempt.js";
|
|
16
|
-
import { HarnessRunSpec, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts,
|
|
16
|
+
import { HarnessRunSpec, ModeKind as ModeKindSchema, QuotaSnapshot as QuotaSnapshotSchema, isBlocking, makeOutcomeFacts, strictifyOutputSchema, estimateEffectiveAuthRoute, } from "@claudexor/schema";
|
|
17
17
|
import { globalConfigDir, loadConfig } from "@claudexor/config";
|
|
18
|
-
import { acceptedTryOutput, AnswerAssembly, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
19
|
-
import { assertRouteModelsAllowed } from "./modelGovernance.js";
|
|
18
|
+
import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
|
|
19
|
+
import { assertRouteModelsAllowed, runModelGovernedRoute } from "./modelGovernance.js";
|
|
20
20
|
import { authModeForCredentialRoute, authModeForPreference } from "./auth-route-classification.js";
|
|
21
21
|
import { governRouteEffort } from "./effortGovernance.js";
|
|
22
22
|
import { isFullAccess, RequestRequirementsResolver } from "./requestRequirements.js";
|
|
@@ -32,7 +32,8 @@ import { arbitrationBudgetOptions, decisionBudgetSummary } from "./decisionBudge
|
|
|
32
32
|
import { buildRevisePrompt } from "./revisePrompt.js";
|
|
33
33
|
import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure, } from "./runTerminals.js";
|
|
34
34
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
35
|
-
import {
|
|
35
|
+
import { finalizeStructuredOutput } from "./structuredOutput.js";
|
|
36
|
+
import { admitRun } from "./runAdmission.js";
|
|
36
37
|
import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted, emitTransientRetryPlan, observeReadonlySpend, } from "./laneStreamEvents.js";
|
|
37
38
|
import { promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPrimaryDivergence, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
|
|
38
39
|
import { candidateStatusInRouteContext, resolveReadOnlyRouteContext, } from "./routeContext.js";
|
|
@@ -53,7 +54,7 @@ import { interactionChannelFor } from "./interaction.js";
|
|
|
53
54
|
import { gateSpecsFromContract, renderTestsEvidence } from "./contract-gates.js";
|
|
54
55
|
import { buildTaskContract } from "./task-contract-builder.js";
|
|
55
56
|
import { ArtifactStore } from "@claudexor/artifact-store";
|
|
56
|
-
import {
|
|
57
|
+
import { buildContextPack, rawContextForEnvelope, preflightEvidence, writeEvidencePacket, } from "@claudexor/context";
|
|
57
58
|
import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull, ensureClaudeBridge, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
|
|
58
59
|
import { blockedDecisionOverride, finalVerifyBlocks, finalVerifyPatch, verifyAndDeliver, } from "@claudexor/delivery";
|
|
59
60
|
import { HarnessGateway } from "@claudexor/gateway";
|
|
@@ -62,7 +63,6 @@ import { arbitrate } from "@claudexor/arbitration";
|
|
|
62
63
|
import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
|
|
63
64
|
import { attemptCostEvidence, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, reviewUsageCostSettlement, } from "@claudexor/budget";
|
|
64
65
|
import { readTextSafe, appendLine, assertNoInlineSecretValues, DELEGATION_ENV, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, sha256, userConfigDir, writeText, } from "@claudexor/util";
|
|
65
|
-
import { assertWriteIsolation } from "./write-isolation.js";
|
|
66
66
|
const LABELS = "ABCDEFGHIJ".split("");
|
|
67
67
|
const NO_PROJECT_ROOT = noProjectRepoRoot();
|
|
68
68
|
/** Concurrency cap for parallel candidates/explorers (locked decision: min(n, 4)). */
|
|
@@ -134,42 +134,11 @@ export class Orchestrator {
|
|
|
134
134
|
delegationParentRunId: runId,
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
denyPaths: resolved.denyPaths,
|
|
142
|
-
inPlace: resolved.inPlace,
|
|
143
|
-
repoRoot: resolved.repoRoot,
|
|
144
|
-
executionRoot: this.execRootOf(resolved),
|
|
137
|
+
resolved.outputSchema = admitRun(resolved, mode, {
|
|
138
|
+
accessDefault: this.config(resolved.repoRoot).trust.access_default,
|
|
139
|
+
projectProtectedPaths: () => this.projectConfig(resolved.repoRoot).constraints.protected_paths,
|
|
140
|
+
mandatoryFiles: () => this.projectConfig(resolved.repoRoot).context.mandatory_files,
|
|
145
141
|
});
|
|
146
|
-
// outputSchema constrains the run's final ANSWER. It is honored exactly
|
|
147
|
-
// where a final answer is delivered (agent race incl. synthesis, and ask);
|
|
148
|
-
// every other strategy refuses loudly rather than carrying a contract the
|
|
149
|
-
// engine would not validate (INV-023). The schema itself is normalized for
|
|
150
|
-
// the native structured-output routes here at the boundary — unsupported
|
|
151
|
-
// shapes (external/cyclic $ref, non-object root) are a typed refusal, not a mid-run 400.
|
|
152
|
-
if (resolved.outputSchema !== undefined && resolved.outputSchema !== null) {
|
|
153
|
-
if (mode !== "agent" && mode !== "ask") {
|
|
154
|
-
throw new Error(`outputSchema constrains the final answer and applies to agent/ask runs (got mode=${mode}); drop the schema or switch modes`);
|
|
155
|
-
}
|
|
156
|
-
if (resolved.untilClean || (resolved.attempts !== undefined && resolved.attempts !== null)) {
|
|
157
|
-
throw new Error("outputSchema is not supported with convergence flags (--until-clean/--attempts): convergence delivers a gated patch, not a structured answer; drop the schema or the convergence flags");
|
|
158
|
-
}
|
|
159
|
-
// Shape-refuse unsupported schemas, then PROVE it compiles under the same
|
|
160
|
-
// ajv the engine validator uses — a malformed schema is a preflight
|
|
161
|
-
// refusal here (before any run dir), never a mid-run validator crash. The
|
|
162
|
-
// contract keeps the ORIGINAL (conformance authority); local-ref inlining
|
|
163
|
-
// and strictification are transport-only transforms in harnessSpecKnobs.
|
|
164
|
-
resolved.outputSchema = normalizeUserOutputSchema(resolved.outputSchema);
|
|
165
|
-
assertOutputSchemaCompiles(resolved.outputSchema);
|
|
166
|
-
}
|
|
167
|
-
// P1: a versioned `mandatory_files` contract is enforced UNIFORMLY here, for
|
|
168
|
-
// every mode, so the same repo state can't pass `run`/`ask` while failing
|
|
169
|
-
// `audit`. No-op when the list is empty (the default) or for no-project runs.
|
|
170
|
-
if (resolved.repoRoot !== NO_PROJECT_ROOT) {
|
|
171
|
-
assertMandatoryContext(resolved.repoRoot, this.projectConfig(resolved.repoRoot).context.mandatory_files);
|
|
172
|
-
}
|
|
173
142
|
// Reviewer panels are validated only inside Agent strategies that actually
|
|
174
143
|
// review (race/convergence; Plan Council is the plan critique path) — AFTER run-dir
|
|
175
144
|
// creation, so a doomed explicit panel yields typed failure ARTIFACTS
|
|
@@ -491,8 +460,7 @@ export class Orchestrator {
|
|
|
491
460
|
const policy = input.web ?? input.externalContextPolicy ?? "auto";
|
|
492
461
|
const pool = [];
|
|
493
462
|
const dropped = [];
|
|
494
|
-
//
|
|
495
|
-
// drop is recorded with its typed STAGE so the disclosure preserves the
|
|
463
|
+
// Every auto-pool drop is recorded with its typed STAGE so the disclosure preserves the
|
|
496
464
|
// real cause instead of collapsing to one reason.
|
|
497
465
|
const droppedLanes = [];
|
|
498
466
|
// The ONE explicit-lane admission gate shared by every drop site (QA-043 /
|
|
@@ -501,14 +469,18 @@ export class Orchestrator {
|
|
|
501
469
|
// substitution or self-race duplication; an AUTO lane is dropped with a
|
|
502
470
|
// typed omission recorded for the degradation receipt.
|
|
503
471
|
const dropLane = (harnessId, stage, detail) => {
|
|
504
|
-
if (explicitPool)
|
|
472
|
+
if (explicitPool) {
|
|
473
|
+
if (stage === "access")
|
|
474
|
+
throw new AccessProfileIncompatibleError(detail);
|
|
505
475
|
throw new HarnessUnavailableError(detail);
|
|
476
|
+
}
|
|
506
477
|
dropped.push(detail);
|
|
507
478
|
droppedLanes.push({ harnessId, stage, detail });
|
|
508
479
|
};
|
|
509
480
|
// One observation set for the whole admission pass, so two lanes cannot be
|
|
510
481
|
// judged against different vendor epochs.
|
|
511
482
|
const vendorQuota = this.credentials.vendorQuotaObservations();
|
|
483
|
+
const liveUnusable = this.deps.credentialUnusable?.() ?? [];
|
|
512
484
|
for (const id of ids) {
|
|
513
485
|
const adapter = this.deps.registry.get(id);
|
|
514
486
|
if (!adapter) {
|
|
@@ -577,14 +549,8 @@ export class Orchestrator {
|
|
|
577
549
|
const profileAdapter = this.deps.registry.get(id);
|
|
578
550
|
const profileProbe = profileAdapter?.probeCredentialProfile?.bind(profileAdapter);
|
|
579
551
|
const explicitPin = this.credentials.effectiveProfileId(input, id);
|
|
580
|
-
|
|
581
|
-
//
|
|
582
|
-
// admission — the thread-bound row followed by the harness's enabled
|
|
583
|
-
// pool rows, consulted only when the default doctor did not already
|
|
584
|
-
// admit the lane. A signed-in registry row is invisible to the default
|
|
585
|
-
// doctor (cursor after the host-Keychain retirement, or a named-rows-
|
|
586
|
-
// only claude/codex install), so an unpinned run must be admitted on
|
|
587
|
-
// row readiness exactly the way explicit pins already are.
|
|
552
|
+
const model = input.models?.[id] ?? cfgEntry?.default_model ?? null;
|
|
553
|
+
// Pins and bound/pool rows are checked against their own readiness.
|
|
588
554
|
const rowCandidateIds = [];
|
|
589
555
|
if (explicitPin) {
|
|
590
556
|
rowCandidateIds.push(explicitPin);
|
|
@@ -603,9 +569,12 @@ export class Orchestrator {
|
|
|
603
569
|
probe: profileProbe,
|
|
604
570
|
// `verification: passed` from the local store only means a login file
|
|
605
571
|
// is present. The poller's authenticated vendor call is the only
|
|
606
|
-
// liveness evidence
|
|
607
|
-
// act on it — otherwise the run dispatches into a revoked token.
|
|
572
|
+
// liveness evidence; admission must act on it or dispatch may use a revoked token.
|
|
608
573
|
quota: vendorQuota,
|
|
574
|
+
unusable: liveUnusable,
|
|
575
|
+
model,
|
|
576
|
+
// Only an explicit/bound route may consume bounded stale LKG evidence.
|
|
577
|
+
allowStale: explicitPin !== null || input.threadAccountBindings?.[id] === candidateId,
|
|
609
578
|
});
|
|
610
579
|
if (verdict === "available") {
|
|
611
580
|
profileAdmitted = true;
|
|
@@ -645,7 +614,7 @@ export class Orchestrator {
|
|
|
645
614
|
const readOnlyIntent = intent === "plan" || intent === "spec" || intent === "explain" || intent === "audit";
|
|
646
615
|
const requiredAccess = this.requestRequirements.adapterAccess(intent, manifest.capabilities.implementation_transport, readOnlyIntent
|
|
647
616
|
? "readonly"
|
|
648
|
-
: (input.access ?? this.config(input.repoRoot).trust.access_default)
|
|
617
|
+
: (input.access ?? this.config(input.repoRoot).trust.access_default));
|
|
649
618
|
const accessSupported = !requiredAccess || manifest.access_profiles_supported.includes(requiredAccess);
|
|
650
619
|
const webSupport = manifest.capabilities.web_policy;
|
|
651
620
|
// Match routeSpecKnobs: a per-harness web default upgrades run-level auto,
|
|
@@ -679,6 +648,7 @@ export class Orchestrator {
|
|
|
679
648
|
harnessId: id,
|
|
680
649
|
requested: input.browser === true,
|
|
681
650
|
manifestCapable: manifest.capabilities.browser_tool,
|
|
651
|
+
requiresFullAccess: manifest.capability_profile.mcp_injection_requires_full_access,
|
|
682
652
|
webPolicy: routePolicy,
|
|
683
653
|
access: requiredAccess,
|
|
684
654
|
}),
|
|
@@ -730,6 +700,9 @@ export class Orchestrator {
|
|
|
730
700
|
}
|
|
731
701
|
}
|
|
732
702
|
if (pool.length === 0) {
|
|
703
|
+
if (droppedLanes.length > 0 && droppedLanes.every((lane) => lane.stage === "access")) {
|
|
704
|
+
throw new AccessProfileIncompatibleError(`no harness can enforce the requested access profile (${droppedLanes.map((lane) => lane.detail).join(", ")})`);
|
|
705
|
+
}
|
|
733
706
|
throw new HarnessUnavailableError(`no harness can perform '${intent}' for this mode${dropped.length ? ` (skipped: ${dropped.join(", ")})` : ""}`);
|
|
734
707
|
}
|
|
735
708
|
// Quota admission must use the account that will actually spawn. In
|
|
@@ -850,7 +823,7 @@ export class Orchestrator {
|
|
|
850
823
|
}
|
|
851
824
|
}
|
|
852
825
|
// Strict pre-run model gate (INV-104) — see modelGovernance.ts.
|
|
853
|
-
await assertRouteModelsAllowed(out, input.models, this.execRootOf(input));
|
|
826
|
+
await assertRouteModelsAllowed(out, input.models, this.execRootOf(input), (id) => routeContext ? (routeContext.envForHarness?.(id) ?? routeContext.env) : undefined);
|
|
854
827
|
return out;
|
|
855
828
|
}
|
|
856
829
|
/**
|
|
@@ -1382,8 +1355,8 @@ export class Orchestrator {
|
|
|
1382
1355
|
/** Decided by `harnessHomeFor` in the CALLER, so the per-attempt applied
|
|
1383
1356
|
* facts exist in the caller's catch too: an attempt that ran and then threw
|
|
1384
1357
|
* must record what it ran under, not just why it stopped. REQUIRED (no
|
|
1385
|
-
* default): a silent fallback here would spawn a delegated
|
|
1386
|
-
* operator's real home while the record still claimed
|
|
1358
|
+
* default): a silent fallback here would spawn a delegated attempt on the
|
|
1359
|
+
* operator's real home while the record still claimed scoped state. */
|
|
1387
1360
|
harnessHome) {
|
|
1388
1361
|
const adapter = routed.adapter;
|
|
1389
1362
|
const knobs = this.routeSpecKnobs(routed, contract, modelHint, effortHint);
|
|
@@ -1412,8 +1385,8 @@ export class Orchestrator {
|
|
|
1412
1385
|
// Engine-derived read-only prompt constraints: protected/auto-protected
|
|
1413
1386
|
// paths PLUS the exact typed gate argv the run will execute (QA-022 FIX B).
|
|
1414
1387
|
prompt: promptWithEngineConstraints(
|
|
1415
|
-
//
|
|
1416
|
-
[prompt,
|
|
1388
|
+
// The child is told that Claudexor adds no outer OS boundary (disclosure 2 of 3).
|
|
1389
|
+
[prompt, outerBoundaryNotice(harnessHome), laneContinuity?.pointerLine, continuationPointer]
|
|
1417
1390
|
.filter((s) => typeof s === "string" && s.length > 0)
|
|
1418
1391
|
.join("\n\n"), contract.constraints.protected_paths, contract.constraints.auto_protected_paths, contract.constraints.protected_path_approvals, contract.tests.commands),
|
|
1419
1392
|
attachments: runInput?.attachments ?? [],
|
|
@@ -1440,11 +1413,8 @@ export class Orchestrator {
|
|
|
1440
1413
|
// Scoped harness home for isolated envelopes AND for every delegated run;
|
|
1441
1414
|
// an ordinary in-place run keeps the native environment so the resumed
|
|
1442
1415
|
// vendor session is reachable. See scopedHarnessHome for the cost a
|
|
1443
|
-
// delegated in-place attempt pays for that
|
|
1416
|
+
// delegated in-place attempt pays for that scoped state.
|
|
1444
1417
|
...(harnessHome.env ? { env: harnessHome.env } : {}),
|
|
1445
|
-
// The OS boundary itself. The shared CLI run loop wraps the argv with it;
|
|
1446
|
-
// no adapter opts in, so no adapter can forget.
|
|
1447
|
-
confinement: harnessHome.confinement,
|
|
1448
1418
|
raw_context_packet: rawContextPacket,
|
|
1449
1419
|
stream_deltas: streamDeltas,
|
|
1450
1420
|
});
|
|
@@ -1523,7 +1493,7 @@ export class Orchestrator {
|
|
|
1523
1493
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
1524
1494
|
let rawPatch = null;
|
|
1525
1495
|
try {
|
|
1526
|
-
const watched = withInactivityWatchdog(
|
|
1496
|
+
const watched = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec), {
|
|
1527
1497
|
timeoutMs: inactivityMs,
|
|
1528
1498
|
countsAsProgress: countsAsAgentProgress,
|
|
1529
1499
|
onTimeout: () => {
|
|
@@ -1791,7 +1761,7 @@ export class Orchestrator {
|
|
|
1791
1761
|
worktreePath: envelope.worktree_path,
|
|
1792
1762
|
artifactRelativeDir,
|
|
1793
1763
|
diff,
|
|
1794
|
-
persistPatch: secretDiffRefusal === undefined,
|
|
1764
|
+
persistPatch: secretDiffRefusal === undefined && isMutatingAccess(access),
|
|
1795
1765
|
persistProducedMedia: secretDiffRefusal === undefined,
|
|
1796
1766
|
answerText,
|
|
1797
1767
|
record: {
|
|
@@ -1807,8 +1777,8 @@ export class Orchestrator {
|
|
|
1807
1777
|
...(secretDiffRefusal ? { secret_diff_refusal: secretDiffRefusal } : {}),
|
|
1808
1778
|
gates: gates.map((g) => ({ id: g.id, status: g.status })),
|
|
1809
1779
|
branch: envelope.branch_name,
|
|
1810
|
-
// Applied facts, not promises:
|
|
1811
|
-
//
|
|
1780
|
+
// Applied facts, not promises: historical proofs stay readable and
|
|
1781
|
+
// current delegated runs state deliberate outer-boundary absence.
|
|
1812
1782
|
// Built by the SAME shape the failure path writes.
|
|
1813
1783
|
...applied,
|
|
1814
1784
|
},
|
|
@@ -1888,6 +1858,7 @@ export class Orchestrator {
|
|
|
1888
1858
|
// announced: a refused run must fail the request loudly, not 200 a runId
|
|
1889
1859
|
// and leave an orphaned run dir without a terminal event.
|
|
1890
1860
|
const contract = this.buildContract(input, taskId, mode);
|
|
1861
|
+
const mutatingRun = isMutatingAccess(contract.access.effective_profile);
|
|
1891
1862
|
const planBrief = verifiedPlanBrief(input);
|
|
1892
1863
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
1893
1864
|
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
@@ -1918,28 +1889,32 @@ export class Orchestrator {
|
|
|
1918
1889
|
// directory, a filesystem root, or one that cannot be classified), which
|
|
1919
1890
|
// gets a typed refusal BEFORE any mutation (INV-075). For an isolated
|
|
1920
1891
|
// thread the execution root is already a git worktree: a no-op there.
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1892
|
+
if (mutatingRun) {
|
|
1893
|
+
const gitPreconditionError = await ensureWriteModeGitBoundary(execRoot, log, store, paths, runId, mode);
|
|
1894
|
+
if (gitPreconditionError) {
|
|
1895
|
+
return {
|
|
1896
|
+
runId,
|
|
1897
|
+
taskId,
|
|
1898
|
+
mode,
|
|
1899
|
+
lifecycle: "failed",
|
|
1900
|
+
facts: makeOutcomeFacts("failed", { reason: gitPreconditionError.reason }),
|
|
1901
|
+
winner: null,
|
|
1902
|
+
runDir: paths.root,
|
|
1903
|
+
summary: gitPreconditionError.message,
|
|
1904
|
+
candidates: [],
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1934
1907
|
}
|
|
1935
1908
|
// Same run-prep stage as the git boundary: if the PROJECT root uses AGENTS.md
|
|
1936
1909
|
// with no CLAUDE.md, bridge it so a Claude Code candidate reads it (INV-113).
|
|
1937
|
-
|
|
1910
|
+
if (mutatingRun) {
|
|
1911
|
+
this.ensureClaudeBridgeForRun(input.repoRoot, input.inPlace === true, log);
|
|
1912
|
+
}
|
|
1938
1913
|
// Pre-turn snapshot of the live tree for in-place runs: the revert restore
|
|
1939
1914
|
// target (server-owned revertInPlace). A snapshot failure must never fail the
|
|
1940
1915
|
// run — revert is simply unavailable then.
|
|
1941
1916
|
let preTurnSha = null;
|
|
1942
|
-
if (input.inPlace === true) {
|
|
1917
|
+
if (mutatingRun && input.inPlace === true) {
|
|
1943
1918
|
try {
|
|
1944
1919
|
preTurnSha = await snapshotTree(execRoot);
|
|
1945
1920
|
}
|
|
@@ -2109,9 +2084,10 @@ export class Orchestrator {
|
|
|
2109
2084
|
accessProfile: candidateAccess,
|
|
2110
2085
|
// Direct-workspace singletons run in place. Races and patch-envelope
|
|
2111
2086
|
// transports stay isolated and adopt through the delivery service.
|
|
2112
|
-
inPlace:
|
|
2113
|
-
|
|
2114
|
-
|
|
2087
|
+
inPlace: !mutatingRun ||
|
|
2088
|
+
(input.inPlace === true &&
|
|
2089
|
+
requestedSingleCandidate &&
|
|
2090
|
+
slot.routed.implementationTransport !== "git_patch_envelope"),
|
|
2115
2091
|
});
|
|
2116
2092
|
harnessHome = this.harnessHomeFor(wsm, envelope, slot.routed, input);
|
|
2117
2093
|
const run = await this.runCandidateInEnvelope(slot.routed, envelope, slot.attemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, candidateAccess, (ev) => {
|
|
@@ -2287,8 +2263,8 @@ export class Orchestrator {
|
|
|
2287
2263
|
};
|
|
2288
2264
|
await runBounded(slots, Math.min(slots.length, MAX_PARALLEL_CANDIDATES), runSlot);
|
|
2289
2265
|
const runs = runsBySlot.filter((r) => r !== undefined);
|
|
2290
|
-
// Fail-closed terminal: a delegated mutating run whose attempts
|
|
2291
|
-
//
|
|
2266
|
+
// Fail-closed terminal: a delegated mutating run whose attempts state
|
|
2267
|
+
// neither historical proof nor deliberate absence refuses instead of passing.
|
|
2292
2268
|
assertDelegatedEvidence(input.delegated === true, candidateAccess, runs);
|
|
2293
2269
|
const cancelledCandidates = () => runs.map((r) => ({
|
|
2294
2270
|
attemptId: r.attemptId,
|
|
@@ -2306,7 +2282,10 @@ export class Orchestrator {
|
|
|
2306
2282
|
// arbitration (as the race-adoption path does) would fold those user edits
|
|
2307
2283
|
// into the revert target and let a later revert clobber them.
|
|
2308
2284
|
let earlyPostTurnSha = null;
|
|
2309
|
-
if (
|
|
2285
|
+
if (mutatingRun &&
|
|
2286
|
+
input.inPlace &&
|
|
2287
|
+
requestedSingleCandidate &&
|
|
2288
|
+
runs.every((run) => !run.secretDiffRefusal)) {
|
|
2310
2289
|
try {
|
|
2311
2290
|
earlyPostTurnSha = await snapshotTree(execRoot);
|
|
2312
2291
|
}
|
|
@@ -2322,13 +2301,15 @@ export class Orchestrator {
|
|
|
2322
2301
|
if (failedDelegation) {
|
|
2323
2302
|
const failure = delegateFailure.candidateFailureTerminal(failedDelegation, "race");
|
|
2324
2303
|
await disposeReviewEnvelopes();
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2304
|
+
if (mutatingRun) {
|
|
2305
|
+
await delegateFailure.persistFailedInPlaceWorkProduct({
|
|
2306
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode },
|
|
2307
|
+
live: input.inPlace === true && failedDelegation.reviewCwd === execRoot,
|
|
2308
|
+
run: failedDelegation,
|
|
2309
|
+
postTurnSha: earlyPostTurnSha,
|
|
2310
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2332
2313
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null);
|
|
2333
2314
|
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), failure.metadata);
|
|
2334
2315
|
}
|
|
@@ -2510,7 +2491,13 @@ export class Orchestrator {
|
|
|
2510
2491
|
return cancelledRaceResult();
|
|
2511
2492
|
}
|
|
2512
2493
|
// Synthesis: if worthwhile, run a synthesizer as a NEW, re-checked candidate.
|
|
2513
|
-
const synth =
|
|
2494
|
+
const synth = mutatingRun
|
|
2495
|
+
? decideSynthesis(evidences, input.synthesis ?? "auto")
|
|
2496
|
+
: {
|
|
2497
|
+
synthesize: false,
|
|
2498
|
+
reason: "readonly access has no write-backed synthesis lifecycle",
|
|
2499
|
+
sources: evidences.map((evidence) => evidence.attemptId),
|
|
2500
|
+
};
|
|
2514
2501
|
store.writeYaml(join(paths.arbitrationDir, "synthesis.yaml"), synth);
|
|
2515
2502
|
log.emit("synthesis.started", { synthesize: synth.synthesize, reason: synth.reason });
|
|
2516
2503
|
if (synth.synthesize && !budgetStopped) {
|
|
@@ -2639,7 +2626,8 @@ export class Orchestrator {
|
|
|
2639
2626
|
// instead defers verification until immediately before delivery.
|
|
2640
2627
|
const inPlaceWinner = winnerRun?.reviewCwd === execRoot;
|
|
2641
2628
|
const deferredRaceVerify = input.inPlace === true && !inPlaceWinner;
|
|
2642
|
-
if (
|
|
2629
|
+
if (mutatingRun &&
|
|
2630
|
+
winnerRun &&
|
|
2643
2631
|
!inPlaceWinner &&
|
|
2644
2632
|
!deferredRaceVerify &&
|
|
2645
2633
|
winnerRun.diff.trim().length > 0 &&
|
|
@@ -2674,17 +2662,7 @@ export class Orchestrator {
|
|
|
2674
2662
|
})) {
|
|
2675
2663
|
log.emit("output.ready", { kind: "artifact", path });
|
|
2676
2664
|
}
|
|
2677
|
-
secretDiff.assertNoSecretLikeTokens("final patch diff", winnerRun.diff);
|
|
2678
|
-
const patchSha256 = sha256(winnerRun.diff);
|
|
2679
|
-
store.writeText(join(paths.finalDir, "patch.diff"), winnerRun.diff);
|
|
2680
|
-
const wstats = diffStats(winnerRun.diff);
|
|
2681
|
-
const hasDiff = winnerRun.diff.trim().length > 0;
|
|
2682
|
-
const blockers = winnerEvidence
|
|
2683
|
-
? winnerEvidence.findings.filter((f) => isBlocking(f)).length
|
|
2684
|
-
: 0;
|
|
2685
|
-
// Prose from an empty-diff winner is an answer, never a patch.
|
|
2686
2665
|
const winnerAnswer = winnerRun.answerText ?? "";
|
|
2687
|
-
const resultKind = hasDiff ? "patch" : winnerAnswer.length > 0 ? "answer" : "none";
|
|
2688
2666
|
// The winner's final MESSAGE is the human-facing answer and materializes
|
|
2689
2667
|
// for diff-ful runs too: the chat renders final/answer.md (the projection
|
|
2690
2668
|
// prefers it), never the arbitration summary — "Run … Winner: a01 …" is
|
|
@@ -2705,107 +2683,120 @@ export class Orchestrator {
|
|
|
2705
2683
|
answerText: winnerAnswer,
|
|
2706
2684
|
});
|
|
2707
2685
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
}, log);
|
|
2731
|
-
raceDeliveryReceipt = applied;
|
|
2732
|
-
store.writeYaml(join(paths.finalDir, "delivery_receipt.yaml"), applied);
|
|
2733
|
-
finalVerify = applied.finalVerify;
|
|
2734
|
-
if (applied.applied) {
|
|
2686
|
+
if (mutatingRun) {
|
|
2687
|
+
secretDiff.assertNoSecretLikeTokens("final patch diff", winnerRun.diff);
|
|
2688
|
+
const patchSha256 = sha256(winnerRun.diff);
|
|
2689
|
+
store.writeText(join(paths.finalDir, "patch.diff"), winnerRun.diff);
|
|
2690
|
+
const wstats = diffStats(winnerRun.diff);
|
|
2691
|
+
const hasDiff = winnerRun.diff.trim().length > 0;
|
|
2692
|
+
const blockers = winnerEvidence
|
|
2693
|
+
? winnerEvidence.findings.filter((f) => isBlocking(f)).length
|
|
2694
|
+
: 0;
|
|
2695
|
+
const resultKind = hasDiff ? "patch" : winnerAnswer.length > 0 ? "answer" : "none";
|
|
2696
|
+
// Only a fully verified, applyable success may auto-adopt; a not-verified
|
|
2697
|
+
// or needs-decision terminal remains an inspectable artifact.
|
|
2698
|
+
const adoptable = facts.lifecycle === "succeeded" &&
|
|
2699
|
+
facts.review === "approved" &&
|
|
2700
|
+
facts.checks !== "failed";
|
|
2701
|
+
let adopted = null;
|
|
2702
|
+
let applyState = "not_applied";
|
|
2703
|
+
let postTurnSha = null;
|
|
2704
|
+
let revertAnchorId = null;
|
|
2705
|
+
if (input.inPlace === true && hasDiff) {
|
|
2706
|
+
if (inPlaceWinner) {
|
|
2707
|
+
// Already live: the candidate ran in-place and wrote the tree itself.
|
|
2735
2708
|
adopted = true;
|
|
2736
|
-
applyState = "applied";
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2709
|
+
applyState = adoptable ? "applied" : "applied_review_blocked";
|
|
2710
|
+
// The pre-review fence excludes later user edits from the target.
|
|
2711
|
+
postTurnSha = earlyPostTurnSha;
|
|
2712
|
+
}
|
|
2713
|
+
else if (adoptable) {
|
|
2714
|
+
// Protected apply preserves the live tree or reports tree_mutated.
|
|
2715
|
+
const applied = await verifyAndDeliver(execRoot, winnerRun.diff, { mode: "apply", protectedApply: true }, gateSpecsFromContract(contract), (freshVerify) => {
|
|
2716
|
+
finalVerify = freshVerify;
|
|
2717
|
+
return finalVerifyBlocks(freshVerify)
|
|
2718
|
+
? (freshVerify.reason ?? "final verify failed before race adoption")
|
|
2719
|
+
: null;
|
|
2720
|
+
}, log);
|
|
2721
|
+
raceDeliveryReceipt = applied;
|
|
2722
|
+
store.writeYaml(join(paths.finalDir, "delivery_receipt.yaml"), applied);
|
|
2723
|
+
finalVerify = applied.finalVerify;
|
|
2724
|
+
if (applied.applied) {
|
|
2725
|
+
adopted = true;
|
|
2726
|
+
applyState = "applied";
|
|
2727
|
+
log.emit("work_product.adopted", {
|
|
2728
|
+
applied: true,
|
|
2729
|
+
patch_sha256: patchSha256,
|
|
2730
|
+
winner: winnerRun.attemptId,
|
|
2731
|
+
});
|
|
2732
|
+
try {
|
|
2733
|
+
postTurnSha = await snapshotTree(execRoot);
|
|
2734
|
+
}
|
|
2735
|
+
catch {
|
|
2736
|
+
postTurnSha = null;
|
|
2737
|
+
}
|
|
2738
|
+
revertAnchorId = createRevertAnchorFromPatchOrNull(execRoot, winnerRun.diff);
|
|
2744
2739
|
}
|
|
2745
|
-
|
|
2746
|
-
|
|
2740
|
+
else {
|
|
2741
|
+
adopted = false;
|
|
2742
|
+
applyState = "not_applied";
|
|
2743
|
+
deliveryFailureReason = applied.detail ?? "race adoption delivery was refused";
|
|
2744
|
+
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
2745
|
+
if (finalVerifyBlocks(finalVerify))
|
|
2746
|
+
finalVerifyFailed = true;
|
|
2747
|
+
log.emit("work_product.adopted", {
|
|
2748
|
+
applied: false,
|
|
2749
|
+
patch_sha256: patchSha256,
|
|
2750
|
+
detail: redactSecrets(applied.detail ?? "apply failed"),
|
|
2751
|
+
tree_mutated: applied.treeMutated,
|
|
2752
|
+
});
|
|
2747
2753
|
}
|
|
2748
|
-
revertAnchorId = createRevertAnchorFromPatchOrNull(execRoot, winnerRun.diff);
|
|
2749
|
-
}
|
|
2750
|
-
else {
|
|
2751
|
-
adopted = false;
|
|
2752
|
-
applyState = "not_applied";
|
|
2753
|
-
deliveryFailureReason = applied.detail ?? "race adoption delivery was refused";
|
|
2754
|
-
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
2755
|
-
if (finalVerifyBlocks(finalVerify))
|
|
2756
|
-
finalVerifyFailed = true;
|
|
2757
|
-
log.emit("work_product.adopted", {
|
|
2758
|
-
applied: false,
|
|
2759
|
-
patch_sha256: patchSha256,
|
|
2760
|
-
detail: redactSecrets(applied.detail ?? "apply failed"),
|
|
2761
|
-
tree_mutated: applied.treeMutated,
|
|
2762
|
-
});
|
|
2763
2754
|
}
|
|
2764
2755
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
: {
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2756
|
+
writeRaceDeliveryDecision(store, decisionPath, {
|
|
2757
|
+
decision: result.decision,
|
|
2758
|
+
facts,
|
|
2759
|
+
reviewVerified: actualReviewVerified,
|
|
2760
|
+
finalVerify,
|
|
2761
|
+
deliveryFailureReason,
|
|
2762
|
+
deliveryReceiptPath: raceDeliveryReceipt ? "final/delivery_receipt.yaml" : null,
|
|
2763
|
+
});
|
|
2764
|
+
if (inPlaceWinner && requestedSingleCandidate && adopted === true) {
|
|
2765
|
+
revertAnchorId = await createRevertAnchorOrNull(execRoot, preTurnSha, postTurnSha);
|
|
2766
|
+
}
|
|
2767
|
+
store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
|
|
2768
|
+
id: newId("wp"),
|
|
2769
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
2770
|
+
source_task_id: taskId,
|
|
2771
|
+
producer_attempt_id: winnerRun.attemptId,
|
|
2772
|
+
...(raceDeliveryReceipt
|
|
2773
|
+
? { files: { delivery_receipt: "final/delivery_receipt.yaml" } }
|
|
2774
|
+
: {}),
|
|
2775
|
+
meta: {
|
|
2776
|
+
harness_id: winnerRun.harnessId,
|
|
2777
|
+
synthesis: synth,
|
|
2778
|
+
mode,
|
|
2779
|
+
// Artifact-only apply reads the same terminal axes as the daemon (D8).
|
|
2780
|
+
lifecycle: facts.lifecycle,
|
|
2781
|
+
outcome_facts: facts,
|
|
2782
|
+
review_verified: actualReviewVerified,
|
|
2783
|
+
budget_stopped: budgetStopped,
|
|
2784
|
+
patch_sha256: patchSha256,
|
|
2785
|
+
result_kind: resultKind,
|
|
2786
|
+
diffstat: {
|
|
2787
|
+
files: wstats.paths.length,
|
|
2788
|
+
additions: wstats.additions,
|
|
2789
|
+
deletions: wstats.deletions,
|
|
2790
|
+
},
|
|
2791
|
+
blockers,
|
|
2792
|
+
adopted,
|
|
2793
|
+
apply_state: applyState,
|
|
2794
|
+
pre_turn_sha: preTurnSha,
|
|
2795
|
+
post_turn_sha: postTurnSha,
|
|
2796
|
+
revert_anchor_id: revertAnchorId,
|
|
2800
2797
|
},
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
apply_state: applyState,
|
|
2804
|
-
pre_turn_sha: preTurnSha,
|
|
2805
|
-
post_turn_sha: postTurnSha,
|
|
2806
|
-
revert_anchor_id: revertAnchorId,
|
|
2807
|
-
},
|
|
2808
|
-
});
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2809
2800
|
store.writeText(join(paths.finalDir, "summary.md"), renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified));
|
|
2810
2801
|
// summary.md is a DIAGNOSTIC artifact only (V8/PLAN addendum 2): it no
|
|
2811
2802
|
// longer carries primary-output authority. A clean applyable success or a
|
|
@@ -2906,7 +2897,9 @@ export class Orchestrator {
|
|
|
2906
2897
|
});
|
|
2907
2898
|
}
|
|
2908
2899
|
}
|
|
2909
|
-
|
|
2900
|
+
if (mutatingRun && winnerRun) {
|
|
2901
|
+
log.emit("work_product.emitted", { winner: result.decision.winner });
|
|
2902
|
+
}
|
|
2910
2903
|
if (!isFailureTerminal) {
|
|
2911
2904
|
log.emit("run.completed", { lifecycle: facts.lifecycle, facts, reason: facts.reason });
|
|
2912
2905
|
}
|
|
@@ -4032,15 +4025,14 @@ export class Orchestrator {
|
|
|
4032
4025
|
store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
|
|
4033
4026
|
log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
|
|
4034
4027
|
// W3.3: ONE resolved read-only context — the routing point-probe and every
|
|
4035
|
-
// planner spawn consume the SAME scoped env (see routeContext.ts).
|
|
4036
|
-
// probe home stays a disposable throwaway even for a thread lane turn (auth
|
|
4037
|
-
// truth is home-independent); only the planner spawn swaps in the durable
|
|
4038
|
-
// per-lane home below so its recorded native session survives.
|
|
4039
|
-
const roHome = resolveReadOnlyRouteContext(this.execRootOf(input));
|
|
4028
|
+
// planner spawn consume the SAME scoped env (see routeContext.ts).
|
|
4040
4029
|
// A thread PLAN turn is a chat turn (INV-034): plan candidates are distinct
|
|
4041
|
-
// harnesses run sequentially,
|
|
4042
|
-
//
|
|
4030
|
+
// harnesses run sequentially. Doctor, model inventory, and spawn share each
|
|
4031
|
+
// lane's durable HOME; the disposable context remains the non-thread fallback.
|
|
4043
4032
|
const laneRun = Boolean(input.threadId);
|
|
4033
|
+
const roHome = resolveReadOnlyRouteContext(this.execRootOf(input), laneRun
|
|
4034
|
+
? (id) => this.laneHomeEnvFor(input, id, input.credentialProfileId ?? null)
|
|
4035
|
+
: undefined);
|
|
4044
4036
|
let adapters;
|
|
4045
4037
|
try {
|
|
4046
4038
|
adapters = await this.resolveCandidateAdapters({ ...input, n: undefined }, "plan", ledger, log, roHome, runId);
|
|
@@ -4401,17 +4393,13 @@ export class Orchestrator {
|
|
|
4401
4393
|
: Math.min(Math.max(input.n ?? 2, 1), 3);
|
|
4402
4394
|
// W3.3: ONE resolved read-only context — the routing point-probe and every
|
|
4403
4395
|
// read-only attempt spawn consume the SAME scoped env (see routeContext.ts).
|
|
4404
|
-
// The point-probe home is a disposable throwaway even for a thread lane
|
|
4405
|
-
// turn: readiness auth truth is home-INDEPENDENT (credentials come from the
|
|
4406
|
-
// profile/keychain/default store, never the scoped home), so the probe and
|
|
4407
|
-
// the run share the same auth source; only the ACTUAL spawn swaps in the
|
|
4408
|
-
// durable per-lane home below so the recorded native session survives.
|
|
4409
|
-
const roHome = resolveReadOnlyRouteContext(this.execRootOf(input));
|
|
4410
4396
|
// A thread ASK turn is a chat turn: its native session is recorded per lane
|
|
4411
|
-
// and the next lane turn resumes it (INV-034).
|
|
4412
|
-
//
|
|
4413
|
-
// NOT lane chat turns — they keep the disposable home and record nothing.
|
|
4397
|
+
// and the next lane turn resumes it (INV-034). Its doctor/model/spawn path
|
|
4398
|
+
// shares that lane HOME; deep-scan and one-shot asks keep the disposable one.
|
|
4414
4399
|
const laneRun = Boolean(input.threadId) && opts.mode === "ask" && !opts.deepScan;
|
|
4400
|
+
const roHome = resolveReadOnlyRouteContext(this.execRootOf(input), laneRun
|
|
4401
|
+
? (id) => this.laneHomeEnvFor(input, id, input.credentialProfileId ?? null)
|
|
4402
|
+
: undefined);
|
|
4415
4403
|
let adapters;
|
|
4416
4404
|
try {
|
|
4417
4405
|
adapters = await this.resolveCandidateAdapters({ ...input, prompt, n: width }, opts.intent, ledger, log, roHome, runId,
|
|
@@ -4707,7 +4695,7 @@ export class Orchestrator {
|
|
|
4707
4695
|
...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
|
|
4708
4696
|
});
|
|
4709
4697
|
try {
|
|
4710
|
-
const watchedReport = withInactivityWatchdog(
|
|
4698
|
+
const watchedReport = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec), {
|
|
4711
4699
|
timeoutMs: harnessInactivityTimeoutMs(this.config(input.repoRoot)),
|
|
4712
4700
|
countsAsProgress: countsAsAgentProgress,
|
|
4713
4701
|
onTimeout: () => {
|