sneakoscope 4.6.5 → 4.7.1

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 (48) hide show
  1. package/README.md +18 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/install-helpers.js +80 -15
  7. package/dist/commands/codex-app.js +34 -3
  8. package/dist/commands/doctor.js +1 -1
  9. package/dist/core/agents/agent-effort-policy.js +110 -5
  10. package/dist/core/agents/agent-orchestrator.js +27 -3
  11. package/dist/core/agents/agent-plan.js +2 -2
  12. package/dist/core/agents/agent-roster.js +8 -0
  13. package/dist/core/agents/agent-worker-slot.js +4 -0
  14. package/dist/core/codex/agent-config-file-repair.js +4 -4
  15. package/dist/core/codex-app/codex-app-fast-ui-repair.js +4 -2
  16. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +14 -3
  17. package/dist/core/codex-app/glm-model-profile.js +1 -1
  18. package/dist/core/codex-app/glm-profile-installer.js +111 -7
  19. package/dist/core/codex-app/glm-profile-schema.js +21 -2
  20. package/dist/core/codex-app.js +2 -0
  21. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  22. package/dist/core/codex-model-guard.js +3 -1
  23. package/dist/core/commands/fast-mode-command.js +5 -2
  24. package/dist/core/commands/glm-command.js +2 -1
  25. package/dist/core/commands/mad-sks-command.js +24 -0
  26. package/dist/core/doctor/doctor-codex-startup-repair.js +111 -9
  27. package/dist/core/feature-registry.js +3 -3
  28. package/dist/core/fsx.js +1 -1
  29. package/dist/core/harness-guard.js +89 -3
  30. package/dist/core/init.js +13 -16
  31. package/dist/core/managed-assets/managed-assets-manifest.js +9 -8
  32. package/dist/core/pipeline-internals/runtime-core.js +3 -3
  33. package/dist/core/providers/glm/glm-52-profile.js +46 -0
  34. package/dist/core/providers/glm/glm-52-request.js +11 -2
  35. package/dist/core/providers/glm/glm-direct-run.js +1 -0
  36. package/dist/core/providers/glm/glm-mad-launch.js +3 -2
  37. package/dist/core/providers/glm/glm-profile-resolver.js +69 -4
  38. package/dist/core/providers/glm/naruto/glm-naruto-command.js +2 -1
  39. package/dist/core/team-live.js +2 -2
  40. package/dist/core/version.js +1 -1
  41. package/dist/scripts/agent-role-config-repair-check.js +5 -3
  42. package/dist/scripts/codex-native-reference-cache-check.js +8 -1
  43. package/dist/scripts/doctor-codex-startup-repair-check.js +21 -0
  44. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +6 -2
  45. package/dist/scripts/naruto-shadow-clone-swarm-check.js +11 -3
  46. package/dist/scripts/release-readiness-report.js +1 -1
  47. package/dist/scripts/sks-3-1-6-directive-check-lib.js +11 -0
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -49,7 +49,7 @@ sks seo-geo-optimizer apply latest --mode seo --apply --json
49
49
  sks seo-geo-optimizer audit --mode geo --target package --offline --json
50
50
  ```
51
51
 
52
- > 📋 **Current release: `v4.6.5`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
52
+ > 📋 **Current release: `v4.7.0`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
53
53
 
54
54
  ## 🍥 Parallelism, UX, And Integrations
55
55
 
@@ -321,6 +321,8 @@ sks fix-path
321
321
  sks update now
322
322
  ```
323
323
 
324
+ `sks doctor --fix` repairs stale SKS-managed Codex TOML, agent role `config_file` paths, and unsupported managed role fields. It now dedupes repeated TOML tables before repair, keeps the first existing external MCP block such as `context7` or `supabase` unchanged, and only removes/repairs SKS-owned stale MCP blocks such as broken `node_repl`.
325
+
324
326
  ### Open Codex CLI With Zellij
325
327
 
326
328
  ```sh
@@ -330,7 +332,7 @@ sks team open-zellij latest
330
332
  sks team attach-zellij latest
331
333
  ```
332
334
 
333
- Interactive SKS sessions use Zellij layouts. By default SKS launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, the selected `model_reasoning_effort`, and `--no-alt-screen` for Zellij-backed interactive panes so terminal scrollback captures the conversation transcript. Non-GLM SKS sessions force the model to `gpt-5.5`; `sks --mad --glm` is the OpenRouter GLM 5.2 exception. `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove the non-GLM model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort, and `SKS_ZELLIJ_CODEX_ALT_SCREEN=1` restores Codex's alternate-screen UI for the next launch. Use `sks --mad --workspace <name>` for an explicit MAD session and `sks help` for CLI help.
335
+ Interactive SKS sessions use Zellij layouts. By default SKS launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, the selected `model_reasoning_effort`, and `--no-alt-screen` for Zellij-backed interactive panes so terminal scrollback captures the conversation transcript. Non-GLM SKS sessions force the model to `gpt-5.5`, while the model guard also recognizes `gpt-5.4-mini` as a supported GPT-5.4 mini model; `sks --mad --glm` is the OpenRouter GLM 5.2 exception. `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove the non-GLM model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort, and `SKS_ZELLIJ_CODEX_ALT_SCREEN=1` restores Codex's alternate-screen UI for the next launch. Use `sks --mad --workspace <name>` for an explicit MAD session and `sks help` for CLI help.
334
336
 
335
337
  Before opening the interactive runtime, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens the runtime with the updated Codex CLI.
336
338
 
