@whittlelabs/sifter 0.33.3 → 0.34.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.
Files changed (3) hide show
  1. package/bin.js +578 -139
  2. package/bin.js.map +4 -4
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -3122,6 +3122,37 @@ var require_registry = __commonJS({
3122
3122
  }
3123
3123
  });
3124
3124
 
3125
+ // ../../packages/loom/dist/runtime/requirements.js
3126
+ var require_requirements = __commonJS({
3127
+ "../../packages/loom/dist/runtime/requirements.js"(exports2) {
3128
+ "use strict";
3129
+ Object.defineProperty(exports2, "__esModule", { value: true });
3130
+ exports2.REQUIRES_EXECUTOR_TAG = exports2.EXECUTOR_ID = void 0;
3131
+ exports2.requirementTags = requirementTags;
3132
+ exports2.readRequiredExecutor = readRequiredExecutor;
3133
+ exports2.EXECUTOR_ID = /^[a-z][a-z0-9-]{0,63}$/;
3134
+ exports2.REQUIRES_EXECUTOR_TAG = "requires-executor:";
3135
+ function requirementTags(requires) {
3136
+ if (!requires?.executor)
3137
+ return [];
3138
+ if (!exports2.EXECUTOR_ID.test(requires.executor)) {
3139
+ throw new Error(`prompt-execution: requires.executor "${requires.executor}" is not an executor id`);
3140
+ }
3141
+ return [`${exports2.REQUIRES_EXECUTOR_TAG}${requires.executor}`];
3142
+ }
3143
+ function readRequiredExecutor(tags) {
3144
+ for (const tag of tags ?? []) {
3145
+ if (!tag.startsWith(exports2.REQUIRES_EXECUTOR_TAG))
3146
+ continue;
3147
+ const executor = tag.slice(exports2.REQUIRES_EXECUTOR_TAG.length);
3148
+ if (exports2.EXECUTOR_ID.test(executor))
3149
+ return executor;
3150
+ }
3151
+ return null;
3152
+ }
3153
+ }
3154
+ });
3155
+
3125
3156
  // ../../packages/loom/dist/runtime/prompt-execution.js
