sneakoscope 10.5.2 → 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.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +1 -1
- package/dist/cli/router.js +9 -3
- package/dist/commands/bridge.js +8 -3
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/native-worker-backend-router.js +20 -12
- package/dist/core/codex-app/codex-app-restart-policy.js +4 -0
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +15 -1
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +2 -1
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +72 -12
- package/dist/core/codex-lb/desktop-controller-v3.js +1 -1
- package/dist/core/commands/align-command.js +2 -1
- package/dist/core/commands/naruto-command.js +44 -0
- package/dist/core/commands/naruto-config-command.js +23 -0
- package/dist/core/commands/pipeline-command.js +2 -1
- package/dist/core/decisions/child-model-choice.js +2 -1
- package/dist/core/decisions/cli.js +9 -2
- package/dist/core/hooks-runtime/hook-context.js +4 -4
- package/dist/core/hooks-runtime/jev-spawn-routing.js +8 -8
- package/dist/core/hooks-runtime/spawn-role-pin-heal.js +1 -1
- package/dist/core/hooks-runtime/subagent-context.js +3 -3
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +18 -11
- package/dist/core/hooks-runtime.js +1 -1
- package/dist/core/pipeline-internals/runtime-core.js +2 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/command-contract/contracts.js +11 -1
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/subagents/child-model-allowlist.js +107 -8
- package/dist/core/subagents/child-model-plan.js +18 -27
- package/dist/core/subagents/model-tiers.js +45 -10
- package/dist/core/subagents/naruto-command-args.js +48 -7
- package/dist/core/subagents/naruto-command-input-contract.js +40 -4
- package/dist/core/subagents/naruto-execution-mode.js +62 -0
- package/dist/core/subagents/naruto-help-contract.js +9 -0
- package/dist/core/subagents/naruto-host-credentials.js +24 -5
- package/dist/core/subagents/official-subagent-config.js +6 -6
- package/dist/core/subagents/official-subagent-preparation.js +8 -6
- package/dist/core/subagents/official-subagent-prompt.js +11 -12
- package/dist/core/subagents/official-subagent-runner.js +5 -8
- package/dist/core/subagents/read-only-list-role.js +27 -6
- package/dist/core/subagents/role-model-preferences.js +21 -8
- package/dist/core/update/update-migration-state.js +10 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OpenRouterOnlyState.swift +20 -4
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +15 -4
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +105 -1
- package/dist/native/sks-menubar/Sources/SubagentModelsModels.swift +66 -11
- package/dist/native/sks-menubar/Sources/SubagentModelsViewController.swift +43 -22
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
|
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 {
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
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
|
|
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
|
|
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
|
|
41
|
+
if (allowlist.mode === 'tiers' && opts.narutoParent === false)
|
|
41
42
|
return null;
|
|
42
|
-
if (allowlist.mode
|
|
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
|
|
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
|
|
313
|
+
const line = allowlist.mode !== 'tiers'
|
|
314
314
|
? openRouterOnlyJevTurnLine(allowlist, decision.effort, orchestrationRequired)
|
|
315
315
|
: tierLine;
|
|
316
316
|
if (!line)
|
|
@@ -29,6 +29,7 @@ import { classifyTaskProfile, gateProfileForTask } from '../runtime/task-profile
|
|
|
29
29
|
import { chooseVerificationBudget } from '../runtime/verification-budget.js';
|
|
30
30
|
import { stopFinalizationRitualsEnforced } from '../verification-profile.js';
|
|
31
31
|
import { HARD_NARUTO_MAX_THREADS } from '../subagents/thread-budget.js';
|
|
32
|
+
import { narutoUsesCurrentSession } from '../subagents/naruto-execution-mode.js';
|
|
32
33
|
import { NARUTO_GATE_FILENAME, NARUTO_SUMMARY_FILENAME, SUBAGENT_PLAN_FILENAME, prepareOfficialSubagentMission } from '../subagents/official-subagent-preparation.js';
|
|
33
34
|
import { buildSubagentEvidence, readSubagentEvents, recordSubagentEvent, SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME } from '../subagents/subagent-evidence.js';
|
|
34
35
|
import { effectiveSubagentTarget, normalizeLegacySubagentCountFields, subagentCountContractBlockers } from '../subagents/wave-lifecycle.js';
|
|
@@ -601,7 +602,7 @@ export async function prepareRoute(root, prompt, state = {}, opts = {}) {
|
|
|
601
602
|
...selectedRoute,
|
|
602
603
|
requiredSkills: managedSkillNamesForPrompt(selectedRoute, cleanPrompt)
|
|
603
604
|
} : null;
|
|
604
|
-
const codexThreadId = String(process.env.CODEX_THREAD_ID || '').trim();
|
|
605
|
+
const codexThreadId = narutoUsesCurrentSession() ? String(process.env.CODEX_THREAD_ID || '').trim() : '';
|
|
605
606
|
const sessionKey = opts.sessionKey || codexThreadId || state?._session_key || null;
|
|
606
607
|
const madSksAuthorization = hasMadSksSignal(cleanPrompt);
|
|
607
608
|
const task = stripDollarCommand(stripMadSksSignal(cleanPrompt)) || stripMadSksSignal(stripDollarCommand(cleanPrompt)) || String(cleanPrompt || '').trim();
|
package/dist/core/routes.js
CHANGED
|
@@ -206,7 +206,7 @@ export const ROUTES = [
|
|
|
206
206
|
reasoningPolicy: 'high',
|
|
207
207
|
stopGate: 'naruto-gate.json',
|
|
208
208
|
coverage_required: true,
|
|
209
|
-
cliEntrypoint: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] | sks naruto status|subagents|proof | sks naruto parent-summary --mission M-... --stdin',
|
|
209
|
+
cliEntrypoint: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] | sks naruto status|subagents|proof | sks naruto execution status | sks naruto execution set --mode auto|current-session|standalone [--restart] | sks naruto parent-summary --mission M-... --stdin',
|
|
210
210
|
examples: ['$Naruto run review twelve independent packages with --agents 12', '$Work']
|
|
211
211
|
},
|
|
212
212
|
{
|
|
@@ -4,12 +4,22 @@ const ARGUMENT_BUILDERS = {
|
|
|
4
4
|
none: () => [],
|
|
5
5
|
'json-only': jsonFlag,
|
|
6
6
|
naruto: (input) => {
|
|
7
|
+
const action = stringValue(input.action, input.prompt || input.task ? 'run' : 'help');
|
|
8
|
+
if (action === 'execution') {
|
|
9
|
+
const executionCommand = stringValue(input.execution_command, 'status');
|
|
10
|
+
return [
|
|
11
|
+
'execution',
|
|
12
|
+
...(executionCommand === 'status' ? [] : [executionCommand]),
|
|
13
|
+
...valueFlag(input, 'mode', '--mode'),
|
|
14
|
+
...booleanFlag(input, 'restart', '--restart'),
|
|
15
|
+
...jsonFlag(input)
|
|
16
|
+
];
|
|
17
|
+
}
|
|
7
18
|
const task = typeof input.prompt === 'string'
|
|
8
19
|
? input.prompt
|
|
9
20
|
: typeof input.task === 'string'
|
|
10
21
|
? input.task
|
|
11
22
|
: '';
|
|
12
|
-
const action = stringValue(input.action, task ? 'run' : 'help');
|
|
13
23
|
return [
|
|
14
24
|
action,
|
|
15
25
|
...(task ? [task] : []),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NARUTO_ACTIONS = ['run', 'status', 'subagents', 'proof', 'parent-summary', 'help'];
|
|
1
|
+
export const NARUTO_ACTIONS = ['run', 'status', 'subagents', 'proof', 'parent-summary', 'execution', 'help'];
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
6
|
import { isOpenRouterModelId } from '../imagegen/imagegen-config.js';
|
|
6
|
-
import { latestTierModelSet } from './model-tiers.js';
|
|
7
|
+
import { latestTierModelSet, selectableChildModels } from './model-tiers.js';
|
|
8
|
+
import { normalizeCodexModelId } from '../codex-app/codex-model-catalog.js';
|
|
9
|
+
import { withFileLock } from '../locks/file-lock.js';
|
|
10
|
+
import { ensureConfinedDirectory, inspectConfinedPath } from '../managed-path-safety.js';
|
|
7
11
|
export const OPENROUTER_ONLY_SCHEMA = 'sks.openrouter-only.v1';
|
|
8
12
|
export const OPENROUTER_ONLY_FILENAME = 'sks-openrouter-only.json';
|
|
9
13
|
export const MAX_SUBAGENT_MODELS = 16;
|
|
10
14
|
export const MAX_SUBAGENT_CRITERIA_CHARS = 240;
|
|
11
15
|
export const SUBAGENT_MODEL_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
|
|
16
|
+
export const NATIVE_SUBAGENT_MODEL_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
17
|
+
export const SUBAGENT_MODEL_LISTS_SCHEMA = 'sks.subagent-model-lists.v1';
|
|
12
18
|
export function openRouterOnlyStatePath(input = {}) {
|
|
13
19
|
const env = input.env || process.env;
|
|
14
20
|
return path.join(input.home || env.HOME || os.homedir(), '.codex', 'sks', OPENROUTER_ONLY_FILENAME);
|
|
@@ -19,8 +25,9 @@ export function defaultOpenRouterOnlyState() {
|
|
|
19
25
|
export function canonicalChildModelId(value) {
|
|
20
26
|
return String(value ?? '').trim().toLowerCase();
|
|
21
27
|
}
|
|
22
|
-
export function isSubagentModelEffort(value) {
|
|
23
|
-
|
|
28
|
+
export function isSubagentModelEffort(value, profile = 'openrouter_only') {
|
|
29
|
+
const efforts = profile === 'openrouter_only' ? SUBAGENT_MODEL_EFFORTS : NATIVE_SUBAGENT_MODEL_EFFORTS;
|
|
30
|
+
return typeof value === 'string' && efforts.includes(value);
|
|
24
31
|
}
|
|
25
32
|
function cleanCriteria(value) {
|
|
26
33
|
return String(value ?? '')
|
|
@@ -29,7 +36,7 @@ function cleanCriteria(value) {
|
|
|
29
36
|
.trim()
|
|
30
37
|
.slice(0, MAX_SUBAGENT_CRITERIA_CHARS);
|
|
31
38
|
}
|
|
32
|
-
export function normalizeSubagentModelList(raw) {
|
|
39
|
+
export function normalizeSubagentModelList(raw, profile = 'openrouter_only') {
|
|
33
40
|
const rows = Array.isArray(raw) ? raw : [];
|
|
34
41
|
const issues = [];
|
|
35
42
|
const entries = [];
|
|
@@ -37,7 +44,7 @@ export function normalizeSubagentModelList(raw) {
|
|
|
37
44
|
rows.forEach((row, index) => {
|
|
38
45
|
const record = row && typeof row === 'object' ? row : {};
|
|
39
46
|
const model = String(record.model ?? '').trim();
|
|
40
|
-
if (!isOpenRouterModelId(model)) {
|
|
47
|
+
if (!(profile === 'openrouter_only' ? isOpenRouterModelId(model) : normalizeCodexModelId(model))) {
|
|
41
48
|
issues.push({ index, code: 'subagent_model_id_invalid' });
|
|
42
49
|
return;
|
|
43
50
|
}
|
|
@@ -47,7 +54,7 @@ export function normalizeSubagentModelList(raw) {
|
|
|
47
54
|
return;
|
|
48
55
|
}
|
|
49
56
|
const effort = record.reasoning_effort ?? null;
|
|
50
|
-
if (effort !== null && effort !== '' && !isSubagentModelEffort(effort)) {
|
|
57
|
+
if (effort !== null && effort !== '' && !isSubagentModelEffort(effort, profile)) {
|
|
51
58
|
issues.push({ index, code: 'subagent_model_effort_invalid' });
|
|
52
59
|
return;
|
|
53
60
|
}
|
|
@@ -57,9 +64,9 @@ export function normalizeSubagentModelList(raw) {
|
|
|
57
64
|
}
|
|
58
65
|
seen.add(key);
|
|
59
66
|
entries.push({
|
|
60
|
-
model: key,
|
|
67
|
+
model: profile === 'openrouter_only' ? key : model,
|
|
61
68
|
criteria: cleanCriteria(record.criteria),
|
|
62
|
-
reasoning_effort: isSubagentModelEffort(effort) ? effort : null,
|
|
69
|
+
reasoning_effort: isSubagentModelEffort(effort, profile) ? effort : null,
|
|
63
70
|
default: record.default === true
|
|
64
71
|
});
|
|
65
72
|
});
|
|
@@ -116,6 +123,78 @@ export function subagentEntryForModel(state, model) {
|
|
|
116
123
|
return null;
|
|
117
124
|
return state.subagent_models.find((entry) => canonicalChildModelId(entry.model) === key) || null;
|
|
118
125
|
}
|
|
126
|
+
export function childModelListProfile(list) {
|
|
127
|
+
return list.mode === 'openrouter_only' ? 'openrouter_only' : list.profile;
|
|
128
|
+
}
|
|
129
|
+
export function childModelListLabel(list) {
|
|
130
|
+
return list.mode === 'openrouter_only' ? 'OpenRouter Only' : list.profile === 'codex_lb' ? 'Codex-LB' : 'OpenAI OAuth';
|
|
131
|
+
}
|
|
132
|
+
export function listChildModelEffort(entry, requested) {
|
|
133
|
+
const supported = entry.supported_reasoning_efforts ?? SUBAGENT_MODEL_EFFORTS;
|
|
134
|
+
const effort = entry.reasoning_effort || String(requested || '');
|
|
135
|
+
return supported.includes(effort) && isSubagentModelEffort(effort, 'openai') ? effort : null;
|
|
136
|
+
}
|
|
137
|
+
export function subagentModelListsPath(input = {}) {
|
|
138
|
+
return path.join(path.dirname(openRouterOnlyStatePath(input)), 'sks-subagent-model-lists.json');
|
|
139
|
+
}
|
|
140
|
+
export function nativeSubagentModelProfile(input = {}) {
|
|
141
|
+
if (input.nativeProfile)
|
|
142
|
+
return input.nativeProfile;
|
|
143
|
+
const file = path.join(path.dirname(openRouterOnlyStatePath(input)), 'desktop-bridge-settings.json');
|
|
144
|
+
try {
|
|
145
|
+
if (fs.statSync(file).size > 4 * 1024 * 1024)
|
|
146
|
+
return 'openai';
|
|
147
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))?.auth_priority_enabled === true ? 'codex_lb' : 'openai';
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return 'openai';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function readNativeSubagentModelStore(input = {}) {
|
|
154
|
+
const file = subagentModelListsPath(input);
|
|
155
|
+
const empty = { schema: SUBAGENT_MODEL_LISTS_SCHEMA, profiles: { codex_lb: [], openai: [] }, updated_at: null };
|
|
156
|
+
try {
|
|
157
|
+
if (fs.statSync(file).size > 256 * 1024)
|
|
158
|
+
throw new Error('oversized');
|
|
159
|
+
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
160
|
+
if (raw?.schema !== SUBAGENT_MODEL_LISTS_SCHEMA || !raw.profiles || Array.isArray(raw.profiles))
|
|
161
|
+
throw new Error('schema');
|
|
162
|
+
for (const profile of ['codex_lb', 'openai']) {
|
|
163
|
+
if (!Array.isArray(raw.profiles[profile]))
|
|
164
|
+
throw new Error('profile');
|
|
165
|
+
const normalized = normalizeSubagentModelList(raw.profiles[profile], profile);
|
|
166
|
+
if (normalized.issues.length)
|
|
167
|
+
throw new Error('entries');
|
|
168
|
+
empty.profiles[profile] = normalized.entries;
|
|
169
|
+
}
|
|
170
|
+
empty.updated_at = typeof raw.updated_at === 'string' ? raw.updated_at : null;
|
|
171
|
+
return { path: file, store: empty, blockers: [] };
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
return { path: file, store: empty, blockers: error?.code === 'ENOENT' ? [] : ['subagent_model_lists_unreadable'] };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export async function writeNativeSubagentModels(profile, raw, input = {}) {
|
|
178
|
+
const { entries, issues } = normalizeSubagentModelList(raw, profile);
|
|
179
|
+
if (!Array.isArray(raw) || issues.length)
|
|
180
|
+
throw new Error('subagent_models_payload_invalid');
|
|
181
|
+
const file = subagentModelListsPath(input);
|
|
182
|
+
const root = path.dirname(path.dirname(file));
|
|
183
|
+
await fsp.mkdir(root, { recursive: true });
|
|
184
|
+
await ensureConfinedDirectory(root, path.dirname(file));
|
|
185
|
+
await withFileLock({ lockPath: `${file}.lock`, timeoutMs: 5_000, staleMs: 30_000 }, async () => {
|
|
186
|
+
if ((await inspectConfinedPath(root, file)).leafSymlink)
|
|
187
|
+
throw new Error('subagent_model_lists_unsafe_path');
|
|
188
|
+
const current = readNativeSubagentModelStore(input);
|
|
189
|
+
if (current.blockers.length)
|
|
190
|
+
throw new Error(current.blockers[0]);
|
|
191
|
+
await writeJsonAtomic(file, {
|
|
192
|
+
...current.store,
|
|
193
|
+
profiles: { ...current.store.profiles, [profile]: entries },
|
|
194
|
+
updated_at: nowIso()
|
|
195
|
+
}, { mode: 0o600 });
|
|
196
|
+
});
|
|
197
|
+
}
|
|
119
198
|
function openRouterRoutedModels(input) {
|
|
120
199
|
const env = input.env || process.env;
|
|
121
200
|
const file = path.join(input.home || env.HOME || os.homedir(), '.codex', 'sks', 'sks-bridge-route-policy.json');
|
|
@@ -158,6 +237,26 @@ export function effectiveChildModelAllowlist(input = {}) {
|
|
|
158
237
|
unroutable
|
|
159
238
|
};
|
|
160
239
|
}
|
|
240
|
+
const profile = nativeSubagentModelProfile(input);
|
|
241
|
+
const saved = readNativeSubagentModelStore(input);
|
|
242
|
+
const selected = saved.blockers.length ? [] : saved.store.profiles[profile];
|
|
243
|
+
if (selected.length || saved.blockers.length) {
|
|
244
|
+
const available = selectableChildModels(profile, input);
|
|
245
|
+
const entries = selected.flatMap((entry) => {
|
|
246
|
+
const model = available.find((row) => canonicalChildModelId(row.public_id) === canonicalChildModelId(entry.model));
|
|
247
|
+
return model && (!entry.reasoning_effort || model.reasoning_efforts.includes(entry.reasoning_effort))
|
|
248
|
+
? [{ ...entry, model: model.public_id, supported_reasoning_efforts: model.reasoning_efforts }]
|
|
249
|
+
: [];
|
|
250
|
+
});
|
|
251
|
+
const preferred = entries.findIndex((entry) => entry.default);
|
|
252
|
+
entries.forEach((entry, index) => { entry.default = index === (preferred < 0 ? 0 : preferred); });
|
|
253
|
+
return {
|
|
254
|
+
mode: 'configured', profile, models: entries.map((entry) => entry.model), entries,
|
|
255
|
+
default_model: defaultSubagentEntry({ subagent_models: entries })?.model ?? null,
|
|
256
|
+
unroutable: selected.filter((row) => !entries.some((entry) => canonicalChildModelId(entry.model) === canonicalChildModelId(row.model))).map((row) => row.model),
|
|
257
|
+
blockers: saved.blockers
|
|
258
|
+
};
|
|
259
|
+
}
|
|
161
260
|
return { mode: 'tiers', models: [...latestTierModelSet(input)], entries: [], default_model: null };
|
|
162
261
|
}
|
|
163
262
|
export function isAllowedChildModel(model, allowlist) {
|
|
@@ -1,40 +1,29 @@
|
|
|
1
1
|
import { chooseChildModels } from '../decisions/child-model-choice.js';
|
|
2
2
|
import { MAX_JEV_ROUTING_ROLES } from '../decisions/routing.js';
|
|
3
|
-
import {
|
|
4
|
-
import { latestTierModelSet } from './model-tiers.js';
|
|
3
|
+
import { defaultSubagentEntry, effectiveChildModelAllowlist, childModelListProfile, listChildModelEffort } from './child-model-allowlist.js';
|
|
5
4
|
import { readCodexMainModel } from './naruto-host-credentials.js';
|
|
6
5
|
import { READ_ONLY_LIST_ROLE } from './read-only-list-role.js';
|
|
7
6
|
export function readChildModelPlanContext(location = {}) {
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
10
|
-
return { allowlist
|
|
11
|
-
}
|
|
12
|
-
const { state, unroutable } = routableSubagentState(stored, location);
|
|
13
|
-
const allowlist = {
|
|
14
|
-
mode: 'openrouter_only',
|
|
15
|
-
models: state.subagent_models.map((entry) => entry.model),
|
|
16
|
-
entries: state.subagent_models,
|
|
17
|
-
default_model: defaultSubagentEntry(state)?.model ?? null,
|
|
18
|
-
unroutable
|
|
19
|
-
};
|
|
7
|
+
const allowlist = effectiveChildModelAllowlist(location);
|
|
8
|
+
if (allowlist.mode === 'tiers')
|
|
9
|
+
return { allowlist, list: null };
|
|
10
|
+
const state = { subagent_models: allowlist.entries };
|
|
20
11
|
return { allowlist, list: { state, allowlist, mainModel: readCodexMainModel(location) } };
|
|
21
12
|
}
|
|
22
13
|
export function listEntryEffort(entry, roleEffort) {
|
|
23
|
-
|
|
24
|
-
return entry.reasoning_effort;
|
|
25
|
-
const effort = String(roleEffort || '');
|
|
26
|
-
return SUBAGENT_MODEL_EFFORTS.includes(effort) ? effort : null;
|
|
14
|
+
return listChildModelEffort(entry, roleEffort);
|
|
27
15
|
}
|
|
28
|
-
function routedRow(row, choice) {
|
|
16
|
+
function routedRow(row, choice, list) {
|
|
17
|
+
const prefix = list.mode === 'openrouter_only' ? 'openrouter_only' : 'configured_list';
|
|
29
18
|
return {
|
|
30
19
|
...row,
|
|
31
|
-
routed_provider: 'openrouter',
|
|
20
|
+
routed_provider: list.mode === 'openrouter_only' ? 'openrouter' : list.profile === 'codex_lb' ? 'codex-lb' : 'openai',
|
|
32
21
|
routed_model: choice?.entry.model ?? null,
|
|
33
22
|
routed_model_reasoning_effort: choice ? listEntryEffort(choice.entry, row.model_reasoning_effort) : null,
|
|
34
|
-
routed_model_policy: choice?.source === 'jev' ? '
|
|
23
|
+
routed_model_policy: `${prefix}_${choice?.source === 'jev' ? 'jev' : 'default'}`,
|
|
35
24
|
routing_dynamic: true,
|
|
36
|
-
role_model_preference_source:
|
|
37
|
-
openrouter_only_choice: choice
|
|
25
|
+
role_model_preference_source: `ignored_${prefix}`,
|
|
26
|
+
[list.mode === 'openrouter_only' ? 'openrouter_only_choice' : 'subagent_list_choice']: choice
|
|
38
27
|
? { model: choice.entry.model, source: choice.source, reason: choice.reason, default_entry: choice.entry.default }
|
|
39
28
|
: { model: null, source: 'default', reason: 'subagent_list_empty', default_entry: false }
|
|
40
29
|
};
|
|
@@ -42,7 +31,7 @@ function routedRow(row, choice) {
|
|
|
42
31
|
export function listBaselineAgentRouting(agentRouting, list) {
|
|
43
32
|
const fallback = defaultSubagentEntry(list.state);
|
|
44
33
|
const choice = fallback ? { entry: fallback, source: 'default', reason: 'baseline' } : null;
|
|
45
|
-
return Object.fromEntries(Object.entries(agentRouting).map(([name, row]) => [name, routedRow(row, choice)]));
|
|
34
|
+
return Object.fromEntries(Object.entries(agentRouting).map(([name, row]) => [name, routedRow(row, choice, list.allowlist)]));
|
|
46
35
|
}
|
|
47
36
|
export function childModelLanes(input) {
|
|
48
37
|
const assigned = new Set(input.slices.map((slice) => String(slice.agent || '')).filter(Boolean));
|
|
@@ -65,6 +54,7 @@ export async function chooseListRoleModels(input) {
|
|
|
65
54
|
goal: input.goal || 'Plan official subagent roles.',
|
|
66
55
|
lanes,
|
|
67
56
|
state: input.list.state,
|
|
57
|
+
profile: childModelListProfile(input.list.allowlist),
|
|
68
58
|
...(input.env ? { env: input.env } : {})
|
|
69
59
|
});
|
|
70
60
|
return { lanes: lanes.map((lane) => lane.id), choices };
|
|
@@ -94,8 +84,8 @@ export function applyListRoleModels(agents, chosen, list, readOnlyRoleInstalled
|
|
|
94
84
|
for (const [name, row] of Object.entries(agents)) {
|
|
95
85
|
const choice = chosen?.choices[name]
|
|
96
86
|
?? (fallback ? { entry: fallback, source: 'default', reason: routed.has(name) ? 'keep_baseline' : capped } : null);
|
|
97
|
-
next[name] = routedRow(row, choice);
|
|
98
|
-
roles[name] = next[name].openrouter_only_choice;
|
|
87
|
+
next[name] = routedRow(row, choice, list.allowlist);
|
|
88
|
+
roles[name] = next[name].openrouter_only_choice ?? next[name].subagent_list_choice;
|
|
99
89
|
if (choice?.source === 'jev')
|
|
100
90
|
jevModels[name] = choice.entry.model;
|
|
101
91
|
}
|
|
@@ -104,6 +94,7 @@ export function applyListRoleModels(agents, chosen, list, readOnlyRoleInstalled
|
|
|
104
94
|
jevModels,
|
|
105
95
|
evidence: {
|
|
106
96
|
enabled: true,
|
|
97
|
+
...(list.allowlist.mode === 'configured' ? { profile: list.allowlist.profile } : {}),
|
|
107
98
|
main_model: list.mainModel,
|
|
108
99
|
default_subagent_model: fallback?.model ?? null,
|
|
109
100
|
subagent_models: list.state.subagent_models.map((entry) => ({ ...entry })),
|
|
@@ -111,7 +102,7 @@ export function applyListRoleModels(agents, chosen, list, readOnlyRoleInstalled
|
|
|
111
102
|
jev_decided_roles: Object.keys(jevModels),
|
|
112
103
|
roles,
|
|
113
104
|
read_only_role: { name: READ_ONLY_LIST_ROLE.codex_name, installed: readOnlyRoleInstalled },
|
|
114
|
-
warnings: readOnlyRoleInstalled === false ? ['
|
|
105
|
+
warnings: readOnlyRoleInstalled === false ? [`${list.allowlist.mode === 'openrouter_only' ? 'openrouter_only' : 'subagent_list'}_read_only_role_missing`] : []
|
|
115
106
|
}
|
|
116
107
|
};
|
|
117
108
|
}
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { codexHomePath, readTopLevelTomlString } from '../codex-app/codex-model-catalog.js';
|
|
5
|
+
import { codexHomePath, inferProviderFromModel, normalizeCodexModelId, readTopLevelTomlString } from '../codex-app/codex-model-catalog.js';
|
|
6
6
|
const MODELS_CACHE_FILENAME = 'models_cache.json';
|
|
7
7
|
function modelsCachePath(input) {
|
|
8
8
|
return path.join(codexHomePath(input), MODELS_CACHE_FILENAME);
|
|
@@ -150,19 +150,30 @@ export function effortForTier(tier, resolved = resolveLatestModelTiers()) {
|
|
|
150
150
|
const byDistance = [...order].sort((a, b) => Math.abs(order.indexOf(a) - index) - Math.abs(order.indexOf(b) - index));
|
|
151
151
|
return byDistance.find((effort) => supported.includes(effort)) || wanted;
|
|
152
152
|
}
|
|
153
|
-
function
|
|
153
|
+
function readModelRows(file) {
|
|
154
154
|
let parsed;
|
|
155
155
|
try {
|
|
156
|
+
const stat = fs.statSync(file);
|
|
157
|
+
if (!stat.isFile() || stat.size > 16 * 1024 * 1024)
|
|
158
|
+
return [];
|
|
156
159
|
parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
157
160
|
}
|
|
158
161
|
catch {
|
|
159
162
|
return [];
|
|
160
163
|
}
|
|
161
164
|
const models = Array.isArray(parsed?.models) ? parsed.models : Array.isArray(parsed) ? parsed : [];
|
|
165
|
+
return models.slice(0, 1024).filter((row) => row && typeof row === 'object' && !Array.isArray(row));
|
|
166
|
+
}
|
|
167
|
+
function modelEfforts(row) {
|
|
168
|
+
return Array.isArray(row.supported_reasoning_levels)
|
|
169
|
+
? [...new Set(row.supported_reasoning_levels
|
|
170
|
+
.map((level) => String(typeof level === 'string' ? level : level?.effort || '').trim().toLowerCase())
|
|
171
|
+
.filter(Boolean))]
|
|
172
|
+
: [];
|
|
173
|
+
}
|
|
174
|
+
function readCandidateRows(file) {
|
|
162
175
|
const rows = [];
|
|
163
|
-
for (const row of
|
|
164
|
-
if (!row || typeof row !== 'object')
|
|
165
|
-
continue;
|
|
176
|
+
for (const row of readModelRows(file)) {
|
|
166
177
|
const slug = String(row.slug || row.model || row.id || '').trim();
|
|
167
178
|
const id = parseGptModelId(slug);
|
|
168
179
|
if (!id)
|
|
@@ -170,15 +181,39 @@ function readCandidateRows(file) {
|
|
|
170
181
|
const visibility = String(row.visibility || 'list').toLowerCase();
|
|
171
182
|
if (visibility === 'hide' || visibility === 'hidden')
|
|
172
183
|
continue;
|
|
173
|
-
const efforts =
|
|
174
|
-
? row.supported_reasoning_levels
|
|
175
|
-
.map((level) => String(typeof level === 'string' ? level : level?.effort || '').trim().toLowerCase())
|
|
176
|
-
.filter(Boolean)
|
|
177
|
-
: [];
|
|
184
|
+
const efforts = modelEfforts(row);
|
|
178
185
|
rows.push({ slug, version: id.version, family: id.family, efforts });
|
|
179
186
|
}
|
|
180
187
|
return rows;
|
|
181
188
|
}
|
|
189
|
+
export function selectableChildModels(profile, input = {}) {
|
|
190
|
+
const file = profile === 'codex_lb' ? configuredCatalogPath(input) : modelsCachePath(input);
|
|
191
|
+
if (!file)
|
|
192
|
+
return [];
|
|
193
|
+
const models = [];
|
|
194
|
+
const seen = new Set();
|
|
195
|
+
for (const row of readModelRows(file)) {
|
|
196
|
+
const model = normalizeCodexModelId(row.slug || row.public_id || row.model || row.id);
|
|
197
|
+
const provider = String(row.provider_id || row.provider || inferProviderFromModel(model));
|
|
198
|
+
if (!model || seen.has(model.toLowerCase()) || ['hide', 'hidden'].includes(String(row.visibility).toLowerCase())
|
|
199
|
+
|| row.multi_agent_version === 'disabled'
|
|
200
|
+
|| (profile === 'openai' && model.includes('/'))
|
|
201
|
+
|| provider !== (profile === 'codex_lb' ? 'codex-lb' : 'openai'))
|
|
202
|
+
continue;
|
|
203
|
+
seen.add(model.toLowerCase());
|
|
204
|
+
models.push({ public_id: model, display_name: String(row.display_name || model), reasoning_efforts: modelEfforts(row) });
|
|
205
|
+
}
|
|
206
|
+
const newest = new Map();
|
|
207
|
+
for (const row of models) {
|
|
208
|
+
const id = parseGptModelId(row.public_id);
|
|
209
|
+
if (id && (!newest.has(id.family) || compareModelVersions(id.version, newest.get(id.family)) > 0))
|
|
210
|
+
newest.set(id.family, id.version);
|
|
211
|
+
}
|
|
212
|
+
return models.filter((row) => {
|
|
213
|
+
const id = parseGptModelId(row.public_id);
|
|
214
|
+
return !id || compareModelVersions(id.version, newest.get(id.family)) === 0;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
182
217
|
export function compareModelVersions(a, b) {
|
|
183
218
|
const length = Math.max(a.length, b.length);
|
|
184
219
|
for (let index = 0; index < length; index += 1) {
|