@@ -338,6 +340,7 @@ For [codex-lb](https://github.com/Soju06/codex-lb), start the server, create a d
338
340
 
339
341
  ```sh
340
342
  sks codex-lb setup --host https://your-codex-lb.example.com --api-key "sk-clb-..."
343
+ sks codex-lb setup --host https://your-codex-lb.example.com --api-key-stdin --yes
341
344
  sks codex-lb health
342
345
  sks codex-lb repair
343
346
  sks
@@ -353,6 +356,15 @@ sks codex-lb set-key --api-key-stdin # or: sks codex-lb set-key --api-key "sk-
353
356
 
354
357
  (To also change the host, use `sks codex-lb reconfigure --host <domain> --api-key <key>`.)
355
358
 
359
+ For OpenRouter-backed GLM 5.2 in Codex Desktop/SKS, store the key and install the GLM provider/profiles with:
360
+
361
+ ```sh
362
+ sks codex-app set-openrouter-key --api-key-stdin
363
+ sks codex-app glm-profile install
364
+ ```
365
+
366
+ This writes the OpenRouter key to the SKS user secret store, writes redacted metadata only, and installs Codex Desktop-compatible `openrouter` provider plus selectable `sks-glm-52-*` reasoning profiles. The npm package does not patch the Codex Desktop macOS menu binary; a top-level `Set OpenRouter Key` menu/modal requires upstream Codex Desktop UI support. The package-owned command surface above is the supported repair path.
367
+
356
368
  ### Switching auth mode: codex-lb ↔ ChatGPT OAuth
357
369
 
358
370
  Switch between the codex-lb API key and your ChatGPT OAuth login at any time with intent-named commands:
@@ -390,7 +402,7 @@ sks --mad
390
402
  sks --mad --allow-package-install --allow-service-control --allow-network --yes
391
403
  ```
392
404
 
393
- This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` xhigh maintenance profile, opens the MAD-SKS permission gate for that Zellij run, starts a same-mission read-only native agent swarm, and launches a Codex CLI layout whose right-side lanes read that MAD ledger. Bare `sks --mad` grants target-project file and shell scope only; add explicit `--allow-*` flags for packages, services, network, Computer Use, browser use, generated assets, file permissions, DB writes, or other high-risk scopes. MAD-SKS is not a DB-only unlock and does not create a MadDB capability. Catastrophic database wipe/all-row/project-management safeguards remain active outside the first-class MadDB route, and the pipeline contract still forbids unrequested fallback implementation code.
405
+ This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` xhigh maintenance profile, opens the MAD-SKS permission gate for that Zellij run, starts a same-mission bounded native agent swarm, and launches a Codex CLI layout whose right-side lanes read that MAD ledger. Bare `sks --mad` grants target-project file and shell scope only; add explicit `--allow-*` flags for packages, services, network, Computer Use, browser use, generated assets, file permissions, DB writes, or other high-risk scopes. MAD-SKS is not a DB-only unlock and does not create a MadDB capability. Catastrophic database wipe/all-row/project-management safeguards remain active outside the first-class MadDB route, and the pipeline contract still forbids unrequested fallback implementation code.
394
406
 
395
407
  Before launching, SKS checks npm for a newer `sneakoscope` and prints a non-blocking update notice when one is available; use `sks update now` or `sks doctor --fix` when you want SKS to update itself. Use `--yes` to approve missing dependency installs automatically. Tune MAD swarm startup with `--mad-agents <n>`, `--mad-swarm-work-items <n>`, and `--mad-swarm-backend <backend>`; `--no-mad-swarm` keeps only the cockpit UI if you need a temporary fallback.
396
408
 
@@ -429,7 +441,7 @@ Manual fan-out syntax:
429
441
  - Team prompt role counts: `$Team <task> executor:8 reviewer:5`
430
442
  - Team CLI flag: `sks team "<task>" --agents 8`
431
443
 
432
- Effort is assigned per agent. Simple read-only/docs slices can run low, ordinary tooling and lease mapping use medium, safety/DB/schema/release lanes use high, and frontier/forensic research can escalate to xhigh. If a lease conflict, schema failure, proof blocker, DB risk, or release risk appears, the parent can escalate that lane while keeping unrelated lanes cheaper and faster.
444
+ Effort and model tier are assigned per agent. Managed native agents use bounded workspace-write TOML profiles, but every write still needs an assigned lease, non-overlap proof, and parent-owned integration. When the main model is GPT, simple bounded code/docs slices can downshift to `gpt-5.4-mini`, ordinary tool/lease work uses `gpt-5.5` with low model reasoning, and safety/DB/schema/release lanes use `gpt-5.5` with high model reasoning. When the main model is GLM, native workers stay locked to `z-ai/glm-5.2` and receive GLM effort tiers (`minimal`, `low`, `high`, or `xhigh`) instead of falling back to GPT. If a lease conflict, schema failure, proof blocker, DB risk, or release risk appears, the parent can escalate that lane while keeping unrelated lanes cheaper and faster.
433
445
 
434
446
  ### Naruto Massive Parallel Work Swarm (`$Naruto`)
435
447
 
@@ -528,6 +540,8 @@ sks codex-app remote-control -- --help
528
540
 
529
541
  `sks codex-app check` reports whether the installed Codex CLI is new enough, whether the required app flags are visible, whether Fast/speed-selector config is unlocked, whether Codex App Git Actions can use Commit, Push, Commit and Push, and PR flows, whether the Codex Chrome Extension path is ready for web/browser/webapp verification, and whether installed OpenAI default plugins such as Browser, Chrome, Computer Use, Documents, Presentations, Spreadsheets, and LaTeX are enabled. `sks-fast-high` intentionally does not pin `sandbox_mode`, so the Codex App/IDE permissions selector owns Full Access vs workspace-write while SKS supplies the model, Fast service tier, approval, and reasoning defaults. `sks codex-app chrome-extension --json` is the rapid preflight for web QA/UX/browser routes. When codex-lb is configured, SKS keeps it selected as the top-level Codex App provider while still preserving required app flags and plugin settings. Codex CLI 0.130.0+ app-server/remote-control threads can pick up config changes live; older CLI/TUI sessions should still be restarted after `.codex/config.toml` or MCP/plugin changes.
530
542
 
543
+ For GLM in Codex Desktop, run `sks codex-app glm-profile install`. This writes the OpenRouter provider and selectable GLM 5.2 profiles into `~/.codex/config.toml`: `sks-glm-52-mad`, `sks-glm-52-minimal`, `sks-glm-52-low`, `sks-glm-52-medium`, `sks-glm-52-high`, and `sks-glm-52-xhigh`. Each profile pins `model = "z-ai/glm-5.2"` and the matching `model_reasoning_effort`, so Desktop profile selection can choose both GLM and the reasoning level without editing TOML by hand.
544
+
531
545
  For web-related verification, SKS follows the official Codex Chrome Extension setup path first: https://developers.openai.com/codex/app/chrome-extension. `$QA-LOOP`, `$UX-Review`, `$Image-UX-Review`, browser smoke, authenticated web checks, localhost checks, and web visual review must halt quickly if that extension is missing or disabled. Only after the user says the extension setup is complete should the pipeline resume. Codex Computer Use is for native Mac/non-web targets only; it must not be used as browser/web-app verification evidence.
532
546
 
533
547
  Imagegen is a core SKS capability, not a decorative add-on. `$Image-UX-Review`, `$UX-Review`, `$Visual-Review`, `$UI-UX-Review`, and PPT generated-review paths require real Codex App `$imagegen`/`gpt-image-2` output before full visual verification can pass. For newest-model image requests, prompts should say "Use ChatGPT Images 2.0 / GPT Image 2.0 with gpt-image-2" while still invoking Codex App `$imagegen` when live generation is needed. Use `imagegen-source-scout` when current official docs plus X/social prompt-workflow signals are needed; social sources are prompt heuristics only, not capability or evidence specs. `npm run imagegen:capability` checks that the official Codex App imagegen surface is visible and records that capability detection is not output proof; OpenAI API, Responses image-generation, codex-lb, or `CODEX_LB_API_KEY` fallbacks are non-Codex paths and do not satisfy Codex App generated-image evidence unless a separate API fallback task is explicitly requested. The README architecture asset uses the same rule: run `npm run imagegen:readme-architecture:prompt` to print/write the official prompt, generate the image in Codex App `$imagegen`, then rerun `npm run imagegen:readme-architecture -- --output <path>` after Codex App creates a real gpt-image-2 output. When exactly one current generated_images candidate exists after the prompt, `npm run imagegen:readme-architecture -- --auto-pick-latest` can select it automatically. To let the verifier wait while Codex App writes the file, use `npm run imagegen:readme-architecture -- --wait-ms <milliseconds>`; it still accepts only one current candidate under `$CODEX_HOME/generated_images`. Env forms such as `SKS_CODEX_APP_IMAGEGEN_OUTPUT=<path>` remain supported for automation. Use the selected file directly under `$CODEX_HOME/generated_images`; moved or copied files are not accepted as provenance evidence. Disabled or missing `image_generation` remains a blocker that `sks codex-app check`, `npm run imagegen:capability`, and selftest cover.
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "4.6.5"
79
+ version = "4.7.0"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "4.6.5"
3
+ version = "4.7.0"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 4.6.5"),
7
+ Some("--version") => println!("sks-rs 4.7.0"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '4.6.5';
2
+ const FAST_PACKAGE_VERSION = '4.7.0';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -14,6 +14,8 @@ import { checkZellijCapability } from '../core/zellij/zellij-capability.js';
14
14
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
15
15
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
16
16
  import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
17
+ import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from '../core/providers/glm/glm-52-profile.js';
18
+ import { GLM_52_OPENROUTER_MODEL } from '../core/providers/glm/glm-52-settings.js';
17
19
  import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
18
20
  import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
19
21
  const DEFAULT_CODEX_APP_PLUGINS = [
@@ -1499,6 +1501,63 @@ export function upsertCodexLbConfig(text = '', baseUrl, selectDefault = true) {
1499
1501
  next = upsertTomlTable(next, 'model_providers.codex-lb', block);
1500
1502
  return `${next.trim()}\n`;
1501
1503
  }
1504
+ export function upsertCodexAppGlmConfig(text = '') {
1505
+ let next = String(text || '');
1506
+ const providerBlock = [
1507
+ `[model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}]`,
1508
+ 'name = "OpenRouter"',
1509
+ 'base_url = "https://openrouter.ai/api/v1"',
1510
+ 'wire_api = "responses"',
1511
+ 'env_key = "OPENROUTER_API_KEY"',
1512
+ 'requires_openai_auth = false'
1513
+ ].join('\n');
1514
+ next = upsertTomlTable(next, `model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}`, providerBlock);
1515
+ for (const profile of GLM_CODEX_CONFIG_REASONING_PROFILES) {
1516
+ const profileBlock = [
1517
+ `[profiles.${profile.id}]`,
1518
+ `model_provider = "${GLM_CODEX_CONFIG_PROVIDER_ID}"`,
1519
+ `model = "${GLM_52_OPENROUTER_MODEL}"`,
1520
+ `model_reasoning_effort = "${profile.reasoning_effort}"`,
1521
+ 'service_tier = "default"',
1522
+ 'approval_policy = "on-request"'
1523
+ ].join('\n');
1524
+ next = upsertTomlTable(next, `profiles.${profile.id}`, profileBlock);
1525
+ }
1526
+ return `${next.trim()}\n`;
1527
+ }
1528
+ export async function ensureGlobalCodexAppGlmProfile(opts = {}) {
1529
+ if (process.env.SKS_SKIP_CODEX_GLM_PROFILE_REPAIR === '1' && opts.force !== true) {
1530
+ return { ok: true, status: 'skipped', reason: 'SKS_SKIP_CODEX_GLM_PROFILE_REPAIR=1' };
1531
+ }
1532
+ const home = opts.home || process.env.HOME || os.homedir();
1533
+ const configPath = opts.configPath || codexLbConfigPath(home);
1534
+ try {
1535
+ await ensureDir(path.dirname(configPath));
1536
+ const current = await readText(configPath, '');
1537
+ const next = upsertCodexAppGlmConfig(current);
1538
+ const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'glm-profile');
1539
+ return {
1540
+ ...safeWrite,
1541
+ status: safeWrite.status === 'written' ? 'updated' : safeWrite.status,
1542
+ provider: GLM_CODEX_CONFIG_PROVIDER_ID,
1543
+ model: GLM_52_OPENROUTER_MODEL,
1544
+ codex_config_profile: GLM_CODEX_CONFIG_PROFILE_ID,
1545
+ reasoning_profiles: GLM_CODEX_CONFIG_REASONING_PROFILES.map((profile) => profile.id)
1546
+ };
1547
+ }
1548
+ catch (err) {
1549
+ return {
1550
+ ok: false,
1551
+ status: 'failed',
1552
+ config_path: configPath,
1553
+ error: err.message,
1554
+ provider: GLM_CODEX_CONFIG_PROVIDER_ID,
1555
+ model: GLM_52_OPENROUTER_MODEL,
1556
+ codex_config_profile: GLM_CODEX_CONFIG_PROFILE_ID,
1557
+ reasoning_profiles: GLM_CODEX_CONFIG_REASONING_PROFILES.map((profile) => profile.id)
1558
+ };
1559
+ }
1560
+ }
1502
1561
  function detectCodexLbSetupDrift(state = {}) {
1503
1562
  const drift = [];
1504
1563
  if (state.useDefaultProvider && state.selected !== true)
@@ -1582,7 +1641,10 @@ export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
1582
1641
  return { status: 'unparseable_config_preserved', config_path: configPath, backup_path: backupPath, parse_smoke: currentSmoke };
1583
1642
  }
1584
1643
  }
1585
- const next = normalizeCodexFastModeUiConfig(current, { forceFastMode: opts.forceFastMode === true });
1644
+ const next = normalizeCodexFastModeUiConfig(current, {
1645
+ forceFastMode: opts.forceFastMode === true,
1646
+ forceFastModeOff: opts.forceFastModeOff === true
1647
+ });
1586
1648
  if (next === ensureTrailingNewline(current))
1587
1649
  return { status: 'present', config_path: configPath };
1588
1650
  // Safety gate 2: never WRITE a config that would not parse.
@@ -1604,17 +1666,18 @@ export function normalizeCodexFastModeUiConfig(text = '', opts = {}) {
1604
1666
  return normalizeCodexFastModeUiConfigOnce(normalizeCodexFastModeUiConfigOnce(text, opts), opts);
1605
1667
  }
1606
1668
  function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1607
- // Preserve user-owned top-level scalars (model / service_tier / model_reasoning_effort):
1608
- // SKS only supplies a default when the user has not chosen one, and never strips the
1609
- // user's own reasoning effort. SKS continues to manage its own namespaced tables below
1610
- // ([features], [profiles.sks-*], [user.fast_mode], [plugins]).
1669
+ // Keep model and reasoning selection out of top-level config so Codex Desktop can
1670
+ // expose its native model/speed selectors. SKS-owned defaults live in profiles below.
1611
1671
  let next = String(text || '');
1672
+ next = removeLegacyTopLevelCodexModeLocks(next);
1612
1673
  next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
1613
1674
  next = removeTomlTableKey(next, 'features', 'codex_hooks');
1614
- next = upsertTopLevelTomlStringIfAbsent(next, 'model', 'gpt-5.5');
1615
- next = opts.forceFastMode === true
1616
- ? upsertTopLevelTomlString(next, 'service_tier', 'fast')
1617
- : next;
1675
+ if (opts.forceFastMode === true) {
1676
+ next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
1677
+ }
1678
+ else if (opts.forceFastModeOff === true) {
1679
+ next = upsertTopLevelTomlString(next, 'service_tier', 'default');
1680
+ }
1618
1681
  // Codex App feature flags / fast-mode UI / suppress-warning are SET-IF-ABSENT: a fresh
1619
1682
  // config still gets SKS's defaults, but SKS NEVER overrides (re-enables) a feature the
1620
1683
  // user disabled in the App, and never rejects-then-hides UI by forcing an unrecognized
@@ -1627,10 +1690,12 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1627
1690
  'guardian_approval = true', 'tool_suggest = true', 'apps = true', 'plugins = true'
1628
1691
  ])
