@whittlelabs/sifter 0.33.3 → 0.35.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 +1167 -639
  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);
@@ -10984,6 +11072,7 @@ var require_claude_code = __commonJS({
10984
11072
  let finalText = null;
10985
11073
  const readPaths = [];
10986
11074
  let usage = null;
11075
+ const toolCalls = {};
10987
11076
  let resultMeta = null;
10988
11077
  let structuredOutput = null;
10989
11078
  let sessionId = null;
@@ -11011,6 +11100,8 @@ var require_claude_code = __commonJS({
11011
11100
  const b = block;
11012
11101
  if (b?.type !== "tool_use")
11013
11102
  continue;
11103
+ if (typeof b.name === "string")
11104
+ toolCalls[b.name] = (toolCalls[b.name] ?? 0) + 1;
11014
11105
  const fp = b.input?.file_path;
11015
11106
  if (b.name === "Read" && typeof fp === "string") {
11016
11107
  readPaths.push(fp);
@@ -11037,13 +11128,19 @@ var require_claude_code = __commonJS({
11037
11128
  }
11038
11129
  const u = event.usage;
11039
11130
  const cost = event.total_cost_usd;
11131
+ const turns = event.num_turns;
11040
11132
  usage = {
11041
11133
  ...typeof u?.input_tokens === "number" ? { inputTokens: u.input_tokens } : {},
11042
11134
  ...typeof u?.output_tokens === "number" ? { outputTokens: u.output_tokens } : {},
11043
- ...typeof cost === "number" ? { costUsd: cost } : {}
11135
+ ...typeof u?.cache_read_input_tokens === "number" ? { cacheReadInputTokens: u.cache_read_input_tokens } : {},
11136
+ ...typeof u?.cache_creation_input_tokens === "number" ? { cacheCreationInputTokens: u.cache_creation_input_tokens } : {},
11137
+ ...typeof cost === "number" ? { costUsd: cost } : {},
11138
+ ...typeof turns === "number" ? { turns } : {}
11044
11139
  };
11045
11140
  }
11046
11141
  }
11142
+ if (usage && Object.keys(toolCalls).length > 0)
11143
+ usage.toolCalls = toolCalls;
11047
11144
  return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, model, toolInputs };
11048
11145
  }
11049
11146
  }
@@ -11306,7 +11403,10 @@ var require_codex = __commonJS({
11306
11403
  loginRemedy: "run `codex login` and sign in (or `codex login --with-api-key`)",
11307
11404
  limitRemedy: "wait for the plan's usage limit to reset, or raise it",
11308
11405
  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
11406
+ outOfUsage: /usage limit|hit your (?:\w+ )?limit|insufficient_quota|exceeded your current quota|429 too many requests/i,
11407
+ // OpenAI's words for a model the key may not use, and codex's for one a
11408
+ // ChatGPT sign-in does not include.
11409
+ 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
11410
  };
11311
11411
  var READINESS_PROMPT = "Reply with the single word: ok";
11312
11412
  var READINESS_TIMEOUT_MS = 12e4;
@@ -11727,6 +11827,10 @@ var require_copilot = __commonJS({
11727
11827
  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
11828
  signedOut: /not (?:logged|signed) in|run `?copilot login|no authentication|authentication (?:failed|required)|bad credentials/i,
11729
11829
  outOfUsage: /exceeded your monthly quota|premium request|usage limit|hit your (?:\w+ )?limit|exceeded .*(?:allowance|limit)/i,
11830
+ // copilot 1.0.98 offers only the models the account may use, so a model
11831
+ // that does not exist and one a policy disables both read: Model "x" from
11832
+ // --model flag is not available.
11833
+ modelUnavailable: /model "?[^"\n]*"? (?:from --model flag )?is not available|model .*is not (?:enabled|supported)/i,
11730
11834
  notEntitled: {
11731
11835
  pattern: /access denied by policy|not authorized to use this copilot feature/i,
11732
11836
  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 +13239,7 @@ var require_anthropic_api = __commonJS({
13135
13239
  var keep_1 = require_dist2();
13136
13240
  var env_ref_1 = require_env_ref();
13137
13241
  var validate_output_1 = require_validate_output();
13242
+ var types_1 = require_types2();
13138
13243
  var models_1 = require_models();
13139
13244
  var DEFAULT_BASE_URL = "https://api.anthropic.com/v1/messages";
13140
13245
  var DEFAULT_MODEL = "claude-sonnet-4-6";
@@ -13251,7 +13356,11 @@ var require_anthropic_api = __commonJS({
13251
13356
  const apiErr = envelope?.error;
13252
13357
  if (apiErr?.message) {
13253
13358
  const kind = apiErr.type ? ` (${apiErr.type})` : "";
13254
- throw new Error(`Anthropic API error${kind}: ${apiErr.message} [HTTP ${response.status}]`);
13359
+ const message = `Anthropic API error${kind}: ${apiErr.message} [HTTP ${response.status}]`;
13360
+ if (response.status === 404 && apiErr.type === "not_found_error") {
13361
+ throw new types_1.ExecutorModelUnavailableError(message);
13362
+ }
13363
+ throw new Error(message);
13255
13364
  }
13256
13365
  throw new Error(`HTTP ${response.status}${bodyText ? `: ${bodyText}` : ""}`);
13257
13366
  }
@@ -13832,10 +13941,11 @@ var require_scaffold = __commonJS({
13832
13941
  function planScaffold2(brand, setupOptions, existing) {
13833
13942
  const pools = Array.isArray(existing.pools) ? existing.pools : [];
13834
13943
  const first = pools[0];
13835
- const prior = first !== null && typeof first === "object" && typeof first.executor === "string" ? first.executor : void 0;
13944
+ const pooled = first !== null && typeof first === "object" && typeof first.executor === "string" ? first.executor : void 0;
13945
+ const executors = existing.executors !== null && typeof existing.executors === "object" ? existing.executors : {};
13946
+ const prior = pooled ?? Object.keys(executors)[0];
13836
13947
  const executor = setupOptions.executor ?? prior ?? brand.defaultExecutor;
13837
13948
  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
13949
  const block = executor === void 0 ? void 0 : executors[executor];
13840
13950
  const keptBlock = setupOptions.executor === void 0 && block !== null && typeof block === "object" ? block : void 0;
13841
13951
  return { executor, source, keptBlock };
@@ -13910,7 +14020,7 @@ var require_checks = __commonJS({
13910
14020
  }
13911
14021
  function activeAgent(config) {
13912
14022
  const configured = configuredExecutors(config);
13913
- const pooled = config.pools.map((p) => p.executor).find((e) => AGENT_EXECUTORS2.includes(e) && configured.includes(e));
14023
+ const pooled = config.pools.map((p) => p.executor).find((e) => e !== void 0 && AGENT_EXECUTORS2.includes(e) && configured.includes(e));
13914
14024
  return pooled ?? configured.find((e) => AGENT_EXECUTORS2.includes(e)) ?? null;
13915
14025
  }
13916
14026
  function agentSettings(config, subject) {
@@ -22240,6 +22350,8 @@ var require_subscribe = __commonJS({
22240
22350
  return outcome;
22241
22351
  }
22242
22352
  var STOPPED = Symbol("stopped");
22353
+ var DECLINE_WINDOW = 25;
22354
+ var DECLINED_MEMORY = 1e3;
22243
22355
  function subscribe(opts) {
22244
22356
  const logger = opts.logger ?? NULL_LOGGER;
22245
22357
  const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
@@ -22257,6 +22369,16 @@ var require_subscribe = __commonJS({
22257
22369
  let wakeStartup = null;
22258
22370
  let unavailableSince = null;
22259
22371
  let rateLimited = null;
22372
+ const accepts = opts.accepts;
22373
+ const declined = /* @__PURE__ */ new Set();
22374
+ function noteDeclined(jobId) {
22375
+ if (declined.has(jobId))
22376
+ return;
22377
+ if (declined.size >= DECLINED_MEMORY)
22378
+ declined.clear();
22379
+ declined.add(jobId);
22380
+ logger.debug?.("declined a job this subscriber cannot run; it stays queued for another member", { jobId });
22381
+ }
22260
22382
  function noteReachable() {
22261
22383
  if (unavailableSince !== null) {
22262
22384
  logger.info(`Sift is reachable again after ${formatDuration(Date.now() - unavailableSince)}`);
@@ -22422,9 +22544,10 @@ var require_subscribe = __commonJS({
22422
22544
  }
22423
22545
  try {
22424
22546
  const paused = holds.size > 0;
22547
+ const free = maxConcurrency - inflight;
22425
22548
  const { data, directives } = await opts.jobsClient.listDispatchableJobs({
22426
22549
  pools: resolvedPoolIds,
22427
- limit: paused ? 1 : maxConcurrency - inflight,
22550
+ limit: paused ? 1 : accepts ? Math.max(free, DECLINE_WINDOW) : free,
22428
22551
  sort: "priority:desc,queued_at:asc"
22429
22552
  });
22430
22553
  noteReachable();
@@ -22447,6 +22570,10 @@ var require_subscribe = __commonJS({
22447
22570
  for (const job of data) {
22448
22571
  if (inflight >= maxConcurrency)
22449
22572
  break;
22573
+ if (accepts && !accepts(job)) {
22574
+ noteDeclined(job.id);
22575
+ continue;
22576
+ }
22450
22577
  inflight++;
22451
22578
  void runOnce(job).finally(() => {
22452
22579
  inflight--;
@@ -22542,7 +22669,8 @@ var require_subscribe = __commonJS({
22542
22669
  }
22543
22670
  await opts.jobsClient.submit(dispatch.job.id, {
22544
22671
  status: result.status,
22545
- ...result.reason !== void 0 ? { reason: result.reason } : {}
22672
+ ...result.reason !== void 0 ? { reason: result.reason } : {},
22673
+ ...result.status === "failed" && result.failureCode !== void 0 ? { failureCode: result.failureCode } : {}
22546
22674
  });
22547
22675
  } catch (err) {
22548
22676
  if (isFatal(err)) {
@@ -22664,7 +22792,12 @@ var require_schema4 = __commonJS({
22664
22792
  name: zod_1.z.string().optional(),
22665
22793
  tags: zod_1.z.array(zod_1.z.string()).optional(),
22666
22794
  minPriority: zod_1.z.number().int().optional(),
22667
- executor: zod_1.z.string()
22795
+ /**
22796
+ * Deprecated (whittlelabs ADR 0118): a Sifter chooses the executor for
22797
+ * each job from `executors`, in their order. A pool that still names one,
22798
+ * as configs before 0.34 did, tries it first for its own jobs.
22799
+ */
22800
+ executor: zod_1.z.string().optional()
22668
22801
  }).refine((pool) => pool.id !== void 0 || pool.name !== void 0, {
22669
22802
  message: 'Each pool must specify at least one of "id" or "name"'
22670
22803
  });
@@ -22704,6 +22837,11 @@ var require_schema4 = __commonJS({
22704
22837
  concurrency: zod_1.z.object({
22705
22838
  maxJobs: zod_1.z.number().int().positive().default(defaults_1.DEFAULTS.concurrency.maxJobs)
22706
22839
  }).default(defaults_1.DEFAULTS.concurrency),
22840
+ /**
22841
+ * The executors this runner may run jobs on, keyed by id. The order they
22842
+ * are written in is the order a job tries them (ADR 0118): a job that
22843
+ * requires none runs on the first that can run jobs on this machine.
22844
+ */
22707
22845
  executors: zod_1.z.record(zod_1.z.string(), executorSchema),
22708
22846
  logging: zod_1.z.object({
22709
22847
  level: zod_1.z.enum(["debug", "info", "warn", "error"]).default(defaults_1.DEFAULTS.logging.level),
@@ -22719,6 +22857,7 @@ var require_loader = __commonJS({
22719
22857
  "../../packages/shuttle/dist/config/loader.js"(exports2) {
22720
22858
  "use strict";
22721
22859
  Object.defineProperty(exports2, "__esModule", { value: true });
22860
+ exports2.resolveConfigPath = resolveConfigPath;
22722
22861
  exports2.loadConfig = loadConfig;
22723
22862
  var fs_1 = require("fs");
22724
22863
  var path_1 = require("path");
@@ -25994,174 +26133,425 @@ var require_chain = __commonJS({
25994
26133
  }
25995
26134
  });
25996
26135
 
25997
- // ../../packages/shuttle/dist/workspace/git.js
25998
- var require_git = __commonJS({
25999
- "../../packages/shuttle/dist/workspace/git.js"(exports2) {
26136
+ // ../../packages/shuttle/dist/executors/output-gates.js
26137
+ var require_output_gates = __commonJS({
26138
+ "../../packages/shuttle/dist/executors/output-gates.js"(exports2) {
26000
26139
  "use strict";
26001
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
26002
- return mod && mod.__esModule ? mod : { "default": mod };
26003
- };
26004
26140
  Object.defineProperty(exports2, "__esModule", { value: true });
26005
- exports2.gitMaterializer = void 0;
26006
- exports2.buildGitAuthEnv = buildGitAuthEnv;
26007
- var child_process_1 = require("child_process");
26008
- var fs_1 = require("fs");
26009
- var path_1 = __importDefault(require("path"));
26010
- var DEFAULT_GIT_TIMEOUT_MS = 12e4;
26011
- var DEFAULT_GIT_PROTOCOLS = "https:ssh:git";
26012
- var DEFAULT_MAX_CONTEXT_REPOS = 5;
26013
- exports2.gitMaterializer = {
26014
- namespace: "git",
26015
- materialize: materializeGit
26016
- };
26017
- async function materializeGit(request, runRoot, options) {
26018
- const resolvedPrimary = readGitWorkspace(request);
26019
- const primaryDir = path_1.default.join(runRoot, "primary");
26020
- await fs_1.promises.mkdir(primaryDir, { recursive: true });
26021
- if ("warning" in resolvedPrimary) {
26022
- return { materialized: false, warning: resolvedPrimary.warning, context: [] };
26023
- }
26024
- const primary = resolvedPrimary.git;
26025
- const timeoutMs = options.commandTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
26026
- const allowedProtocols = options.allowedProtocols ?? DEFAULT_GIT_PROTOCOLS;
26027
- const signal = options.signal;
26028
- const maxContext = options.maxContextRepos ?? DEFAULT_MAX_CONTEXT_REPOS;
26029
- const contextPlan = planContextRepos(primary.context, maxContext);
26030
- const ref = primary.fetchCredential;
26031
- let gitToken = null;
26032
- if (ref && typeof ref.name === "string" && typeof ref.principal === "string" && options.credentials) {
26033
- try {
26034
- gitToken = await options.credentials.reveal(ref);
26035
- } catch {
26036
- gitToken = null;
26037
- }
26038
- }
26039
- const authEnv = gitToken !== null ? buildGitAuthEnv([primary.remote, ...contextPlan.materialize.map((c) => c.git.remote)], gitToken) : void 0;
26040
- const git = (args, cwd) => runGit(args, cwd, { timeoutMs, signal, allowedProtocols, extraEnv: authEnv });
26041
- const primaryResult = await materializeCommit(primary, primaryDir, git);
26042
- if (!primaryResult.ok) {
26043
- await fs_1.promises.rm(primaryDir, { recursive: true, force: true }).catch(() => {
26044
- });
26045
- await fs_1.promises.mkdir(primaryDir).catch(() => {
26046
- });
26047
- return { materialized: false, warning: primaryResult.warning, context: [] };
26048
- }
26049
- const context = [];
26050
- for (const planned of contextPlan.skip) {
26051
- context.push({ ...planned, materialized: false });
26141
+ exports2.isSanctionedPaint = isSanctionedPaint;
26142
+ exports2.unsanctionedPaints = unsanctionedPaints;
26143
+ exports2.resolvePath = resolvePath;
26144
+ exports2.evaluateOutputGates = evaluateOutputGates;
26145
+ exports2.renderRepairPrompt = renderRepairPrompt;
26146
+ var INPUT_REF = "input:";
26147
+ var COLOR_BEARING = ["fill", "stroke", "color", "stop-color", "flood-color", "lighting-color"];
26148
+ var INERT = /* @__PURE__ */ new Set(["none", "inherit", "initial", "unset", "transparent", "currentcolor"]);
26149
+ function isSanctionedPaint(raw) {
26150
+ const value = raw.trim().toLowerCase();
26151
+ if (value === "")
26152
+ return true;
26153
+ if (INERT.has(value))
26154
+ return true;
26155
+ if (/^url\(\s*['"]?#[^)'"]+['"]?\s*\)$/.test(value))
26156
+ return true;
26157
+ const varMatch = /^var\(\s*(--[a-z0-9-]+)\s*(?:,([\s\S]*))?\)$/.exec(value);
26158
+ if (!varMatch)
26159
+ return false;
26160
+ const [, name, fallback] = varMatch;
26161
+ if (!name.startsWith("--color-"))
26162
+ return false;
26163
+ return fallback === void 0 || isSanctionedPaint(fallback);
26164
+ }
26165
+ function unsanctionedPaints(source) {
26166
+ const found = [];
26167
+ const attrs = COLOR_BEARING.join("|");
26168
+ const attribute = new RegExp(`\\b(?:${attrs})\\s*=\\s*(['"])([\\s\\S]*?)\\1`, "gi");
26169
+ const declaration = new RegExp(`\\b(?:${attrs})\\s*:\\s*([^;"'}\\n]+)`, "gi");
26170
+ for (const [, , value] of source.matchAll(attribute)) {
26171
+ if (!isSanctionedPaint(value))
26172
+ found.push(value.trim());
26052
26173
  }
26053
- if (contextPlan.materialize.length > 0) {
26054
- const contextRoot = path_1.default.join(runRoot, "context");
26055
- await fs_1.promises.mkdir(contextRoot, { recursive: true });
26056
- for (const entry of contextPlan.materialize) {
26057
- const dir = path_1.default.join(contextRoot, entry.name);
26058
- await fs_1.promises.mkdir(dir);
26059
- const result = await materializeCommit(entry.git, dir, git);
26060
- context.push({
26061
- name: entry.name,
26062
- requestedName: entry.requestedName,
26063
- materialized: result.ok,
26064
- ...result.ok ? {} : { warning: result.warning }
26065
- });
26066
- }
26174
+ for (const [, value] of source.matchAll(declaration)) {
26175
+ if (!isSanctionedPaint(value))
26176
+ found.push(value.trim());
26067
26177
  }
26068
- context.sort((a, b) => contextOrder(contextPlan, a.requestedName) - contextOrder(contextPlan, b.requestedName));
26069
- return { materialized: true, primaryDir, context };
26178
+ return found;
26070
26179
  }
26071
- function planContextRepos(raw, maxContext) {
26072
- const plan = { materialize: [], skip: [], order: /* @__PURE__ */ new Map() };
26073
- if (raw === void 0)
26074
- return plan;
26075
- if (!Array.isArray(raw))
26076
- return plan;
26077
- const usedNames = /* @__PURE__ */ new Set();
26078
- raw.forEach((entry, index) => {
26079
- const requestedName = entry && typeof entry.name === "string" ? entry.name : `context-${index}`;
26080
- plan.order.set(requestedName, index);
26081
- const resolved = readContextRepo(entry, index);
26082
- if ("warning" in resolved) {
26083
- plan.skip.push({ name: sanitizeMountName(requestedName, usedNames), requestedName, warning: resolved.warning });
26084
- return;
26085
- }
26086
- if (plan.materialize.length >= maxContext) {
26087
- plan.skip.push({
26088
- name: sanitizeMountName(requestedName, usedNames),
26089
- requestedName,
26090
- warning: `skipped: exceeds the ${maxContext}-context-repo cap`
26091
- });
26092
- return;
26180
+ function resolvePath(root, path) {
26181
+ let ctx = root;
26182
+ for (const seg of path.split(".")) {
26183
+ const project = seg.endsWith("[*]");
26184
+ const key = project ? seg.slice(0, -3) : seg;
26185
+ if (Array.isArray(ctx)) {
26186
+ ctx = ctx.map((el) => key ? el?.[key] : el);
26187
+ } else {
26188
+ ctx = key ? ctx?.[key] : ctx;
26093
26189
  }
26094
- plan.materialize.push({
26095
- git: resolved.git,
26096
- name: sanitizeMountName(requestedName, usedNames),
26097
- requestedName
26098
- });
26099
- });
26100
- return plan;
26101
- }
26102
- function contextOrder(plan, requestedName) {
26103
- return plan.order.get(requestedName) ?? Number.MAX_SAFE_INTEGER;
26104
- }
26105
- async function materializeCommit(gitWorkspace, dir, git) {
26106
- const hasCommit = () => git(["cat-file", "-e", `${gitWorkspace.commit}^{commit}`], dir).then((r) => r.code === 0);
26107
- const init = await git(["init", "-q"], dir);
26108
- if (init.code !== 0)
26109
- return { ok: false, warning: `git init failed: ${firstLine(init.stderr)}` };
26110
- const attempts = [];
26111
- for (const ref of gitWorkspace.fetchRefs ?? []) {
26112
- attempts.push({
26113
- source: `${gitWorkspace.remote} ${ref}`,
26114
- args: ["fetch", "--no-tags", "--depth=1", gitWorkspace.remote, ref]
26115
- });
26116
- }
26117
- attempts.push({
26118
- source: `${gitWorkspace.remote} commit`,
26119
- args: ["fetch", "--no-tags", "--depth=1", gitWorkspace.remote, gitWorkspace.commit]
26120
- });
26121
- const failures = [];
26122
- let present = await hasCommit();
26123
- for (const { source, args } of attempts) {
26124
- if (present)
26125
- break;
26126
- const result = await git(args, dir);
26127
- if (result.code !== 0) {
26128
- failures.push(`${source}: ${firstLine(result.stderr)}`);
26129
- continue;
26190
+ if (project && !Array.isArray(ctx)) {
26191
+ ctx = ctx == null ? [] : [ctx];
26130
26192
  }
26131
- present = await hasCommit();
26132
- if (!present)
26133
- failures.push(`${source}: fetch succeeded but ${gitWorkspace.commit} is still absent`);
26134
- }
26135
- if (!present) {
26136
- return {
26137
- ok: false,
26138
- warning: `could not materialize ${gitWorkspace.commit} from ${gitWorkspace.remote}` + (failures.length > 0 ? ` (${failures.join("; ")})` : "")
26139
- };
26140
26193
  }
26141
- const checkout = await git(["checkout", "--detach", gitWorkspace.commit], dir);
26142
- if (checkout.code !== 0)
26143
- return { ok: false, warning: `git checkout failed: ${firstLine(checkout.stderr)}` };
26144
- return { ok: true };
26194
+ return ctx;
26145
26195
  }
26146
- function buildGitAuthEnv(remotes, token) {
26147
- const origins = [];
26148
- for (const remote of remotes) {
26149
- try {
26150
- const url = new URL(remote);
26151
- if (url.protocol !== "https:")
26152
- continue;
26153
- const origin = `${url.protocol}//${url.host}/`;
26154
- if (!origins.includes(origin))
26155
- origins.push(origin);
26156
- } catch {
26157
- continue;
26158
- }
26196
+ function resolveRef(ref, output, inputs) {
26197
+ if (ref.startsWith(INPUT_REF)) {
26198
+ const label = ref.slice(INPUT_REF.length);
26199
+ const row = inputs.find((i) => i.label === label) ?? inputs.find((i) => i.label === "artifacts" && i.payload.label === label);
26200
+ if (!row)
26201
+ return void 0;
26202
+ return Object.prototype.hasOwnProperty.call(row.payload, "value") ? row.payload.value : row.payload;
26159
26203
  }
26160
- if (origins.length === 0)
26161
- return void 0;
26162
- const basic = Buffer.from(`x-access-token:${token}`).toString("base64");
26163
- const env = { GIT_CONFIG_COUNT: String(origins.length) };
26164
- origins.forEach((origin, i) => {
26204
+ return resolvePath(output, ref);
26205
+ }
26206
+ function asStrings(value) {
26207
+ if (Array.isArray(value))
26208
+ return value.map((v) => String(v));
26209
+ if (value == null)
26210
+ return [];
26211
+ return [String(value)];
26212
+ }
26213
+ var PRIMITIVES = {
26214
+ /**
26215
+ * `coverage` — the values at `got` must exactly cover the set at `want`:
26216
+ * every wanted value present, nothing invented, none repeated. Params:
26217
+ * `{ got: <output path>, want: <ref>, subject?: <noun> }`.
26218
+ */
26219
+ coverage(params, output, inputs) {
26220
+ const subject = typeof params.subject === "string" ? params.subject : "item";
26221
+ const got = asStrings(resolveRef(String(params.got), output, inputs));
26222
+ const want = asStrings(resolveRef(String(params.want), output, inputs));
26223
+ const gotSet = new Set(got);
26224
+ const wantSet = new Set(want);
26225
+ const missing = want.filter((v) => !gotSet.has(v));
26226
+ const unknown = [...new Set(got.filter((v) => !wantSet.has(v)))];
26227
+ const seen = /* @__PURE__ */ new Set();
26228
+ const duplicated = [];
26229
+ for (const v of got) {
26230
+ if (seen.has(v))
26231
+ duplicated.push(v);
26232
+ else
26233
+ seen.add(v);
26234
+ }
26235
+ if (missing.length === 0 && unknown.length === 0 && duplicated.length === 0)
26236
+ return [];
26237
+ const parts = [];
26238
+ if (missing.length)
26239
+ parts.push(`missing ${subject}(s): [${missing.join(", ")}]`);
26240
+ if (unknown.length)
26241
+ parts.push(`not a known ${subject}: [${unknown.join(", ")}]`);
26242
+ if (duplicated.length)
26243
+ parts.push(`assessed more than once: [${[...new Set(duplicated)].join(", ")}]`);
26244
+ return [
26245
+ `every ${subject} must be covered exactly once \u2014 ${parts.join("; ")}. Return one entry per ${subject}, using its exact id.`
26246
+ ];
26247
+ },
26248
+ /**
26249
+ * `non_empty_when` — the array at `path` must be non-empty, optionally only
26250
+ * when `when.path` equals `when.equals`. Params:
26251
+ * `{ path: <output path>, when?: { path: <output path>, equals: <value> } }`.
26252
+ */
26253
+ non_empty_when(params, output) {
26254
+ const when = params.when;
26255
+ if (when && typeof when.path === "string") {
26256
+ if (resolvePath(output, when.path) !== when.equals)
26257
+ return [];
26258
+ }
26259
+ const path = String(params.path);
26260
+ const value = resolvePath(output, path);
26261
+ if (Array.isArray(value) && value.length > 0)
26262
+ return [];
26263
+ const cond = when && typeof when.path === "string" ? ` when ${when.path} is ${JSON.stringify(when.equals)}` : "";
26264
+ return [
26265
+ `'${path}' must be a non-empty array${cond}; it was ${Array.isArray(value) ? "empty" : "absent"}.`
26266
+ ];
26267
+ },
26268
+ /**
26269
+ * `bounded_palette` — every colour in the sources at `path` must be named
26270
+ * by role rather than written as a value. Params:
26271
+ * `{ path: <output path>, roles?: <role names for the repair turn> }`.
26272
+ *
26273
+ * A drawing the model authors is markup the house did not write, and a
26274
+ * literal in it is a colour outside the consuming palette: unproved
26275
+ * against the colours beside it, unchecked for contrast on the ground it
26276
+ * lands on, and fixed to one theme. The consumer strips such a paint at
26277
+ * render, so an artifact that carries one silently loses it. Catching it
26278
+ * HERE is what makes that a repair the model can make while it still has
26279
+ * the drawing in hand, instead of a defect nobody sees until the picture
26280
+ * comes out plain.
26281
+ */
26282
+ bounded_palette(params, output) {
26283
+ const path = String(params.path);
26284
+ const value = resolvePath(output, path);
26285
+ const sources = (Array.isArray(value) ? value : [value]).filter((v) => typeof v === "string" && v.length > 0);
26286
+ const offenders = [...new Set(sources.flatMap(unsanctionedPaints))];
26287
+ if (offenders.length === 0)
26288
+ return [];
26289
+ const roles = Array.isArray(params.roles) ? params.roles.map(String) : [];
26290
+ const vocabulary = roles.length > 0 ? ` Use one of: ${roles.join(", ")}.` : "";
26291
+ return [
26292
+ `'${path}' states ${offenders.length} colour value${offenders.length === 1 ? "" : "s"} directly (${offenders.slice(0, 8).join(", ")}${offenders.length > 8 ? ", \u2026" : ""}). Every colour must be named by role \u2014 var(--color-\u2026), currentColor, or none \u2014 never as a hex, rgb(), hsl() or colour name, because a value cannot adapt to the reader's theme and was never checked against the colours beside it.${vocabulary} Redraw with roles and return the whole output again.`
26293
+ ];
26294
+ }
26295
+ };
26296
+ function evaluateOutputGates(gates, output, inputs) {
26297
+ const findings = [];
26298
+ for (const gate of gates) {
26299
+ const primitive = PRIMITIVES[gate.primitive];
26300
+ if (!primitive) {
26301
+ findings.push({ gate: gate.name, message: `unknown gate primitive '${gate.primitive}'` });
26302
+ continue;
26303
+ }
26304
+ for (const message of primitive(gate.params, output, inputs)) {
26305
+ findings.push({ gate: gate.name, message });
26306
+ }
26307
+ }
26308
+ return { ok: findings.length === 0, findings };
26309
+ }
26310
+ function renderRepairPrompt(findings) {
26311
+ return [
26312
+ "Your previous answer did not satisfy these output checks. Return the corrected, complete answer that resolves every item below. Produce the same output shape with the problems fixed, and nothing else \u2014 no commentary, no explanation of the changes.",
26313
+ "",
26314
+ ...findings.map((f) => `- [${f.gate}] ${f.message}`)
26315
+ ].join("\n");
26316
+ }
26317
+ }
26318
+ });
26319
+
26320
+ // ../../packages/shuttle/dist/logging/logger.js
26321
+ var require_logger = __commonJS({
26322
+ "../../packages/shuttle/dist/logging/logger.js"(exports2) {
26323
+ "use strict";
26324
+ Object.defineProperty(exports2, "__esModule", { value: true });
26325
+ exports2.createLogger = createLogger;
26326
+ exports2.setGlobalLogger = setGlobalLogger;
26327
+ exports2.getLogger = getLogger;
26328
+ var LEVEL_ORDER = {
26329
+ debug: 0,
26330
+ info: 1,
26331
+ warn: 2,
26332
+ error: 3
26333
+ };
26334
+ function formatContext(context) {
26335
+ return Object.entries(context).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ");
26336
+ }
26337
+ function formatPretty(level, message, context) {
26338
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
26339
+ const tag = level.toUpperCase().padEnd(5);
26340
+ const base = `[${timestamp}] ${tag} ${message}`;
26341
+ if (context && Object.keys(context).length > 0) {
26342
+ return `${base} ${formatContext(context)}`;
26343
+ }
26344
+ return base;
26345
+ }
26346
+ function formatJson(level, message, context) {
26347
+ const entry = {
26348
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
26349
+ level,
26350
+ message,
26351
+ ...context
26352
+ };
26353
+ return JSON.stringify(entry);
26354
+ }
26355
+ function createLogger(options) {
26356
+ const threshold = LEVEL_ORDER[options.level];
26357
+ const format = options.format === "json" ? formatJson : formatPretty;
26358
+ function emit(level, message, context) {
26359
+ if (LEVEL_ORDER[level] < threshold)
26360
+ return;
26361
+ const line = format(level, message, context);
26362
+ if (level === "error") {
26363
+ console.error(line);
26364
+ } else if (level === "warn") {
26365
+ console.warn(line);
26366
+ } else {
26367
+ console.log(line);
26368
+ }
26369
+ }
26370
+ return {
26371
+ debug: (message, context) => emit("debug", message, context),
26372
+ info: (message, context) => emit("info", message, context),
26373
+ warn: (message, context) => emit("warn", message, context),
26374
+ error: (message, context) => emit("error", message, context)
26375
+ };
26376
+ }
26377
+ var globalLogger = createLogger({ level: "info", format: "pretty" });
26378
+ function setGlobalLogger(logger) {
26379
+ globalLogger = logger;
26380
+ }
26381
+ function getLogger() {
26382
+ return globalLogger;
26383
+ }
26384
+ }
26385
+ });
26386
+
26387
+ // ../../packages/shuttle/dist/workspace/git.js
26388
+ var require_git = __commonJS({
26389
+ "../../packages/shuttle/dist/workspace/git.js"(exports2) {
26390
+ "use strict";
26391
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
26392
+ return mod && mod.__esModule ? mod : { "default": mod };
26393
+ };
26394
+ Object.defineProperty(exports2, "__esModule", { value: true });
26395
+ exports2.gitMaterializer = void 0;
26396
+ exports2.buildGitAuthEnv = buildGitAuthEnv;
26397
+ var child_process_1 = require("child_process");
26398
+ var fs_1 = require("fs");
26399
+ var path_1 = __importDefault(require("path"));
26400
+ var DEFAULT_GIT_TIMEOUT_MS = 12e4;
26401
+ var DEFAULT_GIT_PROTOCOLS = "https:ssh:git";
26402
+ var DEFAULT_MAX_CONTEXT_REPOS = 5;
26403
+ exports2.gitMaterializer = {
26404
+ namespace: "git",
26405
+ materialize: materializeGit
26406
+ };
26407
+ async function materializeGit(request, runRoot, options) {
26408
+ const resolvedPrimary = readGitWorkspace(request);
26409
+ const primaryDir = path_1.default.join(runRoot, "primary");
26410
+ await fs_1.promises.mkdir(primaryDir, { recursive: true });
26411
+ if ("warning" in resolvedPrimary) {
26412
+ return { materialized: false, warning: resolvedPrimary.warning, context: [] };
26413
+ }
26414
+ const primary = resolvedPrimary.git;
26415
+ const timeoutMs = options.commandTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
26416
+ const allowedProtocols = options.allowedProtocols ?? DEFAULT_GIT_PROTOCOLS;
26417
+ const signal = options.signal;
26418
+ const maxContext = options.maxContextRepos ?? DEFAULT_MAX_CONTEXT_REPOS;
26419
+ const contextPlan = planContextRepos(primary.context, maxContext);
26420
+ const ref = primary.fetchCredential;
26421
+ let gitToken = null;
26422
+ if (ref && typeof ref.name === "string" && typeof ref.principal === "string" && options.credentials) {
26423
+ try {
26424
+ gitToken = await options.credentials.reveal(ref);
26425
+ } catch {
26426
+ gitToken = null;
26427
+ }
26428
+ }
26429
+ const authEnv = gitToken !== null ? buildGitAuthEnv([primary.remote, ...contextPlan.materialize.map((c) => c.git.remote)], gitToken) : void 0;
26430
+ const git = (args, cwd) => runGit(args, cwd, { timeoutMs, signal, allowedProtocols, extraEnv: authEnv });
26431
+ const primaryResult = await materializeCommit(primary, primaryDir, git);
26432
+ if (!primaryResult.ok) {
26433
+ await fs_1.promises.rm(primaryDir, { recursive: true, force: true }).catch(() => {
26434
+ });
26435
+ await fs_1.promises.mkdir(primaryDir).catch(() => {
26436
+ });
26437
+ return { materialized: false, warning: primaryResult.warning, context: [] };
26438
+ }
26439
+ const context = [];
26440
+ for (const planned of contextPlan.skip) {
26441
+ context.push({ ...planned, materialized: false });
26442
+ }
26443
+ if (contextPlan.materialize.length > 0) {
26444
+ const contextRoot = path_1.default.join(runRoot, "context");
26445
+ await fs_1.promises.mkdir(contextRoot, { recursive: true });
26446
+ for (const entry of contextPlan.materialize) {
26447
+ const dir = path_1.default.join(contextRoot, entry.name);
26448
+ await fs_1.promises.mkdir(dir);
26449
+ const result = await materializeCommit(entry.git, dir, git);
26450
+ context.push({
26451
+ name: entry.name,
26452
+ requestedName: entry.requestedName,
26453
+ materialized: result.ok,
26454
+ ...result.ok ? {} : { warning: result.warning }
26455
+ });
26456
+ }
26457
+ }
26458
+ context.sort((a, b) => contextOrder(contextPlan, a.requestedName) - contextOrder(contextPlan, b.requestedName));
26459
+ return { materialized: true, primaryDir, context };
26460
+ }
26461
+ function planContextRepos(raw, maxContext) {
26462
+ const plan = { materialize: [], skip: [], order: /* @__PURE__ */ new Map() };
26463
+ if (raw === void 0)
26464
+ return plan;
26465
+ if (!Array.isArray(raw))
26466
+ return plan;
26467
+ const usedNames = /* @__PURE__ */ new Set();
26468
+ raw.forEach((entry, index) => {
26469
+ const requestedName = entry && typeof entry.name === "string" ? entry.name : `context-${index}`;
26470
+ plan.order.set(requestedName, index);
26471
+ const resolved = readContextRepo(entry, index);
26472
+ if ("warning" in resolved) {
26473
+ plan.skip.push({ name: sanitizeMountName(requestedName, usedNames), requestedName, warning: resolved.warning });
26474
+ return;
26475
+ }
26476
+ if (plan.materialize.length >= maxContext) {
26477
+ plan.skip.push({
26478
+ name: sanitizeMountName(requestedName, usedNames),
26479
+ requestedName,
26480
+ warning: `skipped: exceeds the ${maxContext}-context-repo cap`
26481
+ });
26482
+ return;
26483
+ }
26484
+ plan.materialize.push({
26485
+ git: resolved.git,
26486
+ name: sanitizeMountName(requestedName, usedNames),
26487
+ requestedName
26488
+ });
26489
+ });
26490
+ return plan;
26491
+ }
26492
+ function contextOrder(plan, requestedName) {
26493
+ return plan.order.get(requestedName) ?? Number.MAX_SAFE_INTEGER;
26494
+ }
26495
+ async function materializeCommit(gitWorkspace, dir, git) {
26496
+ const hasCommit = () => git(["cat-file", "-e", `${gitWorkspace.commit}^{commit}`], dir).then((r) => r.code === 0);
26497
+ const init = await git(["init", "-q"], dir);
26498
+ if (init.code !== 0)
26499
+ return { ok: false, warning: `git init failed: ${firstLine(init.stderr)}` };
26500
+ const attempts = [];
26501
+ for (const ref of gitWorkspace.fetchRefs ?? []) {
26502
+ attempts.push({
26503
+ source: `${gitWorkspace.remote} ${ref}`,
26504
+ args: ["fetch", "--no-tags", "--depth=1", gitWorkspace.remote, ref]
26505
+ });
26506
+ }
26507
+ attempts.push({
26508
+ source: `${gitWorkspace.remote} commit`,
26509
+ args: ["fetch", "--no-tags", "--depth=1", gitWorkspace.remote, gitWorkspace.commit]
26510
+ });
26511
+ const failures = [];
26512
+ let present = await hasCommit();
26513
+ for (const { source, args } of attempts) {
26514
+ if (present)
26515
+ break;
26516
+ const result = await git(args, dir);
26517
+ if (result.code !== 0) {
26518
+ failures.push(`${source}: ${firstLine(result.stderr)}`);
26519
+ continue;
26520
+ }
26521
+ present = await hasCommit();
26522
+ if (!present)
26523
+ failures.push(`${source}: fetch succeeded but ${gitWorkspace.commit} is still absent`);
26524
+ }
26525
+ if (!present) {
26526
+ return {
26527
+ ok: false,
26528
+ warning: `could not materialize ${gitWorkspace.commit} from ${gitWorkspace.remote}` + (failures.length > 0 ? ` (${failures.join("; ")})` : "")
26529
+ };
26530
+ }
26531
+ const checkout = await git(["checkout", "--detach", gitWorkspace.commit], dir);
26532
+ if (checkout.code !== 0)
26533
+ return { ok: false, warning: `git checkout failed: ${firstLine(checkout.stderr)}` };
26534
+ return { ok: true };
26535
+ }
26536
+ function buildGitAuthEnv(remotes, token) {
26537
+ const origins = [];
26538
+ for (const remote of remotes) {
26539
+ try {
26540
+ const url = new URL(remote);
26541
+ if (url.protocol !== "https:")
26542
+ continue;
26543
+ const origin = `${url.protocol}//${url.host}/`;
26544
+ if (!origins.includes(origin))
26545
+ origins.push(origin);
26546
+ } catch {
26547
+ continue;
26548
+ }
26549
+ }
26550
+ if (origins.length === 0)
26551
+ return void 0;
26552
+ const basic = Buffer.from(`x-access-token:${token}`).toString("base64");
26553
+ const env = { GIT_CONFIG_COUNT: String(origins.length) };
26554
+ origins.forEach((origin, i) => {
26165
26555
  env[`GIT_CONFIG_KEY_${i}`] = `http.${origin}.extraheader`;
26166
26556
  env[`GIT_CONFIG_VALUE_${i}`] = `Authorization: Basic ${basic}`;
26167
26557
  });
@@ -26500,6 +26890,130 @@ var require_workspace = __commonJS({
26500
26890
  }
26501
26891
  });
26502
26892
 
26893
+ // ../../packages/shuttle/dist/core/run-dispatch.js
26894
+ var require_run_dispatch = __commonJS({
26895
+ "../../packages/shuttle/dist/core/run-dispatch.js"(exports2) {
26896
+ "use strict";
26897
+ Object.defineProperty(exports2, "__esModule", { value: true });
26898
+ exports2.GATE_REPAIR_BUDGET = void 0;
26899
+ exports2.runDispatch = runDispatch;
26900
+ exports2.runOutputGates = runOutputGates;
26901
+ exports2.mergeRepairedResponse = mergeRepairedResponse;
26902
+ var loom_1 = require_dist();
26903
+ var output_gates_1 = require_output_gates();
26904
+ var logger_1 = require_logger();
26905
+ var workspace_1 = require_workspace();
26906
+ exports2.GATE_REPAIR_BUDGET = 2;
26907
+ async function runDispatch(dispatch, executor, options) {
26908
+ const { credentials, signal } = options;
26909
+ const environment = executor.prepareEnvironment ? await executor.prepareEnvironment(dispatch) : void 0;
26910
+ let workspace = null;
26911
+ if (environment?.workspaceRequest) {
26912
+ try {
26913
+ workspace = await (0, workspace_1.materializeWorkspace)(environment.workspaceRequest, {
26914
+ ...environment.materializeOptions ?? {},
26915
+ credentials,
26916
+ signal
26917
+ });
26918
+ } catch (err) {
26919
+ (0, logger_1.getLogger)().warn("workspace materialization failed; running prompt-only", {
26920
+ jobId: dispatch.job.id,
26921
+ error: err instanceof Error ? err.message : String(err)
26922
+ });
26923
+ workspace = null;
26924
+ }
26925
+ }
26926
+ const ctx = {
26927
+ ...options.reportProgress ? { reportProgress: options.reportProgress } : {},
26928
+ workspace,
26929
+ credentials
26930
+ };
26931
+ let response;
26932
+ try {
26933
+ response = await executor.execute(dispatch, signal, ctx);
26934
+ } catch (error) {
26935
+ return { kind: "executor-failed", error };
26936
+ } finally {
26937
+ if (workspace)
26938
+ await workspace.cleanup();
26939
+ }
26940
+ const gated = await runOutputGates(dispatch, response, executor, signal, ctx);
26941
+ if (!gated.ok)
26942
+ return { kind: "gate-failed", reason: gated.reason };
26943
+ return { kind: "completed", response: gated.response };
26944
+ }
26945
+ async function runOutputGates(dispatch, response, executor, signal, ctx) {
26946
+ let spec;
26947
+ try {
26948
+ spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
26949
+ } catch {
26950
+ return { ok: true, response };
26951
+ }
26952
+ const gates = spec.gates;
26953
+ if (!gates || gates.length === 0)
26954
+ return { ok: true, response };
26955
+ let current = response;
26956
+ let result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
26957
+ let attempts = 0;
26958
+ while (!result.ok && attempts < exports2.GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
26959
+ try {
26960
+ const repaired = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
26961
+ current = mergeRepairedResponse(current, repaired);
26962
+ } catch {
26963
+ break;
26964
+ }
26965
+ result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
26966
+ attempts += 1;
26967
+ }
26968
+ if (result.ok)
26969
+ return { ok: true, response: current };
26970
+ const detail = result.findings.map((f) => `[${f.gate}] ${f.message}`).join(" | ");
26971
+ return {
26972
+ ok: false,
26973
+ reason: `output gate(s) failed${attempts > 0 ? ` after ${attempts} repair attempt(s)` : ""}: ${detail}`
26974
+ };
26975
+ }
26976
+ function mergeRepairedResponse(original, repaired) {
26977
+ const replaced = new Set(repaired.outputs.map((o) => o.label));
26978
+ const outputs = [...original.outputs.filter((o) => !replaced.has(o.label)), ...repaired.outputs];
26979
+ const usage = sumUsage(original.usage, repaired.usage);
26980
+ return {
26981
+ ...original,
26982
+ ...repaired,
26983
+ outputs,
26984
+ ...usage ? { usage } : {}
26985
+ };
26986
+ }
26987
+ var ADDITIVE_USAGE = [
26988
+ "inputTokens",
26989
+ "outputTokens",
26990
+ "cacheReadInputTokens",
26991
+ "cacheCreationInputTokens",
26992
+ "costUsd",
26993
+ "turns"
26994
+ ];
26995
+ function sumUsage(a, b) {
26996
+ if (!a)
26997
+ return b;
26998
+ if (!b)
26999
+ return a;
27000
+ const sum = { ...a, ...b };
27001
+ for (const key of ADDITIVE_USAGE) {
27002
+ if (a[key] === void 0 && b[key] === void 0)
27003
+ continue;
27004
+ sum[key] = (a[key] ?? 0) + (b[key] ?? 0);
27005
+ }
27006
+ if (a.toolCalls || b.toolCalls) {
27007
+ const calls = { ...a.toolCalls ?? {} };
27008
+ for (const [name, n] of Object.entries(b.toolCalls ?? {}))
27009
+ calls[name] = (calls[name] ?? 0) + n;
27010
+ sum.toolCalls = calls;
27011
+ }
27012
+ return sum;
27013
+ }
27014
+ }
27015
+ });
27016
+
26503
27017
  // ../../packages/shuttle/dist/credentials/registry.js
26504
27018
  var require_registry2 = __commonJS({
26505
27019
  "../../packages/shuttle/dist/credentials/registry.js"(exports2) {
@@ -26519,350 +27033,118 @@ var require_registry2 = __commonJS({
26519
27033
  return reveal.apiKey;
26520
27034
  });
26521
27035
  registerCredentialResolver("github-user-token", async (keep, principal) => {
26522
- try {
26523
- const reveal = await keep.getUserGithubToken(principal);
26524
- return reveal.access_token;
26525
- } catch {
26526
- return null;
26527
- }
26528
- });
26529
- }
26530
- });
26531
-
26532
- // ../../packages/shuttle/dist/credentials/broker.js
26533
- var require_broker = __commonJS({
26534
- "../../packages/shuttle/dist/credentials/broker.js"(exports2) {
26535
- "use strict";
26536
- Object.defineProperty(exports2, "__esModule", { value: true });
26537
- exports2.createCredentialBroker = createCredentialBroker;
26538
- var keep_1 = require_dist2();
26539
- var env_ref_1 = require_env_ref();
26540
- var registry_1 = require_registry2();
26541
- function createCredentialBroker(config, deps = {}) {
26542
- if (typeof config !== "object" || config === null || config.apiKeyFrom !== "job-metadata") {
26543
- return null;
26544
- }
26545
- const cfg = config;
26546
- const keepApiUrl = (0, env_ref_1.resolveConfigStringOrEnv)(cfg, "keepApiUrl", "KEEP_API_URL");
26547
- const clientId = (0, env_ref_1.resolveConfigStringOrEnv)(cfg, "keepClientId", "KEEP_CLIENT_ID");
26548
- const clientSecret = (0, env_ref_1.resolveConfigStringOrEnv)(cfg, "keepClientSecret", "KEEP_CLIENT_SECRET");
26549
- const stmFactory = deps.buildServiceTokenManager ?? ((c) => new keep_1.ServiceTokenManager(c));
26550
- const stm = stmFactory({ keepApiUrl, clientId, clientSecret });
26551
- const keepFactory = deps.buildKeepClient ?? ((c) => new keep_1.KeepClient(c));
26552
- return {
26553
- async reveal({ name, principal }) {
26554
- const resolver = (0, registry_1.getCredentialResolver)(name);
26555
- if (!resolver)
26556
- return null;
26557
- const serviceToken = await stm.getToken();
26558
- if (!serviceToken) {
26559
- throw new Error("credential broker: Keep service-token exchange failed. Check KEEP_CLIENT_ID / KEEP_CLIENT_SECRET.");
26560
- }
26561
- const keep = keepFactory({ baseUrl: keepApiUrl, serviceToken });
26562
- return resolver(keep, principal);
26563
- }
26564
- };
26565
- }
26566
- }
26567
- });
26568
-
26569
- // ../../packages/shuttle/dist/credentials/index.js
26570
- var require_credentials = __commonJS({
26571
- "../../packages/shuttle/dist/credentials/index.js"(exports2) {
26572
- "use strict";
26573
- Object.defineProperty(exports2, "__esModule", { value: true });
26574
- exports2.getCredentialResolver = exports2.registerCredentialResolver = exports2.createCredentialBroker = void 0;
26575
- var broker_1 = require_broker();
26576
- Object.defineProperty(exports2, "createCredentialBroker", { enumerable: true, get: function() {
26577
- return broker_1.createCredentialBroker;
26578
- } });
26579
- var registry_1 = require_registry2();
26580
- Object.defineProperty(exports2, "registerCredentialResolver", { enumerable: true, get: function() {
26581
- return registry_1.registerCredentialResolver;
26582
- } });
26583
- Object.defineProperty(exports2, "getCredentialResolver", { enumerable: true, get: function() {
26584
- return registry_1.getCredentialResolver;
26585
- } });
26586
- }
26587
- });
26588
-
26589
- // ../../packages/shuttle/dist/executors/output-gates.js
26590
- var require_output_gates = __commonJS({
26591
- "../../packages/shuttle/dist/executors/output-gates.js"(exports2) {
26592
- "use strict";
26593
- Object.defineProperty(exports2, "__esModule", { value: true });
26594
- exports2.isSanctionedPaint = isSanctionedPaint;
26595
- exports2.unsanctionedPaints = unsanctionedPaints;
26596
- exports2.resolvePath = resolvePath;
26597
- exports2.evaluateOutputGates = evaluateOutputGates;
26598
- exports2.renderRepairPrompt = renderRepairPrompt;
26599
- var INPUT_REF = "input:";
26600
- var COLOR_BEARING = ["fill", "stroke", "color", "stop-color", "flood-color", "lighting-color"];
26601
- var INERT = /* @__PURE__ */ new Set(["none", "inherit", "initial", "unset", "transparent", "currentcolor"]);
26602
- function isSanctionedPaint(raw) {
26603
- const value = raw.trim().toLowerCase();
26604
- if (value === "")
26605
- return true;
26606
- if (INERT.has(value))
26607
- return true;
26608
- if (/^url\(\s*['"]?#[^)'"]+['"]?\s*\)$/.test(value))
26609
- return true;
26610
- const varMatch = /^var\(\s*(--[a-z0-9-]+)\s*(?:,([\s\S]*))?\)$/.exec(value);
26611
- if (!varMatch)
26612
- return false;
26613
- const [, name, fallback] = varMatch;
26614
- if (!name.startsWith("--color-"))
26615
- return false;
26616
- return fallback === void 0 || isSanctionedPaint(fallback);
26617
- }
26618
- function unsanctionedPaints(source) {
26619
- const found = [];
26620
- const attrs = COLOR_BEARING.join("|");
26621
- const attribute = new RegExp(`\\b(?:${attrs})\\s*=\\s*(['"])([\\s\\S]*?)\\1`, "gi");
26622
- const declaration = new RegExp(`\\b(?:${attrs})\\s*:\\s*([^;"'}\\n]+)`, "gi");
26623
- for (const [, , value] of source.matchAll(attribute)) {
26624
- if (!isSanctionedPaint(value))
26625
- found.push(value.trim());
26626
- }
26627
- for (const [, value] of source.matchAll(declaration)) {
26628
- if (!isSanctionedPaint(value))
26629
- found.push(value.trim());
26630
- }
26631
- return found;
26632
- }
26633
- function resolvePath(root, path) {
26634
- let ctx = root;
26635
- for (const seg of path.split(".")) {
26636
- const project = seg.endsWith("[*]");
26637
- const key = project ? seg.slice(0, -3) : seg;
26638
- if (Array.isArray(ctx)) {
26639
- ctx = ctx.map((el) => key ? el?.[key] : el);
26640
- } else {
26641
- ctx = key ? ctx?.[key] : ctx;
26642
- }
26643
- if (project && !Array.isArray(ctx)) {
26644
- ctx = ctx == null ? [] : [ctx];
26645
- }
26646
- }
26647
- return ctx;
26648
- }
26649
- function resolveRef(ref, output, inputs) {
26650
- if (ref.startsWith(INPUT_REF)) {
26651
- const label = ref.slice(INPUT_REF.length);
26652
- const row = inputs.find((i) => i.label === label) ?? inputs.find((i) => i.label === "artifacts" && i.payload.label === label);
26653
- if (!row)
26654
- return void 0;
26655
- return Object.prototype.hasOwnProperty.call(row.payload, "value") ? row.payload.value : row.payload;
26656
- }
26657
- return resolvePath(output, ref);
26658
- }
26659
- function asStrings(value) {
26660
- if (Array.isArray(value))
26661
- return value.map((v) => String(v));
26662
- if (value == null)
26663
- return [];
26664
- return [String(value)];
26665
- }
26666
- var PRIMITIVES = {
26667
- /**
26668
- * `coverage` — the values at `got` must exactly cover the set at `want`:
26669
- * every wanted value present, nothing invented, none repeated. Params:
26670
- * `{ got: <output path>, want: <ref>, subject?: <noun> }`.
26671
- */
26672
- coverage(params, output, inputs) {
26673
- const subject = typeof params.subject === "string" ? params.subject : "item";
26674
- const got = asStrings(resolveRef(String(params.got), output, inputs));
26675
- const want = asStrings(resolveRef(String(params.want), output, inputs));
26676
- const gotSet = new Set(got);
26677
- const wantSet = new Set(want);
26678
- const missing = want.filter((v) => !gotSet.has(v));
26679
- const unknown = [...new Set(got.filter((v) => !wantSet.has(v)))];
26680
- const seen = /* @__PURE__ */ new Set();
26681
- const duplicated = [];
26682
- for (const v of got) {
26683
- if (seen.has(v))
26684
- duplicated.push(v);
26685
- else
26686
- seen.add(v);
26687
- }
26688
- if (missing.length === 0 && unknown.length === 0 && duplicated.length === 0)
26689
- return [];
26690
- const parts = [];
26691
- if (missing.length)
26692
- parts.push(`missing ${subject}(s): [${missing.join(", ")}]`);
26693
- if (unknown.length)
26694
- parts.push(`not a known ${subject}: [${unknown.join(", ")}]`);
26695
- if (duplicated.length)
26696
- parts.push(`assessed more than once: [${[...new Set(duplicated)].join(", ")}]`);
26697
- return [
26698
- `every ${subject} must be covered exactly once \u2014 ${parts.join("; ")}. Return one entry per ${subject}, using its exact id.`
26699
- ];
26700
- },
26701
- /**
26702
- * `non_empty_when` — the array at `path` must be non-empty, optionally only
26703
- * when `when.path` equals `when.equals`. Params:
26704
- * `{ path: <output path>, when?: { path: <output path>, equals: <value> } }`.
26705
- */
26706
- non_empty_when(params, output) {
26707
- const when = params.when;
26708
- if (when && typeof when.path === "string") {
26709
- if (resolvePath(output, when.path) !== when.equals)
26710
- return [];
26711
- }
26712
- const path = String(params.path);
26713
- const value = resolvePath(output, path);
26714
- if (Array.isArray(value) && value.length > 0)
26715
- return [];
26716
- const cond = when && typeof when.path === "string" ? ` when ${when.path} is ${JSON.stringify(when.equals)}` : "";
26717
- return [
26718
- `'${path}' must be a non-empty array${cond}; it was ${Array.isArray(value) ? "empty" : "absent"}.`
26719
- ];
26720
- },
26721
- /**
26722
- * `bounded_palette` — every colour in the sources at `path` must be named
26723
- * by role rather than written as a value. Params:
26724
- * `{ path: <output path>, roles?: <role names for the repair turn> }`.
26725
- *
26726
- * A drawing the model authors is markup the house did not write, and a
26727
- * literal in it is a colour outside the consuming palette: unproved
26728
- * against the colours beside it, unchecked for contrast on the ground it
26729
- * lands on, and fixed to one theme. The consumer strips such a paint at
26730
- * render, so an artifact that carries one silently loses it. Catching it
26731
- * HERE is what makes that a repair the model can make while it still has
26732
- * the drawing in hand, instead of a defect nobody sees until the picture
26733
- * comes out plain.
26734
- */
26735
- bounded_palette(params, output) {
26736
- const path = String(params.path);
26737
- const value = resolvePath(output, path);
26738
- const sources = (Array.isArray(value) ? value : [value]).filter((v) => typeof v === "string" && v.length > 0);
26739
- const offenders = [...new Set(sources.flatMap(unsanctionedPaints))];
26740
- if (offenders.length === 0)
26741
- return [];
26742
- const roles = Array.isArray(params.roles) ? params.roles.map(String) : [];
26743
- const vocabulary = roles.length > 0 ? ` Use one of: ${roles.join(", ")}.` : "";
26744
- return [
26745
- `'${path}' states ${offenders.length} colour value${offenders.length === 1 ? "" : "s"} directly (${offenders.slice(0, 8).join(", ")}${offenders.length > 8 ? ", \u2026" : ""}). Every colour must be named by role \u2014 var(--color-\u2026), currentColor, or none \u2014 never as a hex, rgb(), hsl() or colour name, because a value cannot adapt to the reader's theme and was never checked against the colours beside it.${vocabulary} Redraw with roles and return the whole output again.`
26746
- ];
26747
- }
26748
- };
26749
- function evaluateOutputGates(gates, output, inputs) {
26750
- const findings = [];
26751
- for (const gate of gates) {
26752
- const primitive = PRIMITIVES[gate.primitive];
26753
- if (!primitive) {
26754
- findings.push({ gate: gate.name, message: `unknown gate primitive '${gate.primitive}'` });
26755
- continue;
26756
- }
26757
- for (const message of primitive(gate.params, output, inputs)) {
26758
- findings.push({ gate: gate.name, message });
26759
- }
26760
- }
26761
- return { ok: findings.length === 0, findings };
26762
- }
26763
- function renderRepairPrompt(findings) {
26764
- return [
26765
- "Your previous answer did not satisfy these output checks. Return the corrected, complete answer that resolves every item below. Produce the same output shape with the problems fixed, and nothing else \u2014 no commentary, no explanation of the changes.",
26766
- "",
26767
- ...findings.map((f) => `- [${f.gate}] ${f.message}`)
26768
- ].join("\n");
26769
- }
27036
+ try {
27037
+ const reveal = await keep.getUserGithubToken(principal);
27038
+ return reveal.access_token;
27039
+ } catch {
27040
+ return null;
27041
+ }
27042
+ });
26770
27043
  }
26771
27044
  });
26772
27045
 
26773
- // ../../packages/shuttle/dist/logging/logger.js
26774
- var require_logger = __commonJS({
26775
- "../../packages/shuttle/dist/logging/logger.js"(exports2) {
27046
+ // ../../packages/shuttle/dist/credentials/broker.js
27047
+ var require_broker = __commonJS({
27048
+ "../../packages/shuttle/dist/credentials/broker.js"(exports2) {
26776
27049
  "use strict";
26777
27050
  Object.defineProperty(exports2, "__esModule", { value: true });
26778
- exports2.createLogger = createLogger;
26779
- exports2.setGlobalLogger = setGlobalLogger;
26780
- exports2.getLogger = getLogger;
26781
- var LEVEL_ORDER = {
26782
- debug: 0,
26783
- info: 1,
26784
- warn: 2,
26785
- error: 3
26786
- };
26787
- function formatContext(context) {
26788
- return Object.entries(context).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ");
26789
- }
26790
- function formatPretty(level, message, context) {
26791
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
26792
- const tag = level.toUpperCase().padEnd(5);
26793
- const base = `[${timestamp}] ${tag} ${message}`;
26794
- if (context && Object.keys(context).length > 0) {
26795
- return `${base} ${formatContext(context)}`;
26796
- }
26797
- return base;
26798
- }
26799
- function formatJson(level, message, context) {
26800
- const entry = {
26801
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
26802
- level,
26803
- message,
26804
- ...context
26805
- };
26806
- return JSON.stringify(entry);
26807
- }
26808
- function createLogger(options) {
26809
- const threshold = LEVEL_ORDER[options.level];
26810
- const format = options.format === "json" ? formatJson : formatPretty;
26811
- function emit(level, message, context) {
26812
- if (LEVEL_ORDER[level] < threshold)
26813
- return;
26814
- const line = format(level, message, context);
26815
- if (level === "error") {
26816
- console.error(line);
26817
- } else if (level === "warn") {
26818
- console.warn(line);
26819
- } else {
26820
- console.log(line);
26821
- }
27051
+ exports2.createCredentialBroker = createCredentialBroker;
27052
+ var keep_1 = require_dist2();
27053
+ var env_ref_1 = require_env_ref();
27054
+ var registry_1 = require_registry2();
27055
+ function createCredentialBroker(config, deps = {}) {
27056
+ if (typeof config !== "object" || config === null || config.apiKeyFrom !== "job-metadata") {
27057
+ return null;
26822
27058
  }
27059
+ const cfg = config;
27060
+ const keepApiUrl = (0, env_ref_1.resolveConfigStringOrEnv)(cfg, "keepApiUrl", "KEEP_API_URL");
27061
+ const clientId = (0, env_ref_1.resolveConfigStringOrEnv)(cfg, "keepClientId", "KEEP_CLIENT_ID");
27062
+ const clientSecret = (0, env_ref_1.resolveConfigStringOrEnv)(cfg, "keepClientSecret", "KEEP_CLIENT_SECRET");
27063
+ const stmFactory = deps.buildServiceTokenManager ?? ((c) => new keep_1.ServiceTokenManager(c));
27064
+ const stm = stmFactory({ keepApiUrl, clientId, clientSecret });
27065
+ const keepFactory = deps.buildKeepClient ?? ((c) => new keep_1.KeepClient(c));
26823
27066
  return {
26824
- debug: (message, context) => emit("debug", message, context),
26825
- info: (message, context) => emit("info", message, context),
26826
- warn: (message, context) => emit("warn", message, context),
26827
- error: (message, context) => emit("error", message, context)
27067
+ async reveal({ name, principal }) {
27068
+ const resolver = (0, registry_1.getCredentialResolver)(name);
27069
+ if (!resolver)
27070
+ return null;
27071
+ const serviceToken = await stm.getToken();
27072
+ if (!serviceToken) {
27073
+ throw new Error("credential broker: Keep service-token exchange failed. Check KEEP_CLIENT_ID / KEEP_CLIENT_SECRET.");
27074
+ }
27075
+ const keep = keepFactory({ baseUrl: keepApiUrl, serviceToken });
27076
+ return resolver(keep, principal);
27077
+ }
26828
27078
  };
26829
27079
  }
26830
- var globalLogger = createLogger({ level: "info", format: "pretty" });
26831
- function setGlobalLogger(logger) {
26832
- globalLogger = logger;
26833
- }
26834
- function getLogger() {
26835
- return globalLogger;
26836
- }
26837
27080
  }
26838
27081
  });
26839
27082
 
26840
- // ../../packages/shuttle/dist/core/demux.js
26841
- var require_demux = __commonJS({
26842
- "../../packages/shuttle/dist/core/demux.js"(exports2) {
27083
+ // ../../packages/shuttle/dist/credentials/index.js
27084
+ var require_credentials = __commonJS({
27085
+ "../../packages/shuttle/dist/credentials/index.js"(exports2) {
27086
+ "use strict";
27087
+ Object.defineProperty(exports2, "__esModule", { value: true });
27088
+ exports2.getCredentialResolver = exports2.registerCredentialResolver = exports2.createCredentialBroker = void 0;
27089
+ var broker_1 = require_broker();
27090
+ Object.defineProperty(exports2, "createCredentialBroker", { enumerable: true, get: function() {
27091
+ return broker_1.createCredentialBroker;
27092
+ } });
27093
+ var registry_1 = require_registry2();
27094
+ Object.defineProperty(exports2, "registerCredentialResolver", { enumerable: true, get: function() {
27095
+ return registry_1.registerCredentialResolver;
27096
+ } });
27097
+ Object.defineProperty(exports2, "getCredentialResolver", { enumerable: true, get: function() {
27098
+ return registry_1.getCredentialResolver;
27099
+ } });
27100
+ }
27101
+ });
27102
+
27103
+ // ../../packages/shuttle/dist/core/executor-choice.js
27104
+ var require_executor_choice = __commonJS({
27105
+ "../../packages/shuttle/dist/core/executor-choice.js"(exports2) {
26843
27106
  "use strict";
26844
27107
  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
- }
27108
+ exports2.createExecutorChooser = createExecutorChooser;
27109
+ var loom_1 = require_dist();
27110
+ function createExecutorChooser(executors, pools) {
26852
27111
  const byName = new Map(executors.map((e) => [e.capability.id, e]));
26853
- const byPool = /* @__PURE__ */ new Map();
27112
+ const legacyFirst = /* @__PURE__ */ new Map();
26854
27113
  for (const pool of pools) {
26855
- const named = byName.get(pool.executor);
26856
- if (!named)
27114
+ if (!pool.executor || !byName.has(pool.executor))
26857
27115
  continue;
26858
27116
  if (pool.id)
26859
- byPool.set(`id:${pool.id}`, named);
27117
+ legacyFirst.set(`id:${pool.id}`, pool.executor);
26860
27118
  if (pool.name)
26861
- byPool.set(`name:${pool.name}`, named);
27119
+ legacyFirst.set(`name:${pool.name}`, pool.executor);
26862
27120
  }
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);
27121
+ const orderFor = (pool) => {
27122
+ const first = (pool.id !== void 0 ? legacyFirst.get(`id:${pool.id}`) : void 0) ?? (pool.name !== void 0 ? legacyFirst.get(`name:${pool.name}`) : void 0);
27123
+ const head = first ? byName.get(first) : void 0;
27124
+ return head ? [head, ...executors.filter((e) => e !== head)] : [...executors];
27125
+ };
27126
+ return {
27127
+ accepts(tags) {
27128
+ const required = (0, loom_1.readRequiredExecutor)(tags);
27129
+ return required === null || byName.has(required);
27130
+ },
27131
+ choose({ pool, strategy, required }) {
27132
+ if (required !== null) {
27133
+ const named = byName.get(required);
27134
+ if (!named) {
27135
+ return { ok: false, reason: `this job requires the ${required} executor, which this runner does not have` };
27136
+ }
27137
+ if (!named.strategies.includes(strategy)) {
27138
+ return { ok: false, reason: `this job requires the ${required} executor, which does not run "${strategy}" jobs` };
27139
+ }
27140
+ return { ok: true, executors: [named], required };
27141
+ }
27142
+ const capable = orderFor(pool).filter((e) => e.strategies.includes(strategy));
27143
+ if (capable.length === 0)
27144
+ return { ok: false, reason: `no executor for shuttle strategy "${strategy}"` };
27145
+ return { ok: true, executors: capable, required: null };
27146
+ },
27147
+ orderFor
26866
27148
  };
26867
27149
  }
26868
27150
  }
@@ -27378,9 +27660,8 @@ var require_shuttle = __commonJS({
27378
27660
  var loom_1 = require_dist();
27379
27661
  var chain_1 = require_chain();
27380
27662
  var types_1 = require_types2();
27381
- var workspace_1 = require_workspace();
27663
+ var run_dispatch_1 = require_run_dispatch();
27382
27664
  var credentials_1 = require_credentials();
27383
- var output_gates_1 = require_output_gates();
27384
27665
  var keep_1 = require_dist2();
27385
27666
  var apply_1 = require_apply();
27386
27667
  var environment_1 = require_environment();
@@ -27389,7 +27670,7 @@ var require_shuttle = __commonJS({
27389
27670
  var logger_1 = require_logger();
27390
27671
  var registry_1 = require_registry();
27391
27672
  var claude_code_1 = require_claude_code();
27392
- var demux_1 = require_demux();
27673
+ var executor_choice_1 = require_executor_choice();
27393
27674
  var codex_1 = require_codex();
27394
27675
  var copilot_1 = require_copilot();
27395
27676
  var http_api_1 = require_http_api();
@@ -27400,8 +27681,16 @@ var require_shuttle = __commonJS({
27400
27681
  var audit_log_1 = require_audit_log();
27401
27682
  var spend_tracker_1 = require_spend_tracker();
27402
27683
  var store_1 = require_store();
27403
- var GATE_REPAIR_BUDGET = 2;
27404
27684
  var ENVIRONMENT_HOLD = "executor-environment";
27685
+ function requiredExecutor(dispatch, strategy) {
27686
+ if (strategy !== loom_1.PROMPT_EXECUTION_STRATEGY)
27687
+ return null;
27688
+ try {
27689
+ return (0, loom_1.readPromptExecutionSpec)(dispatch.inputs).requires?.executor ?? null;
27690
+ } catch {
27691
+ return null;
27692
+ }
27693
+ }
27405
27694
  var RECHECK_INITIAL_MS = 15e3;
27406
27695
  var RECHECK_MAX_MS = 3e5;
27407
27696
  var KEEP_RETRY_INITIAL_MS = 2e3;
@@ -27458,36 +27747,6 @@ var require_shuttle = __commonJS({
27458
27747
  function formatInstant(epochMs) {
27459
27748
  return new Date(epochMs).toISOString().replace(/\.\d{3}Z$/, "Z");
27460
27749
  }
27461
- async function runOutputGates(dispatch, response, executor, signal, ctx) {
27462
- let spec;
27463
- try {
27464
- spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
27465
- } catch {
27466
- return { ok: true, response };
27467
- }
27468
- const gates = spec.gates;
27469
- if (!gates || gates.length === 0)
27470
- return { ok: true, response };
27471
- let current = response;
27472
- let result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
27473
- let attempts = 0;
27474
- while (!result.ok && attempts < GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
27475
- try {
27476
- current = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
27477
- } catch {
27478
- break;
27479
- }
27480
- result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
27481
- attempts += 1;
27482
- }
27483
- if (result.ok)
27484
- return { ok: true, response: current };
27485
- const detail = result.findings.map((f) => `[${f.gate}] ${f.message}`).join(" | ");
27486
- return {
27487
- ok: false,
27488
- reason: `output gate(s) failed${attempts > 0 ? ` after ${attempts} repair attempt(s)` : ""}: ${detail}`
27489
- };
27490
- }
27491
27750
  var Shuttle = class {
27492
27751
  brand;
27493
27752
  config;
@@ -27511,8 +27770,15 @@ var require_shuttle = __commonJS({
27511
27770
  lifetime = deferred();
27512
27771
  /** Aborted by `stop()`: ends the startup waits and the readiness re-checks. */
27513
27772
  stopping = new AbortController();
27514
- /** When claiming was paused on an executor environment fault; null while claiming. */
27515
- environmentPausedAt = null;
27773
+ /**
27774
+ * The executors that cannot run jobs on this machine just now (signed out,
27775
+ * missing, out of plan), with why and since when. Empty while every one can.
27776
+ */
27777
+ unwell = /* @__PURE__ */ new Map();
27778
+ /** Every configured executor, in the configured order. */
27779
+ executors = [];
27780
+ /** Whether claiming is paused because no executor can run jobs. */
27781
+ environmentHeld = false;
27516
27782
  constructor(options) {
27517
27783
  this.brand = options.brand;
27518
27784
  this.config = options.config;
@@ -27596,7 +27862,8 @@ var require_shuttle = __commonJS({
27596
27862
  const registry = new registry_1.ExecutorRegistry(this.brand.executorAllowlist);
27597
27863
  registerBuiltInExecutors(registry, this.brand.executorAllowlist);
27598
27864
  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);
27865
+ this.executors = executors;
27866
+ const chooser = (0, executor_choice_1.createExecutorChooser)(executors, this.config.pools);
27600
27867
  const modelsByExecutor = await listExecutorModels(executors, this.stopping.signal);
27601
27868
  if (this.stopping.signal.aborted)
27602
27869
  return;
@@ -27621,12 +27888,31 @@ var require_shuttle = __commonJS({
27621
27888
  const attendanceId = dispatch.attendanceId;
27622
27889
  const startedAt = Date.now();
27623
27890
  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 };
27891
+ const close = async (result) => {
27892
+ await observerChain.notify({
27893
+ kind: "attendance.closed",
27894
+ jobId,
27895
+ attendanceId,
27896
+ status: result.status === "completed" ? "completed" : "failed",
27897
+ ...result.reason !== void 0 ? { reason: result.reason } : {},
27898
+ totalDurationMs: Date.now() - startedAt
27899
+ }, onObserverError);
27900
+ return result;
27901
+ };
27902
+ const choice = chooser.choose({ pool: dispatch.pool, strategy, required: requiredExecutor(dispatch, strategy) });
27903
+ if (!choice.ok)
27904
+ return close({ status: "failed", reason: choice.reason });
27905
+ if (choice.required !== null) {
27906
+ const fault = this.unwell.get(choice.required);
27907
+ if (fault) {
27908
+ return close({
27909
+ status: "failed",
27910
+ reason: `executor failed: ${choice.required} cannot run jobs on this machine (${fault.err.message}). To fix it, ${fault.err.remedy}.`
27911
+ });
27912
+ }
27629
27913
  }
27914
+ const well = choice.executors.filter((e) => !this.unwell.has(e.capability.id));
27915
+ const candidates = well.length > 0 ? well : choice.executors;
27630
27916
  await observerChain.notify({
27631
27917
  kind: "job.claimed",
27632
27918
  jobId,
@@ -27637,9 +27923,9 @@ var require_shuttle = __commonJS({
27637
27923
  dispatch
27638
27924
  }, onObserverError);
27639
27925
  let estimate = {};
27640
- if (executor.estimate) {
27926
+ if (candidates[0].estimate) {
27641
27927
  try {
27642
- estimate = await executor.estimate(dispatch);
27928
+ estimate = await candidates[0].estimate(dispatch);
27643
27929
  } catch {
27644
27930
  }
27645
27931
  }
@@ -27647,85 +27933,80 @@ var require_shuttle = __commonJS({
27647
27933
  await observerChain.notify({ kind: "spend.preflight", jobId, attendanceId, estimate });
27648
27934
  } catch (err) {
27649
27935
  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) {
27658
- try {
27659
- preparedWorkspace = await (0, workspace_1.materializeWorkspace)(environment.workspaceRequest, {
27660
- ...environment.materializeOptions ?? {},
27661
- credentials: this.broker,
27662
- signal
27663
- });
27664
- } 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;
27936
+ return close({ status: "failed", reason });
27937
+ }
27938
+ const runOn = async (executor) => {
27939
+ await observerChain.notify({ kind: "executor.dispatch.started", jobId, attendanceId, executor: executor.capability.id, strategy }, onObserverError);
27940
+ const dispatchStartedAt = Date.now();
27941
+ const outcome = await (0, run_dispatch_1.runDispatch)(dispatch, executor, {
27942
+ credentials: this.broker,
27943
+ signal,
27944
+ reportProgress: (channel, frames) => {
27945
+ void jobsClient.postProgress(jobId, { channel, frames }).then(() => activity.record()).catch(() => {
27946
+ });
27947
+ }
27948
+ });
27949
+ if (outcome.kind === "executor-failed") {
27950
+ const err = outcome.error;
27951
+ const reason = `executor failed: ${err instanceof Error ? err.message : String(err)}`;
27952
+ if (this.broker === null && (0, types_1.isExecutorEnvironmentError)(err)) {
27953
+ this.markUnwell(executor, err, subscribeLogger);
27954
+ return { kind: "environment", reason };
27955
+ }
27956
+ if ((0, types_1.isExecutorModelUnavailableError)(err)) {
27957
+ return { kind: "result", result: { status: "failed", reason, failureCode: types_1.MODEL_UNAVAILABLE } };
27958
+ }
27959
+ return { kind: "result", result: { status: "failed", reason } };
27670
27960
  }
27671
- }
27672
- const executionCtx = {
27673
- reportProgress: (channel, frames) => {
27674
- void jobsClient.postProgress(jobId, { channel, frames }).then(() => activity.record()).catch(() => {
27961
+ if (outcome.kind === "gate-failed") {
27962
+ return { kind: "result", result: { status: "failed", reason: outcome.reason } };
27963
+ }
27964
+ const response = outcome.response;
27965
+ await observerChain.notify({
27966
+ kind: "executor.dispatch.completed",
27967
+ jobId,
27968
+ attendanceId,
27969
+ executor: executor.capability.id,
27970
+ response: { rawText: response.rawText ?? "", parsed: response.parsed, usage: response.usage },
27971
+ durationMs: Date.now() - dispatchStartedAt
27972
+ }, onObserverError);
27973
+ const outputs = [...response.outputs];
27974
+ if (response.usage) {
27975
+ outputs.push({
27976
+ label: "usage",
27977
+ content: { ...response.usage, executor: executor.capability.id }
27675
27978
  });
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
27979
  }
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 };
27980
+ for (const output of outputs) {
27981
+ await observerChain.notify({ kind: "output.submitted", jobId, attendanceId, label: output.label, content: output.content }, onObserverError);
27982
+ }
27983
+ return { kind: "result", result: { status: "completed", outputs } };
27984
+ };
27985
+ for (const [i, executor] of candidates.entries()) {
27986
+ const outcome = await runOn(executor);
27987
+ if (outcome.kind === "result")
27988
+ return close(outcome.result);
27989
+ const next = candidates[i + 1];
27990
+ if (!next)
27991
+ return close({ status: "failed", reason: outcome.reason });
27992
+ subscribeLogger.info(`${executor.capability.id} cannot run jobs on this machine, so job ${jobId} runs on ${next.capability.id} instead.`);
27993
+ }
27994
+ return close({ status: "failed", reason: `no executor for shuttle strategy "${strategy}"` });
27720
27995
  };
27721
27996
  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
27997
  const poolMetadata = (ref) => {
27724
- const executor = poolExecutors.get(ref);
27725
- const models = executor ? modelsByExecutor.get(executor) : void 0;
27726
- if (!executor || !models)
27998
+ const reported = chooser.orderFor(ref).map((e) => {
27999
+ const models = modelsByExecutor.get(e.capability.id) ?? [];
28000
+ return { id: e.capability.id, ...models.length > 0 ? { models } : {} };
28001
+ });
28002
+ const [first] = reported;
28003
+ if (!first)
27727
28004
  return void 0;
27728
- return { executor, ...models.length > 0 ? { models } : {} };
28005
+ return {
28006
+ executors: reported,
28007
+ executor: first.id,
28008
+ ...first.models ? { models: first.models } : {}
28009
+ };
27729
28010
  };
27730
28011
  const attemptedUpgrades = /* @__PURE__ */ new Set();
27731
28012
  this.subscription = (0, jobs_1.subscribe)({
@@ -27747,6 +28028,9 @@ var require_shuttle = __commonJS({
27747
28028
  poolMetadata
27748
28029
  },
27749
28030
  onJob,
28031
+ // Decline, at discovery, a job that requires an executor this runner
28032
+ // does not have; it stays queued for a member that has it (ADR 0118).
28033
+ accepts: (job) => chooser.accepts(job.tags),
27750
28034
  onDirectives: async (directives) => {
27751
28035
  const decommission = (0, decommission_1.readDecommissionDirective)(directives);
27752
28036
  if (decommission) {
@@ -27831,20 +28115,31 @@ var require_shuttle = __commonJS({
27831
28115
  this.subscription?.done().catch((err) => this.lifetime.reject(err));
27832
28116
  }
27833
28117
  /**
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.
28118
+ * Note that an executor cannot run jobs on this machine, and keep checking
28119
+ * until it can. While another executor can, jobs that do not require this
28120
+ * one run there; once none can, claiming pauses, so a signed-out laptop does
28121
+ * not burn a queue of reviews one failure at a time. One warning says why
28122
+ * and what to do; the re-checks stay at debug; one line says when it works
28123
+ * again. Idempotent: a second failing job on the same executor changes
28124
+ * nothing.
27838
28125
  */
27839
- pauseForEnvironment(executor, err, logger) {
27840
- if (this.environmentPausedAt !== null || !this.subscription)
28126
+ markUnwell(executor, err, logger) {
28127
+ const id = executor.capability.id;
28128
+ if (this.unwell.has(id) || !this.subscription)
27841
28129
  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.`);
28130
+ this.unwell.set(id, { since: Date.now(), err });
28131
+ const others = this.executors.filter((e) => !this.unwell.has(e.capability.id)).map((e) => e.capability.id);
28132
+ if (others.length === 0) {
28133
+ this.environmentHeld = true;
28134
+ this.subscription.pause(ENVIRONMENT_HOLD);
28135
+ 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.`);
28136
+ } else {
28137
+ 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.`);
28138
+ }
27845
28139
  void this.recheckEnvironment(executor, logger);
27846
28140
  }
27847
28141
  async recheckEnvironment(executor, logger) {
28142
+ const id = executor.capability.id;
27848
28143
  const signal = this.stopping.signal;
27849
28144
  let delay = this.environmentRecheck.initialMs;
27850
28145
  for (; ; ) {
@@ -27860,15 +28155,21 @@ var require_shuttle = __commonJS({
27860
28155
  if (signal.aborted)
27861
28156
  return;
27862
28157
  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.`);
28158
+ const since = this.unwell.get(id)?.since ?? Date.now();
28159
+ this.unwell.delete(id);
28160
+ const lasted = formatDuration(Date.now() - since);
28161
+ if (this.environmentHeld) {
28162
+ this.environmentHeld = false;
28163
+ this.subscription?.resume(ENVIRONMENT_HOLD);
28164
+ logger.info(`${id} can run jobs again; claiming resumed after ${lasted} paused.`);
28165
+ } else {
28166
+ logger.info(`${id} can run jobs again after ${lasted}.`);
28167
+ }
27867
28168
  return;
27868
28169
  }
27869
28170
  delay = Math.min(delay * 2, this.environmentRecheck.maxMs);
27870
28171
  logger.debug?.("executor still cannot run jobs", {
27871
- executor: executor.capability.id,
28172
+ executor: id,
27872
28173
  reason: readiness.reason,
27873
28174
  nextCheckIn: formatDuration(delay)
27874
28175
  });
@@ -28356,6 +28657,212 @@ var require_revoke = __commonJS({
28356
28657
  }
28357
28658
  });
28358
28659
 
28660
+ // ../../packages/shuttle/dist/config/executor-order.js
28661
+ var require_executor_order = __commonJS({
28662
+ "../../packages/shuttle/dist/config/executor-order.js"(exports2) {
28663
+ "use strict";
28664
+ Object.defineProperty(exports2, "__esModule", { value: true });
28665
+ exports2.executorOrder = executorOrder;
28666
+ exports2.foldPoolExecutors = foldPoolExecutors;
28667
+ exports2.addExecutor = addExecutor;
28668
+ exports2.removeExecutor = removeExecutor;
28669
+ var yaml_1 = require_dist3();
28670
+ function executorOrder(doc) {
28671
+ const map = doc.get("executors", true);
28672
+ if (!(0, yaml_1.isMap)(map))
28673
+ return [];
28674
+ return map.items.flatMap((pair) => (0, yaml_1.isScalar)(pair.key) ? [String(pair.key.value)] : []);
28675
+ }
28676
+ function executorsMap(doc) {
28677
+ const existing = doc.get("executors", true);
28678
+ if ((0, yaml_1.isMap)(existing))
28679
+ return existing;
28680
+ const created = doc.createNode({});
28681
+ doc.set("executors", created);
28682
+ return created;
28683
+ }
28684
+ function indexOf(map, id) {
28685
+ return map.items.findIndex((pair) => (0, yaml_1.isScalar)(pair.key) && pair.key.value === id);
28686
+ }
28687
+ function foldPoolExecutors(doc) {
28688
+ const pools = doc.get("pools", true);
28689
+ if (!(0, yaml_1.isSeq)(pools))
28690
+ return null;
28691
+ let named = null;
28692
+ for (const pool of pools.items) {
28693
+ if (!(0, yaml_1.isMap)(pool))
28694
+ continue;
28695
+ const value = pool.get("executor");
28696
+ if (typeof value === "string" && named === null)
28697
+ named = value;
28698
+ pool.delete("executor");
28699
+ }
28700
+ if (named === null)
28701
+ return null;
28702
+ const map = executorsMap(doc);
28703
+ const at = indexOf(map, named);
28704
+ if (at > 0) {
28705
+ const [pair] = map.items.splice(at, 1);
28706
+ map.items.unshift(pair);
28707
+ }
28708
+ return named;
28709
+ }
28710
+ function addExecutor(doc, id, block, options = {}) {
28711
+ const map = executorsMap(doc);
28712
+ const at = indexOf(map, id);
28713
+ if (at >= 0) {
28714
+ if (!options.first || at === 0)
28715
+ return "unchanged";
28716
+ const [pair2] = map.items.splice(at, 1);
28717
+ map.items.unshift(pair2);
28718
+ return "moved";
28719
+ }
28720
+ const pair = doc.createPair(id, block);
28721
+ if (options.first)
28722
+ map.items.unshift(pair);
28723
+ else
28724
+ map.items.push(pair);
28725
+ return "added";
28726
+ }
28727
+ function removeExecutor(doc, id) {
28728
+ const map = executorsMap(doc);
28729
+ const at = indexOf(map, id);
28730
+ if (at < 0)
28731
+ return "absent";
28732
+ if (map.items.length === 1)
28733
+ return "last";
28734
+ map.items.splice(at, 1);
28735
+ return "removed";
28736
+ }
28737
+ }
28738
+ });
28739
+
28740
+ // ../../packages/shuttle/dist/cli/executors.js
28741
+ var require_executors = __commonJS({
28742
+ "../../packages/shuttle/dist/cli/executors.js"(exports2) {
28743
+ "use strict";
28744
+ Object.defineProperty(exports2, "__esModule", { value: true });
28745
+ exports2.buildAddCommand = buildAddCommand;
28746
+ exports2.buildRemoveCommand = buildRemoveCommand;
28747
+ var fs_1 = require("fs");
28748
+ var commander_1 = require_commander();
28749
+ var yaml_1 = require_dist3();
28750
+ var apply_1 = require_apply();
28751
+ var loader_1 = require_loader();
28752
+ var executor_order_1 = require_executor_order();
28753
+ var preflight_1 = require_preflight();
28754
+ function target(brand, options) {
28755
+ const profile = (0, apply_1.resolveProfile)(brand, options.profile);
28756
+ try {
28757
+ const path = (0, loader_1.resolveConfigPath)({
28758
+ explicitPath: options.config,
28759
+ searchPaths: (0, apply_1.runConfigSearchPaths)(brand, profile),
28760
+ productId: brand.product.id
28761
+ });
28762
+ return { path, profile };
28763
+ } catch (err) {
28764
+ console.error(err instanceof Error ? err.message : String(err));
28765
+ console.error(`Run \`${brand.product.cliBinary} init\` first: it writes the config this edits.`);
28766
+ process.exit(1);
28767
+ }
28768
+ }
28769
+ function allowed(brand, id) {
28770
+ return brand.executorAllowlist[0] === "*" || brand.executorAllowlist.includes(id);
28771
+ }
28772
+ function orderLine(order) {
28773
+ 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.`;
28774
+ }
28775
+ function foldedLine(folded) {
28776
+ return folded === null ? [] : [`Your pools named ${folded} as their executor, a setting from before 0.34; it is first in the order instead now.`];
28777
+ }
28778
+ async function newFailures(brand, t, write, restore) {
28779
+ const paths = (0, apply_1.resolvePaths)(brand, t.profile);
28780
+ const run = () => (0, preflight_1.runPreflight)((0, preflight_1.createProbes)({
28781
+ brand,
28782
+ paths,
28783
+ configSearchPaths: (0, apply_1.runConfigSearchPaths)(brand, t.profile),
28784
+ explicitConfigPath: t.path
28785
+ }), { brand, paths, phase: "doctor", deep: false });
28786
+ const before = await run();
28787
+ write();
28788
+ try {
28789
+ const after = await run();
28790
+ const failedBefore = new Set(before.verdicts.filter((v) => v.status === "FAIL").map((v) => v.name));
28791
+ return after.verdicts.filter((v) => v.status === "FAIL" && !failedBefore.has(v.name));
28792
+ } catch (err) {
28793
+ restore();
28794
+ throw err;
28795
+ }
28796
+ }
28797
+ function buildAddCommand(brand) {
28798
+ const add = new commander_1.Command("add").description(`Add to this ${brand.product.title}'s setup`);
28799
+ 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) => {
28800
+ if (!allowed(brand, id)) {
28801
+ console.error(`${id} is not an executor this build runs. It runs: ${brand.executorAllowlist.join(", ")}.`);
28802
+ process.exit(1);
28803
+ }
28804
+ const t = target(brand, options);
28805
+ const original = (0, fs_1.readFileSync)(t.path, "utf8");
28806
+ const doc = (0, yaml_1.parseDocument)(original);
28807
+ const folded = (0, executor_order_1.foldPoolExecutors)(doc);
28808
+ const block = brand.executorBlock?.(id, { ...options.tmpDir ? { tmpDir: options.tmpDir } : {} }) ?? { type: id };
28809
+ const outcome = (0, executor_order_1.addExecutor)(doc, id, block, { first: options.first === true });
28810
+ if (outcome === "unchanged" && folded === null) {
28811
+ console.log(`${id} is already set up. ${orderLine((0, executor_order_1.executorOrder)(doc))}`);
28812
+ return;
28813
+ }
28814
+ const write = () => (0, fs_1.writeFileSync)(t.path, doc.toString());
28815
+ const restore = () => (0, fs_1.writeFileSync)(t.path, original);
28816
+ if (outcome === "added") {
28817
+ const failures = await newFailures(brand, t, write, restore);
28818
+ if (failures.length > 0) {
28819
+ restore();
28820
+ (0, preflight_1.printReport)(failures, false);
28821
+ console.error("");
28822
+ console.error(`${id} was not added: it could not run jobs on this machine. Fix what is above, then run this again.`);
28823
+ process.exit(preflight_1.PREFLIGHT_EXIT_CODE);
28824
+ }
28825
+ } else {
28826
+ write();
28827
+ }
28828
+ const verb = outcome === "added" ? "Added" : outcome === "moved" ? "Moved" : "Kept";
28829
+ console.log(`${verb} ${id}${outcome === "moved" ? " to the front" : ""} in ${t.path}.`);
28830
+ for (const line of foldedLine(folded))
28831
+ console.log(line);
28832
+ console.log(orderLine((0, executor_order_1.executorOrder)(doc)));
28833
+ console.log(`Restart \`${brand.product.cliBinary} run\` for it to take effect.`);
28834
+ });
28835
+ return add;
28836
+ }
28837
+ function buildRemoveCommand(brand) {
28838
+ const remove = new commander_1.Command("remove").description(`Remove from this ${brand.product.title}'s setup`);
28839
+ 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) => {
28840
+ const t = target(brand, options);
28841
+ const doc = (0, yaml_1.parseDocument)((0, fs_1.readFileSync)(t.path, "utf8"));
28842
+ const folded = (0, executor_order_1.foldPoolExecutors)(doc);
28843
+ const outcome = (0, executor_order_1.removeExecutor)(doc, id);
28844
+ if (outcome === "last") {
28845
+ console.error(`${id} is the only executor set up, so it stays: with none, nothing could run.`);
28846
+ console.error(`Add another first with \`${brand.product.cliBinary} add executor <id>\`.`);
28847
+ process.exit(1);
28848
+ }
28849
+ if (outcome === "absent" && folded === null) {
28850
+ console.log(`${id} is not set up. ${orderLine((0, executor_order_1.executorOrder)(doc))}`);
28851
+ return;
28852
+ }
28853
+ (0, fs_1.writeFileSync)(t.path, doc.toString());
28854
+ if (outcome === "removed")
28855
+ console.log(`Removed ${id} from ${t.path}.`);
28856
+ for (const line of foldedLine(folded))
28857
+ console.log(line);
28858
+ console.log(orderLine((0, executor_order_1.executorOrder)(doc)));
28859
+ console.log(`Restart \`${brand.product.cliBinary} run\` for it to take effect.`);
28860
+ });
28861
+ return remove;
28862
+ }
28863
+ }
28864
+ });
28865
+
28359
28866
  // ../../packages/shuttle/dist/cli.js
28360
28867
  var require_cli = __commonJS({
28361
28868
  "../../packages/shuttle/dist/cli.js"(exports2) {
@@ -28371,6 +28878,7 @@ var require_cli = __commonJS({
28371
28878
  var caps_1 = require_caps();
28372
28879
  var list_1 = require_list();
28373
28880
  var revoke_1 = require_revoke();
28881
+ var executors_1 = require_executors();
28374
28882
  function createCli2(brand) {
28375
28883
  const program = new commander_1.Command();
28376
28884
  program.name(brand.product.cliBinary).description(brand.product.description).version(brand.product.version);
@@ -28382,6 +28890,8 @@ var require_cli = __commonJS({
28382
28890
  program.addCommand((0, caps_1.buildCapsCommand)(brand));
28383
28891
  program.addCommand((0, list_1.buildListCommand)(brand));
28384
28892
  program.addCommand((0, revoke_1.buildRevokeCommand)(brand));
28893
+ program.addCommand((0, executors_1.buildAddCommand)(brand));
28894
+ program.addCommand((0, executors_1.buildRemoveCommand)(brand));
28385
28895
  return program;
28386
28896
  }
28387
28897
  }
@@ -28392,8 +28902,8 @@ var require_dist5 = __commonJS({
28392
28902
  "../../packages/shuttle/dist/index.js"(exports2) {
28393
28903
  "use strict";
28394
28904
  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;
28905
+ 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.GATE_REPAIR_BUDGET = exports2.mergeRepairedResponse = exports2.runDispatch = exports2.Shuttle = exports2.createCli = void 0;
28906
+ 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 = exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = void 0;
28397
28907
  var cli_1 = require_cli();
28398
28908
  Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
28399
28909
  return cli_1.createCli;
@@ -28402,6 +28912,16 @@ var require_dist5 = __commonJS({
28402
28912
  Object.defineProperty(exports2, "Shuttle", { enumerable: true, get: function() {
28403
28913
  return shuttle_1.Shuttle;
28404
28914
  } });
28915
+ var run_dispatch_1 = require_run_dispatch();
28916
+ Object.defineProperty(exports2, "runDispatch", { enumerable: true, get: function() {
28917
+ return run_dispatch_1.runDispatch;
28918
+ } });
28919
+ Object.defineProperty(exports2, "mergeRepairedResponse", { enumerable: true, get: function() {
28920
+ return run_dispatch_1.mergeRepairedResponse;
28921
+ } });
28922
+ Object.defineProperty(exports2, "GATE_REPAIR_BUDGET", { enumerable: true, get: function() {
28923
+ return run_dispatch_1.GATE_REPAIR_BUDGET;
28924
+ } });
28405
28925
  var loader_1 = require_loader();
28406
28926
  Object.defineProperty(exports2, "loadConfig", { enumerable: true, get: function() {
28407
28927
  return loader_1.loadConfig;
@@ -28468,6 +28988,12 @@ var require_dist5 = __commonJS({
28468
28988
  Object.defineProperty(exports2, "ExecutorEnvironmentError", { enumerable: true, get: function() {
28469
28989
  return types_1.ExecutorEnvironmentError;
28470
28990
  } });
28991
+ Object.defineProperty(exports2, "ExecutorModelUnavailableError", { enumerable: true, get: function() {
28992
+ return types_1.ExecutorModelUnavailableError;
28993
+ } });
28994
+ Object.defineProperty(exports2, "MODEL_UNAVAILABLE", { enumerable: true, get: function() {
28995
+ return types_1.MODEL_UNAVAILABLE;
28996
+ } });
28471
28997
  var claude_code_1 = require_claude_code();
28472
28998
  Object.defineProperty(exports2, "createClaudeCodeExecutor", { enumerable: true, get: function() {
28473
28999
  return claude_code_1.createClaudeCodeExecutor;
@@ -28606,7 +29132,7 @@ var import_path = require("path");
28606
29132
  var import_promises = require("fs/promises");
28607
29133
  var import_yaml = __toESM(require_dist3());
28608
29134
  var import_shuttle = __toESM(require_dist5());
28609
- var buildVersion = true ? "0.33.3" : pkg.version;
29135
+ var buildVersion = true ? "0.35.0" : pkg.version;
28610
29136
  var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
28611
29137
  var sifterBrand = {
28612
29138
  product: {
@@ -28635,6 +29161,8 @@ var sifterBrand = {
28635
29161
  // sifter.yaml names one. The init pre-flight reads it too, to check git and
28636
29162
  // claude before the pairing code is spent.
28637
29163
  defaultExecutor: "claude-code",
29164
+ // The block a fresh executor gets, from `init` and from `add executor`.
29165
+ executorBlock: buildExecutorBlock,
28638
29166
  // Floors the pre-flight enforces on the binaries the claude-code executor
28639
29167
  // spawns. Both are deliberately conservative — below the floor FAILS, so a
28640
29168
  // number chosen for tidiness rather than necessity would refuse a working
@@ -28778,7 +29306,8 @@ var sifterBrand = {
28778
29306
  const executorBlock = plan.keptBlock !== void 0 ? {
28779
29307
  ...plan.keptBlock,
28780
29308
  ...AGENT_EXECUTORS.includes(executor) && ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
28781
- } : await buildExecutorBlock(executor, ctx);
29309
+ } : buildExecutorBlock(executor, { ...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {} });
29310
+ const { [executor]: _replaced, ...otherExecutors } = existingExecutors;
28782
29311
  const doc = {
28783
29312
  auth: {
28784
29313
  jobsApiUrl: pairing.jobsApiUrl
@@ -28789,12 +29318,11 @@ var sifterBrand = {
28789
29318
  },
28790
29319
  pools: pairing.jobPools.map((p) => ({
28791
29320
  id: p.id,
28792
- name: p.name,
28793
- executor
29321
+ name: p.name
28794
29322
  })),
28795
29323
  executors: {
28796
- ...existingExecutors,
28797
- [executor]: executorBlock
29324
+ [executor]: executorBlock,
29325
+ ...otherExecutors
28798
29326
  }
28799
29327
  };
28800
29328
  const pollIntervalMs = ctx.setupOptions.pollIntervalMs ?? existing.polling?.intervalMs;
@@ -28813,14 +29341,14 @@ var sifterBrand = {
28813
29341
  function overlayOrigin(origin) {
28814
29342
  return Object.fromEntries(AGENT_EXECUTORS.map((e) => [e, { overlayAllowedHosts: [origin] }]));
28815
29343
  }
28816
- async function buildExecutorBlock(executor, ctx) {
29344
+ function buildExecutorBlock(executor, options) {
28817
29345
  switch (executor) {
28818
29346
  case "claude-code":
28819
29347
  case "codex":
28820
29348
  case "copilot": {
28821
29349
  return {
28822
29350
  type: executor,
28823
- ...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
29351
+ ...options.tmpDir ? { tmpDir: options.tmpDir } : {}
28824
29352
  };
28825
29353
  }
28826
29354
  case "anthropic-api": {