sneakoscope 10.5.3 → 10.5.4

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 (31) hide show
  1. package/README.md +1 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/commands/bridge.js +8 -3
  5. package/dist/config/skills-manifest.json +1 -1
  6. package/dist/core/agents/native-worker-backend-router.js +20 -12
  7. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +71 -7
  8. package/dist/core/codex-lb/desktop-controller-v3.js +1 -1
  9. package/dist/core/decisions/child-model-choice.js +2 -1
  10. package/dist/core/decisions/cli.js +9 -2
  11. package/dist/core/hooks-runtime/hook-context.js +4 -4
  12. package/dist/core/hooks-runtime/jev-spawn-routing.js +8 -8
  13. package/dist/core/hooks-runtime/spawn-role-pin-heal.js +1 -1
  14. package/dist/core/hooks-runtime/subagent-context.js +3 -3
  15. package/dist/core/hooks-runtime/subagent-spawn-policy.js +18 -11
  16. package/dist/core/hooks-runtime.js +1 -1
  17. package/dist/core/subagents/child-model-allowlist.js +107 -8
  18. package/dist/core/subagents/child-model-plan.js +18 -27
  19. package/dist/core/subagents/model-tiers.js +45 -10
  20. package/dist/core/subagents/naruto-host-credentials.js +24 -5
  21. package/dist/core/subagents/official-subagent-config.js +6 -6
  22. package/dist/core/subagents/official-subagent-preparation.js +8 -6
  23. package/dist/core/subagents/official-subagent-prompt.js +11 -12
  24. package/dist/core/subagents/official-subagent-runner.js +3 -3
  25. package/dist/core/subagents/read-only-list-role.js +27 -6
  26. package/dist/core/subagents/role-model-preferences.js +21 -8
  27. package/dist/core/version.js +1 -1
  28. package/dist/native/sks-menubar/Sources/OpenRouterOnlyState.swift +20 -4
  29. package/dist/native/sks-menubar/Sources/SubagentModelsModels.swift +66 -11
  30. package/dist/native/sks-menubar/Sources/SubagentModelsViewController.swift +43 -22
  31. package/package.json +1 -1
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
18
18
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
19
19
 
20
- Current package: **SKS 10.5.3**. Install the latest stable release from npm.
20
+ Current package: **SKS 10.5.4**. Install the latest stable release from npm.
21
21
 
22
22
  Sneakoscope Codex (`sks`) is a local trust layer for Codex CLI and ChatGPT Desktop. It keeps agent work bounded, preserves project context, records machine-verifiable evidence, and prevents release claims that current tests or artifacts cannot support.
23
23
 
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.5.3"
262
+ version = "10.5.4"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.5.3"
3
+ version = "10.5.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -40,7 +40,8 @@ export function usage(command = 'bridge') {
40
40
  '',
41
41
  'Provider secrets are accepted only through --api-key-stdin. Readiness changes the exit code only with --strict or --require-ready.',
42
42
  'OpenRouter Only Mode and Codex-LB authentication priority are mutually exclusive; turning one on turns the other off.',
43
- 'subagent-models set reads {"subagent_models":[{"model","criteria","reasoning_effort","default"}]} (max 16) from stdin.'
43
+ 'subagent-models set reads {"profile":"codex_lb|openai|openrouter_only","subagent_models":[{"model","criteria","reasoning_effort","default"}]} (max 16) from stdin.',
44
+ 'The profile must match the current connection. Omitting profile preserves the legacy OpenRouter-list staging behavior.'
44
45
  ].join('\n');
45
46
  }
