coding-tool-x 3.7.10 → 3.7.12

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 (54) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +1 -1
  3. package/dist/web/assets/{Analytics-CkUtnoz9.js → Analytics-8dRv1aYW.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-BwPqhE-N.js → ConfigTemplates-BQRXSICO.js} +1 -1
  5. package/dist/web/assets/Home-BtzWhKIa.js +1 -0
  6. package/dist/web/assets/Home-DYVy4cGZ.css +1 -0
  7. package/dist/web/assets/{PluginManager-JMXWl37b.js → PluginManager-BTa-Ygf6.js} +1 -1
  8. package/dist/web/assets/{ProjectList-CZwDgVvt.js → ProjectList-F_cB8ldA.js} +1 -1
  9. package/dist/web/assets/{SessionList-2fZmN7Au.js → SessionList-LOrbPQvu.js} +1 -1
  10. package/dist/web/assets/SkillManager-BsX8Qpj0.css +1 -0
  11. package/dist/web/assets/SkillManager-BzI_t-yV.js +1 -0
  12. package/dist/web/assets/{WorkspaceManager-B3lhhBD0.js → WorkspaceManager-Db9RGiz7.js} +1 -1
  13. package/dist/web/assets/index-DIWJmyL6.js +2 -0
  14. package/dist/web/assets/{index-C5nLTbOk.css → index-bJRG3RXH.css} +1 -1
  15. package/dist/web/index.html +2 -2
  16. package/docs/superpowers/plans/2026-08-01-omp-skill-scan-settings.md +759 -0
  17. package/docs/superpowers/specs/2026-08-01-omp-skill-scan-settings-design.md +224 -0
  18. package/package.json +1 -1
  19. package/src/commands/proxy-control.js +12 -12
  20. package/src/commands/toggle-proxy.js +16 -17
  21. package/src/commands/ui.js +11 -1
  22. package/src/config/default.js +1 -1
  23. package/src/config/paths.js +52 -29
  24. package/src/index.js +42 -16
  25. package/src/server/api/omp-channels.js +6 -4
  26. package/src/server/api/omp-config.js +1 -1
  27. package/src/server/api/omp-proxy.js +13 -42
  28. package/src/server/api/plugins.js +119 -47
  29. package/src/server/api/skills.js +63 -10
  30. package/src/server/index.js +3 -3
  31. package/src/server/omp-proxy-server.js +142 -10
  32. package/src/server/services/channel-scheduler.js +59 -9
  33. package/src/server/services/model-definition-schema.js +2 -1
  34. package/src/server/services/omp-auth-gateway-client.js +62 -0
  35. package/src/server/services/omp-channels.js +168 -12
  36. package/src/server/services/omp-config.js +71 -6
  37. package/src/server/services/omp-gateway-routing.js +295 -0
  38. package/src/server/services/omp-gateway.js +919 -0
  39. package/src/server/services/omp-native-plugin-adapter.js +456 -0
  40. package/src/server/services/omp-session-log-observer.js +138 -0
  41. package/src/server/services/omp-sessions.js +80 -0
  42. package/src/server/services/omp-settings-manager.js +338 -87
  43. package/src/server/services/omp-skill-discovery.js +358 -0
  44. package/src/server/services/omp-skill-settings-service.js +97 -0
  45. package/src/server/services/pi-omp-migration.js +273 -0
  46. package/src/server/services/platform-resolution.js +44 -0
  47. package/src/server/services/plugins-service.js +85 -561
  48. package/src/server/services/project-path-validation.js +65 -0
  49. package/src/server/services/skill-service.js +169 -77
  50. package/dist/web/assets/Home-BANl91zG.js +0 -1
  51. package/dist/web/assets/Home-CqnBXFxj.css +0 -1
  52. package/dist/web/assets/SkillManager-Bhzhkeis.js +0 -1
  53. package/dist/web/assets/SkillManager-D7pd-d_P.css +0 -1
  54. package/dist/web/assets/index-LYvv7wtx.js +0 -2
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const { spawnSync } = require('child_process');
4
4
  const yaml = require('js-yaml');
5
5
  const ompConfig = require('./omp-config');
6
+ const { prepareManagedOmpChannels } = require('./omp-gateway-routing');
6
7
  const { MODEL_METADATA, MODEL_ALIASES } = require('../../config/model-metadata');
