sneakoscope 10.3.9 → 10.3.10

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 (68) hide show
  1. package/README.md +5 -5
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/config/skills-manifest.json +1 -1
  5. package/dist/core/agent-bridge/async-command.js +5 -3
  6. package/dist/core/agents/agent-effort-policy.js +3 -6
  7. package/dist/core/agents/agent-orchestrator.js +0 -1
  8. package/dist/core/agents/agent-roster.js +2 -4
  9. package/dist/core/agents/native-worker-backend-router.js +5 -5
  10. package/dist/core/codex-app/codex-model-catalog.js +0 -3
  11. package/dist/core/codex-app/openrouter-provider.js +0 -1
  12. package/dist/core/codex-compat/codex-capability-matrix.js +0 -9
  13. package/dist/core/codex-compat/codex-runtime-contract.js +0 -1
  14. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  15. package/dist/core/codex-lb/combined-catalog/builder.js +2 -2
  16. package/dist/core/codex-lb/combined-catalog/latest-generation.js +53 -0
  17. package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +2 -3
  18. package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +2 -3
  19. package/dist/core/codex-lb/desktop-controller-v3/probe-route.js +23 -0
  20. package/dist/core/codex-model-guard.js +0 -9
  21. package/dist/core/commands/naruto-command.js +2 -2
  22. package/dist/core/doctor/doctor-codex-startup-repair.js +8 -0
  23. package/dist/core/doctor/stale-subagent-default.js +30 -0
  24. package/dist/core/harness-conflicts.js +1 -1
  25. package/dist/core/hooks-runtime/exclusive-gui-surface-gate.js +155 -0
  26. package/dist/core/hooks-runtime/global-role-pin-refresh.js +25 -0
  27. package/dist/core/hooks-runtime/hook-context.js +3 -1
  28. package/dist/core/hooks-runtime/jev-spawn-routing.js +2 -6
  29. package/dist/core/hooks-runtime/managed-guidance-preflight.js +20 -2
  30. package/dist/core/hooks-runtime/parent-orchestration-gate.js +3 -3
  31. package/dist/core/hooks-runtime/payload-signals.js +1 -1
  32. package/dist/core/hooks-runtime/spawn-role-pin-heal.js +15 -0
  33. package/dist/core/hooks-runtime/spawn-tool-name.js +7 -0
  34. package/dist/core/hooks-runtime/subagent-spawn-policy.js +7 -4
  35. package/dist/core/hooks-runtime/subagent-stop-hook.js +2 -0
  36. package/dist/core/hooks-runtime.js +12 -1
  37. package/dist/core/init.js +1 -0
  38. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  39. package/dist/core/pipeline-internals/runtime-core.js +2 -3
  40. package/dist/core/provider/model-router.js +8 -11
  41. package/dist/core/recallpulse/policy.js +3 -3
  42. package/dist/core/recallpulse.js +1 -1
  43. package/dist/core/research/mock-result.js +3 -3
  44. package/dist/core/research/research-adversarial-review.js +12 -12
  45. package/dist/core/research/research-claim-synthesizer.js +5 -5
  46. package/dist/core/research/research-falsification-runner.js +5 -5
  47. package/dist/core/research/research-super-search.js +2 -2
  48. package/dist/core/research/research-synthesis-writer.js +4 -4
  49. package/dist/core/research.js +7 -7
  50. package/dist/core/routes.js +5 -3
  51. package/dist/core/subagents/agent-catalog.js +3 -0
  52. package/dist/core/subagents/exclusive-surface-rule.js +3 -0
  53. package/dist/core/subagents/exclusive-tool-surface.js +19 -0
  54. package/dist/core/subagents/model-policy.js +8 -11
  55. package/dist/core/subagents/model-tiers.js +99 -22
  56. package/dist/core/subagents/naruto-help-contract.js +7 -7
  57. package/dist/core/subagents/naruto-host-credentials.js +3 -3
  58. package/dist/core/subagents/official-subagent-config.js +11 -5
  59. package/dist/core/subagents/official-subagent-prompt.js +3 -1
  60. package/dist/core/subagents/role-model-pins.js +84 -0
  61. package/dist/core/version.js +1 -1
  62. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +1 -1
  63. package/dist/scripts/codex-sdk-backend-router-check.js +4 -5
  64. package/package.json +1 -1
  65. package/dist/core/codex-app/codex-desktop-routing-ownership.js +0 -90
  66. package/dist/core/codex-app/codex-models-cache.js +0 -153
  67. package/dist/core/codex-app/multi-provider-router-support.js +0 -25
  68. package/dist/core/codex-app/role-model-catalog-priority.js +0 -61
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  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.
17
17
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
18
18
 
