snipara-companion 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  Release notes for `snipara-companion`, newest first.
4
4
 
5
+ ## New In 3.5.0
6
+
7
+ - Adds an explicit `workflow run --strong-repair` handoff contract for one
8
+ bounded strong-adapter repair after local proof or output failure.
9
+ - Keeps Companion recommendation-only: it records final authority, proof,
10
+ scope, and `main_agent` fallback without silently launching workers.
11
+
12
+ ## New In 3.4.1
13
+
14
+ - Declares provider API keys by environment-variable name and supports
15
+ `authorization` or `x-api-key` headers without persisting secret values.
16
+ - Resolves declared Codex and Claude CLI workers to explicit native adapters;
17
+ unsupported generic CLI profiles fail closed.
18
+ - Preserves opt-in local routing behavior while forwarding provider auth metadata
19
+ to the orchestrator catalog.
20
+
5
21
  ## New In 3.4.0
6
22
 
7
23
  - Adds repeatable `workers execute --output-fragment` contracts.
package/dist/index.d.ts CHANGED
@@ -3238,6 +3238,14 @@ interface AdaptiveRoutingResolution {
3238
3238
  warnings?: string[];
3239
3239
  rejectedReasons?: Record<string, string[]>;
3240
3240
  }
3241
+ interface AdaptiveStrongRepairContract {
3242
+ enabled: boolean;
3243
+ maxAttempts: 1;
3244
+ finalAuthority: "strong_adapter";
3245
+ proofRequired: true;
3246
+ scopePreserved: true;
3247
+ fallback: "main_agent";
3248
+ }
3241
3249
  interface AdaptiveWorkRoutingRecommendation {
3242
3250
  workProfile: AdaptiveWorkProfile;
3243
3251
  requirements: AdaptiveModelRequirements;
@@ -3245,6 +3253,7 @@ interface AdaptiveWorkRoutingRecommendation {
3245
3253
  gateway?: AdaptiveRoutingGatewayStatus;
3246
3254
  runtimeCatalog?: AdaptiveRoutingRuntimeCatalog;
3247
3255
  resolution?: AdaptiveRoutingResolution;
3256
+ strongRepair?: AdaptiveStrongRepairContract;
3248
3257
  }
3249
3258
  interface AdaptiveWorkRoutingOptions {
3250
3259
  query: string;
@@ -3259,6 +3268,7 @@ interface AdaptiveWorkRoutingOptions {
3259
3268
  catalogLimit?: number;
3260
3269
  dailyBudgetCents?: number;
3261
3270
  monthlyBudgetCents?: number;
3271
+ strongRepair?: boolean;
3262
3272
  }
3263
3273
  interface OrchestratorHandoffArtifact {
3264
3274
  schemaVersion: "snipara.orchestrator.handoff.v1";
@@ -3289,6 +3299,7 @@ interface OrchestratorHandoffArtifact {
3289
3299
  gateway?: AdaptiveRoutingGatewayStatus;
3290
3300
  runtimeCatalog?: AdaptiveRoutingRuntimeCatalog;
3291
3301
  resolution?: AdaptiveRoutingResolution;
3302
+ strongRepair?: AdaptiveStrongRepairContract;
3292
3303
  };
3293
3304
  task: {
3294
3305
  title: string;
@@ -4478,6 +4489,8 @@ interface LocalWorkerDeclaration {
4478
4489
  endpointType: "local";
4479
4490
  provider: string;
4480
4491
  baseUrl: string;
4492
+ apiKeyEnv?: string;
4493
+ apiKeyHeader?: "authorization" | "x-api-key";
4481
4494
  command?: string;
4482
4495
  model?: string;
4483
4496
  preferModel?: string;
@@ -4496,11 +4509,16 @@ interface LocalWorkersConfig {
4496
4509
  interface LocalWorkerRoutingDefaults {
4497
4510
  worker: LocalWorkerDeclaration;
4498
4511
  routeLocalWorkers: true;
4512
+ routingLocalTransport: "openai_http" | "cli";
4513
+ routingLocalAdapter?: "codex_app_server" | "claude_cli";
4514
+ routingLocalCommand?: string;
4499
4515
  routingWorkerRole: string;
4500
4516
  routingLocalBaseUrl: string;
4501
4517
  routingLocalModel?: string;
4502
4518
  routingLocalPreferModel?: string;
4503
4519
  routingLocalProvider: string;
4520
+ routingLocalApiKeyEnv?: string;
4521
+ routingLocalApiKeyHeader?: "authorization" | "x-api-key";
4504
4522
  routingPreferredEndpoints: string[];
4505
4523
  routingAllowedEndpoints: string[];
4506
4524
  plannerRetainsReasoning: true;
@@ -4520,6 +4538,8 @@ interface LocalWorkerAddOptions {
4520
4538
  json?: boolean;
4521
4539
  transport?: "openai_http" | "cli";
4522
4540
  command?: string;
4541
+ apiKeyEnv?: string;
4542
+ apiKeyHeader?: "authorization" | "x-api-key";
4523
4543
  }
4524
4544
  interface LocalWorkerStatusOptions {
4525
4545
  json?: boolean;
@@ -4542,6 +4562,8 @@ interface LocalWorkerProbeOptions {
4542
4562
  writeScope?: string[];
4543
4563
  reasoning?: "low" | "medium" | "high";
4544
4564
  contextWindow?: number;
4565
+ apiKeyEnv?: string;
4566
+ apiKeyHeader?: "authorization" | "x-api-key";
4545
4567
  json?: boolean;
4546
4568
  }
4547
4569
  declare function workersLocalAddCommand(options: LocalWorkerAddOptions): void;
package/dist/index.js CHANGED
@@ -9755,7 +9755,9 @@ function normalizeWorkerProfileTransport(value) {
9755
9755
  baseUrl,
9756
9756
  ...stringValue(value.model) ? { model: stringValue(value.model) } : {},
9757
9757
  ...stringValue(value.preferModel) ? { preferModel: stringValue(value.preferModel) } : {},
9758
- ...stringValue(value.provider) ? { provider: stringValue(value.provider) } : {}
9758
+ ...stringValue(value.provider) ? { provider: stringValue(value.provider) } : {},
9759
+ ...safeEnvironmentVariableName(value.apiKeyEnv) ? { apiKeyEnv: safeEnvironmentVariableName(value.apiKeyEnv) } : {},
9760
+ ...value.apiKeyHeader === "authorization" || value.apiKeyHeader === "x-api-key" ? { apiKeyHeader: value.apiKeyHeader } : {}
9759
9761
  };
9760
9762
  }
9761
9763
  if (kind === "cli") {
@@ -9897,6 +9899,10 @@ function numberValue(value) {
9897
9899
  function booleanValue(value) {
9898
9900
  return value === true;
9899
9901
  }
9902
+ function safeEnvironmentVariableName(value) {
9903
+ const normalized = stringValue(value);
9904
+ return normalized && /^[A-Za-z_][A-Za-z0-9_]*$/.test(normalized) ? normalized : void 0;
9905
+ }
9900
9906
  function stringValue(value) {
9901
9907
  if (value === null || value === void 0) {
9902
9908
  return void 0;
@@ -14201,7 +14207,8 @@ function buildOrchestratorHandoff(options) {
14201
14207
  routingCard: options.adaptiveRouting.routingCard,
14202
14208
  ...options.adaptiveRouting.gateway ? { gateway: options.adaptiveRouting.gateway } : {},
14203
14209
  ...options.adaptiveRouting.runtimeCatalog ? { runtimeCatalog: options.adaptiveRouting.runtimeCatalog } : {},
14204
- ...options.adaptiveRouting.resolution ? { resolution: options.adaptiveRouting.resolution } : {}
14210
+ ...options.adaptiveRouting.resolution ? { resolution: options.adaptiveRouting.resolution } : {},
14211
+ ...options.adaptiveRouting.strongRepair ? { strongRepair: options.adaptiveRouting.strongRepair } : {}
14205
14212
  } : {}
14206
14213
  },
14207
14214
  task: {
@@ -14318,7 +14325,17 @@ function buildAdaptiveWorkRoutingRecommendation(options) {
14318
14325
  return {
14319
14326
  workProfile,
14320
14327
  requirements,
14321
- routingCard
14328
+ routingCard,
14329
+ ...options.strongRepair ? {
14330
+ strongRepair: {
14331
+ enabled: true,
14332
+ maxAttempts: 1,
14333
+ finalAuthority: "strong_adapter",
14334
+ proofRequired: true,
14335
+ scopePreserved: true,
14336
+ fallback: "main_agent"
14337
+ }
14338
+ } : {}
14322
14339
  };
14323
14340
  }
14324
14341
  function inferAdaptiveTaskType(query, changedFiles) {
@@ -19627,6 +19644,9 @@ function workersLocalAddCommand(options) {
19627
19644
  printKeyValue("Role:", result.worker.workerRole);
19628
19645
  printKeyValue("Provider:", result.worker.provider);
19629
19646
  printKeyValue("Base URL:", result.worker.baseUrl);
19647
+ if (result.worker.apiKeyEnv) {
19648
+ printKeyValue("API key env:", result.worker.apiKeyEnv);
19649
+ }
19630
19650
  if (result.worker.model) {
19631
19651
  printKeyValue("Model:", result.worker.model);
19632
19652
  } else if (result.worker.preferModel) {
@@ -19718,7 +19738,9 @@ function workersLocalProbeCommand(options) {
19718
19738
  capabilities: normalizeStringList3(options.capabilities),
19719
19739
  writeScope: normalizeStringList3(options.writeScope),
19720
19740
  model,
19721
- preferModel
19741
+ preferModel,
19742
+ apiKeyEnv: options.apiKeyEnv,
19743
+ apiKeyHeader: options.apiKeyHeader
19722
19744
  });
19723
19745
  const catalogContextWindow = resolveContextWindowFromCatalog(catalog, model, preferModel);
19724
19746
  const contextWindow = normalizeContextWindow(
@@ -19737,7 +19759,9 @@ function workersLocalProbeCommand(options) {
19737
19759
  baseUrl,
19738
19760
  ...model ? { model } : {},
19739
19761
  ...preferModel ? { preferModel } : {},
19740
- provider
19762
+ provider,
19763
+ ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {},
19764
+ ...options.apiKeyHeader ? { apiKeyHeader: options.apiKeyHeader } : {}
19741
19765
  },
19742
19766
  capabilities: {
19743
19767
  roles: [role],
@@ -19891,7 +19915,26 @@ function resolveLocalWorkerRoutingDefaults(options = {}) {
19891
19915
  throw new Error(`Local worker ${requestedId} is not declared.`);
19892
19916
  }
19893
19917
  if (!worker.baseUrl || worker.baseUrl === "cli://command") {
19894
- return null;
19918
+ const command = worker.command?.toLowerCase() ?? "";
19919
+ const adapter = command.includes("codex") ? "codex_app_server" : command.includes("claude") ? "claude_cli" : void 0;
19920
+ if (!adapter) {
19921
+ throw new Error(
19922
+ `CLI worker ${worker.id} is not mapped to a supported native adapter; use a Codex or Claude command.`
19923
+ );
19924
+ }
19925
+ return {
19926
+ worker,
19927
+ routeLocalWorkers: true,
19928
+ routingLocalTransport: "cli",
19929
+ routingLocalBaseUrl: "cli://command",
19930
+ routingLocalAdapter: adapter,
19931
+ routingLocalCommand: worker.command,
19932
+ routingWorkerRole: worker.workerRole,
19933
+ routingLocalProvider: worker.provider,
19934
+ routingPreferredEndpoints: ["local"],
19935
+ routingAllowedEndpoints: ["local"],
19936
+ plannerRetainsReasoning: true
19937
+ };
19895
19938
  }
19896
19939
  if (!worker.model && !worker.preferModel) {
19897
19940
  const hasCatalogHint = true;
@@ -19900,11 +19943,14 @@ function resolveLocalWorkerRoutingDefaults(options = {}) {
19900
19943
  return {
19901
19944
  worker,
19902
19945
  routeLocalWorkers: true,
19946
+ routingLocalTransport: "openai_http",
19903
19947
  routingWorkerRole: worker.workerRole,
19904
19948
  routingLocalBaseUrl: worker.baseUrl,
19905
19949
  ...worker.model ? { routingLocalModel: worker.model } : {},
19906
19950
  ...worker.preferModel ? { routingLocalPreferModel: worker.preferModel } : {},
19907
19951
  routingLocalProvider: worker.provider,
19952
+ ...worker.apiKeyEnv ? { routingLocalApiKeyEnv: worker.apiKeyEnv } : {},
19953
+ ...worker.apiKeyHeader ? { routingLocalApiKeyHeader: worker.apiKeyHeader } : {},
19908
19954
  routingPreferredEndpoints: ["local"],
19909
19955
  routingAllowedEndpoints: ["local"],
19910
19956
  plannerRetainsReasoning: true
@@ -20012,7 +20058,9 @@ function normalizeWorkerProfileRecord(value) {
20012
20058
  transport: {
20013
20059
  ...transport,
20014
20060
  baseUrl,
20015
- provider: normalizeProvider(transport.provider ?? "lm-studio")
20061
+ provider: normalizeProvider(transport.provider ?? "lm-studio"),
20062
+ ...transport.apiKeyEnv ? { apiKeyEnv: transport.apiKeyEnv } : {},
20063
+ ...transport.apiKeyHeader ? { apiKeyHeader: transport.apiKeyHeader } : {}
20016
20064
  }
20017
20065
  };
20018
20066
  }
@@ -20047,7 +20095,9 @@ function toProfileFromLegacyWorker(worker) {
20047
20095
  baseUrl: worker.baseUrl,
20048
20096
  ...worker.model ? { model: worker.model } : {},
20049
20097
  ...worker.preferModel ? { preferModel: worker.preferModel } : {},
20050
- provider: worker.provider
20098
+ provider: worker.provider,
20099
+ ...worker.apiKeyEnv ? { apiKeyEnv: worker.apiKeyEnv } : {},
20100
+ ...worker.apiKeyHeader ? { apiKeyHeader: worker.apiKeyHeader } : {}
20051
20101
  },
20052
20102
  capabilities: {
20053
20103
  roles: [normalizeWorkerRole(worker.workerRole)],
@@ -20090,6 +20140,8 @@ function toLocalWorkerDeclaration(profile) {
20090
20140
  endpointType: "local",
20091
20141
  provider: normalizeProvider(profile.transport.provider ?? "lm-studio"),
20092
20142
  baseUrl: profile.transport.baseUrl,
20143
+ ...profile.transport.apiKeyEnv ? { apiKeyEnv: profile.transport.apiKeyEnv } : {},
20144
+ ...profile.transport.apiKeyHeader ? { apiKeyHeader: profile.transport.apiKeyHeader } : {},
20093
20145
  ...stringValue6(profile.transport.model) ? { model: stringValue6(profile.transport.model) } : {},
20094
20146
  ...stringValue6(profile.transport.preferModel) && !profile.transport.model ? { preferModel: stringValue6(profile.transport.preferModel) } : {},
20095
20147
  capabilities: normalizeWorkerCapabilities(profile.capabilities.roles),
@@ -20195,6 +20247,12 @@ function runOrchestratorLocalCatalog(options) {
20195
20247
  if (options.preferModel) {
20196
20248
  args.push("--prefer-model", options.preferModel);
20197
20249
  }
20250
+ if (options.apiKeyEnv) {
20251
+ args.push("--api-key-env", options.apiKeyEnv);
20252
+ }
20253
+ if (options.apiKeyHeader) {
20254
+ args.push("--api-key-header", options.apiKeyHeader);
20255
+ }
20198
20256
  try {
20199
20257
  const result = (0, import_node_child_process9.execFileSync)("snipara-orchestrator", args, {
20200
20258
  encoding: "utf8",
@@ -20330,12 +20388,15 @@ function normalizeWorkerTransport(options) {
20330
20388
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? "http://127.0.0.1:1234");
20331
20389
  const model = stringValue6(options.model);
20332
20390
  const preferModel = stringValue6(options.preferModel);
20391
+ const apiKeyEnv = normalizeApiKeyEnvironmentName(options.apiKeyEnv);
20333
20392
  return {
20334
20393
  kind: "openai_http",
20335
20394
  baseUrl,
20336
20395
  ...model ? { model } : {},
20337
20396
  ...preferModel ? { preferModel } : {},
20338
- provider: normalizeProvider(stringValue6(options.provider) ?? "lm-studio")
20397
+ provider: normalizeProvider(stringValue6(options.provider) ?? "lm-studio"),
20398
+ ...apiKeyEnv ? { apiKeyEnv } : {},
20399
+ ...options.apiKeyHeader ? { apiKeyHeader: options.apiKeyHeader } : {}
20339
20400
  };
20340
20401
  }
20341
20402
  function normalizeProvider(value) {
@@ -20357,6 +20418,8 @@ function normalizeLegacyWorkerDeclaration(value) {
20357
20418
  endpointType: "local",
20358
20419
  provider: normalizeProvider(stringValue6(value.provider) ?? "lm-studio"),
20359
20420
  baseUrl,
20421
+ ...stringValue6(value.apiKeyEnv) ? { apiKeyEnv: stringValue6(value.apiKeyEnv) } : {},
20422
+ ...value.apiKeyHeader === "authorization" || value.apiKeyHeader === "x-api-key" ? { apiKeyHeader: value.apiKeyHeader } : {},
20360
20423
  ...stringValue6(value.model) ? { model: stringValue6(value.model) } : {},
20361
20424
  ...stringValue6(value.preferModel) ? { preferModel: stringValue6(value.preferModel) } : {},
20362
20425
  capabilities: normalizeStringList3(value.capabilities).length > 0 ? normalizeStringList3(value.capabilities) : defaultCapabilitiesForWorker(normalizeWorkerRole(value.workerRole)),
@@ -20434,6 +20497,16 @@ function stringValue6(value) {
20434
20497
  const stringified = String(value).trim();
20435
20498
  return stringified || void 0;
20436
20499
  }
20500
+ function normalizeApiKeyEnvironmentName(value) {
20501
+ const normalized = stringValue6(value);
20502
+ if (!normalized) {
20503
+ return void 0;
20504
+ }
20505
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(normalized)) {
20506
+ throw new Error("API key env must be a shell-safe environment variable name.");
20507
+ }
20508
+ return normalized;
20509
+ }
20437
20510
  function isRecord10(value) {
20438
20511
  return typeof value === "object" && value !== null && !Array.isArray(value);
20439
20512
  }
@@ -25105,6 +25178,10 @@ function printAdaptiveRoutingRecommendation(routing) {
25105
25178
  if (routing.requirements.plannerRetainsReasoning) {
25106
25179
  printKeyValue2("Planner retains reasoning:", "yes");
25107
25180
  }
25181
+ if (routing.strongRepair?.enabled) {
25182
+ printKeyValue2("Strong repair:", "one bounded attempt after proof/output failure");
25183
+ printKeyValue2("Repair authority:", routing.strongRepair.finalAuthority);
25184
+ }
25108
25185
  if (routing.gateway) {
25109
25186
  printKeyValue2(
25110
25187
  routing.gateway.source === "local_orchestrator" ? "Local route:" : "Hosted catalog:",
@@ -25425,7 +25502,7 @@ function shouldResolveAdaptiveRoutingLocally(options, routing) {
25425
25502
  return preferred.includes("local") || allowed.length === 0 || allowed.includes("local");
25426
25503
  }
25427
25504
  function hasLocalRoutingRequest(options) {
25428
- return options.routeLocalWorkers === true || Boolean(options.routingLocalWorker) || Boolean(options.routingLocalBaseUrl) || Boolean(options.routingLocalModel) || Boolean(options.routingLocalPreferModel) || Boolean(options.routingLocalProvider) || normalizeRoutingEndpointTypes(options.routingPreferredEndpoints).includes("local") || normalizeRoutingEndpointTypes(options.routingAllowedEndpoints).includes("local");
25505
+ return options.routeLocalWorkers === true || Boolean(options.routingLocalWorker) || Boolean(options.routingLocalBaseUrl) || Boolean(options.routingLocalModel) || Boolean(options.routingLocalPreferModel) || Boolean(options.routingLocalProvider) || Boolean(options.routingLocalApiKeyEnv) || normalizeRoutingEndpointTypes(options.routingPreferredEndpoints).includes("local") || normalizeRoutingEndpointTypes(options.routingAllowedEndpoints).includes("local");
25429
25506
  }
25430
25507
  function applyLocalWorkerRoutingDefaults(options, defaults) {
25431
25508
  if (!defaults) {
@@ -25434,6 +25511,9 @@ function applyLocalWorkerRoutingDefaults(options, defaults) {
25434
25511
  return {
25435
25512
  ...options,
25436
25513
  routeLocalWorkers: options.routeLocalWorkers ?? defaults.routeLocalWorkers,
25514
+ routingLocalTransport: options.routingLocalTransport ?? defaults.routingLocalTransport,
25515
+ routingLocalAdapter: options.routingLocalAdapter ?? defaults.routingLocalAdapter,
25516
+ routingLocalCommand: options.routingLocalCommand ?? defaults.routingLocalCommand,
25437
25517
  routingWorkerRole: options.routingWorkerRole ?? defaults.routingWorkerRole,
25438
25518
  routingPreferredEndpoints: options.routingPreferredEndpoints && options.routingPreferredEndpoints.length > 0 ? options.routingPreferredEndpoints : defaults.routingPreferredEndpoints,
25439
25519
  routingAllowedEndpoints: options.routingAllowedEndpoints && options.routingAllowedEndpoints.length > 0 ? options.routingAllowedEndpoints : defaults.routingAllowedEndpoints,
@@ -25441,6 +25521,8 @@ function applyLocalWorkerRoutingDefaults(options, defaults) {
25441
25521
  routingLocalModel: options.routingLocalModel ?? defaults.routingLocalModel,
25442
25522
  routingLocalPreferModel: options.routingLocalPreferModel ?? defaults.routingLocalPreferModel,
25443
25523
  routingLocalProvider: options.routingLocalProvider ?? defaults.routingLocalProvider,
25524
+ routingLocalApiKeyEnv: options.routingLocalApiKeyEnv ?? defaults.routingLocalApiKeyEnv,
25525
+ routingLocalApiKeyHeader: options.routingLocalApiKeyHeader ?? defaults.routingLocalApiKeyHeader,
25444
25526
  plannerRetainsReasoning: options.plannerRetainsReasoning ?? defaults.plannerRetainsReasoning
25445
25527
  };
25446
25528
  }
@@ -25455,6 +25537,67 @@ function runOrchestratorJsonCommand(args, cwd = process.cwd()) {
25455
25537
  }
25456
25538
  function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = process.cwd()) {
25457
25539
  try {
25540
+ if (options.routingLocalTransport === "cli") {
25541
+ const candidateId = options.routingLocalAdapter ?? options.routingLocalCommand ?? "declared-cli";
25542
+ const candidate = {
25543
+ candidateId,
25544
+ workerClass: routing.requirements.workerRole,
25545
+ catalogSource: "declared_cli_worker",
25546
+ endpointType: "local",
25547
+ workerRoles: [routing.requirements.workerRole],
25548
+ capabilities: routing.requirements.capabilities ?? [],
25549
+ writeScope: routing.requirements.writeScope ?? [],
25550
+ reasoning: routing.requirements.reasoning,
25551
+ speed: "normal",
25552
+ cost: "balanced",
25553
+ contextBudget: routing.requirements.contextBudget,
25554
+ qualityScore: null,
25555
+ isAvailable: true,
25556
+ requiresApiKey: false,
25557
+ metadata: {
25558
+ adapter: options.routingLocalAdapter,
25559
+ command: options.routingLocalCommand
25560
+ }
25561
+ };
25562
+ const resolution2 = normalizeAdaptiveRoutingResolution(
25563
+ runOrchestratorJsonCommand(
25564
+ [
25565
+ "route",
25566
+ "--dry-run",
25567
+ "--json",
25568
+ "--work-profile-json",
25569
+ JSON.stringify(routing.workProfile),
25570
+ "--requirements-json",
25571
+ JSON.stringify(routing.requirements),
25572
+ "--candidate-json",
25573
+ JSON.stringify(candidate)
25574
+ ],
25575
+ cwd
25576
+ )
25577
+ );
25578
+ return {
25579
+ ...routing,
25580
+ gateway: {
25581
+ source: "local_orchestrator",
25582
+ success: true,
25583
+ resolutionStatus: resolution2?.status ?? "resolved",
25584
+ candidateCount: 1,
25585
+ fallback: "main_agent",
25586
+ warnings: []
25587
+ },
25588
+ runtimeCatalog: {
25589
+ source: "declared_cli_worker",
25590
+ candidates: [candidate],
25591
+ workerEndpoints: {
25592
+ [candidateId]: {
25593
+ adapter: options.routingLocalAdapter,
25594
+ command: options.routingLocalCommand
25595
+ }
25596
+ }
25597
+ },
25598
+ resolution: resolution2
25599
+ };
25600
+ }
25458
25601
  const catalogArgs = [
25459
25602
  "local-model-catalog",
25460
25603
  "--json",
@@ -25477,6 +25620,12 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
25477
25620
  if (options.routingLocalProvider) {
25478
25621
  catalogArgs.push("--provider", options.routingLocalProvider);
25479
25622
  }
25623
+ if (options.routingLocalApiKeyEnv) {
25624
+ catalogArgs.push("--api-key-env", options.routingLocalApiKeyEnv);
25625
+ }
25626
+ if (options.routingLocalApiKeyHeader) {
25627
+ catalogArgs.push("--api-key-header", options.routingLocalApiKeyHeader);
25628
+ }
25480
25629
  const runtimeCatalog = normalizeAdaptiveRoutingCatalog(
25481
25630
  runOrchestratorJsonCommand(catalogArgs, cwd)
25482
25631
  );
@@ -27367,7 +27516,7 @@ async function workflowRunCommand(options) {
27367
27516
  }
27368
27517
  function shouldBuildAdaptiveRouting(options) {
27369
27518
  return Boolean(
27370
- options.adaptiveRoutingDryRun || options.routeLocalWorkers || options.routingLocalWorker || options.routingWorkerRole || options.routingPreferredEndpoints?.length || options.routingAllowedEndpoints?.length || options.routingLocalBaseUrl || options.routingLocalModel || options.routingLocalPreferModel || options.routingLocalProvider || options.plannerRetainsReasoning
27519
+ options.adaptiveRoutingDryRun || options.routeLocalWorkers || options.routingLocalWorker || options.routingWorkerRole || options.routingPreferredEndpoints?.length || options.routingAllowedEndpoints?.length || options.routingLocalBaseUrl || options.routingLocalModel || options.routingLocalPreferModel || options.routingLocalProvider || options.routingLocalApiKeyEnv || options.plannerRetainsReasoning || options.strongRepair
27371
27520
  );
27372
27521
  }
27373
27522
  function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = []) {
@@ -27411,6 +27560,7 @@ function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = [
27411
27560
  allowedEndpointTypes,
27412
27561
  workerRole,
27413
27562
  plannerRetainsReasoning: options.plannerRetainsReasoning ?? policy?.plannerRetainsReasoning ?? (options.routeLocalWorkers ? true : void 0),
27563
+ strongRepair: options.strongRepair,
27414
27564
  catalogLimit: policy?.catalogLimit ?? DEFAULT_ADAPTIVE_ROUTING_CATALOG_LIMIT,
27415
27565
  dailyBudgetCents: policy?.dailyBudgetCents,
27416
27566
  monthlyBudgetCents: policy?.monthlyBudgetCents
@@ -38817,8 +38967,17 @@ workflow.command("run").description("Run a LITE/STANDARD/FULL/orchestrate workfl
38817
38967
  "--routing-local-prefer-model <text>",
38818
38968
  "Prefer a local /v1/models entry containing this text during worker routing"
38819
38969
  ).option("--routing-local-provider <provider>", "Provider label for local worker routing").option(
38970
+ "--routing-local-api-key-env <env>",
38971
+ "Environment variable containing the local/remote provider API key"
38972
+ ).option(
38973
+ "--routing-local-api-key-header <header>",
38974
+ "Provider API key header: authorization or x-api-key"
38975
+ ).option(
38820
38976
  "--planner-retains-reasoning",
38821
38977
  "Mark the main planner as retaining deep reasoning while the worker executes scoped work"
38978
+ ).option(
38979
+ "--strong-repair",
38980
+ "Allow one bounded strong-adapter repair after local proof or output validation fails"
38822
38981
  ).option("--write-plan-file <file>", "Write the generated FULL-mode plan as workflow JSON").option(
38823
38982
  "--start-workflow-from-plan",
38824
38983
  "Start a local managed workflow from the generated FULL-mode plan"
@@ -38844,7 +39003,10 @@ workflow.command("run").description("Run a LITE/STANDARD/FULL/orchestrate workfl
38844
39003
  routingLocalModel: options.routingLocalModel,
38845
39004
  routingLocalPreferModel: options.routingLocalPreferModel,
38846
39005
  routingLocalProvider: options.routingLocalProvider,
39006
+ routingLocalApiKeyEnv: options.routingLocalApiKeyEnv,
39007
+ routingLocalApiKeyHeader: options.routingLocalApiKeyHeader,
38847
39008
  plannerRetainsReasoning: options.plannerRetainsReasoning ? true : void 0,
39009
+ strongRepair: Boolean(options.strongRepair),
38848
39010
  writePlanFile: options.writePlanFile,
38849
39011
  startWorkflowFromPlan: Boolean(options.startWorkflowFromPlan),
38850
39012
  workflowId: options.workflowId,
@@ -38944,7 +39106,11 @@ localWorkers.command("add").description("Declare a local worker and enable local
38944
39106
  "--role <role>",
38945
39107
  "Worker role, for example coding, documentation, tests, or review",
38946
39108
  "coding"
38947
- ).option("--provider <provider>", "Provider label", "lm-studio").option("--base-url <url>", "OpenAI-compatible local runtime base URL", "http://127.0.0.1:1234").option("--model <id>", "Exact model id exposed by the local runtime").option(
39109
+ ).option("--provider <provider>", "Provider label", "lm-studio").option("--base-url <url>", "OpenAI-compatible local runtime base URL", "http://127.0.0.1:1234").option("--api-key-env <env>", "Environment variable containing the provider API key").option(
39110
+ "--api-key-header <header>",
39111
+ "Provider API key header: authorization or x-api-key",
39112
+ "authorization"
39113
+ ).option("--model <id>", "Exact model id exposed by the local runtime").option(
38948
39114
  "--prefer-model <text>",
38949
39115
  "Fallback model id substring to prefer when no exact model is set"
38950
39116
  ).option("--transport <openai_http|cli>", "Worker transport: openai_http (default) or cli").option("--command <command>", "CLI command to execute when transport is cli").option("--capability <capability>", "Worker capability; repeatable", collectOption, []).option(
@@ -38971,6 +39137,8 @@ localWorkers.command("add").description("Declare a local worker and enable local
38971
39137
  preferModel: options.preferModel,
38972
39138
  transport: options.transport,
38973
39139
  command: options.command,
39140
+ apiKeyEnv: options.apiKeyEnv,
39141
+ apiKeyHeader: options.apiKeyHeader,
38974
39142
  capabilities: options.capability,
38975
39143
  reasoning: options.reasoning ? options.reasoning.toLowerCase() : void 0,
38976
39144
  contextWindow: options.contextWindow,
@@ -38988,7 +39156,11 @@ localWorkers.command("list").description("List declared local workers").option("
38988
39156
  localWorkers.command("remove").description("Remove a declared local worker").argument("id", "Local worker id").option("--json", "Print raw JSON").action((id, options) => {
38989
39157
  workersLocalRemoveCommand({ id, json: options.json });
38990
39158
  });
38991
- localWorkers.command("probe").description("Probe an OpenAI-compatible endpoint and draft a worker suggestion").option("--base-url <url>", "OpenAI-compatible local runtime base URL", "http://127.0.0.1:1234").option("--provider <provider>", "Provider label", "lm-studio").option("--model <id>", "Exact model id exposed by the local runtime").option("--prefer-model <text>", "Fallback local model substring when exact model is unset").option(
39159
+ localWorkers.command("probe").description("Probe an OpenAI-compatible endpoint and draft a worker suggestion").option("--base-url <url>", "OpenAI-compatible local runtime base URL", "http://127.0.0.1:1234").option("--api-key-env <env>", "Environment variable containing the provider API key").option(
39160
+ "--api-key-header <header>",
39161
+ "Provider API key header: authorization or x-api-key",
39162
+ "authorization"
39163
+ ).option("--provider <provider>", "Provider label", "lm-studio").option("--model <id>", "Exact model id exposed by the local runtime").option("--prefer-model <text>", "Fallback local model substring when exact model is unset").option(
38992
39164
  "--role <role>",
38993
39165
  "Worker role; for example coding, documentation, tests, or review",
38994
39166
  "coding"
@@ -39019,6 +39191,8 @@ localWorkers.command("probe").description("Probe an OpenAI-compatible endpoint a
39019
39191
  reasoning: options.reasoning ? options.reasoning.toLowerCase() : void 0,
39020
39192
  contextWindow: options.contextWindow,
39021
39193
  writeScope: options.writeScope,
39194
+ apiKeyEnv: options.apiKeyEnv,
39195
+ apiKeyHeader: options.apiKeyHeader,
39022
39196
  json: options.json
39023
39197
  });
39024
39198
  });
@@ -421,6 +421,7 @@ snipara-companion workflow run \
421
421
  --routing-allowed-endpoint local \
422
422
  --routing-allowed-endpoint cloud \
423
423
  --planner-retains-reasoning \
424
+ --strong-repair \
424
425
  "Update documentation for the new gateway"
425
426
  ```
426
427
 
@@ -464,7 +465,8 @@ snipara-companion workers local add \
464
465
  --role coding \
465
466
  --provider lm-studio \
466
467
  --base-url http://127.0.0.1:1234 \
467
- --model openai/gpt-oss-20b
468
+ --model openai/gpt-oss-20b \
469
+ --api-key-env LM_STUDIO_API_KEY
468
470
  ```
469
471
 
470
472
  The declaration is written under `.snipara/workers/<worker-id>.json`; Companion
@@ -499,7 +501,11 @@ Do not store secrets in worker profiles. Local endpoints such as
499
501
  `http://127.0.0.1:1234` are safe to commit when they contain no credentials, but
500
502
  cloud CLI transports or authenticated endpoints must reference secrets through
501
503
  environment variables rather than embedding API keys, bearer tokens, passwords,
502
- or private URLs directly in `.snipara/workers/*.json`.
504
+ or private URLs directly in `.snipara/workers/*.json`. `--api-key-env` stores
505
+ only the variable name; pair it with `--api-key-header authorization` (default)
506
+ or `--api-key-header x-api-key`. Native Codex and Claude profiles use their
507
+ host-managed credentials. A generic `cli://command` profile fails closed unless
508
+ the command maps to a supported Codex or Claude adapter.
503
509
 
504
510
  Use `workers local probe` to query a local endpoint and preview a declaration
505
511
  proposal before committing it:
@@ -518,6 +524,12 @@ routing/handoff contract: Companion resolves the local candidate through
518
524
  `snipara-orchestrator`, records metadata, and leaves execution plus proof review
519
525
  to the supervising agent workflow.
520
526
 
527
+ Add `--strong-repair` when the supervising workflow should permit one strong
528
+ adapter repair after a local proof or output failure. The handoff records the
529
+ contract (`maxAttempts: 1`, same scope and proof, strong adapter as final
530
+ authority, `main_agent` fallback); Companion remains recommendation-only and
531
+ does not launch the worker.
532
+
521
533
  Use Qwen for reflection, architecture, and documentation:
522
534
 
523
535
  ```bash
@@ -559,6 +571,39 @@ snipara-orchestrator local-model-catalog \
559
571
  --json > .snipara/local-devstral-runtime-catalog.json
560
572
  ```
561
573
 
574
+ For a remote OpenAI-compatible runtime, use an explicit allowlist and an
575
+ environment-backed key when running the native host:
576
+
577
+ ```bash
578
+ snipara-orchestrator host run \
579
+ --adapter openai_compatible \
580
+ --base-url https://provider.example.com --allow-remote \
581
+ --api-key-env PROVIDER_API_KEY --api-key-header authorization \
582
+ --model provider/coder --task "Bounded task" --workspace . \
583
+ --write-scope docs/** --proof "git diff --check" --execute
584
+ ```
585
+
586
+ For a loopback local worker with one explicit strong repair attempt:
587
+
588
+ ```bash
589
+ snipara-orchestrator host run \
590
+ --adapter openai_compatible --base-url http://127.0.0.1:1234 \
591
+ --model local/coder --task "Bounded task" --workspace . \
592
+ --write-scope docs/** --proof "git diff --check" \
593
+ --strong-repair --repair-adapter codex_app_server --execute
594
+ ```
595
+
596
+ The repair reuses the approval envelope when approval is required, reruns the
597
+ same proof, and records redacted repair metrics in the durable host receipt.
598
+ Scope violations, unavailable repair hosts, skipped proof, or a failed repair
599
+ escalate without a retry loop.
600
+
601
+ Native host proof commands run automatically after dispatch. Use
602
+ `--no-run-proof` only when a reviewer will validate the receipt; the state then
603
+ remains `verification_required`. Use `--require-approval` with both an approval
604
+ receipt id and `--approval-receipt-file <json>` for unattended execution; an id
605
+ alone never bypasses the approval gate.
606
+
562
607
  The local catalog records the OpenAI-compatible routes exposed by LM Studio:
563
608
  `GET /v1/models`, `POST /v1/responses`, `POST /v1/chat/completions`,
564
609
  `POST /v1/completions`, and `POST /v1/embeddings`. `--prefer-model devstral`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {