sneakoscope 10.3.2 → 10.3.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 (71) hide show
  1. package/README.md +22 -12
  2. package/config/skills-hash-ledger.v1.json +7 -1
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/config/skills-manifest.json +13 -7
  7. package/dist/core/agents/agent-effort-policy.js +15 -13
  8. package/dist/core/agents/native-worker-backend-router.js +13 -8
  9. package/dist/core/codex-hooks/codex-hook-managed-install.js +56 -2
  10. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +1 -0
  11. package/dist/core/codex-native/core-skill-manifest.js +4 -4
  12. package/dist/core/decisions/cli.js +9 -2
  13. package/dist/core/decisions/config.js +7 -0
  14. package/dist/core/decisions/integration.js +54 -12
  15. package/dist/core/decisions/policy.js +27 -4
  16. package/dist/core/decisions/questions.js +39 -9
  17. package/dist/core/decisions/routing.js +15 -10
  18. package/dist/core/decisions/types.js +11 -25
  19. package/dist/core/doctor/current-project-guidance.js +38 -1
  20. package/dist/core/doctor/skill-legacy-surface.js +2 -1
  21. package/dist/core/hooks-runtime/hook-context.js +1 -1
  22. package/dist/core/hooks-runtime/jev-spawn-routing.js +21 -2
  23. package/dist/core/hooks-runtime/managed-guidance-preflight.js +30 -0
  24. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -3
  25. package/dist/core/hooks-runtime/parent-orchestration-gate.js +372 -0
  26. package/dist/core/hooks-runtime/subagent-context.js +13 -3
  27. package/dist/core/hooks-runtime/subagent-spawn-policy.js +5 -10
  28. package/dist/core/hooks-runtime.js +46 -8
  29. package/dist/core/init/skills/inventory.js +5 -1
  30. package/dist/core/init/skills.js +4 -4
  31. package/dist/core/init.js +39 -7
  32. package/dist/core/managed-assets/managed-assets-manifest.js +17 -17
  33. package/dist/core/pipeline-internals/runtime-core.js +2 -2
  34. package/dist/core/provider/model-router.js +8 -5
  35. package/dist/core/recallpulse/policy.js +2 -2
  36. package/dist/core/recallpulse.js +3 -3
  37. package/dist/core/release/gate-affected-globs.js +0 -1
  38. package/dist/core/research/mock-result.js +2 -2
  39. package/dist/core/research/research-adversarial-review.js +7 -7
  40. package/dist/core/research/research-claim-synthesizer.js +3 -3
  41. package/dist/core/research/research-falsification-runner.js +3 -3
  42. package/dist/core/research/research-super-search.js +5 -2
  43. package/dist/core/research/research-synthesis-writer.js +2 -2
  44. package/dist/core/research.js +5 -5
  45. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  46. package/dist/core/routes.js +10 -10
  47. package/dist/core/runtime/task-profile.js +9 -5
  48. package/dist/core/subagents/model-policy.js +27 -45
  49. package/dist/core/subagents/model-tiers.js +153 -0
  50. package/dist/core/subagents/naruto-command-args.js +3 -3
  51. package/dist/core/subagents/naruto-help-contract.js +18 -13
  52. package/dist/core/subagents/naruto-host-credentials.js +8 -13
  53. package/dist/core/subagents/official-subagent-config.js +11 -7
  54. package/dist/core/subagents/official-subagent-preparation.js +12 -8
  55. package/dist/core/subagents/official-subagent-prompt.js +33 -33
  56. package/dist/core/subagents/official-subagent-runner.js +3 -3
  57. package/dist/core/subagents/role-model-preferences.js +26 -21
  58. package/dist/core/update/managed-permission-repair.js +462 -0
  59. package/dist/core/update/update-migration-state/retired-local-decision.js +32 -2
  60. package/dist/core/update/update-migration-state/simple-stages.js +42 -5
  61. package/dist/core/update/update-migration-state.js +63 -1
  62. package/dist/core/update-check.js +35 -8
  63. package/dist/core/version.js +1 -1
  64. package/dist/scripts/codex-native-agent-role-content-check.js +10 -5
  65. package/dist/scripts/codex-native-gate-lib.js +3 -1
  66. package/dist/scripts/current-surface-update-e2e-check.js +1 -0
  67. package/dist/scripts/mutation-callsite-coverage-check.js +2 -2
  68. package/dist/scripts/release-affected-selector-check.js +1 -2
  69. package/dist/scripts/typed-routing-gate-lib.js +3 -1
  70. package/package.json +1 -1
  71. package/release-gates.v2.json +2 -6
@@ -6,7 +6,7 @@ import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_ONLY_POLICY, COD
6
6
  import { getdesignReferencePolicyText, imageUxReviewPipelinePolicyText } from './routes/design-policy.js';
7
7
  import { PPT_PIPELINE_SKILL_ALLOWLIST, pptPipelineAllowlistPolicyText } from './routes/ppt-policy.js';
8
8
  import { normalizeDollarSkillName, prefixKnownSksDollarReferences, sksPrefixedDollarCommand, sksPrefixedSkillName, unprefixedSksSkillName } from './routes/dollar-prefix.js';
9
- import { classifyTaskProfile, isTaskProfile, looksLikeDatabaseWorkRequest } from './runtime/task-profile.js';
9
+ import { classifyTaskProfile, IMPLEMENTATION_VERB_RE, isTaskProfile, looksLikeDatabaseWorkRequest } from './runtime/task-profile.js';
10
10
  import { legacyCoreSkillNames } from './codex-native/core-skill-manifest.js';
11
11
  export * from './routes/constants.js';
12
12
  export * from './routes/design-policy.js';
