sneakoscope 10.7.0 → 10.7.2

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 (66) hide show
  1. package/README.md +27 -5
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/command-manifest-lite.js +1 -1
  5. package/dist/cli/install-helpers-codex-lb-config.js +55 -20
  6. package/dist/commands/bridge.js +14 -1
  7. package/dist/commands/codex.js +5 -1
  8. package/dist/commands/doctor-console.js +2 -0
  9. package/dist/commands/doctor.js +8 -9
  10. package/dist/config/skills-manifest.json +1 -1
  11. package/dist/core/agents/agent-role-config.js +6 -2
  12. package/dist/core/agents/sks-model-pin-hygiene.js +150 -0
  13. package/dist/core/codex/codex-config-guard.js +18 -13
  14. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +7 -1
  15. package/dist/core/codex-app/menubar/constants.js +0 -1
  16. package/dist/core/codex-lb/bridge-contracts.js +5 -0
  17. package/dist/core/codex-lb/combined-catalog/builder.js +4 -2
  18. package/dist/core/codex-lb/combined-catalog/latest-generation.js +7 -34
  19. package/dist/core/codex-lb/combined-catalog/normalize.js +3 -1
  20. package/dist/core/codex-lb/combined-catalog/persistence.js +4 -0
  21. package/dist/core/codex-lb/combined-catalog/storage.js +4 -1
  22. package/dist/core/codex-lb/desktop-bridge/codex-lb-turn-state.js +35 -0
  23. package/dist/core/codex-lb/desktop-bridge/compaction-adapter.js +3 -0
  24. package/dist/core/codex-lb/desktop-bridge/header-policy.js +10 -4
  25. package/dist/core/codex-lb/desktop-bridge/http-forward.js +138 -36
  26. package/dist/core/codex-lb/desktop-bridge/models-endpoint.js +62 -0
  27. package/dist/core/codex-lb/desktop-bridge/provider-handoff.js +88 -0
  28. package/dist/core/codex-lb/desktop-bridge/provider-request-compat.js +89 -0
  29. package/dist/core/codex-lb/desktop-bridge/rejection-log.js +61 -0
  30. package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +108 -16
  31. package/dist/core/codex-lb/desktop-bridge/security.js +101 -55
  32. package/dist/core/codex-lb/desktop-bridge/server.js +15 -2
  33. package/dist/core/codex-lb/desktop-bridge/types.js +3 -0
  34. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +3 -0
  35. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +2 -6
  36. package/dist/core/codex-lb/desktop-bridge-migration.js +14 -2
  37. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +125 -3
  38. package/dist/core/codex-lb/desktop-controller-v3/model-refresh.js +140 -0
  39. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +5 -5
  40. package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +14 -11
  41. package/dist/core/codex-lb/desktop-controller-v3/status.js +2 -3
  42. package/dist/core/codex-lb/desktop-controller-v3.js +3 -1
  43. package/dist/core/codex-lb/desktop-service.js +51 -17
  44. package/dist/core/codex-lb/model-refresh-after.js +16 -0
  45. package/dist/core/codex-lb/request-route-resolver.js +1 -1
  46. package/dist/core/codex-runtime/codex-desktop-config-policy.js +0 -1
  47. package/dist/core/commands/basic-cli.js +5 -0
  48. package/dist/core/commands/menubar-command.js +5 -1
  49. package/dist/core/doctor/desktop-bridge-catalog-repair.js +20 -17
  50. package/dist/core/hooks-runtime/jev-spawn-routing.js +1 -5
  51. package/dist/core/hooks-runtime/parent-orchestration-gate.js +6 -8
  52. package/dist/core/hooks-runtime.js +13 -9
  53. package/dist/core/init/skills.js +22 -3
  54. package/dist/core/provider/provider-context.js +4 -1
  55. package/dist/core/search/context.js +2 -2
  56. package/dist/core/subagents/model-tiers.js +19 -3
  57. package/dist/core/update/update-migration-state/desktop-bridge-catalog-repair-stage.js +4 -3
  58. package/dist/core/update/update-migration-state.js +10 -40
  59. package/dist/core/version.js +1 -1
  60. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +0 -1
  61. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +32 -0
  62. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +15 -5
  63. package/dist/native/sks-menubar/Sources/SidebarItem.swift +0 -2
  64. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +3 -0
  65. package/package.json +2 -2
  66. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +0 -49
package/README.md CHANGED
@@ -17,8 +17,9 @@
17
17
  Sneakoscope Codex (`sks`) is an open-source local tool for the Codex CLI and Codex desktop app. It adds a macOS control center, bounded agent workflows, repository context, and evidence checks for SKS releases.
18
18
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
19
19
 
