sneakoscope 10.1.2 → 10.1.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.
Files changed (41) hide show
  1. package/README.md +5 -3
  2. package/config/skills-hash-ledger.v1.json +2 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/config/skills-manifest.json +5 -3
  6. package/dist/core/agents/agent-effort-policy.js +21 -140
  7. package/dist/core/agents/agent-orchestrator.js +4 -4
  8. package/dist/core/agents/native-worker-backend-router.js +26 -44
  9. package/dist/core/codex-compat/codex-hook-output-builders.js +0 -1
  10. package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +286 -0
  11. package/dist/core/codex-lb/desktop-bridge/security.js +4 -0
  12. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +10 -0
  13. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +2 -2
  14. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +2 -2
  15. package/dist/core/codex-lb/desktop-controller-v3/shared.js +3 -1
  16. package/dist/core/codex-lb/desktop-controller-v3/status.js +2 -1
  17. package/dist/core/codex-lb/desktop-service.js +22 -15
  18. package/dist/core/codex-native/core-skill-manifest.js +1 -1
  19. package/dist/core/hooks-runtime/hook-context.js +1 -1
  20. package/dist/core/hooks-runtime/subagent-context.js +7 -3
  21. package/dist/core/hooks-runtime/subagent-spawn-policy.js +15 -0
  22. package/dist/core/hooks-runtime.js +5 -1
  23. package/dist/core/init/skills.js +1 -1
  24. package/dist/core/init.js +4 -4
  25. package/dist/core/managed-assets/managed-assets-manifest.js +2 -2
  26. package/dist/core/provider/model-router.js +9 -14
  27. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  28. package/dist/core/routes.js +4 -4
  29. package/dist/core/subagents/model-policy.js +2 -2
  30. package/dist/core/subagents/naruto-help-contract.js +4 -4
  31. package/dist/core/subagents/naruto-host-credentials.js +7 -3
  32. package/dist/core/subagents/official-subagent-config.js +5 -3
  33. package/dist/core/subagents/official-subagent-preparation.js +8 -19
  34. package/dist/core/subagents/official-subagent-prompt.js +23 -67
  35. package/dist/core/subagents/role-model-preferences.js +18 -91
  36. package/dist/core/update/update-migration-state/desktop-bridge-restage.js +48 -52
  37. package/dist/core/version.js +1 -1
  38. package/dist/native/sks-menubar/Sources/AuthPriorityState.swift +1 -1
  39. package/dist/scripts/codex-native-agent-role-content-check.js +2 -2
  40. package/dist/scripts/codex-native-gate-lib.js +1 -1
  41. 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.1.2**. Install the latest stable release from npm.
19
+ Current package: **SKS 10.1.4**. 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
 
@@ -133,12 +133,13 @@ configuration, transport checks, and recovery commands.
133
133
 
134
134
  ## Naruto workflow
135
135
 
136
- SKS uses official Codex subagents. The parent owns decomposition, integration,
136
+ SKS enforces GPT-6 Astra for every managed child and varies effort by task.
137
+ The parent owns decomposition, integration,
137
138
  and final verification; children receive bounded tasks and do not spawn children.
138
139
 
139
140
  | Work | Managed model | Effort |
140
141
  | --- | --- | --- |
141
- | Tiny mechanical tasks | GPT-5.6 Luna | max |
142
+ | Tiny mechanical tasks | GPT-6 Astra | low |
142
143
  | Exploration, large-context reads, and direct tool operation | GPT-6 Astra | medium |
143
144
  | Implementation | GPT-6 Astra | high |
144
145
  | Review, debugging, and focused judgment | GPT-6 Astra | max |
@@ -176,6 +177,7 @@ freshness semantics.
176
177
  - [Essential Trust](docs/essential-trust.md) — verification profiles and safety boundaries.
177
178
  - [Astra guidance](docs/astra-guidance.md) — how SKS applies the official model recommendations.
178
179
  - [Agent Bridge](docs/AGENT-BRIDGE.md) — integrate through the CLI or MCP interface.
