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
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
18
18
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
19
19
|
|
|
20
|
-
Current package: **SKS 10.5.
|
|
20
|
+
Current package: **SKS 10.5.4**. Install the latest stable release from npm.
|
|
21
21
|
|
|
22
22
|
Sneakoscope Codex (`sks`) is a local trust layer for Codex CLI and ChatGPT Desktop. It keeps agent work bounded, preserves project context, records machine-verifiable evidence, and prevents release claims that current tests or artifacts cannot support.
|
|
23
23
|
|
|
@@ -377,7 +377,7 @@ const COMMAND_HELP_LITE = {
|
|
|
377
377
|
description: 'Project TriWiki context-pack memory into managed AGENTS.md blocks or run bounded memory cleanup.'
|
|
378
378
|
},
|
|
379
379
|
naruto: {
|
|
380
|
-
usage: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] [--json] | sks naruto status|subagents|proof [latest|M-...] [--json] | sks naruto parent-summary --mission M-... --stdin [--json]',
|
|
380
|
+
usage: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] [--json] | sks naruto status|subagents|proof [latest|M-...] [--json] | sks naruto execution status [--json] | sks naruto execution set --mode auto|current-session|standalone [--restart] [--json] | sks naruto parent-summary --mission M-... --stdin [--json]',
|
|
381
381
|
description: 'Run or inspect the Codex official subagent workflow: an orchestrating parent (latest deep-tier standalone default), children on the newest model of their tier (Jev picks the tier on spawn when Jev mode is on), max_depth=1, and structured parent-thread completion evidence.'
|
|
382
382
|
},
|
|
383
383
|
reasoning: {
|
package/dist/cli/router.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ui as cliUi } from './cli-theme.js';
|
|
|
5
5
|
import { helpResult, isHelpRequest, renderManifestHelp } from './help.js';
|
|
6
6
|
import { findRetiredGlobalExecutionArgumentErrors } from './global-mode-router.js';
|
|
7
7
|
import { normalizeIntentCommand, } from '../core/commands/intent-normalization/normalizer.js';
|
|
8
|
+
import { narutoUsesCurrentSession } from '../core/subagents/naruto-execution-mode.js';
|
|
8
9
|
const routerIntentStorage = new AsyncLocalStorage();
|
|
9
10
|
export function prepareRouterExecutionIntent(argv, inheritance = {}) {
|
|
10
11
|
const effectiveArgv = argv.length ? [...argv] : ['doctor'];
|
|
@@ -241,9 +242,9 @@ async function ensureActiveRouteCommandGate(command, args) {
|
|
|
241
242
|
import('../core/mission.js')
|
|
242
243
|
]);
|
|
243
244
|
const root = await projectRoot(process.cwd()).catch(() => process.cwd());
|
|
244
|
-
const appSessionKey =
|
|
245
|
-
? ''
|
|
246
|
-
:
|
|
245
|
+
const appSessionKey = narutoUsesCurrentSession()
|
|
246
|
+
? String(process.env.CODEX_THREAD_ID || '').trim()
|
|
247
|
+
: '';
|
|
247
248
|
const state = await loadOwnedRouteState(root, appSessionKey);
|
|
248
249
|
if (safeActiveRouteContinuation(command, args, state))
|
|
249
250
|
return { ok: true, status: 'allowed_active_route_continuation' };
|
|
@@ -296,6 +297,9 @@ export function safeReadOnlySubcommand(command, args) {
|
|
|
296
297
|
if (command === 'naruto' && ['status', 'subagents', 'proof'].includes(sub)) {
|
|
297
298
|
return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
|
|
298
299
|
}
|
|
300
|
+
if (command === 'naruto' && sub === 'execution' && nested === 'status') {
|
|
301
|
+
return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
|
|
302
|
+
}
|
|
299
303
|
if (command === 'mcp' && sub === 'config' && ['list', 'test', 'backups', 'show'].includes(nested)) {
|
|
300
304
|
return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
|
|
301
305
|
}
|
|
@@ -316,6 +320,8 @@ export function safeActiveRouteContinuation(command, args, state = {}) {
|
|
|
316
320
|
const subcommand = String(args[0] || '').toLowerCase();
|
|
317
321
|
const activeRoute = String(state.route || state.route_command || state.mode || '').replace(/^\$/, '').replace(/[-_]/g, '').toUpperCase();
|
|
318
322
|
if (command === 'naruto') {
|
|
323
|
+
if (subcommand === 'execution' && String(args[1] || '').toLowerCase() === 'set')
|
|
324
|
+
return true;
|
|
319
325
|
if (subcommand === 'parent-summary') {
|
|
320
326
|
const explicitParentSummaryMission = optionValue(args, ['--mission']);
|
|
321
327
|
return Boolean(state.mission_id)
|
package/dist/commands/bridge.js
CHANGED
|
@@ -40,7 +40,8 @@ export function usage(command = 'bridge') {
|
|
|
40
40
|
'',
|
|
41
41
|
'Provider secrets are accepted only through --api-key-stdin. Readiness changes the exit code only with --strict or --require-ready.',
|
|
42
42
|
'OpenRouter Only Mode and Codex-LB authentication priority are mutually exclusive; turning one on turns the other off.',
|
|
43
|
-
'subagent-models set reads {"subagent_models":[{"model","criteria","reasoning_effort","default"}]} (max 16) from stdin.'
|
|
43
|
+
'subagent-models set reads {"profile":"codex_lb|openai|openrouter_only","subagent_models":[{"model","criteria","reasoning_effort","default"}]} (max 16) from stdin.',
|
|
44
|
+
'The profile must match the current connection. Omitting profile preserves the legacy OpenRouter-list staging behavior.'
|
|
44
45
|
].join('\n');
|
|
45
46
|
}
|
|
46
47
|
export async function run(_command, args = []) {
|
|
@@ -152,7 +153,7 @@ async function parseInvocation(args, io) {
|
|
|
152
153
|
...base,
|
|
153
154
|
request: {
|
|
154
155
|
operation: 'subagent-models.set',
|
|
155
|
-
|
|
156
|
+
...subagentModelsFromStdin(await io.readStdin()),
|
|
156
157
|
no_restart: parsed.flags.has('--no-restart')
|
|
157
158
|
},
|
|
158
159
|
label: 'Subagent model list update'
|
|
@@ -376,7 +377,11 @@ function subagentModelsFromStdin(text) {
|
|
|
376
377
|
: undefined;
|
|
377
378
|
if (!Array.isArray(models))
|
|
378
379
|
throw new BridgeCliError('bridge_subagent_models_stdin_invalid', 'retry_with_stdin');
|
|
379
|
-
|
|
380
|
+
const profile = parsed.profile;
|
|
381
|
+
if (profile !== undefined && profile !== 'openrouter_only' && profile !== 'codex_lb' && profile !== 'openai') {
|
|
382
|
+
throw new BridgeCliError('bridge_subagent_models_profile_invalid', 'retry_with_stdin');
|
|
383
|
+
}
|
|
384
|
+
return { subagent_models: models, ...(profile === undefined ? {} : { profile }) };
|
|
380
385
|
}
|
|
381
386
|
function rejectSecretArgv(args) {
|
|
382
387
|
for (const arg of args) {
|
|
@@ -14,7 +14,7 @@ import { decideSubagentModel } from '../subagents/model-policy.js';
|
|
|
14
14
|
import { latestTierModelSet } from '../subagents/model-tiers.js';
|
|
15
15
|
import { consultJevTurnModel } from '../decisions/integration.js';
|
|
16
16
|
import { chooseChildModel, fallbackChildModel } from '../decisions/child-model-choice.js';
|
|
17
|
-
import {
|
|
17
|
+
import { allowlistedChildModel, effectiveChildModelAllowlist, childModelListProfile, listChildModelEffort, isSubagentModelEffort } from '../subagents/child-model-allowlist.js';
|
|
18
18
|
export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
|
|
19
19
|
export async function runNativeWorkerBackendRouter(input) {
|
|
20
20
|
const root = path.resolve(input.agentRoot);
|
|
@@ -93,8 +93,10 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
93
93
|
cwd: String(input.intake.cwd || root),
|
|
94
94
|
prompt: buildWorkerPrompt(input.slice),
|
|
95
95
|
model: modelRouting.choice.model,
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
...(modelRouting.choice.reasoning ? {
|
|
97
|
+
reasoningEffort: modelRouting.choice.reasoning,
|
|
98
|
+
modelReasoningEffort: modelRouting.choice.reasoning
|
|
99
|
+
} : {}),
|
|
98
100
|
serviceTier: modelRouting.choice.serviceTier,
|
|
99
101
|
inputFiles: input.intake.input_files || [],
|
|
100
102
|
inputImages: input.intake.input_images || [],
|
|
@@ -232,7 +234,7 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
|
232
234
|
const category = categoryForWorkerRole(String(input.agent?.role || 'executor'), taskKindText);
|
|
233
235
|
const env = deps.env || process.env;
|
|
234
236
|
const allowlist = effectiveChildModelAllowlist({ env });
|
|
235
|
-
if (allowlist.mode
|
|
237
|
+
if (allowlist.mode !== 'tiers') {
|
|
236
238
|
return resolveOpenRouterOnlyWorkerRouting(input, deps, { allowlist, category, env, taskKindText, riskText });
|
|
237
239
|
}
|
|
238
240
|
const lbCatalog = narutoOnly
|
|
@@ -306,11 +308,12 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
|
306
308
|
}
|
|
307
309
|
async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
|
|
308
310
|
const { allowlist, category, env } = ctx;
|
|
309
|
-
const state = {
|
|
311
|
+
const state = { subagent_models: allowlist.entries };
|
|
310
312
|
const explicitModel = String(env.SKS_WORKER_MODEL || env.SKS_AGENT_MODEL || '').trim();
|
|
311
|
-
const
|
|
313
|
+
const rawEffort = env.SKS_WORKER_REASONING || env.SKS_WORKER_MODEL_REASONING;
|
|
314
|
+
const explicitReasoning = allowlist.mode === 'openrouter_only' ? listReasoning(rawEffort) : normalizeModelReasoning(rawEffort);
|
|
312
315
|
const explicitTier = normalizeServiceTier(String(env.SKS_WORKER_SERVICE_TIER || env.SKS_SERVICE_TIER || '').trim());
|
|
313
|
-
const planned = /^
|
|
316
|
+
const planned = /^(openrouter_only|configured_list)_/.test(String(input.agent?.routed_model_policy || ''))
|
|
314
317
|
? allowlistedChildModel(input.agent?.routed_model, allowlist)
|
|
315
318
|
: null;
|
|
316
319
|
const requested = String(input.agent?.routed_model || input.agent?.model || '').trim() || null;
|
|
@@ -322,18 +325,23 @@ async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
|
|
|
322
325
|
? fallbackChildModel(state, planned, 'planned')
|
|
323
326
|
: deps.consultJev === false || !task
|
|
324
327
|
? fallbackChildModel(state, requested, deps.consultJev === false ? 'jev_skipped' : 'empty_task')
|
|
325
|
-
: await chooseChildModel({ root: deps.root || process.cwd(), task, role: input.agent?.role || null, requestedModel: requested, state, env })
|
|
328
|
+
: await chooseChildModel({ root: deps.root || process.cwd(), task, role: input.agent?.role || null, requestedModel: requested, state, env, profile: childModelListProfile(allowlist) })
|
|
326
329
|
.catch(() => fallbackChildModel(state, requested, 'consult_failed'));
|
|
327
330
|
const taskPolicy = decideSubagentModel({ title: ctx.taskKindText, description: ctx.riskText, role: input.agent?.role });
|
|
328
331
|
const choiceModel = choice?.entry.model || '';
|
|
329
332
|
const routed = {
|
|
330
333
|
model: choiceModel,
|
|
331
|
-
reasoning:
|
|
334
|
+
reasoning: allowlist.mode === 'openrouter_only'
|
|
335
|
+
? explicitReasoning || choice?.entry.reasoning_effort || listReasoning(taskPolicy.modelReasoningEffort) || 'medium'
|
|
336
|
+
: explicitReasoning || (choice ? listChildModelEffort(choice.entry, null) : null),
|
|
332
337
|
serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
|
|
333
338
|
};
|
|
334
339
|
const blockers = [
|
|
335
|
-
...(!allowlist.entries.length ? ['openrouter_only_subagent_list_empty'] : []),
|
|
336
|
-
...(explicitModel && !explicitListed ? ['openrouter_only_subagent_model_not_listed'] : []),
|
|
340
|
+
...(!allowlist.entries.length ? [allowlist.mode === 'openrouter_only' ? 'openrouter_only_subagent_list_empty' : 'configured_subagent_list_unavailable'] : []),
|
|
341
|
+
...(explicitModel && !explicitListed ? [allowlist.mode === 'openrouter_only' ? 'openrouter_only_subagent_model_not_listed' : 'configured_subagent_model_not_listed'] : []),
|
|
342
|
+
...(allowlist.mode === 'configured' && rawEffort
|
|
343
|
+
&& (!explicitReasoning || !choice?.entry.supported_reasoning_efforts?.includes(explicitReasoning))
|
|
344
|
+
? ['configured_subagent_effort_unsupported'] : []),
|
|
337
345
|
...(!choiceModel ? ['naruto_worker_model_unavailable'] : [])
|
|
338
346
|
];
|
|
339
347
|
const why = choice ? `${choice.source}${choice.reason === 'applied' ? '' : `:${choice.reason}`}` : 'blocked';
|
|
@@ -343,7 +351,7 @@ async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
|
|
|
343
351
|
explicit: Boolean(explicitModel),
|
|
344
352
|
lb_catalog: null,
|
|
345
353
|
blockers: [...new Set(blockers)],
|
|
346
|
-
reason: `${category}->${choiceModel || 'blocked'}@${routed.reasoning} (openrouter only list: ${why})`
|
|
354
|
+
reason: `${category}->${choiceModel || 'blocked'}@${routed.reasoning || 'default'} (${allowlist.mode === 'openrouter_only' ? 'openrouter only' : allowlist.profile} list: ${why})`
|
|
347
355
|
};
|
|
348
356
|
}
|
|
349
357
|
function listReasoning(value) {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { isCodexAppRunningByBundleId } from './menubar/config.js';
|
|
2
2
|
import { restartCodexApp } from './codex-app-restart.js';
|
|
3
|
+
export const CODEX_RESTART_FAILED_BLOCKER = 'codex_restart_failed_manual_restart_required';
|
|
4
|
+
export function codexRestartBlockers(restart) {
|
|
5
|
+
return restart?.attempted && !restart.ok ? [CODEX_RESTART_FAILED_BLOCKER] : [];
|
|
6
|
+
}
|
|
3
7
|
export async function maybeRestartRunningCodexApp(input) {
|
|
4
8
|
const skipped = (reason) => ({ attempted: false, running: null, status: 'skipped', reason, ok: true, blockers: [] });
|
|
5
9
|
if (input.noRestart)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readText, writeJsonAtomic } from '../../fsx.js';
|
|
2
2
|
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
3
|
+
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
3
4
|
import { BRIDGE_MODEL_SELECTION_SCHEMA, MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, pruneSelection, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
4
5
|
import { listOpenRouterModels } from '../../providers/openrouter/openrouter-account.js';
|
|
5
6
|
import { buildCombinedBridgeCatalog, readActiveCombinedBridgeCatalog, stageCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
@@ -338,6 +339,7 @@ export async function listSelectableModels(options) {
|
|
|
338
339
|
}
|
|
339
340
|
export async function selectExposedModels(publicIds, options) {
|
|
340
341
|
const paths = controllerPaths(options);
|
|
342
|
+
const before = await readBridgeModelSelectionState(paths.home, nowIso(options));
|
|
341
343
|
const requested = [...new Set(publicIds.map((id) => String(id || '').trim()).filter(Boolean))].sort();
|
|
342
344
|
if (requested.length > MAX_SELECTED_OPENROUTER_MODELS) {
|
|
343
345
|
throw new Error('bridge_model_selection_limit_exceeded');
|
|
@@ -352,7 +354,19 @@ export async function selectExposedModels(publicIds, options) {
|
|
|
352
354
|
const activation = sync.activation && typeof sync.activation === 'object'
|
|
353
355
|
? sync.activation
|
|
354
356
|
: {};
|
|
355
|
-
|
|
357
|
+
const changed = before.selection.openrouter.public_ids.join('\0') !== requested.join('\0');
|
|
358
|
+
const restart = sync.ok === true
|
|
359
|
+
? await maybeRestartRunningCodexApp({
|
|
360
|
+
env: controllerEnv(options),
|
|
361
|
+
changed,
|
|
362
|
+
noRestart: false,
|
|
363
|
+
...(options.platform ? { platform: options.platform } : {}),
|
|
364
|
+
...(options.codexAppRunningImpl ? { isRunningImpl: options.codexAppRunningImpl } : {}),
|
|
365
|
+
...(options.codexAppRestartImpl ? { restartImpl: options.codexAppRestartImpl } : {})
|
|
366
|
+
})
|
|
367
|
+
: null;
|
|
368
|
+
const restartBlockers = codexRestartBlockers(restart);
|
|
369
|
+
return commandResult('models.select', sync.ok === true && restartBlockers.length === 0, status, { selected_count: requested.length, changed, catalog_sync: sync, codex_restart: restart }, [...(sync.ok === true ? [] : stringArray(activation.blockers)), ...restartBlockers], options);
|
|
356
370
|
}
|
|
357
371
|
function seedSelectionFromConfiguredModel(selection, configText) {
|
|
358
372
|
const configured = readTopLevelTomlString(configText, 'model');
|
|
@@ -8,7 +8,8 @@ import { DESKTOP_BRIDGE_OFFICIAL_UPSTREAM_BASE_URL } from '../desktop-bridge/ind
|
|
|
8
8
|
import { resolveBridgeRequestRoute } from '../request-route-resolver.js';
|
|
9
9
|
import { applyOfficialModelPassthrough, buildBridgeRoutingPolicy, setBridgeRoutingDefault, writeBridgeRoutingPolicy } from '../provider-route-policy.js';
|
|
10
10
|
import { syncCatalogInternal } from './catalog.js';
|
|
11
|
-
import { codexRestartBlockers
|
|
11
|
+
import { codexRestartBlockers } from '../../codex-app/codex-app-restart-policy.js';
|
|
12
|
+
import { releaseOpenRouterOnlyForAuthPriority, releaseOpenRouterOnlyWithoutBridge, restartCodexAfterOpenRouterOnlyOff } from './openrouter-only-commands.js';
|
|
12
13
|
import { openRouterOnlyStatus } from './openrouter-only-status.js';
|
|
13
14
|
import { commandResult, controllerPaths, nowIso, persistRuntimeSettings, providerCode, providerRegistrySnapshot, stringArray } from './shared.js';
|
|
14
15
|
import { desktopBridgeStatusV3, loadCore, statusFromCore } from './status.js';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
2
|
-
import { maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
2
|
+
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
3
3
|
import { readText } from '../../fsx.js';
|
|
4
4
|
import { isOpenRouterModelId } from '../../imagegen/imagegen-config.js';
|
|
5
|
-
import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
|
|
5
|
+
import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, readNativeSubagentModelStore, writeNativeSubagentModels, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
|
|
6
|
+
import { selectableChildModels } from '../../subagents/model-tiers.js';
|
|
6
7
|
import { assertDesktopBridgeStatusV3 } from '../bridge-runtime-validation.js';
|
|
7
8
|
import { readActiveCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
8
9
|
import { MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
@@ -19,12 +20,43 @@ async function finish(operation, ok, blockers, extra, options) {
|
|
|
19
20
|
const status = statusFromCore(core, options);
|
|
20
21
|
assertDesktopBridgeStatusV3(status);
|
|
21
22
|
const openRouterOnly = await openRouterOnlyStatusFromCore(core, options);
|
|
23
|
+
const subagentSettings = operation.startsWith('subagent-models.')
|
|
24
|
+
? await subagentModelSettings(core, openRouterOnly)
|
|
25
|
+
: null;
|
|
22
26
|
return commandResult(operation, ok, status, {
|
|
23
27
|
openrouter_only: openRouterOnly,
|
|
24
28
|
auth_priority: status.auth_priority,
|
|
25
|
-
...extra
|
|
29
|
+
...extra,
|
|
30
|
+
...(subagentSettings ? { subagent_model_settings: subagentSettings, available: subagentSettings.available } : {})
|
|
26
31
|
}, blockers, options);
|
|
27
32
|
}
|
|
33
|
+
async function subagentModelSettings(core, openRouterOnly) {
|
|
34
|
+
if (openRouterOnly.enabled) {
|
|
35
|
+
return {
|
|
36
|
+
schema: 'sks.subagent-model-settings.v1', profile: 'openrouter_only',
|
|
37
|
+
configured: true, editable: true, error: openRouterOnly.error,
|
|
38
|
+
subagent_models: openRouterOnly.subagent_models,
|
|
39
|
+
jev_enabled: openRouterOnly.jev_enabled, warnings: openRouterOnly.warnings,
|
|
40
|
+
available: (await availableOpenRouterModels(core.paths.home)).map((row) => ({ ...row, reasoning_efforts: ['low', 'medium', 'high', 'xhigh'] }))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const profile = core.authPriorityEnabled ? 'codex_lb' : 'openai';
|
|
44
|
+
const location = openRouterOnlyStoreLocation(core.paths);
|
|
45
|
+
const saved = readNativeSubagentModelStore(location);
|
|
46
|
+
const available = selectableChildModels(profile, location);
|
|
47
|
+
const entries = saved.store.profiles[profile];
|
|
48
|
+
return {
|
|
49
|
+
schema: 'sks.subagent-model-settings.v1', profile,
|
|
50
|
+
configured: entries.length > 0, editable: saved.blockers.length === 0,
|
|
51
|
+
error: saved.blockers[0] ?? (available.length ? null : 'subagent_model_catalog_unavailable'),
|
|
52
|
+
subagent_models: entries.map((entry) => ({
|
|
53
|
+
...entry,
|
|
54
|
+
routable: available.some((row) => row.public_id === entry.model
|
|
55
|
+
&& (!entry.reasoning_effort || row.reasoning_efforts.includes(entry.reasoning_effort)))
|
|
56
|
+
})),
|
|
57
|
+
jev_enabled: openRouterOnly.jev_enabled, warnings: saved.blockers, available
|
|
58
|
+
};
|
|
59
|
+
}
|
|
28
60
|
function syncBlockers(sync) {
|
|
29
61
|
const activation = sync.activation && typeof sync.activation === 'object' && !Array.isArray(sync.activation)
|
|
30
62
|
? sync.activation
|
|
@@ -32,11 +64,7 @@ function syncBlockers(sync) {
|
|
|
32
64
|
const blockers = stringArray(activation.blockers);
|
|
33
65
|
return blockers.length > 0 ? blockers : ['combined_catalog_sync_failed'];
|
|
34
66
|
}
|
|
35
|
-
export const CODEX_RESTART_FAILED_BLOCKER = 'codex_restart_failed_manual_restart_required';
|
|
36
67
|
export const CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING = 'codex_relaunch_required_for_new_subagent_models';
|
|
37
|
-
export function codexRestartBlockers(restart) {
|
|
38
|
-
return restart?.attempted && !restart.ok ? [CODEX_RESTART_FAILED_BLOCKER] : [];
|
|
39
|
-
}
|
|
40
68
|
function restartCodex(changed, noRestart, options) {
|
|
41
69
|
return maybeRestartRunningCodexApp({
|
|
42
70
|
env: controllerEnv(options),
|
|
@@ -241,23 +269,55 @@ async function availableOpenRouterModels(home) {
|
|
|
241
269
|
}
|
|
242
270
|
}
|
|
243
271
|
export async function listSubagentModels(options) {
|
|
244
|
-
|
|
245
|
-
return finish('subagent-models.list', true, [], { available }, options);
|
|
272
|
+
return finish('subagent-models.list', true, [], {}, options);
|
|
246
273
|
}
|
|
247
|
-
export async function setSubagentModels(raw, noRestart, options) {
|
|
274
|
+
export async function setSubagentModels(raw, noRestart, options, expectedProfile) {
|
|
248
275
|
if (!Array.isArray(raw))
|
|
249
276
|
return finish('subagent-models.set', false, ['subagent_models_payload_invalid'], {}, options);
|
|
250
|
-
const
|
|
277
|
+
const core = await loadCore(options);
|
|
278
|
+
const activeProfile = core.openRouterOnlyEnabled ? 'openrouter_only' : core.authPriorityEnabled ? 'codex_lb' : 'openai';
|
|
279
|
+
if (expectedProfile && activeProfile !== expectedProfile) {
|
|
280
|
+
return finish('subagent-models.set', false, ['subagent_model_profile_changed'], {}, options);
|
|
281
|
+
}
|
|
282
|
+
const profile = expectedProfile ?? 'openrouter_only';
|
|
283
|
+
const { entries, issues } = normalizeSubagentModelList(raw, profile);
|
|
251
284
|
if (issues.length > 0) {
|
|
252
285
|
return finish('subagent-models.set', false, issues.map((issue) => `${issue.code}:${issue.index}`), {}, options);
|
|
253
286
|
}
|
|
254
287
|
const paths = controllerPaths(options);
|
|
255
288
|
const location = openRouterOnlyStoreLocation(paths);
|
|
256
289
|
const before = readOpenRouterOnlyStateSync(location);
|
|
257
|
-
if (!before.enabled) {
|
|
290
|
+
if (!before.enabled && profile === 'openrouter_only') {
|
|
258
291
|
await writeOpenRouterOnlyState({ subagent_models: entries }, location);
|
|
259
292
|
return finish('subagent-models.set', true, [], { catalog_sync: null }, options);
|
|
260
293
|
}
|
|
294
|
+
if (profile !== 'openrouter_only') {
|
|
295
|
+
const nativeProfile = core.authPriorityEnabled ? 'codex_lb' : 'openai';
|
|
296
|
+
const available = selectableChildModels(nativeProfile, location);
|
|
297
|
+
const blockers = entries.flatMap((entry, index) => {
|
|
298
|
+
const model = available.find((row) => row.public_id === entry.model);
|
|
299
|
+
return !model ? [`subagent_model_not_available:${index}`]
|
|
300
|
+
: entry.reasoning_effort && !model.reasoning_efforts.includes(entry.reasoning_effort)
|
|
301
|
+
? [`subagent_model_effort_unsupported:${index}`] : [];
|
|
302
|
+
});
|
|
303
|
+
const saved = readNativeSubagentModelStore(location);
|
|
304
|
+
if (saved.blockers.length || blockers.length) {
|
|
305
|
+
return finish('subagent-models.set', false, [...saved.blockers, ...blockers], {}, options);
|
|
306
|
+
}
|
|
307
|
+
const role = entries.length ? await ensureUserReadOnlyListRole(paths.home) : 'unchanged';
|
|
308
|
+
if (role === 'preserved_user_file') {
|
|
309
|
+
return finish('subagent-models.set', false, ['subagent_model_read_only_role_unavailable'], {}, options);
|
|
310
|
+
}
|
|
311
|
+
await writeNativeSubagentModels(nativeProfile, entries, location);
|
|
312
|
+
const changed = JSON.stringify(saved.store.profiles[nativeProfile]) !== JSON.stringify(entries);
|
|
313
|
+
const roleChanged = role === 'created' || role === 'updated';
|
|
314
|
+
const restart = await restartCodex(roleChanged, noRestart, options);
|
|
315
|
+
const relaunchPending = roleChanged && !restart.attempted && restart.reason !== 'codex_not_running';
|
|
316
|
+
return finish('subagent-models.set', true, codexRestartBlockers(restart), {
|
|
317
|
+
changed, catalog_sync: null, read_only_role: role, codex_restart: restart,
|
|
318
|
+
warnings: relaunchPending ? [CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING] : []
|
|
319
|
+
}, options);
|
|
320
|
+
}
|
|
261
321
|
const exposedBefore = await exposedModelIds(paths);
|
|
262
322
|
const sync = await syncCatalogInternal(options, {
|
|
263
323
|
openRouterOnly: { ...before, subagent_models: entries },
|
|
@@ -73,7 +73,7 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
|
73
73
|
return setOpenRouterOnly(request.enabled, request.no_restart === true, options);
|
|
74
74
|
}
|
|
75
75
|
if (request.operation === 'subagent-models.set') {
|
|
76
|
-
return setSubagentModels(request.subagent_models, request.no_restart === true, options);
|
|
76
|
+
return setSubagentModels(request.subagent_models, request.no_restart === true, options, request.profile);
|
|
77
77
|
}
|
|
78
78
|
if (request.operation === 'route.official-models')
|
|
79
79
|
return setOfficialModelsMode(request.mode, options);
|
|
@@ -9,6 +9,7 @@ import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
|
|
|
9
9
|
import { sksPrefixedDollarCommand } from '../routes/dollar-prefix.js';
|
|
10
10
|
import { closeWorkOrderLedgerForRouteResult } from '../work-order-ledger.js';
|
|
11
11
|
import { flag, positionalArgs, promptOf, warnOnMultipleActiveSessions } from './command-utils.js';
|
|
12
|
+
import { narutoUsesCurrentSession } from '../subagents/naruto-execution-mode.js';
|
|
12
13
|
const ACTIONS = new Set(['prepare', 'run', 'status', 'proof', 'fixture', 'help', '--help', '-h']);
|
|
13
14
|
export async function alignCommand(sub = 'prepare', args = []) {
|
|
14
15
|
const action = String(sub || 'prepare').toLowerCase();
|
|
@@ -262,7 +263,7 @@ async function closeVerifiedAlignRoute(root, missionId, sessionKey) {
|
|
|
262
263
|
return closed?.ok === true;
|
|
263
264
|
}
|
|
264
265
|
function ownerSessionKey() {
|
|
265
|
-
if (
|
|
266
|
+
if (!narutoUsesCurrentSession())
|
|
266
267
|
return null;
|
|
267
268
|
return String(process.env.CODEX_THREAD_ID || '').trim() || null;
|
|
268
269
|
}
|
|
@@ -16,6 +16,7 @@ import { effectiveSubagentTarget, refreshSubagentWaveLifecycle } from '../subage
|
|
|
16
16
|
import { HOST_CAPABILITY_HOOK_RUNTIME_FILENAME, bindParentSummaryToHostCapabilityEvidence, createHostCapabilityHookRuntimeBinding, resolveHostCapabilityHookRuntimeBinding } from '../agent-bridge/host-capability-runtime.js';
|
|
17
17
|
import { renderHostCapabilityBlockedLines } from '../agent-bridge/host-capability-policy.js';
|
|
18
18
|
import { uniqueStrings } from '../text/strings.js';
|
|
19
|
+
import { configureNarutoExecution } from './naruto-config-command.js';
|
|
19
20
|
import { completeNarutoTerminalBundle, refreshOfficialSubagentCompletionArtifacts } from '../hooks-runtime/official-subagent-lifecycle.js';
|
|
20
21
|
export { buildNarutoGateResult } from '../subagents/official-subagent-preparation.js';
|
|
21
22
|
export { attachNarutoLaunchDiagnostics } from '../subagents/naruto-launch-diagnostics.js';
|
|
@@ -65,10 +66,53 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
65
66
|
return narutoSubagents(parsed);
|
|
66
67
|
if (parsed.action === 'proof')
|
|
67
68
|
return narutoProof(parsed);
|
|
69
|
+
if (parsed.action === 'execution')
|
|
70
|
+
return narutoExecution(parsed);
|
|
68
71
|
if (parsed.action === 'parent-summary')
|
|
69
72
|
return narutoParentSummary(parsed);
|
|
70
73
|
return narutoRun(parsed);
|
|
71
74
|
}
|
|
75
|
+
async function narutoExecution(parsed) {
|
|
76
|
+
const configuration = await configureNarutoExecution({
|
|
77
|
+
...(parsed.executionMode ? { mode: parsed.executionMode } : {}),
|
|
78
|
+
restart: parsed.restart
|
|
79
|
+
});
|
|
80
|
+
const result = {
|
|
81
|
+
schema: 'sks.naruto-execution-result.v1',
|
|
82
|
+
ok: configuration.ok,
|
|
83
|
+
action: 'execution',
|
|
84
|
+
command: parsed.executionCommand,
|
|
85
|
+
mode: configuration.mode,
|
|
86
|
+
stored: configuration.stored,
|
|
87
|
+
changed: configuration.changed,
|
|
88
|
+
path: configuration.path,
|
|
89
|
+
blockers: configuration.blockers,
|
|
90
|
+
restart: configuration.restart
|
|
91
|
+
};
|
|
92
|
+
return emit(parsed, result, () => {
|
|
93
|
+
if (!configuration.ok) {
|
|
94
|
+
for (const blocker of configuration.blockers)
|
|
95
|
+
console.error(`Naruto execution setting blocked: ${blocker}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (parsed.executionCommand === 'status') {
|
|
99
|
+
console.log(`Naruto execution mode: ${configuration.mode} (${configuration.stored ? 'saved' : 'default'})`);
|
|
100
|
+
console.log(`Preference: ${configuration.path}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const restart = configuration.restart;
|
|
104
|
+
const suffix = restart?.status === 'restarted'
|
|
105
|
+
? ' Codex was restarted; start a new task.'
|
|
106
|
+
: restart?.reason === 'codex_not_running'
|
|
107
|
+
? ' Codex is not running; the setting applies on its next launch.'
|
|
108
|
+
: restart?.reason === 'not_macos'
|
|
109
|
+
? ' The setting is saved; restart Codex on macOS to apply it.'
|
|
110
|
+
: restart?.status === 'skipped'
|
|
111
|
+
? ` The setting is saved; restart skipped (${restart.reason || 'policy'}).`
|
|
112
|
+
: '';
|
|
113
|
+
console.log(`Naruto execution mode saved: ${configuration.mode}.${suffix}`);
|
|
114
|
+
}, !configuration.ok);
|
|
115
|
+
}
|
|
72
116
|
async function narutoParentSummary(parsed) {
|
|
73
117
|
const root = await sksRoot();
|
|
74
118
|
const appSession = detectCodexAppSession();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../codex-app/codex-app-restart-policy.js';
|
|
2
|
+
import { readNarutoExecutionMode, writeNarutoExecutionMode } from '../subagents/naruto-execution-mode.js';
|
|
3
|
+
export async function configureNarutoExecution(input) {
|
|
4
|
+
const env = input.env || process.env;
|
|
5
|
+
try {
|
|
6
|
+
const preference = input.mode
|
|
7
|
+
? await writeNarutoExecutionMode(input.mode, env)
|
|
8
|
+
: { ...readNarutoExecutionMode(env), changed: false };
|
|
9
|
+
if (preference.blockers.length)
|
|
10
|
+
return { ...preference, ok: false, restart: null };
|
|
11
|
+
const restart = input.mode && input.restart
|
|
12
|
+
? await maybeRestartRunningCodexApp({ env, changed: true, noRestart: false, ...input.restartOptions })
|
|
13
|
+
: null;
|
|
14
|
+
const blockers = restart && !restart.ok
|
|
15
|
+
? [...restart.blockers, ...codexRestartBlockers(restart)]
|
|
16
|
+
: [];
|
|
17
|
+
return { ...preference, ok: blockers.length === 0, blockers, restart };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return { ...readNarutoExecutionMode(env), ok: false, changed: false, restart: null,
|
|
21
|
+
blockers: [error instanceof Error ? error.message : 'naruto_execution_config_failed'] };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -7,10 +7,11 @@ import { PIPELINE_PLAN_ARTIFACT, pipelinePlanState, projectGateStatus, sealRoute
|
|
|
7
7
|
import { routePrompt } from '../routes.js';
|
|
8
8
|
import { positionalArgs } from '../../cli/args.js';
|
|
9
9
|
import { flag, readFlagValue, resolveMissionId } from './command-utils.js';
|
|
10
|
+
import { narutoUsesCurrentSession } from '../subagents/naruto-execution-mode.js';
|
|
10
11
|
export async function pipelineCommand(args = []) {
|
|
11
12
|
const root = await projectRoot();
|
|
12
13
|
const action = args[0] || 'status';
|
|
13
|
-
const sessionKey = process.env.
|
|
14
|
+
const sessionKey = narutoUsesCurrentSession() ? process.env.CODEX_THREAD_ID : '';
|
|
14
15
|
const state = await loadOwnedRouteState(root, sessionKey);
|
|
15
16
|
const sessions = await listSessionStates(root);
|
|
16
17
|
if (action === 'status') {
|
|
@@ -72,7 +72,7 @@ export async function chooseChildModels(input) {
|
|
|
72
72
|
workflowId: input.workflowId,
|
|
73
73
|
goal: input.goal,
|
|
74
74
|
questions,
|
|
75
|
-
facts: { openrouter_only:
|
|
75
|
+
facts: { openrouter_only: (input.profile ?? 'openrouter_only') === 'openrouter_only', subagent_model_profile: input.profile ?? 'openrouter_only', subagent_models: entries.map((entry) => canonicalChildModelId(entry.model)) },
|
|
76
76
|
...(input.env ? { env: input.env } : {}),
|
|
77
77
|
...(input.deadlineMs === undefined ? {} : { deadlineMs: input.deadlineMs })
|
|
78
78
|
})
|
|
@@ -100,6 +100,7 @@ export async function chooseChildModel(input) {
|
|
|
100
100
|
goal: input.task || 'Spawn a child agent.',
|
|
101
101
|
lanes: [{ id: 'spawn', task: input.task, role: input.role ?? null, requestedModel: input.requestedModel ?? null }],
|
|
102
102
|
state: input.state,
|
|
103
|
+
...(input.profile ? { profile: input.profile } : {}),
|
|
103
104
|
...(input.env ? { env: input.env } : {}),
|
|
104
105
|
...(input.deadlineMs === undefined ? {} : { deadlineMs: input.deadlineMs })
|
|
105
106
|
});
|
|
@@ -145,9 +145,10 @@ export async function statusReport(env) {
|
|
|
145
145
|
: 'ready';
|
|
146
146
|
const childModels = effectiveChildModelAllowlist({ env });
|
|
147
147
|
const openRouterOnly = childModels.mode === 'openrouter_only';
|
|
148
|
+
const listMode = childModels.mode !== 'tiers';
|
|
148
149
|
const childTierPoints = new Set(['spawn_tier', 'worker_tier', 'role_tiers', 'role_omission']);
|
|
149
150
|
const decisionPoints = ['turn_route', 'turn_tier', 'spawn_tier', 'worker_tier', 'role_tiers', 'role_omission', 'plan', 'context', 'parent_edit_delegation', 'image_need', 'image_parameters', 'qa_effort_escalation']
|
|
150
|
-
.filter((point) => !
|
|
151
|
+
.filter((point) => !listMode || !childTierPoints.has(point));
|
|
151
152
|
return {
|
|
152
153
|
schema: 'sks.jev-decision-status.v1',
|
|
153
154
|
ok: true,
|
|
@@ -167,8 +168,14 @@ export async function statusReport(env) {
|
|
|
167
168
|
recovery: config.capabilities.recovery
|
|
168
169
|
},
|
|
169
170
|
decision_points: jevEnabled(config)
|
|
170
|
-
? [...decisionPoints, ...(
|
|
171
|
+
? [...decisionPoints, ...(listMode ? ['child_model'] : [])]
|
|
171
172
|
: [],
|
|
173
|
+
subagent_model_list: {
|
|
174
|
+
active: listMode,
|
|
175
|
+
profile: childModels.mode === 'configured' ? childModels.profile : openRouterOnly ? 'openrouter_only' : null,
|
|
176
|
+
models: listMode ? childModels.models : [],
|
|
177
|
+
default_model: childModels.default_model
|
|
178
|
+
},
|
|
172
179
|
openrouter_only: {
|
|
173
180
|
enabled: openRouterOnly,
|
|
174
181
|
subagent_model_count: openRouterOnly ? childModels.models.length : 0,
|
|
@@ -9,7 +9,7 @@ import { renderAuthoritativeSksSkillContext } from '../codex-native/sks-skill-pa
|
|
|
9
9
|
import { resolveManagedSkillSourcesForAdmission } from './managed-skill-admission.js';
|
|
10
10
|
import { managedSkillDigestBlocksEnforced } from '../verification-profile.js';
|
|
11
11
|
import { looksLikeActiveContinuationPrompt } from './naruto-decision-gate.js';
|
|
12
|
-
import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
|
|
12
|
+
import { effectiveChildModelAllowlist, childModelListLabel } from '../subagents/child-model-allowlist.js';
|
|
13
13
|
import { EXCLUSIVE_SURFACE_SPAWN_LINE } from '../subagents/exclusive-surface-rule.js';
|
|
14
14
|
import { renderChildModelCriteria, SUBAGENT_MODELS_SETTINGS_HINT } from './subagent-spawn-policy.js';
|
|
15
15
|
import { extractUserPrompt, looksLikeCodexGitAction, looksLikeCodexUiSettingsEvent } from './payload-signals.js';
|
|
@@ -31,12 +31,12 @@ const OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT = [
|
|
|
31
31
|
].join('\n');
|
|
32
32
|
function openRouterOnlyChildModelLine(allowlist) {
|
|
33
33
|
if (!allowlist.models.length) {
|
|
34
|
-
return `- SKS
|
|
34
|
+
return `- SKS ${childModelListLabel(allowlist)} mode is on and the subagent model list is empty: every spawn_agent call is denied until the user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
|
|
35
35
|
}
|
|
36
|
-
return `- SKS
|
|
36
|
+
return `- SKS ${childModelListLabel(allowlist)} mode is on: every child runs a model from the user's subagent list, and the SKS PreToolUse hook denies any other model. The list, with the user's rule for each model: ${renderChildModelCriteria(allowlist)}. The hook routes each spawn_agent call to a list model (Jev picks by these rules when Jev mode is on; otherwise a listed requested model, else the default), so pass \`fork_turns="none"\` or a positive bounded turn count and do not tune model or effort yourself.`;
|
|
37
37
|
}
|
|
38
38
|
export function officialSubagentSpawnCompatibilityContext(allowlist = effectiveChildModelAllowlist()) {
|
|
39
|
-
if (allowlist.mode
|
|
39
|
+
if (allowlist.mode === 'tiers')
|
|
40
40
|
return OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT;
|
|
41
41
|
return [...OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD, openRouterOnlyChildModelLine(allowlist)].join('\n');
|
|
42
42
|
}
|