rivet-design 0.14.9 → 0.14.10
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/agent-variants/WorkerExecutorService.d.ts +64 -9
- package/dist/agent-variants/WorkerExecutorService.d.ts.map +1 -1
- package/dist/agent-variants/WorkerExecutorService.js +167 -104
- package/dist/agent-variants/WorkerExecutorService.js.map +1 -1
- package/dist/agent-variants/directionAuthoring.d.ts.map +1 -1
- package/dist/agent-variants/directionAuthoring.js +12 -0
- package/dist/agent-variants/directionAuthoring.js.map +1 -1
- package/dist/agent-variants/workerRuntime.d.ts +216 -0
- package/dist/agent-variants/workerRuntime.d.ts.map +1 -0
- package/dist/agent-variants/workerRuntime.js +652 -0
- package/dist/agent-variants/workerRuntime.js.map +1 -0
- package/dist/services/ConfigManager.d.ts +18 -0
- package/dist/services/ConfigManager.d.ts.map +1 -1
- package/dist/services/ConfigManager.js +15 -0
- package/dist/services/ConfigManager.js.map +1 -1
- package/dist/services/VariantGenerationService.d.ts +5 -0
- package/dist/services/VariantGenerationService.d.ts.map +1 -1
- package/dist/services/VariantGenerationService.js +36 -11
- package/dist/services/VariantGenerationService.js.map +1 -1
- package/dist/services/VariantRunService.d.ts.map +1 -1
- package/dist/services/VariantRunService.js +13 -3
- package/dist/services/VariantRunService.js.map +1 -1
- package/dist/services/VariantsRuntime.d.ts.map +1 -1
- package/dist/services/VariantsRuntime.js +6 -0
- package/dist/services/VariantsRuntime.js.map +1 -1
- package/dist/utils/hostAgentEnv.d.ts +19 -0
- package/dist/utils/hostAgentEnv.d.ts.map +1 -0
- package/dist/utils/hostAgentEnv.js +32 -0
- package/dist/utils/hostAgentEnv.js.map +1 -0
- package/package.json +1 -1
- package/src/ui/dist/assets/{main-CQho_sDZ.js → main-QevbGOvt.js} +2 -2
- package/src/ui/dist/index.html +1 -1
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { type WorkerEffortLevel } from '../hosts/workerDefaults';
|
|
3
3
|
import { type HostVariantWorkItemSnapshot } from './contracts';
|
|
4
|
+
import { type PreflightSpawn, type WorkerInvocationAdapter, type WorkerRuntimeDecision } from './workerRuntime';
|
|
5
|
+
import type { RunnerVendor } from '../cli/runnerSelection';
|
|
6
|
+
import type { WorkItemInitiator } from '../types/change-request-types';
|
|
4
7
|
/** Lease owner marking work the executor runs — hosts must not implement it. */
|
|
5
8
|
export declare const EXECUTOR_LEASE_OWNER = "rivet-executor";
|
|
9
|
+
/**
|
|
10
|
+
* The awaited lease-owner decision for one request: which owner to lease work
|
|
11
|
+
* to, the preflighted runtime to pass into `run()` when the executor owns it,
|
|
12
|
+
* and a one-line summary for run feedback / server.log (`workers: codex` /
|
|
13
|
+
* `workers: host path (claude: probe exited 1: …)`). The runtime choice must
|
|
14
|
+
* never be a surprise, and vendors never switch after leasing.
|
|
15
|
+
*/
|
|
16
|
+
export type WorkerLeaseChoice = {
|
|
17
|
+
leaseOwner: typeof EXECUTOR_LEASE_OWNER | 'rivet-host-agent';
|
|
18
|
+
runtime?: WorkerInvocationAdapter;
|
|
19
|
+
summary: string;
|
|
20
|
+
};
|
|
21
|
+
export declare const chooseWorkerLeaseOwner: (executor: WorkerExecutorService | undefined, args?: {
|
|
22
|
+
initiator?: WorkItemInitiator;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}) => Promise<WorkerLeaseChoice>;
|
|
6
25
|
/**
|
|
7
26
|
* The executor is the DEFAULT execution path for variant workers: leaner,
|
|
8
27
|
* cheaper, and deterministic vs host-agent subagents (see PR #948 evidence).
|
|
@@ -68,6 +87,8 @@ export type WorkerSpawn = (args: {
|
|
|
68
87
|
args: string[];
|
|
69
88
|
cwd: string;
|
|
70
89
|
prompt: string;
|
|
90
|
+
/** Vendor-shaped environment (e.g. claude strips nested-session markers). */
|
|
91
|
+
env?: NodeJS.ProcessEnv;
|
|
71
92
|
}) => ReturnType<typeof spawn>;
|
|
72
93
|
export type WorkerExecutorOptions = {
|
|
73
94
|
orchestrator: ExecutorOrchestrator;
|
|
@@ -86,6 +107,14 @@ export type WorkerExecutorOptions = {
|
|
|
86
107
|
timeoutMs?: number;
|
|
87
108
|
maxConcurrent?: number;
|
|
88
109
|
spawnWorker?: WorkerSpawn;
|
|
110
|
+
/** Lazily-read user config hooks for vendor resolution (see
|
|
111
|
+
* WorkerRuntimeResolver): `workerVendor`, `workerVendorFallback`,
|
|
112
|
+
* `workerModels.<vendor>` from ~/.rivet/config.json. */
|
|
113
|
+
configuredWorkerVendor?: () => string | undefined;
|
|
114
|
+
workerVendorFallbackEnabled?: () => boolean;
|
|
115
|
+
configuredWorkerModelFor?: (vendor: RunnerVendor) => string | undefined;
|
|
116
|
+
preflightTimeoutMs?: number;
|
|
117
|
+
spawnProbe?: PreflightSpawn;
|
|
89
118
|
};
|
|
90
119
|
/**
|
|
91
120
|
* M0 worker executor: runs artifact-backed implementation work items as
|
|
@@ -113,11 +142,29 @@ export declare class WorkerExecutorService {
|
|
|
113
142
|
private readonly spawnWorker;
|
|
114
143
|
private active;
|
|
115
144
|
private readonly waiters;
|
|
145
|
+
/** Per-request vendor resolution + preflight (multi-vendor workers). */
|
|
146
|
+
private readonly resolver;
|
|
147
|
+
/** Claude adapter over the legacy workerCommand — what runs when a caller
|
|
148
|
+
* invokes `run()` without a resolved runtime (tests, legacy paths). */
|
|
149
|
+
private readonly defaultAdapter;
|
|
150
|
+
private readonly configuredWorkerModelFor?;
|
|
116
151
|
constructor(options: WorkerExecutorOptions);
|
|
117
152
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
153
|
+
* Resolve + preflight the worker runtime for one request. Awaited by the
|
|
154
|
+
* generation services BEFORE they choose a lease owner: `executor` means
|
|
155
|
+
* lease to `rivet-executor` and pass `decision.adapter` into `run()`;
|
|
156
|
+
* `host` means lease to the host agent (the pre-executor degraded mode)
|
|
157
|
+
* and surface `decision.diagnostics`. Vendors never switch after leasing.
|
|
158
|
+
*/
|
|
159
|
+
resolveRuntime(args?: {
|
|
160
|
+
initiator?: WorkItemInitiator;
|
|
161
|
+
signal?: AbortSignal;
|
|
162
|
+
}): Promise<WorkerRuntimeDecision>;
|
|
163
|
+
/**
|
|
164
|
+
* True when the legacy claude worker binary is on PATH and answers
|
|
165
|
+
* `--version`. Superseded by {@link resolveRuntime} for lease-owner
|
|
166
|
+
* decisions — a `--version` probe passes on a binary whose auth is dead
|
|
167
|
+
* (the July 2026 Bedrock incident). Kept for cheap diagnostics only.
|
|
121
168
|
*/
|
|
122
169
|
isRuntimeAvailable(): boolean;
|
|
123
170
|
/** Kill every in-flight worker for a session (cancel/teardown). */
|
|
@@ -134,17 +181,25 @@ export declare class WorkerExecutorService {
|
|
|
134
181
|
id: string;
|
|
135
182
|
attempt: number;
|
|
136
183
|
}>;
|
|
137
|
-
}): Promise<void>;
|
|
184
|
+
}, runtime?: WorkerInvocationAdapter): Promise<void>;
|
|
138
185
|
/** reportComplete that can never strand a lease: a throw on a succeeded
|
|
139
186
|
* report (e.g. invalid artifact) is converted into a failed report so the
|
|
140
187
|
* item reaches a terminal state either way. */
|
|
141
188
|
private reportSafely;
|
|
142
|
-
/**
|
|
189
|
+
/** Worker tier for one work item, per vendor.
|
|
190
|
+
*
|
|
191
|
+
* Claude keeps the full legacy precedence: explicit ops overrides
|
|
192
|
+
* (constructor options / RIVET_EXECUTOR_* env) > the user's
|
|
193
|
+
* `workerModels.claude` config > the run's user-selected fidelity > lane
|
|
194
|
+
* defaults; reference-backed creates keep their own override lane because
|
|
195
|
+
* reference fidelity is the deliverable there.
|
|
143
196
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
197
|
+
* Codex/cursor read the executor tier registry (fidelity → reasoning
|
|
198
|
+
* effort for codex; deliberately fidelity-invariant for cursor), with the
|
|
199
|
+
* user's `workerModels.<vendor>` config and the blunt
|
|
200
|
+
* RIVET_EXECUTOR_MODEL env override applying to the active vendor. Their
|
|
201
|
+
* reference-create lane pins the high tier until benchmarked (mid-tier
|
|
202
|
+
* workers regressed reference-backed creates — see workerDefaults). */
|
|
148
203
|
private workerTierFor;
|
|
149
204
|
private runOne;
|
|
150
205
|
private spawnAndWait;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerExecutorService.d.ts","sourceRoot":"","sources":["../../src/agent-variants/WorkerExecutorService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,KAAK,2BAA2B,EACjC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkerExecutorService.d.ts","sourceRoot":"","sources":["../../src/agent-variants/WorkerExecutorService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorkerExecutorService = exports.modelFailureReason = exports.isModelAvailabilityFailure = exports.isWorkerExecutorEnabled = exports.EXECUTOR_LEASE_OWNER = void 0;
|
|
3
|
+
exports.WorkerExecutorService = exports.modelFailureReason = exports.isModelAvailabilityFailure = exports.isWorkerExecutorEnabled = exports.chooseWorkerLeaseOwner = exports.EXECUTOR_LEASE_OWNER = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const logger_1 = require("../utils/logger");
|
|
6
6
|
const workItemBriefing_1 = require("./workItemBriefing");
|
|
7
7
|
const workerDefaults_1 = require("../hosts/workerDefaults");
|
|
8
8
|
const contracts_1 = require("./contracts");
|
|
9
|
+
const workerRuntime_1 = require("./workerRuntime");
|
|
9
10
|
const log = (0, logger_1.createLogger)('WorkerExecutor');
|
|
10
11
|
/** Lease owner marking work the executor runs — hosts must not implement it. */
|
|
11
12
|
exports.EXECUTOR_LEASE_OWNER = 'rivet-executor';
|
|
13
|
+
const chooseWorkerLeaseOwner = async (executor, args) => {
|
|
14
|
+
if (!executor) {
|
|
15
|
+
return { leaseOwner: 'rivet-host-agent', summary: 'host path' };
|
|
16
|
+
}
|
|
17
|
+
const decision = await executor.resolveRuntime(args);
|
|
18
|
+
if (decision.mode === 'executor') {
|
|
19
|
+
return {
|
|
20
|
+
leaseOwner: exports.EXECUTOR_LEASE_OWNER,
|
|
21
|
+
runtime: decision.adapter,
|
|
22
|
+
summary: `${decision.adapter.vendor} (${decision.source})`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const reasons = decision.diagnostics
|
|
26
|
+
.map((d) => `${d.vendor}: ${d.reason.slice(0, 200)}`)
|
|
27
|
+
.join('; ');
|
|
28
|
+
return {
|
|
29
|
+
leaseOwner: 'rivet-host-agent',
|
|
30
|
+
summary: `host path${reasons ? ` (${reasons})` : ' (no worker runtime installed)'}`,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
exports.chooseWorkerLeaseOwner = chooseWorkerLeaseOwner;
|
|
12
34
|
/**
|
|
13
35
|
* The executor is the DEFAULT execution path for variant workers: leaner,
|
|
14
36
|
* cheaper, and deterministic vs host-agent subagents (see PR #948 evidence).
|
|
@@ -63,67 +85,33 @@ const modelFailureReason = (message, apiErrorStatus) => {
|
|
|
63
85
|
: 'model_unavailable';
|
|
64
86
|
};
|
|
65
87
|
exports.modelFailureReason = modelFailureReason;
|
|
66
|
-
/** Error/result text from the headless CLI's stdout JSON. With
|
|
67
|
-
* `--output-format json` the CLI reports API and model failures in the
|
|
68
|
-
* stdout result object (`is_error` + `result` text), not on stderr — the
|
|
69
|
-
* failure message must include it or the model-fallback rung never sees
|
|
70
|
-
* model-availability errors. */
|
|
71
|
-
const parseWorkerResultError = (stdout) => {
|
|
72
|
-
try {
|
|
73
|
-
const trimmed = stdout.trim();
|
|
74
|
-
const start = trimmed.indexOf('{');
|
|
75
|
-
if (start === -1)
|
|
76
|
-
return {};
|
|
77
|
-
const parsed = JSON.parse(trimmed.slice(start));
|
|
78
|
-
const parts = [parsed.subtype, parsed.result, parsed.error]
|
|
79
|
-
.filter((value) => typeof value === 'string')
|
|
80
|
-
.join(': ');
|
|
81
|
-
return {
|
|
82
|
-
...(parts.length > 0 ? { text: parts } : {}),
|
|
83
|
-
...(typeof parsed.api_error_status === 'number'
|
|
84
|
-
? { apiErrorStatus: parsed.api_error_status }
|
|
85
|
-
: {}),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return {};
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
88
|
/** API statuses that mean the MODEL (not the work) was rejected: auth,
|
|
93
89
|
* permission, unknown model, rate limit. Structured and non-brittle —
|
|
94
90
|
* preferred over text matching whenever the CLI reports one. */
|
|
95
91
|
const MODEL_AVAILABILITY_STATUSES = new Set([401, 403, 404, 429]);
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const usage = parsed.usage;
|
|
106
|
-
if (!usage && typeof parsed.total_cost_usd !== 'number')
|
|
107
|
-
return undefined;
|
|
108
|
-
const sum = (...values) => values.reduce((total, value) => total + (typeof value === 'number' ? value : 0), 0);
|
|
109
|
-
return {
|
|
110
|
-
...(usage
|
|
111
|
-
? {
|
|
112
|
-
tokensIn: sum(usage.input_tokens, usage.cache_read_input_tokens, usage.cache_creation_input_tokens),
|
|
113
|
-
tokensOut: sum(usage.output_tokens),
|
|
114
|
-
}
|
|
115
|
-
: {}),
|
|
116
|
-
...(typeof parsed.total_cost_usd === 'number'
|
|
117
|
-
? { costUsd: parsed.total_cost_usd }
|
|
118
|
-
: {}),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
92
|
+
/** Display label for a tier's model — codex/cursor tiers may omit the model
|
|
93
|
+
* (the user's own CLI default applies). */
|
|
94
|
+
const tierModelLabel = (tier) => ('model' in tier && tier.model) || `${tier.vendor}-default`;
|
|
95
|
+
const tierEffortLabel = (tier) => {
|
|
96
|
+
if (tier.vendor === 'claude')
|
|
97
|
+
return tier.effort;
|
|
98
|
+
if (tier.vendor === 'codex')
|
|
99
|
+
return tier.reasoningEffort;
|
|
100
|
+
return null;
|
|
124
101
|
};
|
|
125
|
-
|
|
102
|
+
/** A failure of the RUNTIME rather than the work: binary missing, CLI too
|
|
103
|
+
* old for a flag we pass, auth rejected. These invalidate the cached
|
|
104
|
+
* preflight so the next run re-probes instead of repeating the failure. */
|
|
105
|
+
const isRuntimeShapedFailure = (message, apiErrorStatus) => {
|
|
106
|
+
if (apiErrorStatus === 401 || apiErrorStatus === 403)
|
|
107
|
+
return true;
|
|
108
|
+
if (!message)
|
|
109
|
+
return false;
|
|
110
|
+
return /ENOENT|EACCES|unknown option|unexpected argument|command not found|credential|authentication|unauthorized|logged? ?in/i.test(message);
|
|
111
|
+
};
|
|
112
|
+
const defaultSpawn = ({ command, args, cwd, env }) => (0, child_process_1.spawn)(command, args, {
|
|
126
113
|
cwd,
|
|
114
|
+
...(env ? { env } : {}),
|
|
127
115
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
116
|
// Own process group so a timeout/cancel kills the whole worker tree.
|
|
129
117
|
detached: process.platform !== 'win32',
|
|
@@ -162,6 +150,12 @@ class WorkerExecutorService {
|
|
|
162
150
|
spawnWorker;
|
|
163
151
|
active = 0;
|
|
164
152
|
waiters = [];
|
|
153
|
+
/** Per-request vendor resolution + preflight (multi-vendor workers). */
|
|
154
|
+
resolver;
|
|
155
|
+
/** Claude adapter over the legacy workerCommand — what runs when a caller
|
|
156
|
+
* invokes `run()` without a resolved runtime (tests, legacy paths). */
|
|
157
|
+
defaultAdapter;
|
|
158
|
+
configuredWorkerModelFor;
|
|
165
159
|
constructor(options) {
|
|
166
160
|
this.orchestrator = options.orchestrator;
|
|
167
161
|
this.telemetry = options.telemetry;
|
|
@@ -202,11 +196,43 @@ class WorkerExecutorService {
|
|
|
202
196
|
Number(process.env.RIVET_EXECUTOR_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS);
|
|
203
197
|
this.maxConcurrent = options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
204
198
|
this.spawnWorker = options.spawnWorker ?? defaultSpawn;
|
|
199
|
+
this.defaultAdapter = (0, workerRuntime_1.makeClaudeAdapter)(this.workerCommand);
|
|
200
|
+
this.configuredWorkerModelFor = options.configuredWorkerModelFor;
|
|
201
|
+
const resolverOptions = {
|
|
202
|
+
configuredVendor: options.configuredWorkerVendor,
|
|
203
|
+
crossVendorFallback: options.workerVendorFallbackEnabled,
|
|
204
|
+
configuredModelFor: options.configuredWorkerModelFor,
|
|
205
|
+
// Preflight mirror of runOne's fallback-model rung: an account that
|
|
206
|
+
// lacks the default claude worker model must stay on the executor
|
|
207
|
+
// (probed healthy on the fallback model), not degrade to host path.
|
|
208
|
+
probeModelFallback: {
|
|
209
|
+
fallbackModel: () => this.fallbackModel,
|
|
210
|
+
isModelAvailabilityFailure: ({ message, apiErrorStatus, model }) => MODEL_AVAILABILITY_STATUSES.has(apiErrorStatus ?? -1) ||
|
|
211
|
+
(0, exports.isModelAvailabilityFailure)(message, model),
|
|
212
|
+
},
|
|
213
|
+
telemetry: this.telemetry,
|
|
214
|
+
...(options.preflightTimeoutMs !== undefined
|
|
215
|
+
? { probeTimeoutMs: options.preflightTimeoutMs }
|
|
216
|
+
: {}),
|
|
217
|
+
...(options.spawnProbe ? { spawnProbe: options.spawnProbe } : {}),
|
|
218
|
+
};
|
|
219
|
+
this.resolver = new workerRuntime_1.WorkerRuntimeResolver(resolverOptions);
|
|
205
220
|
}
|
|
206
221
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
222
|
+
* Resolve + preflight the worker runtime for one request. Awaited by the
|
|
223
|
+
* generation services BEFORE they choose a lease owner: `executor` means
|
|
224
|
+
* lease to `rivet-executor` and pass `decision.adapter` into `run()`;
|
|
225
|
+
* `host` means lease to the host agent (the pre-executor degraded mode)
|
|
226
|
+
* and surface `decision.diagnostics`. Vendors never switch after leasing.
|
|
227
|
+
*/
|
|
228
|
+
async resolveRuntime(args) {
|
|
229
|
+
return this.resolver.resolve(args);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* True when the legacy claude worker binary is on PATH and answers
|
|
233
|
+
* `--version`. Superseded by {@link resolveRuntime} for lease-owner
|
|
234
|
+
* decisions — a `--version` probe passes on a binary whose auth is dead
|
|
235
|
+
* (the July 2026 Bedrock incident). Kept for cheap diagnostics only.
|
|
210
236
|
*/
|
|
211
237
|
isRuntimeAvailable() {
|
|
212
238
|
if (this.runtimeAvailable === undefined) {
|
|
@@ -237,7 +263,8 @@ class WorkerExecutorService {
|
|
|
237
263
|
* thrown errors on the intake path. Pass the caller's lease so ids that
|
|
238
264
|
* never appear in the session snapshot can still be terminal-failed.
|
|
239
265
|
*/
|
|
240
|
-
async run(sessionId, workItemIds, lease) {
|
|
266
|
+
async run(sessionId, workItemIds, lease, runtime) {
|
|
267
|
+
const adapter = runtime ?? this.defaultAdapter;
|
|
241
268
|
const claimed = new Set(workItemIds);
|
|
242
269
|
const claimedItems = this.orchestrator
|
|
243
270
|
.getHostWork(sessionId)
|
|
@@ -252,7 +279,7 @@ class WorkerExecutorService {
|
|
|
252
279
|
// them on the caller's lease rather than stranding until lease expiry.
|
|
253
280
|
const unseen = workItemIds.filter((workItemId) => !claimedItems.some((item) => item.workId === workItemId));
|
|
254
281
|
await Promise.all([
|
|
255
|
-
...runnable.map((item) => this.runOne(sessionId, item, { retried: false })),
|
|
282
|
+
...runnable.map((item) => this.runOne(sessionId, item, { retried: false }, adapter)),
|
|
256
283
|
...stranded.map((item) => this.reportSafely(sessionId, {
|
|
257
284
|
workItemId: item.workId,
|
|
258
285
|
leaseId: item.leaseId,
|
|
@@ -311,49 +338,84 @@ class WorkerExecutorService {
|
|
|
311
338
|
}
|
|
312
339
|
}
|
|
313
340
|
}
|
|
314
|
-
/**
|
|
341
|
+
/** Worker tier for one work item, per vendor.
|
|
315
342
|
*
|
|
316
|
-
*
|
|
317
|
-
* env) > the
|
|
318
|
-
* >
|
|
319
|
-
*
|
|
320
|
-
|
|
343
|
+
* Claude keeps the full legacy precedence: explicit ops overrides
|
|
344
|
+
* (constructor options / RIVET_EXECUTOR_* env) > the user's
|
|
345
|
+
* `workerModels.claude` config > the run's user-selected fidelity > lane
|
|
346
|
+
* defaults; reference-backed creates keep their own override lane because
|
|
347
|
+
* reference fidelity is the deliverable there.
|
|
348
|
+
*
|
|
349
|
+
* Codex/cursor read the executor tier registry (fidelity → reasoning
|
|
350
|
+
* effort for codex; deliberately fidelity-invariant for cursor), with the
|
|
351
|
+
* user's `workerModels.<vendor>` config and the blunt
|
|
352
|
+
* RIVET_EXECUTOR_MODEL env override applying to the active vendor. Their
|
|
353
|
+
* reference-create lane pins the high tier until benchmarked (mid-tier
|
|
354
|
+
* workers regressed reference-backed creates — see workerDefaults). */
|
|
355
|
+
workerTierFor(item, vendor) {
|
|
321
356
|
const referenceBacked = Boolean(item.contextFiles?.length || item.contextBundlePath);
|
|
322
357
|
const isReferenceCreate = item.mode === 'create' && referenceBacked;
|
|
358
|
+
if (vendor !== 'claude') {
|
|
359
|
+
// Unset fidelity means the server default, same as the claude lane —
|
|
360
|
+
// only reference-backed creates pin the high tier (mid-tier workers
|
|
361
|
+
// regressed those; see workerDefaults).
|
|
362
|
+
const fidelity = isReferenceCreate
|
|
363
|
+
? 'high'
|
|
364
|
+
: (0, contracts_1.isWorkerFidelity)(item.fidelity)
|
|
365
|
+
? item.fidelity
|
|
366
|
+
: workerDefaults_1.DEFAULT_WORKER_FIDELITY;
|
|
367
|
+
const base = workerRuntime_1.EXECUTOR_FIDELITY_TIERS[vendor][fidelity];
|
|
368
|
+
const model = process.env.RIVET_EXECUTOR_MODEL ??
|
|
369
|
+
this.configuredWorkerModelFor?.(vendor);
|
|
370
|
+
return model ? { ...base, model } : base;
|
|
371
|
+
}
|
|
372
|
+
const configured = this.configuredWorkerModelFor?.('claude');
|
|
323
373
|
const overridden = isReferenceCreate
|
|
324
374
|
? this.referenceCreateTierOverridden
|
|
325
375
|
: this.workerTierOverridden;
|
|
326
376
|
if (!overridden && (0, contracts_1.isWorkerFidelity)(item.fidelity)) {
|
|
327
|
-
|
|
377
|
+
const base = workerRuntime_1.EXECUTOR_FIDELITY_TIERS.claude[item.fidelity];
|
|
378
|
+
return configured ? { ...base, model: configured } : base;
|
|
328
379
|
}
|
|
329
380
|
if (isReferenceCreate) {
|
|
330
381
|
return {
|
|
331
|
-
|
|
382
|
+
vendor: 'claude',
|
|
383
|
+
model: this.referenceCreateTierOverridden || !configured
|
|
384
|
+
? this.referenceCreateModel
|
|
385
|
+
: configured,
|
|
332
386
|
effort: this.referenceCreateEffort,
|
|
333
387
|
};
|
|
334
388
|
}
|
|
335
|
-
return {
|
|
389
|
+
return {
|
|
390
|
+
vendor: 'claude',
|
|
391
|
+
model: this.workerTierOverridden || !configured
|
|
392
|
+
? this.workerModel
|
|
393
|
+
: configured,
|
|
394
|
+
effort: this.workerEffort,
|
|
395
|
+
};
|
|
336
396
|
}
|
|
337
|
-
async runOne(sessionId, item, state) {
|
|
397
|
+
async runOne(sessionId, item, state, adapter) {
|
|
338
398
|
await this.acquire();
|
|
339
399
|
const startedAt = Date.now();
|
|
340
|
-
const tier = this.workerTierFor(item);
|
|
400
|
+
const tier = this.workerTierFor(item, adapter.vendor);
|
|
341
401
|
this.telemetry?.track('agent_variants.executor_worker_started', {
|
|
342
402
|
source: 'mcp',
|
|
343
403
|
sessionId,
|
|
344
404
|
workItemId: item.workId,
|
|
345
|
-
|
|
346
|
-
|
|
405
|
+
vendor: adapter.vendor,
|
|
406
|
+
model: tierModelLabel(tier),
|
|
407
|
+
effort: tierEffortLabel(tier),
|
|
347
408
|
fidelity: item.fidelity ?? null,
|
|
348
409
|
attempt: item.attempt,
|
|
349
410
|
});
|
|
350
411
|
let outcome;
|
|
351
|
-
let ranModel = tier
|
|
412
|
+
let ranModel = tierModelLabel(tier);
|
|
352
413
|
try {
|
|
353
|
-
outcome = await this.spawnAndWait(sessionId, item);
|
|
354
|
-
// Model-unavailable rung
|
|
355
|
-
//
|
|
356
|
-
//
|
|
414
|
+
outcome = await this.spawnAndWait(sessionId, item, adapter, tier);
|
|
415
|
+
// Model-unavailable rung (claude only — the one vendor reporting
|
|
416
|
+
// structured api_error_status): an account without access to the
|
|
417
|
+
// default worker model (or a model-scoped rate limit) must degrade to
|
|
418
|
+
// a lesser worker, never to a failed variant. One retry, fallback
|
|
357
419
|
// model, same effort. Gated on the failure message implicating the
|
|
358
420
|
// model so ordinary worker failures are not silently downgraded.
|
|
359
421
|
// Workspace-pristine proxy: workers always emit output tokens before
|
|
@@ -363,6 +425,8 @@ class WorkerExecutorService {
|
|
|
363
425
|
// vary/refine treat existing files as source and would corrupt.
|
|
364
426
|
const workspacePristine = (outcome.usage?.tokensOut ?? 0) === 0;
|
|
365
427
|
if (outcome.status === 'failed' &&
|
|
428
|
+
adapter.supportsModelFallback &&
|
|
429
|
+
tier.vendor === 'claude' &&
|
|
366
430
|
tier.model !== this.fallbackModel &&
|
|
367
431
|
workspacePristine &&
|
|
368
432
|
(MODEL_AVAILABILITY_STATUSES.has(outcome.apiErrorStatus ?? -1) ||
|
|
@@ -376,7 +440,8 @@ class WorkerExecutorService {
|
|
|
376
440
|
reason: (0, exports.modelFailureReason)(outcome.message, outcome.apiErrorStatus),
|
|
377
441
|
});
|
|
378
442
|
ranModel = this.fallbackModel;
|
|
379
|
-
outcome = await this.spawnAndWait(sessionId, item, {
|
|
443
|
+
outcome = await this.spawnAndWait(sessionId, item, adapter, {
|
|
444
|
+
vendor: 'claude',
|
|
380
445
|
model: this.fallbackModel,
|
|
381
446
|
effort: tier.effort,
|
|
382
447
|
});
|
|
@@ -406,11 +471,17 @@ class WorkerExecutorService {
|
|
|
406
471
|
// debugging must not require telemetry forensics or asking the user
|
|
407
472
|
// to reconstruct what the worker's stderr said.
|
|
408
473
|
log.error(`worker failed (workItem ${item.workId}, attempt ${item.attempt}, ` +
|
|
409
|
-
`model ${ranModel}` +
|
|
474
|
+
`vendor ${adapter.vendor}, model ${ranModel}` +
|
|
410
475
|
(outcome.apiErrorStatus !== undefined
|
|
411
476
|
? `, apiStatus ${outcome.apiErrorStatus}`
|
|
412
477
|
: '') +
|
|
413
478
|
`): ${outcome.message ?? 'no failure detail captured'}`);
|
|
479
|
+
// Runtime-shaped failures (binary missing, unknown CLI flag, auth
|
|
480
|
+
// rejection) invalidate the cached-healthy preflight so the NEXT run
|
|
481
|
+
// re-probes instead of leasing more items to a dead runtime.
|
|
482
|
+
if (isRuntimeShapedFailure(outcome.message, outcome.apiErrorStatus)) {
|
|
483
|
+
this.resolver.markUnhealthy(adapter.vendor, outcome.message ?? 'worker failed');
|
|
484
|
+
}
|
|
414
485
|
}
|
|
415
486
|
const result = await this.reportSafely(sessionId, {
|
|
416
487
|
workItemId: item.workId,
|
|
@@ -461,7 +532,7 @@ class WorkerExecutorService {
|
|
|
461
532
|
.filter(isExecutableImplementationItem)
|
|
462
533
|
.find((candidate) => candidate.workId === item.workId);
|
|
463
534
|
if (renewed) {
|
|
464
|
-
await this.runOne(sessionId, renewed, { retried: true });
|
|
535
|
+
await this.runOne(sessionId, renewed, { retried: true }, adapter);
|
|
465
536
|
return;
|
|
466
537
|
}
|
|
467
538
|
// Renewed lease exists but the item is not visible/executable —
|
|
@@ -508,31 +579,22 @@ class WorkerExecutorService {
|
|
|
508
579
|
}
|
|
509
580
|
}
|
|
510
581
|
}
|
|
511
|
-
spawnAndWait(sessionId, item,
|
|
512
|
-
const tier = tierOverride ?? this.workerTierFor(item);
|
|
582
|
+
spawnAndWait(sessionId, item, adapter, tier) {
|
|
513
583
|
const prompt = (0, workItemBriefing_1.renderWorkItemBriefing)(item, { completion: 'executor' });
|
|
584
|
+
// The adapter owns the vendor CLI's arg grammar. Prompt delivery differs
|
|
585
|
+
// per vendor: stdin for claude/codex, argv for cursor-agent (its print
|
|
586
|
+
// mode does not read the prompt from stdin).
|
|
587
|
+
const args = adapter.buildArgs(tier);
|
|
588
|
+
if (adapter.promptDelivery === 'argv')
|
|
589
|
+
args.push(prompt);
|
|
514
590
|
const child = this.spawnWorker({
|
|
515
|
-
command:
|
|
516
|
-
args
|
|
517
|
-
'--print',
|
|
518
|
-
'--output-format',
|
|
519
|
-
'json',
|
|
520
|
-
'--model',
|
|
521
|
-
tier.model,
|
|
522
|
-
'--effort',
|
|
523
|
-
tier.effort,
|
|
524
|
-
'--dangerously-skip-permissions',
|
|
525
|
-
'--allowed-tools',
|
|
526
|
-
'Read,Write,Edit,Bash,Glob,Grep',
|
|
527
|
-
// Workers of one session differ only by cwd/briefing; moving the
|
|
528
|
-
// per-machine sections out of the system prompt lets the N parallel
|
|
529
|
-
// workers (and QA-requeue retries) share one prompt-cache prefix.
|
|
530
|
-
'--exclude-dynamic-system-prompt-sections',
|
|
531
|
-
],
|
|
591
|
+
command: adapter.binary,
|
|
592
|
+
args,
|
|
532
593
|
cwd: item.workspace.kind === 'git_worktree'
|
|
533
594
|
? item.workspace.projectPath
|
|
534
595
|
: item.workspace.rootPath,
|
|
535
596
|
prompt,
|
|
597
|
+
env: adapter.spawnEnv(),
|
|
536
598
|
});
|
|
537
599
|
if (child.pid) {
|
|
538
600
|
const children = this.childrenBySession.get(sessionId) ?? new Set();
|
|
@@ -540,7 +602,9 @@ class WorkerExecutorService {
|
|
|
540
602
|
this.childrenBySession.set(sessionId, children);
|
|
541
603
|
child.on('close', () => children.delete(child.pid));
|
|
542
604
|
}
|
|
543
|
-
|
|
605
|
+
if (adapter.promptDelivery === 'stdin') {
|
|
606
|
+
child.stdin?.write(prompt);
|
|
607
|
+
}
|
|
544
608
|
child.stdin?.end();
|
|
545
609
|
let stderrTail = '';
|
|
546
610
|
child.stderr?.on('data', (chunk) => {
|
|
@@ -566,8 +630,7 @@ class WorkerExecutorService {
|
|
|
566
630
|
});
|
|
567
631
|
child.on('close', (code) => {
|
|
568
632
|
clearTimeout(timer);
|
|
569
|
-
const usage =
|
|
570
|
-
const { text: resultText, apiErrorStatus } = parseWorkerResultError(stdoutTail);
|
|
633
|
+
const { usage, resultText, apiErrorStatus } = adapter.parseResult(stdoutTail);
|
|
571
634
|
resolve(code === 0
|
|
572
635
|
? { status: 'succeeded', ...(usage ? { usage } : {}) }
|
|
573
636
|
: {
|