sneakoscope 10.3.7 → 10.3.8

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 (63) hide show
  1. package/README.md +12 -181
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/commands/bridge.js +61 -1
  5. package/dist/commands/codex-app.js +4 -1
  6. package/dist/config/skills-manifest.json +1 -1
  7. package/dist/core/agents/native-worker-backend-router.js +55 -0
  8. package/dist/core/codex-app/codex-app-restart-policy.js +20 -0
  9. package/dist/core/codex-app/codex-context-window.js +61 -24
  10. package/dist/core/codex-app/menubar/constants.js +4 -0
  11. package/dist/core/codex-compat/codex-hook-output-builders.js +3 -1
  12. package/dist/core/codex-lb/bridge-cli-contract.js +2 -0
  13. package/dist/core/codex-lb/desktop-bridge/exclusive-provider-guard.js +59 -0
  14. package/dist/core/codex-lb/desktop-bridge/http-forward.js +23 -15
  15. package/dist/core/codex-lb/desktop-bridge/index.js +1 -0
  16. package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +10 -3
  17. package/dist/core/codex-lb/desktop-bridge/security.js +63 -1
  18. package/dist/core/codex-lb/desktop-bridge/server.js +5 -0
  19. package/dist/core/codex-lb/desktop-bridge/state.js +3 -0
  20. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +5 -1
  21. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +31 -5
  22. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +23 -4
  23. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-catalog.js +39 -0
  24. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +286 -0
  25. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-main-model.js +101 -0
  26. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-status.js +68 -0
  27. package/dist/core/codex-lb/desktop-controller-v3/status.js +8 -5
  28. package/dist/core/codex-lb/desktop-controller-v3.js +26 -2
  29. package/dist/core/codex-lb/desktop-service.js +13 -2
  30. package/dist/core/commands/naruto-command.js +1 -1
  31. package/dist/core/decisions/child-model-choice.js +107 -0
  32. package/dist/core/decisions/cli.js +19 -2
  33. package/dist/core/decisions/routing.js +5 -4
  34. package/dist/core/hooks-runtime/hook-context.js +21 -7
  35. package/dist/core/hooks-runtime/hook-io.js +3 -2
  36. package/dist/core/hooks-runtime/jev-spawn-routing.js +83 -5
  37. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -2
  38. package/dist/core/hooks-runtime/parent-orchestration-gate.js +18 -1
  39. package/dist/core/hooks-runtime/subagent-context.js +13 -2
  40. package/dist/core/hooks-runtime/subagent-spawn-policy.js +49 -6
  41. package/dist/core/hooks-runtime.js +12 -5
  42. package/dist/core/managed-assets/managed-assets-manifest.js +9 -4
  43. package/dist/core/subagents/child-model-allowlist.js +179 -0
  44. package/dist/core/subagents/child-model-plan.js +117 -0
  45. package/dist/core/subagents/naruto-host-credentials.js +82 -13
  46. package/dist/core/subagents/official-subagent-config.js +66 -15
  47. package/dist/core/subagents/official-subagent-preparation.js +45 -12
  48. package/dist/core/subagents/official-subagent-prompt.js +82 -11
  49. package/dist/core/subagents/official-subagent-runner.js +3 -0
  50. package/dist/core/subagents/read-only-list-role.js +73 -0
  51. package/dist/core/subagents/role-model-preferences.js +54 -17
  52. package/dist/core/version.js +1 -1
  53. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -0
  54. package/dist/native/sks-menubar/Sources/OpenRouterOnlyState.swift +314 -0
  55. package/dist/native/sks-menubar/Sources/ProcessClient.swift +2 -1
  56. package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +15 -3
  57. package/dist/native/sks-menubar/Sources/ProvidersOpenRouterOnly.swift +162 -0
  58. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +36 -18
  59. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +10 -4
  60. package/dist/native/sks-menubar/Sources/SidebarItem.swift +2 -0
  61. package/dist/native/sks-menubar/Sources/SubagentModelsModels.swift +201 -0
  62. package/dist/native/sks-menubar/Sources/SubagentModelsViewController.swift +373 -0
  63. package/package.json +1 -1
