codexmate 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +7 -2
  2. package/README.zh.md +7 -2
  3. package/cli/builtin-proxy.js +636 -95
  4. package/cli/openai-bridge.js +497 -5
  5. package/cli.js +75 -29
  6. package/lib/cli-models-utils.js +71 -10
  7. package/package.json +3 -1
  8. package/plugins/prompt-templates/computed.mjs +1 -1
  9. package/plugins/prompt-templates/methods.mjs +0 -66
  10. package/plugins/prompt-templates/overview.mjs +1 -0
  11. package/web-ui/app.js +16 -16
  12. package/web-ui/logic.codex.mjs +56 -0
  13. package/web-ui/logic.sessions.mjs +56 -0
  14. package/web-ui/modules/app.computed.dashboard.mjs +54 -0
  15. package/web-ui/modules/app.computed.session.mjs +48 -0
  16. package/web-ui/modules/app.methods.claude-config.mjs +18 -7
  17. package/web-ui/modules/app.methods.codex-config.mjs +35 -3
  18. package/web-ui/modules/app.methods.providers.mjs +9 -1
  19. package/web-ui/modules/app.methods.session-actions.mjs +2 -5
  20. package/web-ui/modules/app.methods.session-browser.mjs +4 -5
  21. package/web-ui/modules/app.methods.session-trash.mjs +19 -4
  22. package/web-ui/modules/app.methods.startup-claude.mjs +12 -1
  23. package/web-ui/modules/i18n.dict.mjs +28 -32
  24. package/web-ui/modules/provider-url-display.mjs +17 -0
  25. package/web-ui/partials/index/panel-config-claude.html +5 -1
  26. package/web-ui/partials/index/panel-config-codex.html +33 -4
  27. package/web-ui/partials/index/panel-plugins.html +3 -29
  28. package/web-ui/partials/index/panel-sessions.html +0 -10
  29. package/web-ui/partials/index/panel-settings.html +62 -67
  30. package/web-ui/partials/index/panel-usage.html +31 -2
  31. package/web-ui/session-helpers.mjs +2 -2
  32. package/web-ui/styles/base-theme.css +47 -34
  33. package/web-ui/styles/controls-forms.css +27 -28
  34. package/web-ui/styles/layout-shell.css +37 -34
  35. package/web-ui/styles/modals-core.css +12 -10
  36. package/web-ui/styles/navigation-panels.css +36 -35
  37. package/web-ui/styles/responsive.css +4 -4
  38. package/web-ui/styles/sessions-list.css +10 -6
  39. package/web-ui/styles/sessions-usage.css +95 -0
  40. package/web-ui/styles/settings-panel.css +19 -0
  41. package/web-ui/styles/titles-cards.css +90 -26