7
8
  const {
8
9
  MODEL_METADATA_MODES,
@@ -15,7 +16,7 @@ const {
15
16
 
16
17
  const MANAGED_PROVIDER_PREFIX = 'ctx-';
17
18
  const MODELS_BACKUP_MARKER = '.ctx-backup-';
18
- const VISIBILITY_STATE_VERSION = 1;
19
+ const VISIBILITY_STATE_VERSION = 2;
19
20
  const MANAGED_VISIBILITY_STATE_FILE = 'coding-tool-x-omp-managed-visibility.json';
20
21
  const NO_MANAGED_MODELS_SELECTOR = `${MANAGED_PROVIDER_PREFIX}coding-tool-x/__no_models_configured__`;
21
22
  let lastManagedOmpSyncResult = null;
@@ -45,6 +46,12 @@ function normalizeProviderId(value = '') {
45
46
  }
46
47
 
47
48
  function getManagedProviderId(channel = {}) {
49
+ const explicitId = normalizeProviderId(channel.managedProviderId || '');
50
+ if (channel.managedProviderId) {
51
+ return explicitId.startsWith(MANAGED_PROVIDER_PREFIX)
52
+ ? explicitId
53
+ : `${MANAGED_PROVIDER_PREFIX}${explicitId}`;
54
+ }
48
55
  const baseId = normalizeProviderId(channel.providerKey || channel.provider || channel.name || channel.id);
49
56
  return baseId.startsWith(MANAGED_PROVIDER_PREFIX) ? baseId : `${MANAGED_PROVIDER_PREFIX}${baseId}`;
50
57
  }
@@ -116,6 +123,49 @@ function getOwnProperty(object, key) {
116
123
  : false;
117
124
  }
118
125
 
126
+ function cloneJson(value) {
127
+ if (value === undefined) return undefined;
128
+ return JSON.parse(JSON.stringify(value));
129
+ }
130
+
131
+ function captureFileSnapshots(filePaths = []) {
132
+ return filePaths.map((filePath) => ({
133
+ filePath,
134
+ existed: Boolean(filePath && fs.existsSync(filePath)),
135
+ content: filePath && fs.existsSync(filePath) ? fs.readFileSync(filePath) : null
136
+ }));
137
+ }
138
+
139
+ function restoreFileSnapshots(snapshots = []) {
140
+ snapshots.forEach((snapshot) => {
141
+ if (!snapshot.filePath) return;
142
+ if (snapshot.existed) {
143
+ ensureOmpDir(path.dirname(snapshot.filePath));
144
+ writeFileAtomic(snapshot.filePath, snapshot.content);
145
+ } else if (fs.existsSync(snapshot.filePath)) {
146
+ fs.unlinkSync(snapshot.filePath);
147
+ }
148
+ });
149
+ }
150
+
151
+ function writeFileAtomic(filePath, content) {
152
+ ensureOmpDir(path.dirname(filePath));
153
+ const temporaryPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
154
+ const mode = fs.existsSync(filePath)
155
+ ? fs.statSync(filePath).mode & 0o777
156
+ : 0o600;
157
+ try {
158
+ fs.writeFileSync(temporaryPath, content, { mode });
159
+ fs.chmodSync(temporaryPath, mode);
160
+ fs.renameSync(temporaryPath, filePath);
161
+ } catch (error) {
162
+ if (fs.existsSync(temporaryPath)) {
163
+ fs.unlinkSync(temporaryPath);
164
+ }
165
+ throw error;
166
+ }
167
+ }
168
+
119
169
  function normalizeModels(channel = {}, options = {}) {
120
170
  const models = [];
121
171
  const catalogIndex = options.catalogIndex instanceof Map ? options.catalogIndex : new Map();
@@ -170,7 +220,9 @@ function normalizeModels(channel = {}, options = {}) {
170
220
  }
171
221
 
172
222
  function readOmpCatalogModels(command, providerId, options = {}) {
173
- const requestedModelIds = uniqueStrings(options.requestedModelIds);
223
+ const requestedModelIds = uniqueStrings(options.requestedModelIds)
224
+ .map(modelId => splitModelSelector(modelId).modelId)
225
+ .filter(Boolean);
174
226
  const requested = new Set(requestedModelIds.map(modelId => modelId.toLowerCase()));
175
227
  const useFullCatalog = requested.size > 0;
176
228
  const cacheKey = `${command}\u0000${useFullCatalog ? `requested:${providerId}:${[...requested].sort().join(',')}` : providerId}`;
@@ -277,13 +329,32 @@ function buildProviderEntry(channel = {}, options = {}) {
277
329
  const providerName = channel.providerKey || channel.provider || channel.name || channel.id || 'provider';
278
330
  options.warnings.push(...normalizedProvider.warnings.map(message => `${providerName}: ${message}`));
279
331
  }
332
+ let models = normalizeModels(channel, { catalogIndex, warnings: options.warnings });
333
+ if (channel._ompGatewayRoute) {
334
+ models = models.map((model) => {
335
+ const sanitized = { ...model };
336
+ delete sanitized.baseUrl;
337
+ delete sanitized.headers;
338
+ return sanitized;
339
+ });
340
+ }
280
341
  const entry = compactObject({
281
342
  ...normalizedProvider.config,
282
343
  baseUrl: String(channel.baseUrl || providerConfig.baseUrl || '').trim(),
283
344
  api: normalizeProviderApi(channel.providerApi || channel.api || channel.wireApi || providerConfig.api),
284
- models: normalizeModels(channel, { catalogIndex, warnings: options.warnings })
345
+ models
285
346
  });
286
347
 
348
+ if (channel._ompGatewayRoute) {
349
+ delete entry.headers;
350
+ delete entry.apiKey;
351
+ if (channel.authMode === 'none') {
352
+ entry.auth = 'none';
353
+ } else {
354
+ delete entry.auth;
355
+ }
356
+ }
357
+
287
358
  const apiKey = String(channel.apiKey || providerConfig.apiKey || '').trim();
288
359
  if (apiKey) {
289
360
  entry.apiKey = apiKey;
@@ -321,7 +392,7 @@ function writeModelsConfig(config, filePath = getOmpPaths().modelsYml) {
321
392
  noRefs: true,
322
393
  sortKeys: false
323
394
  });
324
- fs.writeFileSync(filePath, doc, 'utf8');
395
+ writeFileAtomic(filePath, doc);
325
396
  }
326
397
 
327
398
  function getOmpSettingsPath(paths = getOmpPaths()) {
@@ -359,7 +430,7 @@ function writeOmpSettingsConfig(config, filePath = getOmpSettingsPath()) {
359
430
  noRefs: true,
360
431
  sortKeys: false
361
432
  });
362
- fs.writeFileSync(filePath, doc, 'utf8');
433
+ writeFileAtomic(filePath, doc);
363
434
  }
364
435
 
365
436
  function getModelsBackupPrefix(filePath) {
@@ -420,7 +491,7 @@ function readManagedVisibilityState(paths = getOmpPaths()) {
420
491
  function writeManagedVisibilityState(state, paths = getOmpPaths()) {
421
492
  const filePath = getManagedVisibilityStatePath(paths);
422
493
  ensureOmpDir(path.dirname(filePath));
423
- fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
494
+ writeFileAtomic(filePath, `${JSON.stringify(state, null, 2)}\n`);
424
495
  return filePath;
425
496
  }
426
497
 
@@ -602,6 +673,9 @@ function buildManagedModelsConfig(channels = [], baseConfig = readModelsConfig()
602
673
  }
603
674
 
604
675
  function getOriginalProviderId(channel = {}) {
676
+ if (channel.originalProviderId) {
677
+ return normalizeProviderId(channel.originalProviderId);
678
+ }
605
679
  const providerId = normalizeProviderId(channel.providerKey || channel.provider || channel.name || channel.id);
606
680
  return isManagedProviderId(providerId)
607
681
  ? providerId.slice(MANAGED_PROVIDER_PREFIX.length)
@@ -679,11 +753,18 @@ function captureOriginalVisibility(settings = {}) {
679
753
  const modelRoles = settings.modelRoles && typeof settings.modelRoles === 'object' && !Array.isArray(settings.modelRoles)
680
754
  ? settings.modelRoles
681
755
  : {};
756
+ const fallbackChains = settings.retry && typeof settings.retry === 'object' && !Array.isArray(settings.retry)
757
+ ? settings.retry.fallbackChains
758
+ : undefined;
682
759
  return {
683
760
  enabledModels: getOwnProperty(settings, 'enabledModels') ? uniqueStrings(settings.enabledModels) : null,
684
761
  disabledProviders: getOwnProperty(settings, 'disabledProviders') ? uniqueStrings(settings.disabledProviders) : null,
685
762
  modelRolesHadDefault: getOwnProperty(modelRoles, 'default'),
686
- modelRolesDefault: getOwnProperty(modelRoles, 'default') ? String(modelRoles.default || '') : null
763
+ modelRolesDefault: getOwnProperty(modelRoles, 'default') ? String(modelRoles.default || '') : null,
764
+ modelRolesHadValue: getOwnProperty(settings, 'modelRoles'),
765
+ modelRoles: getOwnProperty(settings, 'modelRoles') ? cloneJson(modelRoles) : null,
766
+ retryFallbackChainsHadValue: fallbackChains !== undefined,
767
+ retryFallbackChains: fallbackChains !== undefined ? cloneJson(fallbackChains) : null
687
768
  };
688
769
  }
689
770
 
@@ -701,22 +782,86 @@ function collectUserDisabledProviders(settings = {}, previousState = null) {
701
782
  return uniqueStrings([...originalDisabled, ...userAddedDuringManagedMode]);
702
783
  }
703
784
 
704
- function applyManagedModelRole(settings, visibility, previousState = null) {
705
- const roles = settings.modelRoles && typeof settings.modelRoles === 'object' && !Array.isArray(settings.modelRoles)
706
- ? { ...settings.modelRoles }
707
- : {};
708
- const currentDefault = String(roles.default || '').trim();
709
- const previousManagedDefault = String(previousState?.managedDefaultModel || '').trim();
710
- if (!visibility.managedDefaultModel) {
711
- return roles;
785
+ function buildProviderMappings(channels = []) {
786
+ const originalToManaged = {};
787
+ const managedToOriginal = {};
788
+ channels.forEach((channel) => {
789
+ const original = getOriginalProviderId(channel);
790
+ const managed = getManagedProviderId(channel);
791
+ if (!original || !managed) return;
792
+ if (!originalToManaged[original]) {
793
+ originalToManaged[original] = managed;
794
+ }
795
+ managedToOriginal[managed] = original;
796
+ });
797
+ return { originalToManaged, managedToOriginal };
798
+ }
799
+
800
+ function rewriteModelSelector(value, providerMap = {}) {
801
+ if (typeof value !== 'string') return value;
802
+ const index = value.indexOf('/');
803
+ if (index <= 0) return value;
804
+ const providerId = value.slice(0, index);
805
+ const replacement = providerMap[providerId];
806
+ return replacement ? `${replacement}${value.slice(index)}` : value;
807
+ }
808
+
809
+ function rewriteSelectorTree(value, providerMap = {}) {
810
+ if (typeof value === 'string') return rewriteModelSelector(value, providerMap);
811
+ if (Array.isArray(value)) return value.map(item => rewriteSelectorTree(item, providerMap));
812
+ if (value && typeof value === 'object') {
813
+ return Object.fromEntries(
814
+ Object.entries(value).map(([key, item]) => [key, rewriteSelectorTree(item, providerMap)])
815
+ );
816
+ }
817
+ return value;
818
+ }
819
+
820
+ function removeManagedSelectorTree(value) {
821
+ if (typeof value === 'string') {
822
+ const providerId = String(value).split('/')[0];
823
+ return isManagedProviderId(providerId) ? undefined : value;
824
+ }
825
+ if (Array.isArray(value)) {
826
+ return value
827
+ .map(removeManagedSelectorTree)
828
+ .filter(item => item !== undefined);
829
+ }
830
+ if (value && typeof value === 'object') {
831
+ return Object.fromEntries(
832
+ Object.entries(value)
833
+ .map(([key, item]) => [key, removeManagedSelectorTree(item)])
834
+ .filter(([, item]) => item !== undefined)
835
+ );
712
836
  }
713
- const defaultIsManaged = visibility.managedEnabledModels.includes(currentDefault);
714
- if (!currentDefault || !defaultIsManaged || currentDefault === previousManagedDefault) {
837
+ return value;
838
+ }
839
+
840
+ function applyManagedModelRoles(settings, visibility, mappings, previousState = null) {
841
+ const originalRoles = previousState?.active && previousState.original?.modelRolesHadValue
842
+ ? previousState.original.modelRoles
843
+ : settings.modelRoles;
844
+ const roles = originalRoles && typeof originalRoles === 'object' && !Array.isArray(originalRoles)
845
+ ? rewriteSelectorTree(cloneJson(originalRoles), mappings.originalToManaged)
846
+ : {};
847
+ const originalDefault = String(originalRoles?.default || '');
848
+ const mappedDefault = rewriteModelSelector(originalDefault, mappings.originalToManaged);
849
+ const defaultWasMapped = originalDefault && mappedDefault !== originalDefault;
850
+ const defaultIsManaged = isManagedProviderId(String(roles.default || '').split('/')[0]);
851
+ if (visibility.managedDefaultModel && (!roles.default || (!defaultWasMapped && !defaultIsManaged))) {
715
852
  roles.default = visibility.managedDefaultModel;
716
853
  }
717
854
  return roles;
718
855
  }
719
856
 
857
+ function applyManagedFallbackChains(settings, mappings, previousState = null) {
858
+ const source = previousState?.active && previousState.original?.retryFallbackChainsHadValue
859
+ ? previousState.original.retryFallbackChains
860
+ : settings.retry?.fallbackChains;
861
+ if (source === undefined) return undefined;
862
+ return rewriteSelectorTree(cloneJson(source), mappings.originalToManaged);
863
+ }
864
+
720
865
  function syncManagedOmpVisibility(channels = [], modelsConfig = { providers: {} }, options = {}) {
721
866
  const paths = getOmpPaths();
722
867
  const settingsPath = getOmpSettingsPath(paths);
@@ -724,6 +869,7 @@ function syncManagedOmpVisibility(channels = [], modelsConfig = { providers: {}
724
869
  const settings = readOmpSettingsConfig(settingsPath);
725
870
  const previousState = readManagedVisibilityState(paths);
726
871
  const visibility = collectManagedVisibility(channels, modelsConfig, options);
872
+ const mappings = buildProviderMappings(channels);
727
873
  const original = previousState?.active && previousState.original
728
874
  ? previousState.original
729
875
  : captureOriginalVisibility(settings);
@@ -735,7 +881,13 @@ function syncManagedOmpVisibility(channels = [], modelsConfig = { providers: {}
735
881
  ...collectUserDisabledProviders(settings, previousState),
736
882
  ...visibility.managedDisabledProviders
737
883
  ]);
738
- settings.modelRoles = applyManagedModelRole(settings, visibility, previousState);
884
+ settings.modelRoles = applyManagedModelRoles(settings, visibility, mappings, previousState);
885
+ const fallbackChains = applyManagedFallbackChains(settings, mappings, previousState);
886
+ if (fallbackChains !== undefined) {
887
+ settings.retry = settings.retry && typeof settings.retry === 'object' && !Array.isArray(settings.retry)
888
+ ? { ...settings.retry, fallbackChains }
889
+ : { fallbackChains };
890
+ }
739
891
 
740
892
  const after = JSON.stringify(settings);
741
893
  if (before !== after) {
@@ -750,7 +902,12 @@ function syncManagedOmpVisibility(channels = [], modelsConfig = { providers: {}
750
902
  managedEnabledModelsForSettings: visibility.managedEnabledModelsForSettings,
751
903
  managedDisabledProviders: visibility.managedDisabledProviders,
752
904
  managedDefaultModel: visibility.managedDefaultModel,
905
+ providerMappings: mappings,
753
906
  original,
907
+ applied: {
908
+ modelRoles: cloneJson(settings.modelRoles),
909
+ retryFallbackChains: cloneJson(settings.retry?.fallbackChains)
910
+ },
754
911
  updatedAt: new Date().toISOString()
755
912
  };
756
913
  writeManagedVisibilityState(state, paths);
@@ -819,7 +976,39 @@ function removeManagedOmpVisibility(options = {}) {
819
976
  warnings.push('OMP disabledProviders was changed while coding-tool-x managed visibility was active; removed only managed provider entries.');
820
977
  }
821
978
 
822
- if (settings.modelRoles && typeof settings.modelRoles === 'object' && !Array.isArray(settings.modelRoles)) {
979
+ if (state.version >= 2 && state.original) {
980
+ const currentRoles = settings.modelRoles && typeof settings.modelRoles === 'object' && !Array.isArray(settings.modelRoles)
981
+ ? settings.modelRoles
982
+ : undefined;
983
+ if (JSON.stringify(currentRoles) === JSON.stringify(state.applied?.modelRoles)) {
984
+ if (state.original.modelRolesHadValue) {
985
+ settings.modelRoles = cloneJson(state.original.modelRoles);
986
+ } else {
987
+ delete settings.modelRoles;
988
+ }
989
+ } else if (currentRoles) {
990
+ settings.modelRoles = rewriteSelectorTree(currentRoles, state.providerMappings?.managedToOriginal || {});
991
+ warnings.push('OMP modelRoles was changed while coding-tool-x managed visibility was active; restored managed provider references only.');
992
+ }
993
+
994
+ const currentFallback = settings.retry?.fallbackChains;
995
+ if (JSON.stringify(currentFallback) === JSON.stringify(state.applied?.retryFallbackChains)) {
996
+ if (state.original.retryFallbackChainsHadValue) {
997
+ settings.retry = settings.retry && typeof settings.retry === 'object' && !Array.isArray(settings.retry)
998
+ ? { ...settings.retry, fallbackChains: cloneJson(state.original.retryFallbackChains) }
999
+ : { fallbackChains: cloneJson(state.original.retryFallbackChains) };
1000
+ } else if (settings.retry && typeof settings.retry === 'object' && !Array.isArray(settings.retry)) {
1001
+ delete settings.retry.fallbackChains;
1002
+ if (Object.keys(settings.retry).length === 0) delete settings.retry;
1003
+ }
1004
+ } else if (currentFallback !== undefined) {
1005
+ settings.retry.fallbackChains = rewriteSelectorTree(
1006
+ currentFallback,
1007
+ state.providerMappings?.managedToOriginal || {}
1008
+ );
1009
+ warnings.push('OMP retry.fallbackChains was changed while coding-tool-x managed visibility was active; restored managed provider references only.');
1010
+ }
1011
+ } else if (settings.modelRoles && typeof settings.modelRoles === 'object' && !Array.isArray(settings.modelRoles)) {
823
1012
  const roles = { ...settings.modelRoles };
824
1013
  if (roles.default === state.managedDefaultModel) {
825
1014
  if (state.original?.modelRolesHadDefault) {
@@ -845,6 +1034,39 @@ function removeManagedOmpVisibility(options = {}) {
845
1034
  }
846
1035
  warnings.push('Removed stale coding-tool-x managed OMP enabledModels entries without a visibility state file.');
847
1036
  }
1037
+
1038
+ if (settings.modelRoles && typeof settings.modelRoles === 'object' && !Array.isArray(settings.modelRoles)) {
1039
+ const roles = Object.fromEntries(
1040
+ Object.entries(settings.modelRoles).filter(([, model]) => {
1041
+ const providerId = String(model || '').split('/')[0];
1042
+ return !isManagedProviderId(providerId);
1043
+ })
1044
+ );
1045
+ if (Object.keys(roles).length !== Object.keys(settings.modelRoles).length) {
1046
+ settingsBackupPath = settingsBackupPath || createModelsBackupIfNeeded(settingsPath, options);
1047
+ if (Object.keys(roles).length > 0) {
1048
+ settings.modelRoles = roles;
1049
+ } else {
1050
+ delete settings.modelRoles;
1051
+ }
1052
+ warnings.push('Removed stale coding-tool-x managed OMP modelRoles entries without a visibility state file.');
1053
+ }
1054
+ }
1055
+
1056
+ const currentFallback = settings.retry?.fallbackChains;
1057
+ if (currentFallback !== undefined) {
1058
+ const fallbackChains = removeManagedSelectorTree(currentFallback);
1059
+ if (JSON.stringify(fallbackChains) !== JSON.stringify(currentFallback)) {
1060
+ settingsBackupPath = settingsBackupPath || createModelsBackupIfNeeded(settingsPath, options);
1061
+ if (fallbackChains && typeof fallbackChains === 'object' && Object.keys(fallbackChains).length > 0) {
1062
+ settings.retry.fallbackChains = fallbackChains;
1063
+ } else {
1064
+ delete settings.retry.fallbackChains;
1065
+ if (Object.keys(settings.retry).length === 0) delete settings.retry;
1066
+ }
1067
+ warnings.push('Removed stale coding-tool-x managed OMP retry.fallbackChains entries without a visibility state file.');
1068
+ }
1069
+ }
848
1070
  }
849
1071
 
850
1072
  const after = JSON.stringify(settings);
@@ -865,81 +1087,110 @@ function removeManagedOmpVisibility(options = {}) {
865
1087
 
866
1088
  function writeManagedOmpProviders(channels = [], options = {}) {
867
1089
  const paths = getOmpPaths();
1090
+ const settingsPath = getOmpSettingsPath(paths);
1091
+ const statePath = getManagedVisibilityStatePath(paths);
1092
+ const snapshots = captureFileSnapshots([paths.modelsYml, settingsPath, statePath]);
868
1093
  const buildWarnings = [];
869
- const config = buildManagedModelsConfig(channels, readModelsConfig(paths.modelsYml), {
870
- ...options,
871
- warnings: buildWarnings
872
- });
873
- const backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
874
- writeModelsConfig(config, paths.modelsYml);
875
- const visibility = syncManagedOmpVisibility(channels, config, options);
876
- const validation = options.validateWithCli === true
877
- ? validateOmpModelsConfig(options)
878
- : { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
879
- const warnings = [
880
- ...buildWarnings,
881
- ...(visibility.warnings || []),
882
- ...(validation.warnings || [])
883
- ];
884
- recordManagedOmpSyncResult({
885
- path: paths.modelsYml,
886
- modelsPath: paths.modelsYml,
887
- settingsPath: visibility.settingsPath,
888
- statePath: visibility.statePath,
889
- backupPath,
890
- modelsBackupPath: backupPath,
891
- settingsBackupPath: visibility.settingsBackupPath,
892
- changed: true,
893
- visibilityChanged: visibility.changed,
894
- managedEnabledModels: visibility.managedEnabledModels,
895
- managedEnabledModelsForSettings: visibility.managedEnabledModelsForSettings,
896
- managedDisabledProviders: visibility.managedDisabledProviders,
897
- managedDefaultModel: visibility.managedDefaultModel,
898
- validation,
899
- warnings
1094
+ const orderedChannels = options.activeChannelId
1095
+ ? [
1096
+ ...channels.filter(channel => channel?.id === options.activeChannelId),
1097
+ ...channels.filter(channel => channel?.id !== options.activeChannelId)
1098
+ ]
1099
+ : channels;
1100
+ const prepared = options.gateway
1101
+ ? prepareManagedOmpChannels(orderedChannels, options.gateway)
1102
+ : { managedChannels: orderedChannels, unsupportedChannels: [] };
1103
+ const effectiveChannels = prepared.managedChannels;
1104
+ prepared.unsupportedChannels.forEach((channel) => {
1105
+ buildWarnings.push(`OMP channel "${channel.name || channel.id}" was not managed: ${channel.reason}.`);
900
1106
  });
901
- removeLegacyManagedExtension(paths);
902
- return paths.modelsYml;
903
- }
904
-
905
- function removeManagedOmpProviders(options = {}) {
906
- const paths = getOmpPaths();
907
- const config = readModelsConfig(paths.modelsYml);
908
- const visibility = removeManagedOmpVisibility(options);
909
- const before = Object.keys(config.providers || {}).length;
910
- pruneManagedProviders(config.providers);
911
- const after = Object.keys(config.providers || {}).length;
912
- let backupPath = null;
913
- let validation = {
914
- skipped: true,
915
- reason: before === after ? 'no-managed-providers' : 'not-run',
916
- warnings: []
917
- };
918
- if (before !== after) {
919
- backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
1107
+ try {
1108
+ const config = buildManagedModelsConfig(effectiveChannels, readModelsConfig(paths.modelsYml), {
1109
+ ...options,
1110
+ warnings: buildWarnings
1111
+ });
1112
+ const backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
920
1113
  writeModelsConfig(config, paths.modelsYml);
921
- validation = options.validateWithCli === true
1114
+ const visibility = syncManagedOmpVisibility(effectiveChannels, config, options);
1115
+ const validation = options.validateWithCli === true
922
1116
  ? validateOmpModelsConfig(options)
923
1117
  : { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
924
- }
925
- recordManagedOmpSyncResult({
926
- path: paths.modelsYml,
927
- modelsPath: paths.modelsYml,
928
- settingsPath: visibility.settingsPath,
929
- statePath: visibility.statePath,
930
- backupPath,
931
- modelsBackupPath: backupPath,
932
- settingsBackupPath: visibility.settingsBackupPath,
933
- changed: before !== after,
934
- visibilityChanged: visibility.changed,
935
- stateRemoved: visibility.stateRemoved,
936
- validation,
937
- warnings: [
1118
+ const warnings = [
1119
+ ...buildWarnings,
938
1120
  ...(visibility.warnings || []),
939
1121
  ...(validation.warnings || [])
940
- ]
941
- });
942
- removeLegacyManagedExtension(paths);
1122
+ ];
1123
+ recordManagedOmpSyncResult({
1124
+ path: paths.modelsYml,
1125
+ modelsPath: paths.modelsYml,
1126
+ settingsPath: visibility.settingsPath,
1127
+ statePath: visibility.statePath,
1128
+ backupPath,
1129
+ modelsBackupPath: backupPath,
1130
+ settingsBackupPath: visibility.settingsBackupPath,
1131
+ changed: true,
1132
+ visibilityChanged: visibility.changed,
1133
+ managedEnabledModels: visibility.managedEnabledModels,
1134
+ managedEnabledModelsForSettings: visibility.managedEnabledModelsForSettings,
1135
+ managedDisabledProviders: visibility.managedDisabledProviders,
1136
+ managedDefaultModel: visibility.managedDefaultModel,
1137
+ validation,
1138
+ warnings
1139
+ });
1140
+ removeLegacyManagedExtension(paths);
1141
+ return paths.modelsYml;
1142
+ } catch (error) {
1143
+ restoreFileSnapshots(snapshots);
1144
+ throw error;
1145
+ }
1146
+ }
1147
+
1148
+ function removeManagedOmpProviders(options = {}) {
1149
+ const paths = getOmpPaths();
1150
+ const settingsPath = getOmpSettingsPath(paths);
1151
+ const statePath = getManagedVisibilityStatePath(paths);
1152
+ const snapshots = captureFileSnapshots([paths.modelsYml, settingsPath, statePath]);
1153
+ try {
1154
+ const config = readModelsConfig(paths.modelsYml);
1155
+ const visibility = removeManagedOmpVisibility(options);
1156
+ const before = Object.keys(config.providers || {}).length;
1157
+ pruneManagedProviders(config.providers);
1158
+ const after = Object.keys(config.providers || {}).length;
1159
+ let backupPath = null;
1160
+ let validation = {
1161
+ skipped: true,
1162
+ reason: before === after ? 'no-managed-providers' : 'not-run',
1163
+ warnings: []
1164
+ };
1165
+ if (before !== after) {
1166
+ backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
1167
+ writeModelsConfig(config, paths.modelsYml);
1168
+ validation = options.validateWithCli === true
1169
+ ? validateOmpModelsConfig(options)
1170
+ : { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
1171
+ }
1172
+ recordManagedOmpSyncResult({
1173
+ path: paths.modelsYml,
1174
+ modelsPath: paths.modelsYml,
1175
+ settingsPath: visibility.settingsPath,
1176
+ statePath: visibility.statePath,
1177
+ backupPath,
1178
+ modelsBackupPath: backupPath,
1179
+ settingsBackupPath: visibility.settingsBackupPath,
1180
+ changed: before !== after,
1181
+ visibilityChanged: visibility.changed,
1182
+ stateRemoved: visibility.stateRemoved,
1183
+ validation,
1184
+ warnings: [
1185
+ ...(visibility.warnings || []),
1186
+ ...(validation.warnings || [])
1187
+ ]
1188
+ });
1189
+ removeLegacyManagedExtension(paths);
1190
+ } catch (error) {
1191
+ restoreFileSnapshots(snapshots);
1192
+ throw error;
1193
+ }
943
1194
  }
944
1195
 
945
1196
  function isManagedOmpProvidersActive() {