180
+ - [Codex-LB priority](docs/codex-lb-priority.md) — how Codex App WebSockets follow the Center priority setting.
179
181
  - [Release readiness](docs/release-readiness.md) — build, verify, and publish a release.
180
182
  - [Release evidence](docs/release-proof-truth.md) — what each verification result proves.
181
183
  - [Changelog](CHANGELOG.md) — changes by version.
@@ -282,6 +282,7 @@
282
282
  {
283
283
  "canonical_name": "sks-naruto",
284
284
  "trusted_sha256": [
285
+ "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
285
286
  "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
286
287
  "d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
287
288
  "34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
@@ -337,6 +338,7 @@
337
338
  {
338
339
  "canonical_name": "sks-reasoning-router",
339
340
  "trusted_sha256": [
341
+ "647f730520a1c7dc89c2642f5b8502624c65a861ae41aecf1fb137db40fd0611",
340
342
  "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
341
343
  "1595beabf2d31397217311fa1323bd62c801cf6c87db9029ff544c457efec8bb",
342
344
  "a7d879d157c6d5b6b81a67dba7b5b731ac18ea986841fe1ab2cafbd6623481c3",
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.1.2"
262
+ version = "10.1.4"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.1.2"
3
+ version = "10.1.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.1.2",
3
+ "package_version": "10.1.4",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -346,8 +346,9 @@
346
346
  {
347
347
  "canonical_name": "sks-naruto",
348
348
  "type": "core",
349
- "content_sha256": "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
349
+ "content_sha256": "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
350
350
  "hash_history": [
351
+ "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
351
352
  "d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
352
353
  "34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
353
354
  "f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
@@ -414,8 +415,9 @@
414
415
  {
415
416
  "canonical_name": "sks-reasoning-router",
416
417
  "type": "official",
417
- "content_sha256": "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
418
+ "content_sha256": "647f730520a1c7dc89c2642f5b8502624c65a861ae41aecf1fb137db40fd0611",
418
419
  "hash_history": [
420
+ "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
419
421
  "1595beabf2d31397217311fa1323bd62c801cf6c87db9029ff544c457efec8bb",
420
422
  "a7d879d157c6d5b6b81a67dba7b5b731ac18ea986841fe1ab2cafbd6623481c3",
421
423
  "dc0b8c6a4093269c2c36b58432b25409704d06fea8ca558f43b598625ca0d9de"
@@ -1,67 +1,8 @@
1
1
  import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
2
- import { OPENROUTER_DEFAULT_MODEL } from '../codex-app/openrouter-provider.js';
3
2
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
4
- import { isNarutoGpt56Model } from '../provider/model-router.js';
5
- import { decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
6
- const XHIGH_SIGNAL_RE = /(frontier|autoresearch|novelty|hypothesis|falsif|forensic|from-chat-img|image\s*work\s*order|새로운\s*연구|가설|포렌식)/i;
7
- const HIGH_SIGNAL_RE = /(database|supabase|sql|migration|security|permission|mad|release|publish|deploy|architecture|policy|schema|hook|rollback|db|보안|배포|마이그레이션|데이터베이스|권한|릴리즈)/i;
8
- const MEDIUM_SIGNAL_RE = /(terminal|cli|tool(?:\s|-)?call|router|routing|orchestrat|pipeline|multi[-\s]?session|multi[-\s]?agent|lease|ledger|proof|검증|파이프라인|오케스트레이션|병렬|에이전트)/i;
9
- const SIMPLE_SIGNAL_RE = /(tiny|simple|small|one[-\s]?line|typo|copy|label|spacing|rename|readme|docs?|간단|단순|오타|문구|라벨)/i;
10
- const SIMPLE_CODE_MOD_RE = /(tiny|simple|small|one[-\s]?line|typo|copy|label|spacing|rename|readme|docs?|minor|bounded|간단|단순|작은|오타|문구|라벨|이름\s*변경)/i;
3
+ import { ASTRA_SUBAGENT_MODEL, decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
11
4
  export function decideAgentEffort(input = {}) {
12
- const persona = input.persona || {};
13
- const prompt = String(input.prompt || '');
14
- const role = String(persona.role || '');
15
- const agentId = String(input.agentId || persona.id || 'agent');
16
- const text = [prompt, role, persona.risk_focus, persona.write_policy, persona.stable_id].join(' ');
17
- let effort = promptEffort(text);
18
- let reason = effortReason(effort);
19
- if (/(db|safety|security|release|schema)/i.test([role, agentId, persona.stable_id, persona.risk_focus].join(' '))) {
20
- effort = effort === 'xhigh' ? 'xhigh' : 'high';
21
- reason = 'risk_guardian_lane';
22
- }
23
- else if (/(integrator|architect|verifier)/i.test([role, agentId, persona.stable_id].join(' ')) && effort === 'low') {
24
- effort = 'medium';
25
- reason = 'planning_verification_minimum';
26
- }
27
- else if (input.readonly === true && SIMPLE_SIGNAL_RE.test(prompt) && !HIGH_SIGNAL_RE.test(text) && !MEDIUM_SIGNAL_RE.test(text)) {
28
- effort = 'low';
29
- reason = 'read_only_simple_slice';
30
- }
31
- else if (/implementer/i.test(role) && effort === 'xhigh') {
32
- effort = 'high';
33
- reason = 'implementation_lane_capped_at_high';
34
- }
35
- const modelDecision = decideAgentWorkerModel({ effort, prompt, role, agentId, readonly: input.readonly === true || persona.read_only === true, writePolicy: String(persona.write_policy || '') });
36
- const modelCapability = codexModelEffortCapability({ model: modelDecision.model, defaultEffort: modelDecision.model_reasoning_effort });
37
- return {
38
- schema: 'sks.agent-effort-decision.v1',
39
- policy_version: 1,
40
- agent_id: agentId,
41
- role,
42
- model: modelDecision.model,
43
- reasoning_effort: effort,
44
- model_reasoning_effort: modelDecision.model_reasoning_effort,
45
- model_tier: modelDecision.model_tier,
46
- model_profile: modelDecision.model_profile,
47
- model_selection_reason: modelDecision.reason,
48
- model_effort_capability: modelCapability,
49
- reasoning_profile: reasoningProfileName(effort),
50
- service_tier: 'fast',
51
- reason,
52
- dynamic: true,
53
- escalation_triggers: [
54
- 'DB/security/release/schema risk detected',
55
- 'lease conflict or proof blocker appears',
56
- 'verification fails or output schema validation fails',
57
- 'user requests real backend or broader agent fan-out'
58
- ],
59
- downshift_triggers: [
60
- 'read-only simple docs/copy slice',
61
- 'mock fixture backend with no risky file ownership',
62
- 'agent assigned narrow inventory-only work'
63
- ]
64
- };
5
+ return decideOfficialSubagentModel(input);
65
6
  }
66
7
  export function decideOfficialSubagentModel(input = {}) {
67
8
  const persona = input.persona || {};
@@ -104,7 +45,7 @@ export function decideOfficialSubagentModel(input = {}) {
104
45
  downshift_triggers: [
105
46
  'ordinary UI, logic, backend, or native implementation selects Astra High',
106
47
  'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects Astra Medium',
107
- 'tiny short-context mechanical search/typing/rename work selects Luna Max'
48
+ 'tiny short-context mechanical search/typing/rename work selects Astra Low'
108
49
  ]
109
50
  };
110
51
  }
@@ -158,7 +99,7 @@ export function decideOfficialSubagentModel(input = {}) {
158
99
  downshift_triggers: [
159
100
  'ordinary UI, logic, backend, or native implementation selects Astra High',
160
101
  'long-context, Browser/Chrome, Computer Use, or image-generation execution selects Astra Medium',
161
- 'tiny short-context mechanical work selects Luna Max'
102
+ 'tiny short-context mechanical work selects Astra Low'
162
103
  ]
163
104
  };
164
105
  }
@@ -177,101 +118,41 @@ export function buildAgentEffortPolicy(roster = {}) {
177
118
  reason: agent.reasoning_reason,
178
119
  dynamic: true
179
120
  })) : [];
180
- const narutoFamilyOnly = decisions.length > 0 && decisions.every((decision) => isNarutoGpt56Model(decision.model));
181
121
  return {
182
122
  schema: 'sks.agent-effort-policy.v1',
183
123
  policy_version: 1,
184
124
  dynamic: true,
185
125
  service_tier: 'fast',
186
- model_catalog_policy: narutoFamilyOnly ? 'official_subagent_four_profile_matrix' : 'codex_catalog_passthrough',
187
- model_constraint: narutoFamilyOnly ? ['gpt-5.6-luna', 'gpt-6-astra'] : null,
188
- model_tiers: narutoFamilyOnly
189
- ? ['gpt-5.6-luna-max', 'gpt-6-astra-high', 'gpt-6-astra-max', 'gpt-6-astra-medium']
190
- : ['codex-selected-low', 'codex-selected-medium', 'codex-selected-high', 'codex-selected-xhigh', 'glm-5.2-minimal', 'glm-5.2-low', 'glm-5.2-high', 'glm-5.2-xhigh'],
191
- allowed_efforts: narutoFamilyOnly ? ['medium', 'high', 'max'] : codexModelEffortCapability().advertised_efforts,
192
- model_effort_capability: codexModelEffortCapability(),
126
+ model_catalog_policy: 'official_subagent_four_profile_matrix',
127
+ model_constraint: [ASTRA_SUBAGENT_MODEL],
128
+ model_tiers: ['low', 'medium', 'high', 'max'].map((effort) => `${ASTRA_SUBAGENT_MODEL}-${effort}`),
129
+ allowed_efforts: ['low', 'medium', 'high', 'max'],
130
+ model_effort_capability: codexModelEffortCapability({ model: ASTRA_SUBAGENT_MODEL }),
193
131
  max_agents: roster.max_agents || 20,
194
132
  agent_count: roster.agent_count || decisions.length,
195
133
  concurrency: roster.concurrency || decisions.length,
196
134
  decisions,
197
- rule: narutoFamilyOnly
198
- ? 'Official Naruto subagents use GPT-5.6 Luna Max only for tiny short-context mechanical work, GPT-6 Astra High for ordinary implementation, GPT-6 Astra Max for judgment-heavy work, and GPT-6 Astra Medium for long-context or Browser/Chrome, Computer Use, and image-generation execution. Judgment wins when one slice cannot be safely split.'
199
- : 'Codex/OpenAI workers inherit the current Codex-selected model, including future catalog entries; SKS changes only advertised reasoning effort. Explicit non-Codex provider modes retain their provider model.'
135
+ rule: 'All child agents use GPT-6 Astra: Low for tiny short-context mechanical work, Medium for reads and tool execution, High for ordinary implementation, and Max for focused judgment. The parent keeps its user-selected model.'
200
136
  };
201
137
  }
202
138
  export function reasoningProfileName(effort) {
203
139
  return 'sks-agent-' + String(effort || 'medium') + '-fast';
204
140
  }
205
- function promptEffort(text) {
206
- if (XHIGH_SIGNAL_RE.test(text))
207
- return 'xhigh';
208
- if (HIGH_SIGNAL_RE.test(text))
209
- return 'high';
210
- if (SIMPLE_SIGNAL_RE.test(text) && !MEDIUM_SIGNAL_RE.test(text))
211
- return 'low';
212
- if (MEDIUM_SIGNAL_RE.test(text))
213
- return 'medium';
214
- return 'medium';
215
- }
216
- function effortReason(effort) {
217
- if (effort === 'xhigh')
218
- return 'frontier_or_forensic_signal';
219
- if (effort === 'high')
220
- return 'safety_release_db_schema_signal';
221
- if (effort === 'low')
222
- return 'simple_bounded_slice';
223
- return 'default_orchestration_slice';
224
- }
225
141
  export function decideAgentWorkerModel(input = {}) {
226
- const mainModel = String(input.mainModel || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || '').trim();
227
- const glmMain = isGlmWorkerMode(mainModel);
228
- const effort = String(input.effort || 'medium');
229
- const text = [input.prompt, input.role, input.agentId, input.writePolicy].map((item) => String(item || '')).join(' ');
230
- const simpleText = [input.prompt, input.role, input.agentId].map((item) => String(item || '')).join(' ');
231
- const risky = HIGH_SIGNAL_RE.test(text) || XHIGH_SIGNAL_RE.test(text);
232
- const simple = SIMPLE_CODE_MOD_RE.test(simpleText) && !HIGH_SIGNAL_RE.test(simpleText) && !XHIGH_SIGNAL_RE.test(simpleText);
233
- if (glmMain) {
234
- const openRouterEffort = openRouterWorkerEffort({ effort, risky, simple });
235
- return {
236
- model: OPENROUTER_DEFAULT_MODEL,
237
- model_reasoning_effort: openRouterEffort,
238
- model_tier: `glm-5.2-${openRouterEffort === 'none' ? 'minimal' : openRouterEffort}`,
239
- model_profile: desktopBridgeOpenRouterProfile(openRouterEffort),
240
- reason: `desktop_bridge_openrouter_${openRouterEffort}_worker`
241
- };
242
- }
243
- const modelEffort = risky || effort === 'high' || effort === 'xhigh'
244
- ? 'high'
245
- : simple || effort === 'low'
246
- ? 'low'
247
- : 'medium';
248
- const modelLabel = mainModel || 'codex-selected';
142
+ const decision = decideOfficialSubagentModel({
143
+ persona: { role: String(input.role || ''), write_policy: String(input.writePolicy || '') },
144
+ prompt: String(input.prompt || ''),
145
+ agentId: String(input.agentId || 'agent'),
146
+ readonly: input.readonly === true
147
+ });
249
148
  return {
250
- model: mainModel,
251
- model_reasoning_effort: modelEffort,
252
- model_tier: `${modelLabel}-${modelEffort}`,
253
- model_profile: `sks-agent-${safeProfileSegment(modelLabel)}-${modelEffort}-fast`,
254
- reason: mainModel ? 'explicit_model_preserved' : 'codex_catalog_model_inherited'
149
+ model: decision.model,
150
+ model_reasoning_effort: decision.model_reasoning_effort,
151
+ model_tier: decision.model_tier,
152
+ model_profile: decision.model_profile,
153
+ reason: decision.model_selection_reason
255
154
  };
256
155
  }
257
156
  function safeProfileSegment(value) {
258
157
  return String(value || 'codex-selected').toLowerCase().replace(/[^a-z0-9.-]+/g, '-').replace(/^-+|-+$/g, '') || 'codex-selected';
259
158
  }
260
- function isGlmWorkerMode(mainModel) {
261
- const model = String(mainModel || '').trim().toLowerCase();
262
- return model === OPENROUTER_DEFAULT_MODEL
263
- || model === 'glm-5.2'
264
- || model === 'glm5.2';
265
- }
266
- function openRouterWorkerEffort(input) {
267
- if (input.effort === 'xhigh')
268
- return 'xhigh';
269
- if (input.risky || input.effort === 'high')
270
- return 'high';
271
- if (input.simple || input.effort === 'low')
272
- return 'minimal';
273
- return 'low';
274
- }
275
- function desktopBridgeOpenRouterProfile(effort) {
276
- return `desktop-bridge-openrouter-${effort}`;
277
- }
@@ -1925,12 +1925,12 @@ function buildProvidedAgentRoster(input, opts = {}) {
1925
1925
  index: index + 1,
1926
1926
  write_policy: String(entry.write_policy || (readOnly ? 'read-only' : 'route-local-artifact')),
1927
1927
  status: 'pending',
1928
- model: entry.model || modelDecision.model,
1928
+ model: modelDecision.model,
1929
1929
  reasoning_effort: reasoningEffort,
1930
1930
  model_reasoning_effort: modelReasoningEffort,
1931
- model_tier: entry.model_tier || modelDecision.model_tier,
1932
- model_profile: entry.model_profile || modelDecision.model_profile,
1933
- model_selection_reason: entry.model_selection_reason || modelDecision.reason,
1931
+ model_tier: modelDecision.model_tier,
1932
+ model_profile: modelDecision.model_profile,
1933
+ model_selection_reason: modelDecision.reason,
1934
1934
  reasoning_profile: entry.reasoning_profile || (readOnly ? 'sks-logic-high' : 'sks-logic-medium'),
1935
1935
  service_tier: entry.service_tier,
1936
1936
  reasoning_reason: entry.reasoning_reason || 'route_native_agent_plan',
@@ -10,7 +10,7 @@ import { leanEngineeringCompactText, leanPolicyReference } from '../lean-enginee
10
10
  import { readCodexLbModelCatalog, readLbHealth } from '../codex-lb/codex-lb-env.js';
11
11
  import { categoryForWorkerRole, isNarutoGpt56Model, modelRouteReason, routeModel } from '../provider/model-router.js';
12
12
  import { codexTimeoutClassForRoute } from '../codex-control/codex-reliability-shield.js';
13
- import { readConfiguredCodexModelRoutingContext } from '../codex-app/codex-model-catalog.js';
13
+ import { ASTRA_SUBAGENT_MODEL, decideSubagentModel } from '../subagents/model-policy.js';
14
14
  export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
15
15
  export async function runNativeWorkerBackendRouter(input) {
16
16
  const root = path.resolve(input.agentRoot);
@@ -227,22 +227,8 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
227
227
  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(' ');
228
228
  const category = categoryForWorkerRole(String(input.agent?.role || 'executor'), taskKindText);
229
229
  const env = deps.env || process.env;
230
- const configuredRouting = !Object.prototype.hasOwnProperty.call(deps, 'env') || env.CODEX_HOME || env.HOME
231
- ? await readConfiguredCodexModelRoutingContext({ env }).catch(() => null)
232
- : null;
233
- const sealedPlanModel = ['user_role_model_preference', 'active_main_model'].includes(String(input.agent?.routed_model_policy || ''))
234
- ? String(input.agent?.routed_model || '').trim()
235
- : '';
236
- const configuredMainModel = String(input.intake?.main_model
237
- || input.intake?.selected_model
238
- || input.intake?.provider_model
239
- || configuredRouting?.selected_model
240
- || '').trim();
241
- const managedAgentModel = String(input.agent?.model || '').trim();
242
- const inheritedModel = sealedPlanModel || configuredMainModel || managedAgentModel;
243
- const providerModelSelected = Boolean(sealedPlanModel || configuredMainModel);
244
230
  const lbHealth = Object.prototype.hasOwnProperty.call(deps, 'lbHealth') ? deps.lbHealth : await readLbHealth().catch(() => null);
245
- const lbCatalog = narutoOnly && !providerModelSelected
231
+ const lbCatalog = narutoOnly
246
232
  ? Object.prototype.hasOwnProperty.call(deps, 'lbCatalog')
247
233
  ? deps.lbCatalog
248
234
  : input.intake?.naruto_model_catalog || await readCodexLbModelCatalog().catch(() => null)
@@ -255,35 +241,31 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
255
241
  const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoGpt56Model(explicitModel);
256
242
  const explicitNarutoReasoningInvalid = narutoOnly && Boolean(explicitReasoningRaw) && !explicitReasoning;
257
243
  const explicitNarutoTierInvalid = narutoOnly && Boolean(explicitTierRaw) && !explicitTier;
258
- const inheritedReasoning = normalizeModelReasoning(sealedPlanModel
259
- ? input.agent?.routed_model_reasoning_effort
260
- : input.agent?.model_reasoning_effort);
261
- const routed = providerModelSelected
262
- ? {
263
- model: inheritedModel,
264
- reasoning: explicitReasoning || inheritedReasoning || 'medium',
244
+ const savedAstraEffort = input.agent?.routed_model_policy === 'user_role_model_preference'
245
+ && input.agent?.routed_model === ASTRA_SUBAGENT_MODEL
246
+ ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort)
247
+ : null;
248
+ const taskPolicy = decideSubagentModel({ title: taskKindText, description: riskText, role: input.agent?.role });
249
+ const routed = narutoOnly
250
+ ? await routeModel(category, {
251
+ lbHealth,
252
+ narutoOnly: true,
253
+ taskText: taskKindText,
254
+ riskText,
255
+ reasoningEffort: savedAstraEffort,
256
+ availableModels: lbCatalog?.models || [],
257
+ availableModelEfforts: lbCatalog?.model_efforts || {},
258
+ ...(explicitModel ? { model: explicitModel } : {})
259
+ })
260
+ : {
261
+ model: ASTRA_SUBAGENT_MODEL,
262
+ reasoning: explicitReasoning || savedAstraEffort || taskPolicy.modelReasoningEffort,
265
263
  serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
266
- }
267
- : explicitModel && !narutoOnly
268
- ? {
269
- model: explicitModel,
270
- reasoning: explicitReasoning || inheritedReasoning || 'medium',
271
- serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
272
- }
273
- : await routeModel(category, {
274
- lbHealth,
275
- ...(narutoOnly ? {
276
- narutoOnly: true,
277
- taskText: taskKindText,
278
- riskText,
279
- availableModels: lbCatalog?.models || [],
280
- availableModelEfforts: lbCatalog?.model_efforts || {},
281
- ...(explicitModel ? { model: explicitModel } : {})
282
- } : { model: inheritedModel || null })
283
- });
264
+ };
284
265
  const blockers = [
285
- ...(narutoOnly && !providerModelSelected && !lbCatalog?.ok ? (lbCatalog?.blockers || ['codex_lb_model_catalog_unavailable']) : []),
266
+ ...(narutoOnly && !lbCatalog?.ok ? (lbCatalog?.blockers || ['codex_lb_model_catalog_unavailable']) : []),
286
267
  ...(explicitNarutoModelInvalid ? ['naruto_worker_model_outside_gpt_5_6_family'] : []),
268
+ ...(!narutoOnly && explicitModel && explicitModel !== ASTRA_SUBAGENT_MODEL ? ['subagent_model_must_be_astra'] : []),
287
269
  ...(explicitNarutoReasoningInvalid ? ['naruto_reasoning_override_invalid'] : []),
288
270
  ...(explicitNarutoTierInvalid ? ['naruto_service_tier_override_invalid'] : []),
289
271
  ...(narutoOnly && explicitReasoning && explicitReasoning !== routed.reasoning ? ['naruto_reasoning_override_conflicts_with_policy'] : []),
@@ -293,12 +275,12 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
293
275
  return {
294
276
  category,
295
277
  choice: routed,
296
- explicit: Boolean(explicitModel || providerModelSelected),
278
+ explicit: Boolean(explicitModel),
297
279
  lb_health: lbHealth,
298
280
  lb_catalog: lbCatalog,
299
281
  blockers: [...new Set(blockers)],
300
282
  reason: modelRouteReason(category, routed, {
301
- explicit: Boolean(explicitModel || providerModelSelected),
283
+ explicit: Boolean(explicitModel),
302
284
  quotaLow: lbHealth?.quota_low === true,
303
285
  degraded: lbHealth?.degraded_models || []
304
286
  })
@@ -13,7 +13,6 @@ export function buildPreToolUseContinue(options = {}) {
13
13
  export function buildPreToolUseDeny(reason, options = {}) {
14
14
  const trimmed = requiredReason(reason, 'PreToolUse deny requires a non-empty reason');
15
15
  return withOptionalSystemMessage({
16
- continue: true,
17
16
  hookSpecificOutput: {
18
17
  hookEventName: 'PreToolUse',
19
18
  permissionDecision: 'deny',