20
- Current package: **SKS 10.7.0**, prepared for npm publication.
20
+ Current package: **SKS 10.7.2**, prepared for npm publication.
21
21
  [Latest release notes](https://github.com/mandarange/Sneakoscope-Codex/releases/latest)
22
+ · [npm package](https://www.npmjs.com/package/sneakoscope)
22
23
  · [Changelog](CHANGELOG.md). Install the latest stable release from npm.
23
24
 
24
25
  Ordinary work stays with the main agent. Optional Jev decisions use OpenRouter
@@ -132,9 +133,13 @@ sks doctor --json
132
133
 
133
134
  `sks update` updates the **global** SKS package, verifies the installed command,
134
135
  and reconciles SKS-managed skills, hooks, configuration and SKS Center where
135
- available. In 10.5.5 and later it also repairs the known ignored Codex settings
136
- and refreshes managed guidance. User-authored text and explicit preferences
137
- are preserved. If a step fails, follow the recovery action in its output.
136
+ available. Cleanup follows the current package's manifest and ownership markers
137
+ regardless of the previous version, including mixed leftovers from interrupted
138
+ installs or a missing old generation manifest. Running it again when already
139
+ current still repairs the managed setup. It also repairs the known ignored
140
+ Codex settings and refreshes managed guidance. User-authored text, credentials
141
+ and explicit preferences are preserved. A required cleanup failure blocks
142
+ completion and is reported with its recovery action.
138
143
 
139
144
  To check available updates first, run `sks update check`. For a repair without
140
145
  requesting a newer package, run `sks doctor --fix` yourself in the terminal.
@@ -155,7 +160,24 @@ references and rebuilds TriWiki. Update and user-run `sks doctor --fix` share
155
160
  that maintenance and repair known ignored configuration settings. User text
156
161
  and model choices are preserved. See [harness maintenance](docs/align-modernization.md).
157
162
 
158
- ### New in 10.5.7
163
+ ### New in 10.7.0
164
+
165
+ - **A smaller command surface:** duplicate and unused commands were removed;
166
+ use `sks help <command>` for the supported command path.
167
+ - **Project-aware verification:** Plans and Proof Field reports use the target
168
+ project's own package scripts and checks instead of SKS maintainer commands.
169
+ - **Project-owned marketing:** SEO/GEO research and apply flows describe the
170
+ project they run in and preserve an existing project description.
171
+ - **Essential-profile guidance:** read-only paths stay lightweight while
172
+ safety checks remain active; destructive defaults and misleading proof paths
173
+ are refused.
174
+ - **Faster read-only commands:** status and help paths no longer load unrelated
175
+ update-migration machinery.
176
+
177
+ These changes are product behavior and validation improvements; they do not
178
+ claim a measured model-quality or general speed increase.
179
+
180
+ ### Earlier highlights: 10.5.7
159
181
 
160
182
  - **Jev execution profiles:** a decision can propose a bounded execution plan.
161
183
  The default policy is `baseline`; fast profiles require `optimize` and an
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.7.0"
262
+ version = "10.7.2"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.7.0"
3
+ version = "10.7.2"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -273,7 +273,7 @@ const COMMAND_HELP_LITE = {
273
273
  },
274
274
  doctor: {
275
275
  usage: 'sks doctor [--fix] [--local-only] [--json] [--install-scope global|project]',
276
- description: 'Check and repair SKS generated files, while blocking setup if another Codex harness is detected.'
276
+ description: 'Check and repair SKS generated files, while blocking setup if another Codex harness is detected. Every run refreshes the Desktop Bridge model list; without --fix it writes only SKS\'s own catalog files, never ~/.codex/config.toml.'
277
277
  },
278
278
  git: {
279
279
  usage: 'sks git policy|install|status|doctor|precommit|publish-plan|summary [--json]',
@@ -1,26 +1,57 @@
1
1
  import path from 'node:path';
2
2
  import { ensureTrailingNewline, upsertTopLevelTomlString } from '../core/codex-runtime/codex-desktop-config-policy.js';
3
3
  import { escapeRegExp } from '../core/text/regex.js';
4
+ export function codexReadsBridgeModels(text) {
5
+ const source = String(text || '');
6
+ return topLevelHasLine(source, DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER)
7
+ && Boolean(topLevelTomlString(source, 'openai_base_url'))
8
+ && !topLevelTomlString(source, 'model_catalog_json');
9
+ }
10
+ export function hasUserOwnedModelCatalog(text) {
11
+ const source = String(text || '');
12
+ return Boolean(topLevelTomlString(source, 'model_catalog_json'))
13
+ && !topLevelHasLine(source, DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER);
14
+ }
15
+ export function setDesktopBridgeManagedModelCatalog(text, combinedCatalogPath) {
16
+ const source = String(text || '');
17
+ if (!topLevelHasLine(source, DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER) || hasUserOwnedModelCatalog(source))
18
+ return source;
19
+ if (combinedCatalogPath === null) {
20
+ if (!topLevelTomlString(source, 'model_catalog_json'))
21
+ return source;
22
+ return ensureTrailingNewline(removeTopLevelLine(removeTopLevelTomlKey(source, 'model_catalog_json'), DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER));
23
+ }
24
+ assertCombinedCatalogPath(combinedCatalogPath);
25
+ if (topLevelTomlString(source, 'model_catalog_json') === combinedCatalogPath)
26
+ return source;
27
+ const next = upsertTopLevelTomlString(source, 'model_catalog_json', combinedCatalogPath);
28
+ return ensureTrailingNewline(addTopLevelMarkerBeforeKey(next, 'model_catalog_json', DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER));
29
+ }
30
+ function assertCombinedCatalogPath(combinedCatalogPath) {
31
+ if (!combinedCatalogPath || !path.isAbsolute(combinedCatalogPath)
32
+ || path.basename(combinedCatalogPath) !== 'sks-bridge-catalog.json') {
33
+ throw new Error('desktop_bridge_combined_catalog_path_invalid');
34
+ }
35
+ }
4
36
  export const DESKTOP_BRIDGE_MANAGED_MARKER = '# sks-desktop-bridge-managed';
5
37
  export const DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER = '# sks-desktop-bridge-managed-base-url';
6
38
  export const DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER = '# sks-desktop-bridge-managed-model-catalog';
7
- export function bindCodexClientProvider(text, provider) {
8
- const selected = topLevelTomlString(text, 'model_provider');
9
- if (selected && selected !== 'openai' && selected !== 'codex-lb')
10
- return text;
11
- if (selected === provider)
12
- return text;
13
- return upsertTopLevelTomlString(String(text || ''), 'model_provider', provider);
39
+ export function repairManagedCodexLbClientSelection(text) {
40
+ const source = String(text || '');
41
+ if (topLevelTomlString(source, 'model_provider') !== 'codex-lb')
42
+ return source;
43
+ if (!topLevelHasLine(source, DESKTOP_BRIDGE_MANAGED_MARKER) || hasCodexLbProviderTable(source))
44
+ return source;
45
+ return upsertTopLevelTomlString(source, 'model_provider', 'openai');
46
+ }
47
+ export function hasCodexLbProviderTable(text) {
48
+ return /^\s*\[model_providers\.(?:"codex-lb"|codex-lb)\]\s*(?:#.*)?$/m.test(text);
14
49
  }
15
50
  export function upsertDesktopBridgeManagedConfig(text, input) {
16
51
  const bridgeBaseUrl = normalizeManagedBridgeBaseUrl(input.bridgeBaseUrl);
17
52
  const combinedCatalogPath = String(input.combinedCatalogPath || '').trim();
18
- if (!combinedCatalogPath || !path.isAbsolute(combinedCatalogPath)) {
19
- throw new Error('desktop_bridge_combined_catalog_path_invalid');
20
- }
21
- if (path.basename(combinedCatalogPath) !== 'sks-bridge-catalog.json') {
22
- throw new Error('desktop_bridge_combined_catalog_path_invalid');
23
- }
53
+ assertCombinedCatalogPath(combinedCatalogPath);
54
+ const bindModelCatalog = input.bindModelCatalog !== false;
24
55
  const orphanCleanup = removeDesktopBridgeOrphanManagedMarkers(String(text || ''));
25
56
  let next = orphanCleanup.text;
26
57
  const selectedProvider = topLevelTomlString(next, 'model_provider');
@@ -36,17 +67,20 @@ export function upsertDesktopBridgeManagedConfig(text, input) {
36
67
  }
37
68
  const existingCatalog = topLevelTomlString(next, 'model_catalog_json');
38
69
  const catalogOwned = topLevelHasLine(next, DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER);
39
- if (existingCatalog && !catalogOwned) {
70
+ if (existingCatalog && !catalogOwned && bindModelCatalog) {
40
71
  throw new Error('historical_user_owned_config_conflict:model_catalog_json');
41
72
  }
42
- const keepCodexLb = selectedProvider === 'codex-lb';
43
- next = removeManagedBridgeTopLevelBindings(next);
73
+ const userCatalog = existingCatalog && !catalogOwned ? existingCatalog : null;
74
+ const keepCodexLb = selectedProvider === 'codex-lb' && hasCodexLbProviderTable(next);
75
+ next = removeManagedBridgeTopLevelBindings(next, { keepModelCatalog: userCatalog !== null });
44
76
  next = upsertTopLevelTomlString(next, 'model_provider', keepCodexLb ? 'codex-lb' : 'openai');
45
77
  next = addTopLevelMarkerBeforeKey(next, 'model_provider', DESKTOP_BRIDGE_MANAGED_MARKER);
46
78
  next = upsertTopLevelTomlString(next, 'openai_base_url', bridgeBaseUrl);
47
79
  next = addTopLevelMarkerBeforeKey(next, 'openai_base_url', DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER);
48
- next = upsertTopLevelTomlString(next, 'model_catalog_json', combinedCatalogPath);
49
- next = addTopLevelMarkerBeforeKey(next, 'model_catalog_json', DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER);
80
+ if (bindModelCatalog) {
81
+ next = upsertTopLevelTomlString(next, 'model_catalog_json', combinedCatalogPath);
82
+ next = addTopLevelMarkerBeforeKey(next, 'model_catalog_json', DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER);
83
+ }
50
84
  return ensureTrailingNewline(next);
51
85
  }
52
86
  export function removeDesktopBridgeManagedConfig(text) {
@@ -132,11 +166,12 @@ function removeTopLevelTomlKey(text, key) {
132
166
  .replace(/^\n+/, '')
133
167
  .replace(/\n{3,}/g, '\n\n');
134
168
  }
135
- function removeManagedBridgeTopLevelBindings(text) {
169
+ function removeManagedBridgeTopLevelBindings(text, options = {}) {
136
170
  let next = String(text || '');
137
171
  next = removeTopLevelTomlKey(next, 'model_provider');
138
172
  next = removeTopLevelTomlKey(next, 'openai_base_url');
139
- next = removeTopLevelTomlKey(next, 'model_catalog_json');
173
+ if (!options.keepModelCatalog)
174
+ next = removeTopLevelTomlKey(next, 'model_catalog_json');
140
175
  for (const marker of [
141
176
  DESKTOP_BRIDGE_MANAGED_MARKER,
142
177
  DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER,
@@ -31,6 +31,7 @@ export function usage(command = 'bridge') {
31
31
  ` sks ${command} catalog sync|status [--json]`,
32
32
  ` sks ${command} models list [--json]`,
33
33
  ` sks ${command} models select --set <public-id,...> [--json]`,
34
+ ` sks ${command} models refresh [--json]`,
34
35
  ` sks ${command} route list [--json]`,
35
36
  ` sks ${command} route set-default <codex-lb|openrouter> [--json]`,
36
37
  ` sks ${command} route official-models <passthrough|gateway|auto> [--json]`,
@@ -86,7 +87,7 @@ export async function executeBridgeCommand(args = [], options = {}) {
86
87
  : await (options.facade || await loadBridgeCommandFacade()).execute(parsed.request);
87
88
  const output = parsed.request.operation === 'verify'
88
89
  ? verificationOutput(raw, parsed.request.level, parsed.strict)
89
- : ordinaryOutput(raw, parsed.label);
90
+ : ordinaryOutput(raw, parsed.request.operation === 'models.refresh' ? modelRefreshLine(raw) ?? parsed.label : parsed.label);
90
91
  const sanitized = sanitizeBridgeValue(output, parsed.secrets);
91
92
  const decorated = mergeMetadata(sanitized, options.metadata);
92
93
  return {
@@ -104,6 +105,10 @@ export async function executeBridgeCommand(args = [], options = {}) {
104
105
  };
105
106
  }
106
107
  }
108
+ function modelRefreshLine(raw) {
109
+ const result = raw?.result;
110
+ return typeof result?.model_refresh?.line === 'string' ? result.model_refresh.line : null;
111
+ }
107
112
  export async function loadBridgeCommandFacade() {
108
113
  const controller = await import('../core/codex-lb/desktop-controller.js');
109
114
  const execute = controller[CONTROLLER_FACADE_EXPORT];
@@ -260,6 +265,14 @@ async function parseInvocation(args, io) {
260
265
  allowOnly(parsed, ['--json'], []);
261
266
  return { ...base, request: { operation: 'models.list' }, label: 'Selectable bridge models' };
262
267
  }
268
+ if (action === 'refresh' && target === undefined) {
269
+ allowOnly(parsed, ['--json'], []);
270
+ return {
271
+ ...base,
272
+ request: { operation: 'models.refresh', reason: 'cli', force: true, allow_config_writes: true },
273
+ label: 'Model list refresh'
274
+ };
275
+ }
263
276
  if (action === 'select') {
264
277
  allowOnly(parsed, ['--json'], ['--set']);
265
278
  const raw = parsed.values.get('--set') || '';
@@ -45,7 +45,9 @@ export async function run(_command, args = []) {
45
45
  return;
46
46
  }
47
47
  if (action === 'update' || action === 'update-now') {
48
- const result = await updateCodexCliNow();
48
+ const updated = await updateCodexCliNow();
49
+ const modelRefresh = await (await import('../core/codex-lb/model-refresh-after.js')).refreshModelListAfter('codex-update');
50
+ const result = modelRefresh ? { ...updated, model_refresh: modelRefresh } : updated;
49
51
  if (flag(args, '--json')) {
50
52
  printJson(result);
51
53
  if (!result.ok)
@@ -54,6 +56,8 @@ export async function run(_command, args = []) {
54
56
  }
55
57
  for (const line of codexCliUpdateConsoleLines(result))
56
58
  console.log(line);
59
+ if (typeof modelRefresh?.line === 'string')
60
+ console.log(modelRefresh.line);
57
61
  if (!result.ok)
58
62
  process.exitCode = 1;
59
63
  return;
@@ -238,6 +238,8 @@ export function renderDoctorConsoleReport(result, extras) {
238
238
  if (codexCurrentAppDoctor.fixed?.length)
239
239
  line(` doctor --fix repaired: ${codexCurrentAppDoctor.fixed.join(', ')}`);
240
240
  line(`Desktop Bridge: ${desktopBridge.ok ? 'ready' : 'blocked'} (${desktopBridge.status?.readiness?.state || 'unavailable'})`);
241
+ if (desktopBridge.model_refresh?.line)
242
+ line(` ${desktopBridge.model_refresh.line}`);
241
243
  for (const providerId of ['codex-lb', 'openrouter']) {
242
244
  const provider = desktopBridge.providers?.[providerId];
243
245
  if (!provider)
@@ -376,6 +376,8 @@ async function runDoctorGlobalOnlyFix(args = [], root, deps = {}) {
376
376
  ? 'repaired'
377
377
  : bridgeRepair?.ok === false ? 'failed' : 'nothing_to_repair'}`);
378
378
  console.log(`Desktop Bridge: ${result.desktop_bridge.ok ? 'ready' : 'blocked'} (${result.desktop_bridge.status?.readiness?.state || 'unavailable'})`);
379
+ if (result.desktop_bridge_repair?.model_refresh?.line)
380
+ console.log(result.desktop_bridge_repair.model_refresh.line);
379
381
  if (result.migration_receipt) {
380
382
  console.log(`Migration receipt: ${result.migration_receipt.status} (${result.home})`);
381
383
  }
@@ -737,9 +739,8 @@ async function runDoctor(args = [], root, doctorFix, deps = {}) {
737
739
  const codexApp = deepDiagnostics
738
740
  ? await codexAppIntegrationStatus({ codex }).catch((err) => ({ ok: false, error: err.message }))
739
741
  : { ok: false, skipped: true, warnings: ['codex_app_optional_diagnostic_skipped'] };
740
- const desktopBridgeBeforeFix = await inspectDoctorDesktopBridgeStatus({
741
- processEnv: process.env
742
- }, { desktopBridgeStatusImpl: deps.desktopBridgeStatusImpl });
742
+ let desktopBridgeModelRefresh = doctorFix ? null : (await (deps.desktopBridgeRepairImpl || repairDoctorDesktopBridgeCatalog)({ fix: false, reason: 'doctor' }).catch(() => null))?.model_refresh ?? null;
743
+ const desktopBridgeBeforeFix = await inspectDoctorDesktopBridgeStatus({ processEnv: process.env }, { desktopBridgeStatusImpl: deps.desktopBridgeStatusImpl });
743
744
  const providerContext = deepDiagnostics
744
745
  ? await resolveProviderContext({ root, route: '$Doctor', serviceTier: process.env.SKS_SERVICE_TIER || 'fast' }).catch((err) => ({
745
746
  schema: 'sks.provider-context.v2',
@@ -1091,7 +1092,7 @@ async function runDoctor(args = [], root, doctorFix, deps = {}) {
1091
1092
  {
1092
1093
  id: 'desktop_bridge_catalog_repair',
1093
1094
  required_for_ready: false,
1094
- run: async () => repairDoctorDesktopBridgeCatalog({ fix: doctorFix })
1095
+ run: async () => { const repair = await repairDoctorDesktopBridgeCatalog({ fix: doctorFix }); desktopBridgeModelRefresh = repair.model_refresh ?? null; return repair; }
1095
1096
  }
1096
1097
  ].filter((phase) => doctorPhaseIds.includes(phase.id))
1097
1098
  }).catch((err) => ({
@@ -1118,11 +1119,9 @@ async function runDoctor(args = [], root, doctorFix, deps = {}) {
1118
1119
  }))
1119
1120
  : null;
1120
1121
  const doctorFixPostcheck = doctorFix ? (await import('../core/doctor/doctor-repair-postcheck.js')).doctorRepairPostcheck(doctorFixTransaction) : null;
1121
- const desktopBridge = doctorFixTransaction
1122
- ? await inspectDoctorDesktopBridgeStatus({
1123
- processEnv: process.env
1124
- }, { desktopBridgeStatusImpl: deps.desktopBridgeStatusImpl })
1125
- : desktopBridgeBeforeFix;
1122
+ const desktopBridge = { ...(doctorFixTransaction
1123
+ ? await inspectDoctorDesktopBridgeStatus({ processEnv: process.env }, { desktopBridgeStatusImpl: deps.desktopBridgeStatusImpl })
1124
+ : desktopBridgeBeforeFix), model_refresh: desktopBridgeModelRefresh };
1126
1125
  const startupRoleRepair = startupConfigRepair?.role_repair;
1127
1126
  const agentRoleConfigRepair = doctorFix && startupRoleRepair
1128
1127
  ? startupRoleRepair
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.7.0",
3
+ "package_version": "10.7.2",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -5,6 +5,7 @@ import { nowIso, readText, writeJsonAtomic } from '../fsx.js';
5
5
  import { inspectConfinedPath, moveConfinedPath, removeConfinedDirectoryIfEmpty, removeManagedPathVerified, uniqueConfinedPath, walkConfinedEntries } from '../managed-path-safety.js';
6
6
  import { MANAGED_OFFICIAL_SUBAGENT_ROLES, RETIRED_MANAGED_AGENT_ROLE_TOMBSTONES, managedAgentRoleOwnsText } from '../managed-assets/managed-assets-manifest.js';
7
7
  import { installOfficialSubagentAgentConfigs, refreshGlobalOfficialSubagentAgentConfigs } from '../subagents/official-subagent-config.js';
8
+ import { reconcileStaleSksModelPins } from './sks-model-pin-hygiene.js';
8
9
  export const AGENT_ROLE_CONFIG_REPAIR_SCHEMA = 'sks.agent-role-config-repair.v1';
9
10
  export async function repairAgentRoleConfigs(input) {
10
11
  const root = path.resolve(input.root);
@@ -18,6 +19,7 @@ export async function repairAgentRoleConfigs(input) {
18
19
  ...(input.globalRuntimeRoot ? { globalRuntimeRoot: input.globalRuntimeRoot } : {}),
19
20
  fix: input.apply === true
20
21
  });
22
+ const modelPinHygiene = await reconcileStaleSksModelPins({ root, codexHome, fix: input.apply === true });
21
23
  const officialRepair = await installOfficialSubagentAgentConfigs(root, { apply: input.apply === true });
22
24
  const globalRepair = path.join(root, '.codex') === codexHome
23
25
  ? null
@@ -38,7 +40,7 @@ export async function repairAgentRoleConfigs(input) {
38
40
  schema: AGENT_ROLE_CONFIG_REPAIR_SCHEMA,
39
41
  generated_at: nowIso(),
40
42
  ok: input.apply
41
- ? requiredFixes === appliedFixes && manualBlockers.length === 0 && retiredRoleCleanup.ok
43
+ ? requiredFixes === appliedFixes && manualBlockers.length === 0 && retiredRoleCleanup.ok && modelPinHygiene.errors.length === 0
42
44
  : manualBlockers.length === 0 && retiredRoleCleanup.ok && (globalRepair?.stale.length || 0) === 0,
43
45
  apply: input.apply === true,
44
46
  missing,
@@ -51,12 +53,14 @@ export async function repairAgentRoleConfigs(input) {
51
53
  manual_blockers: manualBlockers,
52
54
  manifest_role_ids: MANAGED_OFFICIAL_SUBAGENT_ROLES.map((role) => role.id),
53
55
  retired_role_cleanup: retiredRoleCleanup,
56
+ model_pin_hygiene: modelPinHygiene,
54
57
  warnings_suppressed: true,
55
58
  blockers: [
56
59
  ...manualBlockers,
57
60
  ...(!input.apply ? (globalRepair?.stale.map((file) => `stale_global_official_subagent_agent:${globalPath(file)}`) || []) : []),
58
61
  ...(input.apply && requiredFixes !== appliedFixes ? ['agent_role_config_repair_incomplete'] : []),
59
- ...(!retiredRoleCleanup.ok ? ['retired_agent_role_cleanup_incomplete'] : [])
62
+ ...(!retiredRoleCleanup.ok ? ['retired_agent_role_cleanup_incomplete'] : []),
63
+ ...modelPinHygiene.errors.map((error) => `model_pin_hygiene_failed:${error}`)
60
64
  ]
61
65
  };
62
66
  if (input.reportPath)
@@ -0,0 +1,150 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readText, writeTextAtomic } from '../fsx.js';
4
+ import { hasCodexLbProviderTable } from '../../cli/install-helpers-codex-lb-config.js';
5
+ import { hasSksManagedCodexConfigMarker, writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
6
+ import { bridgeRouteIndexPath, combinedBridgeCatalogPath } from '../codex-lb/combined-catalog.js';
7
+ import { readActiveCombinedBridgeCatalog } from '../codex-lb/combined-catalog/persistence.js';
8
+ import { backupInvalidToml } from '../subagents/official-subagent-config.js';
9
+ export const MODEL_PIN_HYGIENE_SCHEMA = 'sks.model-pin-hygiene.v1';
10
+ const LEGACY_ROLE_NAMES = new Set(['sks-planner', 'sks-implementer', 'sks-explorer', 'sks-checker']);
11
+ async function catalogPinState(codexHome) {
12
+ const read = await readActiveCombinedBridgeCatalog(combinedBridgeCatalogPath(codexHome), bridgeRouteIndexPath(codexHome)).catch(() => null);
13
+ if (!read?.ok)
14
+ return null;
15
+ const rows = new Map();
16
+ for (const row of read.catalog.models) {
17
+ for (const id of [row.public_id, row.slug])
18
+ if (typeof id === 'string' && id)
19
+ rows.set(id, row);
20
+ }
21
+ return (model) => {
22
+ const row = rows.get(model);
23
+ if (!row)
24
+ return 'unlisted';
25
+ const visibility = String(row.visibility || 'list').toLowerCase();
26
+ const upgrade = row.upgrade && typeof row.upgrade === 'object' ? row.upgrade.model : null;
27
+ return visibility === 'hide' || visibility === 'hidden' || (typeof upgrade === 'string' && upgrade) ? 'superseded' : 'current';
28
+ };
29
+ }
30
+ function topLevelEnd(lines) {
31
+ const end = lines.findIndex((line) => /^\s*\[/.test(line) || /^\s*developer_instructions\s*=/.test(line));
32
+ return end === -1 ? lines.length : end;
33
+ }
34
+ function stalePinLines(text, pinState, codexLbProviderDefined) {
35
+ const lines = text.split('\n');
36
+ const indexes = [];
37
+ const labels = [];
38
+ for (let index = 0; index < topLevelEnd(lines); index += 1) {
39
+ const match = /^\s*(model|model_provider)\s*=\s*"([^"]*)"\s*(?:#.*)?$/.exec(lines[index] || '');
40
+ if (!match)
41
+ continue;
42
+ const [, key, value] = match;
43
+ const stale = key === 'model_provider'
44
+ ? value === 'codex-lb' && !codexLbProviderDefined
45
+ : pinState !== null && pinState(value) !== 'current';
46
+ if (!stale)
47
+ continue;
48
+ indexes.push(index);
49
+ labels.push(`${key} = "${value}"`);
50
+ }
51
+ return { indexes, labels };
52
+ }
53
+ function withoutLines(text, indexes) {
54
+ const drop = new Set(indexes);
55
+ return text.split('\n').filter((_, index) => !drop.has(index)).join('\n');
56
+ }
57
+ const CODEX_LB_SELECTION_EVIDENCE = [
58
+ /^\s*model_provider\s*=\s*["']codex-lb["']\s*(?:#.*)?$/,
59
+ /^\s*\[model_providers\.(?:"codex-lb"|codex-lb)\]\s*(?:#.*)?$/
60
+ ];
61
+ function sksOwnedProjectConfig(text) {
62
+ const withoutSelection = text.split('\n')
63
+ .filter((line) => !CODEX_LB_SELECTION_EVIDENCE.some((pattern) => pattern.test(line)))
64
+ .join('\n');
65
+ return hasSksManagedCodexConfigMarker(withoutSelection);
66
+ }
67
+ function isLegacySksRole(file, text) {
68
+ if (/sks_managed_body_sha256/.test(text))
69
+ return false;
70
+ const name = /^\s*name\s*=\s*"([^"]+)"/m.exec(text)?.[1] ?? path.basename(file, '.toml');
71
+ return /^\s*description\s*=\s*"SKS managed\b/m.test(text) || LEGACY_ROLE_NAMES.has(name);
72
+ }
73
+ async function regularFile(file) {
74
+ const stat = await fsp.lstat(file).catch(() => null);
75
+ return Boolean(stat?.isFile() && !stat.isSymbolicLink());
76
+ }
77
+ async function roleFiles(dir) {
78
+ const entries = await fsp.readdir(dir).catch(() => []);
79
+ return entries.filter((name) => name.endsWith('.toml')).sort().map((name) => path.join(dir, name));
80
+ }
81
+ export async function reconcileStaleSksModelPins(input) {
82
+ const root = path.resolve(input.root);
83
+ const codexHome = path.resolve(input.codexHome);
84
+ const pinState = await catalogPinState(codexHome);
85
+ const report = {
86
+ schema: MODEL_PIN_HYGIENE_SCHEMA,
87
+ fix: input.fix,
88
+ catalog_available: pinState !== null,
89
+ changes: [],
90
+ remaining: [],
91
+ errors: []
92
+ };
93
+ const projectConfig = path.join(root, '.codex', 'config.toml');
94
+ const projectText = projectConfig !== path.join(codexHome, 'config.toml') && await regularFile(projectConfig)
95
+ ? await readText(projectConfig, '')
96
+ : null;
97
+ const providerDefinedAbove = hasCodexLbProviderTable(await readText(path.join(codexHome, 'config.toml'), ''))
98
+ || (projectText !== null && hasCodexLbProviderTable(projectText));
99
+ const agentDirs = [...new Set([path.join(codexHome, 'agents'), path.join(root, '.codex', 'agents')])];
100
+ for (const dir of agentDirs) {
101
+ for (const file of await roleFiles(dir)) {
102
+ if (!(await regularFile(file)))
103
+ continue;
104
+ const text = await readText(file, '');
105
+ if (!isLegacySksRole(file, text))
106
+ continue;
107
+ const pins = stalePinLines(text, pinState, providerDefinedAbove || hasCodexLbProviderTable(text));
108
+ if (!pins.indexes.length)
109
+ continue;
110
+ if (!input.fix) {
111
+ report.remaining.push({ file, removed: pins.labels, backup_path: null });
112
+ continue;
113
+ }
114
+ try {
115
+ const backup = await backupInvalidToml(file, text, 'model-pin-hygiene');
116
+ const mode = (await fsp.stat(file)).mode & 0o777;
117
+ await writeTextAtomic(file, withoutLines(text, pins.indexes), { mode });
118
+ report.changes.push({ file, removed: pins.labels, backup_path: backup });
119
+ }
120
+ catch (error) {
121
+ report.errors.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
122
+ report.remaining.push({ file, removed: pins.labels, backup_path: null });
123
+ }
124
+ }
125
+ }
126
+ if (projectText !== null) {
127
+ const text = projectText;
128
+ const pins = sksOwnedProjectConfig(text) ? stalePinLines(text, pinState, providerDefinedAbove) : { indexes: [], labels: [] };
129
+ if (pins.indexes.length && !input.fix)
130
+ report.remaining.push({ file: projectConfig, removed: pins.labels, backup_path: null });
131
+ else if (pins.indexes.length) {
132
+ const write = await writeCodexConfigGuarded({
133
+ root,
134
+ configPath: projectConfig,
135
+ before: text,
136
+ mutate: () => withoutLines(text, pins.indexes),
137
+ cause: 'model-pin-hygiene',
138
+ backupTag: 'model-pin-hygiene',
139
+ verifyUnchangedBeforeWrite: true
140
+ });
141
+ if (write.ok)
142
+ report.changes.push({ file: projectConfig, removed: pins.labels, backup_path: write.backup_path });
143
+ else {
144
+ report.errors.push(`${projectConfig}: ${write.status}`);
145
+ report.remaining.push({ file: projectConfig, removed: pins.labels, backup_path: null });
146
+ }
147
+ }
148
+ }
149
+ return report;
150
+ }
@@ -585,19 +585,24 @@ export function removeLegacyTopLevelCodexModeLocks(text = '') {
585
585
  const lines = String(ensureTrailingNewline(text) || '').split('\n');
586
586
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
587
587
  const end = firstTable === -1 ? lines.length : firstTable;
588
- return ensureTrailingNewline(lines.filter((line, index) => {
589
- if (index >= end)
590
- return true;
591
- if (!/^\s*(?:model|model_reasoning_effort)\s*=/.test(line))
592
- return true;
593
- return !hasSksModeLockProvenance(lines, index);
594
- }).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n'));
588
+ const drop = new Set();
589
+ for (let index = 0; index < end; index += 1) {
590
+ if (!/^\s*(?:model|model_reasoning_effort)\s*=/.test(lines[index] || ''))
591
+ continue;
592
+ const marker = sksModeLockProvenance(lines, index);
593
+ if (marker === null)
594
+ continue;
595
+ drop.add(index);
596
+ if (marker >= 0)
597
+ drop.add(marker);
598
+ }
599
+ return ensureTrailingNewline(lines.filter((_, index) => !drop.has(index)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n'));
595
600
  }
596
- function hasSksModeLockProvenance(lines, index) {
601
+ function sksModeLockProvenance(lines, index) {
597
602
  const current = String(lines[index] || '');
598
603
  const inlineComment = current.includes('#') ? current.slice(current.indexOf('#')) : '';
599
604
  if (isSksModeLockMarker(inlineComment))
600
- return true;
605
+ return -1;
601
606
  const allowedManagedKeys = new Set([
602
607
  'service_tier', 'model', 'model_reasoning_effort', 'model_provider',
603
608
  'approval_policy', 'sandbox_mode', 'web_search', 'notify', 'preferred_auth_method'
@@ -609,17 +614,17 @@ function hasSksModeLockProvenance(lines, index) {
609
614
  continue;
610
615
  if (candidate.startsWith('#')) {
611
616
  if (isSksModeLockMarker(candidate))
612
- return true;
617
+ return cursor;
613
618
  continue;
614
619
  }
615
620
  const key = candidate.match(/^([A-Za-z0-9_-]+)\s*=/)?.[1] || '';
616
621
  if (!allowedManagedKeys.has(key))
617
- return false;
622
+ return null;
618
623
  }
619
- return false;
624
+ return null;
620
625
  }
621
626
  function isSksModeLockMarker(value = '') {
622
- return /^#\s*(?:SKS|Sneakoscope)\b.*(?:moved machine-local Codex config|forced fast UI|managed (?:Codex )?(?:model|reasoning)|codex-lb (?:model|reasoning))/i.test(String(value || '').trim());
627
+ return /^#\s*(?:SKS|Sneakoscope)\b.*(?:forced fast UI|managed (?:Codex )?(?:model|reasoning)|codex-lb (?:model|reasoning))/i.test(String(value || '').trim());
623
628
  }
624
629
  function mergeLostFastUiKeys(before, nextInput) {
625
630
  let next = String(nextInput || '');
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { exists, nowIso, readText, sha256, writeJsonAtomic } from '../fsx.js';
5
+ import { HISTORICAL_DESKTOP_COMPAT_MARKER, HISTORICAL_OAUTH_SELECTION_MARKER, HISTORICAL_PROVIDER_SELECTION_MARKER } from '../codex-lb/desktop-bridge-migration/historical-toml.js';
5
6
  export const CODEX_APP_UI_STATE_SNAPSHOT_SCHEMA = 'sks.codex-app-ui-state-snapshot.v1';
6
7
  export const PROJECT_LOCAL_FORBIDDEN_CODEX_KEYS = [
7
8
  'openai_base_url',
@@ -14,6 +15,9 @@ export const PROJECT_LOCAL_FORBIDDEN_CODEX_KEYS = [
14
15
  const HOST_OWNED_KEY_RE = /^(?:model|model_reasoning_effort|openai_base_url|chatgpt_base_url|apps_mcp_product_sku|model_provider|model_providers(?:\.|$)|notify|profile|profiles(?:\.|$)|experimental_realtime_ws_base_url|otel(?:\.|$)|features\.fast_mode|service_tier|user\.fast_mode(?:\.|$))/;
15
16
  const SECRET_KEY_RE = /(?:key|token|secret|password|credential|cookie|authorization|auth|bearer|refresh|access|headers?|env)/i;
16
17
  const SKS_GLOBAL_UI_LOCK_KEYS = new Set(['model_provider', 'model', 'model_reasoning_effort']);
18
+ const RETIRED_SKS_SELECTION_MARKERS = new Set([
19
+ HISTORICAL_DESKTOP_COMPAT_MARKER, HISTORICAL_PROVIDER_SELECTION_MARKER, HISTORICAL_OAUTH_SELECTION_MARKER
20
+ ]);
17
21
  const SKS_GLOBAL_UI_LOCK_CONTEXT_KEYS = new Set([
18
22
  'service_tier', 'model_provider', 'model', 'model_reasoning_effort',
19
23
  'approval_policy', 'sandbox_mode', 'web_search', 'notify', 'preferred_auth_method'
@@ -155,6 +159,8 @@ export function isSksOwnedGlobalUiLock(lines, index) {
155
159
  const inlineComment = current.includes('#') ? current.slice(current.indexOf('#')) : '';
156
160
  if (isSksGlobalUiLockMarker(inlineComment))
157
161
  return true;
162
+ if (key === 'model_provider' && RETIRED_SKS_SELECTION_MARKERS.has(String(lines[index - 1] || '').trim()))
163
+ return true;
158
164
  const lowerBound = Math.max(0, index - 16);
159
165
  for (let cursor = index - 1; cursor >= lowerBound; cursor -= 1) {
160
166
  const candidate = String(lines[cursor] || '').trim();
@@ -174,7 +180,7 @@ export function isSksOwnedGlobalUiLock(lines, index) {
174
180
  return false;
175
181
  }
176
182
  function isSksGlobalUiLockMarker(value) {
177
- return /^#\s*(?:SKS|Sneakoscope)\b.*(?:moved machine-local Codex config|forced fast UI|legacy (?:provider|model|reasoning) lock|managed (?:Codex )?(?:provider|model|reasoning)|codex-lb (?:provider|model|reasoning))/i.test(String(value || '').trim());
183
+ return /^#\s*(?:SKS|Sneakoscope)\b.*(?:forced fast UI|legacy (?:provider|model|reasoning) lock|managed (?:Codex )?(?:provider|model|reasoning)|codex-lb (?:provider|model|reasoning))/i.test(String(value || '').trim());
178
184
  }
179
185
  export function scanProjectLocalForbiddenKeys(text) {
180
186
  const { signals } = scanTomlSignals(text);
@@ -33,7 +33,6 @@ export const NATIVE_SOURCE_FILES = [
33
33
  'ProvidersOpenRouterOnly.swift',
34
34
  'SubagentModelsModels.swift',
35
35
  'SubagentModelsViewController.swift',
36
- 'RemoteCodingViewController.swift',
37
36
  'DiagnosticsViewController.swift',
38
37
  'SettingsViewController.swift',
39
38
  'LocalDecisionModels.swift',
@@ -1,3 +1,8 @@
1
1
  export const MANAGED_ROUTING_RUNTIME = 'desktop-bridge';
2
2
  export const BRIDGE_PROVIDER_IDS = ['codex-lb', 'openrouter'];
3
3
  export const BRIDGE_OFFICIAL_ROUTE_ID = 'openai';
4
+ export const BRIDGE_REMOTE_COMPACTION = Object.freeze({
5
+ openai: 'native',
6
+ 'codex-lb': 'native',
7
+ openrouter: 'bridge_summary',
8
+ });