46
47
  export async function run(_command, args = []) {
@@ -152,7 +153,7 @@ async function parseInvocation(args, io) {
152
153
  ...base,
153
154
  request: {
154
155
  operation: 'subagent-models.set',
155
- subagent_models: subagentModelsFromStdin(await io.readStdin()),
156
+ ...subagentModelsFromStdin(await io.readStdin()),
156
157
  no_restart: parsed.flags.has('--no-restart')
157
158
  },
158
159
  label: 'Subagent model list update'
@@ -376,7 +377,11 @@ function subagentModelsFromStdin(text) {
376
377
  : undefined;
377
378
  if (!Array.isArray(models))
378
379
  throw new BridgeCliError('bridge_subagent_models_stdin_invalid', 'retry_with_stdin');
379
- return models;
380
+ const profile = parsed.profile;
381
+ if (profile !== undefined && profile !== 'openrouter_only' && profile !== 'codex_lb' && profile !== 'openai') {
382
+ throw new BridgeCliError('bridge_subagent_models_profile_invalid', 'retry_with_stdin');
383
+ }
384
+ return { subagent_models: models, ...(profile === undefined ? {} : { profile }) };
380
385
  }
381
386
  function rejectSecretArgv(args) {
382
387
  for (const arg of args) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.5.3",
3
+ "package_version": "10.5.4",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -14,7 +14,7 @@ import { decideSubagentModel } from '../subagents/model-policy.js';
14
14
  import { latestTierModelSet } from '../subagents/model-tiers.js';
15
15
  import { consultJevTurnModel } from '../decisions/integration.js';
16
16
  import { chooseChildModel, fallbackChildModel } from '../decisions/child-model-choice.js';
17
- import { OPENROUTER_ONLY_SCHEMA, allowlistedChildModel, effectiveChildModelAllowlist, isSubagentModelEffort } from '../subagents/child-model-allowlist.js';
17
+ import { allowlistedChildModel, effectiveChildModelAllowlist, childModelListProfile, listChildModelEffort, isSubagentModelEffort } from '../subagents/child-model-allowlist.js';
18
18
  export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
19
19
  export async function runNativeWorkerBackendRouter(input) {
20
20
  const root = path.resolve(input.agentRoot);
@@ -93,8 +93,10 @@ export async function runNativeWorkerBackendRouter(input) {
93
93
  cwd: String(input.intake.cwd || root),
94
94
  prompt: buildWorkerPrompt(input.slice),
95
95
  model: modelRouting.choice.model,
96
- reasoningEffort: modelRouting.choice.reasoning,
97
- modelReasoningEffort: modelRouting.choice.reasoning,
96
+ ...(modelRouting.choice.reasoning ? {
97
+ reasoningEffort: modelRouting.choice.reasoning,
98
+ modelReasoningEffort: modelRouting.choice.reasoning
99
+ } : {}),
98
100
  serviceTier: modelRouting.choice.serviceTier,
99
101
  inputFiles: input.intake.input_files || [],
100
102
  inputImages: input.intake.input_images || [],
@@ -232,7 +234,7 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
232
234
  const category = categoryForWorkerRole(String(input.agent?.role || 'executor'), taskKindText);
233
235
  const env = deps.env || process.env;
234
236
  const allowlist = effectiveChildModelAllowlist({ env });
235
- if (allowlist.mode === 'openrouter_only') {
237
+ if (allowlist.mode !== 'tiers') {
236
238
  return resolveOpenRouterOnlyWorkerRouting(input, deps, { allowlist, category, env, taskKindText, riskText });
237
239
  }
238
240
  const lbCatalog = narutoOnly
@@ -306,11 +308,12 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
306
308
  }
307
309
  async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
308
310
  const { allowlist, category, env } = ctx;
309
- const state = { schema: OPENROUTER_ONLY_SCHEMA, enabled: true, subagent_models: allowlist.entries, restore: null, updated_at: null };
311
+ const state = { subagent_models: allowlist.entries };
310
312
  const explicitModel = String(env.SKS_WORKER_MODEL || env.SKS_AGENT_MODEL || '').trim();
311
- const explicitReasoning = listReasoning(env.SKS_WORKER_REASONING || env.SKS_WORKER_MODEL_REASONING);
313
+ const rawEffort = env.SKS_WORKER_REASONING || env.SKS_WORKER_MODEL_REASONING;
314
+ const explicitReasoning = allowlist.mode === 'openrouter_only' ? listReasoning(rawEffort) : normalizeModelReasoning(rawEffort);
312
315
  const explicitTier = normalizeServiceTier(String(env.SKS_WORKER_SERVICE_TIER || env.SKS_SERVICE_TIER || '').trim());
313
- const planned = /^openrouter_only_/.test(String(input.agent?.routed_model_policy || ''))
316
+ const planned = /^(openrouter_only|configured_list)_/.test(String(input.agent?.routed_model_policy || ''))
314
317
  ? allowlistedChildModel(input.agent?.routed_model, allowlist)
315
318
  : null;
316
319
  const requested = String(input.agent?.routed_model || input.agent?.model || '').trim() || null;
@@ -322,18 +325,23 @@ async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
322
325
  ? fallbackChildModel(state, planned, 'planned')
323
326
  : deps.consultJev === false || !task
324
327
  ? fallbackChildModel(state, requested, deps.consultJev === false ? 'jev_skipped' : 'empty_task')
325
- : await chooseChildModel({ root: deps.root || process.cwd(), task, role: input.agent?.role || null, requestedModel: requested, state, env })
328
+ : await chooseChildModel({ root: deps.root || process.cwd(), task, role: input.agent?.role || null, requestedModel: requested, state, env, profile: childModelListProfile(allowlist) })
326
329
  .catch(() => fallbackChildModel(state, requested, 'consult_failed'));
327
330
  const taskPolicy = decideSubagentModel({ title: ctx.taskKindText, description: ctx.riskText, role: input.agent?.role });
328
331
  const choiceModel = choice?.entry.model || '';
329
332
  const routed = {
330
333
  model: choiceModel,
331
- reasoning: explicitReasoning || choice?.entry.reasoning_effort || listReasoning(taskPolicy.modelReasoningEffort) || 'medium',
334
+ reasoning: allowlist.mode === 'openrouter_only'
335
+ ? explicitReasoning || choice?.entry.reasoning_effort || listReasoning(taskPolicy.modelReasoningEffort) || 'medium'
336
+ : explicitReasoning || (choice ? listChildModelEffort(choice.entry, null) : null),
332
337
  serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
333
338
  };
334
339
  const blockers = [
335
- ...(!allowlist.entries.length ? ['openrouter_only_subagent_list_empty'] : []),
336
- ...(explicitModel && !explicitListed ? ['openrouter_only_subagent_model_not_listed'] : []),
340
+ ...(!allowlist.entries.length ? [allowlist.mode === 'openrouter_only' ? 'openrouter_only_subagent_list_empty' : 'configured_subagent_list_unavailable'] : []),
341
+ ...(explicitModel && !explicitListed ? [allowlist.mode === 'openrouter_only' ? 'openrouter_only_subagent_model_not_listed' : 'configured_subagent_model_not_listed'] : []),
342
+ ...(allowlist.mode === 'configured' && rawEffort
343
+ && (!explicitReasoning || !choice?.entry.supported_reasoning_efforts?.includes(explicitReasoning))
344
+ ? ['configured_subagent_effort_unsupported'] : []),
337
345
  ...(!choiceModel ? ['naruto_worker_model_unavailable'] : [])
338
346
  ];
339
347
  const why = choice ? `${choice.source}${choice.reason === 'applied' ? '' : `:${choice.reason}`}` : 'blocked';
@@ -343,7 +351,7 @@ async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
343
351
  explicit: Boolean(explicitModel),
344
352
  lb_catalog: null,
345
353
  blockers: [...new Set(blockers)],
346
- reason: `${category}->${choiceModel || 'blocked'}@${routed.reasoning} (openrouter only list: ${why})`
354
+ reason: `${category}->${choiceModel || 'blocked'}@${routed.reasoning || 'default'} (${allowlist.mode === 'openrouter_only' ? 'openrouter only' : allowlist.profile} list: ${why})`
347
355
  };
348
356
  }
349
357
  function listReasoning(value) {
@@ -2,7 +2,8 @@ import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
2
2
  import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
3
3
  import { readText } from '../../fsx.js';
4
4
  import { isOpenRouterModelId } from '../../imagegen/imagegen-config.js';
5
- import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
5
+ import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, readNativeSubagentModelStore, writeNativeSubagentModels, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
6
+ import { selectableChildModels } from '../../subagents/model-tiers.js';
6
7
  import { assertDesktopBridgeStatusV3 } from '../bridge-runtime-validation.js';
7
8
  import { readActiveCombinedBridgeCatalog } from '../combined-catalog.js';
8
9
  import { MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
@@ -19,12 +20,43 @@ async function finish(operation, ok, blockers, extra, options) {
19
20
  const status = statusFromCore(core, options);
20
21
  assertDesktopBridgeStatusV3(status);
21
22
  const openRouterOnly = await openRouterOnlyStatusFromCore(core, options);
23
+ const subagentSettings = operation.startsWith('subagent-models.')
24
+ ? await subagentModelSettings(core, openRouterOnly)
25
+ : null;
22
26
  return commandResult(operation, ok, status, {
23
27
  openrouter_only: openRouterOnly,
24
28
  auth_priority: status.auth_priority,
25
- ...extra
29
+ ...extra,
30
+ ...(subagentSettings ? { subagent_model_settings: subagentSettings, available: subagentSettings.available } : {})
26
31
  }, blockers, options);
27
32
  }
33
+ async function subagentModelSettings(core, openRouterOnly) {
34
+ if (openRouterOnly.enabled) {
35
+ return {
36
+ schema: 'sks.subagent-model-settings.v1', profile: 'openrouter_only',
37
+ configured: true, editable: true, error: openRouterOnly.error,
38
+ subagent_models: openRouterOnly.subagent_models,
39
+ jev_enabled: openRouterOnly.jev_enabled, warnings: openRouterOnly.warnings,
40
+ available: (await availableOpenRouterModels(core.paths.home)).map((row) => ({ ...row, reasoning_efforts: ['low', 'medium', 'high', 'xhigh'] }))
41
+ };
42
+ }
43
+ const profile = core.authPriorityEnabled ? 'codex_lb' : 'openai';
44
+ const location = openRouterOnlyStoreLocation(core.paths);
45
+ const saved = readNativeSubagentModelStore(location);
46
+ const available = selectableChildModels(profile, location);
47
+ const entries = saved.store.profiles[profile];
48
+ return {
49
+ schema: 'sks.subagent-model-settings.v1', profile,
50
+ configured: entries.length > 0, editable: saved.blockers.length === 0,
51
+ error: saved.blockers[0] ?? (available.length ? null : 'subagent_model_catalog_unavailable'),
52
+ subagent_models: entries.map((entry) => ({
53
+ ...entry,
54
+ routable: available.some((row) => row.public_id === entry.model
55
+ && (!entry.reasoning_effort || row.reasoning_efforts.includes(entry.reasoning_effort)))
56
+ })),
57
+ jev_enabled: openRouterOnly.jev_enabled, warnings: saved.blockers, available
58
+ };
59
+ }
28
60
  function syncBlockers(sync) {
29
61
  const activation = sync.activation && typeof sync.activation === 'object' && !Array.isArray(sync.activation)
30
62
  ? sync.activation
@@ -237,23 +269,55 @@ async function availableOpenRouterModels(home) {
237
269
  }
238
270
  }
239
271
  export async function listSubagentModels(options) {
240
- const available = await availableOpenRouterModels(controllerPaths(options).home);
241
- return finish('subagent-models.list', true, [], { available }, options);
272
+ return finish('subagent-models.list', true, [], {}, options);
242
273
  }
243
- export async function setSubagentModels(raw, noRestart, options) {
274
+ export async function setSubagentModels(raw, noRestart, options, expectedProfile) {
244
275
  if (!Array.isArray(raw))
245
276
  return finish('subagent-models.set', false, ['subagent_models_payload_invalid'], {}, options);
246
- const { entries, issues } = normalizeSubagentModelList(raw);
277
+ const core = await loadCore(options);
278
+ const activeProfile = core.openRouterOnlyEnabled ? 'openrouter_only' : core.authPriorityEnabled ? 'codex_lb' : 'openai';
279
+ if (expectedProfile && activeProfile !== expectedProfile) {
280
+ return finish('subagent-models.set', false, ['subagent_model_profile_changed'], {}, options);
281
+ }
282
+ const profile = expectedProfile ?? 'openrouter_only';
283
+ const { entries, issues } = normalizeSubagentModelList(raw, profile);
247
284
  if (issues.length > 0) {
248
285
  return finish('subagent-models.set', false, issues.map((issue) => `${issue.code}:${issue.index}`), {}, options);
249
286
  }
250
287
  const paths = controllerPaths(options);
251
288
  const location = openRouterOnlyStoreLocation(paths);
252
289
  const before = readOpenRouterOnlyStateSync(location);
253
- if (!before.enabled) {
290
+ if (!before.enabled && profile === 'openrouter_only') {
254
291
  await writeOpenRouterOnlyState({ subagent_models: entries }, location);
255
292
  return finish('subagent-models.set', true, [], { catalog_sync: null }, options);
256
293
  }
294
+ if (profile !== 'openrouter_only') {
295
+ const nativeProfile = core.authPriorityEnabled ? 'codex_lb' : 'openai';
296
+ const available = selectableChildModels(nativeProfile, location);
297
+ const blockers = entries.flatMap((entry, index) => {
298
+ const model = available.find((row) => row.public_id === entry.model);
299
+ return !model ? [`subagent_model_not_available:${index}`]
300
+ : entry.reasoning_effort && !model.reasoning_efforts.includes(entry.reasoning_effort)
301
+ ? [`subagent_model_effort_unsupported:${index}`] : [];
302
+ });
303
+ const saved = readNativeSubagentModelStore(location);
304
+ if (saved.blockers.length || blockers.length) {
305
+ return finish('subagent-models.set', false, [...saved.blockers, ...blockers], {}, options);
306
+ }
307
+ const role = entries.length ? await ensureUserReadOnlyListRole(paths.home) : 'unchanged';
308
+ if (role === 'preserved_user_file') {
309
+ return finish('subagent-models.set', false, ['subagent_model_read_only_role_unavailable'], {}, options);
310
+ }
311
+ await writeNativeSubagentModels(nativeProfile, entries, location);
312
+ const changed = JSON.stringify(saved.store.profiles[nativeProfile]) !== JSON.stringify(entries);
313
+ const roleChanged = role === 'created' || role === 'updated';
314
+ const restart = await restartCodex(roleChanged, noRestart, options);
315
+ const relaunchPending = roleChanged && !restart.attempted && restart.reason !== 'codex_not_running';
316
+ return finish('subagent-models.set', true, codexRestartBlockers(restart), {
317
+ changed, catalog_sync: null, read_only_role: role, codex_restart: restart,
318
+ warnings: relaunchPending ? [CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING] : []
319
+ }, options);
320
+ }
257
321
  const exposedBefore = await exposedModelIds(paths);
258
322
  const sync = await syncCatalogInternal(options, {
259
323
  openRouterOnly: { ...before, subagent_models: entries },
@@ -73,7 +73,7 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
73
73
  return setOpenRouterOnly(request.enabled, request.no_restart === true, options);
74
74
  }
75
75
  if (request.operation === 'subagent-models.set') {
76
- return setSubagentModels(request.subagent_models, request.no_restart === true, options);
76
+ return setSubagentModels(request.subagent_models, request.no_restart === true, options, request.profile);
77
77
  }
78
78
  if (request.operation === 'route.official-models')
79
79
  return setOfficialModelsMode(request.mode, options);
@@ -72,7 +72,7 @@ export async function chooseChildModels(input) {
72
72
  workflowId: input.workflowId,
73
73
  goal: input.goal,
74
74
  questions,
75
- facts: { openrouter_only: true, subagent_models: entries.map((entry) => canonicalChildModelId(entry.model)) },
75
+ facts: { openrouter_only: (input.profile ?? 'openrouter_only') === 'openrouter_only', subagent_model_profile: input.profile ?? 'openrouter_only', subagent_models: entries.map((entry) => canonicalChildModelId(entry.model)) },
76
76
  ...(input.env ? { env: input.env } : {}),
77
77
  ...(input.deadlineMs === undefined ? {} : { deadlineMs: input.deadlineMs })
78
78
  })
@@ -100,6 +100,7 @@ export async function chooseChildModel(input) {
100
100
  goal: input.task || 'Spawn a child agent.',
101
101
  lanes: [{ id: 'spawn', task: input.task, role: input.role ?? null, requestedModel: input.requestedModel ?? null }],
102
102
  state: input.state,
103
+ ...(input.profile ? { profile: input.profile } : {}),
103
104
  ...(input.env ? { env: input.env } : {}),
104
105
  ...(input.deadlineMs === undefined ? {} : { deadlineMs: input.deadlineMs })
105
106
  });
@@ -145,9 +145,10 @@ export async function statusReport(env) {
145
145
  : 'ready';
146
146
  const childModels = effectiveChildModelAllowlist({ env });
147
147
  const openRouterOnly = childModels.mode === 'openrouter_only';
148
+ const listMode = childModels.mode !== 'tiers';
148
149
  const childTierPoints = new Set(['spawn_tier', 'worker_tier', 'role_tiers', 'role_omission']);
149
150
  const decisionPoints = ['turn_route', 'turn_tier', 'spawn_tier', 'worker_tier', 'role_tiers', 'role_omission', 'plan', 'context', 'parent_edit_delegation', 'image_need', 'image_parameters', 'qa_effort_escalation']
150
- .filter((point) => !openRouterOnly || !childTierPoints.has(point));
151
+ .filter((point) => !listMode || !childTierPoints.has(point));
151
152
  return {
152
153
  schema: 'sks.jev-decision-status.v1',
153
154
  ok: true,
@@ -167,8 +168,14 @@ export async function statusReport(env) {
167
168
  recovery: config.capabilities.recovery
168
169
  },
169
170
  decision_points: jevEnabled(config)
170
- ? [...decisionPoints, ...(openRouterOnly ? ['child_model'] : [])]
171
+ ? [...decisionPoints, ...(listMode ? ['child_model'] : [])]
171
172
  : [],
173
+ subagent_model_list: {
174
+ active: listMode,
175
+ profile: childModels.mode === 'configured' ? childModels.profile : openRouterOnly ? 'openrouter_only' : null,
176
+ models: listMode ? childModels.models : [],
177
+ default_model: childModels.default_model
178
+ },
172
179
  openrouter_only: {
173
180
  enabled: openRouterOnly,
174
181
  subagent_model_count: openRouterOnly ? childModels.models.length : 0,
@@ -9,7 +9,7 @@ import { renderAuthoritativeSksSkillContext } from '../codex-native/sks-skill-pa
9
9
  import { resolveManagedSkillSourcesForAdmission } from './managed-skill-admission.js';
10
10
  import { managedSkillDigestBlocksEnforced } from '../verification-profile.js';
11
11
  import { looksLikeActiveContinuationPrompt } from './naruto-decision-gate.js';
12
- import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
12
+ import { effectiveChildModelAllowlist, childModelListLabel } from '../subagents/child-model-allowlist.js';
13
13
  import { EXCLUSIVE_SURFACE_SPAWN_LINE } from '../subagents/exclusive-surface-rule.js';
14
14
  import { renderChildModelCriteria, SUBAGENT_MODELS_SETTINGS_HINT } from './subagent-spawn-policy.js';
15
15
  import { extractUserPrompt, looksLikeCodexGitAction, looksLikeCodexUiSettingsEvent } from './payload-signals.js';
@@ -31,12 +31,12 @@ const OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT = [
31
31
  ].join('\n');
32
32
  function openRouterOnlyChildModelLine(allowlist) {
33
33
  if (!allowlist.models.length) {
34
- return `- SKS OpenRouter Only mode is on and the subagent model list is empty: every spawn_agent call is denied until the user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
34
+ return `- SKS ${childModelListLabel(allowlist)} mode is on and the subagent model list is empty: every spawn_agent call is denied until the user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
35
35
  }
36
- return `- SKS OpenRouter Only mode is on: every child runs a model from the user's subagent list, and the SKS PreToolUse hook denies any other model. The list, with the user's rule for each model: ${renderChildModelCriteria(allowlist)}. The hook routes each spawn_agent call to a list model (Jev picks by these rules when Jev mode is on; otherwise a listed requested model, else the default), so pass \`fork_turns="none"\` or a positive bounded turn count and do not tune model or effort yourself.`;
36
+ return `- SKS ${childModelListLabel(allowlist)} mode is on: every child runs a model from the user's subagent list, and the SKS PreToolUse hook denies any other model. The list, with the user's rule for each model: ${renderChildModelCriteria(allowlist)}. The hook routes each spawn_agent call to a list model (Jev picks by these rules when Jev mode is on; otherwise a listed requested model, else the default), so pass \`fork_turns="none"\` or a positive bounded turn count and do not tune model or effort yourself.`;
37
37
  }
38
38
  export function officialSubagentSpawnCompatibilityContext(allowlist = effectiveChildModelAllowlist()) {
39
- if (allowlist.mode !== 'openrouter_only')
39
+ if (allowlist.mode === 'tiers')
40
40
  return OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT;
41
41
  return [...OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD, openRouterOnlyChildModelLine(allowlist)].join('\n');
42
42
  }
@@ -1,7 +1,7 @@
1
1
  import { consultJevTurnModel } from '../decisions/integration.js';
2
2
  import { chooseChildModel, fallbackChildModel } from '../decisions/child-model-choice.js';
3
3
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
4
- import { OPENROUTER_ONLY_SCHEMA, effectiveChildModelAllowlist, isSubagentModelEffort } from '../subagents/child-model-allowlist.js';
4
+ import { effectiveChildModelAllowlist, childModelListLabel, childModelListProfile, listChildModelEffort } from '../subagents/child-model-allowlist.js';
5
5
  import { subagentModelProfile } from '../subagents/model-policy.js';
6
6
  import { effortForTier, latestModelForTier, latestTierModelSet } from '../subagents/model-tiers.js';
7
7
  import { readRoleModelPreferences } from '../subagents/role-model-preferences.js';
@@ -52,12 +52,12 @@ export async function jevSpawnRouting(root, state, payload) {
52
52
  if (!input)
53
53
  return { input: null, route: null };
54
54
  const allowlist = effectiveChildModelAllowlist();
55
- if (allowlist.mode === 'openrouter_only')
55
+ if (allowlist.mode !== 'tiers')
56
56
  return openRouterOnlySpawnRouting(root, state, input, allowlist);
57
57
  return { input: await tierSpawnRewrite(root, state, input), route: null };
58
58
  }
59
59
  function listState(allowlist) {
60
- return { schema: OPENROUTER_ONLY_SCHEMA, enabled: true, subagent_models: allowlist.entries, restore: null, updated_at: null };
60
+ return { subagent_models: allowlist.entries };
61
61
  }
62
62
  async function openRouterOnlySpawnRouting(root, parentState, input, allowlist) {
63
63
  if (fullHistoryForkContext(input))
@@ -70,13 +70,14 @@ async function openRouterOnlySpawnRouting(root, parentState, input, allowlist) {
70
70
  const agent = String(input.agent_type || input.agentType || '').trim() || null;
71
71
  const task = spawnTask(input, { items: true });
72
72
  const choice = task
73
- ? await chooseChildModel({ root, task, role: agent, requestedModel: requested, state })
73
+ ? await chooseChildModel({ root, task, role: agent, requestedModel: requested, state, profile: childModelListProfile(allowlist) })
74
74
  .catch(() => fallbackChildModel(state, requested, 'consult_failed'))
75
75
  : fallbackChildModel(state, requested, 'empty_task');
76
76
  if (!choice)
77
77
  return { input: null, route: null };
78
78
  const route = {
79
- mode: 'openrouter_only',
79
+ mode: allowlist.mode,
80
+ ...(allowlist.mode === 'configured' ? { profile: allowlist.profile } : {}),
80
81
  model: choice.entry.model,
81
82
  source: choice.source,
82
83
  reason: choice.reason
@@ -90,8 +91,7 @@ async function openRouterOnlySpawnRouting(root, parentState, input, allowlist) {
90
91
  else
91
92
  delete next.agent_type;
92
93
  }
93
- const effort = choice.entry.reasoning_effort
94
- || (isSubagentModelEffort(input.reasoning_effort) ? input.reasoning_effort : null);
94
+ const effort = listChildModelEffort(choice.entry, input.reasoning_effort);
95
95
  if (effort)
96
96
  next.reasoning_effort = effort;
97
97
  else
@@ -149,5 +149,5 @@ export function openRouterOnlyJevTurnLine(allowlist, effort, orchestrationRequir
149
149
  const list = allowlist.entries.length
150
150
  ? `every spawn_agent child runs a model from the user's subagent list (${renderChildModelList(allowlist)}); Jev picks the list model for each spawn from the user's criteria, and any other model is denied.`
151
151
  : `the subagent model list is empty, so spawn_agent is denied until the user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
152
- return `${hint}SKS OpenRouter Only mode: ${list}`;
152
+ return `${hint}SKS ${childModelListLabel(allowlist)} mode: ${list}`;
153
153
  }
@@ -8,7 +8,7 @@ export async function healStaleRolePinForSpawn(payload, root) {
8
8
  const agent = String(input.agent_type || input.agentType || '').trim();
9
9
  if (!agent || !stalePinForAgentType(agent, { root }))
10
10
  return false;
11
- if (effectiveChildModelAllowlist().mode === 'openrouter_only')
11
+ if (effectiveChildModelAllowlist().mode !== 'tiers')
12
12
  return false;
13
13
  await refreshStaleManagedRolePins({ root });
14
14
  return true;
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import { readJson } from '../fsx.js';
3
3
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
4
4
  import { latestModelForTier } from '../subagents/model-tiers.js';
5
- import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
5
+ import { effectiveChildModelAllowlist, childModelListLabel } from '../subagents/child-model-allowlist.js';
6
6
  import { jevEnabled, readDecisionConfig } from '../decisions/config.js';
7
7
  import { renderChildModelList } from './subagent-spawn-policy.js';
8
8
  export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
@@ -16,11 +16,11 @@ export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
16
16
  if (!agentName)
17
17
  return '';
18
18
  const allowlist = effectiveChildModelAllowlist();
19
- if (allowlist.mode === 'openrouter_only') {
19
+ if (allowlist.mode !== 'tiers') {
20
20
  return [
21
21
  'SKS sealed child routing:',
22
22
  `- custom agent: ${agentName}`,
23
- `- model and model_reasoning_effort: sealed on the spawn call from the OpenRouter Only subagent list (${renderChildModelList(allowlist) || 'empty'})`,
23
+ `- model and model_reasoning_effort: sealed on the spawn call from the ${childModelListLabel(allowlist)} subagent list (${renderChildModelList(allowlist) || 'empty'})`,
24
24
  '- keep this sealed profile; do not retarget model/effort or spawn nested agents'
25
25
  ].join('\n');
26
26
  }
@@ -1,29 +1,30 @@
1
1
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
2
- import { READ_ONLY_LIST_ROLE } from '../subagents/read-only-list-role.js';
2
+ import { READ_ONLY_LIST_ROLE, readOnlyListRoleInstalled } from '../subagents/read-only-list-role.js';
3
3
  import { stalePinBlockReason, stalePinForAgentType } from '../subagents/role-model-pins.js';
4
4
  import { isSpawnAgentToolName, spawnPayloadToolName } from './spawn-tool-name.js';
5
- import { effectiveChildModelAllowlist, isAllowedChildModel } from '../subagents/child-model-allowlist.js';
5
+ import { effectiveChildModelAllowlist, childModelListLabel, isAllowedChildModel } from '../subagents/child-model-allowlist.js';
6
6
  export const SUBAGENT_MODELS_SETTINGS_HINT = 'SKS Control Center > Subagent Models';
7
7
  export function renderChildModelList(allowlist) {
8
- if (allowlist.mode !== 'openrouter_only')
8
+ if (allowlist.mode === 'tiers')
9
9
  return allowlist.models.join(', ');
10
10
  return allowlist.entries.map((entry) => `${entry.model}${entry.default ? ' (default)' : ''}`).join(', ');
11
11
  }
12
12
  export function renderChildModelCriteria(allowlist) {
13
- if (allowlist.mode !== 'openrouter_only')
13
+ if (allowlist.mode === 'tiers')
14
14
  return allowlist.models.join(', ');
15
15
  return allowlist.entries
16
16
  .map((entry) => `${entry.model}${entry.default ? ' (default)' : ''}: ${entry.criteria || 'general work'}`)
17
17
  .join('; ');
18
18
  }
19
19
  function openRouterOnlyBlockReason(allowlist) {
20
- if (!allowlist.models.length && allowlist.mode === 'openrouter_only' && allowlist.unroutable?.length) {
21
- return `SKS OpenRouter Only mode: no model on the subagent list has an OpenRouter route right now (${allowlist.unroutable.join(', ')}), so no child may be spawned. Ask the user to replace them in ${SUBAGENT_MODELS_SETTINGS_HINT}, then retry spawn_agent. Do not implement the slice in the parent instead.`;
20
+ const label = childModelListLabel(allowlist);
21
+ if (!allowlist.models.length && allowlist.unroutable?.length) {
22
+ return `SKS ${label} mode: no model on the subagent list is available with its saved effort right now (${allowlist.unroutable.join(', ')}), so no child may be spawned. Ask the user to replace them in ${SUBAGENT_MODELS_SETTINGS_HINT}, then retry spawn_agent. Do not implement the slice in the parent instead.`;
22
23
  }
23
24
  if (!allowlist.models.length) {
24
- return `SKS OpenRouter Only mode is on but the subagent model list is empty, so no child may be spawned. Ask the user to add models in ${SUBAGENT_MODELS_SETTINGS_HINT} (or to turn OpenRouter Only off), then retry spawn_agent. Do not implement the slice in the parent instead.`;
25
+ return `SKS ${label} mode: the subagent model list is empty or unavailable, so no child may be spawned. Ask the user to review the list in ${SUBAGENT_MODELS_SETTINGS_HINT}, then retry spawn_agent. Do not implement the slice in the parent instead.`;
25
26
  }
26
- return `SKS OpenRouter Only mode: children may run only a model on the user's subagent list: ${renderChildModelList(allowlist)}. Every other model is denied. Retry spawn_agent with one of them (the SKS hook routes each spawn to a list model, by Jev when Jev mode is on) and fork_turns="none" or a positive bounded turn count. Include the complete slice contract in message; do not inherit the parent model. The user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
27
+ return `SKS ${label} mode: children may run only a model on the user's subagent list: ${renderChildModelList(allowlist)}. Every other model is denied. Retry spawn_agent with one of them (the SKS hook routes each spawn to a list model, by Jev when Jev mode is on) and fork_turns="none" or a positive bounded turn count. Include the complete slice contract in message; do not inherit the parent model. The user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
27
28
  }
28
29
  export function fullHistoryForkContext(input) {
29
30
  return input.fork_context === true || String(input.fork_context ?? '').trim().toLowerCase() === 'true';
@@ -37,16 +38,22 @@ export function subagentSpawnPolicyBlockReason(payload = {}, opts = {}) {
37
38
  return null;
38
39
  const input = payload.tool_input || payload.toolInput || payload.tool?.input || {};
39
40
  const allowlist = effectiveChildModelAllowlist();
40
- if (allowlist.mode !== 'openrouter_only' && opts.narutoParent === false)
41
+ if (allowlist.mode === 'tiers' && opts.narutoParent === false)
41
42
  return null;
42
- if (allowlist.mode === 'openrouter_only') {
43
+ if (allowlist.mode !== 'tiers') {
43
44
  if (!isAllowedChildModel(input.model, allowlist))
44
45
  return openRouterOnlyBlockReason(allowlist);
45
46
  if (fullHistoryForkContext(input))
46
47
  return `${FORK_BLOCK_REASON} Omit fork_context (fork_context=true is a full-history fork).`;
47
48
  const agent = String(input.agent_type || input.agentType || '').trim();
49
+ if (allowlist.mode === 'configured' && agent && agent !== READ_ONLY_LIST_ROLE.codex_name) {
50
+ return `SKS ${childModelListLabel(allowlist)} subagent list: agent_type "${agent}" may pin its own model or effort, which Codex uses instead of the spawn values. Omit agent_type and include the role brief in message; read-only slices must use agent_type="${READ_ONLY_LIST_ROLE.codex_name}".`;
51
+ }
52
+ if (allowlist.mode === 'configured' && agent === READ_ONLY_LIST_ROLE.codex_name && opts.root && !readOnlyListRoleInstalled(opts.root)) {
53
+ return `SKS could not verify the model-less read-only role for this project. A missing role or custom role/config override must be resolved before retrying ${READ_ONLY_LIST_ROLE.codex_name}; do not remove the role to bypass its read-only sandbox.`;
54
+ }
48
55
  if (agent && managedOfficialSubagentRoleByName(agent)) {
49
- return `SKS OpenRouter Only mode: agent_type "${agent}" pins a tier model, so it cannot run a list model. Omit agent_type and put the role brief in message; read-only slices pass agent_type="${READ_ONLY_LIST_ROLE.codex_name}".`;
56
+ return `SKS ${childModelListLabel(allowlist)} mode: agent_type "${agent}" pins a tier model, so it cannot run a list model. Omit agent_type and put the role brief in message; read-only slices pass agent_type="${READ_ONLY_LIST_ROLE.codex_name}".`;
50
57
  }
51
58
  }
52
59
  else {
@@ -310,7 +310,7 @@ async function attachJevTurnRouting(root, payload, result, orchestrationRequired
310
310
  : `Jev rated this turn as ${decision.effort}-effort work (${decision.model} tier). Treat it as this turn's reasoning hint; the parent model, effort, and service tier stay as the user set them.`
311
311
  : '';
312
312
  const allowlist = effectiveChildModelAllowlist();
313
- const line = allowlist.mode === 'openrouter_only'
313
+ const line = allowlist.mode !== 'tiers'
314
314
  ? openRouterOnlyJevTurnLine(allowlist, decision.effort, orchestrationRequired)
315
315
  : tierLine;
316
316
  if (!line)