3126
3157
  var require_prompt_execution = __commonJS({
3127
3158
  "../../packages/loom/dist/runtime/prompt-execution.js"(exports2) {
@@ -3131,6 +3162,7 @@ var require_prompt_execution = __commonJS({
3131
3162
  exports2.renderPromptExecution = renderPromptExecution;
3132
3163
  exports2.pickProviderHints = pickProviderHints;
3133
3164
  exports2.renderTemplate = renderTemplate;
3165
+ var requirements_1 = require_requirements();
3134
3166
  function readPromptExecutionSpec(inputs) {
3135
3167
  const specRow = inputs.find((i) => i.label === "spec");
3136
3168
  if (!specRow)
@@ -3162,6 +3194,15 @@ var require_prompt_execution = __commonJS({
3162
3194
  }
3163
3195
  }
3164
3196
  }
3197
+ if (spec.requires !== void 0) {
3198
+ const req = spec.requires;
3199
+ if (typeof req !== "object" || req === null || Array.isArray(req)) {
3200
+ throw new Error("prompt-execution: spec.requires must be an object when present");
3201
+ }
3202
+ if (req.executor !== void 0 && (typeof req.executor !== "string" || !requirements_1.EXECUTOR_ID.test(req.executor))) {
3203
+ throw new Error("prompt-execution: spec.requires.executor must be an executor id");
3204
+ }
3205
+ }
3165
3206
  if (spec.gates !== void 0) {
3166
3207
  if (!Array.isArray(spec.gates)) {
3167
3208
  throw new Error("prompt-execution: spec.gates must be an array when present");
@@ -3337,7 +3378,7 @@ var require_runtime = __commonJS({
3337
3378
  "../../packages/loom/dist/runtime/index.js"(exports2) {
3338
3379
  "use strict";
3339
3380
  Object.defineProperty(exports2, "__esModule", { value: true });
3340
- exports2.validateAgainstSchema = exports2.readShuttleStrategy = exports2.shuttleStrategyInput = exports2.PROMPT_EXECUTION_STRATEGY = exports2.SHUTTLE_STRATEGY_LABEL = exports2.pickProviderHints = exports2.renderTemplate = exports2.renderPromptExecution = exports2.readPromptExecutionSpec = void 0;
3381
+ exports2.validateAgainstSchema = exports2.readRequiredExecutor = exports2.requirementTags = exports2.REQUIRES_EXECUTOR_TAG = exports2.EXECUTOR_ID = exports2.readShuttleStrategy = exports2.shuttleStrategyInput = exports2.PROMPT_EXECUTION_STRATEGY = exports2.SHUTTLE_STRATEGY_LABEL = exports2.pickProviderHints = exports2.renderTemplate = exports2.renderPromptExecution = exports2.readPromptExecutionSpec = void 0;
3341
3382
  var prompt_execution_1 = require_prompt_execution();
3342
3383
  Object.defineProperty(exports2, "readPromptExecutionSpec", { enumerable: true, get: function() {
3343
3384
  return prompt_execution_1.readPromptExecutionSpec;
@@ -3364,6 +3405,19 @@ var require_runtime = __commonJS({
3364
3405
  Object.defineProperty(exports2, "readShuttleStrategy", { enumerable: true, get: function() {
3365
3406
  return strategy_1.readShuttleStrategy;
3366
3407
  } });
3408
+ var requirements_1 = require_requirements();
3409
+ Object.defineProperty(exports2, "EXECUTOR_ID", { enumerable: true, get: function() {
3410
+ return requirements_1.EXECUTOR_ID;
3411
+ } });
3412
+ Object.defineProperty(exports2, "REQUIRES_EXECUTOR_TAG", { enumerable: true, get: function() {
3413
+ return requirements_1.REQUIRES_EXECUTOR_TAG;
3414
+ } });
3415
+ Object.defineProperty(exports2, "requirementTags", { enumerable: true, get: function() {
3416
+ return requirements_1.requirementTags;
3417
+ } });
3418
+ Object.defineProperty(exports2, "readRequiredExecutor", { enumerable: true, get: function() {
3419
+ return requirements_1.readRequiredExecutor;
3420
+ } });
3367
3421
  var validate_1 = require_validate();
3368
3422
  Object.defineProperty(exports2, "validateAgainstSchema", { enumerable: true, get: function() {
3369
3423
  return validate_1.validateAgainstSchema;
@@ -3377,7 +3431,9 @@ var require_prompt_execution2 = __commonJS({
3377
3431
  "use strict";
3378
3432
  Object.defineProperty(exports2, "__esModule", { value: true });
3379
3433
  exports2.buildPromptExecutionInputs = buildPromptExecutionInputs;
3434
+ exports2.promptExecutionTags = promptExecutionTags;
3380
3435
  var strategy_1 = require_strategy();
3436
+ var requirements_1 = require_requirements();
3381
3437
  function buildPromptExecutionInputs(options) {
3382
3438
  const spec = {
3383
3439
  rendering: options.rendering,
@@ -3386,6 +3442,10 @@ var require_prompt_execution2 = __commonJS({
3386
3442
  };
3387
3443
  if (options.workspace)
3388
3444
  spec.workspace = options.workspace;
3445
+ if (options.requires?.executor) {
3446
+ (0, requirements_1.requirementTags)(options.requires);
3447
+ spec.requires = { executor: options.requires.executor };
3448
+ }
3389
3449
  if (options.providerHints)
3390
3450
  spec.providerHints = options.providerHints;
3391
3451
  if (options.costCapHint)
@@ -3423,6 +3483,10 @@ var require_prompt_execution2 = __commonJS({
3423
3483
  }
3424
3484
  return inputs;
3425
3485
  }
3486
+ function promptExecutionTags(options) {
3487
+ const tags = [.../* @__PURE__ */ new Set([...options.tags ?? [], ...(0, requirements_1.requirementTags)(options.requires)])];
3488
+ return tags.length > 0 ? tags : void 0;
3489
+ }
3426
3490
  }
3427
3491
  });
3428
3492
 
@@ -9780,12 +9844,15 @@ var require_dist = __commonJS({
9780
9844
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
9781
9845
  };
9782
9846
  Object.defineProperty(exports2, "__esModule", { value: true });
9783
- exports2.SeedWorkflowsError = exports2.WorkflowBuilderError = exports2.seedWorkflows = exports2.canonicaliseWorkflow = exports2.synthesiseLinearChain = exports2.describeJobPoolRef = exports2.literalJobPool = exports2.principalJobPool = exports2.jobsCreateNode = exports2.httpNode = exports2.promptNode = exports2.defineWorkflow = exports2.buildPromptExecutionInputs = void 0;
9847
+ exports2.SeedWorkflowsError = exports2.WorkflowBuilderError = exports2.seedWorkflows = exports2.canonicaliseWorkflow = exports2.synthesiseLinearChain = exports2.describeJobPoolRef = exports2.literalJobPool = exports2.principalJobPool = exports2.jobsCreateNode = exports2.httpNode = exports2.promptNode = exports2.defineWorkflow = exports2.promptExecutionTags = exports2.buildPromptExecutionInputs = void 0;
9784
9848
  __exportStar(require_runtime(), exports2);
9785
9849
  var prompt_execution_1 = require_prompt_execution2();
9786
9850
  Object.defineProperty(exports2, "buildPromptExecutionInputs", { enumerable: true, get: function() {
9787
9851
  return prompt_execution_1.buildPromptExecutionInputs;
9788
9852
  } });
9853
+ Object.defineProperty(exports2, "promptExecutionTags", { enumerable: true, get: function() {
9854
+ return prompt_execution_1.promptExecutionTags;
9855
+ } });
9789
9856
  var define_workflow_1 = require_define_workflow();
9790
9857
  Object.defineProperty(exports2, "defineWorkflow", { enumerable: true, get: function() {
9791
9858
  return define_workflow_1.defineWorkflow;
@@ -9837,8 +9904,9 @@ var require_types2 = __commonJS({
9837
9904
  "../../packages/shuttle/dist/executors/types.js"(exports2) {
9838
9905
  "use strict";
9839
9906
  Object.defineProperty(exports2, "__esModule", { value: true });
9840
- exports2.ExecutorEnvironmentError = void 0;
9907
+ exports2.MODEL_UNAVAILABLE = exports2.ExecutorModelUnavailableError = exports2.ExecutorEnvironmentError = void 0;
9841
9908
  exports2.isExecutorEnvironmentError = isExecutorEnvironmentError;
9909
+ exports2.isExecutorModelUnavailableError = isExecutorModelUnavailableError;
9842
9910
  var ExecutorEnvironmentError = class extends Error {
9843
9911
  /** What the operator should do, as one clause: "run `claude` and sign in". */
9844
9912
  remedy;
@@ -9852,6 +9920,17 @@ var require_types2 = __commonJS({
9852
9920
  function isExecutorEnvironmentError(err) {
9853
9921
  return err instanceof Error && err.name === "ExecutorEnvironmentError";
9854
9922
  }
9923
+ var ExecutorModelUnavailableError = class extends Error {
9924
+ constructor(message) {
9925
+ super(message);
9926
+ this.name = "ExecutorModelUnavailableError";
9927
+ }
9928
+ };
9929
+ exports2.ExecutorModelUnavailableError = ExecutorModelUnavailableError;
9930
+ function isExecutorModelUnavailableError(err) {
9931
+ return err instanceof Error && err.name === "ExecutorModelUnavailableError";
9932
+ }
9933
+ exports2.MODEL_UNAVAILABLE = "model-unavailable";
9855
9934
  }
9856
9935
  });
9857
9936
 
@@ -10070,6 +10149,7 @@ var require_agent_process = __commonJS({
10070
10149
  exports2.firstLine = firstLine;
10071
10150
  exports2.keepTranscript = keepTranscript;
10072
10151
  exports2.classifyAgentFault = classifyAgentFault;
10152
+ exports2.isModelUnavailable = isModelUnavailable;
10073
10153
  exports2.agentFailure = agentFailure;
10074
10154
  var types_1 = require_types2();
10075
10155
  var child_process_1 = require("child_process");
@@ -10187,11 +10267,16 @@ var require_agent_process = __commonJS({
10187
10267
  }
10188
10268
  return null;
10189
10269
  }
10270
+ function isModelUnavailable(failure, vocab) {
10271
+ return failure.apiErrorStatus === 404 || vocab.modelUnavailable.test(failure.text ?? "");
10272
+ }
10190
10273
  function agentFailure(local, message, facts, vocab) {
10191
- if (!local)
10192
- return new Error(message);
10193
- const remedy = classifyAgentFault(facts, vocab);
10194
- return remedy ? new types_1.ExecutorEnvironmentError(message, remedy) : new Error(message);
10274
+ const remedy = local ? classifyAgentFault(facts, vocab) : null;
10275
+ if (remedy)
10276
+ return new types_1.ExecutorEnvironmentError(message, remedy);
10277
+ if (isModelUnavailable(facts, vocab))
10278
+ return new types_1.ExecutorModelUnavailableError(message);
10279
+ return new Error(message);
10195
10280
  }
10196
10281
  }
10197
10282
  });
@@ -10611,7 +10696,10 @@ var require_claude_code = __commonJS({
10611
10696
  loginRemedy: LOGIN_REMEDY,
10612
10697
  limitRemedy: LIMIT_REMEDY,
10613
10698
  signedOut: SIGNED_OUT,
10614
- outOfUsage: OUT_OF_USAGE
10699
+ outOfUsage: OUT_OF_USAGE,
10700
+ // claude 2.x: "There's an issue with the selected model (x). It may not
10701
+ // exist or you may not have access to it." beside the provider's 404.
10702
+ modelUnavailable: /issue with the selected model|may not exist or you may not have access/i
10615
10703
  };
10616
10704
  function classifyEnvironmentFault(failure) {
10617
10705
  return (0, agent_process_1.classifyAgentFault)(failure, CLAUDE_FAULTS);
@@ -11306,7 +11394,10 @@ var require_codex = __commonJS({
11306
11394
  loginRemedy: "run `codex login` and sign in (or `codex login --with-api-key`)",
11307
11395
  limitRemedy: "wait for the plan's usage limit to reset, or raise it",
11308
11396
  signedOut: /not logged in|run `?codex login|401 unauthorized|invalid api key|refresh token|token (?:has )?expired|sign in again/i,
11309
- outOfUsage: /usage limit|hit your (?:\w+ )?limit|insufficient_quota|exceeded your current quota|429 too many requests/i
11397
+ outOfUsage: /usage limit|hit your (?:\w+ )?limit|insufficient_quota|exceeded your current quota|429 too many requests/i,
11398
+ // OpenAI's words for a model the key may not use, and codex's for one a
11399
+ // ChatGPT sign-in does not include.
11400
+ modelUnavailable: /model_not_found|model .*(?:does not exist|you do not have access to it)|model is not supported|404 not found.*model/i
11310
11401
  };
11311
11402
  var READINESS_PROMPT = "Reply with the single word: ok";
11312
11403
  var READINESS_TIMEOUT_MS = 12e4;
@@ -11727,6 +11818,10 @@ var require_copilot = __commonJS({
11727
11818
  limitRemedy: "the account has no Copilot allowance left: on an organization seat, an owner must allow paid usage in the organization's Copilot policies and fund a budget for it; otherwise wait for the monthly reset",
11728
11819
  signedOut: /not (?:logged|signed) in|run `?copilot login|no authentication|authentication (?:failed|required)|bad credentials/i,
11729
11820
  outOfUsage: /exceeded your monthly quota|premium request|usage limit|hit your (?:\w+ )?limit|exceeded .*(?:allowance|limit)/i,
11821
+ // copilot 1.0.98 offers only the models the account may use, so a model
11822
+ // that does not exist and one a policy disables both read: Model "x" from
11823
+ // --model flag is not available.
11824
+ modelUnavailable: /model "?[^"\n]*"? (?:from --model flag )?is not available|model .*is not (?:enabled|supported)/i,
11730
11825
  notEntitled: {
11731
11826
  pattern: /access denied by policy|not authorized to use this copilot feature/i,
11732
11827
  remedy: "this account's Copilot does not allow the Copilot CLI: on an organization seat, an owner must assign the seat and enable the Copilot CLI policy, then run `copilot login` again so the new seat is picked up"
@@ -13135,6 +13230,7 @@ var require_anthropic_api = __commonJS({
13135
13230
  var keep_1 = require_dist2();
13136
13231
  var env_ref_1 = require_env_ref();
13137
13232
  var validate_output_1 = require_validate_output();
13233
+ var types_1 = require_types2();
13138
13234
  var models_1 = require_models();
13139
13235
  var DEFAULT_BASE_URL = "https://api.anthropic.com/v1/messages";
13140
13236
  var DEFAULT_MODEL = "claude-sonnet-4-6";
@@ -13251,7 +13347,11 @@ var require_anthropic_api = __commonJS({
13251
13347
  const apiErr = envelope?.error;
13252
13348
  if (apiErr?.message) {
13253
13349
  const kind = apiErr.type ? ` (${apiErr.type})` : "";
13254
- throw new Error(`Anthropic API error${kind}: ${apiErr.message} [HTTP ${response.status}]`);
13350
+ const message = `Anthropic API error${kind}: ${apiErr.message} [HTTP ${response.status}]`;
13351
+ if (response.status === 404 && apiErr.type === "not_found_error") {
13352
+ throw new types_1.ExecutorModelUnavailableError(message);
13353
+ }
13354
+ throw new Error(message);
13255
13355
  }
13256
13356
  throw new Error(`HTTP ${response.status}${bodyText ? `: ${bodyText}` : ""}`);
13257
13357
  }
@@ -13832,10 +13932,11 @@ var require_scaffold = __commonJS({
13832
13932
  function planScaffold2(brand, setupOptions, existing) {
13833
13933
  const pools = Array.isArray(existing.pools) ? existing.pools : [];
13834
13934
  const first = pools[0];
13835
- const prior = first !== null && typeof first === "object" && typeof first.executor === "string" ? first.executor : void 0;
13935
+ const pooled = first !== null && typeof first === "object" && typeof first.executor === "string" ? first.executor : void 0;
13936
+ const executors = existing.executors !== null && typeof existing.executors === "object" ? existing.executors : {};
13937
+ const prior = pooled ?? Object.keys(executors)[0];
13836
13938
  const executor = setupOptions.executor ?? prior ?? brand.defaultExecutor;
13837
13939
  const source = setupOptions.executor !== void 0 ? "flag" : prior !== void 0 ? "existing" : "default";
13838
- const executors = existing.executors !== null && typeof existing.executors === "object" ? existing.executors : {};
13839
13940
  const block = executor === void 0 ? void 0 : executors[executor];
13840
13941
  const keptBlock = setupOptions.executor === void 0 && block !== null && typeof block === "object" ? block : void 0;
13841
13942
  return { executor, source, keptBlock };
@@ -13910,7 +14011,7 @@ var require_checks = __commonJS({
13910
14011
  }
13911
14012
  function activeAgent(config) {
13912
14013
  const configured = configuredExecutors(config);
13913
- const pooled = config.pools.map((p) => p.executor).find((e) => AGENT_EXECUTORS2.includes(e) && configured.includes(e));
14014
+ const pooled = config.pools.map((p) => p.executor).find((e) => e !== void 0 && AGENT_EXECUTORS2.includes(e) && configured.includes(e));
13914
14015
  return pooled ?? configured.find((e) => AGENT_EXECUTORS2.includes(e)) ?? null;
13915
14016
  }
13916
14017
  function agentSettings(config, subject) {
@@ -22240,6 +22341,8 @@ var require_subscribe = __commonJS({
22240
22341
  return outcome;
22241
22342
  }
22242
22343
  var STOPPED = Symbol("stopped");
22344
+ var DECLINE_WINDOW = 25;
22345
+ var DECLINED_MEMORY = 1e3;
22243
22346
  function subscribe(opts) {
22244
22347
  const logger = opts.logger ?? NULL_LOGGER;
22245
22348
  const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
@@ -22257,6 +22360,16 @@ var require_subscribe = __commonJS({
22257
22360
  let wakeStartup = null;
22258
22361
  let unavailableSince = null;
22259
22362
  let rateLimited = null;
22363
+ const accepts = opts.accepts;
22364
+ const declined = /* @__PURE__ */ new Set();
22365
+ function noteDeclined(jobId) {
22366
+ if (declined.has(jobId))
22367
+ return;
22368
+ if (declined.size >= DECLINED_MEMORY)
22369
+ declined.clear();
22370
+ declined.add(jobId);
22371
+ logger.debug?.("declined a job this subscriber cannot run; it stays queued for another member", { jobId });
22372
+ }
22260
22373
  function noteReachable() {
22261
22374
  if (unavailableSince !== null) {
22262
22375
  logger.info(`Sift is reachable again after ${formatDuration(Date.now() - unavailableSince)}`);
@@ -22422,9 +22535,10 @@ var require_subscribe = __commonJS({
22422
22535
  }
22423
22536
  try {
22424
22537
  const paused = holds.size > 0;
22538
+ const free = maxConcurrency - inflight;
22425
22539
  const { data, directives } = await opts.jobsClient.listDispatchableJobs({
22426
22540
  pools: resolvedPoolIds,
22427
- limit: paused ? 1 : maxConcurrency - inflight,
22541
+ limit: paused ? 1 : accepts ? Math.max(free, DECLINE_WINDOW) : free,
22428
22542
  sort: "priority:desc,queued_at:asc"
22429
22543
  });
22430
22544
  noteReachable();
@@ -22447,6 +22561,10 @@ var require_subscribe = __commonJS({
22447
22561
  for (const job of data) {
22448
22562
  if (inflight >= maxConcurrency)
22449
22563
  break;
22564
+ if (accepts && !accepts(job)) {
22565
+ noteDeclined(job.id);
22566
+ continue;
22567
+ }
22450
22568
  inflight++;
22451
22569
  void runOnce(job).finally(() => {
22452
22570
  inflight--;
@@ -22542,7 +22660,8 @@ var require_subscribe = __commonJS({
22542
22660
  }
22543
22661
  await opts.jobsClient.submit(dispatch.job.id, {
22544
22662
  status: result.status,
22545
- ...result.reason !== void 0 ? { reason: result.reason } : {}
22663
+ ...result.reason !== void 0 ? { reason: result.reason } : {},
22664
+ ...result.status === "failed" && result.failureCode !== void 0 ? { failureCode: result.failureCode } : {}
22546
22665
  });
22547
22666
  } catch (err) {
22548
22667
  if (isFatal(err)) {
@@ -22664,7 +22783,12 @@ var require_schema4 = __commonJS({
22664
22783
  name: zod_1.z.string().optional(),
22665
22784
  tags: zod_1.z.array(zod_1.z.string()).optional(),
22666
22785
  minPriority: zod_1.z.number().int().optional(),
22667
- executor: zod_1.z.string()
22786
+ /**
22787
+ * Deprecated (whittlelabs ADR 0118): a Sifter chooses the executor for
22788
+ * each job from `executors`, in their order. A pool that still names one,
22789
+ * as configs before 0.34 did, tries it first for its own jobs.
22790
+ */
22791
+ executor: zod_1.z.string().optional()
22668
22792
  }).refine((pool) => pool.id !== void 0 || pool.name !== void 0, {
22669
22793
  message: 'Each pool must specify at least one of "id" or "name"'
22670
22794
  });
@@ -22704,6 +22828,11 @@ var require_schema4 = __commonJS({
22704
22828
  concurrency: zod_1.z.object({
22705
22829
  maxJobs: zod_1.z.number().int().positive().default(defaults_1.DEFAULTS.concurrency.maxJobs)
22706
22830
  }).default(defaults_1.DEFAULTS.concurrency),
22831
+ /**
22832
+ * The executors this runner may run jobs on, keyed by id. The order they
22833
+ * are written in is the order a job tries them (ADR 0118): a job that
22834
+ * requires none runs on the first that can run jobs on this machine.
22835
+ */
22707
22836
  executors: zod_1.z.record(zod_1.z.string(), executorSchema),
22708
22837
  logging: zod_1.z.object({
22709
22838
  level: zod_1.z.enum(["debug", "info", "warn", "error"]).default(defaults_1.DEFAULTS.logging.level),
@@ -22719,6 +22848,7 @@ var require_loader = __commonJS({
22719
22848
  "../../packages/shuttle/dist/config/loader.js"(exports2) {
22720
22849
  "use strict";
22721
22850
  Object.defineProperty(exports2, "__esModule", { value: true });
22851
+ exports2.resolveConfigPath = resolveConfigPath;
22722
22852
  exports2.loadConfig = loadConfig;
22723
22853
  var fs_1 = require("fs");
22724
22854
  var path_1 = require("path");
@@ -26837,32 +26967,51 @@ var require_logger = __commonJS({
26837
26967
  }
26838
26968
  });
26839
26969
 
26840
- // ../../packages/shuttle/dist/core/demux.js
26841
- var require_demux = __commonJS({
26842
- "../../packages/shuttle/dist/core/demux.js"(exports2) {
26970
+ // ../../packages/shuttle/dist/core/executor-choice.js
26971
+ var require_executor_choice = __commonJS({
26972
+ "../../packages/shuttle/dist/core/executor-choice.js"(exports2) {
26843
26973
  "use strict";
26844
26974
  Object.defineProperty(exports2, "__esModule", { value: true });
26845
- exports2.createDemux = createDemux;
26846
- function createDemux(executors, pools) {
26847
- const byStrategy = /* @__PURE__ */ new Map();
26848
- for (const executor of executors) {
26849
- for (const strategy of executor.strategies)
26850
- byStrategy.set(strategy, executor);
26851
- }
26975
+ exports2.createExecutorChooser = createExecutorChooser;
26976
+ var loom_1 = require_dist();
26977
+ function createExecutorChooser(executors, pools) {
26852
26978
  const byName = new Map(executors.map((e) => [e.capability.id, e]));
26853
- const byPool = /* @__PURE__ */ new Map();
26979
+ const legacyFirst = /* @__PURE__ */ new Map();
26854
26980
  for (const pool of pools) {
26855
- const named = byName.get(pool.executor);
26856
- if (!named)
26981
+ if (!pool.executor || !byName.has(pool.executor))
26857
26982
  continue;
26858
26983
  if (pool.id)
26859
- byPool.set(`id:${pool.id}`, named);
26984
+ legacyFirst.set(`id:${pool.id}`, pool.executor);
26860
26985
  if (pool.name)
26861
- byPool.set(`name:${pool.name}`, named);
26986
+ legacyFirst.set(`name:${pool.name}`, pool.executor);
26862
26987
  }
26863
- return (pool, strategy) => {
26864
- const named = byPool.get(`id:${pool.id}`) ?? byPool.get(`name:${pool.name}`);
26865
- return named && named.strategies.includes(strategy) ? named : byStrategy.get(strategy);
26988
+ const orderFor = (pool) => {
26989
+ const first = (pool.id !== void 0 ? legacyFirst.get(`id:${pool.id}`) : void 0) ?? (pool.name !== void 0 ? legacyFirst.get(`name:${pool.name}`) : void 0);
26990
+ const head = first ? byName.get(first) : void 0;
26991
+ return head ? [head, ...executors.filter((e) => e !== head)] : [...executors];
26992
+ };
26993
+ return {
26994
+ accepts(tags) {
26995
+ const required = (0, loom_1.readRequiredExecutor)(tags);
26996
+ return required === null || byName.has(required);
26997
+ },
26998
+ choose({ pool, strategy, required }) {
26999
+ if (required !== null) {
27000
+ const named = byName.get(required);
27001
+ if (!named) {
27002
+ return { ok: false, reason: `this job requires the ${required} executor, which this runner does not have` };
27003
+ }
27004
+ if (!named.strategies.includes(strategy)) {
27005
+ return { ok: false, reason: `this job requires the ${required} executor, which does not run "${strategy}" jobs` };
27006
+ }
27007
+ return { ok: true, executors: [named], required };
27008
+ }
27009
+ const capable = orderFor(pool).filter((e) => e.strategies.includes(strategy));
27010
+ if (capable.length === 0)
27011
+ return { ok: false, reason: `no executor for shuttle strategy "${strategy}"` };
27012
+ return { ok: true, executors: capable, required: null };
27013
+ },
27014
+ orderFor
26866
27015
  };
26867
27016
  }
26868
27017
  }
@@ -27389,7 +27538,7 @@ var require_shuttle = __commonJS({
27389
27538
  var logger_1 = require_logger();
27390
27539
  var registry_1 = require_registry();
27391
27540
  var claude_code_1 = require_claude_code();
27392
- var demux_1 = require_demux();
27541
+ var executor_choice_1 = require_executor_choice();
27393
27542
  var codex_1 = require_codex();
27394
27543
  var copilot_1 = require_copilot();
27395
27544
  var http_api_1 = require_http_api();
@@ -27402,6 +27551,15 @@ var require_shuttle = __commonJS({
27402
27551
  var store_1 = require_store();
27403
27552
  var GATE_REPAIR_BUDGET = 2;
27404
27553
  var ENVIRONMENT_HOLD = "executor-environment";
27554
+ function requiredExecutor(dispatch, strategy) {
27555
+ if (strategy !== loom_1.PROMPT_EXECUTION_STRATEGY)
27556
+ return null;
27557
+ try {
27558
+ return (0, loom_1.readPromptExecutionSpec)(dispatch.inputs).requires?.executor ?? null;
27559
+ } catch {
27560
+ return null;
27561
+ }
27562
+ }
27405
27563
  var RECHECK_INITIAL_MS = 15e3;
27406
27564
  var RECHECK_MAX_MS = 3e5;
27407
27565
  var KEEP_RETRY_INITIAL_MS = 2e3;
@@ -27511,8 +27669,15 @@ var require_shuttle = __commonJS({
27511
27669
  lifetime = deferred();
27512
27670
  /** Aborted by `stop()`: ends the startup waits and the readiness re-checks. */
27513
27671
  stopping = new AbortController();
27514
- /** When claiming was paused on an executor environment fault; null while claiming. */
27515
- environmentPausedAt = null;
27672
+ /**
27673
+ * The executors that cannot run jobs on this machine just now (signed out,
27674
+ * missing, out of plan), with why and since when. Empty while every one can.
27675
+ */
27676
+ unwell = /* @__PURE__ */ new Map();
27677
+ /** Every configured executor, in the configured order. */
27678
+ executors = [];
27679
+ /** Whether claiming is paused because no executor can run jobs. */
27680
+ environmentHeld = false;
27516
27681
  constructor(options) {
27517
27682
  this.brand = options.brand;
27518
27683
  this.config = options.config;
@@ -27596,7 +27761,8 @@ var require_shuttle = __commonJS({
27596
27761
  const registry = new registry_1.ExecutorRegistry(this.brand.executorAllowlist);
27597
27762
  registerBuiltInExecutors(registry, this.brand.executorAllowlist);
27598
27763
  const executors = registry.build(mergeExecutorDefaults((0, environment_1.executorDefaultsFor)(this.brand, this.config.auth.jobsApiUrl), this.config.executors));
27599
- const executorFor = (0, demux_1.createDemux)(executors, this.config.pools);
27764
+ this.executors = executors;
27765
+ const chooser = (0, executor_choice_1.createExecutorChooser)(executors, this.config.pools);
27600
27766
  const modelsByExecutor = await listExecutorModels(executors, this.stopping.signal);
27601
27767
  if (this.stopping.signal.aborted)
27602
27768
  return;
@@ -27621,12 +27787,31 @@ var require_shuttle = __commonJS({
27621
27787
  const attendanceId = dispatch.attendanceId;
27622
27788
  const startedAt = Date.now();
27623
27789
  const strategy = (0, loom_1.readShuttleStrategy)(dispatch.inputs) ?? "";
27624
- const executor = executorFor(dispatch.pool, strategy);
27625
- if (!executor) {
27626
- const reason = `no executor for shuttle strategy "${strategy}"`;
27627
- await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
27628
- return { status: "failed", reason };
27790
+ const close = async (result) => {
27791
+ await observerChain.notify({
27792
+ kind: "attendance.closed",
27793
+ jobId,
27794
+ attendanceId,
27795
+ status: result.status === "completed" ? "completed" : "failed",
27796
+ ...result.reason !== void 0 ? { reason: result.reason } : {},
27797
+ totalDurationMs: Date.now() - startedAt
27798
+ }, onObserverError);
27799
+ return result;
27800
+ };
27801
+ const choice = chooser.choose({ pool: dispatch.pool, strategy, required: requiredExecutor(dispatch, strategy) });
27802
+ if (!choice.ok)
27803
+ return close({ status: "failed", reason: choice.reason });
27804
+ if (choice.required !== null) {
27805
+ const fault = this.unwell.get(choice.required);
27806
+ if (fault) {
27807
+ return close({
27808
+ status: "failed",
27809
+ reason: `executor failed: ${choice.required} cannot run jobs on this machine (${fault.err.message}). To fix it, ${fault.err.remedy}.`
27810
+ });
27811
+ }
27629
27812
  }
27813
+ const well = choice.executors.filter((e) => !this.unwell.has(e.capability.id));
27814
+ const candidates = well.length > 0 ? well : choice.executors;
27630
27815
  await observerChain.notify({
27631
27816
  kind: "job.claimed",
27632
27817
  jobId,
@@ -27637,9 +27822,9 @@ var require_shuttle = __commonJS({
27637
27822
  dispatch
27638
27823
  }, onObserverError);
27639
27824
  let estimate = {};
27640
- if (executor.estimate) {
27825
+ if (candidates[0].estimate) {
27641
27826
  try {
27642
- estimate = await executor.estimate(dispatch);
27827
+ estimate = await candidates[0].estimate(dispatch);
27643
27828
  } catch {
27644
27829
  }
27645
27830
  }
@@ -27647,85 +27832,102 @@ var require_shuttle = __commonJS({
27647
27832
  await observerChain.notify({ kind: "spend.preflight", jobId, attendanceId, estimate });
27648
27833
  } catch (err) {
27649
27834
  const reason = err instanceof chain_1.SpendCapExceeded ? "refused_over_cap" : `preflight rejected: ${err instanceof Error ? err.message : String(err)}`;
27650
- await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
27651
- return { status: "failed", reason };
27652
- }
27653
- await observerChain.notify({ kind: "executor.dispatch.started", jobId, attendanceId, executor: executor.capability.id, strategy }, onObserverError);
27654
- const dispatchStartedAt = Date.now();
27655
- const environment = executor.prepareEnvironment ? await executor.prepareEnvironment(dispatch) : void 0;
27656
- let preparedWorkspace = null;
27657
- if (environment?.workspaceRequest) {
27835
+ return close({ status: "failed", reason });
27836
+ }
27837
+ const runOn = async (executor) => {
27838
+ await observerChain.notify({ kind: "executor.dispatch.started", jobId, attendanceId, executor: executor.capability.id, strategy }, onObserverError);
27839
+ const dispatchStartedAt = Date.now();
27840
+ const environment = executor.prepareEnvironment ? await executor.prepareEnvironment(dispatch) : void 0;
27841
+ let preparedWorkspace = null;
27842
+ if (environment?.workspaceRequest) {
27843
+ try {
27844
+ preparedWorkspace = await (0, workspace_1.materializeWorkspace)(environment.workspaceRequest, {
27845
+ ...environment.materializeOptions ?? {},
27846
+ credentials: this.broker,
27847
+ signal
27848
+ });
27849
+ } catch (err) {
27850
+ (0, logger_1.getLogger)().warn("workspace materialization failed; running prompt-only", {
27851
+ jobId,
27852
+ error: err instanceof Error ? err.message : String(err)
27853
+ });
27854
+ preparedWorkspace = null;
27855
+ }
27856
+ }
27857
+ const executionCtx = {
27858
+ reportProgress: (channel, frames) => {
27859
+ void jobsClient.postProgress(jobId, { channel, frames }).then(() => activity.record()).catch(() => {
27860
+ });
27861
+ },
27862
+ workspace: preparedWorkspace,
27863
+ credentials: this.broker
27864
+ };
27865
+ let response;
27658
27866
  try {
27659
- preparedWorkspace = await (0, workspace_1.materializeWorkspace)(environment.workspaceRequest, {
27660
- ...environment.materializeOptions ?? {},
27661
- credentials: this.broker,
27662
- signal
27663
- });
27867
+ response = await executor.execute(dispatch, signal, executionCtx);
27664
27868
  } catch (err) {
27665
- (0, logger_1.getLogger)().warn("workspace materialization failed; running prompt-only", {
27666
- jobId,
27667
- error: err instanceof Error ? err.message : String(err)
27668
- });
27669
- preparedWorkspace = null;
27869
+ const reason = `executor failed: ${err instanceof Error ? err.message : String(err)}`;
27870
+ if (this.broker === null && (0, types_1.isExecutorEnvironmentError)(err)) {
27871
+ this.markUnwell(executor, err, subscribeLogger);
27872
+ return { kind: "environment", reason };
27873
+ }
27874
+ if ((0, types_1.isExecutorModelUnavailableError)(err)) {
27875
+ return { kind: "result", result: { status: "failed", reason, failureCode: types_1.MODEL_UNAVAILABLE } };
27876
+ }
27877
+ return { kind: "result", result: { status: "failed", reason } };
27878
+ } finally {
27879
+ if (preparedWorkspace)
27880
+ await preparedWorkspace.cleanup();
27670
27881
  }
27671
- }
27672
- const executionCtx = {
27673
- reportProgress: (channel, frames) => {
27674
- void jobsClient.postProgress(jobId, { channel, frames }).then(() => activity.record()).catch(() => {
27882
+ const gated = await runOutputGates(dispatch, response, executor, signal, executionCtx);
27883
+ if (!gated.ok)
27884
+ return { kind: "result", result: { status: "failed", reason: gated.reason } };
27885
+ response = gated.response;
27886
+ await observerChain.notify({
27887
+ kind: "executor.dispatch.completed",
27888
+ jobId,
27889
+ attendanceId,
27890
+ executor: executor.capability.id,
27891
+ response: { rawText: response.rawText ?? "", parsed: response.parsed, usage: response.usage },
27892
+ durationMs: Date.now() - dispatchStartedAt
27893
+ }, onObserverError);
27894
+ const outputs = [...response.outputs];
27895
+ if (response.usage) {
27896
+ outputs.push({
27897
+ label: "usage",
27898
+ content: { ...response.usage, executor: executor.capability.id }
27675
27899
  });
27676
- },
27677
- workspace: preparedWorkspace,
27678
- credentials: this.broker
27679
- };
27680
- let response;
27681
- try {
27682
- response = await executor.execute(dispatch, signal, executionCtx);
27683
- } catch (err) {
27684
- const reason = `executor failed: ${err instanceof Error ? err.message : String(err)}`;
27685
- if (this.broker === null && (0, types_1.isExecutorEnvironmentError)(err)) {
27686
- this.pauseForEnvironment(executor, err, subscribeLogger);
27687
27900
  }
27688
- await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
27689
- return { status: "failed", reason };
27690
- } finally {
27691
- if (preparedWorkspace)
27692
- await preparedWorkspace.cleanup();
27693
- }
27694
- const gated = await runOutputGates(dispatch, response, executor, signal, executionCtx);
27695
- if (!gated.ok) {
27696
- await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason: gated.reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
27697
- return { status: "failed", reason: gated.reason };
27698
- }
27699
- response = gated.response;
27700
- await observerChain.notify({
27701
- kind: "executor.dispatch.completed",
27702
- jobId,
27703
- attendanceId,
27704
- executor: executor.capability.id,
27705
- response: { rawText: response.rawText ?? "", parsed: response.parsed, usage: response.usage },
27706
- durationMs: Date.now() - dispatchStartedAt
27707
- }, onObserverError);
27708
- const outputs = [...response.outputs];
27709
- if (response.usage) {
27710
- outputs.push({
27711
- label: "usage",
27712
- content: { ...response.usage, executor: executor.capability.id }
27713
- });
27714
- }
27715
- for (const output of outputs) {
27716
- await observerChain.notify({ kind: "output.submitted", jobId, attendanceId, label: output.label, content: output.content }, onObserverError);
27717
- }
27718
- await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "completed", totalDurationMs: Date.now() - startedAt }, onObserverError);
27719
- return { status: "completed", outputs };
27901
+ for (const output of outputs) {
27902
+ await observerChain.notify({ kind: "output.submitted", jobId, attendanceId, label: output.label, content: output.content }, onObserverError);
27903
+ }
27904
+ return { kind: "result", result: { status: "completed", outputs } };
27905
+ };
27906
+ for (const [i, executor] of candidates.entries()) {
27907
+ const outcome = await runOn(executor);
27908
+ if (outcome.kind === "result")
27909
+ return close(outcome.result);
27910
+ const next = candidates[i + 1];
27911
+ if (!next)
27912
+ return close({ status: "failed", reason: outcome.reason });
27913
+ subscribeLogger.info(`${executor.capability.id} cannot run jobs on this machine, so job ${jobId} runs on ${next.capability.id} instead.`);
27914
+ }
27915
+ return close({ status: "failed", reason: `no executor for shuttle strategy "${strategy}"` });
27720
27916
  };
27721
27917
  const pools = this.config.pools.map((p) => p.id ? { id: p.id } : { name: p.name });
27722
- const poolExecutors = new Map(pools.map((ref, i) => [ref, this.config.pools[i].executor]));
27723
27918
  const poolMetadata = (ref) => {
27724
- const executor = poolExecutors.get(ref);
27725
- const models = executor ? modelsByExecutor.get(executor) : void 0;
27726
- if (!executor || !models)
27919
+ const reported = chooser.orderFor(ref).map((e) => {
27920
+ const models = modelsByExecutor.get(e.capability.id) ?? [];
27921
+ return { id: e.capability.id, ...models.length > 0 ? { models } : {} };
27922
+ });
27923
+ const [first] = reported;
27924
+ if (!first)
27727
27925
  return void 0;
27728
- return { executor, ...models.length > 0 ? { models } : {} };
27926
+ return {
27927
+ executors: reported,
27928
+ executor: first.id,
27929
+ ...first.models ? { models: first.models } : {}
27930
+ };
27729
27931
  };
27730
27932
  const attemptedUpgrades = /* @__PURE__ */ new Set();
27731
27933
  this.subscription = (0, jobs_1.subscribe)({
@@ -27747,6 +27949,9 @@ var require_shuttle = __commonJS({
27747
27949
  poolMetadata
27748
27950
  },
27749
27951
  onJob,
27952
+ // Decline, at discovery, a job that requires an executor this runner
27953
+ // does not have; it stays queued for a member that has it (ADR 0118).
27954
+ accepts: (job) => chooser.accepts(job.tags),
27750
27955
  onDirectives: async (directives) => {
27751
27956
  const decommission = (0, decommission_1.readDecommissionDirective)(directives);
27752
27957
  if (decommission) {
@@ -27831,20 +28036,31 @@ var require_shuttle = __commonJS({
27831
28036
  this.subscription?.done().catch((err) => this.lifetime.reject(err));
27832
28037
  }
27833
28038
  /**
27834
- * Stop claiming because the executor says this machine cannot run jobs,
27835
- * and keep checking until it can. One warning says why and what to do; the
27836
- * re-checks stay at debug; one line says when claiming resumes. Idempotent:
27837
- * a second failing job while paused (maxJobs > 1) changes nothing.
28039
+ * Note that an executor cannot run jobs on this machine, and keep checking
28040
+ * until it can. While another executor can, jobs that do not require this
28041
+ * one run there; once none can, claiming pauses, so a signed-out laptop does
28042
+ * not burn a queue of reviews one failure at a time. One warning says why
28043
+ * and what to do; the re-checks stay at debug; one line says when it works
28044
+ * again. Idempotent: a second failing job on the same executor changes
28045
+ * nothing.
27838
28046
  */
27839
- pauseForEnvironment(executor, err, logger) {
27840
- if (this.environmentPausedAt !== null || !this.subscription)
28047
+ markUnwell(executor, err, logger) {
28048
+ const id = executor.capability.id;
28049
+ if (this.unwell.has(id) || !this.subscription)
27841
28050
  return;
27842
- this.environmentPausedAt = Date.now();
27843
- this.subscription.pause(ENVIRONMENT_HOLD);
27844
- logger.warn(`Claiming paused: ${executor.capability.id} cannot run jobs on this machine (${err.message}). To fix it, ${err.remedy}. The ${this.brand.product.title} checks again on its own and resumes claiming when it works.`);
28051
+ this.unwell.set(id, { since: Date.now(), err });
28052
+ const others = this.executors.filter((e) => !this.unwell.has(e.capability.id)).map((e) => e.capability.id);
28053
+ if (others.length === 0) {
28054
+ this.environmentHeld = true;
28055
+ this.subscription.pause(ENVIRONMENT_HOLD);
28056
+ logger.warn(`Claiming paused: ${id} cannot run jobs on this machine (${err.message}). To fix it, ${err.remedy}. The ${this.brand.product.title} checks again on its own and resumes claiming when it works.`);
28057
+ } else {
28058
+ logger.warn(`${id} cannot run jobs on this machine (${err.message}). To fix it, ${err.remedy}. Until then, jobs that do not require ${id} run on ${others.join(", ")}; the ${this.brand.product.title} checks ${id} again on its own.`);
28059
+ }
27845
28060
  void this.recheckEnvironment(executor, logger);
27846
28061
  }
27847
28062
  async recheckEnvironment(executor, logger) {
28063
+ const id = executor.capability.id;
27848
28064
  const signal = this.stopping.signal;
27849
28065
  let delay = this.environmentRecheck.initialMs;
27850
28066
  for (; ; ) {
@@ -27860,15 +28076,21 @@ var require_shuttle = __commonJS({
27860
28076
  if (signal.aborted)
27861
28077
  return;
27862
28078
  if (readiness.ready) {
27863
- const pausedFor = formatDuration(Date.now() - (this.environmentPausedAt ?? Date.now()));
27864
- this.environmentPausedAt = null;
27865
- this.subscription?.resume(ENVIRONMENT_HOLD);
27866
- logger.info(`${executor.capability.id} can run jobs again; claiming resumed after ${pausedFor} paused.`);
28079
+ const since = this.unwell.get(id)?.since ?? Date.now();
28080
+ this.unwell.delete(id);
28081
+ const lasted = formatDuration(Date.now() - since);
28082
+ if (this.environmentHeld) {
28083
+ this.environmentHeld = false;
28084
+ this.subscription?.resume(ENVIRONMENT_HOLD);
28085
+ logger.info(`${id} can run jobs again; claiming resumed after ${lasted} paused.`);
28086
+ } else {
28087
+ logger.info(`${id} can run jobs again after ${lasted}.`);
28088
+ }
27867
28089
  return;
27868
28090
  }
27869
28091
  delay = Math.min(delay * 2, this.environmentRecheck.maxMs);
27870
28092
  logger.debug?.("executor still cannot run jobs", {
27871
- executor: executor.capability.id,
28093
+ executor: id,
27872
28094
  reason: readiness.reason,
27873
28095
  nextCheckIn: formatDuration(delay)
27874
28096
  });
@@ -28356,6 +28578,212 @@ var require_revoke = __commonJS({
28356
28578
  }
28357
28579
  });
28358
28580
 
28581
+ // ../../packages/shuttle/dist/config/executor-order.js
28582
+ var require_executor_order = __commonJS({
28583
+ "../../packages/shuttle/dist/config/executor-order.js"(exports2) {
28584
+ "use strict";
28585
+ Object.defineProperty(exports2, "__esModule", { value: true });
28586
+ exports2.executorOrder = executorOrder;
28587
+ exports2.foldPoolExecutors = foldPoolExecutors;
28588
+ exports2.addExecutor = addExecutor;
28589
+ exports2.removeExecutor = removeExecutor;
28590
+ var yaml_1 = require_dist3();
28591
+ function executorOrder(doc) {
28592
+ const map = doc.get("executors", true);
28593
+ if (!(0, yaml_1.isMap)(map))
28594
+ return [];
28595
+ return map.items.flatMap((pair) => (0, yaml_1.isScalar)(pair.key) ? [String(pair.key.value)] : []);
28596
+ }
28597
+ function executorsMap(doc) {
28598
+ const existing = doc.get("executors", true);
28599
+ if ((0, yaml_1.isMap)(existing))
28600
+ return existing;
28601
+ const created = doc.createNode({});
28602
+ doc.set("executors", created);
28603
+ return created;
28604
+ }
28605
+ function indexOf(map, id) {
28606
+ return map.items.findIndex((pair) => (0, yaml_1.isScalar)(pair.key) && pair.key.value === id);
28607
+ }
28608
+ function foldPoolExecutors(doc) {
28609
+ const pools = doc.get("pools", true);
28610
+ if (!(0, yaml_1.isSeq)(pools))
28611
+ return null;
28612
+ let named = null;
28613
+ for (const pool of pools.items) {
28614
+ if (!(0, yaml_1.isMap)(pool))
28615
+ continue;
28616
+ const value = pool.get("executor");
28617
+ if (typeof value === "string" && named === null)
28618
+ named = value;
28619
+ pool.delete("executor");
28620
+ }
28621
+ if (named === null)
28622
+ return null;
28623
+ const map = executorsMap(doc);
28624
+ const at = indexOf(map, named);
28625
+ if (at > 0) {
28626
+ const [pair] = map.items.splice(at, 1);
28627
+ map.items.unshift(pair);
28628
+ }
28629
+ return named;
28630
+ }
28631
+ function addExecutor(doc, id, block, options = {}) {
28632
+ const map = executorsMap(doc);
28633
+ const at = indexOf(map, id);
28634
+ if (at >= 0) {
28635
+ if (!options.first || at === 0)
28636
+ return "unchanged";
28637
+ const [pair2] = map.items.splice(at, 1);
28638
+ map.items.unshift(pair2);
28639
+ return "moved";
28640
+ }
28641
+ const pair = doc.createPair(id, block);
28642
+ if (options.first)
28643
+ map.items.unshift(pair);
28644
+ else
28645
+ map.items.push(pair);
28646
+ return "added";
28647
+ }
28648
+ function removeExecutor(doc, id) {
28649
+ const map = executorsMap(doc);
28650
+ const at = indexOf(map, id);
28651
+ if (at < 0)
28652
+ return "absent";
28653
+ if (map.items.length === 1)
28654
+ return "last";
28655
+ map.items.splice(at, 1);
28656
+ return "removed";
28657
+ }
28658
+ }
28659
+ });
28660
+
28661
+ // ../../packages/shuttle/dist/cli/executors.js
28662
+ var require_executors = __commonJS({
28663
+ "../../packages/shuttle/dist/cli/executors.js"(exports2) {
28664
+ "use strict";
28665
+ Object.defineProperty(exports2, "__esModule", { value: true });
28666
+ exports2.buildAddCommand = buildAddCommand;
28667
+ exports2.buildRemoveCommand = buildRemoveCommand;
28668
+ var fs_1 = require("fs");
28669
+ var commander_1 = require_commander();
28670
+ var yaml_1 = require_dist3();
28671
+ var apply_1 = require_apply();
28672
+ var loader_1 = require_loader();
28673
+ var executor_order_1 = require_executor_order();
28674
+ var preflight_1 = require_preflight();
28675
+ function target(brand, options) {
28676
+ const profile = (0, apply_1.resolveProfile)(brand, options.profile);
28677
+ try {
28678
+ const path = (0, loader_1.resolveConfigPath)({
28679
+ explicitPath: options.config,
28680
+ searchPaths: (0, apply_1.runConfigSearchPaths)(brand, profile),
28681
+ productId: brand.product.id
28682
+ });
28683
+ return { path, profile };
28684
+ } catch (err) {
28685
+ console.error(err instanceof Error ? err.message : String(err));
28686
+ console.error(`Run \`${brand.product.cliBinary} init\` first: it writes the config this edits.`);
28687
+ process.exit(1);
28688
+ }
28689
+ }
28690
+ function allowed(brand, id) {
28691
+ return brand.executorAllowlist[0] === "*" || brand.executorAllowlist.includes(id);
28692
+ }
28693
+ function orderLine(order) {
28694
+ return order.length === 1 ? `Every job runs on ${order[0]}.` : `A job that names no executor tries them in this order: ${order.join(", ")}. A job that names one runs on that one or fails.`;
28695
+ }
28696
+ function foldedLine(folded) {
28697
+ return folded === null ? [] : [`Your pools named ${folded} as their executor, a setting from before 0.34; it is first in the order instead now.`];
28698
+ }
28699
+ async function newFailures(brand, t, write, restore) {
28700
+ const paths = (0, apply_1.resolvePaths)(brand, t.profile);
28701
+ const run = () => (0, preflight_1.runPreflight)((0, preflight_1.createProbes)({
28702
+ brand,
28703
+ paths,
28704
+ configSearchPaths: (0, apply_1.runConfigSearchPaths)(brand, t.profile),
28705
+ explicitConfigPath: t.path
28706
+ }), { brand, paths, phase: "doctor", deep: false });
28707
+ const before = await run();
28708
+ write();
28709
+ try {
28710
+ const after = await run();
28711
+ const failedBefore = new Set(before.verdicts.filter((v) => v.status === "FAIL").map((v) => v.name));
28712
+ return after.verdicts.filter((v) => v.status === "FAIL" && !failedBefore.has(v.name));
28713
+ } catch (err) {
28714
+ restore();
28715
+ throw err;
28716
+ }
28717
+ }
28718
+ function buildAddCommand(brand) {
28719
+ const add = new commander_1.Command("add").description(`Add to this ${brand.product.title}'s setup`);
28720
+ add.command("executor <id>").description(`Add an executor this ${brand.product.title} may run jobs on`).option("--first", "Put it first in the order, so a job that names no executor tries it first").option("--tmp-dir <dir>", "Base directory for each job's throwaway checkout (agent executors)").option("--config <path>", "Path to the runtime YAML to edit").option("-p, --profile <name>", 'Config profile to edit (default "default")').action(async (id, options) => {
28721
+ if (!allowed(brand, id)) {
28722
+ console.error(`${id} is not an executor this build runs. It runs: ${brand.executorAllowlist.join(", ")}.`);
28723
+ process.exit(1);
28724
+ }
28725
+ const t = target(brand, options);
28726
+ const original = (0, fs_1.readFileSync)(t.path, "utf8");
28727
+ const doc = (0, yaml_1.parseDocument)(original);
28728
+ const folded = (0, executor_order_1.foldPoolExecutors)(doc);
28729
+ const block = brand.executorBlock?.(id, { ...options.tmpDir ? { tmpDir: options.tmpDir } : {} }) ?? { type: id };
28730
+ const outcome = (0, executor_order_1.addExecutor)(doc, id, block, { first: options.first === true });
28731
+ if (outcome === "unchanged" && folded === null) {
28732
+ console.log(`${id} is already set up. ${orderLine((0, executor_order_1.executorOrder)(doc))}`);
28733
+ return;
28734
+ }
28735
+ const write = () => (0, fs_1.writeFileSync)(t.path, doc.toString());
28736
+ const restore = () => (0, fs_1.writeFileSync)(t.path, original);
28737
+ if (outcome === "added") {
28738
+ const failures = await newFailures(brand, t, write, restore);
28739
+ if (failures.length > 0) {
28740
+ restore();
28741
+ (0, preflight_1.printReport)(failures, false);
28742
+ console.error("");
28743
+ console.error(`${id} was not added: it could not run jobs on this machine. Fix what is above, then run this again.`);
28744
+ process.exit(preflight_1.PREFLIGHT_EXIT_CODE);
28745
+ }
28746
+ } else {
28747
+ write();
28748
+ }
28749
+ const verb = outcome === "added" ? "Added" : outcome === "moved" ? "Moved" : "Kept";
28750
+ console.log(`${verb} ${id}${outcome === "moved" ? " to the front" : ""} in ${t.path}.`);
28751
+ for (const line of foldedLine(folded))
28752
+ console.log(line);
28753
+ console.log(orderLine((0, executor_order_1.executorOrder)(doc)));
28754
+ console.log(`Restart \`${brand.product.cliBinary} run\` for it to take effect.`);
28755
+ });
28756
+ return add;
28757
+ }
28758
+ function buildRemoveCommand(brand) {
28759
+ const remove = new commander_1.Command("remove").description(`Remove from this ${brand.product.title}'s setup`);
28760
+ remove.command("executor <id>").description(`Stop this ${brand.product.title} running jobs on an executor`).option("--config <path>", "Path to the runtime YAML to edit").option("-p, --profile <name>", 'Config profile to edit (default "default")').action((id, options) => {
28761
+ const t = target(brand, options);
28762
+ const doc = (0, yaml_1.parseDocument)((0, fs_1.readFileSync)(t.path, "utf8"));
28763
+ const folded = (0, executor_order_1.foldPoolExecutors)(doc);
28764
+ const outcome = (0, executor_order_1.removeExecutor)(doc, id);
28765
+ if (outcome === "last") {
28766
+ console.error(`${id} is the only executor set up, so it stays: with none, nothing could run.`);
28767
+ console.error(`Add another first with \`${brand.product.cliBinary} add executor <id>\`.`);
28768
+ process.exit(1);
28769
+ }
28770
+ if (outcome === "absent" && folded === null) {
28771
+ console.log(`${id} is not set up. ${orderLine((0, executor_order_1.executorOrder)(doc))}`);
28772
+ return;
28773
+ }
28774
+ (0, fs_1.writeFileSync)(t.path, doc.toString());
28775
+ if (outcome === "removed")
28776
+ console.log(`Removed ${id} from ${t.path}.`);
28777
+ for (const line of foldedLine(folded))
28778
+ console.log(line);
28779
+ console.log(orderLine((0, executor_order_1.executorOrder)(doc)));
28780
+ console.log(`Restart \`${brand.product.cliBinary} run\` for it to take effect.`);
28781
+ });
28782
+ return remove;
28783
+ }
28784
+ }
28785
+ });
28786
+
28359
28787
  // ../../packages/shuttle/dist/cli.js
28360
28788
  var require_cli = __commonJS({
28361
28789
  "../../packages/shuttle/dist/cli.js"(exports2) {
@@ -28371,6 +28799,7 @@ var require_cli = __commonJS({
28371
28799
  var caps_1 = require_caps();
28372
28800
  var list_1 = require_list();
28373
28801
  var revoke_1 = require_revoke();
28802
+ var executors_1 = require_executors();
28374
28803
  function createCli2(brand) {
28375
28804
  const program = new commander_1.Command();
28376
28805
  program.name(brand.product.cliBinary).description(brand.product.description).version(brand.product.version);
@@ -28382,6 +28811,8 @@ var require_cli = __commonJS({
28382
28811
  program.addCommand((0, caps_1.buildCapsCommand)(brand));
28383
28812
  program.addCommand((0, list_1.buildListCommand)(brand));
28384
28813
  program.addCommand((0, revoke_1.buildRevokeCommand)(brand));
28814
+ program.addCommand((0, executors_1.buildAddCommand)(brand));
28815
+ program.addCommand((0, executors_1.buildRemoveCommand)(brand));
28385
28816
  return program;
28386
28817
  }
28387
28818
  }
@@ -28392,8 +28823,8 @@ var require_dist5 = __commonJS({
28392
28823
  "../../packages/shuttle/dist/index.js"(exports2) {
28393
28824
  "use strict";
28394
28825
  Object.defineProperty(exports2, "__esModule", { value: true });
28395
- exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = exports2.renderEnvironmentReport = exports2.isWorthReporting = exports2.hostOf = exports2.executorDefaultsFor = exports2.environmentReportJson = exports2.environmentPolledBy = exports2.applyEnvironment = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createCopilotExecutor = exports2.createCodexExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorEnvironmentError = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.runPreflight = exports2.renderLines = exports2.renderJson = exports2.printReport = exports2.hasFailure = exports2.createProbes = exports2.checksFor = exports2.PREFLIGHT_FLOORS = exports2.PREFLIGHT_EXIT_CODE = exports2.CHECKS = exports2.readResponseLatestVersion = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.LATEST_VERSION_HEADER = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
28396
- exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = void 0;
28826
+ exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = exports2.renderEnvironmentReport = exports2.isWorthReporting = exports2.hostOf = exports2.executorDefaultsFor = exports2.environmentReportJson = exports2.environmentPolledBy = exports2.applyEnvironment = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createCopilotExecutor = exports2.createCodexExecutor = exports2.createClaudeCodeExecutor = exports2.MODEL_UNAVAILABLE = exports2.ExecutorModelUnavailableError = exports2.ExecutorEnvironmentError = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.runPreflight = exports2.renderLines = exports2.renderJson = exports2.printReport = exports2.hasFailure = exports2.createProbes = exports2.checksFor = exports2.PREFLIGHT_FLOORS = exports2.PREFLIGHT_EXIT_CODE = exports2.CHECKS = exports2.readResponseLatestVersion = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.LATEST_VERSION_HEADER = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
28827
+ exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = void 0;
28397
28828
  var cli_1 = require_cli();
28398
28829
  Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
28399
28830
  return cli_1.createCli;
@@ -28468,6 +28899,12 @@ var require_dist5 = __commonJS({
28468
28899
  Object.defineProperty(exports2, "ExecutorEnvironmentError", { enumerable: true, get: function() {
28469
28900
  return types_1.ExecutorEnvironmentError;
28470
28901
  } });
28902
+ Object.defineProperty(exports2, "ExecutorModelUnavailableError", { enumerable: true, get: function() {
28903
+ return types_1.ExecutorModelUnavailableError;
28904
+ } });
28905
+ Object.defineProperty(exports2, "MODEL_UNAVAILABLE", { enumerable: true, get: function() {
28906
+ return types_1.MODEL_UNAVAILABLE;
28907
+ } });
28471
28908
  var claude_code_1 = require_claude_code();
28472
28909
  Object.defineProperty(exports2, "createClaudeCodeExecutor", { enumerable: true, get: function() {
28473
28910
  return claude_code_1.createClaudeCodeExecutor;
@@ -28606,7 +29043,7 @@ var import_path = require("path");
28606
29043
  var import_promises = require("fs/promises");
28607
29044
  var import_yaml = __toESM(require_dist3());
28608
29045
  var import_shuttle = __toESM(require_dist5());
28609
- var buildVersion = true ? "0.33.3" : pkg.version;
29046
+ var buildVersion = true ? "0.34.0" : pkg.version;
28610
29047
  var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
28611
29048
  var sifterBrand = {
28612
29049
  product: {
@@ -28635,6 +29072,8 @@ var sifterBrand = {
28635
29072
  // sifter.yaml names one. The init pre-flight reads it too, to check git and
28636
29073
  // claude before the pairing code is spent.
28637
29074
  defaultExecutor: "claude-code",
29075
+ // The block a fresh executor gets, from `init` and from `add executor`.
29076
+ executorBlock: buildExecutorBlock,
28638
29077
  // Floors the pre-flight enforces on the binaries the claude-code executor
28639
29078
  // spawns. Both are deliberately conservative — below the floor FAILS, so a
28640
29079
  // number chosen for tidiness rather than necessity would refuse a working
@@ -28778,7 +29217,8 @@ var sifterBrand = {
28778
29217
  const executorBlock = plan.keptBlock !== void 0 ? {
28779
29218
  ...plan.keptBlock,
28780
29219
  ...AGENT_EXECUTORS.includes(executor) && ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
28781
- } : await buildExecutorBlock(executor, ctx);
29220
+ } : buildExecutorBlock(executor, { ...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {} });
29221
+ const { [executor]: _replaced, ...otherExecutors } = existingExecutors;
28782
29222
  const doc = {
28783
29223
  auth: {
28784
29224
  jobsApiUrl: pairing.jobsApiUrl
@@ -28789,12 +29229,11 @@ var sifterBrand = {
28789
29229
  },
28790
29230
  pools: pairing.jobPools.map((p) => ({
28791
29231
  id: p.id,
28792
- name: p.name,
28793
- executor
29232
+ name: p.name
28794
29233
  })),
28795
29234
  executors: {
28796
- ...existingExecutors,
28797
- [executor]: executorBlock
29235
+ [executor]: executorBlock,
29236
+ ...otherExecutors
28798
29237
  }
28799
29238
  };
28800
29239
  const pollIntervalMs = ctx.setupOptions.pollIntervalMs ?? existing.polling?.intervalMs;
@@ -28813,14 +29252,14 @@ var sifterBrand = {
28813
29252
  function overlayOrigin(origin) {
28814
29253
  return Object.fromEntries(AGENT_EXECUTORS.map((e) => [e, { overlayAllowedHosts: [origin] }]));
28815
29254
  }
28816
- async function buildExecutorBlock(executor, ctx) {
29255
+ function buildExecutorBlock(executor, options) {
28817
29256
  switch (executor) {
28818
29257
  case "claude-code":
28819
29258
  case "codex":
28820
29259
  case "copilot": {
28821
29260
  return {
28822
29261
  type: executor,
28823
- ...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
29262
+ ...options.tmpDir ? { tmpDir: options.tmpDir } : {}
28824
29263
  };
28825
29264
  }
28826
29265
  case "anthropic-api": {