19
- Current package: **SKS 10.3.9**. Install the latest stable release from npm.
19
+ Current package: **SKS 10.3.10**. Install the latest stable release from npm.
20
20
 
21
21
  [Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
22
22
 
@@ -59,8 +59,8 @@ for setup boundaries and reported execution evidence.
59
59
 
60
60
  ## Everyday commands
61
61
 
62
- SKS enables experimental Astra context management by default during setup and
63
- repair. Turn it off in **SKS Center → Settings → Astra context management**, or
62
+ SKS enables experimental Codex context management by default during setup and
63
+ repair. Turn it off in **SKS Center → Settings → Context management**, or
64
64
  use `sks codex-app context-management off`. Updates preserve an explicit opt-out.
65
65
  Start a new task after changing the setting. Availability depends on a supported
66
66
  Codex client and eligible ChatGPT sign-in; API-key and custom-provider sessions
@@ -183,8 +183,8 @@ soon as Codex lists it:
183
183
  | Work | Tier | Effort | Today |
184
184
  | --- | --- | --- | --- |
185
185
  | Tiny mechanical tasks | fast | low | `gpt-6-luna` |
186
- | Instructed implementation | balanced | low | `gpt-6-sol` |
187
- | Exploration, large-context reads, and direct tool operation | context | medium | `gpt-6-sol` |
186
+ | Instructed implementation | balanced | low | `gpt-6.1-sol` |
187
+ | Exploration, large-context reads, and direct tool operation | context | medium | `gpt-6.1-sol` |
188
188
  | Planning, review, debugging, and focused judgment | deep | max | `gpt-6-astra` |
189
189
 
190
190
  With Jev mode on, Jev picks the tier for every spawn, every gated parent edit,
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.3.9"
262
+ version = "10.3.10"
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.3.9"
3
+ version = "10.3.10"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.3.9",
3
+ "package_version": "10.3.10",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -8,6 +8,7 @@ import { desktopBridgeStatusV3 } from '../codex-lb/desktop-controller-v3.js';
8
8
  import { bridgeClientUrl } from '../codex-lb/desktop-controller-v3/shared.js';
9
9
  import { runProcess } from '../fsx.js';
10
10
  import { createResponsesTransport } from './responses-transport.js';
11
+ import { latestModelForTier } from '../subagents/model-tiers.js';
11
12
  const USAGE = 'sks agent-bridge async --prompt "task" [--tools status,stats] [--json]';
12
13
  const MAX_PROMPT_BYTES = 16 * 1024;
13
14
  export function redactAsyncToolOutput(stdout) {
@@ -55,7 +56,7 @@ export function parseAsyncCommandArgs(args) {
55
56
  export async function agentBridgeAsyncCommand(args) {
56
57
  const json = args.includes('--json');
57
58
  if (args.includes('--help')) {
58
- const help = { schema: 'sks.async-tool-run.v1', ok: true, status: 'help', usage: USAGE, model: 'gpt-6-astra', provider: 'codex-lb', tools: 'Explicitly selected R0 remote-readable SKS command contracts only.' };
59
+ const help = { schema: 'sks.async-tool-run.v1', ok: true, status: 'help', usage: USAGE, model: latestModelForTier('deep'), provider: 'codex-lb', tools: 'Explicitly selected R0 remote-readable SKS command contracts only.' };
59
60
  console.log(json ? JSON.stringify(help, null, 2) : USAGE);
60
61
  return help;
61
62
  }
@@ -63,9 +64,10 @@ export async function agentBridgeAsyncCommand(args) {
63
64
  const parsed = parseAsyncCommandArgs(args);
64
65
  const home = homedir();
65
66
  const status = await desktopBridgeStatusV3({ home });
66
- const model = 'codex-lb:gpt-6-astra';
67
+ const deepModel = latestModelForTier('deep');
68
+ const model = `codex-lb:${deepModel}`;
67
69
  const route = status.routing.policy?.model_routes[model];
68
- if (!status.service.running || !status.service.loopback_origin || route?.provider_id !== 'codex-lb' || route.upstream_model !== 'gpt-6-astra') {
70
+ if (!status.service.running || !status.service.loopback_origin || route?.provider_id !== 'codex-lb' || route.upstream_model !== deepModel) {
69
71
  throw new Error('async_astra_bridge_route_unavailable');
70
72
  }
71
73
  const endpoint = await bridgeClientUrl(status.service.loopback_origin, '/backend-api/codex/responses', { home });
@@ -2,9 +2,6 @@ import { codexModelEffortCapability } from '../codex-control/codex-model-capabil
2
2
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
3
3
  import { decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
4
4
  import { MODEL_TIERS, resolveLatestModelTiers } from '../subagents/model-tiers.js';
5
- export function decideAgentEffort(input = {}) {
6
- return decideOfficialSubagentModel(input);
7
- }
8
5
  export function decideOfficialSubagentModel(input = {}) {
9
6
  const persona = input.persona || {};
10
7
  const prompt = String(input.prompt || '');
@@ -46,7 +43,7 @@ export function decideOfficialSubagentModel(input = {}) {
46
43
  downshift_triggers: [
47
44
  'instructed UI, logic, backend, or native implementation selects the balanced tier',
48
45
  'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects the context tier',
49
- 'tiny short-context mechanical search/typing/rename work selects the balanced tier'
46
+ 'tiny short-context mechanical search/typing/rename work selects the fast tier'
50
47
  ]
51
48
  };
52
49
  }
@@ -100,7 +97,7 @@ export function decideOfficialSubagentModel(input = {}) {
100
97
  downshift_triggers: [
101
98
  'instructed UI, logic, backend, or native implementation selects the balanced tier',
102
99
  'long-context, Browser/Chrome, Computer Use, or image-generation execution selects the context tier',
103
- 'tiny short-context mechanical work selects the balanced tier'
100
+ 'tiny short-context mechanical work selects the fast tier'
104
101
  ]
105
102
  };
106
103
  }
@@ -125,7 +122,7 @@ export function buildAgentEffortPolicy(roster = {}) {
125
122
  policy_version: 1,
126
123
  dynamic: true,
127
124
  service_tier: 'fast',
128
- model_catalog_policy: 'official_subagent_four_profile_matrix',
125
+ model_catalog_policy: 'official_subagent_model_tiers',
129
126
  model_constraint: [...new Set(MODEL_TIERS.map((tier) => latest.models[tier]))],
130
127
  model_tiers: MODEL_TIERS.map((tier) => `${tier}:${latest.models[tier]}-${latest.efforts[tier]}`),
131
128
  allowed_efforts: ['low', 'medium', 'high', 'max'],
@@ -93,7 +93,6 @@ export async function runNativeAgentOrchestrator(opts = {}) {
93
93
  concurrency: opts.concurrency,
94
94
  prompt,
95
95
  maxAgentCount,
96
- officialSubagentPolicy,
97
96
  ...(opts.readonly === undefined ? {} : { readonly: opts.readonly })
98
97
  });
99
98
  roster = applyFastModeToRoster(roster, fastModePolicy);
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_AGENT_CONCURRENCY, DEFAULT_AGENT_COUNT, HARD_AGENT_CONCURRENCY, MAX_AGENT_COUNT, agentSessionId } from './agent-schema.js';
2
2
  import { defaultAgentPersonas, validatePersonaUniqueness } from './agent-persona.js';
3
- import { buildAgentEffortPolicy, decideAgentEffort, decideOfficialSubagentModel } from './agent-effort-policy.js';
3
+ import { buildAgentEffortPolicy, decideOfficialSubagentModel } from './agent-effort-policy.js';
4
4
  function resolveMaxAgentCount(value) {
5
5
  const parsed = Number(value);
6
6
  if (!Number.isFinite(parsed) || parsed < 1)
@@ -35,9 +35,7 @@ export function buildAgentRoster(opts = {}) {
35
35
  if (!uniqueness.ok)
36
36
  throw new Error('Invalid agent personas: ' + JSON.stringify(uniqueness));
37
37
  const roster = personas.map((persona, index) => {
38
- const effort = opts.officialSubagentPolicy === true
39
- ? decideOfficialSubagentModel({ persona, prompt: opts.prompt || '', agentId: persona.id, readonly: opts.readonly === true || persona.read_only })
40
- : decideAgentEffort({ persona, prompt: opts.prompt || '', agentId: persona.id, readonly: opts.readonly === true || persona.read_only });
38
+ const effort = decideOfficialSubagentModel({ persona, prompt: opts.prompt || '', agentId: persona.id, readonly: opts.readonly === true || persona.read_only });
41
39
  return {
42
40
  id: persona.id,
43
41
  session_id: agentSessionId(persona.id, index + 1),
@@ -8,7 +8,7 @@ import { runCodexTask } from '../codex-control/codex-control-plane.js';
8
8
  import { CODEX_AGENT_WORKER_RESULT_SCHEMA_ID, codexAgentWorkerResultSchema } from '../codex-control/schemas/agent-worker-result.schema.js';
9
9
  import { leanEngineeringCompactText, leanPolicyReference } from '../lean-engineering-policy.js';
10
10
  import { readCodexLbModelCatalog, readLbHealth } from '../codex-lb/codex-lb-env.js';
11
- import { categoryForWorkerRole, isNarutoGpt56Model, modelRouteReason, routeModel } from '../provider/model-router.js';
11
+ import { categoryForWorkerRole, isNarutoTierModel, modelRouteReason, routeModel } from '../provider/model-router.js';
12
12
  import { codexTimeoutClassForRoute } from '../codex-control/codex-reliability-shield.js';
13
13
  import { decideSubagentModel } from '../subagents/model-policy.js';
14
14
  import { latestTierModelSet } from '../subagents/model-tiers.js';
@@ -246,7 +246,7 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
246
246
  const explicitTierRaw = String(env.SKS_WORKER_SERVICE_TIER || env.SKS_SERVICE_TIER || '').trim();
247
247
  const explicitReasoning = normalizeModelReasoning(explicitReasoningRaw);
248
248
  const explicitTier = normalizeServiceTier(explicitTierRaw);
249
- const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoGpt56Model(explicitModel);
249
+ const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoTierModel(explicitModel);
250
250
  const explicitNarutoReasoningInvalid = narutoOnly && Boolean(explicitReasoningRaw) && !explicitReasoning;
251
251
  const explicitNarutoTierInvalid = narutoOnly && Boolean(explicitTierRaw) && !explicitTier;
252
252
  const currentModels = latestTierModelSet();
@@ -254,7 +254,7 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
254
254
  && currentModels.has(String(input.agent?.routed_model || ''))
255
255
  ? String(input.agent?.routed_model)
256
256
  : '';
257
- const savedAstraEffort = savedPreferenceModel
257
+ const savedPreferenceEffort = savedPreferenceModel
258
258
  ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort)
259
259
  : null;
260
260
  const jevModel = !explicitModel && input.agent?.routed_model_policy === 'jev_sealed_routing'
@@ -277,14 +277,14 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
277
277
  narutoOnly: true,
278
278
  taskText: taskKindText,
279
279
  riskText,
280
- reasoningEffort: jevEffort || savedAstraEffort || liveJevEffort,
280
+ reasoningEffort: jevEffort || savedPreferenceEffort || liveJevEffort,
281
281
  availableModels: lbCatalog?.models || [],
282
282
  availableModelEfforts: lbCatalog?.model_efforts || {},
283
283
  ...(selectedModel ? { model: selectedModel } : {})
284
284
  })
285
285
  : {
286
286
  model: (explicitModel && currentModels.has(explicitModel) ? explicitModel : '') || savedPreferenceModel || liveJevModel || taskPolicy.model,
287
- reasoning: explicitReasoning || savedAstraEffort || liveJevEffort || taskPolicy.modelReasoningEffort,
287
+ reasoning: explicitReasoning || savedPreferenceEffort || liveJevEffort || taskPolicy.modelReasoningEffort,
288
288
  serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
289
289
  };
290
290
  const blockers = [
@@ -14,9 +14,6 @@ export function codexHomePath(input = {}) {
14
14
  export function codexUserConfigPath(input = {}) {
15
15
  return path.join(codexHomePath(input), 'config.toml');
16
16
  }
17
- export function defaultOpenCodexCatalogPath(input = {}) {
18
- return path.join(codexHomePath(input), 'opencodex-catalog.json');
19
- }
20
17
  export async function readConfiguredCodexModelRoutingContext(input = {}) {
21
18
  const configPath = input.configPath || codexUserConfigPath(input);
22
19
  let config = '';
@@ -1,4 +1,3 @@
1
- export const OPENROUTER_DEFAULT_MODEL = 'z-ai/glm-5.2';
2
1
  export function normalizeOpenRouterModelId(value) {
3
2
  const model = String(value || '').trim();
4
3
  if (!model)
@@ -7,7 +7,6 @@ export const SKS_CODEX_CAPABILITY_IDS = [
7
7
  'agents_max_concurrent_threads_per_session',
8
8
  'thread_list_search_read',
9
9
  'mcp_startup_tool_timeouts',
10
- 'gpt56_terra_luna_sol_routing',
11
10
  'exec_mcp_transient_recovery',
12
11
  'rollout_token_budget'
13
12
  ];
@@ -46,13 +45,6 @@ export function buildCodexCapabilityMatrix(input = {}) {
46
45
  sources: [help, config],
47
46
  forceAvailable: true
48
47
  });
49
- const gpt56 = resolveBooleanCapability({
50
- id: 'gpt56_terra_luna_sol_routing',
51
- policy: policies.gpt56TerraLunaSolRouting || 'delegate',
52
- markers: [/gpt-5\.6-(?:luna|terra|sol)/],
53
- sources: [help, config],
54
- forceAvailable: Boolean(detected)
55
- });
56
48
  const execMcp = resolveBooleanCapability({
57
49
  id: 'exec_mcp_transient_recovery',
58
50
  policy: policies.execMcpTransientRecovery || 'probe',
@@ -75,7 +67,6 @@ export function buildCodexCapabilityMatrix(input = {}) {
75
67
  agents_max_concurrent_threads_per_session: agentsConcurrency,
76
68
  thread_list_search_read: threadList,
77
69
  mcp_startup_tool_timeouts: mcpTimeouts,
78
- gpt56_terra_luna_sol_routing: gpt56,
79
70
  exec_mcp_transient_recovery: execMcp,
80
71
  rollout_token_budget: rollout
81
72
  },
@@ -26,7 +26,6 @@ export const CURRENT_CODEX_RUNTIME_CONTRACT = {
26
26
  remoteNativeEnvironment: 'probe',
27
27
  rolloutTokenBudget: 'probe',
28
28
  mcpStartupToolTimeouts: 'wrap',
29
- gpt56TerraLunaSolRouting: 'delegate',
30
29
  mcpPaginatedDiscovery: 'wrap',
31
30
  portableAgentPlugins: 'delegate',
32
31
  automaticApprovalReview: 'delegate'
@@ -1,4 +1,5 @@
1
1
  import { mapCodexSdkSandboxPolicy } from './codex-sdk-sandbox-policy.js';
2
+ import { parseGptModelId } from '../subagents/model-tiers.js';
2
3
  export function buildCodexSdkConfig(input) {
3
4
  const model = String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || '').trim();
4
5
  const serviceTier = String(input.serviceTier || process.env.SKS_SERVICE_TIER || 'fast');
@@ -26,7 +27,7 @@ export function buildCodexSdkConfig(input) {
26
27
  return config;
27
28
  }
28
29
  export function normalizeAstraSdkEffort(model, effort) {
29
- return model.trim().toLowerCase() === 'gpt-6-astra' && ['none', 'minimal'].includes(effort.trim().toLowerCase())
30
+ return parseGptModelId(model.trim().toLowerCase())?.family === 'astra' && ['none', 'minimal'].includes(effort.trim().toLowerCase())
30
31
  ? 'low' : effort;
31
32
  }
32
33
  export function buildCodexExecutionPolicy(input) {
@@ -1,6 +1,7 @@
1
1
  import { buildBridgeRouteIndex, sha256Stable } from '../route-index.js';
2
2
  import { COMBINED_BRIDGE_CATALOG_SCHEMA } from './contracts.js';
3
3
  import { normalizeProviderCatalog, providerCatalogStatus, routeProviderState } from './normalize.js';
4
+ import { pinCatalogToLatestGenerations } from './latest-generation.js';
4
5
  import { compareModels, unique } from './shared.js';
5
6
  import { applyBridgeModelSelection, availableModelRows, emptyBridgeModelSelection } from './model-selection.js';
6
7
  export function buildCombinedBridgeCatalog(registry, options) {
@@ -10,8 +11,7 @@ export function buildCombinedBridgeCatalog(registry, options) {
10
11
  openrouter: normalizeProviderCatalog(options.catalogs.openrouter)
11
12
  };
12
13
  const selection = options.selection || emptyBridgeModelSelection(createdAt);
13
- const allModels = [...normalized['codex-lb'].models, ...normalized.openrouter.models]
14
- .sort(compareModels);
14
+ const allModels = pinCatalogToLatestGenerations([...normalized['codex-lb'].models, ...normalized.openrouter.models].sort(compareModels));
15
15
  const availableOpenRouterModels = availableModelRows(allModels, selection);
16
16
  const models = options.selection ? applyBridgeModelSelection(allModels, selection) : allModels;
17
17
  const routeBuild = buildBridgeRouteIndex({
@@ -0,0 +1,53 @@
1
+ import { compareModelVersions, parseGptModelId } from '../../subagents/model-tiers.js';
2
+ function isListed(model) {
3
+ const visibility = String(model.visibility || 'list').toLowerCase();
4
+ return visibility !== 'hide' && visibility !== 'hidden';
5
+ }
6
+ function escapeRegExp(text) {
7
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
8
+ }
9
+ function displayNamePattern(displayName) {
10
+ const body = escapeRegExp(displayName.trim()).replace(/\\?-|\s+/g, '[-\\s]');
11
+ return new RegExp(`(?<![\\w-])${body}(?![\\w-])`, 'gi');
12
+ }
13
+ export function pinCatalogToLatestGenerations(models) {
14
+ const newest = new Map();
15
+ for (const model of models) {
16
+ const id = parseGptModelId(model.public_id);
17
+ if (!id || !isListed(model))
18
+ continue;
19
+ const best = newest.get(id.family);
20
+ if (!best || compareModelVersions(id.version, parseGptModelId(best.public_id).version) > 0)
21
+ newest.set(id.family, model);
22
+ }
23
+ const byId = new Map(models.map((model) => [model.public_id, model]));
24
+ return models.map((model) => {
25
+ const id = parseGptModelId(model.public_id);
26
+ let next = model;
27
+ if (id && isListed(model)) {
28
+ const best = newest.get(id.family);
29
+ if (best && compareModelVersions(parseGptModelId(best.public_id).version, id.version) > 0)
30
+ next = { ...next, visibility: 'hide' };
31
+ }
32
+ const upgrade = model.upgrade;
33
+ const target = upgrade && typeof upgrade === 'object' ? String(upgrade.model || '') : '';
34
+ const targetId = parseGptModelId(target);
35
+ const replacement = targetId ? newest.get(targetId.family) : undefined;
36
+ if (targetId && replacement && replacement.public_id !== target
37
+ && compareModelVersions(parseGptModelId(replacement.public_id).version, targetId.version) > 0) {
38
+ const old = byId.get(target);
39
+ const markdown = upgrade.migration_markdown;
40
+ next = {
41
+ ...next,
42
+ upgrade: {
43
+ ...upgrade,
44
+ model: replacement.public_id,
45
+ ...(typeof markdown === 'string' && old?.display_name
46
+ ? { migration_markdown: markdown.replace(displayNamePattern(old.display_name), () => replacement.display_name) }
47
+ : {})
48
+ }
49
+ };
50
+ }
51
+ return next;
52
+ });
53
+ }
@@ -5,6 +5,7 @@ import { runVoiceRealtimeProbeV3 } from '../probes/voice-realtime-probe.js';
5
5
  import { runAuxiliarySurfacesProbeV3 } from '../probes/auxiliary-surfaces-probe.js';
6
6
  import { capabilityProbeResultV3 } from '../probes/probe-evidence.js';
7
7
  import { validateCapabilityDeepEvidenceV2 } from '../trusted-deep-evidence.js';
8
+ import { probeRouteForProvider } from './probe-route.js';
8
9
  import { bridgeClientUrl, providerCode, safeCode, timeoutMs, unique } from './shared.js';
9
10
  export function textResponsePayloadValid(contentType, text) {
10
11
  if (/text\/event-stream/i.test(contentType || '')) {
@@ -40,9 +41,7 @@ export function textResponsePayloadValid(contentType, text) {
40
41
  return payload !== null && typeof payload === 'object';
41
42
  }
42
43
  export async function probeProviderText(core, providerId, loopbackOrigin, context, options) {
43
- const route = core.policy
44
- ? Object.entries(core.policy.model_routes).find(([, target]) => target.provider_id === providerId)
45
- : null;
44
+ const route = probeRouteForProvider(core.policy, providerId);
46
45
  if (!loopbackOrigin || !route) {
47
46
  return capabilityProbeResultV3({
48
47
  ...context,
@@ -1,4 +1,5 @@
1
1
  import { capabilityProbeResultV3 } from '../probes/probe-evidence.js';
2
+ import { probeRouteForProvider } from './probe-route.js';
2
3
  import { activeProviderIds, providerCode } from './shared.js';
3
4
  export function nativeIdentityProbe(core, context) {
4
5
  const oauth = core.auth.mode === 'chatgpt_oauth' || core.auth.mode === 'mixed';
@@ -123,9 +124,7 @@ export function providerAuthProbe(core, providerId, context) {
123
124
  });
124
125
  }
125
126
  export function providerModelRouteProbe(core, providerId, context, liveText = null) {
126
- const route = core.policy
127
- ? Object.entries(core.policy.model_routes).find(([, target]) => target.provider_id === providerId)
128
- : null;
127
+ const route = probeRouteForProvider(core.policy, providerId);
129
128
  const verified = Boolean(route && core.catalogSync.providers[providerId].state === 'verified');
130
129
  const liveRouteProven = Boolean(verified && route && liveText
131
130
  && liveText.capability === 'text_responses'
@@ -0,0 +1,23 @@
1
+ import { compareModelVersions, parseGptModelId } from '../../subagents/model-tiers.js';
2
+ const FAMILY_ORDER = ['sol', 'astra', 'luna', 'terra'];
3
+ function familyRank(family) {
4
+ const index = FAMILY_ORDER.indexOf(family);
5
+ return index === -1 ? FAMILY_ORDER.length : index;
6
+ }
7
+ export function probeRouteForProvider(policy, providerId) {
8
+ if (!policy)
9
+ return null;
10
+ const routes = Object.entries(policy.model_routes).filter(([, target]) => target.provider_id === providerId);
11
+ let best = null;
12
+ for (const entry of routes) {
13
+ const id = parseGptModelId(entry[0]);
14
+ if (!id)
15
+ continue;
16
+ if (!best
17
+ || compareModelVersions(id.version, best.version) > 0
18
+ || (compareModelVersions(id.version, best.version) === 0 && familyRank(id.family) < familyRank(best.family))) {
19
+ best = { entry, version: id.version, family: id.family };
20
+ }
21
+ }
22
+ return best?.entry ?? routes[0] ?? null;
23
+ }
@@ -5,12 +5,3 @@ function copyArgs(args = []) {
5
5
  export function preserveCodexModelArgs(args = []) {
6
6
  return copyArgs(args);
7
7
  }
8
- export function forceRequiredCodexModelArgs(args = []) {
9
- return preserveCodexModelArgs(args);
10
- }
11
- export function forceRequiredCodexModelConfigArgs(args = []) {
12
- return preserveCodexModelArgs(args);
13
- }
14
- export function isForbiddenCodexModel(_value = '') {
15
- return false;
16
- }
@@ -740,8 +740,8 @@ function blockGlmOverride(json) {
740
740
  schema: NARUTO_RESULT_SCHEMA,
741
741
  ok: false,
742
742
  status: 'blocked',
743
- reason: 'naruto_gpt_5_6_family_only_glm_override_forbidden',
744
- blockers: ['naruto_gpt_5_6_family_only_glm_override_forbidden'],
743
+ reason: 'retired_glm_naruto_flag:--glm',
744
+ blockers: ['retired_glm_naruto_flag:--glm'],
745
745
  hint: 'Use normal sks naruto for the official Codex subagent workflow. OpenRouter models are selected in SKS Center Providers via Use OpenRouter.'
746
746
  };
747
747
  process.exitCode = 1;
@@ -3,6 +3,7 @@ import os from 'node:os';
3
3
  import { exists, nowIso, readText, writeJsonAtomic } from '../fsx.js';
4
4
  import { isUnmanagedProjectCodexConfig, writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
5
5
  import { escapeRegExp } from '../text/regex.js';
6
+ import { moveStaleSubagentDefault } from './stale-subagent-default.js';
6
7
  export const DOCTOR_CODEX_STARTUP_REPAIR_SCHEMA = 'sks.doctor-codex-startup-repair.v1';
7
8
  export async function runDoctorCodexStartupRepair(input) {
8
9
  const root = path.resolve(input.root || process.cwd());
@@ -103,6 +104,13 @@ async function inspectOrRepairConfig(root, candidate, fix, nodeReplCommandCandid
103
104
  warnings.push(...nodeReplRepair.warnings);
104
105
  staleMcpBlocksRemoved.push(...nodeReplRepair.removed);
105
106
  mcpBlocksRepaired.push(...nodeReplRepair.repaired);
107
+ if (candidate.scope === 'global') {
108
+ const moved = moveStaleSubagentDefault(next);
109
+ if (fix)
110
+ next = moved.text;
111
+ if (moved.from)
112
+ warnings.push(`default_subagent_model_${fix ? 'moved_to_latest' : 'not_latest'}:${moved.from}->${moved.to}`);
113
+ }
106
114
  for (const server of ['supabase_sauron']) {
107
115
  if (tomlBlock(next, `mcp_servers.${server}`))
108
116
  optionalMcpBlocksIgnored.push(server);
@@ -0,0 +1,30 @@
1
+ import { catalogIsAuthoritative, latestModelForTier, supersededByNewerSameFamily } from '../subagents/model-tiers.js';
2
+ const ANY_TABLE = /^\s*\[/;
3
+ const AGENTS_TABLE = /^\s*\[agents\]\s*(?:#.*)?$/;
4
+ const DEFAULT_LINE = /^(\s*default_subagent_model\s*=\s*")([^"]*)("\s*(?:#[^\r\n]*)?\s*)$/;
5
+ export function moveStaleSubagentDefault(text) {
6
+ const unchanged = { text, from: null, to: null };
7
+ if (!catalogIsAuthoritative())
8
+ return unchanged;
9
+ const lines = text.split('\n');
10
+ let inAgents = false;
11
+ for (let index = 0; index < lines.length; index += 1) {
12
+ const line = lines[index] || '';
13
+ if (ANY_TABLE.test(line)) {
14
+ inAgents = AGENTS_TABLE.test(line);
15
+ continue;
16
+ }
17
+ if (!inAgents)
18
+ continue;
19
+ const match = DEFAULT_LINE.exec(line);
20
+ if (!match)
21
+ continue;
22
+ const current = String(match[2] || '');
23
+ if (!supersededByNewerSameFamily(current))
24
+ return unchanged;
25
+ const latest = latestModelForTier('deep');
26
+ lines[index] = `${match[1]}${latest}${match[3]}`;
27
+ return { text: lines.join('\n'), from: current, to: latest };
28
+ }
29
+ return unchanged;
30
+ }
@@ -349,7 +349,7 @@ export function formatHarnessConflictReport(scan, opts = {}) {
349
349
  }
350
350
  export function llmHarnessCleanupPrompt(scan) {
351
351
  const paths = (scan?.conflicts || []).map((x) => `- ${x.scope}: ${x.path} (${x.reason})`).join('\n') || '- No paths supplied. Re-run `sks doctor --json` first.';
352
- return `Use GPT-5.6 with reasoning effort high.
352
+ return `Use the model currently selected in Codex with reasoning effort high.
353
353
 
354
354
  Goal: completely remove the conflicting Codex harnesses before installing Sneakoscope Codex.
355
355