@@ -121,9 +121,6 @@ export function noUnrequestedFallbackCodePolicyText() {
121
121
  export function outcomeRubricPolicyText() {
122
122
  return 'Outcome rubric: apply the Core Engineering Directive, then use Proof Field, route-gate, reflection, and Honest Mode evidence to judge goal fit, touched surface, verification, and escalation.';
123
123
  }
124
- export function speedLanePolicyText() {
125
- return 'Proof Field speed lane policy: after the intended write scope is known, run or mentally apply `sks proof-field scan --intent "<goal>" --changed <files>`. Fast lanes keep the parent-owned minimal patch, listed verification, TriWiki validate, and Honest Mode; DB, security, visual-forensic, unknown surface, broad changes, failed verification, or unsupported claims fail closed to the normal Naruto/Honest path.';
126
- }
127
124
  export function hasFromChatImgSignal(prompt = '') {
128
125
  return /(?:^|\s)\$?(?:sks-)?from-chat-img(?:\s|:|$)/i.test(String(prompt || ''));
129
126
  }
@@ -233,7 +230,7 @@ export const ROUTES = [
233
230
  command: '$Naruto',
234
231
  mode: 'NARUTO',
235
232
  route: 'Codex official subagent workflow',
236
- description: '$Naruto runs explicit parallel work through Codex official subagents. The selected parent owns decomposition, integration, and scoped verification; standalone launches default to GPT-6 Astra. Delegate independent slices with disjoint writes, preserve the Astra-only child role profiles, honor explicit counts and measured host limits, and reuse returned capacity.',
233
+ description: '$Naruto runs implementation work through Codex official subagents. The parent orchestrates: it owns decomposition, integration, and scoped verification, spawns a child per disjoint slice, and does not implement slices itself; standalone launches default to the latest deep-tier model. Each child runs the newest model of the tier its work needs; Jev mode picks the tier on spawn, and a stored role preference wins. Honor explicit counts and measured host limits, and reuse returned capacity.',
237
234
  requiredSkills: ['naruto', 'pipeline-runner', 'prompt-pipeline', 'honest-mode'],
238
235
  dollarAliases: ['$Work'],
239
236
  appSkillAliases: ['work', 'from-chat-img'],
@@ -699,7 +696,7 @@ export const COMMAND_CATALOG = [
699
696
  { name: 'wiki', usage: 'sks wiki coords|pack|refresh|publish|rebuild-index|validate|validate-shared|wrongness ...', description: 'Build, refresh, publish shared shards, rebuild ignored indexes, validate, and attach wrongness-memory context to RGBA/trig LLM Wiki packs with attention.use_first and attention.hydrate_first for compact recall plus source hydration.' },
700
697
  { name: 'memory', usage: 'sks memory build [--json] | sks memory gc [--dry-run]', description: 'Project TriWiki context-pack memory into managed AGENTS.md blocks or run bounded memory cleanup.' },
701
698
  { name: 'hproof', usage: 'sks hproof check [mission-id|latest]', description: 'Evaluate the H-Proof done gate for a mission.' },
702
- { name: 'naruto', usage: 'sks naruto run \"task\" [--agents N] [--max-threads N] [--trusted-project] [--json] | sks naruto status|subagents|proof [latest|M-...] [--json] | sks naruto parent-summary --mission M-... --stdin [--json]', description: 'Run or inspect the Codex official subagent workflow with an Astra standalone parent default and Astra Low mechanical and instructed implementation, Astra Max judgment, and Astra Medium long-context/tool profiles, max_depth=1, and structured parent-thread completion evidence.' },
699
+ { name: 'naruto', usage: 'sks naruto run \"task\" [--agents N] [--max-threads N] [--trusted-project] [--json] | sks naruto status|subagents|proof [latest|M-...] [--json] | sks naruto parent-summary --mission M-... --stdin [--json]', description: 'Run or inspect the Codex official subagent workflow: an orchestrating parent (latest deep-tier standalone default), children on the newest model of their tier (Jev picks the tier on spawn when Jev mode is on), max_depth=1, and structured parent-thread completion evidence.' },
703
700
  { name: 'reasoning', usage: 'sks reasoning ["prompt"] [--json]', description: 'Show SKS temporary reasoning-effort routing: medium for simple tasks, high for logic, xhigh for research.' },
704
701
  { name: 'gx', usage: 'sks gx init|render|validate|drift|snapshot [name]', description: 'Create and verify deterministic SVG/HTML visual context cartridges.' },
705
702
  { name: 'profile', usage: 'sks profile show|set <model>', description: 'Inspect or set the current SKS model profile metadata.' },
@@ -1207,6 +1204,9 @@ export function narutoDecisionForRoute(route, prompt = '', profile = classifyTas
1207
1204
  if (NARUTO_GATE_SPECIALIZED_PARALLEL_ROUTE_IDS.has(routeId)) {
1208
1205
  return narutoRouteDecision('generic_naruto', routeId, profile, `specialized_route_default_parallel:${routeId}`, false);
1209
1206
  }
1207
+ if (routeId === 'Naruto' && profile === 'answer') {
1208
+ return narutoRouteDecision('generic_naruto', routeId, profile, 'naruto_route_work_without_change_verb', false);
1209
+ }
1210
1210
  if (profile === 'passthrough' || profile === 'answer' || profile === 'tiny-change') {
1211
1211
  return narutoRouteDecision('none', routeId, profile, `task_profile_${profile}_bypass`, true);
1212
1212
  }
@@ -1263,7 +1263,8 @@ export function reflectionRequiredForRoute(route) {
1263
1263
  export function looksLikeCodeChangingWork(prompt = '') {
1264
1264
  const text = String(prompt || '');
1265
1265
  return /\b(implement|build|make|add|edit|modify|change|fix|refactor|simplify|optimi[sz]e|improve|rewrite|migrate|create|delete|remove|rename|update|patch)\b/i.test(text)
1266
- || /(코드|구현|개발|수정|변경|추가|삭제|제거|최적화|개선|단순화|정리|해결|고쳐|바꿔|리팩터|마이그레이션)/i.test(text);
1266
+ || /(코드|구현|개발|수정|변경|추가|삭제|제거|최적화|개선|단순화|정리|해결|고쳐|바꿔|리팩터|마이그레이션)/i.test(text)
1267
+ || IMPLEMENTATION_VERB_RE.test(text);
1267
1268
  }
1268
1269
  export function classifyPromptExecutionEffect(prompt = '') {
1269
1270
  const text = String(prompt || '').trim();
@@ -1303,9 +1304,8 @@ export function subagentExecutionPolicyText(route, prompt = '') {
1303
1304
  return 'Subagent policy: not required for this task profile. Keep the work parent-owned unless a later, concrete decomposition reveals independent slices.';
1304
1305
  }
1305
1306
  return [
1306
- 'Codex subagent workflow: required for this explicit Naruto or parallel task.',
1307
- 'The parent agent owns decomposition, integration, scoped verification, and the final answer. Preserve its selected settings; every child uses gpt-6-astra with the sealed role effort, regardless of parent model or saved role preferences.',
1308
- 'Delegate only genuinely independent slices. Use Astra Low for tiny short-context mechanical work and instructed ordinary implementation, Astra Max for review/debug/planning/analysis/architecture/integration/risk judgment, and Astra Medium for long-context or Computer Use, Browser/Chrome, and image-generation execution. Explicit Astra effort preferences, including High, remain supported.',
1307
+ 'Codex subagent workflow: required. The parent orchestrates only: it decomposes the task into disjoint slices, spawns a child for each slice, waits, integrates, verifies, and writes the final answer. It does not implement slice work itself; the PreToolUse gate denies parent source edits before the first child starts and while children are running.',
1308
+ 'The parent keeps its user-selected model, effort, and service tier. Every child runs the newest model of the tier its work needs: fast for mechanical work, balanced for instructed implementation, context for long-context, browser, Computer Use, and image work, and deep for planning, review, debugging, and risk judgment. When Jev mode is on, Jev picks each spawn\'s tier and SKS seals it; do not pick child models yourself. A stored user role-model preference wins in both modes.',
1309
1309
  'Parallel writes require disjoint paths; serialize overlapping paths, prohibit nested delegation, avoid duplicate work, wait for all requested agent threads, and close completed threads after collecting results.',
1310
1310
  'Completion evidence comes from official SubagentStart/SubagentStop events plus the parent integration summary, not process counts or PID evidence.'
1311
1311
  ].join(' ');
@@ -16,7 +16,11 @@ const GENERIC_DATABASE_DOMAIN_RE = /\bDB\b|디비/i;
16
16
  const DATABASE_WORK_RE = /\b(?:apply|execute|run|fix|change|modify|migrate|audit|review|inspect|analy[sz]e|query|seed|backfill|optimi[sz]e|create|alter|drop|truncate|update|delete|repair)\b|적용|실행|수정|변경|검수|검토|점검|분석|조회|쿼리|시드|백필|최적화|생성|추가|삭제|복구|만들어/i;
17
17
  const DATABASE_CONTROL_SURFACE_META_RE = /\bsks\s+db\b|\b(?:db|database|migrate|migrations?)\b[\s\S]{0,48}\b(?:command|cli|route|routing|parser|classifier|regex|help|usage|topic|docs?|documentation|constant|keyword|alias)\b|\b(?:command|cli|route|routing|parser|classifier|regex|help|usage|topic|docs?|documentation|constant|keyword|alias)\b[\s\S]{0,48}\b(?:db|database|migrate|migrations?)\b|(?:db|디비|데이터베이스|migration|migrate|마이그레이션)\s*(?:커맨드|명령|명령어|CLI|라우트|라우팅|파서|분류기|정규식|도움말|헬프|사용법|토픽|문서|상수|키워드|별칭)|(?:커맨드|명령|명령어|CLI|라우트|라우팅|파서|분류기|정규식|도움말|헬프|사용법|토픽|문서|상수|키워드|별칭)[\s\S]{0,32}(?:db|디비|데이터베이스|migration|migrate|마이그레이션)/i;
18
18
  const PARALLEL_CUE_RE = /\b(parallel|subagents?|one agent per|fan out|independent slices?|naruto)\b|병렬|하위\s*에이전트|서브\s*에이전트|나루토|분담/i;
19
- const CHANGE_RE = /\b(fix|implement|implementation|change|edit|add|remove|delete|drop|modify|refactor|simplify|optimi[sz]e|improve|build|create|write|update|rename|rewrite|patch|apply|execute|repair|resolve|solve|publish|release|deploy|migrate)\b|\bwork\s+on\b|고쳐|고치|수정|변경|추가|삭제|제거|최적화|개선|단순화|정리|구현|리팩터|작성|생성|만들어|업데이트|적용|실행|해결|이름\s*변경|배포|출시|마이그레이션/i;
19
+ export const IMPLEMENTATION_VERB_RE = /\b(?:make|set\s+up|port|convert|wire(?:\s+up)?|hook\s+up|handle|integrate|connect|enable|disable|configure|upgrade|bump|replace|move|split|merge|extend|scaffold|introduce)\b|바꿔|바꾸|개발|붙여|연동|연결해|옮겨|설정해|세팅|셋업|넣어|합쳐|분리해|교체|도입|처리해|올려\s*(?:줘|주세요)|짜\s*(?:줘|주세요)/i;
20
+ const BASE_CHANGE_RE = /\b(fix|implement|implementation|change|edit|add|remove|delete|drop|modify|refactor|simplify|optimi[sz]e|improve|build|create|write|update|rename|rewrite|patch|apply|execute|repair|resolve|solve|publish|release|deploy|migrate)\b|\bwork\s+on\b|고쳐|고치|수정|변경|추가|삭제|제거|최적화|개선|단순화|정리|구현|리팩터|작성|생성|만들어|업데이트|적용|실행|해결|이름\s*변경|배포|출시|마이그레이션/i;
21
+ function hasChangeVerb(text) {
22
+ return BASE_CHANGE_RE.test(text) || IMPLEMENTATION_VERB_RE.test(text);
23
+ }
20
24
  const BOUNDED_READ_WORK_RE = /\b(audit|review|inspect|analy[sz]e|diagnose|trace|map|verify|test|check|investigate|evaluate)\b|감사|검토|점검|분석|진단|추적|매핑|검증|테스트|조사|평가/i;
21
25
  const TINY_CHANGE_RE = /\b(typo|copy|wording|label|spacing|whitespace|punctuation|spelling|one[-\s]?line|single[-\s]?(?:line|word)|rename only)\b|오타|문구|라벨|띄어쓰기|공백|맞춤법|구두점|한\s*줄|단어\s*하나|이름만\s*변경/i;
22
26
  const NON_RUNTIME_TINY_SURFACE_RE = /\b(copy|wording|label|spacing|whitespace|punctuation|spelling|readme|docs?|documentation|comment)\b|문구|라벨|띄어쓰기|공백|맞춤법|구두점|문서|주석/i;
@@ -30,18 +34,18 @@ export function classifyTaskProfile(prompt) {
30
34
  if (looksLikeExplanationQuestion(text))
31
35
  return 'answer';
32
36
  const databaseWork = looksLikeDatabaseWorkRequest(text);
33
- const highRiskMutation = databaseWork || (CHANGE_RE.test(text) && NON_DATABASE_HIGH_RISK_RE.test(text));
37
+ const highRiskMutation = databaseWork || (hasChangeVerb(text) && NON_DATABASE_HIGH_RISK_RE.test(text));
34
38
  if (looksLikeTinyChange(text) && (!highRiskMutation || NON_RUNTIME_TINY_SURFACE_RE.test(text)))
35
39
  return 'tiny-change';
36
40
  if (highRiskMutation)
37
41
  return 'high-risk';
38
- if (PARALLEL_CUE_RE.test(text) && CHANGE_RE.test(text))
42
+ if (PARALLEL_CUE_RE.test(text) && hasChangeVerb(text))
39
43
  return 'parallel-write';
40
44
  if (PARALLEL_CUE_RE.test(text))
41
45
  return 'parallel-read';
42
46
  if (DATABASE_CONTROL_SURFACE_META_RE.test(text) && DATABASE_WORK_RE.test(text))
43
47
  return 'bounded-work';
44
- if (CHANGE_RE.test(text))
48
+ if (hasChangeVerb(text))
45
49
  return 'bounded-work';
46
50
  if (BOUNDED_READ_WORK_RE.test(text))
47
51
  return 'bounded-work';
@@ -84,7 +88,7 @@ function matchIndexes(text, pattern) {
84
88
  .filter((index) => Number.isInteger(index));
85
89
  }
86
90
  function looksLikeTinyChange(text) {
87
- return CHANGE_RE.test(text) && TINY_CHANGE_RE.test(text);
91
+ return hasChangeVerb(text) && TINY_CHANGE_RE.test(text);
88
92
  }
89
93
  function looksLikeExplanationQuestion(text) {
90
94
  return (EXPLANATION_QUESTION_RE.test(text) || EXPLANATION_REQUEST_RE.test(text))
@@ -1,44 +1,37 @@
1
+ import { latestModelForTier } from './model-tiers.js';
1
2
  export const NARUTO_PARENT_MODEL = 'gpt-6-astra';
2
3
  export const NARUTO_PARENT_EFFORT = 'max';
4
+ export function narutoParentModel() {
5
+ return latestModelForTier('deep');
6
+ }
3
7
  export const ASTRA_SUBAGENT_MODEL = 'gpt-6-astra';
4
- export const NARUTO_LUNA_MODEL = 'gpt-5.6-luna';
5
- export const NARUTO_SOL_MODEL = 'gpt-5.6-sol';
6
- export const NARUTO_TERRA_MODEL = 'gpt-5.6-terra';
7
- export const LUNA_SUBAGENT_MODEL = ASTRA_SUBAGENT_MODEL;
8
- export const TERRA_SUBAGENT_MODEL = ASTRA_SUBAGENT_MODEL;
9
- export const SOL_SUBAGENT_MODEL = ASTRA_SUBAGENT_MODEL;
10
8
  export const LUNA_SUBAGENT_EFFORT = 'low';
11
9
  export const TERRA_SUBAGENT_EFFORT = 'medium';
12
10
  export const DEFAULT_SUBAGENT_EFFORT = 'low';
13
11
  export const SOL_MAX_SUBAGENT_EFFORT = 'max';
14
- export const DEFAULT_SUBAGENT_MODEL = SOL_SUBAGENT_MODEL;
15
- export const THINKING_SUBAGENT_MODEL = SOL_SUBAGENT_MODEL;
16
12
  export const SUBAGENT_EFFORT = SOL_MAX_SUBAGENT_EFFORT;
13
+ export function thinkingSubagentModel() {
14
+ return latestModelForTier('deep');
15
+ }
16
+ export function defaultSubagentModel() {
17
+ return latestModelForTier('deep');
18
+ }
19
+ function tierProfile(policy, kind, tier, modelReasoningEffort) {
20
+ return Object.freeze({
21
+ policy,
22
+ kind,
23
+ tier,
24
+ get model() {
25
+ return latestModelForTier(tier);
26
+ },
27
+ modelReasoningEffort
28
+ });
29
+ }
17
30
  export const SUBAGENT_MODEL_POLICIES = Object.freeze({
18
- luna_max_mechanical: Object.freeze({
19
- policy: 'luna_max_mechanical',
20
- kind: 'worker',
21
- model: LUNA_SUBAGENT_MODEL,
22
- modelReasoningEffort: LUNA_SUBAGENT_EFFORT
23
- }),
24
- sol_high_implementation: Object.freeze({
25
- policy: 'sol_high_implementation',
26
- kind: 'worker',
27
- model: SOL_SUBAGENT_MODEL,
28
- modelReasoningEffort: DEFAULT_SUBAGENT_EFFORT
29
- }),
30
- sol_max_judgment: Object.freeze({
31
- policy: 'sol_max_judgment',
32
- kind: 'expert',
33
- model: SOL_SUBAGENT_MODEL,
34
- modelReasoningEffort: SOL_MAX_SUBAGENT_EFFORT
35
- }),
36
- terra_max_context_tools: Object.freeze({
37
- policy: 'terra_max_context_tools',
38
- kind: 'worker',
39
- model: TERRA_SUBAGENT_MODEL,
40
- modelReasoningEffort: TERRA_SUBAGENT_EFFORT
41
- })
31
+ luna_max_mechanical: tierProfile('luna_max_mechanical', 'worker', 'fast', LUNA_SUBAGENT_EFFORT),
32
+ sol_high_implementation: tierProfile('sol_high_implementation', 'worker', 'balanced', DEFAULT_SUBAGENT_EFFORT),
33
+ sol_max_judgment: tierProfile('sol_max_judgment', 'expert', 'deep', SOL_MAX_SUBAGENT_EFFORT),
34
+ terra_max_context_tools: tierProfile('terra_max_context_tools', 'worker', 'context', TERRA_SUBAGENT_EFFORT)
42
35
  });
43
36
  const JUDGMENT_TASK_RE = new RegExp([
44
37
  '\\breview(?:er|ing)?\\b',
@@ -272,19 +265,8 @@ const IMPLEMENTATION_TASK_RE = new RegExp([
272
265
  const CLEAR_IMPLEMENTATION_ACTION_RE = /(?:^|[.!?]\s*|\bphase\s*:\s*)\s*(?:implement|build|create|add|modify|fix|code|refactor)\b|(?:^|[.!?]\s*)\s*(?:구현|개발|추가|수정|고쳐|코딩|리팩터)/i;
273
266
  const DOCUMENT_EXPLORATION_RE = /\b(?:read|scan|explore|compare|summarize|review)\b[^\n]{0,64}\b(?:docs?|documentation|manual|notes?|references?)\b|(?:문서|매뉴얼|노트|자료)[^\n]{0,32}(?:읽|탐색|조사|비교|정리|검토)/i;
274
267
  export function subagentModelProfile(policy) {
275
- return SUBAGENT_MODEL_POLICIES[policy];
276
- }
277
- export function narutoChildAssignment(policy) {
278
- switch (policy) {
279
- case 'luna_max_mechanical':
280
- return { model: NARUTO_LUNA_MODEL, effort: 'low' };
281
- case 'sol_high_implementation':
282
- return { model: NARUTO_SOL_MODEL, effort: 'low' };
283
- case 'terra_max_context_tools':
284
- return { model: NARUTO_TERRA_MODEL, effort: 'medium' };
285
- case 'sol_max_judgment':
286
- return { model: ASTRA_SUBAGENT_MODEL, effort: 'max' };
287
- }
268
+ const profile = SUBAGENT_MODEL_POLICIES[policy];
269
+ return { policy: profile.policy, kind: profile.kind, tier: profile.tier, model: profile.model, modelReasoningEffort: profile.modelReasoningEffort };
288
270
  }
289
271
  export function decideSubagentModel(input = {}) {
290
272
  const text = [
@@ -0,0 +1,153 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { codexHomePath } from '../codex-app/codex-model-catalog.js';
4
+ const MODELS_CACHE_FILENAME = 'models_cache.json';
5
+ function modelsCachePath(input) {
6
+ return path.join(codexHomePath(input), MODELS_CACHE_FILENAME);
7
+ }
8
+ export const MODEL_TIERS = Object.freeze(['fast', 'balanced', 'context', 'deep']);
9
+ export const MODEL_TIER_EFFORT = Object.freeze({
10
+ fast: 'low',
11
+ balanced: 'low',
12
+ context: 'medium',
13
+ deep: 'max'
14
+ });
15
+ export const MODEL_TIER_SUMMARY = Object.freeze({
16
+ fast: 'Fastest latest model. Mechanical edits, renames, formatting, and other one-step changes.',
17
+ balanced: 'Fast latest model. Simple coding whose result is already specified.',
18
+ context: 'Latest model at medium effort. Search, multi-file reading, broad exploration, and tool use.',
19
+ deep: 'Most capable latest model. Judgment, architecture, ambiguity, security, or high-stakes work.'
20
+ });
21
+ const TIER_FAMILIES = Object.freeze({
22
+ fast: ['luna'],
23
+ balanced: ['sol'],
24
+ context: ['terra', 'sol'],
25
+ deep: ['astra']
26
+ });
27
+ export const BUILTIN_LATEST_TIER_MODELS = Object.freeze({
28
+ fast: 'gpt-6-luna',
29
+ balanced: 'gpt-6-sol',
30
+ context: 'gpt-6-sol',
31
+ deep: 'gpt-6-astra'
32
+ });
33
+ const MODEL_ID_RE = /^gpt-(\d+(?:\.\d+)*)-([a-z]+)$/;
34
+ let memo = null;
35
+ export function resetLatestModelTierCache() {
36
+ memo = null;
37
+ }
38
+ export function resolveLatestModelTiers(input = {}) {
39
+ const file = modelsCachePath(input);
40
+ let stat = null;
41
+ try {
42
+ stat = fs.statSync(file);
43
+ }
44
+ catch {
45
+ stat = null;
46
+ }
47
+ const key = stat ? `${file}:${stat.mtimeMs}:${stat.size}` : `${file}:absent`;
48
+ if (memo?.key === key)
49
+ return memo.value;
50
+ const rows = stat && stat.isFile() && stat.size <= 16 * 1024 * 1024 ? readCandidateRows(file) : [];
51
+ const value = resolveFromRows(rows);
52
+ memo = { key, value };
53
+ return value;
54
+ }
55
+ export function latestModelForTier(tier, input = {}) {
56
+ return resolveLatestModelTiers(input).models[tier];
57
+ }
58
+ export function latestTierModelSet(input = {}) {
59
+ return new Set(Object.values(resolveLatestModelTiers(input).models));
60
+ }
61
+ export function isModelTier(value) {
62
+ return typeof value === 'string' && MODEL_TIERS.includes(value);
63
+ }
64
+ export function modelTierForModel(model) {
65
+ const match = MODEL_ID_RE.exec(String(model || '').trim());
66
+ if (!match)
67
+ return null;
68
+ const family = String(match[2] || '');
69
+ for (const tier of MODEL_TIERS) {
70
+ if (TIER_FAMILIES[tier][0] === family)
71
+ return tier;
72
+ }
73
+ return null;
74
+ }
75
+ export function effortForTier(tier, resolved = resolveLatestModelTiers()) {
76
+ const wanted = MODEL_TIER_EFFORT[tier];
77
+ const supported = resolved.supported_efforts[resolved.models[tier]] || [];
78
+ if (!supported.length || supported.includes(wanted))
79
+ return wanted;
80
+ const order = ['low', 'medium', 'high', 'max'];
81
+ const index = order.indexOf(wanted);
82
+ const byDistance = [...order].sort((a, b) => Math.abs(order.indexOf(a) - index) - Math.abs(order.indexOf(b) - index));
83
+ return byDistance.find((effort) => supported.includes(effort)) || wanted;
84
+ }
85
+ function readCandidateRows(file) {
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ const models = Array.isArray(parsed?.models) ? parsed.models : Array.isArray(parsed) ? parsed : [];
94
+ const rows = [];
95
+ for (const row of models.slice(0, 1024)) {
96
+ if (!row || typeof row !== 'object')
97
+ continue;
98
+ const slug = String(row.slug || row.model || row.id || '').trim();
99
+ const match = MODEL_ID_RE.exec(slug);
100
+ if (!match)
101
+ continue;
102
+ const visibility = String(row.visibility || 'list').toLowerCase();
103
+ if (visibility === 'hide' || visibility === 'hidden')
104
+ continue;
105
+ const efforts = Array.isArray(row.supported_reasoning_levels)
106
+ ? row.supported_reasoning_levels
107
+ .map((level) => String(typeof level === 'string' ? level : level?.effort || '').trim().toLowerCase())
108
+ .filter(Boolean)
109
+ : [];
110
+ rows.push({ slug, version: String(match[1] || '0').split('.').map(Number), family: String(match[2] || ''), efforts });
111
+ }
112
+ return rows;
113
+ }
114
+ function compareVersions(a, b) {
115
+ const length = Math.max(a.length, b.length);
116
+ for (let index = 0; index < length; index += 1) {
117
+ const diff = (a[index] || 0) - (b[index] || 0);
118
+ if (diff !== 0)
119
+ return diff;
120
+ }
121
+ return 0;
122
+ }
123
+ function newestRow(rows) {
124
+ return [...rows].sort((left, right) => compareVersions(right.version, left.version))[0] || null;
125
+ }
126
+ function resolveFromRows(rows) {
127
+ const models = { ...BUILTIN_LATEST_TIER_MODELS };
128
+ const supported = Object.fromEntries(rows.map((row) => [row.slug, row.efforts]));
129
+ let fromCache = true;
130
+ for (const tier of MODEL_TIERS) {
131
+ const families = TIER_FAMILIES[tier];
132
+ const candidates = rows
133
+ .filter((row) => families.includes(row.family))
134
+ .sort((left, right) => compareVersions(right.version, left.version)
135
+ || families.indexOf(left.family) - families.indexOf(right.family));
136
+ const pick = candidates[0] || newestRow(rows);
137
+ if (!pick) {
138
+ fromCache = false;
139
+ continue;
140
+ }
141
+ models[tier] = pick.slug;
142
+ }
143
+ return Object.freeze({
144
+ source: fromCache && rows.length ? 'models_cache' : 'builtin',
145
+ models: Object.freeze(models),
146
+ efforts: MODEL_TIER_EFFORT,
147
+ supported_efforts: Object.freeze(supported)
148
+ });
149
+ }
150
+ export function codexListedEfforts(model, input = {}) {
151
+ const efforts = resolveLatestModelTiers(input).supported_efforts[String(model || '').trim()];
152
+ return efforts && efforts.length ? efforts : null;
153
+ }
@@ -1,7 +1,7 @@
1
1
  import { uniqueStrings } from '../text/strings.js';
2
2
  import { NARUTO_CREDENTIAL_BOOLEAN_FLAGS, NARUTO_CREDENTIAL_VALUE_FLAGS } from './naruto-command-input-contract.js';
3
3
  import { resolveNarutoCredentialPolicy } from './naruto-host-credentials.js';
4
- import { DEFAULT_SUBAGENT_EFFORT, DEFAULT_SUBAGENT_MODEL, NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from './model-policy.js';
4
+ import { DEFAULT_SUBAGENT_EFFORT, defaultSubagentModel, NARUTO_PARENT_EFFORT, narutoParentModel } from './model-policy.js';
5
5
  import { HARD_NARUTO_MAX_THREADS } from './thread-budget.js';
6
6
  export function parseNarutoArgs(args) {
7
7
  const helpRequested = args.includes('--help') || args.includes('-h');
@@ -121,9 +121,9 @@ export function parseNarutoArgs(args) {
121
121
  credentialPolicy: resolveNarutoCredentialPolicy({
122
122
  args: action === 'run' ? optionArgs : [],
123
123
  env: action === 'run' ? process.env : {},
124
- defaultParentModel: NARUTO_PARENT_MODEL,
124
+ defaultParentModel: narutoParentModel(),
125
125
  defaultParentEffort: NARUTO_PARENT_EFFORT,
126
- defaultSubagentModel: DEFAULT_SUBAGENT_MODEL,
126
+ defaultSubagentModel: defaultSubagentModel(),
127
127
  defaultSubagentEffort: DEFAULT_SUBAGENT_EFFORT
128
128
  })
129
129
  };
@@ -1,4 +1,5 @@
1
- import { NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from './model-policy.js';
1
+ import { NARUTO_PARENT_EFFORT, narutoParentModel } from './model-policy.js';
2
+ import { resolveLatestModelTiers } from './model-tiers.js';
2
3
  import { NARUTO_ACTIONS } from '../safety/command-contract/types.js';
3
4
  import { DEFAULT_AUTOMATIC_SUBAGENT_COUNT, MAX_AUTOMATIC_REVIEWER_COUNT, MAX_AUTOMATIC_SUBAGENT_COUNT, MAX_CRITICAL_AUTOMATIC_REVIEWER_COUNT, MAX_MASS_AUTOMATIC_SUBAGENT_COUNT, officialSubagentRolePlan } from './agent-catalog.js';
4
5
  export const NARUTO_HELP_SCHEMA = 'sks.naruto-subagent-workflow.v1';
@@ -21,16 +22,18 @@ export function renderNarutoUsage() {
21
22
  ' --model-provider NAME Host config.toml provider block (host mode only).',
22
23
  ' --provider-env-key NAME Environment-variable name used by that provider.',
23
24
  ' --parent-model NAME Override the parent model identifier.',
24
- ' --parent-effort TIER Astra parent default: max; use model-advertised tiers.',
25
- ' --subagent-model NAME Child model; only gpt-6-astra is accepted.',
26
- ' --subagent-effort TIER Astra low|medium|high|xhigh|max|ultra; vary effort by task.',
25
+ ' --parent-effort TIER Parent default: max; use efforts the model lists.',
26
+ ' --subagent-model NAME Default child model; any current tier model (default: latest deep).',
27
+ ' --subagent-effort TIER low|medium|high|xhigh|max|ultra as the model lists; vary by task.',
27
28
  ' --no-forced-login-method Do not inject a forced login method.',
28
29
  ' --json Emit machine-readable output.',
29
30
  '',
30
- 'Default efforts: Low for instructed ordinary coding and tiny mechanical work, Medium for context/tools, Max for planning/analysis/review judgment; explicit High remains supported.',
31
- 'Automatic fan-out starts at 4/6/8, or 16 for eligible mass mechanical or exploration work on the Astra Low/Medium lanes.',
31
+ 'Model tiers: every child runs the newest model of its tier: fast for tiny mechanical work, balanced for instructed coding, context for reads and tools, deep for planning, analysis, and review judgment.',
32
+ 'Automatic fan-out starts at 4/6/8, or 16 for eligible mass mechanical or exploration work on the fast and context tiers.',
32
33
  'After decomposition, either lane may expand to 256 independent useful children.',
33
- 'A measured lower Codex host or explicit provider/API limit remains authoritative.'
34
+ 'A measured lower Codex host or explicit provider/API limit remains authoritative.',
35
+ 'Jev mode on: Jev picks each Codex App child spawn\'s tier and SKS seals its newest model; a stored role preference wins.',
36
+ 'The parent orchestrates only; the PreToolUse gate denies parent source edits before the first child starts and while children run.'
34
37
  ].join('\n');
35
38
  }
36
39
  export function buildNarutoHelpResult() {
@@ -53,7 +56,7 @@ export function buildNarutoHelpResult() {
53
56
  automatic_subagent_ceiling: MAX_AUTOMATIC_SUBAGENT_COUNT,
54
57
  mass_automatic_subagent_ceiling: MAX_MASS_AUTOMATIC_SUBAGENT_COUNT,
55
58
  absolute_hard_frame_cap: 256,
56
- fanout_contract: 'automatic fan-out starts at 4/6/8/16 for bounded, explicit-parallel, large-scale, and mass mechanical or exploration work on the Astra Low/Medium lanes; after decomposition both lanes may expand to the 256-child SKS ceiling, max_threads defaults to a 256-child frame budget that is a cap rather than a target, measured lower Codex host and explicit provider/API limits remain authoritative, and later waves reuse capacity',
59
+ fanout_contract: 'automatic fan-out starts at 4/6/8/16 for bounded, explicit-parallel, large-scale, and mass mechanical or exploration work on the fast and context tiers; after decomposition both lanes may expand to the 256-child SKS ceiling, max_threads defaults to a 256-child frame budget that is a cap rather than a target, measured lower Codex host and explicit provider/API limits remain authoritative, and later waves reuse capacity',
57
60
  automatic_reviewer_ceiling: MAX_AUTOMATIC_REVIEWER_COUNT,
58
61
  critical_multi_domain_reviewer_ceiling: MAX_CRITICAL_AUTOMATIC_REVIEWER_COUNT,
59
62
  max_threads_is_cap_not_target: true,
@@ -62,17 +65,19 @@ export function buildNarutoHelpResult() {
62
65
  triwiki_context: 'bounded_attention_use_first_with_on_demand_hydration',
63
66
  model_routing_policy: {
64
67
  luna_max: 'tiny_short_context_mechanical_and_mass_shards',
65
- sol_high: 'astra_low_instructed_ordinary_ui_logic_backend_and_native_implementation',
66
- sol_max: 'astra_max_review_debug_planning_architecture_security_database_research_release_and_judgment',
67
- terra_max: 'astra_medium_broad_search_exploration_long_context_long_term_memory_large_first_draft_computer_use_browser_chrome_and_image_generation_execution',
68
- mixed_slice_rule: 'split_execution_from_judgment_when_possible_otherwise_astra_max_wins'
68
+ sol_high: 'balanced_tier_instructed_ordinary_ui_logic_backend_and_native_implementation',
69
+ sol_max: 'deep_tier_review_debug_planning_architecture_security_database_research_release_and_judgment',
70
+ terra_max: 'context_tier_broad_search_exploration_long_context_long_term_memory_large_first_draft_computer_use_browser_chrome_and_image_generation_execution',
71
+ mixed_slice_rule: 'split_execution_from_judgment_when_possible_otherwise_deep_tier_wins',
72
+ tier_models: 'each tier resolves to the newest model the Codex models cache lists; no model family is pinned',
73
+ current_tier_models: resolveLatestModelTiers().models
69
74
  },
70
75
  completion_evidence: {
71
76
  lifecycle_events: ['SubagentStart', 'SubagentStop'],
72
77
  stop_is_success_evidence: false,
73
78
  structured_parent_summary: 'subagent-parent-summary.json'
74
79
  },
75
- parent: { model: NARUTO_PARENT_MODEL, model_reasoning_effort: NARUTO_PARENT_EFFORT },
80
+ parent: { model: narutoParentModel(), model_reasoning_effort: NARUTO_PARENT_EFFORT },
76
81
  agent_catalog_mode: 'full_catalog_only_on_explicit_help',
77
82
  agents: officialSubagentRolePlan()
78
83
  };
@@ -1,4 +1,5 @@
1
- import { ASTRA_SUBAGENT_MODEL, LUNA_SUBAGENT_MODEL } from './model-policy.js';
1
+ import { defaultSubagentModel } from './model-policy.js';
2
+ import { codexListedEfforts, latestTierModelSet } from './model-tiers.js';
2
3
  export const NARUTO_AUTH_MODES = ['managed', 'host'];
3
4
  const IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
4
5
  const RETIRED_DIRECT_MANAGED_PROVIDERS = new Set(['codex-lb', 'openrouter']);
@@ -80,7 +81,7 @@ export function resolveNarutoCredentialPolicy(input) {
80
81
  const models = [
81
82
  { key: 'parentModel', flag: '--parent-model', envKey: 'SKS_NARUTO_PARENT_MODEL', fallback: input.defaultParentModel, effort: false },
82
83
  { key: 'parentEffort', flag: '--parent-effort', envKey: 'SKS_NARUTO_PARENT_EFFORT', fallback: input.defaultParentEffort, effort: true },
83
- { key: 'subagentModel', flag: '--subagent-model', envKey: 'SKS_NARUTO_SUBAGENT_MODEL', fallback: ASTRA_SUBAGENT_MODEL, effort: false },
84
+ { key: 'subagentModel', flag: '--subagent-model', envKey: 'SKS_NARUTO_SUBAGENT_MODEL', fallback: defaultSubagentModel(), effort: false },
84
85
  { key: 'subagentEffort', flag: '--subagent-effort', envKey: 'SKS_NARUTO_SUBAGENT_EFFORT', fallback: input.defaultSubagentEffort, effort: true }
85
86
  ];
86
87
  const resolvedModels = {};
@@ -103,9 +104,9 @@ export function resolveNarutoCredentialPolicy(input) {
103
104
  resolvedModels[entry.key] = raw.value;
104
105
  sources[entry.key] = raw.source;
105
106
  }
106
- if (resolvedModels.subagentModel !== ASTRA_SUBAGENT_MODEL) {
107
- blockers.push('naruto_subagent_model_must_be_astra');
108
- resolvedModels.subagentModel = ASTRA_SUBAGENT_MODEL;
107
+ if (!latestTierModelSet().has(String(resolvedModels.subagentModel))) {
108
+ blockers.push('naruto_subagent_model_must_be_current');
109
+ resolvedModels.subagentModel = defaultSubagentModel();
109
110
  }
110
111
  validateGpt56EffortPair('parent', String(resolvedModels.parentModel), String(resolvedModels.parentEffort), blockers);
111
112
  validateGpt56EffortPair('subagent', String(resolvedModels.subagentModel), String(resolvedModels.subagentEffort), blockers);
@@ -146,15 +147,9 @@ export function resolveNarutoCredentialPolicy(input) {
146
147
  };
147
148
  }
148
149
  function validateGpt56EffortPair(scope, model, effort, blockers) {
149
- const allowed = model === ASTRA_SUBAGENT_MODEL
150
- ? ['low', 'medium', 'high', 'xhigh', 'max', 'ultra']
151
- : model === LUNA_SUBAGENT_MODEL || model === 'gpt-5.6-terra'
152
- ? ['max']
153
- : model === 'gpt-5.6-sol'
154
- ? scope === 'parent' ? ['max'] : ['high', 'max']
155
- : null;
150
+ const allowed = codexListedEfforts(model);
156
151
  if (allowed && !allowed.includes(effort)) {
157
- blockers.push(`naruto_${scope}_gpt56_effort_policy_mismatch:${model}:${effort}:allowed_${allowed.join('_or_')}`);
152
+ blockers.push(`naruto_${scope}_effort_unsupported:${model}:${effort}:allowed_${allowed.join('_or_')}`);
158
153
  }
159
154
  }
160
155
  export function narutoCredentialConfigArgs(policy) {
@@ -5,7 +5,8 @@ import { parse } from 'smol-toml';
5
5
  import { ensureDir, exists, PACKAGE_VERSION, readText, sha256, writeTextAtomic } from '../fsx.js';
6
6
  import { ensureConfinedDirectory, inspectConfinedPath } from '../managed-path-safety.js';
7
7
  import { MANAGED_OFFICIAL_SUBAGENT_ROLES, managedOfficialSubagentRoleContent, managedOfficialSubagentRoleOwnsText } from '../managed-assets/managed-assets-manifest.js';
8
- import { DEFAULT_SUBAGENT_EFFORT, DEFAULT_SUBAGENT_MODEL } from './model-policy.js';
8
+ import { DEFAULT_SUBAGENT_EFFORT, defaultSubagentModel as latestDefaultSubagentModel } from './model-policy.js';
9
+ import { latestTierModelSet } from './model-tiers.js';
9
10
  import { HARD_NARUTO_MAX_THREADS } from './thread-budget.js';
10
11
  import { escapeRegExp } from '../text/regex.js';
11
12
  export const DEFAULT_OFFICIAL_SUBAGENT_MAX_THREADS = 256;
@@ -13,7 +14,9 @@ export const DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH = 1;
13
14
  export const DEFAULT_OFFICIAL_SUBAGENT_JOB_MAX_RUNTIME_SECONDS = 1200;
14
15
  export const DEFAULT_OFFICIAL_SUBAGENT_INTERRUPT_MESSAGE = true;
15
16
  export const DEFAULT_OFFICIAL_SUBAGENT_ENABLED = true;
16
- export const DEFAULT_OFFICIAL_SUBAGENT_MODEL = DEFAULT_SUBAGENT_MODEL;
17
+ export function defaultOfficialSubagentModel() {
18
+ return latestDefaultSubagentModel();
19
+ }
17
20
  export const DEFAULT_OFFICIAL_SUBAGENT_REASONING_EFFORT = DEFAULT_SUBAGENT_EFFORT;
18
21
  export const DEFAULT_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION = DEFAULT_OFFICIAL_SUBAGENT_MAX_THREADS + 1;
19
22
  export const LEGACY_SKS_MAX_THREAD_VALUES = Object.freeze([4, 5, 6, 12]);
@@ -49,7 +52,8 @@ export function mergeOfficialSubagentConfigResult(text = '', opts = {}) {
49
52
  next = upsertDefaultUnlessInherited(next, inheritedAgents, 'enabled', `enabled = ${DEFAULT_OFFICIAL_SUBAGENT_ENABLED}`);
50
53
  next = upsertDefaultUnlessInherited(next, inheritedAgents, 'max_depth', `max_depth = ${DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH}`);
51
54
  next = upsertDefaultUnlessInherited(next, inheritedAgents, 'interrupt_message', `interrupt_message = ${DEFAULT_OFFICIAL_SUBAGENT_INTERRUPT_MESSAGE}`);
52
- next = upsertTomlTableKey(next, 'agents', `default_subagent_model = "${DEFAULT_OFFICIAL_SUBAGENT_MODEL}"`);
55
+ const existingDefault = /^\s*default_subagent_model\s*=\s*"([^"]*)"/m.exec(next)?.[1] || '';
56
+ next = upsertTomlTableKey(next, 'agents', `default_subagent_model = "${latestTierModelSet().has(existingDefault) ? existingDefault : defaultOfficialSubagentModel()}"`);
53
57
  next = upsertDefaultUnlessInherited(next, inheritedAgents, 'default_subagent_reasoning_effort', `default_subagent_reasoning_effort = "${DEFAULT_OFFICIAL_SUBAGENT_REASONING_EFFORT}"`);
54
58
  next = mergeOfficialMultiAgentV2FeatureConfig(next, {
55
59
  sksOwned: opts.sksOwned === true,
@@ -136,17 +140,17 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
136
140
  const effectiveMaxThreads = Math.min(maxThreads.value, HARD_NARUTO_MAX_THREADS);
137
141
  const maxDepth = resolveLayeredValue(projectLayer.agents.max_depth, globalLayer.agents.max_depth, DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH, positiveInteger);
138
142
  const interruptMessage = resolveLayeredValue(projectLayer.agents.interrupt_message, globalLayer.agents.interrupt_message, DEFAULT_OFFICIAL_SUBAGENT_INTERRUPT_MESSAGE, booleanValue);
139
- const defaultSubagentModel = resolveLayeredValue(projectLayer.agents.default_subagent_model, globalLayer.agents.default_subagent_model, DEFAULT_OFFICIAL_SUBAGENT_MODEL, nonEmptyString);
143
+ const defaultSubagentModel = resolveLayeredValue(projectLayer.agents.default_subagent_model, globalLayer.agents.default_subagent_model, defaultOfficialSubagentModel(), nonEmptyString);
140
144
  const defaultSubagentReasoningEffort = resolveLayeredValue(projectLayer.agents.default_subagent_reasoning_effort, globalLayer.agents.default_subagent_reasoning_effort, DEFAULT_OFFICIAL_SUBAGENT_REASONING_EFFORT, nonEmptyString);
141
145
  const multiAgentV2 = resolveMultiAgentV2Layer(projectLayer.features.multi_agent_v2, globalLayer.features.multi_agent_v2, effectiveMaxThreads);
142
146
  const effectiveMultiAgentV2 = {
143
147
  ...multiAgentV2.value,
144
148
  maxConcurrentThreadsPerSession: Math.min(multiAgentV2.value.maxConcurrentThreadsPerSession, HARD_NARUTO_MAX_THREADS + 1)
145
149
  };
146
- const modelCoerced = defaultSubagentModel.value !== DEFAULT_OFFICIAL_SUBAGENT_MODEL;
150
+ const modelCoerced = !latestTierModelSet().has(String(defaultSubagentModel.value));
147
151
  const depthCoerced = maxDepth.value > 1;
148
152
  const warnings = [
149
- ...(modelCoerced ? [`official_subagent_model_coerced_to_astra:${defaultSubagentModel.value}:${defaultSubagentModel.source}`] : []),
153
+ ...(modelCoerced ? [`official_subagent_model_coerced_to_latest:${defaultSubagentModel.value}:${defaultSubagentModel.source}`] : []),
150
154
  ...(depthCoerced ? [`official_subagent_max_depth_coerced_to_one:${maxDepth.value}:${maxDepth.source}`] : []),
151
155
  ...capacityNormalizationWarnings(maxThreads, multiAgentV2),
152
156
  ...(projectLayer.legacyWarnings),
@@ -158,7 +162,7 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
158
162
  maxDepth: depthCoerced ? DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH : maxDepth.value,
159
163
  jobMaxRuntimeSeconds: null,
160
164
  interruptMessage: interruptMessage.value,
161
- defaultSubagentModel: DEFAULT_OFFICIAL_SUBAGENT_MODEL,
165
+ defaultSubagentModel: modelCoerced ? defaultOfficialSubagentModel() : String(defaultSubagentModel.value),
162
166
  defaultSubagentReasoningEffort: defaultSubagentReasoningEffort.value,
163
167
  multiAgentV2: effectiveMultiAgentV2,
164
168
  sources: {