1629
1692
  next = upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1630
- if (opts.forceFastMode === true) {
1693
+ if (opts.forceFastMode === true || opts.forceFastModeOff === true) {
1631
1694
  next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
1632
1695
  next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
1633
- next = upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
1696
+ next = opts.forceFastMode === true
1697
+ ? upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"')
1698
+ : removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1634
1699
  }
1635
1700
  else {
1636
1701
  next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
@@ -1672,7 +1737,7 @@ function removeLegacyTopLevelCodexModeLocks(text = '') {
1672
1737
  return lines.filter((line, index) => {
1673
1738
  if (index >= end)
1674
1739
  return true;
1675
- return !/^\s*model_reasoning_effort\s*=/.test(line);
1740
+ return !/^\s*(?:model|model_reasoning_effort)\s*=/.test(line);
1676
1741
  }).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
1677
1742
  }
1678
1743
  function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
@@ -3051,8 +3116,8 @@ export async function selftestCodexLb(tmp) {
3051
3116
  });
3052
3117
  if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true)
3053
3118
  throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
3054
- if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig))
3055
- throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, explicit Fast profile, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
3119
+ if (!codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig))
3120
+ throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, explicit Fast profile, Codex Git commit generation, or migrate the hooks feature flag');
3056
3121
  if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig))
