sneakoscope 10.3.2 → 10.3.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 (71) hide show
  1. package/README.md +22 -12
  2. package/config/skills-hash-ledger.v1.json +7 -1
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/config/skills-manifest.json +13 -7
  7. package/dist/core/agents/agent-effort-policy.js +15 -13
  8. package/dist/core/agents/native-worker-backend-router.js +13 -8
  9. package/dist/core/codex-hooks/codex-hook-managed-install.js +56 -2
  10. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +1 -0
  11. package/dist/core/codex-native/core-skill-manifest.js +4 -4
  12. package/dist/core/decisions/cli.js +9 -2
  13. package/dist/core/decisions/config.js +7 -0
  14. package/dist/core/decisions/integration.js +54 -12
  15. package/dist/core/decisions/policy.js +27 -4
  16. package/dist/core/decisions/questions.js +39 -9
  17. package/dist/core/decisions/routing.js +15 -10
  18. package/dist/core/decisions/types.js +11 -25
  19. package/dist/core/doctor/current-project-guidance.js +38 -1
  20. package/dist/core/doctor/skill-legacy-surface.js +2 -1
  21. package/dist/core/hooks-runtime/hook-context.js +1 -1
  22. package/dist/core/hooks-runtime/jev-spawn-routing.js +21 -2
  23. package/dist/core/hooks-runtime/managed-guidance-preflight.js +30 -0
  24. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -3
  25. package/dist/core/hooks-runtime/parent-orchestration-gate.js +372 -0
  26. package/dist/core/hooks-runtime/subagent-context.js +13 -3
  27. package/dist/core/hooks-runtime/subagent-spawn-policy.js +5 -10
  28. package/dist/core/hooks-runtime.js +46 -8
  29. package/dist/core/init/skills/inventory.js +5 -1
  30. package/dist/core/init/skills.js +4 -4
  31. package/dist/core/init.js +39 -7
  32. package/dist/core/managed-assets/managed-assets-manifest.js +17 -17
  33. package/dist/core/pipeline-internals/runtime-core.js +2 -2
  34. package/dist/core/provider/model-router.js +8 -5
  35. package/dist/core/recallpulse/policy.js +2 -2
  36. package/dist/core/recallpulse.js +3 -3
  37. package/dist/core/release/gate-affected-globs.js +0 -1
  38. package/dist/core/research/mock-result.js +2 -2
  39. package/dist/core/research/research-adversarial-review.js +7 -7
  40. package/dist/core/research/research-claim-synthesizer.js +3 -3
  41. package/dist/core/research/research-falsification-runner.js +3 -3
  42. package/dist/core/research/research-super-search.js +5 -2
  43. package/dist/core/research/research-synthesis-writer.js +2 -2
  44. package/dist/core/research.js +5 -5
  45. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  46. package/dist/core/routes.js +10 -10
  47. package/dist/core/runtime/task-profile.js +9 -5
  48. package/dist/core/subagents/model-policy.js +27 -45
  49. package/dist/core/subagents/model-tiers.js +153 -0
  50. package/dist/core/subagents/naruto-command-args.js +3 -3
  51. package/dist/core/subagents/naruto-help-contract.js +18 -13
  52. package/dist/core/subagents/naruto-host-credentials.js +8 -13
  53. package/dist/core/subagents/official-subagent-config.js +11 -7
  54. package/dist/core/subagents/official-subagent-preparation.js +12 -8
  55. package/dist/core/subagents/official-subagent-prompt.js +33 -33
  56. package/dist/core/subagents/official-subagent-runner.js +3 -3
  57. package/dist/core/subagents/role-model-preferences.js +26 -21
  58. package/dist/core/update/managed-permission-repair.js +462 -0
  59. package/dist/core/update/update-migration-state/retired-local-decision.js +32 -2
  60. package/dist/core/update/update-migration-state/simple-stages.js +42 -5
  61. package/dist/core/update/update-migration-state.js +63 -1
  62. package/dist/core/update-check.js +35 -8
  63. package/dist/core/version.js +1 -1
  64. package/dist/scripts/codex-native-agent-role-content-check.js +10 -5
  65. package/dist/scripts/codex-native-gate-lib.js +3 -1
  66. package/dist/scripts/current-surface-update-e2e-check.js +1 -0
  67. package/dist/scripts/mutation-callsite-coverage-check.js +2 -2
  68. package/dist/scripts/release-affected-selector-check.js +1 -2
  69. package/dist/scripts/typed-routing-gate-lib.js +3 -1
  70. package/package.json +1 -1
  71. package/release-gates.v2.json +2 -6