@@ -0,0 +1,286 @@
1
+ import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
2
+ import { maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
3
+ import { readText } from '../../fsx.js';
4
+ import { isOpenRouterModelId } from '../../imagegen/imagegen-config.js';
5
+ import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
6
+ import { assertDesktopBridgeStatusV3 } from '../bridge-runtime-validation.js';
7
+ import { readActiveCombinedBridgeCatalog } from '../combined-catalog.js';
8
+ import { MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
9
+ import { ensureUserReadOnlyListRole } from '../../subagents/read-only-list-role.js';
10
+ import { canonicalizeBridgeModelId } from '../route-index.js';
11
+ import { syncCatalogInternal } from './catalog.js';
12
+ import { hasOpenRouterRoute, openRouterOnlyStoreLocation } from './openrouter-only-catalog.js';
13
+ import { applyOpenRouterOnlyMainModel, openRouterOnlyMainModelTarget, restoreOpenRouterOnlyMainModel } from './openrouter-only-main-model.js';
14
+ import { openRouterOnlyProviderBlocker, openRouterOnlyStatusFromCore } from './openrouter-only-status.js';
15
+ import { commandResult, controllerEnv, controllerPaths, nowIso, stringArray } from './shared.js';
16
+ import { loadCore, statusFromCore } from './status.js';
17
+ async function finish(operation, ok, blockers, extra, options) {
18
+ const core = await loadCore(options);
19
+ const status = statusFromCore(core, options);
20
+ assertDesktopBridgeStatusV3(status);
21
+ const openRouterOnly = await openRouterOnlyStatusFromCore(core, options);
22
+ return commandResult(operation, ok, status, {
23
+ openrouter_only: openRouterOnly,
24
+ auth_priority: status.auth_priority,
25
+ ...extra
26
+ }, blockers, options);
27
+ }
28
+ function syncBlockers(sync) {
29
+ const activation = sync.activation && typeof sync.activation === 'object' && !Array.isArray(sync.activation)
30
+ ? sync.activation
31
+ : {};
32
+ const blockers = stringArray(activation.blockers);
33
+ return blockers.length > 0 ? blockers : ['combined_catalog_sync_failed'];
34
+ }
35
+ export const CODEX_RESTART_FAILED_BLOCKER = 'codex_restart_failed_manual_restart_required';
36
+ 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
+ function restartCodex(changed, noRestart, options) {
41
+ return maybeRestartRunningCodexApp({
42
+ env: controllerEnv(options),
43
+ changed,
44
+ noRestart,
45
+ ...(options.platform ? { platform: options.platform } : {}),
46
+ ...(options.codexAppRunningImpl ? { isRunningImpl: options.codexAppRunningImpl } : {}),
47
+ ...(options.codexAppRestartImpl ? { restartImpl: options.codexAppRestartImpl } : {})
48
+ });
49
+ }
50
+ export async function openRouterOnlyStatusCommand(options) {
51
+ return finish('openrouter-only.status', true, [], {}, options);
52
+ }
53
+ export async function setOpenRouterOnly(enabled, noRestart, options) {
54
+ if (enabled)
55
+ return enableOpenRouterOnly(noRestart, options);
56
+ const off = await turnOffOpenRouterOnly(options, { repairCatalog: true });
57
+ if (!off.ok)
58
+ return finish('openrouter-only.set', false, off.blockers, { catalog_sync: off.catalog_sync }, options);
59
+ const restart = await restartCodex(off.changed, noRestart, options);
60
+ return finish('openrouter-only.set', true, [...off.blockers, ...codexRestartBlockers(restart)], {
61
+ changed: off.changed,
62
+ catalog_sync: off.catalog_sync,
63
+ main_model: off.main_model,
64
+ codex_restart: restart
65
+ }, options);
66
+ }
67
+ async function seedSubagentModels(home, configText, options) {
68
+ const selected = (await readBridgeModelSelectionState(home, nowIso(options))).selection.openrouter.public_ids;
69
+ const configured = readTopLevelTomlString(configText, 'model');
70
+ const candidates = (selected.length > 0 ? selected : [configured]).filter(isOpenRouterModelId);
71
+ return normalizeSubagentModelList(candidates.slice(0, MAX_SUBAGENT_MODELS).map((model, index) => ({
72
+ model,
73
+ criteria: '',
74
+ reasoning_effort: null,
75
+ default: index === 0
76
+ }))).entries;
77
+ }
78
+ async function enableOpenRouterOnly(noRestart, options) {
79
+ const core = await loadCore(options);
80
+ const location = openRouterOnlyStoreLocation(core.paths);
81
+ const before = readOpenRouterOnlyStateSync(location);
82
+ const providerBlocker = openRouterOnlyProviderBlocker(core);
83
+ if (providerBlocker)
84
+ return finish('openrouter-only.set', false, [providerBlocker], {}, options);
85
+ const entries = before.subagent_models.length > 0
86
+ ? before.subagent_models
87
+ : await seedSubagentModels(core.paths.home, core.config, options);
88
+ if (entries.length === 0) {
89
+ return finish('openrouter-only.set', false, ['openrouter_only_no_openrouter_models'], {}, options);
90
+ }
91
+ const sync = await syncCatalogInternal({
92
+ ...options,
93
+ settings: { ...(options.settings || {}), auth_priority_enabled: false }
94
+ }, {
95
+ openRouterOnly: { ...before, enabled: true, subagent_models: entries },
96
+ onActivated: async () => {
97
+ await requireRoutableEntry(core.paths, { ...before, enabled: true, subagent_models: entries });
98
+ await writeOpenRouterOnlyState({ enabled: true, subagent_models: entries }, location);
99
+ }
100
+ });
101
+ if (sync.ok !== true)
102
+ return finish('openrouter-only.set', false, syncBlockers(sync), { catalog_sync: sync }, options);
103
+ const main = await switchMainModelToList(core.paths, location, options)
104
+ .catch(() => failedMainModel('openrouter_only_main_model_switch_failed'));
105
+ const readOnlyRole = await ensureUserReadOnlyListRole(core.paths.home).catch(() => 'failed');
106
+ const roleBlockers = readOnlyRole === 'failed' ? ['openrouter_only_read_only_role_install_failed'] : [];
107
+ const changed = !before.enabled || main.changed || readOnlyRole === 'created' || readOnlyRole === 'updated';
108
+ const restart = await restartCodex(changed, noRestart, options);
109
+ return finish('openrouter-only.set', true, [...main.blockers, ...roleBlockers, ...codexRestartBlockers(restart)], {
110
+ changed,
111
+ seeded_subagent_models: before.subagent_models.length === 0,
112
+ catalog_sync: sync,
113
+ main_model: main,
114
+ read_only_role: readOnlyRole,
115
+ codex_restart: restart
116
+ }, options);
117
+ }
118
+ async function switchMainModelToList(paths, location, options) {
119
+ const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
120
+ const state = readOpenRouterOnlyStateSync(location);
121
+ const main = await applyOpenRouterOnlyMainModel(paths, state, active.ok ? active.route_index : null, options);
122
+ if (main.restore !== state.restore)
123
+ await writeOpenRouterOnlyState({ restore: main.restore }, location);
124
+ return main.change;
125
+ }
126
+ function failedMainModel(blocker) {
127
+ return { changed: false, action: 'blocked', previous_model: null, model: null, write_status: null, blockers: [blocker] };
128
+ }
129
+ async function requireRoutableEntry(paths, state) {
130
+ const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
131
+ if (!openRouterOnlyMainModelTarget(state, active.ok ? active.route_index : null)) {
132
+ throw new Error('openrouter_only_no_routable_subagent_model');
133
+ }
134
+ }
135
+ async function exposedModelIds(paths) {
136
+ return new Set(((await activeCatalog(paths))?.catalog.models ?? []).map((model) => String(model.public_id)));
137
+ }
138
+ async function activeCatalog(paths) {
139
+ const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
140
+ return active.ok ? active : null;
141
+ }
142
+ async function keepModelInPickerSelection(home, model, options) {
143
+ if (!isOpenRouterModelId(model))
144
+ return { changed: false, blockers: [] };
145
+ const { selection } = await readBridgeModelSelectionState(home, nowIso(options));
146
+ const ids = selection.openrouter.public_ids;
147
+ const key = canonicalizeBridgeModelId(model);
148
+ if (ids.some((id) => canonicalizeBridgeModelId(id) === key))
149
+ return { changed: false, blockers: [] };
150
+ if (ids.length >= MAX_SELECTED_OPENROUTER_MODELS)
151
+ return { changed: false, blockers: ['openrouter_only_main_model_unexposed'] };
152
+ await writeBridgeModelSelection(home, {
153
+ ...selection,
154
+ updated_at: nowIso(options),
155
+ openrouter: { mode: 'selected', public_ids: [...ids, model] }
156
+ });
157
+ return { changed: true, blockers: [] };
158
+ }
159
+ async function codexLbRowsMissing(options) {
160
+ const core = await loadCore(options);
161
+ const profile = core.registry.profiles['codex-lb'];
162
+ return profile.enabled && profile.state === 'ready' && core.activeCatalog.ok
163
+ && !core.activeCatalog.catalog.models.some((model) => model.provider_id === 'codex-lb');
164
+ }
165
+ async function restoreMainModel(paths, location, before, options) {
166
+ const main = await restoreOpenRouterOnlyMainModel(paths, before.restore, options);
167
+ await writeOpenRouterOnlyState({ restore: main.blockers.length > 0 ? before.restore : null }, location);
168
+ return main;
169
+ }
170
+ export async function turnOffOpenRouterOnly(options, behavior = {}) {
171
+ const paths = controllerPaths(options);
172
+ const location = openRouterOnlyStoreLocation(paths);
173
+ const before = readOpenRouterOnlyStateSync(location);
174
+ const repair = !before.enabled && behavior.repairCatalog === true && await codexLbRowsMissing(options);
175
+ if (!before.enabled && !before.restore && !repair) {
176
+ return { ok: true, changed: false, catalog_sync: null, main_model: null, blockers: [] };
177
+ }
178
+ const off = { ...before, enabled: false };
179
+ const sync = (commit) => syncCatalogInternal(options, {
180
+ ...(behavior.restartService === undefined ? {} : { restartService: behavior.restartService }),
181
+ openRouterOnly: off,
182
+ ...(commit ? { onActivated: () => writeOpenRouterOnlyState({ enabled: false }, location) } : {})
183
+ });
184
+ const configured = readTopLevelTomlString(String(await readText(paths.configPath, '')), 'model');
185
+ const restoreApplies = Boolean(before.restore?.applied_model) && configured === before.restore?.applied_model;
186
+ const kept = restoreApplies ? { changed: false, blockers: [] } : await keepModelInPickerSelection(paths.home, configured, options);
187
+ let catalogSync = null;
188
+ if (before.enabled || repair || kept.changed) {
189
+ catalogSync = await sync(true);
190
+ if (catalogSync.ok !== true) {
191
+ return { ok: false, changed: false, catalog_sync: catalogSync, main_model: null, blockers: syncBlockers(catalogSync) };
192
+ }
193
+ }
194
+ const main = await restoreMainModel(paths, location, before, options);
195
+ const blockers = [...kept.blockers, ...main.blockers];
196
+ if (main.action === 'blocked' && !hasOpenRouterRoute((await activeCatalog(paths))?.route_index ?? null, main.model)) {
197
+ const keptAfter = await keepModelInPickerSelection(paths.home, main.model, options);
198
+ blockers.push(...keptAfter.blockers);
199
+ if (keptAfter.changed) {
200
+ catalogSync = await sync(false);
201
+ if (catalogSync.ok !== true)
202
+ blockers.push(...syncBlockers(catalogSync));
203
+ }
204
+ }
205
+ return {
206
+ ok: true,
207
+ changed: before.enabled || repair || kept.changed || main.changed,
208
+ catalog_sync: catalogSync,
209
+ main_model: main,
210
+ blockers
211
+ };
212
+ }
213
+ export async function releaseOpenRouterOnlyWithoutBridge(options) {
214
+ const paths = controllerPaths(options);
215
+ const location = openRouterOnlyStoreLocation(paths);
216
+ const before = readOpenRouterOnlyStateSync(location);
217
+ if (!before.enabled && !before.restore)
218
+ return null;
219
+ if (before.enabled)
220
+ await writeOpenRouterOnlyState({ enabled: false }, location);
221
+ const main = await restoreMainModel(paths, location, before, options);
222
+ return { ok: true, changed: before.enabled || main.changed, catalog_sync: null, main_model: main, blockers: main.blockers };
223
+ }
224
+ export function releaseOpenRouterOnlyForAuthPriority(options) {
225
+ return turnOffOpenRouterOnly(options, { restartService: false });
226
+ }
227
+ export function restartCodexAfterOpenRouterOnlyOff(off, options) {
228
+ return restartCodex(off.changed, false, options);
229
+ }
230
+ async function availableOpenRouterModels(home) {
231
+ try {
232
+ const parsed = JSON.parse(await readText(bridgeAvailableModelsPath(home), '') || 'null');
233
+ const rows = Array.isArray(parsed?.openrouter) ? parsed.openrouter : [];
234
+ return rows.map((row) => ({
235
+ public_id: String(row?.public_id || ''),
236
+ display_name: String(row?.display_name || row?.public_id || '')
237
+ })).filter((row) => row.public_id);
238
+ }
239
+ catch {
240
+ return [];
241
+ }
242
+ }
243
+ export async function listSubagentModels(options) {
244
+ const available = await availableOpenRouterModels(controllerPaths(options).home);
245
+ return finish('subagent-models.list', true, [], { available }, options);
246
+ }
247
+ export async function setSubagentModels(raw, noRestart, options) {
248
+ if (!Array.isArray(raw))
249
+ return finish('subagent-models.set', false, ['subagent_models_payload_invalid'], {}, options);
250
+ const { entries, issues } = normalizeSubagentModelList(raw);
251
+ if (issues.length > 0) {
252
+ return finish('subagent-models.set', false, issues.map((issue) => `${issue.code}:${issue.index}`), {}, options);
253
+ }
254
+ const paths = controllerPaths(options);
255
+ const location = openRouterOnlyStoreLocation(paths);
256
+ const before = readOpenRouterOnlyStateSync(location);
257
+ if (!before.enabled) {
258
+ await writeOpenRouterOnlyState({ subagent_models: entries }, location);
259
+ return finish('subagent-models.set', true, [], { catalog_sync: null }, options);
260
+ }
261
+ const exposedBefore = await exposedModelIds(paths);
262
+ const sync = await syncCatalogInternal(options, {
263
+ openRouterOnly: { ...before, subagent_models: entries },
264
+ onActivated: async () => {
265
+ if (entries.length > 0)
266
+ await requireRoutableEntry(paths, { ...before, subagent_models: entries });
267
+ await writeOpenRouterOnlyState({ subagent_models: entries }, location);
268
+ }
269
+ });
270
+ if (sync.ok !== true)
271
+ return finish('subagent-models.set', false, syncBlockers(sync), { catalog_sync: sync }, options);
272
+ const main = await switchMainModelToList(paths, location, options)
273
+ .catch(() => failedMainModel('openrouter_only_main_model_switch_failed'));
274
+ const exposedAfter = await exposedModelIds(paths);
275
+ const catalogChanged = [...exposedAfter].some((id) => !exposedBefore.has(id));
276
+ const changed = catalogChanged || main.changed;
277
+ const restart = await restartCodex(changed, noRestart, options);
278
+ const relaunchPending = changed && !restart.attempted && restart.reason !== 'codex_not_running';
279
+ return finish('subagent-models.set', true, [...main.blockers, ...codexRestartBlockers(restart)], {
280
+ changed,
281
+ catalog_sync: sync,
282
+ main_model: main,
283
+ codex_restart: restart,
284
+ warnings: relaunchPending ? [CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING] : []
285
+ }, options);
286
+ }
@@ -0,0 +1,101 @@
1
+ import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
2
+ import { removeTopLevelTomlKeyIfValue, safeWriteCodexConfigToml, upsertTopLevelTomlString } from '../../codex-runtime/codex-desktop-config-policy.js';
3
+ import { exists, readText } from '../../fsx.js';
4
+ import { defaultSubagentEntry } from '../../subagents/child-model-allowlist.js';
5
+ import { canonicalizeBridgeModelId } from '../route-index.js';
6
+ import { hasOpenRouterRoute } from './openrouter-only-catalog.js';
7
+ const MAIN_MODEL_WRITE_CAUSE = 'openrouter-only-main-model';
8
+ function tomlBasicStringBody(value) {
9
+ return value.replace(/[\\"\u0000-\u001f\u007f]/g, (character) => character === '\\'
10
+ ? '\\\\'
11
+ : character === '"' ? '\\"' : `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
12
+ }
13
+ export function openRouterOnlyMainModelTarget(state, routeIndex) {
14
+ const preferred = defaultSubagentEntry(state);
15
+ const ordered = [...(preferred ? [preferred] : []), ...state.subagent_models.filter((entry) => entry !== preferred)];
16
+ for (const entry of ordered) {
17
+ if (hasOpenRouterRoute(routeIndex, entry.model))
18
+ return canonicalizeBridgeModelId(entry.model);
19
+ }
20
+ return null;
21
+ }
22
+ async function writeModelLine(paths, before, next, expected, options) {
23
+ const write = await (options.safeWriteConfigImpl || safeWriteCodexConfigToml)(paths.configPath, before, next, MAIN_MODEL_WRITE_CAUSE, { verifyUnchangedBeforeWrite: true, expectedBeforeExists: await exists(paths.configPath) });
24
+ if (!write.ok)
25
+ return { ok: false, status: write.status, blockers: [`openrouter_only_main_model_write_${write.status}`] };
26
+ const after = write.expected_after?.text ?? next;
27
+ if (readTopLevelTomlString(after, 'model') !== expected) {
28
+ return { ok: false, status: write.status, blockers: ['openrouter_only_main_model_write_not_applied'] };
29
+ }
30
+ return { ok: true, status: write.status, blockers: [] };
31
+ }
32
+ function topLevelModelLinePresent(text) {
33
+ const lines = text.split('\n');
34
+ const firstTable = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
35
+ return lines.slice(0, firstTable === -1 ? lines.length : firstTable).some((line) => /^\s*model\s*=/.test(line));
36
+ }
37
+ export async function applyOpenRouterOnlyMainModel(paths, state, routeIndex, options) {
38
+ const before = String(await readText(paths.configPath, ''));
39
+ const current = readTopLevelTomlString(before, 'model');
40
+ if (current === null && topLevelModelLinePresent(before)) {
41
+ return {
42
+ change: {
43
+ changed: false, action: 'blocked', previous_model: null, model: null, write_status: null,
44
+ blockers: ['openrouter_only_main_model_unparsed']
45
+ },
46
+ restore: state.restore
47
+ };
48
+ }
49
+ if (hasOpenRouterRoute(routeIndex, current)) {
50
+ return {
51
+ change: { changed: false, action: 'kept_openrouter', previous_model: current, model: current, write_status: null, blockers: [] },
52
+ restore: state.restore
53
+ };
54
+ }
55
+ const target = openRouterOnlyMainModelTarget(state, routeIndex);
56
+ if (!target) {
57
+ return {
58
+ change: {
59
+ changed: false, action: 'blocked', previous_model: current, model: current, write_status: null,
60
+ blockers: ['openrouter_only_main_model_not_openrouter']
61
+ },
62
+ restore: state.restore
63
+ };
64
+ }
65
+ const written = await writeModelLine(paths, before, upsertTopLevelTomlString(before, 'model', tomlBasicStringBody(target)), target, options);
66
+ if (!written.ok) {
67
+ return {
68
+ change: { changed: false, action: 'blocked', previous_model: current, model: current, write_status: written.status, blockers: written.blockers },
69
+ restore: state.restore
70
+ };
71
+ }
72
+ return {
73
+ change: { changed: true, action: 'applied', previous_model: current, model: target, write_status: written.status, blockers: [] },
74
+ restore: { previous_model: current, applied_model: target }
75
+ };
76
+ }
77
+ export async function restoreOpenRouterOnlyMainModel(paths, restore, options) {
78
+ const before = String(await readText(paths.configPath, ''));
79
+ const current = readTopLevelTomlString(before, 'model');
80
+ const unchanged = (action) => ({ changed: false, action, previous_model: current, model: current, write_status: null, blockers: [] });
81
+ if (!restore?.applied_model)
82
+ return unchanged('no_record');
83
+ if (current !== restore.applied_model)
84
+ return unchanged('user_changed');
85
+ const previous = restore.previous_model;
86
+ const next = previous === null
87
+ ? removeTopLevelTomlKeyIfValue(before, 'model', restore.applied_model)
88
+ : upsertTopLevelTomlString(before, 'model', tomlBasicStringBody(previous));
89
+ const written = await writeModelLine(paths, before, next, previous, options);
90
+ if (!written.ok) {
91
+ return { changed: false, action: 'blocked', previous_model: current, model: current, write_status: written.status, blockers: written.blockers };
92
+ }
93
+ return {
94
+ changed: true,
95
+ action: previous === null ? 'removed' : 'restored',
96
+ previous_model: current,
97
+ model: previous,
98
+ write_status: written.status,
99
+ blockers: []
100
+ };
101
+ }
@@ -0,0 +1,68 @@
1
+ import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
2
+ import { jevEnabled, readDecisionConfig } from '../../decisions/config.js';
3
+ import { readImagegenConfig } from '../../imagegen/imagegen-config.js';
4
+ import { defaultSubagentEntry } from '../../subagents/child-model-allowlist.js';
5
+ import { hasOpenRouterRoute, readControllerOpenRouterOnlyState } from './openrouter-only-catalog.js';
6
+ import { controllerEnv } from './shared.js';
7
+ import { loadCore } from './status.js';
8
+ export const OPENROUTER_ONLY_UNAVAILABLE_ERRORS = [
9
+ 'openrouter_only_provider_disabled',
10
+ 'openrouter_only_credential_missing',
11
+ 'openrouter_only_subagent_list_empty',
12
+ 'openrouter_only_main_model_not_openrouter',
13
+ 'desktop_bridge_not_running'
14
+ ];
15
+ export const OPENROUTER_ONLY_CODEX_IMAGE_MODE_WARNING = 'openrouter_only_codex_image_mode_unavailable';
16
+ export function openRouterOnlyProviderBlocker(core) {
17
+ const profile = core.registry.profiles.openrouter;
18
+ if (!profile.enabled)
19
+ return 'openrouter_only_provider_disabled';
20
+ if (profile.state !== 'ready')
21
+ return 'openrouter_only_credential_missing';
22
+ return null;
23
+ }
24
+ function activeRouteIndex(core) {
25
+ return core.activeCatalog.ok ? core.activeCatalog.route_index : null;
26
+ }
27
+ export function openRouterOnlyError(core, state) {
28
+ return openRouterOnlyProviderBlocker(core)
29
+ ?? (state.subagent_models.length === 0 ? 'openrouter_only_subagent_list_empty' : null)
30
+ ?? (hasOpenRouterRoute(activeRouteIndex(core), readTopLevelTomlString(core.config, 'model'))
31
+ ? null : 'openrouter_only_main_model_not_openrouter')
32
+ ?? (core.service.running ? null : 'desktop_bridge_not_running');
33
+ }
34
+ export async function openRouterOnlyStatusFromCore(core, options, state = readControllerOpenRouterOnlyState(core.paths)) {
35
+ const routeIndex = activeRouteIndex(core);
36
+ const [jev, imagegen] = await Promise.all([
37
+ readDecisionConfig(controllerEnv(options)).then(jevEnabled).catch(() => false),
38
+ readImagegenConfig({ HOME: core.paths.home }).catch(() => null)
39
+ ]);
40
+ const subagentModels = state.subagent_models.map((entry) => ({
41
+ model: entry.model,
42
+ criteria: entry.criteria,
43
+ reasoning_effort: entry.reasoning_effort,
44
+ default: entry.default,
45
+ routable: hasOpenRouterRoute(routeIndex, entry.model)
46
+ }));
47
+ const error = state.enabled ? openRouterOnlyError(core, state) : null;
48
+ const warnings = state.enabled
49
+ ? [
50
+ ...(imagegen?.mode === 'openrouter' ? [] : [OPENROUTER_ONLY_CODEX_IMAGE_MODE_WARNING]),
51
+ ...subagentModels.filter((entry) => !entry.routable)
52
+ .map((entry) => `openrouter_only_subagent_model_unroutable:${entry.model}`)
53
+ ]
54
+ : [];
55
+ return {
56
+ enabled: state.enabled,
57
+ state: !state.enabled ? 'off' : error ? 'unavailable' : 'active',
58
+ error,
59
+ main_model: readTopLevelTomlString(core.config, 'model'),
60
+ default_subagent_model: defaultSubagentEntry(state)?.model ?? null,
61
+ subagent_models: subagentModels,
62
+ jev_enabled: jev,
63
+ warnings
64
+ };
65
+ }
66
+ export async function openRouterOnlyStatus(options) {
67
+ return openRouterOnlyStatusFromCore(await loadCore(options), options);
68
+ }
@@ -8,6 +8,7 @@ import { readBridgeRoutingPolicy, validateBridgeRoutingPolicy } from '../provide
8
8
  import { sha256Stable } from '../route-index.js';
9
9
  import { assertDesktopBridgeStatusV3 } from '../bridge-runtime-validation.js';
10
10
  import { desktopBridgeReportReadinessV3, readLastDiagnostic } from './diagnostics.js';
11
+ import { readControllerOpenRouterOnlyState } from './openrouter-only-catalog.js';
11
12
  import { activeProviderIds, controllerPaths, emptyScope, makeId, providerCode, providerRegistrySnapshot, recoveryActions, resolveValidatedCredentials, serviceLoopbackOrigin, unique } from './shared.js';
12
13
  export async function desktopBridgeStatusV3(options = {}) {
13
14
  const core = await loadCore(options);
@@ -39,10 +40,11 @@ export async function loadCore(options) {
39
40
  ...(snapshot ? { providerRegistry: snapshot } : {}),
40
41
  ...(policy ? { routePolicy: policy } : {})
41
42
  });
42
- const catalogSync = desktopBridgeCatalogStatusV3(activeCatalog, registry, policy, policyBlockers, checkedAt);
43
+ const openRouterOnlyEnabled = readControllerOpenRouterOnlyState(paths).enabled;
44
+ const catalogSync = desktopBridgeCatalogStatusV3(activeCatalog, registry, policy, policyBlockers, checkedAt, openRouterOnlyEnabled ? ['codex-lb'] : []);
43
45
  const diagnostic = await readLastDiagnostic(paths.diagnosticPath, catalogSync.generation, service.state?.process_generation || null, service.state?.last_verified_probe_ids || []);
44
46
  const settings = await readDesktopBridgeServiceSettings(options.settingsPath || desktopBridgeServicePaths(paths.home).settings_path).catch(() => null);
45
- return { authPriorityEnabled: settings?.auth_priority_enabled === true, paths, checkedAt, config, credentials, registry, activeCatalog, policy, policyBlockers, service, auth, catalogSync, diagnostic };
47
+ return { authPriorityEnabled: settings?.auth_priority_enabled === true, openRouterOnlyEnabled, paths, checkedAt, config, credentials, registry, activeCatalog, policy, policyBlockers, service, auth, catalogSync, diagnostic };
46
48
  }
47
49
  export function scopeCatalogBlockersToActiveProviders(combinedBlockers, activeProviderProblems, inactiveProviderProblems) {
48
50
  return combinedBlockers.filter((blocker) => activeProviderProblems.has(blocker) || !inactiveProviderProblems.has(blocker));
@@ -185,7 +187,8 @@ function providerProfileStatus(core, providerId) {
185
187
  capabilities: core.diagnostic?.report.providers[providerId] || emptyScope(`provider:${providerId}`, core.checkedAt)
186
188
  };
187
189
  }
188
- export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlockers, checkedAt) {
190
+ export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlockers, checkedAt, excludedProviders = []) {
191
+ const catalogEnabled = (providerId) => registry.profiles[providerId].enabled && !excludedProviders.includes(providerId);
189
192
  const providerRows = Object.fromEntries(['codex-lb', 'openrouter'].map((providerId) => {
190
193
  const profile = registry.profiles[providerId];
191
194
  const models = active.ok ? active.catalog.models.filter((model) => model.provider_id === providerId) : [];
@@ -194,7 +197,7 @@ export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlo
194
197
  const expired = Boolean(persisted?.expires_at && Date.parse(persisted.expires_at) <= Date.parse(checkedAt));
195
198
  const state = !active.ok
196
199
  ? 'not_started'
197
- : !profile.enabled ? 'not_started'
200
+ : !catalogEnabled(providerId) ? 'not_started'
198
201
  : expired ? 'stale'
199
202
  : indexed?.state === 'ready' && models.length > 0
200
203
  && persisted?.state === 'verified' && persisted.generation === indexed.catalog_generation
@@ -220,7 +223,7 @@ export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlo
220
223
  };
221
224
  return [providerId, row];
222
225
  }));
223
- const enabled = ['codex-lb', 'openrouter'].filter((id) => registry.profiles[id].enabled);
226
+ const enabled = ['codex-lb', 'openrouter'].filter(catalogEnabled);
224
227
  const verified = enabled.filter((id) => providerRows[id].state === 'verified');
225
228
  const stale = enabled.filter((id) => providerRows[id].state === 'stale');
226
229
  const conflicts = active.ok ? active.route_index.conflicts.length : 0;
@@ -1,6 +1,8 @@
1
1
  import { listSelectableModels, selectExposedModels, syncCatalog } from './desktop-controller-v3/catalog.js';
2
2
  import { explainRoute, ensureDesktopBridge, repairDesktopBridge, rollbackDesktopBridge, setDefaultProvider, setOfficialModelsMode, setAuthPriority, unmanageDesktopBridge } from './desktop-controller-v3/lifecycle-commands.js';
3
3
  import { configureProvider, removeCredential, setProviderState, validateProvider } from './desktop-controller-v3/provider-commands.js';
4
+ import { listSubagentModels, openRouterOnlyStatusCommand, setOpenRouterOnly, setSubagentModels } from './desktop-controller-v3/openrouter-only-commands.js';
5
+ import { openRouterOnlyStatus } from './desktop-controller-v3/openrouter-only-status.js';
4
6
  import { commandResult, safeCode } from './desktop-controller-v3/shared.js';
5
7
  import { desktopBridgeStatusV3 } from './desktop-controller-v3/status.js';
6
8
  import { verifyDesktopBridgeV3 } from './desktop-controller-v3/verification.js';
@@ -8,6 +10,7 @@ import { withFileLock } from '../locks/file-lock.js';
8
10
  import path from 'node:path';
9
11
  export { desktopBridgeDiagnosticBindingCurrentV3, desktopBridgeReportReadinessV3 } from './desktop-controller-v3/diagnostics.js';
10
12
  export { runDesktopBridgeDeepProviderProbesV3 } from './desktop-controller-v3/live-probes.js';
13
+ export { openRouterOnlyStatus } from './desktop-controller-v3/openrouter-only-status.js';
11
14
  export { desktopBridgeCatalogStatusV3, desktopBridgeStatusV3 } from './desktop-controller-v3/status.js';
12
15
  export { verifyDesktopBridgeV3 } from './desktop-controller-v3/verification.js';
13
16
  export async function executeDesktopBridgeCommandV3(request, options = {}) {
@@ -19,6 +22,10 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
19
22
  }
20
23
  if (request.operation === 'verify')
21
24
  return verifyDesktopBridgeV3(request.level, options);
25
+ if (request.operation === 'openrouter-only.status')
26
+ return openRouterOnlyStatusCommand(options);
27
+ if (request.operation === 'subagent-models.list')
28
+ return listSubagentModels(options);
22
29
  try {
23
30
  const home = path.resolve(options.home || process.env.HOME || '.');
24
31
  return await withFileLock({
@@ -62,6 +69,12 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
62
69
  return setDefaultProvider(request.provider_id, options);
63
70
  if (request.operation === 'auth-priority.set')
64
71
  return setAuthPriority(request.enabled, options);
72
+ if (request.operation === 'openrouter-only.set') {
73
+ return setOpenRouterOnly(request.enabled, request.no_restart === true, options);
74
+ }
75
+ if (request.operation === 'subagent-models.set') {
76
+ return setSubagentModels(request.subagent_models, request.no_restart === true, options);
77
+ }
65
78
  if (request.operation === 'route.official-models')
66
79
  return setOfficialModelsMode(request.mode, options);
67
80
  if (request.operation === 'route.explain')
@@ -74,7 +87,18 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
74
87
  catch (error) {
75
88
  const blocker = safeCode(error, 'desktop_bridge_command_failed');
76
89
  const status = await desktopBridgeStatusV3(options).catch(() => null);
77
- return commandResult(request.operation, false, status, request.operation === 'auth-priority.set' && status?.auth_priority
78
- ? { auth_priority: status.auth_priority } : {}, [blocker], options);
90
+ return commandResult(request.operation, false, status, await failureModeEcho(request.operation, status, options), [blocker], options);
79
91
  }
80
92
  }
93
+ async function failureModeEcho(operation, status, options) {
94
+ const modeOperation = operation === 'auth-priority.set'
95
+ || operation.startsWith('openrouter-only.')
96
+ || operation.startsWith('subagent-models.');
97
+ if (!modeOperation)
98
+ return {};
99
+ const openRouterOnly = await openRouterOnlyStatus(options).catch(() => null);
100
+ return {
101
+ ...(status?.auth_priority ? { auth_priority: status.auth_priority } : {}),
102
+ ...(openRouterOnly ? { openrouter_only: openRouterOnly } : {})
103
+ };
104
+ }
@@ -14,6 +14,7 @@ import { captureCodexAuthSnapshot } from './desktop-auth-invariant.js';
14
14
  import { applyOfficialModelPassthrough, bridgeRoutePolicyPath, writeBridgeRoutingPolicy } from './provider-route-policy.js';
15
15
  import { desktopBridgeRuntimeVersion, desktopBridgeRuntimeVersionStale } from './desktop-bridge/state.js';
16
16
  import { PACKAGE_VERSION } from '../version.js';
17
+ import { canonicalChildModelId, readOpenRouterOnlyStateSync } from '../subagents/child-model-allowlist.js';
17
18
  import { BRIDGE_OFFICIAL_ROUTE_ID } from './bridge-contracts.js';
18
19
  import { DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, DESKTOP_BRIDGE_LAUNCHD_LABEL, desktopBridgeConfigGeneration, desktopBridgeLaunchdPlistPath, desktopBridgeProcessExists, desktopBridgeStatePath, getDesktopBridgeStatus, preflightDesktopBridge, readDesktopBridgeState, safeBridgeErrorCode, writeDesktopBridgeLaunchdPlist, selectAvailableDesktopBridgePort, startPreparedDesktopBridge, DESKTOP_BRIDGE_STATE_SCHEMA, DESKTOP_BRIDGE_OFFICIAL_UPSTREAM_BASE_URL, DesktopBridgeError, } from './desktop-bridge/index.js';
19
20
  export const DEFAULT_DESKTOP_BRIDGE_HOST = '127.0.0.1';
@@ -230,6 +231,15 @@ async function runtimeCredentials(settings, options) {
230
231
  });
231
232
  return { registry, resolver, sources: Object.fromEntries([...adopted].map(([id, value]) => [id, value.source])), loaded };
232
233
  }
234
+ export function resolveDesktopBridgeOpenRouterOnly(input) {
235
+ const state = readOpenRouterOnlyStateSync({ home: input.home, ...(input.env ? { env: input.env } : {}) });
236
+ if (!state.enabled)
237
+ return null;
238
+ const models = state.subagent_models
239
+ .map((entry) => canonicalChildModelId(entry.model))
240
+ .filter((model) => canonicalizeBridgeModelId(model) === model);
241
+ return { enabled: true, subagent_models: [...new Set(models)] };
242
+ }
233
243
  function normalizeEndpointForBinding(value) {
234
244
  try {
235
245
  return new URL(value).toString().replace(/\/+$/, '');
@@ -258,7 +268,8 @@ export async function resolveDesktopBridgeRuntimeConfig(options = {}) {
258
268
  throw new Error('desktop_bridge_provider_credentials_unavailable');
259
269
  const persistProviderSessionPins = options.persistProviderSessionPins
260
270
  || ((pins) => persistDesktopBridgeSessionPins(paths.settings_path, settings, pins));
261
- const config = { providerRegistry: settings.provider_registry, routePolicy: settings.route_policy, providerSessionPins: settings.provider_session_pins, ...(options.resolveRequestRoute ? { resolveRequestRoute: options.resolveRequestRoute } : {}), persistProviderSessionPins, resolveProviderCredential: credentials.resolver, clientCapabilitySha256: settings.client_capability_sha256, listenHost: settings.listen_host, listenPort: settings.listen_port, allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, allowedOrigins: settings.allowed_origins, connectTimeoutMs: settings.connect_timeout_ms, idleTimeoutMs: settings.idle_timeout_ms, officialPassthrough: settings.official_passthrough.enabled ? { baseUrl: settings.official_passthrough.base_url } : null };
271
+ const openRouterOnly = resolveDesktopBridgeOpenRouterOnly({ home, ...(options.env ? { env: options.env } : {}) });
272
+ const config = { providerRegistry: settings.provider_registry, routePolicy: settings.route_policy, providerSessionPins: settings.provider_session_pins, ...(options.resolveRequestRoute ? { resolveRequestRoute: options.resolveRequestRoute } : {}), persistProviderSessionPins, resolveProviderCredential: credentials.resolver, clientCapabilitySha256: settings.client_capability_sha256, listenHost: settings.listen_host, listenPort: settings.listen_port, allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, allowedOrigins: settings.allowed_origins, connectTimeoutMs: settings.connect_timeout_ms, idleTimeoutMs: settings.idle_timeout_ms, officialPassthrough: settings.official_passthrough.enabled ? { baseUrl: settings.official_passthrough.base_url } : null, ...(openRouterOnly ? { openRouterOnly } : {}) };
262
273
  const primary = credentials.sources['codex-lb'] || credentials.sources.openrouter;
263
274
  if (!primary)
264
275
  throw new Error('desktop_bridge_provider_credentials_unavailable');
@@ -512,7 +523,7 @@ export async function serveDesktopBridge(options = {}) {
512
523
  },
513
524
  },
514
525
  });
515
- process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, ...(deferredUpstreams.length ? { deferred_upstreams: deferredUpstreams } : {}), secret_fields_redacted: true })}\n`);
526
+ process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, ...(deferredUpstreams.length ? { deferred_upstreams: deferredUpstreams } : {}), ...(runtime.config.openRouterOnly ? { openrouter_only: { enabled: true, subagent_model_count: runtime.config.openRouterOnly.subagent_models.length } } : {}), secret_fields_redacted: true })}\n`);
516
527
  await waitForShutdown(handle);
517
528
  return { schema: 'sks.desktop-bridge-serve.v1', ok: true, status: 'stopped', state: handle.state };
518
529
  }
@@ -44,7 +44,7 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
44
44
  schema: 'sks.naruto-credential-policy.v1',
45
45
  ok: false,
46
46
  blockers: credentialPolicy.blockers,
47
- hint: 'sks naruto run --auth-mode=host --model-provider=<config.toml provider block> --provider-env-key=<ENV NAME>'
47
+ hint: credentialPolicy.hint ?? 'sks naruto run --auth-mode=host --model-provider=<config.toml provider block> --provider-env-key=<ENV NAME>'
48
48
  };
49
49
  if (args.includes('--json'))
50
50
  console.log(JSON.stringify(blocked, null, 2));