@@ -1,3 +1,5 @@
1
+ import { getProviderDisplayUrl, checkIsTransformProvider } from './provider-url-display.mjs';
2
+ import { getCodexModelCatalogForProvider, CODEX_PROVIDER_TEMPLATES } from '../logic.codex.mjs';
1
3
  export function createDashboardComputed() {
2
4
  return {
3
5
  agentsDiffHasChanges() {
@@ -29,6 +31,50 @@ export function createDashboardComputed() {
29
31
  }
30
32
  return list;
31
33
  },
34
+ activeProviderModel() {
35
+ return (name) => {
36
+ const target = String(name || '').trim();
37
+ if (!target) return '';
38
+ const dict = this.currentModels && typeof this.currentModels === 'object' ? this.currentModels : {};
39
+ const fromDict = typeof dict[target] === 'string' ? dict[target].trim() : '';
40
+ if (fromDict) return fromDict;
41
+ const activeName = String(this.currentProvider || '').trim();
42
+ if (target === activeName) {
43
+ const top = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
44
+ if (top && top !== '未设置') return top;
45
+ }
46
+ return '';
47
+ };
48
+ },
49
+ codexModelOptions() {
50
+ const seen = new Set();
51
+ const out = [];
52
+ const push = (val) => {
53
+ const s = typeof val === 'string' ? val.trim() : '';
54
+ if (!s || seen.has(s)) return;
55
+ seen.add(s);
56
+ out.push(s);
57
+ };
58
+ const activeName = String(this.displayCurrentProvider || '').trim();
59
+ const current = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
60
+ if (current && current !== '未设置') push(current);
61
+ const dict = this.currentModels && typeof this.currentModels === 'object' ? this.currentModels : {};
62
+ if (activeName && typeof dict[activeName] === 'string') push(dict[activeName]);
63
+ const remote = Array.isArray(this.models) ? this.models : [];
64
+ for (const m of remote) push(m);
65
+ const list = Array.isArray(this.providersList) ? this.providersList : [];
66
+ const activeProvider = list.find((p) => p && p.name === activeName);
67
+ if (activeProvider) {
68
+ for (const m of getCodexModelCatalogForProvider(activeProvider)) push(m);
69
+ }
70
+ return out;
71
+ },
72
+ codexModelHasList() {
73
+ return this.codexModelOptions.length > 0;
74
+ },
75
+ codexProviderTemplates() {
76
+ return CODEX_PROVIDER_TEMPLATES;
77
+ },
32
78
  displayCurrentProvider() {
33
79
  const switching = String(this.providerSwitchDisplayTarget || '').trim();
34
80
  if (switching) return switching;
@@ -39,6 +85,14 @@ export function createDashboardComputed() {
39
85
  const list = Array.isArray(this.providersList) ? this.providersList : [];
40
86
  return list;
41
87
  },
88
+
89
+ displayProviderUrl() {
90
+ return (provider) => getProviderDisplayUrl(provider);
91
+ },
92
+
93
+ isTransformProvider() {
94
+ return (provider) => checkIsTransformProvider(provider);
95
+ },
42
96
  installTargetCards() {
43
97
  const targets = Array.isArray(this.installStatusTargets) && this.installStatusTargets.length
44
98
  ? this.installStatusTargets
@@ -2,6 +2,7 @@ import {
2
2
  buildSessionTimelineNodes,
3
3
  buildUsageChartGroups,
4
4
  buildUsageHeatmap,
5
+ buildUsageHourlyHeatmap,
5
6
  isSessionQueryEnabled
6
7
  } from '../logic.mjs';
7
8
  import { SESSION_TRASH_PAGE_SIZE } from './app.constants.mjs';
@@ -580,6 +581,53 @@ export function createSessionComputed() {
580
581
  weekdayAxis
581
582
  };
582
583
  },
584
+ sessionUsageHourlyHeatmap() {
585
+ const sessions = this.sessionUsageCharts && Array.isArray(this.sessionUsageCharts.filteredSessions)
586
+ ? this.sessionUsageCharts.filteredSessions
587
+ : this.sessionsUsageList;
588
+ const result = buildUsageHourlyHeatmap(sessions, { range: this.sessionsUsageTimeRange });
589
+ const t = typeof this.t === 'function' ? this.t : null;
590
+ const lang = typeof this.lang === 'string' ? this.lang.trim().toLowerCase() : '';
591
+ const weekdayLabelsZh = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
592
+ const weekdayLabelsEn = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
593
+ const weekdayLabels = lang === 'en' ? weekdayLabelsEn : weekdayLabelsZh;
594
+ const max = Math.max(1, result.maxSessionCount);
595
+ const grid = Array.isArray(result.grid) ? result.grid : [];
596
+ const rows = grid.map((cells, dayIndex) => ({
597
+ weekday: weekdayLabels[dayIndex] || '',
598
+ cells: cells.map((cell, hourIndex) => {
599
+ const ratio = cell.sessionCount > 0 ? (cell.sessionCount / max) : 0;
600
+ const level = cell.sessionCount <= 0
601
+ ? 0
602
+ : (ratio <= 0.25 ? 1 : (ratio <= 0.5 ? 2 : (ratio <= 0.75 ? 3 : 4)));
603
+ const hourLabel = String(hourIndex).padStart(2, '0');
604
+ const tooltipText = t
605
+ ? t('usage.hourlyHeatmap.tooltip', {
606
+ weekday: weekdayLabels[dayIndex],
607
+ hour: hourLabel,
608
+ sessions: cell.sessionCount,
609
+ messages: cell.messageCount,
610
+ tokens: (cell.tokenTotal || 0).toLocaleString('en-US')
611
+ })
612
+ : `${weekdayLabels[dayIndex] || ''} ${hourLabel}:00 · ${cell.sessionCount} sessions · ${cell.messageCount} messages · ${(cell.tokenTotal || 0).toLocaleString('en-US')} tokens`;
613
+ return {
614
+ hour: hourIndex,
615
+ hourLabel,
616
+ sessionCount: cell.sessionCount,
617
+ messageCount: cell.messageCount,
618
+ tokenTotal: cell.tokenTotal,
619
+ level,
620
+ tooltip: tooltipText
621
+ };
622
+ })
623
+ }));
624
+ return {
625
+ range: result.range,
626
+ rows,
627
+ hourLabels: result.hourLabels,
628
+ maxSessionCount: result.maxSessionCount
629
+ };
630
+ },
583
631
  sessionUsageSummaryCards() {
584
632
  const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
585
633
  ? this.sessionUsageCharts.summary
@@ -4,6 +4,7 @@ export function createClaudeConfigMethods(options = {}) {
4
4
  return {
5
5
  switchClaudeConfig(name) {
6
6
  this.currentClaudeConfig = name;
7
+ try { localStorage.setItem('currentClaudeConfig', name || ''); } catch (_) {}
7
8
  this.refreshClaudeModelContext();
8
9
  },
9
10
 
@@ -36,7 +37,10 @@ export function createClaudeConfigMethods(options = {}) {
36
37
  },
37
38
 
38
39
  saveClaudeConfigs() {
39
- localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
40
+ try { localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs)); } catch (_) {}
41
+ if (this.currentClaudeConfig) {
42
+ try { localStorage.setItem('currentClaudeConfig', this.currentClaudeConfig); } catch (_) {}
43
+ }
40
44
  },
41
45
 
42
46
  openEditConfigModal(name) {
@@ -73,7 +77,7 @@ export function createClaudeConfigMethods(options = {}) {
73
77
 
74
78
  const config = this.claudeConfigs[name];
75
79
  if (!config.apiKey) {
76
- this.showMessage('已保存,未应用', 'info');
80
+ this.showMessage('已保存(未填写 API Key)', 'info');
77
81
  this.closeEditConfigModal();
78
82
  if (name === this.currentClaudeConfig) {
79
83
  this.refreshClaudeModelContext();
@@ -81,14 +85,17 @@ export function createClaudeConfigMethods(options = {}) {
81
85
  return;
82
86
  }
83
87
 
88
+ const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
84
89
  try {
85
90
  const res = await api('apply-claude-config', { config });
86
91
  if (res.error || res.success === false) {
87
92
  this.showMessage(res.error || '应用配置失败', 'error');
88
93
  } else {
89
94
  this.currentClaudeConfig = name;
90
- const targetTip = res.targetPath ? `(${res.targetPath})` : '';
91
- this.showMessage(`已保存并应用到 Claude 配置${targetTip}`, 'success');
95
+ if (this._lastAppliedClaudeKey !== _claudeKey) {
96
+ this.showMessage('Claude 配置已生效', 'success');
97
+ this._lastAppliedClaudeKey = _claudeKey;
98
+ }
92
99
  this.closeEditConfigModal();
93
100
  this.refreshClaudeModelContext();
94
101
  }
@@ -143,23 +150,27 @@ export function createClaudeConfigMethods(options = {}) {
143
150
 
144
151
  async applyClaudeConfig(name) {
145
152
  this.currentClaudeConfig = name;
153
+ try { localStorage.setItem('currentClaudeConfig', name || ''); } catch (_) {}
146
154
  this.refreshClaudeModelContext();
147
155
  const config = this.claudeConfigs[name];
148
156
 
149
157
  if (!config.apiKey) {
150
158
  if (config.externalCredentialType) {
151
- return this.showMessage('检测到外部 Claude 认证状态;当前仅支持展示,若需由 codexmate 接管请补充 API Key', 'info');
159
+ return this.showMessage('使用外部认证,无需 API Key', 'info');
152
160
  }
153
161
  return this.showMessage('请先配置 API Key', 'error');
154
162
  }
155
163
 
164
+ const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
156
165
  try {
157
166
  const res = await api('apply-claude-config', { config });
158
167
  if (res.error || res.success === false) {
159
168
  this.showMessage(res.error || '应用配置失败', 'error');
160
169
  } else {
161
- const targetTip = res.targetPath ? `(${res.targetPath})` : '';
162
- this.showMessage(`已应用配置到 Claude 设置: ${name}${targetTip}`, 'success');
170
+ if (this._lastAppliedClaudeKey !== _claudeKey2) {
171
+ this.showMessage('配置已应用', 'success');
172
+ this._lastAppliedClaudeKey = _claudeKey2;
173
+ }
163
174
  }
164
175
  } catch (_) {
165
176
  this.showMessage('应用配置失败', 'error');
@@ -180,7 +180,19 @@ export function createCodexConfigMethods(options = {}) {
180
180
  const previousModels = Array.isArray(this.models) ? [...this.models] : [];
181
181
  const previousModelsSource = this.modelsSource;
182
182
  const previousModelsHasCurrent = this.modelsHasCurrent;
183
+ // 切走前把上一个 provider 的 model 落到内存字典,避免切回时显示抖动。
184
+ if (previousProvider && typeof previousModel === 'string' && previousModel.trim() && previousModel !== '未设置') {
185
+ if (!this.currentModels || typeof this.currentModels !== 'object') this.currentModels = {};
186
+ this.currentModels[previousProvider] = previousModel.trim();
187
+ }
183
188
  this.currentProvider = name;
189
+ // 立即按字典预填,让 UI 不出现空白;远端 /models 后台异步补齐。
190
+ const dictModel = typeof this.activeProviderModel === 'function'
191
+ ? this.activeProviderModel(name)
192
+ : '';
193
+ if (dictModel) {
194
+ this.currentModel = dictModel;
195
+ }
184
196
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
185
197
 
186
198
  // 不要把“切换提供商”强绑定到 /models 成功与否:
@@ -195,7 +207,11 @@ export function createCodexConfigMethods(options = {}) {
195
207
 
196
208
  await Promise.race([modelsTask, delay(250)]);
197
209
 
198
- if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
210
+ // 只在“字典中没有该 provider 记录”时才允许 remote 首项覆盖,否则尊重用户上次选择。
211
+ if (!dictModel
212
+ && this.modelsSource === 'remote'
213
+ && this.models.length > 0
214
+ && !this.models.includes(this.currentModel)) {
199
215
  this.currentModel = this.models[0];
200
216
  this.modelsHasCurrent = true;
201
217
  }
@@ -208,7 +224,10 @@ export function createCodexConfigMethods(options = {}) {
208
224
  await modelsTask;
209
225
 
210
226
  if (this.currentProvider === name) {
211
- if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
227
+ if (!dictModel
228
+ && this.modelsSource === 'remote'
229
+ && this.models.length > 0
230
+ && !this.models.includes(this.currentModel)) {
212
231
  this.currentModel = this.models[0];
213
232
  this.modelsHasCurrent = true;
214
233
  if (getProviderConfigModeMeta(this.configMode)) {
@@ -261,6 +280,12 @@ export function createCodexConfigMethods(options = {}) {
261
280
  },
262
281
 
263
282
  async onModelChange() {
283
+ const name = String(this.currentProvider || '').trim();
284
+ const model = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
285
+ if (name && model) {
286
+ if (!this.currentModels || typeof this.currentModels !== 'object') this.currentModels = {};
287
+ this.currentModels[name] = model;
288
+ }
264
289
  await this.applyCodexConfigDirect();
265
290
  },
266
291
 
@@ -629,6 +654,8 @@ export function createCodexConfigMethods(options = {}) {
629
654
  this.modelContextWindowInput = modelContextWindow.text;
630
655
  this.modelAutoCompactTokenLimitInput = modelAutoCompactTokenLimit.text;
631
656
 
657
+ const _codexKey = `${provider}|${model}|${this.serviceTier || ""}|${this.modelReasoningEffort || ""}|${modelContextWindow.value}|${modelAutoCompactTokenLimit.value}`;
658
+
632
659
  this.codexApplying = true;
633
660
  try {
634
661
  const tplRes = await api('get-config-template', {
@@ -665,7 +692,12 @@ export function createCodexConfigMethods(options = {}) {
665
692
  }
666
693
 
667
694
  if (options.silent !== true) {
668
- this.showMessage('配置已应用', 'success');
695
+ if (this._lastAppliedCodexKey !== _codexKey) {
696
+ this.showMessage('配置已应用', 'success');
697
+ this._lastAppliedCodexKey = _codexKey;
698
+ }
699
+ } else {
700
+ this._lastAppliedCodexKey = _codexKey;
669
701
  }
670
702
 
671
703
  const refreshOptions = options.silent === true
@@ -149,6 +149,9 @@ export function createProvidersMethods(options = {}) {
149
149
  if (this.newProvider && this.newProvider.useTransform) {
150
150
  payload.useTransform = true;
151
151
  }
152
+ const suggestedModel = typeof this.newProvider._suggestedModel === 'string'
153
+ ? this.newProvider._suggestedModel.trim()
154
+ : '';
152
155
  const res = await api('add-provider', payload);
153
156
  if (res.error) {
154
157
  this.showMessage(res.error, 'error');
@@ -158,6 +161,11 @@ export function createProvidersMethods(options = {}) {
158
161
  this.showMessage('操作成功', 'success');
159
162
  this.closeAddModal();
160
163
  await this.loadAll();
164
+ // loadAll 会重拉 status 覆盖字典,所以暗示模型在 loadAll 之后再写。
165
+ if (suggestedModel) {
166
+ if (!this.currentModels || typeof this.currentModels !== 'object') this.currentModels = {};
167
+ this.currentModels[validation.name] = suggestedModel;
168
+ }
161
169
  } catch (e) {
162
170
  this.showMessage('添加失败', 'error');
163
171
  }
@@ -377,7 +385,7 @@ export function createProvidersMethods(options = {}) {
377
385
 
378
386
  closeAddModal() {
379
387
  this.showAddModal = false;
380
- this.newProvider = { name: '', url: '', key: '', useTransform: false };
388
+ this.newProvider = { name: '', url: '', key: '', useTransform: false, _suggestedModel: '' };
381
389
  },
382
390
 
383
391
  closeModelModal() {
@@ -181,12 +181,9 @@ export function createSessionActionMethods(options = {}) {
181
181
  return `gemini -r ${arg}`;
182
182
  }
183
183
  if (source === 'claude') {
184
- return `claude -r ${arg}`;
184
+ return `claude --dangerously-skip-permissions -r ${arg}`;
185
185
  }
186
- if (this.sessionResumeWithYolo) {
187
- return `codex --yolo resume ${arg}`;
188
- }
189
- return `codex resume ${arg}`;
186
+ return `codex --yolo resume ${arg}`;
190
187
  },
191
188
 
192
189
  extractClaudeResumeKeyFromFilePath(filePath) {
@@ -249,11 +249,6 @@ export function createSessionBrowserMethods(options = {}) {
249
249
  }
250
250
  },
251
251
 
252
- onSessionResumeYoloChange() {
253
- const value = this.sessionResumeWithYolo ? '1' : '0';
254
- localStorage.setItem('codexmateSessionResumeYolo', value);
255
- },
256
-
257
252
  normalizeSessionSortMode(value) {
258
253
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
259
254
  return normalized === 'hot' ? 'hot' : 'time';
@@ -778,6 +773,7 @@ export function createSessionBrowserMethods(options = {}) {
778
773
  const normalized = typeof nextRange === 'string' ? nextRange.trim().toLowerCase() : '';
779
774
  const range = normalized === 'all' ? 'all' : (normalized === '30d' ? '30d' : '7d');
780
775
  this.sessionsUsageTimeRange = range;
776
+ try { localStorage.setItem('sessionsUsageTimeRange', range); } catch (_) {}
781
777
  if (range === 'all') {
782
778
  this.sessionsUsageCompareEnabled = false;
783
779
  }
@@ -848,6 +844,9 @@ export function createSessionBrowserMethods(options = {}) {
848
844
  if (loadSucceeded) {
849
845
  this.sessionsUsageLoadedOnce = true;
850
846
  this.sessionsUsageLoadedLimit = limit;
847
+ if (!this.sessionsUsageSelectedDayKey && Array.isArray(this.sessionUsageDailyTableRows) && this.sessionUsageDailyTableRows.length > 0) {
848
+ this.sessionsUsageSelectedDayKey = this.sessionUsageDailyTableRows[0].key;
849
+ }
851
850
  }
852
851
  }
853
852
  },
@@ -208,10 +208,10 @@ export function createSessionTrashMethods(options = {}) {
208
208
  },
209
209
 
210
210
  normalizeSettingsTab(tab) {
211
- if (tab === 'trash' || tab === 'device') {
211
+ if (tab === 'general' || tab === 'data') {
212
212
  return tab;
213
213
  }
214
- return 'backup';
214
+ return 'general';
215
215
  },
216
216
 
217
217
  async onSettingsTabClick(tab) {
@@ -221,7 +221,7 @@ export function createSessionTrashMethods(options = {}) {
221
221
  async switchSettingsTab(tab, options = {}) {
222
222
  const nextTab = this.normalizeSettingsTab(tab);
223
223
  this.settingsTab = nextTab;
224
- if (nextTab !== 'trash') {
224
+ if (nextTab !== 'data') {
225
225
  return;
226
226
  }
227
227
  const forceRefresh = options.forceRefresh === true;
@@ -270,6 +270,20 @@ export function createSessionTrashMethods(options = {}) {
270
270
  }
271
271
  },
272
272
 
273
+ normalizeSessionTrashRetentionDays(value) {
274
+ const numeric = Number(value);
275
+ if (!Number.isFinite(numeric) || numeric < 1) {
276
+ return 30;
277
+ }
278
+ return Math.min(365, Math.max(1, Math.floor(numeric)));
279
+ },
280
+
281
+ setSessionTrashRetentionDays(days) {
282
+ const normalized = this.normalizeSessionTrashRetentionDays(days);
283
+ this.sessionTrashRetentionDays = normalized;
284
+ try { localStorage.setItem('codexmateSessionTrashRetentionDays', String(normalized)); } catch (_) {}
285
+ },
286
+
273
287
  getSessionTrashActionKey(item) {
274
288
  return item && typeof item.trashId === 'string' ? item.trashId : '';
275
289
  },
@@ -294,7 +308,8 @@ export function createSessionTrashMethods(options = {}) {
294
308
  try {
295
309
  const res = await api('list-session-trash', {
296
310
  limit: sessionTrashListLimit,
297
- forceRefresh: !!options.forceRefresh
311
+ forceRefresh: !!options.forceRefresh,
312
+ retentionDays: this.sessionTrashRetentionDays
298
313
  });
299
314
  if (!this.isLatestSessionTrashListRequestToken(requestToken)) {
300
315
  return;
@@ -60,7 +60,14 @@ export function createStartupClaudeMethods(options = {}) {
60
60
  return false;
61
61
  }
62
62
  this.currentProvider = statusRes.provider;
63
- this.currentModel = statusRes.model;
63
+ this.currentModels = statusRes.currentModels && typeof statusRes.currentModels === 'object'
64
+ ? { ...statusRes.currentModels }
65
+ : {};
66
+ const dictModelForCurrent = this.currentProvider
67
+ && typeof this.currentModels[this.currentProvider] === 'string'
68
+ ? this.currentModels[this.currentProvider].trim()
69
+ : '';
70
+ this.currentModel = dictModelForCurrent || statusRes.model;
64
71
  try {
65
72
  const installRes = await withTimeout(api('install-status'), Math.max(0, Math.min(1200, timeLeftMs())));
66
73
  if (installRes && !installRes.error) {
@@ -315,6 +322,7 @@ export function createStartupClaudeMethods(options = {}) {
315
322
  if (matchName) {
316
323
  if (this.currentClaudeConfig !== matchName) {
317
324
  this.currentClaudeConfig = matchName;
325
+ try { localStorage.setItem('currentClaudeConfig', matchName); } catch (_) {}
318
326
  }
319
327
  this.refreshClaudeModelContext({ silentError: silentModelError });
320
328
  return;
@@ -323,6 +331,7 @@ export function createStartupClaudeMethods(options = {}) {
323
331
  if (importedName) {
324
332
  if (this.currentClaudeConfig !== importedName) {
325
333
  this.currentClaudeConfig = importedName;
334
+ try { localStorage.setItem('currentClaudeConfig', importedName); } catch (_) {}
326
335
  }
327
336
  this.refreshClaudeModelContext({ silentError: silentModelError });
328
337
  if (!silent) {
@@ -338,12 +347,14 @@ export function createStartupClaudeMethods(options = {}) {
338
347
  : (configNames[0] || '');
339
348
  if (!fallback) {
340
349
  this.currentClaudeConfig = '';
350
+ try { localStorage.setItem('currentClaudeConfig', ''); } catch (_) {}
341
351
  this.currentClaudeModel = '';
342
352
  this.resetClaudeModelsState();
343
353
  return;
344
354
  }
345
355
  if (this.currentClaudeConfig !== fallback) {
346
356
  this.currentClaudeConfig = fallback;
357
+ try { localStorage.setItem('currentClaudeConfig', fallback); } catch (_) {}
347
358
  }
348
359
  this.refreshClaudeModelContext({ silentError: silentModelError });
349
360
  }