@@ -6,7 +6,8 @@ import { attentionRiskForTask, classifyTaskProfile } from '../runtime/task-profi
6
6
  import { chooseVerificationBudget } from '../runtime/verification-budget.js';
7
7
  import { buildOfficialSubagentPrompt, validateOfficialSubagentSlices } from './official-subagent-prompt.js';
8
8
  import { readOfficialSubagentConfig } from './official-subagent-config.js';
9
- import { ASTRA_SUBAGENT_MODEL, NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from './model-policy.js';
9
+ import { NARUTO_PARENT_EFFORT, narutoParentModel } from './model-policy.js';
10
+ import { latestModelForTier } from './model-tiers.js';
10
11
  import { MAX_ON_DEMAND_SUBAGENT_ROLE_COUNT, officialSubagentFanoutPolicy, officialSubagentOnDemandRolePlan, officialSubagentRoleCatalog, recommendOfficialSubagentRoles } from './agent-catalog.js';
11
12
  import { resolveSubagentThreadBudget } from './thread-budget.js';
12
13
  import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
@@ -270,7 +271,7 @@ async function deriveOfficialSubagentPreparation(input, snapshot) {
270
271
  return [name, {
271
272
  ...config,
272
273
  routed_provider: 'openai',
273
- routed_model: preference?.model || ASTRA_SUBAGENT_MODEL,
274
+ routed_model: preference?.model || config.model || decision.model,
274
275
  routed_model_reasoning_effort: routedReasoning,
275
276
  routed_model_policy: preference
276
277
  ? 'user_role_model_preference'
@@ -341,11 +342,11 @@ async function deriveOfficialSubagentPreparation(input, snapshot) {
341
342
  },
342
343
  slice_safety: sliceSafety,
343
344
  slices,
344
- parent_model_policy: NARUTO_PARENT_MODEL,
345
+ parent_model_policy: narutoParentModel(),
345
346
  observed_parent_model: observedParentModel,
346
347
  parent_model_match: parentModelMatch,
347
348
  parent: {
348
- model: NARUTO_PARENT_MODEL,
349
+ model: narutoParentModel(),
349
350
  model_reasoning_effort: NARUTO_PARENT_EFFORT
350
351
  },
351
352
  agent_catalog: agentCatalog,
@@ -472,7 +473,7 @@ function applyOfficialSubagentDecision(derived, decided) {
472
473
  .filter(([name]) => !derived.roleModelPreferences.store.roles[name])
473
474
  .map(([name, effort]) => [name, {
474
475
  provider: 'openai',
475
- model: selectedRouting.models[name] || 'gpt-6-astra',
476
+ model: selectedRouting.models[name] || latestModelForTier('deep'),
476
477
  reasoning_effort: effort,
477
478
  updated_at: derived.plan.workflow_run_id
478
479
  }]))
@@ -496,6 +497,8 @@ function applyOfficialSubagentDecision(derived, decided) {
496
497
  },
497
498
  routedAgents: agents,
498
499
  narutoChildRouting: derived.mode === 'naruto',
500
+ jevRouting: decided.compiled.kind === 'apply'
501
+ || !['off', 'missing_key'].includes(String(decided.compiled.reason || '')),
499
502
  activeMainModel: derived.activeMainModel,
500
503
  parentOutputMode: derived.mode === 'naruto' && derived.sessionScope ? 'app_naruto_stdin' : 'raw_json',
501
504
  missionId: derived.plan.mission_id,
@@ -503,6 +506,7 @@ function applyOfficialSubagentDecision(derived, decided) {
503
506
  decisionContract: decided.compiled.kind === 'apply'
504
507
  ? {
505
508
  planId: selectedPlan?.id ?? null,
509
+ workerCount: selectedPlan?.workerCount ?? null,
506
510
  keepContextIds: decided.selectedContextIds,
507
511
  routingLane: selectedRouting
508
512
  ? Object.entries(selectedRouting.models).map(([name, model]) => `${name}=${model}`).join(', ')
@@ -914,7 +918,7 @@ export function buildNarutoSummary(input) {
914
918
  workflow_run_id: input.workflowRunId || input.evidence?.run_id || null,
915
919
  mission_id: input.missionId,
916
920
  parent: {
917
- model: NARUTO_PARENT_MODEL,
921
+ model: narutoParentModel(),
918
922
  model_reasoning_effort: NARUTO_PARENT_EFFORT,
919
923
  observed_model: input.observedParentModel || null,
920
924
  observed_model_match: input.parentModelMatch ?? null
@@ -994,7 +998,7 @@ export function buildNarutoGateResult(input) {
994
998
  workflow: 'official_codex_subagent',
995
999
  workflow_run_id: input.workflowRunId || input.evidence?.run_id || null,
996
1000
  mission_id: input.missionId,
997
- parent_model_policy: NARUTO_PARENT_MODEL,
1001
+ parent_model_policy: narutoParentModel(),
998
1002
  observed_parent_model: input.observedParentModel || null,
999
1003
  parent_model_match: input.parentModelMatch ?? null,
1000
1004
  status: passed ? 'passed' : 'blocked',
@@ -1039,7 +1043,7 @@ export function buildNarutoGateResult(input) {
1039
1043
  };
1040
1044
  }
1041
1045
  function observedParentModelMatchesPolicy(model) {
1042
- return model.trim().toLowerCase() === NARUTO_PARENT_MODEL;
1046
+ return model.trim().toLowerCase() === narutoParentModel();
1043
1047
  }
1044
1048
  export const NARUTO_MISSION_RUN_LOCK = '.naruto-run.lock';
1045
1049
  export const NARUTO_MISSION_RUN_STALE_MS = 120_000;
@@ -1,4 +1,4 @@
1
- import { ASTRA_SUBAGENT_MODEL, NARUTO_PARENT_MODEL, NARUTO_PARENT_EFFORT } from './model-policy.js';
1
+ import { latestTierModelSet } from './model-tiers.js';
2
2
  import { HARD_NARUTO_MAX_THREADS } from './thread-budget.js';
3
3
  import { coreEngineeringDirectiveReferenceText } from '../lean-engineering-policy.js';
4
4
  import { MAX_AUTOMATIC_REVIEWER_COUNT, MAX_AUTOMATIC_SUBAGENT_COUNT, MAX_CRITICAL_AUTOMATIC_REVIEWER_COUNT, officialSubagentOnDemandRoleCatalog, officialSubagentRoleCatalog, selectOfficialSubagentRole } from './agent-catalog.js';
@@ -44,30 +44,30 @@ export function buildOfficialSubagentPrompt(input) {
44
44
  ...(input.recommendedAgents || [])
45
45
  ]);
46
46
  const activeMainModel = normalizedActiveMainModel(input.activeMainModel);
47
- const effortPreferences = Object.fromEntries(Object.entries(input.roleModelPreferences || {})
48
- .filter(([name, row]) => row.model === ASTRA_SUBAGENT_MODEL
47
+ const currentModels = latestTierModelSet();
48
+ const rolePreferences = Object.fromEntries(Object.entries(input.roleModelPreferences || {})
49
+ .filter(([name, row]) => currentModels.has(row.model)
49
50
  && ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(row.reasoning_effort)
50
- && officialSubagentOnDemandRoleCatalog([name]).some((role) => role.name === name))
51
- .map(([name, row]) => [name, row.reasoning_effort]));
51
+ && officialSubagentOnDemandRoleCatalog([name]).some((role) => role.name === name)));
52
+ const effortPreferences = Object.fromEntries(Object.entries(rolePreferences).map(([name, row]) => [name, row.reasoning_effort]));
52
53
  const narutoChildren = input.narutoChildRouting === true;
53
- const spawnModelRouting = renderSpawnModelRouting(narutoChildren);
54
+ const jevRouting = input.jevRouting === true;
55
+ const spawnModelRouting = renderSpawnModelRouting(narutoChildren, jevRouting);
56
+ const tierRules = jevRouting
57
+ ? []
58
+ : [
59
+ '- tiers: fast for tiny mechanical shards (search, rename, copy, label, one-line edits), balanced for instructed UI, logic, backend, and native implementation, context for long reads, exploration, large first drafts, Computer Use, browser, or image work, deep for planning, review, debugging, architecture, security, database, research, release, or other judgment',
60
+ '- explicit task class and phase win over incidental keywords; a mixed slice takes the deeper tier; never apply the parent profile to every child'
61
+ ];
54
62
  const childModelRules = narutoChildren
55
63
  ? [
56
64
  '- the parent orchestrates only: decompose the goal, assign disjoint slices, spawn children, and integrate their results',
57
65
  '- do not implement the assigned slice work in the parent thread',
58
- '- use the model and reasoning_effort named in each slice spawn contract',
59
- '- sealed child models are gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra, and gpt-6-astra',
66
+ '- use the model and reasoning_effort named in each slice spawn contract; each is the newest model of its tier',
67
+ ...tierRules,
60
68
  '- keep a stored user role-model preference for that role'
61
69
  ].join('\n')
62
- : [
63
- '- use `worker` with gpt-6-astra and low reasoning for tiny short-context mechanical work such as simple search, typing, rename, copy, label, or one-line edits with no exploration or judgment',
64
- '- use gpt-6-astra with low reasoning for ordinary UI, logic, backend, and native implementation with established instructions',
65
- '- use gpt-6-astra with max reasoning for planning, analysis, review, focused unresolved, high-risk, architecture, security, database, research, release, or other explicit judgment slices',
66
- '- use gpt-6-astra with medium reasoning for long context/memory, large docs/repository reads or exploration, large-scale first-draft code processing, and direct Computer Use, Browser/Chrome, or image generation',
67
- '- explicit task class and phase win over incidental keywords: Astra Medium explores, Astra Low executes mechanical and instructed coding tasks, and Astra Max judges',
68
- '- in mass fan-out, use worker/Astra Low for tiny mechanical shards and explorer/Astra Medium for broad exploration; use Astra Low for instructed implementation and Astra Max for judgment',
69
- '- keep context, exploration, review, debugging, planning, and direct tool operation on their assigned defaults; preserve each sealed role model and effort instead of applying the parent profile to every child'
70
- ].join('\n');
70
+ : tierRules.join('\n');
71
71
  const parentOutputMode = input.parentOutputMode === 'app_naruto_stdin'
72
72
  ? 'app_naruto_stdin'
73
73
  : 'raw_json';
@@ -80,10 +80,10 @@ export function buildOfficialSubagentPrompt(input) {
80
80
  || effortPreferences[agentName]
81
81
  || role?.model_reasoning_effort
82
82
  || 'medium';
83
- const sealedModel = routed?.routed_model || role?.model;
83
+ const sealedModel = routed?.routed_model || rolePreferences[agentName]?.model || role?.model;
84
84
  const spawnContract = role
85
85
  ? `pass model=${JSON.stringify(sealedModel)} and reasoning_effort=${JSON.stringify(sealedReasoning)} from the sealed role policy`
86
- : 'stop before spawning: resolve an installed sealed Astra role and its effort first';
86
+ : 'stop before spawning: resolve an installed sealed role and its tier first';
87
87
  return [
88
88
  `${index + 1}. [${slice.id}] use custom agent \`${agentName}\``,
89
89
  ` ${slice.title}: ${slice.description}`,
@@ -114,15 +114,15 @@ Host capability policy:
114
114
  - Slack delivery is ACAS-runtime-only, never a model tool
115
115
 
116
116
  Subagent rules:
117
- - parent model policy: ${activeMainModel ? `keep the current app-selected main model ${activeMainModel.provider}:${activeMainModel.model}` : `${NARUTO_PARENT_MODEL} with ${NARUTO_PARENT_EFFORT} reasoning`}
117
+ - parent model policy: ${activeMainModel ? `keep the current app-selected main model ${activeMainModel.provider}:${activeMainModel.model}` : 'keep the user-selected parent model'}
118
118
  - use only Codex official subagent threads; do not launch shell workers, a custom scheduler, a worker pool, or model fanout
119
119
  - select the narrowest matching project custom agent by its description; the custom agent name is the spawn type
120
120
  - custom \`agent_type\` selection and spawn-time \`model\`/\`reasoning_effort\` overrides must use \`fork_turns="none"\` or a positive bounded turn count, with the complete bounded slice contract in \`message\`; context contract: pass fork_turns="none" for listed slices
121
- - \`spawn_agent\` has no provider argument; ${narutoChildren ? 'Naruto children use the sealed model named in the spawn contract (gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra, or gpt-6-astra)' : 'every child uses the exact model slug gpt-6-astra'}
121
+ - \`spawn_agent\` has no provider argument; ${narutoChildren ? 'Naruto children use the tier model named in the spawn contract' : 'children use the sealed model slug'}
122
122
  - never combine \`fork_turns="all"\` or the omitted/default full-history mode with \`agent_type\`, \`model\`, or \`reasoning_effort\`; Codex rejects that start before SubagentStart
123
123
  - never use a full-history fork for SKS children
124
124
  ${spawnModelRouting}
125
- ${Object.keys(effortPreferences).length ? `- explicit Astra effort preferences override role defaults, including later slices: ${JSON.stringify(effortPreferences)}` : ''}
125
+ ${Object.keys(effortPreferences).length ? `- stored role effort preferences override role defaults, including later slices: ${JSON.stringify(effortPreferences)}` : ''}
126
126
  ${childModelRules}
127
127
 
128
128
  Plan and capacity:
@@ -228,18 +228,17 @@ function normalizedActiveMainModel(value) {
228
228
  const model = String(value?.model || '').trim();
229
229
  return provider && model ? { provider, model } : null;
230
230
  }
231
- function renderSpawnModelRouting(narutoChildRouting) {
232
- if (narutoChildRouting) {
233
- return [
234
- '- Naruto child models come from the spawn contract, including slices created after parent decomposition',
235
- '- when Jev mode selected a role, that contract names gpt-5.6-luna at low, gpt-5.6-sol at low, gpt-5.6-terra at medium, or gpt-6-astra at max',
236
- '- explicit user role-model preferences stay authoritative',
237
- '- preserve the user-selected parent model, reasoning effort, and service tier'
238
- ].join('\n');
239
- }
231
+ function renderSpawnModelRouting(narutoChildRouting, jevRouting) {
232
+ const lines = jevRouting
233
+ ? [
234
+ '- Jev mode: Jev picks each child tier (fast, balanced, context, or deep) and the SKS PreToolUse hook seals that tier\'s newest model on every spawn_agent call; pass the contract model unchanged and spend no time choosing models or efforts'
235
+ ]
236
+ : [
237
+ `- model routing applies to every child, including slices created after parent decomposition: the newest model of the role tier${narutoChildRouting ? ' named in the spawn contract' : ''}`
238
+ ];
240
239
  return [
241
- '- model routing applies to every child, including slices created after parent decomposition: gpt-6-astra only, with the selected role effort',
242
- '- use sealed Astra Low/Astra Medium/Astra Max role defaults across four task-class profiles; parent selection and saved non-Astra preferences never override the child model; explicit Astra effort preferences, including High, may override role defaults',
240
+ ...lines,
241
+ '- parent selection never overrides the child model; stored user role preferences stay authoritative',
243
242
  '- preserve the user-selected parent model, reasoning effort, and service tier'
244
243
  ].join('\n');
245
244
  }
@@ -330,6 +329,7 @@ function renderDecisionContract(value) {
330
329
  return [
331
330
  'Selected decision contract:',
332
331
  `- execute the selected plan${value.planId ? ` ${value.planId}` : ''} and retained optional context IDs; do not choose them again`,
332
+ value.workerCount ? `- Jev fixed the automatic child target at ${value.workerCount}; do not recompute it (real host capacity still caps each wave)` : '',
333
333
  value.routingLane ? `- Jev sealed models: ${value.routingLane}` : '',
334
334
  value.keepContextIds?.length
335
335
  ? `- retained optional context IDs: ${value.keepContextIds.join(', ')}`
@@ -2,7 +2,7 @@ import fsp from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { randomId, runProcess, writeJsonAtomic } from '../fsx.js';
5
- import { DEFAULT_SUBAGENT_EFFORT, DEFAULT_SUBAGENT_MODEL, NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from './model-policy.js';
5
+ import { DEFAULT_SUBAGENT_EFFORT, defaultSubagentModel, NARUTO_PARENT_EFFORT, narutoParentModel } from './model-policy.js';
6
6
  import { nextAgentGenerationEnv } from '../agents/agent-recursion-guard.js';
7
7
  import { inspectDesktopBridgeCliLaunchGuard, stripRetiredDirectProviderEnv } from '../codex-control/desktop-bridge-launch-guard.js';
8
8
  import { prepareCodexAppServerRuntimeEnv } from '../codex-control/codex-app-server-runtime-env.js';
@@ -93,9 +93,9 @@ export function defaultNarutoCredentialPolicy() {
93
93
  return resolveNarutoCredentialPolicy({
94
94
  args: [],
95
95
  env: {},
96
- defaultParentModel: NARUTO_PARENT_MODEL,
96
+ defaultParentModel: narutoParentModel(),
97
97
  defaultParentEffort: NARUTO_PARENT_EFFORT,
98
- defaultSubagentModel: DEFAULT_SUBAGENT_MODEL,
98
+ defaultSubagentModel: defaultSubagentModel(),
99
99
  defaultSubagentEffort: DEFAULT_SUBAGENT_EFFORT
100
100
  });
101
101
  }
@@ -5,24 +5,28 @@ import { writeTextAtomic } from '../fsx.js';
5
5
  import { MANAGED_OFFICIAL_SUBAGENT_ROLES, managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
6
6
  import { inferProviderFromModel, normalizeCodexModelId, normalizeCodexReasoningEffort, readConfiguredCodexModelRoutingContext } from '../codex-app/codex-model-catalog.js';
7
7
  import { isRecord } from '../json/records.js';
8
- import { ASTRA_SUBAGENT_MODEL, SUBAGENT_MODEL_POLICIES } from './model-policy.js';
8
+ import { codexListedEfforts, latestModelForTier, latestTierModelSet, modelTierForModel } from './model-tiers.js';
9
9
  export const ROLE_MODEL_PREFERENCES_SCHEMA = 'sks.role-model-preferences.v2';
10
10
  const LEGACY_ROLE_MODEL_PREFERENCES_SCHEMA = 'sks.role-model-preferences.v1';
11
11
  const ROLE_MODEL_PROFILE_PRESENTATION_LIMIT = 1_000;
12
- export const SUPPORTED_ROLE_MODEL_PROFILES = Object.freeze([
13
- ...Object.values(SUBAGENT_MODEL_POLICIES).map((profile) => Object.freeze({
12
+ const ROLE_PREFERENCE_EFFORTS = ['low', 'medium', 'high', 'max'];
13
+ export function supportedRoleModelProfiles() {
14
+ const defaults = new Set(MANAGED_OFFICIAL_SUBAGENT_ROLES.map((role) => `${role.model}\0${role.model_reasoning_effort}`));
15
+ return [...latestTierModelSet()].flatMap((model) => (codexListedEfforts(model) || ROLE_PREFERENCE_EFFORTS)
16
+ .filter((effort) => ROLE_PREFERENCE_EFFORTS.includes(effort))
17
+ .map((effort) => ({
14
18
  provider: 'openai',
15
- model: profile.model,
16
- reasoning_effort: profile.modelReasoningEffort,
17
- source: 'managed-default'
18
- })),
19
- Object.freeze({
20
- provider: 'openai',
21
- model: ASTRA_SUBAGENT_MODEL,
22
- reasoning_effort: 'high',
23
- source: 'explicit-override'
24
- })
25
- ]);
19
+ model,
20
+ reasoning_effort: effort,
21
+ source: defaults.has(`${model}\0${effort}`) ? 'managed-default' : 'explicit-override'
22
+ })));
23
+ }
24
+ function currentRoleModel(model, fallback) {
25
+ if (latestTierModelSet().has(model))
26
+ return model;
27
+ const tier = modelTierForModel(model);
28
+ return tier ? latestModelForTier(tier) : fallback;
29
+ }
26
30
  export function roleModelPreferencesPath(env = process.env) {
27
31
  const sksHome = path.resolve(env.SKS_HOME || path.join(env.HOME || os.homedir(), '.sneakoscope'));
28
32
  return path.join(sksHome, 'preferences', 'role-models.json');
@@ -52,10 +56,11 @@ export async function readRoleModelPreferences(input = {}) {
52
56
  blockers.push(`role_model_preference_invalid_profile:${role.codex_name}`);
53
57
  continue;
54
58
  }
55
- const keepEffort = provider === 'openai' && isSupportedRoleModelProfile(model, reasoning);
59
+ const effectiveModel = provider === 'openai' ? currentRoleModel(model, role.model) : role.model;
60
+ const keepEffort = effectiveModel === model && isSupportedRoleModelProfile(effectiveModel, reasoning);
56
61
  roles[role.codex_name] = {
57
62
  provider: 'openai',
58
- model: ASTRA_SUBAGENT_MODEL,
63
+ model: effectiveModel,
59
64
  reasoning_effort: keepEffort ? reasoning : role.model_reasoning_effort,
60
65
  updated_at: String(rawPreference.updated_at || parsed.updated_at || '')
61
66
  };
@@ -91,13 +96,13 @@ export async function roleModelPreferencesStatus(input = {}) {
91
96
  ? []
92
97
  : [`role_model_preference_not_managed:${role}`]));
93
98
  const allProfiles = dedupeProfiles([
94
- ...SUPPORTED_ROLE_MODEL_PROFILES
99
+ ...supportedRoleModelProfiles()
95
100
  ]);
96
101
  const supportedProfiles = allProfiles.slice(0, ROLE_MODEL_PROFILE_PRESENTATION_LIMIT);
97
102
  const roles = MANAGED_OFFICIAL_SUBAGENT_ROLES.map((role) => {
98
103
  const override = read.store.roles[role.codex_name] || null;
99
104
  const effectiveProvider = 'openai';
100
- const effectiveModel = ASTRA_SUBAGENT_MODEL;
105
+ const effectiveModel = override?.model || role.model;
101
106
  const effectiveReasoning = override?.reasoning_effort || role.model_reasoning_effort;
102
107
  return {
103
108
  role: role.codex_name,
@@ -163,8 +168,8 @@ export async function setRoleModelPreference(input) {
163
168
  const reasoning = normalizeCodexReasoningEffort(input.reasoning);
164
169
  if (!model || !reasoning)
165
170
  return mutationBlocked('role_model_profile_invalid');
166
- if (model !== ASTRA_SUBAGENT_MODEL)
167
- return mutationBlocked('role_model_astra_required');
171
+ if (!latestTierModelSet().has(model))
172
+ return mutationBlocked('role_model_current_model_required');
168
173
  const env = input.env || process.env;
169
174
  const routing = await readConfiguredCodexModelRoutingContext({
170
175
  env,
@@ -248,7 +253,7 @@ export async function resetRoleModelPreference(input) {
248
253
  export function isSupportedRoleModelProfile(model, reasoning) {
249
254
  const normalizedModel = normalizeCodexModelId(model);
250
255
  const normalizedReasoning = normalizeCodexReasoningEffort(reasoning);
251
- return SUPPORTED_ROLE_MODEL_PROFILES.some((profile) => (profile.model === normalizedModel
256
+ return supportedRoleModelProfiles().some((profile) => (profile.model === normalizedModel
252
257
  && profile.reasoning_effort === normalizedReasoning));
253
258
  }
254
259
  async function writeOwnerOnlyStore(filePath, store) {