sneakoscope 10.3.6 → 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.
- package/README.md +12 -181
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +61 -1
- package/dist/commands/codex-app.js +4 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/native-worker-backend-router.js +55 -0
- package/dist/core/codex-app/codex-app-restart-policy.js +20 -0
- package/dist/core/codex-app/codex-context-window.js +61 -24
- package/dist/core/codex-app/menubar/constants.js +4 -0
- package/dist/core/codex-compat/codex-hook-output-builders.js +3 -1
- package/dist/core/codex-lb/bridge-cli-contract.js +2 -0
- package/dist/core/codex-lb/desktop-bridge/exclusive-provider-guard.js +59 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +23 -15
- package/dist/core/codex-lb/desktop-bridge/index.js +1 -0
- package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +10 -3
- package/dist/core/codex-lb/desktop-bridge/security.js +63 -1
- package/dist/core/codex-lb/desktop-bridge/server.js +5 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +5 -1
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +31 -5
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +23 -4
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-catalog.js +39 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +286 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-main-model.js +101 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-status.js +68 -0
- package/dist/core/codex-lb/desktop-controller-v3/status.js +8 -5
- package/dist/core/codex-lb/desktop-controller-v3.js +26 -2
- package/dist/core/codex-lb/desktop-service.js +13 -2
- package/dist/core/commands/naruto-command.js +1 -1
- package/dist/core/decisions/child-model-choice.js +107 -0
- package/dist/core/decisions/cli.js +19 -2
- package/dist/core/decisions/routing.js +5 -4
- package/dist/core/doctor/command-alias-cleanup.js +2 -0
- package/dist/core/doctor/skill-legacy-surface.js +46 -1
- package/dist/core/hooks-runtime/hook-context.js +21 -7
- package/dist/core/hooks-runtime/hook-io.js +3 -2
- package/dist/core/hooks-runtime/jev-spawn-routing.js +83 -5
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -2
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +18 -1
- package/dist/core/hooks-runtime/subagent-context.js +13 -2
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +49 -6
- package/dist/core/hooks-runtime.js +12 -5
- package/dist/core/managed-assets/managed-assets-manifest.js +9 -4
- package/dist/core/subagents/child-model-allowlist.js +179 -0
- package/dist/core/subagents/child-model-plan.js +117 -0
- package/dist/core/subagents/naruto-host-credentials.js +82 -13
- package/dist/core/subagents/official-subagent-config.js +66 -15
- package/dist/core/subagents/official-subagent-preparation.js +45 -12
- package/dist/core/subagents/official-subagent-prompt.js +82 -11
- package/dist/core/subagents/official-subagent-runner.js +3 -0
- package/dist/core/subagents/read-only-list-role.js +73 -0
- package/dist/core/subagents/role-model-preferences.js +54 -17
- package/dist/core/update/registry-availability.js +58 -0
- package/dist/core/update-check.js +8 -26
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -0
- package/dist/native/sks-menubar/Sources/OpenRouterOnlyState.swift +314 -0
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +2 -1
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +15 -3
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouterOnly.swift +162 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +36 -18
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +10 -4
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +2 -0
- package/dist/native/sks-menubar/Sources/SubagentModelsModels.swift +201 -0
- package/dist/native/sks-menubar/Sources/SubagentModelsViewController.swift +373 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
17
17
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
18
18
|
|
|
19
|
-
Current package: **SKS 10.3.
|
|
19
|
+
Current package: **SKS 10.3.8**. Install the latest stable release from npm.
|
|
20
20
|
|
|
21
21
|
[Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
|
|
22
22
|
|
|
@@ -101,7 +101,8 @@ home directory; set `SKS_POSTINSTALL_MENUBAR=1` to force the build in a script,
|
|
|
101
101
|
| Page | Use it to |
|
|
102
102
|
| --- | --- |
|
|
103
103
|
| Overview | Check local health and find the next action. |
|
|
104
|
-
| Connections | Connect Codex-LB or OpenRouter, choose
|
|
104
|
+
| Connections | Connect Codex-LB or OpenRouter, choose Prefer Codex-LB or OpenRouter Only, and manage exposed models. |
|
|
105
|
+
| Subagent Models | Keep the OpenRouter models subagents may use in OpenRouter Only Mode, with the criteria Jev routes by. |
|
|
105
106
|
| Updates | Check and update SKS and Codex CLI, with operation progress and recovery details. |
|
|
106
107
|
| MCP Servers | Manage server configuration, health checks, and authentication. |
|
|
107
108
|
| Image Generation | Keep Codex's default image generation, or route it through the Desktop Bridge to an OpenRouter image model you pick. |
|
|
@@ -123,184 +124,11 @@ connection first for eligible models. The switch distinguishes a saved preferenc
|
|
|
123
124
|
from connection readiness: **on but unavailable** means setup needs attention.
|
|
124
125
|
Turning it off restores the configured official-model routing preference.
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
sks bridge auth-priority off --json
|
|
132
|
-
sks bridge status --json
|
|
133
|
-
sks bridge route explain gpt-6-astra --json
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Enter credentials through the native connection dialog or the CLI's
|
|
137
|
-
`--api-key-stdin` option. See the [provider guide](docs/codex-lb.md) for
|
|
138
|
-
configuration, transport checks, and recovery commands.
|
|
139
|
-
|
|
140
|
-
## Image generation
|
|
141
|
-
|
|
142
|
-
SKS image routes (image generation, UX-Review callouts, PPT assets, slide
|
|
143
|
-
reviews) follow one image mode. By default they use Codex's own image
|
|
144
|
-
generation. Turn on a custom image model on the **Image Generation** page to
|
|
145
|
-
send every SKS image through the Desktop Bridge to an OpenRouter image model
|
|
146
|
-
you pick. SKS pins no image model and records which model made each image.
|
|
147
|
-
|
|
148
|
-
```sh
|
|
149
|
-
sks imagegen status --json
|
|
150
|
-
sks imagegen models --json
|
|
151
|
-
sks imagegen enable --model black-forest-labs/flux.2-klein-4b
|
|
152
|
-
sks imagegen generate --prompt "App icon, flat, blue" --out icon.png
|
|
153
|
-
sks imagegen disable
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
See [Image generation](docs/image-generation.md) for both modes, evidence
|
|
157
|
-
sidecars, and the Jev choices.
|
|
158
|
-
|
|
159
|
-
## Naruto workflow
|
|
160
|
-
|
|
161
|
-
The parent orchestrates: it decomposes the task, spawns a child for each
|
|
162
|
-
disjoint slice, waits, and owns integration and final verification. It does not
|
|
163
|
-
implement slices itself. The SKS PreToolUse hook denies parent source edits
|
|
164
|
-
until the first child starts and while children are still running. Children
|
|
165
|
-
receive bounded tasks and do not spawn children.
|
|
166
|
-
|
|
167
|
-
No model family is pinned. Every child runs the newest model of the tier its
|
|
168
|
-
work needs, read from the Codex models cache, so a new model family is used as
|
|
169
|
-
soon as Codex lists it:
|
|
170
|
-
|
|
171
|
-
| Work | Tier | Effort | Today |
|
|
172
|
-
| --- | --- | --- | --- |
|
|
173
|
-
| Tiny mechanical tasks | fast | low | `gpt-6-luna` |
|
|
174
|
-
| Instructed implementation | balanced | low | `gpt-6-sol` |
|
|
175
|
-
| Exploration, large-context reads, and direct tool operation | context | medium | `gpt-6-sol` |
|
|
176
|
-
| Planning, review, debugging, and focused judgment | deep | max | `gpt-6-astra` |
|
|
177
|
-
|
|
178
|
-
With Jev mode on, Jev picks the tier for every spawn, every gated parent edit,
|
|
179
|
-
the plan, and the context, so the parent spends no time on those choices. It
|
|
180
|
-
also picks the pipeline for each prompt; an explicit SKS `<div align="center">
|
|
181
|
-
|
|
182
|
-
<img src="docs/assets/sks-logo.svg" alt="Sneakoscope Codex logo" width="120" height="120" />
|
|
183
|
-
|
|
184
|
-
# Sneakoscope Codex
|
|
185
|
-
|
|
186
|
-
**Plan, build, and verify with Codex.**
|
|
187
|
-
|
|
188
|
-
[](https://www.npmjs.com/package/sneakoscope)
|
|
189
|
-
[](#requirements)
|
|
190
|
-
[](LICENSE)
|
|
191
|
-
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
<!-- BEGIN SKS SEARCH VISIBILITY MARKETING -->
|
|
195
|
-
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.
|
|
196
|
-
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
197
|
-
|
|
198
|
-
Current package: **SKS 10.3.6**. Install the latest stable release from npm.
|
|
199
|
-
|
|
200
|
-
[Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
|
|
201
|
-
|
|
202
|
-
## Install in one command
|
|
203
|
-
|
|
204
|
-
```sh
|
|
205
|
-
npm exec --yes --package=sneakoscope@latest -- sneakoscope install --yes
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
The installer resolves the latest release, installs it globally, runs setup and
|
|
209
|
-
Doctor, and checks that `sks` on your PATH points to the installed version.
|
|
210
|
-
|
|
211
|
-
To set up a project, run this from its root, then open it in Codex:
|
|
212
|
-
|
|
213
|
-
```sh
|
|
214
|
-
sks bootstrap --yes
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## What SKS adds
|
|
218
|
-
|
|
219
|
-
| Capability | What you get |
|
|
220
|
-
| --- | --- |
|
|
221
|
-
| Focused execution | Answers and tiny edits stay lightweight; implementation work runs through official Codex subagents while the parent orchestrates and integrates. |
|
|
222
|
-
| Project context | TriWiki indexes repository code and supplies bounded context that can be checked against source. |
|
|
223
|
-
| Verification | Tests, diagnostics, and release evidence support completion claims. Security and data-integrity checks stay in place. |
|
|
224
|
-
| Native controls | SKS Center brings connections, updates, MCP servers, and diagnostics together on macOS. |
|
|
225
|
-
| Consistent setup | `sks update` installs the latest release, runs Doctor, and reconciles SKS-managed files and legacy assets. |
|
|
226
|
-
|
|
227
|
-
The default `essential` profile avoids repetitive completion rituals. `strict`
|
|
228
|
-
adds stronger completion requirements. See [Essential Trust](docs/essential-trust.md).
|
|
229
|
-
|
|
230
|
-
With Codex-LB connected, `sks agent-bridge async --prompt "Check SKS status while
|
|
231
|
-
explaining what the check covers" --tools status --json` runs native Astra Async
|
|
232
|
-
tool calling with selected read-only SKS tools, using WebSocket first and safe
|
|
233
|
-
HTTP fallback. See [Astra guidance](docs/astra-guidance.md)
|
|
234
|
-
for setup boundaries and reported execution evidence.
|
|
235
|
-
|
|
236
|
-
## Everyday commands
|
|
237
|
-
|
|
238
|
-
SKS enables experimental Astra context management by default during setup and
|
|
239
|
-
repair. Turn it off in **SKS Center → Settings → Astra context management**, or
|
|
240
|
-
use `sks codex-app context-management off`. Updates preserve an explicit opt-out.
|
|
241
|
-
Start a new task after changing the setting. Availability depends on a supported
|
|
242
|
-
Codex client and eligible ChatGPT sign-in; API-key and custom-provider sessions
|
|
243
|
-
may not activate it. See [OpenAI's context management guidance](https://learn.chatgpt.com/docs/models#experimental-context-management).
|
|
244
|
-
|
|
245
|
-
Use these inside a Codex conversation:
|
|
246
|
-
|
|
247
|
-
| Command | Purpose |
|
|
248
|
-
| --- | --- |
|
|
249
|
-
| `$sks-plan "task"` | Create a plan without editing product code. |
|
|
250
|
-
| `$sks-work` | Execute the latest plan. |
|
|
251
|
-
| `$sks-naruto "task"` | Run an official subagent workflow. |
|
|
252
|
-
| `$sks-review` | Review the current changes. |
|
|
253
|
-
| `$sks-help` | Explore available SKS workflows. |
|
|
254
|
-
|
|
255
|
-
Use these in your terminal:
|
|
256
|
-
|
|
257
|
-
```sh
|
|
258
|
-
sks --help
|
|
259
|
-
sks status --json
|
|
260
|
-
sks review --staged
|
|
261
|
-
sks doctor --json
|
|
262
|
-
sks update-check
|
|
263
|
-
sks update
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
`sks update` runs setup reconciliation even when the package is already current.
|
|
267
|
-
It preserves user-authored configuration and cleans up recognized SKS-owned
|
|
268
|
-
legacy assets. If a check needs attention, follow its reported recovery action
|
|
269
|
-
in your terminal.
|
|
270
|
-
|
|
271
|
-
## SKS Center (macOS)
|
|
272
|
-
|
|
273
|
-
Open **SKS Control Center** from the SKS menu bar.
|
|
274
|
-
The Menu Bar app (with the Control Center) is built from source on macOS when you run
|
|
275
|
-
`npm i -g sneakoscope` interactively in a terminal, and by `sks install`, `sks doctor --fix`,
|
|
276
|
-
`sks update`, or `sks menubar install`. Dependency, CI, and piped installs never touch your
|
|
277
|
-
home directory; set `SKS_POSTINSTALL_MENUBAR=1` to force the build in a script, or
|
|
278
|
-
`SKS_POSTINSTALL_NO_MENUBAR=1` to skip it.
|
|
279
|
-
|
|
280
|
-
| Page | Use it to |
|
|
281
|
-
| --- | --- |
|
|
282
|
-
| Overview | Check local health and find the next action. |
|
|
283
|
-
| Connections | Connect Codex-LB or OpenRouter, choose authentication priority, and manage exposed models. |
|
|
284
|
-
| Updates | Check and update SKS and Codex CLI, with operation progress and recovery details. |
|
|
285
|
-
| MCP Servers | Manage server configuration, health checks, and authentication. |
|
|
286
|
-
| Image Generation | Keep Codex's default image generation, or route it through the Desktop Bridge to an OpenRouter image model you pick. |
|
|
287
|
-
| Diagnostics | Inspect issues and run targeted checks. |
|
|
288
|
-
| Settings | Configure lifecycle behavior, notifications, and advanced options. |
|
|
289
|
-
| Remote Coding | Find the independent [Paseo companion](https://paseo.sh/docs) and its setup guidance. |
|
|
290
|
-
|
|
291
|
-
Connection controls appear first. Model catalogs, bridge diagnostics, and
|
|
292
|
-
advanced settings expand when you need them.
|
|
293
|
-
|
|
294
|
-
## Desktop Bridge
|
|
295
|
-
|
|
296
|
-
One local bridge manages routing for independent **Codex-LB** and **OpenRouter**
|
|
297
|
-
profiles. ChatGPT sign-in remains owned by Codex. Provider choices and existing
|
|
298
|
-
session pins remain authoritative; unavailable routes are reported explicitly.
|
|
299
|
-
|
|
300
|
-
In **Connections**, turn on **Prefer Codex-LB** to use the saved Codex-LB
|
|
301
|
-
connection first for eligible models. The switch distinguishes a saved preference
|
|
302
|
-
from connection readiness: **on but unavailable** means setup needs attention.
|
|
303
|
-
Turning it off restores the configured official-model routing preference.
|
|
127
|
+
Turn on **OpenRouter Only** instead to run the main thread and every subagent on
|
|
128
|
+
OpenRouter models. The two switches are mutually exclusive: turning one on turns
|
|
129
|
+
the other off. Subagents may use only the models on the **Subagent Models** page,
|
|
130
|
+
and with Jev mode on, Jev picks one for each child from the criteria you wrote.
|
|
131
|
+
See [OpenRouter Only Mode](docs/openrouter-only-mode.md).
|
|
304
132
|
|
|
305
133
|
The same controls are available from the CLI:
|
|
306
134
|
|
|
@@ -308,6 +136,8 @@ The same controls are available from the CLI:
|
|
|
308
136
|
sks bridge auth-priority status --json
|
|
309
137
|
sks bridge auth-priority on --json
|
|
310
138
|
sks bridge auth-priority off --json
|
|
139
|
+
sks bridge openrouter-only on --json
|
|
140
|
+
sks bridge subagent-models list --json
|
|
311
141
|
sks bridge status --json
|
|
312
142
|
sks bridge route explain gpt-6-astra --json
|
|
313
143
|
```
|
|
@@ -356,7 +186,7 @@ soon as Codex lists it:
|
|
|
356
186
|
|
|
357
187
|
With Jev mode on, Jev picks the tier for every spawn, every gated parent edit,
|
|
358
188
|
the plan, and the context, so the parent spends no time on those choices. It
|
|
359
|
-
command always
|
|
189
|
+
also picks the pipeline for each prompt; an explicit SKS dollar command always
|
|
360
190
|
wins. A stored role-model preference on a current model wins in both modes.
|
|
361
191
|
|
|
362
192
|
An active Codex task keeps the user's selected main model, effort, and service
|
|
@@ -393,6 +223,7 @@ freshness semantics.
|
|
|
393
223
|
- [Astra guidance](docs/astra-guidance.md) — how SKS applies the official model recommendations.
|
|
394
224
|
- [Agent Bridge](docs/AGENT-BRIDGE.md) — integrate through the CLI or MCP interface.
|
|
395
225
|
- [Codex-LB priority](docs/codex-lb-priority.md) — how Codex App WebSockets follow the Center priority setting.
|
|
226
|
+
- [OpenRouter Only Mode](docs/openrouter-only-mode.md) — run everything on OpenRouter with a subagent model list Jev routes by.
|
|
396
227
|
- [Release readiness](docs/release-readiness.md) — build, verify, and publish a release.
|
|
397
228
|
- [Release evidence](docs/release-proof-truth.md) — what each verification result proves.
|
|
398
229
|
- [Changelog](CHANGELOG.md) — changes by version.
|
package/dist/commands/bridge.js
CHANGED
|
@@ -5,6 +5,7 @@ import { serveDesktopBridge } from '../core/codex-lb/desktop-service.js';
|
|
|
5
5
|
import { BridgeCliError, errorOutput, mergeMetadata, ordinaryOutput, sanitizeBridgeValue, textSummary, verificationOutput } from './bridge-command-output.js';
|
|
6
6
|
const CONTROLLER_FACADE_EXPORT = 'executeDesktopBridgeCommand';
|
|
7
7
|
const MAX_STDIN_SECRET_BYTES = 64 * 1024;
|
|
8
|
+
const MAX_STDIN_JSON_BYTES = 64 * 1024;
|
|
8
9
|
const DEFAULT_IO = {
|
|
9
10
|
readStdin,
|
|
10
11
|
stdout: (text) => process.stdout.write(text),
|
|
@@ -18,6 +19,9 @@ export function usage(command = 'bridge') {
|
|
|
18
19
|
`Usage: sks ${command} status [--json]`,
|
|
19
20
|
` sks ${command} ensure|repair [--json]`,
|
|
20
21
|
` sks ${command} auth-priority status|on|off [--json]`,
|
|
22
|
+
` sks ${command} openrouter-only status|on|off [--no-restart] [--json]`,
|
|
23
|
+
` sks ${command} subagent-models list [--json]`,
|
|
24
|
+
` sks ${command} subagent-models set --stdin [--no-restart] [--json]`,
|
|
21
25
|
` sks ${command} verify --level shallow|transport|deep [--strict|--require-ready] [--json]`,
|
|
22
26
|
` sks ${command} provider list [--json]`,
|
|
23
27
|
` sks ${command} provider configure codex-lb --host <host> --api-key-stdin [--json]`,
|
|
@@ -34,7 +38,9 @@ export function usage(command = 'bridge') {
|
|
|
34
38
|
` sks ${command} unmanage --confirm [--json]`,
|
|
35
39
|
` sks ${command} rollback <receipt-id> --confirm [--json]`,
|
|
36
40
|
'',
|
|
37
|
-
'Provider secrets are accepted only through --api-key-stdin. Readiness changes the exit code only with --strict or --require-ready.'
|
|
41
|
+
'Provider secrets are accepted only through --api-key-stdin. Readiness changes the exit code only with --strict or --require-ready.',
|
|
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.'
|
|
38
44
|
].join('\n');
|
|
39
45
|
}
|
|
40
46
|
export async function run(_command, args = []) {
|
|
@@ -120,6 +126,39 @@ async function parseInvocation(args, io) {
|
|
|
120
126
|
? { operation: 'auth-priority.status' }
|
|
121
127
|
: { operation: 'auth-priority.set', enabled: action === 'on' }, label: 'Codex-LB authentication priority' };
|
|
122
128
|
}
|
|
129
|
+
if (area === 'openrouter-only' && target === undefined && (action === 'status' || action === 'on' || action === 'off')) {
|
|
130
|
+
if (action === 'status') {
|
|
131
|
+
allowOnly(parsed, ['--json'], []);
|
|
132
|
+
return { ...base, request: { operation: 'openrouter-only.status' }, label: 'OpenRouter Only Mode' };
|
|
133
|
+
}
|
|
134
|
+
allowOnly(parsed, ['--json', '--no-restart'], []);
|
|
135
|
+
return {
|
|
136
|
+
...base,
|
|
137
|
+
request: { operation: 'openrouter-only.set', enabled: action === 'on', no_restart: parsed.flags.has('--no-restart') },
|
|
138
|
+
label: 'OpenRouter Only Mode'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (area === 'subagent-models' && target === undefined) {
|
|
142
|
+
if (action === 'list') {
|
|
143
|
+
allowOnly(parsed, ['--json'], []);
|
|
144
|
+
return { ...base, request: { operation: 'subagent-models.list' }, label: 'Subagent model list' };
|
|
145
|
+
}
|
|
146
|
+
if (action === 'set') {
|
|
147
|
+
allowOnly(parsed, ['--json', '--stdin', '--no-restart'], []);
|
|
148
|
+
if (!parsed.flags.has('--stdin')) {
|
|
149
|
+
throw new BridgeCliError('bridge_subagent_models_stdin_required', 'retry_with_stdin');
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
...base,
|
|
153
|
+
request: {
|
|
154
|
+
operation: 'subagent-models.set',
|
|
155
|
+
subagent_models: subagentModelsFromStdin(await io.readStdin()),
|
|
156
|
+
no_restart: parsed.flags.has('--no-restart')
|
|
157
|
+
},
|
|
158
|
+
label: 'Subagent model list update'
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
123
162
|
if (area === 'status' && action === undefined) {
|
|
124
163
|
allowOnly(parsed, ['--json'], []);
|
|
125
164
|
return { ...base, request: { operation: 'status' }, label: 'Desktop Bridge status' };
|
|
@@ -318,6 +357,27 @@ function parseArgs(args) {
|
|
|
318
357
|
}
|
|
319
358
|
return { positionals, flags, values };
|
|
320
359
|
}
|
|
360
|
+
function subagentModelsFromStdin(text) {
|
|
361
|
+
const raw = String(text || '').trim();
|
|
362
|
+
if (!raw)
|
|
363
|
+
throw new BridgeCliError('bridge_subagent_models_stdin_empty', 'retry_with_stdin');
|
|
364
|
+
if (Buffer.byteLength(raw, 'utf8') > MAX_STDIN_JSON_BYTES) {
|
|
365
|
+
throw new BridgeCliError('bridge_subagent_models_stdin_too_large', 'retry_with_stdin');
|
|
366
|
+
}
|
|
367
|
+
let parsed;
|
|
368
|
+
try {
|
|
369
|
+
parsed = JSON.parse(raw);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
throw new BridgeCliError('bridge_subagent_models_stdin_invalid_json', 'retry_with_stdin');
|
|
373
|
+
}
|
|
374
|
+
const models = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
375
|
+
? parsed.subagent_models
|
|
376
|
+
: undefined;
|
|
377
|
+
if (!Array.isArray(models))
|
|
378
|
+
throw new BridgeCliError('bridge_subagent_models_stdin_invalid', 'retry_with_stdin');
|
|
379
|
+
return models;
|
|
380
|
+
}
|
|
321
381
|
function rejectSecretArgv(args) {
|
|
322
382
|
for (const arg of args) {
|
|
323
383
|
const option = String(arg).split('=', 1)[0]?.toLowerCase() || '';
|
|
@@ -38,7 +38,10 @@ export async function run(_command, args = []) {
|
|
|
38
38
|
}
|
|
39
39
|
console.log(`Codex 1M Context: ${result.enabled ? 'enabled' : 'disabled'}`);
|
|
40
40
|
console.log(`Config: ${result.config_path}`);
|
|
41
|
-
|
|
41
|
+
const window = result.model_window;
|
|
42
|
+
console.log(`Model: ${result.model || 'not set'}${window.effective_window ? ` · window ${window.effective_window} (default ${window.default_window ?? 'unknown'}, max ${window.max_window ?? 'uncapped'})` : ' · window unknown'}`);
|
|
43
|
+
if (result.larger_window_models.length)
|
|
44
|
+
console.log(`Models with a larger window: ${result.larger_window_models.join(', ')}`);
|
|
42
45
|
for (const [key, target] of Object.entries(result.target)) {
|
|
43
46
|
const state = result.keys[key];
|
|
44
47
|
const detail = state?.present ? `${state.value ?? 'unparsed'}${state.managed ? ' (SKS-managed)' : ''}` : 'not set';
|
|
@@ -13,6 +13,8 @@ import { codexTimeoutClassForRoute } from '../codex-control/codex-reliability-sh
|
|
|
13
13
|
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
|
+
import { chooseChildModel, fallbackChildModel } from '../decisions/child-model-choice.js';
|
|
17
|
+
import { OPENROUTER_ONLY_SCHEMA, allowlistedChildModel, effectiveChildModelAllowlist, isSubagentModelEffort } from '../subagents/child-model-allowlist.js';
|
|
16
18
|
export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
|
|
17
19
|
export async function runNativeWorkerBackendRouter(input) {
|
|
18
20
|
const root = path.resolve(input.agentRoot);
|
|
@@ -229,6 +231,10 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
|
229
231
|
const riskText = [taskKindText, input.slice?.risk, input.slice?.risk_focus, input.slice?.acceptance, input.slice?.parent_prompt, input.slice?.description, input.intake?.prompt].map((value) => String(value || '')).join(' ');
|
|
230
232
|
const category = categoryForWorkerRole(String(input.agent?.role || 'executor'), taskKindText);
|
|
231
233
|
const env = deps.env || process.env;
|
|
234
|
+
const allowlist = effectiveChildModelAllowlist({ env });
|
|
235
|
+
if (allowlist.mode === 'openrouter_only') {
|
|
236
|
+
return resolveOpenRouterOnlyWorkerRouting(input, deps, { allowlist, category, env, taskKindText, riskText });
|
|
237
|
+
}
|
|
232
238
|
const lbHealth = Object.prototype.hasOwnProperty.call(deps, 'lbHealth') ? deps.lbHealth : await readLbHealth().catch(() => null);
|
|
233
239
|
const lbCatalog = narutoOnly
|
|
234
240
|
? Object.prototype.hasOwnProperty.call(deps, 'lbCatalog')
|
|
@@ -305,6 +311,55 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
|
305
311
|
})
|
|
306
312
|
};
|
|
307
313
|
}
|
|
314
|
+
async function resolveOpenRouterOnlyWorkerRouting(input, deps, ctx) {
|
|
315
|
+
const { allowlist, category, env } = ctx;
|
|
316
|
+
const state = { schema: OPENROUTER_ONLY_SCHEMA, enabled: true, subagent_models: allowlist.entries, restore: null, updated_at: null };
|
|
317
|
+
const explicitModel = String(env.SKS_WORKER_MODEL || env.SKS_AGENT_MODEL || '').trim();
|
|
318
|
+
const explicitReasoning = listReasoning(env.SKS_WORKER_REASONING || env.SKS_WORKER_MODEL_REASONING);
|
|
319
|
+
const explicitTier = normalizeServiceTier(String(env.SKS_WORKER_SERVICE_TIER || env.SKS_SERVICE_TIER || '').trim());
|
|
320
|
+
const planned = /^openrouter_only_/.test(String(input.agent?.routed_model_policy || ''))
|
|
321
|
+
? allowlistedChildModel(input.agent?.routed_model, allowlist)
|
|
322
|
+
: null;
|
|
323
|
+
const requested = String(input.agent?.routed_model || input.agent?.model || '').trim() || null;
|
|
324
|
+
const explicitListed = explicitModel ? allowlistedChildModel(explicitModel, allowlist) : null;
|
|
325
|
+
const task = `${ctx.taskKindText}\n${ctx.riskText}`.trim().slice(0, 1200);
|
|
326
|
+
const choice = explicitModel
|
|
327
|
+
? (explicitListed ? fallbackChildModel(state, explicitListed, 'explicit') : null)
|
|
328
|
+
: planned
|
|
329
|
+
? fallbackChildModel(state, planned, 'planned')
|
|
330
|
+
: deps.consultJev === false || !task
|
|
331
|
+
? fallbackChildModel(state, requested, deps.consultJev === false ? 'jev_skipped' : 'empty_task')
|
|
332
|
+
: await chooseChildModel({ root: deps.root || process.cwd(), task, role: input.agent?.role || null, requestedModel: requested, state, env })
|
|
333
|
+
.catch(() => fallbackChildModel(state, requested, 'consult_failed'));
|
|
334
|
+
const taskPolicy = decideSubagentModel({ title: ctx.taskKindText, description: ctx.riskText, role: input.agent?.role });
|
|
335
|
+
const choiceModel = choice?.entry.model || '';
|
|
336
|
+
const routed = {
|
|
337
|
+
model: choiceModel,
|
|
338
|
+
reasoning: explicitReasoning || choice?.entry.reasoning_effort || listReasoning(taskPolicy.modelReasoningEffort) || 'medium',
|
|
339
|
+
serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
|
|
340
|
+
};
|
|
341
|
+
const blockers = [
|
|
342
|
+
...(!allowlist.entries.length ? ['openrouter_only_subagent_list_empty'] : []),
|
|
343
|
+
...(explicitModel && !explicitListed ? ['openrouter_only_subagent_model_not_listed'] : []),
|
|
344
|
+
...(!choiceModel ? ['naruto_worker_model_unavailable'] : [])
|
|
345
|
+
];
|
|
346
|
+
const why = choice ? `${choice.source}${choice.reason === 'applied' ? '' : `:${choice.reason}`}` : 'blocked';
|
|
347
|
+
return {
|
|
348
|
+
category,
|
|
349
|
+
choice: routed,
|
|
350
|
+
explicit: Boolean(explicitModel),
|
|
351
|
+
lb_health: Object.prototype.hasOwnProperty.call(deps, 'lbHealth') ? deps.lbHealth : null,
|
|
352
|
+
lb_catalog: null,
|
|
353
|
+
blockers: [...new Set(blockers)],
|
|
354
|
+
reason: `${category}->${choiceModel || 'blocked'}@${routed.reasoning} (openrouter only list: ${why})`
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function listReasoning(value) {
|
|
358
|
+
const text = String(value || '').trim().toLowerCase();
|
|
359
|
+
if (text === 'max' || text === 'ultra')
|
|
360
|
+
return 'xhigh';
|
|
361
|
+
return isSubagentModelEffort(text) ? text : null;
|
|
362
|
+
}
|
|
308
363
|
function normalizeModelReasoning(value) {
|
|
309
364
|
const text = String(value || '').toLowerCase();
|
|
310
365
|
return text === 'low' || text === 'medium' || text === 'high' || text === 'xhigh' || text === 'max' || text === 'ultra' ? text : null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isCodexAppRunningByBundleId } from './menubar/config.js';
|
|
2
|
+
import { restartCodexApp } from './codex-app-restart.js';
|
|
3
|
+
export async function maybeRestartRunningCodexApp(input) {
|
|
4
|
+
const skipped = (reason) => ({ attempted: false, running: null, status: 'skipped', reason, ok: true, blockers: [] });
|
|
5
|
+
if (input.noRestart)
|
|
6
|
+
return skipped('no_restart_flag');
|
|
7
|
+
if (!input.changed)
|
|
8
|
+
return skipped('config_unchanged');
|
|
9
|
+
if (input.env.SKS_SKIP_CODEX_APP_RESTART === '1')
|
|
10
|
+
return skipped('SKS_SKIP_CODEX_APP_RESTART');
|
|
11
|
+
if ((input.platform || process.platform) !== 'darwin')
|
|
12
|
+
return skipped('not_macos');
|
|
13
|
+
const bundleId = String(input.env.SKS_CODEX_APP_BUNDLE_ID || 'com.openai.codex');
|
|
14
|
+
const running = await (input.isRunningImpl || isCodexAppRunningByBundleId)(bundleId, input.env);
|
|
15
|
+
if (!running) {
|
|
16
|
+
return { attempted: false, running: false, status: 'skipped', reason: 'codex_not_running', ok: true, blockers: [] };
|
|
17
|
+
}
|
|
18
|
+
const result = await (input.restartImpl || restartCodexApp)({ env: input.env, ...(input.root ? { root: input.root } : {}) });
|
|
19
|
+
return { attempted: true, running: true, status: result.status, reason: null, ok: result.ok, blockers: [...result.blockers] };
|
|
20
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { exists, readText } from '../fsx.js';
|
|
2
3
|
import { escapeRegExp } from '../text/regex.js';
|
|
3
4
|
import { safeWriteCodexConfigToml } from '../codex-runtime/codex-desktop-config-policy.js';
|
|
4
|
-
import { codexUserConfigPath, readTopLevelTomlString } from './codex-model-catalog.js';
|
|
5
|
+
import { codexHomePath, codexUserConfigPath, readTopLevelTomlString } from './codex-model-catalog.js';
|
|
5
6
|
import { isCodexAppRunningByBundleId } from './menubar/config.js';
|
|
6
7
|
import { restartCodexApp } from './codex-app-restart.js';
|
|
8
|
+
import { maybeRestartRunningCodexApp } from './codex-app-restart-policy.js';
|
|
7
9
|
export const CODEX_CONTEXT_1M_SCHEMA = 'sks.codex-context-1m.v1';
|
|
8
10
|
export const CODEX_CONTEXT_1M_MARKER = 'sks-codex-context-1m';
|
|
9
11
|
export const CODEX_CONTEXT_1M_TARGETS = {
|
|
10
12
|
model_context_window: 1_000_000,
|
|
11
13
|
model_auto_compact_token_limit: 900_000
|
|
12
14
|
};
|
|
13
|
-
export const CODEX_CONTEXT_1M_MODEL = 'gpt-5.6-sol';
|
|
14
15
|
const MANAGED_KEYS = Object.keys(CODEX_CONTEXT_1M_TARGETS);
|
|
15
16
|
function topLevelRegionEnd(lines) {
|
|
16
17
|
const first = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
|
|
@@ -64,6 +65,50 @@ export function inspectCodexContext1m(text) {
|
|
|
64
65
|
const enabled = MANAGED_KEYS.every((key) => keys[key].managed && keys[key].value === CODEX_CONTEXT_1M_TARGETS[key]);
|
|
65
66
|
return { enabled, model: readTopLevelTomlString(String(text || ''), 'model'), keys, warnings };
|
|
66
67
|
}
|
|
68
|
+
function positiveInt(value) {
|
|
69
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : null;
|
|
70
|
+
}
|
|
71
|
+
export async function readCodexModelWindows(input = {}) {
|
|
72
|
+
const text = await readText(path.join(codexHomePath(input), 'models_cache.json'), '');
|
|
73
|
+
let parsed = null;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(String(text || 'null'));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const models = parsed?.models;
|
|
81
|
+
if (!Array.isArray(models))
|
|
82
|
+
return [];
|
|
83
|
+
return models.flatMap((row) => {
|
|
84
|
+
if (!row || typeof row !== 'object')
|
|
85
|
+
return [];
|
|
86
|
+
const record = row;
|
|
87
|
+
const slug = typeof record.slug === 'string' ? record.slug.trim() : '';
|
|
88
|
+
if (!slug)
|
|
89
|
+
return [];
|
|
90
|
+
return [{
|
|
91
|
+
slug,
|
|
92
|
+
listed: record.visibility !== 'hide',
|
|
93
|
+
default_window: positiveInt(record.context_window),
|
|
94
|
+
max_window: positiveInt(record.max_context_window)
|
|
95
|
+
}];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export function codexContextModelWindow(rows, model) {
|
|
99
|
+
const row = model ? rows.find((candidate) => candidate.slug === model) : undefined;
|
|
100
|
+
if (!row)
|
|
101
|
+
return { source: 'unknown', default_window: null, max_window: null, effective_window: null, extends: null };
|
|
102
|
+
const target = CODEX_CONTEXT_1M_TARGETS.model_context_window;
|
|
103
|
+
const effective = row.max_window === null ? target : Math.min(target, row.max_window);
|
|
104
|
+
return {
|
|
105
|
+
source: 'codex_models_cache',
|
|
106
|
+
default_window: row.default_window,
|
|
107
|
+
max_window: row.max_window,
|
|
108
|
+
effective_window: effective,
|
|
109
|
+
extends: row.default_window === null ? null : effective > row.default_window
|
|
110
|
+
};
|
|
111
|
+
}
|
|
67
112
|
export function enableCodexContext1m(text) {
|
|
68
113
|
const source = String(text || '');
|
|
69
114
|
const lines = source.split('\n');
|
|
@@ -160,15 +205,21 @@ export async function codexContext1mCommand(args = [], opts = {}) {
|
|
|
160
205
|
}
|
|
161
206
|
const inspection = inspectCodexContext1m(afterText);
|
|
162
207
|
warnings.push(...inspection.warnings);
|
|
208
|
+
const windowRows = await readCodexModelWindows({ env, ...(opts.home ? { home: opts.home } : {}) });
|
|
209
|
+
const modelWindow = codexContextModelWindow(windowRows, inspection.model);
|
|
163
210
|
if (action === 'on' || inspection.enabled) {
|
|
164
211
|
if (!inspection.model)
|
|
165
212
|
warnings.push('codex_context_model_line_missing');
|
|
166
|
-
else if (
|
|
167
|
-
warnings.push(`
|
|
213
|
+
else if (modelWindow.source === 'unknown')
|
|
214
|
+
warnings.push(`codex_context_model_window_unknown:${inspection.model}`);
|
|
215
|
+
else if (modelWindow.max_window === null)
|
|
216
|
+
warnings.push(`codex_context_model_window_uncapped:${inspection.model}`);
|
|
217
|
+
else if (modelWindow.extends === false)
|
|
218
|
+
warnings.push(`codex_context_model_window_fixed:${inspection.model}:${modelWindow.max_window}`);
|
|
168
219
|
}
|
|
169
220
|
let restart = null;
|
|
170
221
|
if ((action === 'on' || action === 'off') && !blockers.length) {
|
|
171
|
-
restart = await
|
|
222
|
+
restart = await maybeRestartRunningCodexApp({
|
|
172
223
|
env,
|
|
173
224
|
changed,
|
|
174
225
|
noRestart,
|
|
@@ -186,7 +237,10 @@ export async function codexContext1mCommand(args = [], opts = {}) {
|
|
|
186
237
|
enabled: inspection.enabled,
|
|
187
238
|
config_path: configPath,
|
|
188
239
|
model: inspection.model,
|
|
189
|
-
|
|
240
|
+
model_window: modelWindow,
|
|
241
|
+
larger_window_models: windowRows
|
|
242
|
+
.filter((row) => row.listed && row.max_window !== null && row.default_window !== null && row.max_window > row.default_window)
|
|
243
|
+
.map((row) => row.slug),
|
|
190
244
|
target: { ...CODEX_CONTEXT_1M_TARGETS },
|
|
191
245
|
keys: inspection.keys,
|
|
192
246
|
previous: mutation && action === 'on' ? mutation.previous : null,
|
|
@@ -198,6 +252,7 @@ export async function codexContext1mCommand(args = [], opts = {}) {
|
|
|
198
252
|
warnings,
|
|
199
253
|
notes: [
|
|
200
254
|
'Only new Codex sessions pick up context-window changes; existing conversations keep their previous limits.',
|
|
255
|
+
'Codex caps the window at each model\'s maximum, so every model gets its largest supported window up to 1M.',
|
|
201
256
|
'Requests with more than 272K input tokens are billed at the long-context rate (2x input / 1.5x output) for the entire request.'
|
|
202
257
|
],
|
|
203
258
|
cli_commands: {
|
|
@@ -207,21 +262,3 @@ export async function codexContext1mCommand(args = [], opts = {}) {
|
|
|
207
262
|
}
|
|
208
263
|
};
|
|
209
264
|
}
|
|
210
|
-
async function maybeRestartCodexApp(input) {
|
|
211
|
-
const skippedOutcome = (reason) => ({ attempted: false, running: null, status: 'skipped', reason, ok: true, blockers: [] });
|
|
212
|
-
if (input.noRestart)
|
|
213
|
-
return skippedOutcome('no_restart_flag');
|
|
214
|
-
if (!input.changed)
|
|
215
|
-
return skippedOutcome('config_unchanged');
|
|
216
|
-
if (input.env.SKS_SKIP_CODEX_APP_RESTART === '1')
|
|
217
|
-
return skippedOutcome('SKS_SKIP_CODEX_APP_RESTART');
|
|
218
|
-
if (process.platform !== 'darwin')
|
|
219
|
-
return skippedOutcome('not_macos');
|
|
220
|
-
const bundleId = String(input.env.SKS_CODEX_APP_BUNDLE_ID || 'com.openai.codex');
|
|
221
|
-
const running = await (input.isRunningImpl || isCodexAppRunningByBundleId)(bundleId, input.env);
|
|
222
|
-
if (!running) {
|
|
223
|
-
return { attempted: false, running: false, status: 'skipped', reason: 'codex_not_running', ok: true, blockers: [] };
|
|
224
|
-
}
|
|
225
|
-
const result = await (input.restartImpl || restartCodexApp)({ env: input.env, ...(input.root ? { root: input.root } : {}) });
|
|
226
|
-
return { attempted: true, running: true, status: result.status, reason: null, ok: result.ok, blockers: result.blockers };
|
|
227
|
-
}
|
|
@@ -29,6 +29,10 @@ export const NATIVE_SOURCE_FILES = [
|
|
|
29
29
|
'ProvidersOpenRouter.swift',
|
|
30
30
|
'ProvidersModelExposure.swift',
|
|
31
31
|
'ProvidersBridgeCatalog.swift',
|
|
32
|
+
'OpenRouterOnlyState.swift',
|
|
33
|
+
'ProvidersOpenRouterOnly.swift',
|
|
34
|
+
'SubagentModelsModels.swift',
|
|
35
|
+
'SubagentModelsViewController.swift',
|
|
32
36
|
'RemoteCodingViewController.swift',
|
|
33
37
|
'DiagnosticsViewController.swift',
|
|
34
38
|
'SettingsViewController.swift',
|
|
@@ -24,12 +24,14 @@ export function buildPreToolUseAllowRewrite(updatedInput, options = {}) {
|
|
|
24
24
|
if (updatedInput === undefined || updatedInput === null) {
|
|
25
25
|
throw new Error('PreToolUse allow rewrite requires updatedInput');
|
|
26
26
|
}
|
|
27
|
+
const additionalContext = optionalText(options.additionalContext);
|
|
27
28
|
return withOptionalSystemMessage({
|
|
28
29
|
continue: true,
|
|
29
30
|
hookSpecificOutput: {
|
|
30
31
|
hookEventName: 'PreToolUse',
|
|
31
32
|
permissionDecision: 'allow',
|
|
32
|
-
updatedInput
|
|
33
|
+
updatedInput,
|
|
34
|
+
...(additionalContext ? { additionalContext } : {})
|
|
33
35
|
}
|
|
34
36
|
}, options.systemMessage);
|
|
35
37
|
}
|