3057
3122
  throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
3058
3123
  const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex '--model' 'gpt-5.5'`;
@@ -3068,7 +3133,7 @@ function hasTopLevelCodexModeLock(text = '') {
3068
3133
  const lines = String(text || '').split('\n');
3069
3134
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
3070
3135
  const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
3071
- return /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
3136
+ return /(^|\n)\s*(?:model|model_reasoning_effort)\s*=/.test(top);
3072
3137
  }
3073
3138
  function hasDeprecatedCodexHooksFeatureFlag(text = '') {
3074
3139
  const lines = String(text || '').split('\n');
@@ -1,8 +1,8 @@
1
- import { flag } from '../cli/args.js';
1
+ import { flag, readOption } from '../cli/args.js';
2
2
  import { printJson } from '../cli/output.js';
3
3
  import { codexAccessTokenStatus, codexAppIntegrationStatus, codexChromeExtensionStatus, codexProductDesignPluginStatus, formatCodexAppStatus, formatCodexProductDesignPluginStatus } from '../core/codex-app.js';
4
4
  import { codexAppRemoteControlCommand } from '../cli/codex-app-command.js';
5
- import { sksRoot } from '../core/fsx.js';
5
+ import { readStdin, sksRoot } from '../core/fsx.js';
6
6
  import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
7
7
  import { syncCodexSksSkills } from '../core/codex-app/codex-skill-sync.js';
8
8
  import { syncCodexAgentRoles } from '../core/codex-app/codex-agent-role-sync.js';
@@ -11,6 +11,7 @@ import { buildCodexHookLifecycle } from '../core/codex-app/codex-hook-lifecycle.
11
11
  import { resolveCodexAppExecutionProfile } from '../core/codex-app/codex-app-execution-profile.js';
12
12
  import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
13
13
  import { doctorCodexAppGlmProfile, installCodexAppGlmProfile } from '../core/codex-app/glm-profile-installer.js';
14
+ import { promptForOpenRouterKeyHidden, writeStoredOpenRouterKey } from '../core/providers/openrouter/openrouter-secret-store.js';
14
15
  export async function run(_command, args = []) {
15
16
  const action = args[0] || 'check';
16
17
  if (action === 'remote-control' || action === 'remote')
@@ -37,6 +38,28 @@ export async function run(_command, args = []) {
37
38
  : await doctorCodexAppGlmProfile({ root });
38
39
  return printCodexAppResult(args, result);
39
40
  }
41
+ if (action === 'set-openrouter-key' || action === 'openrouter-key') {
42
+ const root = await sksRoot();
43
+ const key = await readOpenRouterKeyFromArgs(args.slice(1));
44
+ if (!key) {
45
+ const result = { schema: 'sks.codex-app-openrouter-key.v1', ok: false, status: 'missing_key', blockers: ['openrouter_key_missing'], warnings: [] };
46
+ return printCodexAppResult(args, result);
47
+ }
48
+ const record = await writeStoredOpenRouterKey(key);
49
+ const profile = await installCodexAppGlmProfile({ root, apply: true });
50
+ const result = {
51
+ schema: 'sks.codex-app-openrouter-key.v1',
52
+ ok: profile.ok,
53
+ status: profile.ok ? 'stored' : 'stored_profile_blocked',
54
+ key_preview: record.key_preview,
55
+ raw_key_recorded: false,
56
+ secret_store: 'sks-openrouter-secret-store',
57
+ glm_profile: profile,
58
+ blockers: profile.blockers || [],
59
+ warnings: profile.warnings || []
60
+ };
61
+ return printCodexAppResult(args, result);
62
+ }
40
63
  if (action === 'product-design' || action === 'design-product' || action === 'ensure-product-design') {
41
64
  const checkOnly = flag(args, '--check-only') || flag(args, '--no-install');
42
65
  const status = await codexProductDesignPluginStatus({
@@ -97,9 +120,17 @@ export async function run(_command, args = []) {
97
120
  process.exitCode = 1;
98
121
  return;
99
122
  }
100
- console.error('Usage: sks codex-app check|status|harness-matrix|skill-sync|agent-role-sync|init-deep|hook-lifecycle|execution-profile|glm-profile [install|doctor]|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
123
+ console.error('Usage: sks codex-app check|status|harness-matrix|skill-sync|agent-role-sync|init-deep|hook-lifecycle|execution-profile|glm-profile [install|doctor]|set-openrouter-key [--api-key-stdin]|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
101
124
  process.exitCode = 1;
102
125
  }
126
+ async function readOpenRouterKeyFromArgs(args = []) {
127
+ const key = readOption(args, '--api-key', readOption(args, '--key', ''));
128
+ if (key)
129
+ return String(key).trim();
130
+ if (flag(args, '--api-key-stdin') || flag(args, '--key-stdin'))
131
+ return String(await readStdin()).trim();
132
+ return await promptForOpenRouterKeyHidden() || '';
133
+ }
103
134
  function printCodexAppResult(args = [], result) {
104
135
  if (flag(args, '--json')) {
105
136
  printJson(result);
@@ -56,7 +56,7 @@ async function runDoctor(args = [], root, doctorFix) {
56
56
  const actualCodexProbeRequested = flag(args, '--actual-codex') || flag(args, '--require-actual-codex') || Boolean(codexBin);
57
57
  const actualCodexProbeEnabled = deepDiagnostics || actualCodexProbeRequested;
58
58
  const requireActualCodexProbe = flag(args, '--require-actual-codex') || (deepDiagnostics && doctorFix);
59
- const shouldEvaluateCodexAppUiRepair = deepDiagnostics || flag(args, '--repair-codex-app-ui');
59
+ const shouldEvaluateCodexAppUiRepair = doctorFix || deepDiagnostics || flag(args, '--repair-codex-app-ui');
60
60
  const shouldRunZellijRepair = deepDiagnostics || flag(args, '--repair-zellij') || flag(args, '--install-homebrew') || process.env.SKS_REQUIRE_ZELLIJ === '1';
61
61
  const nativeCapabilityDiagnosticsRequested = deepDiagnostics || flag(args, '--repair-native-capabilities');
62
62
  const doctorPhaseIds = doctorPhaseIdsForProfile(doctorProfile);
@@ -1,8 +1,11 @@
1
1
  import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
2
+ import { GPT54_MINI_CODEX_MODEL, REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
3
+ import { GLM_52_OPENROUTER_MODEL } from '../providers/glm/glm-52-settings.js';
2
4
  const XHIGH_SIGNAL_RE = /(frontier|autoresearch|novelty|hypothesis|falsif|forensic|from-chat-img|image\s*work\s*order|새로운\s*연구|가설|포렌식)/i;
3
5
  const HIGH_SIGNAL_RE = /(database|supabase|sql|migration|security|permission|mad|release|publish|deploy|architecture|policy|schema|hook|rollback|db|보안|배포|마이그레이션|데이터베이스|권한|릴리즈)/i;
4
6
  const MEDIUM_SIGNAL_RE = /(tmux|terminal|cli|tool(?:\s|-)?call|router|routing|orchestrat|pipeline|multi[-\s]?session|multi[-\s]?agent|lease|ledger|proof|검증|파이프라인|오케스트레이션|병렬|에이전트)/i;
5
7
  const SIMPLE_SIGNAL_RE = /(tiny|simple|small|one[-\s]?line|typo|copy|label|spacing|rename|readme|docs?|간단|단순|오타|문구|라벨)/i;
8
+ const SIMPLE_CODE_MOD_RE = /(tiny|simple|small|one[-\s]?line|typo|copy|label|spacing|rename|readme|docs?|minor|bounded|간단|단순|작은|오타|문구|라벨|이름\s*변경)/i;
6
9
  export function decideAgentEffort(input = {}) {
7
10
  const persona = input.persona || {};
8
11
  const prompt = String(input.prompt || '');
@@ -27,14 +30,19 @@ export function decideAgentEffort(input = {}) {
27
30
  effort = 'high';
28
31
  reason = 'implementation_lane_capped_at_high';
29
32
  }
30
- const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
33
+ const modelDecision = decideAgentWorkerModel({ effort, prompt, role, agentId, readonly: input.readonly === true || persona.read_only === true, writePolicy: String(persona.write_policy || '') });
34
+ const modelCapability = codexModelEffortCapability({ model: modelDecision.model, defaultEffort: modelDecision.model_reasoning_effort });
31
35
  return {
32
36
  schema: 'sks.agent-effort-decision.v1',
33
37
  policy_version: 1,
34
38
  agent_id: agentId,
35
39
  role,
40
+ model: modelDecision.model,
36
41
  reasoning_effort: effort,
37
- model_reasoning_effort: effort,
42
+ model_reasoning_effort: modelDecision.model_reasoning_effort,
43
+ model_tier: modelDecision.model_tier,
44
+ model_profile: modelDecision.model_profile,
45
+ model_selection_reason: modelDecision.reason,
38
46
  model_effort_capability: modelCapability,
39
47
  reasoning_profile: reasoningProfileName(effort),
40
48
  service_tier: 'fast',
@@ -76,14 +84,19 @@ export function decideNarutoCloneEffort(input = {}) {
76
84
  const writes = !readonly || /write|edit|route-local|workspace|patch|integrat/i.test(writePolicy) || hasActionTool;
77
85
  const toolUse = writes || NARUTO_ACTION_TOOL_RE.test(prompt);
78
86
  const effort = toolUse ? 'medium' : 'low';
79
- const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
87
+ const modelDecision = decideAgentWorkerModel({ effort, prompt, role, agentId, readonly, writePolicy: String(writePolicy || '') });
88
+ const modelCapability = codexModelEffortCapability({ model: modelDecision.model, defaultEffort: modelDecision.model_reasoning_effort });
80
89
  return {
81
90
  schema: 'sks.agent-effort-decision.v1',
82
91
  policy_version: 1,
83
92
  agent_id: agentId,
84
93
  role,
94
+ model: modelDecision.model,
85
95
  reasoning_effort: effort,
86
- model_reasoning_effort: effort,
96
+ model_reasoning_effort: modelDecision.model_reasoning_effort,
97
+ model_tier: modelDecision.model_tier,
98
+ model_profile: modelDecision.model_profile,
99
+ model_selection_reason: modelDecision.reason,
87
100
  model_effort_capability: modelCapability,
88
101
  reasoning_profile: reasoningProfileName(effort),
89
102
  service_tier: 'fast',
@@ -102,7 +115,12 @@ export function buildAgentEffortPolicy(roster = {}) {
102
115
  agent_id: agent.id,
103
116
  session_id: agent.session_id,
104
117
  role: agent.role,
118
+ model: agent.model,
105
119
  reasoning_effort: agent.reasoning_effort,
120
+ model_reasoning_effort: agent.model_reasoning_effort,
121
+ model_tier: agent.model_tier,
122
+ model_profile: agent.model_profile,
123
+ model_selection_reason: agent.model_selection_reason,
106
124
  reasoning_profile: agent.reasoning_profile,
107
125
  reason: agent.reasoning_reason,
108
126
  dynamic: true
@@ -112,13 +130,15 @@ export function buildAgentEffortPolicy(roster = {}) {
112
130
  policy_version: 1,
113
131
  dynamic: true,
114
132
  service_tier: 'fast',
133
+ allowed_models: [GPT54_MINI_CODEX_MODEL, REQUIRED_CODEX_MODEL, GLM_52_OPENROUTER_MODEL],
134
+ model_tiers: ['gpt-5.4-mini', 'gpt-5.5-low', 'gpt-5.5-high', 'glm-5.2-minimal', 'glm-5.2-low', 'glm-5.2-high', 'glm-5.2-xhigh'],
115
135
  allowed_efforts: codexModelEffortCapability().advertised_efforts,
116
136
  model_effort_capability: codexModelEffortCapability(),
117
137
  max_agents: roster.max_agents || 20,
118
138
  agent_count: roster.agent_count || decisions.length,
119
139
  concurrency: roster.concurrency || decisions.length,
120
140
  decisions,
121
- rule: 'Parent orchestration assigns per-agent effort from prompt risk, persona role, lease ownership, and proof state; lanes can escalate on blockers and downshift for narrow read-only work.'
141
+ rule: 'Parent orchestration assigns per-agent model tiers from prompt risk, persona role, lease ownership, and proof state: simple bounded GPT workers can downshift to gpt-5.4-mini; ordinary GPT workers use gpt-5.5 low; risky GPT lanes use gpt-5.5 high. In GLM mode, native workers stay on z-ai/glm-5.2 and receive GLM effort tiers.'
122
142
  };
123
143
  }
124
144
  export function reasoningProfileName(effort) {
@@ -144,4 +164,89 @@ function effortReason(effort) {
144
164
  return 'simple_bounded_slice';
145
165
  return 'default_orchestration_slice';
146
166
  }
167
+ export function decideAgentWorkerModel(input = {}) {
168
+ const mainModel = String(input.mainModel || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || REQUIRED_CODEX_MODEL).trim();
169
+ const glmMain = isGlmWorkerMode(mainModel);
170
+ const gptMain = !glmMain && (!mainModel || /^gpt-/i.test(mainModel));
171
+ const effort = String(input.effort || 'medium');
172
+ const text = [input.prompt, input.role, input.agentId, input.writePolicy].map((item) => String(item || '')).join(' ');
173
+ const simpleText = [input.prompt, input.role, input.agentId].map((item) => String(item || '')).join(' ');
174
+ const risky = HIGH_SIGNAL_RE.test(text) || XHIGH_SIGNAL_RE.test(text);
175
+ const simple = SIMPLE_CODE_MOD_RE.test(simpleText) && !HIGH_SIGNAL_RE.test(simpleText) && !XHIGH_SIGNAL_RE.test(simpleText);
176
+ if (glmMain) {
177
+ const glmEffort = glmWorkerEffort({ effort, risky, simple });
178
+ return {
179
+ model: GLM_52_OPENROUTER_MODEL,
180
+ model_reasoning_effort: glmEffort,
181
+ model_tier: `glm-5.2-${glmEffort === 'none' ? 'minimal' : glmEffort}`,
182
+ model_profile: glmProfileForReasoning(glmEffort),
183
+ reason: `glm_52_${glmEffort}_worker`
184
+ };
185
+ }
186
+ if (gptMain && risky) {
187
+ return {
188
+ model: REQUIRED_CODEX_MODEL,
189
+ model_reasoning_effort: 'high',
190
+ model_tier: 'gpt-5.5-high',
191
+ model_profile: 'sks-agent-gpt-5.5-high-fast',
192
+ reason: 'risk_signal_worker'
193
+ };
194
+ }
195
+ if (gptMain && (simple || effort === 'low')) {
196
+ return {
197
+ model: GPT54_MINI_CODEX_MODEL,
198
+ model_reasoning_effort: 'low',
199
+ model_tier: 'gpt-5.4-mini',
200
+ model_profile: 'sks-agent-gpt-5.4-mini-fast',
201
+ reason: simple ? 'simple_code_or_docs_slice_downshift' : 'low_effort_worker_downshift'
202
+ };
203
+ }
204
+ if (gptMain && (effort === 'high' || effort === 'xhigh')) {
205
+ return {
206
+ model: REQUIRED_CODEX_MODEL,
207
+ model_reasoning_effort: 'high',
208
+ model_tier: 'gpt-5.5-high',
209
+ model_profile: 'sks-agent-gpt-5.5-high-fast',
210
+ reason: 'risk_or_high_effort_worker'
211
+ };
212
+ }
213
+ return {
214
+ model: gptMain ? REQUIRED_CODEX_MODEL : mainModel || REQUIRED_CODEX_MODEL,
215
+ model_reasoning_effort: 'low',
216
+ model_tier: 'gpt-5.5-low',
217
+ model_profile: 'sks-agent-gpt-5.5-low-fast',
218
+ reason: gptMain ? 'ordinary_worker_gpt55_low' : 'non_gpt_main_model_preserved'
219
+ };
220
+ }
221
+ function isGlmWorkerMode(mainModel) {
222
+ const model = String(mainModel || '').trim().toLowerCase();
223
+ return model === GLM_52_OPENROUTER_MODEL
224
+ || model === 'glm-5.2'
225
+ || model === 'glm5.2'
226
+ || process.env.SKS_GLM_MODE === '1'
227
+ || process.env.SKS_GLM_WRAPPER_ACTIVE === '1'
228
+ || process.env.SKS_GLM_MAD_ACTIVE === '1';
229
+ }
230
+ function glmWorkerEffort(input) {
231
+ if (input.effort === 'xhigh')
232
+ return 'xhigh';
233
+ if (input.risky || input.effort === 'high')
234
+ return 'high';
235
+ if (input.simple || input.effort === 'low')
236
+ return 'minimal';
237
+ return 'low';
238
+ }
239
+ function glmProfileForReasoning(effort) {
240
+ if (effort === 'xhigh')
241
+ return 'sks-glm-52-xhigh';
242
+ if (effort === 'high')
243
+ return 'sks-glm-52-high';
244
+ if (effort === 'medium')
245
+ return 'sks-glm-52-medium';
246
+ if (effort === 'low')
247
+ return 'sks-glm-52-low';
248
+ if (effort === 'none')
249
+ return 'sks-glm-52-mad';
250
+ return 'sks-glm-52-minimal';
251
+ }
147
252
  //# sourceMappingURL=agent-effort-policy.js.map
@@ -24,6 +24,7 @@ import { runFakeAgent } from './agent-runner-fake.js';
24
24
  import { runProcessAgent } from './agent-runner-process.js';
25
25
  import { classifyOllamaWorkerSlice, runOllamaAgent } from './agent-runner-ollama.js';
26
26
  import { resolveOllamaWorkerConfig } from './ollama-worker-config.js';
27
+ import { decideAgentWorkerModel } from './agent-effort-policy.js';
27
28
  import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
28
29
  import { writeAgentCleanupReport } from './agent-cleanup.js';
29
30
  import { writeAgentTrustReport } from './agent-trust-report.js';
@@ -93,7 +94,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
93
94
  const selectedCoreSkill = routeSkillSelection ? skillProofRecord(routeSkillSelection) : null;
94
95
  const namespace = await buildProjectNamespace({ root, missionId });
95
96
  await writeProjectNamespaceArtifact(dir, namespace);
96
- let roster = buildProvidedAgentRoster(opts.roster, { concurrency: opts.concurrency, readonly: opts.readonly, maxAgentCount }) || buildAgentRoster({ agents: opts.agents, concurrency: opts.concurrency, prompt, maxAgentCount, ...(opts.readonly === undefined ? {} : { readonly: opts.readonly }) });
97
+ let roster = buildProvidedAgentRoster(opts.roster, { concurrency: opts.concurrency, readonly: opts.readonly, maxAgentCount, prompt }) || buildAgentRoster({ agents: opts.agents, concurrency: opts.concurrency, prompt, maxAgentCount, ...(opts.readonly === undefined ? {} : { readonly: opts.readonly }) });
97
98
  roster = applyFastModeToRoster(roster, fastModePolicy);
98
99
  roster.roster = roster.roster.map((agent) => ({
99
100
  ...agent,
@@ -1638,6 +1639,19 @@ function buildProvidedAgentRoster(input, opts = {}) {
1638
1639
  const roster = sourceRows.map((entry, index) => {
1639
1640
  const readOnly = opts.readonly === true || entry.read_only === true;
1640
1641
  const id = String(entry.id || entry.agent_id || `agent_${index + 1}`);
1642
+ const reasoningEffort = entry.reasoning_effort || entry.model_reasoning_effort || (readOnly ? 'high' : 'medium');
1643
+ const modelDecision = decideAgentWorkerModel({
1644
+ effort: reasoningEffort,
1645
+ prompt: opts.prompt || input?.prompt || '',
1646
+ role: String(entry.role || 'verifier'),
1647
+ agentId: id,
1648
+ readonly: readOnly,
1649
+ writePolicy: String(entry.write_policy || '')
1650
+ });
1651
+ const suppliedModelEffort = String(entry.model_reasoning_effort || '');
1652
+ const modelReasoningEffort = suppliedModelEffort === 'low' || suppliedModelEffort === 'high'
1653
+ ? suppliedModelEffort
1654
+ : modelDecision.model_reasoning_effort;
1641
1655
  return {
1642
1656
  id,
1643
1657
  session_id: String(entry.session_id || `${id}-session-${String(index + 1).padStart(2, '0')}`),
@@ -1646,8 +1660,12 @@ function buildProvidedAgentRoster(input, opts = {}) {
1646
1660
  index: index + 1,
1647
1661
  write_policy: String(entry.write_policy || (readOnly ? 'read-only' : 'route-local-artifact')),
1648
1662
  status: 'pending',
1649
- reasoning_effort: entry.reasoning_effort || entry.model_reasoning_effort || (readOnly ? 'high' : 'medium'),
1650
- model_reasoning_effort: entry.model_reasoning_effort || entry.reasoning_effort || (readOnly ? 'high' : 'medium'),
1663
+ model: entry.model || modelDecision.model,
1664
+ reasoning_effort: reasoningEffort,
1665
+ model_reasoning_effort: modelReasoningEffort,
1666
+ model_tier: entry.model_tier || modelDecision.model_tier,
1667
+ model_profile: entry.model_profile || modelDecision.model_profile,
1668
+ model_selection_reason: entry.model_selection_reason || modelDecision.reason,
1651
1669
  reasoning_profile: entry.reasoning_profile || (readOnly ? 'sks-logic-high' : 'sks-logic-medium'),
1652
1670
  service_tier: entry.service_tier,
1653
1671
  reasoning_reason: entry.reasoning_reason || 'route_native_agent_plan',
@@ -1690,6 +1708,9 @@ async function runAgentByBackend(backend, agent, slice, opts) {
1690
1708
  sessionId: String(agent.session_id || ''),
1691
1709
  cwd: String(opts.cwd || process.cwd()),
1692
1710
  prompt: buildDirectSdkWorkerPrompt(slice),
1711
+ model: agent.model || null,
1712
+ reasoningEffort: agent.reasoning_effort || null,
1713
+ modelReasoningEffort: agent.model_reasoning_effort || agent.reasoning_effort || null,
1693
1714
  inputFiles: Array.isArray(opts.inputFiles) ? opts.inputFiles.map(String) : [],
1694
1715
  inputImages: Array.isArray(opts.inputImages) ? opts.inputImages.map(String) : [],
1695
1716
  outputSchemaId: CODEX_AGENT_WORKER_RESULT_SCHEMA_ID,
@@ -1723,6 +1744,9 @@ async function runAgentByBackend(backend, agent, slice, opts) {
1723
1744
  structured_output_valid: sdkTask.structuredOutputValid,
1724
1745
  worker_result_path: path.relative(ledgerRoot, sdkTask.workerResultPath),
1725
1746
  patch_envelope_path: sdkTask.patchEnvelopePath ? path.relative(ledgerRoot, sdkTask.patchEnvelopePath) : null,
1747
+ model: agent.model || null,
1748
+ model_reasoning_effort: agent.model_reasoning_effort || null,
1749
+ model_tier: agent.model_tier || null,
1726
1750
  service_tier: opts.serviceTier || 'fast',
1727
1751
  fast_mode: opts.fastMode !== false,
1728
1752
  blockers: sdkTask.blockers
@@ -36,8 +36,8 @@ export function agentPipelineStage(policy = {}) {
36
36
  agent_count: required ? Number(policy.agent_count || DEFAULT_AGENT_COUNT) : 0,
37
37
  max_parallel_native_sessions: Number(policy.agent_count || DEFAULT_AGENT_COUNT),
38
38
  backend: 'native-agent-kernel',
39
- read_only: true,
40
- write_policy: 'read-only analysis; parent-owned integration',
39
+ read_only: false,
40
+ write_policy: 'bounded workspace-write analysis leases; parent-owned integration',
41
41
  outputs: policy.outputs || ['agents/agent-proof-evidence.json']
42
42
  };
43
43
  }